refactor: admin 模块按 handler/model/service 分层
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
adminmodel "wx_service/internal/admin/model"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (s *Service) EnsureDefaultAdmin(ctx context.Context) error {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).Model(&adminmodel.Admin{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
username := s.defaultUsername
|
||||
if username == "" {
|
||||
username = "admin"
|
||||
}
|
||||
password := strings.TrimSpace(s.defaultPassword)
|
||||
if password == "" {
|
||||
password = "admin123"
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := &adminmodel.Admin{
|
||||
Username: username,
|
||||
Password: string(hashedPassword),
|
||||
Role: "super_admin",
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Create(record).Error; err != nil {
|
||||
if isDuplicateError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoginResult struct {
|
||||
Token string `json:"token"`
|
||||
Admin *adminmodel.Admin `json:"admin"`
|
||||
}
|
||||
|
||||
func (s *Service) Login(ctx context.Context, username, password string) (*LoginResult, error) {
|
||||
username = strings.TrimSpace(username)
|
||||
if username == "" || password == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
|
||||
var admin adminmodel.Admin
|
||||
if err := s.db.WithContext(ctx).Where("username = ?", username).First(&admin).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(password)); err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
token, err := s.generateToken(&admin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if err := s.db.WithContext(ctx).Model(&admin).Update("last_login_at", now).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
admin.LastLoginAt = &now
|
||||
admin.Password = ""
|
||||
|
||||
return &LoginResult{
|
||||
Token: token,
|
||||
Admin: &admin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) ParseToken(token string) (*Claims, error) {
|
||||
if len(s.jwtSecret) == 0 {
|
||||
return nil, ErrMissingJWTSecret
|
||||
}
|
||||
|
||||
claims := &Claims{}
|
||||
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
return s.jwtSecret, nil
|
||||
})
|
||||
if err != nil || parsed == nil || !parsed.Valid {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
if claims.AdminID == 0 {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (s *Service) generateToken(admin *adminmodel.Admin) (string, error) {
|
||||
if len(s.jwtSecret) == 0 {
|
||||
return "", ErrMissingJWTSecret
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
claims := &Claims{
|
||||
AdminID: admin.ID,
|
||||
Username: admin.Username,
|
||||
Role: admin.Role,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: fmt.Sprintf("%d", admin.ID),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(s.tokenTTL)),
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
},
|
||||
}
|
||||
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return t.SignedString(s.jwtSecret)
|
||||
}
|
||||
|
||||
func (s *Service) GetProfile(ctx context.Context, adminID uint) (*adminmodel.Admin, error) {
|
||||
var admin adminmodel.Admin
|
||||
if err := s.db.WithContext(ctx).
|
||||
Select("id", "username", "role", "last_login_at", "created_at", "updated_at").
|
||||
Where("id = ?", adminID).
|
||||
First(&admin).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin, nil
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
expirymodel "wx_service/internal/expiry"
|
||||
membershipmodel "wx_service/internal/membership/model"
|
||||
"wx_service/internal/model"
|
||||
rmmodel "wx_service/internal/remove_watermark/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ListMiniProgramsQuery struct {
|
||||
Page int
|
||||
PageSize int
|
||||
Keyword string
|
||||
}
|
||||
|
||||
type MiniProgramItem struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AppID string `json:"app_id"`
|
||||
Description string `json:"description"`
|
||||
UserCount int64 `json:"user_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
AppSecretSet bool `json:"app_secret_set"`
|
||||
}
|
||||
|
||||
type ListMiniProgramsResult struct {
|
||||
List []MiniProgramItem `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
func (s *Service) ListMiniPrograms(ctx context.Context, query ListMiniProgramsQuery) (*ListMiniProgramsResult, error) {
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize < 1 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
if query.PageSize > 100 {
|
||||
query.PageSize = 100
|
||||
}
|
||||
query.Keyword = strings.TrimSpace(query.Keyword)
|
||||
|
||||
dbQuery := s.db.WithContext(ctx).Model(&model.MiniProgram{})
|
||||
if query.Keyword != "" {
|
||||
keywordLike := "%" + query.Keyword + "%"
|
||||
dbQuery = dbQuery.Where("name LIKE ? OR app_id LIKE ?", keywordLike, keywordLike)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var miniPrograms []model.MiniProgram
|
||||
if total > 0 {
|
||||
if err := dbQuery.Order("id DESC").
|
||||
Limit(query.PageSize).
|
||||
Offset((query.Page - 1) * query.PageSize).
|
||||
Find(&miniPrograms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ids := make([]uint, 0, len(miniPrograms))
|
||||
for _, item := range miniPrograms {
|
||||
ids = append(ids, item.ID)
|
||||
}
|
||||
|
||||
userCountMap, err := s.groupCountByMiniProgramID(ctx, &model.User{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]MiniProgramItem, 0, len(miniPrograms))
|
||||
for _, item := range miniPrograms {
|
||||
result = append(result, MiniProgramItem{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
AppID: item.AppID,
|
||||
Description: item.Description,
|
||||
UserCount: userCountMap[item.ID],
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
AppSecretSet: item.AppSecret != "",
|
||||
})
|
||||
}
|
||||
|
||||
return &ListMiniProgramsResult{
|
||||
List: result,
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetMiniProgram(ctx context.Context, id uint) (*MiniProgramItem, error) {
|
||||
var miniProgram model.MiniProgram
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&miniProgram).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrMiniProgramNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userCount int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).Where("mini_program_id = ?", id).Count(&userCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MiniProgramItem{
|
||||
ID: miniProgram.ID,
|
||||
Name: miniProgram.Name,
|
||||
AppID: miniProgram.AppID,
|
||||
Description: miniProgram.Description,
|
||||
UserCount: userCount,
|
||||
CreatedAt: miniProgram.CreatedAt,
|
||||
UpdatedAt: miniProgram.UpdatedAt,
|
||||
AppSecretSet: miniProgram.AppSecret != "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
type CreateMiniProgramInput struct {
|
||||
Name string
|
||||
AppID string
|
||||
AppSecret string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (s *Service) CreateMiniProgram(ctx context.Context, input CreateMiniProgramInput) (*MiniProgramItem, error) {
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
input.AppID = strings.TrimSpace(input.AppID)
|
||||
input.AppSecret = strings.TrimSpace(input.AppSecret)
|
||||
input.Description = strings.TrimSpace(input.Description)
|
||||
if input.Name == "" || input.AppID == "" || input.AppSecret == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
|
||||
item := &model.MiniProgram{
|
||||
Name: input.Name,
|
||||
AppID: input.AppID,
|
||||
AppSecret: input.AppSecret,
|
||||
Description: input.Description,
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Create(item).Error; err != nil {
|
||||
if isDuplicateError(err) {
|
||||
return nil, ErrMiniProgramAppIDUsed
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetMiniProgram(ctx, item.ID)
|
||||
}
|
||||
|
||||
type UpdateMiniProgramInput struct {
|
||||
Name string
|
||||
AppID string
|
||||
AppSecret *string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (s *Service) UpdateMiniProgram(ctx context.Context, id uint, input UpdateMiniProgramInput) (*MiniProgramItem, error) {
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
input.AppID = strings.TrimSpace(input.AppID)
|
||||
input.Description = strings.TrimSpace(input.Description)
|
||||
if input.Name == "" || input.AppID == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
|
||||
var item model.MiniProgram
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrMiniProgramNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateData := map[string]interface{}{
|
||||
"name": input.Name,
|
||||
"app_id": input.AppID,
|
||||
"description": input.Description,
|
||||
}
|
||||
if input.AppSecret != nil {
|
||||
trimmedSecret := strings.TrimSpace(*input.AppSecret)
|
||||
if trimmedSecret != "" {
|
||||
updateData["app_secret"] = trimmedSecret
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.db.WithContext(ctx).Model(&item).Updates(updateData).Error; err != nil {
|
||||
if isDuplicateError(err) {
|
||||
return nil, ErrMiniProgramAppIDUsed
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetMiniProgram(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteMiniProgram(ctx context.Context, id uint) error {
|
||||
var item model.MiniProgram
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrMiniProgramNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var userCount int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).Where("mini_program_id = ?", id).Count(&userCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if userCount > 0 {
|
||||
return ErrMiniProgramHasUsers
|
||||
}
|
||||
|
||||
return s.db.WithContext(ctx).Delete(&item).Error
|
||||
}
|
||||
|
||||
type MiniProgramDetailStats struct {
|
||||
UserCount int64 `json:"user_count"`
|
||||
DataCount int64 `json:"data_count"`
|
||||
}
|
||||
|
||||
func (s *Service) GetMiniProgramStats(ctx context.Context, id uint) (*MiniProgramDetailStats, error) {
|
||||
var exists int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.MiniProgram{}).Where("id = ?", id).Count(&exists).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists == 0 {
|
||||
return nil, ErrMiniProgramNotFound
|
||||
}
|
||||
|
||||
userCounts, err := s.groupCountByMiniProgramID(ctx, &model.User{}, []uint{id}, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expiryCounts, err := s.groupCountByMiniProgramID(ctx, &expirymodel.ExpiryItem{}, []uint{id}, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoCounts, err := s.groupCountByMiniProgramID(ctx, &rmmodel.VideoParseLog{}, []uint{id}, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
redeemCounts, err := s.groupCountByMiniProgramID(ctx, &membershipmodel.MembershipRedemption{}, []uint{id}, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataCount := expiryCounts[id] + videoCounts[id] + redeemCounts[id]
|
||||
return &MiniProgramDetailStats{
|
||||
UserCount: userCounts[id],
|
||||
DataCount: dataCount,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
expirymodel "wx_service/internal/expiry"
|
||||
membershipmodel "wx_service/internal/membership/model"
|
||||
"wx_service/internal/model"
|
||||
rmmodel "wx_service/internal/remove_watermark/model"
|
||||
)
|
||||
|
||||
type OverviewStats struct {
|
||||
TotalMiniPrograms int64 `json:"total_mini_programs"`
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
TotalMembers int64 `json:"total_members"`
|
||||
TodayNewUsers int64 `json:"today_new_users"`
|
||||
}
|
||||
|
||||
func (s *Service) StatsOverview(ctx context.Context) (*OverviewStats, error) {
|
||||
var result OverviewStats
|
||||
if err := s.db.WithContext(ctx).Model(&model.MiniProgram{}).Count(&result.TotalMiniPrograms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).Count(&result.TotalUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if err := s.db.WithContext(ctx).Model(&model.UserMembership{}).
|
||||
Where("status = ? AND ends_at > ?", "active", now).
|
||||
Count(&result.TotalMembers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).
|
||||
Where("created_at >= ?", todayStart).
|
||||
Count(&result.TodayNewUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
type MiniProgramStatsItem struct {
|
||||
MiniProgramID uint `json:"mini_program_id"`
|
||||
Name string `json:"name"`
|
||||
UserCount int64 `json:"user_count"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
DataCount int64 `json:"data_count"`
|
||||
TodayActive int64 `json:"today_active"`
|
||||
}
|
||||
|
||||
func (s *Service) StatsMiniPrograms(ctx context.Context) ([]MiniProgramStatsItem, error) {
|
||||
var miniPrograms []model.MiniProgram
|
||||
if err := s.db.WithContext(ctx).
|
||||
Select("id", "name", "app_id", "description", "created_at", "updated_at").
|
||||
Order("id ASC").
|
||||
Find(&miniPrograms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(miniPrograms) == 0 {
|
||||
return []MiniProgramStatsItem{}, nil
|
||||
}
|
||||
|
||||
ids := make([]uint, 0, len(miniPrograms))
|
||||
for _, item := range miniPrograms {
|
||||
ids = append(ids, item.ID)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
|
||||
userCounts, err := s.groupCountByMiniProgramID(ctx, &model.User{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memberCounts, err := s.groupCountByMiniProgramID(ctx, &model.UserMembership{}, ids, "status = ? AND ends_at > ?", "active", now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
todayUserCounts, err := s.groupCountByMiniProgramID(ctx, &model.User{}, ids, "created_at >= ?", todayStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expiryCounts, err := s.groupCountByMiniProgramID(ctx, &expirymodel.ExpiryItem{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoCounts, err := s.groupCountByMiniProgramID(ctx, &rmmodel.VideoParseLog{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
redeemCounts, err := s.groupCountByMiniProgramID(ctx, &membershipmodel.MembershipRedemption{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]MiniProgramStatsItem, 0, len(miniPrograms))
|
||||
for _, item := range miniPrograms {
|
||||
dataCount := expiryCounts[item.ID] + videoCounts[item.ID] + redeemCounts[item.ID]
|
||||
result = append(result, MiniProgramStatsItem{
|
||||
MiniProgramID: item.ID,
|
||||
Name: item.Name,
|
||||
UserCount: userCounts[item.ID],
|
||||
MemberCount: memberCounts[item.ID],
|
||||
DataCount: dataCount,
|
||||
TodayActive: todayUserCounts[item.ID],
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type UserGrowthPoint struct {
|
||||
Date string `json:"date"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
func (s *Service) StatsUserGrowth(ctx context.Context, days int) ([]UserGrowthPoint, error) {
|
||||
if days <= 0 {
|
||||
days = 7
|
||||
}
|
||||
if days > 90 {
|
||||
days = 90
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
startDate := todayStart.AddDate(0, 0, -(days - 1))
|
||||
endDate := todayStart.AddDate(0, 0, 1)
|
||||
|
||||
type row struct {
|
||||
Date string `gorm:"column:date"`
|
||||
Count int64 `gorm:"column:count"`
|
||||
}
|
||||
var rows []row
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.User{}).
|
||||
Select("DATE(created_at) AS date, COUNT(*) AS count").
|
||||
Where("created_at >= ? AND created_at < ?", startDate, endDate).
|
||||
Group("DATE(created_at)").
|
||||
Order("DATE(created_at) ASC").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countByDate := make(map[string]int64, len(rows))
|
||||
for _, item := range rows {
|
||||
countByDate[item.Date] = item.Count
|
||||
}
|
||||
|
||||
result := make([]UserGrowthPoint, 0, days)
|
||||
for i := 0; i < days; i++ {
|
||||
date := startDate.AddDate(0, 0, i).Format("2006-01-02")
|
||||
result = append(result, UserGrowthPoint{
|
||||
Date: date,
|
||||
Count: countByDate[date],
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) groupCountByMiniProgramID(ctx context.Context, modelObj interface{}, ids []uint, where string, args ...interface{}) (map[uint]int64, error) {
|
||||
result := make(map[uint]int64)
|
||||
if len(ids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
query := s.db.WithContext(ctx).
|
||||
Model(modelObj).
|
||||
Select("mini_program_id, COUNT(*) AS count").
|
||||
Where("mini_program_id IN ?", ids)
|
||||
|
||||
if where != "" {
|
||||
query = query.Where(where, args...)
|
||||
}
|
||||
|
||||
var rows []groupedCountRow
|
||||
if err := query.Group("mini_program_id").Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range rows {
|
||||
result[item.MiniProgramID] = item.Count
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrMissingJWTSecret = errors.New("missing jwt secret")
|
||||
ErrMiniProgramNotFound = errors.New("mini program not found")
|
||||
ErrMiniProgramHasUsers = errors.New("mini program has users")
|
||||
ErrMiniProgramAppIDUsed = errors.New("mini program app_id already exists")
|
||||
ErrInvalidInput = errors.New("invalid input")
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
AdminID uint `json:"admin_id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
jwtSecret []byte
|
||||
tokenTTL time.Duration
|
||||
defaultUsername string
|
||||
defaultPassword string
|
||||
}
|
||||
|
||||
func NewService(db *gorm.DB, jwtSecret string, tokenTTL time.Duration, defaultUsername, defaultPassword string) *Service {
|
||||
if tokenTTL <= 0 {
|
||||
tokenTTL = 24 * time.Hour
|
||||
}
|
||||
return &Service{
|
||||
db: db,
|
||||
jwtSecret: []byte(strings.TrimSpace(jwtSecret)),
|
||||
tokenTTL: tokenTTL,
|
||||
defaultUsername: strings.TrimSpace(defaultUsername),
|
||||
defaultPassword: defaultPassword,
|
||||
}
|
||||
}
|
||||
|
||||
type groupedCountRow struct {
|
||||
MiniProgramID uint `gorm:"column:mini_program_id"`
|
||||
Count int64 `gorm:"column:count"`
|
||||
}
|
||||
|
||||
func isDuplicateError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
errText := strings.ToLower(err.Error())
|
||||
return strings.Contains(errText, "duplicate") || strings.Contains(errText, "unique")
|
||||
}
|
||||
Reference in New Issue
Block a user