feat: 合并Gin_Vue

This commit is contained in:
TsMask
2023-10-16 17:10:38 +08:00
parent 5289818fd4
commit 40a32cb67f
203 changed files with 19719 additions and 178 deletions

View File

@@ -0,0 +1,85 @@
package common
import (
"ems.agt/src/framework/logger"
"ems.agt/src/framework/middleware"
"ems.agt/src/modules/common/controller"
"github.com/gin-gonic/gin"
)
// 模块路由注册
func Setup(router *gin.Engine) {
logger.Infof("开始加载 ====> common 模块路由")
// 路由主页
indexGroup := router.Group("/")
indexGroup.GET("",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 10,
Type: middleware.LIMIT_IP,
}),
controller.NewIndex.Handler,
)
// 验证码操作处理
indexGroup.GET("/captchaImage",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 60,
Type: middleware.LIMIT_IP,
}),
controller.NewCaptcha.Image,
)
// 账号身份操作处理
{
indexGroup.POST("/login",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 10,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Login,
)
indexGroup.GET("/getInfo", middleware.PreAuthorize(nil), controller.NewAccount.Info)
indexGroup.GET("/getRouters", middleware.PreAuthorize(nil), controller.NewAccount.Router)
indexGroup.POST("/logout",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 5,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Logout,
)
}
// 账号注册操作处理
{
indexGroup.POST("/register",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 10,
Type: middleware.LIMIT_IP,
}),
controller.NewRegister.UserName,
)
}
// 通用请求
commonGroup := router.Group("/common")
{
commonGroup.GET("/hash", middleware.PreAuthorize(nil), controller.NewCommont.Hash)
}
// 文件操作处理
fileGroup := router.Group("/file")
{
fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download)
fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload)
fileGroup.POST("/chunkCheck", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck)
fileGroup.POST("/chunkUpload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload)
fileGroup.POST("/chunkMerge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge)
}
}

View File

