migrate from github
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user