This commit is contained in:
2025-07-28 01:46:39 +08:00
parent b03129667a
commit 357639462a
219 changed files with 21634 additions and 8138 deletions

View File

@@ -0,0 +1,406 @@
package api
import (
"context"
"errors"
"tyapi-server/internal/application/api/commands"
"tyapi-server/internal/application/api/dto"
"tyapi-server/internal/config"
entities "tyapi-server/internal/domains/api/entities"
"tyapi-server/internal/domains/api/repositories"
"tyapi-server/internal/domains/api/services"
"tyapi-server/internal/domains/api/services/processors"
finance_services "tyapi-server/internal/domains/finance/services"
product_services "tyapi-server/internal/domains/product/services"
"tyapi-server/internal/shared/crypto"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
"go.uber.org/zap"
)
type ApiApplicationService interface {
CallApi(ctx context.Context, cmd *commands.ApiCallCommand) (string, string, error)
// 获取用户API密钥
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
// 用户白名单管理
GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error)
AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
// 获取用户API调用记录
GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error)
}
type ApiApplicationServiceImpl struct {
apiCallService services.ApiCallAggregateService
apiUserService services.ApiUserAggregateService
apiRequestService *services.ApiRequestService
apiCallRepository repositories.ApiCallRepository
walletService finance_services.WalletAggregateService
productManagementService *product_services.ProductManagementService
productSubscriptionService *product_services.ProductSubscriptionService
txManager *database.TransactionManager
config *config.Config
logger *zap.Logger
}
func NewApiApplicationService(apiCallService services.ApiCallAggregateService, apiUserService services.ApiUserAggregateService, apiRequestService *services.ApiRequestService, apiCallRepository repositories.ApiCallRepository, walletService finance_services.WalletAggregateService, productManagementService *product_services.ProductManagementService, productSubscriptionService *product_services.ProductSubscriptionService, txManager *database.TransactionManager, config *config.Config, logger *zap.Logger) ApiApplicationService {
return &ApiApplicationServiceImpl{apiCallService: apiCallService, apiUserService: apiUserService, apiRequestService: apiRequestService, apiCallRepository: apiCallRepository, walletService: walletService, productManagementService: productManagementService, productSubscriptionService: productSubscriptionService, txManager: txManager, config: config, logger: logger}
}
// CallApi 应用服务层统一入口
func (s *ApiApplicationServiceImpl) CallApi(ctx context.Context, cmd *commands.ApiCallCommand) (string, string, error) {
// 在事务外创建ApiCall
apiCall, err := s.apiCallService.CreateApiCall(cmd.AccessId, cmd.Data, cmd.ClientIP)
if err != nil {
s.logger.Error("创建ApiCall失败", zap.Error(err))
return "", "", ErrSystem
}
transactionId := apiCall.TransactionId
// 先保存初始状态
err = s.apiCallService.SaveApiCall(ctx, apiCall)
if err != nil {
s.logger.Error("保存ApiCall初始状态失败", zap.Error(err))
return "", "", ErrSystem
}
var encryptedResponse string
var businessError error
// 在事务中执行业务逻辑
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 1. 查ApiUser
apiUser, err := s.apiUserService.LoadApiUserByAccessId(txCtx, cmd.AccessId)
if err != nil {
s.logger.Error("查ApiUser失败", zap.Error(err))
businessError = ErrInvalidAccessId
return ErrInvalidAccessId
}
// 加入UserId
apiCall.UserId = &apiUser.UserId
if apiUser.IsFrozen() {
s.logger.Error("账户已冻结", zap.String("userId", apiUser.UserId))
businessError = ErrFrozenAccount
return ErrFrozenAccount
}
// 在开发环境下跳过IP白名单校验
if s.config.App.IsDevelopment() {
s.logger.Info("开发环境跳过IP白名单校验", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
} else {
if !apiUser.IsWhiteListed(cmd.ClientIP) {
s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
businessError = ErrInvalidIP
return ErrInvalidIP
}
}
// 2. 查钱包
wallet, err := s.walletService.LoadWalletByUserId(txCtx, apiUser.UserId)
if err != nil {
s.logger.Error("查钱包失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
}
if wallet.IsArrears() {
s.logger.Error("账户已欠费", zap.String("userId", apiUser.UserId))
businessError = ErrArrears
return ErrArrears
}
// 3. 查产品
product, err := s.productManagementService.GetProductByCode(txCtx, cmd.ApiName)
if err != nil {
s.logger.Error("查产品失败", zap.Error(err))
businessError = ErrProductNotFound
return ErrProductNotFound
}
// 4. 查订阅
subscription, err := s.productSubscriptionService.GetUserSubscribedProduct(txCtx, apiUser.UserId, product.ID)
if err != nil {
s.logger.Error("查订阅失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
}
if subscription == nil {
s.logger.Error("用户未订阅该产品", zap.String("userId", apiUser.UserId), zap.String("productId", product.ID))
businessError = ErrNotSubscribed
return ErrNotSubscribed
}
apiCall.ProductId = &product.ID
if !product.IsValid() {
s.logger.Error("产品已停用", zap.String("productId", product.ID))
businessError = ErrProductDisabled
return ErrProductDisabled
}
// 5. 解密参数
requestParams, err := crypto.AesDecrypt(cmd.Data, apiUser.SecretKey)
if err != nil {
s.logger.Error("解密参数失败", zap.Error(err))
businessError = ErrDecryptFail
return ErrDecryptFail
}
// 6. 调用API
response, err := s.apiRequestService.PreprocessRequestApi(txCtx, cmd.ApiName, requestParams, &cmd.Options)
if err != nil {
if errors.Is(err, processors.ErrDatasource) {
s.logger.Error("调用API失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
} else if errors.Is(err, processors.ErrInvalidParam) {
s.logger.Error("调用API失败", zap.Error(err))
businessError = ErrInvalidParam
return ErrInvalidParam
} else if errors.Is(err, processors.ErrNotFound) {
s.logger.Error("调用API失败", zap.Error(err))
businessError = ErrQueryEmpty
return ErrQueryEmpty
} else {
s.logger.Error("调用API失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
}
}
// 7. 加密响应
encryptedResponse, err = crypto.AesEncrypt(response, apiUser.SecretKey)
if err != nil {
s.logger.Error("加密响应失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
}
apiCall.ResponseData = &encryptedResponse
// 8. 更新订阅使用次数
subscription.IncrementAPIUsage(1)
err = s.productSubscriptionService.SaveSubscription(txCtx, subscription)
if err != nil {
s.logger.Error("保存订阅失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
}
// 9. 扣钱
err = s.walletService.Deduct(txCtx, apiUser.UserId, subscription.Price, apiCall.ID, transactionId, product.ID)
if err != nil {
s.logger.Error("扣钱失败", zap.Error(err))
businessError = ErrSystem
return ErrSystem
}
apiCall.Cost = &subscription.Price
// 10. 标记成功
apiCall.MarkSuccess(encryptedResponse, subscription.Price)
return nil
})
// 根据事务结果更新ApiCall状态
if err != nil {
// 事务失败根据错误类型标记ApiCall
if businessError != nil {
// 使用业务错误类型
switch businessError {
case ErrInvalidAccessId:
apiCall.MarkFailed(entities.ApiCallErrorInvalidAccess, err.Error())
case ErrFrozenAccount:
apiCall.MarkFailed(entities.ApiCallErrorFrozenAccount, "")
case ErrInvalidIP:
apiCall.MarkFailed(entities.ApiCallErrorInvalidIP, "")
case ErrArrears:
apiCall.MarkFailed(entities.ApiCallErrorArrears, "")
case ErrProductNotFound:
apiCall.MarkFailed(entities.ApiCallErrorProductNotFound, err.Error())
case ErrProductDisabled:
apiCall.MarkFailed(entities.ApiCallErrorProductDisabled, "")
case ErrNotSubscribed:
apiCall.MarkFailed(entities.ApiCallErrorNotSubscribed, "")
case ErrDecryptFail:
apiCall.MarkFailed(entities.ApiCallErrorDecryptFail, err.Error())
case ErrInvalidParam:
apiCall.MarkFailed(entities.ApiCallErrorInvalidParam, err.Error())
case ErrQueryEmpty:
apiCall.MarkFailed(entities.ApiCallErrorQueryEmpty, "")
default:
apiCall.MarkFailed(entities.ApiCallErrorSystem, err.Error())
}
} else {
// 系统错误
apiCall.MarkFailed(entities.ApiCallErrorSystem, err.Error())
}
}
// 保存最终状态
err = s.apiCallService.SaveApiCall(ctx, apiCall)
if err != nil {
s.logger.Error("保存ApiCall最终状态失败", zap.Error(err))
// 即使保存失败,也返回业务结果
}
if businessError != nil {
return transactionId, "", businessError
}
return transactionId, encryptedResponse, nil
}
// GetUserApiKeys 获取用户API密钥
func (s *ApiApplicationServiceImpl) GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error) {
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
if err != nil {
return nil, err
}
return &dto.ApiKeysResponse{
ID: apiUser.ID,
UserID: apiUser.UserId,
AccessID: apiUser.AccessId,
SecretKey: apiUser.SecretKey,
Status: apiUser.Status,
CreatedAt: apiUser.CreatedAt,
UpdatedAt: apiUser.UpdatedAt,
}, nil
}
// GetUserWhiteList 获取用户白名单列表
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error) {
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
if err != nil {
return nil, err
}
// 确保WhiteList不为nil
if apiUser.WhiteList == nil {
apiUser.WhiteList = []string{}
}
// 将白名单字符串数组转换为响应格式
var items []dto.WhiteListResponse
for _, ip := range apiUser.WhiteList {
items = append(items, dto.WhiteListResponse{
ID: apiUser.ID, // 使用API用户ID作为标识
UserID: apiUser.UserId,
IPAddress: ip,
CreatedAt: apiUser.CreatedAt, // 使用API用户创建时间
})
}
return &dto.WhiteListListResponse{
Items: items,
Total: len(items),
}, nil
}
// AddWhiteListIP 添加白名单IP
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
if err != nil {
return err
}
// 确保WhiteList不为nil
if apiUser.WhiteList == nil {
apiUser.WhiteList = []string{}
}
// 使用实体的领域方法添加IP到白名单
err = apiUser.AddToWhiteList(ipAddress)
if err != nil {
return err
}
// 保存更新
err = s.apiUserService.SaveApiUser(ctx, apiUser)
if err != nil {
return err
}
return nil
}
// DeleteWhiteListIP 删除白名单IP
func (s *ApiApplicationServiceImpl) DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
if err != nil {
return err
}
// 确保WhiteList不为nil
if apiUser.WhiteList == nil {
apiUser.WhiteList = []string{}
}
// 使用实体的领域方法删除IP
err = apiUser.RemoveFromWhiteList(ipAddress)
if err != nil {
return err
}
// 保存更新
err = s.apiUserService.SaveApiUser(ctx, apiUser)
if err != nil {
return err
}
return nil
}
// GetUserApiCalls 获取用户API调用记录
func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error) {
// 查询API调用记录包含产品名称
productNameMap, calls, total, err := s.apiCallRepository.ListByUserIdWithFiltersAndProductName(ctx, userID, filters, options)
if err != nil {
s.logger.Error("查询API调用记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 转换为响应DTO
var items []dto.ApiCallRecordResponse
for _, call := range calls {
item := dto.ApiCallRecordResponse{
ID: call.ID,
AccessId: call.AccessId,
UserId: *call.UserId,
TransactionId: call.TransactionId,
ClientIp: call.ClientIp,
Status: call.Status,
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: call.UpdatedAt.Format("2006-01-02 15:04:05"),
}
// 处理可选字段
if call.ProductId != nil {
item.ProductId = call.ProductId
}
// 从映射中获取产品名称
if productName, exists := productNameMap[call.ID]; exists {
item.ProductName = &productName
}
if call.EndAt != nil {
endAt := call.EndAt.Format("2006-01-02 15:04:05")
item.EndAt = &endAt
}
if call.Cost != nil {
cost := call.Cost.String()
item.Cost = &cost
}
if call.ErrorType != nil {
item.ErrorType = call.ErrorType
}
if call.ErrorMsg != nil {
item.ErrorMsg = call.ErrorMsg
}
items = append(items, item)
}
return &dto.ApiCallListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}

View File

@@ -0,0 +1,18 @@
package commands
type ApiCallCommand struct {
ClientIP string `json:"-"`
AccessId string `json:"-"`
ApiName string `json:"-"`
Data string `json:"data" binding:"required"`
Options ApiCallOptions `json:"options,omitempty"`
}
type ApiCallOptions struct {
Json bool `json:"json,omitempty"` // 是否返回JSON格式
}
// EncryptCommand 加密命令
type EncryptCommand struct {
Data map[string]interface{} `json:"data" binding:"required"`
}

View File

@@ -0,0 +1,90 @@
package dto
import "time"
// ApiCallResponse API调用响应结构
type ApiCallResponse struct {
Code int `json:"code"`
Message string `json:"message"`
TransactionId string `json:"transaction_id"`
Data string `json:"data,omitempty"`
}
// ApiKeysResponse API密钥响应结构
type ApiKeysResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
AccessID string `json:"access_id"`
SecretKey string `json:"secret_key"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 白名单相关DTO
type WhiteListResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
IPAddress string `json:"ip_address"`
CreatedAt time.Time `json:"created_at"`
}
type WhiteListRequest struct {
IPAddress string `json:"ip_address" binding:"required,ip"`
}
type WhiteListListResponse struct {
Items []WhiteListResponse `json:"items"`
Total int `json:"total"`
}
// API调用记录相关DTO
type ApiCallRecordResponse struct {
ID string `json:"id"`
AccessId string `json:"access_id"`
UserId string `json:"user_id"`
ProductId *string `json:"product_id,omitempty"`
ProductName *string `json:"product_name,omitempty"`
TransactionId string `json:"transaction_id"`
ClientIp string `json:"client_ip"`
Status string `json:"status"`
StartAt string `json:"start_at"`
EndAt *string `json:"end_at,omitempty"`
Cost *string `json:"cost,omitempty"`
ErrorType *string `json:"error_type,omitempty"`
ErrorMsg *string `json:"error_msg,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ApiCallListResponse struct {
Items []ApiCallRecordResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// EncryptResponse 加密响应
type EncryptResponse struct {
EncryptedData string `json:"encrypted_data"`
}
// NewSuccessResponse 创建成功响应
func NewSuccessResponse(transactionId, data string) *ApiCallResponse {
return &ApiCallResponse{
Code: 0,
Message: "业务成功",
TransactionId: transactionId,
Data: data,
}
}
// NewErrorResponse 创建错误响应
func NewErrorResponse(code int, message, transactionId string) *ApiCallResponse {
return &ApiCallResponse{
Code: code,
Message: message,
TransactionId: transactionId,
Data: "",
}
}

View File

@@ -0,0 +1,47 @@
package api
import "errors"
// API调用相关错误类型
var (
ErrQueryEmpty = errors.New("查询为空")
ErrSystem = errors.New("接口异常")
ErrDecryptFail = errors.New("解密失败")
ErrRequestParam = errors.New("请求参数结构不正确")
ErrInvalidParam = errors.New("参数校验不正确")
ErrInvalidIP = errors.New("未经授权的IP")
ErrMissingAccessId = errors.New("缺少Access-Id")
ErrInvalidAccessId = errors.New("未经授权的AccessId")
ErrFrozenAccount = errors.New("账户已冻结")
ErrArrears = errors.New("账户余额不足,无法请求")
ErrProductNotFound = errors.New("产品不存在")
ErrProductDisabled = errors.New("产品已停用")
ErrNotSubscribed = errors.New("未订阅此产品")
ErrBusiness = errors.New("业务失败")
)
// 错误码映射 - 严格按照用户要求
var ErrorCodeMap = map[error]int{
ErrQueryEmpty: 1000,
ErrSystem: 1001,
ErrDecryptFail: 1002,
ErrRequestParam: 1003,
ErrInvalidParam: 1003,
ErrInvalidIP: 1004,
ErrMissingAccessId: 1005,
ErrInvalidAccessId: 1006,
ErrFrozenAccount: 1007,
ErrArrears: 1007,
ErrProductNotFound: 1008,
ErrProductDisabled: 1008,
ErrNotSubscribed: 1008,
ErrBusiness: 2001,
}
// GetErrorCode 获取错误对应的错误码
func GetErrorCode(err error) int {
if code, exists := ErrorCodeMap[err]; exists {
return code
}
return 1001 // 默认返回接口异常
}

View File

@@ -12,46 +12,25 @@ import (
// 负责用例协调,提供精简的应用层接口
type CertificationApplicationService interface {
// ================ 用户操作用例 ================
// 创建认证申请
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
// 提交企业信息
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.CertificationResponse, error)
// 确认状态
ConfirmAuth(ctx context.Context, cmd *queries.ConfirmAuthCommand) (*responses.ConfirmAuthResponse, error)
// 确认签署
ConfirmSign(ctx context.Context, cmd *queries.ConfirmSignCommand) (*responses.ConfirmSignResponse, error)
// 申请合同签署
ApplyContract(ctx context.Context, cmd *commands.ApplyContractCommand) (*responses.ContractSignUrlResponse, error)
// 重试失败操作
RetryOperation(ctx context.Context, cmd *commands.RetryOperationCommand) (*responses.CertificationResponse, error)
// ================ 查询用例 ================
// 获取认证详情
GetCertification(ctx context.Context, query *queries.GetCertificationQuery) (*responses.CertificationResponse, error)
// 获取用户认证列表
GetUserCertifications(ctx context.Context, query *queries.GetUserCertificationsQuery) (*responses.CertificationListResponse, error)
// 获取认证列表(管理员)
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
// 搜索认证
SearchCertifications(ctx context.Context, query *queries.SearchCertificationsQuery) (*responses.CertificationListResponse, error)
// 获取认证统计
GetCertificationStatistics(ctx context.Context, query *queries.GetCertificationStatisticsQuery) (*responses.CertificationStatisticsResponse, error)
// ================ e签宝回调处理 ================
// 处理e签宝回调
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) (*responses.CallbackResponse, error)
// ================ 管理员操作 ================
// 手动状态转换(管理员)
ForceTransitionStatus(ctx context.Context, cmd *commands.ForceTransitionStatusCommand) (*responses.CertificationResponse, error)
// 获取系统监控数据
GetSystemMonitoring(ctx context.Context, query *queries.GetSystemMonitoringQuery) (*responses.SystemMonitoringResponse, error)
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) error
}

