feat():learning后台管理项目初始化
This commit is contained in:
82
internal/storage/README.md
Normal file
82
internal/storage/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 存储层
|
||||
|
||||
本模块负责数据访问层的实现,提供数据库操作接口。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 统一的数据库连接管理
|
||||
- 自动数据库迁移
|
||||
- 接口化的数据访问层
|
||||
- 支持分页查询
|
||||
- 支持条件查询
|
||||
|
||||
## 模块结构
|
||||
|
||||
```
|
||||
storage/
|
||||
├── database.go # 数据库连接和迁移
|
||||
├── user_storage.go # 用户数据访问
|
||||
├── role_storage.go # 角色数据访问
|
||||
├── menu_storage.go # 菜单数据访问
|
||||
├── log_storage.go # 日志数据访问
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 初始化数据库
|
||||
|
||||
```go
|
||||
// 初始化数据库连接
|
||||
err := storage.InitDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 自动迁移数据库表
|
||||
err = storage.AutoMigrate()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### 使用存储接口
|
||||
|
||||
```go
|
||||
// 创建用户存储实例
|
||||
userStorage := storage.NewUserStorage()
|
||||
|
||||
// 创建用户
|
||||
user := &models.User{
|
||||
Username: "admin",
|
||||
Email: "admin@example.com",
|
||||
Password: "password",
|
||||
}
|
||||
err := userStorage.Create(user)
|
||||
|
||||
// 获取用户列表
|
||||
req := &models.UserListRequest{
|
||||
PageRequest: models.PageRequest{Page: 1, Size: 10},
|
||||
Username: "admin",
|
||||
}
|
||||
users, total, err := userStorage.List(req)
|
||||
```
|
||||
|
||||
## 接口设计
|
||||
|
||||
所有存储接口都遵循统一的模式:
|
||||
|
||||
- `Create()` - 创建记录
|
||||
- `GetByID()` - 根据ID获取记录
|
||||
- `Update()` - 更新记录
|
||||
- `Delete()` - 删除记录
|
||||
- `List()` - 获取列表(支持分页和条件查询)
|
||||
|
||||
## 数据库配置
|
||||
|
||||
数据库配置通过配置文件进行管理,支持以下配置项:
|
||||
|
||||
- `dsn` - 数据库连接字符串
|
||||
- `maxIdleConns` - 最大空闲连接数
|
||||
- `maxOpenConns` - 最大打开连接数
|
||||
|
||||
114
internal/storage/audit_log_storage.go
Normal file
114
internal/storage/audit_log_storage.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AuditLogStorage 审计日志存储接口
|
||||
type AuditLogStorage interface {
|
||||
Create(log *models.AuditLog) error
|
||||
GetByID(id uint) (*models.AuditLog, error)
|
||||
List(req *models.AuditLogListRequest) ([]models.AuditLog, int64, error)
|
||||
}
|
||||
|
||||
type auditLogStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAuditLogStorage 创建审计日志存储实例
|
||||
func NewAuditLogStorage() AuditLogStorage {
|
||||
return &auditLogStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建审计日志
|
||||
func (s *auditLogStorage) Create(log *models.AuditLog) error {
|
||||
// 如果操作时间为空,设置为当前时间
|
||||
if log.OperationTime.IsZero() {
|
||||
log.OperationTime = time.Now()
|
||||
}
|
||||
return s.db.Create(log).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取审计日志
|
||||
func (s *auditLogStorage) GetByID(id uint) (*models.AuditLog, error) {
|
||||
var log models.AuditLog
|
||||
err := s.db.First(&log, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// List 查询审计日志列表
|
||||
func (s *auditLogStorage) List(req *models.AuditLogListRequest) ([]models.AuditLog, int64, error) {
|
||||
var logs []models.AuditLog
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.AuditLog{})
|
||||
|
||||
// 操作类型筛选
|
||||
if req.OperationType != "" {
|
||||
query = query.Where("operation_type = ?", req.OperationType)
|
||||
}
|
||||
|
||||
// 操作人筛选
|
||||
if req.OperatorEmail != "" {
|
||||
query = query.Where("operator_email = ?", req.OperatorEmail)
|
||||
}
|
||||
|
||||
// 操作对象搜索(模糊匹配)
|
||||
if req.TargetEmail != "" {
|
||||
query = query.Where("target_email LIKE ?", "%"+req.TargetEmail+"%")
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if req.StartTime != "" {
|
||||
startTime, err := time.Parse("2006-01-02 15:04:05", req.StartTime)
|
||||
if err == nil {
|
||||
query = query.Where("operation_time >= ?", startTime)
|
||||
}
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
endTime, err := time.Parse("2006-01-02 15:04:05", req.EndTime)
|
||||
if err == nil {
|
||||
query = query.Where("operation_time <= ?", endTime)
|
||||
}
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 排序
|
||||
sortBy := req.SortBy
|
||||
if sortBy == "" {
|
||||
sortBy = "operation_time"
|
||||
}
|
||||
sortOrder := req.SortOrder
|
||||
if sortOrder == "" {
|
||||
sortOrder = "desc"
|
||||
}
|
||||
query = query.Order(sortBy + " " + sortOrder)
|
||||
|
||||
// 分页
|
||||
page := req.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
size := req.Size
|
||||
if size < 1 {
|
||||
size = 20
|
||||
}
|
||||
offset := (page - 1) * size
|
||||
query = query.Offset(offset).Limit(size)
|
||||
|
||||
// 查询
|
||||
if err := query.Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
22
internal/storage/balance_operation_log_storage.go
Normal file
22
internal/storage/balance_operation_log_storage.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BalanceOperationLogStorage interface {
|
||||
Create(log *models.BalanceOperationLog) error
|
||||
}
|
||||
|
||||
type balanceOperationLogStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewBalanceOperationLogStorage() BalanceOperationLogStorage {
|
||||
return &balanceOperationLogStorage{db: DB}
|
||||
}
|
||||
|
||||
func (s *balanceOperationLogStorage) Create(log *models.BalanceOperationLog) error {
|
||||
return s.db.Create(log).Error
|
||||
}
|
||||
226
internal/storage/database.go
Normal file
226
internal/storage/database.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"goalfymax-admin/internal/config"
|
||||
"goalfymax-admin/internal/models"
|
||||
"goalfymax-admin/pkg/utils"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
// GormLoggerAdapter GORM logger适配器
|
||||
type GormLoggerAdapter struct {
|
||||
logger *utils.Logger
|
||||
logLevel logger.LogLevel
|
||||
}
|
||||
|
||||
// LogMode 设置日志级别
|
||||
func (g *GormLoggerAdapter) LogMode(level logger.LogLevel) logger.Interface {
|
||||
g.logLevel = level
|
||||
return g
|
||||
}
|
||||
|
||||
// Info 记录信息日志
|
||||
func (g *GormLoggerAdapter) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||
g.logger.Info(msg, zap.Any("data", data))
|
||||
}
|
||||
|
||||
// Warn 记录警告日志
|
||||
func (g *GormLoggerAdapter) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||
g.logger.Warn(msg, zap.Any("data", data))
|
||||
}
|
||||
|
||||
// Error 记录错误日志
|
||||
func (g *GormLoggerAdapter) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||
g.logger.Error(msg, zap.Any("data", data))
|
||||
}
|
||||
|
||||
// Trace 记录跟踪日志
|
||||
func (g *GormLoggerAdapter) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||
// 根据日志级别决定是否记录
|
||||
if g.logLevel < logger.Info {
|
||||
return
|
||||
}
|
||||
|
||||
elapsed := time.Since(begin)
|
||||
sql, rows := fc()
|
||||
|
||||
if err != nil {
|
||||
g.logger.Error("SQL执行失败",
|
||||
zap.String("sql", sql),
|
||||
zap.Int64("rows", rows),
|
||||
zap.Duration("elapsed", elapsed),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
g.logger.Debug("SQL执行成功",
|
||||
zap.String("sql", sql),
|
||||
zap.Int64("rows", rows),
|
||||
zap.Duration("elapsed", elapsed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// InitDatabase 初始化数据库连接
|
||||
func InitDatabase(appLogger *utils.Logger) error {
|
||||
cfg := config.GetConfig()
|
||||
|
||||
// 配置数据库连接
|
||||
dbConfig := mysql.Config{
|
||||
DSN: cfg.Database.DSN,
|
||||
}
|
||||
|
||||
// 创建GORM logger适配器
|
||||
gormLogger := &GormLoggerAdapter{logger: appLogger}
|
||||
|
||||
// 根据配置设置日志级别
|
||||
switch cfg.Database.LogLevel {
|
||||
case "silent":
|
||||
gormLogger.LogMode(logger.Silent)
|
||||
case "error":
|
||||
gormLogger.LogMode(logger.Error)
|
||||
case "warn":
|
||||
gormLogger.LogMode(logger.Warn)
|
||||
case "info":
|
||||
gormLogger.LogMode(logger.Info)
|
||||
default:
|
||||
gormLogger.LogMode(logger.Info)
|
||||
}
|
||||
|
||||
// 连接数据库
|
||||
db, err := gorm.Open(mysql.Open(dbConfig.DSN), &gorm.Config{
|
||||
Logger: gormLogger,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置连接池
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取数据库实例失败: %w", err)
|
||||
}
|
||||
|
||||
sqlDB.SetMaxIdleConns(cfg.Database.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(cfg.Database.MaxOpenConns)
|
||||
|
||||
DB = db
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoMigrate 自动迁移数据库表
|
||||
func AutoMigrate() error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("数据库未初始化")
|
||||
}
|
||||
|
||||
// 迁移所有模型
|
||||
err := DB.AutoMigrate(
|
||||
&models.UserLevelConfig{},
|
||||
&models.GoalfyMaxUser{},
|
||||
&models.BalanceOperationLog{},
|
||||
&models.InviteCodeApplication{},
|
||||
//&models.AuditLog{},
|
||||
//&models.User{},
|
||||
//&models.Role{},
|
||||
//&models.Permission{},
|
||||
//&models.Menu{},
|
||||
// &models.SystemConfig{},
|
||||
//&models.LoginLog{},
|
||||
//&models.OperationLog{},
|
||||
//&models.UserProjectQuota{},
|
||||
//&models.PKCEState{},
|
||||
//&models.LoginInfo{},
|
||||
//&models.User{},
|
||||
//&models.Role{},
|
||||
//&models.Permission{},
|
||||
//&models.Menu{},
|
||||
//&models.SystemConfig{},
|
||||
//&models.LoginLog{},
|
||||
//&models.OperationLog{},
|
||||
//&models.UserProjectQuota{},
|
||||
//&models.PKCEState{},
|
||||
//&models.LoginInfo{},
|
||||
// &models.InviteCode{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("数据库迁移失败: %w", err)
|
||||
}
|
||||
|
||||
// 初始化默认用户等级配置
|
||||
if err := initDefaultUserLevelConfigs(); err != nil {
|
||||
return fmt.Errorf("初始化默认用户等级配置失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initDefaultUserLevelConfigs 初始化默认用户等级配置
|
||||
func initDefaultUserLevelConfigs() error {
|
||||
// 检查是否已存在配置
|
||||
var count int64
|
||||
if err := DB.Model(&models.UserLevelConfig{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果已存在配置,则跳过
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建默认配置
|
||||
defaultConfigs := []models.UserLevelConfig{
|
||||
{
|
||||
LevelName: "普通",
|
||||
LevelCode: "normal",
|
||||
ProjectLimit: 2,
|
||||
Description: "普通用户等级,可创建2个项目",
|
||||
SortOrder: 1,
|
||||
Status: 1,
|
||||
},
|
||||
{
|
||||
LevelName: "VIP",
|
||||
LevelCode: "vip",
|
||||
ProjectLimit: 10,
|
||||
Description: "VIP用户等级,可创建10个项目",
|
||||
SortOrder: 2,
|
||||
Status: 1,
|
||||
},
|
||||
{
|
||||
LevelName: "内部",
|
||||
LevelCode: "internal",
|
||||
ProjectLimit: 0,
|
||||
Description: "内部用户等级,无项目数限制",
|
||||
SortOrder: 3,
|
||||
Status: 1,
|
||||
},
|
||||
}
|
||||
|
||||
return DB.Create(&defaultConfigs).Error
|
||||
}
|
||||
|
||||
// GetDB 获取数据库实例
|
||||
func GetDB() *gorm.DB {
|
||||
return DB
|
||||
}
|
||||
|
||||
// Close 关闭数据库连接
|
||||
func Close() error {
|
||||
if DB != nil {
|
||||
sqlDB, err := DB.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
123
internal/storage/goalfymax_user_storage.go
Normal file
123
internal/storage/goalfymax_user_storage.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GoalfyMaxUserStorage interface {
|
||||
Create(user *models.GoalfyMaxUser) error
|
||||
GetByID(id uint) (*models.GoalfyMaxUser, error)
|
||||
GetByUserID(userID int) (*models.GoalfyMaxUser, error)
|
||||
GetByUsername(username string) (*models.GoalfyMaxUser, error)
|
||||
GetByEmail(email string) (*models.GoalfyMaxUser, error)
|
||||
Update(user *models.GoalfyMaxUser) error
|
||||
Delete(id uint) error
|
||||
List(req *models.GoalfyMaxUserListRequest) ([]models.GoalfyMaxUser, int64, error)
|
||||
SetBanned(id uint, reason string, adminID int) error
|
||||
Unban(id uint) error
|
||||
}
|
||||
|
||||
type goalfyMaxUserStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewGoalfyMaxUserStorage() GoalfyMaxUserStorage {
|
||||
return &goalfyMaxUserStorage{db: DB}
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) Create(user *models.GoalfyMaxUser) error {
|
||||
return s.db.Create(user).Error
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) GetByID(id uint) (*models.GoalfyMaxUser, error) {
|
||||
var user models.GoalfyMaxUser
|
||||
if err := s.db.First(&user, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) GetByUserID(userID int) (*models.GoalfyMaxUser, error) {
|
||||
var user models.GoalfyMaxUser
|
||||
if err := s.db.Where("user_id = ?", userID).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) GetByUsername(username string) (*models.GoalfyMaxUser, error) {
|
||||
var user models.GoalfyMaxUser
|
||||
if err := s.db.Where("username = ?", username).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) GetByEmail(email string) (*models.GoalfyMaxUser, error) {
|
||||
var user models.GoalfyMaxUser
|
||||
if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) Update(user *models.GoalfyMaxUser) error {
|
||||
return s.db.Save(user).Error
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.GoalfyMaxUser{}, id).Error
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) List(req *models.GoalfyMaxUserListRequest) ([]models.GoalfyMaxUser, int64, error) {
|
||||
var users []models.GoalfyMaxUser
|
||||
var total int64
|
||||
|
||||
q := s.db.Model(&models.GoalfyMaxUser{})
|
||||
if req.Username != "" {
|
||||
q = q.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.Email != "" {
|
||||
q = q.Where("email LIKE ?", "%"+req.Email+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
if *req.Status == 0 {
|
||||
q = q.Where("is_banned = ?", true)
|
||||
} else if *req.Status == 1 {
|
||||
q = q.Where("is_banned = ?", false)
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (req.Page - 1) * req.Size
|
||||
if err := q.Offset(offset).Limit(req.Size).Order("id DESC").Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) SetBanned(id uint, reason string, adminID int) error {
|
||||
return s.db.Model(&models.GoalfyMaxUser{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"is_banned": true,
|
||||
"ban_reason": reason,
|
||||
"banned_by": adminID,
|
||||
"banned_at": gorm.Expr("NOW()"),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (s *goalfyMaxUserStorage) Unban(id uint) error {
|
||||
return s.db.Model(&models.GoalfyMaxUser{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"is_banned": false,
|
||||
"ban_reason": "",
|
||||
"banned_by": 0,
|
||||
"banned_at": nil,
|
||||
}).Error
|
||||
}
|
||||
155
internal/storage/invite_code.go
Normal file
155
internal/storage/invite_code.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type InviteCodeStorage interface {
|
||||
Create(inviteCode *models.InviteCode) error
|
||||
GetByID(id uint) (*models.InviteCode, error)
|
||||
GetByCode(code string) (*models.InviteCode, error)
|
||||
List(req *models.InviteCodeListRequest) ([]models.InviteCode, int64, error)
|
||||
Update(inviteCode *models.InviteCode) error
|
||||
Delete(id uint) error
|
||||
GetStatistics() (*models.InviteCodeStatistics, error)
|
||||
IsExpired(inviteCode *models.InviteCode) bool
|
||||
}
|
||||
|
||||
type inviteCodeStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewInviteCodeStorage() InviteCodeStorage {
|
||||
return &inviteCodeStorage{db: DB}
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) Create(inviteCode *models.InviteCode) error {
|
||||
// 若目标库缺少 is_used 列,则在插入时省略该列,避免 Unknown column 错误
|
||||
if columnExistsIsUsed(s.db) {
|
||||
return s.db.Create(inviteCode).Error
|
||||
}
|
||||
return s.db.Omit("is_used").Create(inviteCode).Error
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) GetByID(id uint) (*models.InviteCode, error) {
|
||||
var inviteCode models.InviteCode
|
||||
err := s.db.Where("deleted_at IS NULL").First(&inviteCode, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inviteCode, nil
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) GetByCode(code string) (*models.InviteCode, error) {
|
||||
var inviteCode models.InviteCode
|
||||
err := s.db.Where("code = ? AND deleted_at IS NULL", code).First(&inviteCode).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inviteCode, nil
|
||||
}
|
||||
|
||||
// IsExpired 检查邀请码是否过期
|
||||
func (s *inviteCodeStorage) IsExpired(inviteCode *models.InviteCode) bool {
|
||||
if inviteCode.ExpiresAt == nil {
|
||||
return false // 没有设置过期时间,永不过期
|
||||
}
|
||||
return time.Now().After(*inviteCode.ExpiresAt)
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) List(req *models.InviteCodeListRequest) ([]models.InviteCode, int64, error) {
|
||||
var inviteCodes []models.InviteCode
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.InviteCode{}).Where("deleted_at IS NULL")
|
||||
|
||||
// 筛选条件
|
||||
if req.Code != "" {
|
||||
query = query.Where("code LIKE ?", "%"+req.Code+"%")
|
||||
}
|
||||
// 仅当存在 is_used 列时才应用过滤
|
||||
if req.IsUsed != nil {
|
||||
if columnExistsIsUsed(s.db) {
|
||||
query = query.Where("is_used = ?", *req.IsUsed)
|
||||
}
|
||||
}
|
||||
if req.StartTime != "" {
|
||||
query = query.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
query = query.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
err := query.Order("created_at DESC").Offset(offset).Limit(req.Size).Find(&inviteCodes).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return inviteCodes, total, nil
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) Update(inviteCode *models.InviteCode) error {
|
||||
return s.db.Save(inviteCode).Error
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.InviteCode{}, id).Error
|
||||
}
|
||||
|
||||
func (s *inviteCodeStorage) GetStatistics() (*models.InviteCodeStatistics, error) {
|
||||
var stats models.InviteCodeStatistics
|
||||
|
||||
// 总数
|
||||
var total int64
|
||||
if err := s.db.Model(&models.InviteCode{}).Where("deleted_at IS NULL").Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.Total = int(total)
|
||||
|
||||
// is_used 列可能不存在,存在时统计已使用/未使用
|
||||
if columnExistsIsUsed(s.db) {
|
||||
var used int64
|
||||
if err := s.db.Model(&models.InviteCode{}).Where("deleted_at IS NULL AND is_used = ?", true).Count(&used).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.Used = int(used)
|
||||
|
||||
var unused int64
|
||||
if err := s.db.Model(&models.InviteCode{}).Where("deleted_at IS NULL AND is_used = ?", false).Count(&unused).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.Unused = int(unused)
|
||||
} else {
|
||||
// 列不存在时,给出合理默认值
|
||||
stats.Used = 0
|
||||
stats.Unused = int(total)
|
||||
}
|
||||
|
||||
// 今日新增
|
||||
today := time.Now().Format("2006-01-02")
|
||||
var todayCreated int64
|
||||
if err := s.db.Model(&models.InviteCode{}).Where("deleted_at IS NULL AND DATE(created_at) = ?", today).Count(&todayCreated).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.TodayCreated = int(todayCreated)
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// columnExistsIsUsed 检查当前数据库中 admin_invite_codes 表是否存在 is_used 列
|
||||
func columnExistsIsUsed(db *gorm.DB) bool {
|
||||
var count int64
|
||||
// 使用当前连接的数据库名
|
||||
db.Raw("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'is_used'", "admin_invite_codes").Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
120
internal/storage/log_storage.go
Normal file
120
internal/storage/log_storage.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LogStorage 日志存储接口
|
||||
type LogStorage interface {
|
||||
CreateLoginLog(log *models.LoginLog) error
|
||||
CreateOperationLog(log *models.OperationLog) error
|
||||
GetLoginLogs(req *models.LoginLogListRequest) ([]models.LoginLog, int64, error)
|
||||
GetOperationLogs(req *models.OperationLogListRequest) ([]models.OperationLog, int64, error)
|
||||
DeleteLoginLogs(beforeDate string) error
|
||||
DeleteOperationLogs(beforeDate string) error
|
||||
}
|
||||
|
||||
type logStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewLogStorage 创建日志存储实例
|
||||
func NewLogStorage() LogStorage {
|
||||
return &logStorage{db: DB}
|
||||
}
|
||||
|
||||
// CreateLoginLog 创建登录日志
|
||||
func (s *logStorage) CreateLoginLog(log *models.LoginLog) error {
|
||||
return s.db.Create(log).Error
|
||||
}
|
||||
|
||||
// CreateOperationLog 创建操作日志
|
||||
func (s *logStorage) CreateOperationLog(log *models.OperationLog) error {
|
||||
return s.db.Create(log).Error
|
||||
}
|
||||
|
||||
// GetLoginLogs 获取登录日志列表
|
||||
func (s *logStorage) GetLoginLogs(req *models.LoginLogListRequest) ([]models.LoginLog, int64, error) {
|
||||
var logs []models.LoginLog
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.LoginLog{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.Username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.IP != "" {
|
||||
query = query.Where("ip LIKE ?", "%"+req.IP+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
if req.StartTime != "" {
|
||||
query = query.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
query = query.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
err := query.Order("created_at DESC").Offset(offset).Limit(req.Size).Find(&logs).Error
|
||||
|
||||
return logs, total, err
|
||||
}
|
||||
|
||||
// GetOperationLogs 获取操作日志列表
|
||||
func (s *logStorage) GetOperationLogs(req *models.OperationLogListRequest) ([]models.OperationLog, int64, error) {
|
||||
var logs []models.OperationLog
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.OperationLog{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.Username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.Module != "" {
|
||||
query = query.Where("module LIKE ?", "%"+req.Module+"%")
|
||||
}
|
||||
if req.Operation != "" {
|
||||
query = query.Where("operation LIKE ?", "%"+req.Operation+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
if req.StartTime != "" {
|
||||
query = query.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
query = query.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
err := query.Order("created_at DESC").Offset(offset).Limit(req.Size).Find(&logs).Error
|
||||
|
||||
return logs, total, err
|
||||
}
|
||||
|
||||
// DeleteLoginLogs 删除指定日期之前的登录日志
|
||||
func (s *logStorage) DeleteLoginLogs(beforeDate string) error {
|
||||
return s.db.Where("created_at < ?", beforeDate).Delete(&models.LoginLog{}).Error
|
||||
}
|
||||
|
||||
// DeleteOperationLogs 删除指定日期之前的操作日志
|
||||
func (s *logStorage) DeleteOperationLogs(beforeDate string) error {
|
||||
return s.db.Where("created_at < ?", beforeDate).Delete(&models.OperationLog{}).Error
|
||||
}
|
||||
154
internal/storage/message_push_storage.go
Normal file
154
internal/storage/message_push_storage.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"goalfymax-admin/internal/models"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MessagePushStorage 消息推送存储接口
|
||||
type MessagePushStorage interface {
|
||||
Create(ctx context.Context, log *models.MessagePushLog) error
|
||||
List(ctx context.Context, req *models.MessagePushListRequest) ([]models.MessagePushLog, int64, error)
|
||||
GetByID(ctx context.Context, id int64) (*models.MessagePushLog, error)
|
||||
UpdateStatus(ctx context.Context, id int64, status int, successCount, failCount int, errorMessage string) error
|
||||
SearchUsers(ctx context.Context, keyword string, limit int) ([]models.UserSearchItem, error)
|
||||
}
|
||||
|
||||
type messagePushStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewMessagePushStorage 创建消息推送存储实例
|
||||
func NewMessagePushStorage() MessagePushStorage {
|
||||
return &messagePushStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建推送记录
|
||||
func (s *messagePushStorage) Create(ctx context.Context, log *models.MessagePushLog) error {
|
||||
if err := s.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
return fmt.Errorf("创建推送记录失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 获取推送记录列表
|
||||
func (s *messagePushStorage) List(ctx context.Context, req *models.MessagePushListRequest) ([]models.MessagePushLog, int64, error) {
|
||||
var logs []models.MessagePushLog
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&models.MessagePushLog{})
|
||||
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
if req.SenderID != nil {
|
||||
query = query.Where("sender_id = ?", *req.SenderID)
|
||||
}
|
||||
if req.StartTime != "" {
|
||||
query = query.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
query = query.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("查询总数失败: %w", err)
|
||||
}
|
||||
|
||||
if req.Page > 0 && req.PageSize > 0 {
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
query = query.Offset(offset).Limit(req.PageSize)
|
||||
}
|
||||
|
||||
if err := query.Order("created_at DESC").Find(&logs).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("查询推送记录失败: %w", err)
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取推送记录
|
||||
func (s *messagePushStorage) GetByID(ctx context.Context, id int64) (*models.MessagePushLog, error) {
|
||||
var log models.MessagePushLog
|
||||
if err := s.db.WithContext(ctx).First(&log, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("推送记录不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取推送记录失败: %w", err)
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新推送状态
|
||||
func (s *messagePushStorage) UpdateStatus(ctx context.Context, id int64, status int, successCount, failCount int, errorMessage string) error {
|
||||
updates := map[string]interface{}{
|
||||
"status": status,
|
||||
"success_count": successCount,
|
||||
"fail_count": failCount,
|
||||
}
|
||||
|
||||
if errorMessage != "" {
|
||||
updates["error_message"] = errorMessage
|
||||
}
|
||||
|
||||
if status == 2 || status == 3 { // 发送成功或失败时更新发送时间
|
||||
now := time.Now()
|
||||
updates["sent_at"] = &now
|
||||
}
|
||||
|
||||
if err := s.db.WithContext(ctx).Model(&models.MessagePushLog{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
return fmt.Errorf("更新推送状态失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchUsers 搜索用户
|
||||
func (s *messagePushStorage) SearchUsers(ctx context.Context, keyword string, limit int) ([]models.UserSearchItem, error) {
|
||||
var users []models.UserSearchItem
|
||||
|
||||
// 从admin_goalfymax_users表搜索用户
|
||||
query := s.db.WithContext(ctx).Table("admin_goalfymax_users").
|
||||
Select("user_id as id, username, email").
|
||||
Where("deleted_at IS NULL") // 排除已删除的用户
|
||||
|
||||
// 如果有关键词,添加搜索条件
|
||||
if keyword != "" {
|
||||
query = query.Where("username LIKE ? OR email LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
|
||||
query = query.Limit(limit)
|
||||
|
||||
// 添加调试日志
|
||||
fmt.Printf("🔍 [SearchUsers] 搜索关键词: %s, 限制: %d\n", keyword, limit)
|
||||
|
||||
if err := query.Find(&users).Error; err != nil {
|
||||
fmt.Printf("❌ [SearchUsers] 查询失败: %v\n", err)
|
||||
return nil, fmt.Errorf("搜索用户失败: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ [SearchUsers] 找到 %d 个用户\n", len(users))
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// ParseTargetUsers 解析目标用户JSON
|
||||
func ParseTargetUsers(targetUsersJSON string) ([]int, error) {
|
||||
var userIDs []int
|
||||
if err := json.Unmarshal([]byte(targetUsersJSON), &userIDs); err != nil {
|
||||
return nil, fmt.Errorf("解析目标用户失败: %w", err)
|
||||
}
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
// SerializeTargetUsers 序列化目标用户为JSON
|
||||
func SerializeTargetUsers(userIDs []int) (string, error) {
|
||||
jsonData, err := json.Marshal(userIDs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("序列化目标用户失败: %w", err)
|
||||
}
|
||||
return string(jsonData), nil
|
||||
}
|
||||
101
internal/storage/page_storage.go
Normal file
101
internal/storage/page_storage.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PageStorage 页面存储接口
|
||||
type PageStorage interface {
|
||||
Create(page *models.Page) error
|
||||
GetByID(id uint) (*models.Page, error)
|
||||
GetByPath(path string) (*models.Page, error)
|
||||
Update(page *models.Page) error
|
||||
Delete(id uint) error
|
||||
List(req *models.PageListRequest) ([]models.Page, int64, error)
|
||||
}
|
||||
|
||||
type pageStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPageStorage 创建页面存储实例
|
||||
func NewPageStorage() PageStorage {
|
||||
return &pageStorage{
|
||||
db: DB,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建页面
|
||||
func (s *pageStorage) Create(page *models.Page) error {
|
||||
return s.db.Create(page).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取页面
|
||||
func (s *pageStorage) GetByID(id uint) (*models.Page, error) {
|
||||
var page models.Page
|
||||
err := s.db.Where("id = ? AND deleted_at IS NULL", id).First(&page).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &page, nil
|
||||
}
|
||||
|
||||
// GetByPath 根据路径获取页面
|
||||
func (s *pageStorage) GetByPath(path string) (*models.Page, error) {
|
||||
var page models.Page
|
||||
err := s.db.Where("path = ? AND deleted_at IS NULL", path).First(&page).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &page, nil
|
||||
}
|
||||
|
||||
// Update 更新页面
|
||||
func (s *pageStorage) Update(page *models.Page) error {
|
||||
return s.db.Save(page).Error
|
||||
}
|
||||
|
||||
// Delete 删除页面
|
||||
func (s *pageStorage) Delete(id uint) error {
|
||||
return s.db.Where("id = ?", id).Delete(&models.Page{}).Error
|
||||
}
|
||||
|
||||
// List 获取页面列表
|
||||
func (s *pageStorage) List(req *models.PageListRequest) ([]models.Page, int64, error) {
|
||||
var pages []models.Page
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.Page{}).Where("deleted_at IS NULL")
|
||||
|
||||
// 应用过滤条件
|
||||
if req.Name != "" {
|
||||
query = query.Where("name LIKE ?", "%"+req.Name+"%")
|
||||
}
|
||||
if req.Path != "" {
|
||||
query = query.Where("path LIKE ?", "%"+req.Path+"%")
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
query = query.Where("is_active = ?", *req.IsActive)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
err := query.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用分页(如果没有指定分页参数,返回所有数据)
|
||||
if req.Page > 0 && req.Size > 0 {
|
||||
offset := (req.Page - 1) * req.Size
|
||||
err = query.Order("sort_order ASC, id ASC").Offset(offset).Limit(req.Size).Find(&pages).Error
|
||||
} else {
|
||||
// 不分页,返回所有数据
|
||||
err = query.Order("sort_order ASC, id ASC").Find(&pages).Error
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return pages, total, nil
|
||||
}
|
||||
69
internal/storage/postgres.go
Normal file
69
internal/storage/postgres.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"goalfymax-admin/internal/config"
|
||||
"goalfymax-admin/pkg/utils"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var PG *gorm.DB
|
||||
|
||||
// InitPostgres 初始化PostgreSQL连接
|
||||
func InitPostgres(appLogger *utils.Logger) error {
|
||||
cfg := config.GetConfig()
|
||||
pg := cfg.PostgreSQL
|
||||
// 兼容 DSN 或字段拼接
|
||||
dsn := pg.DSN
|
||||
if dsn == "" {
|
||||
ssl := pg.SSLMode
|
||||
if ssl == "" {
|
||||
ssl = "disable"
|
||||
}
|
||||
dsn = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s TimeZone=UTC",
|
||||
pg.Host, pg.Port, pg.User, pg.Password, pg.DBName, ssl,
|
||||
)
|
||||
}
|
||||
|
||||
lw := log.New(os.Stdout, "", log.LstdFlags)
|
||||
gormLogger := logger.New(lw, logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond,
|
||||
LogLevel: logger.Info,
|
||||
IgnoreRecordNotFoundError: true,
|
||||
ParameterizedQueries: true,
|
||||
Colorful: false,
|
||||
})
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: gormLogger})
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接PostgreSQL失败: %w", err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取PostgreSQL实例失败: %w", err)
|
||||
}
|
||||
if pg.MaxOpenConns > 0 {
|
||||
sqlDB.SetMaxOpenConns(pg.MaxOpenConns)
|
||||
}
|
||||
if pg.MaxIdleConns > 0 {
|
||||
sqlDB.SetMaxIdleConns(pg.MaxIdleConns)
|
||||
}
|
||||
if pg.ConnMaxLifetime > 0 {
|
||||
sqlDB.SetConnMaxLifetime(pg.ConnMaxLifetime)
|
||||
}
|
||||
if pg.ConnMaxIdleTime > 0 {
|
||||
sqlDB.SetConnMaxIdleTime(pg.ConnMaxIdleTime)
|
||||
}
|
||||
|
||||
PG = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetPG() *gorm.DB { return PG }
|
||||
117
internal/storage/rbac_storage.go
Normal file
117
internal/storage/rbac_storage.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RBACStorage 简化的RBAC存储接口
|
||||
type RBACStorage interface {
|
||||
// 角色页面权限管理
|
||||
AssignRolePagePermissions(roleID uint, pageIDs []uint) error
|
||||
RemoveRolePagePermissions(roleID uint, pageIDs []uint) error
|
||||
GetRolePagePermissions(roleID uint) ([]models.Page, error)
|
||||
GetRolePagePermissionIDs(roleID uint) ([]uint, error)
|
||||
|
||||
// 页面权限检查
|
||||
CheckUserRolePagePermission(userID uint, pagePath string) (bool, error)
|
||||
GetUserRoleAccessiblePages(userID uint) ([]string, error)
|
||||
|
||||
// 角色管理
|
||||
GetDefaultRoleID(roleID *uint) error
|
||||
GetRoleByID(roleID uint) (*models.Role, error)
|
||||
}
|
||||
|
||||
type rbacStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRBACStorage 创建RBAC存储实例
|
||||
func NewRBACStorage() RBACStorage {
|
||||
return &rbacStorage{db: DB}
|
||||
}
|
||||
|
||||
// AssignRolePagePermissions 分配角色页面权限
|
||||
func (s *rbacStorage) AssignRolePagePermissions(roleID uint, pageIDs []uint) error {
|
||||
var rolePagePermissions []models.RolePagePermission
|
||||
for _, pageID := range pageIDs {
|
||||
rolePagePermissions = append(rolePagePermissions, models.RolePagePermission{
|
||||
RoleID: roleID,
|
||||
PageID: pageID,
|
||||
})
|
||||
}
|
||||
return s.db.Create(&rolePagePermissions).Error
|
||||
}
|
||||
|
||||
// RemoveRolePagePermissions 移除角色页面权限
|
||||
func (s *rbacStorage) RemoveRolePagePermissions(roleID uint, pageIDs []uint) error {
|
||||
return s.db.Where("role_id = ? AND page_id IN ?", roleID, pageIDs).Delete(&models.RolePagePermission{}).Error
|
||||
}
|
||||
|
||||
// GetRolePagePermissions 获取角色页面权限
|
||||
func (s *rbacStorage) GetRolePagePermissions(roleID uint) ([]models.Page, error) {
|
||||
var pages []models.Page
|
||||
err := s.db.Table("admin_pages").
|
||||
Joins("JOIN admin_role_page_permissions ON admin_pages.id = admin_role_page_permissions.page_id").
|
||||
Where("admin_role_page_permissions.role_id = ? AND admin_role_page_permissions.deleted_at IS NULL", roleID).
|
||||
Find(&pages).Error
|
||||
return pages, err
|
||||
}
|
||||
|
||||
// GetRolePagePermissionIDs 获取角色页面权限ID列表
|
||||
func (s *rbacStorage) GetRolePagePermissionIDs(roleID uint) ([]uint, error) {
|
||||
var pageIDs []uint
|
||||
err := s.db.Model(&models.RolePagePermission{}).
|
||||
Where("role_id = ?", roleID).
|
||||
Pluck("page_id", &pageIDs).Error
|
||||
return pageIDs, err
|
||||
}
|
||||
|
||||
// CheckUserRolePagePermission 检查用户基于角色的页面权限
|
||||
func (s *rbacStorage) CheckUserRolePagePermission(userID uint, pagePath string) (bool, error) {
|
||||
var count int64
|
||||
err := s.db.Table("admin_users").
|
||||
Joins("JOIN admin_role_page_permissions ON admin_users.role_id = admin_role_page_permissions.role_id").
|
||||
Joins("JOIN admin_pages ON admin_role_page_permissions.page_id = admin_pages.id").
|
||||
Where("admin_users.id = ? AND admin_pages.path = ? AND admin_pages.is_active = TRUE AND admin_users.deleted_at IS NULL AND admin_role_page_permissions.deleted_at IS NULL", userID, pagePath).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// GetUserRoleAccessiblePages 获取用户基于角色的可访问页面
|
||||
func (s *rbacStorage) GetUserRoleAccessiblePages(userID uint) ([]string, error) {
|
||||
var pages []string
|
||||
|
||||
// 添加调试日志
|
||||
fmt.Printf("🔍 [RBACStorage] 查询用户 %d 的可访问页面\n", userID)
|
||||
|
||||
err := s.db.Table("admin_users").
|
||||
Joins("JOIN admin_role_page_permissions ON admin_users.role_id = admin_role_page_permissions.role_id").
|
||||
Joins("JOIN admin_pages ON admin_role_page_permissions.page_id = admin_pages.id").
|
||||
Where("admin_users.id = ? AND admin_pages.is_active = TRUE AND admin_users.deleted_at IS NULL AND admin_role_page_permissions.deleted_at IS NULL", userID).
|
||||
Select("DISTINCT admin_pages.path").
|
||||
Pluck("admin_pages.path", &pages).Error
|
||||
|
||||
fmt.Printf("🔍 [RBACStorage] 用户 %d 可访问页面: %v\n", userID, pages)
|
||||
|
||||
return pages, err
|
||||
}
|
||||
|
||||
// GetDefaultRoleID 获取默认角色ID
|
||||
func (s *rbacStorage) GetDefaultRoleID(roleID *uint) error {
|
||||
return s.db.Table("admin_roles").
|
||||
Where("is_default = TRUE AND deleted_at IS NULL").
|
||||
Select("id").
|
||||
First(roleID).Error
|
||||
}
|
||||
|
||||
// GetRoleByID 根据ID获取角色
|
||||
func (s *rbacStorage) GetRoleByID(roleID uint) (*models.Role, error) {
|
||||
var role models.Role
|
||||
err := s.db.Where("id = ? AND deleted_at IS NULL", roleID).First(&role).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
93
internal/storage/role_storage.go
Normal file
93
internal/storage/role_storage.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RoleStorage 角色存储接口
|
||||
type RoleStorage interface {
|
||||
Create(role *models.Role) error
|
||||
GetByID(id uint) (*models.Role, error)
|
||||
GetByName(name string) (*models.Role, error)
|
||||
Update(role *models.Role) error
|
||||
Delete(id uint) error
|
||||
List(req *models.RoleListRequest) ([]models.Role, int64, error)
|
||||
UpdateStatus(id uint, status int) error
|
||||
}
|
||||
|
||||
type roleStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRoleStorage 创建角色存储实例
|
||||
func NewRoleStorage() RoleStorage {
|
||||
return &roleStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建角色
|
||||
func (s *roleStorage) Create(role *models.Role) error {
|
||||
return s.db.Create(role).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取角色
|
||||
func (s *roleStorage) GetByID(id uint) (*models.Role, error) {
|
||||
var role models.Role
|
||||
err := s.db.First(&role, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// GetByName 根据名称获取角色
|
||||
func (s *roleStorage) GetByName(name string) (*models.Role, error) {
|
||||
var role models.Role
|
||||
err := s.db.Where("name = ?", name).First(&role).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// Update 更新角色
|
||||
func (s *roleStorage) Update(role *models.Role) error {
|
||||
return s.db.Save(role).Error
|
||||
}
|
||||
|
||||
// Delete 删除角色
|
||||
func (s *roleStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.Role{}, id).Error
|
||||
}
|
||||
|
||||
// List 获取角色列表
|
||||
func (s *roleStorage) List(req *models.RoleListRequest) ([]models.Role, int64, error) {
|
||||
var roles []models.Role
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.Role{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.Name != "" {
|
||||
query = query.Where("name LIKE ?", "%"+req.Name+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
err := query.Offset(offset).Limit(req.Size).Find(&roles).Error
|
||||
|
||||
return roles, total, err
|
||||
}
|
||||
|
||||
// UpdateStatus 更新角色状态
|
||||
func (s *roleStorage) UpdateStatus(id uint, status int) error {
|
||||
return s.db.Model(&models.Role{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
124
internal/storage/sso_storage.go
Normal file
124
internal/storage/sso_storage.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PKCEStateStorage PKCE状态存储接口
|
||||
type PKCEStateStorage interface {
|
||||
Create(pkceState *models.PKCEState) error
|
||||
GetByState(state string) (*models.PKCEState, error)
|
||||
DeleteByState(state string) error
|
||||
CleanExpired() error
|
||||
}
|
||||
|
||||
// LoginInfoStorage 登录信息存储接口
|
||||
type LoginInfoStorage interface {
|
||||
Create(loginInfo *models.LoginInfo) error
|
||||
GetByUserID(userID int) (*models.LoginInfo, error)
|
||||
GetByUserIDAndUUID(userID int, uuid string) (*models.LoginInfo, error)
|
||||
Update(loginInfo *models.LoginInfo) error
|
||||
SetUserOffline(userID int) error
|
||||
ListOnlineUsers() ([]*models.LoginInfo, error)
|
||||
CountOnlineUsers() (int64, error)
|
||||
DeleteByUserID(userID int) error
|
||||
}
|
||||
|
||||
type pkceStateStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPKCEStateStorage 创建PKCE状态存储实例
|
||||
func NewPKCEStateStorage() PKCEStateStorage {
|
||||
return &pkceStateStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建PKCE状态
|
||||
func (s *pkceStateStorage) Create(pkceState *models.PKCEState) error {
|
||||
return s.db.Create(pkceState).Error
|
||||
}
|
||||
|
||||
// GetByState 根据状态获取PKCE状态
|
||||
func (s *pkceStateStorage) GetByState(state string) (*models.PKCEState, error) {
|
||||
var pkceState models.PKCEState
|
||||
err := s.db.Where("state = ?", state).First(&pkceState).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pkceState, nil
|
||||
}
|
||||
|
||||
// DeleteByState 根据状态删除PKCE状态
|
||||
func (s *pkceStateStorage) DeleteByState(state string) error {
|
||||
return s.db.Where("state = ?", state).Delete(&models.PKCEState{}).Error
|
||||
}
|
||||
|
||||
// CleanExpired 清理过期的PKCE状态
|
||||
func (s *pkceStateStorage) CleanExpired() error {
|
||||
// 删除创建时间超过1小时的记录
|
||||
return s.db.Where("created_at < ?", "NOW() - INTERVAL 1 HOUR").Delete(&models.PKCEState{}).Error
|
||||
}
|
||||
|
||||
type loginInfoStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewLoginInfoStorage 创建登录信息存储实例
|
||||
func NewLoginInfoStorage() LoginInfoStorage {
|
||||
return &loginInfoStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建登录信息
|
||||
func (s *loginInfoStorage) Create(loginInfo *models.LoginInfo) error {
|
||||
return s.db.Create(loginInfo).Error
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取登录信息
|
||||
func (s *loginInfoStorage) GetByUserID(userID int) (*models.LoginInfo, error) {
|
||||
var loginInfo models.LoginInfo
|
||||
err := s.db.Where("user_id = ?", userID).First(&loginInfo).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &loginInfo, nil
|
||||
}
|
||||
|
||||
// GetByUserIDAndUUID 根据用户ID和UUID获取登录信息
|
||||
func (s *loginInfoStorage) GetByUserIDAndUUID(userID int, uuid string) (*models.LoginInfo, error) {
|
||||
var loginInfo models.LoginInfo
|
||||
err := s.db.Where("user_id = ? AND uuid = ?", userID, uuid).First(&loginInfo).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &loginInfo, nil
|
||||
}
|
||||
|
||||
// Update 更新登录信息
|
||||
func (s *loginInfoStorage) Update(loginInfo *models.LoginInfo) error {
|
||||
return s.db.Save(loginInfo).Error
|
||||
}
|
||||
|
||||
// SetUserOffline 设置用户离线
|
||||
func (s *loginInfoStorage) SetUserOffline(userID int) error {
|
||||
return s.db.Model(&models.LoginInfo{}).Where("user_id = ?", userID).Update("is_online", false).Error
|
||||
}
|
||||
|
||||
// ListOnlineUsers 获取在线用户列表
|
||||
func (s *loginInfoStorage) ListOnlineUsers() ([]*models.LoginInfo, error) {
|
||||
var loginInfos []*models.LoginInfo
|
||||
err := s.db.Where("is_online = ?", true).Find(&loginInfos).Error
|
||||
return loginInfos, err
|
||||
}
|
||||
|
||||
// CountOnlineUsers 获取在线用户数量
|
||||
func (s *loginInfoStorage) CountOnlineUsers() (int64, error) {
|
||||
var count int64
|
||||
err := s.db.Model(&models.LoginInfo{}).Where("is_online = ?", true).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// DeleteByUserID 根据用户ID删除登录信息
|
||||
func (s *loginInfoStorage) DeleteByUserID(userID int) error {
|
||||
return s.db.Where("user_id = ?", userID).Delete(&models.LoginInfo{}).Error
|
||||
}
|
||||
106
internal/storage/system_config_storage.go
Normal file
106
internal/storage/system_config_storage.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SystemConfigStorage 系统配置存储接口
|
||||
type SystemConfigStorage interface {
|
||||
Create(config *models.SystemConfig) error
|
||||
GetByID(id uint) (*models.SystemConfig, error)
|
||||
GetByKey(key string) (*models.SystemConfig, error)
|
||||
Update(config *models.SystemConfig) error
|
||||
Delete(id uint) error
|
||||
List(req *models.SystemConfigListRequest) ([]models.SystemConfig, int64, error)
|
||||
UpdateStatus(id uint, status int) error
|
||||
GetAll() ([]models.SystemConfig, error)
|
||||
}
|
||||
|
||||
type systemConfigStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewSystemConfigStorage 创建系统配置存储实例
|
||||
func NewSystemConfigStorage() SystemConfigStorage {
|
||||
return &systemConfigStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建系统配置
|
||||
func (s *systemConfigStorage) Create(config *models.SystemConfig) error {
|
||||
return s.db.Create(config).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取系统配置
|
||||
func (s *systemConfigStorage) GetByID(id uint) (*models.SystemConfig, error) {
|
||||
var config models.SystemConfig
|
||||
err := s.db.First(&config, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// GetByKey 根据配置标识获取配置
|
||||
func (s *systemConfigStorage) GetByKey(key string) (*models.SystemConfig, error) {
|
||||
var config models.SystemConfig
|
||||
err := s.db.Where("`key` = ?", key).First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Update 更新系统配置
|
||||
func (s *systemConfigStorage) Update(config *models.SystemConfig) error {
|
||||
return s.db.Save(config).Error
|
||||
}
|
||||
|
||||
// Delete 删除系统配置
|
||||
func (s *systemConfigStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.SystemConfig{}, id).Error
|
||||
}
|
||||
|
||||
// List 获取系统配置列表
|
||||
func (s *systemConfigStorage) List(req *models.SystemConfigListRequest) ([]models.SystemConfig, int64, error) {
|
||||
var configs []models.SystemConfig
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.SystemConfig{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.Key != "" {
|
||||
query = query.Where("`key` LIKE ?", "%"+req.Key+"%")
|
||||
}
|
||||
if req.Name != "" {
|
||||
query = query.Where("name LIKE ?", "%"+req.Name+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
if err := query.Order("id DESC").Offset(offset).Limit(req.Size).Find(&configs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return configs, total, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新状态
|
||||
func (s *systemConfigStorage) UpdateStatus(id uint, status int) error {
|
||||
return s.db.Model(&models.SystemConfig{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
|
||||
// GetAll 获取所有系统配置(不分页)
|
||||
func (s *systemConfigStorage) GetAll() ([]models.SystemConfig, error) {
|
||||
var configs []models.SystemConfig
|
||||
err := s.db.Where("status = ?", 1).Order("id DESC").Find(&configs).Error
|
||||
return configs, err
|
||||
}
|
||||
188
internal/storage/user_feedback_storage.go
Normal file
188
internal/storage/user_feedback_storage.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"goalfymax-admin/internal/models"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserFeedbackStorage 用户反馈存储层
|
||||
type UserFeedbackStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserFeedbackStorage 创建用户反馈存储实例
|
||||
func NewUserFeedbackStorage() *UserFeedbackStorage {
|
||||
return &UserFeedbackStorage{db: DB}
|
||||
}
|
||||
|
||||
// List 获取用户反馈列表
|
||||
func (s *UserFeedbackStorage) List(ctx context.Context, req *models.UserFeedbackListRequest) ([]models.UserFeedback, int64, error) {
|
||||
var feedbacks []models.UserFeedback
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&models.UserFeedback{})
|
||||
|
||||
// 状态筛选
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
|
||||
// 用户ID筛选
|
||||
if req.UserID != nil {
|
||||
query = query.Where("uid = ?", *req.UserID)
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if req.Keyword != "" {
|
||||
query = query.Where("content LIKE ?", "%"+req.Keyword+"%")
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if req.StartTime != "" {
|
||||
if startTime, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil {
|
||||
query = query.Where("created_at >= ?", startTime)
|
||||
}
|
||||
}
|
||||
if req.EndTime != "" {
|
||||
if endTime, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil {
|
||||
query = query.Where("created_at <= ?", endTime)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("获取反馈总数失败: %w", err)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
if err := query.Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(req.PageSize).
|
||||
Find(&feedbacks).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("获取反馈列表失败: %w", err)
|
||||
}
|
||||
|
||||
return feedbacks, total, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户反馈
|
||||
func (s *UserFeedbackStorage) GetByID(ctx context.Context, id int64) (*models.UserFeedback, error) {
|
||||
var feedback models.UserFeedback
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&feedback).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("获取用户反馈失败: %w", err)
|
||||
}
|
||||
return &feedback, nil
|
||||
}
|
||||
|
||||
// MarkHandled 标记为已处理或未处理(切换状态)
|
||||
func (s *UserFeedbackStorage) MarkHandled(ctx context.Context, id int64, handledBy int, note string) error {
|
||||
// 先获取当前状态
|
||||
feedback, err := s.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取反馈信息失败: %w", err)
|
||||
}
|
||||
if feedback == nil {
|
||||
return fmt.Errorf("反馈不存在")
|
||||
}
|
||||
|
||||
// 切换状态:如果当前是已处理(1),则改为未处理(0);如果当前是未处理(0),则改为已处理(1)
|
||||
newStatus := 0
|
||||
var updates map[string]interface{}
|
||||
|
||||
if feedback.Status == 0 {
|
||||
// 从未处理改为已处理
|
||||
newStatus = 1
|
||||
now := time.Now()
|
||||
updates = map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"handled_by": handledBy,
|
||||
"handled_at": now,
|
||||
}
|
||||
} else {
|
||||
// 从已处理改为未处理
|
||||
newStatus = 0
|
||||
updates = map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"handled_by": nil,
|
||||
"handled_at": nil,
|
||||
}
|
||||
}
|
||||
|
||||
result := s.db.WithContext(ctx).Model(&models.UserFeedback{}).
|
||||
Where("id = ?", id).
|
||||
Updates(updates)
|
||||
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("切换状态失败: %w", result.Error)
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("切换状态失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除用户反馈
|
||||
func (s *UserFeedbackStorage) Delete(ctx context.Context, id int64) error {
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", id).Delete(&models.UserFeedback{}).Error; err != nil {
|
||||
return fmt.Errorf("删除用户反馈失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create 创建用户反馈(如果需要)
|
||||
func (s *UserFeedbackStorage) Create(ctx context.Context, feedback *models.UserFeedback) error {
|
||||
if err := s.db.WithContext(ctx).Create(feedback).Error; err != nil {
|
||||
return fmt.Errorf("创建用户反馈失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStatistics 获取反馈统计信息
|
||||
func (s *UserFeedbackStorage) GetStatistics(ctx context.Context) (map[string]interface{}, error) {
|
||||
var stats struct {
|
||||
Total int64 `json:"total"`
|
||||
Unhandled int64 `json:"unhandled"`
|
||||
Handled int64 `json:"handled"`
|
||||
TodayCount int64 `json:"today_count"`
|
||||
}
|
||||
|
||||
// 总数
|
||||
if err := s.db.WithContext(ctx).Model(&models.UserFeedback{}).Count(&stats.Total).Error; err != nil {
|
||||
return nil, fmt.Errorf("获取总数失败: %w", err)
|
||||
}
|
||||
|
||||
// 未处理数
|
||||
if err := s.db.WithContext(ctx).Model(&models.UserFeedback{}).Where("status = 0").Count(&stats.Unhandled).Error; err != nil {
|
||||
return nil, fmt.Errorf("获取未处理数失败: %w", err)
|
||||
}
|
||||
|
||||
// 已处理数
|
||||
if err := s.db.WithContext(ctx).Model(&models.UserFeedback{}).Where("status = 1").Count(&stats.Handled).Error; err != nil {
|
||||
return nil, fmt.Errorf("获取已处理数失败: %w", err)
|
||||
}
|
||||
|
||||
// 今日新增
|
||||
today := time.Now().Format("2006-01-02")
|
||||
if err := s.db.WithContext(ctx).Model(&models.UserFeedback{}).
|
||||
Where("DATE(created_at) = ?", today).
|
||||
Count(&stats.TodayCount).Error; err != nil {
|
||||
return nil, fmt.Errorf("获取今日新增数失败: %w", err)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"total": stats.Total,
|
||||
"unhandled": stats.Unhandled,
|
||||
"handled": stats.Handled,
|
||||
"today_count": stats.TodayCount,
|
||||
}, nil
|
||||
}
|
||||
103
internal/storage/user_level_config_storage.go
Normal file
103
internal/storage/user_level_config_storage.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserLevelConfigStorage 用户等级配置存储接口
|
||||
type UserLevelConfigStorage interface {
|
||||
Create(config *models.UserLevelConfig) error
|
||||
GetByID(id uint) (*models.UserLevelConfig, error)
|
||||
GetByLevelCode(levelCode string) (*models.UserLevelConfig, error)
|
||||
Update(config *models.UserLevelConfig) error
|
||||
Delete(id uint) error
|
||||
List(req *models.UserLevelConfigListRequest) ([]models.UserLevelConfig, int64, error)
|
||||
UpdateStatus(id uint, status int) error
|
||||
GetAll() ([]models.UserLevelConfig, error)
|
||||
}
|
||||
|
||||
type userLevelConfigStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserLevelConfigStorage 创建用户等级配置存储实例
|
||||
func NewUserLevelConfigStorage() UserLevelConfigStorage {
|
||||
return &userLevelConfigStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建用户等级配置
|
||||
func (s *userLevelConfigStorage) Create(config *models.UserLevelConfig) error {
|
||||
return s.db.Create(config).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户等级配置
|
||||
func (s *userLevelConfigStorage) GetByID(id uint) (*models.UserLevelConfig, error) {
|
||||
var config models.UserLevelConfig
|
||||
err := s.db.First(&config, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// GetByLevelCode 根据等级代码获取配置
|
||||
func (s *userLevelConfigStorage) GetByLevelCode(levelCode string) (*models.UserLevelConfig, error) {
|
||||
var config models.UserLevelConfig
|
||||
err := s.db.Where("level_code = ?", levelCode).First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Update 更新用户等级配置
|
||||
func (s *userLevelConfigStorage) Update(config *models.UserLevelConfig) error {
|
||||
return s.db.Save(config).Error
|
||||
}
|
||||
|
||||
// Delete 删除用户等级配置
|
||||
func (s *userLevelConfigStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.UserLevelConfig{}, id).Error
|
||||
}
|
||||
|
||||
// List 获取用户等级配置列表
|
||||
func (s *userLevelConfigStorage) List(req *models.UserLevelConfigListRequest) ([]models.UserLevelConfig, int64, error) {
|
||||
var configs []models.UserLevelConfig
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.UserLevelConfig{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.LevelName != "" {
|
||||
query = query.Where("level_name LIKE ?", "%"+req.LevelName+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
if err := query.Order("sort_order ASC, id DESC").Offset(offset).Limit(req.Size).Find(&configs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return configs, total, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新状态
|
||||
func (s *userLevelConfigStorage) UpdateStatus(id uint, status int) error {
|
||||
return s.db.Model(&models.UserLevelConfig{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
|
||||
// GetAll 获取所有用户等级配置(不分页)
|
||||
func (s *userLevelConfigStorage) GetAll() ([]models.UserLevelConfig, error) {
|
||||
var configs []models.UserLevelConfig
|
||||
err := s.db.Where("status = ?", 1).Order("sort_order ASC").Find(&configs).Error
|
||||
return configs, err
|
||||
}
|
||||
75
internal/storage/user_project_quota_storage.go
Normal file
75
internal/storage/user_project_quota_storage.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserProjectQuotaFilter struct {
|
||||
UserID string
|
||||
Enabled *bool
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
type UserProjectQuotaStorage interface {
|
||||
Create(q *models.UserProjectQuota) error
|
||||
Update(q *models.UserProjectQuota) error
|
||||
Delete(id uint) error
|
||||
GetByID(id uint) (*models.UserProjectQuota, error)
|
||||
GetByUserID(userID string) (*models.UserProjectQuota, error)
|
||||
List(filter UserProjectQuotaFilter) ([]models.UserProjectQuota, int64, error)
|
||||
}
|
||||
|
||||
type userProjectQuotaStorage struct{ db *gorm.DB }
|
||||
|
||||
func NewUserProjectQuotaStorage() UserProjectQuotaStorage { return &userProjectQuotaStorage{db: DB} }
|
||||
|
||||
func (s *userProjectQuotaStorage) Create(q *models.UserProjectQuota) error {
|
||||
return s.db.Create(q).Error
|
||||
}
|
||||
func (s *userProjectQuotaStorage) Update(q *models.UserProjectQuota) error { return s.db.Save(q).Error }
|
||||
func (s *userProjectQuotaStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.UserProjectQuota{}, id).Error
|
||||
}
|
||||
func (s *userProjectQuotaStorage) GetByID(id uint) (*models.UserProjectQuota, error) {
|
||||
var out models.UserProjectQuota
|
||||
if err := s.db.First(&out, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
func (s *userProjectQuotaStorage) GetByUserID(userID string) (*models.UserProjectQuota, error) {
|
||||
var out models.UserProjectQuota
|
||||
if err := s.db.Where("user_id = ?", userID).First(&out).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
func (s *userProjectQuotaStorage) List(filter UserProjectQuotaFilter) ([]models.UserProjectQuota, int64, error) {
|
||||
var (
|
||||
items []models.UserProjectQuota
|
||||
total int64
|
||||
)
|
||||
q := s.db.Model(&models.UserProjectQuota{})
|
||||
if filter.UserID != "" {
|
||||
q = q.Where("user_id LIKE ?", "%"+filter.UserID+"%")
|
||||
}
|
||||
if filter.Enabled != nil {
|
||||
q = q.Where("enabled = ?", *filter.Enabled)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
page, size := filter.Page, filter.Size
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if size <= 0 || size > 200 {
|
||||
size = 20
|
||||
}
|
||||
if err := q.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
107
internal/storage/user_storage.go
Normal file
107
internal/storage/user_storage.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"goalfymax-admin/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserStorage 用户存储接口
|
||||
type UserStorage interface {
|
||||
Create(user *models.User) error
|
||||
GetByID(id uint) (*models.User, error)
|
||||
GetByUsername(username string) (*models.User, error)
|
||||
GetByEmail(email string) (*models.User, error)
|
||||
Update(user *models.User) error
|
||||
Delete(id uint) error
|
||||
List(req *models.UserListRequest) ([]models.User, int64, error)
|
||||
UpdateStatus(id uint, status int) error
|
||||
}
|
||||
|
||||
type userStorage struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserStorage 创建用户存储实例
|
||||
func NewUserStorage() UserStorage {
|
||||
return &userStorage{db: DB}
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
func (s *userStorage) Create(user *models.User) error {
|
||||
return s.db.Create(user).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户
|
||||
func (s *userStorage) GetByID(id uint) (*models.User, error) {
|
||||
var user models.User
|
||||
err := s.db.First(&user, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByUsername 根据用户名获取用户
|
||||
func (s *userStorage) GetByUsername(username string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := s.db.Where("username = ?", username).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByEmail 根据邮箱获取用户
|
||||
func (s *userStorage) GetByEmail(email string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := s.db.Where("email = ?", email).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Update 更新用户
|
||||
func (s *userStorage) Update(user *models.User) error {
|
||||
return s.db.Save(user).Error
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (s *userStorage) Delete(id uint) error {
|
||||
return s.db.Delete(&models.User{}, id).Error
|
||||
}
|
||||
|
||||
// List 获取用户列表
|
||||
func (s *userStorage) List(req *models.UserListRequest) ([]models.User, int64, error) {
|
||||
var users []models.User
|
||||
var total int64
|
||||
|
||||
query := s.db.Model(&models.User{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.Username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.Email != "" {
|
||||
query = query.Where("email LIKE ?", "%"+req.Email+"%")
|
||||
}
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.Size
|
||||
err := query.Offset(offset).Limit(req.Size).Find(&users).Error
|
||||
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
// UpdateStatus 更新用户状态
|
||||
func (s *userStorage) UpdateStatus(id uint, status int) error {
|
||||
return s.db.Model(&models.User{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
Reference in New Issue
Block a user