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

View File

@@ -0,0 +1,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)
}

View File

@@ -1,391 +0,0 @@
package state_machine
import (
"context"
"encoding/json"
"fmt"
"tyapi-server/internal/domains/certification/enums"
"go.uber.org/zap"
)
// EsignCallbackData e签宝回调数据结构
type EsignCallbackData struct {
// 基础信息
Action string `json:"action"` // 回调动作类型
FlowID string `json:"flow_id"` // 流程ID
AccountID string `json:"account_id"` // 账户ID
Status string `json:"status"` // 状态
Message string `json:"message"` // 消息
Timestamp int64 `json:"timestamp"` // 时间戳
// 扩展数据
Data map[string]interface{} `json:"data,omitempty"` // 扩展数据
OriginalData string `json:"original_data"` // 原始回调数据
}
// EsignCallbackHandler e签宝回调处理器
// 负责处理e签宝的异步回调将外部回调转换为内部状态转换
type EsignCallbackHandler struct {
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewEsignCallbackHandler 创建e签宝回调处理器
func NewEsignCallbackHandler(
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *EsignCallbackHandler {
return &EsignCallbackHandler{
stateMachine: stateMachine,
logger: logger,
}
}
// HandleCallback 处理e签宝回调
func (h *EsignCallbackHandler) HandleCallback(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("接收到e签宝回调",
zap.String("certification_id", certificationID),
zap.String("action", callbackData.Action),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
// 根据动作类型分发处理
switch callbackData.Action {
case "auth_result":
return h.handleAuthResult(ctx, certificationID, callbackData)
case "sign_result":
return h.handleSignResult(ctx, certificationID, callbackData)
case "flow_status":
return h.handleFlowStatus(ctx, certificationID, callbackData)
default:
h.logger.Warn("未知的e签宝回调动作",
zap.String("certification_id", certificationID),
zap.String("action", callbackData.Action))
return fmt.Errorf("未知的回调动作: %s", callbackData.Action)
}
}
// handleAuthResult 处理企业认证结果回调
func (h *EsignCallbackHandler) handleAuthResult(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理企业认证结果回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
switch callbackData.Status {
case "success", "verified", "completed":
// 认证成功
_, err := h.stateMachine.TransitionToEnterpriseVerified(
ctx,
certificationID,
callbackData.FlowID,
)
return err
case "failed", "rejected", "error":
// 认证失败
failureReason := h.parseAuthFailureReason(callbackData)
_, err := h.stateMachine.TransitionToInfoRejected(
ctx,
certificationID,
failureReason,
callbackData.Message,
)
return err
default:
h.logger.Warn("未知的企业认证状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
return fmt.Errorf("未知的认证状态: %s", callbackData.Status)
}
}
// handleSignResult 处理合同签署结果回调
func (h *EsignCallbackHandler) handleSignResult(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理合同签署结果回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
switch callbackData.Status {
case "signed", "completed", "success":
// 签署成功
contractURL := h.extractContractURL(callbackData)
_, err := h.stateMachine.TransitionToContractSigned(
ctx,
certificationID,
contractURL,
)
return err
case "rejected", "refused":
// 用户拒绝签署
_, err := h.stateMachine.TransitionToContractRejected(
ctx,
certificationID,
enums.FailureReasonContractRejectedByUser,
callbackData.Message,
)
return err
case "expired", "timeout":
// 签署超时
_, err := h.stateMachine.TransitionToContractExpired(
ctx,
certificationID,
fmt.Sprintf("合同签署超时: %s", callbackData.Message),
)
return err
case "failed", "error":
// 签署失败
failureReason := h.parseSignFailureReason(callbackData)
_, err := h.stateMachine.TransitionToContractRejected(
ctx,
certificationID,
failureReason,
callbackData.Message,
)
return err
default:
h.logger.Warn("未知的合同签署状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
return fmt.Errorf("未知的签署状态: %s", callbackData.Status)
}
}
// handleFlowStatus 处理流程状态回调
func (h *EsignCallbackHandler) handleFlowStatus(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理流程状态回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
// 流程状态回调主要用于监控和日志记录
// 实际的状态转换由具体的auth_result和sign_result处理
switch callbackData.Status {
case "started", "processing", "in_progress":
h.logger.Info("e签宝流程进行中",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID))
case "paused", "suspended":
h.logger.Warn("e签宝流程被暂停",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("message", callbackData.Message))
case "cancelled", "terminated":
h.logger.Warn("e签宝流程被取消",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("message", callbackData.Message))
default:
h.logger.Info("收到其他流程状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
}
return nil
}
// parseAuthFailureReason 解析企业认证失败原因
func (h *EsignCallbackHandler) parseAuthFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
// 根据e签宝返回的错误信息解析失败原因
message := callbackData.Message
// 检查扩展数据中的错误码
if errorCode, exists := callbackData.Data["error_code"]; exists {
switch errorCode {
case "ENTERPRISE_NOT_FOUND", "ORG_NOT_EXISTS":
return enums.FailureReasonEnterpriseNotExists
case "INFO_MISMATCH", "ORG_INFO_ERROR":
return enums.FailureReasonEnterpriseInfoMismatch
case "STATUS_ABNORMAL", "ORG_STATUS_ERROR":
return enums.FailureReasonEnterpriseStatusAbnormal
case "LEGAL_PERSON_ERROR", "LEGAL_REP_ERROR":
return enums.FailureReasonLegalPersonMismatch
case "DOCUMENT_INVALID", "ID_CARD_ERROR":
return enums.FailureReasonInvalidDocument
}
}
// 根据错误消息文本判断
if message != "" {
if h.containsKeywords(message, []string{"企业不存在", "机构不存在", "not found"}) {
return enums.FailureReasonEnterpriseNotExists
}
if h.containsKeywords(message, []string{"信息不匹配", "信息错误", "mismatch"}) {
return enums.FailureReasonEnterpriseInfoMismatch
}
if h.containsKeywords(message, []string{"状态异常", "status abnormal"}) {
return enums.FailureReasonEnterpriseStatusAbnormal
}
if h.containsKeywords(message, []string{"法定代表人", "legal person", "法人"}) {
return enums.FailureReasonLegalPersonMismatch
}
if h.containsKeywords(message, []string{"证件", "身份证", "document", "id card"}) {
return enums.FailureReasonInvalidDocument
}
}
// 默认返回e签宝验证失败
return enums.FailureReasonEsignVerificationFailed
}
// parseSignFailureReason 解析合同签署失败原因
func (h *EsignCallbackHandler) parseSignFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
// 根据e签宝返回的错误信息解析失败原因
message := callbackData.Message
// 检查扩展数据中的错误码
if errorCode, exists := callbackData.Data["error_code"]; exists {
switch errorCode {
case "USER_REJECTED", "SIGN_REJECTED":
return enums.FailureReasonContractRejectedByUser
case "FLOW_EXPIRED", "SIGN_EXPIRED":
return enums.FailureReasonContractExpired
case "FLOW_ERROR", "SIGN_PROCESS_ERROR":
return enums.FailureReasonSignProcessFailed
case "ESIGN_ERROR", "SYSTEM_ERROR":
return enums.FailureReasonEsignFlowError
}
}
// 根据错误消息文本判断
if message != "" {
if h.containsKeywords(message, []string{"拒绝", "rejected", "refused"}) {
return enums.FailureReasonContractRejectedByUser
}
if h.containsKeywords(message, []string{"过期", "超时", "expired", "timeout"}) {
return enums.FailureReasonContractExpired
}
if h.containsKeywords(message, []string{"流程错误", "process error", "flow error"}) {
return enums.FailureReasonSignProcessFailed
}
}
// 默认返回e签宝流程错误
return enums.FailureReasonEsignFlowError
}
// extractContractURL 提取合同URL
func (h *EsignCallbackHandler) extractContractURL(callbackData *EsignCallbackData) string {
// 优先从扩展数据中获取
if contractURL, exists := callbackData.Data["contract_url"]; exists {
if url, ok := contractURL.(string); ok && url != "" {
return url
}
}
if downloadURL, exists := callbackData.Data["download_url"]; exists {
if url, ok := downloadURL.(string); ok && url != "" {
return url
}
}
if fileURL, exists := callbackData.Data["file_url"]; exists {
if url, ok := fileURL.(string); ok && url != "" {
return url
}
}
// 如果没有找到URL返回空字符串
h.logger.Warn("未能从回调数据中提取合同URL",
zap.Any("callback_data", callbackData.Data))
return ""
}
// containsKeywords 检查文本是否包含关键词
func (h *EsignCallbackHandler) containsKeywords(text string, keywords []string) bool {
for _, keyword := range keywords {
if len(text) >= len(keyword) {
for i := 0; i <= len(text)-len(keyword); i++ {
if text[i:i+len(keyword)] == keyword {
return true
}
}
}
}
return false
}
// ValidateCallbackData 验证回调数据
func (h *EsignCallbackHandler) ValidateCallbackData(callbackData *EsignCallbackData) error {
if callbackData == nil {
return fmt.Errorf("回调数据不能为空")
}
if callbackData.Action == "" {
return fmt.Errorf("回调动作不能为空")
}
if callbackData.FlowID == "" {
return fmt.Errorf("流程ID不能为空")
}
if callbackData.Status == "" {
return fmt.Errorf("状态不能为空")
}
return nil
}
// ParseCallbackData 解析原始回调数据
func (h *EsignCallbackHandler) ParseCallbackData(rawData string) (*EsignCallbackData, error) {
var callbackData EsignCallbackData
if err := json.Unmarshal([]byte(rawData), &callbackData); err != nil {
h.logger.Error("解析e签宝回调数据失败", zap.Error(err), zap.String("raw_data", rawData))
return nil, fmt.Errorf("解析回调数据失败: %w", err)
}
// 保存原始数据
callbackData.OriginalData = rawData
// 验证数据完整性
if err := h.ValidateCallbackData(&callbackData); err != nil {
return nil, fmt.Errorf("回调数据验证失败: %w", err)
}
return &callbackData, nil
}
// GetCallbackType 获取回调类型描述
func (h *EsignCallbackHandler) GetCallbackType(action string) string {
types := map[string]string{
"auth_result": "企业认证结果",
"sign_result": "合同签署结果",
"flow_status": "流程状态更新",
}
if typeName, exists := types[action]; exists {
return typeName
}
return "未知类型"
}

View File

@@ -1,438 +0,0 @@
package state_machine
import (
"fmt"
"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"`
IsSystemAction bool `json:"is_system_action"`
TimestampField string `json:"timestamp_field,omitempty"`
Description string `json:"description"`
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
AllowedActors []enums.ActorType `json:"allowed_actors"`
}
// StateTransitionRule 状态转换规则
type StateTransitionRule struct {
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
TransitionName string `json:"transition_name"`
AllowedActors []enums.ActorType `json:"allowed_actors"`
RequiresValidation bool `json:"requires_validation"`
Description string `json:"description"`
BusinessRules []string `json:"business_rules"`
}
// StateConfigManager 状态配置管理器
type StateConfigManager struct {
stateConfigs map[enums.CertificationStatus]*StateConfig
transitionRules map[string]*StateTransitionRule // key: "from_status->to_status"
actorPermissions map[enums.ActorType][]string // actor允许的操作
}
// NewStateConfigManager 创建状态配置管理器
func NewStateConfigManager() *StateConfigManager {
manager := &StateConfigManager{
stateConfigs: make(map[enums.CertificationStatus]*StateConfig),
transitionRules: make(map[string]*StateTransitionRule),
actorPermissions: make(map[enums.ActorType][]string),
}
manager.initializeStateConfigs()
manager.initializeTransitionRules()
manager.initializeActorPermissions()
return manager
}
// initializeStateConfigs 初始化状态配置
func (m *StateConfigManager) initializeStateConfigs() {
configs := []*StateConfig{
{
Status: enums.StatusPending,
Name: "待认证",
ProgressPercentage: 0,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "等待用户提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusInfoSubmitted,
Name: "已提交企业信息",
ProgressPercentage: 25,
IsUserActionRequired: false,
IsSystemAction: true,
TimestampField: "InfoSubmittedAt",
Description: "企业信息已提交等待e签宝验证",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoRejected},
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
},
{
Status: enums.StatusEnterpriseVerified,
Name: "已企业认证",
ProgressPercentage: 50,
IsUserActionRequired: true,
IsSystemAction: false,
TimestampField: "EnterpriseVerifiedAt",
Description: "企业认证完成,用户可申请合同",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusContractApplied,
Name: "已申请签署合同",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: true,
TimestampField: "ContractAppliedAt",
Description: "合同已生成,等待用户签署",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired},
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
},
{
Status: enums.StatusContractSigned,
Name: "认证完成",
ProgressPercentage: 100,
IsUserActionRequired: false,
IsSystemAction: false,
TimestampField: "ContractSignedAt",
Description: "认证流程已完成",
NextValidStatuses: []enums.CertificationStatus{},
AllowedActors: []enums.ActorType{},
},
// 失败状态
{
Status: enums.StatusInfoRejected,
Name: "企业信息被拒绝",
ProgressPercentage: 25,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "企业信息验证失败,需要重新提交",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusContractRejected,
Name: "合同被拒签",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "用户拒绝签署合同",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified},
AllowedActors: []enums.ActorType{enums.ActorTypeUser, enums.ActorTypeSystem},
},
{
Status: enums.StatusContractExpired,
Name: "合同签署超时",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "合同签署链接已过期",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified},
AllowedActors: []enums.ActorType{enums.ActorTypeUser, enums.ActorTypeSystem},
},
}
for _, config := range configs {
m.stateConfigs[config.Status] = config
}
}
// initializeTransitionRules 初始化状态转换规则
func (m *StateConfigManager) initializeTransitionRules() {
rules := []*StateTransitionRule{
// 用户提交企业信息
{
FromStatus: enums.StatusPending,
ToStatus: enums.StatusInfoSubmitted,
TransitionName: "submit_enterprise_info",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户提交企业信息",
BusinessRules: []string{"enterprise_info_complete", "enterprise_info_valid"},
},
// e签宝企业认证成功
{
FromStatus: enums.StatusInfoSubmitted,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "enterprise_verification_success",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝企业认证成功",
BusinessRules: []string{"auth_flow_id_exists"},
},
// e签宝企业认证失败
{
FromStatus: enums.StatusInfoSubmitted,
ToStatus: enums.StatusInfoRejected,
TransitionName: "enterprise_verification_failed",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝企业认证失败",
BusinessRules: []string{"failure_reason_provided"},
},
// 用户申请合同
{
FromStatus: enums.StatusEnterpriseVerified,
ToStatus: enums.StatusContractApplied,
TransitionName: "apply_contract",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户申请合同签署",
BusinessRules: []string{"enterprise_verified", "auth_flow_id_exists"},
},
// e签宝合同签署成功
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractSigned,
TransitionName: "contract_sign_success",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝合同签署成功",
BusinessRules: []string{"contract_info_complete"},
},
// 合同签署失败
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractRejected,
TransitionName: "contract_sign_rejected",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "用户拒绝签署合同",
BusinessRules: []string{"failure_reason_provided"},
},
// 合同签署超时
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractExpired,
TransitionName: "contract_sign_expired",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "合同签署超时",
BusinessRules: []string{"failure_reason_provided"},
},
// 重新提交企业信息
{
FromStatus: enums.StatusInfoRejected,
ToStatus: enums.StatusInfoSubmitted,
TransitionName: "resubmit_enterprise_info",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户重新提交企业信息",
BusinessRules: []string{"enterprise_info_complete", "enterprise_info_valid", "retry_limit_check"},
},
// 从合同失败状态恢复
{
FromStatus: enums.StatusContractRejected,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "reset_from_contract_rejected",
AllowedActors: []enums.ActorType{enums.ActorTypeSystem, enums.ActorTypeUser},
RequiresValidation: false,
Description: "从合同拒签状态恢复",
BusinessRules: []string{"retry_limit_check"},
},
{
FromStatus: enums.StatusContractExpired,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "reset_from_contract_expired",
AllowedActors: []enums.ActorType{enums.ActorTypeSystem, enums.ActorTypeUser},
RequiresValidation: false,
Description: "从合同超时状态恢复",
BusinessRules: []string{"retry_limit_check"},
},
}
for _, rule := range rules {
key := string(rule.FromStatus) + "->" + string(rule.ToStatus)
m.transitionRules[key] = rule
}
}
// initializeActorPermissions 初始化操作者权限
func (m *StateConfigManager) initializeActorPermissions() {
m.actorPermissions = map[enums.ActorType][]string{
enums.ActorTypeUser: {
"submit_enterprise_info",
"apply_contract",
"view_certification",
"retry_from_failure",
},
enums.ActorTypeSystem: {
"auto_transition",
"system_recovery",
"timeout_handling",
"data_cleanup",
},
enums.ActorTypeEsign: {
"verification_callback",
"sign_callback",
"status_notification",
},
enums.ActorTypeAdmin: {
"manual_intervention",
"force_transition",
"view_all_certifications",
"system_configuration",
},
}
}
// GetStateConfig 获取状态配置
func (m *StateConfigManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return m.stateConfigs[status]
}
// GetTransitionRule 获取状态转换规则
func (m *StateConfigManager) GetTransitionRule(fromStatus, toStatus enums.CertificationStatus) *StateTransitionRule {
key := string(fromStatus) + "->" + string(toStatus)
return m.transitionRules[key]
}
// CanTransition 检查是否可以执行状态转换
func (m *StateConfigManager) CanTransition(fromStatus, toStatus enums.CertificationStatus, actor enums.ActorType) (bool, string) {
// 获取转换规则
rule := m.GetTransitionRule(fromStatus, toStatus)
if rule == nil {
return false, "不支持的状态转换"
}
// 检查操作者权限
allowed := false
for _, allowedActor := range rule.AllowedActors {
if actor == allowedActor {
allowed = true
break
}
}
if !allowed {
return false, "操作者无权限执行此转换"
}
return true, ""
}
// GetAllowedTransitions 获取指定状态下允许的转换
func (m *StateConfigManager) GetAllowedTransitions(fromStatus enums.CertificationStatus, actor enums.ActorType) []*StateTransitionRule {
var allowedTransitions []*StateTransitionRule
config := m.GetStateConfig(fromStatus)
if config == nil {
return allowedTransitions
}
for _, toStatus := range config.NextValidStatuses {
if canTransition, _ := m.CanTransition(fromStatus, toStatus, actor); canTransition {
if rule := m.GetTransitionRule(fromStatus, toStatus); rule != nil {
allowedTransitions = append(allowedTransitions, rule)
}
}
}
return allowedTransitions
}
// GetActorPermissions 获取操作者权限
func (m *StateConfigManager) GetActorPermissions(actor enums.ActorType) []string {
if permissions, exists := m.actorPermissions[actor]; exists {
return permissions
}
return []string{}
}
// HasPermission 检查操作者是否有指定权限
func (m *StateConfigManager) HasPermission(actor enums.ActorType, permission string) bool {
permissions := m.GetActorPermissions(actor)
for _, p := range permissions {
if p == permission {
return true
}
}
return false
}
// ValidateBusinessRules 验证业务规则
func (m *StateConfigManager) ValidateBusinessRules(rule *StateTransitionRule, context map[string]interface{}) error {
for _, businessRule := range rule.BusinessRules {
if err := m.validateSingleBusinessRule(businessRule, context); err != nil {
return err
}
}
return nil
}
// validateSingleBusinessRule 验证单个业务规则
func (m *StateConfigManager) validateSingleBusinessRule(ruleName string, context map[string]interface{}) error {
switch ruleName {
case "enterprise_info_complete":
if enterpriseInfo, exists := context["enterprise_info"]; !exists || enterpriseInfo == nil {
return fmt.Errorf("企业信息不能为空")
}
case "enterprise_info_valid":
// 这里可以添加更复杂的企业信息验证逻辑
return nil
case "auth_flow_id_exists":
if authFlowID, exists := context["auth_flow_id"]; !exists || authFlowID == "" {
return fmt.Errorf("认证流程ID不能为空")
}
case "failure_reason_provided":
if reason, exists := context["failure_reason"]; !exists || reason == "" {
return fmt.Errorf("失败原因不能为空")
}
case "enterprise_verified":
if status, exists := context["current_status"]; !exists || status != string(enums.StatusEnterpriseVerified) {
return fmt.Errorf("企业必须先完成认证")
}
case "contract_info_complete":
if contractInfo, exists := context["contract_info"]; !exists || contractInfo == nil {
return fmt.Errorf("合同信息不能为空")
}
case "retry_limit_check":
if retryCount, exists := context["retry_count"]; exists {
if count, ok := retryCount.(int); ok && count >= 3 {
return fmt.Errorf("已达到最大重试次数限制")
}
}
}
return nil
}
// GetStateProgress 获取状态进度信息
func (m *StateConfigManager) GetStateProgress(status enums.CertificationStatus) int {
if config := m.GetStateConfig(status); config != nil {
return config.ProgressPercentage
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (m *StateConfigManager) IsUserActionRequired(status enums.CertificationStatus) bool {
if config := m.GetStateConfig(status); config != nil {
return config.IsUserActionRequired
}
return false
}
// IsSystemAction 检查是否为系统操作状态
func (m *StateConfigManager) IsSystemAction(status enums.CertificationStatus) bool {
if config := m.GetStateConfig(status); config != nil {
return config.IsSystemAction
}
return false
}
// GetTimestampField 获取状态对应的时间戳字段
func (m *StateConfigManager) GetTimestampField(status enums.CertificationStatus) string {
if config := m.GetStateConfig(status); config != nil {
return config.TimestampField
}
return ""
}

View File

@@ -1,181 +0,0 @@
package value_objects
import (
"fmt"
"regexp"
"strings"
)
// EnterpriseInfo 企业信息值对象
// 负责封装和验证企业认证相关的信息
type EnterpriseInfo struct {
CompanyName string `json:"company_name" validate:"required,min=2,max=100"`
UnifiedSocialCode string `json:"unified_social_code" validate:"required,len=18"`
LegalPersonName string `json:"legal_person_name" validate:"required,min=2,max=50"`
LegalPersonID string `json:"legal_person_id" validate:"required,len=18"`
LegalPersonPhone string `json:"legal_person_phone" validate:"required,mobile"`
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(
companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone 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),
}
// 验证数据
if err := info.Validate(); err != nil {
return nil, err
}
return info, nil
}
// Validate 验证企业信息
func (e *EnterpriseInfo) Validate() error {
// 验证公司名称
if e.CompanyName == "" {
return fmt.Errorf("公司名称不能为空")
}
if len(e.CompanyName) < 2 || len(e.CompanyName) > 100 {
return fmt.Errorf("公司名称长度应在2-100个字符之间")
}
// 验证统一社会信用代码
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
// 验证法人姓名
if e.LegalPersonName == "" {
return fmt.Errorf("法人姓名不能为空")
}
if len(e.LegalPersonName) < 2 || len(e.LegalPersonName) > 50 {
return fmt.Errorf("法人姓名长度应在2-50个字符之间")
}
// 验证法人身份证
if err := e.validateLegalPersonID(); err != nil {
return err
}
// 验证法人手机号
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
return nil
}
// validateUnifiedSocialCode 验证统一社会信用代码
func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return fmt.Errorf("统一社会信用代码不能为空")
}
if len(e.UnifiedSocialCode) != 18 {
return fmt.Errorf("统一社会信用代码应为18位")
}
// 检查格式:应为数字和大写字母组合
matched, _ := regexp.MatchString(`^[0-9A-Z]{18}$`, e.UnifiedSocialCode)
if !matched {
return fmt.Errorf("统一社会信用代码格式不正确")
}
// TODO: 可以添加更严格的校验算法
return nil
}
// validateLegalPersonID 验证法人身份证号
func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return fmt.Errorf("法人身份证号不能为空")
}
if len(e.LegalPersonID) != 18 {
return fmt.Errorf("法人身份证号应为18位")
}
// 检查格式前17位为数字最后一位为数字或X
matched, _ := regexp.MatchString(`^\d{17}[\dX]$`, e.LegalPersonID)
if !matched {
return fmt.Errorf("法人身份证号格式不正确")
}
// TODO: 可以添加身份证校验算法
return nil
}
// validateLegalPersonPhone 验证法人手机号
func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return fmt.Errorf("法人手机号不能为空")
}
// 检查中国大陆手机号格式
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, e.LegalPersonPhone)
if !matched {
return fmt.Errorf("法人手机号格式不正确")
}
return nil
}
// ================ 业务方法 ================
// GetDisplayName 获取显示名称
func (e *EnterpriseInfo) GetDisplayName() string {
return fmt.Sprintf("%s%s", e.CompanyName, e.LegalPersonName)
}
// IsSame 判断是否为同一企业信息
func (e *EnterpriseInfo) IsSame(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonID == other.LegalPersonID
}
// GetMaskedPhone 获取脱敏手机号
func (e *EnterpriseInfo) GetMaskedPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
// GetMaskedIDNumber 获取脱敏身份证号
func (e *EnterpriseInfo) GetMaskedIDNumber() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
return e.LegalPersonID[:6] + "******" + e.LegalPersonID[14:]
}
// ToMap 转换为Map格式
func (e *EnterpriseInfo) ToMap() map[string]string {
return map[string]string{
"company_name": e.CompanyName,
"unified_social_code": e.UnifiedSocialCode,
"legal_person_name": e.LegalPersonName,
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
}
}
// String 字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[公司=%s, 法人=%s, 信用代码=%s]",
e.CompanyName, e.LegalPersonName, e.UnifiedSocialCode)
}

View File

@@ -0,0 +1,140 @@
package entities
import (
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// AlipayOrderStatus 支付宝订单状态枚举
type AlipayOrderStatus string
const (
AlipayOrderStatusPending AlipayOrderStatus = "pending" // 待支付
AlipayOrderStatusSuccess AlipayOrderStatus = "success" // 支付成功
AlipayOrderStatusFailed AlipayOrderStatus = "failed" // 支付失败
AlipayOrderStatusCancelled AlipayOrderStatus = "cancelled" // 已取消
AlipayOrderStatusClosed AlipayOrderStatus = "closed" // 已关闭
)
const (
AlipayOrderPlatformApp = "app" // 支付宝APP支付
AlipayOrderPlatformH5 = "h5" // 支付宝H5支付
AlipayOrderPlatformPC = "pc" // 支付宝PC支付
)
// AlipayOrder 支付宝订单详情实体
type AlipayOrder struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"支付宝订单唯一标识"`
RechargeID string `gorm:"type:varchar(36);not null;uniqueIndex" json:"recharge_id" comment:"关联充值记录ID"`
OutTradeNo string `gorm:"type:varchar(64);not null;uniqueIndex" json:"out_trade_no" comment:"商户订单号"`
TradeNo *string `gorm:"type:varchar(64);uniqueIndex" json:"trade_no,omitempty" comment:"支付宝交易号"`
// 订单信息
Subject string `gorm:"type:varchar(200);not null" json:"subject" comment:"订单标题"`
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"amount" comment:"订单金额"`
Platform string `gorm:"type:varchar(20);not null" json:"platform" comment:"支付平台app/h5/pc"`
Status AlipayOrderStatus `gorm:"type:varchar(20);not null;default:'pending';index" json:"status" comment:"订单状态"`
// 支付宝返回信息
BuyerID string `gorm:"type:varchar(64)" json:"buyer_id,omitempty" comment:"买家支付宝用户ID"`
SellerID string `gorm:"type:varchar(64)" json:"seller_id,omitempty" comment:"卖家支付宝用户ID"`
PayAmount decimal.Decimal `gorm:"type:decimal(20,8)" json:"pay_amount,omitempty" comment:"实际支付金额"`
ReceiptAmount decimal.Decimal `gorm:"type:decimal(20,8)" json:"receipt_amount,omitempty" comment:"实收金额"`
// 回调信息
NotifyTime *time.Time `gorm:"index" json:"notify_time,omitempty" comment:"异步通知时间"`
ReturnTime *time.Time `gorm:"index" json:"return_time,omitempty" comment:"同步返回时间"`
// 错误信息
ErrorCode string `gorm:"type:varchar(64)" json:"error_code,omitempty" comment:"错误码"`
ErrorMessage string `gorm:"type:text" json:"error_message,omitempty" comment:"错误信息"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
}
// TableName 指定数据库表名
func (AlipayOrder) TableName() string {
return "alipay_orders"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (a *AlipayOrder) BeforeCreate(tx *gorm.DB) error {
if a.ID == "" {
a.ID = uuid.New().String()
}
return nil
}
// IsPending 检查是否为待支付状态
func (a *AlipayOrder) IsPending() bool {
return a.Status == AlipayOrderStatusPending
}
// IsSuccess 检查是否为支付成功状态
func (a *AlipayOrder) IsSuccess() bool {
return a.Status == AlipayOrderStatusSuccess
}
// IsFailed 检查是否为支付失败状态
func (a *AlipayOrder) IsFailed() bool {
return a.Status == AlipayOrderStatusFailed
}
// IsCancelled 检查是否为已取消状态
func (a *AlipayOrder) IsCancelled() bool {
return a.Status == AlipayOrderStatusCancelled
}
// IsClosed 检查是否为已关闭状态
func (a *AlipayOrder) IsClosed() bool {
return a.Status == AlipayOrderStatusClosed
}
// MarkSuccess 标记为支付成功
func (a *AlipayOrder) MarkSuccess(tradeNo, buyerID, sellerID string, payAmount, receiptAmount decimal.Decimal) {
a.Status = AlipayOrderStatusSuccess
a.TradeNo = &tradeNo
a.BuyerID = buyerID
a.SellerID = sellerID
a.PayAmount = payAmount
a.ReceiptAmount = receiptAmount
now := time.Now()
a.NotifyTime = &now
}
// MarkFailed 标记为支付失败
func (a *AlipayOrder) MarkFailed(errorCode, errorMessage string) {
a.Status = AlipayOrderStatusFailed
a.ErrorCode = errorCode
a.ErrorMessage = errorMessage
}
// MarkCancelled 标记为已取消
func (a *AlipayOrder) MarkCancelled() {
a.Status = AlipayOrderStatusCancelled
}
// MarkClosed 标记为已关闭
func (a *AlipayOrder) MarkClosed() {
a.Status = AlipayOrderStatusClosed
}
// NewAlipayOrder 工厂方法 - 创建支付宝订单
func NewAlipayOrder(rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) *AlipayOrder {
return &AlipayOrder{
ID: uuid.New().String(),
RechargeID: rechargeID,
OutTradeNo: outTradeNo,
Subject: subject,
Amount: amount,
Platform: platform,
Status: AlipayOrderStatusPending,
}
}

View File

@@ -0,0 +1,177 @@
package entities
import (
"errors"
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// RechargeType 充值类型枚举
type RechargeType string
const (
RechargeTypeAlipay RechargeType = "alipay" // 支付宝充值
RechargeTypeTransfer RechargeType = "transfer" // 对公转账
RechargeTypeGift RechargeType = "gift" // 赠送
)
// RechargeStatus 充值状态枚举
type RechargeStatus string
const (
RechargeStatusPending RechargeStatus = "pending" // 待处理
RechargeStatusSuccess RechargeStatus = "success" // 成功
RechargeStatusFailed RechargeStatus = "failed" // 失败
RechargeStatusCancelled RechargeStatus = "cancelled" // 已取消
)
// RechargeRecord 充值记录实体
// 记录用户的各种充值操作,包括支付宝充值、对公转账、赠送等
type RechargeRecord 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"`
// 充值信息
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"amount" comment:"充值金额"`
RechargeType RechargeType `gorm:"type:varchar(20);not null;index" json:"recharge_type" comment:"充值类型"`
Status RechargeStatus `gorm:"type:varchar(20);not null;default:'pending';index" json:"status" comment:"充值状态"`
// 订单号字段(根据充值类型使用不同字段)
AlipayOrderID *string `gorm:"type:varchar(64);uniqueIndex" json:"alipay_order_id,omitempty" comment:"支付宝订单号"`
TransferOrderID *string `gorm:"type:varchar(64);uniqueIndex" json:"transfer_order_id,omitempty" comment:"转账订单号"`
// 通用字段
Notes string `gorm:"type:varchar(500)" json:"notes,omitempty" comment:"备注信息"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
}
// TableName 指定数据库表名
func (RechargeRecord) TableName() string {
return "recharge_records"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (r *RechargeRecord) BeforeCreate(tx *gorm.DB) error {
if r.ID == "" {
r.ID = uuid.New().String()
}
return nil
}
// IsPending 检查是否为待处理状态
func (r *RechargeRecord) IsPending() bool {
return r.Status == RechargeStatusPending
}
// IsSuccess 检查是否为成功状态
func (r *RechargeRecord) IsSuccess() bool {
return r.Status == RechargeStatusSuccess
}
// IsFailed 检查是否为失败状态
func (r *RechargeRecord) IsFailed() bool {
return r.Status == RechargeStatusFailed
}
// IsCancelled 检查是否为已取消状态
func (r *RechargeRecord) IsCancelled() bool {
return r.Status == RechargeStatusCancelled
}
// MarkSuccess 标记为成功
func (r *RechargeRecord) MarkSuccess() {
r.Status = RechargeStatusSuccess
}
// MarkFailed 标记为失败
func (r *RechargeRecord) MarkFailed() {
r.Status = RechargeStatusFailed
}
// MarkCancelled 标记为已取消
func (r *RechargeRecord) MarkCancelled() {
r.Status = RechargeStatusCancelled
}
// ValidatePaymentMethod 验证支付方式:支付宝订单号和转账订单号只能有一个存在
func (r *RechargeRecord) ValidatePaymentMethod() error {
hasAlipay := r.AlipayOrderID != nil && *r.AlipayOrderID != ""
hasTransfer := r.TransferOrderID != nil && *r.TransferOrderID != ""
if hasAlipay && hasTransfer {
return errors.New("支付宝订单号和转账订单号不能同时存在")
}
if !hasAlipay && !hasTransfer {
return errors.New("必须提供支付宝订单号或转账订单号")
}
return nil
}
// GetOrderID 获取订单号(根据充值类型返回对应的订单号)
func (r *RechargeRecord) GetOrderID() string {
switch r.RechargeType {
case RechargeTypeAlipay:
if r.AlipayOrderID != nil {
return *r.AlipayOrderID
}
case RechargeTypeTransfer:
if r.TransferOrderID != nil {
return *r.TransferOrderID
}
}
return ""
}
// SetAlipayOrderID 设置支付宝订单号
func (r *RechargeRecord) SetAlipayOrderID(orderID string) {
r.AlipayOrderID = &orderID
}
// SetTransferOrderID 设置转账订单号
func (r *RechargeRecord) SetTransferOrderID(orderID string) {
r.TransferOrderID = &orderID
}
// NewAlipayRechargeRecord 工厂方法 - 创建支付宝充值记录
func NewAlipayRechargeRecord(userID string, amount decimal.Decimal, alipayOrderID string) *RechargeRecord {
return &RechargeRecord{
UserID: userID,
Amount: amount,
RechargeType: RechargeTypeAlipay,
Status: RechargeStatusPending,
AlipayOrderID: &alipayOrderID,
}
}
// NewTransferRechargeRecord 工厂方法 - 创建对公转账充值记录
func NewTransferRechargeRecord(userID string, amount decimal.Decimal, transferOrderID, notes string) *RechargeRecord {
return &RechargeRecord{
UserID: userID,
Amount: amount,
RechargeType: RechargeTypeTransfer,
Status: RechargeStatusPending,
TransferOrderID: &transferOrderID,
Notes: notes,
}
}
// NewGiftRechargeRecord 工厂方法 - 创建赠送充值记录
func NewGiftRechargeRecord(userID string, amount decimal.Decimal, notes string) *RechargeRecord {
return &RechargeRecord{
UserID: userID,
Amount: amount,
RechargeType: RechargeTypeGift,
Status: RechargeStatusSuccess, // 赠送直接标记为成功
Notes: notes,
}
}

View File

@@ -1,76 +0,0 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// UserSecrets 用户密钥实体
// 存储用户的API访问密钥信息用于第三方服务集成和API调用
// 支持密钥的生命周期管理,包括激活状态、过期时间、使用统计等
type UserSecrets struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" comment:"密钥记录唯一标识"`
UserID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"关联用户ID"`
AccessID string `gorm:"type:varchar(100);not null;uniqueIndex" comment:"访问ID(用于API认证)"`
AccessKey string `gorm:"type:varchar(255);not null" comment:"访问密钥(加密存储)"`
// 密钥状态 - 密钥的生命周期管理
IsActive bool `gorm:"default:true" comment:"密钥是否激活"`
LastUsedAt *time.Time `comment:"最后使用时间"`
ExpiresAt *time.Time `comment:"密钥过期时间"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// TableName 指定数据库表名
func (UserSecrets) TableName() string {
return "user_secrets"
}
// IsExpired 检查密钥是否已过期
// 判断密钥是否超过有效期,过期后需要重新生成或续期
func (u *UserSecrets) IsExpired() bool {
if u.ExpiresAt == nil {
return false // 没有过期时间表示永不过期
}
return time.Now().After(*u.ExpiresAt)
}
// IsValid 检查密钥是否有效
// 综合判断密钥是否可用,包括激活状态和过期状态检查
func (u *UserSecrets) IsValid() bool {
return u.IsActive && !u.IsExpired()
}
// UpdateLastUsedAt 更新最后使用时间
// 在密钥被使用时调用,记录最新的使用时间,用于使用统计和监控
func (u *UserSecrets) UpdateLastUsedAt() {
now := time.Now()
u.LastUsedAt = &now
}
// Deactivate 停用密钥
// 将密钥设置为非激活状态禁止使用该密钥进行API调用
func (u *UserSecrets) Deactivate() {
u.IsActive = false
}
// Activate 激活密钥
// 重新启用密钥允许使用该密钥进行API调用
func (u *UserSecrets) Activate() {
u.IsActive = true
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (u *UserSecrets) BeforeCreate(tx *gorm.DB) error {
if u.ID == "" {
u.ID = uuid.New().String()
}
return nil
}

View File

@@ -9,9 +9,12 @@ import (
"gorm.io/gorm"
)
// Wallet 钱包实体
// Wallet 钱包聚合根
// 用户数字钱包的核心信息,支持多种钱包类型和精确的余额管理
// 使用decimal类型确保金额计算的精确性避免浮点数精度问题
// 支持欠费(余额<0但只允许扣到小于0一次之后不能再扣
// 新建钱包时可配置默认额度
type Wallet struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"钱包唯一标识"`
@@ -20,10 +23,7 @@ type Wallet struct {
// 钱包状态 - 钱包的基本状态信息
IsActive bool `gorm:"default:true" json:"is_active" comment:"钱包是否激活"`
Balance decimal.Decimal `gorm:"type:decimal(20,8);default:0" json:"balance" comment:"钱包余额(精确到8位小数)"`
// 钱包信息 - 钱包的详细配置信息
WalletAddress string `gorm:"type:varchar(255)" json:"wallet_address,omitempty" comment:"钱包地址"`
WalletType string `gorm:"type:varchar(50);default:'MAIN'" json:"wallet_type" comment:"钱包类型(MAIN/DEPOSIT/WITHDRAWAL)"` // MAIN, DEPOSIT, WITHDRAWAL
Version int64 `gorm:"version" json:"version" comment:"乐观锁版本号"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
@@ -37,36 +37,53 @@ func (Wallet) TableName() string {
}
// IsZeroBalance 检查余额是否为零
// 判断钱包余额是否为零,用于业务逻辑判断
func (w *Wallet) IsZeroBalance() bool {
return w.Balance.IsZero()
}
// HasSufficientBalance 检查是否有足够余额
// 判断钱包余额是否足够支付指定金额,用于交易前的余额验证
// HasSufficientBalance 检查是否有足够余额(允许透支额度)
func (w *Wallet) HasSufficientBalance(amount decimal.Decimal) bool {
return w.Balance.GreaterThanOrEqual(amount)
// 允许扣到额度下限
return w.Balance.Sub(amount).GreaterThanOrEqual(decimal.Zero)
}
// AddBalance 增加余额
// 向钱包增加指定金额,用于充值、收入等场景
// IsArrears 是否欠费(余额<0
func (w *Wallet) IsArrears() bool {
return w.Balance.LessThan(decimal.Zero)
}
// IsLowBalance 是否余额较低(余额<300
func (w *Wallet) IsLowBalance() bool {
return w.Balance.LessThan(decimal.NewFromInt(300))
}
// GetBalanceStatus 获取余额状态
func (w *Wallet) GetBalanceStatus() string {
if w.IsArrears() {
return "arrears" // 欠费
} else if w.IsLowBalance() {
return "low" // 余额较低
} else {
return "normal" // 正常
}
}
// AddBalance 增加余额(只做加法,业务规则由服务层控制是否允许充值)
func (w *Wallet) AddBalance(amount decimal.Decimal) {
w.Balance = w.Balance.Add(amount)
}
// SubtractBalance 减少余额
// 从钱包扣除指定金额,用于消费、转账等场景
// 如果余额不足会返回错误,确保资金安全
// SubtractBalance 扣减余额,含欠费业务规则
func (w *Wallet) SubtractBalance(amount decimal.Decimal) error {
if !w.HasSufficientBalance(amount) {
return fmt.Errorf("余额不足")
if w.Balance.LessThan(decimal.Zero) {
return fmt.Errorf("已欠费,不能再扣款")
}
w.Balance = w.Balance.Sub(amount)
newBalance := w.Balance.Sub(amount)
w.Balance = newBalance
return nil
}
// GetFormattedBalance 获取格式化的余额字符串
// 将decimal类型的余额转换为字符串格式便于显示和传输
func (w *Wallet) GetFormattedBalance() string {
return w.Balance.String()
}
@@ -78,3 +95,13 @@ func (w *Wallet) BeforeCreate(tx *gorm.DB) error {
}
return nil
}
// NewWallet 工厂方法
func NewWallet(userID string, defaultCreditLimit decimal.Decimal) *Wallet {
return &Wallet{
UserID: userID,
IsActive: true,
Balance: defaultCreditLimit,
Version: 0,
}
}

View File

@@ -0,0 +1,52 @@
package entities
import (
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// WalletTransaction 钱包扣款记录
// 记录API调用产生的扣款操作
type WalletTransaction 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"`
ApiCallID string `gorm:"type:varchar(64);not null;uniqueIndex" json:"api_call_id" comment:"关联API调用ID"`
TransactionID string `gorm:"type:varchar(64);not null;uniqueIndex" json:"transaction_id" comment:"交易ID"`
ProductID string `gorm:"type:varchar(64);not null;index" json:"product_id" comment:"产品ID"`
// 扣款信息
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"amount" comment:"扣款金额"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
}
// TableName 指定数据库表名
func (WalletTransaction) TableName() string {
return "wallet_transactions"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (t *WalletTransaction) BeforeCreate(tx *gorm.DB) error {
if t.ID == "" {
t.ID = uuid.New().String()
}
return nil
}
// NewWalletTransaction 工厂方法 - 创建扣款记录
func NewWalletTransaction(userID, apiCallID, transactionID, productID string, amount decimal.Decimal) *WalletTransaction {
return &WalletTransaction{
UserID: userID,
ApiCallID: apiCallID,
TransactionID: transactionID,
ProductID: productID,
Amount: amount,
}
}

View File

@@ -0,0 +1,20 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/finance/entities"
)
// AlipayOrderRepository 支付宝订单仓储接口
type AlipayOrderRepository interface {
Create(ctx context.Context, order entities.AlipayOrder) (entities.AlipayOrder, error)
GetByID(ctx context.Context, id string) (entities.AlipayOrder, error)
GetByOutTradeNo(ctx context.Context, outTradeNo string) (*entities.AlipayOrder, error)
GetByRechargeID(ctx context.Context, rechargeID string) (*entities.AlipayOrder, error)
GetByUserID(ctx context.Context, userID string) ([]entities.AlipayOrder, error)
Update(ctx context.Context, order entities.AlipayOrder) error
UpdateStatus(ctx context.Context, id string, status entities.AlipayOrderStatus) error
Delete(ctx context.Context, id string) error
Exists(ctx context.Context, id string) (bool, error)
}

View File

@@ -1,57 +0,0 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// FinanceStats 财务统计信息
type FinanceStats struct {
TotalWallets int64
ActiveWallets int64
TotalBalance string
TodayTransactions int64
}
// WalletRepository 钱包仓储接口
type WalletRepository interface {
interfaces.Repository[entities.Wallet]
// 基础查询 - 直接使用实体
GetByUserID(ctx context.Context, userID string) (*entities.Wallet, error)
GetByWalletAddress(ctx context.Context, walletAddress string) (*entities.Wallet, error)
GetByWalletType(ctx context.Context, userID string, walletType string) (*entities.Wallet, error)
// 复杂查询 - 使用查询参数
ListWallets(ctx context.Context, query *queries.ListWalletsQuery) ([]*entities.Wallet, int64, error)
// 业务操作
UpdateBalance(ctx context.Context, walletID string, balance string) error
AddBalance(ctx context.Context, walletID string, amount string) error
SubtractBalance(ctx context.Context, walletID string, amount string) error
ActivateWallet(ctx context.Context, walletID string) error
DeactivateWallet(ctx context.Context, walletID string) error
// 统计信息
GetStats(ctx context.Context) (*FinanceStats, error)
GetUserWalletStats(ctx context.Context, userID string) (*FinanceStats, error)
}
// UserSecretsRepository 用户密钥仓储接口
type UserSecretsRepository interface {
interfaces.Repository[entities.UserSecrets]
// 基础查询 - 直接使用实体
GetByUserID(ctx context.Context, userID string) (*entities.UserSecrets, error)
GetBySecretType(ctx context.Context, userID string, secretType string) (*entities.UserSecrets, error)
// 复杂查询 - 使用查询参数
ListUserSecrets(ctx context.Context, query *queries.ListUserSecretsQuery) ([]*entities.UserSecrets, int64, error)
// 业务操作
UpdateSecret(ctx context.Context, userID string, secretType string, secretValue string) error
DeleteSecret(ctx context.Context, userID string, secretType string) error
ValidateSecret(ctx context.Context, userID string, secretType string, secretValue string) (bool, error)
}

View File

@@ -0,0 +1,23 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/shared/interfaces"
)
// RechargeRecordRepository 充值记录仓储接口
type RechargeRecordRepository interface {
Create(ctx context.Context, record entities.RechargeRecord) (entities.RechargeRecord, error)
GetByID(ctx context.Context, id string) (entities.RechargeRecord, error)
GetByUserID(ctx context.Context, userID string) ([]entities.RechargeRecord, error)
GetByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error)
GetByTransferOrderID(ctx context.Context, transferOrderID string) (*entities.RechargeRecord, error)
Update(ctx context.Context, record entities.RechargeRecord) error
UpdateStatus(ctx context.Context, id string, status entities.RechargeStatus) error
// 管理员查询方法
List(ctx context.Context, options interfaces.ListOptions) ([]entities.RechargeRecord, error)
Count(ctx context.Context, options interfaces.CountOptions) (int64, error)
}

View File

@@ -0,0 +1,37 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/shared/interfaces"
)
// FinanceStats 财务统计信息
type FinanceStats struct {
TotalWallets int64
ActiveWallets int64
TotalBalance string
TodayTransactions int64
}
// WalletRepository 钱包仓储接口
// 只保留核心方法,聚合服务负责业务规则
// 业务操作只保留乐观锁更新和基础更新
type WalletRepository interface {
interfaces.Repository[entities.Wallet]
// 基础查询
GetByUserID(ctx context.Context, userID string) (*entities.Wallet, error)
// 乐观锁更新(自动重试)
UpdateBalanceWithVersion(ctx context.Context, walletID string, newBalance string, oldVersion int64) (bool, error)
// 状态操作
ActivateWallet(ctx context.Context, walletID string) error
DeactivateWallet(ctx context.Context, walletID string) error
// 统计
GetStats(ctx context.Context) (*FinanceStats, error)
GetUserWalletStats(ctx context.Context, userID string) (*FinanceStats, error)
}

View File

@@ -0,0 +1,28 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/shared/interfaces"
)
// WalletTransactionRepository 钱包扣款记录仓储接口
type WalletTransactionRepository interface {
interfaces.Repository[entities.WalletTransaction]
// 基础查询
GetByUserID(ctx context.Context, userID string, limit, offset int) ([]*entities.WalletTransaction, error)
GetByApiCallID(ctx context.Context, apiCallID string) (*entities.WalletTransaction, error)
// 新增:分页查询用户钱包交易记录
ListByUserId(ctx context.Context, userId string, options interfaces.ListOptions) ([]*entities.WalletTransaction, int64, error)
// 新增:根据条件筛选钱包交易记录
ListByUserIdWithFilters(ctx context.Context, userId string, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.WalletTransaction, int64, error)
// 新增:根据条件筛选钱包交易记录(包含产品名称)
ListByUserIdWithFiltersAndProductName(ctx context.Context, userId string, filters map[string]interface{}, options interfaces.ListOptions) (map[string]string, []*entities.WalletTransaction, int64, error)
// 新增:统计用户钱包交易次数
CountByUserId(ctx context.Context, userId string) (int64, error)
}

View File

@@ -1,160 +0,0 @@
package services
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories"
)
// FinanceService 财务领域服务
// 负责财务相关的业务逻辑,包括钱包管理、余额操作等
type FinanceService struct {
walletRepo repositories.WalletRepository
logger *zap.Logger
}
// NewFinanceService 创建财务领域服务
func NewFinanceService(
walletRepo repositories.WalletRepository,
logger *zap.Logger,
) *FinanceService {
return &FinanceService{
walletRepo: walletRepo,
logger: logger,
}
}
// CreateWallet 创建钱包
func (s *FinanceService) CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
// 检查用户是否已有钱包
existingWallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err == nil && existingWallet != nil {
return nil, fmt.Errorf("用户已有钱包")
}
// 创建钱包
wallet := &entities.Wallet{
UserID: userID,
Balance: decimal.Zero,
IsActive: true,
WalletType: "MAIN",
}
createdWallet, err := s.walletRepo.Create(ctx, *wallet)
if err != nil {
s.logger.Error("创建钱包失败", zap.Error(err))
return nil, fmt.Errorf("创建钱包失败: %w", err)
}
s.logger.Info("钱包创建成功",
zap.String("wallet_id", createdWallet.ID),
zap.String("user_id", userID),
)
return &createdWallet, nil
}
// GetWallet 获取钱包信息
func (s *FinanceService) GetWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在: %w", err)
}
return wallet, nil
}
// GetWalletByID 根据ID获取钱包
func (s *FinanceService) GetWalletByID(ctx context.Context, walletID string) (*entities.Wallet, error) {
wallet, err := s.walletRepo.GetByID(ctx, walletID)
if err != nil {
return nil, fmt.Errorf("钱包不存在: %w", err)
}
return &wallet, nil
}
// RechargeWallet 充值钱包
func (s *FinanceService) RechargeWallet(ctx context.Context, userID string, amount float64) error {
if amount <= 0 {
return fmt.Errorf("充值金额必须大于0")
}
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在: %w", err)
}
// 更新余额
amountDecimal := decimal.NewFromFloat(amount)
wallet.AddBalance(amountDecimal)
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
s.logger.Error("充值失败", zap.Error(err))
return fmt.Errorf("充值失败: %w", err)
}
s.logger.Info("钱包充值成功",
zap.String("wallet_id", wallet.ID),
zap.String("user_id", userID),
zap.Float64("amount", amount),
zap.String("new_balance", wallet.GetFormattedBalance()),
)
return nil
}
// DeductWallet 扣减钱包余额
func (s *FinanceService) DeductWallet(ctx context.Context, userID string, amount float64) error {
if amount <= 0 {
return fmt.Errorf("扣减金额必须大于0")
}
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在: %w", err)
}
amountDecimal := decimal.NewFromFloat(amount)
if err := wallet.SubtractBalance(amountDecimal); err != nil {
return err
}
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
s.logger.Error("扣减失败", zap.Error(err))
return fmt.Errorf("扣减失败: %w", err)
}
s.logger.Info("钱包扣减成功",
zap.String("wallet_id", wallet.ID),
zap.String("user_id", userID),
zap.Float64("amount", amount),
zap.String("new_balance", wallet.GetFormattedBalance()),
)
return nil
}
// GetWalletBalance 获取钱包余额
func (s *FinanceService) GetWalletBalance(ctx context.Context, userID string) (float64, error) {
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return 0, fmt.Errorf("钱包不存在: %w", err)
}
balance, _ := wallet.Balance.Float64()
return balance, nil
}
// CheckWalletBalance 检查钱包余额是否足够
func (s *FinanceService) CheckWalletBalance(ctx context.Context, userID string, amount float64) (bool, error) {
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return false, fmt.Errorf("钱包不存在: %w", err)
}
amountDecimal := decimal.NewFromFloat(amount)
return wallet.HasSufficientBalance(amountDecimal), nil
}

