197 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			197 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | 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 | |||
|  | } |