new
This commit is contained in:
133
internal/shared/export/export.go
Normal file
133
internal/shared/export/export.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ExportConfig 定义了导出所需的配置
|
||||
type ExportConfig struct {
|
||||
SheetName string // 工作表名称
|
||||
Headers []string // 表头
|
||||
Data [][]interface{} // 导出数据
|
||||
ColumnWidths []float64 // 列宽
|
||||
}
|
||||
|
||||
// ExportManager 负责管理不同格式的导出
|
||||
type ExportManager struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewExportManager 创建一个新的ExportManager
|
||||
func NewExportManager(logger *zap.Logger) *ExportManager {
|
||||
return &ExportManager{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Export 根据配置和格式生成导出文件
|
||||
func (m *ExportManager) Export(ctx context.Context, config *ExportConfig, format string) ([]byte, error) {
|
||||
switch format {
|
||||
case "excel":
|
||||
return m.generateExcel(ctx, config)
|
||||
case "csv":
|
||||
return m.generateCSV(ctx, config)
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的导出格式: %s", format)
|
||||
}
|
||||
}
|
||||
|
||||
// generateExcel 生成Excel导出文件
|
||||
func (m *ExportManager) generateExcel(ctx context.Context, config *ExportConfig) ([]byte, error) {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
m.logger.Error("关闭Excel文件失败", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
sheetName := config.SheetName
|
||||
index, err := f.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.SetActiveSheet(index)
|
||||
|
||||
// 设置表头
|
||||
for i, header := range config.Headers {
|
||||
cell, err := excelize.CoordinatesToCellName(i+1, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成表头单元格坐标失败: %v", err)
|
||||
}
|
||||
f.SetCellValue(sheetName, cell, header)
|
||||
}
|
||||
|
||||
// 设置表头样式
|
||||
headerStyle, err := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true},
|
||||
Fill: excelize.Fill{Type: "pattern", Color: []string{"#E6F3FF"}, Pattern: 1},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 计算表头范围
|
||||
lastCol, err := excelize.CoordinatesToCellName(len(config.Headers), 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成表头范围失败: %v", err)
|
||||
}
|
||||
headerRange := fmt.Sprintf("A1:%s", lastCol)
|
||||
f.SetCellStyle(sheetName, headerRange, headerRange, headerStyle)
|
||||
|
||||
// 批量写入数据
|
||||
for i, rowData := range config.Data {
|
||||
row := i + 2 // 从第2行开始写入数据
|
||||
for j, value := range rowData {
|
||||
cell, err := excelize.CoordinatesToCellName(j+1, row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成数据单元格坐标失败: %v", err)
|
||||
}
|
||||
f.SetCellValue(sheetName, cell, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置列宽
|
||||
for i, width := range config.ColumnWidths {
|
||||
col, err := excelize.ColumnNumberToName(i + 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成列名失败: %v", err)
|
||||
}
|
||||
f.SetColWidth(sheetName, col, col, width)
|
||||
}
|
||||
|
||||
buf, err := f.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.logger.Info("Excel文件生成完成", zap.Int("file_size", len(buf.Bytes())))
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// generateCSV 生成CSV导出文件
|
||||
func (m *ExportManager) generateCSV(ctx context.Context, config *ExportConfig) ([]byte, error) {
|
||||
var csvData strings.Builder
|
||||
|
||||
// 写入CSV头部
|
||||
csvData.WriteString(strings.Join(config.Headers, ",") + "\n")
|
||||
|
||||
// 写入数据行
|
||||
for _, rowData := range config.Data {
|
||||
rowStrings := make([]string, len(rowData))
|
||||
for i, value := range rowData {
|
||||
rowStrings[i] = fmt.Sprintf("%v", value) // 使用%v通用格式化
|
||||
}
|
||||
csvData.WriteString(strings.Join(rowStrings, ",") + "\n")
|
||||
}
|
||||
|
||||
return []byte(csvData.String()), nil
|
||||
}
|
||||
@@ -34,6 +34,10 @@ type DailyRateLimitConfig struct {
|
||||
BlockedCountries []string `mapstructure:"blocked_countries"` // 被阻止的国家/地区
|
||||
EnableProxyCheck bool `mapstructure:"enable_proxy_check"` // 是否检查代理
|
||||
MaxConcurrent int `mapstructure:"max_concurrent"` // 最大并发请求数
|
||||
// 路径排除配置
|
||||
ExcludePaths []string `mapstructure:"exclude_paths"` // 排除频率限制的路径
|
||||
// 域名排除配置
|
||||
ExcludeDomains []string `mapstructure:"exclude_domains"` // 排除频率限制的域名
|
||||
}
|
||||
|
||||
// DailyRateLimitMiddleware 每日请求限制中间件
|
||||
@@ -94,6 +98,19 @@ func (m *DailyRateLimitMiddleware) Handle() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 检查是否在排除路径中
|
||||
if m.isExcludedPath(c.Request.URL.Path) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否在排除域名中
|
||||
host := c.Request.Host
|
||||
if m.isExcludedDomain(host) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取客户端标识
|
||||
clientIP := m.getClientIP(c)
|
||||
|
||||
@@ -177,6 +194,64 @@ func (m *DailyRateLimitMiddleware) Handle() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// isExcludedDomain 检查域名是否在排除列表中
|
||||
func (m *DailyRateLimitMiddleware) isExcludedDomain(host string) bool {
|
||||
for _, excludeDomain := range m.limitConfig.ExcludeDomains {
|
||||
// 支持通配符匹配
|
||||
if strings.HasPrefix(excludeDomain, "*") {
|
||||
// 后缀匹配,如 "*.api.example.com" 匹配 "api.example.com"
|
||||
if strings.HasSuffix(host, excludeDomain[1:]) {
|
||||
return true
|
||||
}
|
||||
} else if strings.HasSuffix(excludeDomain, "*") {
|
||||
// 前缀匹配,如 "api.*" 匹配 "api.example.com"
|
||||
if strings.HasPrefix(host, excludeDomain[:len(excludeDomain)-1]) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// 精确匹配
|
||||
if host == excludeDomain {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isExcludedPath 检查路径是否在排除列表中
|
||||
func (m *DailyRateLimitMiddleware) isExcludedPath(path string) bool {
|
||||
for _, excludePath := range m.limitConfig.ExcludePaths {
|
||||
// 支持多种匹配模式
|
||||
if strings.HasPrefix(excludePath, "*") {
|
||||
// 前缀匹配,如 "*api_name" 匹配 "/api/v1/any_api_name"
|
||||
if strings.Contains(path, excludePath[1:]) {
|
||||
return true
|
||||
}
|
||||
} else if strings.HasSuffix(excludePath, "*") {
|
||||
// 后缀匹配,如 "/api/v1/*" 匹配 "/api/v1/any_api_name"
|
||||
if strings.HasPrefix(path, excludePath[:len(excludePath)-1]) {
|
||||
return true
|
||||
}
|
||||
} else if strings.Contains(excludePath, "*") {
|
||||
// 中间通配符匹配,如 "/api/v1/*api_name" 匹配 "/api/v1/any_api_name"
|
||||
parts := strings.Split(excludePath, "*")
|
||||
if len(parts) == 2 {
|
||||
prefix := parts[0]
|
||||
suffix := parts[1]
|
||||
if strings.HasPrefix(path, prefix) && strings.HasSuffix(path, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 精确匹配
|
||||
if path == excludePath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsGlobal 是否为全局中间件
|
||||
func (m *DailyRateLimitMiddleware) IsGlobal() bool {
|
||||
return false // 不是全局中间件,需要手动应用到特定路由
|
||||
|
||||
117
internal/shared/services/export_service.go
Normal file
117
internal/shared/services/export_service.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ExportConfig 定义了导出所需的配置
|
||||
type ExportConfig struct {
|
||||
SheetName string // 工作表名称
|
||||
Headers []string // 表头
|
||||
Data [][]interface{} // 导出数据
|
||||
ColumnWidths []float64 // 列宽
|
||||
}
|
||||
|
||||
// ExportManager 负责管理不同格式的导出
|
||||
type ExportManager struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewExportManager 创建一个新的ExportManager
|
||||
func NewExportManager(logger *zap.Logger) *ExportManager {
|
||||
return &ExportManager{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Export 根据配置和格式生成导出文件
|
||||
func (m *ExportManager) Export(ctx context.Context, config *ExportConfig, format string) ([]byte, error) {
|
||||
switch format {
|
||||
case "excel":
|
||||
return m.generateExcel(ctx, config)
|
||||
case "csv":
|
||||
return m.generateCSV(ctx, config)
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的导出格式: %s", format)
|
||||
}
|
||||
}
|
||||
|
||||
// generateExcel 生成Excel导出文件
|
||||
func (m *ExportManager) generateExcel(ctx context.Context, config *ExportConfig) ([]byte, error) {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
m.logger.Error("关闭Excel文件失败", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
sheetName := config.SheetName
|
||||
index, err := f.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.SetActiveSheet(index)
|
||||
|
||||
// 设置表头
|
||||
for i, header := range config.Headers {
|
||||
cell := fmt.Sprintf("%c1", 'A'+i)
|
||||
f.SetCellValue(sheetName, cell, header)
|
||||
}
|
||||
|
||||
// 设置表头样式
|
||||
headerStyle, err := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true},
|
||||
Fill: excelize.Fill{Type: "pattern", Color: []string{"#E6F3FF"}, Pattern: 1},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headerRange := fmt.Sprintf("A1:%c1", 'A'+len(config.Headers)-1)
|
||||
f.SetCellStyle(sheetName, headerRange, headerRange, headerStyle)
|
||||
|
||||
// 批量写入数据
|
||||
for i, rowData := range config.Data {
|
||||
row := i + 2 // 从第2行开始写入数据
|
||||
for j, value := range rowData {
|
||||
cell := fmt.Sprintf("%c%d", 'A'+j, row)
|
||||
f.SetCellValue(sheetName, cell, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置列宽
|
||||
for i, width := range config.ColumnWidths {
|
||||
col := fmt.Sprintf("%c", 'A'+i)
|
||||
f.SetColWidth(sheetName, col, col, width)
|
||||
}
|
||||
|
||||
buf, err := f.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// generateCSV 生成CSV导出文件
|
||||
func (m *ExportManager) generateCSV(ctx context.Context, config *ExportConfig) ([]byte, error) {
|
||||
var csvData strings.Builder
|
||||
|
||||
// 写入CSV头部
|
||||
csvData.WriteString(strings.Join(config.Headers, ",") + "\n")
|
||||
|
||||
// 写入数据行
|
||||
for _, rowData := range config.Data {
|
||||
rowStrings := make([]string, len(rowData))
|
||||
for i, value := range rowData {
|
||||
rowStrings[i] = fmt.Sprintf("%v", value) // 使用%v通用格式化
|
||||
}
|
||||
csvData.WriteString(strings.Join(rowStrings, ",") + "\n")
|
||||
}
|
||||
|
||||
return []byte(csvData.String()), nil
|
||||
}
|
||||
Reference in New Issue
Block a user