package product import ( "context" "encoding/json" authHelper "perms-system-server/internal/logic/auth" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/types" "github.com/zeromicro/go-zero/core/logx" ) type FetchInitialCredentialsLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewFetchInitialCredentialsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FetchInitialCredentialsLogic { return &FetchInitialCredentialsLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // FetchInitialCredentials 凭 CreateProduct 响应中的 credentialsTicket 一次性领取 appSecret 与初始 // adminPassword。Ticket 在 Redis 中以 initialCredentialsKeyPrefix 前缀保存,短 TTL(5 分钟), // 一次消费后立即删除;调用此接口的通常是 CreateProduct 的直接后继,因此要求超管身份——即便 ticket // 泄漏到日志,非超管也没法消费,进一步压缩攻击面(审计 M-4)。 func (l *FetchInitialCredentialsLogic) FetchInitialCredentials(req *types.FetchInitialCredentialsReq) (*types.FetchInitialCredentialsResp, error) { if err := authHelper.RequireSuperAdmin(l.ctx); err != nil { return nil, err } if req == nil || req.Ticket == "" { return nil, response.ErrBadRequest("ticket 不能为空") } key := initialCredentialsKeyPrefix + req.Ticket // GetDelCtx 语义 = GET + DEL 原子化,确保一次性消费:即便并发两次请求同一 ticket,只有一次 // 能拿到非空返回,另一次被识别为已消费/已过期。 val, err := l.svcCtx.Redis.GetDelCtx(l.ctx, key) if err != nil { logx.WithContext(l.ctx).Errorf("FetchInitialCredentials: redis getdel failed: %v", err) return nil, response.NewCodeError(503, "凭证服务暂时不可用,请稍后重试") } if val == "" { return nil, response.ErrBadRequest("凭证票据无效或已过期") } var payload initialCredentialsPayload if err := json.Unmarshal([]byte(val), &payload); err != nil { logx.WithContext(l.ctx).Errorf("FetchInitialCredentials: unmarshal payload failed: %v", err) return nil, response.NewCodeError(500, "凭证数据异常,请联系管理员") } return &types.FetchInitialCredentialsResp{ AppKey: payload.AppKey, AppSecret: payload.AppSecret, AdminUser: payload.AdminUser, AdminPassword: payload.AdminPassword, }, nil }