View File

@@ -0,0 +1,349 @@
package services
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
)
// RechargeRecordService 充值记录服务接口
type RechargeRecordService interface {
// 对公转账充值
TransferRecharge(ctx context.Context, userID string, amount decimal.Decimal, transferOrderID, notes string) (*entities.RechargeRecord, error)
// 赠送充值
GiftRecharge(ctx context.Context, userID string, amount decimal.Decimal, operatorID, notes string) (*entities.RechargeRecord, error)
// 支付宝充值
CreateAlipayRecharge(ctx context.Context, userID string, amount decimal.Decimal, alipayOrderID string) (*entities.RechargeRecord, error)
GetRechargeRecordByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error)
// 支付宝订单管理
CreateAlipayOrder(ctx context.Context, rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) error
HandleAlipayPaymentSuccess(ctx context.Context, outTradeNo string, amount decimal.Decimal, tradeNo string) error
// 通用查询
GetByID(ctx context.Context, id string) (*entities.RechargeRecord, error)
GetByUserID(ctx context.Context, userID string) ([]entities.RechargeRecord, error)
GetByTransferOrderID(ctx context.Context, transferOrderID string) (*entities.RechargeRecord, error)
// 管理员查询
GetAll(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]entities.RechargeRecord, error)
Count(ctx context.Context, filters map[string]interface{}) (int64, error)
}
// RechargeRecordServiceImpl 充值记录服务实现
type RechargeRecordServiceImpl struct {
rechargeRecordRepo repositories.RechargeRecordRepository
alipayOrderRepo repositories.AlipayOrderRepository
walletRepo repositories.WalletRepository
walletService WalletAggregateService
txManager *database.TransactionManager
logger *zap.Logger
}
func NewRechargeRecordService(
rechargeRecordRepo repositories.RechargeRecordRepository,
alipayOrderRepo repositories.AlipayOrderRepository,
walletRepo repositories.WalletRepository,
walletService WalletAggregateService,
txManager *database.TransactionManager,
logger *zap.Logger,
) RechargeRecordService {
return &RechargeRecordServiceImpl{
rechargeRecordRepo: rechargeRecordRepo,
alipayOrderRepo: alipayOrderRepo,
walletRepo: walletRepo,
walletService: walletService,
txManager: txManager,
logger: logger,
}
}
// TransferRecharge 对公转账充值
func (s *RechargeRecordServiceImpl) TransferRecharge(ctx context.Context, userID string, amount decimal.Decimal, transferOrderID, notes string) (*entities.RechargeRecord, error) {
// 检查钱包是否存在
_, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
// 检查转账订单号是否已存在
existingRecord, _ := s.rechargeRecordRepo.GetByTransferOrderID(ctx, transferOrderID)
if existingRecord != nil {
return nil, fmt.Errorf("转账订单号已存在")
}
var createdRecord entities.RechargeRecord
// 在事务中执行所有更新操作
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 创建充值记录
rechargeRecord := entities.NewTransferRechargeRecord(userID, amount, transferOrderID, notes)
record, err := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
if err != nil {
s.logger.Error("创建转账充值记录失败", zap.Error(err))
return err
}
createdRecord = record
// 使用钱包聚合服务更新钱包余额
err = s.walletService.Recharge(txCtx, userID, amount)
if err != nil {
return err
}
// 标记充值记录为成功
createdRecord.MarkSuccess()
err = s.rechargeRecordRepo.Update(txCtx, createdRecord)
if err != nil {
s.logger.Error("更新充值记录状态失败", zap.Error(err))
return err
}
return nil
})
if err != nil {
return nil, err
}
s.logger.Info("对公转账充值成功",
zap.String("user_id", userID),
zap.String("amount", amount.String()),
zap.String("transfer_order_id", transferOrderID))
return &createdRecord, nil
}
// GiftRecharge 赠送充值
func (s *RechargeRecordServiceImpl) GiftRecharge(ctx context.Context, userID string, amount decimal.Decimal, operatorID, notes string) (*entities.RechargeRecord, error) {
// 检查钱包是否存在
_, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
var createdRecord entities.RechargeRecord
// 在事务中执行所有更新操作
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 创建赠送充值记录
rechargeRecord := entities.NewGiftRechargeRecord(userID, amount, notes)
record, err := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
if err != nil {
s.logger.Error("创建赠送充值记录失败", zap.Error(err))
return err
}
createdRecord = record
// 使用钱包聚合服务更新钱包余额
err = s.walletService.Recharge(txCtx, userID, amount)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
s.logger.Info("赠送充值成功",
zap.String("user_id", userID),
zap.String("amount", amount.String()),
zap.String("notes", notes))
return &createdRecord, nil
}
// CreateAlipayRecharge 创建支付宝充值记录
func (s *RechargeRecordServiceImpl) CreateAlipayRecharge(ctx context.Context, userID string, amount decimal.Decimal, alipayOrderID string) (*entities.RechargeRecord, error) {
// 检查钱包是否存在
_, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
// 检查支付宝订单号是否已存在
existingRecord, _ := s.rechargeRecordRepo.GetByAlipayOrderID(ctx, alipayOrderID)
if existingRecord != nil {
return nil, fmt.Errorf("支付宝订单号已存在")
}
// 创建充值记录
rechargeRecord := entities.NewAlipayRechargeRecord(userID, amount, alipayOrderID)
createdRecord, err := s.rechargeRecordRepo.Create(ctx, *rechargeRecord)
if err != nil {
s.logger.Error("创建支付宝充值记录失败", zap.Error(err))
return nil, err
}
s.logger.Info("支付宝充值记录创建成功",
zap.String("user_id", userID),
zap.String("amount", amount.String()),
zap.String("alipay_order_id", alipayOrderID),
zap.String("recharge_id", createdRecord.ID))
return &createdRecord, nil
}
// CreateAlipayOrder 创建支付宝订单
func (s *RechargeRecordServiceImpl) CreateAlipayOrder(ctx context.Context, rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) error {
// 检查充值记录是否存在
_, err := s.rechargeRecordRepo.GetByID(ctx, rechargeID)
if err != nil {
s.logger.Error("充值记录不存在", zap.String("recharge_id", rechargeID), zap.Error(err))
return fmt.Errorf("充值记录不存在")
}
// 检查支付宝订单号是否已存在
existingOrder, _ := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if existingOrder != nil {
s.logger.Info("支付宝订单已存在,跳过重复创建", zap.String("out_trade_no", outTradeNo))
return nil
}
// 创建支付宝订单
alipayOrder := entities.NewAlipayOrder(rechargeID, outTradeNo, subject, amount, platform)
_, err = s.alipayOrderRepo.Create(ctx, *alipayOrder)
if err != nil {
s.logger.Error("创建支付宝订单失败", zap.Error(err))
return err
}
s.logger.Info("支付宝订单创建成功",
zap.String("recharge_id", rechargeID),
zap.String("out_trade_no", outTradeNo),
zap.String("subject", subject),
zap.String("amount", amount.String()),
zap.String("platform", platform))
return nil
}
// GetRechargeRecordByAlipayOrderID 根据支付宝订单号获取充值记录
func (s *RechargeRecordServiceImpl) GetRechargeRecordByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error) {
return s.rechargeRecordRepo.GetByAlipayOrderID(ctx, alipayOrderID)
}
// HandleAlipayPaymentSuccess 处理支付宝支付成功回调
func (s *RechargeRecordServiceImpl) HandleAlipayPaymentSuccess(ctx context.Context, outTradeNo string, amount decimal.Decimal, tradeNo 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("支付宝订单不存在")
}
// 检查订单状态
if alipayOrder.Status == entities.AlipayOrderStatusSuccess {
s.logger.Info("支付宝订单已处理成功,跳过重复处理",
zap.String("out_trade_no", outTradeNo),
zap.String("order_id", alipayOrder.ID),
)
return nil
}
// 查找对应的充值记录
rechargeRecord, err := s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
if err != nil {
s.logger.Error("查找充值记录失败", zap.String("recharge_id", alipayOrder.RechargeID), zap.Error(err))
return fmt.Errorf("查找充值记录失败: %w", err)
}
// 检查充值记录状态
if rechargeRecord.Status == entities.RechargeStatusSuccess {
s.logger.Info("充值记录已处理成功,跳过重复处理",
zap.String("recharge_id", rechargeRecord.ID),
)
return nil
}
// 在事务中执行所有更新操作
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 更新支付宝订单状态为成功
alipayOrder.MarkSuccess(tradeNo, "", "", amount, amount)
err := s.alipayOrderRepo.Update(txCtx, *alipayOrder)
if err != nil {
s.logger.Error("更新支付宝订单状态失败", zap.Error(err))
return err
}
// 更新充值记录状态为成功
rechargeRecord.MarkSuccess()
err = s.rechargeRecordRepo.Update(txCtx, rechargeRecord)
if err != nil {
s.logger.Error("更新充值记录状态失败", zap.Error(err))
return err
}
// 使用钱包聚合服务更新钱包余额
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, amount)
if err != nil {
s.logger.Error("更新钱包余额失败", zap.String("user_id", rechargeRecord.UserID), zap.Error(err))
return err
}
return nil
})
if err != nil {
return err
}
s.logger.Info("支付宝支付成功回调处理成功",
zap.String("user_id", rechargeRecord.UserID),
zap.String("amount", amount.String()),
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", tradeNo),
zap.String("recharge_id", rechargeRecord.ID),
zap.String("order_id", alipayOrder.ID))
return nil
}
// GetByID 根据ID获取充值记录
func (s *RechargeRecordServiceImpl) GetByID(ctx context.Context, id string) (*entities.RechargeRecord, error) {
record, err := s.rechargeRecordRepo.GetByID(ctx, id)
if err != nil {
return nil, err
}
return &record, nil
}
// GetByUserID 根据用户ID获取充值记录列表
func (s *RechargeRecordServiceImpl) GetByUserID(ctx context.Context, userID string) ([]entities.RechargeRecord, error) {
return s.rechargeRecordRepo.GetByUserID(ctx, userID)
}
// GetByTransferOrderID 根据转账订单号获取充值记录
func (s *RechargeRecordServiceImpl) GetByTransferOrderID(ctx context.Context, transferOrderID string) (*entities.RechargeRecord, error) {
return s.rechargeRecordRepo.GetByTransferOrderID(ctx, transferOrderID)
}
// GetAll 获取所有充值记录(管理员功能)
func (s *RechargeRecordServiceImpl) GetAll(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]entities.RechargeRecord, error) {
return s.rechargeRecordRepo.List(ctx, options)
}
// Count 统计充值记录数量(管理员功能)
func (s *RechargeRecordServiceImpl) Count(ctx context.Context, filters map[string]interface{}) (int64, error) {
countOptions := interfaces.CountOptions{
Filters: filters,
}
return s.rechargeRecordRepo.Count(ctx, countOptions)
}

