feat: 新增第三方登录认证和管理

This commit is contained in:
TsMask
2025-08-12 09:52:10 +08:00
parent d3f7c75ab4
commit c79786e1a1
50 changed files with 1678 additions and 157 deletions

View File

@@ -67,6 +67,52 @@ func Setup(router *gin.Engine) {
)
}
// 登录认证源
account := controller.NewAccount
accountGroup := router.Group("/auth")
{
accountGroup.GET("/login/source",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 60,
Type: middleware.LIMIT_IP,
}),
account.LoginSource,
)
accountGroup.POST("/login/ldap",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.LDAP,
)
accountGroup.POST("/login/smtp",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.SMTP,
)
accountGroup.GET("/login/oauth2/authorize",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.OAuth2CodeURL,
)
accountGroup.POST("/login/oauth2/token",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
account.OAuth2Token,
)
}
// 账号注册操作处理
{
indexGroup.POST("/register",

View File

@@ -5,6 +5,7 @@ import (
commonConstants "be.ems/src/framework/constants/common"
tokenConstants "be.ems/src/framework/constants/token"
"be.ems/src/framework/i18n"
"be.ems/src/framework/resp"
"be.ems/src/framework/utils/ctx"
tokenUtils "be.ems/src/framework/utils/token"
"be.ems/src/framework/vo"
@@ -201,3 +202,11 @@ func (s *AccountController) Logout(c *gin.Context) {
c.JSON(200, result.OkMsg(i18n.TKey(language, "app.common.logoutSuccess")))
}
// LoginSource 登录认证源
//
// GET /auth/login/source
func (s AccountController) LoginSource(c *gin.Context) {
data := s.accountService.LoginSource()
c.JSON(200, resp.OkData(data))
}

View File

@@ -0,0 +1,67 @@
package controller
import (
"fmt"
commonConstants "be.ems/src/framework/constants/common"
tokenConstants "be.ems/src/framework/constants/token"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
tokenUtils "be.ems/src/framework/utils/token"
"be.ems/src/framework/vo/result"
"be.ems/src/modules/common/model"
"github.com/gin-gonic/gin"
)
// LDAP LDAP认证登录
//
// POST /auth/ldap
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/ldap [post]
func (s AccountController) LDAP(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginSourceBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 登录用户信息
loginUser, err := s.accountService.ByLDAP(body)
if err != nil {
c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 生成令牌,创建系统访问记录
tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser)
if tokenStr == "" {
c.JSON(200, result.Err(nil))
return
} else {
s.accountService.UpdateLoginDateAndIP(&loginUser)
// 登录成功
s.sysLogLoginService.CreateSysLogLogin(
body.Username, commonConstants.STATUS_YES, "app.common.loginSuccess",
ipaddr, location, os, browser,
)
}
c.JSON(200, result.OkData(map[string]any{
tokenConstants.RESPONSE_FIELD: tokenStr,
}))
}

View File

@@ -0,0 +1,94 @@
package controller
import (
"fmt"
commonConstants "be.ems/src/framework/constants/common"
tokenConstants "be.ems/src/framework/constants/token"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
tokenUtils "be.ems/src/framework/utils/token"
"be.ems/src/framework/vo/result"
"be.ems/src/modules/common/model"
"github.com/gin-gonic/gin"
)
// OAuth2CodeURL OAuth2认证跳转登录URL
//
// GET /auth/login/oauth2/authorize
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login/oauth2/authorize [get]
func (s AccountController) OAuth2CodeURL(c *gin.Context) {
state := c.Query("state")
if state == "" {
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: state is empty"))
return
}
redirectURL, err := s.accountService.ByOAuth2CodeURL(state)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
c.Redirect(302, redirectURL)
}
// OAuth2 OAuth2认证登录
//
// POST /auth/login/oauth2/token
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login/oauth2/token [post]
func (s AccountController) OAuth2Token(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginSourceOauth2Body
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 登录用户信息
loginUser, err := s.accountService.ByOAuth2(body)
if err != nil {
c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 生成令牌,创建系统访问记录
tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser)
if tokenStr == "" {
c.JSON(200, result.Err(nil))
return
} else {
s.accountService.UpdateLoginDateAndIP(&loginUser)
// 登录成功
s.sysLogLoginService.CreateSysLogLogin(
body.State, commonConstants.STATUS_YES, "app.common.loginSuccess",
ipaddr, location, os, browser,
)
}
c.JSON(200, result.OkData(map[string]any{
tokenConstants.RESPONSE_FIELD: tokenStr,
}))
}

View File

@@ -0,0 +1,67 @@
package controller
import (
"fmt"
commonConstants "be.ems/src/framework/constants/common"
tokenConstants "be.ems/src/framework/constants/token"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
tokenUtils "be.ems/src/framework/utils/token"
"be.ems/src/framework/vo/result"
"be.ems/src/modules/common/model"
"github.com/gin-gonic/gin"
)
// SMTP SMTP认证登录
//
// POST /auth/smtp
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/smtp [post]
func (s AccountController) SMTP(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginSourceBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 登录用户信息
loginUser, err := s.accountService.BySMTP(body)
if err != nil {
c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 生成令牌,创建系统访问记录
tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser)
if tokenStr == "" {
c.JSON(200, result.Err(nil))
return
} else {
s.accountService.UpdateLoginDateAndIP(&loginUser)
// 登录成功
s.sysLogLoginService.CreateSysLogLogin(
body.Username, commonConstants.STATUS_YES, "app.common.loginSuccess",
ipaddr, location, os, browser,
)
}
c.JSON(200, result.OkData(map[string]any{
tokenConstants.RESPONSE_FIELD: tokenStr,
}))
}

View File

@@ -0,0 +1,22 @@
package model
// LoginSourceVo 认证源登录对象
type LoginSourceVo struct {
UID string `json:"uid"` // UID 认证源UID
Name string `json:"name"` // Name 认证源名称
Type string `json:"type"` // Type 认证源类型
Icon string `json:"icon"` // Icon 认证源图标
}
// LoginSourceBody 认证源用户登录对象
type LoginSourceBody struct {
Username string `json:"username" binding:"required"` // Username 用户名
Password string `json:"password" binding:"required"` // Password 用户密码
UID string `json:"uid" binding:"required"` // UID 认证源唯一标识
}
// LoginSourceOauth2Body 认证源OAuth2用户登录对象
type LoginSourceOauth2Body struct {
Code string `json:"code" binding:"required"` // Code 授权码
State string `json:"state" binding:"required"` // State 状态-认证源唯一标识
}

View File

@@ -5,6 +5,7 @@ import (
"time"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
adminConstants "be.ems/src/framework/constants/admin"
"be.ems/src/framework/constants/cachekey"
"be.ems/src/framework/constants/common"
@@ -12,16 +13,18 @@ import (
"be.ems/src/framework/utils/crypto"
"be.ems/src/framework/utils/parse"
"be.ems/src/framework/vo"
"be.ems/src/modules/system/model"
"be.ems/src/modules/common/model"
systemModel "be.ems/src/modules/system/model"
systemService "be.ems/src/modules/system/service"
)
// 实例化服务层 Account 结构体
var NewAccount = &Account{
sysUserService: systemService.NewSysUserImpl,
sysConfigService: systemService.NewSysConfigImpl,
sysRoleService: systemService.NewSysRoleImpl,
sysMenuService: systemService.NewSysMenuImpl,
sysUserService: systemService.NewSysUserImpl,
sysConfigService: systemService.NewSysConfigImpl,
sysRoleService: systemService.NewSysRoleImpl,
sysMenuService: systemService.NewSysMenuImpl,
sysLogSourceService: systemService.NewSysLoginSource,
}
// 账号身份操作服务 服务层处理
@@ -33,7 +36,8 @@ type Account struct {
// 角色服务
sysRoleService systemService.ISysRole
// 菜单服务
sysMenuService systemService.ISysMenu
sysMenuService systemService.ISysMenu
sysLogSourceService *systemService.SysLoginSource // 认证源
}
// ValidateCaptcha 校验验证码
@@ -72,7 +76,7 @@ func (s *Account) LoginByUsername(username, password string) (vo.LoginUser, erro
}
// 查询用户登录账号
sysUser := s.sysUserService.SelectUserByUserName(username)
sysUser := s.sysUserService.FindByUserName(username, "System", "#")
if sysUser.UserName != username {
return loginUser, fmt.Errorf("login.errNameOrPasswd")
}
@@ -113,7 +117,7 @@ func (s *Account) LoginByUsername(username, password string) (vo.LoginUser, erro
// UpdateLoginDateAndIP 更新登录时间和IP
func (s *Account) UpdateLoginDateAndIP(loginUser *vo.LoginUser) bool {
sysUser := loginUser.User
userInfo := model.SysUser{
userInfo := systemModel.SysUser{
UserID: sysUser.UserID,
LoginIP: sysUser.LoginIP,
LoginDate: sysUser.LoginDate,
@@ -192,3 +196,44 @@ func (s *Account) RouteMenus(userId string, isAdmin bool) []vo.Router {
}
return buildMenus
}
// LoginSource 登录认证源
func (s Account) LoginSource() []model.LoginSourceVo {
rows := s.sysLogSourceService.FindByActive("")
data := make([]model.LoginSourceVo, 0)
for _, v := range rows {
data = append(data, model.LoginSourceVo{
UID: v.UID,
Name: v.Name,
Type: v.Type,
Icon: v.Icon,
})
}
return data
}
// initLoginSourceUser 初始化登录源用户
func (s *Account) initLoginSourceUser(uid, sType, username, password string) systemModel.SysUser {
sysUser := systemModel.SysUser{
UserName: username,
NickName: username, // 昵称使用名称账号
Password: password, // 原始密码
UserType: sType,
UserSource: uid,
Sex: "0", // 性别未选择
Status: constants.STATUS_YES, // 账号状态激活
DeptID: "101", // 归属部门为根节点
CreateBy: sType, // 创建来源
}
// 新增用户的角色管理
sysUser.RoleIDs = []string{"5"}
// 新增用户的岗位管理
sysUser.PostIDs = []string{}
insertId := s.sysUserService.InsertUser(sysUser)
if insertId != "" {
sysUser.UserID = insertId
}
return s.sysUserService.FindByUserName(username, sType, uid)
}

View File

@@ -0,0 +1,111 @@
package service
import (
"encoding/json"
"fmt"
"github.com/go-ldap/ldap/v3"
"be.ems/src/framework/config"
adminConstants "be.ems/src/framework/constants/admin"
"be.ems/src/framework/constants/common"
"be.ems/src/framework/utils/parse"
"be.ems/src/framework/vo"
"be.ems/src/modules/common/model"
systemModelVo "be.ems/src/modules/system/model/vo"
)
// ByLDAP 登录创建用户信息
func (s *Account) ByLDAP(body model.LoginSourceBody) (vo.LoginUser, error) {
loginUser := vo.LoginUser{}
rows := s.sysLogSourceService.FindByActive(body.UID)
if len(rows) != 1 {
return loginUser, fmt.Errorf("ldap auth source not exist")
}
item := rows[0]
if item.Config == "" {
return loginUser, fmt.Errorf("ldap auth source config is empty")
}
var source systemModelVo.SysLoginSourceLDAP
if err := json.Unmarshal([]byte(item.Config), &source); err != nil {
return loginUser, err
}
// 校验LDAP用户
err := ldapAuth(source, body.Username, body.Password)
if err != nil {
return loginUser, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(body.Username, item.Type, item.UID)
if sysUser.UserID == "" || sysUser.UserName == "" {
sysUser = s.initLoginSourceUser(item.UID, item.Type, body.Username, body.Password)
}
if sysUser.UserID == "" || sysUser.UserName != body.Username {
return loginUser, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == common.STATUS_YES {
// 对不起,您的账号已被删除
return loginUser, fmt.Errorf("login.errDelFlag")
}
if sysUser.Status == common.STATUS_NO {
return loginUser, fmt.Errorf("login.errStatus")
}
// 登录用户信息
loginUser.UserID = sysUser.UserID
loginUser.DeptID = sysUser.DeptID
loginUser.User = sysUser
// 用户权限组标识
isAdmin := config.IsAdmin(sysUser.UserID)
if isAdmin {
loginUser.Permissions = []string{adminConstants.PERMISSION}
} else {
perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID)
loginUser.Permissions = parse.RemoveDuplicates(perms)
}
return loginUser, nil
}
// ldapAuth 校验LDAP用户
func ldapAuth(source systemModelVo.SysLoginSourceLDAP, username, password string) error {
// 连接LDAP
l, err := ldap.DialURL(source.URL)
if err != nil {
return err
}
defer l.Close()
// 绑定DN校验
if source.BindDN != "" && source.BindPassword != "" {
if err := l.Bind(source.BindDN, source.BindPassword); err != nil {
return fmt.Errorf("ldap user bind %s", err)
}
}
// 搜索用户
searchRequest := ldap.NewSearchRequest(
source.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(source.UserFilter, ldap.EscapeFilter(username)),
[]string{"dn", "uid"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
return fmt.Errorf("ldap user search %s", err)
}
// for _, entry := range sr.Entries {
// fmt.Printf("%s ==== %v\n", entry.DN, entry.GetAttributeValue("uid"))
// }
if len(sr.Entries) != 1 {
return fmt.Errorf("ldap user does not exist or too many entries returned")
}
// 校验密码
if err = l.Bind(sr.Entries[0].DN, password); err != nil {
return fmt.Errorf("ldap user bind %s", err)
}
return nil
}

View File

@@ -0,0 +1,171 @@
package service
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"golang.org/x/oauth2"
"be.ems/src/framework/config"
adminConstants "be.ems/src/framework/constants/admin"
"be.ems/src/framework/constants/common"
"be.ems/src/framework/utils/parse"
"be.ems/src/framework/vo"
"be.ems/src/modules/common/model"
systemModelVo "be.ems/src/modules/system/model/vo"
)
// ByOAuth2CodeURL 获取OAuth2登录URL
func (s *Account) ByOAuth2CodeURL(state string) (string, error) {
rows := s.sysLogSourceService.FindByActive(state)
if len(rows) != 1 {
return "", fmt.Errorf("oauth2 auth source not exist")
}
item := rows[0]
if item.Config == "" {
return "", fmt.Errorf("oauth2 auth source config is empty")
}
var source systemModelVo.SysLoginSourceOAuth2
json.Unmarshal([]byte(item.Config), &source)
conf := oauth2.Config{
ClientID: source.ClientID,
ClientSecret: source.ClientSecret,
RedirectURL: source.RedirectURL,
Scopes: source.Scopes,
Endpoint: oauth2.Endpoint{
AuthURL: source.AuthURL,
TokenURL: source.TokenURL,
},
}
return conf.AuthCodeURL(state, oauth2.AccessTypeOffline), nil
}
// ByOAuth2 登录创建用户信息
func (s *Account) ByOAuth2(body model.LoginSourceOauth2Body) (vo.LoginUser, error) {
loginUser := vo.LoginUser{}
rows := s.sysLogSourceService.FindByActive(body.State)
if len(rows) != 1 {
return loginUser, fmt.Errorf("oauth2 auth source not exist")
}
item := rows[0]
if item.Config == "" {
return loginUser, fmt.Errorf("oauth2 auth source config is empty")
}
var source systemModelVo.SysLoginSourceOAuth2
if err := json.Unmarshal([]byte(item.Config), &source); err != nil {
return loginUser, err
}
// 校验OAuth2用户
account, err := oauth2Auth(source, body.Code)
if err != nil {
return loginUser, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(account, item.Type, item.UID)
if sysUser.UserID == "" || sysUser.UserName == "" {
sysUser = s.initLoginSourceUser(item.UID, item.Type, account, account)
}
if sysUser.UserID == "" || sysUser.UserName != account {
return loginUser, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == common.STATUS_YES {
// 对不起,您的账号已被删除
return loginUser, fmt.Errorf("login.errDelFlag")
}
if sysUser.Status == common.STATUS_NO {
return loginUser, fmt.Errorf("login.errStatus")
}
// 登录用户信息
loginUser.UserID = sysUser.UserID
loginUser.DeptID = sysUser.DeptID
loginUser.User = sysUser
// 用户权限组标识
isAdmin := config.IsAdmin(sysUser.UserID)
if isAdmin {
loginUser.Permissions = []string{adminConstants.PERMISSION}
} else {
perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID)
loginUser.Permissions = parse.RemoveDuplicates(perms)
}
return loginUser, nil
}
// oauth2Auth 校验OAuth2用户
func oauth2Auth(source systemModelVo.SysLoginSourceOAuth2, code string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conf := oauth2.Config{
ClientID: source.ClientID,
ClientSecret: source.ClientSecret,
RedirectURL: source.RedirectURL,
Scopes: source.Scopes,
Endpoint: oauth2.Endpoint{
AuthURL: source.AuthURL,
TokenURL: source.TokenURL,
},
}
token, err := conf.Exchange(ctx, code)
if err != nil {
return "", err
}
// 使用token创建HTTP客户端 请求用户信息
resp, err := conf.Client(ctx, token).Get(source.UserURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 解析用户信息
var userInfo map[string]any
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
return "", err
}
// 读取嵌套数据
value, found := getValueByPath(userInfo, source.AccountField)
if !found {
return "", fmt.Errorf("oauth2 auth source account field not exist")
}
return fmt.Sprintf("%v", value), nil
}
// getValueByPath 从嵌套的 map[string]any 获取嵌套键对应的值
func getValueByPath(data map[string]any, path string) (any, bool) {
keys := strings.Split(path, ".") // 按照 "." 拆分路径
return getValue(data, keys)
}
// getValue 是递归查找嵌套 map 的函数
func getValue(data map[string]any, keys []string) (any, bool) {
if len(keys) == 0 {
return data, false
}
// 获取当前键
key := keys[0]
// 获取当前键的值
val, ok := data[key]
if !ok {
return nil, false // 找不到键,返回 false
}
// 如果还有嵌套键,继续查找
if len(keys) > 1 {
// 递归查找嵌套 map
if reflect.TypeOf(val).Kind() == reflect.Map {
// 将 `any` 转换为 `map[string]any` 类型
if nestedMap, ok := val.(map[string]any); ok {
return getValue(nestedMap, keys[1:])
}
}
}
return val, true
}

View File

@@ -0,0 +1,91 @@
package service
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/wneessen/go-mail"
"be.ems/src/framework/config"
adminConstants "be.ems/src/framework/constants/admin"
"be.ems/src/framework/constants/common"
"be.ems/src/framework/utils/parse"
"be.ems/src/framework/vo"
"be.ems/src/modules/common/model"
systemModelVo "be.ems/src/modules/system/model/vo"
)
// BySMTP 登录创建用户信息
func (s *Account) BySMTP(body model.LoginSourceBody) (vo.LoginUser, error) {
loginUser := vo.LoginUser{}
rows := s.sysLogSourceService.FindByActive(body.UID)
if len(rows) != 1 {
return loginUser, fmt.Errorf("smtp auth source not exist")
}
item := rows[0]
if item.Config == "" {
return loginUser, fmt.Errorf("smtp auth source config is empty")
}
var source systemModelVo.SysLoginSourceSMTP
if err := json.Unmarshal([]byte(item.Config), &source); err != nil {
return loginUser, err
}
// 校验SMTP用户
err := smtpAuth(source, body.Username, body.Password)
if err != nil {
return loginUser, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(body.Username, item.Type, item.UID)
if sysUser.UserID == "" || sysUser.UserName == "" {
sysUser = s.initLoginSourceUser(item.UID, item.Type, body.Username, body.Password)
}
if sysUser.UserID == "" || sysUser.UserName != body.Username {
return loginUser, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == common.STATUS_YES {
// 对不起,您的账号已被删除
return loginUser, fmt.Errorf("login.errDelFlag")
}
if sysUser.Status == common.STATUS_NO {
return loginUser, fmt.Errorf("login.errStatus")
}
// 登录用户信息
loginUser.UserID = sysUser.UserID
loginUser.DeptID = sysUser.DeptID
loginUser.User = sysUser
// 用户权限组标识
isAdmin := config.IsAdmin(sysUser.UserID)
if isAdmin {
loginUser.Permissions = []string{adminConstants.PERMISSION}
} else {
perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID)
loginUser.Permissions = parse.RemoveDuplicates(perms)
}
return loginUser, nil
}
// smtpAuth 校验SMTP用户
func smtpAuth(source systemModelVo.SysLoginSourceSMTP, username, password string) error {
client, err := mail.NewClient(source.Host,
mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover),
mail.WithUsername(username),
mail.WithPort(source.Port),
mail.WithPassword(password),
mail.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
)
if err != nil {
return fmt.Errorf("failed to create mail client %s", err)
}
// 连接到邮件SMTP服务器
if err = client.DialWithContext(context.Background()); err != nil {
return err
}
defer client.Close()
return nil
}

View File

@@ -0,0 +1,163 @@
package controller
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/system/model"
"be.ems/src/modules/system/service"
)
// NewLoginSource 实例化控制层
var NewSysLoginSource = &SysLoginSourceController{
sysLoginSourceService: service.NewSysLoginSource,
}
// SysLoginSourceController 认证源管理 控制层处理
//
// PATH /sys/login-source
type SysLoginSourceController struct {
sysLoginSourceService *service.SysLoginSource // 认证源信息服务
}
// List 列表
//
// GET /list
func (s SysLoginSourceController) List(c *gin.Context) {
query := reqctx.QueryMap(c)
rows, total := s.sysLoginSourceService.FindByPage(query)
c.JSON(200, resp.OkData(map[string]any{"rows": rows, "total": total}))
}
// Info 信息
//
// GET /:id
func (s SysLoginSourceController) Info(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id is empty"))
return
}
info := s.sysLoginSourceService.FindById(parse.Number(id))
if info.Id == parse.Number(id) {
c.JSON(200, resp.OkData(info))
return
}
c.JSON(200, resp.ErrMsg("id does not exist"))
}
// Add 新增
//
// POST /
func (s SysLoginSourceController) Add(c *gin.Context) {
var body model.SysLoginSource
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
if body.Id > 0 {
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id not is empty"))
return
}
if len(body.Config) < 7 || !json.Valid([]byte(body.Config)) {
c.JSON(200, resp.ErrMsg("config json format error"))
return
}
configStr, err := s.sysLoginSourceService.CheckConfigJSON(body.Type, body.Config)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
body.Config = configStr
body.CreateBy = reqctx.LoginUserToUserName(c)
insertId := s.sysLoginSourceService.Insert(body)
if insertId > 0 {
c.JSON(200, resp.OkData(insertId))
return
}
c.JSON(200, resp.Err(nil))
}
// Edit 更新
//
// PUT /
func (s SysLoginSourceController) Edit(c *gin.Context) {
var body model.SysLoginSource
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs))
return
}
if body.Id <= 0 {
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id is empty"))
return
}
if len(body.Config) < 7 || !json.Valid([]byte(body.Config)) {
c.JSON(200, resp.ErrMsg("config json format error"))
return
}
configStr, err := s.sysLoginSourceService.CheckConfigJSON(body.Type, body.Config)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
body.Config = configStr
// 查询信息
info := s.sysLoginSourceService.FindById(body.Id)
if info.Id != body.Id {
c.JSON(200, resp.ErrMsg("modification failed, data not exist"))
return
}
info.Type = body.Type
info.Name = body.Name
info.Icon = body.Icon
info.Config = body.Config
info.ActiveFlag = body.ActiveFlag
info.Remark = body.Remark
info.UpdateBy = reqctx.LoginUserToUserName(c)
rowsAffected := s.sysLoginSourceService.Update(info)
if rowsAffected > 0 {
c.JSON(200, resp.Ok(nil))
return
}
c.JSON(200, resp.Err(nil))
}
// Remove 删除
//
// DELETE /:id
func (s SysLoginSourceController) Remove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "bind err: id is empty"))
return
}
// 处理字符转id数组后去重
uniqueIDs := parse.RemoveDuplicatesToArray(id, ",")
// 转换成int64数组类型
ids := make([]int64, 0)
for _, v := range uniqueIDs {
ids = append(ids, parse.Number(v))
}
rows, err := s.sysLoginSourceService.DeleteByIds(ids)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
c.JSON(200, resp.OkMsg(msg))
}

