2025-07-28 01:46:39 +08:00
|
|
|
|
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 {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
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:"最后欠费预警时间"`
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
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 工厂方法
|
2025-09-12 01:15:09 +08:00
|
|
|
|
func NewApiUser(userId string, defaultAlertEnabled bool, defaultAlertThreshold float64) (*ApiUser, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
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{
|
2025-09-12 01:15:09 +08:00
|
|
|
|
ID: uuid.New().String(),
|
|
|
|
|
|
UserId: userId,
|
|
|
|
|
|
AccessId: accessId,
|
|
|
|
|
|
SecretKey: secretKey,
|
|
|
|
|
|
Status: ApiUserStatusNormal,
|
|
|
|
|
|
WhiteList: []string{},
|
|
|
|
|
|
BalanceAlertEnabled: defaultAlertEnabled,
|
|
|
|
|
|
BalanceAlertThreshold: defaultAlertThreshold,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}, 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 余额预警相关方法
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|