View File

@@ -0,0 +1,142 @@
package services
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/config"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories"
)
// WalletAggregateService 钱包聚合服务接口
type WalletAggregateService interface {
CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error)
Recharge(ctx context.Context, userID string, amount decimal.Decimal) error
Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID, transactionID, productID string) error
GetBalance(ctx context.Context, userID string) (decimal.Decimal, error)
LoadWalletByUserId(ctx context.Context, userID string) (*entities.Wallet, error)
}
// WalletAggregateServiceImpl 实现
// WalletAggregateServiceImpl 钱包聚合服务实现
type WalletAggregateServiceImpl struct {
walletRepo repositories.WalletRepository
transactionRepo repositories.WalletTransactionRepository
logger *zap.Logger
cfg *config.Config
}
func NewWalletAggregateService(
walletRepo repositories.WalletRepository,
transactionRepo repositories.WalletTransactionRepository,
logger *zap.Logger,
cfg *config.Config,
) WalletAggregateService {
return &WalletAggregateServiceImpl{
walletRepo: walletRepo,
transactionRepo: transactionRepo,
logger: logger,
cfg: cfg,
}
}
// CreateWallet 创建钱包
func (s *WalletAggregateServiceImpl) CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
// 检查是否已存在
w, _ := s.walletRepo.GetByUserID(ctx, userID)
if w != nil {
return nil, fmt.Errorf("用户已存在钱包")
}
wallet := entities.NewWallet(userID, decimal.NewFromFloat(s.cfg.Wallet.DefaultCreditLimit))
created, err := s.walletRepo.Create(ctx, *wallet)
if err != nil {
s.logger.Error("创建钱包失败", zap.Error(err))
return nil, err
}
s.logger.Info("钱包创建成功", zap.String("user_id", userID), zap.String("wallet_id", created.ID))
return &created, nil
}
// Recharge 充值
func (s *WalletAggregateServiceImpl) Recharge(ctx context.Context, userID string, amount decimal.Decimal) error {
w, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在")
}
// 更新钱包余额
w.AddBalance(amount)
ok, err := s.walletRepo.UpdateBalanceWithVersion(ctx, w.ID, w.Balance.String(), w.Version)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("高并发下充值失败,请重试")
}
s.logger.Info("钱包充值成功",
zap.String("user_id", userID),
zap.String("wallet_id", w.ID),
zap.String("amount", amount.String()),
zap.String("balance_after", w.Balance.String()))
return nil
}
// Deduct 扣款,含欠费规则
func (s *WalletAggregateServiceImpl) Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID, transactionID, productID string) error {
w, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在")
}
// 扣减余额
if err := w.SubtractBalance(amount); err != nil {
return err
}
// 更新钱包余额
ok, err := s.walletRepo.UpdateBalanceWithVersion(ctx, w.ID, w.Balance.String(), w.Version)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("高并发下扣款失败,请重试")
}
// 创建扣款记录
transaction := entities.NewWalletTransaction(userID, apiCallID, transactionID, productID, amount)
_, err = s.transactionRepo.Create(ctx, *transaction)
if err != nil {
s.logger.Error("创建扣款记录失败", zap.Error(err))
// 不返回错误,因为钱包余额已经更新成功
}
s.logger.Info("钱包扣款成功",
zap.String("user_id", userID),
zap.String("wallet_id", w.ID),
zap.String("amount", amount.String()),
zap.String("balance_after", w.Balance.String()),
zap.String("api_call_id", apiCallID))
return nil
}
// GetBalance 查询余额
func (s *WalletAggregateServiceImpl) GetBalance(ctx context.Context, userID string) (decimal.Decimal, error) {
w, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return decimal.Zero, fmt.Errorf("钱包不存在")
}
return w.Balance, nil
}
func (s *WalletAggregateServiceImpl) LoadWalletByUserId(ctx context.Context, userID string) (*entities.Wallet, error) {
return s.walletRepo.GetByUserID(ctx, userID)
}