@@ -0,0 +1,144 @@
package controller
import (
"ems.agt/src/framework/config"
commonConstants "ems.agt/src/framework/constants/common"
tokenConstants "ems.agt/src/framework/constants/token"
ctxUtils "ems.agt/src/framework/utils/ctx"
tokenUtils "ems.agt/src/framework/utils/token"
"ems.agt/src/framework/vo/result"
libAccount "ems.agt/src/lib_features/account"
commonModel "ems.agt/src/modules/common/model"
commonService "ems.agt/src/modules/common/service"
systemService "ems.agt/src/modules/system/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 AccountController 结构体
var NewAccount = &AccountController{
accountService: commonService.NewAccountImpl,
sysLogLoginService: systemService.NewSysLogLoginImpl,
}
// 账号身份操作处理
//
// PATH /
type AccountController struct {
// 账号身份操作服务
accountService commonService.IAccount
// 系统登录访问
sysLogLoginService systemService.ISysLogLogin
}
// 系统登录
//
// POST /login
func (s *AccountController) Login(c *gin.Context) {
var loginBody commonModel.LoginBody
if err := c.ShouldBindJSON(&loginBody); err != nil {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// 当前请求信息
ipaddr, location := ctxUtils.IPAddrLocation(c)
os, browser := ctxUtils.UaOsBrowser(c)
// 校验验证码
err := s.accountService.ValidateCaptcha(
loginBody.Code,
loginBody.UUID,
)
// 根据错误信息,创建系统访问记录
if err != nil {
msg := err.Error() + " " + loginBody.Code
s.sysLogLoginService.NewSysLogLogin(
loginBody.Username, commonConstants.STATUS_NO, msg,
ipaddr, location, os, browser,
)
c.JSON(200, result.ErrMsg(err.Error()))
return
}
// 登录用户信息
loginUser, err := s.accountService.LoginByUsername(loginBody.Username, loginBody.Password)
if err != nil {
c.JSON(200, result.ErrMsg(err.Error()))
return
}
// 生成令牌,创建系统访问记录
tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser)
if tokenStr == "" {
c.JSON(200, result.Err(nil))
return
} else {
s.sysLogLoginService.NewSysLogLogin(
loginBody.Username, commonConstants.STATUS_YES, "登录成功",
ipaddr, location, os, browser,
)
}
// 设置登录会话-兼容旧登录方式
libAccount.SessionToken(loginBody.Username, ipaddr)
c.JSON(200, result.OkData(map[string]any{
tokenConstants.RESPONSE_FIELD: tokenStr,
}))
}
// 登录用户信息
//
// GET /getInfo
func (s *AccountController) Info(c *gin.Context) {
loginUser, err := ctxUtils.LoginUser(c)
if err != nil {
c.JSON(401, result.CodeMsg(401, err.Error()))
return
}
// 角色权限集合,管理员拥有所有权限
isAdmin := config.IsAdmin(loginUser.UserID)
roles, perms := s.accountService.RoleAndMenuPerms(loginUser.UserID, isAdmin)
c.JSON(200, result.OkData(map[string]any{
"user": loginUser.User,
"roles": roles,
"permissions": perms,
}))
}
// 登录用户路由信息
//
// GET /getRouters
func (s *AccountController) Router(c *gin.Context) {
userID := ctxUtils.LoginUserToUserID(c)
// 前端路由,管理员拥有所有
isAdmin := config.IsAdmin(userID)
buildMenus := s.accountService.RouteMenus(userID, isAdmin)
c.JSON(200, result.OkData(buildMenus))
}
// 系统登出
//
// POST /logout
func (s *AccountController) Logout(c *gin.Context) {
tokenStr := ctxUtils.Authorization(c)
if tokenStr != "" {
// 存在token时记录退出信息
userName := tokenUtils.Remove(tokenStr)
if userName != "" {
// 当前请求信息
ipaddr, location := ctxUtils.IPAddrLocation(c)
os, browser := ctxUtils.UaOsBrowser(c)
// 创建系统访问记录
s.sysLogLoginService.NewSysLogLogin(
userName, commonConstants.STATUS_NO, "退出成功",
ipaddr, location, os, browser,
)
}
}
c.JSON(200, result.OkMsg("退出成功"))
}

View File

@@ -0,0 +1,129 @@
package controller
import (
"time"
"ems.agt/src/framework/config"
"ems.agt/src/framework/constants/cachekey"
"ems.agt/src/framework/constants/captcha"
"ems.agt/src/framework/logger"
"ems.agt/src/framework/redis"
"ems.agt/src/framework/utils/parse"
"ems.agt/src/framework/vo/result"
systemService "ems.agt/src/modules/system/service"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
)
// 实例化控制层 CaptchaController 结构体
var NewCaptcha = &CaptchaController{
sysConfigService: systemService.NewSysConfigImpl,
}
// 验证码操作处理
//
// PATH /
type CaptchaController struct {
// 参数配置服务
sysConfigService systemService.ISysConfig
}
// 获取验证码
//
// GET /captchaImage
func (s *CaptchaController) Image(c *gin.Context) {
// 从数据库配置获取验证码开关 true开启false关闭
captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled")
captchaEnabled := parse.Boolean(captchaEnabledStr)
if !captchaEnabled {
c.JSON(200, result.Ok(map[string]any{
"captchaEnabled": captchaEnabled,
}))
return
}
// 生成唯一标识
verifyKey := ""
data := map[string]any{
"captchaEnabled": captchaEnabled,
"uuid": "",
"img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
}
// 从数据库配置获取验证码类型 math 数值计算 char 字符验证
captchaType := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaType")
if captchaType == captcha.TYPE_MATH {
math := config.Get("mathCaptcha").(map[string]any)
driverCaptcha := &base64Captcha.DriverMath{
//Height png height in pixel.
Height: math["height"].(int),
// Width Captcha png width in pixel.
Width: math["width"].(int),
//NoiseCount text noise count.
NoiseCount: math["noise"].(int),
//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
ShowLineOptions: base64Captcha.OptionShowHollowLine,
}
if math["color"].(bool) {
//BgColor captcha image background color (optional)
driverCaptcha.BgColor = parse.Color(math["background"].(string))
}
// 验证码生成
id, question, answer := driverCaptcha.GenerateIdQuestionAnswer()
// 验证码表达式解析输出
item, err := driverCaptcha.DrawCaptcha(question)
if err != nil {
logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err)
} else {
data["uuid"] = id
data["img"] = item.EncodeB64string()
expiration := captcha.EXPIRATION * time.Second
verifyKey = cachekey.CAPTCHA_CODE_KEY + id
redis.SetByExpire("", verifyKey, answer, expiration)
}
}
if captchaType == captcha.TYPE_CHAR {
char := config.Get("charCaptcha").(map[string]any)
driverCaptcha := &base64Captcha.DriverString{
//Height png height in pixel.
Height: char["height"].(int),
// Width Captcha png width in pixel.
Width: char["width"].(int),
//NoiseCount text noise count.
NoiseCount: char["noise"].(int),
//Length random string length.
Length: char["size"].(int),
//Source is a unicode which is the rand string from.
Source: char["chars"].(string),
//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
ShowLineOptions: base64Captcha.OptionShowHollowLine,
}
if char["color"].(bool) {
//BgColor captcha image background color (optional)
driverCaptcha.BgColor = parse.Color(char["background"].(string))
}
// 验证码生成
id, question, answer := driverCaptcha.GenerateIdQuestionAnswer()
// 验证码表达式解析输出
item, err := driverCaptcha.DrawCaptcha(question)
if err != nil {
logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err)
} else {
data["uuid"] = id
data["img"] = item.EncodeB64string()
expiration := captcha.EXPIRATION * time.Second
verifyKey = cachekey.CAPTCHA_CODE_KEY + id
redis.SetByExpire("", verifyKey, answer, expiration)
}
}
// 本地开发下返回验证码结果,方便接口调试
if config.Env() == "local" {
text, _ := redis.Get("", verifyKey)
data["text"] = text
c.JSON(200, result.Ok(data))
return
}
c.JSON(200, result.Ok(data))
}

