Bladeren bron

eta1.4登录相关接口

hsun 1 jaar geleden
bovenliggende
commit
1efceb0b4b

+ 65 - 20
controllers/sys_admin.go

@@ -296,6 +296,7 @@ func (this *SysAdminController) ListSysuser() {
 	br.Data = resp
 }
 
+// Add
 // @Title 新增系统用户
 // @Description 新增系统用户接口
 // @Param	request	body system.SysuserAddReq true "type json string"
@@ -332,7 +333,22 @@ func (this *SysAdminController) Add() {
 		br.IsSendEmail = false
 		return
 	}
+
+	// 手机号和邮箱必填一个
+	req.Mobile = strings.TrimSpace(req.Mobile)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Mobile == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
 	if req.Mobile != "" {
+		if req.TelAreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Mobile) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+
 		mobileCount, err := system.GetSysAdminCountByMobile(req.Mobile, 0)
 		if err != nil {
 			br.Msg = "判断手机号是否存在失败"
@@ -345,7 +361,17 @@ func (this *SysAdminController) Add() {
 			return
 		}
 	}
-	//req.Mobile
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+		_, e := system.GetSysUserByEmail(req.Email)
+		if e.Error() != utils.ErrNoRow() {
+			br.Msg = "邮箱已存在, 请重新填写"
+			return
+		}
+	}
 
 	var roleName, departmentName, groupName, teamName string
 
@@ -431,7 +457,7 @@ func (this *SysAdminController) Add() {
 		return
 	}
 	pwdStr := string(pwdByte)