View File

@@ -1,7 +1,6 @@
package commands
import (
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
)
@@ -12,7 +11,6 @@ type CreateCertificationCommand struct {
// ApplyContractCommand 申请合同命令
type ApplyContractCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"user_id" validate:"required"`
}
@@ -26,13 +24,54 @@ type RetryOperationCommand struct {
// EsignCallbackCommand e签宝回调命令
type EsignCallbackCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
CallbackType string `json:"callback_type" validate:"required,oneof=auth_result sign_result flow_status"`
RawData string `json:"raw_data" validate:"required"`
Headers map[string]string `json:"headers,omitempty"`
QueryParams map[string]string `json:"query_params,omitempty"`
Data *EsignCallbackData `json:"data"`
Headers map[string]string `json:"headers"`
QueryParams map[string]string `json:"query_params"`
}
// EsignCallbackData e签宝回调数据结构
type EsignCallbackData struct {
Action string `json:"action"`
Timestamp int64 `json:"timestamp"`
AuthFlowId string `json:"authFlowId,omitempty"`
SignFlowId string `json:"signFlowId,omitempty"`
CustomBizNum string `json:"customBizNum,omitempty"`
SignOrder int `json:"signOrder,omitempty"`
OperateTime int64 `json:"operateTime,omitempty"`
SignResult int `json:"signResult,omitempty"`
ResultDescription string `json:"resultDescription,omitempty"`
AuthType string `json:"authType,omitempty"`
SignFlowStatus string `json:"signFlowStatus,omitempty"`
Operator *EsignOperator `json:"operator,omitempty"`
PsnInfo *EsignPsnInfo `json:"psnInfo,omitempty"`
Organization *EsignOrganization `json:"organization,omitempty"`
}
// EsignOperator 签署人信息
type EsignOperator struct {
PsnId string `json:"psnId"`
PsnAccount *EsignPsnAccount `json:"psnAccount"`
}
// EsignPsnInfo 个人认证信息
type EsignPsnInfo struct {
PsnId string `json:"psnId"`
PsnAccount *EsignPsnAccount `json:"psnAccount"`
}
// EsignPsnAccount 个人账户信息
type EsignPsnAccount struct {
AccountMobile string `json:"accountMobile"`
AccountEmail string `json:"accountEmail"`
}
// EsignOrganization 企业信息
type EsignOrganization struct {
OrgName string `json:"orgName"`
// 可以根据需要添加更多企业信息字段
}
// ForceTransitionStatusCommand 强制状态转换命令(管理员)
type ForceTransitionStatusCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
@@ -44,7 +83,13 @@ type ForceTransitionStatusCommand struct {
// SubmitEnterpriseInfoCommand 提交企业信息命令
type SubmitEnterpriseInfoCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"-" validate:"required"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info" validate:"required"`
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
CompanyName string `json:"company_name" binding:"required,min=2,max=100" comment:"企业名称,如:北京科技有限公司"`
UnifiedSocialCode string `json:"unified_social_code" binding:"required,social_credit_code" comment:"统一社会信用代码18位企业唯一标识91110000123456789X"`
LegalPersonName string `json:"legal_person_name" binding:"required,min=2,max=20" comment:"法定代表人姓名,如:张三"`
LegalPersonID string `json:"legal_person_id" binding:"required,id_card" comment:"法定代表人身份证号码18位110101199001011234"`
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号11位13800138000"`
EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"`
EnterpriseEmail string `json:"enterprise_email" binding:"required,enterprise_email" comment:"企业邮箱info@example.com"`
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
}

View File

