固化七月迭代审计治理进展以隔离线上热修

Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。

Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。

Confidence: medium

Scope-risk: broad

Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。

Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。

Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
This commit is contained in:
2026-08-03 09:47:22 +08:00
parent cf2ff0ac1c
commit b3499adfca
114 changed files with 16961 additions and 2782 deletions

View File

@@ -452,6 +452,10 @@ func (h *AssetHandler) Orders(c *fiber.Ctx) error {
// OperationLogs 查询资产操作审计日志
// GET /api/admin/assets/:identifier/operation-logs
func (h *AssetHandler) OperationLogs(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
if h.assetAuditService == nil {
return errors.New(errors.CodeInternalError, "资产审计服务未配置")
}

View File

@@ -0,0 +1,265 @@
package admin
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
auditquery "github.com/break/junhong_cmp_fiber/internal/query/audit"
integrationquery "github.com/break/junhong_cmp_fiber/internal/query/integration"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// AuditHandler 提供平台基础审计调查只读接口。
type AuditHandler struct {
auditQuery *auditquery.Query
integrationQuery *integrationquery.Query
}
// NewAuditHandler 创建平台基础审计调查 Handler。
func NewAuditHandler(auditQuery *auditquery.Query, integrationQuery *integrationquery.Query) *AuditHandler {
return &AuditHandler{auditQuery: auditQuery, integrationQuery: integrationQuery}
}
// ListEvents 查询平台全局审计事件。
// GET /api/admin/audit/events
func (h *AuditHandler) ListEvents(c *fiber.Ctx) error {
var request dto.AuditEventListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
if err != nil || invalidAuditPage(request.Page, request.PageSize) {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.auditQuery.List(c.UserContext(), auditquery.EventFilter{
CreatedFrom: from, CreatedTo: to, Action: request.Action, Category: request.Category,
ActorKind: request.ActorKind, ActorID: request.ActorID, Source: request.Source,
Result: request.Result, Risk: request.Risk, ScopeType: request.ScopeType, ScopeID: request.ScopeID,
ResourceType: request.ResourceType, ResourceID: request.ResourceID, ResourceKey: request.ResourceKey,
RequestID: request.RequestID, CorrelationID: request.CorrelationID,
Page: request.Page, PageSize: request.PageSize,
})
if err != nil {
return err
}
return response.Success(c, result)
}
// GetEvent 查询单个稳定审计事件详情。
// GET /api/admin/audit/events/:event_id
func (h *AuditHandler) GetEvent(c *fiber.Ctx) error {
result, err := h.auditQuery.Get(c.UserContext(), c.Params("event_id"))
if err != nil {
return err
}
return response.Success(c, result)
}
// ListActorEvents 查询操作者行为时间线。
// GET /api/admin/audit/actors/:kind/:id/events
func (h *AuditHandler) ListActorEvents(c *fiber.Ctx) error {
var request dto.AuditActorEventsRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
request.Kind, request.ID = c.Params("kind"), c.Params("id")
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
if err != nil || invalidAuditPage(request.Page, request.PageSize) {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.auditQuery.ListActorEvents(c.UserContext(), auditquery.ActorEventFilter{
Kind: request.Kind, ID: request.ID, Action: request.Action, Result: request.Result, Risk: request.Risk,
ResourceType: request.ResourceType, ResourceID: request.ResourceID,
CreatedFrom: from, CreatedTo: to, Page: request.Page, PageSize: request.PageSize,
})
if err != nil {
return err
}
return response.Success(c, result)
}
// SearchResources 按注册业务标识精确搜索资源。
// GET /api/admin/audit/resources/search
func (h *AuditHandler) SearchResources(c *fiber.Ctx) error {
var request dto.AuditResourceSearchRequest
if err := c.QueryParser(&request); err != nil || invalidAuditPage(request.Page, request.PageSize) {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.auditQuery.SearchResources(c.UserContext(), auditquery.ResourceSearchFilter{
ResourceType: request.ResourceType, Keyword: request.Keyword,
Page: request.Page, PageSize: request.PageSize,
})
if err != nil {
return err
}
return response.Success(c, result)
}
// ResourceTimeline 查询资源作为任意关系参与的通用事件时间线。
// GET /api/admin/audit/resources/:resource_type/:resource_id/timeline
func (h *AuditHandler) ResourceTimeline(c *fiber.Ctx) error {
var request dto.AuditResourceTimelineRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
request.ResourceType, request.ResourceID = c.Params("resource_type"), c.Params("resource_id")
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
if err != nil || invalidAuditPage(request.Page, request.PageSize) {
return errors.New(errors.CodeInvalidParam)
}
result, err := h.auditQuery.ResourceTimeline(c.UserContext(), auditquery.ResourceTimelineFilter{
ResourceType: request.ResourceType, ResourceID: request.ResourceID,
CreatedFrom: from, CreatedTo: to, Action: request.Action, Result: request.Result,
Page: request.Page, PageSize: request.PageSize,
})
if err != nil {
return err
}
return response.Success(c, result)
}
// AgentResourceActivities 查询代理范围内的安全资源活动。
// GET /api/admin/agent/resource-activities/:resource_type/:identifier
func (h *AuditHandler) AgentResourceActivities(c *fiber.Ctx) error {
request, err := subjectActivityRequest(c)
if err != nil {
return err
}
result, err := h.auditQuery.AgentResourceActivities(c.UserContext(), auditquery.SubjectActivityFilter{
ResourceType: request.ResourceType, Identifier: request.Identifier,
Page: request.Page, PageSize: request.PageSize,
})
if err != nil {
return err
}
return response.Success(c, result)
}
// EnterpriseResourceActivities 查询企业当前有效授权资产的安全资源活动。
// GET /api/admin/enterprise/resource-activities/:resource_type/:identifier
func (h *AuditHandler) EnterpriseResourceActivities(c *fiber.Ctx) error {
request, err := subjectActivityRequest(c)
if err != nil {
return err
}
result, err := h.auditQuery.EnterpriseResourceActivities(c.UserContext(), auditquery.SubjectActivityFilter{
ResourceType: request.ResourceType, Identifier: request.Identifier,
Page: request.Page, PageSize: request.PageSize,
})
if err != nil {
return err
}
return response.Success(c, result)
}
func subjectActivityRequest(c *fiber.Ctx) (dto.SubjectResourceActivityRequest, error) {
var request dto.SubjectResourceActivityRequest
if err := c.QueryParser(&request); err != nil || invalidAuditPage(request.Page, request.PageSize) {
return request, errors.New(errors.CodeInvalidParam)
}
request.ResourceType = c.Params("resource_type")
request.Identifier = c.Params("identifier")
if request.ResourceType == "" || request.Identifier == "" {
return request, errors.New(errors.CodeInvalidParam)
}
return request, nil
}
// IntegrationOverview 查询外部集成交互总览。
// GET /api/admin/audit/integrations/overview
func (h *AuditHandler) IntegrationOverview(c *fiber.Ctx) error {
var request dto.IntegrationOverviewRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam)
}
filter, err := integrationFilter(request.IntegrationFilterRequest)
if err != nil {
return err
}
result, err := h.integrationQuery.Overview(c.UserContext(), integrationquery.OverviewFilter{
ListFilter: filter,
Bucket: request.Bucket,
})
if err != nil {
return err
}
return response.Success(c, result)
}
// ListIntegrations 查询外部集成交互列表。
// GET /api/admin/audit/integrations
func (h *AuditHandler) ListIntegrations(c *fiber.Ctx) error {
var request dto.IntegrationListRequest
if err := c.QueryParser(&request); err != nil || invalidAuditPage(request.Page, request.PageSize) {
return errors.New(errors.CodeInvalidParam)
}
filter, err := integrationFilter(request.IntegrationFilterRequest)
if err != nil {
return err
}
filter.Page, filter.PageSize = request.Page, request.PageSize
result, err := h.integrationQuery.List(c.UserContext(), filter)
if err != nil {
return err
}
return response.Success(c, result)
}
// GetIntegration 查询稳定外部集成记录详情。
// GET /api/admin/audit/integrations/:integration_id
func (h *AuditHandler) GetIntegration(c *fiber.Ctx) error {
result, err := h.integrationQuery.Get(c.UserContext(), c.Params("integration_id"))
if err != nil {
return err
}
return response.Success(c, result)
}
func integrationFilter(request dto.IntegrationFilterRequest) (integrationquery.ListFilter, error) {
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
if err != nil {
return integrationquery.ListFilter{}, errors.New(errors.CodeInvalidParam)
}
return integrationquery.ListFilter{
CreatedFrom: from, CreatedTo: to, IntegrationID: request.IntegrationID,
Provider: request.Provider, Direction: request.Direction, Operation: request.Operation,
Result: request.Result, ResultCategory: request.ResultCategory, ExternalID: request.ExternalID,
ResourceType: request.ResourceType, ResourceID: request.ResourceID, ResourceKey: request.ResourceKey,
TriggerSource: request.TriggerSource, TriggerScene: request.TriggerScene, TriggerSeries: request.TriggerSeries,
StateChanged: request.StateChanged, HTTPStatus: request.HTTPStatus, ProviderCode: request.ProviderCode,
RequestID: request.RequestID, CorrelationID: request.CorrelationID,
}, nil
}
func auditTimeRange(fromValue, toValue string) (*time.Time, *time.Time, error) {
from, err := optionalAuditTime(fromValue)
if err != nil {
return nil, nil, err
}
to, err := optionalAuditTime(toValue)
if err != nil {
return nil, nil, err
}
if from != nil && to != nil && !from.Before(*to) {
return nil, nil, errors.New(errors.CodeInvalidParam)
}
return from, to, nil
}
func optionalAuditTime(value string) (*time.Time, error) {
if value == "" {
return nil, nil
}
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
return nil, err
}
return &parsed, nil
}
func invalidAuditPage(page, pageSize int) bool {
return page < 0 || pageSize < 0 || pageSize > 100
}

View File

@@ -137,7 +137,7 @@ func (h *ShopHandler) UpdateCreditLimit(c *fiber.Ctx) error {
if h.changeCreditService == nil {
return errors.New(errors.CodeInternalError, "店铺信用额度服务未配置")
}
result, err := h.changeCreditService.Execute(c.UserContext(), uint(id), *request.CreditEnabled, *request.CreditLimit, *request.Version)
result, err := h.changeCreditService.Execute(c.UserContext(), uint(id), *request.CreditEnabled, *request.CreditLimit)
if err != nil {
return err
}

View File

@@ -77,3 +77,13 @@ func recordDisabledCarrierCallback(
})
return err
}
// completeResolvedCarrierCallback 在回调已精确解析卡后补充真实本地资源ID。
func completeResolvedCarrierCallback(ctx context.Context, repository *integrationlog.Repository, integrationID, result string, stateChanged bool, reason string, cardID uint) error {
resourceID := strconv.FormatUint(uint64(cardID), 10)
_, err := repository.Complete(ctx, integrationID, integrationlog.Completion{
Result: result, HTTPStatus: fiber.StatusOK, StateChanged: stateChanged, ResourceID: &resourceID,
ResponseSummary: map[string]any{"reason": reason},
})
return err
}

View File

@@ -115,7 +115,7 @@ func (h *CMCCRealnameHandler) process(ctx context.Context, body []byte, contentT
h.logger.Warn("移动实名事实已应用但提前完成观测序列失败", zap.Uint("card_id", card.ID), zap.Error(err))
}
}
return h.complete(ctx, log.IntegrationID, constants.IntegrationResultSuccess, decision.StatusChanged, "实名事实已幂等应用")
return completeResolvedCarrierCallback(ctx, h.integration, log.IntegrationID, constants.IntegrationResultSuccess, decision.StatusChanged, "实名事实已幂等应用", card.ID)
}
func (h *CMCCRealnameHandler) recordConflict(ctx context.Context, body []byte, contentType, baseKey string, translated carriercallback.CMCCRealnameTranslation, requestID *string) error {

View File

@@ -141,7 +141,7 @@ func (h *CTCCRealnameHandler) process(ctx context.Context, body []byte, contentT
h.logger.Warn("电信实名事实已应用但提前完成观测序列失败", zap.Uint("card_id", card.ID), zap.Error(err))
}
}
return h.complete(ctx, log.IntegrationID, constants.IntegrationResultSuccess, decision.StatusChanged, "实名事实已幂等应用")
return completeResolvedCarrierCallback(ctx, h.integration, log.IntegrationID, constants.IntegrationResultSuccess, decision.StatusChanged, "实名事实已幂等应用", card.ID)
}
func (h *CTCCRealnameHandler) failPending(ctx context.Context, integrationID string, original error) error {

View File

@@ -114,7 +114,7 @@ func (h *CUCCRealnameHandler) process(ctx context.Context, body []byte, contentT
h.logger.Warn("联通实名事实已应用但提前完成观测序列失败", zap.Uint("card_id", card.ID), zap.Error(err))
}
}
return h.complete(ctx, log.IntegrationID, constants.IntegrationResultSuccess, decision.StatusChanged, "实名事实已幂等应用")
return completeResolvedCarrierCallback(ctx, h.integration, log.IntegrationID, constants.IntegrationResultSuccess, decision.StatusChanged, "实名事实已幂等应用", card.ID)
}
func (h *CUCCRealnameHandler) recordConflict(ctx context.Context, body []byte, contentType, baseKey string, translated carriercallback.CUCCRealnameTranslation, requestID *string) error {

View File

@@ -93,7 +93,7 @@ func (h *CUCCRealnameRemovalHandler) process(ctx context.Context, body []byte, c
if len(cards) > 1 {
return h.complete(ctx, log.IntegrationID, constants.IntegrationResultConflict, "精确列匹配多张卡")
}
return h.complete(ctx, log.IntegrationID, constants.IntegrationResultIgnored, "解除实名仅留痕,不修改本地实名事实")
return completeResolvedCarrierCallback(ctx, h.integration, log.IntegrationID, constants.IntegrationResultIgnored, false, "解除实名仅留痕,不修改本地实名事实", cards[0].ID)
}
func (h *CUCCRealnameRemovalHandler) recordConflict(ctx context.Context, body []byte, contentType, baseKey string, translated carriercallback.CUCCRealnameRemovalTranslation, requestID *string) error {

View File

@@ -20,6 +20,7 @@ import (
rechargeOrderSvc "github.com/break/junhong_cmp_fiber/internal/service/recharge_order"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/alipay"
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/fuiou"
@@ -112,6 +113,7 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
)
return errors.New(errors.CodeWechatCallbackInvalid, "微信支付服务未配置")
}
ctx = paymentCallbackContext(ctx, constants.IntegrationProviderWechatPay)
switch cfg.ProviderType {
case model.ProviderTypeWechatV2:
@@ -218,7 +220,9 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
if h.agentPaymentConfirm == nil || h.integration == nil {
return errors.New(errors.CodeInternalError, "代理充值支付回调能力未配置")
}
resourceID, correlationID := callback.PaymentNo, callback.PaymentNo
resourceKey, correlationID := callback.PaymentNo, callback.PaymentNo
ctx = auditcontext.With(ctx, auditcontext.Context{CorrelationID: correlationID})
linkage := auditcontext.From(ctx)
idempotencyKey := callback.TransactionID
if idempotencyKey == "" {
idempotencyKey = callback.PaymentNo
@@ -226,7 +230,7 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
log, _, err := h.integration.RecordInbound(ctx, integrationlog.InboundAttempt{
IdempotencyKey: idempotencyKey, Provider: callback.Provider,
Operation: constants.IntegrationOperationPaymentCallback, ExternalID: callback.TransactionID,
ResourceType: constants.IntegrationResourceTypeAgentRechargePayment, ResourceID: &resourceID,
ResourceType: constants.IntegrationResourceTypeAgentRechargePayment, ResourceKey: &resourceKey,
RawPayload: callback.RawPayload, ContentType: callback.ContentType,
RequestID: pkgmiddleware.GetRequestIDFromContext(ctx), CorrelationID: &correlationID,
})
@@ -236,20 +240,27 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
result, confirmErr := h.agentPaymentConfirm.Execute(ctx, agentrechargeApp.ConfirmOnlinePaymentCommand{
PaymentNo: callback.PaymentNo, PaymentMethod: callback.PaymentMethod, ConfigID: callback.ConfigID,
MerchantIdentity: callback.MerchantIdentity, ThirdPartyTradeNo: callback.TransactionID,
Amount: callback.Amount, PaidAt: callback.PaidAt, CorrelationID: correlationID,
Amount: callback.Amount, PaidAt: callback.PaidAt, RequestID: linkage.RequestID,
CorrelationID: correlationID, ParentEventID: linkage.ParentEventID,
})
if confirmErr != nil {
h.logger.Error("代理充值支付确认失败",
zap.String("integration_id", log.IntegrationID),
zap.String("payment_no", callback.PaymentNo),
zap.Error(confirmErr),
)
h.completePaymentCallbackLog(ctx, log, integrationlog.Completion{
Result: constants.IntegrationResultFailed, ProviderMessage: confirmErr.Error(),
Result: constants.IntegrationResultFailed, SafeProviderMessage: "代理充值支付确认失败",
ResponseSummary: map[string]any{"confirmed": false},
})
return confirmErr
}
if log.Result == constants.IntegrationResultPending {
resolvedResourceID := strconv.FormatUint(uint64(result.PaymentID), 10)
_, err = h.integration.Complete(ctx, log.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultSuccess, ProviderCode: "SUCCESS",
ResponseSummary: map[string]any{"confirmed": true, "already_confirmed": result.AlreadyConfirmed},
StateChanged: !result.AlreadyConfirmed,
StateChanged: !result.AlreadyConfirmed, ResourceID: &resolvedResourceID,
})
if err != nil {
return err
@@ -305,6 +316,7 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
)
return errors.New(errors.CodeWechatCallbackInvalid, "支付配置不可用")
}
ctx = paymentCallbackContext(ctx, constants.IntegrationProviderAlipay)
// 使用支付宝公钥验签DecodeNotification 内部完成签名校验)
notification, err := alipay.DecodeNotification(ctx, cfg, values)
@@ -513,6 +525,7 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
)
return c.Send(fuiou.BuildNotifyFailResponse("payment config unavailable"))
}
ctx = paymentCallbackContext(ctx, model.ProviderTypeFuiou)
if cfg.ProviderType != model.ProviderTypeFuiou ||
strings.TrimSpace(preNotify.InsCd) != strings.TrimSpace(cfg.FyInsCd) ||
strings.TrimSpace(preNotify.MchntCd) != strings.TrimSpace(cfg.FyMchntCd) {
@@ -604,6 +617,13 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
return c.Send(fuiou.BuildNotifySuccessResponse())
}
func paymentCallbackContext(ctx context.Context, provider string) context.Context {
return auditcontext.With(ctx, auditcontext.Context{
ActorKind: constants.AuditActorExternalSystem, ActorID: provider,
ActorName: provider, Source: constants.AuditSourceCallback,
})
}
// fuiouCallbackPayload 提取富友回调载荷,兼容 body、form req 和 query req 三种来源。
func fuiouCallbackPayload(c *fiber.Ctx) ([]byte, string) {
if req := strings.TrimSpace(c.FormValue("req")); req != "" {