优化结构

This commit is contained in:
Yvan
2026-08-28 14:54:06 +08:00
parent 56f9440a6b
commit ae5345de14
15 changed files with 188 additions and 93 deletions
+9
View File
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
stdmime "mime"
"os"
"path"
"path/filepath"
@@ -246,10 +247,18 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin
return fail(errors.New("整文件校验失败"))
}
media := &MediaFile{Name: session.FileName, URL: stored.URL, Tag: strings.TrimPrefix(filepath.Ext(session.FileName), "."), Key: key, Size: stored.Size, Mime: mime, MD5: hash, UserID: userID}
if strings.TrimSpace(media.Mime) == "" {
media.Mime = stdmime.TypeByExtension(ext)
if media.Mime == "" {
media.Mime = "application/octet-stream"
}
}
if err = uc.CreateMedia(ctx, media); err != nil {
return fail(err)
}
if err = uc.CompleteUploadSession(ctx, uploadID, key, media.ID); err != nil {
_ = uc.DeleteMedia(ctx, media.ID)
_ = uc.files.Delete(ctx, key)
_ = uc.FailUploadSession(ctx, uploadID)
return nil, fmt.Errorf("完成上传会话失败: %w", err)
}
+32
View File
@@ -87,6 +87,38 @@ func (s *Store) Delete(ctx context.Context, key string) error {
return nil
}
func (s *Store) GetAndDelete(ctx context.Context, key string) (string, bool, error) {
if client := s.client(); client != nil {
value, err := client.Eval(ctx, "local v=redis.call('GET',KEYS[1]); if v then redis.call('DEL',KEYS[1]); end; return v", []string{key}).Result()
if err == redis.Nil {
return "", false, nil
}
if err != nil {
return "", false, err
}
if value == nil {
return "", false, nil
}
switch typed := value.(type) {
case string:
return typed, true, nil
case []byte:
return string(typed), true, nil
default:
return "", false, nil
}
}
s.mu.Lock()
defer s.mu.Unlock()
entry, ok := s.memory[key]
if !ok || (!entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt)) {
delete(s.memory, key)
return "", false, nil
}
delete(s.memory, key)
return entry.value, true, nil
}
func (s *Store) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) {
if client := s.client(); client != nil {
if expiration > 0 {
+1 -1
View File
@@ -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", "miniapp", "mini_program":
case "", "create", "trade_create", "alipay_trade_create", "jsapi", "js_api", "miniapp", "mini_app", "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
+1 -1
View File
@@ -183,7 +183,7 @@ func wechatV2CreateMethod(extra, config map[string]any) (string, error) {
}
normalized := paymentkit.NormalizePaymentMethod(value)
switch normalized {
case "", "jsapi", "js_api", "mini", "miniapp", "mini_program", "miniprogram", "applet":
case "", "jsapi", "js_api", "mini", "miniapp", "mini_app", "mini_program", "miniprogram", "applet":
return gopayWechat.TradeType_JsApi, nil
case "app", "app_pay":
return gopayWechat.TradeType_App, nil
+1 -1
View File
@@ -49,7 +49,7 @@ func (a *wechatV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentReq
tradeType = normalizeWechatV3TradeType(firstAny(c, "trade_type", "pay_type", "method"))
}
switch tradeType {
case "", "jsapi", "mini", "miniprogram", "mini_program", "applet":
case "", "jsapi", "js_api", "mini", "miniapp", "mini_app", "miniprogram", "mini_program", "applet":
if openID := firstAny(req.Extra, "openid", "open_id"); openID != "" {
bm.SetBodyMap("payer", func(payer gopay.BodyMap) { payer.Set("openid", openID) })
}
+1 -1
View File
@@ -165,7 +165,7 @@ var routes = map[string]routeValue{
"POST /info/createInfo": {group: "公告", description: "新建公告", audit: true},
"POST /init/checkdb": {group: "初始化", description: "检查数据库", public: true},
"POST /init/initdb": {group: "初始化", description: "初始化数据库", public: true},
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接", audit: true, bodyPolicy: BodyPolicyPaymentConfig},
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接", audit: true, bodyPolicy: BodyPolicyIntegrationConfig},
"POST /jwt/jsonInBlacklist": {group: "jwt", description: "jwt加入黑名单(退出,必选)"},
"POST /mediaUpload/chunk": {group: "媒体上传", description: "上传分片", bodyPolicy: BodyPolicyUpload},
"POST /mediaUpload/complete": {group: "媒体上传", description: "完成大文件上传"},
+3 -1
View File
@@ -61,7 +61,9 @@ func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.Access
if ws != nil && ws.Enabled() {
engine.GET(ws.Path(), handleWebSocket)
}
registerSwagger(engine, prefix, version, logger)
if snapshot == nil || snapshot.Admin == nil || snapshot.Admin.App == nil || strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env)) != "production" {
registerSwagger(engine, prefix, version, logger)
}
staticfiles.Register(engine, runtime)
if logger != nil {
for _, route := range engine.Routes() {
+8 -5
View File
@@ -31,7 +31,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
started := time.Now()
var requestBody []byte
multipart := strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data")
mediaUpload := multipart && routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path) == routecatalog.BodyPolicyUpload
mediaUpload := routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path) == routecatalog.BodyPolicyUpload
var admin *config.Admin
if runtime != nil {
if snapshot := runtime.Snapshot(); snapshot != nil {
@@ -46,7 +46,14 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
}
}
bytesIn := c.Request.ContentLength
logLimit := 1024
if admin != nil && admin.Zap != nil && admin.Zap.AccessLogMaxBytes > 0 {
logLimit = admin.Zap.AccessLogMaxBytes
}
maxBytes := 1 << 20
if logLimit > maxBytes {
maxBytes = logLimit
}
writer := &captureWriter{ResponseWriter: c.Writer, maxBytes: maxBytes}
c.Writer = writer
c.Header("X-Kra-Version", version)
@@ -75,10 +82,6 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
if bytesIn < 0 {
bytesIn = 0
}
logLimit := 1024
if admin != nil && admin.Zap != nil && admin.Zap.AccessLogMaxBytes > 0 {
logLimit = admin.Zap.AccessLogMaxBytes
}
bodyPolicy := routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path)
paymentCallback := bodyPolicy == routecatalog.BodyPolicyPaymentCallback
paymentConfigWrite := bodyPolicy == routecatalog.BodyPolicyPaymentConfig
-4
View File
@@ -65,10 +65,6 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
c.Request.Body = io.NopCloser(bytes.NewReader(requestBody))
}
}
// The reference middleware captures up to the global 1 MiB response
// safety limit and only applies the configured operation-log limit when a
// download response is recorded. Using maxBytes here would silently
// truncate ordinary JSON responses before that decision is possible.
started := time.Now()
c.Next()
userID := uint(0)
+1 -1
View File
@@ -34,7 +34,7 @@ func CORS(runtime *config.Store) gin.HandlerFunc {
corsHandled := false
if mode == "allow-all" {
if origin != "" {
setCORSHeaders(c, origin, defaultCORSHeaders, defaultCORSMethods, defaultCORSExpose, true)
setCORSHeaders(c, origin, defaultCORSHeaders, defaultCORSMethods, defaultCORSExpose, false)
corsHandled = true
}
} else if rule := matchingCORSRule(cors.Whitelist, origin); rule != nil {
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
integrationbiz "kra/internal/biz/integration"
"strings"
"kra/internal/service/dto"
)
@@ -38,13 +39,13 @@ func (s *IntegrationConfigService) List(ctx context.Context, kind string) ([]*dt
enabled = config.Enabled
delete(byProvider, definition.Provider)
}
result = append(result, &dto.IntegrationConfigResponse{Kind: definition.Kind, Provider: definition.Provider, Name: definition.Name, Description: definition.Description, Enabled: enabled, Configured: configured, Config: values, Fields: integrationFieldsDTO(definition.Fields)})
result = append(result, &dto.IntegrationConfigResponse{Kind: definition.Kind, Provider: definition.Provider, Name: definition.Name, Description: definition.Description, Enabled: enabled, Configured: configured, Config: redactIntegrationJSON(values), Fields: integrationFieldsDTO(definition.Fields)})
}
for _, config := range configs {
if _, exists := byProvider[config.Provider]; !exists {
continue
}
result = append(result, &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: config.Values})
result = append(result, &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: redactIntegrationJSON(config.Values)})
}
return result, nil
}
@@ -55,16 +56,44 @@ func (s *IntegrationConfigService) Find(ctx context.Context, kind, provider stri
return nil, err
}
definition, found := integrationbiz.IntegrationDefinition(config.Kind, config.Provider)
response := &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: config.Values}
response := &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: redactIntegrationJSON(config.Values)}
if found {
response.Name = definition.Name
response.Description = definition.Description
response.Fields = integrationFieldsDTO(definition.Fields)
response.Config = mergeConfigJSON(definition.Defaults, config.Values)
response.Config = redactIntegrationJSON(mergeConfigJSON(definition.Defaults, config.Values))
}
return response, nil
}
func redactIntegrationJSON(raw json.RawMessage) json.RawMessage {
var value any
if json.Unmarshal(raw, &value) != nil {
return raw
}
var walk func(any)
walk = func(v any) {
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") {
x[k] = "******"
} else {
walk(item)
}
}
case []any:
for _, item := range x {
walk(item)
}
}
}
walk(value)
out, _ := json.Marshal(value)
return out
}
func (s *IntegrationConfigService) Save(ctx context.Context, kind, provider string, req *dto.IntegrationConfigRequest) error {
if req == nil {
return s.uc.Save(ctx, nil)
+12 -2
View File
@@ -53,7 +53,13 @@ func (s *ExportService) IssueToken(ctx context.Context, templateID string, param
}
func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBlank bool) (*ExportToken, error) {
raw, ok, err := s.cache.Get(ctx, "export:"+token)
key := "export:" + token
raw, ok, err := s.cache.Get(ctx, key)
if atomic, supported := s.cache.(interface {
GetAndDelete(context.Context, string) (string, bool, error)
}); supported {
raw, ok, err = atomic.GetAndDelete(ctx, key)
}
if err != nil || !ok {
return nil, ErrExportTokenInvalid
}
@@ -64,7 +70,11 @@ func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBl
if value.Blank != expectBlank {
return nil, ErrExportTokenType
}
_ = s.cache.Delete(ctx, "export:"+token)
if _, supported := s.cache.(interface {
GetAndDelete(context.Context, string) (string, bool, error)
}); !supported {
_ = s.cache.Delete(ctx, key)
}
return &ExportToken{TemplateID: value.TemplateID, Params: value.Params, Blank: value.Blank}, nil
}
func exportTemplateDomain(value *dto.ExportTemplateRequest) *system.ExportTemplate {
+1 -1
View File
@@ -40,5 +40,5 @@ func convertUser(user *system.User) *dto.UserResponse {
if user.Department != nil {
department = departmentResponse(user.Department)
}
return &dto.UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, DeletedAt: nil, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting, PasswordUpdatedAt: user.PasswordUpdatedAt}
return &dto.UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting, PasswordUpdatedAt: user.PasswordUpdatedAt}
}