Files
tyapi-server/internal/domains/api/entities/api_user.go
2025-09-12 01:15:09 +08:00

266 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"` // 支持多个白名单
// 余额预警配置
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"`
}
// 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, defaultAlertEnabled bool, defaultAlertThreshold float64) (*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{},
BalanceAlertEnabled: defaultAlertEnabled,
BalanceAlertThreshold: defaultAlertThreshold,
}, 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
}
// 余额预警相关方法
// 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 == "" {
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
}