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

@@ -22,6 +22,7 @@ import (
// 产品域实体
productEntities "tyapi-server/internal/domains/product/entities"
apiEntities "tyapi-server/internal/domains/api/entities"
"tyapi-server/internal/infrastructure/database"
)
@@ -206,6 +207,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
&entities.User{},
&entities.SMSCode{},
&entities.EnterpriseInfo{},
&entities.ContractInfo{},
// 认证域
&certEntities.Certification{},
@@ -215,13 +217,20 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
// 财务域
&financeEntities.Wallet{},
&financeEntities.UserSecrets{},
&financeEntities.WalletTransaction{},
&financeEntities.RechargeRecord{},
&financeEntities.AlipayOrder{},
// 产品域
&productEntities.Product{},
&productEntities.ProductPackageItem{},
&productEntities.ProductCategory{},
&productEntities.Subscription{},
&productEntities.ProductDocumentation{},
&productEntities.ProductApiConfig{},
// api
&apiEntities.ApiUser{},
&apiEntities.ApiCall{},
)
}

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
}

View File

@@ -12,6 +12,7 @@ type Config struct {
Cache CacheConfig `mapstructure:"cache"`
Logger LoggerConfig `mapstructure:"logger"`
JWT JWTConfig `mapstructure:"jwt"`
API APIConfig `mapstructure:"api"`
SMS SMSConfig `mapstructure:"sms"`
Storage StorageConfig `mapstructure:"storage"`
OCR OCRConfig `mapstructure:"ocr"`
@@ -23,6 +24,12 @@ type Config struct {
App AppConfig `mapstructure:"app"`
WechatWork WechatWorkConfig `mapstructure:"wechat_work"`
Esign EsignConfig `mapstructure:"esign"`
Wallet WalletConfig `mapstructure:"wallet"`
WestDex WestDexConfig `mapstructure:"westdex"`
AliPay AliPayConfig `mapstructure:"alipay"`
Recharge RechargeConfig `mapstructure:"recharge"`
Yushan YushanConfig `mapstructure:"yushan"`
Domain DomainConfig `mapstructure:"domain"`
}
// ServerConfig HTTP服务器配置
@@ -141,6 +148,11 @@ type AppConfig struct {
Env string `mapstructure:"env"`
}
// APIConfig API配置
type APIConfig struct {
Domain string `mapstructure:"domain"`
}
// SMSConfig 短信配置
type SMSConfig struct {
AccessKeyID string `mapstructure:"access_key_id"`
@@ -218,11 +230,10 @@ type EsignConfig struct {
AppSecret string `mapstructure:"app_secret"` // 应用密钥
ServerURL string `mapstructure:"server_url"` // 服务器URL
TemplateID string `mapstructure:"template_id"` // 模板ID
Contract ContractConfig `mapstructure:"contract"` // 合同配置
Auth AuthConfig `mapstructure:"auth"` // 认证配置
Sign SignConfig `mapstructure:"sign"` // 签署配置
Notify NotifyConfig `mapstructure:"notify"` // 通知配置
}
// ContractConfig 合同配置
@@ -234,10 +245,11 @@ type ContractConfig struct {
// AuthConfig 认证配置
type AuthConfig struct {
OrgAuthModes []string `mapstructure:"org_auth_modes"` // 机构可用认证模式
DefaultAuthMode string `mapstructure:"default_auth_mode"` // 默认认证模式
PsnAuthModes []string `mapstructure:"psn_auth_modes"` // 个人可用认证模式
OrgAuthModes []string `mapstructure:"org_auth_modes"` // 机构可用认证模式
DefaultAuthMode string `mapstructure:"default_auth_mode"` // 默认认证模式
PsnAuthModes []string `mapstructure:"psn_auth_modes"` // 个人可用认证模式
WillingnessAuthModes []string `mapstructure:"willingness_auth_modes"` // 意愿认证模式
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
}
// SignConfig 签署配置
@@ -245,10 +257,46 @@ type SignConfig struct {
AutoFinish bool `mapstructure:"auto_finish"` // 是否自动完结
SignFieldStyle int `mapstructure:"sign_field_style"` // 签署区样式
ClientType string `mapstructure:"client_type"` // 客户端类型
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
}
// NotifyConfig 通知配置
type NotifyConfig struct {
Types string `mapstructure:"types"` // 通知类型
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
// WalletConfig 钱包配置
type WalletConfig struct {
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
}
// WestDexConfig WestDex配置
type WestDexConfig struct {
URL string `mapstructure:"url"`
Key string `mapstructure:"key"`
SecretId string `mapstructure:"secret_id"`
SecretSecondId string `mapstructure:"secret_second_id"`
}
// AliPayConfig 支付宝配置
type AliPayConfig struct {
AppID string `mapstructure:"app_id"`
PrivateKey string `mapstructure:"private_key"`
AlipayPublicKey string `mapstructure:"alipay_public_key"`
IsProduction bool `mapstructure:"is_production"`
NotifyURL string `mapstructure:"notify_url"`
ReturnURL string `mapstructure:"return_url"`
}
// RechargeConfig 充值配置
type RechargeConfig struct {
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
}
// YushanConfig 羽山配置
type YushanConfig struct {
URL string `mapstructure:"url"`
APIKey string `mapstructure:"api_key"`
AcctID string `mapstructure:"acct_id"`
}
// DomainConfig 域名配置
type DomainConfig struct {
API string `mapstructure:"api"` // API域名
}

View File

@@ -26,25 +26,35 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
BloomFilter: false,
AutoInvalidate: true,
InvalidateDelay: 100 * time.Millisecond,
// 配置启用缓存的表
EnabledTables: []string{
"users",
"products",
"product_categories",
"product",
"product_category",
"enterprise_info_submit_records",
"sms_codes",
"wallets",
"subscription",
"product_category",
"product_documentation",
"enterprise_infos",
"api_users",
// 添加更多需要缓存的表
},
// 配置禁用缓存的表(日志表等)
DisabledTables: []string{
"sms_codes", // 短信验证码变化频繁
"audit_logs", // 审计日志
"system_logs", // 系统日志
"operation_logs", // 操作日志
"audit_logs", // 审计日志
"system_logs", // 系统日志
"operation_logs", // 操作日志
"api_calls", // API调用日志表变化频繁不适合缓存
},
}
// 初始化全局缓存配置管理器
cache.InitCacheConfigManager(cacheConfig)
// 创建缓存插件
cachePlugin := cache.NewGormCachePlugin(cacheService, logger, cacheConfig)
@@ -54,7 +64,7 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
return err
}
logger.Info("GORM缓存插件已成功注册",
logger.Info("GORM缓存插件已成功注册",
zap.Duration("default_ttl", cacheConfig.DefaultTTL),
zap.Strings("enabled_tables", cacheConfig.EnabledTables),
zap.Strings("disabled_tables", cacheConfig.DisabledTables),
@@ -67,52 +77,67 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
func GetCacheConfig(cfg *config.Config) cache.CacheConfig {
// 生产环境配置
if cfg.Server.Mode == "release" {
return cache.CacheConfig{
DefaultTTL: 60 * time.Minute, // 生产环境延长缓存时间
cacheConfig := cache.CacheConfig{
DefaultTTL: 60 * time.Minute, // 生产环境延长缓存时间
TablePrefix: "prod_cache",
MaxCacheSize: 5000, // 生产环境增加缓存大小
CacheComplexSQL: false, // 生产环境不缓存复杂SQL
MaxCacheSize: 5000, // 生产环境增加缓存大小
CacheComplexSQL: false, // 生产环境不缓存复杂SQL
EnableStats: true,
EnableWarmup: true,
PenetrationGuard: true,
BloomFilter: true, // 生产环境启用布隆过滤器
BloomFilter: true, // 生产环境启用布隆过滤器
AutoInvalidate: true,
InvalidateDelay: 50 * time.Millisecond,
EnabledTables: []string{
"users", "products", "product_categories",
"enterprise_info_submit_records", "certifications",
"product_documentations",
"users",
"product",
"product_category",
"enterprise_info_submit_records",
"sms_codes",
},
DisabledTables: []string{
"sms_codes", "audit_logs", "system_logs",
"operation_logs", "sessions", "api_keys",
"api_calls", // API调用日志表变化频繁不适合缓存
},
}
// 初始化全局缓存配置管理器
cache.InitCacheConfigManager(cacheConfig)
return cacheConfig
}
// 开发环境配置
return cache.CacheConfig{
DefaultTTL: 10 * time.Minute, // 开发环境缩短缓存时间,便于测试
cacheConfig := cache.CacheConfig{
DefaultTTL: 10 * time.Minute, // 开发环境缩短缓存时间,便于测试
TablePrefix: "dev_cache",
MaxCacheSize: 500,
CacheComplexSQL: true, // 开发环境允许缓存复杂SQL便于调试
CacheComplexSQL: true, // 开发环境允许缓存复杂SQL便于调试
EnableStats: true,
EnableWarmup: false, // 开发环境关闭预热
PenetrationGuard: false, // 开发环境关闭穿透保护
EnableWarmup: false, // 开发环境关闭预热
PenetrationGuard: false, // 开发环境关闭穿透保护
BloomFilter: false,
AutoInvalidate: true,
InvalidateDelay: 200 * time.Millisecond,
EnabledTables: []string{
"users", "products", "product_categories",
"users",
"product",
"product_category",
"enterprise_info_submit_records",
"sms_codes",
},
DisabledTables: []string{
"sms_codes", "audit_logs",
"api_calls", // API调用日志表变化频繁不适合缓存
},
}
// 初始化全局缓存配置管理器
cache.InitCacheConfigManager(cacheConfig)
return cacheConfig
}
// CacheMetrics 缓存性能指标
@@ -136,7 +161,7 @@ func GetCacheMetrics(cacheService interfaces.CacheService) (*CacheMetrics, error
total := stats.Hits + stats.Misses
hitRate := float64(0)
missRate := float64(0)
if total > 0 {
hitRate = float64(stats.Hits) / float64(total) * 100
missRate = float64(stats.Misses) / float64(total) * 100
@@ -150,4 +175,4 @@ func GetCacheMetrics(cacheService interfaces.CacheService) (*CacheMetrics, error
CacheSize: stats.Memory,
CachedTables: int(stats.Keys),
}, nil
}
}

View File

@@ -2,7 +2,6 @@ package container
import (
"context"
"fmt"
"time"
"go.uber.org/fx"
@@ -15,6 +14,7 @@ import (
"tyapi-server/internal/application/user"
"tyapi-server/internal/config"
domain_certification_repo "tyapi-server/internal/domains/certification/repositories"
certification_service "tyapi-server/internal/domains/certification/services"
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
finance_service "tyapi-server/internal/domains/finance/services"
domain_product_repo "tyapi-server/internal/domains/product/repositories"
@@ -28,6 +28,8 @@ import (
"tyapi-server/internal/infrastructure/external/ocr"
"tyapi-server/internal/infrastructure/external/sms"
"tyapi-server/internal/infrastructure/external/storage"
"tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/yushan"
"tyapi-server/internal/infrastructure/http/handlers"
"tyapi-server/internal/infrastructure/http/routes"
shared_database "tyapi-server/internal/shared/database"
@@ -41,9 +43,9 @@ import (
"tyapi-server/internal/shared/metrics"
"tyapi-server/internal/shared/middleware"
sharedOCR "tyapi-server/internal/shared/ocr"
"tyapi-server/internal/shared/payment"
"tyapi-server/internal/shared/resilience"
"tyapi-server/internal/shared/saga"
sharedStorage "tyapi-server/internal/shared/storage"
"tyapi-server/internal/shared/tracing"
"tyapi-server/internal/shared/validator"
@@ -51,6 +53,11 @@ import (
user_repo "tyapi-server/internal/infrastructure/database/repositories/user"
"github.com/redis/go-redis/v9"
api_app "tyapi-server/internal/application/api"
domain_api_repo "tyapi-server/internal/domains/api/repositories"
api_service "tyapi-server/internal/domains/api/services"
api_repo "tyapi-server/internal/infrastructure/database/repositories/api"
)
// Container 应用容器
@@ -156,7 +163,6 @@ func NewContainer() *Container {
logger,
)
},
fx.As(new(sharedStorage.StorageService)),
),
// OCR服务
fx.Annotate(
@@ -169,19 +175,49 @@ func NewContainer() *Container {
},
fx.As(new(sharedOCR.OCRService)),
),
// e签宝服务
func(cfg *config.Config) *esign.Client {
esignConfig, err := esign.NewConfig(
// e签宝配置
func(cfg *config.Config) (*esign.Config, error) {
return esign.NewConfig(
cfg.Esign.AppID,
cfg.Esign.AppSecret,
cfg.Esign.ServerURL,
cfg.Esign.TemplateID,
&esign.EsignContractConfig{
Name: cfg.Esign.Contract.Name,
ExpireDays: cfg.Esign.Contract.ExpireDays,
RetryCount: cfg.Esign.Contract.RetryCount,
},
&esign.EsignAuthConfig{
OrgAuthModes: cfg.Esign.Auth.OrgAuthModes,
DefaultAuthMode: cfg.Esign.Auth.DefaultAuthMode,
PsnAuthModes: cfg.Esign.Auth.PsnAuthModes,
WillingnessAuthModes: cfg.Esign.Auth.WillingnessAuthModes,
RedirectUrl: cfg.Esign.Auth.RedirectURL,
},
&esign.EsignSignConfig{
AutoFinish: cfg.Esign.Sign.AutoFinish,
SignFieldStyle: cfg.Esign.Sign.SignFieldStyle,
ClientType: cfg.Esign.Sign.ClientType,
RedirectUrl: cfg.Esign.Sign.RedirectURL,
},
)
if err != nil {
panic(fmt.Sprintf("e签宝配置创建失败: %v", err))
}
},
// e签宝服务
func(esignConfig *esign.Config) *esign.Client {
return esign.NewClient(esignConfig)
},
// 支付宝支付服务
func(cfg *config.Config) *payment.AliPayService {
config := payment.AlipayConfig{
AppID: cfg.AliPay.AppID,
PrivateKey: cfg.AliPay.PrivateKey,
AlipayPublicKey: cfg.AliPay.AlipayPublicKey,
IsProduction: cfg.AliPay.IsProduction,
NotifyUrl: cfg.AliPay.NotifyURL,
ReturnURL: cfg.AliPay.ReturnURL,
}
return payment.NewAliPayService(config)
},
),
// 高级特性模块
@@ -210,6 +246,22 @@ func NewContainer() *Container {
fx.Provide(
sharedhttp.NewResponseBuilder,
validator.NewRequestValidator,
// WestDexService - 需要从配置中获取参数
func(cfg *config.Config) *westdex.WestDexService {
return westdex.NewWestDexService(
cfg.WestDex.URL,
cfg.WestDex.Key,
cfg.WestDex.SecretId,
cfg.WestDex.SecretSecondId,
)
},
func(cfg *config.Config) *yushan.YushanService {
return yushan.NewYushanService(
cfg.Yushan.URL,
cfg.Yushan.APIKey,
cfg.Yushan.AcctID,
)
},
sharedhttp.NewGinRouter,
),
@@ -224,6 +276,7 @@ func NewContainer() *Container {
middleware.NewJWTAuthMiddleware,
middleware.NewOptionalAuthMiddleware,
middleware.NewAdminAuthMiddleware,
middleware.NewDomainAuthMiddleware,
middleware.NewTraceIDMiddleware,
middleware.NewErrorTrackingMiddleware,
NewRequestBodyLoggerMiddlewareWrapper,
@@ -236,16 +289,22 @@ func NewContainer() *Container {
user_repo.NewGormUserRepository,
fx.As(new(domain_user_repo.UserRepository)),
),
// 企业信息仓储 - 同时注册具体类型和接口类型
fx.Annotate(
user_repo.NewGormEnterpriseInfoRepository,
fx.As(new(domain_user_repo.EnterpriseInfoRepository)),
),
// 短信验证码仓储 - 同时注册具体类型和接口类型
fx.Annotate(
user_repo.NewGormSMSCodeRepository,
fx.As(new(domain_user_repo.SMSCodeRepository)),
),
// 用户信息仓储 - 同时注册具体类型和接口类型
fx.Annotate(
user_repo.NewGormEnterpriseInfoRepository,
fx.As(new(domain_user_repo.EnterpriseInfoRepository)),
),
// 合同信息仓储 - 同时注册具体类型和接口类型
fx.Annotate(
user_repo.NewGormContractInfoRepository,
fx.As(new(domain_user_repo.ContractInfoRepository)),
),
),
// 仓储层 - 认证域
@@ -260,6 +319,11 @@ func NewContainer() *Container {
certification_repo.NewGormCertificationQueryRepository,
fx.As(new(domain_certification_repo.CertificationQueryRepository)),
),
// 企业信息提交记录仓储
fx.Annotate(
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
),
),
// 仓储层 - 财务域
@@ -269,10 +333,20 @@ func NewContainer() *Container {
finance_repo.NewGormWalletRepository,
fx.As(new(domain_finance_repo.WalletRepository)),
),
// 用户密钥仓储
// 钱包交易记录仓储
fx.Annotate(
finance_repo.NewGormUserSecretsRepository,
fx.As(new(domain_finance_repo.UserSecretsRepository)),
finance_repo.NewGormWalletTransactionRepository,
fx.As(new(domain_finance_repo.WalletTransactionRepository)),
),
// 充值记录仓储
fx.Annotate(
finance_repo.NewGormRechargeRecordRepository,
fx.As(new(domain_finance_repo.RechargeRecordRepository)),
),
// 支付宝订单仓储
fx.Annotate(
finance_repo.NewGormAlipayOrderRepository,
fx.As(new(domain_finance_repo.AlipayOrderRepository)),
),
),
@@ -293,18 +367,50 @@ func NewContainer() *Container {
product_repo.NewGormSubscriptionRepository,
fx.As(new(domain_product_repo.SubscriptionRepository)),
),
// 产品API配置仓储 - 同时注册具体类型和接口类型
fx.Annotate(
product_repo.NewGormProductApiConfigRepository,
fx.As(new(domain_product_repo.ProductApiConfigRepository)),
),
),
// API域仓储层
fx.Provide(
fx.Annotate(
api_repo.NewGormApiUserRepository,
fx.As(new(domain_api_repo.ApiUserRepository)),
),
fx.Annotate(
api_repo.NewGormApiCallRepository,
fx.As(new(domain_api_repo.ApiCallRepository)),
),
),
// 领域服务
fx.Provide(
user_service.NewUserManagementService,
user_service.NewUserAggregateService,
user_service.NewUserAuthService,
user_service.NewSMSCodeService,
user_service.NewEnterpriseService,
user_service.NewContractAggregateService,
product_service.NewProductManagementService,
product_service.NewProductSubscriptionService,
// 认证域的领域服务已经整合到应用服务中
finance_service.NewFinanceService,
product_service.NewProductApiConfigService,
finance_service.NewWalletAggregateService,
finance_service.NewRechargeRecordService,
certification_service.NewCertificationAggregateService,
certification_service.NewEnterpriseInfoSubmitRecordService,
),
// API域服务层
fx.Provide(
api_service.NewApiUserAggregateService,
api_service.NewApiCallAggregateService,
api_service.NewApiRequestService,
),
// API域应用服务
fx.Provide(
api_app.NewApiApplicationService,
),
// 应用服务
@@ -329,6 +435,11 @@ func NewContainer() *Container {
product.NewProductApplicationService,
fx.As(new(product.ProductApplicationService)),
),
// 产品API配置应用服务 - 绑定到接口
fx.Annotate(
product.NewProductApiConfigApplicationService,
fx.As(new(product.ProductApiConfigApplicationService)),
),
// 分类应用服务 - 绑定到接口
fx.Annotate(
product.NewCategoryApplicationService,
@@ -353,6 +464,8 @@ func NewContainer() *Container {
handlers.NewProductHandler,
// 产品管理员HTTP处理器
handlers.NewProductAdminHandler,
// API Handler
handlers.NewApiHandler,
),
// 路由注册
@@ -367,6 +480,8 @@ func NewContainer() *Container {
routes.NewProductRoutes,
// 产品管理员路由
routes.NewProductAdminRoutes,
// API路由
routes.NewApiRoutes,
),
// 应用生命周期
@@ -445,12 +560,16 @@ func RegisterRoutes(
financeRoutes *routes.FinanceRoutes,
productRoutes *routes.ProductRoutes,
productAdminRoutes *routes.ProductAdminRoutes,
apiRoutes *routes.ApiRoutes,
cfg *config.Config,
logger *zap.Logger,
) {
router.SetupDefaultRoutes()
// 注册所有路由
// api域名路由
apiRoutes.Register(router)
// 所有域名路由路由
userRoutes.Register(router)
certificationRoutes.Register(router)
financeRoutes.Register(router)

View File

@@ -0,0 +1,173 @@
package dto
type FLXG3D56Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
TimeRange string `json:"time_range" validate:"omitempty,validTimeRange"` // 非必填字段
}
type FLXG75FEReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXG0V3BReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXG0V4BReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
}
type FLXG54F5Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type FLXG162AReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXG0687Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type FLXG970FReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXG5876Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type FLXG9687Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXGC9D1Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXGCA3DReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type FLXGDEC7Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZ385EReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZ5733Req struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type IVYZ9363Req struct {
ManName string `json:"man_name" validate:"required,min=1,validName"`
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
}
type JRZQ0A03Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type JRZQ4AA8Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type JRZQ8203Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type JRZQDBCEReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
BankCard string `json:"bank_card" validate:"required,validBankCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type QYGL2ACDReq struct {
EntName string `json:"ent_name" validate:"required,min=1,validName"`
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
}
type QYGL6F2DReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type QYGL45BDReq struct {
EntName string `json:"ent_name" validate:"required,min=1,validName"`
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type QYGL8261Req struct {
EntName string `json:"ent_name" validate:"required,min=1,validName"`
}
type QYGL8271Req struct {
EntName string `json:"ent_name" validate:"required,min=1,validName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
}
type QYGLB4C0Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type YYSY4B37Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSY4B21Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSY6F2EReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
MobileType string `json:"mobile_type" validate:"omitempty,validMobileType"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type YYSY09CDReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
MobileType string `json:"mobile_type" validate:"omitempty,validMobileType"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZ0b03Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type YYSYBE08Req struct{
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type YYSYD50FReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type YYSYF7DBReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
StartDate string `json:"start_date" validate:"required,validDate" encrypt:"false"`
}
type IVYZ9A2BReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type COMB298YReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
}
type COMB86PMReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
}

View File

@@ -0,0 +1,196 @@
package entities
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"sync"
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// ApiCallStatus API调用状态
const (
ApiCallStatusPending = "pending"
ApiCallStatusSuccess = "success"
ApiCallStatusFailed = "failed"
)
// ApiCall错误类型常量定义供各领域服务和应用层统一引用。
// 使用时可通过 entities.ApiCallErrorInvalidAccess 方式获得,编辑器可自动补全和提示。
// 错误类型与业务含义:
//
// ApiCallErrorInvalidAccess = "invalid_access" // 无效AccessId
// ApiCallErrorFrozenAccount = "frozen_account" // 账户冻结
// ApiCallErrorInvalidIP = "invalid_ip" // IP无效
// ApiCallErrorArrears = "arrears" // 账户欠费
// ApiCallErrorNotSubscribed = "not_subscribed" // 未订阅产品
// ApiCallErrorProductNotFound = "product_not_found" // 产品不存在
// ApiCallErrorProductDisabled = "product_disabled" // 产品已停用
// ApiCallErrorSystem = "system_error" // 系统错误
// ApiCallErrorDatasource = "datasource_error" // 数据源异常
// ApiCallErrorInvalidParam = "invalid_param" // 参数不正确
// ApiCallErrorDecryptFail = "decrypt_fail" // 解密失败
const (
ApiCallErrorInvalidAccess = "invalid_access" // 无效AccessId
ApiCallErrorFrozenAccount = "frozen_account" // 账户冻结
ApiCallErrorInvalidIP = "invalid_ip" // IP无效
ApiCallErrorArrears = "arrears" // 账户欠费
ApiCallErrorNotSubscribed = "not_subscribed" // 未订阅产品
ApiCallErrorProductNotFound = "product_not_found" // 产品不存在
ApiCallErrorProductDisabled = "product_disabled" // 产品已停用
ApiCallErrorSystem = "system_error" // 系统错误
ApiCallErrorDatasource = "datasource_error" // 数据源异常
ApiCallErrorInvalidParam = "invalid_param" // 参数不正确
ApiCallErrorDecryptFail = "decrypt_fail" // 解密失败
ApiCallErrorQueryEmpty = "query_empty" // 查询为空
)
// ApiCall API调用聚合根
type ApiCall struct {
ID string `gorm:"type:varchar(64);primaryKey" json:"id"`
AccessId string `gorm:"type:varchar(64);not null;index" json:"access_id"`
UserId *string `gorm:"type:varchar(36);index" json:"user_id,omitempty"`
ProductId *string `gorm:"type:varchar(64);index" json:"product_id,omitempty"`
TransactionId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"transaction_id"`
ClientIp string `gorm:"type:varchar(64);not null;index" json:"client_ip"`
RequestParams string `gorm:"type:text" json:"request_params"`
ResponseData *string `gorm:"type:text" json:"response_data,omitempty"`
Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"`
StartAt time.Time `gorm:"not null;index" json:"start_at"`
EndAt *time.Time `gorm:"index" json:"end_at,omitempty"`
Cost *decimal.Decimal `gorm:"default:0" json:"cost,omitempty"`
ErrorType *string `gorm:"type:varchar(32)" json:"error_type,omitempty"`
ErrorMsg *string `gorm:"type:varchar(256)" json:"error_msg,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
// NewApiCall 工厂方法
func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
if accessId == "" {
return nil, errors.New("AccessId不能为空")
}
if requestParams == "" {
return nil, errors.New("请求参数不能为空")
}
if clientIp == "" {
return nil, errors.New("ClientIp不能为空")
}
return &ApiCall{
ID: uuid.New().String(),
AccessId: accessId,
TransactionId: GenerateTransactionID(),
ClientIp: clientIp,
RequestParams: requestParams,
Status: ApiCallStatusPending,
StartAt: time.Now(),
}, nil
}
// MarkSuccess 标记为成功
func (a *ApiCall) MarkSuccess(responseData string, cost decimal.Decimal) error {
// 校验除ErrorMsg和ErrorType外所有字段不能为空
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.RequestParams == "" || a.Status == "" || a.StartAt.IsZero() {
return errors.New("ApiCall字段不能为空除ErrorMsg和ErrorType")
}
// 可选字段也要有值
if a.UserId == nil || a.ProductId == nil || responseData == "" {
return errors.New("ApiCall标记成功时UserId、ProductId、ResponseData不能为空")
}
a.Status = ApiCallStatusSuccess
a.ResponseData = &responseData
endAt := time.Now()
a.EndAt = &endAt
a.Cost = &cost
a.ErrorType = nil
a.ErrorMsg = nil
return nil
}
// MarkFailed 标记为失败
func (a *ApiCall) MarkFailed(errorType, errorMsg string) {
a.Status = ApiCallStatusFailed
a.ErrorType = &errorType
shortMsg := errorMsg
if len(shortMsg) > 120 {
shortMsg = shortMsg[:120]
}
a.ErrorMsg = &shortMsg
endAt := time.Now()
a.EndAt = &endAt
}
// Validate 校验ApiCall聚合根的业务规则
func (a *ApiCall) Validate() error {
if a.ID == "" {
return errors.New("ID不能为空")
}
if a.AccessId == "" {
return errors.New("AccessId不能为空")
}
if a.TransactionId == "" {
return errors.New("TransactionId不能为空")
}
if a.RequestParams == "" {
return errors.New("请求参数不能为空")
}
if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed {
return errors.New("无效的调用状态")
}
return nil
}
// 全局计数器用于确保TransactionID的唯一性
var (
transactionCounter int64
counterMutex sync.Mutex
)
// GenerateTransactionID 生成16位数的交易单号
func GenerateTransactionID() string {
// 使用互斥锁确保计数器的线程安全
counterMutex.Lock()
transactionCounter++
currentCounter := transactionCounter
counterMutex.Unlock()
// 获取当前时间戳(微秒精度)
timestamp := time.Now().UnixMicro()
// 组合时间戳和计数器,确保唯一性
combined := fmt.Sprintf("%d%06d", timestamp, currentCounter%1000000)
// 如果长度超出16位截断如果不够填充随机字符
if len(combined) >= 16 {
return combined[:16]
}
// 如果长度不够,使用随机字节填充
if len(combined) < 16 {
randomBytes := make([]byte, 8)
rand.Read(randomBytes)
randomHex := hex.EncodeToString(randomBytes)
combined += randomHex[:16-len(combined)]
}
return combined
}
// TableName 指定数据库表名
func (ApiCall) TableName() string {
return "api_calls"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (c *ApiCall) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}

View File

@@ -0,0 +1,193 @@
package entities
import (
"crypto/rand"
"encoding/hex"
"errors"
"io"
"net"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ApiUserStatus API用户状态
const (
ApiUserStatusNormal = "normal"
ApiUserStatusFrozen = "frozen"
)
// ApiUser API用户聚合根
type ApiUser struct {
ID string `gorm:"primaryKey;type:varchar(64)" json:"id"`
UserId string `gorm:"type:varchar(36);not null;uniqueIndex" json:"user_id"`
AccessId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"access_id"`
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
WhiteList []string `gorm:"type:json;serializer:json;default:'[]'" json:"white_list"` // 支持多个白名单
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
// IsWhiteListed 校验IP/域名是否在白名单
func (u *ApiUser) IsWhiteListed(target string) bool {
for _, w := range u.WhiteList {
if w == target {
return true
}
}
return false
}
// IsActive 是否可用
func (u *ApiUser) IsActive() bool {
return u.Status == ApiUserStatusNormal
}
// IsFrozen 是否冻结
func (u *ApiUser) IsFrozen() bool {
return u.Status == ApiUserStatusFrozen
}
// NewApiUser 工厂方法
func NewApiUser(userId string) (*ApiUser, error) {
if userId == "" {
return nil, errors.New("用户ID不能为空")
}
accessId, err := GenerateSecretId()
if err != nil {
return nil, err
}
secretKey, err := GenerateSecretKey()
if err != nil {
return nil, err
}
return &ApiUser{
ID: uuid.New().String(),
UserId: userId,
AccessId: accessId,
SecretKey: secretKey,
Status: ApiUserStatusNormal,
WhiteList: []string{},
}, nil
}
// 领域行为
func (u *ApiUser) Freeze() {
u.Status = ApiUserStatusFrozen
}
func (u *ApiUser) Unfreeze() {
u.Status = ApiUserStatusNormal
}
func (u *ApiUser) UpdateWhiteList(list []string) {
u.WhiteList = list
}
// AddToWhiteList 新增白名单项(防御性校验)
func (u *ApiUser) AddToWhiteList(entry string) error {
if len(u.WhiteList) >= 10 {
return errors.New("白名单最多只能有10个")
}
if net.ParseIP(entry) == nil {
return errors.New("非法IP")
}
for _, w := range u.WhiteList {
if w == entry {
return errors.New("白名单已存在")
}
}
u.WhiteList = append(u.WhiteList, entry)
return nil
}
// BeforeUpdate GORM钩子更新前确保WhiteList不为nil
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
if u.WhiteList == nil {
u.WhiteList = []string{}
}
return nil
}
// RemoveFromWhiteList 删除白名单项
func (u *ApiUser) RemoveFromWhiteList(entry string) error {
newList := make([]string, 0, len(u.WhiteList))
for _, w := range u.WhiteList {
if w != entry {
newList = append(newList, w)
}
}
if len(newList) == len(u.WhiteList) {
return errors.New("白名单不存在")
}
u.WhiteList = newList
return nil
}
// Validate 校验ApiUser聚合根的业务规则
func (u *ApiUser) Validate() error {
if u.UserId == "" {
return errors.New("用户ID不能为空")
}
if u.AccessId == "" {
return errors.New("AccessId不能为空")
}
if u.SecretKey == "" {
return errors.New("SecretKey不能为空")
}
switch u.Status {
case ApiUserStatusNormal, ApiUserStatusFrozen:
// ok
default:
return errors.New("无效的用户状态")
}
if len(u.WhiteList) > 10 {
return errors.New("白名单最多只能有10个")
}
for _, ip := range u.WhiteList {
if net.ParseIP(ip) == nil {
return errors.New("白名单项必须为合法IP地址: " + ip)
}
}
return nil
}
// 生成AES-128密钥的函数符合市面规范
func GenerateSecretKey() (string, error) {
key := make([]byte, 16) // 16字节密钥
_, err := io.ReadFull(rand.Reader, key)
if err != nil {
return "", err
}
return hex.EncodeToString(key), nil
}
func GenerateSecretId() (string, error) {
// 创建一个字节数组,用于存储随机数据
bytes := make([]byte, 8) // 因为每个字节表示两个16进制字符
// 读取随机字节到数组中
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
// 将字节数组转换为16进制字符串
return hex.EncodeToString(bytes), nil
}
// TableName 指定数据库表名
func (ApiUser) TableName() string {
return "api_users"
}
// BeforeCreate GORM钩子创建前自动生成UUID并确保WhiteList不为nil
func (c *ApiUser) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
if c.WhiteList == nil {
c.WhiteList = []string{}
}
return nil
}

View File

@@ -0,0 +1,29 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/api/entities"
"tyapi-server/internal/shared/interfaces"
)
type ApiCallRepository interface {
Create(ctx context.Context, call *entities.ApiCall) error
Update(ctx context.Context, call *entities.ApiCall) error
FindById(ctx context.Context, id string) (*entities.ApiCall, error)
FindByUserId(ctx context.Context, userId string, limit, offset int) ([]*entities.ApiCall, error)
// 新增分页查询用户API调用记录
ListByUserId(ctx context.Context, userId string, options interfaces.ListOptions) ([]*entities.ApiCall, int64, error)
// 新增根据条件筛选API调用记录
ListByUserIdWithFilters(ctx context.Context, userId string, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.ApiCall, int64, error)
// 新增根据条件筛选API调用记录包含产品名称
ListByUserIdWithFiltersAndProductName(ctx context.Context, userId string, filters map[string]interface{}, options interfaces.ListOptions) (map[string]string, []*entities.ApiCall, int64, error)
// 新增统计用户API调用次数
CountByUserId(ctx context.Context, userId string) (int64, error)
// 新增根据TransactionID查询
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
}

View File

@@ -0,0 +1,13 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/api/entities"
)
type ApiUserRepository interface {
Create(ctx context.Context, user *entities.ApiUser) error
Update(ctx context.Context, user *entities.ApiUser) error
FindByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error)
FindByUserId(ctx context.Context, userId string) (*entities.ApiUser, error)
}

