169 lines
5.7 KiB
Go
169 lines
5.7 KiB
Go
|
|
// 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 - 添加成功后的统计ID,err - 错误信息
|
|||
|
|
// 功能: 创建新的数据统计记录
|
|||
|
|
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)
|
|||
|
|
}
|