View File

@@ -4,30 +4,32 @@ import (
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// Product 产品实体
type Product struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"产品ID"`
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
Description string `gorm:"type:text" comment:"产品简介"`
Content string `gorm:"type:text" comment:"产品内容"`
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
Price float64 `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
IsVisible bool `gorm:"default:true" comment:"是否展示"`
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
ID string `gorm:"primaryKey;type:varchar(36)" comment:"产品ID"`
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
Description string `gorm:"type:text" comment:"产品简介"`
Content string `gorm:"type:text" comment:"产品内容"`
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
IsVisible bool `gorm:"default:true" comment:"是否展示"`
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
// 组合包相关关联
PackageItems []*ProductPackageItem `gorm:"foreignKey:PackageID" comment:"组合包项目列表"`
// SEO信息
SEOTitle string `gorm:"type:varchar(200)" comment:"SEO标题"`
SEODescription string `gorm:"type:text" comment:"SEO描述"`
SEOKeywords string `gorm:"type:text" comment:"SEO关键词"`
// 关联关系
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"产品分类"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
@@ -56,13 +58,6 @@ func (p *Product) CanBeSubscribed() bool {
return p.IsValid() && p.IsVisible
}
// GetDisplayPrice 获取显示价格
func (p *Product) GetDisplayPrice() float64 {
if p.Price < 0 {
return 0
}
return p.Price
}
// UpdateSEO 更新SEO信息
func (p *Product) UpdateSEO(title, description, keywords string) {
@@ -96,7 +91,6 @@ func (p *Product) SetAsPackage() {
p.IsPackage = true
}
// SetAsSingle 设置为单个产品
func (p *Product) SetAsSingle() {
p.IsPackage = false
}
func (p *Product) IsCombo() bool {
return p.IsPackage
}

View File

@@ -0,0 +1,159 @@
package entities
import (
"encoding/json"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ProductApiConfig 产品API配置实体
type ProductApiConfig struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"配置ID"`
ProductID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"产品ID"`
// 请求参数配置
RequestParams string `gorm:"type:json;not null" comment:"请求参数配置JSON"`
// 响应字段配置
ResponseFields string `gorm:"type:json;not null" comment:"响应字段配置JSON"`
// 响应示例
ResponseExample string `gorm:"type:json;not null" comment:"响应示例JSON"`
// 关联关系
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// RequestParam 请求参数结构
type RequestParam 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:"验证规则"`
}
// ResponseField 响应字段结构
type ResponseField 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:"字段示例"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (pac *ProductApiConfig) BeforeCreate(tx *gorm.DB) error {
if pac.ID == "" {
pac.ID = uuid.New().String()
}
return nil
}
// Validate 验证产品API配置
func (pac *ProductApiConfig) Validate() error {
if pac.ProductID == "" {
return NewValidationError("产品ID不能为空")
}
if pac.RequestParams == "" {
return NewValidationError("请求参数配置不能为空")
}
if pac.ResponseFields == "" {
return NewValidationError("响应字段配置不能为空")
}
if pac.ResponseExample == "" {
return NewValidationError("响应示例不能为空")
}
return nil
}
// NewValidationError 创建验证错误
func NewValidationError(message string) error {
return &ValidationError{Message: message}
}
// ValidationError 验证错误
type ValidationError struct {
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
// GetRequestParams 获取请求参数列表
func (pac *ProductApiConfig) GetRequestParams() ([]RequestParam, error) {
var params []RequestParam
if pac.RequestParams != "" {
err := json.Unmarshal([]byte(pac.RequestParams), &params)
if err != nil {
return nil, err
}
}
return params, nil
}
// SetRequestParams 设置请求参数列表
func (pac *ProductApiConfig) SetRequestParams(params []RequestParam) error {
data, err := json.Marshal(params)
if err != nil {
return err
}
pac.RequestParams = string(data)
return nil
}
// GetResponseFields 获取响应字段列表
func (pac *ProductApiConfig) GetResponseFields() ([]ResponseField, error) {
var fields []ResponseField
if pac.ResponseFields != "" {
err := json.Unmarshal([]byte(pac.ResponseFields), &fields)
if err != nil {
return nil, err
}
}
return fields, nil
}
// SetResponseFields 设置响应字段列表
func (pac *ProductApiConfig) SetResponseFields(fields []ResponseField) error {
data, err := json.Marshal(fields)
if err != nil {
return err
}
pac.ResponseFields = string(data)
return nil
}
// GetResponseExample 获取响应示例
func (pac *ProductApiConfig) GetResponseExample() (map[string]interface{}, error) {
var example map[string]interface{}
if pac.ResponseExample != "" {
err := json.Unmarshal([]byte(pac.ResponseExample), &example)
if err != nil {
return nil, err
}
}
return example, nil
}
// SetResponseExample 设置响应示例
func (pac *ProductApiConfig) SetResponseExample(example map[string]interface{}) error {
data, err := json.Marshal(example)
if err != nil {
return err
}
pac.ResponseExample = string(data)
return nil
}

View File

@@ -9,17 +9,17 @@ import (
// ProductCategory 产品分类实体
type ProductCategory struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"分类ID"`
Name string `gorm:"type:varchar(100);not null" comment:"分类名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"分类编号"`
Description string `gorm:"type:text" comment:"分类描述"`
Sort int `gorm:"default:0" comment:"排序"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
IsVisible bool `gorm:"default:true" comment:"是否展示"`
ID string `gorm:"primaryKey;type:varchar(36)" comment:"分类ID"`
Name string `gorm:"type:varchar(100);not null" comment:"分类名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"分类编号"`
Description string `gorm:"type:text" comment:"分类描述"`
Sort int `gorm:"default:0" comment:"排序"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
IsVisible bool `gorm:"default:true" comment:"是否展示"`
// 关联关系
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
@@ -61,4 +61,4 @@ func (pc *ProductCategory) Show() {
// Hide 隐藏分类
func (pc *ProductCategory) Hide() {
pc.IsVisible = false
}
}

View File

@@ -0,0 +1,32 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ProductPackageItem 产品组合包项目
type ProductPackageItem struct {
ID string `gorm:"primaryKey;type:varchar(36)"`
PackageID string `gorm:"type:varchar(36);not null;index" comment:"组合包产品ID"`
ProductID string `gorm:"type:varchar(36);not null;index" comment:"子产品ID"`
SortOrder int `gorm:"default:0" comment:"排序"`
// 关联关系
Package *Product `gorm:"foreignKey:PackageID" comment:"组合包产品"`
Product *Product `gorm:"foreignKey:ProductID" comment:"子产品"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (ppi *ProductPackageItem) BeforeCreate(tx *gorm.DB) error {
if ppi.ID == "" {
ppi.ID = uuid.New().String()
}
return nil
}

View File

@@ -0,0 +1,53 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ProductParameter 产品参数配置实体
type ProductParameter struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"参数配置ID"`
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
Name string `gorm:"type:varchar(100);not null" comment:"参数名称"`
Field string `gorm:"type:varchar(50);not null" comment:"参数字段名"`
Type string `gorm:"type:varchar(20);not null;default:'string'" comment:"参数类型"`
Required bool `gorm:"default:true" comment:"是否必填"`
Description string `gorm:"type:text" comment:"参数描述"`
Example string `gorm:"type:varchar(200)" comment:"参数示例"`
Validation string `gorm:"type:text" comment:"验证规则"`
SortOrder int `gorm:"default:0" comment:"排序"`
// 关联关系
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (pp *ProductParameter) BeforeCreate(tx *gorm.DB) error {
if pp.ID == "" {
pp.ID = uuid.New().String()
}
return nil
}
// IsValid 检查参数配置是否有效
func (pp *ProductParameter) IsValid() bool {
return pp.DeletedAt.Time.IsZero()
}
// GetValidationRules 获取验证规则
func (pp *ProductParameter) GetValidationRules() map[string]interface{} {
if pp.Validation == "" {
return nil
}
// 这里可以解析JSON格式的验证规则
// 暂时返回空map后续可以扩展
return make(map[string]interface{})
}

View File

@@ -4,6 +4,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
@@ -12,7 +13,7 @@ type Subscription struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
Price float64 `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
Price decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
// 关联关系

View File

@@ -0,0 +1,27 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/product/entities"
)
// ProductApiConfigRepository 产品API配置仓库接口
type ProductApiConfigRepository interface {
// 基础CRUD操作
Create(ctx context.Context, config entities.ProductApiConfig) error
Update(ctx context.Context, config entities.ProductApiConfig) error
Delete(ctx context.Context, id string) error
GetByID(ctx context.Context, id string) (*entities.ProductApiConfig, error)
// 根据产品ID查找配置
FindByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error)
// 根据产品代码查找配置
FindByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error)
// 批量获取配置
FindByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error)
// 检查配置是否存在
ExistsByProductID(ctx context.Context, productID string) (bool, error)
}

