* fix:修复响应污染问题 * fix:修改存储相关文件为中文注释 * fix:修复二次退款问题 * fix:修复多个考虑不全面问题 1.误删用户数据 2.R2存储默认区域 3.删除失败导致孤儿记录 4.网络抖动事变时重试 5.前端无限次请求获取图片 6.文件管理权限 * fix:修复字符串截断异常问题和文件下载超时问题 * fix:修复多图问题 * fix:异步生成图片时未配置oss提示错误信息 * fix:修复关闭异步后同步生成出错问题 * feat:新增文件存储测试功能,防止设置后不生效 * fix:修复同步图片转异步后计费错误 * fix:修复无下限扣款问题 * fix:修复结算重复扣款问题 * fix:修复计费出错导致的0费用结算 * fix:修复回调双重履约 * fix:修复预扣事务 * fix:修复费用字段类型问题 * fix:修复缓存问题 * fix:修复安全问题 * fix:修复数据库操作事务问题 * refactor:重构事务使用方式,改为官方推荐的dao形式 * fix:修复余额更新隐患 * fix:修复N+1查询问题 * fix:修复密钥安全问题 * fix:结算失败时打印错误日志 * improve:优化扣款 * fix:修复用户接口越权 --------- Co-authored-by: chengqian <wuchengqian@kkidc.com>
76 lines
1.8 KiB
Go
76 lines
1.8 KiB
Go
package export
|
||
|
||
import (
|
||
"encoding/csv"
|
||
"strconv"
|
||
|
||
"github.com/gogf/gf/v2/net/ghttp"
|
||
)
|
||
|
||
// StreamCSV writes a CSV file as a streaming HTTP response.
|
||
// queryFn receives a yield callback; call yield(row) for each data row.
|
||
// Data is fetched and written in batches to avoid loading everything into memory.
|
||
func StreamCSV(r *ghttp.Request, config Config, queryFn func(yield func(map[string]any) bool)) error {
|
||
filename := config.Filename
|
||
if filename == "" {
|
||
filename = defaultFilename("export")
|
||
}
|
||
setDownloadHeaders(r, filename, "csv")
|
||
|
||
w := r.Response.RawWriter()
|
||
|
||
// UTF-8 BOM for Excel compatibility
|
||
w.Write([]byte{0xEF, 0xBB, 0xBF})
|
||
|
||
writer := csv.NewWriter(w)
|
||
writer.UseCRLF = true
|
||
|
||
// Write header row
|
||
headers := make([]string, len(config.Columns))
|
||
for i, col := range config.Columns {
|
||
headers[i] = col.Header
|
||
}
|
||
if err := writer.Write(headers); err != nil {
|
||
return err
|
||
}
|
||
|
||
// Write data rows via the query callback
|
||
rowCount := 0
|
||
queryFn(func(row map[string]any) bool {
|
||
record := make([]string, len(config.Columns))
|
||
for i, col := range config.Columns {
|
||
// B14:CSV 公式注入防御,中和以 =+-@ 等开头的用户可控字段
|
||
record[i] = sanitizeCSVField(GetCellValue(row, col))
|
||
}
|
||
_ = writer.Write(record)
|
||
rowCount++
|
||
|
||
// Flush every 500 rows to keep memory low
|
||
if rowCount%500 == 0 {
|
||
writer.Flush()
|
||
}
|
||
return true
|
||
})
|
||
|
||
writer.Flush()
|
||
return writer.Error()
|
||
}
|
||
|
||
// WriteCSVAll writes a CSV file from a complete data slice.
|
||
// For large datasets, prefer StreamCSV.
|
||
func WriteCSVAll(r *ghttp.Request, config Config, data []map[string]any) error {
|
||
return StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||
for _, row := range data {
|
||
if !yield(row) {
|
||
break
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// parseCSVInt safely parses an int from a CSV cell value.
|
||
func parseCSVInt(s string) int {
|
||
n, _ := strconv.Atoi(s)
|
||
return n
|
||
}
|