@@ -9,18 +9,27 @@ import (
// GetCertificationQuery 获取认证详情查询
type GetCertificationQuery struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"user_id,omitempty"` // 用于权限验证
UserID string `json:"user_id,omitempty"` // 用于权限验证
}
// ConfirmAuthCommand 确认认证状态命令
type ConfirmAuthCommand struct {
UserID string `json:"-"`
}
// ConfirmSignCommand 确认签署状态命令
type ConfirmSignCommand struct {
UserID string `json:"-"`
}
// GetUserCertificationsQuery 获取用户认证列表查询
type GetUserCertificationsQuery struct {
UserID string `json:"user_id" validate:"required"`
Status enums.CertificationStatus `json:"status,omitempty"`
IncludeCompleted bool `json:"include_completed,omitempty"`
IncludeFailed bool `json:"include_failed,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id" validate:"required"`
Status enums.CertificationStatus `json:"status,omitempty"`
IncludeCompleted bool `json:"include_completed,omitempty"`
IncludeFailed bool `json:"include_failed,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// ToDomainQuery 转换为领域查询对象
@@ -39,19 +48,19 @@ func (q *GetUserCertificationsQuery) ToDomainQuery() *domainQueries.UserCertific
// ListCertificationsQuery 认证列表查询(管理员)
type ListCertificationsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
UserID string `json:"user_id,omitempty"`
Status enums.CertificationStatus `json:"status,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
CreatedAfter *time.Time `json:"created_after,omitempty"`
CreatedBefore *time.Time `json:"created_before,omitempty"`
CompanyName string `json:"company_name,omitempty"`
LegalPersonName string `json:"legal_person_name,omitempty"`
SearchKeyword string `json:"search_keyword,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
UserID string `json:"user_id,omitempty"`
Status enums.CertificationStatus `json:"status,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
CreatedAfter *time.Time `json:"created_after,omitempty"`
CreatedBefore *time.Time `json:"created_before,omitempty"`
CompanyName string `json:"company_name,omitempty"`
LegalPersonName string `json:"legal_person_name,omitempty"`
SearchKeyword string `json:"search_keyword,omitempty"`
}
// ToDomainQuery 转换为领域查询对象
@@ -77,15 +86,15 @@ func (q *ListCertificationsQuery) ToDomainQuery() *domainQueries.ListCertificati
// SearchCertificationsQuery 搜索认证查询
type SearchCertificationsQuery struct {
Keyword string `json:"keyword" validate:"required,min=2"`
SearchFields []string `json:"search_fields,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
UserID string `json:"user_id,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
ExactMatch bool `json:"exact_match,omitempty"`
Keyword string `json:"keyword" validate:"required,min=2"`
SearchFields []string `json:"search_fields,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
UserID string `json:"user_id,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
ExactMatch bool `json:"exact_match,omitempty"`
}
// ToDomainQuery 转换为领域查询对象
@@ -107,15 +116,15 @@ func (q *SearchCertificationsQuery) ToDomainQuery() *domainQueries.SearchCertifi
// GetCertificationStatisticsQuery 认证统计查询
type GetCertificationStatisticsQuery struct {
StartDate time.Time `json:"start_date" validate:"required"`
EndDate time.Time `json:"end_date" validate:"required"`
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
GroupBy []string `json:"group_by,omitempty"`
UserIDs []string `json:"user_ids,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
StartDate time.Time `json:"start_date" validate:"required"`
EndDate time.Time `json:"end_date" validate:"required"`
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
GroupBy []string `json:"group_by,omitempty"`
UserIDs []string `json:"user_ids,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
}
// ToDomainQuery 转换为领域查询对象
@@ -135,7 +144,7 @@ func (q *GetCertificationStatisticsQuery) ToDomainQuery() *domainQueries.Certifi
// GetSystemMonitoringQuery 系统监控查询
type GetSystemMonitoringQuery struct {
TimeRange string `json:"time_range" validate:"oneof=1h 6h 24h 7d 30d"`
TimeRange string `json:"time_range" validate:"oneof=1h 6h 24h 7d 30d"`
Metrics []string `json:"metrics,omitempty"` // 指定要获取的指标类型
}
@@ -175,7 +184,7 @@ func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool {
if len(q.Metrics) == 0 {
return true // 如果没有指定,包含所有指标
}
for _, m := range q.Metrics {
if m == metric {
return true

View File

@@ -1,55 +1,65 @@
package responses
import (
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/services/state_machine"
)
// CertificationResponse 认证响应
type CertificationResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StatusName string `json:"status_name"`
Progress int `json:"progress"`
ID string `json:"id"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StatusName string `json:"status_name"`
Progress int `json:"progress"`
// 企业信息
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
// 合同信息
ContractInfo *value_objects.ContractInfo `json:"contract_info,omitempty"`
ContractInfo *value_objects.ContractInfo `json:"contract_info,omitempty"`
// 时间戳
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
// 业务状态
IsCompleted bool `json:"is_completed"`
IsFailed bool `json:"is_failed"`
IsUserActionRequired bool `json:"is_user_action_required"`
IsCompleted bool `json:"is_completed"`
IsFailed bool `json:"is_failed"`
IsUserActionRequired bool `json:"is_user_action_required"`
// 失败信息
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
FailureReasonName string `json:"failure_reason_name,omitempty"`
FailureMessage string `json:"failure_message,omitempty"`
CanRetry bool `json:"can_retry,omitempty"`
RetryCount int `json:"retry_count,omitempty"`
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
FailureReasonName string `json:"failure_reason_name,omitempty"`
FailureMessage string `json:"failure_message,omitempty"`
CanRetry bool `json:"can_retry,omitempty"`
RetryCount int `json:"retry_count,omitempty"`
// 用户操作提示
NextAction string `json:"next_action,omitempty"`
AvailableActions []string `json:"available_actions,omitempty"`
NextAction string `json:"next_action,omitempty"`
AvailableActions []string `json:"available_actions,omitempty"`
// 元数据
Metadata map[string]interface{} `json:"metadata,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// ConfirmAuthResponse 确认认证状态响应
type ConfirmAuthResponse struct {
Status enums.CertificationStatus `json:"status"`
Reason string `json:"reason"`
}
// ConfirmSignResponse 确认签署状态响应
type ConfirmSignResponse struct {
Status enums.CertificationStatus `json:"status"`
Reason string `json:"reason"`
}
// CertificationListResponse 认证列表响应
@@ -63,63 +73,42 @@ type CertificationListResponse struct {
// ContractSignUrlResponse 合同签署URL响应
type ContractSignUrlResponse struct {
CertificationID string `json:"certification_id"`
ContractSignURL string `json:"contract_sign_url"`
ContractURL string `json:"contract_url,omitempty"`
ExpireAt *time.Time `json:"expire_at,omitempty"`
NextAction string `json:"next_action"`
Message string `json:"message"`
CertificationID string `json:"certification_id"`
ContractSignURL string `json:"contract_sign_url"`
ContractURL string `json:"contract_url,omitempty"`
ExpireAt *time.Time `json:"expire_at,omitempty"`
NextAction string `json:"next_action"`
Message string `json:"message"`
}
// CallbackResponse 回调响应
type CallbackResponse struct {
Success bool `json:"success"`
CertificationID string `json:"certification_id"`
CallbackType string `json:"callback_type"`
ProcessedAt time.Time `json:"processed_at"`
OldStatus enums.CertificationStatus `json:"old_status,omitempty"`
NewStatus enums.CertificationStatus `json:"new_status,omitempty"`
Message string `json:"message"`
StateTransition *state_machine.StateTransitionResult `json:"state_transition,omitempty"`
}
// CertificationStatisticsResponse 认证统计响应
type CertificationStatisticsResponse struct {
Period string `json:"period"`
TimeRange string `json:"time_range"`
Statistics *repositories.CertificationStatistics `json:"statistics"`
ProgressStats *repositories.CertificationProgressStats `json:"progress_stats,omitempty"`
Charts map[string]interface{} `json:"charts,omitempty"`
GeneratedAt time.Time `json:"generated_at"`
}
// SystemMonitoringResponse 系统监控响应
type SystemMonitoringResponse struct {
TimeRange string `json:"time_range"`
Metrics map[string]interface{} `json:"metrics"`
Alerts []SystemAlert `json:"alerts,omitempty"`
SystemHealth SystemHealthStatus `json:"system_health"`
LastUpdatedAt time.Time `json:"last_updated_at"`
TimeRange string `json:"time_range"`
Metrics map[string]interface{} `json:"metrics"`
Alerts []SystemAlert `json:"alerts,omitempty"`
SystemHealth SystemHealthStatus `json:"system_health"`
LastUpdatedAt time.Time `json:"last_updated_at"`
}
// SystemAlert 系统警告
type SystemAlert struct {
Level string `json:"level"` // info, warning, error, critical
Type string `json:"type"` // 警告类型
Message string `json:"message"` // 警告消息
Metric string `json:"metric"` // 相关指标
Value interface{} `json:"value"` // 当前值
Threshold interface{} `json:"threshold"` // 阈值
CreatedAt time.Time `json:"created_at"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
Level string `json:"level"` // info, warning, error, critical
Type string `json:"type"` // 警告类型
Message string `json:"message"` // 警告消息
Metric string `json:"metric"` // 相关指标
Value interface{} `json:"value"` // 当前值
Threshold interface{} `json:"threshold"` // 阈值
CreatedAt time.Time `json:"created_at"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// SystemHealthStatus 系统健康状态
type SystemHealthStatus struct {
Overall string `json:"overall"` // healthy, warning, critical
Components map[string]string `json:"components"` // 各组件状态
LastCheck time.Time `json:"last_check"`
Details map[string]interface{} `json:"details,omitempty"`
Overall string `json:"overall"` // healthy, warning, critical
Components map[string]string `json:"components"` // 各组件状态
LastCheck time.Time `json:"last_check"`
Details map[string]interface{} `json:"details,omitempty"`
}
// ================ 响应构建辅助方法 ================
@@ -130,7 +119,7 @@ func NewCertificationListResponse(items []*CertificationResponse, total int64, p
if totalPages == 0 {
totalPages = 1
}
return &CertificationListResponse{
Items: items,
Total: total,
@@ -149,24 +138,14 @@ func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextActio
NextAction: nextAction,
Message: message,
}
// 设置过期时间默认24小时
expireAt := time.Now().Add(24 * time.Hour)
response.ExpireAt = &expireAt
return response
}
// NewCallbackResponse 创建回调响应
func NewCallbackResponse(success bool, certificationID, callbackType, message string) *CallbackResponse {
return &CallbackResponse{
Success: success,
CertificationID: certificationID,
CallbackType: callbackType,
ProcessedAt: time.Now(),
Message: message,
}
}
// NewSystemAlert 创建系统警告
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
@@ -182,30 +161,6 @@ func NewSystemAlert(level, alertType, message, metric string, value, threshold i
}
}
// IsSuccess 检查响应是否成功
func (r *CallbackResponse) IsSuccess() bool {
return r.Success
}
// HasStateTransition 检查是否有状态转换
func (r *CallbackResponse) HasStateTransition() bool {
return r.StateTransition != nil
}
// GetStatusChange 获取状态变更描述
func (r *CallbackResponse) GetStatusChange() string {
if !r.HasStateTransition() {
return ""
}
if r.OldStatus == r.NewStatus {
return "状态无变化"
}
return fmt.Sprintf("从 %s 转换为 %s",
enums.GetStatusName(r.OldStatus),
enums.GetStatusName(r.NewStatus))
}
// IsHealthy 检查系统是否健康
func (r *SystemMonitoringResponse) IsHealthy() bool {

View File

@@ -1,69 +1,31 @@
package commands
import (
"time"
"github.com/shopspring/decimal"
)
// CreateWalletCommand 创建钱包命令
type CreateWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
}
// UpdateWalletCommand 更新钱包命令
type UpdateWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Balance decimal.Decimal `json:"balance" binding:"omitempty"`
IsActive *bool `json:"is_active"`
// TransferRechargeCommand 对公转账充值命令
type TransferRechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount string `json:"amount" binding:"required"`
TransferOrderID string `json:"transfer_order_id" binding:"required" comment:"转账订单号"`
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
}
// RechargeWalletCommand 充值钱包命令
type RechargeWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
// GiftRechargeCommand 赠送充值命令
type GiftRechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount string `json:"amount" binding:"required"`
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
}
// RechargeCommand 充值命令
type RechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
}
// WithdrawWalletCommand 提现钱包命令
type WithdrawWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
}
// WithdrawCommand 提现命令
type WithdrawCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
}
// CreateUserSecretsCommand 创建用户密钥命令
type CreateUserSecretsCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
}
// RegenerateAccessKeyCommand 重新生成访问密钥命令
type RegenerateAccessKeyCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
}
// DeactivateUserSecretsCommand 停用用户密钥命令
type DeactivateUserSecretsCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
}
// WalletTransactionCommand 钱包交易命令
type WalletTransactionCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
FromUserID string `json:"from_user_id" binding:"required,uuid"`
ToUserID string `json:"to_user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
Notes string `json:"notes" binding:"omitempty,max=200"`
// CreateAlipayRechargeCommand 创建支付宝充值订单命令
type CreateAlipayRechargeCommand struct {
UserID string `json:"-"` // 用户ID从token获取
Amount string `json:"amount" binding:"required"` // 充值金额
Subject string `json:"-"` // 订单标题
Platform string `json:"platform" binding:"required,oneof=app h5 pc"` // 支付平台app/h5/pc
}

View File

@@ -0,0 +1,25 @@
package responses
import (
"time"
"github.com/shopspring/decimal"
)
// AlipayOrderStatusResponse 支付宝订单状态响应
type AlipayOrderStatusResponse struct {
OutTradeNo string `json:"out_trade_no"` // 商户订单号
TradeNo *string `json:"trade_no"` // 支付宝交易号
Status string `json:"status"` // 订单状态
Amount decimal.Decimal `json:"amount"` // 订单金额
Subject string `json:"subject"` // 订单标题
Platform string `json:"platform"` // 支付平台
CreatedAt time.Time `json:"created_at"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
NotifyTime *time.Time `json:"notify_time"` // 异步通知时间
ReturnTime *time.Time `json:"return_time"` // 同步返回时间
ErrorCode *string `json:"error_code"` // 错误码
ErrorMessage *string `json:"error_message"` // 错误信息
IsProcessing bool `json:"is_processing"` // 是否处理中
CanRetry bool `json:"can_retry"` // 是否可以重试
}

View File

@@ -8,24 +8,21 @@ import (
// WalletResponse 钱包响应
type WalletResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
IsActive bool `json:"is_active"`
Balance decimal.Decimal `json:"balance"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
UserID string `json:"user_id"`
IsActive bool `json:"is_active"`
Balance decimal.Decimal `json:"balance"`
BalanceStatus string `json:"balance_status"` // normal, low, arrears
IsArrears bool `json:"is_arrears"` // 是否欠费
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TransactionResponse 交易响应
type TransactionResponse struct {
TransactionID string `json:"transaction_id"`
FromUserID string `json:"from_user_id"`
ToUserID string `json:"to_user_id"`
Amount decimal.Decimal `json:"amount"`
FromBalance decimal.Decimal `json:"from_balance"`
ToBalance decimal.Decimal `json:"to_balance"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
}
// UserSecretsResponse 用户密钥响应
@@ -49,3 +46,62 @@ type WalletStatsResponse struct {
TodayTransactions int64 `json:"today_transactions"`
TodayVolume decimal.Decimal `json:"today_volume"`
}
// RechargeRecordResponse 充值记录响应
type RechargeRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Amount decimal.Decimal `json:"amount"`
RechargeType string `json:"recharge_type"`
Status string `json:"status"`
AlipayOrderID string `json:"alipay_order_id,omitempty"`
TransferOrderID string `json:"transfer_order_id,omitempty"`
Notes string `json:"notes,omitempty"`
OperatorID string `json:"operator_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletTransactionResponse 钱包交易记录响应
type WalletTransactionResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
ApiCallID string `json:"api_call_id"`
TransactionID string `json:"transaction_id"`
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
Amount decimal.Decimal `json:"amount"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletTransactionListResponse 钱包交易记录列表响应
type WalletTransactionListResponse struct {
Items []WalletTransactionResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// RechargeRecordListResponse 充值记录列表响应
type RechargeRecordListResponse struct {
Items []RechargeRecordResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// AlipayRechargeOrderResponse 支付宝充值订单响应
type AlipayRechargeOrderResponse struct {
PayURL string `json:"pay_url"` // 支付链接
OutTradeNo string `json:"out_trade_no"` // 商户订单号
Amount decimal.Decimal `json:"amount"` // 充值金额
Platform string `json:"platform"` // 支付平台
Subject string `json:"subject"` // 订单标题
}
// RechargeConfigResponse 充值配置响应
type RechargeConfigResponse struct {
MinAmount string `json:"min_amount"` // 最低充值金额
MaxAmount string `json:"max_amount"` // 最高充值金额
}

View File

@@ -2,23 +2,36 @@ package finance
import (
"context"
"net/http"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
"tyapi-server/internal/shared/interfaces"
)
// FinanceApplicationService 财务应用服务接口
type FinanceApplicationService interface {
CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error)
GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error)
UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error
Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error)
Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error)
CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error)
GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error)
RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error)
DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error
WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error)
GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error)
CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error)
HandleAlipayCallback(ctx context.Context, r *http.Request) error
HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error)
GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error)
TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error)
GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error)
// 获取用户钱包交易记录
GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
// 获取用户充值记录
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
// 管理员获取充值记录
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
// 获取充值配置
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
}

View File

@@ -2,88 +2,649 @@ package finance
import (
"context"
"fmt"
"go.uber.org/zap"
"net/http"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
"tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/config"
finance_entities "tyapi-server/internal/domains/finance/entities"
finance_repositories "tyapi-server/internal/domains/finance/repositories"
finance_services "tyapi-server/internal/domains/finance/services"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/payment"
"github.com/shopspring/decimal"
"github.com/smartwalle/alipay/v3"
"go.uber.org/zap"
)
// FinanceApplicationServiceImpl 财务应用服务实现
type FinanceApplicationServiceImpl struct {
walletRepo repositories.WalletRepository
userSecretsRepo repositories.UserSecretsRepository
logger *zap.Logger
aliPayClient *payment.AliPayService
walletService finance_services.WalletAggregateService
rechargeRecordService finance_services.RechargeRecordService
walletTransactionRepository finance_repositories.WalletTransactionRepository
alipayOrderRepo finance_repositories.AlipayOrderRepository
txManager *database.TransactionManager
logger *zap.Logger
config *config.Config
}
// NewFinanceApplicationService 创建财务应用服务
func NewFinanceApplicationService(
walletRepo repositories.WalletRepository,
userSecretsRepo repositories.UserSecretsRepository,
aliPayClient *payment.AliPayService,
walletService finance_services.WalletAggregateService,
rechargeRecordService finance_services.RechargeRecordService,
walletTransactionRepository finance_repositories.WalletTransactionRepository,
alipayOrderRepo finance_repositories.AlipayOrderRepository,
txManager *database.TransactionManager,
logger *zap.Logger,
config *config.Config,
) FinanceApplicationService {
return &FinanceApplicationServiceImpl{
walletRepo: walletRepo,
userSecretsRepo: userSecretsRepo,
logger: logger,
aliPayClient: aliPayClient,
walletService: walletService,
rechargeRecordService: rechargeRecordService,
walletTransactionRepository: walletTransactionRepository,
alipayOrderRepo: alipayOrderRepo,
txManager: txManager,
logger: logger,
config: config,
}
}
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 调用钱包聚合服务创建钱包
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
if err != nil {
s.logger.Error("创建钱包失败", zap.Error(err))
return nil, err
}
return &responses.WalletResponse{
ID: wallet.ID,
UserID: wallet.UserID,
IsActive: wallet.IsActive,
Balance: wallet.Balance,
BalanceStatus: wallet.GetBalanceStatus(),
IsArrears: wallet.IsArrears(),
IsLowBalance: wallet.IsLowBalance(),
CreatedAt: wallet.CreatedAt,
UpdatedAt: wallet.UpdatedAt,
}, nil
}
func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 调用钱包聚合服务获取钱包信息
wallet, err := s.walletService.LoadWalletByUserId(ctx, query.UserID)
if err != nil {
s.logger.Error("获取钱包信息失败", zap.Error(err))
return nil, err
}
return &responses.WalletResponse{
ID: wallet.ID,
UserID: wallet.UserID,
IsActive: wallet.IsActive,
Balance: wallet.Balance,
BalanceStatus: wallet.GetBalanceStatus(),
IsArrears: wallet.IsArrears(),
IsLowBalance: wallet.IsLowBalance(),
CreatedAt: wallet.CreatedAt,
UpdatedAt: wallet.UpdatedAt,
}, nil
}
func (s *FinanceApplicationServiceImpl) UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error {
// ... implementation from old service
return fmt.Errorf("not implemented")
// CreateAlipayRechargeOrder 创建支付宝充值订单(完整流程编排)
func (s *FinanceApplicationServiceImpl) CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error) {
cmd.Subject = "天远数据API充值"
// 将字符串金额转换为 decimal.Decimal
amount, err := decimal.NewFromString(cmd.Amount)
if err != nil {
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
return nil, fmt.Errorf("金额格式错误: %w", err)
}
// 验证金额是否大于0
if amount.LessThanOrEqual(decimal.Zero) {
return nil, fmt.Errorf("充值金额必须大于0")
}
// 从配置中获取充值限制
minAmount, err := decimal.NewFromString(s.config.Recharge.MinAmount)
if err != nil {
s.logger.Error("配置中的最低充值金额格式错误", zap.String("min_amount", s.config.Recharge.MinAmount), zap.Error(err))
return nil, fmt.Errorf("系统配置错误: %w", err)
}
maxAmount, err := decimal.NewFromString(s.config.Recharge.MaxAmount)
if err != nil {
s.logger.Error("配置中的最高充值金额格式错误", zap.String("max_amount", s.config.Recharge.MaxAmount), zap.Error(err))
return nil, fmt.Errorf("系统配置错误: %w", err)
}
// 验证充值金额范围
if amount.LessThan(minAmount) {
return nil, fmt.Errorf("充值金额不能少于%s元", minAmount.String())
}
if amount.GreaterThan(maxAmount) {
return nil, fmt.Errorf("单次充值金额不能超过%s元", maxAmount.String())
}
// 1. 生成订单号
outTradeNo := s.aliPayClient.GenerateOutTradeNo()
var payUrl string
// 2. 进入事务,创建充值记录和支付宝订单本地记录
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
var err error
// 创建充值记录
rechargeRecord, err := s.rechargeRecordService.CreateAlipayRecharge(txCtx, cmd.UserID, amount, outTradeNo)
if err != nil {
s.logger.Error("创建支付宝充值记录失败", zap.Error(err))
return fmt.Errorf("创建支付宝充值记录失败: %w", err)
}
// 创建支付宝订单本地记录
err = s.rechargeRecordService.CreateAlipayOrder(txCtx, rechargeRecord.ID, outTradeNo, cmd.Subject, amount, cmd.Platform)
if err != nil {
s.logger.Error("创建支付宝订单记录失败", zap.Error(err))
return fmt.Errorf("创建支付宝订单记录失败: %w", err)
}
// 3. 创建支付宝订单调用支付宝API非事务内
payUrl, err = s.aliPayClient.CreateAlipayOrder(ctx, cmd.Platform, amount, cmd.Subject, outTradeNo)
if err != nil {
s.logger.Error("创建支付宝订单失败", zap.Error(err))
return fmt.Errorf("创建支付宝订单失败: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
s.logger.Info("支付宝充值订单创建成功",
zap.String("user_id", cmd.UserID),
zap.String("out_trade_no", outTradeNo),
zap.String("amount", amount.String()),
zap.String("platform", cmd.Platform),
)
return &responses.AlipayRechargeOrderResponse{
PayURL: payUrl,
OutTradeNo: outTradeNo,
Amount: amount,
Platform: cmd.Platform,
Subject: cmd.Subject,
}, nil
}
func (s *FinanceApplicationServiceImpl) Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// TransferRecharge 对公转账充值
func (s *FinanceApplicationServiceImpl) TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error) {
// 将字符串金额转换为 decimal.Decimal
amount, err := decimal.NewFromString(cmd.Amount)
if err != nil {
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
return nil, fmt.Errorf("金额格式错误: %w", err)
}
// 验证金额是否大于0
if amount.LessThanOrEqual(decimal.Zero) {
return nil, fmt.Errorf("充值金额必须大于0")
}
// 调用充值记录服务进行对公转账充值
rechargeRecord, err := s.rechargeRecordService.TransferRecharge(ctx, cmd.UserID, amount, cmd.TransferOrderID, cmd.Notes)
if err != nil {
s.logger.Error("对公转账充值失败", zap.Error(err))
return nil, err
}
transferOrderID := ""
if rechargeRecord.TransferOrderID != nil {
transferOrderID = *rechargeRecord.TransferOrderID
}
return &responses.RechargeRecordResponse{
ID: rechargeRecord.ID,
UserID: rechargeRecord.UserID,
Amount: rechargeRecord.Amount,
RechargeType: string(rechargeRecord.RechargeType),
Status: string(rechargeRecord.Status),
TransferOrderID: transferOrderID,
Notes: rechargeRecord.Notes,
CreatedAt: rechargeRecord.CreatedAt,
UpdatedAt: rechargeRecord.UpdatedAt,
}, nil
}
func (s *FinanceApplicationServiceImpl) Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// GiftRecharge 赠送充值
func (s *FinanceApplicationServiceImpl) GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error) {
// 将字符串金额转换为 decimal.Decimal
amount, err := decimal.NewFromString(cmd.Amount)
if err != nil {
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
return nil, fmt.Errorf("金额格式错误: %w", err)
}
// 验证金额是否大于0
if amount.LessThanOrEqual(decimal.Zero) {
return nil, fmt.Errorf("充值金额必须大于0")
}
// 获取当前操作员ID这里假设从上下文中获取实际可能需要从认证中间件获取
operatorID := "system" // 临时使用,实际应该从认证上下文获取
// 调用充值记录服务进行赠送充值
rechargeRecord, err := s.rechargeRecordService.GiftRecharge(ctx, cmd.UserID, amount, operatorID, cmd.Notes)
if err != nil {
s.logger.Error("赠送充值失败", zap.Error(err))
return nil, err
}
return &responses.RechargeRecordResponse{
ID: rechargeRecord.ID,
UserID: rechargeRecord.UserID,
Amount: rechargeRecord.Amount,
RechargeType: string(rechargeRecord.RechargeType),
Status: string(rechargeRecord.Status),
OperatorID: "system", // 临时使用,实际应该从认证上下文获取
Notes: rechargeRecord.Notes,
CreatedAt: rechargeRecord.CreatedAt,
UpdatedAt: rechargeRecord.UpdatedAt,
}, nil
}
func (s *FinanceApplicationServiceImpl) CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// GetUserWalletTransactions 获取用户钱包交易记录
func (s *FinanceApplicationServiceImpl) GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error) {
// 查询钱包交易记录(包含产品名称)
productNameMap, transactions, total, err := s.walletTransactionRepository.ListByUserIdWithFiltersAndProductName(ctx, userID, filters, options)
if err != nil {
s.logger.Error("查询钱包交易记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 转换为响应DTO
var items []responses.WalletTransactionResponse
for _, transaction := range transactions {
item := responses.WalletTransactionResponse{
ID: transaction.ID,
UserID: transaction.UserID,
ApiCallID: transaction.ApiCallID,
TransactionID: transaction.TransactionID,
ProductID: transaction.ProductID,
ProductName: productNameMap[transaction.ProductID], // 从映射中获取产品名称
Amount: transaction.Amount,
CreatedAt: transaction.CreatedAt,
UpdatedAt: transaction.UpdatedAt,
}
items = append(items, item)
}
return &responses.WalletTransactionListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}
func (s *FinanceApplicationServiceImpl) GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// HandleAlipayCallback 处理支付宝回调
func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context, r *http.Request) error {
// 解析并验证支付宝回调通知
notification, err := s.aliPayClient.HandleAliPaymentNotification(r)
if err != nil {
s.logger.Error("支付宝回调验证失败", zap.Error(err))
return err
}
// 记录回调数据
s.logger.Info("支付宝回调数据",
zap.String("out_trade_no", notification.OutTradeNo),
zap.String("trade_no", notification.TradeNo),
zap.String("trade_status", string(notification.TradeStatus)),
zap.String("total_amount", notification.TotalAmount),
zap.String("buyer_id", notification.BuyerId),
zap.String("seller_id", notification.SellerId),
)
// 检查交易状态
if !s.aliPayClient.IsAlipayPaymentSuccess(notification) {
s.logger.Warn("支付宝交易未成功",
zap.String("out_trade_no", notification.OutTradeNo),
zap.String("trade_status", string(notification.TradeStatus)),
)
return nil // 不返回错误,因为这是正常的业务状态
}
// 使用公共方法处理支付成功逻辑
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
if err != nil {
s.logger.Error("处理支付宝支付成功失败",
zap.String("out_trade_no", notification.OutTradeNo),
zap.Error(err),
)
return err
}
return nil
}
func (s *FinanceApplicationServiceImpl) RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// processAlipayPaymentSuccess 处理支付宝支付成功的公共逻辑
func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.Context, outTradeNo, tradeNo, totalAmount, buyerID, sellerID string) error {
// 解析金额
amount, err := decimal.NewFromString(totalAmount)
if err != nil {
s.logger.Error("解析支付宝金额失败",
zap.String("total_amount", totalAmount),
zap.Error(err),
)
return err
}
// 直接调用充值记录服务处理支付成功逻辑
// 该服务内部会处理所有必要的检查、事务和更新操作
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
if err != nil {
s.logger.Error("处理支付宝支付成功失败",
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
return err
}
s.logger.Info("支付宝支付成功处理完成",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", tradeNo),
zap.String("amount", amount.String()),
)
return nil
}
func (s *FinanceApplicationServiceImpl) DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error {
// ... implementation from old service
return fmt.Errorf("not implemented")
// updateAlipayOrderStatus 根据支付宝状态更新本地订单状态
func (s *FinanceApplicationServiceImpl) updateAlipayOrderStatus(ctx context.Context, outTradeNo string, alipayStatus alipay.TradeStatus, tradeNo, totalAmount string) error {
// 查找支付宝订单
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if err != nil {
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
return fmt.Errorf("查找支付宝订单失败: %w", err)
}
if alipayOrder == nil {
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
return fmt.Errorf("支付宝订单不存在")
}
switch alipayStatus {
case alipay.TradeStatusSuccess:
// 支付成功,调用公共处理逻辑
return s.processAlipayPaymentSuccess(ctx, outTradeNo, tradeNo, totalAmount, "", "")
case alipay.TradeStatusClosed:
// 交易关闭
s.logger.Info("支付宝订单已关闭", zap.String("out_trade_no", outTradeNo))
alipayOrder.MarkClosed()
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
if err != nil {
s.logger.Error("更新支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
return err
}
case alipay.TradeStatusWaitBuyerPay:
// 等待买家付款保持pending状态
s.logger.Info("支付宝订单等待买家付款", zap.String("out_trade_no", outTradeNo))
default:
// 其他状态,记录日志
s.logger.Info("支付宝订单其他状态", zap.String("out_trade_no", outTradeNo), zap.String("status", string(alipayStatus)))
}
return nil
}
func (s *FinanceApplicationServiceImpl) WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// HandleAlipayReturn 处理支付宝同步回调
func (s *FinanceApplicationServiceImpl) HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error) {
if outTradeNo == "" {
return "", fmt.Errorf("缺少商户订单号")
}
// 查找支付宝订单
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if err != nil {
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
return "", fmt.Errorf("查找支付宝订单失败: %w", err)
}
if alipayOrder == nil {
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
return "", fmt.Errorf("支付宝订单不存在")
}
// 记录同步回调查询
s.logger.Info("支付宝同步回调查询订单状态",
zap.String("out_trade_no", outTradeNo),
zap.String("order_status", string(alipayOrder.Status)),
zap.String("trade_no", func() string {
if alipayOrder.TradeNo != nil {
return *alipayOrder.TradeNo
}
return ""
}()),
)
// 返回订单状态
switch alipayOrder.Status {
case finance_entities.AlipayOrderStatusSuccess:
return "TRADE_SUCCESS", nil
case finance_entities.AlipayOrderStatusPending:
// 对于pending状态需要特殊处理
// 可能是用户支付了但支付宝异步回调还没到,或者用户还没支付
// 这里可以尝试主动查询支付宝订单状态但为了简化处理先返回WAIT_BUYER_PAY
// 让前端显示"支付处理中"的状态,用户可以通过刷新页面或等待异步回调来更新状态
s.logger.Info("支付宝订单状态为pending建议用户等待异步回调或刷新页面",
zap.String("out_trade_no", outTradeNo),
)
return "WAIT_BUYER_PAY", nil
case finance_entities.AlipayOrderStatusFailed:
return "TRADE_FAILED", nil
case finance_entities.AlipayOrderStatusClosed:
return "TRADE_CLOSED", nil
default:
return "UNKNOWN", nil
}
}
func (s *FinanceApplicationServiceImpl) GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// GetAlipayOrderStatus 获取支付宝订单状态
func (s *FinanceApplicationServiceImpl) GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error) {
if outTradeNo == "" {
return nil, fmt.Errorf("缺少商户订单号")
}
// 查找支付宝订单
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if err != nil {
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
return nil, fmt.Errorf("查找支付宝订单失败: %w", err)
}
if alipayOrder == nil {
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
return nil, fmt.Errorf("支付宝订单不存在")
}
// 如果订单状态为pending主动查询支付宝订单状态
if alipayOrder.Status == finance_entities.AlipayOrderStatusPending {
s.logger.Info("订单状态为pending主动查询支付宝订单状态", zap.String("out_trade_no", outTradeNo))
// 调用支付宝查询接口
alipayResp, err := s.aliPayClient.QueryOrderStatus(ctx, outTradeNo)
if err != nil {
s.logger.Error("查询支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
// 查询失败不影响返回,继续使用数据库中的状态
} else {
// 解析支付宝返回的状态
alipayStatus := alipayResp.TradeStatus
s.logger.Info("支付宝返回订单状态",
zap.String("out_trade_no", outTradeNo),
zap.String("alipay_status", string(alipayStatus)),
zap.String("trade_no", alipayResp.TradeNo),
)
// 使用公共方法更新订单状态
err = s.updateAlipayOrderStatus(ctx, outTradeNo, alipayStatus, alipayResp.TradeNo, alipayResp.TotalAmount)
if err != nil {
s.logger.Error("更新支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
}
// 重新获取更新后的订单信息
updatedOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if err == nil && updatedOrder != nil {
alipayOrder = updatedOrder
}
}
}
// 判断是否处理中
isProcessing := alipayOrder.Status == finance_entities.AlipayOrderStatusPending
// 判断是否可以重试(失败状态可以重试)
canRetry := alipayOrder.Status == finance_entities.AlipayOrderStatusFailed
// 转换为响应DTO
response := &responses.AlipayOrderStatusResponse{
OutTradeNo: alipayOrder.OutTradeNo,
TradeNo: alipayOrder.TradeNo,
Status: string(alipayOrder.Status),
Amount: alipayOrder.Amount,
Subject: alipayOrder.Subject,
Platform: alipayOrder.Platform,
CreatedAt: alipayOrder.CreatedAt,
UpdatedAt: alipayOrder.UpdatedAt,
NotifyTime: alipayOrder.NotifyTime,
ReturnTime: alipayOrder.ReturnTime,
ErrorCode: &alipayOrder.ErrorCode,
ErrorMessage: &alipayOrder.ErrorMessage,
IsProcessing: isProcessing,
CanRetry: canRetry,
}
// 如果错误码为空设置为nil
if alipayOrder.ErrorCode == "" {
response.ErrorCode = nil
}
if alipayOrder.ErrorMessage == "" {
response.ErrorMessage = nil
}
s.logger.Info("查询支付宝订单状态完成",
zap.String("out_trade_no", outTradeNo),
zap.String("status", string(alipayOrder.Status)),
zap.Bool("is_processing", isProcessing),
zap.Bool("can_retry", canRetry),
)
return response, nil
}
// GetUserRechargeRecords 获取用户充值记录
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
// 查询用户充值记录
records, err := s.rechargeRecordService.GetByUserID(ctx, userID)
if err != nil {
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 计算总数
total := int64(len(records))
// 转换为响应DTO
var items []responses.RechargeRecordResponse
for _, record := range records {
item := responses.RechargeRecordResponse{
ID: record.ID,
UserID: record.UserID,
Amount: record.Amount,
RechargeType: string(record.RechargeType),
Status: string(record.Status),
Notes: record.Notes,
CreatedAt: record.CreatedAt,
UpdatedAt: record.UpdatedAt,
}
// 根据充值类型设置相应的订单号
if record.AlipayOrderID != nil {
item.AlipayOrderID = *record.AlipayOrderID
}
if record.TransferOrderID != nil {
item.TransferOrderID = *record.TransferOrderID
}
items = append(items, item)
}
return &responses.RechargeRecordListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}
// GetAdminRechargeRecords 管理员获取充值记录
func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
// 查询所有充值记录(管理员可以查看所有用户的充值记录)
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
if err != nil {
s.logger.Error("查询管理员充值记录失败", zap.Error(err))
return nil, err
}
// 获取总数
total, err := s.rechargeRecordService.Count(ctx, filters)
if err != nil {
s.logger.Error("统计管理员充值记录失败", zap.Error(err))
return nil, err
}
// 转换为响应DTO
var items []responses.RechargeRecordResponse
for _, record := range records {
item := responses.RechargeRecordResponse{
ID: record.ID,
UserID: record.UserID,
Amount: record.Amount,
RechargeType: string(record.RechargeType),
Status: string(record.Status),
Notes: record.Notes,
CreatedAt: record.CreatedAt,
UpdatedAt: record.UpdatedAt,
}
// 根据充值类型设置相应的订单号
if record.AlipayOrderID != nil {
item.AlipayOrderID = *record.AlipayOrderID
}
if record.TransferOrderID != nil {
item.TransferOrderID = *record.TransferOrderID
}
items = append(items, item)
}
return &responses.RechargeRecordListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}
// GetRechargeConfig 获取充值配置
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
return &responses.RechargeConfigResponse{
MinAmount: s.config.Recharge.MinAmount,
MaxAmount: s.config.Recharge.MaxAmount,
}, nil
}

View File

@@ -35,12 +35,12 @@ func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd
if err := s.validateCreateCategory(cmd); err != nil {
return err
}
// 2. 验证分类编号唯一性
if err := s.validateCategoryCode(cmd.Code, ""); err != nil {
return err
}
// 3. 创建分类实体
category := &entities.ProductCategory{
Name: cmd.Name,
@@ -50,14 +50,14 @@ func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
}
// 4. 保存到仓储
createdCategory, err := s.categoryRepo.Create(ctx, *category)
if err != nil {
s.logger.Error("创建分类失败", zap.Error(err), zap.String("code", cmd.Code))
return fmt.Errorf("创建分类失败: %w", err)
}
s.logger.Info("创建分类成功", zap.String("id", createdCategory.ID), zap.String("code", cmd.Code))
return nil
}
@@ -68,18 +68,18 @@ func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd
if err := s.validateUpdateCategory(cmd); err != nil {
return err
}
// 2. 获取现有分类
existingCategory, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
// 3. 验证分类编号唯一性(排除当前分类)
if err := s.validateCategoryCode(cmd.Code, cmd.ID); err != nil {
return err
}
// 4. 更新分类信息
existingCategory.Name = cmd.Name
existingCategory.Code = cmd.Code
@@ -87,13 +87,13 @@ func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd
existingCategory.Sort = cmd.Sort
existingCategory.IsEnabled = cmd.IsEnabled
existingCategory.IsVisible = cmd.IsVisible
// 5. 保存到仓储
if err := s.categoryRepo.Update(ctx, existingCategory); err != nil {
s.logger.Error("更新分类失败", zap.Error(err), zap.String("id", cmd.ID))
return fmt.Errorf("更新分类失败: %w", err)
}
s.logger.Info("更新分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
return nil
}
@@ -105,16 +105,16 @@ func (s *CategoryApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
// 2. 检查是否有产品(可选,根据业务需求决定)
// 这里可以添加检查逻辑,如果有产品则不允许删除
// 3. 删除分类
if err := s.categoryRepo.Delete(ctx, cmd.ID); err != nil {
s.logger.Error("删除分类失败", zap.Error(err), zap.String("id", cmd.ID))
return fmt.Errorf("删除分类失败: %w", err)
}
s.logger.Info("删除分类成功", zap.String("id", cmd.ID), zap.String("code", existingCategory.Code))
return nil
}
@@ -226,11 +226,11 @@ func (s *CategoryApplicationServiceImpl) validateCategoryCode(code, excludeID st
if code == "" {
return errors.New("分类编号不能为空")
}
existingCategory, err := s.categoryRepo.FindByCode(context.Background(), code)
if err == nil && existingCategory != nil && existingCategory.ID != excludeID {
return errors.New("分类编号已存在")
}
return nil
}
}

View File

@@ -0,0 +1,27 @@
package commands
// AddPackageItemCommand 添加组合包子产品命令
type AddPackageItemCommand struct {
ProductID string `json:"product_id" binding:"required,uuid" comment:"子产品ID"`
}
// UpdatePackageItemCommand 更新组合包子产品命令
type UpdatePackageItemCommand struct {
SortOrder int `json:"sort_order" binding:"required,min=0" comment:"排序"`
}
// ReorderPackageItemsCommand 重新排序组合包子产品命令
type ReorderPackageItemsCommand struct {
ItemIDs []string `json:"item_ids" binding:"required,dive,uuid" comment:"子产品ID列表"`
}
// UpdatePackageItemsCommand 批量更新组合包子产品命令
type UpdatePackageItemsCommand struct {
Items []PackageItemData `json:"items" binding:"required,dive" comment:"子产品列表"`
}
// PackageItemData 组合包子产品数据
type PackageItemData struct {
ProductID string `json:"product_id" binding:"required,uuid" comment:"子产品ID"`
SortOrder int `json:"sort_order" binding:"required,min=0" comment:"排序"`
}

View File

@@ -11,7 +11,7 @@ type CreateProductCommand struct {
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// SEO信息
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
@@ -30,7 +30,7 @@ type UpdateProductCommand struct {
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// SEO信息
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
@@ -40,4 +40,4 @@ type UpdateProductCommand struct {
// DeleteProductCommand 删除产品命令
type DeleteProductCommand struct {
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
}
}

View File

@@ -0,0 +1,10 @@
package queries
// GetAvailableProductsQuery 获取可选子产品查询
type GetAvailableProductsQuery struct {
ExcludePackageID string `form:"exclude_package_id" binding:"omitempty,uuid" comment:"排除的组合包ID"`
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
CategoryID string `form:"category_id" binding:"omitempty,uuid" comment:"分类ID"`
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
}

View File

@@ -0,0 +1,43 @@
package responses
import "time"
// ProductApiConfigResponse 产品API配置响应
type ProductApiConfigResponse struct {
ID string `json:"id" comment:"配置ID"`
ProductID string `json:"product_id" comment:"产品ID"`
RequestParams []RequestParamResponse `json:"request_params" comment:"请求参数配置"`
ResponseFields []ResponseFieldResponse `json:"response_fields" comment:"响应字段配置"`
ResponseExample map[string]interface{} `json:"response_example" comment:"响应示例"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}
// RequestParamResponse 请求参数响应
type RequestParamResponse struct {
Name string `json:"name" comment:"参数名称"`
Field string `json:"field" comment:"参数字段名"`
Type string `json:"type" comment:"参数类型"`
Required bool `json:"required" comment:"是否必填"`
Description string `json:"description" comment:"参数描述"`
Example string `json:"example" comment:"参数示例"`
Validation string `json:"validation" comment:"验证规则"`
}
// ResponseFieldResponse 响应字段响应
type ResponseFieldResponse struct {
Name string `json:"name" comment:"字段名称"`
Path string `json:"path" comment:"字段路径"`
Type string `json:"type" comment:"字段类型"`
Description string `json:"description" comment:"字段描述"`
Required bool `json:"required" comment:"是否必填"`
Example string `json:"example" comment:"字段示例"`
}
// ProductApiConfigListResponse 产品API配置列表响应
type ProductApiConfigListResponse struct {
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Items []ProductApiConfigResponse `json:"items" comment:"配置列表"`
}

View File

@@ -2,6 +2,16 @@ package responses
import "time"
// PackageItemResponse 组合包项目响应
type PackageItemResponse struct {
ID string `json:"id" comment:"项目ID"`
ProductID string `json:"product_id" comment:"子产品ID"`
ProductCode string `json:"product_code" comment:"子产品编号"`
ProductName string `json:"product_name" comment:"子产品名称"`
SortOrder int `json:"sort_order" comment:"排序"`
Price float64 `json:"price" comment:"子产品价格"`
}
// ProductInfoResponse 产品详情响应
type ProductInfoResponse struct {
ID string `json:"id" comment:"产品ID"`
@@ -23,6 +33,9 @@ type ProductInfoResponse struct {
// 关联信息
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
// 组合包信息
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}

View File

@@ -0,0 +1,206 @@
package product
import (
"context"
"errors"
"tyapi-server/internal/application/product/dto/responses"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/services"
"go.uber.org/zap"
)
// ProductApiConfigApplicationService 产品API配置应用服务接口
type ProductApiConfigApplicationService interface {
// 获取产品API配置
GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error)
// 根据产品代码获取API配置
GetProductApiConfigByCode(ctx context.Context, productCode string) (*responses.ProductApiConfigResponse, error)
// 批量获取产品API配置
GetProductApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*responses.ProductApiConfigResponse, error)
// 创建产品API配置
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
// 更新产品API配置
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
// 删除产品API配置
DeleteProductApiConfig(ctx context.Context, configID string) error
}
// ProductApiConfigApplicationServiceImpl 产品API配置应用服务实现
type ProductApiConfigApplicationServiceImpl struct {
apiConfigService services.ProductApiConfigService
logger *zap.Logger
}
// NewProductApiConfigApplicationService 创建产品API配置应用服务
func NewProductApiConfigApplicationService(
apiConfigService services.ProductApiConfigService,
logger *zap.Logger,
) ProductApiConfigApplicationService {
return &ProductApiConfigApplicationServiceImpl{
apiConfigService: apiConfigService,
logger: logger,
}
}
// GetProductApiConfig 获取产品API配置
func (s *ProductApiConfigApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
config, err := s.apiConfigService.GetApiConfigByProductID(ctx, productID)
if err != nil {
return nil, err
}
return s.convertToResponse(config), nil
}
// GetProductApiConfigByCode 根据产品代码获取API配置
func (s *ProductApiConfigApplicationServiceImpl) GetProductApiConfigByCode(ctx context.Context, productCode string) (*responses.ProductApiConfigResponse, error) {
config, err := s.apiConfigService.GetApiConfigByProductCode(ctx, productCode)
if err != nil {
return nil, err
}
return s.convertToResponse(config), nil
}
// GetProductApiConfigsByProductIDs 批量获取产品API配置
func (s *ProductApiConfigApplicationServiceImpl) GetProductApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*responses.ProductApiConfigResponse, error) {
configs, err := s.apiConfigService.GetApiConfigsByProductIDs(ctx, productIDs)
if err != nil {
return nil, err
}
var responses []*responses.ProductApiConfigResponse
for _, config := range configs {
responses = append(responses, s.convertToResponse(config))
}
return responses, nil
}
// CreateProductApiConfig 创建产品API配置
func (s *ProductApiConfigApplicationServiceImpl) CreateProductApiConfig(ctx context.Context, productID string, configResponse *responses.ProductApiConfigResponse) error {
// 检查是否已存在配置
exists, err := s.apiConfigService.ExistsByProductID(ctx, productID)
if err != nil {
return err
}
if exists {
return errors.New("产品API配置已存在")
}
// 转换为实体
config := s.convertToEntity(configResponse)
config.ProductID = productID
return s.apiConfigService.CreateApiConfig(ctx, config)
}
// UpdateProductApiConfig 更新产品API配置
func (s *ProductApiConfigApplicationServiceImpl) UpdateProductApiConfig(ctx context.Context, configID string, configResponse *responses.ProductApiConfigResponse) error {
// 获取现有配置
existingConfig, err := s.apiConfigService.GetApiConfigByProductID(ctx, configResponse.ProductID)
if err != nil {
return err
}
// 更新配置
config := s.convertToEntity(configResponse)
config.ID = configID
config.ProductID = existingConfig.ProductID
return s.apiConfigService.UpdateApiConfig(ctx, config)
}
// DeleteProductApiConfig 删除产品API配置
func (s *ProductApiConfigApplicationServiceImpl) DeleteProductApiConfig(ctx context.Context, configID string) error {
return s.apiConfigService.DeleteApiConfig(ctx, configID)
}
// convertToResponse 转换为响应DTO
func (s *ProductApiConfigApplicationServiceImpl) convertToResponse(config *entities.ProductApiConfig) *responses.ProductApiConfigResponse {
requestParams, _ := config.GetRequestParams()
responseFields, _ := config.GetResponseFields()
responseExample, _ := config.GetResponseExample()
// 转换请求参数
var requestParamResponses []responses.RequestParamResponse
for _, param := range requestParams {
requestParamResponses = append(requestParamResponses, responses.RequestParamResponse{
Name: param.Name,
Field: param.Field,
Type: param.Type,
Required: param.Required,
Description: param.Description,
Example: param.Example,
Validation: param.Validation,
})
}
// 转换响应字段
var responseFieldResponses []responses.ResponseFieldResponse
for _, field := range responseFields {
responseFieldResponses = append(responseFieldResponses, responses.ResponseFieldResponse{
Name: field.Name,
Path: field.Path,
Type: field.Type,
Description: field.Description,
Required: field.Required,
Example: field.Example,
})
}
return &responses.ProductApiConfigResponse{
ID: config.ID,
ProductID: config.ProductID,
RequestParams: requestParamResponses,
ResponseFields: responseFieldResponses,
ResponseExample: responseExample,
CreatedAt: config.CreatedAt,
UpdatedAt: config.UpdatedAt,
}
}
// convertToEntity 转换为实体
func (s *ProductApiConfigApplicationServiceImpl) convertToEntity(configResponse *responses.ProductApiConfigResponse) *entities.ProductApiConfig {
// 转换请求参数
var requestParams []entities.RequestParam
for _, param := range configResponse.RequestParams {
requestParams = append(requestParams, entities.RequestParam{
Name: param.Name,
Field: param.Field,
Type: param.Type,
Required: param.Required,
Description: param.Description,
Example: param.Example,
Validation: param.Validation,
})
}
// 转换响应字段
var responseFields []entities.ResponseField
for _, field := range configResponse.ResponseFields {
responseFields = append(responseFields, entities.ResponseField{
Name: field.Name,
Path: field.Path,
Type: field.Type,
Description: field.Description,
Required: field.Required,
Example: field.Example,
})
}
config := &entities.ProductApiConfig{}
// 设置JSON字段
config.SetRequestParams(requestParams)
config.SetResponseFields(responseFields)
config.SetResponseExample(configResponse.ResponseExample)
return config
}

