new
This commit is contained in:
@@ -271,6 +271,12 @@ type IVYZ5E3FReq struct {
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type IVYZ7F3AReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type YYSY4F2EReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -56,7 +52,7 @@ type ApiCall struct {
|
||||
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"`
|
||||
TransactionId string `gorm:"type:varchar(36);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"`
|
||||
@@ -145,40 +141,9 @@ func (a *ApiCall) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 全局计数器,用于确保TransactionID的唯一性
|
||||
var (
|
||||
transactionCounter int64
|
||||
counterMutex sync.Mutex
|
||||
)
|
||||
|
||||
// GenerateTransactionID 生成16位数的交易单号
|
||||
// GenerateTransactionID 生成UUID格式的交易单号
|
||||
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
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
|
||||
@@ -20,12 +20,20 @@ const (
|
||||
|
||||
// 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"` // 支持多个白名单
|
||||
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"` // 支持多个白名单
|
||||
|
||||
// 余额预警配置
|
||||
BalanceAlertEnabled bool `gorm:"default:true" json:"balance_alert_enabled" comment:"是否启用余额预警"`
|
||||
BalanceAlertThreshold float64 `gorm:"default:200.00" json:"balance_alert_threshold" comment:"余额预警阈值"`
|
||||
AlertPhone string `gorm:"type:varchar(20)" json:"alert_phone" comment:"预警手机号"`
|
||||
LastLowBalanceAlert *time.Time `json:"last_low_balance_alert" comment:"最后低余额预警时间"`
|
||||
LastArrearsAlert *time.Time `json:"last_arrears_alert" comment:"最后欠费预警时间"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
@@ -51,7 +59,7 @@ func (u *ApiUser) IsFrozen() bool {
|
||||
}
|
||||
|
||||
// NewApiUser 工厂方法
|
||||
func NewApiUser(userId string) (*ApiUser, error) {
|
||||
func NewApiUser(userId string, defaultAlertEnabled bool, defaultAlertThreshold float64) (*ApiUser, error) {
|
||||
if userId == "" {
|
||||
return nil, errors.New("用户ID不能为空")
|
||||
}
|
||||
@@ -64,12 +72,14 @@ func NewApiUser(userId string) (*ApiUser, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &ApiUser{
|
||||
ID: uuid.New().String(),
|
||||
UserId: userId,
|
||||
AccessId: accessId,
|
||||
SecretKey: secretKey,
|
||||
Status: ApiUserStatusNormal,
|
||||
WhiteList: []string{},
|
||||
ID: uuid.New().String(),
|
||||
UserId: userId,
|
||||
AccessId: accessId,
|
||||
SecretKey: secretKey,
|
||||
Status: ApiUserStatusNormal,
|
||||
WhiteList: []string{},
|
||||
BalanceAlertEnabled: defaultAlertEnabled,
|
||||
BalanceAlertThreshold: defaultAlertThreshold,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -124,6 +134,68 @@ func (u *ApiUser) RemoveFromWhiteList(entry string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 余额预警相关方法
|
||||
|
||||
// UpdateBalanceAlertSettings 更新余额预警设置
|
||||
func (u *ApiUser) UpdateBalanceAlertSettings(enabled bool, threshold float64, phone string) error {
|
||||
if threshold < 0 {
|
||||
return errors.New("预警阈值不能为负数")
|
||||
}
|
||||
if phone != "" && len(phone) != 11 {
|
||||
return errors.New("手机号格式不正确")
|
||||
}
|
||||
|
||||
u.BalanceAlertEnabled = enabled
|
||||
u.BalanceAlertThreshold = threshold
|
||||
u.AlertPhone = phone
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldSendLowBalanceAlert 是否应该发送低余额预警(24小时冷却期)
|
||||
func (u *ApiUser) ShouldSendLowBalanceAlert(balance float64) bool {
|
||||
if !u.BalanceAlertEnabled || u.AlertPhone == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 余额低于阈值
|
||||
if balance < u.BalanceAlertThreshold {
|
||||
// 检查是否已经发送过预警(避免频繁发送)
|
||||
if u.LastLowBalanceAlert != nil {
|
||||
// 如果距离上次预警不足24小时,不发送
|
||||
if time.Since(*u.LastLowBalanceAlert) < 24*time.Hour {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ShouldSendArrearsAlert 是否应该发送欠费预警(不受冷却期限制)
|
||||
func (u *ApiUser) ShouldSendArrearsAlert(balance float64) bool {
|
||||
if !u.BalanceAlertEnabled || u.AlertPhone == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 余额为负数(欠费)- 欠费预警不受冷却期限制
|
||||
if balance < 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarkLowBalanceAlertSent 标记低余额预警已发送
|
||||
func (u *ApiUser) MarkLowBalanceAlertSent() {
|
||||
now := time.Now()
|
||||
u.LastLowBalanceAlert = &now
|
||||
}
|
||||
|
||||
// MarkArrearsAlertSent 标记欠费预警已发送
|
||||
func (u *ApiUser) MarkArrearsAlertSent() {
|
||||
now := time.Now()
|
||||
u.LastArrearsAlert = &now
|
||||
}
|
||||
|
||||
// Validate 校验ApiUser聚合根的业务规则
|
||||
func (u *ApiUser) Validate() error {
|
||||
if u.UserId == "" {
|
||||
|
||||
@@ -2,6 +2,7 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
@@ -27,6 +28,20 @@ type ApiCallRepository interface {
|
||||
// 新增:根据TransactionID查询
|
||||
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
|
||||
|
||||
// 统计相关方法
|
||||
CountByUserIdAndDateRange(ctx context.Context, userId string, startDate, endDate time.Time) (int64, error)
|
||||
GetDailyStatsByUserId(ctx context.Context, userId string, startDate, endDate time.Time) ([]map[string]interface{}, error)
|
||||
GetMonthlyStatsByUserId(ctx context.Context, userId string, startDate, endDate time.Time) ([]map[string]interface{}, error)
|
||||
|
||||
// 管理端:根据条件筛选所有API调用记录(包含产品名称)
|
||||
ListWithFiltersAndProductName(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (map[string]string, []*entities.ApiCall, int64, error)
|
||||
|
||||
// 系统级别统计方法
|
||||
GetSystemTotalCalls(ctx context.Context) (int64, error)
|
||||
GetSystemCallsByDateRange(ctx context.Context, startDate, endDate time.Time) (int64, error)
|
||||
GetSystemDailyStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error)
|
||||
GetSystemMonthlyStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error)
|
||||
|
||||
// API受欢迎程度排行榜
|
||||
GetApiPopularityRanking(ctx context.Context, period string, limit int) ([]map[string]interface{}, error)
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ2A8B": ivyz.ProcessIVYZ2A8BRequest,
|
||||
"IVYZ7C9D": ivyz.ProcessIVYZ7C9DRequest,
|
||||
"IVYZ5E3F": ivyz.ProcessIVYZ5E3FRequest,
|
||||
"IVYZ7F3A": ivyz.ProcessIVYZ7F3ARequest,
|
||||
|
||||
// COMB系列处理器
|
||||
"COMB298Y": comb.ProcessCOMB298YRequest,
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
repo "tyapi-server/internal/domains/api/repositories"
|
||||
)
|
||||
@@ -20,14 +21,15 @@ type ApiUserAggregateService interface {
|
||||
|
||||
type ApiUserAggregateServiceImpl struct {
|
||||
repo repo.ApiUserRepository
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewApiUserAggregateService(repo repo.ApiUserRepository) ApiUserAggregateService {
|
||||
return &ApiUserAggregateServiceImpl{repo: repo}
|
||||
func NewApiUserAggregateService(repo repo.ApiUserRepository, cfg *config.Config) ApiUserAggregateService {
|
||||
return &ApiUserAggregateServiceImpl{repo: repo, cfg: cfg}
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) CreateApiUser(ctx context.Context, apiUserId string) error {
|
||||
apiUser, err := entities.NewApiUser(apiUserId)
|
||||
apiUser, err := entities.NewApiUser(apiUserId, s.cfg.Wallet.BalanceAlert.DefaultEnabled, s.cfg.Wallet.BalanceAlert.DefaultThreshold)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ7F3ARequest IVYZ7F3A API处理方法 - 身份二要素认证(ZCI004)
|
||||
func ProcessIVYZ7F3ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ7F3AReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI004", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
Reference in New Issue
Block a user