-	pwdStr = strings.ToLower(pwdStr)
+	//pwdStr = strings.ToLower(pwdStr)
 	if pwdStr == "" {
 		br.Msg = "请输入密码"
 		return
@@ -507,6 +533,7 @@ func (this *SysAdminController) Add() {
 		admin.Role = "admin"
 	}
 	admin.EmployeeId = req.EmployeeId
+	admin.Email = req.Email
 
 	var authority int
 	if roleItem.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
@@ -530,6 +557,7 @@ func (this *SysAdminController) Add() {
 	admin.ProvinceCode = req.ProvinceCode
 	admin.City = req.City
 	admin.CityCode = req.CityCode
+	admin.TelAreaCode = req.TelAreaCode
 	err = system.AddAdmin(admin)
 	if err != nil {
 		br.Msg = "新增失败"
@@ -630,26 +658,25 @@ func (this *SysAdminController) Edit() {
 		return
 	}
 
-	// 手机号
-	if item != nil {
-		if req.Mobile != "" && req.Mobile != item.Mobile {
-			mobileCount, err := system.GetSysAdminCountByMobile(req.Mobile, req.AdminId)
-			if err != nil {
-				br.Msg = "判断手机号是否存在失败"
-				br.ErrMsg = "判断手机号是否存在失败,Err:" + err.Error()
-				return
-			}
-			if mobileCount > 0 {
-				br.Msg = "手机号已存在,请重新填写"
-				br.IsSendEmail = false
+	// 手机号和邮箱必填一个
+	req.Mobile = strings.TrimSpace(req.Mobile)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Mobile == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+	if req.Mobile != "" {
+		if req.TelAreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Mobile) {
+				br.Msg = "手机号格式有误, 请检查"
 				return
 			}
 		}
-	} else {
-		mobileCount, err := system.GetSysAdminCountByMobile(req.Mobile, req.AdminId)
-		if err != nil {
+
+		mobileCount, e := system.GetSysAdminCountByMobile(req.Mobile, adminInfo.AdminId)
+		if e != nil {
 			br.Msg = "判断手机号是否存在失败"
-			br.ErrMsg = "判断手机号是否存在失败,Err:" + err.Error()
+			br.ErrMsg = "判断手机号是否存在失败,Err:" + e.Error()
 			return
 		}
 		if mobileCount > 0 {
@@ -658,6 +685,22 @@ func (this *SysAdminController) Edit() {
 			return
 		}
 	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+		emailUser, e := system.GetSysUserByEmail(req.Email)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "邮箱获取用户信息失败, Err: " + e.Error()
+			return
+		}
+		if emailUser != nil && emailUser.AdminId != adminInfo.AdminId {
+			br.Msg = "邮箱已存在, 请检查"
+			return
+		}
+	}
 
 	// 角色
 	var roleName string
@@ -758,9 +801,11 @@ func (this *SysAdminController) Edit() {
 	adminInfo.City = req.City
 	adminInfo.CityCode = req.CityCode
 	adminInfo.EmployeeId = req.EmployeeId
+	adminInfo.Email = req.Email
+	adminInfo.TelAreaCode = req.TelAreaCode
 	cols := []string{
 		"AdminName", "RealName", "LastUpdatedTime", "Mobile", "RoleId", "RoleName", "Enabled", "Authority",
-		"Position", "RoleTypeCode", "Province", "ProvinceCode", "City", "CityCode", "EmployeeId",
+		"Position", "RoleTypeCode", "Province", "ProvinceCode", "City", "CityCode", "EmployeeId", "Email", "TelAreaCode",
 	}
 	if e := adminInfo.Update(cols); e != nil {
 		br.Msg = "编辑失败"
@@ -1230,7 +1275,7 @@ func (this *SysAdminController) ResetPass() {
 		return
 	}
 	pwd := string(b)
-	pwd = strings.ToLower(pwd)
+	//pwd = strings.ToLower(pwd)
 	pwd = utils.MD5(pwd)
 
 	adminInfo.Password = pwd

+ 891 - 0
controllers/user_login.go

@@ -0,0 +1,891 @@
+package controllers
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"github.com/mojocn/base64Captcha"
+	"hongze/hz_crm_api/models"
+	"hongze/hz_crm_api/models/company"
+	"hongze/hz_crm_api/models/system"
+	"hongze/hz_crm_api/services"
+	"hongze/hz_crm_api/utils"
+	"image/color"
+	"strings"
+	"time"
+)
+
+// UserLoginController 登录-无需Token
+type UserLoginController struct {
+	BaseCommonController
+}
+
+// UserLoginAuthController 登录-需Token
+type UserLoginAuthController struct {
+	BaseAuthController
+}
+
+// GenerateCaptcha
+// @Title 生成图形验证码
+// @Description 生成图形验证码
+// @Success 200 Ret=200 获取成功
+// @router /get_captcha [get]
+func (this *UserLoginController) GenerateCaptcha() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	//driver := base64Captcha.DefaultDriverDigit
+	// 自定义验证码样式
+	var driver base64Captcha.Driver
+	driverString := base64Captcha.DriverString{
+		Height:          60,    //高度
+		Width:           120,   //宽度
+		NoiseCount:      0,     //干扰数
+		ShowLineOptions: 2 | 4, //展示个数
+		Length:          4,     //长度
+		//Source:          "1234567890qwertyuioplkjhgfdsazxcvbnm", //验证码随机字符串来源
+		Source: "1234567890", //验证码随机字符串来源
+		BgColor: &color.RGBA{ // 背景颜色
+			R: 0,
+			G: 0,
+			B: 0,
+			A: 0,
+		},
+		Fonts: []string{"wqy-microhei.ttc"}, // 字体
+	}
+	driver = driverString.ConvertFonts()
+
+	// 生成验证码
+	store := services.CaptchaRedis{}
+	captcha := base64Captcha.NewCaptcha(driver, store)
+	id, b64s, err := captcha.Generate()
+	if err != nil {
+		br.Msg = "生成失败"
+		br.ErrMsg = "生成验证码失败, Err: " + err.Error()
+		return
+	}
+
+	type CaptchaResult struct {
+		Id         string
+		Base64Blob string
+	}
+	res := new(CaptchaResult)
+	res.Id = id
+	res.Base64Blob = b64s
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = res
+}
+
+// GetVerifyCode
+// @Title 获取短信/邮箱验证码
+// @Description 获取短信/邮箱验证码
+// @Param	request	body VerifyCodeReq true "type json string"
+// @Success 200 Ret=200 获取成功
+// @router /verify_code [post]
+func (this *UserLoginController) GetVerifyCode() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	type VerifyCodeReq struct {
+		VerifyType  int    `description:"验证方式: 1-手机号; 2-邮箱"`
+		CaptchaId   string `description:"验证码ID"`
+		CaptchaCode string `description:"图形验证码"`
+		Mobile      string `description:"手机号"`
+		TelAreaCode string `description:"手机区号"`
+		Email       string `description:"邮箱"`
+		Source      int    `description:"来源:1-登录;2-异常登录校验;3-忘记密码"`
+	}
+	var req VerifyCodeReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.VerifyType != 1 && req.VerifyType != 2 {
+		br.Msg = "验证方式有误"
+		br.ErrMsg = "验证方式有误"
+		return
+	}
+	if req.Source != 1 && req.Source != 2 && req.Source != 3 {
+		br.Msg = "来源有误"
+		br.ErrMsg = "来源有误"
+		return
+	}
+	// 忘记密码图形校验在前一步, 这一步不再校验图形验证码
+	if req.Source != 3 {
+		if req.CaptchaId == "" || req.CaptchaCode == "" {
+			br.Msg = "请输入图形验证码"
+			return
+		}
+	}
+	if req.VerifyType == 1 {
+		if req.TelAreaCode == "" {
+			br.Msg = "请选择区号"
+			return
+		}
+		if req.Mobile == "" {
+			br.Msg = "请输入手机号"
+			return
+		}
+		if req.TelAreaCode == "86" && !utils.ValidateMobileFormatat(req.Mobile) {
+			br.Msg = "您的手机号输入有误, 请检查"
+			return
+		}
+		_, e := system.GetSysUserByMobile(req.Mobile)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "您的手机号未绑定账号, 请检查"
+				return
+			}
+			br.Msg = "您的手机号未绑定账号, 请检查"
+			br.ErrMsg = "手机号获取用户失败, Err:" + e.Error()
+			return
+		}
+	}
+	if req.VerifyType == 2 {
+		if req.Email == "" {
+			br.Msg = "请输入邮箱"
+			return
+		}
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "您的邮箱输入有误, 请检查"
+			return
+		}
+		_, e := system.GetSysUserByEmail(req.Email)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "您的邮箱未绑定账号, 请检查"
+				return
+			}
+			br.Msg = "您的邮箱未绑定账号, 请检查"
+			br.ErrMsg = "邮箱获取用户失败, Err:" + e.Error()
+			return
+		}
+	}
+	if req.Source != 3 {
+		store := services.CaptchaRedis{}
+		ok := store.Verify(req.CaptchaId, req.CaptchaCode, true)
+		if !ok {
+			br.Msg = "图形验证码错误, 请重新输入"
+			return
+		}
+	}
+
+	// 限制最多60s获取一次
+	var lockKey string
+	if req.VerifyType == 1 {
+		lockKey = fmt.Sprint(utils.CaptchaCachePrefix, req.Mobile)
+	}
+	if req.VerifyType == 2 {
+		lockKey = fmt.Sprint(utils.CaptchaCachePrefix, req.Email)
+	}
+	locked := utils.Rc.SetNX(lockKey, 1, time.Minute)
+	if !locked {
+		br.Msg = "请勿频繁发送"
+		return
+	}
+
+	// 发送验证码
+	sendRes := false
+	if req.VerifyType == 1 {
+		r, e := services.SendAdminMobileVerifyCode(req.Source, req.Mobile, req.TelAreaCode)
+		if e != nil {
+			br.Msg = "发送失败"
+			br.ErrMsg = "发送短信验证码失败, Err: " + e.Error()
+			return
+		}
+		sendRes = r
+	}
+	if req.VerifyType == 2 {
+		r, e := services.SendAdminEmailVerifyCode(req.Source, req.Email)
+		if e != nil {
+			br.Msg = "发送失败"
+			br.ErrMsg = "发送邮箱验证码失败, Err: " + e.Error()
+			return
+		}
+		sendRes = r
+	}
+	if !sendRes {
+		br.Msg = "发送失败"
+		br.ErrMsg = "发送短信验证码失败"
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "发送成功"
+}
+
+// Login
+// @Title 用户登录
+// @Description 用户登录
+// @Param	request	body UserLoginReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /login [post]
+func (this *UserLoginController) Login() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg != "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	// 入参
+	type UserLoginReq struct {
+		LoginType  int    `description:"登录方式: 1-账号; 2-手机号; 3-邮箱"`
+		Username   string `description:"账号"`
+		Password   string `description:"密码"`
+		Mobile     string `description:"手机号"`
+		Email      string `description:"邮箱"`
+		VerifyCode string `description:"验证码"`
+	}
+	var req UserLoginReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.Username = strings.TrimSpace(req.Username)
+	req.Mobile = strings.TrimSpace(req.Mobile)
+	if req.Mobile != "" {
+		if !utils.ValidateMobileFormatat(req.Mobile) {
+			br.Msg = "您的手机号输入有误, 请检查"
+			return
+		}
+	}
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "您的邮箱输入有误, 请检查"
+			return
+		}
+	}
+	req.VerifyCode = strings.TrimSpace(req.VerifyCode)
+	if req.LoginType != 1 && req.LoginType != 2 && req.LoginType != 3 {
+		br.Msg = "登录方式有误"
+		br.ErrMsg = fmt.Sprintf("登录方式有误, Type: %d", req.LoginType)
+		return
+	}
+
+	sysUser := new(system.Admin)
+	// 账号密码登录
+	if req.LoginType == 1 {
+		if req.Username == "" {
+			br.Msg = "请输入账号"
+			return
+		}
+		if req.Password == "" {
+			br.Msg = "请输入密码"
+			return
+		}
+
+		// 判断账号是否为异常登录, 算作异常的话就需要换另外的方式去做登录了
+		abnormalKey := fmt.Sprint(utils.CACHE_ABNORMAL_LOGIN, req.Username)
+		isAbnormal, _ := utils.Rc.RedisString(abnormalKey)
+		if isAbnormal != "" {
+			br.Ret = models.BaseRespCodeAbnormalLogin
+			br.Msg = "账号异常, 请进行手机号/邮箱校验"
+			return
+		}
+
+		// 账号密码校验
+		errPassKey := fmt.Sprint(utils.CACHE_LOGIN_ERR_PASS, req.Username)
+		accountUser, e := system.CheckSysUser(req.Username, req.Password)
+		if e != nil {
+			br.Ret = models.BaseRespCodeLoginErr
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "登录失败, 账号或密码错误"
+				if isAbnormal != "" {
+					return
+				}
+				// 错误密码计数, 超过6次标记异常
+				if !utils.Rc.IsExist(errPassKey) {
+					_ = utils.Rc.Put(errPassKey, 1, utils.GetTodayLastSecond())
+					return
+				}
+				errNum, _ := utils.Rc.RedisInt(errPassKey)
+				errNum += 1
+				if errNum >= 6 {
+					br.Ret = models.BaseRespCodeAbnormalLogin
+					br.Msg = "账号异常, 请进行手机号/邮箱校验"
+					// 标记异常登录, 重置计数
+					_ = utils.Rc.Put(abnormalKey, "true", utils.GetTodayLastSecond())
+					_ = utils.Rc.Delete(errPassKey)
+					return
+				}
+				_ = utils.Rc.Put(errPassKey, errNum, utils.GetTodayLastSecond())
+				return
+			}
+			br.Msg = "登录失败, 账号或密码错误"
+			br.ErrMsg = "登录失败, Err:" + e.Error()
+			return
+		}
+		if accountUser.Enabled == 0 {
+			br.Msg = "您的账号已被禁用, 如需登录, 请联系管理员"
+			br.ErrMsg = fmt.Sprintf("账号已被禁用, 登录账号: %s, 账户名称: %s", accountUser.AdminName, accountUser.RealName)
+			return
+		}
+
+		// 异常登录-是否登录间隔大于60天
+		if isAbnormal == "" {
+			abnormalTime := time.Now().AddDate(0, 0, -60)
+			lastLogin, _ := time.ParseInLocation(utils.FormatDateTime, accountUser.LastLoginTime, time.Local)
+			if !lastLogin.IsZero() && lastLogin.Before(abnormalTime) {
+				br.Msg = "请进行异常登录校验"
+				br.Ret = models.BaseRespCodeAbnormalLogin
+				// 标记异常登录
+				_ = utils.Rc.Put(abnormalKey, "true", utils.GetTodayLastSecond())
+				return
+			}
+		}
+
+		sysUser = accountUser
+	}
+
+	// 手机号
+	if req.LoginType == 2 {
+		if req.Mobile == "" {
+			br.Msg = "请输入手机号"
+			return
+		}
+		if !utils.ValidateMobileFormatat(req.Mobile) {
+			br.Msg = "您的手机号输入有误, 请检查"
+			return
+		}
+		if req.VerifyCode == "" {
+			br.Msg = "请输入验证码"
+			return
+		}
+		expiredTime := time.Now().Add(utils.VerifyCodeExpireMinute * time.Minute).Format(utils.FormatDateTime)
+		recordCond := ` AND source = ? AND mobile = ? AND code = ? AND expired_time <= ?`
+		recordPars := make([]interface{}, 0)
+		recordPars = append(recordPars, system.AdminVerifyCodeRecordSourceLogin, req.Mobile, req.VerifyCode, expiredTime)
+		recordOb := new(system.AdminVerifyCodeRecord)
+		_, e := recordOb.GetItemByCondition(recordCond, recordPars)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "验证码错误, 请重新输入"
+				return
+			}
+			br.Msg = "验证码错误, 请重新输入"
+			br.ErrMsg = "获取手机号登陆验证码失败, Err: " + e.Error()
+			return
+		}
+
+		mobileUser, e := system.GetSysUserByMobile(req.Mobile)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "您的手机号未绑定账号, 请检查"
+				return
+			}
+			br.Msg = "您的手机号未绑定账号, 请检查"
+			br.ErrMsg = "手机号获取用户失败, Err:" + e.Error()
+			return
+		}
+		if mobileUser.Enabled == 0 {
+			br.Msg = "您的账号已被禁用, 如需登录, 请联系管理员"
+			br.ErrMsg = fmt.Sprintf("账号已被禁用, 登录手机号: %s", req.Mobile)
+			return
+		}
+		sysUser = mobileUser
+	}
+
+	// 邮箱登录
+	if req.LoginType == 3 {
+		if req.Email == "" {
+			br.Msg = "请输入邮箱"
+			return
+		}
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "您的邮箱输入有误, 请检查"
+			return
+		}
+		if req.VerifyCode == "" {
+			br.Msg = "请输入验证码"
+			return
+		}
+		expiredTime := time.Now().Add(utils.VerifyCodeExpireMinute * time.Minute).Format(utils.FormatDateTime)
+		recordCond := ` AND source = ? AND email = ? AND code = ? AND expired_time <= ?`
+		recordPars := make([]interface{}, 0)
+		recordPars = append(recordPars, system.AdminVerifyCodeRecordSourceLogin, req.Email, req.VerifyCode, expiredTime)
+		recordOb := new(system.AdminVerifyCodeRecord)
+		_, e := recordOb.GetItemByCondition(recordCond, recordPars)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "验证码错误, 请重新输入"
+				return
+			}
+			br.Msg = "验证码错误, 请重新输入"
+			br.ErrMsg = "获取手机号登陆验证码失败, Err: " + e.Error()
+			return
+		}
+
+		emailUser, e := system.GetSysUserByEmail(req.Email)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "您的邮箱未绑定账号, 请检查"
+				return
+			}
+			br.Msg = "您的邮箱未绑定账号, 请检查"
+			br.ErrMsg = "邮箱获取用户失败, Err:" + e.Error()
+			return
+		}
+		if emailUser.Enabled == 0 {
+			br.Msg = "您的账号已被禁用, 如需登录, 请联系管理员"
+			br.ErrMsg = fmt.Sprintf("账号已被禁用, 登录邮箱: %s", req.Email)
+			return
+		}
+		sysUser = emailUser
+	}
+
+	// 登录成功(无论哪种方式登录), 清除异常标记
+	abnormalCache := fmt.Sprint(utils.CACHE_ABNORMAL_LOGIN, sysUser.AdminName)
+	errPassCache := fmt.Sprint(utils.CACHE_LOGIN_ERR_PASS, sysUser.AdminName)
+	_ = utils.Rc.Delete(abnormalCache)
+	_ = utils.Rc.Delete(errPassCache)
+
+	account := utils.MD5(sysUser.AdminName)
+	token := utils.GenToken(account)
+	sysSession := new(system.SysSession)
+	sysSession.UserName = req.Username
+	sysSession.SysUserId = sysUser.AdminId
+	sysSession.ExpiredTime = time.Now().AddDate(0, 0, 90)
+	sysSession.IsRemember = 0 // 均需要做过期校验
+	sysSession.CreatedTime = time.Now()
+	sysSession.LastUpdatedTime = time.Now()
+	sysSession.AccessToken = token
+	if e := system.AddSysSession(sysSession); e != nil {
+		br.Msg = "登录失败"
+		br.ErrMsg = "新增session信息失败, Err:" + e.Error()
+		return
+	}
+
+	// 修改最后登录时间
+	{
+		sysUser.LastLoginTime = time.Now().Format(utils.FormatDateTime)
+		sysUser.LastUpdatedTime = time.Now().Format(utils.FormatDateTime)
+		_ = sysUser.Update([]string{"LastLoginTime", "LastUpdatedTime"})
+	}
+
+	resp := new(system.LoginResp)
+	resp.Authorization = token
+	resp.Authorization = "authorization=" + token + "$account=" + account
+	resp.RealName = sysUser.RealName
+	resp.AdminName = sysUser.AdminName
+	resp.RoleName = sysUser.RoleName
+	resp.SysRoleTypeCode = sysUser.RoleTypeCode //系统角色编码
+	resp.RoleTypeCode = sysUser.RoleTypeCode
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_FICC_GROUP {
+		resp.RoleTypeCode = utils.ROLE_TYPE_CODE_FICC_SELLER
+	}
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_FICC_TEAM {
+		resp.RoleTypeCode = utils.ROLE_TYPE_CODE_FICC_SELLER
+	}
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_FICC_DEPARTMENT {
+		resp.RoleTypeCode = utils.ROLE_TYPE_CODE_FICC_SELLER
+	}
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_RAI_GROUP {
+		resp.RoleTypeCode = utils.ROLE_TYPE_CODE_RAI_SELLER
+	}
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_RAI_DEPARTMENT {
+		resp.RoleTypeCode = utils.ROLE_TYPE_CODE_RAI_SELLER
+	}
+	if sysUser.RoleName == utils.ROLE_NAME_FICC_DIRECTOR {
+		resp.RoleTypeCode = utils.ROLE_TYPE_CODE_FICC_SELLER
+	}
+	resp.AdminId = sysUser.AdminId
+	var productName string
+	productId := services.GetProductId(sysUser.RoleTypeCode)
+	if productId == 1 {
+		productName = utils.COMPANY_PRODUCT_FICC_NAME
+	} else if productId == 2 {
+		productName = utils.COMPANY_PRODUCT_RAI_NAME
+	} else {
+		productName = "admin"
+	}
+	resp.ProductName = productName
+	resp.Authority = sysUser.Authority
+
+	// 设置redis缓存
+	{
+		// 获取不可信的登录态,并将该登录态重置掉,不允许多次登录
+		noTrustLoginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST, sysUser.AdminId)
+		noTrustLoginId, _ := utils.Rc.RedisString(noTrustLoginKey)
+		if noTrustLoginId != `` { // 如果存在不可信设备,那么将其下架
+			oldNoTrustLoginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN, noTrustLoginId)
+			_ = utils.Rc.Put(oldNoTrustLoginKey, "0", 30*time.Minute)
+		}
+
+		// 如果当前是不可信设备,那么将其加入到不可信名单
+		loginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN, sysSession.Id)
+		_ = utils.Rc.Put(loginKey, "1", 30*time.Minute)
+		_ = utils.Rc.Put(noTrustLoginKey, sysSession.Id, 30*time.Minute)
+	}
+
+	// 新增登录记录
+	go func() {
+		record := new(system.SysUserLoginRecord)
+		record.Uid = sysUser.AdminId
+		record.UserName = req.Username
+		record.Ip = this.Ctx.Input.IP()
+		record.Stage = "login"
+		record.CreateTime = time.Now()
+		_ = system.AddSysUserLoginRecord(record)
+	}()
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "登录成功"
+}
+
+// ForgetAccountGet
+// @Title 忘记密码-账号校验
+// @Description 忘记密码-账号校验
+// @Param	request	body ForgetAccountGetReq true "type json string"
+// @Success 200 Ret=200 获取成功
+// @router /forget/account_get [post]
+func (this *UserLoginController) ForgetAccountGet() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	type ForgetAccountGetReq struct {
+		CaptchaId   string `description:"验证码ID"`
+		CaptchaCode string `description:"图形验证码"`
+		UserName    string `description:"用户名"`
+	}
+	var req ForgetAccountGetReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.UserName = strings.TrimSpace(req.UserName)
+	if req.UserName == "" {
+		br.Msg = "请输入账号"
+		return
+	}
+	if req.CaptchaId == "" || req.CaptchaCode == "" {
+		br.Msg = "请输入图形验证码"
+		return
+	}
+	store := services.CaptchaRedis{}
+	ok := store.Verify(req.CaptchaId, req.CaptchaCode, true)
+	if !ok {
+		br.Msg = "验证码错误, 请重新输入"
+		return
+	}
+
+	sysUser, e := system.GetSysUserByAdminName(req.UserName)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "用户不存在, 请检查"
+			return
+		}
+		br.Msg = "账号错误, 请重新输入"
+		br.ErrMsg = "用户名获取用户失败, Err: " + e.Error()
+		return
+	}
+
+	type ForgetAccountCheckResp struct {
+		Mobile      string `description:"手机号"`
+		Email       string `description:"邮箱"`
+		TelAreaCode string `description:"手机区号"`
+	}
+	resp := ForgetAccountCheckResp{
+		Mobile:      sysUser.Mobile,
+		Email:       sysUser.Email,
+		TelAreaCode: sysUser.TelAreaCode,
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// ForgetCodeVerify
+// @Title 忘记密码-验证码校验
+// @Description 忘记密码-验证码校验
+// @Param	request	body ForgetCodeVerifyReq true "type json string"
+// @Success 200 Ret=200 获取成功
+// @router /forget/code_verify [post]
+func (this *UserLoginController) ForgetCodeVerify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	type ForgetCodeVerifyReq struct {
+		FindType   int    `description:"密码找回方式: 1-手机号; 2-邮箱"`
+		VerifyCode string `description:"验证码"`
+		UserName   string `description:"用户名"`
+		Mobile     string `description:"手机号"`
+		Email      string `description:"邮箱"`
+	}
+	var req ForgetCodeVerifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.UserName = strings.TrimSpace(req.UserName)
+	if req.UserName == "" {
+		br.Msg = "请输入账号"
+		return
+	}
+	if req.VerifyCode == "" {
+		br.Msg = "请输入验证码"
+		return
+	}
+
+	// 手机号找回
+	var verifyRes bool
+	if req.FindType == 1 {
+		req.Mobile = strings.TrimSpace(req.Mobile)
+		if req.Mobile == "" {
+			br.Msg = "请输入手机号"
+			return
+		}
+		if !utils.ValidateMobileFormatat(req.Mobile) {
+			br.Msg = "您的手机号输入有误, 请检查"
+			return
+		}
+		expiredTime := time.Now().Add(utils.VerifyCodeExpireMinute * time.Minute).Format(utils.FormatDateTime)
+		recordCond := ` AND source = ? AND mobile = ? AND code = ? AND expired_time <= ?`
+		recordPars := make([]interface{}, 0)
+		recordPars = append(recordPars, system.AdminVerifyCodeRecordSourceForget, req.Mobile, req.VerifyCode, expiredTime)
+		recordOb := new(system.AdminVerifyCodeRecord)
+		_, e := recordOb.GetItemByCondition(recordCond, recordPars)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "验证码错误, 请重新输入"
+				return
+			}
+			br.Msg = "验证码错误, 请重新输入"
+			br.ErrMsg = "获取手机号登陆验证码失败, Err: " + e.Error()
+			return
+		}
+
+		verifyRes = true
+	}
+
+	// 邮箱找回
+	if req.FindType == 2 {
+		req.Email = strings.TrimSpace(req.Email)
+		if req.Email == "" {
+			br.Msg = "请输入邮箱"
+			return
+		}
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "您的邮箱输入有误, 请检查"
+			return
+		}
+		expiredTime := time.Now().Add(utils.VerifyCodeExpireMinute * time.Minute).Format(utils.FormatDateTime)
+		recordCond := ` AND source = ? AND email = ? AND code = ? AND expired_time <= ?`
+		recordPars := make([]interface{}, 0)
+		recordPars = append(recordPars, system.AdminVerifyCodeRecordSourceForget, req.Email, req.VerifyCode, expiredTime)
+		recordOb := new(system.AdminVerifyCodeRecord)
+		_, e := recordOb.GetItemByCondition(recordCond, recordPars)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "验证码错误, 请重新输入"
+				return
+			}
+			br.Msg = "验证码错误, 请重新输入"
+			br.ErrMsg = "获取手机号登陆验证码失败, Err: " + e.Error()
+			return
+		}
+
+		verifyRes = true
+	}
+
+	if verifyRes {
+		successKey := fmt.Sprint(utils.CACHE_FIND_PASS_VERIFY, req.UserName)
+		_ = utils.Rc.Put(successKey, "true", utils.GetTodayLastSecond())
+	}
+
+	br.Data = verifyRes
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ForgetResetPass
+// @Title 忘记密码-重置密码
+// @Description 忘记密码-重置密码
+// @Param	request	body ForgetResetPassReq true "type json string"
+// @Success 200 Ret=200 获取成功
+// @router /forget/reset_pass [post]
+func (this *UserLoginController) ForgetResetPass() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	type ForgetResetPassReq struct {
+		UserName   string `description:"用户名"`
+		Password   string `description:"密码"`
+		RePassword string `description:"重复密码"`
+	}
+	var req ForgetResetPassReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.UserName = strings.TrimSpace(req.UserName)
+	if req.UserName == "" {
+		br.Msg = "请输入账号"
+		return
+	}
+	req.Password = strings.TrimSpace(req.Password)
+	if req.Password == "" {
+		br.Msg = "请输入新密码"
+		return
+	}
+	req.RePassword = strings.TrimSpace(req.RePassword)
+	if req.RePassword == "" {
+		br.Msg = "请确认新密码"
+		return
+	}
+	if req.Password != req.RePassword {
+		br.Msg = "两次密码输入不一致, 请检查"
+		return
+	}
+
+	// 接上一步-验证码校验成功后才允许修改
+	successKey := fmt.Sprint(utils.CACHE_FIND_PASS_VERIFY, req.UserName)
+	verifyOk, _ := utils.Rc.RedisString(successKey)
+	if verifyOk != "true" {
+		br.Msg = "验证码校验失效, 请重新验证"
+		br.ErrMsg = "重置密码异常, 验证码校验已失效"
+		return
+	}
+
+	sysUser, e := system.GetSysAdminByName(req.UserName)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "用户不存在, 请检查"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "密码找回获取用户失败, Err:" + e.Error()
+		return
+	}
+
+	b, e := base64.StdEncoding.DecodeString(req.Password)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "解析密码失败, Err:" + e.Error()
+		return
+	}
+	pwd := string(b)
+	pwd = utils.MD5(pwd)
+
+	sysUser.Password = pwd
+	sysUser.LastUpdatedPasswordTime = time.Now().Format(utils.FormatDateTime)
+	// 重置密码后更新一下登录时间, 不然账号密码登录如果超过60天还会被异常校验再校验一次, 影响体验
+	sysUser.LastLoginTime = time.Now().Format(utils.FormatDateTime)
+	sysUser.LastUpdatedTime = time.Now().Format(utils.FormatDateTime)
+	if e = sysUser.Update([]string{"Password", "LastUpdatedPasswordTime", "LastLoginTime", "LastUpdatedTime"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新密码失败, Err: " + e.Error()
+		return
+	}
+
+	// 如果有异常标记也清除掉
+	abnormalKey := fmt.Sprint(utils.CACHE_ABNORMAL_LOGIN, req.UserName)
+	_ = utils.Rc.Delete(abnormalKey)
+	_ = utils.Rc.Delete(successKey)
+
+	// 同步ETA用户缓存
+	var syncData system.SyncAdminData
+	syncData.Source = utils.SOURCE_CRM_FLAG
+	syncData.AdminName = sysUser.AdminName
+	_ = utils.Rc.LPush(utils.CACHE_SYNC_ADMIN, syncData)
+
+	br.Data = true
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// AreaCodeList
+// @Title 手机号区号列表
+// @Description 手机号区号列表
+// @Success 200 Ret=200 获取成功
+// @router /area_code/list [get]
+func (this *UserLoginController) AreaCodeList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	type AreaCodeListResp struct {
+		Name  string `description:"地区"`
+		Value string `description:"区号"`
+	}
+	resp := make([]AreaCodeListResp, 0)
+	confAuth, e := company.GetConfigDetailByCode(company.ConfAreaCodeListKey)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取手机号区号配置失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
+		br.Msg = "获取失败"
+		br.ErrMsg = "手机号区号配置为空"
+		return
+	}
+	if e := json.Unmarshal([]byte(confAuth.ConfigValue), &resp); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "手机号区号配置有误"
+		return
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 4 - 2
go.mod

