优化结构
This commit is contained in:
+2
-2
@@ -15,9 +15,9 @@
|
||||
- `routecatalog`:统一声明 HTTP 路由的公开性、操作审计、请求体策略和 API 元数据
|
||||
- `server`:Gin server 组合与生命周期;横切 HTTP 代码按子包维护:
|
||||
`server/handler`、`server/middleware`、`server/router`、`server/staticfiles`;
|
||||
通用响应和 Cookie 工具位于 `pkg/httpx`
|
||||
通用响应和 Cookie 工具位于 `server/httpx`
|
||||
- `service`:按 `system`、`payment`、`integration`、`task` 分模块的应用服务、
|
||||
DTO 与领域对象转换;DTO 集中在 `service/dto`,根包是兼容旧调用方的类型/构造器门面
|
||||
DTO 与领域对象转换;DTO 集中在 `service/dto`,根包仅聚合 Wire ProviderSet
|
||||
- `worker`:定时任务执行与调度
|
||||
|
||||
目录代表边界,模块文件按资源命名。DTO、handler、中间件、路由和 HTTP
|
||||
|
||||
@@ -120,7 +120,7 @@ func (uc *IntegrationConfigUsecase) Save(ctx context.Context, config *Integratio
|
||||
return err
|
||||
}
|
||||
if definition, ok := IntegrationDefinition(config.Kind, config.Provider); ok {
|
||||
values = mergeIntegrationDefaults(definition.Defaults, values)
|
||||
values = MergeIntegrationDefaults(definition.Defaults, values)
|
||||
if config.Enabled {
|
||||
if err := ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||
return err
|
||||
@@ -153,7 +153,7 @@ func (uc *IntegrationConfigUsecase) Test(ctx context.Context, config *Integratio
|
||||
return err
|
||||
}
|
||||
if definition, ok := IntegrationDefinition(config.Kind, config.Provider); ok {
|
||||
values = mergeIntegrationDefaults(definition.Defaults, values)
|
||||
values = MergeIntegrationDefaults(definition.Defaults, values)
|
||||
}
|
||||
if err := ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||
return err
|
||||
@@ -209,11 +209,7 @@ func DefaultIntegrationConfig(kind, provider string) map[string]any {
|
||||
if !ok {
|
||||
return map[string]any{}
|
||||
}
|
||||
return mergeIntegrationDefaults(definition.Defaults, nil)
|
||||
}
|
||||
|
||||
func mergeIntegrationDefaults(defaults, values map[string]any) map[string]any {
|
||||
return MergeIntegrationDefaults(defaults, values)
|
||||
return MergeIntegrationDefaults(definition.Defaults, nil)
|
||||
}
|
||||
|
||||
// MergeIntegrationDefaults returns a new map with stored values overriding defaults.
|
||||
|
||||
@@ -89,7 +89,7 @@ var genericPaymentDefaults = map[string]any{
|
||||
}
|
||||
|
||||
func genericPaymentDefinition(provider, name, description string) IntegrationConfigDefinition {
|
||||
defaults := mergeIntegrationDefaults(genericPaymentDefaults, nil)
|
||||
defaults := MergeIntegrationDefaults(genericPaymentDefaults, nil)
|
||||
fields := append([]IntegrationConfigField(nil), genericPaymentFields...)
|
||||
return paymentDefinition(provider, name, description, defaults, fields...)
|
||||
}
|
||||
|
||||
@@ -349,15 +349,6 @@ func (r *PaymentFulfillmentRegistry) Handler(kind string) PaymentFulfillmentHand
|
||||
return r.handlers[kind]
|
||||
}
|
||||
|
||||
func (r *PaymentFulfillmentRegistry) Len() int {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.handlers)
|
||||
}
|
||||
|
||||
type PaymentUsecase struct {
|
||||
repo PaymentRepo
|
||||
orders PaymentOrderRepo
|
||||
@@ -387,12 +378,6 @@ func NewPaymentUsecase(repo PaymentRepo, orders PaymentOrderRepo, hooks PaymentH
|
||||
}
|
||||
}
|
||||
|
||||
func NewConfiguredPaymentUsecase(repo PaymentRepo, orders PaymentOrderRepo, hooks PaymentHooks, sources *PaymentOrderSourceRegistry, fulfillments *PaymentFulfillmentRegistry, appLogger *slog.Logger) (*PaymentUsecase, error) {
|
||||
// Business modules are optional in the template. Missing modules are
|
||||
// reported when the corresponding payment operation is invoked.
|
||||
return NewPaymentUsecase(repo, orders, hooks, sources, fulfillments, appLogger), nil
|
||||
}
|
||||
|
||||
func (uc *PaymentUsecase) RegisterBusinessModule(module PaymentBusinessModule) error {
|
||||
if module == nil {
|
||||
return errors.New("支付业务模块为空")
|
||||
@@ -464,14 +449,11 @@ func (uc *PaymentUsecase) Create(ctx context.Context, req *PaymentRequest) (*Pay
|
||||
if req.OriginalAmount < req.Amount {
|
||||
return nil, errors.New("原始金额不能小于支付订单金额")
|
||||
}
|
||||
canonicalProvider, canonicalTradeNo := req.Provider, req.TradeNo
|
||||
canonicalBusinessType, canonicalBusinessID := req.BusinessType, req.BusinessID
|
||||
canonicalSubject, canonicalCurrency, canonicalAmount := req.Subject, req.Currency, req.Amount
|
||||
canonicalOriginalAmount, canonicalPaymentMode := req.OriginalAmount, req.PaymentMode
|
||||
canonicalExtra, err := json.Marshal(req.Extra)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("支付扩展参数无效: %w", err)
|
||||
}
|
||||
canonicalFingerprint := paymentOrderFingerprint(req, canonicalExtra)
|
||||
if err := uc.hooks.BeforeCreate(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -479,7 +461,7 @@ func (uc *PaymentUsecase) Create(ctx context.Context, req *PaymentRequest) (*Pay
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("支付扩展参数无效: %w", err)
|
||||
}
|
||||
if req.Provider != canonicalProvider || req.TradeNo != canonicalTradeNo || req.BusinessType != canonicalBusinessType || req.BusinessID != canonicalBusinessID || req.Subject != canonicalSubject || req.Currency != canonicalCurrency || req.Amount != canonicalAmount || req.OriginalAmount != canonicalOriginalAmount || req.PaymentMode != canonicalPaymentMode || string(currentExtra) != string(canonicalExtra) {
|
||||
if paymentOrderFingerprint(req, currentExtra) != canonicalFingerprint {
|
||||
return nil, ErrPaymentOrderConflict
|
||||
}
|
||||
return uc.createWithOrder(ctx, req)
|
||||
@@ -926,10 +908,7 @@ func (uc *PaymentUsecase) refundWithOrder(ctx context.Context, provider, tradeNo
|
||||
return nil, err
|
||||
}
|
||||
if !accepted {
|
||||
if providerErr != nil {
|
||||
return nil, providerErr
|
||||
}
|
||||
return nil, errors.New(message)
|
||||
return nil, providerErr
|
||||
}
|
||||
result.Status = "refund_pending"
|
||||
return attachOrderResult(result, order), nil
|
||||
|
||||
@@ -182,15 +182,6 @@ func (r *PaymentOrderSourceRegistry) Source(kind string) PaymentOrderSource {
|
||||
return r.sources[kind]
|
||||
}
|
||||
|
||||
func (r *PaymentOrderSourceRegistry) Len() int {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.sources)
|
||||
}
|
||||
|
||||
func (r *PaymentOrderSourceRegistry) Unregister(kind string) {
|
||||
if r == nil {
|
||||
return
|
||||
|
||||
@@ -9,5 +9,5 @@ var ProviderSet = wire.NewSet(
|
||||
NewPaymentHooks,
|
||||
NewPaymentOrderSourceRegistry,
|
||||
NewPaymentFulfillmentRegistry,
|
||||
NewConfiguredPaymentUsecase,
|
||||
NewPaymentUsecase,
|
||||
)
|
||||
|
||||
@@ -96,7 +96,7 @@ 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}
|
||||
copy := &MediaFile{Name: name, URL: media.URL, Tag: media.Tag, Key: media.Key, Size: media.Size, MD5: media.MD5, Mime: media.Mime, UserID: userID}
|
||||
if createErr := uc.CreateMedia(ctx, copy); createErr == nil {
|
||||
return nil, copy, nil, nil
|
||||
}
|
||||
@@ -249,7 +249,10 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin
|
||||
if err = uc.CreateMedia(ctx, media); err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
_ = uc.CompleteUploadSession(ctx, uploadID, key, media.ID)
|
||||
if err = uc.CompleteUploadSession(ctx, uploadID, key, media.ID); err != nil {
|
||||
_ = uc.FailUploadSession(ctx, uploadID)
|
||||
return nil, fmt.Errorf("完成上传会话失败: %w", err)
|
||||
}
|
||||
_ = uc.DeleteChunks(ctx, uploadID)
|
||||
_ = uc.files.DeletePrefix(ctx, uc.chunkPrefix(uploadID))
|
||||
return media, nil
|
||||
|
||||
@@ -270,6 +270,9 @@ func (r *Store) watchLoop(watcher *fsnotify.Watcher, stop <-chan struct{}, done
|
||||
// take down a running process or publish invalid state.
|
||||
continue
|
||||
}
|
||||
// File watching publishes the merged immutable snapshot only. Storage
|
||||
// clients are rebuilt by the explicit system reload flow in data;
|
||||
// keeping that lifecycle out of the watcher preserves rollback safety.
|
||||
r.Replace(MergeRuntimeConfig(r.Snapshot(), next))
|
||||
case _, ok := <-errors:
|
||||
// fsnotify errors are intentionally non-fatal; the watcher remains
|
||||
|
||||
@@ -122,6 +122,8 @@ func (d *Data) removeIntegrationConfigFromFile() error {
|
||||
}
|
||||
|
||||
func (d *Data) reloadConfig(ctx context.Context) error {
|
||||
// Explicit reload is the lifecycle boundary that rebuilds database, cache,
|
||||
// Mongo, storage, and integration clients after a file snapshot change.
|
||||
d.configMu.Lock()
|
||||
defer d.configMu.Unlock()
|
||||
configPath := d.runtime.ConfigPath()
|
||||
|
||||
@@ -411,7 +411,7 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an
|
||||
if value == "" {
|
||||
value = paymentkit.FirstText(config, keys...)
|
||||
}
|
||||
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
normalized := paymentkit.NormalizePaymentMethod(value)
|
||||
switch provider {
|
||||
case bizpayment.PaymentAlipay, bizpayment.PaymentAlipayV3:
|
||||
return !paymentkit.ContainsFold([]string{"pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face"}, normalized)
|
||||
|
||||
@@ -66,3 +66,27 @@ func TestParameterRepositoryKeepsReferenceEmptyQuerySemantics(t *testing.T) {
|
||||
t.Fatalf("empty bulk delete removed rows: count=%d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameterUpdateDoesNotReviveSoftDeletedRow(t *testing.T) {
|
||||
data := newTransactionTestData(t)
|
||||
repo := ¶meterRepo{data: data}
|
||||
ctx := context.Background()
|
||||
item := &system.SystemParameter{Name: "before-delete", Key: "soft-delete-key", Value: "value"}
|
||||
if err := repo.CreateParameter(ctx, item); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := data.gormDB.WithContext(ctx).Delete(¶meterPO{}, item.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
item.Name = "must-not-revive"
|
||||
if err := repo.UpdateParameter(ctx, item); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var persisted parameterPO
|
||||
if err := data.gormDB.WithContext(ctx).Unscoped().First(&persisted, item.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if persisted.Name != "before-delete" || persisted.DeletedAt.Time.IsZero() {
|
||||
t.Fatalf("soft-deleted row changed or revived: %+v", persisted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ lifecycles. Their constructors are exposed through `ProviderSet`; `cmd` binds
|
||||
the `cache.RedisProvider` implementation to the shared `data.Data` container.
|
||||
|
||||
Stateless protocol helpers that do not own clients live in
|
||||
`pkg/paymentkit`. They are intentionally small and dependency
|
||||
`internal/paymentkit`. They are intentionally small and dependency
|
||||
light, while provider adapters remain here.
|
||||
|
||||
## Shared WebSocket and MQ APIs
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"kra/internal/paymentkit"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
@@ -126,7 +127,7 @@ func qqCreateMethod(extra, config map[string]any) (string, error) {
|
||||
if value == "" {
|
||||
value = firstAny(config, "trade_type", "pay_type", "method", "pay_method")
|
||||
}
|
||||
normalized := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(value), "-", "_"))
|
||||
normalized := strings.ToUpper(paymentkit.NormalizePaymentMethod(value))
|
||||
switch normalized {
|
||||
case "", "NATIVE", "QR", "QRCODE":
|
||||
return gopayQQ.TradeType_Native, nil
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"kra/internal/paymentkit"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -120,7 +121,7 @@ func (a *wechatV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentReq
|
||||
}
|
||||
|
||||
func normalizeWechatV3TradeType(value string) string {
|
||||
return strings.NewReplacer(".", "", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
return paymentkit.NormalizePaymentMethod(value)
|
||||
}
|
||||
|
||||
func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*bizpayment.PaymentResult, error) {
|
||||
|
||||
@@ -71,14 +71,10 @@ func skipStackFile(filename string) bool {
|
||||
"/go.uber.org/",
|
||||
"/gorm.io/",
|
||||
"/internal/logging/",
|
||||
"/internal/transport/middleware/",
|
||||
"/internal/transport/router/",
|
||||
"/internal/server/handler/",
|
||||
"/internal/server/middleware/",
|
||||
"/internal/server/router/",
|
||||
"/internal/server/httpx/",
|
||||
"/internal/server/middleware_",
|
||||
"/internal/server/route_",
|
||||
} {
|
||||
if strings.Contains(normalized, marker) {
|
||||
return true
|
||||
|
||||
@@ -144,10 +144,7 @@ func TestSkipStackFileRecognizesServerTransportPackages(t *testing.T) {
|
||||
{name: "middleware", path: `D:\workspace\app\system\internal\server\middleware\auth.go`, want: true},
|
||||
{name: "router", path: `/workspace/internal/server/router/user.go`, want: true},
|
||||
{name: "http helper", path: `/workspace/internal/server/httpx/response.go`, want: true},
|
||||
{name: "legacy flattened middleware", path: `D:\workspace\app\system\internal\server\middleware_auth.go`, want: true},
|
||||
{name: "legacy flattened route", path: `/workspace/internal/server/route_user.go`, want: true},
|
||||
{name: "handler remains application boundary", path: `/workspace/internal/server/handler_user.go`, want: false},
|
||||
{name: "legacy middleware", path: `/workspace/internal/transport/middleware/auth.go`, want: true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
BodyPolicyIntegrationConfig RouteBodyPolicy = "integration_config"
|
||||
BodyPolicyPaymentConfig RouteBodyPolicy = "payment_config"
|
||||
BodyPolicyPaymentCallback RouteBodyPolicy = "payment_callback"
|
||||
BodyPolicyUpload RouteBodyPolicy = "upload"
|
||||
)
|
||||
|
||||
// Descriptor is the shared HTTP contract consumed by service bootstrap,
|
||||
@@ -160,13 +161,13 @@ var routes = map[string]routeValue{
|
||||
"POST /fileUploadAndDownload/getFileList": {group: "文件上传与下载", description: "获取上传文件列表"},
|
||||
"POST /fileUploadAndDownload/importURL": {group: "文件上传与下载", description: "导入URL"},
|
||||
"POST /fileUploadAndDownload/listOssFiles": {group: "文件上传与下载", description: "获取对象存储文件列表"},
|
||||
"POST /fileUploadAndDownload/upload": {group: "文件上传与下载", description: "文件上传(建议选择)"},
|
||||
"POST /fileUploadAndDownload/upload": {group: "文件上传与下载", description: "文件上传(建议选择)", bodyPolicy: BodyPolicyUpload},
|
||||
"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},
|
||||
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接", audit: true, bodyPolicy: BodyPolicyPaymentConfig},
|
||||
"POST /jwt/jsonInBlacklist": {group: "jwt", description: "jwt加入黑名单(退出,必选)"},
|
||||
"POST /mediaUpload/chunk": {group: "媒体上传", description: "上传分片"},
|
||||
"POST /mediaUpload/chunk": {group: "媒体上传", description: "上传分片", bodyPolicy: BodyPolicyUpload},
|
||||
"POST /mediaUpload/complete": {group: "媒体上传", description: "完成大文件上传"},
|
||||
"POST /mediaUpload/init": {group: "媒体上传", description: "初始化大文件上传"},
|
||||
"POST /menu/addBaseMenu": {group: "菜单", description: "新增菜单", audit: true},
|
||||
|
||||
@@ -6,7 +6,7 @@ grouped by role:
|
||||
- `handler/`: resource handlers and HTTP boundary validation
|
||||
- `middleware/`: request metadata, auth, access control, audit, recovery, CORS
|
||||
- `router/`: resource route registration and the system route registrar
|
||||
- `pkg/httpx/`: transport-level response and cookie helpers shared by handlers
|
||||
- `httpx/`: transport-level response and cookie helpers shared by handlers
|
||||
and middleware
|
||||
- `staticfiles/`: local upload storage route registration and file serving
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ func (h *API) ApplySync(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.ApplyAPISyncRequest(c.Request.Context(), &req); err != nil {
|
||||
Fail(c, "忽略失败")
|
||||
Fail(c, "同步失败")
|
||||
return
|
||||
}
|
||||
OK(c)
|
||||
|
||||
@@ -138,7 +138,7 @@ func (h *Audit) DataAccess(c *gin.Context) {
|
||||
}
|
||||
items, total, err := h.service.DataAccessRequest(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
Fail(c, "获取失败:"+err.Error())
|
||||
Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||
@@ -150,7 +150,7 @@ func (h *Audit) DeleteDataAccess(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil {
|
||||
Fail(c, "删除失败:"+err.Error())
|
||||
Fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, gin.H{}, "删除成功")
|
||||
@@ -238,7 +238,7 @@ func (h *Audit) DeleteError(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteErrors(c.Request.Context(), []uint{id}); err != nil {
|
||||
Fail(c, "删除失败:"+err.Error())
|
||||
Fail(c, "删除失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, gin.H{}, "删除成功")
|
||||
@@ -250,7 +250,7 @@ func (h *Audit) DeleteErrors(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteErrors(c.Request.Context(), ids); err != nil {
|
||||
Fail(c, "批量删除失败:"+err.Error())
|
||||
Fail(c, "批量删除失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
|
||||
@@ -262,7 +262,7 @@ func (h *Audit) UpdateError(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.UpdateErrorRequest(c.Request.Context(), &req); err != nil {
|
||||
Fail(c, "更新失败:"+err.Error())
|
||||
Fail(c, "更新失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, gin.H{}, "更新成功")
|
||||
@@ -275,7 +275,7 @@ func (h *Audit) Error(c *gin.Context) {
|
||||
}
|
||||
item, err := h.service.Error(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
Fail(c, "查询失败:"+err.Error())
|
||||
Fail(c, "查询失败")
|
||||
return
|
||||
}
|
||||
OKWithData(c, item)
|
||||
@@ -297,7 +297,7 @@ func (h *Audit) Errors(c *gin.Context) {
|
||||
}
|
||||
items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("info"), createdAtRange)
|
||||
if err != nil {
|
||||
Fail(c, "获取失败:"+err.Error())
|
||||
Fail(c, "获取失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
|
||||
@@ -309,7 +309,7 @@ func (h *Audit) CreateError(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.recorder.CreateErrorMutationRequest(c.Request.Context(), &req); err != nil {
|
||||
Fail(c, "创建失败:"+err.Error())
|
||||
Fail(c, "创建失败")
|
||||
return
|
||||
}
|
||||
Write(c, CodeSuccess, gin.H{}, "创建成功")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is the handler package's single vocabulary for HTTP replies: the
|
||||
// envelope helpers come from pkg/httpx and Claims from the auth middleware, so
|
||||
// envelope helpers come from server/httpx and Claims from the auth middleware, so
|
||||
// handlers name one package instead of two.
|
||||
package handler
|
||||
|
||||
|
||||
@@ -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 && isMediaUploadRoute(c.FullPath())
|
||||
mediaUpload := multipart && routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path) == routecatalog.BodyPolicyUpload
|
||||
var admin *config.Admin
|
||||
if runtime != nil {
|
||||
if snapshot := runtime.Snapshot(); snapshot != nil {
|
||||
@@ -170,11 +170,6 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||
}
|
||||
}
|
||||
|
||||
func isMediaUploadRoute(route string) bool {
|
||||
return strings.HasSuffix(route, "/fileUploadAndDownload/upload") ||
|
||||
strings.HasSuffix(route, "/mediaUpload/chunk")
|
||||
}
|
||||
|
||||
func paymentCallbackProvider(path string) string {
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
for index := 0; index+2 < len(parts); index++ {
|
||||
|
||||
@@ -80,7 +80,7 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
||||
responseBody := ""
|
||||
if value, ok := c.Get(ctxRespBufferKey); ok {
|
||||
if body, bok := value.(*bytes.Buffer); bok {
|
||||
responseBody = body.String()
|
||||
responseBody = redactJSON(body.Bytes(), c.Writer.Header().Get("Content-Type"), maxBytes)
|
||||
}
|
||||
}
|
||||
if isDownloadResponse(c) && len(responseBody) > maxBytes {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPaymentIntegrationSecretsAreRedacted(t *testing.T) {
|
||||
raw := []byte(`{"enabled":true,"config":{"app_id":"app","mch_key":"merchant-secret","api_v3_key":"v3-secret","client_cert":"certificate","client_key":"private-key","platform_cert":"platform-certificate","credential_code":"credential","webhook_id":"webhook"}}`)
|
||||
raw := []byte(`{"enabled":true,"config":{"app_id":"app","mch_key":"merchant-secret","api_v3_key":"v3-secret","client_cert":"certificate","client_key":"private-key","platform_cert":"platform-certificate","credential_code":"credential","webhook_id":"webhook","key":"institution-secret","public_cert":"public-certificate","signing_key":"jwt-secret","secret_key":"cloud-secret"}}`)
|
||||
redacted := redactJSON(raw, "application/json", 4096)
|
||||
var payload struct {
|
||||
Config map[string]string `json:"config"`
|
||||
@@ -17,7 +17,7 @@ func TestPaymentIntegrationSecretsAreRedacted(t *testing.T) {
|
||||
if err := json.Unmarshal([]byte(redacted), &payload); err != nil {
|
||||
t.Fatalf("decode redacted payload: %v", err)
|
||||
}
|
||||
for _, key := range []string{"mch_key", "api_v3_key", "client_cert", "client_key", "platform_cert", "credential_code", "webhook_id"} {
|
||||
for _, key := range []string{"mch_key", "api_v3_key", "client_cert", "client_key", "platform_cert", "credential_code", "webhook_id", "key", "public_cert", "signing_key", "secret_key"} {
|
||||
if payload.Config[key] != "***" {
|
||||
t.Fatalf("payment secret %q was not redacted: %s", key, redacted)
|
||||
}
|
||||
@@ -55,4 +55,7 @@ func TestPaymentIntegrationConfigUsesRouteLevelSummary(t *testing.T) {
|
||||
if routecatalog.BodyPolicyFor("PUT", "/api/integration/configs/mq/emqx") == routecatalog.BodyPolicyPaymentConfig {
|
||||
t.Fatal("non-payment integration was treated as payment configuration")
|
||||
}
|
||||
if routecatalog.BodyPolicyFor("POST", "/api/integration/configs/payment/saobei/test") != routecatalog.BodyPolicyPaymentConfig {
|
||||
t.Fatal("payment test route was not recognized as sensitive")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ var sensitivePayloadKeys = map[string]struct{}{
|
||||
"password": {}, "newpassword": {}, "oldpassword": {}, "confirmpassword": {},
|
||||
"passwd": {}, "pwd": {}, "token": {}, "accesstoken": {}, "refreshtoken": {},
|
||||
"secret": {}, "clientsecret": {}, "apikey": {}, "privatekey": {}, "idcard": {},
|
||||
"key": {}, "signingkey": {}, "secretkey": {},
|
||||
"appkey": {}, "mchkey": {}, "apiv3key": {}, "clientcert": {}, "clientkey": {},
|
||||
"platformcert": {}, "platformserialno": {}, "credentialcode": {}, "certfile": {},
|
||||
"platformcert": {}, "platformserialno": {}, "publiccert": {}, "credentialcode": {}, "certfile": {},
|
||||
"keyfile": {}, "publickey": {}, "rootcert": {}, "appcert": {}, "webhookid": {},
|
||||
"authorization": {},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`service` adapts HTTP-facing DTOs to business usecases and owns application
|
||||
orchestration. Request/response/filter contracts live in `dto/`; the root
|
||||
package is a compatibility facade and module provider aggregator.
|
||||
package aggregates module provider sets for Wire.
|
||||
|
||||
Implementations are grouped by the same business modules as `biz` and `data`:
|
||||
|
||||
@@ -18,9 +18,8 @@ The stateless router-prefix helper lives in `internal/utils/routepath` because
|
||||
both service modules and HTTP middleware use it; it does not depend on a
|
||||
business usecase or DTO.
|
||||
|
||||
Each module owns its Wire `ProviderSet`. The root `service` package re-exports
|
||||
the existing service types and constructors through aliases and thin wrappers, so handlers, middleware
|
||||
and generated Wire code can migrate independently without a flag day.
|
||||
Each module owns its Wire `ProviderSet`; the root package only composes those
|
||||
sets and does not re-export service types or constructors.
|
||||
|
||||
Within `system/`, larger cross-cutting resources stay in the same package while
|
||||
being split by concern (`audit.go` and its error/log companions, `media.go` and
|
||||
|
||||
@@ -10,7 +10,7 @@ func convertAuthority(value system.Authority) *dto.AuthorityResponse {
|
||||
if value.Menus != nil {
|
||||
menus = menuResponses(value.Menus)
|
||||
}
|
||||
return &dto.AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: value.DeletedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
|
||||
return &dto.AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
|
||||
}
|
||||
func convertUser(user *system.User) *dto.UserResponse {
|
||||
var authorities []*dto.AuthorityResponse
|
||||
|
||||
Reference in New Issue
Block a user