View File

@@ -5,6 +5,7 @@ import (
"tyapi-server/internal/application/product/dto/commands"
"tyapi-server/internal/application/product/dto/queries"
"tyapi-server/internal/application/product/dto/responses"
"tyapi-server/internal/shared/interfaces"
)
// ProductApplicationService 产品应用服务接口
@@ -15,12 +16,26 @@ type ProductApplicationService interface {
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
GetProductByID(ctx context.Context, query *queries.GetProductQuery) (*responses.ProductInfoResponse, error)
ListProducts(ctx context.Context, query *queries.ListProductsQuery) (*responses.ProductListResponse, error)
ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error)
GetProductsByIDs(ctx context.Context, query *queries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error)
// 业务查询
GetSubscribableProducts(ctx context.Context, query *queries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error)
GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error)
// 组合包管理
AddPackageItem(ctx context.Context, packageID string, cmd *commands.AddPackageItemCommand) error
UpdatePackageItem(ctx context.Context, packageID, itemID string, cmd *commands.UpdatePackageItemCommand) error
RemovePackageItem(ctx context.Context, packageID, itemID string) error
ReorderPackageItems(ctx context.Context, packageID string, cmd *commands.ReorderPackageItemsCommand) error
UpdatePackageItems(ctx context.Context, packageID string, cmd *commands.UpdatePackageItemsCommand) error
GetAvailableProducts(ctx context.Context, query *queries.GetAvailableProductsQuery) (*responses.ProductListResponse, error)
// API配置管理
GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error)
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
DeleteProductApiConfig(ctx context.Context, configID string) error
}
// CategoryApplicationService 分类应用服务接口