@@ -22,6 +22,7 @@ require (
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
 	github.com/gorilla/websocket v1.5.0
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
+	github.com/mojocn/base64Captcha v1.3.5
 	github.com/mozillazg/go-pinyin v0.19.0
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.30
@@ -31,7 +32,6 @@ require (
 	github.com/tealeg/xlsx v1.0.5
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.541
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.541
-	github.com/wenzhenxi/gorsa v0.0.0-20210524035706-528c7050d703
 	github.com/xuri/excelize/v2 v2.6.1
 	github.com/yidane/formula v0.0.0-20210902154546-0782e1736717
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
@@ -63,6 +63,7 @@ require (
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
 	github.com/garyburd/redigo v1.6.3 // indirect
 	github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac // indirect
 	github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect
@@ -100,6 +101,7 @@ require (
 	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	golang.org/x/crypto v0.5.0 // indirect
+	golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
 	golang.org/x/net v0.5.0 // indirect
 	golang.org/x/sys v0.4.0 // indirect
 	golang.org/x/text v0.6.0 // indirect
@@ -111,4 +113,4 @@ require (
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	xorm.io/builder v0.3.6 // indirect
 	xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb // indirect
-)
+)

+ 5 - 151
go.sum

@@ -5,27 +5,18 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
 cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8=
 cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
-cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk=
-cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
-cloud.google.com/go/storage v1.23.0 h1:wWRIaDURQA8xxHguFCshYepGlrWIrbBnAmc7wfg07qY=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
 github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
 github.com/SebastiaanKlippert/go-wkhtmltopdf v1.7.2 h1:LORAatv6KuKheYq8HXehiwx3f/VGuzJBNSydUDQ98EM=
 github.com/SebastiaanKlippert/go-wkhtmltopdf v1.7.2/go.mod h1:TY8r0gmwEL1c5Lbd66NgQCkL4ZjGDJCMVqvbbFvUx20=
-github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
-github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
 github.com/alibabacloud-go/alimt-20181012/v2 v2.0.0 h1:RZF3WXYiPB/m1FiZS51udLbpAvg0urYi1wAfB18kiUQ=
@@ -72,9 +63,7 @@ github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomU
 github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M=
 github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
-github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
-github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1656 h1:YTWW7mBjwviuRvqiEpqaAj0AyVPj9AoJmQGCb5lXYUc=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1656/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
@@ -86,11 +75,8 @@ github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x0
 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 h1:o2oaBQGTzO+xNh12e7xWkphNe7H2DTiWv1ml9a2P9PQ=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
-github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
 github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
-github.com/aws/aws-sdk-go v1.42.23 h1:V0V5hqMEyVelgpu1e4gMPVCJ+KhmscdNxP/NWP1iCOA=
 github.com/aws/aws-sdk-go v1.42.23/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
@@ -98,9 +84,7 @@ github.com/beego/bee/v2 v2.0.4 h1:nEjPwxJ8D+cr54eWChJGoGRH7bJ7OQwbhx8rU0OQf7E=
 github.com/beego/bee/v2 v2.0.4/go.mod h1:wq0YrEmPcdNfDNpaUgiTkaW9zso7M8n0HCCShEBOzM0=
 github.com/beego/beego/v2 v2.0.7 h1:9KNnUM40tn3pbCOFfe6SJ1oOL0oTi/oBS/C/wCEdAXA=
 github.com/beego/beego/v2 v2.0.7/go.mod h1:f0uOEkmJWgAuDTlTxUdgJzwG3PDSIf3UWF3NpMohbFE=
-github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
 github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
-github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
 github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -110,39 +94,23 @@ github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6
 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
-github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
 github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
-github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
-github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
-github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
 github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
-github.com/couchbase/go-couchbase v0.1.0 h1:g4bCvDwRL+ZL6HLhYeRlXxEYP31Wpy0VFxnFw6efEp8=
 github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
-github.com/couchbase/gomemcached v0.1.3 h1:HIc5qMYNbuhB7zNaiEtj61DCYkquAwrQlf64q7JzdEY=
 github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
-github.com/couchbase/goutils v0.1.0 h1:0WLlKJilu7IBm98T8nS9+J36lBFVLRUSIUtyD/uWpAE=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
 github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -153,27 +121,19 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
 github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
-github.com/elastic/go-elasticsearch/v6 v6.8.10 h1:2lN0gJ93gMBXvkhwih5xquldszpm8FlUwqG5sPzr6a8=
 github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/flosch/pongo2 v0.0.0-20200529170236-5abacdfa4915 h1:rNVrewdFbSujcoKZifC6cHJfqCTbCIR7XTLHW5TqUWU=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
@@ -183,21 +143,12 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
 github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
-github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c h1:iRTj5SRYwbvsygdwVp+y9kZT145Y1s6xOPpeOEIeGc4=
 github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
-github.com/go-delve/delve v1.5.0 h1:gQsRvFdR0BGk19NROQZsAv6iG4w5QIZoJlxJeEUBb0c=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.12.1-0.20220826005032-a7ba4fa4e289 h1:468Nv6YtYO38Z+pFL6fRSVILGfdTFPei9ksVneiUHUc=
-github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
-github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
 github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc h1:jZY+lpZB92nvBo2f31oPC/ivGll6NcsnEOORm8Fkr4M=
 github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc/go.mod h1:25mL1NKxbJhB63ihiK8MnNeTRd+xAizd6bOdydrTLUQ=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -205,28 +156,22 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
 github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0=
 github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ=
-github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q=
 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -244,9 +189,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
 github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50=
 github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
@@ -262,7 +205,6 @@ github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFR
 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
 github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk=
 github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -273,43 +215,26 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk=
-github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
-github.com/jackc/pgx v3.6.0+incompatible h1:bJeo4JdVbDAW8KB2m8XkFeo8CPipREoG37BwEoKGz+Q=
 github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -318,43 +243,31 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
 github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53 h1:+8X3HMX8A2QhvNg3dImiQTCiVUt6BQXz1mW+/DrWI+k=
 github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53/go.mod h1:E61jD6q4yJ6Cu9uDGRAfiENM1G5TVZhOog0Y3+GgTpQ=
-github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 h1:wxyqOzKxsRJ6vVRL9sXQ64Z45wmBuQ+OTH9sLsC5rKc=
 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
-github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -372,13 +285,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
+github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
 github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c=
 github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
-github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
 github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19 h1:LhWT2dBuNkYexwRSsPpYh67e0ikmH1ebBDaVkGHoMts=
 github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19/go.mod h1:LjhyrWzOLJ9l1azMoNr9iCvfNrHEREqvJHzSLQcD0/o=
@@ -394,7 +306,6 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -402,16 +313,11 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
 github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
-github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.9.2 h1:7NiByeVF4jKSG1lDF3X8LTIkq2/bu+1uYbIm1eS5tzk=
-github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233 h1:jmJndGFBPjNWW+MAYarU/Nl8QrQVzbw4B/AYE0LzETo=
 github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
-github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -443,7 +349,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
 github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
-github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rdlucklib/rdluck_tools v1.0.3 h1:iOtK2QPlPQ6CL6c1htCk5VnFCHzyG6DCfJtunrMswK0=
 github.com/rdlucklib/rdluck_tools v1.0.3/go.mod h1:9Onw9o4w19C8KE5lxb8GyxgRBbZweRVkQSc79v38EaA=
@@ -461,11 +366,8 @@ github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE=
 github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
-github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400 h1:091wFNQB3PXcL5+me0joH7EiyqQaI0wGMpEjVCkK04U=
 github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
 github.com/silenceper/wechat/v2 v2.1.3 h1:vMHnU9PG6wROTqkQI+HnBiEzriEEE+sbmGHg9cdb4yc=
 github.com/silenceper/wechat/v2 v2.1.3/go.mod h1:FoU0YvegD+Z85TBGQhjkXjY8BMb0+cagbe9BqJ0fKhA=
@@ -474,31 +376,20 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/smartwalle/pongo2render v1.0.1 h1:rsPnDTu/+zIT5HEB5RbMjxKY5hisov26j0isZL/7YS0=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
-github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
 github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
-github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/gunit v1.4.2 h1:tyWYZffdPhQPfK5VsMQXfauwnJkqg7Tv5DLuQVYxq3Q=
 github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
-github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
-github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
-github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
-github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -510,9 +401,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
-github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c h1:3eGShk3EQf5gJCYW+WzA0TEJQd37HLOmlYF7N0YJwv0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
@@ -528,12 +417,8 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
 github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
-github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83 h1:9AUN7+NK4IV+A11igqjQM5i8obiOAQo4SXgjaxe+orI=
 github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
-github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
 github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
-github.com/wenzhenxi/gorsa v0.0.0-20210524035706-528c7050d703 h1:Tiqr9EWpYopXZf668mgTNWguzE6ssRIEviULO3gSWnU=
-github.com/wenzhenxi/gorsa v0.0.0-20210524035706-528c7050d703/go.mod h1:nfhBTKji6rC8lrjyikx8NJ85JHg6ZQam0a9Je+2RVOg=
 github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
 github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
 github.com/xuri/excelize/v2 v2.6.1 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k=
@@ -542,31 +427,15 @@ github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Q
 github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
 github.com/yidane/formula v0.0.0-20210902154546-0782e1736717 h1:9CTJJpdISGxMAELfVlprj5kZEsJEaNAWiobv8ZAd72U=
 github.com/yidane/formula v0.0.0-20210902154546-0782e1736717/go.mod h1:9/dQiKiN04yPMdgsuFmKGuI2Hdp6OmFV9gSWS1col6g=
-github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369 h1:g95WlXTqXFLM36fhvDKcZWHTUnBb2KCEn4tuSizk/d8=
 github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod h1:Nv7wKD2/bCdKUFNKcJRa99a+1+aSLlCRJFriFYdjz/I=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973 h1:iCnkJ/qjKZGdZnlcj1N55AxPDan814kpc3s1cDpQKd8=
 github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
-go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
-go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.8.0 h1:FVy7BZCjoA2Nk+fHqIdoTmm554J9wTX+YcrDp+mc368=
-go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk=
-go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
-go.starlark.net v0.0.0-20190702223751-32f345186213 h1:lkYv5AKwvvduv5XWP6szk/bvvgO6aDeUujhZQXIFTes=
-go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
-go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
-go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
-golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -578,18 +447,16 @@ golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
 golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -616,7 +483,6 @@ golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -625,7 +491,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -656,7 +521,6 @@ golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -680,31 +544,25 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
-google.golang.org/api v0.93.0 h1:T2xt9gi0gHdxdnRkVQhT8mIvPaXKNsDNWz+L696M66M=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -718,7 +576,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
@@ -727,7 +584,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
@@ -737,7 +593,6 @@ gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -757,7 +612,6 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
 xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=

+ 5 - 0
models/base.go

@@ -1,5 +1,10 @@
 package models
 
+const (
+	BaseRespCodeAbnormalLogin = 4011 // 异常登录状态码
+	BaseRespCodeLoginErr      = 4012 // 账号或密码输入错误
+)
+
 type BaseResponse struct {
 	Ret         int
 	Msg         string

+ 5 - 1
models/company/company_config.go

@@ -4,6 +4,10 @@ import (
 	"github.com/beego/beego/v2/client/orm"
 )
 
+const (
+	ConfAreaCodeListKey = "area_code_list" // 手机号区号列表
+)
+
 type CrmConfig struct {
 	ConfigValue string `description:"详情"`
 }
@@ -15,7 +19,7 @@ func GetConfigValueByCode(configCode string) (total int, err error) {
 	return
 }
 
-//修改
+// 修改
 func CrmConfigUpdate(newValue, configCode string) (err error) {
 	o := orm.NewOrm()
 	sql := `UPDATE crm_config SET  config_value=?   WHERE config_code=  ?`

+ 1 - 0
models/db.go

@@ -215,6 +215,7 @@ func initSystem() {
 		new(system.SysRoleAdmin),          //管理员账号和角色映射表
 		new(system.AdminConfig),           //系统用户配置表
 		new(system.AdminOperateRecord),
+		new(system.AdminVerifyCodeRecord), // 用户短信邮箱验证码记录表
 	)
 }
 

+ 126 - 0
models/system/admin_verify_code_record.go

@@ -0,0 +1,126 @@
+package system
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	AdminVerifyCodeRecordTypeMobile = 1 // 验证方式-手机号
+	AdminVerifyCodeRecordTypeEmail  = 2 // 验证方式-邮箱
+
+	AdminVerifyCodeRecordSourceLogin    = 1 // 验证码来源-登录
+	AdminVerifyCodeRecordSourceAbnormal = 2 // 验证码来源-异常登录校验
+	AdminVerifyCodeRecordSourceForget   = 3 // 验证码来源-忘记密码
+
+	AdminVerifyCodeRecordStatusSuccess = 1 // 验证码发送状态-已发送
+	AdminVerifyCodeRecordStatusFail    = 2 // 验证码发送状态-发送失败
+)
+
+// AdminVerifyCodeRecord 短信邮箱验证码记录表
+type AdminVerifyCodeRecord struct {
+	Id          int       `orm:"column(id);pk"`
+	VerifyType  int       `description:"验证方式:1-手机号;2-邮箱"`
+	Source      int       `description:"来源:1-登录;2-异常登录校验;3-忘记密码"`
+	Mobile      string    `description:"手机号"`
+	Email       string    `description:"邮箱"`
+	Code        string    `description:"验证码"`
+	ExpiredTime time.Time `description:"验证码过期时间"`
+	SendResult  string    `description:"发送结果"`
+	SendStatus  int       `description:"发送状态:0-待发送;1-已发送;2-发送失败"`
+	CreateTime  time.Time `description:"创建时间"`
+	ModifyTime  time.Time `description:"更新时间"`
+}
+
+func (m *AdminVerifyCodeRecord) TableName() string {
+	return "admin_verify_code_record"
+}
+
+func (m *AdminVerifyCodeRecord) PrimaryId() string {
+	return "id"
+}
+
+func (m *AdminVerifyCodeRecord) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) CreateMulti(items []*AdminVerifyCodeRecord) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.Id).Exec()
+	return
+}
+
+func (m *AdminVerifyCodeRecord) GetItemById(id int) (item *AdminVerifyCodeRecord, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) GetItemByCondition(condition string, pars []interface{}) (item *AdminVerifyCodeRecord, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AdminVerifyCodeRecord, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *AdminVerifyCodeRecord) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AdminVerifyCodeRecord, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}

