发布 0.1.0:通知渠道、告警规则、令牌版本与安全加固
CI / test (push) Successful in 30s
Release / release (push) Successful in 49s

This commit is contained in:
Wang Defa
2026-07-10 17:38:34 +08:00
parent 4af6a0ca92
commit dbba1f4905
78 changed files with 6898 additions and 551 deletions
+26 -9
View File
@@ -1,7 +1,10 @@
package api
import (
"crypto/rand"
"encoding/hex"
"errors"
"log"
"net/http"
"strconv"
@@ -74,7 +77,7 @@ func (h *ociConfigHandler) create(c *gin.Context) {
func (h *ociConfigHandler) list(c *gin.Context) {
items, err := h.svc.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
respondError(c, err)
return
}
c.JSON(http.StatusOK, items)
@@ -168,6 +171,7 @@ func (h *ociConfigHandler) update(c *gin.Context) {
}
// @Summary 删除租户配置
// @Description 删除租户配置及其本地关联任务、快照、回传、告警和 AI 渠道数据,并撤销 Webhook 密钥;不删除 OCI 云端资源。
// @Tags 租户配置
// @Param id path int true "配置 ID"
// @Success 204 "无内容"
@@ -258,17 +262,29 @@ func pathID(c *gin.Context) (uint, bool) {
return uint(id), true
}
// respondError 统一出错响应边界(S-08):OCI 服务错误经脱敏透出,记录缺失
// 映射 404;其余按内部错误处理——响应只含固定文案与关联 ID,完整原因
// (可能携带 SQL / DSN / 路径等内部细节)仅写服务端日志,经同一 ID 对应。
func respondError(c *gin.Context, err error) {
status := http.StatusInternalServerError
if errors.Is(err, gorm.ErrRecordNotFound) {
status = http.StatusNotFound
}
var svcErr common.ServiceError
if errors.As(err, &svcErr) {
respondOCIError(c, err, svcErr)
return
}
c.JSON(status, gin.H{"error": err.Error()})
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "资源不存在"})
return
}
id := newRequestID()
log.Printf("[ERR %s] %s %s: %v", id, c.Request.Method, requestPath(c), err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器内部错误", "requestId": id})
}
// newRequestID 生成错误关联 ID(8 字节随机 hex),响应与服务端日志据此对应。
func newRequestID() string {
b := make([]byte, 8)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
// respondOCIError 提炼 OCI 服务错误:透传 HTTP 状态码,
@@ -278,9 +294,10 @@ func respondOCIError(c *gin.Context, err error, svcErr common.ServiceError) {
if status < http.StatusBadRequest {
status = http.StatusBadGateway
}
// 面板的 401 专属本地 JWT 失效(前端收到即登出)
// 上游 OCI 的 401 是代理调用被拒,改用 502 透出
if status == http.StatusUnauthorized {
// 面板的 401 专属本地 JWT 失效(前端收到即登出)、429 专属本地 IP 限速
// (前端收到即跳 /blocked);上游 OCI 的同码是代理调用被拒/云端限流
// 均改用 502 透出,body 保留 ociCode 供辨识
if status == http.StatusUnauthorized || status == http.StatusTooManyRequests {
status = http.StatusBadGateway
}
body := gin.H{