View File

@@ -2,7 +2,9 @@ package product
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/application/product/dto/commands"
@@ -10,25 +12,29 @@ import (
"tyapi-server/internal/application/product/dto/responses"
"tyapi-server/internal/domains/product/entities"
product_service "tyapi-server/internal/domains/product/services"
"tyapi-server/internal/shared/interfaces"
)
// ProductApplicationServiceImpl 产品应用服务实现
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
type ProductApplicationServiceImpl struct {
productManagementService *product_service.ProductManagementService
productSubscriptionService *product_service.ProductSubscriptionService
logger *zap.Logger
productManagementService *product_service.ProductManagementService
productSubscriptionService *product_service.ProductSubscriptionService
productApiConfigAppService ProductApiConfigApplicationService
logger *zap.Logger
}
// NewProductApplicationService 创建产品应用服务
func NewProductApplicationService(
productManagementService *product_service.ProductManagementService,
productSubscriptionService *product_service.ProductSubscriptionService,
productApiConfigAppService ProductApiConfigApplicationService,
logger *zap.Logger,
) ProductApplicationService {
return &ProductApplicationServiceImpl{
productManagementService: productManagementService,
productSubscriptionService: productSubscriptionService,
productApiConfigAppService: productApiConfigAppService,
logger: logger,
}
}
@@ -43,7 +49,7 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
Description: cmd.Description,
Content: cmd.Content,
CategoryID: cmd.CategoryID,
Price: cmd.Price,
Price: decimal.NewFromFloat(cmd.Price),
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
IsPackage: cmd.IsPackage,
@@ -72,7 +78,7 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
existingProduct.Description = cmd.Description
existingProduct.Content = cmd.Content
existingProduct.CategoryID = cmd.CategoryID
existingProduct.Price = cmd.Price
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
existingProduct.IsEnabled = cmd.IsEnabled
existingProduct.IsVisible = cmd.IsVisible
existingProduct.IsPackage = cmd.IsPackage
@@ -92,22 +98,9 @@ func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *
// ListProducts 获取产品列表
// 业务流程1. 获取产品列表 2. 构建响应数据
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query *appQueries.ListProductsQuery) (*responses.ProductListResponse, error) {
// 根据查询条件获取产品列表
var products []*entities.Product
var err error
if query.CategoryID != "" {
products, err = s.productManagementService.GetProductsByCategory(ctx, query.CategoryID)
} else if query.IsVisible != nil && *query.IsVisible {
products, err = s.productManagementService.GetVisibleProducts(ctx)
} else if query.IsEnabled != nil && *query.IsEnabled {
products, err = s.productManagementService.GetEnabledProducts(ctx)
} else {
// 默认获取可见产品
products, err = s.productManagementService.GetVisibleProducts(ctx)
}
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error) {
// 调用领域服务获取产品列表
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
if err != nil {
return nil, err
}
@@ -119,9 +112,9 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query
}
return &responses.ProductListResponse{
Total: int64(len(items)),
Page: query.Page,
Size: query.PageSize,
Total: total,
Page: options.Page,
Size: options.PageSize,
Items: items,
}, nil
}
@@ -186,6 +179,177 @@ func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*r
}, nil
}
// AddPackageItem 添加组合包子产品
func (s *ProductApplicationServiceImpl) AddPackageItem(ctx context.Context, packageID string, cmd *commands.AddPackageItemCommand) error {
// 验证组合包是否存在
packageProduct, err := s.productManagementService.GetProductByID(ctx, packageID)
if err != nil {
return err
}
if !packageProduct.IsPackage {
return fmt.Errorf("产品不是组合包")
}
// 验证子产品是否存在且不是组合包
subProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ProductID)
if err != nil {
return err
}
if subProduct.IsPackage {
return fmt.Errorf("不能将组合包作为子产品")
}
// 检查是否已经存在
existingItems, err := s.productManagementService.GetPackageItems(ctx, packageID)
if err == nil {
for _, item := range existingItems {
if item.ProductID == cmd.ProductID {
return fmt.Errorf("该产品已在组合包中")
}
}
}
// 获取当前最大排序号
maxSortOrder := 0
if existingItems != nil {
for _, item := range existingItems {
if item.SortOrder > maxSortOrder {
maxSortOrder = item.SortOrder
}
}
}
// 创建组合包项目
packageItem := &entities.ProductPackageItem{
PackageID: packageID,
ProductID: cmd.ProductID,
SortOrder: maxSortOrder + 1,
}
return s.productManagementService.CreatePackageItem(ctx, packageItem)
}
// UpdatePackageItem 更新组合包子产品
func (s *ProductApplicationServiceImpl) UpdatePackageItem(ctx context.Context, packageID, itemID string, cmd *commands.UpdatePackageItemCommand) error {
// 验证组合包项目是否存在
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
if err != nil {
return err
}
if packageItem.PackageID != packageID {
return fmt.Errorf("组合包项目不属于指定组合包")
}
// 更新项目
packageItem.SortOrder = cmd.SortOrder
return s.productManagementService.UpdatePackageItem(ctx, packageItem)
}
// RemovePackageItem 移除组合包子产品
func (s *ProductApplicationServiceImpl) RemovePackageItem(ctx context.Context, packageID, itemID string) error {
// 验证组合包项目是否存在
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
if err != nil {
return err
}
if packageItem.PackageID != packageID {
return fmt.Errorf("组合包项目不属于指定组合包")
}
return s.productManagementService.DeletePackageItem(ctx, itemID)
}
// ReorderPackageItems 重新排序组合包子产品
func (s *ProductApplicationServiceImpl) ReorderPackageItems(ctx context.Context, packageID string, cmd *commands.ReorderPackageItemsCommand) error {
// 验证所有项目是否属于该组合包
for i, itemID := range cmd.ItemIDs {
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
if err != nil {
return err
}
if packageItem.PackageID != packageID {
return fmt.Errorf("组合包项目不属于指定组合包")
}
// 更新排序
packageItem.SortOrder = i + 1
if err := s.productManagementService.UpdatePackageItem(ctx, packageItem); err != nil {
return err
}
}
return nil
}
// UpdatePackageItems 批量更新组合包子产品
func (s *ProductApplicationServiceImpl) UpdatePackageItems(ctx context.Context, packageID string, cmd *commands.UpdatePackageItemsCommand) error {
// 验证组合包是否存在
packageProduct, err := s.productManagementService.GetProductByID(ctx, packageID)
if err != nil {
return err
}
if !packageProduct.IsPackage {
return fmt.Errorf("产品不是组合包")
}
// 验证所有子产品是否存在且不是组合包
for _, item := range cmd.Items {
subProduct, err := s.productManagementService.GetProductByID(ctx, item.ProductID)
if err != nil {
return err
}
if subProduct.IsPackage {
return fmt.Errorf("不能将组合包作为子产品")
}
}
// 使用事务进行批量更新
return s.productManagementService.UpdatePackageItemsBatch(ctx, packageID, cmd.Items)
}
// GetAvailableProducts 获取可选子产品列表
func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context, query *appQueries.GetAvailableProductsQuery) (*responses.ProductListResponse, error) {
// 构建筛选条件
filters := make(map[string]interface{})
filters["is_package"] = false // 只获取非组合包产品
filters["is_enabled"] = true // 只获取启用产品
if query.Keyword != "" {
filters["keyword"] = query.Keyword
}
if query.CategoryID != "" {
filters["category_id"] = query.CategoryID
}
// 设置分页选项
options := interfaces.ListOptions{
Page: query.Page,
PageSize: query.PageSize,
Sort: "created_at",
Order: "desc",
}
// 获取产品列表
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
if err != nil {
return nil, err
}
// 转换为响应对象
items := make([]responses.ProductInfoResponse, len(products))
for i := range products {
items[i] = *s.convertToProductInfoResponse(products[i])
}
return &responses.ProductListResponse{
Total: total,
Page: options.Page,
Size: options.PageSize,
Items: items,
}, nil
}
// convertToProductInfoResponse 转换为产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
response := &responses.ProductInfoResponse{
@@ -195,7 +359,7 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
Price: product.Price,
Price: product.Price.InexactFloat64(),
IsEnabled: product.IsEnabled,
IsVisible: product.IsVisible,
IsPackage: product.IsPackage,
@@ -211,6 +375,21 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
response.Category = s.convertToCategoryInfoResponse(product.Category)
}
// 转换组合包项目信息
if product.IsPackage && len(product.PackageItems) > 0 {
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
for i, item := range product.PackageItems {
response.PackageItems[i] = &responses.PackageItemResponse{
ID: item.ID,
ProductID: item.ProductID,
ProductCode: item.Product.Code,
ProductName: item.Product.Name,
SortOrder: item.SortOrder,
Price: item.Product.Price.InexactFloat64(),
}
}
}
return response
}
@@ -224,4 +403,24 @@ func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *
CreatedAt: category.CreatedAt,
UpdatedAt: category.UpdatedAt,
}
}
// GetProductApiConfig 获取产品API配置
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)
}
// CreateProductApiConfig 创建产品API配置
func (s *ProductApplicationServiceImpl) CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error {
return s.productApiConfigAppService.CreateProductApiConfig(ctx, productID, config)
}
// UpdateProductApiConfig 更新产品API配置
func (s *ProductApplicationServiceImpl) UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error {
return s.productApiConfigAppService.UpdateProductApiConfig(ctx, configID, config)
}
// DeleteProductApiConfig 删除产品API配置
func (s *ProductApplicationServiceImpl) DeleteProductApiConfig(ctx context.Context, configID string) error {
return s.productApiConfigAppService.DeleteProductApiConfig(ctx, configID)
}