View File

@@ -0,0 +1,69 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/domains/api/entities"
repo "tyapi-server/internal/domains/api/repositories"
"gorm.io/gorm"
)
// ApiCallAggregateService 聚合服务管理ApiCall生命周期
type ApiCallAggregateService interface {
CreateApiCall(accessId, requestParams, clientIp string) (*entities.ApiCall, error)
LoadApiCall(ctx context.Context, id string) (*entities.ApiCall, error)
SaveApiCall(ctx context.Context, call *entities.ApiCall) error
}
type ApiCallAggregateServiceImpl struct {
apiUserRepo repo.ApiUserRepository
apiCallRepo repo.ApiCallRepository
}
func NewApiCallAggregateService(apiUserRepo repo.ApiUserRepository, apiCallRepo repo.ApiCallRepository) ApiCallAggregateService {
return &ApiCallAggregateServiceImpl{
apiUserRepo: apiUserRepo,
apiCallRepo: apiCallRepo,
}
}
// NewApiCall 创建ApiCall
func (s *ApiCallAggregateServiceImpl) CreateApiCall(accessId, requestParams, clientIp string) (*entities.ApiCall, error) {
return entities.NewApiCall(accessId, requestParams, clientIp)
}
// GetApiCallById 查询ApiCall
func (s *ApiCallAggregateServiceImpl) LoadApiCall(ctx context.Context, id string) (*entities.ApiCall, error) {
return s.apiCallRepo.FindById(ctx, id)
}
// SaveApiCall 保存ApiCall
func (s *ApiCallAggregateServiceImpl) SaveApiCall(ctx context.Context, call *entities.ApiCall) error {
// 先尝试查找现有记录
existingCall, err := s.apiCallRepo.FindById(ctx, call.ID)
if err != nil {
if err == gorm.ErrRecordNotFound {
// 记录不存在,执行创建
err = s.apiCallRepo.Create(ctx, call)
if err != nil {
return fmt.Errorf("创建ApiCall失败: %w", err)
}
return nil
}
// 其他错误
return fmt.Errorf("查询ApiCall失败: %w", err)
}
// 记录存在,执行更新
if existingCall != nil {
err = s.apiCallRepo.Update(ctx, call)
if err != nil {
return fmt.Errorf("更新ApiCall失败: %w", err)
}
return nil
}
// 理论上不会到达这里,但为了安全起见
return s.apiCallRepo.Create(ctx, call)
}

View File

@@ -0,0 +1,135 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/application/api/commands"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/domains/api/services/processors/comb"
"tyapi-server/internal/domains/api/services/processors/flxg"
"tyapi-server/internal/domains/api/services/processors/ivyz"
"tyapi-server/internal/domains/api/services/processors/jrzq"
"tyapi-server/internal/domains/api/services/processors/qygl"
"tyapi-server/internal/domains/api/services/processors/yysy"
"tyapi-server/internal/domains/product/services"
"tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/yushan"
"tyapi-server/internal/shared/interfaces"
)
var (
ErrDatasource = processors.ErrDatasource
ErrSystem = processors.ErrSystem
ErrInvalidParam = processors.ErrInvalidParam
ErrNotFound = processors.ErrNotFound
)
type ApiRequestService struct {
// 可注入依赖,如第三方服务、模型等
westDexService *westdex.WestDexService
yushanService *yushan.YushanService
validator interfaces.RequestValidator
processorDeps *processors.ProcessorDependencies
combService *comb.CombService
}
func NewApiRequestService(
westDexService *westdex.WestDexService,
yushanService *yushan.YushanService,
validator interfaces.RequestValidator,
productManagementService *services.ProductManagementService,
) *ApiRequestService {
// 创建组合包服务
combService := comb.NewCombService(productManagementService)
// 创建处理器依赖容器
processorDeps := processors.NewProcessorDependencies(westDexService, yushanService, validator, combService)
// 统一注册所有处理器
registerAllProcessors(combService)
return &ApiRequestService{
westDexService: westDexService,
yushanService: yushanService,
validator: validator,
processorDeps: processorDeps,
combService: combService,
}
}
// registerAllProcessors 统一注册所有处理器
func registerAllProcessors(combService *comb.CombService) {
// 定义所有处理器映射
processorMap := map[string]processors.ProcessorFunc{
// FLXG系列处理器
"FLXG0V3B": flxg.ProcessFLXG0V3Bequest,
"FLXG0V4B": flxg.ProcessFLXG0V4BRequest,
"FLXG162A": flxg.ProcessFLXG162ARequest,
"FLXG3D56": flxg.ProcessFLXG3D56Request,
"FLXG54F5": flxg.ProcessFLXG54F5Request,
"FLXG5876": flxg.ProcessFLXG5876Request,
"FLXG75FE": flxg.ProcessFLXG75FERequest,
"FLXG9687": flxg.ProcessFLXG9687Request,
"FLXG970F": flxg.ProcessFLXG970FRequest,
"FLXGC9D1": flxg.ProcessFLXGC9D1Request,
"FLXGCA3D": flxg.ProcessFLXGCA3DRequest,
"FLXGDEC7": flxg.ProcessFLXGDEC7Request,
// JRZQ系列处理器
"JRZQ8203": jrzq.ProcessJRZQ8203Request,
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
// QYGL系列处理器
"QYGL8261": qygl.ProcessQYGL8261Request,
"QYGL2ACD": qygl.ProcessQYGL2ACDRequest,
"QYGL45BD": qygl.ProcessQYGL45BDRequest,
"QYGL6F2D": qygl.ProcessQYGL6F2DRequest,
"QYGL8271": qygl.ProcessQYGL8271Request,
"QYGLB4C0": qygl.ProcessQYGLB4C0Request,
// YYSY系列处理器
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
"YYSY09CD": yysy.ProcessYYSY09CDRequest,
"YYSY4B21": yysy.ProcessYYSY4B21Request,
"YYSY4B37": yysy.ProcessYYSY4B37Request,
"YYSY6F2E": yysy.ProcessYYSY6F2ERequest,
"YYSYBE08": yysy.ProcessYYSYBE08Request,
"YYSYF7DB": yysy.ProcessYYSYF7DBRequest,
// IVYZ系列处理器
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
"IVYZ2125": ivyz.ProcessIVYZ2125Request,
"IVYZ385E": ivyz.ProcessIVYZ385ERequest,
"IVYZ5733": ivyz.ProcessIVYZ5733Request,
"IVYZ9363": ivyz.ProcessIVYZ9363Request,
"IVYZ9A2B": ivyz.ProcessIVYZ9A2BRequest,
"IVYZADEE": ivyz.ProcessIVYZADEERequest,
// COMB系列处理器
"COMB298Y": comb.ProcessCOMB298YRequest,
}
// 批量注册到组合包服务
for apiCode, processor := range processorMap {
combService.RegisterProcessor(apiCode, processor)
}
// 同时设置全局处理器映射
RequestProcessors = processorMap
}
// 注册API处理器 - 现在通过registerAllProcessors统一管理
var RequestProcessors map[string]processors.ProcessorFunc
// PreprocessRequestApi 调用指定的请求处理函数
func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode string, params []byte, options *commands.ApiCallOptions) ([]byte, error) {
if processor, exists := RequestProcessors[apiCode]; exists {
// 设置Options到依赖容器
depsWithOptions := a.processorDeps.WithOptions(options)
return processor(ctx, params, depsWithOptions)
}
return nil, fmt.Errorf("%w: %s", ErrSystem, "api请求, 未找到相应的处理程序")
}

View File

@@ -0,0 +1,124 @@
package services
import (
"context"
"tyapi-server/internal/domains/api/entities"
repo "tyapi-server/internal/domains/api/repositories"
)
type ApiUserAggregateService interface {
CreateApiUser(ctx context.Context, apiUserId string) error
UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error
AddToWhiteList(ctx context.Context, apiUserId string, entry string) error
RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error
FreezeApiUser(ctx context.Context, apiUserId string) error
UnfreezeApiUser(ctx context.Context, apiUserId string) error
LoadApiUserByUserId(ctx context.Context, apiUserId string) (*entities.ApiUser, error)
LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error)
SaveApiUser(ctx context.Context, apiUser *entities.ApiUser) error
}
type ApiUserAggregateServiceImpl struct {
repo repo.ApiUserRepository
}
func NewApiUserAggregateService(repo repo.ApiUserRepository) ApiUserAggregateService {
return &ApiUserAggregateServiceImpl{repo: repo}
}
func (s *ApiUserAggregateServiceImpl) CreateApiUser(ctx context.Context, apiUserId string) error {
apiUser, err := entities.NewApiUser(apiUserId)
if err != nil {
return err
}
if err := apiUser.Validate(); err != nil {
return err
}
return s.repo.Create(ctx, apiUser)
}
func (s *ApiUserAggregateServiceImpl) UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error {
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
if err != nil {
return err
}
apiUser.UpdateWhiteList(whiteList)
return s.repo.Update(ctx, apiUser)
}
func (s *ApiUserAggregateServiceImpl) AddToWhiteList(ctx context.Context, apiUserId string, entry string) error {
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
if err != nil {
return err
}
err = apiUser.AddToWhiteList(entry)
if err != nil {
return err
}
return s.repo.Update(ctx, apiUser)
}
func (s *ApiUserAggregateServiceImpl) RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error {
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
if err != nil {
return err
}
err = apiUser.RemoveFromWhiteList(entry)
if err != nil {
return err
}
return s.repo.Update(ctx, apiUser)
}
func (s *ApiUserAggregateServiceImpl) FreezeApiUser(ctx context.Context, apiUserId string) error {
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
if err != nil {
return err
}
apiUser.Freeze()
return s.repo.Update(ctx, apiUser)
}
func (s *ApiUserAggregateServiceImpl) UnfreezeApiUser(ctx context.Context, apiUserId string) error {
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
if err != nil {
return err
}
apiUser.Unfreeze()
return s.repo.Update(ctx, apiUser)
}
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
return s.repo.FindByAccessId(ctx, accessId)
}
func (s *ApiUserAggregateServiceImpl) LoadApiUserByUserId(ctx context.Context, apiUserId string) (*entities.ApiUser, error) {
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
if err != nil {
return nil, err
}
// 确保WhiteList不为nil
if apiUser.WhiteList == nil {
apiUser.WhiteList = []string{}
}
return apiUser, nil
}
func (s *ApiUserAggregateServiceImpl) SaveApiUser(ctx context.Context, apiUser *entities.ApiUser) error {
exists, err := s.repo.FindByUserId(ctx, apiUser.UserId)
if err != nil {
return err
}
if exists != nil {
// 确保WhiteList不为nil
if apiUser.WhiteList == nil {
apiUser.WhiteList = []string{}
}
return s.repo.Update(ctx, apiUser)
} else {
return s.repo.Create(ctx, apiUser)
}
}

