telco_server/app/admin/service/bus_service/s_sms_manage.go

1741 lines
44 KiB
Go
Raw 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 bus_service
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/service"
"github.com/xuri/excelize/v2"
"go-admin/app/admin/models/bus_models"
"go-admin/common/redisx"
"golang.org/x/net/context"
"gorm.io/gorm"
"io"
"math/rand"
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
)
const (
maxPhonesPerShard = 10000
cacheExpire = time.Hour
TimeFormat = "2006-01-02T15:04:05+08:00"
ExportFile = "/www/server/images/export/"
MiGuExportUrl = "https://telecom.deovo.com/load/export/"
)
type SmsService struct {
service.Service
}
// ReadExcelFile 读取 Excel 文件并提取第一列的手机号
func (s *SmsService) ReadExcelFile(file []byte) ([]string, error) {
// 使用 excelize.OpenReader 直接从文件流读取内容
f, err := excelize.OpenReader(bytes.NewReader(file))
if err != nil {
return nil, fmt.Errorf("无法打开 Excel 文件: %v", err)
}
var phones []string
// 获取所有工作表列表
sheetList := f.GetSheetList()
if len(sheetList) == 0 {
return nil, fmt.Errorf("excel 文件中没有工作表")
}
// 选择第一个工作表
sheet := sheetList[0]
rows, err := f.GetRows(sheet)
if err != nil {
return nil, fmt.Errorf("读取 Excel 行失败: %v", err)
}
// 假设第一列是手机号
for _, row := range rows {
if len(row) > 0 {
phone := row[0]
phones = append(phones, phone)
}
}
return phones, nil
}
func (s *SmsService) CachePhonesByShard(importSerial string, phones []string) error {
ctx := context.Background()
total := 0
for i := 0; i < len(phones); i += maxPhonesPerShard {
end := i + maxPhonesPerShard
if end > len(phones) {
end = len(phones)
}
shard := phones[i:end]
total++
key := fmt.Sprintf("sms:import:%s:%d", importSerial, total)
if err := redisx.Client.RPush(ctx, key, shard).Err(); err != nil {
return err
}
redisx.Client.Expire(ctx, key, cacheExpire)
}
// 保存总分片数量
totalKey := fmt.Sprintf("sms:import:%s:total", importSerial)
err := redisx.Client.Set(ctx, totalKey, total, cacheExpire).Err()
return err
}
func (s *SmsService) AppendPhonesToRedis(serial string, phones []string) error {
ctx := context.Background()
// 拿当前最大分片号
totalKey := fmt.Sprintf("sms:import:%s:total", serial)
totalStr, err := redisx.Client.Get(ctx, totalKey).Result()
if err != nil {
return fmt.Errorf("无法读取缓存分片: %v", err)
}
total, _ := strconv.Atoi(totalStr)
if total == 0 {
return fmt.Errorf("缓存分片不存在")
}
// 直接往最后一片中添加(你也可以按量分片再扩展)
lastKey := fmt.Sprintf("sms:import:%s:%d", serial, total)
values := make([]interface{}, len(phones))
for i, p := range phones {
values[i] = p
}
err = redisx.Client.RPush(ctx, lastKey, values...).Err()
return err
}
func (s *SmsService) SubmitSmsTaskFromRedis(ctx context.Context,
serial, content, sendTime, coopNum, coopName string) error {
// 读取分片数量
totalKey := fmt.Sprintf("sms:import:%s:total", serial)
totalStr, err := redisx.Client.Get(ctx, totalKey).Result()
if err != nil {
return fmt.Errorf("获取缓存分片数失败: %v", err)
}
total, _ := strconv.Atoi(totalStr)
if total == 0 {
return fmt.Errorf("分片数为 0无法创建任务")
}
// 统计总手机号数量
var totalPhones int
phoneCounts := make([]int, total)
for i := 1; i <= total; i++ {
redisKey := fmt.Sprintf("sms:import:%s:%d", serial, i)
count, err := redisx.Client.LLen(ctx, redisKey).Result()
if err != nil {
return fmt.Errorf("读取缓存分片 %d 失败: %v", i, err)
}
totalPhones += int(count)
phoneCounts[i-1] = int(count)
}
if totalPhones == 0 {
return fmt.Errorf("手机号为空,不能创建任务")
}
var planTime time.Time
// 判断是否设置了定时发送
if sendTime != "" {
loc, _ := time.LoadLocation("Asia/Shanghai")
planTime, err = time.ParseInLocation(TimeFormat, sendTime, loc)
if err != nil {
return fmt.Errorf("解析时间出错:%s", err.Error())
}
}
// 计算短信条数按70字分割
// 短信条数计算70字以内算1条超过则每67字拆1条长短信按67字分段
contentCost := (len([]rune(content)) + 66) / 67
totalSmsCount := totalPhones * contentCost
// 使用数据库事务
return s.Orm.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 生成批次ID
batchID, _ := GenerateBatchID(s.Orm)
// 插入任务
task := &bus_models.SmsTask{
CooperativeNumber: coopNum, // 可根据需要填充
CooperativeName: coopName,
BatchID: batchID,
ImportID: serial,
SmsContent: content,
SmsContentCost: contentCost,
TotalPhoneCount: totalPhones,
TotalSmsCount: totalSmsCount,
Status: 0,
InterceptFailCount: 0,
ChannelFailCount: 0,
ScheduleTime: &planTime,
}
if err := tx.Create(task).Error; err != nil {
return fmt.Errorf("创建短信任务失败: %v", err)
}
// 插入批次记录
for i := 1; i <= total; i++ {
batch := &bus_models.SmsTaskBatch{
CooperativeNumber: coopNum, // 可根据需要填充
CooperativeName: coopName,
TaskID: task.ID,
BatchID: batchID,
ImportID: serial,
Num: i,
PhoneCount: phoneCounts[i-1],
SmsCount: phoneCounts[i-1] * contentCost,
SmsContent: content,
Status: 0,
ScheduleTime: &planTime,
}
if err := tx.Create(batch).Error; err != nil {
return fmt.Errorf("创建批次失败: %v", err)
}
}
// ✅ 提示:不在这里生成 SmsSendRecord后续通过消费者从 Redis 中读取批次生成
// 否则数据量大时会影响事务和响应速度
return nil
})
}
// GenerateBatchID 生成唯一的批次ID日期YYYYMMDD+ 8位随机数共16位
func GenerateBatchID(db *gorm.DB) (string, error) {
// 获取当前日期(年月日)
dateStr := time.Now().Format("20060102") // 例如20250411
// 生成一个8位随机数范围00000000 ~ 99999999
rand.Seed(time.Now().UnixNano())
randomNum := rand.Int63n(100000000) // 8位最大是1亿即10^8
// 格式化为8位字符串左侧补0
randomNumStr := fmt.Sprintf("%08d", randomNum)
// 拼接成 batch_id
batchID := fmt.Sprintf("%s%s", dateStr, randomNumStr)
// 检查 sms_task 表中是否已存在该 batch_id
var count int64
err := db.Table("sms_task").Where("batch_id = ?", batchID).Count(&count).Error
if err != nil {
return "", err
}
// 如果已存在,则递归重试
if count > 0 {
return GenerateBatchID(db)
}
return batchID, nil
}
func (s *SmsService) ExportPhoneListToExcel(importSerial string, directPhones []string) (string, error) {
ctx := context.Background()
var allPhones []string
// 从 Redis 获取导入的号码
if importSerial != "" {
totalKey := fmt.Sprintf("sms:import:%s:total", importSerial)
totalStr, err := redisx.Client.Get(ctx, totalKey).Result()
if err == nil {
total, _ := strconv.Atoi(totalStr)
for i := 1; i <= total; i++ {
key := fmt.Sprintf("sms:import:%s:%d", importSerial, i)
shardPhones, err := redisx.Client.LRange(ctx, key, 0, -1).Result()
if err == nil {
allPhones = append(allPhones, shardPhones...)
}
}
}
}
// 追加直接输入的号码
if len(directPhones) > 0 {
allPhones = append(allPhones, directPhones...)
}
if len(allPhones) == 0 {
return "", fmt.Errorf("没有可导出的号码")
}
// 创建 Excel 文件
file := excelize.NewFile()
sheet := "Sheet1"
for i, phone := range allPhones {
cell := fmt.Sprintf("A%d", i+1)
file.SetCellValue(sheet, cell, phone)
}
// 设置样式
style, _ := file.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
_ = file.SetCellStyle(sheet, "A1", fmt.Sprintf("A%d", len(allPhones)), style)
file.SetColWidth(sheet, "A", "A", 20)
// 保存文件
fileName := time.Now().Format("20060102150405") + "_号码导出.xlsx"
url := MiGuExportUrl + fileName
if err := file.SaveAs(ExportFile + fileName); err != nil {
logger.Errorf("导出Excel失败: %v", err)
return "", err
}
return url, nil
}
// CheckSensitiveWords 检查短信内容是否包含敏感词
func (s *SmsService) CheckSensitiveWords(content string) ([]string, error) {
var sensitiveWords []bus_models.SensitiveWord
err := s.Orm.Table("sensitive_words").Where("is_enabled = ?", 1).
Find(&sensitiveWords).Error
if err != nil {
return nil, err
}
var hits []string
for _, sw := range sensitiveWords {
if strings.Contains(content, sw.Word) {
hits = append(hits, sw.Word)
}
}
return hits, nil
}
// QuerySmsTaskList 查询短信下行记录
func (s *SmsService) QuerySmsTaskList(req bus_models.SmsTaskQueryRequest, db *gorm.DB) (*bus_models.SmsTaskQueryResponse, error) {
var tasks []bus_models.SmsTask
var total int64
query := db.Model(&bus_models.SmsTask{}).Where("status = 2")
if req.BatchID != "" {
query = query.Where("batch_id = ?", req.BatchID)
}
if req.MinTotalSms > 0 {
query = query.Where("total_sms_count >= ?", req.MinTotalSms)
}
if req.MinPhoneCount > 0 {
query = query.Where("total_phone_count >= ?", req.MinPhoneCount)
}
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
}
if req.StartTime != "" && req.EndTime != "" {
query = query.Where("created_at BETWEEN ? AND ?", req.StartTime, req.EndTime)
}
// 统计总数
if err := query.Count(&total).Error; err != nil {
return nil, err
}
// 分页处理
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
// 查询分页数据
if err := query.
Order("created_at DESC").
Limit(pageSize).
Offset(offset).
Find(&tasks).Error; err != nil {
return nil, err
}
totalPage := int((total + int64(pageSize) - 1) / int64(pageSize))
if totalPage < 1 {
totalPage = 1
}
return &bus_models.SmsTaskQueryResponse{
List: tasks,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}, nil
}
func (s *SmsService) QuerySmsSendRecords(req bus_models.SmsSendRecordQueryReq) (bus_models.SmsSendRecordQueryResp, error) {
var resp bus_models.SmsSendRecordQueryResp
db := s.Orm.Model(&bus_models.SmsSendRecord{})
if req.BatchID != "" {
db = db.Where("batch_id = ?", req.BatchID)
}
if req.Phone != "" {
db = db.Where("phone = ?", req.Phone)
}
if req.SmsCode != "" {
db = db.Where("sms_code = ?", req.SmsCode)
}
if req.StartTime != "" {
db = db.Where("receive_time >= ?", req.StartTime)
}
if req.EndTime != "" {
db = db.Where("receive_time <= ?", req.EndTime)
}
if req.MinSegments > 0 {
// 计算短信计费条数的粗略估算每67字1条注意实际可以存字段
db = db.Where("(length(sms_content) / 67) + 1 >= ?", req.MinSegments)
}
err := db.Count(&resp.Total).Error
if err != nil {
return resp, err
}
// 分页处理
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
// 查询分页数据
if err = db.
Order("created_at DESC").
Limit(pageSize).
Offset(offset).
Find(&resp.List).Error; err != nil {
return resp, err
}
totalPage := int((resp.Total + int64(pageSize) - 1) / int64(pageSize))
if totalPage < 1 {
totalPage = 1
}
resp.Page = page
resp.PageSize = pageSize
resp.TotalPage = totalPage
return resp, nil
}
// GetPhonesFromCache 获取导入手机号前 limit 条
func (s *SmsService) GetPhonesFromCache(importSerial string, limit int) ([]string, error) {
ctx := context.Background()
totalKey := fmt.Sprintf("sms:import:%s:total", importSerial)
totalStr, err := redisx.Client.Get(ctx, totalKey).Result()
if err != nil {
return nil, err
}
totalShards, err := strconv.Atoi(totalStr)
if err != nil {
return nil, err
}
result := make([]string, 0, limit)
for i := 1; i <= totalShards; i++ {
key := fmt.Sprintf("sms:import:%s:%d", importSerial, i)
phones, err := redisx.Client.LRange(ctx, key, 0, -1).Result()
if err != nil {
return nil, err
}
result = append(result, phones...)
if len(result) >= limit {
return result[:limit], nil
}
}
return result, nil
}
// GetSentPhonesByBatchID 从数据库查询已发送的手机号,最多返回 limit 条
func (s *SmsService) GetSentPhonesByBatchID(db *gorm.DB, batchID string, limit int) ([]string, error) {
var records []bus_models.SmsSendRecord
err := db.
Where("batch_id = ?", batchID).
Order("id DESC").
Limit(limit).
Find(&records).Error
if err != nil {
return nil, err
}
var phones []string
for _, record := range records {
phones = append(phones, record.Phone)
}
return phones, nil
}
// QueryScheduledSmsTaskList 查询定时短信任务
func (s *SmsService) QueryScheduledSmsTaskList(req bus_models.SmsTaskScheduledQueryRequest, db *gorm.DB) (*bus_models.SmsTaskQueryResponse, error) {
var tasks []bus_models.SmsTask
var total int64
query := db.Model(&bus_models.SmsTask{}).
Where("schedule_time IS NOT NULL").
Where("status = ?", 0)
if req.StartTime != "" && req.EndTime != "" {
query = query.Where("schedule_time BETWEEN ? AND ?", req.StartTime, req.EndTime)
}
// 统计总数
if err := query.Count(&total).Error; err != nil {
return nil, err
}
// 分页处理
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
if err := query.
Order("schedule_time ASC").
Limit(pageSize).
Offset(offset).
Find(&tasks).Error; err != nil {
return nil, err
}
totalPage := int((total + int64(pageSize) - 1) / int64(pageSize))
if totalPage < 1 {
totalPage = 1
}
return &bus_models.SmsTaskQueryResponse{
List: tasks,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}, nil
}
func (s *SmsService) QuerySmsUplinkList(req bus_models.SmsUplinkQueryRequest, db *gorm.DB) (bus_models.SmsUplinkQueryResponse, error) {
var resp bus_models.SmsUplinkQueryResponse
var total int64
query := db.Table("sms_uplink_log AS uplink").
Select(`
uplink.id AS uplink_id,
uplink.phone_number,
sr.id AS send_id,
sr.sms_content,
uplink.reply_content,
uplink.created_at AS receive_time,
sr.created_at AS send_time
`).
Joins("LEFT JOIN sms_send_record sr ON uplink.batch_id = sr.batch_id AND uplink.phone_number = sr.phone")
// 条件拼接
if req.PhoneNumber != "" {
query = query.Where("uplink.phone_number LIKE ?", "%"+req.PhoneNumber+"%")
}
if req.SendID > 0 {
query = query.Where("sr.id = ?", req.SendID)
}
if req.UplinkID > 0 {
query = query.Where("uplink.id = ?", req.UplinkID)
}
if req.ReplyContent != "" {
query = query.Where("uplink.reply_content LIKE ?", "%"+req.ReplyContent+"%")
}
if req.StartTime != "" && req.EndTime != "" {
query = query.Where("uplink.created_at BETWEEN ? AND ?", req.StartTime, req.EndTime)
}
// 统计总数
if err := query.Count(&total).Error; err != nil {
return resp, err
}
// 分页处理
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
var results []bus_models.SmsUplinkRecordResponse
// 查询数据
err := query.Order("uplink.created_at DESC").Limit(pageSize).Offset(offset).Scan(&results).Error
if err != nil {
return resp, err
}
resp.List = results
resp.Total = total
resp.Page = page
resp.PageSize = pageSize
resp.TotalPage = (total + int64(page) - 1) / int64(pageSize)
if resp.TotalPage < 1 {
resp.TotalPage = 1
}
return resp, nil
}
// BatchUpdateSmsContent 修改短信内容
func (s *SmsService) BatchUpdateSmsContent(req bus_models.BatchUpdateSmsContentRequest, db *gorm.DB) error {
// 计算短信内容消耗每70字符1条超出部分算1条
runeCount := utf8.RuneCountInString(req.SmsContent)
smsCost := runeCount / 70
if runeCount%70 != 0 {
smsCost++
}
// 批量更新 SmsTask
if err := db.Model(&bus_models.SmsTask{}).
Where("id IN ?", req.TaskIDs).
Updates(map[string]interface{}{
"sms_content": req.SmsContent,
"sms_content_cost": smsCost,
}).Error; err != nil {
return err
}
// 同步更新 SmsTaskBatch 中相应内容
if err := db.Model(&bus_models.SmsTaskBatch{}).
Where("task_id IN ?", req.TaskIDs).
Updates(map[string]interface{}{
"sms_content": req.SmsContent,
}).Error; err != nil {
return err
}
return nil
}
func (s *SmsService) BatchCancelSmsTasks(req bus_models.BatchUpdateRequest, db *gorm.DB) error {
if len(req.TaskIDs) == 0 {
return errors.New("任务ID列表不能为空")
}
// 取消任务状态为 4取消
if err := db.Model(&bus_models.SmsTask{}).
Where("id IN ?", req.TaskIDs).
Updates(map[string]interface{}{
"status": 4,
}).Error; err != nil {
return err
}
// 同步取消子任务
if err := db.Model(&bus_models.SmsTaskBatch{}).
Where("task_id IN ?", req.TaskIDs).
Updates(map[string]interface{}{
"status": 4,
}).Error; err != nil {
return err
}
return nil
}
// BatchResetScheduleTime 批量重置定时时间
func (s *SmsService) BatchResetScheduleTime(req bus_models.BatchResetScheduleTimeRequest, db *gorm.DB) error {
if len(req.TaskIDs) == 0 || req.ScheduleTime == "" {
return errors.New("任务ID和定时时间不能为空")
}
var planTime time.Time
var err error
// 判断是否设置了定时发送
if req.ScheduleTime != "" {
loc, _ := time.LoadLocation("Asia/Shanghai")
planTime, err = time.ParseInLocation(TimeFormat, req.ScheduleTime, loc)
if err != nil {
return fmt.Errorf("解析时间出错:%s", err.Error())
}
}
// 更新 SmsTask
if err := db.Model(&bus_models.SmsTask{}).
Where("id IN ?", req.TaskIDs).
Updates(map[string]interface{}{
"schedule_time": planTime,
}).Error; err != nil {
return err
}
// 同步更新 SmsTaskBatch
if err := db.Model(&bus_models.SmsTaskBatch{}).
Where("task_id IN ?", req.TaskIDs).
Updates(map[string]interface{}{
"schedule_time": planTime,
}).Error; err != nil {
return err
}
return nil
}
// CreateSignatureRealname 创建签名实名制记录
func (s *SmsService) CreateSignatureRealname(data *bus_models.SmsSignatureRealname, db *gorm.DB) error {
return db.Create(data).Error
}
// UpdateSignatureRealname 编辑签名实名制记录
func (s *SmsService) UpdateSignatureRealname(data *bus_models.SmsSignatureRealname, db *gorm.DB) error {
if data.ID == 0 {
return errors.New("ID不能为空")
}
return db.Model(&bus_models.SmsSignatureRealname{}).
Where("id = ?", data.ID).
Updates(data).Error
}
// BatchDeleteSignatureRealname 删除签名实名制记录
func (s *SmsService) BatchDeleteSignatureRealname(ids []uint, db *gorm.DB) error {
if len(ids) == 0 {
return errors.New("删除ID列表不能为空")
}
return db.Where("id IN ?", ids).Delete(&bus_models.SmsSignatureRealname{}).Error
}
func (s *SmsService) ListSignatureRealname(req bus_models.SignatureRealnameQuery, db *gorm.DB) (bus_models.SignatureRealnameQueryResp, error) {
var resp bus_models.SignatureRealnameQueryResp
var list []bus_models.SmsSignatureRealname
var total int64
query := db.Model(&bus_models.SmsSignatureRealname{})
if req.Signature != "" {
query = query.Where("signature LIKE ?", "%"+req.Signature+"%")
}
if req.CooperativeName != "" {
query = query.Where("cooperative_name LIKE ?", "%"+req.CooperativeName+"%")
}
if req.CompanyName != "" {
query = query.Where("company_name LIKE ?", "%"+req.CompanyName+"%")
}
if req.CompanyCreditCode != "" {
query = query.Where("company_credit_code LIKE ?", "%"+req.CompanyCreditCode+"%")
}
if req.ResponsibleName != "" {
query = query.Where("responsible_name LIKE ?", "%"+req.ResponsibleName+"%")
}
if req.UsageCategory != 0 {
query = query.Where("usage_category = ?", req.UsageCategory)
}
if req.IsActive != 0 {
query = query.Where("is_active = ?", req.IsActive)
}
// 获取总条数
err := query.Count(&total).Error
if err != nil {
return resp, err
}
// 分页处理
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
// 分页查询
err = query.Order("id desc").
Offset(offset).
Limit(pageSize).
Find(&list).Error
if err != nil {
return resp, err
}
// 构建分页响应
totalPage := (total + int64(pageSize) - 1) / int64(pageSize)
if totalPage < 1 {
totalPage = 1
}
resp = bus_models.SignatureRealnameQueryResp{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}
return resp, nil
}
func (s *SmsService) ListContacts(req bus_models.ContactQuery, db *gorm.DB) (bus_models.ContactQueryResp, error) {
var resp bus_models.ContactQueryResp
var list []bus_models.SmsContact
var total int64
query := db.Model(&bus_models.SmsContact{})
if len(req.CategoryID) != 0 {
query = query.Where("category_id IN ?", req.CategoryID)
}
if req.Name != "" {
query = query.Where("name LIKE ?", "%"+req.Name+"%")
}
if req.PhoneNumber != "" {
query = query.Where("phone_number LIKE ?", "%"+req.PhoneNumber+"%")
}
err := query.Count(&total).Error
if err != nil {
return resp, err
}
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
err = query.Order("id desc").
Offset(offset).
Limit(pageSize).
Find(&list).Error
if err != nil {
return resp, err
}
totalPage := (total + int64(pageSize) - 1) / int64(pageSize)
if totalPage < 1 {
totalPage = 1
}
resp = bus_models.ContactQueryResp{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}
return resp, nil
}
// AddContact 添加联系人
func (s *SmsService) AddContact(contact bus_models.SmsContact, db *gorm.DB) error {
return db.Create(&contact).Error
}
// EditContact 编辑联系人
func (s *SmsService) EditContact(id uint64, contact bus_models.SmsContact, db *gorm.DB) error {
var existingContact bus_models.SmsContact
if err := db.Where("id = ?", id).First(&existingContact).Error; err != nil {
return err
}
// 更新联系人字段
existingContact.Name = contact.Name
existingContact.PhoneNumber = contact.PhoneNumber
existingContact.Gender = contact.Gender
existingContact.Birthday = contact.Birthday
existingContact.Company = contact.Company
existingContact.Address = contact.Address
existingContact.Remark = contact.Remark
existingContact.CooperativeName = contact.CooperativeName
existingContact.CooperativeNumber = contact.CooperativeNumber
return db.Save(&existingContact).Error
}
// BulkDeleteContacts 批量删除联系人
func (s *SmsService) BulkDeleteContacts(req bus_models.ContactDeleteRequest, db *gorm.DB) error {
return db.Where("id IN (?)", req.ContactIDs).Delete(&bus_models.SmsContact{}).Error
}
// ImportContactsFromExcel 从Excel导入联系人含格式校验
func (s *SmsService) ImportContactsFromExcel(r io.Reader, db *gorm.DB, coopNum, coopName string, categoryId uint64) error {
excelFile, err := excelize.OpenReader(r)
if err != nil {
return errors.New("解析Excel失败")
}
rows, err := excelFile.GetRows("Sheet1")
if err != nil || len(rows) < 2 {
return errors.New("excel格式错误或无有效数据")
}
var contacts []bus_models.SmsContact
for i, row := range rows {
if i == 0 {
continue
}
if len(row) < 7 {
// 如果一行少于7个字段补充缺失字段为空字符串或默认值
for len(row) < 7 {
row = append(row, "")
}
}
phone := strings.TrimSpace(row[3])
if !isValidPhoneNumber(phone) {
return fmt.Errorf("第 %d 行手机号码格式不正确: %s", i+1, phone)
}
birthday, err := parseExcelDate(row[4])
if err != nil {
return fmt.Errorf("第 %d 行生日格式不正确应为YYYY-MM-DD: %s", i+1, row[4])
}
contacts = append(contacts, bus_models.SmsContact{
CategoryID: categoryId,
CooperativeNumber: coopNum,
CooperativeName: coopName,
Name: strings.TrimSpace(row[0]),
Company: strings.TrimSpace(row[1]),
Gender: strings.TrimSpace(row[2]),
PhoneNumber: phone,
Birthday: birthday,
Address: strings.TrimSpace(row[5]),
Remark: strings.TrimSpace(row[6]),
})
}
if len(contacts) == 0 {
return errors.New("无有效联系人记录")
}
err = db.Create(&contacts).Error
if err != nil {
return errors.New("导入失败:" + err.Error())
}
return nil
}
func isValidPhoneNumber(phone string) bool {
match, _ := regexp.MatchString(`^1\d{10}$`, phone)
return match
}
func parseExcelDate(dateStr string) (*time.Time, error) {
if dateStr == "" {
return nil, nil
}
layouts := []string{"2006-01-02", "2006/01/02"}
for _, layout := range layouts {
if t, err := time.Parse(layout, dateStr); err == nil {
return &t, nil
}
}
return nil, fmt.Errorf("日期格式不正确")
}
func (s *SmsService) ExportContactsToExcel(db *gorm.DB, req bus_models.ExportContactsRequest) (string, error) {
var contacts []bus_models.SmsContact
query := db.Order("id desc")
if !req.All {
if len(req.IDs) == 0 {
return "", fmt.Errorf("未传入ID且未选择导出全部")
}
query = query.Where("id IN ?", req.IDs)
}
if err := query.Find(&contacts).Error; err != nil {
return "", err
}
if len(contacts) == 0 {
return "", fmt.Errorf("没有可导出的联系人")
}
file := excelize.NewFile()
sheet := "Sheet1"
headers := []string{"姓名", "公司", "性别", "手机号码", "生日", "地址", "备注"}
// 表头
for i, h := range headers {
col := string('A' + i)
file.SetCellValue(sheet, col+"1", h)
}
// 内容
for i, c := range contacts {
row := i + 2
file.SetCellValue(sheet, "A"+strconv.Itoa(row), c.Name)
file.SetCellValue(sheet, "B"+strconv.Itoa(row), c.Company)
file.SetCellValue(sheet, "C"+strconv.Itoa(row), c.Gender)
file.SetCellValue(sheet, "D"+strconv.Itoa(row), c.PhoneNumber)
if c.Birthday != nil {
file.SetCellValue(sheet, "E"+strconv.Itoa(row), c.Birthday.Format("2006-01-02"))
}
file.SetCellValue(sheet, "F"+strconv.Itoa(row), c.Address)
file.SetCellValue(sheet, "G"+strconv.Itoa(row), c.Remark)
}
style, _ := file.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
file.SetColWidth(sheet, "A", "G", 20)
file.SetCellStyle(sheet, "A1", fmt.Sprintf("G%d", len(contacts)+1), style)
// 保存文件
fileName := time.Now().Format("20060102150405") + "_导出通讯录.xlsx"
filePath := ExportFile + fileName
fileUrl := MiGuExportUrl + fileName
if err := file.SaveAs(filePath); err != nil {
logger.Errorf("导出通讯录失败: %v", err)
return "", err
}
return fileUrl, nil
}
func (s *SmsService) AddPhraseCategory(req bus_models.AddPhraseCategoryReq, db *gorm.DB) error {
return db.Create(&bus_models.SmsPhraseCategory{
Name: req.Name,
ParentID: uint64(req.ParentID),
}).Error
}
func (s *SmsService) EditPhraseCategory(req bus_models.EditPhraseCategoryReq, db *gorm.DB) error {
return db.Model(&bus_models.SmsPhraseCategory{}).
Where("id = ?", req.ID).
Update("name", req.Name).
Error
}
func (s *SmsService) DeletePhraseCategories(ids []uint, db *gorm.DB) error {
if len(ids) == 0 {
return errors.New("未指定需要删除的分类")
}
var total int64
if err := db.Model(&bus_models.SmsPhraseCategory{}).Count(&total).Error; err != nil {
return err
}
// 递归找出所有要删除的 ID含子分类
var allIDs []uint
var walk func(uint)
walk = func(parentID uint) {
var children []bus_models.SmsPhraseCategory
db.Where("parent_id = ?", parentID).Find(&children)
for _, child := range children {
allIDs = append(allIDs, uint(child.ID))
walk(uint(child.ID))
}
}
for _, id := range ids {
allIDs = append(allIDs, id)
walk(id)
}
// 保证删除后至少保留一个分类节点
if len(allIDs) >= int(total) {
return errors.New("无法删除所有分类节点,至少保留一个")
}
// 删除短语
if err := db.Where("category_id IN ?", allIDs).Delete(&bus_models.SmsPhrase{}).Error; err != nil {
return err
}
// 删除分类
return db.Where("id IN ?", allIDs).Delete(&bus_models.SmsPhraseCategory{}).Error
}
func (s *SmsService) ListPhrases(req bus_models.SmsPhraseQuery, db *gorm.DB) (bus_models.SmsPhraseListResp, error) {
var list []bus_models.SmsPhrase
var total int64
query := db.Model(&bus_models.SmsPhrase{})
if req.Content != "" {
query = query.Where("content LIKE ?", "%"+req.Content+"%")
}
if req.CategoryID != 0 {
query = query.Where("category_id = ?", req.CategoryID)
}
err := query.Count(&total).Error
if err != nil {
return bus_models.SmsPhraseListResp{}, err
}
// 处理分页
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
err = query.Order("id desc").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&list).Error
if err != nil {
return bus_models.SmsPhraseListResp{}, err
}
return bus_models.SmsPhraseListResp{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: (total + int64(pageSize) - 1) / int64(pageSize),
}, nil
}
// AddPhrase 新增
func (s *SmsService) AddPhrase(req bus_models.SmsPhraseAddOrEdit, db *gorm.DB) error {
phrase := bus_models.SmsPhrase{
Content: req.Content,
CategoryID: req.CategoryID,
}
return db.Create(&phrase).Error
}
// EditPhrase 编辑
func (s *SmsService) EditPhrase(req bus_models.SmsPhraseAddOrEdit, db *gorm.DB) error {
return db.Model(&bus_models.SmsPhrase{}).
Where("id = ?", req.ID).
Updates(map[string]interface{}{
"content": req.Content,
"category_id": req.CategoryID,
}).Error
}
// DeletePhrases 批量删除
func (s *SmsService) DeletePhrases(ids []uint, db *gorm.DB) error {
return db.Where("id IN (?)", ids).Delete(&bus_models.SmsPhrase{}).Error
}
func (s *SmsService) GetPhraseCategoryTree(db *gorm.DB, parentID uint64) ([]bus_models.SmsPhraseCategoryTree, error) {
var categories []bus_models.SmsPhraseCategory
if err := db.Find(&categories).Error; err != nil {
return nil, err
}
// 构建 map[id]category
idMap := make(map[uint64]*bus_models.SmsPhraseCategoryTree)
for _, cat := range categories {
node := &bus_models.SmsPhraseCategoryTree{
ID: cat.ID,
Name: cat.Name,
ParentID: cat.ParentID,
}
idMap[cat.ID] = node
}
// 构建树结构
for _, node := range idMap {
if parent, ok := idMap[node.ParentID]; ok {
parent.Children = append(parent.Children, *node)
}
}
// 返回指定节点下的树
if parentID != 0 {
if root, ok := idMap[parentID]; ok {
return []bus_models.SmsPhraseCategoryTree{*root}, nil
}
return []bus_models.SmsPhraseCategoryTree{}, nil // 指定节点不存在
}
// 否则返回整棵树
var roots []bus_models.SmsPhraseCategoryTree
for _, node := range idMap {
if node.ParentID == 0 {
roots = append(roots, *node)
}
}
return roots, nil
}
func (s *SmsService) AddContactsCategory(req bus_models.AddContactCategoryReq, db *gorm.DB) error {
return db.Create(&bus_models.SmsContactCategory{
Name: req.Name,
ParentID: uint64(req.ParentID),
}).Error
}
func (s *SmsService) EditContactsCategory(req bus_models.EditContactCategoryReq, db *gorm.DB) error {
return db.Model(&bus_models.SmsContactCategory{}).
Where("id = ?", req.ID).
Update("name", req.Name).
Error
}
func (s *SmsService) DeleteContactsCategories(ids []uint, db *gorm.DB) error {
if len(ids) == 0 {
return errors.New("未指定需要删除的分类")
}
var total int64
if err := db.Model(&bus_models.SmsContactCategory{}).Count(&total).Error; err != nil {
return err
}
// 递归找出所有要删除的 ID含子分类
var allIDs []uint
var walk func(uint)
walk = func(parentID uint) {
var children []bus_models.SmsContactCategory
db.Where("parent_id = ?", parentID).Find(&children)
for _, child := range children {
allIDs = append(allIDs, uint(child.ID))
walk(uint(child.ID))
}
}
for _, id := range ids {
allIDs = append(allIDs, id)
walk(id)
}
// 保证删除后至少保留一个分类节点
if len(allIDs) >= int(total) {
return errors.New("无法删除所有分类节点,至少保留一个")
}
// 删除短语
if err := db.Where("category_id IN ?", allIDs).Delete(&bus_models.SmsContact{}).Error; err != nil {
return err
}
// 删除分类
return db.Where("id IN ?", allIDs).Delete(&bus_models.SmsContactCategory{}).Error
}
func (s *SmsService) GetContactsCategoryTree(db *gorm.DB, parentID uint64) ([]bus_models.SmsContactCategoryTree, error) {
var categories []bus_models.SmsContactCategory
if err := db.Find(&categories).Error; err != nil {
return nil, err
}
// 构建 map[id]category
idMap := make(map[uint64]*bus_models.SmsContactCategoryTree)
for _, cat := range categories {
node := &bus_models.SmsContactCategoryTree{
ID: cat.ID,
Name: cat.Name,
ParentID: cat.ParentID,
}
idMap[cat.ID] = node
}
// 构建树结构
for _, node := range idMap {
if parent, ok := idMap[node.ParentID]; ok {
parent.Children = append(parent.Children, *node)
}
}
// 返回指定节点下的树
if parentID != 0 {
if root, ok := idMap[parentID]; ok {
return []bus_models.SmsContactCategoryTree{*root}, nil
}
return []bus_models.SmsContactCategoryTree{}, nil // 指定节点不存在
}
// 否则返回整棵树
var roots []bus_models.SmsContactCategoryTree
for _, node := range idMap {
if node.ParentID == 0 {
roots = append(roots, *node)
}
}
return roots, nil
}
func (s *SmsService) ListCommonNumbers(req bus_models.SmsCommonNumberQuery, db *gorm.DB) (bus_models.SmsCommonNumberListResp, error) {
var dbList []bus_models.SmsCommonNumber
var total int64
query := db.Model(&bus_models.SmsCommonNumber{})
if req.Name != "" {
query = query.Where("name LIKE ?", "%"+req.Name+"%")
}
err := query.Count(&total).Error
if err != nil {
return bus_models.SmsCommonNumberListResp{}, err
}
// 处理分页
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
err = query.Order("id desc").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&dbList).Error
if err != nil {
return bus_models.SmsCommonNumberListResp{}, err
}
// 转换为返回结构体
var respList []bus_models.SmsCommonNumber
for _, item := range dbList {
respList = append(respList, bus_models.SmsCommonNumber{
Name: item.Name,
PhoneNumbers: item.PhoneNumbers,
PhoneCount: len(strings.Split(item.PhoneNumbers, ",")),
})
}
totalPage := (total + int64(page) - 1) / int64(pageSize)
if totalPage < 1 {
totalPage = 1
}
return bus_models.SmsCommonNumberListResp{
List: respList,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}, nil
}
func (s *SmsService) AddCommonNumber(req bus_models.SmsCommonNumberAddReq, db *gorm.DB) error {
if req.Name == "" {
return errors.New("名称不能为空")
}
return db.Create(&bus_models.SmsCommonNumber{
Name: req.Name,
PhoneNumbers: strings.Join(req.PhoneList, ","),
}).Error
}
// AppendCommonNumber 向已有常用号码记录中追加号码(自动去重)
// 逻辑:获取原始号码列表 + 新号码列表 => 合并去重 => 更新保存
func (s *SmsService) AppendCommonNumber(req bus_models.SmsCommonNumberAppendReq, db *gorm.DB) error {
var record bus_models.SmsCommonNumber
err := db.Where("id = ?", req.ID).First(&record).Error
if err != nil {
return err
}
// 拆分原始号码
originalNumbers := strings.Split(record.PhoneNumbers, ",")
numberSet := make(map[string]struct{})
// 原号码去重填入 map
for _, num := range originalNumbers {
num = strings.TrimSpace(num)
if num != "" {
numberSet[num] = struct{}{}
}
}
// 新号码去重合并
for _, num := range req.PhoneList {
num = strings.TrimSpace(num)
if num != "" {
numberSet[num] = struct{}{}
}
}
var deduplicated []string
for num := range numberSet {
deduplicated = append(deduplicated, num)
}
sort.Strings(deduplicated) // 方便前端对比、稳定输出顺序
record.PhoneNumbers = strings.Join(deduplicated, ",")
return db.Save(&record).Error
}
func (s *SmsService) GetCommonNumberDetail(id int64, db *gorm.DB) (bus_models.SmsCommonNumber, error) {
var record bus_models.SmsCommonNumber
err := db.First(&record, id).Error
return record, err
}
func (s *SmsService) DeleteCommonNumbers(ids []uint64, db *gorm.DB) error {
return db.Where("id IN (?)", ids).Delete(&bus_models.SmsCommonNumber{}).Error
}
// ExportCommonNumbers 导出多个常用号码名称下的所有号码到一个文件
func (s *SmsService) ExportCommonNumbers(req bus_models.SmsCommonNumberExportReq, db *gorm.DB) (string, error) {
var records []bus_models.SmsCommonNumber
query := db.Model(&bus_models.SmsCommonNumber{}).Order("id desc")
if !req.All {
if len(req.Ids) == 0 {
return "", fmt.Errorf("未传入号码且未选择导出全部")
}
query = query.Where("id IN ?", req.Ids)
}
err := query.Find(&records).Error
if err != nil {
return "", err
}
if len(records) == 0 {
return "", errors.New("未找到对应的常用号码数据")
}
// 创建文件
fileName := time.Now().Format("20060102150405") + "_常用号码.xlsx"
filePath := ExportFile + fileName
fileUrl := MiGuExportUrl + fileName
file, err := os.Create(filePath)
if err != nil {
return "", err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// 写数据
for _, record := range records {
numbers := strings.Split(record.PhoneNumbers, ",")
for _, number := range numbers {
trimmed := strings.TrimSpace(number)
if trimmed != "" {
writer.Write([]string{trimmed})
}
}
}
return fileUrl, nil
}
func (s *SmsService) AddBlacklistNumber(req bus_models.BlacklistAddReq, db *gorm.DB) error {
if len(req.PhoneList) == 0 {
return fmt.Errorf("手机号列表不能为空")
}
var records []bus_models.SmsBlackList
for _, phone := range req.PhoneList {
records = append(records, bus_models.SmsBlackList{
PhoneNumber: phone,
Remark: req.Remark,
})
}
return db.Create(&records).Error
}
func (s *SmsService) ListBlacklist(req bus_models.BlacklistQuery, db *gorm.DB) (bus_models.BlacklistListResp, error) {
var list []bus_models.SmsBlackList
var total int64
query := db.Model(&bus_models.SmsBlackList{})
if req.PhoneNumber != "" {
query = query.Where("phone_number LIKE ?", "%"+req.PhoneNumber+"%")
}
err := query.Count(&total).Error
if err != nil {
return bus_models.BlacklistListResp{}, err
}
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
err = query.Order("id desc").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&list).Error
if err != nil {
return bus_models.BlacklistListResp{}, err
}
totalPage := (total + int64(pageSize) - 1) / int64(pageSize)
if totalPage < 1 {
totalPage = 1
}
return bus_models.BlacklistListResp{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}, nil
}
func (s *SmsService) ExportBlacklistToExcel(db *gorm.DB, req bus_models.ExportBlacklistRequest) (string, error) {
var blacklists []bus_models.SmsBlackList
query := db.Model(&bus_models.SmsBlackList{}).Order("id desc")
if !req.All {
if len(req.Ids) == 0 {
return "", fmt.Errorf("未传入号码且未选择导出全部")
}
query = query.Where("id IN ?", req.Ids)
}
if err := query.Find(&blacklists).Error; err != nil {
return "", err
}
if len(blacklists) == 0 {
return "", fmt.Errorf("没有可导出的黑名单记录")
}
file := excelize.NewFile()
sheet := "Sheet1"
headers := []string{"手机号", "备注", "创建时间"}
for i, h := range headers {
col := string('A' + i)
file.SetCellValue(sheet, col+"1", h)
}
for i, b := range blacklists {
row := strconv.Itoa(i + 2)
file.SetCellValue(sheet, "A"+row, b.PhoneNumber)
file.SetCellValue(sheet, "B"+row, b.Remark)
file.SetCellValue(sheet, "C"+row, b.CreatedAt.Format("2006-01-02 15:04:05"))
}
style, _ := file.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
file.SetColWidth(sheet, "A", "C", 20)
file.SetCellStyle(sheet, "A1", fmt.Sprintf("C%d", len(blacklists)+1), style)
fileName := time.Now().Format("20060102150405") + "_导出黑名单.xlsx"
filePath := ExportFile + fileName
fileUrl := MiGuExportUrl + fileName
if err := file.SaveAs(filePath); err != nil {
logger.Errorf("导出黑名单失败: %v", err)
return "", err
}
return fileUrl, nil
}
// DeleteBlacklist 批量删除黑名单记录
func (s *SmsService) DeleteBlacklist(ids []uint, db *gorm.DB) error {
return db.Where("id IN ?", ids).Delete(&bus_models.SmsBlackList{}).Error
}
// CreateSmsTemplate 创建短信模版
func (s *SmsService) CreateSmsTemplate(data *bus_models.SmsTemplate, db *gorm.DB) error {
return db.Create(data).Error
}
// DeleteSmsTemplates 批量删除短信模版
func (s *SmsService) DeleteSmsTemplates(ids []uint, db *gorm.DB) error {
return db.Delete(&bus_models.SmsTemplate{}, ids).Error
}
// UpdateSmsTemplate 更新短信模版
func (s *SmsService) UpdateSmsTemplate(req *bus_models.SmsTemplateUpdateRequest, db *gorm.DB) error {
var tmpl bus_models.SmsTemplate
// 查找原始数据
if err := db.First(&tmpl, req.ID).Error; err != nil {
return fmt.Errorf("未找到指定的短信模版")
}
// 更新模版内容
tmpl.Content = req.Content
// 更新到期时间(如果传入了新的时间)
if !req.ExpireAt.IsZero() {
tmpl.ExpireAt = req.ExpireAt
}
// 更新更新时间
tmpl.UpdatedAt = time.Now()
// 保存更新后的模版
return db.Save(&tmpl).Error
}
// ApproveSmsTemplate 审核短信模版
func (s *SmsService) ApproveSmsTemplate(id uint, status int, db *gorm.DB) error {
return db.Model(&bus_models.SmsTemplate{}).
Where("id = ?", id).
Update("status", status).Error
}
func (s *SmsService) ListSmsTemplates(req bus_models.SmsTemplateQuery, db *gorm.DB) (bus_models.SmsTemplateListResp, error) {
var list []bus_models.SmsTemplate
var total int64
query := db.Model(&bus_models.SmsTemplate{})
if req.Content != "" {
query = query.Where("content LIKE ?", "%"+req.Content+"%")
}
if req.Status != 0 {
query = query.Where("status = ?", req.Status)
}
if !req.CreateStart.IsZero() && !req.CreateEnd.IsZero() {
query = query.Where("created_at BETWEEN ? AND ?", req.CreateStart, req.CreateEnd)
}
err := query.Count(&total).Error
if err != nil {
return bus_models.SmsTemplateListResp{}, err
}
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 10
}
err = query.Order("id desc").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&list).Error
if err != nil {
return bus_models.SmsTemplateListResp{}, err
}
totalPage := (total + int64(pageSize) - 1) / int64(pageSize)
if totalPage < 1 {
totalPage = 1
}
return bus_models.SmsTemplateListResp{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPage: totalPage,
}, nil
}
func (s *SmsService) ExportSmsTemplates(req bus_models.SmsTemplateExportReq, db *gorm.DB) (string, error) {
var templates []bus_models.SmsTemplate
query := db.Model(&bus_models.SmsTemplate{}).Order("id desc")
if !req.All {
if len(req.Ids) == 0 {
return "", fmt.Errorf("未传入模版且未选择导出全部")
}
query = query.Where("id IN ?", req.Ids)
}
err := query.Find(&templates).Error
if err != nil {
return "", err
}
if len(templates) == 0 {
return "", errors.New("未找到对应的短信模版数据")
}
// 生成文件
fileName := time.Now().Format("20060102150405") + "_短信模版导出.csv"
filePath := ExportFile + fileName
fileUrl := MiGuExportUrl + fileName
file, err := os.Create(filePath)
if err != nil {
return "", err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// 写表头
writer.Write([]string{"模版ID", "模版内容", "备注", "状态", "创建时间"})
// 写内容
for _, tmpl := range templates {
status := map[int]string{
0: "待审核",
1: "正常",
2: "审核拒绝",
3: "已过期",
}[tmpl.Status]
writer.Write([]string{
strconv.Itoa(int(tmpl.ID)),
tmpl.Content,
tmpl.Remark,
status,
tmpl.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
return fileUrl, nil
}