add casbin ent adapter & code migration

This commit is contained in:
landaiqing
2024-11-14 16:02:11 +08:00
parent 3b8e3df27a
commit b2d753e832
79 changed files with 1856 additions and 6462 deletions

View File

@@ -50,6 +50,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/reset_password",
Handler: user.ResetPasswordHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/token/refresh",
Handler: user.RefreshTokenHandler(serverCtx),
},
}...,
),
rest.WithSignature(serverCtx.Config.Signature),

View File

@@ -19,7 +19,7 @@ func PhoneLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
l := user.NewPhoneLoginLogic(r.Context(), svcCtx)
resp, err := l.PhoneLogin(&req)
resp, err := l.PhoneLogin(r, w, &req)
if err != nil || resp.Code == 500 {
httpx.ErrorCtx(r.Context(), w, err)
} else {

View File

@@ -0,0 +1,22 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"schisandra-album-cloud-microservices/app/core/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
)
func RefreshTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := user.NewRefreshTokenLogic(r.Context(), svcCtx)
resp, err := l.RefreshToken(r)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -32,12 +32,12 @@ func (l *GenerateClientIdLogic) GenerateClientId(clientIP string) (resp *types.R
clientId := l.svcCtx.RedisClient.Get(l.ctx, constant.UserClientPrefix+clientIP).Val()
if clientId != "" {
return response.Success(clientId), nil
return response.SuccessWithData(clientId), nil
}
simpleUuid := kgo.SimpleUuid()
if err = l.svcCtx.RedisClient.SetEx(l.ctx, constant.UserClientPrefix+clientIP, simpleUuid, time.Hour*24*7).Err(); err != nil {
l.Error(err)
return response.Error(), err
}
return response.Success(simpleUuid), nil
return response.SuccessWithData(simpleUuid), nil
}

View File