View File

@@ -0,0 +1,195 @@
# Options 使用指南
## 概述
Options 机制允许在 API 调用时传递额外的配置选项,这些选项会传递给所有处理器(包括组合包处理器和子处理器),实现灵活的配置控制。
## 架构设计
```
ApiCallCommand.Options → api_application_service → api_request_service → 所有处理器
组合包处理器 → 子处理器
```
## Options 字段说明
```go
type ApiCallOptions struct {
Json bool `json:"json,omitempty"` // 是否返回JSON格式
Debug bool `json:"debug,omitempty"` // 调试模式
Timeout int `json:"timeout,omitempty"` // 超时时间(秒)
RetryCount int `json:"retry_count,omitempty"` // 重试次数
Async bool `json:"async,omitempty"` // 异步处理
Priority int `json:"priority,omitempty"` // 优先级(1-10)
Cache bool `json:"cache,omitempty"` // 是否使用缓存
Compress bool `json:"compress,omitempty"` // 是否压缩响应
Encrypt bool `json:"encrypt,omitempty"` // 是否加密响应
Validate bool `json:"validate,omitempty"` // 是否严格验证
LogLevel string `json:"log_level,omitempty"` // 日志级别
}
```
## 使用示例
### 1. 普通处理器中使用 Options
```go
func ProcessYYSY4B37Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
// ... 参数验证 ...
reqData := map[string]interface{}{
"mobile": paramsDto.Mobile,
}
// 使用 Options 调整请求参数
if deps.Options != nil {
if deps.Options.Timeout > 0 {
reqData["timeout"] = deps.Options.Timeout
}
if deps.Options.RetryCount > 0 {
reqData["retry_count"] = deps.Options.RetryCount
}
if deps.Options.Debug {
reqData["debug"] = true
}
}
return deps.YushanService.CallAPI("YYSY4B37", reqData)
}
```
### 2. 组合包处理器中使用 Options
```go
func ProcessCOMB298YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
// ... 参数验证 ...
// 根据 Options 调整组合策略
if deps.Options != nil {
if deps.Options.Priority > 0 {
// 高优先级时调整处理策略
fmt.Printf("组合包处理优先级: %d\n", deps.Options.Priority)
}
if deps.Options.Debug {
fmt.Printf("组合包调试模式开启\n")
}
}
// Options 会自动传递给所有子处理器
return deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB298Y")
}
```
### 3. 客户端调用示例
```json
{
"data": "加密的请求数据",
"options": {
"debug": true,
"timeout": 30,
"retry_count": 3,
"priority": 5,
"cache": true,
"log_level": "debug"
}
}
```
## 最佳实践
### 1. 空值检查
始终检查 `deps.Options` 是否为 `nil`,避免空指针异常:
```go
if deps.Options != nil {
// 使用 Options
}
```
### 2. 默认值处理
为 Options 字段提供合理的默认值:
```go
timeout := 30 // 默认30秒
if deps.Options != nil && deps.Options.Timeout > 0 {
timeout = deps.Options.Timeout
}
```
### 3. 验证选项值
对 Options 中的值进行验证:
```go
if deps.Options != nil {
if deps.Options.Priority < 1 || deps.Options.Priority > 10 {
return nil, fmt.Errorf("优先级必须在1-10之间")
}
}
```
### 4. 日志记录
在调试模式下记录 Options 信息:
```go
if deps.Options != nil && deps.Options.Debug {
log.Printf("处理器选项: %+v", deps.Options)
}
```
## 扩展性
### 1. 添加新的 Options 字段
`ApiCallOptions` 结构体中添加新字段:
```go
type ApiCallOptions struct {
// ... 现有字段 ...
CustomField string `json:"custom_field,omitempty"` // 自定义字段
}
```
### 2. 处理器特定选项
可以为特定处理器创建专门的选项结构:
```go
type YYSYOptions struct {
ApiCallOptions
YYSYSpecific bool `json:"yysy_specific,omitempty"`
}
```
## 注意事项
1. **性能影响**: Options 传递会增加少量性能开销,但影响微乎其微
2. **向后兼容**: 新增的 Options 字段应该使用 `omitempty` 标签
3. **安全性**: 敏感配置不应该通过 Options 传递
4. **文档化**: 新增 Options 字段时应该更新文档
## 调试技巧
### 1. 启用调试模式
```json
{
"options": {
"debug": true,
"log_level": "debug"
}
}
```
### 2. 查看 Options 传递
在处理器中添加日志:
```go
if deps.Options != nil {
log.Printf("处理器 %s 收到选项: %+v", "YYSY4B37", deps.Options)
}
```
### 3. 组合包调试
组合包处理器会自动将 Options 传递给所有子处理器,无需额外配置。

View File

@@ -0,0 +1,155 @@
# API处理器架构说明
## 概述
本目录实现了基于依赖注入容器的API处理器架构支持灵活的依赖管理和组合调用模式。
## 架构模式
### 1. 依赖注入容器模式
#### ProcessorDependencies
```go
type ProcessorDependencies struct {
WestDexService *westdex.WestDexService
YushanService *yushan.YushanService
Validator interfaces.RequestValidator
}
```
**优势:**
- 统一的依赖管理
- 类型安全的依赖注入
- 易于测试和mock
- 支持未来扩展新的服务
#### 处理器函数签名
```go
type ProcessorFunc func(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error)
```
### 2. 组合处理器模式
#### CompositeProcessor
专门用于处理组合包COMB系列的处理器支持
- 动态注册其他处理器
- 批量调用多个处理器
- 结果组合和格式化
```go
type CompositeProcessor struct {
processors map[string]ProcessorFunc
deps *ProcessorDependencies
}
```
## 目录结构
```
processors/
├── dependencies.go # 依赖容器定义
├── comb/
│ ├── comb_processor.go # 组合处理器基类
│ └── comb298y_processor.go # COMB298Y组合处理器
├── flxg/ # FLXG系列处理器
├── jrzq/ # JRZQ系列处理器
├── qygl/ # QYGL系列处理器
├── yysy/ # YYSY系列处理器
└── ivyz/ # IVYZ系列处理器
```
## 服务分配策略
### WestDexService
- FLXG系列使用WestDexService
- JRZQ系列使用WestDexService
- IVYZ系列使用WestDexService
### YushanService
- QYGL系列使用YushanService
- YYSY系列使用YushanService
### 组合包COMB
- 调用多个其他处理器
- 组合结果并返回统一格式
## 使用示例
### 普通处理器
```go
func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error) {
// 参数验证
var paramsDto dto.FLXG0V3BequestReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("参数校验不正确: 解密后的数据格式错误")
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("参数校验不正确: %s", err.Error())
}
// 调用服务
reqData := map[string]interface{}{
"name": paramsDto.Name,
"idCard": paramsDto.IDCard,
"mobile": paramsDto.Mobile,
}
respBytes, err := deps.WestDexService.CallAPI("FLXG0V3B", reqData)
if err != nil {
return nil, fmt.Errorf("调用外部服务失败: %s", err.Error())
}
return respBytes, nil
}
```
### 组合处理器
```go
func ProcessCOMB298YRequest(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error) {
// 创建组合处理器
compositeProcessor := NewCompositeProcessor(deps)
// 注册需要调用的处理器
compositeProcessor.RegisterProcessor("FLXG0V3B", flxg.ProcessFLXG0V3Bequest)
compositeProcessor.RegisterProcessor("JRZQ8203", jrzq.ProcessJRZQ8203Request)
// 调用并组合结果
results := make(map[string]interface{})
flxgResult, err := compositeProcessor.CallProcessor(ctx, "FLXG0V3B", params)
if err != nil {
return nil, fmt.Errorf("调用FLXG0V3B处理器失败: %s", err.Error())
}
results["flxg0v3b"] = string(flxgResult)
// 返回组合结果
return compositeProcessor.CombineResults(results)
}
```
## 扩展指南
### 添加新的服务依赖
1.`ProcessorDependencies` 中添加新字段
2. 更新 `NewProcessorDependencies` 构造函数
3.`ApiRequestService` 中注入新服务
### 添加新的处理器
1. 在对应目录下创建新的处理器文件
2. 实现 `ProcessorFunc` 接口
3.`RequestProcessors` 映射中注册
### 添加新的组合包
1.`comb/` 目录下创建新的组合处理器
2. 使用 `CompositeProcessor` 基类
3. 注册需要调用的处理器并组合结果
## 优势
1. **解耦**:处理器与具体服务实现解耦
2. **可测试**:易于进行单元测试和集成测试
3. **可扩展**:支持添加新的服务和处理器
4. **类型安全**:编译时检查依赖关系
5. **组合支持**:灵活的组合调用模式
6. **维护性**:清晰的代码结构和职责分离

View File

@@ -0,0 +1,117 @@
# 处理器文件更新总结
## 更新概述
已成功将所有36个API处理器文件更新为使用新的依赖注入容器模式。
## 更新统计
### 已更新的处理器文件总数36个
#### FLXG系列 (12个)
- ✅ flxg0v3b_processor.go
- ✅ flxg0v4b_processor.go
- ✅ flxg162a_processor.go
- ✅ flxg3d56_processor.go
- ✅ flxg54f5_processor.go
- ✅ flxg5876_processor.go
- ✅ flxg75fe_processor.go
- ✅ flxg9687_processor.go
- ✅ flxg970f_processor.go
- ✅ flxgc9d1_processor.go
- ✅ flxgca3d_processor.go
- ✅ flxgdec7_processor.go
#### JRZQ系列 (4个)
- ✅ jrzq8203_processor.go
- ✅ jrzq0a03_processor.go
- ✅ jrzq4aa8_processor.go
- ✅ jrzqdcbe_processor.go
#### QYGL系列 (6个)
- ✅ qygl8261_processor.go
- ✅ qygl2acd_processor.go
- ✅ qygl45bd_processor.go
- ✅ qygl6f2d_processor.go
- ✅ qygl8271_processor.go
- ✅ qyglb4c0_processor.go
#### YYSY系列 (7个)
- ✅ yysyd50f_processor.go
- ✅ yysy09cd_processor.go
- ✅ yysy4b21_processor.go
- ✅ yysy4b37_processor.go
- ✅ yysy6f2e_processor.go
- ✅ yysybe08_processor.go
- ✅ yysyf7db_processor.go
#### IVYZ系列 (7个)
- ✅ ivyz0b03_processor.go
- ✅ ivyz2125_processor.go
- ✅ ivyz385e_processor.go
- ✅ ivyz5733_processor.go
- ✅ ivyz9363_processor.go
- ✅ ivyz9a2b_processor.go
- ✅ ivyzadee_processor.go
#### COMB系列 (1个)
- ✅ comb298y_processor.go (组合处理器)
## 更新内容
### 1. 函数签名更新
所有处理器函数的签名已从:
```go
func ProcessXXXRequest(ctx context.Context, params []byte, validator interfaces.RequestValidator) ([]byte, error)
```
更新为:
```go
func ProcessXXXRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error)
```
### 2. 导入更新
- 移除了 `"tyapi-server/internal/shared/interfaces"` 导入
- 添加了 `"tyapi-server/internal/domains/api/services/processors"` 导入
### 3. 验证器调用更新
-`validator.ValidateStruct(paramsDto)`
- 更新为 `deps.Validator.ValidateStruct(paramsDto)`
### 4. 服务调用实现
根据API前缀分配不同的服务
#### WestDexService (FLXG, JRZQ, IVYZ系列)
```go
respBytes, err := deps.WestDexService.CallAPI("API_CODE", reqData)
```
#### YushanService (QYGL, YYSY系列)
```go
respBytes, err := deps.WestDexService.CallAPI("API_CODE", reqData)
```
### 5. 组合处理器
COMB298Y处理器实现了组合调用模式
- 使用 `CompositeProcessor` 基类
- 动态注册其他处理器
- 组合多个处理器的结果
## 架构优势
1. **统一依赖管理**:所有处理器通过 `ProcessorDependencies` 容器访问依赖
2. **类型安全**:编译时检查依赖关系
3. **易于测试**可以轻松mock依赖进行单元测试
4. **可扩展性**:新增服务只需在容器中添加
5. **组合支持**COMB系列支持灵活的组合调用
6. **维护性**:清晰的代码结构和职责分离
## 编译验证
✅ 项目编译成功,无语法错误
## 下一步建议
1. **单元测试**:为各个处理器编写单元测试
2. **集成测试**测试实际的API调用流程
3. **性能测试**:验证新架构的性能表现
4. **文档完善**补充API文档和使用说明

View File

@@ -0,0 +1,26 @@
package comb
import (
"context"
"encoding/json"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
)
// ProcessCOMB298YRequest COMB298Y API处理方法
func ProcessCOMB298YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.COMB298YReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
// 调用组合包服务处理请求
// Options会自动传递给所有子处理器
return deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB298Y")
}

View File

@@ -0,0 +1,26 @@
package comb
import (
"context"
"encoding/json"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
)
// ProcessCOMB86PMRequest COMB86PM API处理方法
func ProcessCOMB86PMRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.COMB86PMReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
// 调用组合包服务处理请求
// Options会自动传递给所有子处理器
return deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB86PM")
}

View File

@@ -0,0 +1,166 @@
package comb
import (
"context"
"encoding/json"
"fmt"
"sort"
"sync"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/services"
)
// CombService 组合包服务
type CombService struct {
productManagementService *services.ProductManagementService
processorRegistry map[string]processors.ProcessorFunc
}
// NewCombService 创建组合包服务
func NewCombService(productManagementService *services.ProductManagementService) *CombService {
return &CombService{
productManagementService: productManagementService,
processorRegistry: make(map[string]processors.ProcessorFunc),
}
}
// RegisterProcessor 注册处理器
func (cs *CombService) RegisterProcessor(apiCode string, processor processors.ProcessorFunc) {
cs.processorRegistry[apiCode] = processor
}
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) ([]byte, error) {
// 1. 根据组合包code获取产品信息
packageProduct, err := cs.productManagementService.GetProductByCode(ctx, packageCode)
if err != nil {
return nil, fmt.Errorf("获取组合包信息失败: %s", err.Error())
}
if !packageProduct.IsPackage {
return nil, fmt.Errorf("产品 %s 不是组合包", packageCode)
}
// 2. 获取组合包的所有子产品
packageItems, err := cs.productManagementService.GetPackageItems(ctx, packageProduct.ID)
if err != nil {
return nil, fmt.Errorf("获取组合包子产品失败: %s", err.Error())
}
if len(packageItems) == 0 {
return nil, fmt.Errorf("组合包 %s 没有配置子产品", packageCode)
}
// 3. 并发调用所有子产品的处理器
results := cs.processSubProducts(ctx, params, deps, packageItems)
// 4. 组合结果
return cs.combineResults(results)
}
// processSubProducts 并发处理子产品
func (cs *CombService) processSubProducts(
ctx context.Context,
params []byte,
deps *processors.ProcessorDependencies,
packageItems []*entities.ProductPackageItem,
) []*SubProductResult {
results := make([]*SubProductResult, 0, len(packageItems))
var wg sync.WaitGroup
var mu sync.Mutex
// 并发处理每个子产品
for _, item := range packageItems {
wg.Add(1)
go func(item *entities.ProductPackageItem) {
defer wg.Done()
result := cs.processSingleSubProduct(ctx, params, deps, item)
mu.Lock()
results = append(results, result)
mu.Unlock()
}(item)
}
wg.Wait()
// 按SortOrder排序
sort.Slice(results, func(i, j int) bool {
return results[i].SortOrder < results[j].SortOrder
})
return results
}
// processSingleSubProduct 处理单个子产品
func (cs *CombService) processSingleSubProduct(
ctx context.Context,
params []byte,
deps *processors.ProcessorDependencies,
item *entities.ProductPackageItem,
) *SubProductResult {
result := &SubProductResult{
ApiCode: item.Product.Code,
SortOrder: item.SortOrder,
Success: false,
}
// 查找对应的处理器
processor, exists := cs.processorRegistry[item.Product.Code]
if !exists {
result.Error = fmt.Sprintf("未找到处理器: %s", item.Product.Code)
return result
}
// 调用处理器
respBytes, err := processor(ctx, params, deps)
if err != nil {
result.Error = err.Error()
return result
}
// 解析响应
var responseData interface{}
if err := json.Unmarshal(respBytes, &responseData); err != nil {
result.Error = fmt.Sprintf("解析响应失败: %s", err.Error())
return result
}
result.Success = true
result.Data = responseData
return result
}
// combineResults 组合所有子产品的结果
func (cs *CombService) combineResults(results []*SubProductResult) ([]byte, error) {
// 构建组合结果
combinedResult := &CombinedResult{
Responses: results,
}
// 序列化结果
respBytes, err := json.Marshal(combinedResult)
if err != nil {
return nil, fmt.Errorf("序列化组合结果失败: %s", err.Error())
}
return respBytes, nil
}
// SubProductResult 子产品处理结果
type SubProductResult struct {
ApiCode string `json:"api_code"` // 子接口标识
Data interface{} `json:"data"` // 子接口返回数据
Success bool `json:"success"` // 是否成功
Error string `json:"error,omitempty"` // 错误信息(仅在失败时)
SortOrder int `json:"-"` // 排序字段不输出到JSON
}
// CombinedResult 组合结果
type CombinedResult struct {
Responses []*SubProductResult `json:"responses"` // 子接口响应列表
}

View File

@@ -0,0 +1,48 @@
package processors
import (
"context"
"tyapi-server/internal/application/api/commands"
"tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/yushan"
"tyapi-server/internal/shared/interfaces"
)
// CombServiceInterface 组合包服务接口
type CombServiceInterface interface {
ProcessCombRequest(ctx context.Context, params []byte, deps *ProcessorDependencies, packageCode string) ([]byte, error)
}
// ProcessorDependencies 处理器依赖容器
type ProcessorDependencies struct {
WestDexService *westdex.WestDexService
YushanService *yushan.YushanService
Validator interfaces.RequestValidator
CombService CombServiceInterface // Changed to interface to break import cycle
Options *commands.ApiCallOptions // 添加Options支持
}
// NewProcessorDependencies 创建处理器依赖容器
func NewProcessorDependencies(
westDexService *westdex.WestDexService,
yushanService *yushan.YushanService,
validator interfaces.RequestValidator,
combService CombServiceInterface, // Changed to interface
) *ProcessorDependencies {
return &ProcessorDependencies{
WestDexService: westDexService,
YushanService: yushanService,
Validator: validator,
CombService: combService,
Options: nil, // 初始化为nil在调用时设置
}
}
// WithOptions 设置Options的便捷方法
func (deps *ProcessorDependencies) WithOptions(options *commands.ApiCallOptions) *ProcessorDependencies {
deps.Options = options
return deps
}
// ProcessorFunc 处理器函数类型定义
type ProcessorFunc func(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error)

View File

@@ -0,0 +1,11 @@
package processors
import "errors"
// 处理器相关错误类型
var (
ErrDatasource = errors.New("数据源异常")
ErrSystem = errors.New("系统异常")
ErrInvalidParam = errors.New("参数校验不正确")
ErrNotFound = errors.New("查询为空")
)

View File

@@ -0,0 +1,41 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG0687Request FLXG0687 API处理方法
func ProcessFLXG0687Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG0687Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
reqData := map[string]interface{}{
"keyWord": paramsDto.IDCard,
"type": 3,
}
respBytes, err := deps.YushanService.CallAPI("RIS031", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG0V3Bequest FLXG0V3B API处理方法
func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG0V3BReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id_card": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G34BJ03", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,136 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
"github.com/tidwall/gjson"
)
// ProcessFLXG0V4BRequest FLXG0V4B API处理方法
func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG0V4BReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idcard": encryptedIDCard,
"inquired_auth": paramsDto.AuthDate,
},
}
respBytes, err := deps.WestDexService.CallAPI("G22SC01", reqData)
if err != nil {
// 数据源错误
if errors.Is(err, westdex.ErrDatasource) {
// 如果有返回内容,优先解析返回内容
if respBytes != nil {
if deps.Options.Json {
parsed, parseErr := ParseJsonResponse(respBytes)
if parseErr == nil {
return parsed, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
}
// 解析失败,返回原始内容和系统错误
return respBytes, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
}
return respBytes, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
}
// 没有返回内容,直接返回数据源错误
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
}
// 其他系统错误
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
// 正常返回
if deps.Options.Json {
parsed, parseErr := ParseJsonResponse(respBytes)
if parseErr != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
}
return parsed, nil
}
return respBytes, nil
}
// ParseWestResponse 解析西部返回的响应数据获取data字段后解析
// westResp: 西部返回的原始响应
// Returns: 解析后的数据字节数组
func ParseWestResponse(westResp []byte) ([]byte, error) {
dataResult := gjson.GetBytes(westResp, "data")
if !dataResult.Exists() {
return nil, errors.New("data not found")
}
return ParseJsonResponse([]byte(dataResult.Raw))
}
// ParseJsonResponse 直接解析JSON响应数据
// jsonResp: JSON响应数据
// Returns: 解析后的数据字节数组
func ParseJsonResponse(jsonResp []byte) ([]byte, error) {
parseResult, err := RecursiveParse(string(jsonResp))
if err != nil {
return nil, err
}
resultResp, marshalErr := json.Marshal(parseResult)
if marshalErr != nil {
return nil, err
}
return resultResp, nil
}
// RecursiveParse 递归解析JSON数据
func RecursiveParse(data interface{}) (interface{}, error) {
switch v := data.(type) {
case string:
var parsed interface{}
if err := json.Unmarshal([]byte(v), &parsed); err == nil {
return RecursiveParse(parsed)
}
return v, nil
case map[string]interface{}:
for key, val := range v {
parsed, err := RecursiveParse(val)
if err != nil {
return nil, err
}
v[key] = parsed
}
return v, nil
case []interface{}:
for i, item := range v {
parsed, err := RecursiveParse(item)
if err != nil {
return nil, err
}
v[i] = parsed
}
return v, nil
default:
return v, nil
}
}

