Files
customer-server/service/data_statistics_service.go

169 lines
5.7 KiB
Go
Raw Permalink Normal View History

2026-03-14 10:02:49 +08:00
// Package service - 数据统计服务
// 功能:对话数据统计分析、报表生成
package service
import (
"archive/zip"
"bytes"
"context"
"customer-server/dao"
"customer-server/model/dto"
"customer-server/model/entity"
"regexp"
"strings"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
var DataStatistics = new(dataStatistics)
type dataStatistics struct{}
// Add 添加数据统计
// 参数: ctx - 上下文req - 添加数据统计请求
// 返回: res - 添加成功后的统计IDerr - 错误信息
// 功能: 创建新的数据统计记录
func (s *dataStatistics) Add(ctx context.Context, req *dto.AddDataStatisticsReq) (res *dto.AddDataStatisticsRes, err error) {
data := &entity.DataStatistics{}
if err = utils.Struct(req, data); err != nil {
return
}
// 使用 gtime 转换日期
if dateTime := gtime.NewFromStr(req.Date); dateTime != nil {
date := dateTime.Time
data.Date = &date // 取地址赋值给指针类型
} else {
return nil, gerror.New("日期格式错误")
}
// 设置基础字段
now := gtime.Now().Time
data.CreatedAt = &now // 取地址赋值给指针类型
data.UpdatedAt = &now // 取地址赋值给指针类型
data.IsDeleted = false
// 注意Creator、Updater、TenantId 保持零值,不设置
if err = dao.DataStatistics.Insert(ctx, data); err != nil {
return
}
res = &dto.AddDataStatisticsRes{Id: data.Id.Hex()}
return
}
// Update 更新数据统计
// 参数: ctx - 上下文req - 更新数据统计请求
// 返回: err - 错误信息
// 功能: 更新数据统计记录内容
func (s *dataStatistics) Update(ctx context.Context, req *dto.UpdateDataStatisticsReq) (err error) {
return dao.DataStatistics.Update(ctx, req)
}
// List 获取数据统计列表
// 参数: ctx - 上下文req - 列表查询请求
// 返回: res - 数据统计列表及分页信息err - 错误信息
// 功能: 分页查询数据统计记录
func (s *dataStatistics) List(ctx context.Context, req *dto.ListDataStatisticsReq) (res *dto.ListDataStatisticsRes, err error) {
list, total, err := dao.DataStatistics.List(ctx, req)
if err != nil {
return
}
res = &dto.ListDataStatisticsRes{
List: list,
Total: int(total),
}
return
}
// Export 导出数据统计为ZIP文件
// 参数: ctx - 上下文req - 导出请求
// 返回: zipData - ZIP文件字节数组filename - 文件名err - 错误信息
// 功能: 将数据统计导出为ZIP文件包含Excel文件
func (s *dataStatistics) Export(ctx context.Context, req *dto.ExportDataStatisticsReq) (zipData []byte, filename string, err error) {
// 1. 查询所有符合条件的数据统计
statistics, err := dao.DataStatistics.FindAllForExport(ctx, req)
if err != nil {
return nil, "", err
}
if len(statistics) == 0 {
return nil, "", gerror.New("没有可导出的数据统计")
}
// 2. 创建 ZIP 文件(内存中)
var buf bytes.Buffer
zipWriter := zip.NewWriter(&buf)
defer zipWriter.Close()
// 3. 为每个数据统计生成 TXT 文件并添加到 ZIP
for _, stat := range statistics {
// 生成 TXT 内容
txtContent := s.generateTxt(stat)
// 生成文件名(清理并替换特殊字符)
dateStr := gtime.New(stat.Date).Format("Y-m-d")
cleanName := strings.ToValidUTF8(stat.CustomerServiceName, "未命名")
safeFilename := s.sanitizeFilename(cleanName)
if safeFilename == "" {
safeFilename = "statistics"
}
txtFilename := dateStr + "_" + safeFilename + "_" + stat.Id.Hex()[:8] + ".txt"
// 添加文件到 ZIP
writer, err := zipWriter.Create(txtFilename)
if err != nil {
return nil, "", gerror.Newf("创建ZIP文件失败: %v", err)
}
if _, err := writer.Write([]byte(txtContent)); err != nil {
return nil, "", gerror.Newf("写入ZIP文件失败: %v", err)
}
}
// 5. 生成下载文件名
timestamp := gtime.Now().Format("Ymd_His")
filename = "data_statistics_export_" + timestamp + ".zip"
return buf.Bytes(), filename, nil
}
// generateTxt 生成数据统计的 TXT 内容
func (s *dataStatistics) generateTxt(stat *entity.DataStatistics) string {
var builder strings.Builder
builder.WriteString("日期: " + gtime.New(stat.Date).Format("Y-m-d") + "\n")
builder.WriteString("客服ID: " + stat.AccountName + "\n")
builder.WriteString("客服名称: " + stat.CustomerServiceName + "\n")
builder.WriteString("客服平台: " + stat.CustomerServicePlatform + "\n")
builder.WriteString("\n=== 统计数据 ===\n")
builder.WriteString("进线数: " + gconv.String(stat.InboundCount) + "\n")
builder.WriteString("开口数: " + gconv.String(stat.ActiveCount) + "\n")
builder.WriteString("接待数: " + gconv.String(stat.ServedCount) + "\n")
builder.WriteString("发名片数: " + gconv.String(stat.ContactCardSentCount) + "\n")
builder.WriteString("发留资卡数: " + gconv.String(stat.NameCardSentCount) + "\n")
builder.WriteString("留资数: " + gconv.String(stat.LeftContactInfoCount) + "\n")
builder.WriteString("\n=== 响应率 ===\n")
builder.WriteString("30s响应率: " + gconv.String(stat.ResponseRate30s) + "%\n")
builder.WriteString("60s响应率: " + gconv.String(stat.ResponseRate60s) + "%\n")
builder.WriteString("360s响应率: " + gconv.String(stat.ResponseRate360s) + "%\n")
builder.WriteString("\n---\n")
builder.WriteString("记录ID: " + stat.Id.Hex() + "\n")
return builder.String()
}
// sanitizeFilename 清理文件名,移除或替换不安全的字符
func (s *dataStatistics) sanitizeFilename(filename string) string {
// 移除或替换特殊字符
reg := regexp.MustCompile(`[<>:"/\\|?*\x00-\x1f]`)
safe := reg.ReplaceAllString(filename, "_")
// 限制文件名长度
if len(safe) > 50 {
safe = safe[:50]
}
return strings.TrimSpace(safe)
}