432 lines
13 KiB
Go
432 lines
13 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"tyapi-server/internal/domains/admin/dto"
|
|
"tyapi-server/internal/domains/admin/entities"
|
|
"tyapi-server/internal/domains/admin/repositories"
|
|
"tyapi-server/internal/shared/interfaces"
|
|
)
|
|
|
|
// AdminService 管理员服务
|
|
type AdminService struct {
|
|
adminRepo repositories.AdminRepository
|
|
loginLogRepo repositories.AdminLoginLogRepository
|
|
operationLogRepo repositories.AdminOperationLogRepository
|
|
permissionRepo repositories.AdminPermissionRepository
|
|
responseBuilder interfaces.ResponseBuilder
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewAdminService 创建管理员服务
|
|
func NewAdminService(
|
|
adminRepo repositories.AdminRepository,
|
|
loginLogRepo repositories.AdminLoginLogRepository,
|
|
operationLogRepo repositories.AdminOperationLogRepository,
|
|
permissionRepo repositories.AdminPermissionRepository,
|
|
responseBuilder interfaces.ResponseBuilder,
|
|
logger *zap.Logger,
|
|
) *AdminService {
|
|
return &AdminService{
|
|
adminRepo: adminRepo,
|
|
loginLogRepo: loginLogRepo,
|
|
operationLogRepo: operationLogRepo,
|
|
permissionRepo: permissionRepo,
|
|
responseBuilder: responseBuilder,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Login 管理员登录
|
|
func (s *AdminService) Login(ctx context.Context, req *dto.AdminLoginRequest, clientIP, userAgent string) (*dto.AdminLoginResponse, error) {
|
|
s.logger.Info("管理员登录", zap.String("username", req.Username))
|
|
|
|
// 查找管理员
|
|
admin, err := s.adminRepo.FindByUsername(ctx, req.Username)
|
|
if err != nil {
|
|
s.logger.Warn("管理员登录失败:用户不存在", zap.String("username", req.Username))
|
|
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "failed", "用户不存在")
|
|
return nil, fmt.Errorf("用户名或密码错误")
|
|
}
|
|
|
|
// 检查管理员状态
|
|
if !admin.IsActive {
|
|
s.logger.Warn("管理员登录失败:账户已禁用", zap.String("username", req.Username))
|
|
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "failed", "账户已禁用")
|
|
return nil, fmt.Errorf("账户已被禁用,请联系管理员")
|
|
}
|
|
|
|
// 验证密码
|
|
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(req.Password)); err != nil {
|
|
s.logger.Warn("管理员登录失败:密码错误", zap.String("username", req.Username))
|
|
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "failed", "密码错误")
|
|
return nil, fmt.Errorf("用户名或密码错误")
|
|
}
|
|
|
|
// 更新登录统计
|
|
if err := s.adminRepo.UpdateLoginStats(ctx, admin.ID); err != nil {
|
|
s.logger.Error("更新登录统计失败", zap.Error(err))
|
|
}
|
|
|
|
// 记录登录日志
|
|
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "success", "登录成功")
|
|
|
|
// 生成JWT令牌
|
|
token, expiresAt, err := s.generateJWTToken(admin)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("生成令牌失败: %w", err)
|
|
}
|
|
|
|
// 获取权限列表
|
|
permissions, err := s.getAdminPermissions(ctx, admin)
|
|
if err != nil {
|
|
s.logger.Error("获取管理员权限失败", zap.Error(err))
|
|
permissions = []string{}
|
|
}
|
|
|
|
// 构建响应
|
|
adminInfo := dto.AdminInfo{
|
|
ID: admin.ID,
|
|
Username: admin.Username,
|
|
Email: admin.Email,
|
|
Phone: admin.Phone,
|
|
RealName: admin.RealName,
|
|
Role: admin.Role,
|
|
IsActive: admin.IsActive,
|
|
LastLoginAt: admin.LastLoginAt,
|
|
LoginCount: admin.LoginCount,
|
|
Permissions: permissions,
|
|
CreatedAt: admin.CreatedAt,
|
|
}
|
|
|
|
s.logger.Info("管理员登录成功", zap.String("username", req.Username))
|
|
return &dto.AdminLoginResponse{
|
|
Token: token,
|
|
ExpiresAt: expiresAt,
|
|
Admin: adminInfo,
|
|
}, nil
|
|
}
|
|
|
|
// CreateAdmin 创建管理员
|
|
func (s *AdminService) CreateAdmin(ctx context.Context, req *dto.AdminCreateRequest, operatorID string) error {
|
|
s.logger.Info("创建管理员", zap.String("username", req.Username))
|
|
|
|
// 检查用户名是否已存在
|
|
if _, err := s.adminRepo.FindByUsername(ctx, req.Username); err == nil {
|
|
return fmt.Errorf("用户名已存在")
|
|
}
|
|
|
|
// 检查邮箱是否已存在
|
|
if _, err := s.adminRepo.FindByEmail(ctx, req.Email); err == nil {
|
|
return fmt.Errorf("邮箱已存在")
|
|
}
|
|
|
|
// 加密密码
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return fmt.Errorf("密码加密失败: %w", err)
|
|
}
|
|
|
|
// 序列化权限
|
|
permissionsJSON := "[]"
|
|
if len(req.Permissions) > 0 {
|
|
permissionsBytes, err := json.Marshal(req.Permissions)
|
|
if err != nil {
|
|
return fmt.Errorf("权限序列化失败: %w", err)
|
|
}
|
|
permissionsJSON = string(permissionsBytes)
|
|
}
|
|
|
|
// 创建管理员
|
|
admin := entities.Admin{
|
|
ID: s.generateID(),
|
|
Username: req.Username,
|
|
Password: string(hashedPassword),
|
|
Email: req.Email,
|
|
Phone: req.Phone,
|
|
RealName: req.RealName,
|
|
Role: req.Role,
|
|
IsActive: true,
|
|
Permissions: permissionsJSON,
|
|
}
|
|
|
|
if err := s.adminRepo.Create(ctx, admin); err != nil {
|
|
return fmt.Errorf("创建管理员失败: %w", err)
|
|
}
|
|
|
|
// 记录操作日志
|
|
s.recordOperationLog(ctx, operatorID, "create", "admin", admin.ID, map[string]interface{}{
|
|
"username": req.Username,
|
|
"email": req.Email,
|
|
"role": req.Role,
|
|
}, "success", "创建管理员成功")
|
|
|
|
s.logger.Info("管理员创建成功", zap.String("username", req.Username))
|
|
return nil
|
|
}
|
|
|
|
// UpdateAdmin 更新管理员
|
|
func (s *AdminService) UpdateAdmin(ctx context.Context, adminID string, req *dto.AdminUpdateRequest, operatorID string) error {
|
|
s.logger.Info("更新管理员", zap.String("admin_id", adminID))
|
|
|
|
// 获取管理员
|
|
admin, err := s.adminRepo.GetByID(ctx, adminID)
|
|
if err != nil {
|
|
return fmt.Errorf("管理员不存在")
|
|
}
|
|
|
|
// 更新字段
|
|
if req.Email != "" {
|
|
// 检查邮箱是否被其他管理员使用
|
|
if existingAdmin, err := s.adminRepo.FindByEmail(ctx, req.Email); err == nil && existingAdmin.ID != adminID {
|
|
return fmt.Errorf("邮箱已被其他管理员使用")
|
|
}
|
|
admin.Email = req.Email
|
|
}
|
|
|
|
if req.Phone != "" {
|
|
admin.Phone = req.Phone
|
|
}
|
|
|
|
if req.RealName != "" {
|
|
admin.RealName = req.RealName
|
|
}
|
|
|
|
if req.Role != "" {
|
|
admin.Role = req.Role
|
|
}
|
|
|
|
if req.IsActive != nil {
|
|
admin.IsActive = *req.IsActive
|
|
}
|
|
|
|
if len(req.Permissions) > 0 {
|
|
permissionsJSON, err := json.Marshal(req.Permissions)
|
|
if err != nil {
|
|
return fmt.Errorf("权限序列化失败: %w", err)
|
|
}
|
|
admin.Permissions = string(permissionsJSON)
|
|
}
|
|
|
|
// 保存更新
|
|
if err := s.adminRepo.Update(ctx, admin); err != nil {
|
|
return fmt.Errorf("更新管理员失败: %w", err)
|
|
}
|
|
|
|
// 记录操作日志
|
|
s.recordOperationLog(ctx, operatorID, "update", "admin", adminID, map[string]interface{}{
|
|
"email": req.Email,
|
|
"phone": req.Phone,
|
|
"real_name": req.RealName,
|
|
"role": req.Role,
|
|
"is_active": req.IsActive,
|
|
}, "success", "更新管理员成功")
|
|
|
|
s.logger.Info("管理员更新成功", zap.String("admin_id", adminID))
|
|
return nil
|
|
}
|
|
|
|
// ChangePassword 修改密码
|
|
func (s *AdminService) ChangePassword(ctx context.Context, adminID string, req *dto.AdminPasswordChangeRequest) error {
|
|
s.logger.Info("修改管理员密码", zap.String("admin_id", adminID))
|
|
|
|
// 获取管理员
|
|
admin, err := s.adminRepo.GetByID(ctx, adminID)
|
|
if err != nil {
|
|
return fmt.Errorf("管理员不存在")
|
|
}
|
|
|
|
// 验证旧密码
|
|
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(req.OldPassword)); err != nil {
|
|
return fmt.Errorf("旧密码错误")
|
|
}
|
|
|
|
// 加密新密码
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return fmt.Errorf("密码加密失败: %w", err)
|
|
}
|
|
|
|
// 更新密码
|
|
admin.Password = string(hashedPassword)
|
|
if err := s.adminRepo.Update(ctx, admin); err != nil {
|
|
return fmt.Errorf("更新密码失败: %w", err)
|
|
}
|
|
|
|
// 记录操作日志
|
|
s.recordOperationLog(ctx, adminID, "change_password", "admin", adminID, nil, "success", "修改密码成功")
|
|
|
|
s.logger.Info("管理员密码修改成功", zap.String("admin_id", adminID))
|
|
return nil
|
|
}
|
|
|
|
// ListAdmins 获取管理员列表
|
|
func (s *AdminService) ListAdmins(ctx context.Context, req *dto.AdminListRequest) (*dto.AdminListResponse, error) {
|
|
s.logger.Info("获取管理员列表", zap.Int("page", req.Page), zap.Int("page_size", req.PageSize))
|
|
|
|
response, err := s.adminRepo.ListAdmins(ctx, req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取管理员列表失败: %w", err)
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// GetAdminStats 获取管理员统计信息
|
|
func (s *AdminService) GetAdminStats(ctx context.Context) (*dto.AdminStatsResponse, error) {
|
|
s.logger.Info("获取管理员统计信息")
|
|
|
|
stats, err := s.adminRepo.GetStats(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// GetAdminByID 根据ID获取管理员
|
|
func (s *AdminService) GetAdminByID(ctx context.Context, adminID string) (*dto.AdminInfo, error) {
|
|
s.logger.Info("获取管理员信息", zap.String("admin_id", adminID))
|
|
|
|
admin, err := s.adminRepo.GetByID(ctx, adminID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("管理员不存在")
|
|
}
|
|
|
|
// 获取权限列表
|
|
permissions, err := s.getAdminPermissions(ctx, &admin)
|
|
if err != nil {
|
|
s.logger.Error("获取管理员权限失败", zap.Error(err))
|
|
permissions = []string{}
|
|
}
|
|
|
|
adminInfo := dto.AdminInfo{
|
|
ID: admin.ID,
|
|
Username: admin.Username,
|
|
Email: admin.Email,
|
|
Phone: admin.Phone,
|
|
RealName: admin.RealName,
|
|
Role: admin.Role,
|
|
IsActive: admin.IsActive,
|
|
LastLoginAt: admin.LastLoginAt,
|
|
LoginCount: admin.LoginCount,
|
|
Permissions: permissions,
|
|
CreatedAt: admin.CreatedAt,
|
|
}
|
|
|
|
return &adminInfo, nil
|
|
}
|
|
|
|
// DeleteAdmin 删除管理员
|
|
func (s *AdminService) DeleteAdmin(ctx context.Context, adminID string, operatorID string) error {
|
|
s.logger.Info("删除管理员", zap.String("admin_id", adminID))
|
|
|
|
// 检查管理员是否存在
|
|
if _, err := s.adminRepo.GetByID(ctx, adminID); err != nil {
|
|
return fmt.Errorf("管理员不存在")
|
|
}
|
|
|
|
// 软删除管理员
|
|
if err := s.adminRepo.SoftDelete(ctx, adminID); err != nil {
|
|
return fmt.Errorf("删除管理员失败: %w", err)
|
|
}
|
|
|
|
// 记录操作日志
|
|
s.recordOperationLog(ctx, operatorID, "delete", "admin", adminID, nil, "success", "删除管理员成功")
|
|
|
|
s.logger.Info("管理员删除成功", zap.String("admin_id", adminID))
|
|
return nil
|
|
}
|
|
|
|
// getAdminPermissions 获取管理员权限
|
|
func (s *AdminService) getAdminPermissions(ctx context.Context, admin *entities.Admin) ([]string, error) {
|
|
// 首先从角色获取权限
|
|
rolePermissions, err := s.adminRepo.GetPermissionsByRole(ctx, admin.Role)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 从角色权限中提取权限代码
|
|
permissions := make([]string, 0, len(rolePermissions))
|
|
for _, perm := range rolePermissions {
|
|
permissions = append(permissions, perm.Code)
|
|
}
|
|
|
|
// 如果有自定义权限,也添加进去
|
|
if admin.Permissions != "" {
|
|
var customPermissions []string
|
|
if err := json.Unmarshal([]byte(admin.Permissions), &customPermissions); err == nil {
|
|
permissions = append(permissions, customPermissions...)
|
|
}
|
|
}
|
|
|
|
return permissions, nil
|
|
}
|
|
|
|
// generateJWTToken 生成JWT令牌
|
|
func (s *AdminService) generateJWTToken(admin *entities.Admin) (string, time.Time, error) {
|
|
// 这里应该使用JWT库生成令牌
|
|
// 为了简化,这里返回一个模拟的令牌
|
|
token := fmt.Sprintf("admin_token_%s_%d", admin.ID, time.Now().Unix())
|
|
expiresAt := time.Now().Add(24 * time.Hour)
|
|
|
|
return token, expiresAt, nil
|
|
}
|
|
|
|
// generateID 生成ID
|
|
func (s *AdminService) generateID() string {
|
|
bytes := make([]byte, 16)
|
|
rand.Read(bytes)
|
|
return hex.EncodeToString(bytes)
|
|
}
|
|
|
|
// recordLoginLog 记录登录日志
|
|
func (s *AdminService) recordLoginLog(ctx context.Context, username, ip, userAgent, status, message string) {
|
|
log := entities.AdminLoginLog{
|
|
ID: s.generateID(),
|
|
Username: username,
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
Status: status,
|
|
Message: message,
|
|
}
|
|
|
|
if err := s.loginLogRepo.Create(ctx, log); err != nil {
|
|
s.logger.Error("记录登录日志失败", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// recordOperationLog 记录操作日志
|
|
func (s *AdminService) recordOperationLog(ctx context.Context, adminID, action, resource, resourceID string, details map[string]interface{}, status, message string) {
|
|
detailsJSON := "{}"
|
|
if details != nil {
|
|
if bytes, err := json.Marshal(details); err == nil {
|
|
detailsJSON = string(bytes)
|
|
}
|
|
}
|
|
|
|
log := entities.AdminOperationLog{
|
|
ID: s.generateID(),
|
|
AdminID: adminID,
|
|
Action: action,
|
|
Resource: resource,
|
|
ResourceID: resourceID,
|
|
Details: detailsJSON,
|
|
Status: status,
|
|
Message: message,
|
|
}
|
|
|
|
if err := s.operationLogRepo.Create(ctx, log); err != nil {
|
|
s.logger.Error("记录操作日志失败", zap.Error(err))
|
|
}
|
|
}
|