View File

@@ -10,22 +10,30 @@ import (
// ProductRepository 产品仓储接口
type ProductRepository interface {
interfaces.Repository[entities.Product]
// 基础查询方法
FindByCode(ctx context.Context, code string) (*entities.Product, error)
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.Product, error)
FindVisible(ctx context.Context) ([]*entities.Product, error)
FindEnabled(ctx context.Context) ([]*entities.Product, error)
// 复杂查询方法
ListProducts(ctx context.Context, query *queries.ListProductsQuery) ([]*entities.Product, int64, error)
// 业务查询方法
FindSubscribableProducts(ctx context.Context, userID string) ([]*entities.Product, error)
FindProductsByIDs(ctx context.Context, ids []string) ([]*entities.Product, error)
// 统计方法
CountByCategory(ctx context.Context, categoryID string) (int64, error)
CountEnabled(ctx context.Context) (int64, error)
CountVisible(ctx context.Context) (int64, error)
}
// 组合包相关方法
GetPackageItems(ctx context.Context, packageID string) ([]*entities.ProductPackageItem, error)
CreatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error
GetPackageItemByID(ctx context.Context, itemID string) (*entities.ProductPackageItem, error)
UpdatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error
DeletePackageItem(ctx context.Context, itemID string) error
DeletePackageItemsByPackageID(ctx context.Context, packageID string) error
}

View File

@@ -2,12 +2,12 @@ package queries
// ListSubscriptionsQuery 订阅列表查询
type ListSubscriptionsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
Keyword string `json:"keyword"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
Keyword string `json:"keyword"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
}
// GetSubscriptionQuery 获取订阅详情查询
@@ -23,4 +23,4 @@ type GetUserSubscriptionsQuery struct {
// GetProductSubscriptionsQuery 获取产品订阅查询
type GetProductSubscriptionsQuery struct {
ProductID string `json:"product_id"`
}
}

View File

