mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
Merge branch 'dev'
This commit is contained in:
@@ -28,7 +28,7 @@ mysql:
|
||||
# redis:
|
||||
# host: localhost
|
||||
# port: 6379
|
||||
# passsord:
|
||||
# password: 111049
|
||||
# db: 0
|
||||
log:
|
||||
# 日志等级, trace, debug, info, warn, error, fatal
|
||||
|
||||
@@ -10,6 +10,7 @@ require (
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/mojocn/base64Captcha v1.3.5 // 验证码
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
@@ -23,6 +24,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mayfly-go/internal/sys/api/form"
|
||||
"mayfly-go/internal/sys/api/vo"
|
||||
"mayfly-go/internal/sys/application"
|
||||
"mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/captcha"
|
||||
"mayfly-go/pkg/ginx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/otp"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils"
|
||||
"regexp"
|
||||
@@ -18,6 +21,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
OtpStatusNone = -1 // 未启用otp校验
|
||||
OtpStatusReg = 1 // 用户otp secret已注册
|
||||
OtpStatusNoReg = 2 // 用户otp secret未注册
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
AccountApp application.Account
|
||||
ResourceApp application.Resource
|
||||
@@ -33,39 +42,141 @@ func (a *Account) Login(rc *req.Ctx) {
|
||||
loginForm := &form.LoginForm{}
|
||||
ginx.BindJsonAndValid(rc.GinCtx, loginForm)
|
||||
|
||||
accountLoginSecurity := a.ConfigApp.GetConfig(entity.ConfigKeyAccountLoginSecurity).ToAccountLoginSecurity()
|
||||
// 判断是否有开启登录验证码校验
|
||||
if a.ConfigApp.GetConfig(entity.ConfigKeyUseLoginCaptcha).BoolValue(true) {
|
||||
if accountLoginSecurity.UseCaptcha {
|
||||
// 校验验证码
|
||||
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
|
||||
}
|
||||
|
||||
username := loginForm.Username
|
||||
clientIp := rc.GinCtx.ClientIP()
|
||||
rc.ReqParam = loginForm.Username
|
||||
rc.ReqParam = fmt.Sprintf("username: %s | ip: %s", loginForm.Username, clientIp)
|
||||
rc.ReqParam = fmt.Sprintf("username: %s | ip: %s", username, clientIp)
|
||||
|
||||
originPwd, err := utils.DefaultRsaDecrypt(loginForm.Password, true)
|
||||
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
|
||||
|
||||
account := &entity.Account{Username: loginForm.Username}
|
||||
err = a.AccountApp.GetAccount(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp")
|
||||
biz.ErrIsNil(err, "用户名或密码错误")
|
||||
biz.IsTrue(utils.CheckPwdHash(originPwd, account.Password), "用户名或密码错误")
|
||||
account := &entity.Account{Username: username}
|
||||
err = a.AccountApp.GetAccount(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
|
||||
|
||||
failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
|
||||
nowFailCount := cache.GetInt(failCountKey)
|
||||
loginFailCount := accountLoginSecurity.LoginFailCount
|
||||
loginFailMin := accountLoginSecurity.LoginFailMin
|
||||
biz.IsTrue(nowFailCount < loginFailCount, fmt.Sprintf("登录失败超过%d次, 请%d分钟后再试", loginFailCount, loginFailMin))
|
||||
|
||||
if err != nil || !utils.CheckPwdHash(originPwd, account.Password) {
|
||||
nowFailCount++
|
||||
cache.SetStr(failCountKey, strconv.Itoa(nowFailCount), time.Minute*time.Duration(loginFailMin))
|
||||
panic(biz.NewBizErr(fmt.Sprintf("用户名或密码错误【当前登录失败%d次】", nowFailCount)))
|
||||
}
|
||||
biz.IsTrue(account.IsEnable(), "该账号不可用")
|
||||
|
||||
// 校验密码强度是否符合
|
||||
// 校验密码强度(新用户第一次登录密码与账号名一致)
|
||||
biz.IsTrueBy(CheckPasswordLever(originPwd), biz.NewBizErrCode(401, "您的密码安全等级较低,请修改后重新登录"))
|
||||
// 保存登录消息
|
||||
go a.saveLogin(account, clientIp)
|
||||
|
||||
rc.ResData = map[string]any{
|
||||
"token": req.CreateToken(account.Id, account.Username),
|
||||
res := map[string]any{
|
||||
"name": account.Name,
|
||||
"username": account.Username,
|
||||
"username": username,
|
||||
"lastLoginTime": account.LastLoginTime,
|
||||
"lastLoginIp": account.LastLoginIp,
|
||||
}
|
||||
|
||||
// 默认为不校验otp
|
||||
otpStatus := OtpStatusNone
|
||||
var token string
|
||||
// 访问系统使用的token
|
||||
accessToken := req.CreateToken(account.Id, username)
|
||||
// 若系统配置中设置开启otp双因素校验,则进行otp校验
|
||||
if accountLoginSecurity.UseOtp {
|
||||
otpSecret := account.OtpSecret
|
||||
// 修改状态为已注册
|
||||
otpStatus = OtpStatusReg
|
||||
// 该token用于opt双因素校验
|
||||
token = utils.RandString(32)
|
||||
// 未注册otp secret或重置了秘钥
|
||||
if otpSecret == "" || otpSecret == "-" {
|
||||
otpStatus = OtpStatusNoReg
|
||||
key, err := otp.NewTOTP(otp.GenerateOpts{
|
||||
AccountName: username,
|
||||
Issuer: accountLoginSecurity.OtpIssuer,
|
||||
})
|
||||
biz.ErrIsNilAppendErr(err, "otp生成失败: %s")
|
||||
res["otpUrl"] = key.URL()
|
||||
// 使用otpSecret充当token进行二次校验
|
||||
otpSecret = key.Secret()
|
||||
}
|
||||
// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
|
||||
otpInfo := &OtpVerifyInfo{
|
||||
AccountId: account.Id,
|
||||
Username: username,
|
||||
OptStatus: otpStatus,
|
||||
OtpSecret: otpSecret,
|
||||
AccessToken: accessToken,
|
||||
}
|
||||
cache.SetStr(fmt.Sprintf("otp:token:%s", token), utils.ToJsonStr(otpInfo), time.Minute*time.Duration(3))
|
||||
} else {
|
||||
// 不进行otp二次校验则直接返回accessToken
|
||||
token = accessToken
|
||||
// 保存登录消息
|
||||
go a.saveLogin(account, clientIp)
|
||||
}
|
||||
|
||||
// 赋值otp状态
|
||||
res["otp"] = otpStatus
|
||||
res["token"] = token
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
type OtpVerifyInfo struct {
|
||||
AccountId uint64
|
||||
Username string
|
||||
OptStatus int
|
||||
AccessToken string
|
||||
OtpSecret string
|
||||
}
|
||||
|
||||
// OTP双因素校验
|
||||
func (a *Account) OtpVerify(rc *req.Ctx) {
|
||||
otpVerify := new(form.OtpVerfiy)
|
||||
ginx.BindJsonAndValid(rc.GinCtx, otpVerify)
|
||||
|
||||
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
|
||||
otpInfoJson := cache.GetStr(tokenKey)
|
||||
biz.NotEmpty(otpInfoJson, "otpToken错误或失效, 请重新登陆获取")
|
||||
otpInfo := new(OtpVerifyInfo)
|
||||
json.Unmarshal([]byte(otpInfoJson), otpInfo)
|
||||
|
||||
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
|
||||
failCount := cache.GetInt(failCountKey)
|
||||
biz.IsTrue(failCount < 5, "双因素校验失败超过5次, 请10分钟后再试")
|
||||
|
||||
otpStatus := otpInfo.OptStatus
|
||||
accessToken := otpInfo.AccessToken
|
||||
accountId := otpInfo.AccountId
|
||||
otpSecret := otpInfo.OtpSecret
|
||||
|
||||
if !otp.Validate(otpVerify.Code, otpSecret) {
|
||||
cache.SetStr(failCountKey, strconv.Itoa(failCount+1), time.Minute*time.Duration(10))
|
||||
panic(biz.NewBizErr("双因素认证授权码不正确"))
|
||||
}
|
||||
|
||||
// 如果是未注册状态,则更新account表的otpSecret信息
|
||||
if otpStatus == OtpStatusNoReg {
|
||||
update := &entity.Account{OtpSecret: otpSecret}
|
||||
update.Id = accountId
|
||||
a.AccountApp.Update(update)
|
||||
}
|
||||
|
||||
la := &entity.Account{Username: otpInfo.Username}
|
||||
la.Id = accountId
|
||||
go a.saveLogin(la, rc.GinCtx.ClientIP())
|
||||
|
||||
cache.Del(tokenKey)
|
||||
rc.ResData = accessToken
|
||||
}
|
||||
|
||||
// 获取当前登录用户的菜单与权限码
|
||||
func (a *Account) GetPermissions(rc *req.Ctx) {
|
||||
account := rc.LoginAccount
|
||||
|
||||
@@ -296,3 +407,12 @@ func (a *Account) SaveRoles(rc *req.Ctx) {
|
||||
a.RoleApp.DeleteAccountRole(aid, v.(uint64))
|
||||
}
|
||||
}
|
||||
|
||||
// 重置otp秘钥
|
||||
func (a *Account) ResetOtpSecret(rc *req.Ctx) {
|
||||
account := &entity.Account{OtpSecret: "-"}
|
||||
accountId := uint64(ginx.PathParamInt(rc.GinCtx, "id"))
|
||||
account.Id = accountId
|
||||
rc.ReqParam = fmt.Sprintf("accountId = %d", accountId)
|
||||
a.AccountApp.Update(account)
|
||||
}
|
||||
|
||||
@@ -7,3 +7,8 @@ type LoginForm struct {
|
||||
Captcha string `json:"captcha"`
|
||||
Cid string `json:"cid"`
|
||||
}
|
||||
|
||||
type OtpVerfiy struct {
|
||||
OtpToken string `json:"otpToken" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (a *configAppImpl) GetConfig(key string) *entity.Config {
|
||||
if err := a.configRepo.GetConfig(config, "Id", "Key", "Value"); err != nil {
|
||||
global.Log.Warnf("不存在key = [%s] 的系统配置", key)
|
||||
} else {
|
||||
cache.SetStr(SysConfigKeyPrefix+key, utils.ToJsonStr(config))
|
||||
cache.SetStr(SysConfigKeyPrefix+key, utils.ToJsonStr(config), -1)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type Account struct {
|
||||
Status int8 `json:"status"`
|
||||
LastLoginTime *time.Time `json:"lastLoginTime"`
|
||||
LastLoginIp string `json:"lastLoginIp"`
|
||||
OtpSecret string `json:"-"`
|
||||
}
|
||||
|
||||
func (a *Account) TableName() string {
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigKeyUseLoginCaptcha string = "UseLoginCaptcha" // 是否使用登录验证码
|
||||
ConfigKeyDbQueryMaxCount string = "DbQueryMaxCount" // 数据库查询的最大数量
|
||||
ConfigKeyDbSaveQuerySQL string = "DbSaveQuerySQL" // 数据库是否记录查询相关sql
|
||||
ConfigKeyAccountLoginSecurity string = "AccountLoginSecurity" // 账号登录安全配置
|
||||
ConfigKeyUseLoginCaptcha string = "UseLoginCaptcha" // 是否使用登录验证码
|
||||
ConfigKeyUseLoginOtp string = "UseLoginOtp" // 是否开启otp双因素校验
|
||||
ConfigKeyDbQueryMaxCount string = "DbQueryMaxCount" // 数据库查询的最大数量
|
||||
ConfigKeyDbSaveQuerySQL string = "DbSaveQuerySQL" // 数据库是否记录查询相关sql
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -26,13 +28,12 @@ func (a *Config) TableName() string {
|
||||
}
|
||||
|
||||
// 若配置信息不存在, 则返回传递的默认值.
|
||||
// 否则只有value == "1"为true,其他为false
|
||||
func (c *Config) BoolValue(defaultValue bool) bool {
|
||||
// 如果值不存在,则返回默认值
|
||||
if c.Id == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
return c.Value == "1"
|
||||
return convertBool(c.Value, defaultValue)
|
||||
}
|
||||
|
||||
// 值返回json map
|
||||
@@ -51,7 +52,47 @@ func (c *Config) IntValue(defaultValue int) int {
|
||||
if c.Id == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
if intV, err := strconv.Atoi(c.Value); err != nil {
|
||||
return convertInt(c.Value, defaultValue)
|
||||
}
|
||||
|
||||
type AccountLoginSecurity struct {
|
||||
UseCaptcha bool // 是否使用登录验证码
|
||||
UseOtp bool // 是否双因素校验
|
||||
OtpIssuer string // otp发行人
|
||||
LoginFailCount int // 允许失败次数
|
||||
LoginFailMin int // 登录失败指定次数后禁止的分钟数
|
||||
}
|
||||
|
||||
// 转换为AccountLoginSecurity结构体
|
||||
func (c *Config) ToAccountLoginSecurity() *AccountLoginSecurity {
|
||||
jm := c.GetJsonMap()
|
||||
als := new(AccountLoginSecurity)
|
||||
als.UseCaptcha = convertBool(jm["useCaptcha"], true)
|
||||
als.UseOtp = convertBool(jm["useOtp"], false)
|
||||
als.LoginFailCount = convertInt(jm["loginFailCount"], 5)
|
||||
als.LoginFailMin = convertInt(jm["loginFailMin"], 10)
|
||||
otpIssuer := jm["otpIssuer"]
|
||||
if otpIssuer == "" {
|
||||
otpIssuer = "mayfly-go"
|
||||
}
|
||||
als.OtpIssuer = otpIssuer
|
||||
return als
|
||||
}
|
||||
|
||||
// 转换配置中的值为bool类型(默认"1"或"true"为true,其他为false)
|
||||
func convertBool(value string, defaultValue bool) bool {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value == "1" || value == "true"
|
||||
}
|
||||
|
||||
// 转换配置值中的值为int
|
||||
func convertInt(value string, defaultValue int) int {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
if intV, err := strconv.Atoi(value); err != nil {
|
||||
return defaultValue
|
||||
} else {
|
||||
return intV
|
||||
|
||||
@@ -27,6 +27,12 @@ func InitAccountRouter(router *gin.RouterGroup) {
|
||||
Handle(a.Login)
|
||||
})
|
||||
|
||||
account.POST("otp-verify", func(g *gin.Context) {
|
||||
req.NewCtxWithGin(g).
|
||||
DontNeedToken().
|
||||
Handle(a.OtpVerify)
|
||||
})
|
||||
|
||||
// 获取个人账号的权限资源信息
|
||||
account.GET("/permissions", func(c *gin.Context) {
|
||||
req.NewCtxWithGin(c).Handle(a.GetPermissions)
|
||||
@@ -77,6 +83,14 @@ func InitAccountRouter(router *gin.RouterGroup) {
|
||||
Handle(a.ChangeStatus)
|
||||
})
|
||||
|
||||
resetOtpSecret := req.NewLogInfo("重置OTP密钥").WithSave(true)
|
||||
account.PUT(":id/reset-otp", func(c *gin.Context) {
|
||||
req.NewCtxWithGin(c).
|
||||
WithRequiredPermission(addAccountPermission).
|
||||
WithLog(resetOtpSecret).
|
||||
Handle(a.ResetOtpSecret)
|
||||
})
|
||||
|
||||
delAccount := req.NewLogInfo("删除账号").WithSave(true)
|
||||
delAccountPermission := req.NewPermission("account:del")
|
||||
account.DELETE(":id", func(c *gin.Context) {
|
||||
|
||||
@@ -21,9 +21,11 @@ func InitSysConfigRouter(router *gin.RouterGroup) {
|
||||
})
|
||||
|
||||
saveConfig := req.NewLogInfo("保存系统配置信息").WithSave(true)
|
||||
saveConfigP := req.NewPermission("config:base")
|
||||
db.POST("", func(c *gin.Context) {
|
||||
req.NewCtxWithGin(c).
|
||||
WithLog(saveConfig).
|
||||
WithRequiredPermission(saveConfigP).
|
||||
Handle(r.SaveConfig)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -350,10 +350,10 @@ CREATE TABLE `t_sys_config` (
|
||||
-- Records of t_sys_config
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `t_sys_config` VALUES (1, '是否启用登录验证码', 'UseLoginCaptcha', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 22:27:17', 1, 'admin', '2022-08-26 10:26:56', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` VALUES (2, '是否启用水印', 'UseWartermark', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` VALUES (3, '数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数。注: 0=不限制', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` VALUES (4, '数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier) VALUES(5, '账号登录安全设置', 'AccountLoginSecurity', '[{"name":"登录验证码","model":"useCaptcha","placeholder":"是否启用登录验证码","options":"true,false"},{"name":"双因素校验","model":"useOtp","placeholder":"是否启用双因素(OTP)校验","options":"true,false"},{"name":"otp签发人","model":"otpIssuer","placeholder":"otp签发人"},{"name":"允许失败次数","model":"loginFailCount","placeholder":"登录失败n次后禁止登录"},{"name":"禁止登录时间","model":"loginFailMin","placeholder":"登录失败指定次数后禁止m分钟内再次登录"}]', '{"useCaptcha":"true","useOtp":"false","loginFailCount":"5","loginFailMin":"10","otpIssuer":"mayfly-go"}', '系统账号登录相关安全设置', '2023-06-17 11:02:11', 1, 'admin', '2023-06-17 14:18:07', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('是否启用水印', 'UseWartermark', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数。注: 0=不限制', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin');
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin');
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
|
||||
61
server/pkg/cache/str_cache.go
vendored
61
server/pkg/cache/str_cache.go
vendored
@@ -1,15 +1,25 @@
|
||||
package cache
|
||||
|
||||
import "mayfly-go/pkg/rediscli"
|
||||
import (
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/rediscli"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var strCache map[string]string
|
||||
var tm *TimedCache
|
||||
|
||||
// 如果系统有设置redis信息,则从redis获取,否则本机内存获取
|
||||
func GetStr(key string) string {
|
||||
if rediscli.GetCli() == nil {
|
||||
checkStrCache()
|
||||
return strCache[key]
|
||||
if !useRedisCache() {
|
||||
checkCache()
|
||||
val, _ := tm.Get(key)
|
||||
if val == nil {
|
||||
return ""
|
||||
}
|
||||
return val.(string)
|
||||
}
|
||||
|
||||
res, err := rediscli.Get(key)
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -17,28 +27,45 @@ func GetStr(key string) string {
|
||||
return res
|
||||
}
|
||||
|
||||
// 如果系统有设置redis信息,则使用redis存,否则存于本机内存
|
||||
func SetStr(key, value string) {
|
||||
if rediscli.GetCli() == nil {
|
||||
checkStrCache()
|
||||
strCache[key] = value
|
||||
func GetInt(key string) int {
|
||||
val := GetStr(key)
|
||||
if val == "" {
|
||||
return 0
|
||||
}
|
||||
if intV, err := strconv.Atoi(val); err != nil {
|
||||
global.Log.Error("获取缓存中的int值转换失败", err)
|
||||
return 0
|
||||
} else {
|
||||
return intV
|
||||
}
|
||||
}
|
||||
|
||||
// 如果系统有设置redis信息,则使用redis存,否则存于本机内存。duration == -1则为永久缓存
|
||||
func SetStr(key, value string, duration time.Duration) {
|
||||
if !useRedisCache() {
|
||||
checkCache()
|
||||
tm.Add(key, value, duration)
|
||||
return
|
||||
}
|
||||
rediscli.Set(key, value, 0)
|
||||
rediscli.Set(key, value, duration)
|
||||
}
|
||||
|
||||
// 删除指定key
|
||||
func Del(key string) {
|
||||
if rediscli.GetCli() == nil {
|
||||
checkStrCache()
|
||||
delete(strCache, key)
|
||||
if !useRedisCache() {
|
||||
checkCache()
|
||||
tm.Delete(key)
|
||||
return
|
||||
}
|
||||
rediscli.Del(key)
|
||||
}
|
||||
|
||||
func checkStrCache() {
|
||||
if strCache == nil {
|
||||
strCache = make(map[string]string)
|
||||
func checkCache() {
|
||||
if tm == nil {
|
||||
tm = NewTimedCache(time.Minute*time.Duration(5), 30*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func useRedisCache() bool {
|
||||
return rediscli.GetCli() != nil
|
||||
}
|
||||
|
||||
8
server/pkg/cache/timed_cache.go
vendored
8
server/pkg/cache/timed_cache.go
vendored
@@ -63,10 +63,10 @@ type timedcache struct {
|
||||
func (c *timedcache) Add(k any, x any, d time.Duration) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_, found := c.get(k)
|
||||
if found {
|
||||
return fmt.Errorf("Item %s already exists", k)
|
||||
}
|
||||
// _, found := c.get(k)
|
||||
// if found {
|
||||
// return fmt.Errorf("Item %s already exists", k)
|
||||
// }
|
||||
c.set(k, x, d)
|
||||
return nil
|
||||
}
|
||||
|
||||
19
server/pkg/otp/otp.go
Normal file
19
server/pkg/otp/otp.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package otp
|
||||
|
||||
import (
|
||||
otp_t "github.com/pquerna/otp"
|
||||
totp_t "github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
type GenerateOpts totp_t.GenerateOpts
|
||||
|
||||
func NewTOTP(opt GenerateOpts) (*otp_t.Key, error) {
|
||||
return totp_t.Generate(totp_t.GenerateOpts(opt))
|
||||
}
|
||||
|
||||
func Validate(code string, secret string) bool {
|
||||
if secret == "" {
|
||||
return true
|
||||
}
|
||||
return totp_t.Validate(code, secret)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func connRedis() *redis.Client {
|
||||
// 测试连接
|
||||
_, e := rdb.Ping(context.TODO()).Result()
|
||||
if e != nil {
|
||||
global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d]", redisConf.Host, redisConf.Port))
|
||||
global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d][%s]", redisConf.Host, redisConf.Port, e.Error()))
|
||||
}
|
||||
return rdb
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ func GetRsaPublicKey() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cache.SetStr(publicKeyK, publicKey)
|
||||
cache.SetStr(privateKeyK, privateKey)
|
||||
cache.SetStr(publicKeyK, publicKey, -1)
|
||||
cache.SetStr(privateKeyK, privateKey, -1)
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ func GetRsaPrivateKey() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cache.SetStr(publicKeyK, publicKey)
|
||||
cache.SetStr(privateKeyK, privateKey)
|
||||
cache.SetStr(publicKeyK, publicKey, -1)
|
||||
cache.SetStr(privateKeyK, privateKey, -1)
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user