View File

@@ -4,12 +4,15 @@ import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/application/product/dto/commands"
appQueries "tyapi-server/internal/application/product/dto/queries"
"tyapi-server/internal/application/product/dto/responses"
"tyapi-server/internal/domains/product/entities"
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
product_service "tyapi-server/internal/domains/product/services"
)
@@ -41,7 +44,7 @@ func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context
}
// 2. 更新订阅价格
subscription.Price = cmd.Price
subscription.Price = decimal.NewFromFloat(cmd.Price)
// 3. 保存订阅
// 这里需要扩展领域服务来支持更新操作
@@ -70,13 +73,26 @@ func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Con
// ListSubscriptions 获取订阅列表
// 业务流程1. 获取订阅列表 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
// 这里需要扩展领域服务来支持列表查询
// 暂时返回空列表
repoQuery := &repoQueries.ListSubscriptionsQuery{
Page: query.Page,
PageSize: query.PageSize,
}
subscriptions, total, err := s.productSubscriptionService.ListSubscriptions(ctx, repoQuery)
if err != nil {
return nil, err
}
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
for i := range subscriptions {
resp := s.convertToSubscriptionInfoResponse(subscriptions[i])
if resp != nil {
items[i] = *resp // 解引用指针
}
}
return &responses.SubscriptionListResponse{
Total: 0,
Total: total,
Page: query.Page,
Size: query.PageSize,
Items: []responses.SubscriptionInfoResponse{},
Items: items,
}, nil
}
@@ -137,7 +153,8 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
Price: subscription.Price,
Price: subscription.Price.InexactFloat64(),
Product: s.convertToProductSimpleResponse(subscription.Product),
APIUsed: subscription.APIUsed,
CreatedAt: subscription.CreatedAt,
UpdatedAt: subscription.UpdatedAt,
@@ -151,7 +168,8 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
Name: product.Name,
Code: product.Code,
Description: product.Description,
Price: product.Price,
Price: product.Price.InexactFloat64(),
Category: s.convertToCategorySimpleResponse(product.Category),
IsPackage: product.IsPackage,
}
}

