新增对象存储/账单/保留IP模块,删桶并发提速与成本增强
CI / test (push) Successful in 33s

This commit is contained in:
2026-07-21 12:11:05 +08:00
parent 9cfde8b702
commit f40f2a20e8
44 changed files with 7788 additions and 54 deletions
+139
View File
@@ -0,0 +1,139 @@
package api
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
_ "oci-portal/internal/oci" // swagger 注解引用
)
// payInvoiceRequest 是付款请求体;email 接收 OSP 付款回执。
type payInvoiceRequest struct {
Email string `json:"email" binding:"required"`
}
// @Summary 发票列表
// @Tags 账单
// @Param id path int true "配置 ID"
// @Param year query int false "自然年过滤(按开票时间);缺省全量"
// @Success 200 {array} oci.Invoice
// @Security BearerAuth
// @Router /api/v1/oci-configs/{id}/invoices [get]
func (h *ociConfigHandler) invoices(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
year, _ := strconv.Atoi(c.Query("year"))
list, err := h.svc.Invoices(c.Request.Context(), id, year)
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, list)
}
// @Summary 发票费用明细
// @Tags 账单
// @Param id path int true "配置 ID"
// @Param internalId path string true "发票内部 ID"
// @Success 200 {array} oci.InvoiceLine
// @Security BearerAuth
// @Router /api/v1/oci-configs/{id}/invoices/{internalId}/lines [get]
func (h *ociConfigHandler) invoiceLines(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
lines, err := h.svc.InvoiceLines(c.Request.Context(), id, c.Param("internalId"))
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, lines)
}
// @Summary 发票 PDF 下载
// @Tags 账单
// @Param id path int true "配置 ID"
// @Param internalId path string true "发票内部 ID"
// @Param number query string false "发票号(用作下载文件名)"
// @Success 200 {file} binary "PDF 原文"
// @Security BearerAuth
// @Router /api/v1/oci-configs/{id}/invoices/{internalId}/pdf [get]
func (h *ociConfigHandler) invoicePdf(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
data, err := h.svc.InvoicePdf(c.Request.Context(), id, c.Param("internalId"))
if err != nil {
respondError(c, err)
return
}
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", pdfFileName(c.Query("number"), c.Param("internalId"))))
c.Data(http.StatusOK, "application/pdf", data)
}
// pdfFileName 生成下载文件名:优先发票号,剔除引号/路径分隔等不安全字符。
func pdfFileName(number, internalID string) string {
name := strings.TrimSpace(number)
if name == "" {
name = internalID
}
name = strings.Map(func(r rune) rune {
if r == '"' || r == '/' || r == '\\' || r < 0x20 {
return '_'
}
return r
}, name)
return name + ".pdf"
}
// @Summary 支付发票
// @Tags 账单
// @Param id path int true "配置 ID"
// @Param internalId path string true "发票内部 ID"
// @Param body body payInvoiceRequest true "回执邮箱"
// @Success 200 {object} map[string]string
// @Security BearerAuth
// @Router /api/v1/oci-configs/{id}/invoices/{internalId}/pay [post]
func (h *ociConfigHandler) payInvoice(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req payInvoiceRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.svc.PayInvoice(c.Request.Context(), id, c.Param("internalId"), req.Email); err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"status": "submitted"})
}
// @Summary 付款方式列表
// @Tags 账单
// @Param id path int true "配置 ID"
// @Success 200 {array} oci.PaymentMethod
// @Security BearerAuth
// @Router /api/v1/oci-configs/{id}/payment-methods [get]
func (h *ociConfigHandler) paymentMethods(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
methods, err := h.svc.PaymentMethods(c.Request.Context(), id)
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, methods)
}