This commit is contained in:
2025-12-04 16:17:27 +08:00
parent d9c2d9f103
commit 0f5c4f4303
9 changed files with 227 additions and 55 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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)
} }
} }

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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") {

View 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;

BIN
worker

Binary file not shown.