@@ -12,7 +12,6 @@ import (
"schisandra-album-cloud-microservices/app/core/api/common/captcha/verify"
"schisandra-album-cloud-microservices/app/core/api/common/constant"
"schisandra-album-cloud-microservices/app/core/api/common/i18n"
"schisandra-album-cloud-microservices/app/core/api/common/jwt"
"schisandra-album-cloud-microservices/app/core/api/common/response"
"schisandra-album-cloud-microservices/app/core/api/common/utils"
@@ -21,8 +20,6 @@ import (
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuserdevice"
types3 "schisandra-album-cloud-microservices/app/core/api/repository/redis_session/types"
types2 "schisandra-album-cloud-microservices/app/core/api/repository/redisx/types"
)
type AccountLoginLogic struct {
@@ -42,7 +39,7 @@ func NewAccountLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Acco
func (l *AccountLoginLogic) AccountLogin(w http.ResponseWriter, r *http.Request, req *types.AccountLoginRequest) (resp *types.Response, err error) {
verifyResult := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
if !verifyResult {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "captcha.verificationFailure", "验证失败!")), nil
return response.ErrorWithI18n(l.ctx, "captcha.verificationFailure", "验证失败!"), nil
}
var user *ent.ScaAuthUser
var query *ent.ScaAuthUserQuery
@@ -55,31 +52,31 @@ func (l *AccountLoginLogic) AccountLogin(w http.ResponseWriter, r *http.Request,
case utils.IsUsername(req.Account):
query = l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.UsernameEQ(req.Account), scaauthuser.DeletedEQ(0))
default:
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.invalidAccount", "无效账号!")), nil
return response.ErrorWithI18n(l.ctx, "login.invalidAccount", "无效账号!"), nil
}
user, err = query.First(l.ctx)
if err != nil {
if ent.IsNotFound(err) {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.notFoundAccount", "无效账号!")), nil
return response.ErrorWithI18n(l.ctx, "login.userNotRegistered", "用户未注册!"), nil
}
return nil, err
}
if !utils.Verify(user.Password, req.Password) {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.invalidPassword", "密码错误!")), nil
return response.ErrorWithI18n(l.ctx, "login.invalidPassword", "密码错误!"), nil
}
data, result := HandleUserLogin(user, l, req.AutoLogin, r, w, l.svcCtx.Ip2Region, l.svcCtx.MySQLClient)
data, result := HandleUserLogin(user, l.svcCtx, req.AutoLogin, r, w, l.ctx)
if !result {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.loginFailed", "登录失败!")), nil
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败!"), nil
}
return response.Success(data), nil
return response.SuccessWithData(data), nil
}
// HandleUserLogin 处理用户登录
func HandleUserLogin(user *ent.ScaAuthUser, l *AccountLoginLogic, autoLogin bool, r *http.Request, w http.ResponseWriter, ip2location *xdb.Searcher, entClient *ent.Client) (*types.LoginResponse, bool) {
func HandleUserLogin(user *ent.ScaAuthUser, svcCtx *svc.ServiceContext, autoLogin bool, r *http.Request, w http.ResponseWriter, ctx context.Context) (*types.LoginResponse, bool) {
// 生成jwt token
accessToken := jwt.GenerateAccessToken(l.svcCtx.Config.Auth.AccessSecret, jwt.AccessJWTPayload{
accessToken := jwt.GenerateAccessToken(svcCtx.Config.Auth.AccessSecret, jwt.AccessJWTPayload{
UserID: user.UID,
})
var days time.Duration
@@ -88,7 +85,7 @@ func HandleUserLogin(user *ent.ScaAuthUser, l *AccountLoginLogic, autoLogin bool
} else {
days = time.Hour * 24
}
refreshToken := jwt.GenerateRefreshToken(l.svcCtx.Config.Auth.AccessSecret, jwt.RefreshJWTPayload{
refreshToken := jwt.GenerateRefreshToken(svcCtx.Config.Auth.AccessSecret, jwt.RefreshJWTPayload{
UserID: user.UID,
}, days)
data := types.LoginResponse{
@@ -100,31 +97,31 @@ func HandleUserLogin(user *ent.ScaAuthUser, l *AccountLoginLogic, autoLogin bool
Status: user.Status,
}
redisToken := types2.RedisToken{
redisToken := types.RedisToken{
AccessToken: accessToken,
UID: user.UID,
}
err := l.svcCtx.RedisClient.SetEx(l.ctx, constant.UserTokenPrefix+user.UID, redisToken, days).Err()
err := svcCtx.RedisClient.Set(ctx, constant.UserTokenPrefix+user.UID, redisToken, days).Err()
if err != nil {
logc.Error(l.ctx, err)
logc.Error(ctx, err)
return nil, false
}
sessionData := types3.SessionData{
sessionData := types.SessionData{
RefreshToken: refreshToken,
UID: user.UID,
}
session, err := l.svcCtx.Session.Get(r, constant.SESSION_KEY)
session, err := svcCtx.Session.Get(r, constant.SESSION_KEY)
if err != nil {
logc.Error(l.ctx, err)
logc.Error(ctx, err)
return nil, false
}
session.Values[constant.SESSION_KEY] = sessionData
if err = session.Save(r, w); err != nil {
logc.Error(l.ctx, err)
logc.Error(ctx, err)
return nil, false
}
// 记录用户登录设备
if !getUserLoginDevice(user.UID, r, ip2location, entClient, l.ctx) {
if !getUserLoginDevice(user.UID, r, svcCtx.Ip2Region, svcCtx.MySQLClient, ctx) {
return nil, false
}
return &data, true
@@ -160,7 +157,7 @@ func getUserLoginDevice(userId string, r *http.Request, ip2location *xdb.Searche
// 如果有错误,表示设备不存在,执行插入
if ent.IsNotFound(err) {
// 创建新的设备记录
entClient.ScaAuthUserDevice.Create().
err = entClient.ScaAuthUserDevice.Create().
SetBot(isBot).
SetAgent(userAgent).
SetBrowser(browser).
@@ -173,10 +170,14 @@ func getUserLoginDevice(userId string, r *http.Request, ip2location *xdb.Searche
SetMobile(mobile).
SetMozilla(mozilla).
SetPlatform(platform).
SaveX(ctx)
Exec(ctx)
if err != nil {
return false
}
return true
} else if err == nil {
// 如果设备存在,执行更新
device.Update().
err = device.Update().
SetBot(isBot).
SetAgent(userAgent).
SetBrowser(browser).
@@ -189,10 +190,13 @@ func getUserLoginDevice(userId string, r *http.Request, ip2location *xdb.Searche
SetMobile(mobile).
SetMozilla(mozilla).
SetPlatform(platform).
SaveX(ctx)
Exec(ctx)
if err != nil {
return false
}
return true
} else {
logc.Error(ctx, err)
return false
}
return true
}

View File

@@ -2,11 +2,20 @@ package user
import (
"context"
"net/http"
"strconv"
"github.com/yitter/idgenerator-go/idgen"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/core/api/common/constant"
randomname "schisandra-album-cloud-microservices/app/core/api/common/random_name"
"schisandra-album-cloud-microservices/app/core/api/common/response"
"schisandra-album-cloud-microservices/app/core/api/common/utils"
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
"schisandra-album-cloud-microservices/app/core/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
)
type PhoneLoginLogic struct {
@@ -23,8 +32,56 @@ func NewPhoneLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PhoneL
}
}
func (l *PhoneLoginLogic) PhoneLogin(req *types.PhoneLoginRequest) (resp *types.Response, err error) {
// todo: add your logic here and delete this line
func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, w http.ResponseWriter, req *types.PhoneLoginRequest) (resp *types.Response, err error) {
if !utils.IsPhone(req.Phone) {
return response.ErrorWithI18n(l.ctx, "login.phoneFormatError", "手机号格式错误"), nil
}
code := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
if code == "" {
return response.ErrorWithI18n(l.ctx, "login.captchaExpired", "验证码已过期"), nil
}
if req.Captcha != code {
return response.ErrorWithI18n(l.ctx, "login.captchaError", "验证码错误"), nil
}
user, err := l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.Phone(req.Phone), scaauthuser.Deleted(0)).Only(l.ctx)
tx, wrong := l.svcCtx.MySQLClient.Tx(l.ctx)
if wrong != nil {
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败"), err
}
if ent.IsNotFound(err) {
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
avatar := utils.GenerateAvatar(uidStr)
name := randomname.GenerateName()
return
addUser, wrong := l.svcCtx.MySQLClient.ScaAuthUser.Create().
SetUID(uidStr).
SetPhone(req.Phone).
SetAvatar(avatar).
SetNickname(name).
SetDeleted(constant.NotDeleted).
SetGender(constant.Male).
Save(l.ctx)
if wrong != nil {
err = tx.Rollback()
return response.ErrorWithI18n(l.ctx, "login.registerError", "注册失败"), err
}
data, result := HandleUserLogin(addUser, l.svcCtx, req.AutoLogin, r, w, l.ctx)
if !result {
err = tx.Rollback()
return response.ErrorWithI18n(l.ctx, "login.registerError", "注册失败"), err
}
err = tx.Commit()
return response.SuccessWithData(data), err
} else if err != nil {
data, result := HandleUserLogin(user, l.svcCtx, req.AutoLogin, r, w, l.ctx)
if !result {
err = tx.Rollback()
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败"), err
}
err = tx.Commit()
return response.SuccessWithData(data), err
} else {
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败"), nil
}
}

View File

@@ -0,0 +1,66 @@
package user
import (
"context"
"encoding/json"
"net/http"
"time"
"schisandra-album-cloud-microservices/app/core/api/common/constant"
"schisandra-album-cloud-microservices/app/core/api/common/jwt"
"schisandra-album-cloud-microservices/app/core/api/common/response"
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
"schisandra-album-cloud-microservices/app/core/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type RefreshTokenLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshTokenLogic {
return &RefreshTokenLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *RefreshTokenLogic) RefreshToken(r *http.Request) (resp *types.Response, err error) {
session, err := l.svcCtx.Session.Get(r, constant.SESSION_KEY)
if err != nil {
return response.ErrorWithCode(403), err
}
sessionData, ok := session.Values[constant.SESSION_KEY]
if !ok {
return response.ErrorWithCode(403), err
}
data := types.SessionData{}
err = json.Unmarshal(sessionData.([]byte), &data)
if err != nil {
return response.ErrorWithCode(403), err
}
refreshToken, result := jwt.ParseRefreshToken(l.svcCtx.Config.Auth.AccessSecret, data.RefreshToken)
if !result {
return response.ErrorWithCode(403), err
}
accessToken := jwt.GenerateAccessToken(l.svcCtx.Config.Auth.AccessSecret, jwt.AccessJWTPayload{
UserID: refreshToken.UserID,
})
if accessToken == "" {
return response.ErrorWithCode(403), err
}
redisToken := types.RedisToken{
AccessToken: accessToken,
UID: refreshToken.UserID,
}
err = l.svcCtx.RedisClient.Set(l.ctx, constant.UserTokenPrefix+refreshToken.UserID, redisToken, time.Hour*24*7).Err()
if err != nil {
return response.ErrorWithCode(403), err
}
return response.SuccessWithData(accessToken), nil
}

View File

@@ -3,8 +3,13 @@ package user
import (
"context"
"schisandra-album-cloud-microservices/app/core/api/common/constant"
"schisandra-album-cloud-microservices/app/core/api/common/response"
"schisandra-album-cloud-microservices/app/core/api/common/utils"
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
"schisandra-album-cloud-microservices/app/core/api/internal/types"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
"github.com/zeromicro/go-zero/core/logx"
)
@@ -24,7 +29,37 @@ func NewResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Res
}
func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (resp *types.Response, err error) {
// todo: add your logic here and delete this line
return
if !utils.IsPhone(req.Phone) {
return response.ErrorWithI18n(l.ctx, "login.phoneFormatError", "手机号格式错误"), nil
}
if req.Password != req.Repassword {
return response.ErrorWithI18n(l.ctx, "login.passwordNotMatch", "两次密码输入不一致"), nil
}
if !utils.IsPassword(req.Password) {
return response.ErrorWithI18n(l.ctx, "login.passwordFormatError", "密码格式错误"), nil
}
code := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
if code == "" {
return response.ErrorWithI18n(l.ctx, "login.captchaExpired", "验证码已过期"), nil
}
if req.Captcha != code {
return response.ErrorWithI18n(l.ctx, "login.captchaError", "验证码错误"), nil
}
// 验证码检查通过后立即删除或标记为已使用
if err = l.svcCtx.RedisClient.Del(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Err(); err != nil {
return response.ErrorWithI18n(l.ctx, "login.captchaError", "验证码错误"), nil
}
user, err := l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.Phone(req.Phone), scaauthuser.Deleted(constant.NotDeleted)).First(l.ctx)
if err != nil && ent.IsNotFound(err) {
return response.ErrorWithI18n(l.ctx, "login.userNotRegistered", "用户未注册"), nil
}
encrypt, err := utils.Encrypt(req.Password)
if err != nil {
return response.ErrorWithI18n(l.ctx, "login.resetPasswordError", "重置密码失败"), nil
}
err = user.Update().SetPassword(encrypt).Exec(l.ctx)
if err != nil {
return response.ErrorWithI18n(l.ctx, "login.resetPasswordError", "重置密码失败"), err
}
return response.Success(), nil
}

View File

@@ -1,6 +1,7 @@
package svc
import (
"github.com/casbin/casbin/v2"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/rbcervilla/redisstore/v9"
"github.com/redis/go-redis/v9"
@@ -10,6 +11,7 @@ import (
"schisandra-album-cloud-microservices/app/core/api/internal/config"
"schisandra-album-cloud-microservices/app/core/api/internal/middleware"
"schisandra-album-cloud-microservices/app/core/api/repository/casbinx"
"schisandra-album-cloud-microservices/app/core/api/repository/ip2region"
"schisandra-album-cloud-microservices/app/core/api/repository/mongodb"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql"
@@ -26,6 +28,7 @@ type ServiceContext struct {
MongoClient *mongo.Database
Session *redisstore.RedisStore
Ip2Region *xdb.Searcher
CasbinEnforcer *casbin.CachedEnforcer
}
func NewServiceContext(c config.Config) *ServiceContext {
@@ -37,5 +40,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
MongoClient: mongodb.NewMongoDB(c.Mongo.Uri, c.Mongo.Username, c.Mongo.Password, c.Mongo.AuthSource, c.Mongo.Database),
Session: redis_session.NewRedisSession(c.Redis.Host, c.Redis.Pass),
Ip2Region: ip2region.NewIP2Region(),
CasbinEnforcer: casbinx.NewCasbin(c.Mysql.DataSource),
}
}

View File

@@ -0,0 +1,16 @@
package types
import "encoding/json"
type RedisToken struct {
AccessToken string `json:"access_token"`
UID string `json:"uid"`
}
func (res RedisToken) MarshalBinary() ([]byte, error) {
return json.Marshal(res)
}
func (res RedisToken) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, &res)
}

View File

@@ -0,0 +1,17 @@
package types
import "encoding/json"
// SessionData 返回数据
type SessionData struct {
RefreshToken string `json:"refresh_token"`
UID string `json:"uid"`
}
func (res SessionData) MarshalBinary() ([]byte, error) {
return json.Marshal(res)
}
func (res SessionData) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, &res)
}