mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
refactor: 引入dayjs、新增refreshToken无感刷新、团队新增有效期、数据库等问题修复
This commit is contained in:
@@ -11,9 +11,11 @@ server:
|
||||
cert-file: ./default.pem
|
||||
jwt:
|
||||
# jwt key,不设置默认使用随机字符串
|
||||
key:
|
||||
# 过期时间单位分钟
|
||||
expire-time: 1440
|
||||
key: 333333000000
|
||||
# accessToken过期时间单位分钟
|
||||
expire-time: 720
|
||||
# refreshToken过期时间单位分钟
|
||||
refresh-token-expire-time: 4320
|
||||
# 资源密码aes加密key
|
||||
aes:
|
||||
key: 1111111111111111
|
||||
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/redis/go-redis/v9 v9.5.1
|
||||
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
||||
github.com/sijms/go-ora/v2 v2.8.16
|
||||
github.com/sijms/go-ora/v2 v2.8.17
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/veops/go-ansiterm v0.0.5
|
||||
go.mongodb.org/mongo-driver v1.15.0 // mongo
|
||||
|
||||
@@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mayfly-go/internal/auth/api/form"
|
||||
"mayfly-go/internal/auth/config"
|
||||
@@ -71,11 +70,12 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
type OtpVerifyInfo struct {
|
||||
AccountId uint64
|
||||
Username string
|
||||
OptStatus int
|
||||
AccessToken string
|
||||
OtpSecret string
|
||||
AccountId uint64
|
||||
Username string
|
||||
OptStatus int
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
OtpSecret string
|
||||
}
|
||||
|
||||
// OTP双因素校验
|
||||
@@ -84,10 +84,9 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
||||
req.BindJsonAndValid(rc, 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)
|
||||
ok := cache.Get(tokenKey, otpInfo)
|
||||
biz.IsTrue(ok, "otpToken错误或失效, 请重新登陆获取")
|
||||
|
||||
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
|
||||
failCount := cache.GetInt(failCountKey)
|
||||
@@ -116,7 +115,20 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
||||
go saveLogin(la, getIpAndRegion(rc))
|
||||
|
||||
cache.Del(tokenKey)
|
||||
rc.ResData = accessToken
|
||||
rc.ResData = collx.Kvs("token", accessToken, "refresh_token", otpInfo.RefreshToken)
|
||||
}
|
||||
|
||||
func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
|
||||
refreshToken := rc.Query("refresh_token")
|
||||
biz.NotEmpty(refreshToken, "refresh_token不能为空")
|
||||
|
||||
accountId, username, err := req.ParseToken(refreshToken)
|
||||
if err != nil {
|
||||
panic(errorx.PermissionErr)
|
||||
}
|
||||
token, refreshToken, err := req.CreateToken(accountId, username)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken)
|
||||
}
|
||||
|
||||
func (a *AccountLogin) Logout(rc *req.Ctx) {
|
||||
|
||||
@@ -41,18 +41,19 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
|
||||
// 默认为不校验otp
|
||||
otpStatus := OtpStatusNone
|
||||
// 访问系统使用的token
|
||||
accessToken, err := req.CreateToken(account.Id, username)
|
||||
accessToken, refreshToken, err := req.CreateToken(account.Id, username)
|
||||
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
|
||||
|
||||
// 若系统配置中设置开启otp双因素校验,则进行otp校验
|
||||
if accountLoginSecurity.UseOtp {
|
||||
otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken)
|
||||
otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken, refreshToken)
|
||||
otpStatus = otpInfo.OptStatus
|
||||
if otpurl != "" {
|
||||
res["otpUrl"] = otpurl
|
||||
}
|
||||
accessToken = otpToken
|
||||
} else {
|
||||
res["refresh_token"] = refreshToken
|
||||
// 不进行otp二次校验则直接返回accessToken
|
||||
// 保存登录消息
|
||||
go saveLogin(account, loginIp)
|
||||
@@ -64,7 +65,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
|
||||
return res
|
||||
}
|
||||
|
||||
func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVerifyInfo, string, string) {
|
||||
func useOtp(account *sysentity.Account, otpIssuer, accessToken string, refreshToken string) (*OtpVerifyInfo, string, string) {
|
||||
biz.ErrIsNil(account.OtpSecretDecrypt())
|
||||
otpSecret := account.OtpSecret
|
||||
// 修改状态为已注册
|
||||
@@ -83,13 +84,14 @@ func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVeri
|
||||
otpUrl = key.URL()
|
||||
otpSecret = key.Secret()
|
||||
}
|
||||
// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
|
||||
// 缓存otpInfo, 只有双因素校验通过才可返回真正的token
|
||||
otpInfo := &OtpVerifyInfo{
|
||||
AccountId: account.Id,
|
||||
Username: account.Username,
|
||||
OptStatus: otpStatus,
|
||||
OtpSecret: otpSecret,
|
||||
AccessToken: accessToken,
|
||||
AccountId: account.Id,
|
||||
Username: account.Username,
|
||||
OptStatus: otpStatus,
|
||||
OtpSecret: otpSecret,
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
}
|
||||
cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
|
||||
return otpInfo, otpUrl, token
|
||||
|
||||
@@ -26,6 +26,8 @@ func Init(router *gin.RouterGroup) {
|
||||
// 用户账号密码登录
|
||||
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSave("用户登录")).DontNeedToken(),
|
||||
|
||||
req.NewGet("/accounts/refreshToken", accountLogin.RefreshToken).DontNeedToken(),
|
||||
|
||||
// 用户退出登录
|
||||
req.NewPost("/accounts/logout", accountLogin.Logout),
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ type Team struct {
|
||||
model.Model
|
||||
entity.RelateTags // 标签信息
|
||||
|
||||
Name string `json:"name"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
Name string `json:"name"` // 名称
|
||||
ValidityStartDate *model.JsonTime `json:"validityStartDate"` // 生效开始时间
|
||||
ValidityEndDate *model.JsonTime `json:"validityEndDate"` // 生效结束时间
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
}
|
||||
|
||||
func (t *Team) GetRelateId() uint64 {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package dto
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
type SaveTeam struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" binding:"required"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" binding:"required"` // 名称
|
||||
ValidityStartDate *model.JsonTime `json:"validityStartDate"` // 生效开始时间
|
||||
ValidityEndDate *model.JsonTime `json:"validityEndDate"` // 生效结束时间
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
|
||||
CodePaths []string `json:"codePaths"` // 关联标签信息
|
||||
}
|
||||
|
||||
@@ -56,7 +56,12 @@ func (p *teamAppImpl) GetPageList(condition *entity.TeamQuery, pageParam *model.
|
||||
}
|
||||
|
||||
func (p *teamAppImpl) SaveTeam(ctx context.Context, saveParam *dto.SaveTeam) error {
|
||||
team := &entity.Team{Name: saveParam.Name, Remark: saveParam.Remark}
|
||||
team := &entity.Team{
|
||||
Name: saveParam.Name,
|
||||
ValidityStartDate: saveParam.ValidityStartDate,
|
||||
ValidityEndDate: saveParam.ValidityEndDate,
|
||||
Remark: saveParam.Remark,
|
||||
}
|
||||
team.Id = saveParam.Id
|
||||
|
||||
if team.Id == 0 {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
// 团队信息
|
||||
type Team struct {
|
||||
model.Model
|
||||
|
||||
Name string `json:"name"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
Name string `json:"name"` // 名称
|
||||
ValidityStartDate *model.JsonTime `json:"validityStartDate"` // 生效开始时间
|
||||
ValidityEndDate *model.JsonTime `json:"validityEndDate"` // 生效结束时间
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
const AccountTagsKey = "mayfly:tag:account:%d"
|
||||
|
||||
func SaveAccountTagPaths(accountId uint64, tags []string) error {
|
||||
return global_cache.Set(fmt.Sprintf(AccountTagsKey, accountId), tags, 30*time.Minute)
|
||||
return global_cache.Set(fmt.Sprintf(AccountTagsKey, accountId), tags, 2*time.Minute)
|
||||
}
|
||||
|
||||
func GetAccountTagPaths(accountId uint64) ([]string, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tagTreeRelateRepoImpl struct {
|
||||
@@ -43,12 +44,10 @@ func (tr *tagTreeRelateRepoImpl) SelectTagPathsByAccountId(accountId uint64) []s
|
||||
sql := `
|
||||
SELECT
|
||||
DISTINCT(t.code_path)
|
||||
FROM
|
||||
t_tag_tree_relate t1
|
||||
JOIN t_team_member t2 ON
|
||||
t1.relate_id = t2.team_id
|
||||
JOIN t_tag_tree t ON
|
||||
t.id = t1.tag_id
|
||||
FROM t_tag_tree_relate t1
|
||||
JOIN t_team_member t2 ON t1.relate_id = t2.team_id
|
||||
JOIN t_team t3 ON t3.id = t2.team_id AND t3.validity_start_date < ? AND t3.validity_end_date > ?
|
||||
JOIN t_tag_tree t ON t.id = t1.tag_id
|
||||
WHERE
|
||||
t1.relate_type = ?
|
||||
AND t2.account_id = ?
|
||||
@@ -58,7 +57,8 @@ WHERE
|
||||
ORDER BY
|
||||
t.code_path
|
||||
`
|
||||
tr.SelectBySql(sql, &res, entity.TagRelateTypeTeam, accountId)
|
||||
now := time.Now()
|
||||
tr.SelectBySql(sql, &res, now, now, entity.TagRelateTypeTeam, accountId)
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
const (
|
||||
AppName = "mayfly-go"
|
||||
Version = "v1.8.3"
|
||||
Version = "v1.8.4"
|
||||
)
|
||||
|
||||
func GetAppInfo() string {
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
)
|
||||
|
||||
type Jwt struct {
|
||||
Key string `yaml:"key"`
|
||||
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
||||
Key string `yaml:"key"`
|
||||
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
||||
RefreshTokenExpireTime uint64 `yaml:"refresh-token-expire-time"` // 刷新token的过期时间,单位分钟
|
||||
}
|
||||
|
||||
func (j *Jwt) Default() {
|
||||
@@ -22,6 +23,10 @@ func (j *Jwt) Default() {
|
||||
j.ExpireTime = 1440
|
||||
logx.Warnf("未配置jwt.expire-time, 默认值: %d", j.ExpireTime)
|
||||
}
|
||||
if j.RefreshTokenExpireTime == 0 {
|
||||
j.RefreshTokenExpireTime = j.ExpireTime * 5
|
||||
logx.Warnf("未配置jwt.refresh-token-expire-time, 默认值: %d", j.RefreshTokenExpireTime)
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Jwt) Valid() {
|
||||
|
||||
@@ -11,10 +11,11 @@ type BizError struct {
|
||||
}
|
||||
|
||||
var (
|
||||
Success BizError = NewBizCode(200, "success")
|
||||
BizErr BizError = NewBizCode(400, "biz error")
|
||||
ServerError BizError = NewBizCode(500, "server error")
|
||||
PermissionErr BizError = NewBizCode(501, "token error")
|
||||
Success BizError = NewBizCode(200, "success")
|
||||
BizErr BizError = NewBizCode(400, "biz error")
|
||||
ServerError BizError = NewBizCode(500, "server error")
|
||||
PermissionErr BizError = NewBizCode(501, "token error")
|
||||
AccessTokenInvalid BizError = NewBizCode(502, "access token invalid")
|
||||
)
|
||||
|
||||
// 错误消息
|
||||
|
||||
@@ -31,7 +31,8 @@ func (j JsonTime) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (j *JsonTime) UnmarshalJSON(b []byte) error {
|
||||
s := strings.ReplaceAll(string(b), "\"", "")
|
||||
t, err := time.Parse(timex.DefaultDateTimeFormat, s)
|
||||
// t, err := time.Parse(timex.DefaultDateTimeFormat, s)
|
||||
t, err := time.ParseInLocation(timex.DefaultDateTimeFormat, s, time.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func PermissionHandler(rc *Ctx) error {
|
||||
}
|
||||
userId, userName, err := ParseToken(tokenStr)
|
||||
if err != nil || userId == 0 {
|
||||
return errorx.PermissionErr
|
||||
return errorx.AccessTokenInvalid
|
||||
}
|
||||
// 权限不为nil,并且permission code不为空,则校验是否有权限code
|
||||
if permission != nil && permission.Code != "" {
|
||||
|
||||
@@ -9,21 +9,33 @@ import (
|
||||
)
|
||||
|
||||
// 创建用户token
|
||||
func CreateToken(userId uint64, username string) (string, error) {
|
||||
func CreateToken(userId uint64, username string) (accessToken string, refreshToken string, err error) {
|
||||
jwtConf := config.Conf.Jwt
|
||||
now := time.Now()
|
||||
|
||||
// 带权限创建令牌
|
||||
// 设置有效期,过期需要重新登录获取token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
accessJwt := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": userId,
|
||||
"username": username,
|
||||
"exp": time.Now().Add(time.Minute * time.Duration(config.Conf.Jwt.ExpireTime)).Unix(),
|
||||
"exp": now.Add(time.Minute * time.Duration(jwtConf.ExpireTime)).Unix(),
|
||||
})
|
||||
|
||||
// refresh token
|
||||
refreshJwt := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": userId,
|
||||
"username": username,
|
||||
"exp": now.Add(time.Minute * time.Duration(jwtConf.RefreshTokenExpireTime)).Unix(),
|
||||
})
|
||||
|
||||
// 使用自定义字符串加密 and get the complete encoded token as a string
|
||||
tokenString, err := token.SignedString([]byte(config.Conf.Jwt.Key))
|
||||
accessToken, err = accessJwt.SignedString([]byte(jwtConf.Key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return
|
||||
}
|
||||
return tokenString, nil
|
||||
|
||||
refreshToken, err = refreshJwt.SignedString([]byte(jwtConf.Key))
|
||||
return
|
||||
}
|
||||
|
||||
// 解析token,并返回登录者账号信息
|
||||
|
||||
Binary file not shown.
@@ -951,6 +951,8 @@ DROP TABLE IF EXISTS `t_team`;
|
||||
CREATE TABLE `t_team` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(36) NOT NULL COMMENT '名称',
|
||||
`validity_start_date` datetime DEFAULT NULL COMMENT '生效开始时间',
|
||||
`validity_end_date` datetime DEFAULT NULL COMMENT '生效结束时间',
|
||||
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint(20) NOT NULL,
|
||||
@@ -967,7 +969,7 @@ CREATE TABLE `t_team` (
|
||||
-- Records of t_team
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `t_team` VALUES (1, 'default_team', '默认团队', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_team` VALUES (1, 'default_team', '2024-05-01 00:00:00', '2050-05-01 00:00:00', '默认团队', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
|
||||
4
server/resources/script/sql/v1.8/v1.8.4.sql
Normal file
4
server/resources/script/sql/v1.8/v1.8.4.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE t_team ADD validity_start_date DATETIME NULL COMMENT '生效开始时间';
|
||||
ALTER TABLE t_team ADD validity_end_date DATETIME NULL COMMENT '生效结束时间';
|
||||
|
||||
UPDATE t_team SET validity_start_date = NOW(), validity_end_date = '2034-01-01 00:00:00'
|
||||
Reference in New Issue
Block a user