migrate from github

This commit is contained in:
2026-07-15 22:42:31 +08:00
commit 3ad559f05e
262 changed files with 21637 additions and 0 deletions
+45
View File
@@ -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
}
+45
View File
@@ -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
}
+35
View File
@@ -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
}
+39
View File
@@ -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
}
+45
View File
@@ -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}
}