@@ -0,0 +1,161 @@
package services
import (
"context"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"go.uber.org/zap"
)
// ProductApiConfigService 产品API配置领域服务接口
type ProductApiConfigService interface {
// 根据产品ID获取API配置
GetApiConfigByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error)
// 根据产品代码获取API配置
GetApiConfigByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error)
// 批量获取产品API配置
GetApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error)
// 创建产品API配置
CreateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error
// 更新产品API配置
UpdateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error
// 删除产品API配置
DeleteApiConfig(ctx context.Context, configID string) error
// 检查产品API配置是否存在
ExistsByProductID(ctx context.Context, productID string) (bool, error)
}
// ProductApiConfigServiceImpl 产品API配置领域服务实现
type ProductApiConfigServiceImpl struct {
apiConfigRepo repositories.ProductApiConfigRepository
logger *zap.Logger
}
// NewProductApiConfigService 创建产品API配置领域服务
func NewProductApiConfigService(
apiConfigRepo repositories.ProductApiConfigRepository,
logger *zap.Logger,
) ProductApiConfigService {
return &ProductApiConfigServiceImpl{
apiConfigRepo: apiConfigRepo,
logger: logger,
}
}
// GetApiConfigByProductID 根据产品ID获取API配置
func (s *ProductApiConfigServiceImpl) GetApiConfigByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error) {
s.logger.Debug("获取产品API配置", zap.String("product_id", productID))
config, err := s.apiConfigRepo.FindByProductID(ctx, productID)
if err != nil {
s.logger.Error("获取产品API配置失败", zap.Error(err), zap.String("product_id", productID))
return nil, err
}
return config, nil
}
// GetApiConfigByProductCode 根据产品代码获取API配置
func (s *ProductApiConfigServiceImpl) GetApiConfigByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error) {
s.logger.Debug("根据产品代码获取API配置", zap.String("product_code", productCode))
config, err := s.apiConfigRepo.FindByProductCode(ctx, productCode)
if err != nil {
s.logger.Error("根据产品代码获取API配置失败", zap.Error(err), zap.String("product_code", productCode))
return nil, err
}
return config, nil
}
// GetApiConfigsByProductIDs 批量获取产品API配置
func (s *ProductApiConfigServiceImpl) GetApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error) {
s.logger.Debug("批量获取产品API配置", zap.Strings("product_ids", productIDs))
configs, err := s.apiConfigRepo.FindByProductIDs(ctx, productIDs)
if err != nil {
s.logger.Error("批量获取产品API配置失败", zap.Error(err), zap.Strings("product_ids", productIDs))
return nil, err
}
return configs, nil
}
// CreateApiConfig 创建产品API配置
func (s *ProductApiConfigServiceImpl) CreateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error {
s.logger.Debug("创建产品API配置", zap.String("product_id", config.ProductID))
// 检查是否已存在配置
exists, err := s.apiConfigRepo.ExistsByProductID(ctx, config.ProductID)
if err != nil {
s.logger.Error("检查产品API配置是否存在失败", zap.Error(err), zap.String("product_id", config.ProductID))
return err
}
if exists {
return entities.NewValidationError("产品API配置已存在")
}
// 验证配置
if err := config.Validate(); err != nil {
s.logger.Error("产品API配置验证失败", zap.Error(err), zap.String("product_id", config.ProductID))
return err
}
// 保存配置
err = s.apiConfigRepo.Create(ctx, *config)
if err != nil {
s.logger.Error("创建产品API配置失败", zap.Error(err), zap.String("product_id", config.ProductID))
return err
}
s.logger.Info("产品API配置创建成功", zap.String("product_id", config.ProductID))
return nil
}
// UpdateApiConfig 更新产品API配置
func (s *ProductApiConfigServiceImpl) UpdateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error {
s.logger.Debug("更新产品API配置", zap.String("config_id", config.ID))
// 验证配置
if err := config.Validate(); err != nil {
s.logger.Error("产品API配置验证失败", zap.Error(err), zap.String("config_id", config.ID))
return err
}
// 更新配置
err := s.apiConfigRepo.Update(ctx, *config)
if err != nil {
s.logger.Error("更新产品API配置失败", zap.Error(err), zap.String("config_id", config.ID))
return err
}
s.logger.Info("产品API配置更新成功", zap.String("config_id", config.ID))
return nil
}
// DeleteApiConfig 删除产品API配置
func (s *ProductApiConfigServiceImpl) DeleteApiConfig(ctx context.Context, configID string) error {
s.logger.Debug("删除产品API配置", zap.String("config_id", configID))
err := s.apiConfigRepo.Delete(ctx, configID)
if err != nil {
s.logger.Error("删除产品API配置失败", zap.Error(err), zap.String("config_id", configID))
return err
}
s.logger.Info("产品API配置删除成功", zap.String("config_id", configID))
return nil
}
// ExistsByProductID 检查产品API配置是否存在
func (s *ProductApiConfigServiceImpl) ExistsByProductID(ctx context.Context, productID string) (bool, error) {
return s.apiConfigRepo.ExistsByProductID(ctx, productID)
}

View File

@@ -8,8 +8,11 @@ import (
"go.uber.org/zap"
"tyapi-server/internal/application/product/dto/commands"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"tyapi-server/internal/domains/product/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// ProductManagementService 产品管理领域服务
@@ -69,6 +72,13 @@ func (s *ProductManagementService) GetProductByID(ctx context.Context, productID
return &product, nil
}
func (s *ProductManagementService) GetProductByCode(ctx context.Context, productCode string) (*entities.Product, error) {
product, err := s.productRepo.FindByCode(ctx, productCode)
if err != nil {
return nil, fmt.Errorf("产品不存在: %w", err)
}
return product, nil
}
// GetProductWithCategory 获取产品及其分类信息
func (s *ProductManagementService) GetProductWithCategory(ctx context.Context, productID string) (*entities.Product, error) {
product, err := s.productRepo.GetByID(ctx, productID)
@@ -84,9 +94,107 @@ func (s *ProductManagementService) GetProductWithCategory(ctx context.Context, p
}
}
// 如果是组合包,加载子产品信息
if product.IsPackage {
packageItems, err := s.productRepo.GetPackageItems(ctx, productID)
if err == nil {
product.PackageItems = packageItems
}
}
return &product, nil
}
// GetPackageItems 获取组合包项目列表
func (s *ProductManagementService) GetPackageItems(ctx context.Context, packageID string) ([]*entities.ProductPackageItem, error) {
packageItems, err := s.productRepo.GetPackageItems(ctx, packageID)
if err != nil {
s.logger.Error("获取组合包项目失败", zap.Error(err))
return nil, fmt.Errorf("获取组合包项目失败: %w", err)
}
return packageItems, nil
}
// CreatePackageItem 创建组合包项目
func (s *ProductManagementService) CreatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error {
if err := s.productRepo.CreatePackageItem(ctx, packageItem); err != nil {
s.logger.Error("创建组合包项目失败", zap.Error(err))
return fmt.Errorf("创建组合包项目失败: %w", err)
}
s.logger.Info("组合包项目创建成功",
zap.String("package_id", packageItem.PackageID),
zap.String("product_id", packageItem.ProductID),
)
return nil
}
// GetPackageItemByID 根据ID获取组合包项目
func (s *ProductManagementService) GetPackageItemByID(ctx context.Context, itemID string) (*entities.ProductPackageItem, error) {
packageItem, err := s.productRepo.GetPackageItemByID(ctx, itemID)
if err != nil {
return nil, fmt.Errorf("组合包项目不存在: %w", err)
}
return packageItem, nil
}
// UpdatePackageItem 更新组合包项目
func (s *ProductManagementService) UpdatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error {
if err := s.productRepo.UpdatePackageItem(ctx, packageItem); err != nil {
s.logger.Error("更新组合包项目失败", zap.Error(err))
return fmt.Errorf("更新组合包项目失败: %w", err)
}
s.logger.Info("组合包项目更新成功",
zap.String("item_id", packageItem.ID),
zap.String("package_id", packageItem.PackageID),
)
return nil
}
// DeletePackageItem 删除组合包项目
func (s *ProductManagementService) DeletePackageItem(ctx context.Context, itemID string) error {
if err := s.productRepo.DeletePackageItem(ctx, itemID); err != nil {
s.logger.Error("删除组合包项目失败", zap.Error(err))
return fmt.Errorf("删除组合包项目失败: %w", err)
}
s.logger.Info("组合包项目删除成功", zap.String("item_id", itemID))
return nil
}
// UpdatePackageItemsBatch 批量更新组合包子产品
func (s *ProductManagementService) UpdatePackageItemsBatch(ctx context.Context, packageID string, items []commands.PackageItemData) error {
// 删除现有的所有子产品
if err := s.productRepo.DeletePackageItemsByPackageID(ctx, packageID); err != nil {
s.logger.Error("删除现有组合包子产品失败", zap.Error(err))
return fmt.Errorf("删除现有组合包子产品失败: %w", err)
}
// 创建新的子产品项目
for _, item := range items {
packageItem := &entities.ProductPackageItem{
PackageID: packageID,
ProductID: item.ProductID,
SortOrder: item.SortOrder,
}
if err := s.productRepo.CreatePackageItem(ctx, packageItem); err != nil {
s.logger.Error("创建组合包子产品失败", zap.Error(err))
return fmt.Errorf("创建组合包子产品失败: %w", err)
}
}
s.logger.Info("批量更新组合包子产品成功",
zap.String("package_id", packageID),
zap.Int("item_count", len(items)),
)
return nil
}
// UpdateProduct 更新产品
func (s *ProductManagementService) UpdateProduct(ctx context.Context, product *entities.Product) error {
// 验证产品信息
@@ -152,7 +260,7 @@ func (s *ProductManagementService) ValidateProduct(product *entities.Product) er
return errors.New("产品编号不能为空")
}
if product.Price < 0 {
if product.Price.IsNegative() {
return errors.New("产品价格不能为负数")
}
@@ -182,4 +290,48 @@ func (s *ProductManagementService) ValidateProductCode(code string, excludeID st
}
return nil
}
}
// ListProducts 获取产品列表(支持筛选和分页)
func (s *ProductManagementService) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.Product, int64, error) {
// 构建查询条件
query := &queries.ListProductsQuery{
Page: options.Page,
PageSize: options.PageSize,
SortBy: options.Sort,
SortOrder: options.Order,
}
// 应用筛选条件
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
query.Keyword = keyword
}
if categoryID, ok := filters["category_id"].(string); ok && categoryID != "" {
query.CategoryID = categoryID
}
if isEnabled, ok := filters["is_enabled"].(bool); ok {
query.IsEnabled = &isEnabled
}
if isVisible, ok := filters["is_visible"].(bool); ok {
query.IsVisible = &isVisible
}
if isPackage, ok := filters["is_package"].(bool); ok {
query.IsPackage = &isPackage
}
// 调用仓储层获取产品列表
products, total, err := s.productRepo.ListProducts(ctx, query)
if err != nil {
s.logger.Error("获取产品列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取产品列表失败: %w", err)
}
s.logger.Info("产品列表查询成功",
zap.Int("count", len(products)),
zap.Int64("total", total),
zap.Int("page", options.Page),
zap.Int("page_size", options.PageSize),
)
return products, total, nil
}

View File

@@ -5,10 +5,13 @@ import (
"errors"
"fmt"
"gorm.io/gorm"
"go.uber.org/zap"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"tyapi-server/internal/domains/product/repositories/queries"
)
// ProductSubscriptionService 产品订阅领域服务
@@ -32,6 +35,31 @@ func NewProductSubscriptionService(
}
}
// UserSubscribedProductByCode 查找用户已订阅的产品
func (s *ProductSubscriptionService) UserSubscribedProductByCode(ctx context.Context, userID string, productCode string) (*entities.Subscription, error) {
product, err := s.productRepo.FindByCode(ctx, productCode)
if err != nil {
return nil, err
}
subscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, product.ID)
if err != nil {
return nil, err
}
return subscription, nil
}
// GetUserSubscribedProduct 查找用户已订阅的产品
func (s *ProductSubscriptionService) GetUserSubscribedProduct(ctx context.Context, userID string, productID string) (*entities.Subscription, error) {
subscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, productID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return subscription, nil
}
// CanUserSubscribeProduct 检查用户是否可以订阅产品
func (s *ProductSubscriptionService) CanUserSubscribeProduct(ctx context.Context, userID string, productID string) (bool, error) {
// 检查产品是否存在且可订阅
@@ -92,6 +120,11 @@ func (s *ProductSubscriptionService) CreateSubscription(ctx context.Context, use
return &createdSubscription, nil
}
// ListSubscriptions 获取订阅列表
func (s *ProductSubscriptionService) ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error) {
return s.subscriptionRepo.ListSubscriptions(ctx, query)
}
// GetUserSubscriptions 获取用户订阅列表
func (s *ProductSubscriptionService) GetUserSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error) {
return s.subscriptionRepo.FindByUserID(ctx, userID)
@@ -141,4 +174,20 @@ func (s *ProductSubscriptionService) GetProductStats(ctx context.Context) (map[s
}
return stats, nil
}
}
func (s *ProductSubscriptionService) SaveSubscription(ctx context.Context, subscription *entities.Subscription) error {
exists, err := s.subscriptionRepo.Exists(ctx, subscription.ID)
if err != nil {
return fmt.Errorf("检查订阅是否存在失败: %w", err)
}
if exists {
return s.subscriptionRepo.Update(ctx, *subscription)
} else {
_, err := s.subscriptionRepo.Create(ctx, *subscription)
if err != nil {
return fmt.Errorf("创建订阅失败: %w", err)
}
return nil
}
}

View File