View File

@@ -0,0 +1,20 @@
package controller
import (
"github.com/gin-gonic/gin"
)
// 实例化控制层 CommontController 结构体
var NewCommont = &CommontController{}
// 通用请求
//
// PATH /
type CommontController struct{}
// 哈希加密
//
// GET /hash
func (s *CommontController) Hash(c *gin.Context) {
c.String(200, "commont Hash")
}

View File

@@ -0,0 +1,185 @@
package controller
import (
"encoding/base64"
"fmt"
"net/url"
"strings"
"ems.agt/src/framework/constants/uploadsubpath"
"ems.agt/src/framework/utils/file"
"ems.agt/src/framework/vo/result"
"github.com/gin-gonic/gin"
)
// 实例化控制层 FileController 结构体
var NewFile = &FileController{}
// 文件操作处理
//
// PATH /
type FileController struct{}
// 下载文件
//
// GET /download/:filePath
func (s *FileController) Download(c *gin.Context) {
filePath := c.Param("filePath")
if len(filePath) < 8 {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// base64解析出地址
decodedBytes, err := base64.StdEncoding.DecodeString(filePath)
if err != nil {
c.JSON(400, result.CodeMsg(400, err.Error()))
return
}
routerPath := string(decodedBytes)
// 地址文件名截取
fileName := routerPath[strings.LastIndex(routerPath, "/")+1:]
// 响应头
c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+url.QueryEscape(fileName)+`"`)
c.Writer.Header().Set("Accept-Ranges", "bytes")
c.Writer.Header().Set("Content-Type", "application/octet-stream")
// 断点续传
headerRange := c.GetHeader("Range")
resultMap, err := file.ReadUploadFileStream(routerPath, headerRange)
if err != nil {
c.JSON(200, result.ErrMsg(err.Error()))
return
}
if headerRange != "" {
c.Writer.Header().Set("Content-Range", fmt.Sprint(resultMap["range"]))
c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["chunkSize"]))
c.Status(206)
} else {
c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["fileSize"]))
c.Status(200)
}
c.Writer.Write(resultMap["data"].([]byte))
}
// 上传文件
//
// POST /upload
func (s *FileController) Upload(c *gin.Context) {
// 上传的文件
formFile, err := c.FormFile("file")
if err != nil {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// 子路径
subPath := c.PostForm("subPath")
if _, ok := uploadsubpath.UploadSubpath[subPath]; !ok {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// 上传文件转存
upFilePath, err := file.TransferUploadFile(formFile, subPath, nil)
if err != nil {
c.JSON(200, result.ErrMsg(err.Error()))
return
}
newFileName := upFilePath[strings.LastIndex(upFilePath, "/")+1:]
c.JSON(200, result.OkData(map[string]string{
"url": "http://" + c.Request.Host + upFilePath,
"fileName": upFilePath,
"newFileName": newFileName,
"originalFileName": formFile.Filename,
}))
}
// 切片文件检查
//
// POST /chunkCheck
func (s *FileController) ChunkCheck(c *gin.Context) {
var body struct {
// 唯一标识
Identifier string `json:"identifier" binding:"required"`
// 文件名
FileName string `json:"fileName" binding:"required"`
}
err := c.ShouldBindJSON(&body)
if err != nil {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// 读取标识目录
chunks, err := file.ChunkCheckFile(body.Identifier, body.FileName)
if err != nil {
c.JSON(200, result.ErrMsg(err.Error()))
return
}
c.JSON(200, result.OkData(chunks))
}
// 切片文件合并
//
// POST /chunkMerge
func (s *FileController) ChunkMerge(c *gin.Context) {
var body struct {
// 唯一标识
Identifier string `json:"identifier" binding:"required"`
// 文件名
FileName string `json:"fileName" binding:"required"`
// 子路径类型
SubPath string `json:"subPath" binding:"required"`
}
err := c.ShouldBindJSON(&body)
if err != nil {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
if _, ok := uploadsubpath.UploadSubpath[body.SubPath]; !ok {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// 切片文件合并
mergeFilePath, err := file.ChunkMergeFile(body.Identifier, body.FileName, body.SubPath)
if err != nil {
c.JSON(200, result.ErrMsg(err.Error()))
return
}
newFileName := mergeFilePath[strings.LastIndex(mergeFilePath, "/")+1:]
c.JSON(200, result.OkData(map[string]string{
"url": "http://" + c.Request.Host + mergeFilePath,
"fileName": mergeFilePath,
"newFileName": newFileName,
"originalFileName": body.FileName,
}))
}
// 切片文件上传
//
// POST /chunkUpload
func (s *FileController) ChunkUpload(c *gin.Context) {
// 切片编号
index := c.PostForm("index")
// 切片唯一标识
identifier := c.PostForm("identifier")
// 上传的文件
formFile, err := c.FormFile("file")
if index == "" || identifier == "" || err != nil {
c.JSON(400, result.CodeMsg(400, "参数错误"))
return
}
// 上传文件转存
chunkFilePath, err := file.TransferChunkUploadFile(formFile, index, identifier)
if err != nil {
c.JSON(200, result.ErrMsg(err.Error()))
return
}
c.JSON(206, result.OkData(chunkFilePath))
}

View File

@@ -0,0 +1,28 @@
package controller
import (
"fmt"
"ems.agt/src/framework/config"
"ems.agt/src/framework/vo/result"
"github.com/gin-gonic/gin"
)
// 实例化控制层 IndexController 结构体
var NewIndex = &IndexController{}
// 路由主页
//
// PATH /
type IndexController struct{}
// 根路由
//
// GET /
func (s *IndexController) Handler(c *gin.Context) {
name := config.Get("framework.name").(string)
version := config.Get("framework.version").(string)
str := "欢迎使用%s后台管理框架当前版本%s请通过前端管理地址访问。"
c.JSON(200, result.OkMsg(fmt.Sprintf(str, name, version)))
}

View File

@@ -0,0 +1,88 @@
package controller
import (
"strings"
commonConstants "ems.agt/src/framework/constants/common"
ctxUtils "ems.agt/src/framework/utils/ctx"
"ems.agt/src/framework/utils/regular"
"ems.agt/src/framework/vo/result"
commonModel "ems.agt/src/modules/common/model"
commonService "ems.agt/src/modules/common/service"
systemService "ems.agt/src/modules/system/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 RegisterController 结构体
var NewRegister = &RegisterController{
registerService: commonService.NewRegisterImpl,
sysLogLoginService: systemService.NewSysLogLoginImpl,
}
// 账号注册操作处理
//
// PATH /
type RegisterController struct {
// 账号注册操作服务
registerService commonService.IRegister
// 系统登录访问
sysLogLoginService systemService.ISysLogLogin
}
// 账号注册
//
// GET /captchaImage
func (s *RegisterController) UserName(c *gin.Context) {
var registerBody commonModel.RegisterBody
if err := c.ShouldBindJSON(&registerBody); err != nil {
c.JSON(400, result.ErrMsg("参数错误"))
return
}
// 判断必传参数
if !regular.ValidUsername(registerBody.Username) {
c.JSON(200, result.ErrMsg("账号不能以数字开头可包含大写小写字母数字且不少于5位"))
return
}
if !regular.ValidPassword(registerBody.Password) {
c.JSON(200, result.ErrMsg("登录密码至少包含大小写字母、数字、特殊符号且不少于6位"))
return
}
if registerBody.Password != registerBody.ConfirmPassword {
c.JSON(200, result.ErrMsg("用户确认输入密码不一致"))
return
}
// 当前请求信息
ipaddr, location := ctxUtils.IPAddrLocation(c)
os, browser := ctxUtils.UaOsBrowser(c)
// 校验验证码
err := s.registerService.ValidateCaptcha(
registerBody.Code,
registerBody.UUID,
)
// 根据错误信息,创建系统访问记录
if err != nil {
msg := err.Error() + " " + registerBody.Code
s.sysLogLoginService.NewSysLogLogin(
registerBody.Username, commonConstants.STATUS_NO, msg,
ipaddr, location, os, browser,
)
c.JSON(200, result.ErrMsg(err.Error()))
return
}
infoStr := s.registerService.ByUserName(registerBody.Username, registerBody.Password, registerBody.UserType)
if !strings.HasPrefix(infoStr, "注册") {
msg := registerBody.Username + " 注册成功 " + infoStr
s.sysLogLoginService.NewSysLogLogin(
registerBody.Username, commonConstants.STATUS_NO, msg,
ipaddr, location, os, browser,
)
c.JSON(200, result.OkMsg("注册成功"))
return
}
c.JSON(200, result.ErrMsg(infoStr))
}

View File

@@ -0,0 +1,16 @@
package model
// LoginBody 用户登录对象
type LoginBody struct {
// Username 用户名
Username string `json:"username" binding:"required"`
// Password 用户密码
Password string `json:"password" binding:"required"`
// Code 验证码
Code string `json:"code"`
// UUID 验证码唯一标识
UUID string `json:"uuid"`
}

View File

@@ -0,0 +1,22 @@
package model
// RegisterBody 用户注册对象
type RegisterBody struct {
// Username 用户名
Username string `json:"username" binding:"required"`
// Password 用户密码
Password string `json:"password" binding:"required"`
// ConfirmPassword 用户确认密码
ConfirmPassword string `json:"confirmPassword" binding:"required"`
// Code 验证码
Code string `json:"code"`
// UUID 验证码唯一标识
UUID string `json:"uuid"`
// UserType 标记用户类型
UserType string `json:"userType"`
}

View File

@@ -0,0 +1,21 @@
package service
import "ems.agt/src/framework/vo"
// 账号身份操作服务 服务层接口
type IAccount interface {
// ValidateCaptcha 校验验证码
ValidateCaptcha(code, uuid string) error
// LoginByUsername 登录生成token
LoginByUsername(username, password string) (vo.LoginUser, error)
// ClearLoginRecordCache 清除错误记录次数
ClearLoginRecordCache(username string) bool
// RoleAndMenuPerms 角色和菜单数据权限
RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string)
// RouteMenus 前端路由所需要的菜单
RouteMenus(userId string, isAdmin bool) []vo.Router
}

View File

@@ -0,0 +1,166 @@
package service
import (
"errors"
"fmt"
"time"
"ems.agt/src/framework/config"
adminConstants "ems.agt/src/framework/constants/admin"
"ems.agt/src/framework/constants/cachekey"
"ems.agt/src/framework/constants/common"
"ems.agt/src/framework/redis"
"ems.agt/src/framework/utils/crypto"
"ems.agt/src/framework/utils/parse"
"ems.agt/src/framework/vo"
systemService "ems.agt/src/modules/system/service"
)
// 实例化服务层 AccountImpl 结构体
var NewAccountImpl = &AccountImpl{
sysUserService: systemService.NewSysUserImpl,
sysConfigService: systemService.NewSysConfigImpl,
sysRoleService: systemService.NewSysRoleImpl,
sysMenuService: systemService.NewSysMenuImpl,
}
// 账号身份操作服务 服务层处理
type AccountImpl struct {
// 用户信息服务
sysUserService systemService.ISysUser
// 参数配置服务
sysConfigService systemService.ISysConfig
// 角色服务
sysRoleService systemService.ISysRole
// 菜单服务
sysMenuService systemService.ISysMenu
}
// ValidateCaptcha 校验验证码
func (s *AccountImpl) ValidateCaptcha(code, uuid string) error {
// 验证码检查,从数据库配置获取验证码开关 true开启false关闭
captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled")
if !parse.Boolean(captchaEnabledStr) {
return nil
}
if code == "" || uuid == "" {
return errors.New("验证码信息错误")
}
verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid
captcha, _ := redis.Get("", verifyKey)
if captcha == "" {
return errors.New("验证码已失效")
}
redis.Del("", verifyKey)
if captcha != code {
return errors.New("验证码错误")
}
return nil
}
// LoginByUsername 登录创建用户信息
func (s *AccountImpl) LoginByUsername(username, password string) (vo.LoginUser, error) {
loginUser := vo.LoginUser{}
// 检查密码重试次数
retrykey, retryCount, lockTime, err := s.passwordRetryCount(username)
if err != nil {
return loginUser, err
}
// 查询用户登录账号
sysUser := s.sysUserService.SelectUserByUserName(username)
if sysUser.UserName != username {
return loginUser, errors.New("用户不存在或密码错误")
}
if sysUser.DelFlag == common.STATUS_YES {
return loginUser, errors.New("对不起,您的账号已被删除")
}
if sysUser.Status == common.STATUS_NO {
return loginUser, errors.New("对不起,您的账号已禁用")
}
// 检验用户密码
compareBool := crypto.BcryptCompare(password, sysUser.Password)
if !compareBool {
redis.SetByExpire("", retrykey, retryCount+1, lockTime)
return loginUser, errors.New("用户不存在或密码错误")
} else {
// 清除错误记录次数
s.ClearLoginRecordCache(username)
}
// 登录用户信息
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
}
// ClearLoginRecordCache 清除错误记录次数
func (s *AccountImpl) ClearLoginRecordCache(username string) bool {
cacheKey := cachekey.PWD_ERR_CNT_KEY + username
hasKey, _ := redis.Has("", cacheKey)
if hasKey {
delOk, _ := redis.Del("", cacheKey)
return delOk
}
return false
}
// passwordRetryCount 密码重试次数
func (s *AccountImpl) passwordRetryCount(username string) (string, int64, time.Duration, error) {
// 验证登录次数和错误锁定时间
maxRetryCount := config.Get("user.password.maxRetryCount").(int)
lockTime := config.Get("user.password.lockTime").(int)
// 验证缓存记录次数
retrykey := cachekey.PWD_ERR_CNT_KEY + username
retryCount, err := redis.Get("", retrykey)
if retryCount == "" || err != nil {
retryCount = "0"
}
// 是否超过错误值
if parse.Number(retryCount) >= int64(maxRetryCount) {
msg := fmt.Sprintf("密码输入错误 %d 次,帐户锁定 %d 分钟", maxRetryCount, lockTime)
return retrykey, int64(maxRetryCount), time.Duration(lockTime) * time.Minute, errors.New(msg)
}
return retrykey, int64(maxRetryCount), time.Duration(lockTime) * time.Minute, nil
}
// RoleAndMenuPerms 角色和菜单数据权限
func (s *AccountImpl) RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) {
if isAdmin {
return []string{adminConstants.ROLE_KEY}, []string{adminConstants.PERMISSION}
} else {
// 角色key
roleGroup := []string{}
roles := s.sysRoleService.SelectRoleListByUserId(userId)
for _, role := range roles {
roleGroup = append(roleGroup, role.RoleKey)
}
// 菜单权限key
perms := s.sysMenuService.SelectMenuPermsByUserId(userId)
return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms)
}
}
// RouteMenus 前端路由所需要的菜单
func (s *AccountImpl) RouteMenus(userId string, isAdmin bool) []vo.Router {
var buildMenus []vo.Router
if isAdmin {
menus := s.sysMenuService.SelectMenuTreeByUserId("*")
buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
} else {
menus := s.sysMenuService.SelectMenuTreeByUserId(userId)
buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
}
return buildMenus
}

View File

@@ -0,0 +1,10 @@
package service
// 账号注册操作处理 服务层接口
type IRegister interface {
// ValidateCaptcha 校验验证码
ValidateCaptcha(code, uuid string) error
// ByUserName 账号注册
ByUserName(username, password, userType string) string
}

View File

@@ -0,0 +1,100 @@
package service
import (
"errors"
"fmt"
"ems.agt/src/framework/constants/cachekey"
"ems.agt/src/framework/constants/common"
"ems.agt/src/framework/redis"
"ems.agt/src/framework/utils/parse"
systemModel "ems.agt/src/modules/system/model"
systemService "ems.agt/src/modules/system/service"
)
// 实例化服务层 RegisterImpl 结构体
var NewRegisterImpl = &RegisterImpl{
sysUserService: systemService.NewSysUserImpl,
sysConfigService: systemService.NewSysConfigImpl,
sysRoleService: systemService.NewSysRoleImpl,
}
// 账号注册操作处理 服务层处理
type RegisterImpl struct {
// 用户信息服务
sysUserService systemService.ISysUser
// 参数配置服务
sysConfigService systemService.ISysConfig
// 角色服务
sysRoleService systemService.ISysRole
}
// ValidateCaptcha 校验验证码
func (s *RegisterImpl) ValidateCaptcha(code, uuid string) error {
// 验证码检查,从数据库配置获取验证码开关 true开启false关闭
captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled")
if !parse.Boolean(captchaEnabledStr) {
return nil
}
if code == "" || uuid == "" {
return errors.New("验证码信息错误")
}
verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid
captcha, err := redis.Get("", verifyKey)
if captcha == "" || err != nil {
return errors.New("验证码已失效")
}
redis.Del("", verifyKey)
if captcha != code {
return errors.New("验证码错误")
}
return nil
}
// ByUserName 账号注册
func (s *RegisterImpl) ByUserName(username, password, userType string) string {
// 检查用户登录账号是否唯一
uniqueUserName := s.sysUserService.CheckUniqueUserName(username, "")
if !uniqueUserName {
return fmt.Sprintf("注册用户【%s】失败注册账号已存在", username)
}
sysUser := systemModel.SysUser{
UserName: username,
NickName: username, // 昵称使用名称账号
Password: password, // 原始密码
Status: common.STATUS_YES, // 账号状态激活
DeptID: "100", // 归属部门为根节点
CreateBy: "注册", // 创建来源
}
// 标记用户类型
if userType == "" {
sysUser.UserType = "sys"
}
// 新增用户的角色管理
sysUser.RoleIDs = s.registerRoleInit(userType)
// 新增用户的岗位管理
sysUser.PostIDs = s.registerPostInit(userType)
insertId := s.sysUserService.InsertUser(sysUser)
if insertId != "" {
return insertId
}
return "注册失败,请联系系统管理人员"
}
// registerRoleInit 注册初始角色
func (s *RegisterImpl) registerRoleInit(userType string) []string {
if userType == "sys" {
return []string{}
}
return []string{}
}
// registerPostInit 注册初始岗位
func (s *RegisterImpl) registerPostInit(userType string) []string {
if userType == "sys" {
return []string{}
}
return []string{}
}