View File

@@ -0,0 +1,31 @@
package queries
import "tyapi-server/internal/domains/user/repositories/queries"
// ListUsersQuery 用户列表查询DTO
type ListUsersQuery struct {
Page int `json:"page" validate:"min=1"`
PageSize int `json:"page_size" validate:"min=1,max=100"`
Phone string `json:"phone"`
UserType string `json:"user_type"` // 用户类型: user/admin
IsActive *bool `json:"is_active"` // 是否激活
IsCertified *bool `json:"is_certified"` // 是否已认证
CompanyName string `json:"company_name"` // 企业名称
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}
// ToDomainQuery 转换为领域查询对象
func (q *ListUsersQuery) ToDomainQuery() *queries.ListUsersQuery {
return &queries.ListUsersQuery{
Page: q.Page,
PageSize: q.PageSize,
Phone: q.Phone,
UserType: q.UserType,
IsActive: q.IsActive,
IsCertified: q.IsCertified,
CompanyName: q.CompanyName,
StartDate: q.StartDate,
EndDate: q.EndDate,
}
}

View File

@@ -0,0 +1,50 @@
package responses
import "time"
// UserListItem 用户列表项
type UserListItem struct {
ID string `json:"id"`
Phone string `json:"phone"`
UserType string `json:"user_type"`
Username string `json:"username"`
IsActive bool `json:"is_active"`
IsCertified bool `json:"is_certified"`
LoginCount int `json:"login_count"`
LastLoginAt *time.Time `json:"last_login_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// 企业信息
EnterpriseInfo *EnterpriseInfoItem `json:"enterprise_info,omitempty"`
// 钱包信息
WalletBalance string `json:"wallet_balance,omitempty"`
}
// EnterpriseInfoItem 企业信息项
type EnterpriseInfoItem struct {
ID string `json:"id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
LegalPersonName string `json:"legal_person_name"`
LegalPersonPhone string `json:"legal_person_phone"`
EnterpriseAddress string `json:"enterprise_address"`
EnterpriseEmail string `json:"enterprise_email"`
CreatedAt time.Time `json:"created_at"`
}
// UserListResponse 用户列表响应
type UserListResponse struct {
Items []*UserListItem `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// UserStatsResponse 用户统计响应
type UserStatsResponse struct {
TotalUsers int64 `json:"total_users"`
ActiveUsers int64 `json:"active_users"`
CertifiedUsers int64 `json:"certified_users"`
}

View File

@@ -19,9 +19,9 @@ type EnterpriseInfoResponse struct {
UnifiedSocialCode string `json:"unified_social_code" example:"91110000123456789X"`
LegalPersonName string `json:"legal_person_name" example:"张三"`
LegalPersonID string `json:"legal_person_id" example:"110101199001011234"`
IsOCRVerified bool `json:"is_ocr_verified" example:"false"`
IsFaceVerified bool `json:"is_face_verified" example:"false"`
IsCertified bool `json:"is_certified" example:"false"`
LegalPersonPhone string `json:"legal_person_phone" example:"13800138000"`
EnterpriseAddress string `json:"enterprise_address" example:"北京市朝阳区xxx街道xxx号"`
EnterpriseEmail string `json:"enterprise_email" example:"contact@example.com"`
CertifiedAt *time.Time `json:"certified_at,omitempty" example:"2024-01-01T00:00:00Z"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`

View File

@@ -4,6 +4,7 @@ import (
"context"
"tyapi-server/internal/application/user/dto/commands"
"tyapi-server/internal/application/user/dto/queries"
"tyapi-server/internal/application/user/dto/responses"
)
@@ -16,4 +17,8 @@ type UserApplicationService interface {
ResetPassword(ctx context.Context, cmd *commands.ResetPasswordCommand) error
GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error)
SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error
// 管理员功能
ListUsers(ctx context.Context, query *queries.ListUsersQuery) (*responses.UserListResponse, error)
GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error)
}

View File

@@ -9,6 +9,7 @@ import (
"tyapi-server/internal/application/user/dto/commands"
"tyapi-server/internal/application/user/dto/queries"
"tyapi-server/internal/application/user/dto/responses"
finance_service "tyapi-server/internal/domains/finance/services"
"tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/domains/user/events"
user_service "tyapi-server/internal/domains/user/services"
@@ -19,33 +20,33 @@ import (
// UserApplicationServiceImpl 用户应用服务实现
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
type UserApplicationServiceImpl struct {
userManagementService *user_service.UserManagementService
userAuthService *user_service.UserAuthService
smsCodeService *user_service.SMSCodeService
enterpriseService *user_service.EnterpriseService
eventBus interfaces.EventBus
jwtAuth *middleware.JWTAuthMiddleware
logger *zap.Logger
userAggregateService user_service.UserAggregateService
userAuthService *user_service.UserAuthService
smsCodeService *user_service.SMSCodeService
walletService finance_service.WalletAggregateService
eventBus interfaces.EventBus
jwtAuth *middleware.JWTAuthMiddleware
logger *zap.Logger
}
// NewUserApplicationService 创建用户应用服务
func NewUserApplicationService(
userManagementService *user_service.UserManagementService,
userAggregateService user_service.UserAggregateService,
userAuthService *user_service.UserAuthService,
smsCodeService *user_service.SMSCodeService,
enterpriseService *user_service.EnterpriseService,
walletService finance_service.WalletAggregateService,
eventBus interfaces.EventBus,
jwtAuth *middleware.JWTAuthMiddleware,
logger *zap.Logger,
) UserApplicationService {
return &UserApplicationServiceImpl{
userManagementService: userManagementService,
userAuthService: userAuthService,
smsCodeService: smsCodeService,
enterpriseService: enterpriseService,
eventBus: eventBus,
jwtAuth: jwtAuth,
logger: logger,
userAggregateService: userAggregateService,
userAuthService: userAuthService,
smsCodeService: smsCodeService,
walletService: walletService,
eventBus: eventBus,
jwtAuth: jwtAuth,
logger: logger,
}
}
@@ -58,7 +59,7 @@ func (s *UserApplicationServiceImpl) Register(ctx context.Context, cmd *commands
}
// 2. 创建用户
user, err := s.userManagementService.CreateUser(ctx, cmd.Phone, cmd.Password)
user, err := s.userAggregateService.CreateUser(ctx, cmd.Phone, cmd.Password)
if err != nil {
return nil, err
}
@@ -95,11 +96,11 @@ func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd
// 3. 如果是管理员,更新登录统计
if user.IsAdmin() {
if err := s.userManagementService.UpdateLoginStats(ctx, user.ID); err != nil {
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
s.logger.Error("更新登录统计失败", zap.Error(err))
}
// 重新获取用户信息以获取最新的登录统计
updatedUser, err := s.userManagementService.GetUserByID(ctx, user.ID)
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
if err != nil {
s.logger.Error("重新获取用户信息失败", zap.Error(err))
} else {
@@ -163,11 +164,11 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
// 4. 如果是管理员,更新登录统计
if user.IsAdmin() {
if err := s.userManagementService.UpdateLoginStats(ctx, user.ID); err != nil {
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
s.logger.Error("更新登录统计失败", zap.Error(err))
}
// 重新获取用户信息以获取最新的登录统计
updatedUser, err := s.userManagementService.GetUserByID(ctx, user.ID)
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
if err != nil {
s.logger.Error("重新获取用户信息失败", zap.Error(err))
} else {
@@ -236,7 +237,7 @@ func (s *UserApplicationServiceImpl) ResetPassword(ctx context.Context, cmd *com
// 业务流程1. 获取用户信息 2. 获取企业信息 3. 构建响应数据
func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error) {
// 1. 获取用户信息(包含企业信息)
user, err := s.enterpriseService.GetUserWithEnterpriseInfo(ctx, userID)
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
if err != nil {
return nil, err
}
@@ -258,6 +259,7 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
Username: user.Username,
UserType: user.UserType,
IsActive: user.Active,
IsCertified: user.IsCertified,
LastLoginAt: user.LastLoginAt,
LoginCount: user.LoginCount,
Permissions: permissions,
@@ -273,6 +275,9 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
UnifiedSocialCode: user.EnterpriseInfo.UnifiedSocialCode,
LegalPersonName: user.EnterpriseInfo.LegalPersonName,
LegalPersonID: user.EnterpriseInfo.LegalPersonID,
LegalPersonPhone: user.EnterpriseInfo.LegalPersonPhone,
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
EnterpriseEmail: user.EnterpriseInfo.EnterpriseEmail,
CreatedAt: user.EnterpriseInfo.CreatedAt,
UpdatedAt: user.EnterpriseInfo.UpdatedAt,
}
@@ -284,7 +289,7 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
// GetUser 获取用户信息
// 业务流程1. 获取用户信息 2. 构建响应数据
func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries.GetUserQuery) (*responses.UserProfileResponse, error) {
user, err := s.userManagementService.GetUserByID(ctx, query.UserID)
user, err := s.userAggregateService.GetUserByID(ctx, query.UserID)
if err != nil {
return nil, err
}
@@ -301,3 +306,78 @@ func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries
UpdatedAt: user.UpdatedAt,
}, nil
}
// ListUsers 获取用户列表(管理员功能)
// 业务流程1. 查询用户列表 2. 构建响应数据
func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queries.ListUsersQuery) (*responses.UserListResponse, error) {
// 1. 查询用户列表
users, total, err := s.userAggregateService.ListUsers(ctx, query.ToDomainQuery())
if err != nil {
return nil, err
}
// 2. 构建响应数据
items := make([]*responses.UserListItem, 0, len(users))
for _, user := range users {
item := &responses.UserListItem{
ID: user.ID,
Phone: user.Phone,
UserType: user.UserType,
Username: user.Username,
IsActive: user.Active,
IsCertified: user.IsCertified,
LoginCount: user.LoginCount,
LastLoginAt: user.LastLoginAt,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
// 添加企业信息
if user.EnterpriseInfo != nil {
item.EnterpriseInfo = &responses.EnterpriseInfoItem{
ID: user.EnterpriseInfo.ID,
CompanyName: user.EnterpriseInfo.CompanyName,
UnifiedSocialCode: user.EnterpriseInfo.UnifiedSocialCode,
LegalPersonName: user.EnterpriseInfo.LegalPersonName,
LegalPersonPhone: user.EnterpriseInfo.LegalPersonPhone,
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
EnterpriseEmail: user.EnterpriseInfo.EnterpriseEmail,
CreatedAt: user.EnterpriseInfo.CreatedAt,
}
}
// 添加钱包余额信息
wallet, err := s.walletService.LoadWalletByUserId(ctx, user.ID)
if err == nil && wallet != nil {
item.WalletBalance = wallet.Balance.String()
} else {
item.WalletBalance = "0"
}
items = append(items, item)
}
return &responses.UserListResponse{
Items: items,
Total: total,
Page: query.Page,
Size: query.PageSize,
}, nil
}
// GetUserStats 获取用户统计信息(管理员功能)
// 业务流程1. 查询用户统计信息 2. 构建响应数据
func (s *UserApplicationServiceImpl) GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error) {
// 1. 查询用户统计信息
stats, err := s.userAggregateService.GetUserStats(ctx)
if err != nil {
return nil, err
}
// 2. 构建响应数据
return &responses.UserStatsResponse{
TotalUsers: stats.TotalUsers,
ActiveUsers: stats.ActiveUsers,
CertifiedUsers: stats.CertifiedUsers,
}, nil
}