v0.1
This commit is contained in:
173
internal/domains/api/dto/api_request_dto.go
Normal file
173
internal/domains/api/dto/api_request_dto.go
Normal 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"`
|
||||
}
|
||||
196
internal/domains/api/entities/api_call.go
Normal file
196
internal/domains/api/entities/api_call.go
Normal 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
|
||||
}
|
||||
193
internal/domains/api/entities/api_user.go
Normal file
193
internal/domains/api/entities/api_user.go
Normal 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
|
||||
}
|
||||
29
internal/domains/api/repositories/api_call_repository.go
Normal file
29
internal/domains/api/repositories/api_call_repository.go
Normal 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)
|
||||
}
|
||||
13
internal/domains/api/repositories/api_user_repository.go
Normal file
13
internal/domains/api/repositories/api_user_repository.go
Normal 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)
|
||||
}
|
||||
69
internal/domains/api/services/api_call_aggregate_service.go
Normal file
69
internal/domains/api/services/api_call_aggregate_service.go
Normal 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)
|
||||
}
|
||||
135
internal/domains/api/services/api_request_service.go
Normal file
135
internal/domains/api/services/api_request_service.go
Normal 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请求, 未找到相应的处理程序")
|
||||
}
|
||||
124
internal/domains/api/services/api_user_aggregate_service.go
Normal file
124
internal/domains/api/services/api_user_aggregate_service.go
Normal 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)
|
||||
}
|
||||
}
|
||||
195
internal/domains/api/services/processors/OPTIONS_USAGE.md
Normal file
195
internal/domains/api/services/processors/OPTIONS_USAGE.md
Normal 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 传递给所有子处理器,无需额外配置。
|
||||
155
internal/domains/api/services/processors/README.md
Normal file
155
internal/domains/api/services/processors/README.md
Normal 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, ¶msDto); 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. **维护性**:清晰的代码结构和职责分离
|
||||
117
internal/domains/api/services/processors/UPDATE_SUMMARY.md
Normal file
117
internal/domains/api/services/processors/UPDATE_SUMMARY.md
Normal 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文档和使用说明
|
||||
@@ -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, ¶msDto); 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")
|
||||
}
|
||||
@@ -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, ¶msDto); 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")
|
||||
}
|
||||
166
internal/domains/api/services/processors/comb/comb_service.go
Normal file
166
internal/domains/api/services/processors/comb/comb_service.go
Normal 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"` // 子接口响应列表
|
||||
}
|
||||
48
internal/domains/api/services/processors/dependencies.go
Normal file
48
internal/domains/api/services/processors/dependencies.go
Normal 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)
|
||||
11
internal/domains/api/services/processors/errors.go
Normal file
11
internal/domains/api/services/processors/errors.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package processors
|
||||
|
||||
import "errors"
|
||||
|
||||
// 处理器相关错误类型
|
||||
var (
|
||||
ErrDatasource = errors.New("数据源异常")
|
||||
ErrSystem = errors.New("系统异常")
|
||||
ErrInvalidParam = errors.New("参数校验不正确")
|
||||
ErrNotFound = errors.New("查询为空")
|
||||
)
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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, ¶msDto); 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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): "用户重新提交企业信息",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 "未知类型"
|
||||
}
|
||||
@@ -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 ""
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
140
internal/domains/finance/entities/alipay_order.go
Normal file
140
internal/domains/finance/entities/alipay_order.go
Normal 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,
|
||||
}
|
||||
}
|
||||
177
internal/domains/finance/entities/recharge_record.go
Normal file
177
internal/domains/finance/entities/recharge_record.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
52
internal/domains/finance/entities/wallet_transaction.go
Normal file
52
internal/domains/finance/entities/wallet_transaction.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
349
internal/domains/finance/services/recharge_record_service.go
Normal file
349
internal/domains/finance/services/recharge_record_service.go
Normal 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)
|
||||
}
|
||||
142
internal/domains/finance/services/wallet_aggregate_service.go
Normal file
142
internal/domains/finance/services/wallet_aggregate_service.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
159
internal/domains/product/entities/product_api_config.go
Normal file
159
internal/domains/product/entities/product_api_config.go
Normal 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), ¶ms)
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
32
internal/domains/product/entities/product_package_item.go
Normal file
32
internal/domains/product/entities/product_package_item.go
Normal 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
|
||||
}
|
||||
53
internal/domains/product/entities/product_parameter.go
Normal file
53
internal/domains/product/entities/product_parameter.go
Normal 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{})
|
||||
}
|
||||
@@ -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调用次数"`
|
||||
|
||||
// 关联关系
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
}
|
||||
|
||||
161
internal/domains/product/services/product_api_config_service.go
Normal file
161
internal/domains/product/services/product_api_config_service.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
317
internal/domains/user/entities/contract_info.go
Normal file
317
internal/domains/user/entities/contract_info.go
Normal 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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user