This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"tyapi-server/internal/application/api/commands"
|
"tyapi-server/internal/application/api/commands"
|
||||||
"tyapi-server/internal/application/api/dto"
|
"tyapi-server/internal/application/api/dto"
|
||||||
@@ -37,8 +39,8 @@ type ApiApplicationService interface {
|
|||||||
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
||||||
|
|
||||||
// 用户白名单管理
|
// 用户白名单管理
|
||||||
GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error)
|
GetUserWhiteList(ctx context.Context, userID string, remarkKeyword string) (*dto.WhiteListListResponse, error)
|
||||||
AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
AddWhiteListIP(ctx context.Context, userID string, ipAddress string, remark string) error
|
||||||
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
||||||
|
|
||||||
// 获取用户API调用记录
|
// 获取用户API调用记录
|
||||||
@@ -466,7 +468,7 @@ func (s *ApiApplicationServiceImpl) GetUserApiKeys(ctx context.Context, userID s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUserWhiteList 获取用户白名单列表
|
// GetUserWhiteList 获取用户白名单列表
|
||||||
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error) {
|
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string, remarkKeyword string) (*dto.WhiteListListResponse, error) {
|
||||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -474,28 +476,49 @@ func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将白名单字符串数组转换为响应格式
|
// 将白名单转换为响应格式
|
||||||
var items []dto.WhiteListResponse
|
var items []dto.WhiteListResponse
|
||||||
for _, ip := range apiUser.WhiteList {
|
for _, item := range apiUser.WhiteList {
|
||||||
|
// 如果提供了备注关键词,进行模糊匹配过滤
|
||||||
|
if remarkKeyword != "" {
|
||||||
|
if !contains(item.Remark, remarkKeyword) {
|
||||||
|
continue // 不匹配则跳过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items = append(items, dto.WhiteListResponse{
|
items = append(items, dto.WhiteListResponse{
|
||||||
ID: apiUser.ID, // 使用API用户ID作为标识
|
ID: apiUser.ID, // 使用API用户ID作为标识
|
||||||
UserID: apiUser.UserId,
|
UserID: apiUser.UserId,
|
||||||
IPAddress: ip,
|
IPAddress: item.IPAddress,
|
||||||
CreatedAt: apiUser.CreatedAt, // 使用API用户创建时间
|
Remark: item.Remark, // 备注
|
||||||
|
CreatedAt: item.AddedAt, // 使用每个IP的实际添加时间
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按添加时间降序排序(新的排在前面)
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
return items[i].CreatedAt.After(items[j].CreatedAt)
|
||||||
|
})
|
||||||
|
|
||||||
return &dto.WhiteListListResponse{
|
return &dto.WhiteListListResponse{
|
||||||
Items: items,
|
Items: items,
|
||||||
Total: len(items),
|
Total: len(items),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// contains 检查字符串是否包含子字符串(不区分大小写)
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
if substr == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||||
|
}
|
||||||
|
|
||||||
// AddWhiteListIP 添加白名单IP
|
// AddWhiteListIP 添加白名单IP
|
||||||
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
|
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string, remark string) error {
|
||||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -503,11 +526,11 @@ func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID s
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用实体的领域方法添加IP到白名单
|
// 使用实体的领域方法添加IP到白名单(会自动记录添加时间和备注)
|
||||||
err = apiUser.AddToWhiteList(ipAddress)
|
err = apiUser.AddToWhiteList(ipAddress, remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -530,7 +553,7 @@ func (s *ApiApplicationServiceImpl) DeleteWhiteListIP(ctx context.Context, userI
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用实体的领域方法删除IP
|
// 使用实体的领域方法删除IP
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ type WhiteListResponse struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
IPAddress string `json:"ip_address"`
|
IPAddress string `json:"ip_address"`
|
||||||
|
Remark string `json:"remark"` // 备注
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WhiteListRequest struct {
|
type WhiteListRequest struct {
|
||||||
IPAddress string `json:"ip_address" binding:"required,ip"`
|
IPAddress string `json:"ip_address" binding:"required,ip"`
|
||||||
|
Remark string `json:"remark"` // 备注(可选)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WhiteListListResponse struct {
|
type WhiteListListResponse struct {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package entities
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"database/sql/driver"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -18,14 +20,86 @@ const (
|
|||||||
ApiUserStatusFrozen = "frozen"
|
ApiUserStatusFrozen = "frozen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WhiteListItem 白名单项,包含IP地址、添加时间和备注
|
||||||
|
type WhiteListItem struct {
|
||||||
|
IPAddress string `json:"ip_address"` // IP地址
|
||||||
|
AddedAt time.Time `json:"added_at"` // 添加时间
|
||||||
|
Remark string `json:"remark"` // 备注
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhiteList 白名单类型,支持向后兼容(旧的字符串数组格式)
|
||||||
|
type WhiteList []WhiteListItem
|
||||||
|
|
||||||
|
// Value 实现 driver.Valuer 接口,用于数据库写入
|
||||||
|
func (w WhiteList) Value() (driver.Value, error) {
|
||||||
|
if w == nil {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(w)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan 实现 sql.Scanner 接口,用于数据库读取(支持向后兼容)
|
||||||
|
func (w *WhiteList) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*w = WhiteList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes []byte
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
bytes = v
|
||||||
|
case string:
|
||||||
|
bytes = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.New("无法扫描 WhiteList 类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bytes) == 0 || string(bytes) == "[]" || string(bytes) == "null" {
|
||||||
|
*w = WhiteList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先尝试解析为新格式(结构体数组)
|
||||||
|
var items []WhiteListItem
|
||||||
|
if err := json.Unmarshal(bytes, &items); err == nil {
|
||||||
|
// 成功解析为新格式
|
||||||
|
*w = WhiteList(items)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果失败,尝试解析为旧格式(字符串数组)
|
||||||
|
var oldFormat []string
|
||||||
|
if err := json.Unmarshal(bytes, &oldFormat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将旧格式转换为新格式
|
||||||
|
now := time.Now()
|
||||||
|
items = make([]WhiteListItem, 0, len(oldFormat))
|
||||||
|
for _, ip := range oldFormat {
|
||||||
|
items = append(items, WhiteListItem{
|
||||||
|
IPAddress: ip,
|
||||||
|
AddedAt: now, // 使用当前时间作为添加时间(因为旧数据没有时间信息)
|
||||||
|
Remark: "", // 旧数据没有备注信息
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*w = WhiteList(items)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ApiUser API用户(聚合根)
|
// ApiUser API用户(聚合根)
|
||||||
type ApiUser struct {
|
type ApiUser struct {
|
||||||
ID string `gorm:"primaryKey;type:varchar(64)" json:"id"`
|
ID string `gorm:"primaryKey;type:varchar(64)" json:"id"`
|
||||||
UserId string `gorm:"type:varchar(36);not null;uniqueIndex" json:"user_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"`
|
AccessId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"access_id"`
|
||||||
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
||||||
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
||||||
WhiteList []string `gorm:"type:json;serializer:json;default:'[]'" json:"white_list"` // 支持多个白名单
|
WhiteList WhiteList `gorm:"type:json;default:'[]'" json:"white_list"` // 支持多个白名单,包含IP和添加时间,支持向后兼容
|
||||||
|
|
||||||
// 余额预警配置
|
// 余额预警配置
|
||||||
BalanceAlertEnabled bool `gorm:"default:true" json:"balance_alert_enabled" comment:"是否启用余额预警"`
|
BalanceAlertEnabled bool `gorm:"default:true" json:"balance_alert_enabled" comment:"是否启用余额预警"`
|
||||||
@@ -41,7 +115,7 @@ type ApiUser struct {
|
|||||||
// IsWhiteListed 校验IP/域名是否在白名单
|
// IsWhiteListed 校验IP/域名是否在白名单
|
||||||
func (u *ApiUser) IsWhiteListed(target string) bool {
|
func (u *ApiUser) IsWhiteListed(target string) bool {
|
||||||
for _, w := range u.WhiteList {
|
for _, w := range u.WhiteList {
|
||||||
if w == target {
|
if w.IPAddress == target {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +151,7 @@ func NewApiUser(userId string, defaultAlertEnabled bool, defaultAlertThreshold f
|
|||||||
AccessId: accessId,
|
AccessId: accessId,
|
||||||
SecretKey: secretKey,
|
SecretKey: secretKey,
|
||||||
Status: ApiUserStatusNormal,
|
Status: ApiUserStatusNormal,
|
||||||
WhiteList: []string{},
|
WhiteList: WhiteList{},
|
||||||
BalanceAlertEnabled: defaultAlertEnabled,
|
BalanceAlertEnabled: defaultAlertEnabled,
|
||||||
BalanceAlertThreshold: defaultAlertThreshold,
|
BalanceAlertThreshold: defaultAlertThreshold,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -90,12 +164,12 @@ func (u *ApiUser) Freeze() {
|
|||||||
func (u *ApiUser) Unfreeze() {
|
func (u *ApiUser) Unfreeze() {
|
||||||
u.Status = ApiUserStatusNormal
|
u.Status = ApiUserStatusNormal
|
||||||
}
|
}
|
||||||
func (u *ApiUser) UpdateWhiteList(list []string) {
|
func (u *ApiUser) UpdateWhiteList(list []WhiteListItem) {
|
||||||
u.WhiteList = list
|
u.WhiteList = WhiteList(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToWhiteList 新增白名单项(防御性校验)
|
// AddToWhiteList 新增白名单项(防御性校验)
|
||||||
func (u *ApiUser) AddToWhiteList(entry string) error {
|
func (u *ApiUser) AddToWhiteList(entry string, remark string) error {
|
||||||
if len(u.WhiteList) >= 10 {
|
if len(u.WhiteList) >= 10 {
|
||||||
return errors.New("白名单最多只能有10个")
|
return errors.New("白名单最多只能有10个")
|
||||||
}
|
}
|
||||||
@@ -103,27 +177,31 @@ func (u *ApiUser) AddToWhiteList(entry string) error {
|
|||||||
return errors.New("非法IP")
|
return errors.New("非法IP")
|
||||||
}
|
}
|
||||||
for _, w := range u.WhiteList {
|
for _, w := range u.WhiteList {
|
||||||
if w == entry {
|
if w.IPAddress == entry {
|
||||||
return errors.New("白名单已存在")
|
return errors.New("白名单已存在")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u.WhiteList = append(u.WhiteList, entry)
|
u.WhiteList = append(u.WhiteList, WhiteListItem{
|
||||||
|
IPAddress: entry,
|
||||||
|
AddedAt: time.Now(),
|
||||||
|
Remark: remark,
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeUpdate GORM钩子:更新前确保WhiteList不为nil
|
// BeforeUpdate GORM钩子:更新前确保WhiteList不为nil
|
||||||
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
|
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
|
||||||
if u.WhiteList == nil {
|
if u.WhiteList == nil {
|
||||||
u.WhiteList = []string{}
|
u.WhiteList = WhiteList{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFromWhiteList 删除白名单项
|
// RemoveFromWhiteList 删除白名单项
|
||||||
func (u *ApiUser) RemoveFromWhiteList(entry string) error {
|
func (u *ApiUser) RemoveFromWhiteList(entry string) error {
|
||||||
newList := make([]string, 0, len(u.WhiteList))
|
newList := make([]WhiteListItem, 0, len(u.WhiteList))
|
||||||
for _, w := range u.WhiteList {
|
for _, w := range u.WhiteList {
|
||||||
if w != entry {
|
if w.IPAddress != entry {
|
||||||
newList = append(newList, w)
|
newList = append(newList, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,9 +294,9 @@ func (u *ApiUser) Validate() error {
|
|||||||
if len(u.WhiteList) > 10 {
|
if len(u.WhiteList) > 10 {
|
||||||
return errors.New("白名单最多只能有10个")
|
return errors.New("白名单最多只能有10个")
|
||||||
}
|
}
|
||||||
for _, ip := range u.WhiteList {
|
for _, item := range u.WhiteList {
|
||||||
if net.ParseIP(ip) == nil {
|
if net.ParseIP(item.IPAddress) == nil {
|
||||||
return errors.New("白名单项必须为合法IP地址: " + ip)
|
return errors.New("白名单项必须为合法IP地址: " + item.IPAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -259,7 +337,26 @@ func (c *ApiUser) BeforeCreate(tx *gorm.DB) error {
|
|||||||
c.ID = uuid.New().String()
|
c.ID = uuid.New().String()
|
||||||
}
|
}
|
||||||
if c.WhiteList == nil {
|
if c.WhiteList == nil {
|
||||||
c.WhiteList = []string{}
|
c.WhiteList = WhiteList{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AfterFind GORM钩子:查询后处理数据,确保AddedAt不为零值
|
||||||
|
func (u *ApiUser) AfterFind(tx *gorm.DB) error {
|
||||||
|
// 如果 WhiteList 为空,初始化为空数组
|
||||||
|
if u.WhiteList == nil {
|
||||||
|
u.WhiteList = WhiteList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保所有项的AddedAt不为零值(处理可能从旧数据迁移的情况)
|
||||||
|
now := time.Now()
|
||||||
|
for i := range u.WhiteList {
|
||||||
|
if u.WhiteList[i].AddedAt.IsZero() {
|
||||||
|
u.WhiteList[i].AddedAt = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
"tyapi-server/internal/config"
|
"tyapi-server/internal/config"
|
||||||
"tyapi-server/internal/domains/api/entities"
|
"tyapi-server/internal/domains/api/entities"
|
||||||
repo "tyapi-server/internal/domains/api/repositories"
|
repo "tyapi-server/internal/domains/api/repositories"
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
type ApiUserAggregateService interface {
|
type ApiUserAggregateService interface {
|
||||||
CreateApiUser(ctx context.Context, apiUserId string) error
|
CreateApiUser(ctx context.Context, apiUserId string) error
|
||||||
UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error
|
UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error
|
||||||
AddToWhiteList(ctx context.Context, apiUserId string, entry string) error
|
AddToWhiteList(ctx context.Context, apiUserId string, entry string, remark string) error
|
||||||
RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error
|
RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error
|
||||||
FreezeApiUser(ctx context.Context, apiUserId string) error
|
FreezeApiUser(ctx context.Context, apiUserId string) error
|
||||||
UnfreezeApiUser(ctx context.Context, apiUserId string) error
|
UnfreezeApiUser(ctx context.Context, apiUserId string) error
|
||||||
@@ -44,16 +45,25 @@ func (s *ApiUserAggregateServiceImpl) UpdateWhiteList(ctx context.Context, apiUs
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
apiUser.UpdateWhiteList(whiteList)
|
// 将字符串数组转换为WhiteListItem数组
|
||||||
|
items := make([]entities.WhiteListItem, 0, len(whiteList))
|
||||||
|
now := time.Now()
|
||||||
|
for _, ip := range whiteList {
|
||||||
|
items = append(items, entities.WhiteListItem{
|
||||||
|
IPAddress: ip,
|
||||||
|
AddedAt: now, // 批量更新时使用当前时间
|
||||||
|
})
|
||||||
|
}
|
||||||
|
apiUser.UpdateWhiteList(items) // UpdateWhiteList 会转换为 WhiteList 类型
|
||||||
return s.repo.Update(ctx, apiUser)
|
return s.repo.Update(ctx, apiUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiUserAggregateServiceImpl) AddToWhiteList(ctx context.Context, apiUserId string, entry string) error {
|
func (s *ApiUserAggregateServiceImpl) AddToWhiteList(ctx context.Context, apiUserId string, entry string, remark string) error {
|
||||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = apiUser.AddToWhiteList(entry)
|
err = apiUser.AddToWhiteList(entry, remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -90,7 +100,6 @@ func (s *ApiUserAggregateServiceImpl) UnfreezeApiUser(ctx context.Context, apiUs
|
|||||||
return s.repo.Update(ctx, apiUser)
|
return s.repo.Update(ctx, apiUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
|
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
|
||||||
return s.repo.FindByAccessId(ctx, accessId)
|
return s.repo.FindByAccessId(ctx, accessId)
|
||||||
}
|
}
|
||||||
@@ -100,12 +109,12 @@ func (s *ApiUserAggregateServiceImpl) LoadApiUserByUserId(ctx context.Context, a
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiUser, nil
|
return apiUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,10 +126,10 @@ func (s *ApiUserAggregateServiceImpl) SaveApiUser(ctx context.Context, apiUser *
|
|||||||
if exists != nil {
|
if exists != nil {
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = []entities.WhiteListItem{}
|
||||||
}
|
}
|
||||||
return s.repo.Update(ctx, apiUser)
|
return s.repo.Update(ctx, apiUser)
|
||||||
} else {
|
} else {
|
||||||
return s.repo.Create(ctx, apiUser)
|
return s.repo.Create(ctx, apiUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,10 @@ func (h *ApiHandler) GetUserWhiteList(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.appService.GetUserWhiteList(c.Request.Context(), userID)
|
// 获取查询参数
|
||||||
|
remarkKeyword := c.Query("remark") // 备注模糊查询关键词
|
||||||
|
|
||||||
|
result, err := h.appService.GetUserWhiteList(c.Request.Context(), userID, remarkKeyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("获取用户白名单失败", zap.Error(err))
|
h.logger.Error("获取用户白名单失败", zap.Error(err))
|
||||||
h.responseBuilder.BadRequest(c, err.Error())
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
@@ -134,7 +137,7 @@ func (h *ApiHandler) AddWhiteListIP(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.appService.AddWhiteListIP(c.Request.Context(), userID, req.IPAddress)
|
err := h.appService.AddWhiteListIP(c.Request.Context(), userID, req.IPAddress, req.Remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("添加白名单IP失败", zap.Error(err))
|
h.logger.Error("添加白名单IP失败", zap.Error(err))
|
||||||
h.responseBuilder.BadRequest(c, err.Error())
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
|||||||
@@ -126,11 +126,11 @@ func (fm *FontManager) tryAddFont(pdf *gofpdf.Fpdf, fontPath, fontName string) b
|
|||||||
absFontPath = newAbsPath
|
absFontPath = newAbsPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用filepath.ToSlash统一路径分隔符(Linux下使用/)
|
// 使用filepath.ToSlash统一路径分隔符(Linux下使用/)
|
||||||
// 注意:ToSlash不会改变路径的绝对/相对性质,只统一分隔符
|
// 注意:ToSlash不会改变路径的绝对/相对性质,只统一分隔符
|
||||||
normalizedPath := filepath.ToSlash(absFontPath)
|
normalizedPath := filepath.ToSlash(absFontPath)
|
||||||
|
|
||||||
// 在Linux下,绝对路径必须以/开头
|
// 在Linux下,绝对路径必须以/开头
|
||||||
// 如果normalizedPath不是以/开头,说明转换有问题
|
// 如果normalizedPath不是以/开头,说明转换有问题
|
||||||
if len(normalizedPath) == 0 || normalizedPath[0] != '/' {
|
if len(normalizedPath) == 0 || normalizedPath[0] != '/' {
|
||||||
@@ -166,14 +166,14 @@ func (fm *FontManager) tryAddFont(pdf *gofpdf.Fpdf, fontPath, fontName string) b
|
|||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录传递给gofpdf的实际路径
|
// 记录传递给gofpdf的实际路径
|
||||||
fm.logger.Info("添加字体到gofpdf",
|
fm.logger.Info("添加字体到gofpdf",
|
||||||
zap.String("font_path", normalizedPath),
|
zap.String("font_path", normalizedPath),
|
||||||
zap.String("font_name", fontName),
|
zap.String("font_name", fontName),
|
||||||
zap.Bool("is_absolute", len(normalizedPath) > 0 && normalizedPath[0] == '/'),
|
zap.Bool("is_absolute", len(normalizedPath) > 0 && normalizedPath[0] == '/'),
|
||||||
)
|
)
|
||||||
|
|
||||||
pdf.AddUTF8Font(fontName, "", normalizedPath) // 常规样式
|
pdf.AddUTF8Font(fontName, "", normalizedPath) // 常规样式
|
||||||
pdf.AddUTF8Font(fontName, "B", normalizedPath) // 粗体样式
|
pdf.AddUTF8Font(fontName, "B", normalizedPath) // 粗体样式
|
||||||
|
|
||||||
@@ -223,7 +223,6 @@ func (fm *FontManager) getWatermarkFontPaths() []string {
|
|||||||
return fm.buildFontPaths(fontNames)
|
return fm.buildFontPaths(fontNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// buildFontPaths 构建字体文件路径列表(仅从resources/pdf/fonts加载)
|
// buildFontPaths 构建字体文件路径列表(仅从resources/pdf/fonts加载)
|
||||||
// 返回所有存在的字体文件的绝对路径
|
// 返回所有存在的字体文件的绝对路径
|
||||||
func (fm *FontManager) buildFontPaths(fontNames []string) []string {
|
func (fm *FontManager) buildFontPaths(fontNames []string) []string {
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent
|
|||||||
// 这样gofpdf在Output时使用相对路径 app/resources/pdf/fonts 就能正确解析
|
// 这样gofpdf在Output时使用相对路径 app/resources/pdf/fonts 就能正确解析
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
// 在Output前验证字体文件路径(此时工作目录应该是根目录/)
|
// 在Output前验证字体文件路径(此时工作目录应该是根目录/)
|
||||||
if workDir, err := os.Getwd(); err == nil {
|
if workDir, err := os.Getwd(); err == nil {
|
||||||
// 验证绝对路径
|
// 验证绝对路径
|
||||||
@@ -188,7 +188,7 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent
|
|||||||
zap.String("work_dir", workDir),
|
zap.String("work_dir", workDir),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证相对路径(gofpdf可能使用的路径)
|
// 验证相对路径(gofpdf可能使用的路径)
|
||||||
fontRelPath := "app/resources/pdf/fonts/simhei.ttf"
|
fontRelPath := "app/resources/pdf/fonts/simhei.ttf"
|
||||||
if relAbsPath, err := filepath.Abs(fontRelPath); err == nil {
|
if relAbsPath, err := filepath.Abs(fontRelPath); err == nil {
|
||||||
@@ -207,14 +207,14 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.logger.Debug("准备生成PDF",
|
g.logger.Debug("准备生成PDF",
|
||||||
zap.String("work_dir", workDir),
|
zap.String("work_dir", workDir),
|
||||||
zap.String("resources_pdf_dir", resourcesDir),
|
zap.String("resources_pdf_dir", resourcesDir),
|
||||||
zap.Bool("work_dir_changed", workDirChanged),
|
zap.Bool("work_dir_changed", workDirChanged),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdf.Output(&buf)
|
err = pdf.Output(&buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 记录详细的错误信息
|
// 记录详细的错误信息
|
||||||
@@ -222,7 +222,7 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent
|
|||||||
if wd, e := os.Getwd(); e == nil {
|
if wd, e := os.Getwd(); e == nil {
|
||||||
currentWorkDir = wd
|
currentWorkDir = wd
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试分析错误:如果是路径问题,记录更多信息
|
// 尝试分析错误:如果是路径问题,记录更多信息
|
||||||
errStr := err.Error()
|
errStr := err.Error()
|
||||||
if strings.Contains(errStr, "stat ") && strings.Contains(errStr, ": no such file") {
|
if strings.Contains(errStr, "stat ") && strings.Contains(errStr, ": no such file") {
|
||||||
|
|||||||
39
scripts/migrate_whitelist.sql
Normal file
39
scripts/migrate_whitelist.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
-- 白名单数据结构迁移脚本
|
||||||
|
-- 将旧的字符串数组格式转换为新的结构体数组格式(包含IP和添加时间)
|
||||||
|
--
|
||||||
|
-- 执行前请备份数据库!
|
||||||
|
--
|
||||||
|
-- 使用方法:
|
||||||
|
-- psql -U your_user -d your_database -f migrate_whitelist.sql
|
||||||
|
|
||||||
|
-- 开始事务
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 更新 api_users 表中的 white_list 字段
|
||||||
|
-- 将旧的字符串数组格式: ["ip1", "ip2"]
|
||||||
|
-- 转换为新格式: [{"ip_address": "ip1", "added_at": "2025-12-04T15:20:19Z"}, ...]
|
||||||
|
|
||||||
|
UPDATE api_users
|
||||||
|
SET white_list = (
|
||||||
|
SELECT json_agg(
|
||||||
|
json_build_object(
|
||||||
|
'ip_address', ip_value,
|
||||||
|
'added_at', COALESCE(
|
||||||
|
(SELECT updated_at FROM api_users WHERE id = api_users.id),
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM json_array_elements_text(white_list::json) AS ip_value
|
||||||
|
)
|
||||||
|
WHERE white_list IS NOT NULL
|
||||||
|
AND white_list != '[]'::json
|
||||||
|
AND white_list::text NOT LIKE '[{%' -- 排除已经是新格式的数据
|
||||||
|
AND json_array_length(white_list::json) > 0;
|
||||||
|
|
||||||
|
-- 提交事务
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- 验证迁移结果(可选)
|
||||||
|
-- SELECT id, white_list FROM api_users WHERE white_list IS NOT NULL LIMIT 5;
|
||||||
|
|
||||||
Reference in New Issue
Block a user