Files
customer-server/service/data_statistics_service.go
2026-03-14 10:02:49 +08:00

169 lines
5.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)
}