View File

@@ -181,7 +181,7 @@ func (s *SysProfileController) UpdateProfile(c *gin.Context) {
rows := s.sysUserService.UpdateUser(sysUser)
if rows > 0 {
// 更新缓存用户信息
loginUser.User = s.sysUserService.SelectUserByUserName(userName)
loginUser.User = s.sysUserService.FindByUserName(userName, "System", "#")
// 用户权限组标识
isAdmin := config.IsAdmin(sysUser.UserID)
if isAdmin {
@@ -323,7 +323,7 @@ func (s *SysProfileController) Avatar(c *gin.Context) {
rows := s.sysUserService.UpdateUser(sysUser)
if rows > 0 {
// 更新缓存用户信息
loginUser.User = s.sysUserService.SelectUserByUserName(loginUser.User.UserName)
loginUser.User = s.sysUserService.FindByUserName(loginUser.User.UserName, "System", "#")
// 用户权限组标识
isAdmin := config.IsAdmin(sysUser.UserID)
if isAdmin {

View File

@@ -526,9 +526,10 @@ func (s *SysUserController) Export(c *gin.Context) {
"C1": i18n.TKey(language, "user.export.nick"),
"D1": i18n.TKey(language, "user.export.role"),
"E1": i18n.TKey(language, "user.export.deptName"),
"F1": i18n.TKey(language, "user.export.loginIP"),
"G1": i18n.TKey(language, "user.export.loginDate"),
"H1": i18n.TKey(language, "user.export.status"),
"F1": i18n.TKey(language, "user.export.userType"),
"G1": i18n.TKey(language, "user.export.loginIP"),
"H1": i18n.TKey(language, "user.export.loginDate"),
"I1": i18n.TKey(language, "user.export.status"),
// "F1": i18n.TKey(language, "user.export.sex"),
// "E1": i18n.TKey(language, "user.export.phone"),
// "D1": i18n.TKey(language, "user.export.email"),
@@ -536,6 +537,8 @@ func (s *SysUserController) Export(c *gin.Context) {
// "K1": i18n.TKey(language, "user.export.deptLeader"),
}
// 读取用户性别字典数据
dictSysUserType := s.sysDictDataService.SelectDictDataByType("sys_user_type")
// 读取用户性别字典数据
// dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex")
// 从第二行开始的数据
dataCells := make([]map[string]any, 0)
@@ -549,6 +552,14 @@ func (s *SysUserController) Export(c *gin.Context) {
// break
// }
// }
// 用户类型
userType := row.UserType
for _, v := range dictSysUserType {
if row.UserType == v.DictValue && row.UserSource != "#" {
userType = i18n.TKey(language, v.DictLabel) + " " + row.UserSource
break
}
}
// 帐号状态
statusValue := i18n.TKey(language, "dictData.disable")
if row.Status == "1" {
@@ -557,7 +568,11 @@ func (s *SysUserController) Export(c *gin.Context) {
// 用户角色, 默认导出首个
userRole := ""
if len(row.Roles) > 0 {
userRole = i18n.TKey(language, row.Roles[0].RoleName)
arr := make([]string, 0)
for _, v := range row.Roles {
arr = append(arr, i18n.TKey(language, v.RoleName))
}
userRole = strings.Join(arr, ",")
}
dataCells = append(dataCells, map[string]any{
"A" + idx: row.UserID,
@@ -565,9 +580,10 @@ func (s *SysUserController) Export(c *gin.Context) {
"C" + idx: row.NickName,
"D" + idx: userRole,
"E" + idx: row.Dept.DeptName,
"F" + idx: row.LoginIP,
"G" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS),
"H" + idx: statusValue,
"F" + idx: userType,
"G" + idx: row.LoginIP,
"H" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS),
"I" + idx: statusValue,
// "E" + idx: row.PhoneNumber,
// "F" + idx: sysUserSex,
// "D" + idx: row.Email,
@@ -752,7 +768,7 @@ func (s *SysUserController) ImportData(c *gin.Context) {
}
// 验证是否存在这个用户
userInfo := s.sysUserService.SelectUserByUserName(newSysUser.UserName)
userInfo := s.sysUserService.FindByUserName(newSysUser.UserName, "System", "#")
if userInfo.UserName != newSysUser.UserName {
newSysUser.CreateBy = operName
insertId := s.sysUserService.InsertUser(newSysUser)

View File

@@ -0,0 +1,22 @@
package model
// SysLoginSource 系统第三方认证源 sys_login_source
type SysLoginSource struct {
Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // ID
UID string `gorm:"column:uid" json:"uid"` // UID 16位长度字符串
Type string `gorm:"column:type" json:"type" binding:"required,oneof=LDAP SMTP OAuth2"` // 认证类型 LDAP SMTP OAuth2
Name string `gorm:"column:name" json:"name" binding:"required"` // 认证名称
Icon string `gorm:"column:icon" json:"icon"` // 图标
ActiveFlag string `gorm:"column:active_flag" json:"activeFlag"` // 激活标记0未激活 1激活
SyncFlag string `gorm:"column:sync_flag" json:"syncFlag"` // 同步标记0未同步 1同步
Config string `gorm:"column:config" json:"config" binding:"required"` // 配置JSON字符串
CreateBy string `gorm:"column:create_by" json:"createBy"` // 创建者
CreateTime int64 `gorm:"column:create_time" json:"createTime"` // 创建时间
UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 更新者
UpdateTime int64 `gorm:"column:update_time" json:"updateTime"` // 更新时间
Remark string `gorm:"column:remark" json:"remark"` // 备注
}
func (*SysLoginSource) TableName() string {
return "sys_login_source"
}

View File

@@ -3,58 +3,64 @@ package model
// SysUser 用户对象 sys_user
type SysUser struct {
// 用户ID
UserID string `json:"userId"`
UserID string `json:"userId" gorm:"column:user_id;type:bigint;primaryKey"`
// 部门ID
DeptID string `json:"deptId"`
DeptID string `json:"deptId" gorm:"column:dept_id"`
// 租户ID
TenantID string `json:"tenantId"`
TenantID string `json:"tenantId" gorm:"column:tenant_id"`
// 用户账号
UserName string `json:"userName" binding:"required"`
UserName string `json:"userName" binding:"required" gorm:"column:user_name"`
// 用户昵称
NickName string `json:"nickName" binding:"required"`
// 用户类型(sys系统用户
UserType string `json:"userType"`
NickName string `json:"nickName" binding:"required" gorm:"column:nick_name"`
// 用户类型(System系统用户)
UserType string `json:"userType" gorm:"column:user_type"`
UserSource string `json:"userSource" gorm:"column:user_source"` // 用户来源UID (系统#)
// 用户邮箱
Email string `json:"email"`
Email string `json:"email" gorm:"column:email"`
// 手机号码
PhoneNumber string `json:"phonenumber"`
PhoneNumber string `json:"phonenumber" gorm:"column:phonenumber"`
// 用户性别0未知 1男 2女
Sex string `json:"sex"`
Sex string `json:"sex" gorm:"column:sex"`
// 头像地址
Avatar string `json:"avatar"`
Avatar string `json:"avatar" gorm:"column:avatar"`
// 密码
Password string `json:"-"`
Password string `json:"-" gorm:"column:password"`
// 帐号状态0停用 1正常
Status string `json:"status"`
Status string `json:"status" gorm:"column:status"`
// 删除标志0代表存在 1代表删除
DelFlag string `json:"delFlag"`
DelFlag string `json:"delFlag" gorm:"column:del_flag"`
// 最后登录IP
LoginIP string `json:"loginIp"`
LoginIP string `json:"loginIp" gorm:"column:login_ip"`
// 最后登录时间
LoginDate int64 `json:"loginDate"`
LoginDate int64 `json:"loginDate" gorm:"column:login_date"`
// 创建者
CreateBy string `json:"createBy"`
CreateBy string `json:"createBy" gorm:"column:create_by"`
// 创建时间
CreateTime int64 `json:"createTime"`
CreateTime int64 `json:"createTime" gorm:"column:create_time"`
// 更新者
UpdateBy string `json:"updateBy"`
UpdateBy string `json:"updateBy" gorm:"column:update_by"`
// 更新时间
UpdateTime int64 `json:"updateTime"`
UpdateTime int64 `json:"updateTime" gorm:"column:update_time"`
// 备注
Remark string `json:"remark"`
Remark string `json:"remark" gorm:"column:remark"`
// ====== 非数据库字段属性 ======
// 部门对象
Dept SysDept `json:"dept,omitempty" binding:"structonly"`
Dept SysDept `json:"dept,omitempty" binding:"structonly" gorm:"-"`
// 租户对象
Tenant SysTenant `json:"tenant,omitempty" binding:"structonly"`
Tenant SysTenant `json:"tenant,omitempty" binding:"structonly" gorm:"-"`
// 角色对象组
Roles []SysRole `json:"roles"`
Roles []SysRole `json:"roles" gorm:"-"`
// 角色ID
RoleID string `json:"roleId,omitempty"`
RoleID string `json:"roleId,omitempty" gorm:"-"`
// 角色组
RoleIDs []string `json:"roleIds,omitempty"`
RoleIDs []string `json:"roleIds,omitempty" gorm:"-"`
// 岗位组
PostIDs []string `json:"postIds,omitempty"`
PostIDs []string `json:"postIds,omitempty" gorm:"-"`
}
// TableName 表名称
func (*SysUser) TableName() string {
return "sys_user"
}

View File

@@ -0,0 +1,28 @@
package vo
// SysLoginSourceLDAP LDAP认证源
type SysLoginSourceLDAP struct {
URL string `json:"url"` // LDAP 服务器ldap://192.168.9.58:11389
BaseDN string `json:"baseDN"` // base DNdc=example,dc=org
UserFilter string `json:"userFilter"` // 用户过滤规则((&(objectClass=organizationalPerson)(uid=%s)))
BindDN string `json:"bindDN"` // 绑定 DNcn=admin,dc=example,dc=org
BindPassword string `json:"bindPassword"` // 绑定密码adminpassword
}
// SysLoginSourceSMTP SMTP认证源
type SysLoginSourceSMTP struct {
Host string `json:"host"` // SMTP 服务器smtp.gmail.com
Port int `json:"port"` // SMTP 端口587
}
// SysLoginSourceOAuth2 OAuth2认证源
type SysLoginSourceOAuth2 struct {
ClientID string `json:"clientID"` // 客户端ID
ClientSecret string `json:"clientSecret"` // 客户端密钥
AuthURL string `json:"authURL"` // 认证URL
TokenURL string `json:"tokenURL"` // 令牌URL
Scopes []string `json:"scopes"` // 授权范围
RedirectURL string `json:"redirectURL"` // 重定向URL
UserURL string `json:"userURL"` // 用户信息URL
AccountField string `json:"accountField"` // 账号字段从用户信息中获取
}

View File

@@ -39,4 +39,7 @@ type ISysDept interface {
// DeleteDeptById 删除部门管理信息
DeleteDeptById(deptId string) int64
// SelectById 通过ID查询信息
SelectById(deptId string) model.SysDept
}

View File

@@ -391,3 +391,25 @@ func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 {
}
return results
}
// SelectById 通过ID查询信息
func (r *SysDeptImpl) SelectById(deptId string) model.SysDept {
if deptId == "" {
return model.SysDept{}
}
querySql := `select d.dept_id, d.parent_id, d.ancestors,
d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status,
(select dept_name from sys_dept where dept_id = d.parent_id) parent_name
from sys_dept d where d.dept_id = ?`
results, err := datasource.RawDB("", querySql, []any{deptId})
if err != nil {
logger.Errorf("query err => %v", err)
return model.SysDept{}
}
// 转换实体
rows := r.convertResultRows(results)
if len(rows) > 0 {
return rows[0]
}
return model.SysDept{}
}

View File

@@ -0,0 +1,150 @@
package repository
import (
"fmt"
"time"
"be.ems/src/framework/database/db"
"be.ems/src/framework/logger"
"be.ems/src/modules/system/model"
)
// NewSysLoginSource 实例化数据层
var NewSysLoginSource = &SysLoginSource{}
// SysLoginSource 认证源数据层处理
type SysLoginSource struct{}
// SelectByPage 分页查询集合
func (r SysLoginSource) SelectByPage(query map[string]string) ([]model.SysLoginSource, int64) {
tx := db.DB("").Model(&model.SysLoginSource{})
// 查询条件拼接
if v, ok := query["name"]; ok && v != "" {
tx = tx.Where("name like ?", v+"%")
}
if v, ok := query["type"]; ok && v != "" {
tx = tx.Where("type = ?", v)
}
if v, ok := query["beginTime"]; ok && v != "" {
if len(v) == 10 {
v = fmt.Sprintf("%s000", v)
}
tx = tx.Where("create_time >= ?", v)
}
if v, ok := query["endTime"]; ok && v != "" {
if len(v) == 10 {
v = fmt.Sprintf("%s999", v)
}
tx = tx.Where("create_time <= ?", v)
}
// 查询结果
var total int64 = 0
rows := []model.SysLoginSource{}
// 查询数量为0直接返回
if err := tx.Count(&total).Error; err != nil || total <= 0 {
return rows, total
}
// 查询数据分页
pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"])
tx = tx.Limit(pageSize).Offset(pageSize * pageNum)
err := tx.Find(&rows).Error
if err != nil {
return rows, total
}
return rows, total
}
// Select 查询集合
func (r SysLoginSource) Select(param model.SysLoginSource) []model.SysLoginSource {
tx := db.DB("").Model(&model.SysLoginSource{})
// 查询条件拼接
if param.UID != "" {
tx = tx.Where("uid = ?", param.UID)
}
if param.Type != "" {
tx = tx.Where("type = ?", param.Type)
}
if param.Name != "" {
tx = tx.Where("name = ?", param.Name)
}
if param.ActiveFlag != "" {
tx = tx.Where("active_flag = ?", param.ActiveFlag)
}
// 查询数据
rows := []model.SysLoginSource{}
if err := tx.Find(&rows).Error; err != nil {
return rows
}
return rows
}
// SelectByIds 通过ID查询信息
func (r SysLoginSource) SelectByIds(ids []int64) []model.SysLoginSource {
rows := []model.SysLoginSource{}
if len(ids) <= 0 {
return rows
}
tx := db.DB("").Model(&model.SysLoginSource{})
// 构建查询条件
tx = tx.Where("id in ?", ids)
// 查询数据
if err := tx.Find(&rows).Error; err != nil {
logger.Errorf("query find err => %v", err.Error())
return rows
}
return rows
}
// Insert 新增信息 返回新增数据ID
func (r SysLoginSource) Insert(param model.SysLoginSource) int64 {
if param.CreateBy != "" {
ms := time.Now().UnixMilli()
param.UpdateBy = param.CreateBy
param.UpdateTime = ms
param.CreateTime = ms
}
// 执行插入
if err := db.DB("").Create(&param).Error; err != nil {
logger.Errorf("insert err => %v", err.Error())
return 0
}
return param.Id
}
// Update 修改信息 返回受影响行数
func (r SysLoginSource) Update(param model.SysLoginSource) int64 {
if param.Id <= 0 {
return 0
}
if param.UpdateBy != "" {
param.UpdateTime = time.Now().UnixMilli()
}
tx := db.DB("").Model(&model.SysLoginSource{})
// 构建查询条件
tx = tx.Where("id = ?", param.Id)
tx = tx.Omit("id", "create_by", "create_time")
// 执行更新
if err := tx.Updates(param).Error; err != nil {
logger.Errorf("update err => %v", err.Error())
return 0
}
return tx.RowsAffected
}
// DeleteByIds 批量删除信息 返回受影响行数
func (r SysLoginSource) DeleteByIds(ids []int64) int64 {
if len(ids) <= 0 {
return 0
}
tx := db.DB("").Where("id in ?", ids)
// 执行删除
if err := tx.Delete(&model.SysLoginSource{}).Error; err != nil {
logger.Errorf("delete err => %v", err.Error())
return 0
}
return tx.RowsAffected
}

View File

@@ -27,4 +27,7 @@ type ISysRole interface {
// CheckUniqueRole 校验角色是否唯一
CheckUniqueRole(sysRole model.SysRole) string
// SelectByUserId 根据用户ID获取角色信息
SelectByUserId(userId string) []model.SysRole
}

View File

@@ -373,3 +373,14 @@ func (r *SysRoleImpl) CheckUniqueRole(sysRole model.SysRole) string {
}
return ""
}
// SelectByUserId 根据用户ID获取角色信息
func (r *SysRoleImpl) SelectByUserId(userId string) []model.SysRole {
querySql := r.selectSql + " where r.del_flag = '0' and ur.user_id = ?"
results, err := datasource.RawDB("", querySql, []any{userId})
if err != nil {
logger.Errorf("query err => %v", err)
return []model.SysRole{}
}
return r.convertResultRows(results)
}

View File

@@ -30,4 +30,7 @@ type ISysUser interface {
// CheckUniqueUser 校验用户信息是否唯一
CheckUniqueUser(sysUser model.SysUser) string
// SelectByUserName 通过登录账号查询信息
SelectByUserName(userName, userType, userSource string) model.SysUser
}

View File

@@ -16,7 +16,7 @@ import (
// 实例化数据层 SysUserImpl 结构体
var NewSysUserImpl = &SysUserImpl{
selectSql: `select
u.user_id, u.dept_id, u.tenant_id, u.user_name, u.nick_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
u.user_id, u.dept_id, u.tenant_id, u.user_name, u.nick_name, u.user_type, u.user_source, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
t.tenant_id, t.parent_id, t.ancestors, t.tenant_name, t.order_num, t.tenancy_type, t.tenancy_key, t.status as tenant_status,
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
@@ -33,6 +33,7 @@ var NewSysUserImpl = &SysUserImpl{
"user_name": "UserName",
"nick_name": "NickName",
"user_type": "UserType",
"user_source": "UserSource",
"email": "Email",
"phonenumber": "PhoneNumber",
"sex": "Sex",
@@ -171,6 +172,14 @@ func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string)
conditions = append(conditions, "u.phonenumber like concat(?, '%')")
params = append(params, v)
}
if v, ok := query["userType"]; ok && v != "" {
conditions = append(conditions, "u.user_type = ?")
params = append(params, v)
}
if v, ok := query["userSource"]; ok && v != "" {
conditions = append(conditions, "u.user_source = ?")
params = append(params, v)
}
beginTime, ok := query["beginTime"]
if !ok {
beginTime, ok = query["params[beginTime]"]
@@ -398,6 +407,32 @@ func (r *SysUserImpl) SelectUserByIds(userIds []string) []model.SysUser {
return r.convertResultRows(results)
}
// SelectByUserName 通过登录账号查询信息
func (r *SysUserImpl) SelectByUserName(userName, userType, userSource string) model.SysUser {
item := model.SysUser{}
if userName == "" {
return item
}
if userType == "" {
userType = "System"
}
if userSource == "" {
userSource = "#"
}
querySql := r.selectSql + " where u.del_flag = '0' and u.user_name = ? and u.user_type = ? and u.user_source = ?"
results, err := datasource.RawDB("", querySql, []any{userName, userType, userSource})
if err != nil {
logger.Errorf("query err => %v", err)
return model.SysUser{}
}
// 转换实体
rows := r.convertResultRows(results)
if len(rows) > 0 {
return rows[0]
}
return item
}
// SelectUserByUserName 通过用户登录账号查询用户
func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser {
querySql := r.selectSql + " where u.del_flag = '0' and u.user_name = ?"
@@ -436,6 +471,9 @@ func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string {
if sysUser.UserType != "" {
params["user_type"] = sysUser.UserType
}
if sysUser.UserSource != "" {
params["user_source"] = sysUser.UserSource
}
if sysUser.Avatar != "" {
params["avatar"] = sysUser.Avatar
}
@@ -509,6 +547,9 @@ func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 {
if sysUser.UserType != "" {
params["user_type"] = sysUser.UserType
}
if sysUser.UserSource != "" {
params["user_source"] = sysUser.UserSource
}
if sysUser.Avatar != "" {
params["avatar"] = sysUser.Avatar
}
@@ -582,6 +623,14 @@ func (r *SysUserImpl) CheckUniqueUser(sysUser model.SysUser) string {
conditions = append(conditions, "email = ?")
params = append(params, sysUser.Email)
}
if sysUser.UserType != "" {
conditions = append(conditions, "user_type = ?")
params = append(params, sysUser.UserType)
}
if sysUser.UserSource != "" {
conditions = append(conditions, "user_source = ?")
params = append(params, sysUser.UserSource)
}
// 构建查询条件语句
whereSql := ""

View File

@@ -0,0 +1,95 @@
package service
import (
"encoding/json"
"fmt"
"be.ems/src/framework/utils/generate"
"be.ems/src/modules/system/model"
"be.ems/src/modules/system/model/vo"
"be.ems/src/modules/system/repository"
)
// NewSysLoginSource 实例化服务层
var NewSysLoginSource = &SysLoginSource{
sysLoginSourceRepository: repository.NewSysLoginSource,
}
// SysLoginSource 认证源 服务层处理
type SysLoginSource struct {
sysLoginSourceRepository *repository.SysLoginSource // 认证源表
}
// FindByPage 分页查询
func (s SysLoginSource) FindByPage(query map[string]string) ([]model.SysLoginSource, int64) {
return s.sysLoginSourceRepository.SelectByPage(query)
}
// FindById 查询ID
func (s SysLoginSource) FindById(id int64) model.SysLoginSource {
rows := s.sysLoginSourceRepository.SelectByIds([]int64{id})
if len(rows) > 0 {
return rows[0]
}
return model.SysLoginSource{}
}
// Insert 新增
func (s SysLoginSource) Insert(param model.SysLoginSource) int64 {
param.UID = generate.Code(8)
return s.sysLoginSourceRepository.Insert(param)
}
// Update 更新
func (s SysLoginSource) Update(param model.SysLoginSource) int64 {
return s.sysLoginSourceRepository.Update(param)
}
// DeleteByIds 批量删除
func (s SysLoginSource) DeleteByIds(ids []int64) (int64, error) {
// 检查是否存在
arr := s.sysLoginSourceRepository.SelectByIds(ids)
if len(arr) <= 0 {
// return 0, fmt.Errorf("没有权限访问认证源数据!")
return 0, fmt.Errorf("no permission to access authentication source data")
}
if len(arr) == len(ids) {
return s.sysLoginSourceRepository.DeleteByIds(ids), nil
}
// return 0, fmt.Errorf("删除认证源信息失败!")
return 0, fmt.Errorf("failed to delete authentication source information")
}
// FindByActive 查询激活
func (s SysLoginSource) FindByActive(uid string) []model.SysLoginSource {
param := model.SysLoginSource{
ActiveFlag: "1",
}
if uid != "" {
param.UID = uid
}
return s.sysLoginSourceRepository.Select(param)
}
// CheckConfigJSON 检查配置JSON
func (s SysLoginSource) CheckConfigJSON(sType, sConfig string) (string, error) {
var source any
switch sType {
case "LDAP":
source = new(vo.SysLoginSourceLDAP)
case "SMTP":
source = new(vo.SysLoginSourceSMTP)
case "OAuth2":
source = new(vo.SysLoginSourceOAuth2)
default:
return "", fmt.Errorf("unsupported login source type: %s", sType)
}
if err := json.Unmarshal([]byte(sConfig), &source); err != nil {
return "", fmt.Errorf("config json format error for %s type: %s", sType, err.Error())
}
configByte, err := json.Marshal(source)
if err != nil {
return "", fmt.Errorf("config json format error")
}
return string(configByte), nil
}

View File

@@ -13,6 +13,11 @@ type ISysUser interface {
// SelectAllocatedPage 根据条件分页查询分配用户角色列表
SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any
// FindByUserName 通过用户名查询用户信息
// userType 系统sys
// userSource 系统#
FindByUserName(userName, userType, userSource string) model.SysUser
// SelectUserByUserName 通过用户名查询用户
SelectUserByUserName(userName string) model.SysUser

View File

@@ -13,6 +13,8 @@ var NewSysUserImpl = &SysUserImpl{
sysUserRepository: repository.NewSysUserImpl,
sysUserRoleRepository: repository.NewSysUserRoleImpl,
sysUserPostRepository: repository.NewSysUserPostImpl,
sysDeptRepository: repository.NewSysDeptImpl,
sysRoleRepository: repository.NewSysRoleImpl,
}
// SysUserImpl 用户 服务层处理
@@ -23,6 +25,10 @@ type SysUserImpl struct {
sysUserRoleRepository repository.ISysUserRole
// 用户与岗位服务
sysUserPostRepository repository.ISysUserPost
// 部门服务
sysDeptRepository repository.ISysDept
// 角色服务
sysRoleRepository repository.ISysRole
}
// SelectUserPage 根据条件分页查询用户列表
@@ -40,6 +46,30 @@ func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL str
return r.sysUserRepository.SelectAllocatedPage(query, dataScopeSQL)
}
// FindByUserName 通过用户名查询用户信息
// userType 系统sys
// userSource 系统#
func (s SysUserImpl) FindByUserName(userName, userType, userSource string) model.SysUser {
userinfo := s.sysUserRepository.SelectByUserName(userName, userType, userSource)
if userinfo.UserName != userName {
return userinfo
}
// 部门
deptInfo := s.sysDeptRepository.SelectById(userinfo.DeptID)
userinfo.Dept = deptInfo
// 角色
roleArr := s.sysRoleRepository.SelectByUserId(userinfo.UserID)
roles := make([]model.SysRole, 0)
roleIds := make([]string, 0)
for _, role := range roleArr {
roles = append(roles, role)
roleIds = append(roleIds, role.RoleID)
}
userinfo.Roles = roles
userinfo.RoleIDs = roleIds
return userinfo
}
// SelectUserByUserName 通过用户名查询用户
func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser {
return r.sysUserRepository.SelectUserByUserName(userName)
@@ -147,7 +177,9 @@ func (r *SysUserImpl) DeleteUserByIds(userIds []string) (int64, error) {
// CheckUniqueUserName 校验用户名称是否唯一
func (r *SysUserImpl) CheckUniqueUserName(userName, userId string) bool {
uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{
UserName: userName,
UserName: userName,
UserType: "System",
UserSource: "#",
})
if uniqueId == userId {
return true
@@ -159,6 +191,8 @@ func (r *SysUserImpl) CheckUniqueUserName(userName, userId string) bool {
func (r *SysUserImpl) CheckUniquePhone(phonenumber, userId string) bool {
uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{
PhoneNumber: phonenumber,
UserType: "System",
UserSource: "#",
})
if uniqueId == userId {
return true
@@ -169,7 +203,9 @@ func (r *SysUserImpl) CheckUniquePhone(phonenumber, userId string) bool {
// CheckUniqueEmail 校验email是否唯一
func (r *SysUserImpl) CheckUniqueEmail(email, userId string) bool {
uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{
Email: email,
Email: email,
UserType: "System",
UserSource: "#",
})
if uniqueId == userId {
return true

View File

@@ -472,6 +472,35 @@ func Setup(router *gin.Engine) {
controller.NewSysLogLogin.Export,
)
}
// 第三方认证-配置认证源
sysLoginSource := controller.NewSysLoginSource
sysLoginSourceGroup := router.Group("/system/login-source")
{
sysLoginSourceGroup.GET("/list",
middleware.PreAuthorize(map[string][]string{"hasRoles": {"admin"}, "hasPerms": {"system:loginSource:list"}}),
sysLoginSource.List,
)
sysLoginSourceGroup.GET("/:id",
middleware.PreAuthorize(map[string][]string{"hasRoles": {"admin"}, "hasPerms": {"system:loginSource:query"}}),
sysLoginSource.Info,
)
sysLoginSourceGroup.POST("",
middleware.PreAuthorize(map[string][]string{"hasRoles": {"admin"}, "hasPerms": {"system:loginSource:add"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLoginSource", collectlogs.BUSINESS_TYPE_INSERT)),
sysLoginSource.Add,
)
sysLoginSourceGroup.PUT("",
middleware.PreAuthorize(map[string][]string{"hasRoles": {"admin"}, "hasPerms": {"system:loginSource:edit"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLoginSource", collectlogs.BUSINESS_TYPE_UPDATE)),
sysLoginSource.Edit,
)
sysLoginSourceGroup.DELETE("/:id",
middleware.PreAuthorize(map[string][]string{"hasRoles": {"admin"}, "hasPerms": {"system:loginSource:remove"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLoginSource", collectlogs.BUSINESS_TYPE_DELETE)),
sysLoginSource.Remove,
)
}
}
// InitLoad 初始参数