feat(admin): complete settings APIs and password update flow

This commit is contained in:
root
2026-03-10 16:40:51 +08:00
parent 59508efb05
commit 6f1f75d983
10 changed files with 444 additions and 3 deletions
+5 -3
View File
@@ -38,9 +38,11 @@ func (s *Service) EnsureDefaultAdmin(ctx context.Context) error {
}
record := &adminmodel.Admin{
Username: username,
Password: string(hashedPassword),
Role: "super_admin",
Username: username,
Password: string(hashedPassword),
Role: "super_admin",
DisplayName: username,
Timezone: "Asia/Shanghai",
}
if err := s.db.WithContext(ctx).Create(record).Error; err != nil {
if isDuplicateError(err) {
@@ -0,0 +1,182 @@
package service
import (
"context"
"errors"
"strings"
adminmodel "wx_service/internal/admin/model"
"gorm.io/gorm"
)
type AdminSettings struct {
Username string `json:"username"`
DisplayName string `json:"display_name"`
Email string `json:"email"`
Phone string `json:"phone"`
Timezone string `json:"timezone"`
}
type UpdateAdminProfileInput struct {
DisplayName string
Email string
Phone string
Timezone string
}
type SystemConfigPayload struct {
SiteName string `json:"site_name"`
AllowRegister bool `json:"allow_register"`
LoginFailLimit int `json:"login_fail_limit"`
DefaultPageSize int `json:"default_page_size"`
AuditLogRetentionDays int `json:"audit_log_retention_days"`
}
func (s *Service) GetAdminSettings(ctx context.Context, adminID uint) (*AdminSettings, error) {
if adminID == 0 {
return nil, ErrInvalidInput
}
var admin adminmodel.Admin
if err := s.db.WithContext(ctx).
Select("username", "display_name", "email", "phone", "timezone").
Where("id = ?", adminID).
First(&admin).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAdminUserNotFound
}
return nil, err
}
return &AdminSettings{
Username: admin.Username,
DisplayName: admin.DisplayName,
Email: admin.Email,
Phone: admin.Phone,
Timezone: normalizeTimezone(admin.Timezone),
}, nil
}
func (s *Service) UpdateAdminProfile(ctx context.Context, adminID uint, input UpdateAdminProfileInput) (*AdminSettings, error) {
if adminID == 0 {
return nil, ErrInvalidInput
}
displayName := strings.TrimSpace(input.DisplayName)
email := strings.TrimSpace(input.Email)
phone := strings.TrimSpace(input.Phone)
timezone := normalizeTimezone(input.Timezone)
if displayName == "" {
return nil, ErrInvalidInput
}
if email != "" && !strings.Contains(email, "@") {
return nil, ErrInvalidInput
}
updates := map[string]interface{}{
"display_name": displayName,
"email": email,
"phone": phone,
"timezone": timezone,
}
result := s.db.WithContext(ctx).
Model(&adminmodel.Admin{}).
Where("id = ?", adminID).
Updates(updates)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, ErrAdminUserNotFound
}
return s.GetAdminSettings(ctx, adminID)
}
func (s *Service) GetSystemConfig(ctx context.Context) (*SystemConfigPayload, error) {
config, err := s.ensureSystemConfig(ctx)
if err != nil {
return nil, err
}
return &SystemConfigPayload{
SiteName: config.SiteName,
AllowRegister: config.AllowRegister,
LoginFailLimit: config.LoginFailLimit,
DefaultPageSize: config.DefaultPageSize,
AuditLogRetentionDays: config.AuditLogRetentionDays,
}, nil
}
func (s *Service) UpdateSystemConfig(ctx context.Context, input SystemConfigPayload) (*SystemConfigPayload, error) {
siteName := strings.TrimSpace(input.SiteName)
if siteName == "" {
return nil, ErrInvalidInput
}
if input.LoginFailLimit < 3 || input.LoginFailLimit > 20 {
return nil, ErrInvalidInput
}
if input.DefaultPageSize < 10 || input.DefaultPageSize > 200 {
return nil, ErrInvalidInput
}
if input.AuditLogRetentionDays < 7 || input.AuditLogRetentionDays > 3650 {
return nil, ErrInvalidInput
}
config, err := s.ensureSystemConfig(ctx)
if err != nil {
return nil, err
}
config.SiteName = siteName
config.AllowRegister = input.AllowRegister
config.LoginFailLimit = input.LoginFailLimit
config.DefaultPageSize = input.DefaultPageSize
config.AuditLogRetentionDays = input.AuditLogRetentionDays
if err := s.db.WithContext(ctx).Save(config).Error; err != nil {
return nil, err
}
return &SystemConfigPayload{
SiteName: config.SiteName,
AllowRegister: config.AllowRegister,
LoginFailLimit: config.LoginFailLimit,
DefaultPageSize: config.DefaultPageSize,
AuditLogRetentionDays: config.AuditLogRetentionDays,
}, nil
}
func (s *Service) ensureSystemConfig(ctx context.Context) (*adminmodel.SystemConfig, error) {
var config adminmodel.SystemConfig
err := s.db.WithContext(ctx).Order("id ASC").First(&config).Error
if err == nil {
return &config, nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
config = adminmodel.SystemConfig{
SiteName: "多小程序管理后台",
AllowRegister: false,
LoginFailLimit: 5,
DefaultPageSize: 20,
AuditLogRetentionDays: 180,
}
if err := s.db.WithContext(ctx).Create(&config).Error; err != nil {
return nil, err
}
return &config, nil
}
func normalizeTimezone(value string) string {
timezone := strings.TrimSpace(value)
if timezone == "" {
return "Asia/Shanghai"
}
return timezone
}
@@ -0,0 +1,52 @@
package service
import (
"context"
"errors"
"strings"
adminmodel "wx_service/internal/admin/model"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
func (s *Service) UpdatePassword(ctx context.Context, adminID uint, oldPassword, newPassword string) error {
oldPassword = strings.TrimSpace(oldPassword)
newPassword = strings.TrimSpace(newPassword)
if adminID == 0 || oldPassword == "" || newPassword == "" {
return ErrInvalidInput
}
if len(newPassword) < 6 {
return ErrPasswordPolicyViolation
}
if oldPassword == newPassword {
return ErrPasswordPolicyViolation
}
var admin adminmodel.Admin
if err := s.db.WithContext(ctx).Where("id = ?", adminID).First(&admin).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrAdminUserNotFound
}
return err
}
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(oldPassword)); err != nil {
return ErrInvalidCredentials
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return err
}
if err := s.db.WithContext(ctx).
Model(&adminmodel.Admin{}).
Where("id = ?", adminID).
Update("password", string(hashedPassword)).Error; err != nil {
return err
}
return nil
}
+1
View File
@@ -19,6 +19,7 @@ var (
ErrAdminUserNotFound = errors.New("admin user not found")
ErrMembershipRedeemCodeNotFound = errors.New("membership redeem code not found")
ErrInvalidInput = errors.New("invalid input")
ErrPasswordPolicyViolation = errors.New("password policy violation")
)
type Claims struct {