View File

@@ -0,0 +1,58 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG162ARequest FLXG162A API处理方法
func ProcessFLXG162ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG162AReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G32BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,62 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG3D56Request FLXG3D56 API处理方法
func ProcessFLXG3D56Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG3D56Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedTimeRange, err := deps.WestDexService.Encrypt(paramsDto.TimeRange)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
"time_range": encryptedTimeRange,
},
}
respBytes, err := deps.WestDexService.CallAPI("G26BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG54F5Request FLXG54F5 API处理方法
func ProcessFLXG54F5Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG54F5Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"mobile": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G03HZ01", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG5876Request FLXG5876 API处理方法
func ProcessFLXG5876Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG5876Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"phone": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G03XM02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,41 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG75FERequest FLXG75FE API处理方法
func ProcessFLXG75FERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG75FEReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
reqData := map[string]interface{}{
"name": paramsDto.Name,
"idCard": paramsDto.IDCard,
"mobile": paramsDto.MobileNo,
}
respBytes, err := deps.WestDexService.CallAPI("FLXG75FE", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,58 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG9687Request FLXG9687 API处理方法
func ProcessFLXG9687Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG9687Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G31BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXG970FRequest FLXG970F API处理方法
func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXG970FReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"cardNo": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("WEST00028", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,58 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXGC9D1Request FLXGC9D1 API处理方法
func ProcessFLXGC9D1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXGC9D1Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G30BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXGCA3DRequest FLXGCA3D API处理方法
func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXGCA3DReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idcard": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G22BJ03", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package flxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessFLXGDEC7Request FLXGDEC7 API处理方法
func ProcessFLXGDEC7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.FLXGDEC7Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id_card": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G23BJ03", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ0B03Request IVYZ0B03 API处理方法
func ProcessIVYZ0B03Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ0b03Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"phone": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G17BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,38 @@
package ivyz
import (
"context"
"fmt"
"tyapi-server/internal/domains/api/services/processors"
)
// ProcessIVYZ2125Request IVYZ2125 API处理方法
func ProcessIVYZ2125Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, "服务已停用")
// var paramsDto dto.IVYZ2125Req
// if err := json.Unmarshal(params, &paramsDto); err != nil {
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
// }
// if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
// return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
// }
// reqData := map[string]interface{}{
// "name": paramsDto.Name,
// "idCard": paramsDto.IDCard,
// "mobile": paramsDto.Mobile,
// }
// respBytes, err := deps.WestDexService.CallAPI("IVYZ2125", reqData)
// if err != nil {
// if errors.Is(err, westdex.ErrDatasource) {
// return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
// } else {
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
// }
// }
// return respBytes, nil
}

View File

@@ -0,0 +1,50 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ385ERequest IVYZ385E API处理方法
func ProcessIVYZ385ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ385EReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"xm": encryptedName,
"gmsfzhm": encryptedIDCard,
}
respBytes, err := deps.WestDexService.CallAPI("WEST00020", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ5733Request IVYZ5733 API处理方法
func ProcessIVYZ5733Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ5733Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G09XM02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,64 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ9363Request IVYZ9363 API处理方法
func ProcessIVYZ9363Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ9363Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedManName, err := deps.WestDexService.Encrypt(paramsDto.ManName)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedManIDCard, err := deps.WestDexService.Encrypt(paramsDto.ManIDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedWomanName, err := deps.WestDexService.Encrypt(paramsDto.WomanName)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedWomanIDCard, err := deps.WestDexService.Encrypt(paramsDto.WomanIDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"nameMan": encryptedManName,
"certNumMan": encryptedManIDCard,
"nameWoman": encryptedWomanName,
"certNumWoman": encryptedWomanIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G10SC02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ9A2BRequest IVYZ9A2B API处理方法
func ProcessIVYZ9A2BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ9A2BReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name_value": encryptedName,
"id_card_value": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G11BJ06", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,38 @@
package ivyz
import (
"context"
"fmt"
"tyapi-server/internal/domains/api/services/processors"
)
// ProcessIVYZADEERequest IVYZADEE API处理方法
func ProcessIVYZADEERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, "服务已停用")
// var paramsDto dto.IVYZADEEReq
// if err := json.Unmarshal(params, &paramsDto); err != nil {
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
// }
// if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
// return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
// }
// reqData := map[string]interface{}{
// "name": paramsDto.Name,
// "idCard": paramsDto.IDCard,
// "mobile": paramsDto.Mobile,
// }
// respBytes, err := deps.WestDexService.CallAPI("IVYZADEE", reqData)
// if err != nil {
// if errors.Is(err, westdex.ErrDatasource) {
// return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
// } else {
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
// }
// }
// return respBytes, nil
}

View File

@@ -0,0 +1,58 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessJRZQ0A03Request JRZQ0A03 API处理方法
func ProcessJRZQ0A03Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQ0A03Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G27BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,58 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessJRZQ4AA8Request JRZQ4AA8 API处理方法
func ProcessJRZQ4AA8Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQ4AA8Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G29BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,58 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessJRZQ8203Request JRZQ8203 API处理方法
func ProcessJRZQ8203Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQ8203Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"id": encryptedIDCard,
"cell": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G28BJ05", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,64 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessJRZQDCBERequest JRZQDCBE API处理方法
func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQDBCEReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedBankCard, err := deps.WestDexService.Encrypt(paramsDto.BankCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idcard": encryptedIDCard,
"mobile": encryptedMobileNo,
"acc_no": encryptedBankCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G20GZ01", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,58 @@
package qygl
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessQYGL2ACDRequest QYGL2ACD API处理方法
func ProcessQYGL2ACDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QYGL2ACDReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedLegalPerson, err := deps.WestDexService.Encrypt(paramsDto.LegalPerson)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedEntCode, err := deps.WestDexService.Encrypt(paramsDto.EntCode)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"entname": encryptedEntName,
"realname": encryptedLegalPerson,
"idcard": encryptedEntCode,
},
}
respBytes, err := deps.WestDexService.CallAPI("WEST00022", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,64 @@
package qygl
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessQYGL45BDRequest QYGL45BD API处理方法
func ProcessQYGL45BDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QYGL45BDReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedLegalPerson, err := deps.WestDexService.Encrypt(paramsDto.LegalPerson)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedEntCode, err := deps.WestDexService.Encrypt(paramsDto.EntCode)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"entname": encryptedEntName,
"realname": encryptedLegalPerson,
"entmark": encryptedEntCode,
"idcard": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("WEST00021", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package qygl
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessQYGL6F2DRequest QYGL6F2D API处理方法
func ProcessQYGL6F2DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QYGL6F2DReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"idno": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G05XM02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package qygl
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessQYGL8261Request QYGL8261 API处理方法
func ProcessQYGL8261Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QYGL8261Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"ent_name": encryptedEntName,
},
}
respBytes, err := deps.WestDexService.CallAPI("Q03BJ03", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,53 @@
package qygl
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessQYGL8271Request QYGL8271 API处理方法
func ProcessQYGL8271Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QYGL8271Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedEntCode, err := deps.WestDexService.Encrypt(paramsDto.EntCode)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"org_name": encryptedEntName,
"uscc": encryptedEntCode,
"inquired_auth": paramsDto.AuthDate, // AuthDate 有 encrypt:"false" 标签,不加密
},
}
respBytes, err := deps.WestDexService.CallAPI("Q03SC01", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,115 @@
package qygl
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
"github.com/tidwall/gjson"
)
// ProcessQYGLB4C0Request QYGLB4C0 API处理方法
func ProcessQYGLB4C0Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QYGLB4C0Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedIDCard := deps.WestDexService.Md5Encrypt(paramsDto.IDCard)
reqData := map[string]interface{}{
"pid": encryptedIDCard,
}
respBytes, err := deps.WestDexService.G05HZ01CallAPI("G05HZ01", reqData)
if err != nil {
// 数据源错误
if errors.Is(err, westdex.ErrDatasource) {
// 如果有返回内容,优先解析返回内容
if respBytes != nil {
var westData map[string]interface{}
if err := json.Unmarshal(respBytes, &westData); err == nil {
if code, ok := westData["code"].(string); ok && code == "1404" {
return nil, fmt.Errorf("%s: %w", processors.ErrNotFound, err)
}
}
return respBytes, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
}
// 没有返回内容,直接返回数据源错误
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
}
// 其他系统错误
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
return respBytes, nil
}
// ParseWestResponse 解析西部返回的响应数据获取data字段后解析
// westResp: 西部返回的原始响应
// Returns: 解析后的数据字节数组
func ParseWestResponse(westResp []byte) ([]byte, error) {
dataResult := gjson.GetBytes(westResp, "data")
if !dataResult.Exists() {
return nil, errors.New("data not found")
}
return ParseJsonResponse([]byte(dataResult.Raw))
}
// ParseJsonResponse 直接解析JSON响应数据
// jsonResp: JSON响应数据
// Returns: 解析后的数据字节数组
func ParseJsonResponse(jsonResp []byte) ([]byte, error) {
parseResult, err := RecursiveParse(string(jsonResp))
if err != nil {
return nil, err
}
resultResp, marshalErr := json.Marshal(parseResult)
if marshalErr != nil {
return nil, err
}
return resultResp, nil
}
// RecursiveParse 递归解析JSON数据
func RecursiveParse(data interface{}) (interface{}, error) {
switch v := data.(type) {
case string:
var parsed interface{}
if err := json.Unmarshal([]byte(v), &parsed); err == nil {
return RecursiveParse(parsed)
}
return v, nil
case map[string]interface{}:
for key, val := range v {
parsed, err := RecursiveParse(val)
if err != nil {
return nil, err
}
v[key] = parsed
}
return v, nil
case []interface{}:
for i, item := range v {
parsed, err := RecursiveParse(item)
if err != nil {
return nil, err
}
v[i] = parsed
}
return v, nil
default:
return v, nil
}
}

View File