+ 5 - 0
models/system/sys_admin.go

@@ -40,6 +40,7 @@ type AdminItem struct {
 	City                    string `description:"市"`
 	CityCode                string `description:"市编码"`
 	EmployeeId              string `description:"员工工号(钉钉/每刻报销)"`
+	TelAreaCode             string `description:"手机区号"`
 }
 
 func GetSysuserList(condition string, pars []interface{}, startSize, pageSize int) (items []*AdminItem, err error) {
@@ -114,6 +115,8 @@ type SysuserAddReq struct {
 	City             string `description:"市"`
 	CityCode         string `description:"市编码"`
 	EmployeeId       string `description:"员工工号(钉钉/每刻报销)"`
+	Email            string `description:"邮箱"`
+	TelAreaCode      string `description:"手机区号"`
 }
 
 func GetSysAdminCount(adminName string) (count int, err error) {
@@ -158,6 +161,8 @@ type SysuserEditReq struct {
 	City             string `description:"市"`
 	CityCode         string `description:"市编码"`
 	EmployeeId       string `description:"员工工号(钉钉/每刻报销)"`
+	Email            string `description:"邮箱"`
+	TelAreaCode      string `description:"手机区号"`
 }
 
 type SysUserMoveReq struct {

+ 52 - 6
models/system/sys_user.go

@@ -62,6 +62,7 @@ type Admin struct {
 	City                      string    `description:"市"`
 	CityCode                  string    `description:"市编码"`
 	EmployeeId                string    `description:"员工工号(钉钉/每刻报销)"`
+	TelAreaCode               string    `description:"手机区号"`
 }
 
 // Update 更新用户基础信息
@@ -130,12 +131,12 @@ func GetAdminList() (items []*Admin, err error) {
 }
 
 // GetSysUserByMobile 根据手机号获取管理信息
-func GetSysUserByMobile(mobile string) (item *Admin, err error) {
-	sql := `SELECT * FROM admin WHERE mobile = ? LIMIT 1`
-	o := orm.NewOrm()
-	err = o.Raw(sql, mobile).QueryRow(&item)
-	return
-}
+//func GetSysUserByMobile(mobile string) (item *Admin, err error) {
+//	sql := `SELECT * FROM admin WHERE mobile = ? LIMIT 1`
+//	o := orm.NewOrm()
+//	err = o.Raw(sql, mobile).QueryRow(&item)
+//	return
+//}
 
 // 通过用户姓名跟身份获取管理员信息
 func CheckSysUserByName(userName, roleTypeCode string) (item *Admin, err error) {
@@ -225,3 +226,48 @@ func (item *Admin) GetItemsByCondition(condition string, pars []interface{}, fie
 	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
+
+// GetSysUserByMobile 手机号获取用户
+func GetSysUserByMobile(mobile string) (item *Admin, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+				a.*, b.role_type_code
+			FROM
+				admin AS a
+			INNER JOIN sys_role AS b ON a.role_id = b.role_id
+			WHERE
+				a.mobile = ?
+			LIMIT 1`
+	err = o.Raw(sql, mobile).QueryRow(&item)
+	return
+}
+
+// GetSysUserByEmail 邮箱获取用户
+func GetSysUserByEmail(email string) (item *Admin, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+				a.*, b.role_type_code
+			FROM
+				admin AS a
+			INNER JOIN sys_role AS b ON a.role_id = b.role_id
+			WHERE
+				a.email = ?
+			LIMIT 1`
+	err = o.Raw(sql, email).QueryRow(&item)
+	return
+}
+
+// GetSysUserByAdminName 账号获取用户
+func GetSysUserByAdminName(adminName string) (item *Admin, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+				a.*, b.role_type_code
+			FROM
+				admin AS a
+			INNER JOIN sys_role AS b ON a.role_id = b.role_id
+			WHERE
+				a.admin_name = ?
+			LIMIT 1`
+	err = o.Raw(sql, adminName).QueryRow(&item)
+	return
+}

+ 63 - 0
routers/commentsRouter.go

@@ -10366,6 +10366,69 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "AreaCodeList",
+            Router: `/area_code/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "ForgetAccountGet",
+            Router: `/forget/account_get`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "ForgetCodeVerify",
+            Router: `/forget/code_verify`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "ForgetResetPass",
+            Router: `/forget/reset_pass`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "GenerateCaptcha",
+            Router: `/get_captcha`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "Login",
+            Router: `/login`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "GetVerifyCode",
+            Router: `/verify_code`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:VarietyTagController"] = append(beego.GlobalControllerRouter["hongze/hz_crm_api/controllers:VarietyTagController"],
         beego.ControllerComments{
             Method: "ClassifyList",

+ 6 - 0
routers/router.go

@@ -318,6 +318,12 @@ func init() {
 				&help_doc.HelpDocController{},
 			),
 		),
+		web.NSNamespace("/user_login",
+			web.NSInclude(
+				&controllers.UserLoginController{},
+				&controllers.UserLoginAuthController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 48 - 0
services/captcha_redis.go

@@ -0,0 +1,48 @@
+package services
+
+import (
+	"fmt"
+	"hongze/hz_crm_api/utils"
+	"time"
+)
+
+// CaptchaRedis Redis验证器
+type CaptchaRedis struct{}
+
+// Set 实现验证器set方法
+func (r CaptchaRedis) Set(id string, value string) (err error) {
+	if utils.Rc == nil {
+		err = fmt.Errorf("redis config err")
+		return
+	}
+	key := utils.CaptchaCachePrefix + id
+	b := utils.Rc.SetNX(key, value, time.Minute*5)
+	if !b {
+		err = fmt.Errorf("redis setnx err")
+	}
+	return
+}
+
+// Get 实现原验证器get方法
+func (r CaptchaRedis) Get(id string, clear bool) (code string) {
+	if utils.Rc == nil {
+		return
+	}
+	key := utils.CaptchaCachePrefix + id
+	val, err := utils.Rc.RedisString(key)
+	if err != nil {
+		return
+	}
+	code = val
+	// clear为true时验证通过删除验证码
+	if clear {
+		_ = utils.Rc.Delete(key)
+	}
+	return
+}
+
+// Verify 实现原验证器verify方法
+func (r CaptchaRedis) Verify(id, answer string, clear bool) bool {
+	v := r.Get(id, clear)
+	return v == answer
+}

+ 2 - 0
services/crm_eta.go

@@ -50,6 +50,7 @@ func GetAuthCodeFromMiddleServer(adminName string) (authCode string, err error)
 
 	contentType := "application/json;charset=utf-8"
 	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Authorization", utils.CrmEtaAuthorization)
 	resp, e := client.Do(req)
 	if e != nil {
 		err = fmt.Errorf("http client do err: %s", e.Error())
@@ -140,6 +141,7 @@ func CodeLoginFromMiddleServer(authCode string) (tokenResp GetEtaTokenData, err
 
 	contentType := "application/json;charset=utf-8"
 	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Authorization", utils.CrmEtaAuthorization)
 	resp, e := client.Do(req)
 	if e != nil {
 		err = fmt.Errorf("http client do err: %s", e.Error())

+ 97 - 20
services/sms.go

@@ -10,16 +10,86 @@ import (
 	"net/url"
 )
 
-func SendSmsCode(mobile, ip string, codeType int, vcode string) bool {
-	flag := false
-	tplId := ""
-	switch codeType {
-	case utils.REGISTER_CODE:
-		tplId = "206722"
-	case utils.LOGIN_CODE:
-		tplId = "65692"
+// SendSmsCode 发送国内短信
+func SendSmsCode(mobile, vCode, tplId string) (flag bool) {
+	if mobile == "" || vCode == "" || tplId == "" {
+		return
+	}
+
+	var (
+		err error
+		res string
+	)
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("短信验证码发送失败, Err: %s; Result: %s", err.Error(), res)
+			utils.FileLog.Info("%s", tips)
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+
+	result, e := sendSms(mobile, tplId, vCode)
+	if e != nil {
+		err = fmt.Errorf("send sms err: %s", e.Error())
+		return
+	}
+	res = string(result)
+
+	var netReturn map[string]interface{}
+	if e = json.Unmarshal(result, &netReturn); e != nil {
+		err = fmt.Errorf("json unmarshal err: %s", e.Error())
+		return
+	}
+	errCode, ok := netReturn["error_code"].(float64)
+	if !ok {
+		err = fmt.Errorf("result code err")
+		return
+	}
+	// 忽略错误的手机号码这种错误
+	if errCode != 0 && errCode != 205401 {
+		err = fmt.Errorf("err code %f", errCode)
+		return
+	}
+	// 发送成功
+	if errCode == 0 {
+		flag = true
+	}
+	return
+}
+
+// sendSms 发送国内短信
+func sendSms(mobile, tplId, code string) (rs []byte, err error) {
+	var Url *url.URL
+	apiURL := "http://v.juhe.cn/sms/send"
+	//初始化参数
+	param := url.Values{}
+	//配置请求参数,方法内部已处理urlencode问题,中文参数可以直接传参
+	param.Set("mobile", mobile) //接受短信的用户手机号码
+	param.Set("tpl_id", tplId)  //您申请的短信模板ID,根据实际情况修改
+	tplVal := fmt.Sprintf(`#code#=%s&#m#=%d`, code, utils.VerifyCodeExpireMinute)
+	param.Set("tpl_value", tplVal)     //您设置的模板变量,根据实际情况
+	param.Set("key", utils.JhGnAppKey) //应用APPKEY(应用详细页查询)
+
+	Url, err = url.Parse(apiURL)
+	if err != nil {
+		fmt.Printf("解析url错误:\r\n%v", err)
+		return nil, err
+	}
+	//如果参数中有中文参数,这个方法会进行URLEncode
+	Url.RawQuery = param.Encode()
+	resp, err := http.Get(Url.String())
+	if err != nil {
+		fmt.Println("err:", err)
+		return nil, err
 	}
-	result, err := sendSms(mobile, tplId, vcode)
+	defer resp.Body.Close()
+	return ioutil.ReadAll(resp.Body)
+}
+
+// SendSmsCodeGj 发送国际短信
+func SendSmsCodeGj(mobile, vCode, areaNum string) bool {
+	flag := false
+	result, err := sendSmsGj(mobile, vCode, areaNum)
 	if err != nil {
 		fmt.Println("发送短信失败")
 		return false
@@ -28,31 +98,35 @@ func SendSmsCode(mobile, ip string, codeType int, vcode string) bool {
 	var netReturn map[string]interface{}
 	err = json.Unmarshal(result, &netReturn)
 	if err != nil {
-		go alarm_msg.SendAlarmMsg("短信验证码发送失败 ErrMsg:"+err.Error()+" result"+string(result), 3)
-		//go utils.SendEmail("短信验证码发送失败","err:"+err.Error()+" result"+string(result), utils.EmailSendToUsers)
+		//go SendEmail("短信验证码发送失败", "err:"+err.Error()+" result"+string(result), utils.EmailSendToUsers)
+		go alarm_msg.SendAlarmMsg("短信验证码发送失败, Err:"+err.Error()+";Result:"+string(result), 2)
 		flag = false
 	}
 	if netReturn["error_code"].(float64) == 0 {
 		fmt.Printf("接口返回result字段是:\r\n%v", netReturn["result"])
 		flag = true
 	} else {
-		go alarm_msg.SendAlarmMsg("短信验证码发送失败 ErrMsg:"+err.Error()+" result"+string(result), 3)
-		//go utils.SendEmail("短信验证码发送失败"," result"+string(result), utils.EmailSendToUsers)
+		// 忽略错误的手机号码这种错误
+		if netReturn["error_code"].(float64) != 205401 {
+			go alarm_msg.SendAlarmMsg("短信验证码发送失败, Result:"+string(result), 2)
+		}
 		flag = false
 	}
 	return flag
 }
 
-func sendSms(mobile, tplId, code string) (rs []byte, err error) {
+// sendSmsGj 发送国际短信
+func sendSmsGj(mobile, code, areaNum string) (rs []byte, err error) {
 	var Url *url.URL
-	apiURL := "http://v.juhe.cn/sms/send"
+	apiURL := "http://v.juhe.cn/smsInternational/send.php"
 	//初始化参数
 	param := url.Values{}
 	//配置请求参数,方法内部已处理urlencode问题,中文参数可以直接传参
-	param.Set("mobile", mobile)            //接受短信的用户手机号码
-	param.Set("tpl_id", tplId)             //您申请的短信模板ID,根据实际情况修改
-	param.Set("tpl_value", "#code#="+code) //您设置的模板变量,根据实际情况
-	param.Set("key", utils.JhGnAppKey)     //应用APPKEY(应用详细页查询)
+	param.Set("mobile", mobile)           //接受短信的用户手机号码
+	param.Set("tplId", "10054")           //您申请的短信模板ID,根据实际情况修改
+	param.Set("tplValue", "#code#="+code) //您设置的模板变量,根据实际情况
+	param.Set("key", utils.JhGjAppKey)    //应用APPKEY(应用详细页查询)
+	param.Set("areaNum", areaNum)         //应用APPKEY(应用详细页查询)
 
 	Url, err = url.Parse(apiURL)
 	if err != nil {
@@ -66,6 +140,9 @@ func sendSms(mobile, tplId, code string) (rs []byte, err error) {
 		fmt.Println("err:", err)
 		return nil, err
 	}
+	utils.FileLog.Info("sendSmsGj:param:" + Url.String())
 	defer resp.Body.Close()
-	return ioutil.ReadAll(resp.Body)
+	body, err := ioutil.ReadAll(resp.Body)
+	utils.FileLog.Info("sendSmsGj:result:" + string(body))
+	return body, err
 }

+ 123 - 0
services/user_login.go

@@ -0,0 +1,123 @@
+package services
+
+import (
+	"encoding/json"
+	"fmt"
+	"hongze/hz_crm_api/models"
+	"hongze/hz_crm_api/models/company"
+	"hongze/hz_crm_api/models/system"
+	"hongze/hz_crm_api/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// SendAdminMobileVerifyCode 发送用户手机验证码
+func SendAdminMobileVerifyCode(source int, mobile, areaCode string) (ok bool, err error) {
+	verifyCode := utils.GetRandDigit(6)
+	record := new(system.AdminVerifyCodeRecord)
+	record.VerifyType = system.AdminVerifyCodeRecordTypeMobile
+	record.Mobile = mobile
+	record.Source = source
+	record.Code = verifyCode
+	record.ExpiredTime = time.Now().Add(utils.VerifyCodeExpireMinute * time.Minute)
+	record.CreateTime = time.Now().Local()
+	record.ModifyTime = time.Now().Local()
+	if e := record.Create(); e != nil {
+		err = fmt.Errorf("新增验证码记录失败, Err: %s", e.Error())
+		return
+	}
+
+	tplId := utils.SmsNewLoginTplId
+	if areaCode == "86" {
+		ok = SendSmsCode(mobile, verifyCode, tplId)
+	} else {
+		ok = SendSmsCodeGj(mobile, verifyCode, areaCode)
+	}
+	record.SendStatus = system.AdminVerifyCodeRecordStatusSuccess
+	if !ok {
+		record.SendStatus = system.AdminVerifyCodeRecordStatusFail
+	}
+	cols := []string{"SendStatus"}
+	if e := record.Update(cols); e != nil {
+		err = fmt.Errorf("更新验证码记录失败, Err: %s", e.Error())
+	}
+	return
+}
+
+// SendAdminEmailVerifyCode 发送用户邮箱验证码
+func SendAdminEmailVerifyCode(source int, email string) (ok bool, err error) {
+	verifyCode := utils.GetRandDigit(6)
+	record := new(system.AdminVerifyCodeRecord)
+	record.VerifyType = system.AdminVerifyCodeRecordTypeEmail
+	record.Email = email
+	record.Source = source
+	record.Code = verifyCode
+	record.ExpiredTime = time.Now().Add(utils.VerifyCodeExpireMinute * time.Minute)
+	record.CreateTime = time.Now().Local()
+	record.ModifyTime = time.Now().Local()
+	if e := record.Create(); e != nil {
+		err = fmt.Errorf("新增验证码记录失败, Err: %s", e.Error())
+		return
+	}
+
+	// 获取邮件配置
+	authKey := "english_report_email_conf"
+	emailConf, e := company.GetConfigDetailByCode(authKey)
+	if e != nil {
+		err = fmt.Errorf("获取群发邮件权限失败, Err: %s", e.Error())
+		return
+	}
+	if emailConf.ConfigValue == "" {
+		err = fmt.Errorf("邮件配置为空, 不可推送")
+		return
+	}
+	conf := new(models.EnglishReportEmailConf)
+	if e = json.Unmarshal([]byte(emailConf.ConfigValue), &conf); e != nil {
+		err = fmt.Errorf("邮件配置有误, 不可推送")
+		return
+	}
+
+	// 获取邮箱模板
+	confKey := "admin_verify_code_email_tmp"
+	confTmp, e := company.GetConfigDetailByCode(confKey)
+	if e != nil {
+		err = fmt.Errorf("获取邮件模板失败, Err: %s", e.Error())
+		return
+	}
+	if confTmp.ConfigValue == `` {
+		err = fmt.Errorf("邮件模板为空, 不可推送")
+		return
+	}
+
+	req := new(EnglishReportSendEmailRequest)
+	req.Subject = "弘则研究登录验证"
+	req.Email = email
+	req.FromAlias = conf.FromAlias
+	// 填充模板
+	t := time.Now().Format("2006年01月02日")
+	ct := confTmp.ConfigValue
+	ct = strings.Replace(ct, "{{VERIFY_CODE}}", verifyCode, 1)
+	ct = strings.Replace(ct, "{{EXPIRED_MINUTE}}", strconv.Itoa(utils.VerifyCodeExpireMinute), 1)
+	ct = strings.Replace(ct, "{{DATE_TIME}}", t, 1)
+	req.HtmlBody = ct
+
+	aliEmail := new(AliyunEmail)
+	o, result, e := aliEmail.SendEmail(req)
+	if e != nil {
+		err = fmt.Errorf("邮箱推送失败, Err: %s", e.Error())
+		return
+	}
+	ok = o
+
+	record.SendStatus = system.AdminVerifyCodeRecordStatusSuccess
+	if !ok {
+		record.SendStatus = system.AdminVerifyCodeRecordStatusFail
+	}
+	record.SendResult = result
+	cols := []string{"SendStatus", "SendResult"}
+	if e = record.Update(cols); e != nil {
+		err = fmt.Errorf("更新验证码记录失败, Err: %s", e.Error())
+	}
+	return
+}

+ 14 - 1
utils/constants.go

@@ -50,7 +50,7 @@ var (
 	JhGnTplId  = "65692"                            //聚合国内模板编码
 	JhGjTplId  = "10054"                            //聚合国内模板编码
 	JhGnAppKey = "4c8504c49dd335e99cfd7b6a3a9e2415" //聚合国内AppKey
-	JhGjAppKey = "3326ad2c1047a4cd92ace153e6044ca3"
+	JhGjAppKey = "3326ad2c1047a4cd92ace153e6044ca3" //聚合国内AppKey
 )
 
 // 科大讯飞--语音合成
@@ -285,6 +285,9 @@ const (
 	CACHE_IMPORT_MANUAL_DATA          = "import:manual:data"                  //手工数据导入后刷新
 	CACHE_ACCESS_TOKEN_LOGIN          = "pc_admin:login:"                     //管理后台登录
 	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST = "pc_admin:login:no_trust:"            //管理后台登录(不可信登录态)
+	CACHE_ABNORMAL_LOGIN              = "pc_admin:login:abnormal:"            //管理后台登录-异常登录
+	CACHE_LOGIN_ERR_PASS              = "pc_admin:login:errPass:"             //管理后台登录-输入错误密码次数
+	CACHE_FIND_PASS_VERIFY            = "pc_admin:findPass:verify:"           //找回密码校验成功标记
 	CACHE_KEY_COMPANY_MATCH_PRE       = "admin:company:match:"                //客户名单匹配
 	CACHE_KEY_MYSTEEL_REFRESH         = "mysteel_chemical:refresh"            //钢联化工刷新
 	CACHE_KEY_DAYNEW_REFRESH          = "admin:day_new:refresh"               //每日资讯拉取企业微信聊天记录
@@ -514,3 +517,13 @@ const BusinessCodeSalt = "dr7WY0OZgGR7upw1"
 const (
 	ApproveUserId = 2 //施琪-出差审批人
 )
+
+// 验证码
+const (
+	CaptchaCachePrefix     = "captcha:lock:crm_" // 验证码缓存Key
+	VerifyCodeExpireMinute = 15                  // 短信/邮箱验证码过期时间-分钟
+	SmsLoginTplId          = "65692"             // 【弘则研究】您的验证码是XXX,如非本人操作,请忽略本短信
+	SmsNewLoginTplId       = "254663"            // 【弘则研究】您的验证码是XXX,有效期15分钟
+)
+
+const CrmEtaAuthorization = "NIi1RbEmH0C2rksXtPGDPBBgRgTZY87Q"