188 lines
4.5 KiB
Go
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
|
|
}
|