优化结构
This commit is contained in:
parent
ae5345de14
commit
a4ed3a02df
|
|
@ -100,3 +100,7 @@
|
|||
4. **V-17/V-18/V-20/V-13b**(秒传一致性、会话回收扩 failed+merging、前端 token、Swagger 开关质量)
|
||||
5. **二节死代码 + 三节双轨 + 四节文档**(一批小清理)
|
||||
6. **五节过分拆**(4 硬+5 轻+2 组可选+seam 统一——纯文件级减法,零行为变更)
|
||||
|
||||
## 已修复(划线标记)
|
||||
|
||||
本轮已确认并完成:~~V-11~~、~~V-15~~、~~V-16b~~、~~V-17~~、~~V-18~~、~~V-19~~、~~V-20(移除控制台泄露)~~、~~V-13b(补充 prod/live/staging 环境屏蔽)~~、~~Z-1~~、~~Y-1~~。
|
||||
|
|
|
|||
|
|
@ -395,6 +395,19 @@ func integrationFirst(values map[string]any, keys ...string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func IsIntegrationSecretKey(key string) bool {
|
||||
normalized := strings.ToLower(strings.ReplaceAll(key, "-", "_"))
|
||||
if strings.Contains(normalized, "secret") || strings.Contains(normalized, "password") || strings.Contains(normalized, "private") || strings.Contains(normalized, "credential") || strings.Contains(normalized, "token") {
|
||||
return true
|
||||
}
|
||||
for _, item := range []string{"key", "key_pem", "api_key", "mch_key", "client_key", "certificate", "cert", "cert_pem", "p12", "pkcs12", "public_key", "platform_cert", "root_cert"} {
|
||||
if normalized == item || strings.HasSuffix(normalized, "_"+item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func integrationInt64(values map[string]any, key string, fallback int64) int64 {
|
||||
value := integrationText(values, key)
|
||||
if value == "" {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,13 @@ func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash
|
|||
completed, err := uc.FindCompletedSession(ctx, userID, strings.ToLower(hash))
|
||||
if err == nil && completed.MediaID != 0 {
|
||||
if media, findErr := uc.FindMedia(ctx, completed.MediaID); findErr == nil {
|
||||
copy := &MediaFile{Name: name, URL: media.URL, Tag: media.Tag, Key: media.Key, Size: media.Size, MD5: media.MD5, Mime: media.Mime, UserID: userID}
|
||||
copy := &MediaFile{Name: name, URL: media.URL, Tag: strings.TrimPrefix(filepath.Ext(name), "."), Key: media.Key, Size: media.Size, MD5: media.MD5, Mime: media.Mime, UserID: userID}
|
||||
if strings.TrimSpace(copy.Mime) == "" {
|
||||
copy.Mime = stdmime.TypeByExtension(strings.ToLower(filepath.Ext(name)))
|
||||
if copy.Mime == "" {
|
||||
copy.Mime = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
if createErr := uc.CreateMedia(ctx, copy); createErr == nil {
|
||||
return nil, copy, nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ func integrationObject(raw json.RawMessage) map[string]any {
|
|||
func maskIntegrationSecrets(kind, provider string, values map[string]any) {
|
||||
secretFields := integrationSecretFields(kind, provider)
|
||||
for key, value := range values {
|
||||
if secretFields[key] || likelyIntegrationSecret(key) {
|
||||
if secretFields[key] || integrationbiz.IsIntegrationSecretKey(key) {
|
||||
if text, ok := value.(string); ok && text != "" {
|
||||
values[key] = config.MaskedSecret
|
||||
}
|
||||
|
|
@ -239,10 +239,12 @@ func maskIntegrationSecrets(kind, provider string, values map[string]any) {
|
|||
func mergeIntegrationSecrets(kind, provider string, values, old map[string]any) {
|
||||
secretFields := integrationSecretFields(kind, provider)
|
||||
for key, value := range values {
|
||||
if secretFields[key] || likelyIntegrationSecret(key) {
|
||||
if secretFields[key] || integrationbiz.IsIntegrationSecretKey(key) {
|
||||
if text, ok := value.(string); ok && text == config.MaskedSecret {
|
||||
if prior, exists := old[key]; exists {
|
||||
values[key] = prior
|
||||
} else {
|
||||
values[key] = ""
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
|
@ -252,6 +254,23 @@ func mergeIntegrationSecrets(kind, provider string, values, old map[string]any)
|
|||
mergeIntegrationSecrets(kind, provider, nested, prior)
|
||||
}
|
||||
}
|
||||
if nested, ok := value.([]any); ok {
|
||||
if prior, ok := old[key].([]any); ok {
|
||||
mergeIntegrationSecretArrays(kind, provider, nested, prior)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mergeIntegrationSecretArrays(kind, provider string, values, old []any) {
|
||||
for i, item := range values {
|
||||
if nested, ok := item.(map[string]any); ok {
|
||||
if i < len(old) {
|
||||
if prior, ok := old[i].(map[string]any); ok {
|
||||
mergeIntegrationSecrets(kind, provider, nested, prior)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,16 +285,3 @@ func integrationSecretFields(kind, provider string) map[string]bool {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func likelyIntegrationSecret(key string) bool {
|
||||
normalized := strings.ToLower(strings.ReplaceAll(key, "-", "_"))
|
||||
if strings.Contains(normalized, "secret") || strings.Contains(normalized, "private") || strings.Contains(normalized, "password") || strings.Contains(normalized, "credential") {
|
||||
return true
|
||||
}
|
||||
for _, item := range []string{"key", "token", "access_token", "api_key", "mch_key", "client_key", "certificate", "cert", "p12", "pkcs12", "public_key", "platform_cert", "root_cert"} {
|
||||
if normalized == item || strings.HasSuffix(normalized, "_"+item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error {
|
|||
}
|
||||
func (r *mediaRepo) StaleUploadSessionIDs(ctx context.Context, before time.Time) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("status = ? AND updated_at < ?", "uploading", before).Pluck("id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("status IN ? AND updated_at < ?", []string{"uploading", "failed", "merging"}, before).Pluck("id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *mediaRepo) DeleteUploadData(ctx context.Context, uploadID uint) error {
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ func alipayCreateMethod(extra, config map[string]any) (string, error) {
|
|||
}
|
||||
normalized := paymentkit.NormalizePaymentMethod(value)
|
||||
switch normalized {
|
||||
case "", "create", "trade_create", "alipay_trade_create", "jsapi", "js_api", "miniapp", "mini_app", "mini_program":
|
||||
case "", "create", "trade_create", "alipay_trade_create", "jsapi", "js_api", "mini", "miniapp", "mini_app", "miniprogram", "mini_program":
|
||||
return "alipay.trade.create", nil
|
||||
case "pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face":
|
||||
return "alipay.trade.pay", nil
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func douyinCreateMethod(extra, config map[string]any) (string, error) {
|
|||
}
|
||||
normalized := paymentkit.NormalizePaymentMethod(value)
|
||||
switch normalized {
|
||||
case "", "jsapi", "js_api", "mini", "mini_program", "miniprogram", "applet":
|
||||
case "", "jsapi", "js_api", "mini", "miniapp", "mini_app", "mini_program", "miniprogram", "applet":
|
||||
return "jsapi", nil
|
||||
case "app", "app_pay", "apporder":
|
||||
return "app", nil
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func (a *lakalaAdapter) Create(ctx context.Context, req *bizpayment.PaymentReque
|
|||
rsp, err = client.CreateJSAPIOrder(ctx, req.TradeNo, bm)
|
||||
case "h5", "h5_pay", "mobile_h5", "mobile_h5_pay":
|
||||
rsp, err = client.CreateH5PayOrder(ctx, req.TradeNo, bm)
|
||||
case "mini", "miniapp", "mini_program", "miniprogram", "microapp":
|
||||
case "mini", "miniapp", "mini_app", "mini_program", "miniprogram", "microapp":
|
||||
rsp, err = client.CreateMiniProgramOrder(ctx, req.TradeNo, bm)
|
||||
case "native", "native_qrcode", "native_qr":
|
||||
rsp, err = client.CreateNativeQRCodeOrder(ctx, req.TradeNo, bm)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,11 @@ func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.Access
|
|||
if ws != nil && ws.Enabled() {
|
||||
engine.GET(ws.Path(), handleWebSocket)
|
||||
}
|
||||
if snapshot == nil || snapshot.Admin == nil || snapshot.Admin.App == nil || strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env)) != "production" {
|
||||
env := ""
|
||||
if snapshot != nil && snapshot.Admin != nil && snapshot.Admin.App != nil {
|
||||
env = strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env))
|
||||
}
|
||||
if env != "production" && env != "prod" && env != "live" && env != "staging" {
|
||||
registerSwagger(engine, prefix, version, logger)
|
||||
}
|
||||
staticfiles.Register(engine, runtime)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ type PageResult struct {
|
|||
}
|
||||
|
||||
func Write(c *gin.Context, code int, data any, message string) {
|
||||
if code == CodeError {
|
||||
message = sanitizeFailureMessage(message)
|
||||
}
|
||||
c.JSON(http.StatusOK, Response{Code: code, Data: data, Msg: message})
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +42,20 @@ func OKWithData(c *gin.Context, data any) { Write(c, CodeSuccess, data, "成功"
|
|||
|
||||
func Fail(c *gin.Context, message string) { Write(c, CodeError, gin.H{}, message) }
|
||||
|
||||
func sanitizeFailureMessage(message string) string {
|
||||
message = strings.TrimSpace(message)
|
||||
lower := strings.ToLower(message)
|
||||
for _, marker := range []string{"gorm", "sql:", "redis", "mysql", "mongo", "dial ", "connection", "provider", "sdk", "json:", "serialize", "timeout", "http 4", "http 5", "tls:"} {
|
||||
if strings.Contains(lower, marker) {
|
||||
return "操作失败"
|
||||
}
|
||||
}
|
||||
if strings.Contains(message, "失败:") {
|
||||
return strings.TrimSpace(strings.SplitN(message, "失败:", 2)[0]) + "失败"
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func NoAuth(c *gin.Context, message string) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, Response{Code: CodeError, Data: nil, Msg: message})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,11 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
|||
}
|
||||
bodyLimit := defaultRequestBodyLimit
|
||||
if mediaUpload {
|
||||
bodyLimit = system.DefaultMaxMediaFileSize + (1 << 20)
|
||||
if admin != nil && admin.Media != nil && admin.Media.MaxFileSize > 0 {
|
||||
bodyLimit = admin.Media.MaxFileSize + (1 << 20)
|
||||
mediaSettings := system.MediaSettings{}
|
||||
if admin != nil && admin.Media != nil {
|
||||
mediaSettings.MaxFileSize = admin.Media.MaxFileSize
|
||||
}
|
||||
bodyLimit = mediaSettings.EffectiveMaxFileSize() + (1 << 20)
|
||||
}
|
||||
bytesIn := c.Request.ContentLength
|
||||
logLimit := 1024
|
||||
|
|
@ -96,6 +97,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
|||
requestText = redactJSON(requestBody, c.GetHeader("Content-Type"), logLimit)
|
||||
}
|
||||
c.Set(ctxReqBodyKey, requestText)
|
||||
c.Set(ctxRespTruncatedKey, writer.Truncated())
|
||||
if paymentCallback {
|
||||
// Callback acknowledgements and provider payloads must not flow into
|
||||
// the generic response/error audit pipeline.
|
||||
|
|
@ -110,6 +112,9 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
|||
return
|
||||
}
|
||||
responseText := redactJSON(writer.body.Bytes(), c.Writer.Header().Get("Content-Type"), logLimit)
|
||||
if writer.Truncated() {
|
||||
responseText = "[超出记录长度]"
|
||||
}
|
||||
if paymentCallback {
|
||||
responseText = "[支付回调响应已省略]"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
|||
}
|
||||
if c.Request.Method == http.MethodGet {
|
||||
requestBody = operationQueryBody(c.Request.URL.RawQuery)
|
||||
} else if value, ok := c.Get(ctxReqBodyKey); ok {
|
||||
requestBody = []byte(stringValue(value))
|
||||
} else if _, ok := c.Get(ctxReqBodyKey); ok {
|
||||
} else if c.Request.Body != nil {
|
||||
// Fallback for tests or custom middleware chains that omit AccessLog.
|
||||
if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
|
||||
|
|
@ -79,13 +78,13 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
|||
responseBody = redactJSON(body.Bytes(), c.Writer.Header().Get("Content-Type"), maxBytes)
|
||||
}
|
||||
}
|
||||
if isDownloadResponse(c) && len(responseBody) > maxBytes {
|
||||
if truncated, _ := c.Get(ctxRespTruncatedKey); truncated == true {
|
||||
responseBody = "[超出记录长度]"
|
||||
}
|
||||
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||
operationBody := ""
|
||||
capturedBody := false
|
||||
if c.Request.Method != http.MethodGet {
|
||||
if c.Request.Method != "GET" {
|
||||
if value, ok := c.Get(ctxReqBodyKey); ok {
|
||||
// AccessLog has already captured, redacted, summarized, and bounded the
|
||||
// request body. Treat that value as final so audit does not process it a
|
||||
|
|
@ -162,27 +161,3 @@ func maskOperationBody(value any) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// operationDownloadHeaders lists the response headers whose presence marks a
|
||||
// download: the value is the substring that identifies it.
|
||||
var operationDownloadHeaders = [...]struct{ header, marker string }{
|
||||
{"Pragma", "public"},
|
||||
{"Expires", "0"},
|
||||
{"Cache-Control", "must-revalidate, post-check=0, pre-check=0"},
|
||||
{"Content-Type", "application/force-download"},
|
||||
{"Content-Type", "application/octet-stream"},
|
||||
{"Content-Type", "application/vnd.ms-excel"},
|
||||
{"Content-Type", "application/download"},
|
||||
{"Content-Disposition", "attachment"},
|
||||
{"Content-Transfer-Encoding", "binary"},
|
||||
}
|
||||
|
||||
func isDownloadResponse(c *gin.Context) bool {
|
||||
header := c.Writer.Header()
|
||||
for _, candidate := range operationDownloadHeaders {
|
||||
if strings.Contains(header.Get(candidate.header), candidate.marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,16 @@ import (
|
|||
// OperationAudit consumes these values after the handler returns, avoiding a
|
||||
// second body read/writer wrapper.
|
||||
const (
|
||||
ctxReqBodyKey = "kra_req_body"
|
||||
ctxRespBufferKey = "kra_resp_buffer"
|
||||
ctxReqBodyKey = "kra_req_body"
|
||||
ctxRespBufferKey = "kra_resp_buffer"
|
||||
ctxRespTruncatedKey = "kra_resp_truncated"
|
||||
)
|
||||
|
||||
type captureWriter struct {
|
||||
gin.ResponseWriter
|
||||
body bytes.Buffer
|
||||
maxBytes int
|
||||
body bytes.Buffer
|
||||
maxBytes int
|
||||
truncated bool
|
||||
}
|
||||
|
||||
func (w *captureWriter) Write(data []byte) (int, error) {
|
||||
|
|
@ -31,6 +33,7 @@ func (w *captureWriter) Write(data []byte) (int, error) {
|
|||
remaining := limit - w.body.Len()
|
||||
if len(data) > remaining {
|
||||
_, _ = w.body.Write(data[:remaining])
|
||||
w.truncated = true
|
||||
} else {
|
||||
_, _ = w.body.Write(data)
|
||||
}
|
||||
|
|
@ -38,6 +41,8 @@ func (w *captureWriter) Write(data []byte) (int, error) {
|
|||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func (w *captureWriter) Truncated() bool { return w != nil && w.truncated }
|
||||
|
||||
func redactJSON(raw []byte, contentType string, limit int) string {
|
||||
if len(raw) == 0 {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
"strings"
|
||||
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
|
@ -76,8 +75,7 @@ func redactIntegrationJSON(raw json.RawMessage) json.RawMessage {
|
|||
switch x := v.(type) {
|
||||
case map[string]any:
|
||||
for k, item := range x {
|
||||
n := strings.ToLower(strings.ReplaceAll(k, "-", "_"))
|
||||
if strings.Contains(n, "secret") || strings.Contains(n, "password") || strings.Contains(n, "private") || strings.Contains(n, "credential") || strings.Contains(n, "token") || strings.HasSuffix(n, "_key") || strings.Contains(n, "cert") || strings.Contains(n, "p12") {
|
||||
if integrationbiz.IsIntegrationSecretKey(k) {
|
||||
x[k] = "******"
|
||||
} else {
|
||||
walk(item)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ const createQrCode = () => {
|
|||
const local = window.location
|
||||
codeUrl.value = local.protocol + '//' + local.host + '/#/scanUpload?id=' + props.classId + '&token=' + userStore.token + '&t=' + Date.now()
|
||||
dialogVisible.value = true
|
||||
console.log(codeUrl.value)
|
||||
}
|
||||
|
||||
const onFinished = () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue