refactor: 引入dayjs、新增refreshToken无感刷新、团队新增有效期、数据库等问题修复

This commit is contained in:
meilin.huang
2024-05-13 19:55:43 +08:00
parent 137ebb8e9e
commit 89e12678eb
54 changed files with 500 additions and 460 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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"` // 关联标签信息
}

View File

@@ -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 {

View File

@@ -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"` // 备注说明
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.8.3"
Version = "v1.8.4"
)
func GetAppInfo() string {

View File

@@ -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() {

View File

@@ -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")
)
// 错误消息

View File

@@ -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
}

View File

@@ -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 != "" {

View File

@@ -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并返回登录者账号信息

View File

@@ -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;
-- ----------------------------

View 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'