Files
backend/internal/repository/user_repo/query_users.go
T
2026-07-15 22:42:31 +08:00

188 lines
4.5 KiB
Go

package user_repo
import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/cache"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type QueryUsersOperation struct {
repo *UserRepository
tx *gorm.DB
keyword *string
userIDs []int64
page int
pageSize int
}
type userQueryCache struct {
UserIDs []int64 `json:"user_ids"`
Total int64 `json:"total"`
}
const userQueryCacheTTL = time.Minute
func (r *UserRepository) QueryUsers() *QueryUsersOperation {
return &QueryUsersOperation{
repo: r,
}
}
func (op *QueryUsersOperation) WithTx(tx *gorm.DB) *QueryUsersOperation {
op.tx = tx
return op
}
func (op *QueryUsersOperation) WithKeyword(keyword *string) *QueryUsersOperation {
op.keyword = keyword
return op
}
func (op *QueryUsersOperation) WithUserIDs(userIDs []int64) *QueryUsersOperation {
op.userIDs = userIDs
return op
}
func (op *QueryUsersOperation) WithPagination(page, pageSize int) *QueryUsersOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *QueryUsersOperation) ExecWithTotal() ([]model.User, int64, error) {
if op.tx == nil {
op.tx = op.repo.db
}
if op.tx == op.repo.db && op.page > 0 && op.pageSize > 0 {
if users, total, ok := op.tryCache(context.Background()); ok {
return users, total, nil
}
}
query := op.tx.Model(&model.User{})
if len(op.userIDs) > 0 {
query = query.Where("id IN ?", op.userIDs)
}
if op.keyword != nil {
trimmed := strings.TrimSpace(*op.keyword)
if trimmed != "" {
like := "%" + trimmed + "%"
query = query.Where("username ILIKE ? OR email ILIKE ? OR external_id ILIKE ?", like, like, like)
}
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
query = query.Offset(offset).Limit(op.pageSize)
}
var users []model.User
if err := query.Order("id DESC").Find(&users).Error; err != nil {
return nil, 0, err
}
if op.tx == op.repo.db && op.page > 0 && op.pageSize > 0 {
op.storeCache(context.Background(), users, total)
}
return users, total, nil
}
func (op *QueryUsersOperation) tryCache(ctx context.Context) ([]model.User, int64, bool) {
version := op.getUserQueryVersion(ctx)
queryHash := buildUserQueryHash(op.keyword, op.userIDs)
cacheKey := key.KeyUserQueryList(fmt.Sprintf("%d:%s", version, queryHash), op.page, op.pageSize)
var cached userQueryCache
ok, err := cache.GetJSON(ctx, cacheKey, &cached)
if err != nil || !ok {
if err != nil {
logrus.Warnf("user query cache read failed: %v", err)
}
return nil, 0, false
}
if len(cached.UserIDs) == 0 {
return []model.User{}, cached.Total, true
}
userMap, err := op.repo.MGetUserByID(cached.UserIDs).WithTx(op.tx).Exec()
if err != nil {
logrus.Warnf("user query cache mget failed: %v", err)
return nil, 0, false
}
users := make([]model.User, 0, len(cached.UserIDs))
for _, id := range cached.UserIDs {
if user, ok := userMap[id]; ok {
users = append(users, *user)
}
}
return users, cached.Total, true
}
func (op *QueryUsersOperation) storeCache(ctx context.Context, users []model.User, total int64) {
userIDs := make([]int64, 0, len(users))
for _, user := range users {
userIDs = append(userIDs, user.ID)
}
version := op.getUserQueryVersion(ctx)
queryHash := buildUserQueryHash(op.keyword, op.userIDs)
cacheKey := key.KeyUserQueryList(fmt.Sprintf("%d:%s", version, queryHash), op.page, op.pageSize)
if err := cache.SetJSON(ctx, cacheKey, &userQueryCache{UserIDs: userIDs, Total: total}, userQueryCacheTTL); err != nil {
logrus.Warnf("user query cache set failed: %v", err)
}
}
func buildUserQueryHash(keyword *string, userIDs []int64) string {
var trimmed string
if keyword != nil {
trimmed = strings.TrimSpace(*keyword)
}
sortedIDs := append([]int64{}, userIDs...)
sort.Slice(sortedIDs, func(i, j int) bool { return sortedIDs[i] < sortedIDs[j] })
builder := strings.Builder{}
builder.WriteString(trimmed)
builder.WriteString("|")
for i, id := range sortedIDs {
if i > 0 {
builder.WriteString(",")
}
builder.WriteString(fmt.Sprintf("%d", id))
}
sum := sha1.Sum([]byte(builder.String()))
return hex.EncodeToString(sum[:])
}
func (op *QueryUsersOperation) getUserQueryVersion(ctx context.Context) int64 {
val, err := op.repo.redis.Get(ctx, key.KeyUserQueryVersion()).Int64()
if err != nil {
return 1
}
if val <= 0 {
return 1
}
return val
}