优化二维码池筛选和日期中文化
This commit is contained in:
@@ -3,6 +3,8 @@ package chat
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -60,9 +62,15 @@ type UpdateQrCodeRequest struct {
|
||||
|
||||
// QrCodeListRequest 列表查询请求
|
||||
type QrCodeListRequest struct {
|
||||
Status string `form:"status"`
|
||||
Page int `form:"page"`
|
||||
Limit int `form:"limit"`
|
||||
Status string `form:"status"`
|
||||
Keyword string `form:"keyword"`
|
||||
WecomRenamed string `form:"wecom_renamed"`
|
||||
Bound string `form:"bound"`
|
||||
ExpireStatus string `form:"expire_status"`
|
||||
CreatedStart string `form:"created_start"`
|
||||
CreatedEnd string `form:"created_end"`
|
||||
Page int `form:"page"`
|
||||
Limit int `form:"limit"`
|
||||
}
|
||||
|
||||
// QrCodeStats 二维码统计
|
||||
@@ -167,32 +175,28 @@ func (r *Repository) ListQrCodes(ctx context.Context, req QrCodeListRequest) ([]
|
||||
req.Limit = 20
|
||||
}
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&model.ChatQrCode{})
|
||||
|
||||
// 状态过滤
|
||||
if req.Status != "" {
|
||||
query = query.Where("status = ?", req.Status)
|
||||
now := time.Now()
|
||||
baseQuery := func() *gorm.DB {
|
||||
return applyQrCodeListFilters(
|
||||
r.db.WithContext(ctx).
|
||||
Table("chat_qrcode_pool AS q").
|
||||
Joins("LEFT JOIN chat_conversations AS c ON c.id = q.conversation_id"),
|
||||
req,
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
if err := baseQuery().Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 查询列表
|
||||
var qrcodes []QrCodeListItem
|
||||
offset := (req.Page - 1) * req.Limit
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("chat_qrcode_pool AS q").
|
||||
if err := baseQuery().
|
||||
Select("q.*, COALESCE(c.title, '') AS bound_conversation_title").
|
||||
Joins("LEFT JOIN chat_conversations AS c ON c.id = q.conversation_id").
|
||||
Scopes(func(db *gorm.DB) *gorm.DB {
|
||||
if req.Status != "" {
|
||||
return db.Where("q.status = ?", req.Status)
|
||||
}
|
||||
return db
|
||||
}).
|
||||
Order("q.id DESC").
|
||||
Offset(offset).
|
||||
Limit(req.Limit).
|
||||
@@ -203,6 +207,88 @@ func (r *Repository) ListQrCodes(ctx context.Context, req QrCodeListRequest) ([]
|
||||
return qrcodes, total, nil
|
||||
}
|
||||
|
||||
func applyQrCodeListFilters(query *gorm.DB, req QrCodeListRequest, now time.Time) *gorm.DB {
|
||||
if req.Status != "" {
|
||||
query = query.Where("q.status = ?", req.Status)
|
||||
}
|
||||
|
||||
if keyword := strings.TrimSpace(req.Keyword); keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
conditions := "(q.group_name LIKE ? OR q.note LIKE ? OR COALESCE(c.title, '') LIKE ?"
|
||||
args := []interface{}{like, like, like}
|
||||
if id, err := strconv.ParseUint(keyword, 10, 64); err == nil {
|
||||
conditions += " OR q.id = ? OR q.conversation_id = ?"
|
||||
args = append(args, id, id)
|
||||
}
|
||||
conditions += ")"
|
||||
query = query.Where(conditions, args...)
|
||||
}
|
||||
|
||||
if value, ok := parseQrCodeBoolFilter(req.WecomRenamed); ok {
|
||||
query = query.Where("q.wecom_renamed = ?", value)
|
||||
}
|
||||
|
||||
if value, ok := parseQrCodeBoolFilter(req.Bound); ok {
|
||||
if value {
|
||||
query = query.Where("q.conversation_id IS NOT NULL")
|
||||
} else {
|
||||
query = query.Where("q.conversation_id IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
switch req.ExpireStatus {
|
||||
case "valid":
|
||||
query = query.Where("(q.expires_at IS NULL OR q.expires_at > ?)", now)
|
||||
case "expired":
|
||||
query = query.Where("q.expires_at IS NOT NULL AND q.expires_at <= ?", now)
|
||||
case "permanent":
|
||||
query = query.Where("q.expires_at IS NULL")
|
||||
case "expiring_soon":
|
||||
query = query.Where("q.expires_at > ? AND q.expires_at <= ?", now, now.Add(72*time.Hour))
|
||||
}
|
||||
|
||||
if start, ok := parseQrCodeFilterTime(req.CreatedStart); ok {
|
||||
query = query.Where("q.created_at >= ?", start)
|
||||
}
|
||||
if end, ok := parseQrCodeFilterTime(req.CreatedEnd); ok {
|
||||
query = query.Where("q.created_at <= ?", end)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func parseQrCodeBoolFilter(value string) (bool, bool) {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "true", "1", "yes":
|
||||
return true, true
|
||||
case "false", "0", "no":
|
||||
return false, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
func parseQrCodeFilterTime(value string) (time.Time, bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return time.Time{}, false
|
||||
}
|
||||
layouts := []string{
|
||||
time.RFC3339,
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02T15:04",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02",
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
if parsed, err := time.Parse(layout, value); err == nil {
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// GetQrCodeStats 获取二维码统计信息
|
||||
func (r *Repository) GetQrCodeStats(ctx context.Context) (*QrCodeStats, error) {
|
||||
stats := &QrCodeStats{}
|
||||
|
||||
@@ -104,6 +104,102 @@ func TestListQrCodesIncludesBoundConversationTitle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListQrCodesFilters(t *testing.T) {
|
||||
db := setupQrCodeTestDB(t)
|
||||
repo := NewRepository(db, nil)
|
||||
now := time.Now()
|
||||
expiredAt := now.Add(-time.Hour)
|
||||
validAt := now.Add(7 * 24 * time.Hour)
|
||||
expiringAt := now.Add(2 * time.Hour)
|
||||
conversation := model.ChatConversation{
|
||||
Title: "账号群 绑定测试",
|
||||
Type: ConversationTypeListingGroup,
|
||||
Status: "active",
|
||||
}
|
||||
if err := db.Create(&conversation).Error; err != nil {
|
||||
t.Fatalf("创建群聊失败: %v", err)
|
||||
}
|
||||
|
||||
qrcodes := []model.ChatQrCode{
|
||||
{
|
||||
ImageURL: "/api/files/object?key=qrcode/unused-a.png",
|
||||
GroupName: "待用群A",
|
||||
Status: QrCodeStatusUnused,
|
||||
CreatedBy: 1,
|
||||
Note: "六月首批",
|
||||
ExpiresAt: &validAt,
|
||||
CreatedAt: now.Add(-48 * time.Hour),
|
||||
},
|
||||
{
|
||||
ImageURL: "/api/files/object?key=qrcode/used-b.png",
|
||||
GroupName: "已发群B",
|
||||
Status: QrCodeStatusUsed,
|
||||
ConversationID: &conversation.ID,
|
||||
CreatedBy: 1,
|
||||
Note: "特殊备注",
|
||||
WecomRenamed: true,
|
||||
ExpiresAt: &expiredAt,
|
||||
CreatedAt: now.Add(-2 * time.Hour),
|
||||
},
|
||||
{
|
||||
ImageURL: "/api/files/object?key=qrcode/permanent-c.png",
|
||||
GroupName: "永久群C",
|
||||
Status: QrCodeStatusDisabled,
|
||||
CreatedBy: 1,
|
||||
Note: "长期备用",
|
||||
CreatedAt: now.Add(-24 * time.Hour),
|
||||
},
|
||||
{
|
||||
ImageURL: "/api/files/object?key=qrcode/expiring-d.png",
|
||||
GroupName: "临期群D",
|
||||
Status: QrCodeStatusUnused,
|
||||
CreatedBy: 1,
|
||||
Note: "三天内到期",
|
||||
ExpiresAt: &expiringAt,
|
||||
CreatedAt: now.Add(-30 * time.Minute),
|
||||
},
|
||||
}
|
||||
if err := db.Create(&qrcodes).Error; err != nil {
|
||||
t.Fatalf("创建二维码失败: %v", err)
|
||||
}
|
||||
|
||||
assertListIDs := func(name string, req QrCodeListRequest, want ...uint64) {
|
||||
t.Helper()
|
||||
req.Page = 1
|
||||
req.Limit = 20
|
||||
items, total, err := repo.ListQrCodes(t.Context(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s 查询二维码失败: %v", name, err)
|
||||
}
|
||||
if total != int64(len(want)) || len(items) != len(want) {
|
||||
t.Fatalf("%s total = %d, len = %d, want %d", name, total, len(items), len(want))
|
||||
}
|
||||
for i, id := range want {
|
||||
if items[i].ID != id {
|
||||
t.Fatalf("%s 第 %d 条 ID = %d, want %d", name, i, items[i].ID, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertListIDs("状态", QrCodeListRequest{Status: QrCodeStatusUnused}, qrcodes[3].ID, qrcodes[0].ID)
|
||||
assertListIDs("关键词群名", QrCodeListRequest{Keyword: "待用群"}, qrcodes[0].ID)
|
||||
assertListIDs("关键词备注", QrCodeListRequest{Keyword: "特殊备注"}, qrcodes[1].ID)
|
||||
assertListIDs("关键词绑定群", QrCodeListRequest{Keyword: "绑定测试"}, qrcodes[1].ID)
|
||||
assertListIDs("企微改名", QrCodeListRequest{WecomRenamed: "true"}, qrcodes[1].ID)
|
||||
assertListIDs("未绑定", QrCodeListRequest{Bound: "false"}, qrcodes[3].ID, qrcodes[2].ID, qrcodes[0].ID)
|
||||
assertListIDs("已过期", QrCodeListRequest{ExpireStatus: "expired"}, qrcodes[1].ID)
|
||||
assertListIDs("永久有效", QrCodeListRequest{ExpireStatus: "permanent"}, qrcodes[2].ID)
|
||||
assertListIDs("即将过期", QrCodeListRequest{ExpireStatus: "expiring_soon"}, qrcodes[3].ID)
|
||||
assertListIDs(
|
||||
"上传时间",
|
||||
QrCodeListRequest{
|
||||
CreatedStart: now.Add(-3 * time.Hour).Format(time.RFC3339),
|
||||
CreatedEnd: now.Add(-90 * time.Minute).Format(time.RFC3339),
|
||||
},
|
||||
qrcodes[1].ID,
|
||||
)
|
||||
}
|
||||
|
||||
func TestParseQrCodeGroupNameCandidates(t *testing.T) {
|
||||
rawText := strings.Join([]string{
|
||||
"使用微信或企业微信扫码加入",
|
||||
|
||||
Reference in New Issue
Block a user