migrate from github
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserCreateOperation struct {
|
||||
repo *UserRepository
|
||||
user *model.User
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) Create(user *model.User) *UserCreateOperation {
|
||||
return &UserCreateOperation{
|
||||
repo: r,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserCreateOperation) WithTx(tx *gorm.DB) *UserCreateOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserCreateOperation) Exec() error {
|
||||
if op.tx != nil {
|
||||
if err := op.tx.Create(op.user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return op.clearQueryCache()
|
||||
}
|
||||
if err := op.repo.db.Create(op.user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return op.clearQueryCache()
|
||||
}
|
||||
|
||||
func (op *UserCreateOperation) clearQueryCache() error {
|
||||
_, _ = op.repo.redis.Incr(context.Background(), key.KeyUserQueryVersion()).Result()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserDeleteOperation struct {
|
||||
repo *UserRepository
|
||||
id int64
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) Delete(id int64) *UserDeleteOperation {
|
||||
return &UserDeleteOperation{
|
||||
repo: r,
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserDeleteOperation) WithTx(tx *gorm.DB) *UserDeleteOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserDeleteOperation) Exec() error {
|
||||
if op.tx != nil {
|
||||
if err := op.tx.Delete(&model.User{}, op.id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return op.clearQueryCache()
|
||||
}
|
||||
if err := op.repo.db.Delete(&model.User{}, op.id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return op.clearQueryCache()
|
||||
}
|
||||
|
||||
func (op *UserDeleteOperation) clearQueryCache() error {
|
||||
_, _ = op.repo.redis.Incr(context.Background(), key.KeyUserQueryVersion()).Result()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserGetByEmailOperation struct {
|
||||
repo *UserRepository
|
||||
email string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByEmail(email string) *UserGetByEmailOperation {
|
||||
return &UserGetByEmailOperation{
|
||||
repo: r,
|
||||
email: email,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserGetByEmailOperation) WithTx(tx *gorm.DB) *UserGetByEmailOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserGetByEmailOperation) Exec() (*model.User, error) {
|
||||
var user model.User
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
err = op.tx.Where("email = ?", op.email).First(&user).Error
|
||||
} else {
|
||||
err = op.repo.db.Where("email = ?", op.email).First(&user).Error
|
||||
}
|
||||
|
||||
return &user, err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserGetByExternalIDOperation struct {
|
||||
repo *UserRepository
|
||||
externalID string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByExternalID(externalID string) *UserGetByExternalIDOperation {
|
||||
return &UserGetByExternalIDOperation{
|
||||
repo: r,
|
||||
externalID: externalID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserGetByExternalIDOperation) WithTx(tx *gorm.DB) *UserGetByExternalIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserGetByExternalIDOperation) Exec() (*model.User, error) {
|
||||
var user model.User
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
err = op.tx.Where("external_id = ?", op.externalID).First(&user).Error
|
||||
} else {
|
||||
err = op.repo.db.Where("external_id = ?", op.externalID).First(&user).Error
|
||||
}
|
||||
|
||||
return &user, err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserGetByIDOperation struct {
|
||||
repo *UserRepository
|
||||
id int64
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByID(id int64) *UserGetByIDOperation {
|
||||
return &UserGetByIDOperation{
|
||||
repo: r,
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserGetByIDOperation) WithTx(tx *gorm.DB) *UserGetByIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserGetByIDOperation) Exec() (*model.User, error) {
|
||||
var user model.User
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
err = op.tx.First(&user, op.id).Error
|
||||
} else {
|
||||
err = op.repo.db.First(&user, op.id).Error
|
||||
}
|
||||
|
||||
return &user, err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserGetIDByExternalIDOperation struct {
|
||||
repo *UserRepository
|
||||
externalID string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetIDByExternalID(externalID string) *UserGetIDByExternalIDOperation {
|
||||
return &UserGetIDByExternalIDOperation{
|
||||
repo: r,
|
||||
externalID: externalID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserGetIDByExternalIDOperation) WithTx(tx *gorm.DB) *UserGetIDByExternalIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserGetIDByExternalIDOperation) Exec() (int64, error) {
|
||||
var id int64
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
err = op.tx.Model(&model.User{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
|
||||
} else {
|
||||
err = op.repo.db.Model(&model.User{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
|
||||
}
|
||||
|
||||
return id, err
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserListOperation struct {
|
||||
repo *UserRepository
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) List() *UserListOperation {
|
||||
return &UserListOperation{
|
||||
repo: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserListOperation) WithTx(tx *gorm.DB) *UserListOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserListOperation) Exec() ([]model.User, error) {
|
||||
var users []model.User
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
err = op.tx.Find(&users).Error
|
||||
} else {
|
||||
err = op.repo.db.Find(&users).Error
|
||||
}
|
||||
|
||||
return users, err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ListUserByExternalOperation struct {
|
||||
repo *UserRepository
|
||||
externalIDList []string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) ListByExternal(externalIDList []string) *ListUserByExternalOperation {
|
||||
return &ListUserByExternalOperation{
|
||||
repo: r,
|
||||
externalIDList: externalIDList,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *ListUserByExternalOperation) WithTx(tx *gorm.DB) *ListUserByExternalOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *ListUserByExternalOperation) Exec() ([]model.User, error) {
|
||||
var users []model.User
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
err = op.tx.Where("external_id IN ?", op.externalIDList).Find(&users).Error
|
||||
} else {
|
||||
err = op.repo.db.Where("external_id IN ?", op.externalIDList).Find(&users).Error
|
||||
}
|
||||
|
||||
return users, err
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MGetUserByIDOperation struct {
|
||||
repo *UserRepository
|
||||
userIDs []int64
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) MGetUserByID(userIDs []int64) *MGetUserByIDOperation {
|
||||
return &MGetUserByIDOperation{
|
||||
repo: r,
|
||||
userIDs: userIDs,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *MGetUserByIDOperation) WithTx(tx *gorm.DB) *MGetUserByIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *MGetUserByIDOperation) Exec() (map[int64]*model.User, error) {
|
||||
if op.tx == nil {
|
||||
op.tx = op.repo.db
|
||||
}
|
||||
result := make(map[int64]*model.User)
|
||||
|
||||
if len(op.userIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var users []model.User
|
||||
var err error
|
||||
|
||||
err = op.tx.Where("id IN ?", op.userIDs).Find(&users).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
userCopy := user
|
||||
result[user.ID] = &userCopy
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserSearchOperation struct {
|
||||
repo *UserRepository
|
||||
query string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) Search(query string) *UserSearchOperation {
|
||||
return &UserSearchOperation{
|
||||
repo: r,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserSearchOperation) WithTx(tx *gorm.DB) *UserSearchOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserSearchOperation) Exec() ([]model.User, error) {
|
||||
var users []model.User
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
db := op.tx.Where("username LIKE ? OR email LIKE ?", "%"+op.query+"%", "%"+op.query+"%")
|
||||
err = db.Or("CAST(id AS CHAR) = ?", op.query).Find(&users).Error
|
||||
} else {
|
||||
db := op.repo.db.Where("username LIKE ? OR email LIKE ?", "%"+op.query+"%", "%"+op.query+"%")
|
||||
err = db.Or("CAST(id AS CHAR) = ?", op.query).Find(&users).Error
|
||||
}
|
||||
|
||||
return users, err
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserUpdateOperation struct {
|
||||
repo *UserRepository
|
||||
user *model.User
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) Update(user *model.User) *UserUpdateOperation {
|
||||
return &UserUpdateOperation{
|
||||
repo: r,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UserUpdateOperation) WithTx(tx *gorm.DB) *UserUpdateOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UserUpdateOperation) Exec() error {
|
||||
if op.tx != nil {
|
||||
if err := op.tx.Save(op.user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return op.clearQueryCache()
|
||||
}
|
||||
if err := op.repo.db.Save(op.user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return op.clearQueryCache()
|
||||
}
|
||||
|
||||
func (op *UserUpdateOperation) clearQueryCache() error {
|
||||
_, _ = op.repo.redis.Incr(context.Background(), key.KeyUserQueryVersion()).Result()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package user_repo
|
||||
|
||||
import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB, redis *redis.Client) *UserRepository {
|
||||
return &UserRepository{db: db, redis: redis}
|
||||
}
|
||||
Reference in New Issue
Block a user