619 lines
19 KiB
Go
619 lines
19 KiB
Go
package services
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"goalfymax-admin/internal/models"
|
||
"goalfymax-admin/internal/storage"
|
||
"goalfymax-admin/pkg/middleware"
|
||
"goalfymax-admin/pkg/utils"
|
||
"net/url"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// SSOService SSO服务接口
|
||
type SSOService interface {
|
||
// 登录相关
|
||
InitiateLogin(ctx context.Context) (*models.SSOLoginResponse, error)
|
||
HandleCallback(ctx context.Context, req *models.SSOCallbackRequest) (*models.SSOCallbackResponse, error)
|
||
RefreshToken(ctx context.Context, req *models.RefreshTokenRequest) (*models.RefreshTokenResponse, error)
|
||
Logout(ctx context.Context, token string) (*models.LogoutResponse, error)
|
||
GetUserInfo(ctx context.Context, token string) (*models.UserInfoResponse, error)
|
||
|
||
// 登录信息管理
|
||
RecordUserLogin(ctx context.Context, req *UserLoginRequest) error
|
||
UserLogout(ctx context.Context, userID int, uuid string) error
|
||
GetUserLoginInfo(ctx context.Context, userID int) (*models.LoginInfo, error)
|
||
IsUserOnline(ctx context.Context, userID int) (bool, error)
|
||
GetOnlineUsers(ctx context.Context) ([]*models.LoginInfo, error)
|
||
GetOnlineUserCount(ctx context.Context) (int64, error)
|
||
BatchUserLogout(ctx context.Context, userIDs []int) error
|
||
}
|
||
|
||
// UserLoginRequest 用户登录请求结构体
|
||
type UserLoginRequest struct {
|
||
UserID int `json:"user_id" binding:"required"`
|
||
UserName string `json:"user_name" binding:"required,max=100"`
|
||
Email string `json:"email" binding:"required,email,max=255"`
|
||
UUID string `json:"uuid" binding:"required"`
|
||
}
|
||
|
||
type ssoService struct {
|
||
client *middleware.SSOClient
|
||
pkceStateStorage storage.PKCEStateStorage
|
||
loginInfoStorage storage.LoginInfoStorage
|
||
rbacService RBACService
|
||
logger *utils.Logger
|
||
}
|
||
|
||
// NewSSOService 创建SSO服务实例
|
||
func NewSSOService(client *middleware.SSOClient, pkceStateStorage storage.PKCEStateStorage, loginInfoStorage storage.LoginInfoStorage, rbacService RBACService, logger *utils.Logger) SSOService {
|
||
return &ssoService{
|
||
client: client,
|
||
pkceStateStorage: pkceStateStorage,
|
||
loginInfoStorage: loginInfoStorage,
|
||
rbacService: rbacService,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// InitiateLogin 初始化SSO登录
|
||
func (s *ssoService) InitiateLogin(ctx context.Context) (*models.SSOLoginResponse, error) {
|
||
// 生成状态参数
|
||
state := fmt.Sprintf("state_%d", time.Now().UnixNano())
|
||
|
||
// 获取授权URL
|
||
authURL, codeVerifier, err := s.client.GetAuthorizationURL(state)
|
||
if err != nil {
|
||
s.logger.Error("failed to generate authorization URL", zap.Error(err))
|
||
return nil, fmt.Errorf("生成授权URL失败: %w", err)
|
||
}
|
||
|
||
// 将state和codeVerifier存储到数据库
|
||
pkceState := &models.PKCEState{
|
||
State: state,
|
||
CodeVerifier: codeVerifier,
|
||
}
|
||
|
||
if err := s.pkceStateStorage.Create(pkceState); err != nil {
|
||
s.logger.Error("failed to store PKCE state", zap.Error(err))
|
||
return nil, fmt.Errorf("存储PKCE状态失败: %w", err)
|
||
}
|
||
|
||
return &models.SSOLoginResponse{
|
||
Success: true,
|
||
Message: "SSO login initiated",
|
||
AuthURL: authURL,
|
||
State: state,
|
||
CodeVerifier: "", // 不返回codeVerifier到前端,保护安全
|
||
}, nil
|
||
}
|
||
|
||
// HandleCallback 处理SSO回调
|
||
func (s *ssoService) HandleCallback(ctx context.Context, req *models.SSOCallbackRequest) (*models.SSOCallbackResponse, error) {
|
||
// 从数据库获取PKCE状态信息
|
||
pkceState, err := s.pkceStateStorage.GetByState(req.State)
|
||
if err != nil {
|
||
s.logger.Error("failed to get PKCE state", zap.String("state", req.State), zap.Error(err))
|
||
return nil, fmt.Errorf("无效或过期的状态参数")
|
||
}
|
||
|
||
// 交换令牌
|
||
tokenResp, err := s.client.ExchangeCodeForToken(ctx, req.Code, pkceState.CodeVerifier)
|
||
if err != nil {
|
||
s.logger.Error("failed to exchange token", zap.Error(err))
|
||
return nil, fmt.Errorf("令牌交换失败: %w", err)
|
||
}
|
||
|
||
// 获取用户信息
|
||
userInfo, err := s.client.GetUserInfo(ctx, tokenResp.AccessToken)
|
||
if err != nil {
|
||
s.logger.Error("failed to get user info", zap.Error(err))
|
||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||
}
|
||
|
||
// 记录用户登录信息
|
||
uid, err := strconv.Atoi(userInfo.Sub)
|
||
if err != nil {
|
||
s.logger.Error("failed to convert user id", zap.String("sub", userInfo.Sub), zap.Error(err))
|
||
return nil, fmt.Errorf("无效的用户ID")
|
||
}
|
||
|
||
// 计算本次登录应赋予的角色ID:sys_admin → 5;否则 → 0
|
||
assignedRoleID := uint(0)
|
||
if len(userInfo.Roles) > 0 {
|
||
for _, r := range userInfo.Roles {
|
||
if r.Name == "sys_admin" {
|
||
assignedRoleID = uint(5)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
if assignedRoleID == 0 {
|
||
s.logger.Info("本次登录未检测到 sys_admin 角色,赋予无权限角色ID=0", zap.Int("userID", uid))
|
||
} else {
|
||
s.logger.Info("本次登录检测到 sys_admin 角色,赋予角色ID=5", zap.Int("userID", uid))
|
||
}
|
||
|
||
// 查找或创建用户记录,并根据本次判定设置 role_id(sys_admin=5,否则=0)
|
||
_, err = s.findOrCreateUserWithRole(uint(uid), userInfo, assignedRoleID)
|
||
if err != nil {
|
||
s.logger.Error("failed to find or create user", zap.Error(err))
|
||
return nil, fmt.Errorf("创建或查找用户失败: %w", err)
|
||
}
|
||
|
||
uuid := uuid.NewString()
|
||
err = s.RecordUserLogin(ctx, &UserLoginRequest{
|
||
UserID: uid,
|
||
UserName: userInfo.Name,
|
||
Email: userInfo.Email,
|
||
UUID: uuid,
|
||
})
|
||
if err != nil {
|
||
s.logger.Error("failed to record user login", zap.Int("user_id", uid), zap.Error(err))
|
||
// 不返回错误,继续处理
|
||
}
|
||
|
||
// 获取用户可访问页面信息
|
||
userPages, err := s.rbacService.GetUserAccessiblePages(uint(uid))
|
||
if err != nil {
|
||
s.logger.Warn("获取用户页面权限失败", zap.Error(err))
|
||
userPages = []string{}
|
||
}
|
||
s.logger.Info("获取用户可访问页面", zap.Uint("userID", uint(uid)), zap.Strings("pages", userPages))
|
||
|
||
// 获取用户角色信息(用于前端体验判断,后端仍以接口鉴权为准)
|
||
var user models.User
|
||
var userRoleName string
|
||
err = storage.DB.Where("id = ?", uint(uid)).First(&user).Error
|
||
if err != nil {
|
||
s.logger.Warn("获取用户信息失败", zap.Error(err))
|
||
} else {
|
||
role, rerr := s.rbacService.GetRoleByID(user.RoleID)
|
||
if rerr != nil {
|
||
s.logger.Warn("获取角色信息失败", zap.Error(rerr))
|
||
} else {
|
||
userRoleName = role.Name
|
||
s.logger.Info("获取用户角色", zap.Uint("userID", uint(uid)), zap.String("roleName", userRoleName))
|
||
}
|
||
}
|
||
|
||
// 构建包含页面权限的用户信息
|
||
userInfoWithPages := &models.UserInfo{
|
||
Sub: userInfo.Sub,
|
||
Name: userInfo.Name,
|
||
Email: userInfo.Email,
|
||
PreferredUsername: userInfo.PreferredUsername,
|
||
Pages: convertPagesFromPaths(userPages),
|
||
Roles: []models.Role{},
|
||
}
|
||
|
||
if userRoleName != "" {
|
||
userInfoWithPages.Roles = append(userInfoWithPages.Roles, models.Role{Name: userRoleName})
|
||
}
|
||
|
||
// 清理PKCE状态
|
||
_ = s.pkceStateStorage.DeleteByState(req.State)
|
||
|
||
return &models.SSOCallbackResponse{
|
||
Success: true,
|
||
Message: "SSO login successful",
|
||
AccessToken: tokenResp.AccessToken,
|
||
IDToken: tokenResp.IDToken,
|
||
RefreshToken: tokenResp.RefreshToken,
|
||
ExpiresIn: tokenResp.ExpiresIn,
|
||
UserInfo: userInfoWithPages,
|
||
UUID: uuid,
|
||
}, nil
|
||
}
|
||
|
||
// convertPagesFromPaths 将页面路径转换为页面对象
|
||
func convertPagesFromPaths(paths []string) []models.Page {
|
||
var pages []models.Page
|
||
|
||
fmt.Printf("🔍 [convertPagesFromPaths] 输入路径: %v\n", paths)
|
||
|
||
// 从数据库获取完整的页面信息
|
||
err := storage.DB.Where("path IN ? AND is_active = TRUE AND deleted_at IS NULL", paths).Find(&pages).Error
|
||
if err != nil {
|
||
fmt.Printf("❌ [convertPagesFromPaths] 数据库查询失败: %v\n", err)
|
||
// 如果数据库查询失败,至少返回路径信息
|
||
for _, path := range paths {
|
||
pages = append(pages, models.Page{
|
||
Path: path,
|
||
IsActive: true, // 默认设置为激活状态
|
||
})
|
||
}
|
||
} else {
|
||
fmt.Printf("✅ [convertPagesFromPaths] 数据库查询成功,找到 %d 个页面\n", len(pages))
|
||
for _, page := range pages {
|
||
fmt.Printf(" - 页面: %s, 激活状态: %v\n", page.Path, page.IsActive)
|
||
}
|
||
}
|
||
|
||
return pages
|
||
}
|
||
|
||
// RefreshToken 刷新令牌
|
||
func (s *ssoService) RefreshToken(ctx context.Context, req *models.RefreshTokenRequest) (*models.RefreshTokenResponse, error) {
|
||
// 刷新令牌
|
||
tokenResp, err := s.client.RefreshToken(ctx, req.RefreshToken)
|
||
if err != nil {
|
||
s.logger.Error("failed to refresh token", zap.Error(err))
|
||
return nil, fmt.Errorf("令牌刷新失败: %w", err)
|
||
}
|
||
|
||
return &models.RefreshTokenResponse{
|
||
Success: true,
|
||
Message: "Token refreshed successfully",
|
||
AccessToken: tokenResp.AccessToken,
|
||
IDToken: tokenResp.IDToken,
|
||
RefreshToken: tokenResp.RefreshToken,
|
||
ExpiresIn: tokenResp.ExpiresIn,
|
||
}, nil
|
||
}
|
||
|
||
// Logout 登出
|
||
func (s *ssoService) Logout(ctx context.Context, token string) (*models.LogoutResponse, error) {
|
||
// 获取用户信息用于记录登出
|
||
userInfo, err := s.client.GetUserInfo(ctx, token)
|
||
if err != nil {
|
||
s.logger.Error("failed to get user info during logout", zap.Error(err))
|
||
// 继续执行登出,不中断流程
|
||
}
|
||
|
||
// 调用SSO服务登出
|
||
err = s.client.Logout(ctx, token)
|
||
if err != nil {
|
||
s.logger.Error("failed to logout", zap.Error(err))
|
||
return nil, fmt.Errorf("登出失败: %w", err)
|
||
}
|
||
|
||
// 记录用户登出信息
|
||
if userInfo != nil {
|
||
uid, err := strconv.Atoi(userInfo.Sub)
|
||
if err != nil {
|
||
s.logger.Error("failed to convert user id during logout", zap.String("sub", userInfo.Sub), zap.Error(err))
|
||
} else {
|
||
// 先根据user_id查找用户的uuid
|
||
loginInfo, err := s.loginInfoStorage.GetByUserID(uid)
|
||
if err != nil {
|
||
s.logger.Error("failed to get user login info during logout", zap.Int("user_id", uid), zap.Error(err))
|
||
} else {
|
||
// 如果UUID为空,直接根据user_id进行登出
|
||
if loginInfo.UUID == "" {
|
||
s.logger.Warn("UUID is empty, logging out by user_id only", zap.Int("user_id", uid))
|
||
err = s.loginInfoStorage.SetUserOffline(uid)
|
||
if err != nil {
|
||
s.logger.Error("failed to set user offline by user_id", zap.Int("user_id", uid), zap.Error(err))
|
||
}
|
||
} else {
|
||
// 使用找到的uuid进行登出
|
||
err = s.UserLogout(ctx, uid, loginInfo.UUID)
|
||
if err != nil {
|
||
s.logger.Error("failed to record user logout", zap.Int("user_id", uid), zap.Error(err))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
redirectUrl := s.client.GetServerUrl() + "/oauth2/logout?redirect_uri=" + url.QueryEscape(s.client.GetRedirectUrl())
|
||
|
||
return &models.LogoutResponse{
|
||
Success: true,
|
||
Message: redirectUrl,
|
||
}, nil
|
||
}
|
||
|
||
// GetUserInfo 获取用户信息
|
||
func (s *ssoService) GetUserInfo(ctx context.Context, token string) (*models.UserInfoResponse, error) {
|
||
// 获取用户信息
|
||
userInfo, err := s.client.GetUserInfo(ctx, token)
|
||
if err != nil {
|
||
s.logger.Error("failed to get user info", zap.Error(err))
|
||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||
}
|
||
|
||
return &models.UserInfoResponse{
|
||
Success: true,
|
||
Message: "User info retrieved successfully",
|
||
UserInfo: userInfo,
|
||
}, nil
|
||
}
|
||
|
||
// RecordUserLogin 记录用户登录状态
|
||
func (s *ssoService) RecordUserLogin(ctx context.Context, req *UserLoginRequest) error {
|
||
// 检查用户是否已存在
|
||
existingUser, err := s.loginInfoStorage.GetByUserID(req.UserID)
|
||
if err != nil && err != gorm.ErrRecordNotFound {
|
||
s.logger.Error("failed to get existing user login info",
|
||
zap.Int("user_id", req.UserID),
|
||
zap.Error(err))
|
||
return fmt.Errorf("获取用户登录信息失败: %w", err)
|
||
}
|
||
|
||
if existingUser != nil {
|
||
// 用户存在直接更新状态
|
||
existingUser.IsOnline = true
|
||
existingUser.UUID = req.UUID // 同时更新UUID,可能有变化
|
||
existingUser.UserName = req.UserName
|
||
existingUser.Email = req.Email
|
||
|
||
err = s.loginInfoStorage.Update(existingUser)
|
||
if err != nil {
|
||
s.logger.Error("failed to update user online status",
|
||
zap.Int("user_id", req.UserID),
|
||
zap.Error(err))
|
||
return fmt.Errorf("更新用户在线状态失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("user login status updated successfully",
|
||
zap.Int("user_id", req.UserID),
|
||
zap.String("user_name", req.UserName))
|
||
} else {
|
||
// 用户不存在,创建新用户并设置为在线
|
||
newLoginInfo := &models.LoginInfo{
|
||
UserID: req.UserID,
|
||
UserName: req.UserName,
|
||
Email: req.Email,
|
||
UUID: req.UUID,
|
||
IsOnline: true,
|
||
}
|
||
|
||
err = s.loginInfoStorage.Create(newLoginInfo)
|
||
if err != nil {
|
||
s.logger.Error("failed to create new user login info",
|
||
zap.Int("user_id", req.UserID),
|
||
zap.String("user_name", req.UserName),
|
||
zap.Error(err))
|
||
return fmt.Errorf("创建用户登录信息失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("new user login info created successfully",
|
||
zap.Int("user_id", req.UserID),
|
||
zap.String("user_name", req.UserName))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// UserLogout 用户登出
|
||
func (s *ssoService) UserLogout(ctx context.Context, userID int, uuid string) error {
|
||
// 检查用户是否存在
|
||
existingUser, err := s.loginInfoStorage.GetByUserIDAndUUID(userID, uuid)
|
||
if err != nil && err != gorm.ErrRecordNotFound {
|
||
s.logger.Error("failed to get user login info for logout",
|
||
zap.Int("user_id", userID),
|
||
zap.String("uuid", uuid),
|
||
zap.Error(err))
|
||
return fmt.Errorf("获取用户登录信息失败: %w", err)
|
||
}
|
||
|
||
if existingUser == nil {
|
||
// 用户不存在,不需要操作
|
||
s.logger.Info("user not found, no logout action needed",
|
||
zap.Int("user_id", userID),
|
||
zap.String("uuid", uuid))
|
||
return nil
|
||
}
|
||
|
||
// 检查用户是否已经离线
|
||
if !existingUser.IsOnline {
|
||
// 用户已经离线,不需要操作
|
||
s.logger.Info("user is already offline, no action needed",
|
||
zap.Int("user_id", userID),
|
||
zap.String("uuid", uuid),
|
||
zap.String("user_name", existingUser.UserName))
|
||
return nil
|
||
}
|
||
|
||
// 设置用户为离线状态
|
||
err = s.loginInfoStorage.SetUserOffline(userID)
|
||
if err != nil {
|
||
s.logger.Error("failed to set user offline",
|
||
zap.Int("user_id", userID),
|
||
zap.String("user_name", existingUser.UserName),
|
||
zap.Error(err))
|
||
return fmt.Errorf("设置用户离线状态失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("user logout successfully",
|
||
zap.Int("user_id", userID),
|
||
zap.String("uuid", uuid),
|
||
zap.String("user_name", existingUser.UserName))
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetUserLoginInfo 获取用户登录信息
|
||
func (s *ssoService) GetUserLoginInfo(ctx context.Context, userID int) (*models.LoginInfo, error) {
|
||
loginInfo, err := s.loginInfoStorage.GetByUserID(userID)
|
||
if err != nil {
|
||
s.logger.Error("failed to get user login info",
|
||
zap.Int("user_id", userID),
|
||
zap.Error(err))
|
||
return nil, fmt.Errorf("获取用户登录信息失败: %w", err)
|
||
}
|
||
|
||
return loginInfo, nil
|
||
}
|
||
|
||
// IsUserOnline 检查用户是否在线
|
||
func (s *ssoService) IsUserOnline(ctx context.Context, userID int) (bool, error) {
|
||
loginInfo, err := s.GetUserLoginInfo(ctx, userID)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
if loginInfo == nil {
|
||
return false, nil
|
||
}
|
||
|
||
return loginInfo.IsOnline, nil
|
||
}
|
||
|
||
// GetOnlineUsers 获取在线用户列表
|
||
func (s *ssoService) GetOnlineUsers(ctx context.Context) ([]*models.LoginInfo, error) {
|
||
onlineUsers, err := s.loginInfoStorage.ListOnlineUsers()
|
||
if err != nil {
|
||
s.logger.Error("failed to get online users", zap.Error(err))
|
||
return nil, fmt.Errorf("获取在线用户列表失败: %w", err)
|
||
}
|
||
|
||
return onlineUsers, nil
|
||
}
|
||
|
||
// GetOnlineUserCount 获取在线用户数量
|
||
func (s *ssoService) GetOnlineUserCount(ctx context.Context) (int64, error) {
|
||
count, err := s.loginInfoStorage.CountOnlineUsers()
|
||
if err != nil {
|
||
s.logger.Error("failed to count online users", zap.Error(err))
|
||
return 0, fmt.Errorf("统计在线用户数量失败: %w", err)
|
||
}
|
||
|
||
return count, nil
|
||
}
|
||
|
||
// BatchUserLogout 批量用户登出(可用于系统维护等场景)
|
||
func (s *ssoService) BatchUserLogout(ctx context.Context, userIDs []int) error {
|
||
if len(userIDs) == 0 {
|
||
return nil
|
||
}
|
||
|
||
for _, userID := range userIDs {
|
||
err := s.loginInfoStorage.SetUserOffline(userID)
|
||
if err != nil {
|
||
s.logger.Error("failed to set user offline in batch",
|
||
zap.Int("user_id", userID),
|
||
zap.Error(err))
|
||
// 继续处理其他用户,不中断整个批量操作
|
||
continue
|
||
}
|
||
}
|
||
|
||
s.logger.Info("batch user logout completed",
|
||
zap.Ints("user_ids", userIDs))
|
||
|
||
return nil
|
||
}
|
||
|
||
// findOrCreateUser 查找或创建用户
|
||
func (s *ssoService) findOrCreateUserWithRole(userID uint, userInfo *models.UserInfo, assignedRoleID uint) (*models.User, error) {
|
||
// 尝试查找现有用户
|
||
var user models.User
|
||
err := storage.DB.Where("id = ?", userID).First(&user).Error
|
||
|
||
if err == nil {
|
||
// 用户存在,更新登录信息
|
||
now := time.Now()
|
||
user.LastLoginAt = &now
|
||
user.LoginCount++
|
||
|
||
// 更新用户信息(如果SSO信息有变化)
|
||
if userInfo.Name != "" && user.Nickname != userInfo.Name {
|
||
user.Nickname = userInfo.Name
|
||
}
|
||
if userInfo.Email != "" && user.Email != userInfo.Email {
|
||
user.Email = userInfo.Email
|
||
}
|
||
|
||
// 同步规则:
|
||
// - 若SSO判定为普通(0),无论当前为何值,一律降级为0
|
||
// - 若SSO判定为sys_admin(5),仅当当前为0时升级为5(避免覆盖业务自定义角色)
|
||
if assignedRoleID == 0 {
|
||
if user.RoleID != 0 {
|
||
s.logger.Info("降级用户角色(→0)", zap.Uint("userID", userID), zap.Uint("oldRoleID", user.RoleID))
|
||
user.RoleID = 0
|
||
} else {
|
||
s.logger.Debug("保持0角色不变", zap.Uint("userID", userID))
|
||
}
|
||
} else if assignedRoleID == 5 {
|
||
if user.RoleID == 0 {
|
||
s.logger.Info("升级用户角色(0→5)", zap.Uint("userID", userID))
|
||
user.RoleID = 5
|
||
} else {
|
||
s.logger.Debug("保持非0角色不变", zap.Uint("userID", userID), zap.Uint("currentRoleID", user.RoleID))
|
||
}
|
||
}
|
||
|
||
err = storage.DB.Save(&user).Error
|
||
if err != nil {
|
||
return nil, fmt.Errorf("更新用户信息失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("用户登录信息已更新",
|
||
zap.Uint("userID", userID),
|
||
zap.String("username", user.Username))
|
||
|
||
return &user, nil
|
||
}
|
||
|
||
if err != gorm.ErrRecordNotFound {
|
||
return nil, fmt.Errorf("查询用户失败: %w", err)
|
||
}
|
||
|
||
// 用户不存在,创建新用户
|
||
now := time.Now()
|
||
user = models.User{
|
||
BaseModel: models.BaseModel{
|
||
ID: userID,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
},
|
||
Username: userInfo.PreferredUsername,
|
||
Email: userInfo.Email,
|
||
Nickname: userInfo.Name,
|
||
Status: 1, // 默认启用
|
||
SSOProvider: "default", // 可以根据实际情况设置
|
||
LastLoginAt: &now,
|
||
LoginCount: 1,
|
||
RoleID: assignedRoleID,
|
||
}
|
||
|
||
// 如果PreferredUsername为空,使用Email作为用户名
|
||
if user.Username == "" {
|
||
user.Username = userInfo.Email
|
||
}
|
||
|
||
err = storage.DB.Create(&user).Error
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("新用户创建成功",
|
||
zap.Uint("userID", userID),
|
||
zap.String("username", user.Username),
|
||
zap.String("email", user.Email))
|
||
|
||
return &user, nil
|
||
}
|
||
|
||
// assignDefaultRole 分配默认角色(L5全员)
|
||
func (s *ssoService) assignDefaultRole(userID uint) error {
|
||
// 获取默认角色ID(L5全员)
|
||
var role models.Role
|
||
err := storage.DB.Where("is_default = ?", true).First(&role).Error
|
||
if err != nil {
|
||
return fmt.Errorf("获取默认角色失败: %w", err)
|
||
}
|
||
|
||
// 更新用户的角色ID
|
||
err = storage.DB.Model(&models.User{}).Where("id = ?", userID).Update("role_id", role.ID).Error
|
||
if err != nil {
|
||
return fmt.Errorf("分配默认角色失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("用户已分配默认角色",
|
||
zap.Uint("userID", userID),
|
||
zap.Uint("roleID", role.ID),
|
||
zap.String("roleName", role.Name))
|
||
|
||
return nil
|
||
}
|