@@ -0,0 +1,59 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSY09CDRequest YYSY09CD API处理方法
func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSY09CDReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idNo": encryptedIDCard,
"phone": encryptedMobileNo,
"phoneType": paramsDto.MobileType,
},
}
respBytes, err := deps.WestDexService.CallAPI("G16BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSY4B21Request YYSY4B21 API处理方法
func ProcessYYSY4B21Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSY4B21Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"phone": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G25BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSY4B37Request YYSY4B37 API处理方法
func ProcessYYSY4B37Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSY4B37Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"phone": encryptedMobileNo,
},
}
respBytes, err := deps.WestDexService.CallAPI("G02BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,59 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSY6F2ERequest YYSY6F2E API处理方法
func ProcessYYSY6F2ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSY6F2EReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idNo": encryptedIDCard,
"phone": encryptedMobileNo,
"phoneType": paramsDto.MobileType,
},
}
respBytes, err := deps.WestDexService.CallAPI("G15BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,55 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSYBE08Request YYSYBE08 API处理方法
func ProcessYYSYBE08Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYBE08Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"xM": encryptedName,
"gMSFZHM": encryptedIDCard,
"customerNumber": deps.WestDexService.GetConfig().Key,
"timeStamp":fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond)),
},
}
respBytes, err := deps.WestDexService.CallAPI("layoutIdcard", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSYD50FRequest YYSYD50F API处理方法
func ProcessYYSYD50FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYD50FReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"phone": encryptedMobileNo,
"idNo": encryptedIDCard,
},
}
respBytes, err := deps.WestDexService.CallAPI("G18BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,47 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessYYSYF7DBRequest YYSYF7DB API处理方法
func ProcessYYSYF7DBRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYF7DBReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"phone": encryptedMobileNo,
"startDate": paramsDto.StartDate, // StartDate 有 encrypt:"false" 标签,不加密
},
}
respBytes, err := deps.WestDexService.CallAPI("G19BJ02", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
} else {
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -16,18 +16,21 @@ import (
// 这是企业认证流程的核心聚合根,封装了完整的认证业务逻辑和状态管理
type Certification struct {
// === 基础信息 ===
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
ID string `gorm:"primaryKey;type:varchar(64)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;unique" json:"user_id" comment:"申请用户ID"`
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
// === 流程时间戳 - 记录每个关键步骤的完成时间 ===
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
ContractFileCreatedAt *time.Time `json:"contract_file_created_at,omitempty" comment:"合同文件生成时间"`
// === e签宝相关信息 ===
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"企业认证流程ID"`
AuthURL string `gorm:"type:varchar(500)" json:"auth_url,omitempty" comment:"企业认证链接"`
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
@@ -110,12 +113,6 @@ func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus,
if !c.validateActorPermission(targetStatus, actor) {
return false, fmt.Sprintf("%s 无权执行此状态转换", enums.GetActorTypeName(actor))
}
// 检查业务规则
if err := c.validateBusinessRules(targetStatus, actor); err != nil {
return false, err.Error()
}
return true, ""
}
@@ -157,7 +154,7 @@ func (c *Certification) TransitionTo(targetStatus enums.CertificationStatus, act
// ================ 业务操作方法 ================
// SubmitEnterpriseInfo 提交企业信息
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo) error {
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo, authURL string, authFlowID string) error {
// 验证当前状态
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
@@ -167,7 +164,12 @@ func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.Enter
if err := enterpriseInfo.Validate(); err != nil {
return fmt.Errorf("企业信息验证失败: %w", err)
}
if authURL != "" {
c.AuthURL = authURL
}
if authFlowID != "" {
c.AuthFlowID = authFlowID
}
// 状态转换
if err := c.TransitionTo(enums.StatusInfoSubmitted, enums.ActorTypeUser, c.UserID, "用户提交企业信息"); err != nil {
return err
@@ -184,6 +186,26 @@ func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.Enter
return nil
}
// 完成企业认证
func (c *Certification) CompleteEnterpriseVerification() error {
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许完成企业认证", enums.GetStatusName(c.Status))
}
if err := c.TransitionTo(enums.StatusEnterpriseVerified, enums.ActorTypeSystem, "system", "企业认证成功"); err != nil {
return err
}
c.addDomainEvent(&EnterpriseVerificationSuccessEvent{
CertificationID: c.ID,
UserID: c.UserID,
AuthFlowID: "",
VerifiedAt: time.Now(),
})
return nil
}
// HandleEnterpriseVerificationCallback 处理企业认证回调
func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authFlowID string, failureReason enums.FailureReason, message string) error {
// 验证当前状态
@@ -227,17 +249,19 @@ func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authF
}
// ApplyContract 申请合同签署
func (c *Certification) ApplyContract() error {
func (c *Certification) ApplyContract(EsignFlowID string, ContractSignURL string) error {
// 验证当前状态
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("当前状态 %s 不允许申请合同", enums.GetStatusName(c.Status))
}
// 状态转换
if err := c.TransitionTo(enums.StatusContractApplied, enums.ActorTypeUser, c.UserID, "用户申请合同签署"); err != nil {
return err
}
c.EsignFlowID = EsignFlowID
c.ContractSignURL = ContractSignURL
now := time.Now()
c.ContractFileCreatedAt = &now
// 添加业务事件
c.addDomainEvent(&ContractAppliedEvent{
CertificationID: c.ID,
@@ -248,6 +272,15 @@ func (c *Certification) ApplyContract() error {
return nil
}
// AddContractFileID 生成合同文件
func (c *Certification) AddContractFileID(contractFileID string, contractURL string) error {
c.ContractFileID = contractFileID
c.ContractURL = contractURL
now := time.Now()
c.ContractFileCreatedAt = &now
return nil
}
// UpdateContractInfo 更新合同信息
func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractInfo) error {
// 验证合同信息
@@ -264,57 +297,76 @@ func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractI
return nil
}
// HandleContractSignCallback 处理合同签署回调
func (c *Certification) HandleContractSignCallback(success bool, contractURL string, failureReason enums.FailureReason, message string) error {
// SignSuccess 签署成功
func (c *Certification) SignSuccess() error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
if success {
// 签署成功 - 认证完成
c.ContractURL = contractURL
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功,认证完成"); err != nil {
return err
}
c.addDomainEvent(&ContractSignedEvent{
CertificationID: c.ID,
UserID: c.UserID,
ContractURL: contractURL,
SignedAt: time.Now(),
})
c.addDomainEvent(&CertificationCompletedEvent{
CertificationID: c.ID,
UserID: c.UserID,
CompletedAt: time.Now(),
})
} else {
// 签署失败
c.setFailureInfo(failureReason, message)
var targetStatus enums.CertificationStatus
if failureReason == enums.FailureReasonContractExpired {
targetStatus = enums.StatusContractExpired
} else {
targetStatus = enums.StatusContractRejected
}
if err := c.TransitionTo(targetStatus, enums.ActorTypeEsign, "esign_system", "合同签署失败"); err != nil {
return err
}
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: failureReason,
FailureMessage: message,
FailedAt: time.Now(),
})
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功"); err != nil {
return err
}
c.addDomainEvent(&ContractSignedEvent{
CertificationID: c.ID,
UserID: c.UserID,
SignedAt: time.Now(),
})
return nil
}
// ContractRejection 处理合同拒签
func (c *Certification) ContractRejection(message string) error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同拒签", enums.GetStatusName(c.Status))
}
// 设置失败信息
c.setFailureInfo(enums.FailureReasonContractRejectedByUser, message)
// 状态转换
if err := c.TransitionTo(enums.StatusContractRejected, enums.ActorTypeEsign, "esign_system", "合同签署被拒绝"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: enums.FailureReasonContractRejectedByUser,
FailureMessage: message,
FailedAt: time.Now(),
})
return nil
}
// ContractExpiration 处理合同过期
func (c *Certification) ContractExpiration() error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同过期", enums.GetStatusName(c.Status))
}
// 设置失败信息
c.setFailureInfo(enums.FailureReasonContractExpired, "合同签署已超时")
// 状态转换
if err := c.TransitionTo(enums.StatusContractExpired, enums.ActorTypeSystem, "system", "合同签署超时"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: enums.FailureReasonContractExpired,
FailureMessage: "合同签署已超时",
FailedAt: time.Now(),
})
return nil
}
@@ -369,7 +421,58 @@ func (c *Certification) RetryFromFailure(actor enums.ActorType, actorID string)
return nil
}
// CompleteCertification 完成认证
func (c *Certification) CompleteCertification() error {
// 验证当前状态
if c.Status != enums.StatusContractSigned {
return fmt.Errorf("当前状态 %s 不允许完成认证", enums.GetStatusName(c.Status))
}
// 验证合同信息完整性
if c.ContractFileID == "" || c.EsignFlowID == "" || c.ContractURL == "" {
return errors.New("合同信息不完整,无法完成认证")
}
// 状态转换
if err := c.TransitionTo(enums.StatusCompleted, enums.ActorTypeSystem, "system", "系统处理完成,认证成功"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&CertificationCompletedEvent{
CertificationID: c.ID,
UserID: c.UserID,
CompletedAt: time.Now(),
})
return nil
}
// ================ 查询方法 ================
// GetDataByStatus 根据当前状态获取对应的数据
func (c *Certification) GetDataByStatus() map[string]interface{} {
data := map[string]interface{}{}
switch c.Status {
case enums.StatusInfoSubmitted:
data["auth_url"] = c.AuthURL
case enums.StatusInfoRejected:
data["failure_reason"] = c.FailureReason
data["failure_message"] = c.FailureMessage
case enums.StatusEnterpriseVerified:
data["ContractURL"] = c.ContractURL
case enums.StatusContractApplied:
data["contract_sign_url"] = c.ContractSignURL
case enums.StatusContractSigned:
data["contract_url"] = c.ContractURL
case enums.StatusCompleted:
data["contract_url"] = c.ContractURL
data["completed_at"] = c.CompletedAt
case enums.StatusContractRejected:
data["failure_reason"] = c.FailureReason
data["failure_message"] = c.FailureMessage
}
return data
}
// GetProgress 获取认证进度百分比
func (c *Certification) GetProgress() int {
@@ -414,9 +517,9 @@ func (c *Certification) IsFinalStatus() bool {
return enums.IsFinalStatus(c.Status)
}
// IsCompleted 是否已完成认证
// IsCompleted 是否已完成
func (c *Certification) IsCompleted() bool {
return c.Status == enums.StatusContractSigned
return c.Status == enums.StatusCompleted
}
// GetNextValidStatuses 获取下一个有效状态
@@ -429,6 +532,28 @@ func (c *Certification) GetFailureInfo() (enums.FailureReason, string) {
return c.FailureReason, c.FailureMessage
}
// IsContractFileExpired 判断合同文件是否过期生成后50分钟过期
func (c *Certification) IsContractFileExpired() bool {
if c.ContractFileCreatedAt == nil && c.Status == enums.StatusEnterpriseVerified {
// 60分钟前
t := time.Now().Add(-60 * time.Minute)
c.ContractFileCreatedAt = &t
return true
}
if c.ContractFileCreatedAt != nil {
return time.Since(*c.ContractFileCreatedAt) > 50*time.Minute
}
return false
}
// IsContractFileNeedUpdate 是否需要更新合同文件
func (c *Certification) IsContractFileNeedUpdate() bool {
if c.IsContractFileExpired() && c.Status == enums.StatusEnterpriseVerified {
return true
}
return false
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
@@ -445,17 +570,20 @@ func (c *Certification) ValidateBusinessRules() error {
// 状态相关验证
switch c.Status {
case enums.StatusEnterpriseVerified:
if c.AuthFlowID == "" {
return errors.New("企业认证状态下必须有认证流程ID")
}
case enums.StatusContractApplied:
if c.AuthFlowID == "" {
return errors.New("合同申请状态下必须有企业认证流程ID")
if c.ContractURL == "" {
return errors.New("企业认证成功后合同文件ID和合同URL不能为空")
}
case enums.StatusContractSigned:
if c.ContractFileID == "" || c.EsignFlowID == "" {
return errors.New("合同签署状态下必须有完整的合同信息")
}
case enums.StatusCompleted:
if c.ContractFileID == "" || c.EsignFlowID == "" || c.ContractURL == "" {
return errors.New("认证完成状态下必须有完整的合同信息")
}
if c.CompletedAt == nil {
return errors.New("认证完成状态下必须有完成时间")
}
}
// 失败状态验证
@@ -475,13 +603,14 @@ func (c *Certification) ValidateBusinessRules() error {
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
// 定义状态转换的权限规则
permissions := map[enums.CertificationStatus][]enums.ActorType{
enums.StatusInfoSubmitted: {enums.ActorTypeUser},
enums.StatusInfoSubmitted: {enums.ActorTypeUser, enums.ActorTypeAdmin},
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractApplied: {enums.ActorTypeUser},
enums.StatusContractApplied: {enums.ActorTypeUser, enums.ActorTypeAdmin},
enums.StatusContractSigned: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractExpired: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusCompleted: {enums.ActorTypeSystem, enums.ActorTypeAdmin},
}
allowedActors, exists := permissions[targetStatus]
@@ -498,44 +627,6 @@ func (c *Certification) validateActorPermission(targetStatus enums.Certification
return false
}
// validateBusinessRules 验证业务规则
func (c *Certification) validateBusinessRules(targetStatus enums.CertificationStatus, actor enums.ActorType) error {
// 用户操作验证
if actor == enums.ActorTypeUser {
switch targetStatus {
case enums.StatusInfoSubmitted:
// 用户提交企业信息时的验证
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
}
case enums.StatusContractApplied:
// 用户申请合同时的验证
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("必须先完成企业认证才能申请合同")
}
if c.AuthFlowID == "" {
return errors.New("缺少企业认证流程ID")
}
}
}
// e签宝回调验证
if actor == enums.ActorTypeEsign {
switch targetStatus {
case enums.StatusEnterpriseVerified, enums.StatusInfoRejected:
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
}
case enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired:
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
}
}
return nil
}
// ================ 辅助方法 ================
// updateTimestampByStatus 根据状态更新对应的时间戳
@@ -551,6 +642,8 @@ func (c *Certification) updateTimestampByStatus(status enums.CertificationStatus
c.ContractAppliedAt = &now
case enums.StatusContractSigned:
c.ContractSignedAt = &now
case enums.StatusCompleted:
c.CompletedAt = &now
}
}

View File

@@ -18,6 +18,8 @@ type EnterpriseInfoSubmitRecord struct {
LegalPersonName string `json:"legal_person_name" gorm:"type:varchar(50);not null"`
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
EnterpriseAddress string `json:"enterprise_address" gorm:"type:varchar(200);not null"` // 新增企业地址
EnterpriseEmail string `json:"enterprise_email" gorm:"type:varchar(100);not null"` // 企业邮箱
// 提交状态
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
@@ -38,7 +40,7 @@ func (EnterpriseInfoSubmitRecord) TableName() string {
// NewEnterpriseInfoSubmitRecord 创建新的企业信息提交记录
func NewEnterpriseInfoSubmitRecord(
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string,
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string,
) *EnterpriseInfoSubmitRecord {
return &EnterpriseInfoSubmitRecord{
ID: uuid.New().String(),
@@ -48,6 +50,8 @@ func NewEnterpriseInfoSubmitRecord(
LegalPersonName: legalPersonName,
LegalPersonID: legalPersonID,
LegalPersonPhone: legalPersonPhone,
EnterpriseAddress: enterpriseAddress,
EnterpriseEmail: enterpriseEmail,
Status: "submitted",
SubmitAt: time.Now(),
CreatedAt: time.Now(),

View File

@@ -12,35 +12,35 @@ import (
// 封装电子合同相关的核心信息,包含合同状态和签署流程管理
type ContractInfo struct {
// 合同基本信息
ContractFileID string `json:"contract_file_id"` // 合同文件ID
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
ContractURL string `json:"contract_url"` // 合同文件访问链接
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
ContractFileID string `json:"contract_file_id"` // 合同文件ID
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
ContractURL string `json:"contract_url"` // 合同文件访问链接
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
// 合同元数据
ContractTitle string `json:"contract_title"` // 合同标题
ContractVersion string `json:"contract_version"` // 合同版本
TemplateID string `json:"template_id"` // 模板ID
ContractTitle string `json:"contract_title"` // 合同标题
ContractVersion string `json:"contract_version"` // 合同版本
TemplateID string `json:"template_id"` // 模板ID
// 签署相关信息
SignerAccount string `json:"signer_account"` // 签署人账号
SignerName string `json:"signer_name"` // 签署人姓名
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
TransactorName string `json:"transactor_name"` // 经办人姓名
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
SignerAccount string `json:"signer_account"` // 签署人账号
SignerName string `json:"signer_name"` // 签署人姓名
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
TransactorName string `json:"transactor_name"` // 经办人姓名
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
// 时间信息
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
// 状态信息
Status string `json:"status"` // 合同状态
SignProgress int `json:"sign_progress"` // 签署进度
Status string `json:"status"` // 合同状态
SignProgress int `json:"sign_progress"` // 签署进度
// 附加信息
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
}
// ContractStatus 合同状态常量
@@ -57,19 +57,19 @@ const (
// NewContractInfo 创建合同信息值对象
func NewContractInfo(contractFileID, esignFlowID, contractURL, contractSignURL string) (*ContractInfo, error) {
info := &ContractInfo{
ContractFileID: strings.TrimSpace(contractFileID),
EsignFlowID: strings.TrimSpace(esignFlowID),
ContractURL: strings.TrimSpace(contractURL),
ContractSignURL: strings.TrimSpace(contractSignURL),
Status: ContractStatusGenerated,
SignProgress: 0,
Metadata: make(map[string]interface{}),
ContractFileID: strings.TrimSpace(contractFileID),
EsignFlowID: strings.TrimSpace(esignFlowID),
ContractURL: strings.TrimSpace(contractURL),
ContractSignURL: strings.TrimSpace(contractSignURL),
Status: ContractStatusGenerated,
SignProgress: 0,
Metadata: make(map[string]interface{}),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("合同信息验证失败: %w", err)
}
return info, nil
}
@@ -78,27 +78,27 @@ func (c *ContractInfo) Validate() error {
if err := c.validateContractFileID(); err != nil {
return err
}
if err := c.validateEsignFlowID(); err != nil {
return err
}
if err := c.validateContractURL(); err != nil {
return err
}
if err := c.validateContractSignURL(); err != nil {
return err
}
if err := c.validateSignerInfo(); err != nil {
return err
}
if err := c.validateStatus(); err != nil {
return err
}
return nil
}
@@ -107,12 +107,12 @@ func (c *ContractInfo) validateContractFileID() error {
if c.ContractFileID == "" {
return errors.New("合同文件ID不能为空")
}
// 简单的格式验证
if len(c.ContractFileID) < 10 {
return errors.New("合同文件ID格式不正确")
}
return nil
}
@@ -121,12 +121,12 @@ func (c *ContractInfo) validateEsignFlowID() error {
if c.EsignFlowID == "" {
return errors.New("e签宝流程ID不能为空")
}
// 简单的格式验证
if len(c.EsignFlowID) < 10 {
return errors.New("e签宝流程ID格式不正确")
}
return nil
}
@@ -135,18 +135,18 @@ func (c *ContractInfo) validateContractURL() error {
if c.ContractURL == "" {
return errors.New("合同访问链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractURL)
if err != nil {
return fmt.Errorf("合同访问链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同访问链接格式不正确必须以http://或https://开头")
}
return nil
}
@@ -155,18 +155,18 @@ func (c *ContractInfo) validateContractSignURL() error {
if c.ContractSignURL == "" {
return errors.New("合同签署链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractSignURL)
if err != nil {
return fmt.Errorf("合同签署链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同签署链接格式不正确必须以http://或https://开头")
}
return nil
}
@@ -177,11 +177,11 @@ func (c *ContractInfo) validateSignerInfo() error {
if c.SignerAccount == "" {
return errors.New("签署人账号不能为空")
}
if c.SignerName == "" {
return errors.New("签署人姓名不能为空")
}
if c.TransactorPhone != "" {
// 手机号格式验证
phonePattern := `^1[3-9]\d{9}$`
@@ -189,12 +189,12 @@ func (c *ContractInfo) validateSignerInfo() error {
if err != nil {
return fmt.Errorf("经办人手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人手机号格式不正确")
}
}
if c.TransactorIDCardNum != "" {
// 身份证号格式验证
idPattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
@@ -202,13 +202,13 @@ func (c *ContractInfo) validateSignerInfo() error {
if err != nil {
return fmt.Errorf("经办人身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人身份证号格式不正确")
}
}
}
return nil
}
@@ -223,13 +223,13 @@ func (c *ContractInfo) validateStatus() error {
ContractStatusRejected,
ContractStatusCancelled,
}
for _, status := range validStatuses {
if c.Status == status {
return nil
}
}
return fmt.Errorf("无效的合同状态: %s", c.Status)
}
@@ -240,7 +240,7 @@ func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone,
c.TransactorPhone = strings.TrimSpace(transactorPhone)
c.TransactorName = strings.TrimSpace(transactorName)
c.TransactorIDCardNum = strings.TrimSpace(transactorIDCardNum)
return c.validateSignerInfo()
}
@@ -248,15 +248,15 @@ func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone,
func (c *ContractInfo) UpdateStatus(status string) error {
oldStatus := c.Status
c.Status = status
if err := c.validateStatus(); err != nil {
c.Status = oldStatus // 回滚
return err
}
// 根据状态更新进度
c.updateProgressByStatus()
return nil
}
@@ -271,7 +271,7 @@ func (c *ContractInfo) updateProgressByStatus() {
ContractStatusRejected: 50,
ContractStatusCancelled: 0,
}
if progress, exists := progressMap[c.Status]; exists {
c.SignProgress = progress
}
@@ -283,7 +283,7 @@ func (c *ContractInfo) MarkAsSigning() error {
c.SignProgress = 50
now := time.Now()
c.SignFlowCreatedAt = &now
return nil
}
@@ -293,7 +293,7 @@ func (c *ContractInfo) MarkAsSigned() error {
c.SignProgress = 100
now := time.Now()
c.SignedAt = &now
return nil
}
@@ -302,7 +302,7 @@ func (c *ContractInfo) MarkAsExpired() error {
c.Status = ContractStatusExpired
now := time.Now()
c.ExpiresAt = &now
return nil
}
@@ -310,7 +310,7 @@ func (c *ContractInfo) MarkAsExpired() error {
func (c *ContractInfo) MarkAsRejected() error {
c.Status = ContractStatusRejected
c.SignProgress = 50
return nil
}
@@ -319,7 +319,7 @@ func (c *ContractInfo) IsExpired() bool {
if c.ExpiresAt == nil {
return false
}
return time.Now().After(*c.ExpiresAt)
}
@@ -344,7 +344,7 @@ func (c *ContractInfo) GetStatusName() string {
ContractStatusRejected: "被拒绝",
ContractStatusCancelled: "已取消",
}
if name, exists := statusNames[c.Status]; exists {
return name
}
@@ -364,7 +364,7 @@ func (c *ContractInfo) GetMaskedSignerAccount() string {
if len(c.SignerAccount) <= 6 {
return c.SignerAccount
}
// 保留前3位和后3位中间用*替代
return c.SignerAccount[:3] + "***" + c.SignerAccount[len(c.SignerAccount)-3:]
}
@@ -374,7 +374,7 @@ func (c *ContractInfo) GetMaskedTransactorPhone() string {
if len(c.TransactorPhone) != 11 {
return c.TransactorPhone
}
// 保留前3位和后4位中间用*替代
return c.TransactorPhone[:3] + "****" + c.TransactorPhone[7:]
}
@@ -384,7 +384,7 @@ func (c *ContractInfo) GetMaskedTransactorIDCardNum() string {
if len(c.TransactorIDCardNum) != 18 {
return c.TransactorIDCardNum
}
// 保留前6位和后4位中间用*替代
return c.TransactorIDCardNum[:6] + "********" + c.TransactorIDCardNum[14:]
}
@@ -411,7 +411,7 @@ func (c *ContractInfo) Equals(other *ContractInfo) bool {
if other == nil {
return false
}
return c.ContractFileID == other.ContractFileID &&
c.EsignFlowID == other.EsignFlowID &&
c.Status == other.Status
@@ -435,7 +435,7 @@ func (c *ContractInfo) Clone() *ContractInfo {
Status: c.Status,
SignProgress: c.SignProgress,
}
// 复制时间字段
if c.GeneratedAt != nil {
generatedAt := *c.GeneratedAt
@@ -453,7 +453,7 @@ func (c *ContractInfo) Clone() *ContractInfo {
expiresAt := *c.ExpiresAt
cloned.ExpiresAt = &expiresAt
}
// 复制元数据
if c.Metadata != nil {
cloned.Metadata = make(map[string]interface{})
@@ -461,38 +461,38 @@ func (c *ContractInfo) Clone() *ContractInfo {
cloned.Metadata[k] = v
}
}
return cloned
}
// String 返回合同信息的字符串表示
func (c *ContractInfo) String() string {
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
c.ContractFileID,
c.EsignFlowID,
c.GetStatusName(),
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
c.ContractFileID,
c.EsignFlowID,
c.GetStatusName(),
c.SignProgress)
}
// ToMap 转换为map格式用于序列化
func (c *ContractInfo) ToMap() map[string]interface{} {
result := map[string]interface{}{
"contract_file_id": c.ContractFileID,
"esign_flow_id": c.EsignFlowID,
"contract_url": c.ContractURL,
"contract_sign_url": c.ContractSignURL,
"contract_title": c.ContractTitle,
"contract_version": c.ContractVersion,
"template_id": c.TemplateID,
"signer_account": c.SignerAccount,
"signer_name": c.SignerName,
"transactor_phone": c.TransactorPhone,
"transactor_name": c.TransactorName,
"transactor_id_card_num": c.TransactorIDCardNum,
"status": c.Status,
"sign_progress": c.SignProgress,
"contract_file_id": c.ContractFileID,
"esign_flow_id": c.EsignFlowID,
"contract_url": c.ContractURL,
"contract_sign_url": c.ContractSignURL,
"contract_title": c.ContractTitle,
"contract_version": c.ContractVersion,
"template_id": c.TemplateID,
"signer_account": c.SignerAccount,
"signer_name": c.SignerName,
"transactor_phone": c.TransactorPhone,
"transactor_name": c.TransactorName,
"transactor_id_card_num": c.TransactorIDCardNum,
"status": c.Status,
"sign_progress": c.SignProgress,
}
// 添加时间字段
if c.GeneratedAt != nil {
result["generated_at"] = c.GeneratedAt
@@ -506,11 +506,11 @@ func (c *ContractInfo) ToMap() map[string]interface{} {
if c.ExpiresAt != nil {
result["expires_at"] = c.ExpiresAt
}
// 添加元数据
if c.Metadata != nil {
result["metadata"] = c.Metadata
}
return result
}
}

View File

@@ -11,35 +11,36 @@ import (
// 封装企业认证所需的核心信息,包含完整的业务规则验证
type EnterpriseInfo struct {
// 企业基本信息
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
// 法定代表人信息
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
LegalPersonPhone string `json:"legal_person_phone"` // 法定代表人手机号
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
LegalPersonPhone string `json:"legal_person_phone"` // 法定代表人手机号
// 企业详细信息
RegisteredAddress string `json:"registered_address"` // 注册地址
BusinessScope string `json:"business_scope"` // 经营范围
RegisteredCapital string `json:"registered_capital"` // 注册资本
EstablishmentDate string `json:"establishment_date"` // 成立日期
RegisteredAddress string `json:"registered_address"` // 注册地址
EnterpriseAddress string `json:"enterprise_address"` // 企业地址(新增)
EnterpriseEmail string `json:"enterprise_email"` // 企业邮箱
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string) (*EnterpriseInfo, error) {
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string) (*EnterpriseInfo, error) {
info := &EnterpriseInfo{
CompanyName: strings.TrimSpace(companyName),
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
LegalPersonName: strings.TrimSpace(legalPersonName),
LegalPersonID: strings.TrimSpace(legalPersonID),
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
EnterpriseAddress: strings.TrimSpace(enterpriseAddress),
EnterpriseEmail: strings.TrimSpace(enterpriseEmail),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("企业信息验证失败: %w", err)
}
return info, nil
}
@@ -48,23 +49,31 @@ func (e *EnterpriseInfo) Validate() error {
if err := e.validateCompanyName(); err != nil {
return err
}
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
if err := e.validateLegalPersonName(); err != nil {
return err
}
if err := e.validateLegalPersonID(); err != nil {
return err
}
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
if err := e.validateEnterpriseAddress(); err != nil {
return err
}
if err := e.validateEnterpriseEmail(); err != nil {
return err
}
return nil
}
@@ -73,15 +82,15 @@ func (e *EnterpriseInfo) validateCompanyName() error {
if e.CompanyName == "" {
return errors.New("企业名称不能为空")
}
if len(e.CompanyName) < 2 {
return errors.New("企业名称长度不能少于2个字符")
}
if len(e.CompanyName) > 100 {
return errors.New("企业名称长度不能超过100个字符")
}
// 检查是否包含非法字符
invalidChars := []string{"`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/"}
for _, char := range invalidChars {
@@ -89,7 +98,7 @@ func (e *EnterpriseInfo) validateCompanyName() error {
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
}
}
return nil
}
@@ -98,18 +107,18 @@ func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return errors.New("统一社会信用代码不能为空")
}
// 统一社会信用代码格式验证18位数字和字母
pattern := `^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$`
matched, err := regexp.MatchString(pattern, e.UnifiedSocialCode)
if err != nil {
return fmt.Errorf("统一社会信用代码格式验证错误: %w", err)
}
if !matched {
return errors.New("统一社会信用代码格式不正确应为18位数字和字母组合")
}
return nil
}
@@ -118,26 +127,26 @@ func (e *EnterpriseInfo) validateLegalPersonName() error {
if e.LegalPersonName == "" {
return errors.New("法定代表人姓名不能为空")
}
if len(e.LegalPersonName) < 2 {
return errors.New("法定代表人姓名长度不能少于2个字符")
}
if len(e.LegalPersonName) > 50 {
return errors.New("法定代表人姓名长度不能超过50个字符")
}
// 中文姓名格式验证
pattern := `^[\u4e00-\u9fa5·]+$`
pattern := "^[一-龥·]+$"
matched, err := regexp.MatchString(pattern, e.LegalPersonName)
if err != nil {
return fmt.Errorf("法定代表人姓名格式验证错误: %w", err)
}
if !matched {
return errors.New("法定代表人姓名只能包含中文字符和间隔号")
}
return nil
}
@@ -146,27 +155,27 @@ func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return errors.New("法定代表人身份证号不能为空")
}
// 身份证号格式验证18位
if len(e.LegalPersonID) != 18 {
return errors.New("身份证号必须为18位")
}
pattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
matched, err := regexp.MatchString(pattern, e.LegalPersonID)
if err != nil {
return fmt.Errorf("身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("身份证号格式不正确")
}
// 身份证号校验码验证
if !e.validateIDChecksum() {
return errors.New("身份证号校验码错误")
}
return nil
}
@@ -175,22 +184,22 @@ func (e *EnterpriseInfo) validateIDChecksum() bool {
if len(e.LegalPersonID) != 18 {
return false
}
// 加权因子
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
// 校验码对应表
checkCodes := []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
sum := 0
for i := 0; i < 17; i++ {
digit := int(e.LegalPersonID[i] - '0')
sum += digit * weights[i]
}
checkCodeIndex := sum % 11
expectedCheckCode := checkCodes[checkCodeIndex]
actualCheckCode := strings.ToUpper(string(e.LegalPersonID[17]))
return expectedCheckCode == actualCheckCode
}
@@ -199,18 +208,53 @@ func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return errors.New("法定代表人手机号不能为空")
}
// 手机号格式验证11位数字1开头
pattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(pattern, e.LegalPersonPhone)
if err != nil {
return fmt.Errorf("手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("手机号格式不正确应为11位数字且以1开头")
}
return nil
}
// validateEnterpriseAddress 验证企业地址
func (e *EnterpriseInfo) validateEnterpriseAddress() error {
if strings.TrimSpace(e.EnterpriseAddress) == "" {
return errors.New("企业地址不能为空")
}
if len(e.EnterpriseAddress) < 5 {
return errors.New("企业地址长度不能少于5个字符")
}
if len(e.EnterpriseAddress) > 200 {
return errors.New("企业地址长度不能超过200个字符")
}
return nil
}
// validateEnterpriseEmail 验证企业邮箱
func (e *EnterpriseInfo) validateEnterpriseEmail() error {
if strings.TrimSpace(e.EnterpriseEmail) == "" {
return errors.New("企业邮箱不能为空")
}
if len(e.EnterpriseEmail) < 5 {
return errors.New("企业邮箱长度不能少于5个字符")
}
if len(e.EnterpriseEmail) > 100 {
return errors.New("企业邮箱长度不能超过100个字符")
}
// 邮箱格式验证
emailPattern := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailPattern.MatchString(e.EnterpriseEmail) {
return errors.New("企业邮箱格式不正确")
}
return nil
}
@@ -220,16 +264,16 @@ func (e *EnterpriseInfo) IsComplete() bool {
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != "" &&
e.LegalPersonPhone != ""
e.LegalPersonPhone != "" &&
e.EnterpriseAddress != "" &&
e.EnterpriseEmail != ""
}
// IsDetailComplete 检查企业详细信息是否完整
func (e *EnterpriseInfo) IsDetailComplete() bool {
return e.IsComplete() &&
e.RegisteredAddress != "" &&
e.BusinessScope != "" &&
e.RegisteredCapital != "" &&
e.EstablishmentDate != ""
e.EnterpriseAddress != ""
}
// GetDisplayName 获取显示用的企业名称
@@ -245,7 +289,7 @@ func (e *EnterpriseInfo) GetMaskedUnifiedSocialCode() string {
if len(e.UnifiedSocialCode) != 18 {
return e.UnifiedSocialCode
}
// 保留前6位和后4位中间用*替代
return e.UnifiedSocialCode[:6] + "********" + e.UnifiedSocialCode[14:]
}
@@ -255,7 +299,7 @@ func (e *EnterpriseInfo) GetMaskedLegalPersonID() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
// 保留前6位和后4位中间用*替代
return e.LegalPersonID[:6] + "********" + e.LegalPersonID[14:]
}
@@ -265,7 +309,7 @@ func (e *EnterpriseInfo) GetMaskedLegalPersonPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
// 保留前3位和后4位中间用*替代
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
@@ -275,34 +319,34 @@ func (e *EnterpriseInfo) Equals(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.CompanyName == other.CompanyName &&
e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonName == other.LegalPersonName &&
e.LegalPersonID == other.LegalPersonID &&
e.LegalPersonPhone == other.LegalPersonPhone
e.LegalPersonPhone == other.LegalPersonPhone &&
e.EnterpriseEmail == other.EnterpriseEmail
}
// Clone 创建企业信息的副本
func (e *EnterpriseInfo) Clone() *EnterpriseInfo {
return &EnterpriseInfo{
CompanyName: e.CompanyName,
UnifiedSocialCode: e.UnifiedSocialCode,
LegalPersonName: e.LegalPersonName,
LegalPersonID: e.LegalPersonID,
LegalPersonPhone: e.LegalPersonPhone,
RegisteredAddress: e.RegisteredAddress,
BusinessScope: e.BusinessScope,
RegisteredCapital: e.RegisteredCapital,
EstablishmentDate: e.EstablishmentDate,
CompanyName: e.CompanyName,
UnifiedSocialCode: e.UnifiedSocialCode,
LegalPersonName: e.LegalPersonName,
LegalPersonID: e.LegalPersonID,
LegalPersonPhone: e.LegalPersonPhone,
RegisteredAddress: e.RegisteredAddress,
EnterpriseAddress: e.EnterpriseAddress,
EnterpriseEmail: e.EnterpriseEmail,
}
}
// String 返回企业信息的字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
e.CompanyName,
e.GetMaskedUnifiedSocialCode(),
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
e.CompanyName,
e.GetMaskedUnifiedSocialCode(),
e.LegalPersonName)
}
@@ -315,9 +359,8 @@ func (e *EnterpriseInfo) ToMap() map[string]interface{} {
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
"registered_address": e.RegisteredAddress,
"business_scope": e.BusinessScope,
"registered_capital": e.RegisteredCapital,
"establishment_date": e.EstablishmentDate,
"enterprise_address": e.EnterpriseAddress,
"enterprise_email": e.EnterpriseEmail,
}
}
@@ -331,22 +374,21 @@ func FromMap(data map[string]interface{}) (*EnterpriseInfo, error) {
}
return ""
}
info := &EnterpriseInfo{
CompanyName: getString("company_name"),
UnifiedSocialCode: getString("unified_social_code"),
LegalPersonName: getString("legal_person_name"),
LegalPersonID: getString("legal_person_id"),
LegalPersonPhone: getString("legal_person_phone"),
RegisteredAddress: getString("registered_address"),
BusinessScope: getString("business_scope"),
RegisteredCapital: getString("registered_capital"),
EstablishmentDate: getString("establishment_date"),
CompanyName: getString("company_name"),
UnifiedSocialCode: getString("unified_social_code"),
LegalPersonName: getString("legal_person_name"),
LegalPersonID: getString("legal_person_id"),
LegalPersonPhone: getString("legal_person_phone"),
RegisteredAddress: getString("registered_address"),
EnterpriseAddress: getString("enterprise_address"),
EnterpriseEmail: getString("enterprise_email"),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("从Map创建企业信息失败: %w", err)
}
return info, nil
}
}