@@ -0,0 +1,317 @@
package entities
import (
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ContractType 合同类型枚举
type ContractType string
const (
ContractTypeCooperation ContractType = "cooperation" // 合作协议
)
// ContractInfo 合同信息聚合根
// 存储企业签署的合同信息,一个企业可以有多个合同
type ContractInfo struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"合同信息唯一标识"`
EnterpriseInfoID string `gorm:"type:varchar(36);not null;index" json:"enterprise_info_id" comment:"关联企业信息ID"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"关联用户ID"`
// 合同基本信息
ContractName string `gorm:"type:varchar(255);not null" json:"contract_name" comment:"合同名称"`
ContractType ContractType `gorm:"type:varchar(50);not null;index" json:"contract_type" comment:"合同类型"`
ContractFileID string `gorm:"type:varchar(100);not null" json:"contract_file_id" comment:"合同文件ID"`
ContractFileURL string `gorm:"type:varchar(500);not null" json:"contract_file_url" comment:"合同文件下载链接"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系
EnterpriseInfo *EnterpriseInfo `gorm:"foreignKey:EnterpriseInfoID" json:"enterprise_info,omitempty" comment:"关联的企业信息"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
func (ContractInfo) TableName() string {
return "contract_infos"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (c *ContractInfo) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}
// ================ 工厂方法 ================
// NewContractInfo 创建新的合同信息
func NewContractInfo(enterpriseInfoID, userID, contractName string, contractType ContractType, contractFileID, contractFileURL string) (*ContractInfo, error) {
if enterpriseInfoID == "" {
return nil, fmt.Errorf("企业信息ID不能为空")
}
if userID == "" {
return nil, fmt.Errorf("用户ID不能为空")
}
if contractName == "" {
return nil, fmt.Errorf("合同名称不能为空")
}
if contractType == "" {
return nil, fmt.Errorf("合同类型不能为空")
}
if contractFileID == "" {
return nil, fmt.Errorf("合同文件ID不能为空")
}
if contractFileURL == "" {
return nil, fmt.Errorf("合同文件URL不能为空")
}
// 验证合同类型
if !isValidContractType(contractType) {
return nil, fmt.Errorf("无效的合同类型: %s", contractType)
}
contractInfo := &ContractInfo{
ID: uuid.New().String(),
EnterpriseInfoID: enterpriseInfoID,
UserID: userID,
ContractName: contractName,
ContractType: contractType,
ContractFileID: contractFileID,
ContractFileURL: contractFileURL,
domainEvents: make([]interface{}, 0),
}
// 添加领域事件
contractInfo.addDomainEvent(&ContractInfoCreatedEvent{
ContractInfoID: contractInfo.ID,
EnterpriseInfoID: enterpriseInfoID,
UserID: userID,
ContractName: contractName,
ContractType: string(contractType),
CreatedAt: time.Now(),
})
return contractInfo, nil
}
// ================ 聚合根核心方法 ================
// UpdateContractInfo 更新合同信息
func (c *ContractInfo) UpdateContractInfo(contractName, contractFileID, contractFileURL string) error {
// 验证输入参数
if contractName == "" {
return fmt.Errorf("合同名称不能为空")
}
if contractFileID == "" {
return fmt.Errorf("合同文件ID不能为空")
}
if contractFileURL == "" {
return fmt.Errorf("合同文件URL不能为空")
}
// 记录原始值用于事件
oldContractName := c.ContractName
oldContractFileID := c.ContractFileID
// 更新字段
c.ContractName = contractName
c.ContractFileID = contractFileID
c.ContractFileURL = contractFileURL
// 添加领域事件
c.addDomainEvent(&ContractInfoUpdatedEvent{
ContractInfoID: c.ID,
EnterpriseInfoID: c.EnterpriseInfoID,
UserID: c.UserID,
OldContractName: oldContractName,
NewContractName: contractName,
OldContractFileID: oldContractFileID,
NewContractFileID: contractFileID,
UpdatedAt: time.Now(),
})
return nil
}
// DeleteContract 删除合同
func (c *ContractInfo) DeleteContract() error {
// 添加领域事件
c.addDomainEvent(&ContractInfoDeletedEvent{
ContractInfoID: c.ID,
EnterpriseInfoID: c.EnterpriseInfoID,
UserID: c.UserID,
ContractName: c.ContractName,
ContractType: string(c.ContractType),
DeletedAt: time.Now(),
})
return nil
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (c *ContractInfo) ValidateBusinessRules() error {
// 基础字段验证
if err := c.validateBasicFields(); err != nil {
return fmt.Errorf("基础字段验证失败: %w", err)
}
// 业务规则验证
if err := c.validateBusinessLogic(); err != nil {
return fmt.Errorf("业务规则验证失败: %w", err)
}
return nil
}
// validateBasicFields 验证基础字段
func (c *ContractInfo) validateBasicFields() error {
if c.EnterpriseInfoID == "" {
return fmt.Errorf("企业信息ID不能为空")
}
if c.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if c.ContractName == "" {
return fmt.Errorf("合同名称不能为空")
}
if c.ContractType == "" {
return fmt.Errorf("合同类型不能为空")
}
if c.ContractFileID == "" {
return fmt.Errorf("合同文件ID不能为空")
}
if c.ContractFileURL == "" {
return fmt.Errorf("合同文件URL不能为空")
}
// 合同类型验证
if !isValidContractType(c.ContractType) {
return fmt.Errorf("无效的合同类型: %s", c.ContractType)
}
return nil
}
// validateBusinessLogic 验证业务逻辑
func (c *ContractInfo) validateBusinessLogic() error {
// 合同名称长度限制
if len(c.ContractName) > 255 {
return fmt.Errorf("合同名称长度不能超过255个字符")
}
// 合同文件URL格式验证
if !isValidURL(c.ContractFileURL) {
return fmt.Errorf("合同文件URL格式无效")
}
return nil
}
// ================ 查询方法 ================
// GetContractTypeName 获取合同类型名称
func (c *ContractInfo) GetContractTypeName() string {
switch c.ContractType {
case ContractTypeCooperation:
return "合作协议"
default:
return "未知类型"
}
}
// IsCooperationContract 检查是否为合作协议
func (c *ContractInfo) IsCooperationContract() bool {
return c.ContractType == ContractTypeCooperation
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (c *ContractInfo) addDomainEvent(event interface{}) {
if c.domainEvents == nil {
c.domainEvents = make([]interface{}, 0)
}
c.domainEvents = append(c.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (c *ContractInfo) GetDomainEvents() []interface{} {
return c.domainEvents
}
// ClearDomainEvents 清除领域事件
func (c *ContractInfo) ClearDomainEvents() {
c.domainEvents = make([]interface{}, 0)
}
// ================ 私有验证方法 ================
// isValidContractType 验证合同类型
func isValidContractType(contractType ContractType) bool {
switch contractType {
case ContractTypeCooperation:
return true
default:
return false
}
}
// isValidURL 验证URL格式
func isValidURL(url string) bool {
// 简单的URL格式验证
if len(url) < 10 {
return false
}
if url[:7] != "http://" && url[:8] != "https://" {
return false
}
return true
}
// ================ 领域事件定义 ================
// ContractInfoCreatedEvent 合同信息创建事件
type ContractInfoCreatedEvent struct {
ContractInfoID string `json:"contract_info_id"`
EnterpriseInfoID string `json:"enterprise_info_id"`
UserID string `json:"user_id"`
ContractName string `json:"contract_name"`
ContractType string `json:"contract_type"`
CreatedAt time.Time `json:"created_at"`
}
// ContractInfoUpdatedEvent 合同信息更新事件
type ContractInfoUpdatedEvent struct {
ContractInfoID string `json:"contract_info_id"`
EnterpriseInfoID string `json:"enterprise_info_id"`
UserID string `json:"user_id"`
OldContractName string `json:"old_contract_name"`
NewContractName string `json:"new_contract_name"`
OldContractFileID string `json:"old_contract_file_id"`
NewContractFileID string `json:"new_contract_file_id"`
UpdatedAt time.Time `json:"updated_at"`
}
// ContractInfoDeletedEvent 合同信息删除事件
type ContractInfoDeletedEvent struct {
ContractInfoID string `json:"contract_info_id"`
EnterpriseInfoID string `json:"enterprise_info_id"`
UserID string `json:"user_id"`
ContractName string `json:"contract_name"`
ContractType string `json:"contract_type"`
DeletedAt time.Time `json:"deleted_at"`
}

View File

@@ -2,13 +2,14 @@ package entities
import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// EnterpriseInfo 企业信息实体
// EnterpriseInfo 企业信息聚合根
// 存储用户在认证过程中验证后的企业信息,认证完成后不可修改
// 与用户是一对一关系,每个用户最多对应一个企业信息
type EnterpriseInfo struct {
@@ -22,6 +23,8 @@ type EnterpriseInfo struct {
LegalPersonName string `gorm:"type:varchar(100);not null" json:"legal_person_name" comment:"法定代表人姓名"`
LegalPersonID string `gorm:"type:varchar(50);not null" json:"legal_person_id" comment:"法定代表人身份证号"`
LegalPersonPhone string `gorm:"type:varchar(50);not null" json:"legal_person_phone" comment:"法定代表人手机号"`
EnterpriseAddress string `json:"enterprise_address" gorm:"type:varchar(200);not null" comment:"企业地址"`
EnterpriseEmail string `json:"enterprise_email" gorm:"type:varchar(100);not null" comment:"企业邮箱"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
@@ -30,6 +33,9 @@ type EnterpriseInfo struct {
// 关联关系
User *User `gorm:"foreignKey:UserID" json:"user,omitempty" comment:"关联的用户信息"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
@@ -37,28 +43,6 @@ func (EnterpriseInfo) TableName() string {
return "enterprise_infos"
}
// IsComplete 检查企业四要素是否完整
// 验证企业名称、统一社会信用代码、法定代表人姓名、身份证号是否都已填写
func (e *EnterpriseInfo) IsComplete() bool {
return e.CompanyName != "" &&
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != ""
}
// Validate 验证企业信息是否有效
// 这里可以添加企业信息的业务验证逻辑
// 比如统一社会信用代码格式验证、身份证号格式验证等
func (e *EnterpriseInfo) Validate() error {
if !e.IsComplete() {
return fmt.Errorf("企业信息不完整")
}
// 这里可以添加企业信息的业务验证逻辑
// 比如统一社会信用代码格式验证、身份证号格式验证等
return nil
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (e *EnterpriseInfo) BeforeCreate(tx *gorm.DB) error {
if e.ID == "" {
@@ -66,3 +50,327 @@ func (e *EnterpriseInfo) BeforeCreate(tx *gorm.DB) error {
}
return nil
}
// ================ 工厂方法 ================
// NewEnterpriseInfo 创建新的企业信息
func NewEnterpriseInfo(userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone,enterpriseAddress, enterpriseEmail string) (*EnterpriseInfo, error) {
if userID == "" {
return nil, fmt.Errorf("用户ID不能为空")
}
if companyName == "" {
return nil, fmt.Errorf("企业名称不能为空")
}
if unifiedSocialCode == "" {
return nil, fmt.Errorf("统一社会信用代码不能为空")
}
if legalPersonName == "" {
return nil, fmt.Errorf("法定代表人姓名不能为空")
}
if legalPersonID == "" {
return nil, fmt.Errorf("法定代表人身份证号不能为空")
}
if legalPersonPhone == "" {
return nil, fmt.Errorf("法定代表人手机号不能为空")
}
if enterpriseAddress == "" {
return nil, fmt.Errorf("企业地址不能为空")
}
if enterpriseEmail == "" {
return nil, fmt.Errorf("企业邮箱不能为空")
}
enterpriseInfo := &EnterpriseInfo{
ID: uuid.New().String(),
UserID: userID,
CompanyName: companyName,
UnifiedSocialCode: unifiedSocialCode,
LegalPersonName: legalPersonName,
LegalPersonID: legalPersonID,
LegalPersonPhone: legalPersonPhone,
EnterpriseAddress: enterpriseAddress,
EnterpriseEmail: enterpriseEmail,
domainEvents: make([]interface{}, 0),
}
// 添加领域事件
enterpriseInfo.addDomainEvent(&EnterpriseInfoCreatedEvent{
EnterpriseInfoID: enterpriseInfo.ID,
UserID: userID,
CompanyName: companyName,
UnifiedSocialCode: unifiedSocialCode,
CreatedAt: time.Now(),
})
return enterpriseInfo, nil
}
// ================ 聚合根核心方法 ================
// UpdateEnterpriseInfo 更新企业信息
func (e *EnterpriseInfo) UpdateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string) error {
// 验证输入参数
if companyName == "" {
return fmt.Errorf("企业名称不能为空")
}
if unifiedSocialCode == "" {
return fmt.Errorf("统一社会信用代码不能为空")
}
if legalPersonName == "" {
return fmt.Errorf("法定代表人姓名不能为空")
}
if legalPersonID == "" {
return fmt.Errorf("法定代表人身份证号不能为空")
}
if legalPersonPhone == "" {
return fmt.Errorf("法定代表人手机号不能为空")
}
if enterpriseAddress == "" {
return fmt.Errorf("企业地址不能为空")
}
if enterpriseEmail == "" {
return fmt.Errorf("企业邮箱不能为空")
}
// 记录原始值用于事件
oldCompanyName := e.CompanyName
oldUnifiedSocialCode := e.UnifiedSocialCode
// 更新字段
e.CompanyName = companyName
e.UnifiedSocialCode = unifiedSocialCode
e.LegalPersonName = legalPersonName
e.LegalPersonID = legalPersonID
e.LegalPersonPhone = legalPersonPhone
e.EnterpriseAddress = enterpriseAddress
e.EnterpriseEmail = enterpriseEmail
// 添加领域事件
e.addDomainEvent(&EnterpriseInfoUpdatedEvent{
EnterpriseInfoID: e.ID,
UserID: e.UserID,
OldCompanyName: oldCompanyName,
NewCompanyName: companyName,
OldUnifiedSocialCode: oldUnifiedSocialCode,
NewUnifiedSocialCode: unifiedSocialCode,
UpdatedAt: time.Now(),
})
return nil
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (e *EnterpriseInfo) ValidateBusinessRules() error {
// 基础字段验证
if err := e.validateBasicFields(); err != nil {
return fmt.Errorf("基础字段验证失败: %w", err)
}
// 业务规则验证
if err := e.validateBusinessLogic(); err != nil {
return fmt.Errorf("业务规则验证失败: %w", err)
}
return nil
}
// validateBasicFields 验证基础字段
func (e *EnterpriseInfo) validateBasicFields() error {
if e.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if e.CompanyName == "" {
return fmt.Errorf("企业名称不能为空")
}
if e.UnifiedSocialCode == "" {
return fmt.Errorf("统一社会信用代码不能为空")
}
if e.LegalPersonName == "" {
return fmt.Errorf("法定代表人姓名不能为空")
}
if e.LegalPersonID == "" {
return fmt.Errorf("法定代表人身份证号不能为空")
}
if e.LegalPersonPhone == "" {
return fmt.Errorf("法定代表人手机号不能为空")
}
if e.EnterpriseEmail == "" {
return fmt.Errorf("企业邮箱不能为空")
}
// 统一社会信用代码格式验证
if !e.isValidUnifiedSocialCode(e.UnifiedSocialCode) {
return fmt.Errorf("统一社会信用代码格式无效")
}
// 身份证号格式验证
if !e.isValidIDCard(e.LegalPersonID) {
return fmt.Errorf("法定代表人身份证号格式无效")
}
// 手机号格式验证
if !e.isValidPhone(e.LegalPersonPhone) {
return fmt.Errorf("法定代表人手机号格式无效")
}
// 邮箱格式验证 (简单示例,实际应更严格)
if !e.isValidEmail(e.EnterpriseEmail) {
return fmt.Errorf("企业邮箱格式无效")
}
return nil
}
// validateBusinessLogic 验证业务逻辑
func (e *EnterpriseInfo) validateBusinessLogic() error {
// 企业名称长度限制
if len(e.CompanyName) > 255 {
return fmt.Errorf("企业名称长度不能超过255个字符")
}
// 法定代表人姓名长度限制
if len(e.LegalPersonName) > 100 {
return fmt.Errorf("法定代表人姓名长度不能超过100个字符")
}
// 企业邮箱格式验证 (简单示例,实际应更严格)
if !e.isValidEmail(e.EnterpriseEmail) {
return fmt.Errorf("企业邮箱格式无效")
}
return nil
}
// ================ 查询方法 ================
// IsComplete 检查企业四要素是否完整
func (e *EnterpriseInfo) IsComplete() bool {
return e.CompanyName != "" &&
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != "" &&
e.LegalPersonPhone != "" &&
e.EnterpriseEmail != ""
}
// GetCertificationProgress 获取认证进度
func (e *EnterpriseInfo) GetCertificationProgress() int {
if e.IsComplete() {
return 100
}
return 50
}
// GetCertificationStatus 获取认证状态描述
func (e *EnterpriseInfo) GetCertificationStatus() string {
if e.IsComplete() {
return "信息完整"
}
return "信息不完整"
}
// CanUpdate 检查是否可以更新
func (e *EnterpriseInfo) CanUpdate() bool {
return true
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (e *EnterpriseInfo) addDomainEvent(event interface{}) {
if e.domainEvents == nil {
e.domainEvents = make([]interface{}, 0)
}
e.domainEvents = append(e.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (e *EnterpriseInfo) GetDomainEvents() []interface{} {
return e.domainEvents
}
// ClearDomainEvents 清除领域事件
func (e *EnterpriseInfo) ClearDomainEvents() {
e.domainEvents = make([]interface{}, 0)
}
// ================ 私有验证方法 ================
// isValidUnifiedSocialCode 验证统一社会信用代码格式
func (e *EnterpriseInfo) isValidUnifiedSocialCode(code string) bool {
// 统一社会信用代码为18位
if len(code) != 18 {
return false
}
// 这里可以添加更详细的格式验证逻辑
return true
}
// isValidIDCard 验证身份证号格式
func (e *EnterpriseInfo) isValidIDCard(id string) bool {
// 身份证号为18位
if len(id) != 18 {
return false
}
// 这里可以添加更详细的格式验证逻辑
return true
}
// isValidPhone 验证手机号格式
func (e *EnterpriseInfo) isValidPhone(phone string) bool {
// 手机号格式验证
if len(phone) != 11 {
return false
}
// 这里可以添加更详细的格式验证逻辑
return true
}
// isValidEmail 验证邮箱格式
func (e *EnterpriseInfo) isValidEmail(email string) bool {
// 简单的邮箱格式验证,实际应更严格
if len(email) > 255 || len(email) < 3 { // 长度限制
return false
}
if !strings.Contains(email, "@") {
return false
}
if !strings.Contains(email, ".") {
return false
}
return true
}
// ================ 领域事件定义 ================
// EnterpriseInfoCreatedEvent 企业信息创建事件
type EnterpriseInfoCreatedEvent struct {
EnterpriseInfoID string `json:"enterprise_info_id"`
UserID string `json:"user_id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
CreatedAt time.Time `json:"created_at"`
}
// EnterpriseInfoUpdatedEvent 企业信息更新事件
type EnterpriseInfoUpdatedEvent struct {
EnterpriseInfoID string `json:"enterprise_info_id"`
UserID string `json:"user_id"`
OldCompanyName string `json:"old_company_name"`
NewCompanyName string `json:"new_company_name"`
OldUnifiedSocialCode string `json:"old_unified_social_code"`
NewUnifiedSocialCode string `json:"new_unified_social_code"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -1,12 +1,13 @@
package entities
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"regexp"
"time"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
@@ -18,7 +19,7 @@ const (
UserTypeAdmin UserType = "admin" // 管理员
)
// User 用户实体
// User 用户聚合根
// 系统用户的核心信息,提供基础的账户管理功能
// 支持手机号登录密码加密存储实现Entity接口便于统一管理
type User struct {
@@ -33,6 +34,7 @@ type User struct {
// 管理员特有字段
Active bool `gorm:"default:true" json:"is_active" comment:"账户是否激活"`
IsCertified bool `gorm:"default:false" json:"is_certified" comment:"是否完成认证"`
LastLoginAt *time.Time `json:"last_login_at" comment:"最后登录时间"`
LoginCount int `gorm:"default:0" json:"login_count" comment:"登录次数统计"`
Permissions string `gorm:"type:text" json:"permissions" comment:"权限列表(JSON格式存储)"`
@@ -44,6 +46,9 @@ type User struct {
// 关联关系
EnterpriseInfo *EnterpriseInfo `gorm:"foreignKey:UserID" json:"enterprise_info,omitempty" comment:"企业信息(认证后获得)"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
@@ -85,14 +90,305 @@ func (u *User) Validate() error {
return NewValidationError("手机号格式无效")
}
// 验证密码强度
if err := u.validatePasswordStrength(u.Password); err != nil {
return nil
}
// CompleteCertification 完成认证
func (u *User) CompleteCertification() error {
u.IsCertified = true
return nil
}
// ================ 企业信息管理方法 ================
// CreateEnterpriseInfo 创建企业信息
func (u *User) CreateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string) error {
// 检查是否已有企业信息
if u.EnterpriseInfo != nil {
return fmt.Errorf("用户已有企业信息")
}
// 创建企业信息实体
enterpriseInfo, err := NewEnterpriseInfo(u.ID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail)
if err != nil {
return fmt.Errorf("创建企业信息失败: %w", err)
}
// 设置关联关系
u.EnterpriseInfo = enterpriseInfo
// 添加领域事件
u.addDomainEvent(&UserEnterpriseInfoCreatedEvent{
UserID: u.ID,
EnterpriseInfoID: enterpriseInfo.ID,
CompanyName: companyName,
UnifiedSocialCode: unifiedSocialCode,
CreatedAt: time.Now(),
})
return nil
}
// UpdateEnterpriseInfo 更新企业信息
func (u *User) UpdateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail string) error {
// 检查是否有企业信息
if u.EnterpriseInfo == nil {
return fmt.Errorf("用户暂无企业信息")
}
// 记录原始值用于事件
oldCompanyName := u.EnterpriseInfo.CompanyName
oldUnifiedSocialCode := u.EnterpriseInfo.UnifiedSocialCode
// 更新企业信息
err := u.EnterpriseInfo.UpdateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress, enterpriseEmail)
if err != nil {
return err
}
// 添加领域事件
u.addDomainEvent(&UserEnterpriseInfoUpdatedEvent{
UserID: u.ID,
EnterpriseInfoID: u.EnterpriseInfo.ID,
OldCompanyName: oldCompanyName,
NewCompanyName: companyName,
OldUnifiedSocialCode: oldUnifiedSocialCode,
NewUnifiedSocialCode: unifiedSocialCode,
UpdatedAt: time.Now(),
})
return nil
}
// GetEnterpriseInfo 获取企业信息
func (u *User) GetEnterpriseInfo() *EnterpriseInfo {
return u.EnterpriseInfo
}
// HasEnterpriseInfo 检查是否有企业信息
func (u *User) HasEnterpriseInfo() bool {
return u.EnterpriseInfo != nil
}
// RemoveEnterpriseInfo 移除企业信息
func (u *User) RemoveEnterpriseInfo() error {
if u.EnterpriseInfo == nil {
return fmt.Errorf("用户暂无企业信息")
}
enterpriseInfoID := u.EnterpriseInfo.ID
u.EnterpriseInfo = nil
// 添加领域事件
u.addDomainEvent(&UserEnterpriseInfoRemovedEvent{
UserID: u.ID,
EnterpriseInfoID: enterpriseInfoID,
RemovedAt: time.Now(),
})
return nil
}
// ValidateEnterpriseInfo 验证企业信息
func (u *User) ValidateEnterpriseInfo() error {
if u.EnterpriseInfo == nil {
return fmt.Errorf("用户暂无企业信息")
}
return u.EnterpriseInfo.ValidateBusinessRules()
}
// ================ 聚合根核心方法 ================
// Register 用户注册
func (u *User) Register() error {
// 验证用户信息
if err := u.Validate(); err != nil {
return fmt.Errorf("用户信息验证失败: %w", err)
}
// 添加领域事件
u.addDomainEvent(&UserRegisteredEvent{
UserID: u.ID,
Phone: u.Phone,
UserType: u.UserType,
CreatedAt: time.Now(),
})
return nil
}
// Login 用户登录
func (u *User) Login(ipAddress, userAgent string) error {
// 检查用户是否可以登录
if !u.CanLogin() {
return fmt.Errorf("用户无法登录")
}
// 更新登录信息
u.UpdateLastLoginAt()
u.IncrementLoginCount()
// 添加领域事件
u.addDomainEvent(&UserLoggedInEvent{
UserID: u.ID,
Phone: u.Phone,
IPAddress: ipAddress,
UserAgent: userAgent,
LoginAt: time.Now(),
})
return nil
}
// ChangePassword 修改密码
func (u *User) ChangePassword(oldPassword, newPassword, confirmPassword string) error {
// 验证旧密码
if !u.CheckPassword(oldPassword) {
return fmt.Errorf("原密码错误")
}
// 验证新密码
if newPassword != confirmPassword {
return fmt.Errorf("两次输入的密码不一致")
}
// 设置新密码
if err := u.SetPassword(newPassword); err != nil {
return fmt.Errorf("设置新密码失败: %w", err)
}
// 添加领域事件
u.addDomainEvent(&UserPasswordChangedEvent{
UserID: u.ID,
Phone: u.Phone,
ChangedAt: time.Now(),
})
return nil
}
// ActivateUser 激活用户
func (u *User) ActivateUser() error {
if u.Active {
return fmt.Errorf("用户已经是激活状态")
}
u.Activate()
// 添加领域事件
u.addDomainEvent(&UserActivatedEvent{
UserID: u.ID,
Phone: u.Phone,
ActivatedAt: time.Now(),
})
return nil
}
// DeactivateUser 停用用户
func (u *User) DeactivateUser() error {
if !u.Active {
return fmt.Errorf("用户已经是停用状态")
}
u.Deactivate()
// 添加领域事件
u.addDomainEvent(&UserDeactivatedEvent{
UserID: u.ID,
Phone: u.Phone,
DeactivatedAt: time.Now(),
})
return nil
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (u *User) ValidateBusinessRules() error {
// 1. 基础字段验证
if err := u.validateBasicFields(); err != nil {
return fmt.Errorf("基础字段验证失败: %w", err)
}
// 2. 业务规则验证
if err := u.validateBusinessLogic(); err != nil {
return fmt.Errorf("业务规则验证失败: %w", err)
}
// 3. 状态一致性验证
if err := u.validateStateConsistency(); err != nil {
return fmt.Errorf("状态一致性验证失败: %w", err)
}
return nil
}
// validateBasicFields 验证基础字段
func (u *User) validateBasicFields() error {
if u.Phone == "" {
return fmt.Errorf("手机号不能为空")
}
if u.Password == "" {
return fmt.Errorf("密码不能为空")
}
// 验证手机号格式
if !u.IsValidPhone() {
return fmt.Errorf("手机号格式无效")
}
// 不对加密后的hash做长度校验
return nil
}
// validateBusinessLogic 验证业务逻辑
func (u *User) validateBusinessLogic() error {
// 管理员用户必须有用户名
// if u.IsAdmin() && u.Username == "" {
// return fmt.Errorf("管理员用户必须有用户名")
// }
// // 普通用户不能有用户名
// if u.IsNormalUser() && u.Username != "" {
// return fmt.Errorf("普通用户不能有用户名")
// }
return nil
}
// validateStateConsistency 验证状态一致性
func (u *User) validateStateConsistency() error {
// 如果用户被删除,不能是激活状态
if u.IsDeleted() && u.Active {
return fmt.Errorf("已删除用户不能是激活状态")
}
return nil
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (u *User) addDomainEvent(event interface{}) {
if u.domainEvents == nil {
u.domainEvents = make([]interface{}, 0)
}
u.domainEvents = append(u.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (u *User) GetDomainEvents() []interface{} {
return u.domainEvents
}
// ClearDomainEvents 清除领域事件
func (u *User) ClearDomainEvents() {
u.domainEvents = make([]interface{}, 0)
}
// ================ 业务方法 ================
// IsAdmin 检查是否为管理员
@@ -131,81 +427,30 @@ func (u *User) Deactivate() {
u.Active = false
}
// ChangePassword 修改密码
// 验证旧密码,检查新密码强度,更新密码
func (u *User) ChangePassword(oldPassword, newPassword, confirmPassword string) error {
// 1. 验证确认密码
if newPassword != confirmPassword {
return NewValidationError("新密码和确认新密码不匹配")
}
// 2. 验证旧密码
if !u.CheckPassword(oldPassword) {
return NewValidationError("当前密码错误")
}
// 3. 验证新密码强度
if err := u.validatePasswordStrength(newPassword); err != nil {
return err
}
// 4. 检查新密码不能与旧密码相同
if u.CheckPassword(newPassword) {
return NewValidationError("新密码不能与当前密码相同")
}
// 5. 更新密码
hashedPassword, err := u.hashPassword(newPassword)
if err != nil {
return fmt.Errorf("密码加密失败: %w", err)
}
u.Password = hashedPassword
return nil
}
// CheckPassword 验证密码是否正确
func (u *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
return err == nil
return u.Password == hashPassword(password)
}
// SetPassword 设置密码(用于注册或重置密码)
func (u *User) SetPassword(password string) error {
// 验证密码强度
// 只对明文做强度校
if err := u.validatePasswordStrength(password); err != nil {
return err
}
// 加密密码
hashedPassword, err := u.hashPassword(password)
if err != nil {
return fmt.Errorf("密码加密失败: %w", err)
}
u.Password = hashedPassword
u.Password = hashPassword(password)
return nil
}
// ResetPassword 重置密码(忘记密码时使用)
func (u *User) ResetPassword(newPassword, confirmPassword string) error {
// 1. 验证确认密码
if newPassword != confirmPassword {
return NewValidationError("新密码和确认新密码不匹配")
}
// 2. 验证新密码强度
if err := u.validatePasswordStrength(newPassword); err != nil {
return err
}
// 3. 更新密码
hashedPassword, err := u.hashPassword(newPassword)
if err != nil {
return fmt.Errorf("密码加密失败: %w", err)
}
u.Password = hashedPassword
u.Password = hashPassword(newPassword)
return nil
}
@@ -265,16 +510,14 @@ func (u *User) GetMaskedPhone() string {
// ================ 私有方法 ================
// hashPassword 加密密码
func (u *User) hashPassword(password string) (string, error) {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedBytes), nil
// hashPassword 使用sha256+hex加密密码
func hashPassword(password string) string {
h := sha256.New()
h.Write([]byte(password))
return hex.EncodeToString(h.Sum(nil))
}
// validatePasswordStrength 验证密码强度
// validatePasswordStrength 只对明文做长度/强度校
func (u *User) validatePasswordStrength(password string) error {
if len(password) < 6 {
return NewValidationError("密码长度不能少于6位")
@@ -347,59 +590,69 @@ func IsValidationError(err error) bool {
return ok
}
// ================ 缓存相关 ================
// ================ 领域事件定义 ================
type UserCache struct {
// 基础标识
ID string `json:"id" comment:"用户唯一标识"`
Phone string `json:"phone" comment:"手机号码(登录账号)"`
Password string `json:"password" comment:"登录密码(加密存储)"`
UserType string `json:"user_type" comment:"用户类型"`
Username string `json:"username" comment:"用户名"`
Active bool `gorm:"default:true" json:"is_active" comment:"账户是否激活"`
LastLoginAt *time.Time `json:"last_login_at" comment:"最后登录时间"`
LoginCount int `gorm:"default:0" json:"login_count" comment:"登录次数统计"`
Permissions string `gorm:"type:text" json:"permissions" comment:"权限列表(JSON格式存储)"`
// 时间戳字段
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `json:"deleted_at" comment:"软删除时间"`
// UserRegisteredEvent 用户注册事件
type UserRegisteredEvent struct {
UserID string `json:"user_id"`
Phone string `json:"phone"`
UserType string `json:"user_type"`
CreatedAt time.Time `json:"created_at"`
}
// ToCache 转换为缓存结构
func (u *User) ToCache() *UserCache {
return &UserCache{
ID: u.ID,
Phone: u.Phone,
Password: u.Password,
UserType: u.UserType,
Username: u.Username,
CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt,
DeletedAt: u.DeletedAt,
// 补充所有字段
// 管理员特有字段
Active: u.Active,
LastLoginAt: u.LastLoginAt,
LoginCount: u.LoginCount,
Permissions: u.Permissions,
}
// UserLoggedInEvent 用户登录事件
type UserLoggedInEvent struct {
UserID string `json:"user_id"`
Phone string `json:"phone"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
LoginAt time.Time `json:"login_at"`
}
// FromCache 从缓存结构恢复
func (u *User) FromCache(cache *UserCache) {
u.ID = cache.ID
u.Phone = cache.Phone
u.Password = cache.Password
u.UserType = cache.UserType
u.Username = cache.Username
u.CreatedAt = cache.CreatedAt
u.UpdatedAt = cache.UpdatedAt
u.DeletedAt = cache.DeletedAt
u.Active = cache.Active
u.LastLoginAt = cache.LastLoginAt
u.LoginCount = cache.LoginCount
u.Permissions = cache.Permissions
// UserPasswordChangedEvent 用户密码修改事件
type UserPasswordChangedEvent struct {
UserID string `json:"user_id"`
Phone string `json:"phone"`
ChangedAt time.Time `json:"changed_at"`
}
// UserActivatedEvent 用户激活事件
type UserActivatedEvent struct {
UserID string `json:"user_id"`
Phone string `json:"phone"`
ActivatedAt time.Time `json:"activated_at"`
}
// UserDeactivatedEvent 用户停用事件
type UserDeactivatedEvent struct {
UserID string `json:"user_id"`
Phone string `json:"phone"`
DeactivatedAt time.Time `json:"deactivated_at"`
}
// UserEnterpriseInfoCreatedEvent 企业信息创建事件
type UserEnterpriseInfoCreatedEvent struct {
UserID string `json:"user_id"`
EnterpriseInfoID string `json:"enterprise_info_id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
CreatedAt time.Time `json:"created_at"`
}
// UserEnterpriseInfoUpdatedEvent 企业信息更新事件
type UserEnterpriseInfoUpdatedEvent struct {
UserID string `json:"user_id"`
EnterpriseInfoID string `json:"enterprise_info_id"`
OldCompanyName string `json:"old_company_name"`
NewCompanyName string `json:"new_company_name"`
OldUnifiedSocialCode string `json:"old_unified_social_code"`
NewUnifiedSocialCode string `json:"new_unified_social_code"`
UpdatedAt time.Time `json:"updated_at"`
}
// UserEnterpriseInfoRemovedEvent 企业信息移除事件
type UserEnterpriseInfoRemovedEvent struct {
UserID string `json:"user_id"`
EnterpriseInfoID string `json:"enterprise_info_id"`
RemovedAt time.Time `json:"removed_at"`
}

View File

@@ -0,0 +1,22 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/user/entities"
)
// ContractInfoRepository 合同信息仓储接口
type ContractInfoRepository interface {
// 基础CRUD操作
Save(ctx context.Context, contract *entities.ContractInfo) error
FindByID(ctx context.Context, contractID string) (*entities.ContractInfo, error)
Delete(ctx context.Context, contractID string) error
// 查询方法
FindByEnterpriseInfoID(ctx context.Context, enterpriseInfoID string) ([]*entities.ContractInfo, error)
FindByUserID(ctx context.Context, userID string) ([]*entities.ContractInfo, error)
FindByContractType(ctx context.Context, enterpriseInfoID string, contractType entities.ContractType) ([]*entities.ContractInfo, error)
ExistsByContractFileID(ctx context.Context, contractFileID string) (bool, error)
ExistsByContractFileIDExcludeID(ctx context.Context, contractFileID, excludeID string) (bool, error)
}

View File

@@ -1,22 +0,0 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/shared/interfaces"
)
// EnterpriseInfoRepository 企业信息仓储接口
type EnterpriseInfoRepository interface {
interfaces.Repository[entities.EnterpriseInfo]
// 基础查询
GetByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfo, error)
GetByUnifiedSocialCode(ctx context.Context, unifiedSocialCode string) (*entities.EnterpriseInfo, error)
// 业务操作
CheckUnifiedSocialCodeExists(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error)
UpdateVerificationStatus(ctx context.Context, userID string, isOCRVerified, isFaceVerified, isCertified bool) error
UpdateOCRData(ctx context.Context, userID string, rawData string, confidence float64) error
CompleteCertification(ctx context.Context, userID string) error
}

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