View File

@@ -9,7 +9,8 @@ const (
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同(认证完成)
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
StatusCompleted CertificationStatus = "completed" // 认证完成
// === 失败状态 ===
StatusInfoRejected CertificationStatus = "info_rejected" // 企业信息被拒绝
@@ -24,6 +25,7 @@ var AllStatuses = []CertificationStatus{
StatusEnterpriseVerified,
StatusContractApplied,
StatusContractSigned,
StatusCompleted,
StatusInfoRejected,
StatusContractRejected,
StatusContractExpired,
@@ -62,7 +64,8 @@ func GetStatusName(status CertificationStatus) string {
StatusInfoSubmitted: "已提交企业信息",
StatusEnterpriseVerified: "已企业认证",
StatusContractApplied: "已申请签署合同",
StatusContractSigned: "认证完成",
StatusContractSigned: "已签署合同",
StatusCompleted: "认证完成",
StatusInfoRejected: "企业信息被拒绝",
StatusContractRejected: "合同被拒签",
StatusContractExpired: "合同签署超时",
@@ -76,7 +79,7 @@ func GetStatusName(status CertificationStatus) string {
// IsFinalStatus 判断是否为最终状态
func IsFinalStatus(status CertificationStatus) bool {
return status == StatusContractSigned
return status == StatusCompleted
}
// IsFailureStatus 判断是否为失败状态
@@ -107,6 +110,9 @@ func GetStatusCategory(status CertificationStatus) string {
if IsFailureStatus(status) {
return "失败状态"
}
if status == StatusCompleted {
return "完成"
}
return "未知"
}
@@ -118,9 +124,10 @@ func GetStatusPriority(status CertificationStatus) int {
StatusEnterpriseVerified: 3,
StatusContractApplied: 4,
StatusContractSigned: 5,
StatusInfoRejected: 6,
StatusContractRejected: 7,
StatusContractExpired: 8,
StatusCompleted: 6,
StatusInfoRejected: 7,
StatusContractRejected: 8,
StatusContractExpired: 9,
}
if priority, exists := priorities[status]; exists {
@@ -137,6 +144,7 @@ func GetProgressPercentage(status CertificationStatus) int {
StatusEnterpriseVerified: 50,
StatusContractApplied: 75,
StatusContractSigned: 100,
StatusCompleted: 100,
StatusInfoRejected: 25,
StatusContractRejected: 75,
StatusContractExpired: 75,
@@ -155,7 +163,8 @@ func IsUserActionRequired(status CertificationStatus) bool {
StatusInfoSubmitted: false, // 等待系统验证
StatusEnterpriseVerified: true, // 需要申请合同
StatusContractApplied: true, // 需要签署合同
StatusContractSigned: false, // 已完成
StatusContractSigned: false, // 合同已签署,等待系统处理
StatusCompleted: false, // 已完成
StatusInfoRejected: true, // 需要重新提交
StatusContractRejected: true, // 需要重新申请
StatusContractExpired: true, // 需要重新申请
@@ -171,10 +180,11 @@ func IsUserActionRequired(status CertificationStatus) bool {
func GetUserActionHint(status CertificationStatus) string {
hints := map[CertificationStatus]string{
StatusPending: "请提交企业信息",
StatusInfoSubmitted: "系统正在验证企业信息,请稍候",
StatusInfoSubmitted: "请完成企业认证",
StatusEnterpriseVerified: "企业认证完成,请申请签署合同",
StatusContractApplied: "请在规定时间内完成合同签署",
StatusContractSigned: "认证已完成",
StatusContractSigned: "合同已签署,等待系统处理",
StatusCompleted: "认证已完成",
StatusInfoRejected: "企业信息验证失败,请修正后重新提交",
StatusContractRejected: "合同签署被拒绝,可重新申请",
StatusContractExpired: "合同签署已超时,请重新申请",
@@ -205,6 +215,9 @@ func GetNextValidStatuses(currentStatus CertificationStatus) []CertificationStat
StatusContractExpired,
},
StatusContractSigned: {
StatusCompleted, // 可以转换到完成状态
},
StatusCompleted: {
// 最终状态,无后续状态
},
StatusInfoRejected: {
@@ -243,6 +256,7 @@ func GetTransitionReason(from, to CertificationStatus) string {
string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败",
string(StatusEnterpriseVerified) + "->" + string(StatusContractApplied): "用户申请签署合同",
string(StatusContractApplied) + "->" + string(StatusContractSigned): "e签宝合同签署成功",
string(StatusContractSigned) + "->" + string(StatusCompleted): "系统处理完成,认证成功",
string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同",
string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时",
string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息",

View File

@@ -114,7 +114,7 @@ func (h *CertificationEventHandler) handleEnterpriseInfoSubmitted(ctx context.Co
)
// 发送通知给用户
message := fmt.Sprintf("✅ 企业信息提交成功!\n\n认证ID: %s\n提交时间: %s\n\n系统正在验证企业信息,请稍候...",
message := fmt.Sprintf("✅ 企业信息提交成功!\n\n认证ID: %s\n提交时间: %s\n\n请完成企业认证...",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
@@ -195,7 +195,7 @@ func (h *CertificationEventHandler) sendUserNotification(ctx context.Context, ev
zap.String("title", title),
zap.String("message", message),
)
h.logger.Info("发送用户通知", zap.String("user_id", userID), zap.String("title", title), zap.String("message", message))
return nil
}

View File

@@ -14,16 +14,16 @@ type CertificationCommandRepository interface {
Create(ctx context.Context, cert entities.Certification) error
Update(ctx context.Context, cert entities.Certification) error
Delete(ctx context.Context, id string) error
// 业务特定的更新操作
UpdateStatus(ctx context.Context, id string, status enums.CertificationStatus) error
UpdateAuthFlowID(ctx context.Context, id string, authFlowID string) error
UpdateContractInfo(ctx context.Context, id string, contractFileID, esignFlowID, contractURL, contractSignURL string) error
UpdateFailureInfo(ctx context.Context, id string, reason enums.FailureReason, message string) error
// 批量操作
BatchUpdateStatus(ctx context.Context, ids []string, status enums.CertificationStatus) error
// 事务支持
WithTx(tx interfaces.Transaction) CertificationCommandRepository
}
}

View File

@@ -16,6 +16,7 @@ type CertificationQueryRepository interface {
GetByID(ctx context.Context, id string) (*entities.Certification, error)
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
Exists(ctx context.Context, id string) (bool, error)
ExistsByUserID(ctx context.Context, userID string) (bool, error)
// 列表查询
List(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
@@ -31,9 +32,6 @@ type CertificationQueryRepository interface {
GetCertificationsByDateRange(ctx context.Context, startDate, endDate time.Time) ([]*entities.Certification, error)
GetUserActiveCertification(ctx context.Context, userID string) (*entities.Certification, error)
// 统计查询
GetStatistics(ctx context.Context, period CertificationTimePeriod) (*CertificationStatistics, error)
CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error)
CountByFailureReason(ctx context.Context, reason enums.FailureReason) (int64, error)
GetProgressStatistics(ctx context.Context) (*CertificationProgressStats, error)
@@ -56,37 +54,6 @@ const (
PeriodYearly CertificationTimePeriod = "yearly"
)
// CertificationStatistics 认证统计信息
type CertificationStatistics struct {
Period CertificationTimePeriod `json:"period"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
// 总体统计
TotalCertifications int64 `json:"total_certifications"`
CompletedCount int64 `json:"completed_count"`
FailedCount int64 `json:"failed_count"`
InProgressCount int64 `json:"in_progress_count"`
// 状态分布
StatusDistribution map[enums.CertificationStatus]int64 `json:"status_distribution"`
// 失败原因分布
FailureDistribution map[enums.FailureReason]int64 `json:"failure_distribution"`
// 成功率统计
SuccessRate float64 `json:"success_rate"`
EnterpriseVerifyRate float64 `json:"enterprise_verify_rate"`
ContractSignRate float64 `json:"contract_sign_rate"`
// 时间统计
AvgProcessingTime time.Duration `json:"avg_processing_time"`
AvgVerificationTime time.Duration `json:"avg_verification_time"`
AvgSigningTime time.Duration `json:"avg_signing_time"`
// 重试统计
RetryStats *CertificationRetryStats `json:"retry_stats"`
}
// CertificationProgressStats 进度统计信息
type CertificationProgressStats struct {

View File

@@ -0,0 +1,14 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
)
type EnterpriseInfoSubmitRecordRepository interface {
Create(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error
Update(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error
Exists(ctx context.Context, ID string) (bool, error)
FindLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
FindLatestVerifiedByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
}

View File

@@ -7,7 +7,6 @@ import (
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/services/state_machine"
"go.uber.org/zap"
)
@@ -19,40 +18,34 @@ type CertificationAggregateService interface {
CreateCertification(ctx context.Context, userID string) (*entities.Certification, error)
LoadCertification(ctx context.Context, certificationID string) (*entities.Certification, error)
SaveCertification(ctx context.Context, cert *entities.Certification) error
// 状态转换管理
TransitionState(ctx context.Context, certificationID string, targetStatus enums.CertificationStatus, actor enums.ActorType, actorID string, reason string, metadata map[string]interface{}) (*state_machine.StateTransitionResult, error)
ValidateStateTransition(ctx context.Context, certificationID string, targetStatus enums.CertificationStatus, actor enums.ActorType) error
LoadCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error)
LoadCertificationByAuthFlowId(ctx context.Context, authFlowId string) (*entities.Certification, error)
LoadCertificationByEsignFlowId(ctx context.Context, esignFlowId string) (*entities.Certification, error)
// 业务规则验证
ValidateBusinessRules(ctx context.Context, cert *entities.Certification) error
CheckInvariance(ctx context.Context, cert *entities.Certification) error
// 查询方法
GetStateInfo(status enums.CertificationStatus) *state_machine.StateConfig
GetValidTransitions(ctx context.Context, certificationID string, actor enums.ActorType) ([]*state_machine.StateTransitionRule, error)
ExistsByUserID(ctx context.Context, userID string) (bool, error)
}
// CertificationAggregateServiceImpl 认证聚合服务实现
type CertificationAggregateServiceImpl struct {
commandRepo repositories.CertificationCommandRepository
queryRepo repositories.CertificationQueryRepository
stateMachine *state_machine.CertificationStateMachine
logger *zap.Logger
commandRepo repositories.CertificationCommandRepository
queryRepo repositories.CertificationQueryRepository
logger *zap.Logger
}
// NewCertificationAggregateService 创建认证聚合服务
func NewCertificationAggregateService(
commandRepo repositories.CertificationCommandRepository,
queryRepo repositories.CertificationQueryRepository,
stateMachine *state_machine.CertificationStateMachine,
logger *zap.Logger,
) CertificationAggregateService {
return &CertificationAggregateServiceImpl{
commandRepo: commandRepo,
queryRepo: queryRepo,
stateMachine: stateMachine,
logger: logger,
commandRepo: commandRepo,
queryRepo: queryRepo,
logger: logger,
}
}
@@ -63,17 +56,15 @@ func (s *CertificationAggregateServiceImpl) CreateCertification(ctx context.Cont
s.logger.Info("创建认证申请", zap.String("user_id", userID))
// 1. 检查用户是否已有认证申请
existingCert, err := s.queryRepo.GetByUserID(ctx, userID)
if err == nil && existingCert != nil {
// 检查现有认证的状态
if !existingCert.IsFinalStatus() {
return nil, fmt.Errorf("用户已有进行中的认证申请,请先完成或取消现有申请")
}
s.logger.Info("用户已有完成的认证申请,允许创建新申请",
zap.String("user_id", userID),
zap.String("existing_cert_id", existingCert.ID),
zap.String("existing_status", string(existingCert.Status)))
exists, err := s.ExistsByUserID(ctx, userID)
if err != nil {
s.logger.Error("检查用户认证是否存在失败", zap.Error(err), zap.String("user_id", userID))
return nil, fmt.Errorf("检查用户认证是否存在失败: %w", err)
}
if exists {
s.logger.Info("用户已有认证申请,不允许创建新申请",
zap.String("user_id", userID))
return nil, fmt.Errorf("用户已有认证申请")
}
// 2. 创建新的认证聚合根
@@ -122,6 +113,48 @@ func (s *CertificationAggregateServiceImpl) LoadCertification(ctx context.Contex
return cert, nil
}
// LoadCertificationByUserID 加载用户认证聚合根
func (s *CertificationAggregateServiceImpl) LoadCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
s.logger.Debug("加载用户认证聚合根", zap.String("user_id", userID))
// 从查询仓储加载
cert, err := s.queryRepo.GetByUserID(ctx, userID)
if err != nil {
s.logger.Error("加载用户认证聚合根失败", zap.Error(err), zap.String("user_id", userID))
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
return cert, nil
}
// LoadCertificationByAuthFlowId 加载认证聚合根
func (s *CertificationAggregateServiceImpl) LoadCertificationByAuthFlowId(ctx context.Context, authFlowId string) (*entities.Certification, error) {
s.logger.Debug("加载认证聚合根", zap.String("auth_flow_id", authFlowId))
// 从查询仓储加载
cert, err := s.queryRepo.FindByAuthFlowID(ctx, authFlowId)
if err != nil {
s.logger.Error("加载认证聚合根失败", zap.Error(err), zap.String("auth_flow_id", authFlowId))
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
return cert, nil
}
// LoadCertificationByEsignFlowId 加载认证聚合根
func (s *CertificationAggregateServiceImpl) LoadCertificationByEsignFlowId(ctx context.Context, esignFlowId string) (*entities.Certification, error) {
s.logger.Debug("加载认证聚合根", zap.String("esign_flow_id", esignFlowId))
// 从查询仓储加载
cert, err := s.queryRepo.FindByEsignFlowID(ctx, esignFlowId)
if err != nil {
s.logger.Error("加载认证聚合根失败", zap.Error(err), zap.String("esign_flow_id", esignFlowId))
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
return cert, nil
}
// SaveCertification 保存认证聚合根
func (s *CertificationAggregateServiceImpl) SaveCertification(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("保存认证聚合根", zap.String("certification_id", cert.ID))
@@ -156,74 +189,6 @@ func (s *CertificationAggregateServiceImpl) SaveCertification(ctx context.Contex
return nil
}
// ================ 状态转换管理 ================
// TransitionState 执行状态转换
func (s *CertificationAggregateServiceImpl) TransitionState(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
actorID string,
reason string,
metadata map[string]interface{},
) (*state_machine.StateTransitionResult, error) {
s.logger.Info("执行状态转换",
zap.String("certification_id", certificationID),
zap.String("target_status", string(targetStatus)),
zap.String("actor", string(actor)),
zap.String("actor_id", actorID))
// 构建状态转换请求
req := &state_machine.StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: targetStatus,
Actor: actor,
ActorID: actorID,
Reason: reason,
Context: metadata,
AllowRollback: true,
}
// 执行状态转换
result, err := s.stateMachine.ExecuteTransition(ctx, req)
if err != nil {
s.logger.Error("状态转换执行失败",
zap.String("certification_id", certificationID),
zap.Error(err))
return result, err
}
s.logger.Info("状态转换执行成功",
zap.String("certification_id", certificationID),
zap.String("from_status", string(result.OldStatus)),
zap.String("to_status", string(result.NewStatus)))
return result, nil
}
// ValidateStateTransition 验证状态转换
func (s *CertificationAggregateServiceImpl) ValidateStateTransition(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
) error {
// 加载认证聚合根
cert, err := s.LoadCertification(ctx, certificationID)
if err != nil {
return err
}
// 检查是否可以转换
canTransition, message := s.stateMachine.CanTransition(cert, targetStatus, actor)
if !canTransition {
return fmt.Errorf("状态转换验证失败: %s", message)
}
return nil
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
@@ -280,26 +245,9 @@ func (s *CertificationAggregateServiceImpl) CheckInvariance(ctx context.Context,
// ================ 查询方法 ================
// GetStateInfo 获取状态信息
func (s *CertificationAggregateServiceImpl) GetStateInfo(status enums.CertificationStatus) *state_machine.StateConfig {
return s.stateMachine.GetStateInfo(status)
}
// GetValidTransitions 获取有效的状态转换
func (s *CertificationAggregateServiceImpl) GetValidTransitions(
ctx context.Context,
certificationID string,
actor enums.ActorType,
) ([]*state_machine.StateTransitionRule, error) {
// 加载认证聚合根
cert, err := s.LoadCertification(ctx, certificationID)
if err != nil {
return nil, err
}
// 获取有效转换
transitions := s.stateMachine.GetValidTransitions(cert, actor)
return transitions, nil
// Exists 判断认证是否存在
func (s *CertificationAggregateServiceImpl) ExistsByUserID(ctx context.Context, userID string) (bool, error) {
return s.queryRepo.ExistsByUserID(ctx, userID)
}
// ================ 私有方法 ================
@@ -344,6 +292,17 @@ func (s *CertificationAggregateServiceImpl) validateStatusInvariance(cert *entit
if cert.ContractSignedAt == nil {
return fmt.Errorf("合同签署状态下必须有签署完成时间")
}
case enums.StatusCompleted:
if cert.ContractFileID == "" || cert.EsignFlowID == "" || cert.ContractURL == "" {
return fmt.Errorf("认证完成状态下必须有完整的合同信息")
}
if cert.ContractSignedAt == nil {
return fmt.Errorf("认证完成状态下必须有合同签署时间")
}
if cert.CompletedAt == nil {
return fmt.Errorf("认证完成状态下必须有完成时间")
}
}
// 失败状态检查
@@ -380,5 +339,11 @@ func (s *CertificationAggregateServiceImpl) validateTimestampInvariance(cert *en
}
}
if cert.ContractSignedAt != nil && cert.CompletedAt != nil {
if cert.ContractSignedAt.After(*cert.CompletedAt) {
return fmt.Errorf("合同签署时间不能晚于认证完成时间")
}
}
return nil
}

View File

@@ -8,27 +8,31 @@ import (
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/services/state_machine"
"tyapi-server/internal/shared/esign"
"go.uber.org/zap"
)
// WorkflowResult 工作流执行结果
type WorkflowResult struct {
Success bool `json:"success"`
CertificationID string `json:"certification_id"`
CurrentStatus enums.CertificationStatus `json:"current_status"`
Message string `json:"message"`
Data map[string]interface{} `json:"data,omitempty"`
StateTransition *state_machine.StateTransitionResult `json:"state_transition,omitempty"`
ExecutedAt time.Time `json:"executed_at"`
Success bool `json:"success"`
CertificationID string `json:"certification_id"`
CurrentStatus enums.CertificationStatus `json:"current_status"`
Message string `json:"message"`
Data map[string]interface{} `json:"data,omitempty"`
ExecutedAt time.Time `json:"executed_at"`
}
// SubmitEnterpriseInfoCommand 提交企业信息命令
type SubmitEnterpriseInfoCommand struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
UserID string `json:"user_id"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
}
// 完成企业认证命令
type CompleteEnterpriseVerificationCommand struct {
AuthFlowId string `json:"auth_flow_id"`
}
// ApplyContractCommand 申请合同命令
@@ -39,16 +43,19 @@ type ApplyContractCommand struct {
// EsignCallbackCommand e签宝回调命令
type EsignCallbackCommand struct {
CertificationID string `json:"certification_id"`
CallbackType string `json:"callback_type"` // "auth_result" | "sign_result" | "flow_status"
CallbackData *state_machine.EsignCallbackData `json:"callback_data"`
CertificationID string `json:"certification_id"`
CallbackType string `json:"callback_type"` // "auth_result" | "sign_result" | "flow_status"
}
// CertificationWorkflowOrchestrator 认证工作流编排器接口
// 负责编排认证业务流程,协调各个领域服务的协作
type CertificationWorkflowOrchestrator interface {
// 用户操作用例
// 提交企业信息
SubmitEnterpriseInfo(ctx context.Context, cmd *SubmitEnterpriseInfoCommand) (*WorkflowResult, error)
// 完成企业认证
CompleteEnterpriseVerification(ctx context.Context, cmd *CompleteEnterpriseVerificationCommand) (*WorkflowResult, error)
// 申请合同签署
ApplyContract(ctx context.Context, cmd *ApplyContractCommand) (*WorkflowResult, error)
// e签宝回调处理
@@ -57,90 +64,170 @@ type CertificationWorkflowOrchestrator interface {
// 异常处理
HandleFailure(ctx context.Context, certificationID string, failureType string, reason string) (*WorkflowResult, error)
RetryOperation(ctx context.Context, certificationID string, operation string) (*WorkflowResult, error)
// 查询操作
GetCertification(ctx context.Context, userID string) (*WorkflowResult, error)
GetWorkflowStatus(ctx context.Context, certificationID string) (*WorkflowResult, error)
}
// CertificationWorkflowOrchestratorImpl 认证工作流编排器实现
type CertificationWorkflowOrchestratorImpl struct {
aggregateService CertificationAggregateService
callbackHandler *state_machine.EsignCallbackHandler
logger *zap.Logger
esignClient *esign.Client
}
// NewCertificationWorkflowOrchestrator 创建认证工作流编排器
func NewCertificationWorkflowOrchestrator(
aggregateService CertificationAggregateService,
callbackHandler *state_machine.EsignCallbackHandler,
logger *zap.Logger,
esignClient *esign.Client,
) CertificationWorkflowOrchestrator {
return &CertificationWorkflowOrchestratorImpl{
aggregateService: aggregateService,
callbackHandler: callbackHandler,
logger: logger,
esignClient: esignClient,
}
}
// ================ 用户操作用例 ================
// GetCertification 获取认证详情
func (o *CertificationWorkflowOrchestratorImpl) GetCertification(
ctx context.Context,
userID string,
) (*WorkflowResult, error) {
exists, err := o.aggregateService.ExistsByUserID(ctx, userID)
if err != nil {
o.logger.Error("获取认证信息失败", zap.Error(err))
return nil, fmt.Errorf("获取认证信息失败: %w", err)
}
var cert *entities.Certification
if !exists {
cert, err = o.aggregateService.CreateCertification(ctx, userID)
if err != nil {
o.logger.Error("创建认证信息失败", zap.Error(err))
return nil, fmt.Errorf("创建认证信息失败: %w", err)
}
} else {
cert, err = o.aggregateService.LoadCertificationByUserID(ctx, userID)
if err != nil {
o.logger.Error("获取认证信息失败", zap.Error(err))
return nil, fmt.Errorf("认证信息不存在: %w", err)
}
}
meta := cert.GetDataByStatus()
return o.createSuccessResult(userID, cert.Status, "获取认证信息成功", meta), nil
}
// SubmitEnterpriseInfo 用户提交企业信息
func (o *CertificationWorkflowOrchestratorImpl) SubmitEnterpriseInfo(
ctx context.Context,
cmd *SubmitEnterpriseInfoCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理企业信息提交",
zap.String("certification_id", cmd.CertificationID),
zap.String("user_id", cmd.UserID))
// 1. 验证命令完整性
if err := o.validateSubmitEnterpriseInfoCommand(cmd); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
// 1. 检查用户认证是否存在
exists, err := o.aggregateService.ExistsByUserID(ctx, cmd.UserID)
if err != nil {
return o.createFailureResult(cmd.UserID, "", fmt.Sprintf("检查用户认证是否存在失败: %s", err.Error())), err
}
if !exists {
// 创建
_, err := o.aggregateService.CreateCertification(ctx, cmd.UserID)
if err != nil {
return o.createFailureResult(cmd.UserID, "", fmt.Sprintf("创建认证信息失败: %s", err.Error())), err
}
}
// 1.1 验证企业信息
err = cmd.EnterpriseInfo.Validate()
if err != nil {
return o.createFailureResult(cmd.UserID, "", fmt.Sprintf("企业信息验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
cert, err := o.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
return o.createFailureResult(cmd.UserID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证业务前置条件
// 3. 验证业务前置条件(暂时没啥用,后面的都会校验)
if err := o.validateEnterpriseInfoSubmissionPreconditions(cert, cmd.UserID); err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
return o.createFailureResult(cmd.UserID, cert.Status, err.Error()), err
}
// 4. 执行状态转换
metadata := map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"user_id": cmd.UserID,
// 5. 调用e签宝看是否进行过认证
respMeta := map[string]interface{}{}
identity, err := o.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
OrgName: cmd.EnterpriseInfo.CompanyName,
})
if identity != nil && identity.Data.RealnameStatus == 1 {
o.logger.Info("企业认证成功", zap.Any("identity", identity))
err = cert.CompleteEnterpriseVerification()
if err != nil {
return o.createFailureResult(cmd.UserID, cert.Status, err.Error()), err
}
respMeta = map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"next_action": "企业已认证,可进行后续操作",
}
} else {
if err != nil {
o.logger.Error("e签宝查询企业认证信息失败或未进行企业认证", zap.Error(err))
}
authURL, err := o.esignClient.GenerateEnterpriseAuth(&esign.EnterpriseAuthRequest{
CompanyName: cmd.EnterpriseInfo.CompanyName,
UnifiedSocialCode: cmd.EnterpriseInfo.UnifiedSocialCode,
LegalPersonName: cmd.EnterpriseInfo.LegalPersonName,
LegalPersonID: cmd.EnterpriseInfo.LegalPersonID,
TransactorName: cmd.EnterpriseInfo.LegalPersonName,
TransactorMobile: cmd.EnterpriseInfo.LegalPersonPhone,
TransactorID: cmd.EnterpriseInfo.LegalPersonID,
})
if err != nil {
o.logger.Error("生成企业认证链接失败", zap.Error(err))
return o.createFailureResult(cmd.UserID, cert.Status, err.Error()), err
}
err = cert.SubmitEnterpriseInfo(cmd.EnterpriseInfo, authURL.AuthShortURL, authURL.AuthFlowID)
if err != nil {
return o.createFailureResult(cmd.UserID, cert.Status, err.Error()), err
}
respMeta = map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"authUrl": authURL.AuthURL,
"next_action": "请完成企业认证",
}
}
result, err := o.aggregateService.TransitionState(
ctx,
cmd.CertificationID,
enums.StatusInfoSubmitted,
enums.ActorTypeUser,
cmd.UserID,
"用户提交企业信息",
metadata,
)
err = o.aggregateService.SaveCertification(ctx, cert)
if err != nil {
o.logger.Error("企业信息提交状态转换失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
}
// 5. 执行后续处理如调用e签宝API
if err := o.triggerEnterpriseVerification(ctx, cmd.CertificationID, cmd.EnterpriseInfo); err != nil {
o.logger.Warn("触发企业认证失败", zap.Error(err))
// 这里不返回错误因为状态已经成功转换e签宝调用失败可以通过重试机制处理
return o.createFailureResult(cmd.UserID, cert.Status, err.Error()), err
}
// 6. 构建成功结果
return o.createSuccessResult(cmd.CertificationID, enums.StatusInfoSubmitted, "企业信息提交成功", map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"next_action": "等待企业认证结果",
}, result), nil
return o.createSuccessResult(cmd.UserID, enums.StatusInfoSubmitted, "企业信息提交成功", respMeta), nil
}
// CompleteEnterpriseVerification 完成企业认证
func (o *CertificationWorkflowOrchestratorImpl) CompleteEnterpriseVerification(
ctx context.Context,
cmd *CompleteEnterpriseVerificationCommand,
) (*WorkflowResult, error) {
cert, err := o.aggregateService.LoadCertificationByAuthFlowId(ctx, cmd.AuthFlowId)
if err != nil {
return o.createFailureResult(cmd.AuthFlowId, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
err = cert.CompleteEnterpriseVerification()
if err != nil {
return o.createFailureResult(cmd.AuthFlowId, "", fmt.Sprintf("完成企业认证失败: %s", err.Error())), err
}
err = o.aggregateService.SaveCertification(ctx, cert)
if err != nil {
return o.createFailureResult(cmd.AuthFlowId, "", fmt.Sprintf("保存认证信息失败: %s", err.Error())), err
}
o.logger.Info("完成企业认证", zap.String("certification_id", cert.ID))
return o.createSuccessResult(cmd.AuthFlowId, enums.StatusEnterpriseVerified, "企业认证成功", map[string]interface{}{}), nil
}
// ApplyContract 用户申请合同签署
@@ -148,55 +235,56 @@ func (o *CertificationWorkflowOrchestratorImpl) ApplyContract(
ctx context.Context,
cmd *ApplyContractCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理合同申请",
zap.String("certification_id", cmd.CertificationID),
zap.String("user_id", cmd.UserID))
return nil, nil
// o.logger.Info("开始处理合同申请",
// zap.String("certification_id", cmd.CertificationID),
// zap.String("user_id", cmd.UserID))
// 1. 验证命令完整性
if err := o.validateApplyContractCommand(cmd); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
}
// // 1. 验证命令完整性
// if err := o.validateApplyContractCommand(cmd); err != nil {
// return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
// }
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// // 2. 加载认证聚合根
// cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
// if err != nil {
// return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
// }
// 3. 验证业务前置条件
if err := o.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
}
// // 3. 验证业务前置条件
// if err := o.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
// return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
// }
// 4. 执行状态转换
result, err := o.aggregateService.TransitionState(
ctx,
cmd.CertificationID,
enums.StatusContractApplied,
enums.ActorTypeUser,
cmd.UserID,
"用户申请合同签署",
map[string]interface{}{},
)
if err != nil {
o.logger.Error("合同申请状态转换失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
}
// // 4. 执行状态转换
// result, err := o.aggregateService.TransitionState(
// ctx,
// cmd.CertificationID,
// enums.StatusContractApplied,
// enums.ActorTypeUser,
// cmd.UserID,
// "用户申请合同签署",
// map[string]interface{}{},
// )
// if err != nil {
// o.logger.Error("合同申请状态转换失败", zap.Error(err))
// return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
// }
// 5. 生成合同和签署链接
contractInfo, err := o.generateContractAndSignURL(ctx, cmd.CertificationID, cert)
if err != nil {
o.logger.Error("生成合同失败", zap.Error(err))
// 需要回滚状态
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("生成合同失败: %s", err.Error())), err
}
// // 5. 生成合同和签署链接
// contractInfo, err := o.generateContractAndSignURL(ctx, cmd.CertificationID, cert)
// if err != nil {
// o.logger.Error("生成合同失败", zap.Error(err))
// // 需要回滚状态
// return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("生成合同失败: %s", err.Error())), err
// }
// 6. 构建成功结果
return o.createSuccessResult(cmd.CertificationID, enums.StatusContractApplied, "合同申请成功", map[string]interface{}{
"contract_sign_url": contractInfo.ContractSignURL,
"contract_url": contractInfo.ContractURL,
"next_action": "请在规定时间内完成合同签署",
}, result), nil
// // 6. 构建成功结果
// return o.createSuccessResult(cmd.CertificationID, enums.StatusContractApplied, "合同申请成功", map[string]interface{}{
// "contract_sign_url": contractInfo.ContractSignURL,
// "contract_url": contractInfo.ContractURL,
// "next_action": "请在规定时间内完成合同签署",
// }, result), nil
}
// ================ e签宝回调处理 ================
@@ -209,56 +297,56 @@ func (o *CertificationWorkflowOrchestratorImpl) HandleEnterpriseVerificationCall
o.logger.Info("开始处理企业认证回调",
zap.String("certification_id", cmd.CertificationID),
zap.String("callback_type", cmd.CallbackType))
return nil, nil
// // 1. 验证回调数据
// if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
// return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
// }
// 1. 验证回调数据
if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
}
// // 2. 加载认证聚合根
// cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
// if err != nil {
// return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
// }
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// // 3. 验证回调处理前置条件
// if cert.Status != enums.StatusInfoSubmitted {
// return o.createFailureResult(cmd.CertificationID, cert.Status,
// fmt.Sprintf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(cert.Status))),
// fmt.Errorf("无效的状态转换")
// }
// 3. 验证回调处理前置条件
if cert.Status != enums.StatusInfoSubmitted {
return o.createFailureResult(cmd.CertificationID, cert.Status,
fmt.Sprintf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(cert.Status))),
fmt.Errorf("无效的状态转换")
}
// // 4. 处理回调
// err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
// if err != nil {
// o.logger.Error("处理企业认证回调失败", zap.Error(err))
// return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
// }
// 4. 处理回调
err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
if err != nil {
o.logger.Error("处理企业认证回调失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
}
// // 5. 重新加载认证信息获取最新状态
// updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
// if err != nil {
// return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
// }
// 5. 重新加载认证信息获取最新状态
updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
}
// // 6. 构建结果
// message := "企业认证回调处理成功"
// data := map[string]interface{}{
// "auth_flow_id": cmd.CallbackData.FlowID,
// "status": cmd.CallbackData.Status,
// }
// 6. 构建结果
message := "企业认证回调处理成功"
data := map[string]interface{}{
"auth_flow_id": cmd.CallbackData.FlowID,
"status": cmd.CallbackData.Status,
}
// if updatedCert.Status == enums.StatusEnterpriseVerified {
// message = "企业认证成功"
// data["next_action"] = "可以申请合同签署"
// } else if updatedCert.Status == enums.StatusInfoRejected {
// message = "企业认证失败"
// data["next_action"] = "请修正企业信息后重新提交"
// data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
// data["failure_message"] = updatedCert.FailureMessage
// }
if updatedCert.Status == enums.StatusEnterpriseVerified {
message = "企业认证成功"
data["next_action"] = "可以申请合同签署"
} else if updatedCert.Status == enums.StatusInfoRejected {
message = "企业认证失败"
data["next_action"] = "请修正企业信息后重新提交"
data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
data["failure_message"] = updatedCert.FailureMessage
}
return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
// return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
}
// HandleContractSignCallback 处理合同签署回调
@@ -270,56 +358,58 @@ func (o *CertificationWorkflowOrchestratorImpl) HandleContractSignCallback(
zap.String("certification_id", cmd.CertificationID),
zap.String("callback_type", cmd.CallbackType))
// 1. 验证回调数据
if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
}
// // 1. 验证回调数据
// if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
// return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
// }
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// // 2. 加载认证聚合根
// cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
// if err != nil {
// return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
// }
// 3. 验证回调处理前置条件
if cert.Status != enums.StatusContractApplied {
return o.createFailureResult(cmd.CertificationID, cert.Status,
fmt.Sprintf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(cert.Status))),
fmt.Errorf("无效的状态转换")
}
// // 3. 验证回调处理前置条件
// if cert.Status != enums.StatusContractApplied {
// return o.createFailureResult(cmd.CertificationID, cert.Status,
// fmt.Sprintf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(cert.Status))),
// fmt.Errorf("无效的状态转换")
// }
// 4. 处理回调
err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
if err != nil {
o.logger.Error("处理合同签署回调失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
}
// // 4. 处理回调
// err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
// if err != nil {
// o.logger.Error("处理合同签署回调失败", zap.Error(err))
// return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
// }
// 5. 重新加载认证信息获取最新状态
updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
}
// // 5. 重新加载认证信息获取最新状态
// updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
// if err != nil {
// return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
// }
// 6. 构建结果
message := "合同签署回调处理成功"
data := map[string]interface{}{
"esign_flow_id": cmd.CallbackData.FlowID,
"status": cmd.CallbackData.Status,
}
// // 6. 构建结果
// message := "合同签署回调处理成功"
// data := map[string]interface{}{
// "esign_flow_id": cmd.CallbackData.FlowID,
// "status": cmd.CallbackData.Status,
// }
if updatedCert.Status == enums.StatusContractSigned {
message = "认证完成"
data["next_action"] = "认证流程已完成"
data["contract_url"] = updatedCert.ContractURL
} else if enums.IsFailureStatus(updatedCert.Status) {
message = "合同签署失败"
data["next_action"] = "可以重新申请合同签署"
data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
data["failure_message"] = updatedCert.FailureMessage
}
// if updatedCert.Status == enums.StatusContractSigned {
// message = "认证完成"
// data["next_action"] = "认证流程已完成"
// data["contract_url"] = updatedCert.ContractURL
// } else if enums.IsFailureStatus(updatedCert.Status) {
// message = "合同签署失败"
// data["next_action"] = "可以重新申请合同签署"
// data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
// data["failure_message"] = updatedCert.FailureMessage
// }
// return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
return nil, nil
return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
}
// ================ 异常处理 ================
@@ -331,138 +421,61 @@ func (o *CertificationWorkflowOrchestratorImpl) HandleFailure(
failureType string,
reason string,
) (*WorkflowResult, error) {
o.logger.Info("开始处理业务失败",
zap.String("certification_id", certificationID),
zap.String("failure_type", failureType),
zap.String("reason", reason))
return nil, nil
// o.logger.Info("开始处理业务失败",
// zap.String("certification_id", certificationID),
// zap.String("failure_type", failureType),
// zap.String("reason", reason))
// 1. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// // 1. 加载认证聚合根
// cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
// if err != nil {
// return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
// }
// 2. 根据失败类型执行相应处理
var targetStatus enums.CertificationStatus
var failureReason enums.FailureReason
// // 2. 根据失败类型执行相应处理
// var targetStatus enums.CertificationStatus
// var failureReason enums.FailureReason
switch failureType {
case "enterprise_verification_failed":
targetStatus = enums.StatusInfoRejected
failureReason = enums.FailureReasonEsignVerificationFailed
case "contract_sign_failed":
targetStatus = enums.StatusContractRejected
failureReason = enums.FailureReasonSignProcessFailed
case "contract_expired":
targetStatus = enums.StatusContractExpired
failureReason = enums.FailureReasonContractExpired
default:
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("未知的失败类型: %s", failureType)),
fmt.Errorf("未知的失败类型")
}
// switch failureType {
// case "enterprise_verification_failed":
// targetStatus = enums.StatusInfoRejected
// failureReason = enums.FailureReasonEsignVerificationFailed
// case "contract_sign_failed":
// targetStatus = enums.StatusContractRejected
// failureReason = enums.FailureReasonSignProcessFailed
// case "contract_expired":
// targetStatus = enums.StatusContractExpired
// failureReason = enums.FailureReasonContractExpired
// default:
// return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("未知的失败类型: %s", failureType)),
// fmt.Errorf("未知的失败类型")
// }
// 3. 执行状态转换
metadata := map[string]interface{}{
"failure_reason": failureReason,
"failure_message": reason,
}
// // 3. 执行状态转换
// metadata := map[string]interface{}{
// "failure_reason": failureReason,
// "failure_message": reason,
// }
result, err := o.aggregateService.TransitionState(
ctx,
certificationID,
targetStatus,
enums.ActorTypeSystem,
"failure_handler",
fmt.Sprintf("系统处理失败: %s", reason),
metadata,
)
if err != nil {
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("失败处理状态转换失败: %s", err.Error())), err
}
// result, err := o.aggregateService.TransitionState(
// ctx,
// certificationID,
// targetStatus,
// enums.ActorTypeSystem,
// "failure_handler",
// fmt.Sprintf("系统处理失败: %s", reason),
// metadata,
// )
// if err != nil {
// return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("失败处理状态转换失败: %s", err.Error())), err
// }
return o.createSuccessResult(certificationID, targetStatus, "失败处理完成", map[string]interface{}{
"failure_type": failureType,
"failure_reason": enums.GetFailureReasonName(failureReason),
"can_retry": enums.IsRetryable(failureReason),
}, result), nil
}
// RetryOperation 重试操作
func (o *CertificationWorkflowOrchestratorImpl) RetryOperation(
ctx context.Context,
certificationID string,
operation string,
) (*WorkflowResult, error) {
o.logger.Info("开始重试操作",
zap.String("certification_id", certificationID),
zap.String("operation", operation))
// 1. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 2. 检查是否可以重试
if !enums.IsFailureStatus(cert.Status) {
return o.createFailureResult(certificationID, cert.Status, "当前状态不是失败状态,无需重试"),
fmt.Errorf("不需要重试")
}
if !enums.IsRetryable(cert.FailureReason) {
return o.createFailureResult(certificationID, cert.Status,
fmt.Sprintf("失败原因 %s 不支持重试", enums.GetFailureReasonName(cert.FailureReason))),
fmt.Errorf("不支持重试")
}
if cert.RetryCount >= 3 {
return o.createFailureResult(certificationID, cert.Status, "已达到最大重试次数限制"),
fmt.Errorf("超过重试限制")
}
// 3. 根据操作类型执行重试
var targetStatus enums.CertificationStatus
var reason string
switch operation {
case "enterprise_verification":
if cert.Status != enums.StatusInfoRejected {
return o.createFailureResult(certificationID, cert.Status, "当前状态不支持企业认证重试"),
fmt.Errorf("无效的重试操作")
}
targetStatus = enums.StatusInfoSubmitted
reason = "重新提交企业信息"
case "contract_application":
if cert.Status != enums.StatusContractRejected && cert.Status != enums.StatusContractExpired {
return o.createFailureResult(certificationID, cert.Status, "当前状态不支持合同申请重试"),
fmt.Errorf("无效的重试操作")
}
targetStatus = enums.StatusEnterpriseVerified
reason = "重置状态,准备重新申请合同"
default:
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("不支持的重试操作: %s", operation)),
fmt.Errorf("不支持的重试操作")
}
// 4. 执行状态转换
result, err := o.aggregateService.TransitionState(
ctx,
certificationID,
targetStatus,
enums.ActorTypeSystem,
"retry_handler",
reason,
map[string]interface{}{},
)
if err != nil {
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("重试状态转换失败: %s", err.Error())), err
}
return o.createSuccessResult(certificationID, targetStatus, "重试操作成功", map[string]interface{}{
"retry_operation": operation,
"retry_count": cert.RetryCount + 1,
"next_action": o.getNextActionForStatus(targetStatus),
}, result), nil
// return o.createSuccessResult(certificationID, targetStatus, "失败处理完成", map[string]interface{}{
// "failure_type": failureType,
// "failure_reason": enums.GetFailureReasonName(failureReason),
// "can_retry": enums.IsRetryable(failureReason),
// }, result), nil
}
// ================ 查询操作 ================
@@ -512,25 +525,11 @@ func (o *CertificationWorkflowOrchestratorImpl) GetWorkflowStatus(
data["contract_signed_at"] = cert.ContractSignedAt
}
return o.createSuccessResult(certificationID, cert.Status, "工作流状态查询成功", data, nil), nil
return o.createSuccessResult(certificationID, cert.Status, "工作流状态查询成功", data), nil
}
// ================ 辅助方法 ================
// validateSubmitEnterpriseInfoCommand 验证提交企业信息命令
func (o *CertificationWorkflowOrchestratorImpl) validateSubmitEnterpriseInfoCommand(cmd *SubmitEnterpriseInfoCommand) error {
if cmd.CertificationID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cmd.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if cmd.EnterpriseInfo == nil {
return fmt.Errorf("企业信息不能为空")
}
return cmd.EnterpriseInfo.Validate()
}
// validateApplyContractCommand 验证申请合同命令
func (o *CertificationWorkflowOrchestratorImpl) validateApplyContractCommand(cmd *ApplyContractCommand) error {
if cmd.CertificationID == "" {
@@ -606,7 +605,6 @@ func (o *CertificationWorkflowOrchestratorImpl) createSuccessResult(
status enums.CertificationStatus,
message string,
data map[string]interface{},
stateTransition *state_machine.StateTransitionResult,
) *WorkflowResult {
return &WorkflowResult{
Success: true,
@@ -614,7 +612,6 @@ func (o *CertificationWorkflowOrchestratorImpl) createSuccessResult(
CurrentStatus: status,
Message: message,
Data: data,
StateTransition: stateTransition,
ExecutedAt: time.Now(),
}
}

View File

@@ -0,0 +1,97 @@
package services
import (
"context"
"errors"
"fmt"
"tyapi-server/internal/config"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/infrastructure/external/westdex"
"github.com/tidwall/gjson"
"go.uber.org/zap"
)
// EnterpriseInfoSubmitRecordService 企业信息提交记录领域服务
// 负责与westdex等外部服务交互
// 领域服务应无状态
type EnterpriseInfoSubmitRecordService struct {
westdexService *westdex.WestDexService
repositories repositories.EnterpriseInfoSubmitRecordRepository
appConfig config.AppConfig
logger *zap.Logger
}
// NewEnterpriseInfoSubmitRecordService 构造函数
func NewEnterpriseInfoSubmitRecordService(westdexService *westdex.WestDexService, repositories repositories.EnterpriseInfoSubmitRecordRepository, appConfig config.AppConfig, logger *zap.Logger) *EnterpriseInfoSubmitRecordService {
return &EnterpriseInfoSubmitRecordService{
westdexService: westdexService,
repositories: repositories,
appConfig: appConfig,
logger: logger,
}
}
// Save 保存企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) Save(ctx context.Context, enterpriseInfoSubmitRecord *entities.EnterpriseInfoSubmitRecord) error {
exists, err := s.repositories.Exists(ctx, enterpriseInfoSubmitRecord.ID)
if err != nil {
return err
}
if exists {
return s.repositories.Update(ctx, enterpriseInfoSubmitRecord)
}
return s.repositories.Create(ctx, enterpriseInfoSubmitRecord)
}
// ValidateWithWestdex 调用westdexService验证企业信息
func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Context, info *value_objects.EnterpriseInfo) error {
if info == nil {
return errors.New("企业信息不能为空")
}
// 先做本地校验
if err := info.Validate(); err != nil {
return err
}
// 开发环境下跳过外部验证
if s.appConfig.IsDevelopment() {
s.logger.Info("开发环境:跳过企业信息外部验证",
zap.String("company_name", info.CompanyName),
zap.String("legal_person", info.LegalPersonName))
return nil
}
// 调用westdexService进行外部校验
reqParams := map[string]interface{}{
"data": map[string]interface{}{
"entname": info.CompanyName,
"realname": info.LegalPersonName,
"entmark": info.UnifiedSocialCode,
"idcard": info.LegalPersonID,
},
}
resp, err := s.westdexService.CallAPI("WEST00021", reqParams)
if err != nil {
return err
}
resultCode := gjson.GetBytes(resp, "code")
if !resultCode.Exists() {
return fmt.Errorf("校验企业信息错误")
}
if resultCode.String() != "00000" {
return fmt.Errorf("校验企业信息错误")
}
resultState := gjson.GetBytes(resp, "data.result.state")
if !resultState.Exists() {
return fmt.Errorf("校验企业信息错误")
}
if resultState.Int() != 1 {
return fmt.Errorf("企业信息不一致")
}
return nil
}

View File

@@ -1,237 +0,0 @@
package services
import (
"tyapi-server/internal/domains/certification/enums"
)
// StateConfig 状态配置
type StateConfig struct {
Status enums.CertificationStatus `json:"status"`
Name string `json:"name"`
ProgressPercentage int `json:"progress_percentage"`
IsUserActionRequired bool `json:"is_user_action_required"`
IsAdminActionRequired bool `json:"is_admin_action_required"`
TimestampField string `json:"timestamp_field,omitempty"`
Description string `json:"description"`
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
}
// TransitionConfig 状态转换配置
type TransitionConfig struct {
From enums.CertificationStatus `json:"from"`
To enums.CertificationStatus `json:"to"`
Action string `json:"action"`
ActionName string `json:"action_name"`
AllowUser bool `json:"allow_user"`
AllowAdmin bool `json:"allow_admin"`
RequiresValidation bool `json:"requires_validation"`
Description string `json:"description"`
}
// CertificationStateManager 认证状态管理器
type CertificationStateManager struct {
stateMap map[enums.CertificationStatus]*StateConfig
transitionMap map[enums.CertificationStatus][]*TransitionConfig
}
// NewCertificationStateManager 创建认证状态管理器
func NewCertificationStateManager() *CertificationStateManager {
manager := &CertificationStateManager{
stateMap: make(map[enums.CertificationStatus]*StateConfig),
transitionMap: make(map[enums.CertificationStatus][]*TransitionConfig),
}
// 初始化状态配置
manager.initStateConfigs()
return manager
}
// initStateConfigs 初始化状态配置
func (manager *CertificationStateManager) initStateConfigs() {
// 状态配置
states := []*StateConfig{
{
Status: enums.StatusPending,
Name: "待认证",
ProgressPercentage: 0,
IsUserActionRequired: true,
IsAdminActionRequired: false,
Description: "等待用户提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
},
{
Status: enums.StatusInfoSubmitted,
Name: "已提交企业信息",
ProgressPercentage: 20,
IsUserActionRequired: true,
IsAdminActionRequired: false,
TimestampField: "InfoSubmittedAt",
Description: "用户已提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
},
{
Status: enums.StatusEnterpriseVerified,
Name: "已企业认证",
ProgressPercentage: 40,
IsUserActionRequired: true,
IsAdminActionRequired: false,
TimestampField: "EnterpriseVerifiedAt",
Description: "企业认证已完成",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
},
{
Status: enums.StatusContractApplied,
Name: "已申请合同",
ProgressPercentage: 60,
IsUserActionRequired: true,
IsAdminActionRequired: false,
TimestampField: "ContractAppliedAt",
Description: "合同已申请",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned},
},
{
Status: enums.StatusContractSigned,
Name: "已签署合同",
ProgressPercentage: 80,
IsUserActionRequired: false,
IsAdminActionRequired: false,
TimestampField: "ContractSignedAt",
Description: "合同已签署",
NextValidStatuses: []enums.CertificationStatus{},
},
// 已完成状态已合并到StatusContractSigned中
}
// 转换配置
transitions := []*TransitionConfig{
// 提交企业信息
{
From: enums.StatusPending,
To: enums.StatusInfoSubmitted,
Action: "submit_info",
ActionName: "提交企业信息",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户提交企业信息",
},
// 重新提交企业信息
{
From: enums.StatusInfoSubmitted,
To: enums.StatusInfoSubmitted,
Action: "resubmit_info",
ActionName: "重新提交企业信息",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户重新提交企业信息",
},
// 企业认证
{
From: enums.StatusInfoSubmitted,
To: enums.StatusEnterpriseVerified,
Action: "enterprise_verify",
ActionName: "企业认证",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户完成企业认证",
},
// 申请合同
{
From: enums.StatusEnterpriseVerified,
To: enums.StatusContractApplied,
Action: "apply_contract",
ActionName: "申请合同",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: false,
Description: "用户申请合同",
},
// 签署合同
{
From: enums.StatusContractApplied,
To: enums.StatusContractSigned,
Action: "sign_contract",
ActionName: "签署合同",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户签署合同",
},
// 合同签署即为认证完成,无需额外状态转换
}
// 构建映射
for _, state := range states {
manager.stateMap[state.Status] = state
}
for _, transition := range transitions {
manager.transitionMap[transition.From] = append(manager.transitionMap[transition.From], transition)
}
}
// GetStateConfig 获取状态配置
func (manager *CertificationStateManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return manager.stateMap[status]
}
// GetTransitionConfigs 获取状态转换配置
func (manager *CertificationStateManager) GetTransitionConfigs(from enums.CertificationStatus) []*TransitionConfig {
return manager.transitionMap[from]
}
// CanTransition 检查是否可以转换
func (manager *CertificationStateManager) CanTransition(from enums.CertificationStatus, to enums.CertificationStatus, isUser bool, isAdmin bool) (bool, string) {
transitions := manager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
if isUser && !transition.AllowUser {
return false, "用户不允许执行此操作"
}
if isAdmin && !transition.AllowAdmin {
return false, "管理员不允许执行此操作"
}
if !isUser && !isAdmin && (transition.AllowUser || transition.AllowAdmin) {
return false, "此操作需要用户或管理员权限"
}
return true, ""
}
}
return false, "不支持的状态转换"
}
// GetProgressPercentage 获取进度百分比
func (manager *CertificationStateManager) GetProgressPercentage(status enums.CertificationStatus) int {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.ProgressPercentage
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (manager *CertificationStateManager) IsUserActionRequired(status enums.CertificationStatus) bool {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.IsUserActionRequired
}
return false
}
// IsAdminActionRequired 检查是否需要管理员操作
func (manager *CertificationStateManager) IsAdminActionRequired(status enums.CertificationStatus) bool {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.IsAdminActionRequired
}
return false
}
// GetNextValidStatuses 获取下一个有效状态
func (manager *CertificationStateManager) GetNextValidStatuses(status enums.CertificationStatus) []enums.CertificationStatus {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.NextValidStatuses
}
return []enums.CertificationStatus{}
}

View File

@@ -1,455 +0,0 @@
package state_machine
import (
"context"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"go.uber.org/zap"
)
// CertificationStateMachine 认证状态机
// 负责管理认证流程的状态转换、业务规则验证和事件发布
type CertificationStateMachine struct {
configManager *StateConfigManager
repository repositories.CertificationCommandRepository
eventPublisher interface{} // TODO: 使用 interfaces.EventPublisher
logger *zap.Logger
}
// NewCertificationStateMachine 创建认证状态机
func NewCertificationStateMachine(
repository repositories.CertificationCommandRepository,
eventPublisher interface{}, // TODO: 使用 interfaces.EventPublisher
logger *zap.Logger,
) *CertificationStateMachine {
return &CertificationStateMachine{
configManager: NewStateConfigManager(),
repository: repository,
eventPublisher: eventPublisher,
logger: logger,
}
}
// StateTransitionRequest 状态转换请求
type StateTransitionRequest struct {
CertificationID string `json:"certification_id"`
TargetStatus enums.CertificationStatus `json:"target_status"`
Actor enums.ActorType `json:"actor"`
ActorID string `json:"actor_id"`
Reason string `json:"reason"`
Context map[string]interface{} `json:"context"`
AllowRollback bool `json:"allow_rollback"`
}
// StateTransitionResult 状态转换结果
type StateTransitionResult struct {
Success bool `json:"success"`
OldStatus enums.CertificationStatus `json:"old_status"`
NewStatus enums.CertificationStatus `json:"new_status"`
Message string `json:"message"`
TransitionedAt time.Time `json:"transitioned_at"`
Events []interface{} `json:"events,omitempty"`
}
// CanTransition 检查是否可以执行状态转换
func (sm *CertificationStateMachine) CanTransition(
cert *entities.Certification,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
) (bool, string) {
// 1. 检查基本状态转换规则
canTransition, message := sm.configManager.CanTransition(cert.Status, targetStatus, actor)
if !canTransition {
return false, message
}
// 2. 检查认证实体的业务规则
if canTransition, message := cert.CanTransitionTo(targetStatus, actor); !canTransition {
return false, message
}
// 3. 检查是否为最终状态
if cert.IsFinalStatus() {
return false, "认证已完成,无法进行状态转换"
}
return true, ""
}
// ExecuteTransition 执行状态转换
func (sm *CertificationStateMachine) ExecuteTransition(
ctx context.Context,
req *StateTransitionRequest,
) (*StateTransitionResult, error) {
sm.logger.Info("开始执行状态转换",
zap.String("certification_id", req.CertificationID),
zap.String("target_status", string(req.TargetStatus)),
zap.String("actor", string(req.Actor)),
zap.String("actor_id", req.ActorID))
// 1. 加载认证聚合根
cert, err := sm.loadCertification(ctx, req.CertificationID)
if err != nil {
return sm.createFailureResult(cert.Status, req.TargetStatus, fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
oldStatus := cert.Status
// 2. 验证转换合法性
if canTransition, message := sm.CanTransition(cert, req.TargetStatus, req.Actor); !canTransition {
return sm.createFailureResult(oldStatus, req.TargetStatus, message), fmt.Errorf("状态转换验证失败: %s", message)
}
// 3. 验证业务规则
if err := sm.validateBusinessRules(cert, req); err != nil {
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("业务规则验证失败: %s", err.Error())), err
}
// 4. 执行状态转换
if err := cert.TransitionTo(req.TargetStatus, req.Actor, req.ActorID, req.Reason); err != nil {
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("状态转换执行失败: %s", err.Error())), err
}
// 5. 保存到数据库
if err := sm.repository.Update(ctx, *cert); err != nil {
// 如果保存失败,需要回滚状态
sm.logger.Error("状态转换保存失败,尝试回滚",
zap.String("certification_id", req.CertificationID),
zap.Error(err))
if req.AllowRollback {
if rollbackErr := sm.rollbackStateTransition(ctx, cert, oldStatus, req.Actor, req.ActorID); rollbackErr != nil {
sm.logger.Error("状态回滚失败", zap.Error(rollbackErr))
}
}
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("保存状态转换失败: %s", err.Error())), err
}
// 6. 发布领域事件
events := cert.GetDomainEvents()
for _, event := range events {
// TODO: 实现事件发布
// if err := sm.eventPublisher.PublishEvent(ctx, event); err != nil {
// sm.logger.Error("发布领域事件失败",
// zap.String("certification_id", req.CertificationID),
// zap.Error(err))
// }
sm.logger.Info("领域事件待发布",
zap.String("certification_id", req.CertificationID),
zap.Any("event", event))
}
// 7. 清理领域事件
cert.ClearDomainEvents()
// 8. 记录成功日志
sm.logger.Info("状态转换执行成功",
zap.String("certification_id", req.CertificationID),
zap.String("from_status", string(oldStatus)),
zap.String("to_status", string(req.TargetStatus)),
zap.String("actor", string(req.Actor)))
// 9. 返回成功结果
return &StateTransitionResult{
Success: true,
OldStatus: oldStatus,
NewStatus: req.TargetStatus,
Message: "状态转换成功",
TransitionedAt: time.Now(),
Events: events,
}, nil
}
// GetValidTransitions 获取有效的状态转换
func (sm *CertificationStateMachine) GetValidTransitions(
cert *entities.Certification,
actor enums.ActorType,
) []*StateTransitionRule {
return sm.configManager.GetAllowedTransitions(cert.Status, actor)
}
// GetStateInfo 获取状态信息
func (sm *CertificationStateMachine) GetStateInfo(status enums.CertificationStatus) *StateConfig {
return sm.configManager.GetStateConfig(status)
}
// ValidateBusinessRules 验证业务规则
func (sm *CertificationStateMachine) ValidateBusinessRules(
cert *entities.Certification,
req *StateTransitionRequest,
) error {
return sm.validateBusinessRules(cert, req)
}
// IsUserActionRequired 检查是否需要用户操作
func (sm *CertificationStateMachine) IsUserActionRequired(status enums.CertificationStatus) bool {
return sm.configManager.IsUserActionRequired(status)
}
// GetProgressPercentage 获取进度百分比
func (sm *CertificationStateMachine) GetProgressPercentage(status enums.CertificationStatus) int {
return sm.configManager.GetStateProgress(status)
}
// ================ 私有方法 ================
// loadCertification 加载认证聚合根
func (sm *CertificationStateMachine) loadCertification(ctx context.Context, certificationID string) (*entities.Certification, error) {
// 这里需要通过查询仓储获取认证信息
// 由于当前只有命令仓储,这里使用简单的方法
// 在实际实现中,应该使用查询仓储
cert := &entities.Certification{ID: certificationID}
// TODO: 实现从查询仓储加载认证信息
// cert, err := sm.queryRepository.GetByID(ctx, certificationID)
// if err != nil {
// return nil, fmt.Errorf("认证信息不存在: %w", err)
// }
return cert, nil
}
// validateBusinessRules 验证业务规则
func (sm *CertificationStateMachine) validateBusinessRules(
cert *entities.Certification,
req *StateTransitionRequest,
) error {
// 获取转换规则
rule := sm.configManager.GetTransitionRule(cert.Status, req.TargetStatus)
if rule == nil {
return fmt.Errorf("找不到状态转换规则")
}
// 如果不需要验证,直接返回
if !rule.RequiresValidation {
return nil
}
// 构建验证上下文
context := make(map[string]interface{})
// 添加认证基本信息
context["certification_id"] = cert.ID
context["user_id"] = cert.UserID
context["current_status"] = string(cert.Status)
context["retry_count"] = cert.RetryCount
context["auth_flow_id"] = cert.AuthFlowID
// 添加请求中的上下文信息
for key, value := range req.Context {
context[key] = value
}
// 执行业务规则验证
return sm.configManager.ValidateBusinessRules(rule, context)
}
// rollbackStateTransition 回滚状态转换
func (sm *CertificationStateMachine) rollbackStateTransition(
ctx context.Context,
cert *entities.Certification,
originalStatus enums.CertificationStatus,
actor enums.ActorType,
actorID string,
) error {
sm.logger.Info("开始回滚状态转换",
zap.String("certification_id", cert.ID),
zap.String("original_status", string(originalStatus)),
zap.String("current_status", string(cert.Status)))
// 直接设置回原状态(跳过业务规则验证)
cert.Status = originalStatus
// 更新审计信息
now := time.Now()
cert.LastTransitionAt = &now
cert.LastTransitionBy = actor
cert.LastTransitionActor = actorID
// 保存回滚结果
if err := sm.repository.Update(ctx, *cert); err != nil {
return fmt.Errorf("保存回滚状态失败: %w", err)
}
sm.logger.Info("状态转换回滚成功",
zap.String("certification_id", cert.ID),
zap.String("rollback_to_status", string(originalStatus)))
return nil
}
// createFailureResult 创建失败结果
func (sm *CertificationStateMachine) createFailureResult(
oldStatus, targetStatus enums.CertificationStatus,
message string,
) *StateTransitionResult {
return &StateTransitionResult{
Success: false,
OldStatus: oldStatus,
NewStatus: targetStatus,
Message: message,
TransitionedAt: time.Now(),
Events: []interface{}{},
}
}
// ================ 状态转换快捷方法 ================
// TransitionToInfoSubmitted 转换到已提交企业信息状态
func (sm *CertificationStateMachine) TransitionToInfoSubmitted(
ctx context.Context,
certificationID string,
actor enums.ActorType,
actorID string,
enterpriseInfo interface{},
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusInfoSubmitted,
Actor: actor,
ActorID: actorID,
Reason: "用户提交企业信息",
Context: map[string]interface{}{
"enterprise_info": enterpriseInfo,
},
AllowRollback: true,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToEnterpriseVerified 转换到已企业认证状态
func (sm *CertificationStateMachine) TransitionToEnterpriseVerified(
ctx context.Context,
certificationID string,
authFlowID string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusEnterpriseVerified,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "e签宝企业认证成功",
Context: map[string]interface{}{
"auth_flow_id": authFlowID,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToInfoRejected 转换到企业信息被拒绝状态
func (sm *CertificationStateMachine) TransitionToInfoRejected(
ctx context.Context,
certificationID string,
failureReason enums.FailureReason,
failureMessage string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusInfoRejected,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "e签宝企业认证失败",
Context: map[string]interface{}{
"failure_reason": failureReason,
"failure_message": failureMessage,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractApplied 转换到已申请合同状态
func (sm *CertificationStateMachine) TransitionToContractApplied(
ctx context.Context,
certificationID string,
actor enums.ActorType,
actorID string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractApplied,
Actor: actor,
ActorID: actorID,
Reason: "用户申请合同签署",
Context: map[string]interface{}{},
AllowRollback: true,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractSigned 转换到已签署合同状态(认证完成)
func (sm *CertificationStateMachine) TransitionToContractSigned(
ctx context.Context,
certificationID string,
contractURL string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractSigned,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "e签宝合同签署成功认证完成",
Context: map[string]interface{}{
"contract_url": contractURL,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractRejected 转换到合同被拒签状态
func (sm *CertificationStateMachine) TransitionToContractRejected(
ctx context.Context,
certificationID string,
failureReason enums.FailureReason,
failureMessage string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractRejected,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "合同签署失败",
Context: map[string]interface{}{
"failure_reason": failureReason,
"failure_message": failureMessage,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractExpired 转换到合同签署超时状态
func (sm *CertificationStateMachine) TransitionToContractExpired(
ctx context.Context,
certificationID string,
failureMessage string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractExpired,
Actor: enums.ActorTypeSystem,
ActorID: "timeout_monitor",
Reason: "合同签署超时",
Context: map[string]interface{}{
"failure_reason": enums.FailureReasonContractExpired,
"failure_message": failureMessage,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}

Some files were not shown because too many files have changed in this diff Show More