🏗️ microservice fabric splitting

This commit is contained in:
2024-12-24 00:38:41 +08:00
parent 462e811742
commit 89d64336f5
311 changed files with 18384 additions and 2428 deletions

View File

@@ -0,0 +1,189 @@
package user
import (
"context"
errors2 "errors"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/mssola/useragent"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/model"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/query"
"schisandra-album-cloud-microservices/common/captcha/verify"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/errors"
"schisandra-album-cloud-microservices/common/i18n"
"schisandra-album-cloud-microservices/common/jwt"
"schisandra-album-cloud-microservices/common/utils"
"time"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type AccountLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAccountLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AccountLoginLogic {
return &AccountLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AccountLoginLogic) AccountLogin(r *http.Request, req *types.AccountLoginRequest) (resp *types.LoginResponse, err error) {
verifyResult := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
if !verifyResult {
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "captcha.verificationFailure"))
}
user := l.svcCtx.DB.ScaAuthUser
var selectedUser query.IScaAuthUserDo
switch {
case utils.IsPhone(req.Account):
selectedUser = user.Where(user.Phone.Eq(req.Account))
case utils.IsEmail(req.Account):
selectedUser = user.Where(user.Email.Eq(req.Account))
case utils.IsUsername(req.Account):
selectedUser = user.Where(user.Username.Eq(req.Account))
default:
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.invalidAccount"))
}
userInfo, err := selectedUser.First()
if err != nil {
if errors2.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.notFoundAccount"))
}
return nil, err
}
if !utils.Verify(userInfo.Password, req.Password) {
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.invalidPassword"))
}
data, err := HandleLoginJWT(userInfo, l.svcCtx, req.AutoLogin, r, l.ctx)
if err != nil {
return nil, err
}
return data, nil
}
// HandleLoginJWT 处理用户登录
func HandleLoginJWT(user *model.ScaAuthUser, svcCtx *svc.ServiceContext, autoLogin bool, r *http.Request, ctx context.Context) (*types.LoginResponse, error) {
// 获取用户登录设备
err := GetUserLoginDevice(user.UID, r, svcCtx.Ip2Region, svcCtx.DB)
if err != nil {
return nil, err
}
// 生成jwt token
accessToken, expireAt := jwt.GenerateAccessToken(svcCtx.Config.Auth.AccessSecret, jwt.AccessJWTPayload{
UserID: user.UID,
Type: constant.JWT_TYPE_ACCESS,
})
var days time.Duration
if autoLogin {
days = 3 * 24 * time.Hour
} else {
days = time.Hour * 24
}
refreshToken := jwt.GenerateRefreshToken(svcCtx.Config.Auth.AccessSecret, jwt.RefreshJWTPayload{
UserID: user.UID,
Type: constant.JWT_TYPE_REFRESH,
}, days)
data := types.LoginResponse{
AccessToken: accessToken,
ExpireAt: expireAt,
UID: user.UID,
Username: user.Username,
Nickname: user.Nickname,
Avatar: user.Avatar,
Status: user.Status,
}
redisToken := types.RedisToken{
AccessToken: accessToken,
RefreshToken: refreshToken,
UID: user.UID,
Revoked: false,
GeneratedAt: time.Now().Format(constant.TimeFormat),
AllowAgent: r.UserAgent(),
GeneratedIP: utils.GetClientIP(r),
UpdatedAt: time.Now().Format(constant.TimeFormat),
}
err = svcCtx.RedisClient.Set(ctx, constant.UserTokenPrefix+user.UID, redisToken, days).Err()
if err != nil {
return nil, err
}
return &data, nil
}
// GetUserLoginDevice 获取用户登录设备
func GetUserLoginDevice(userId string, r *http.Request, ip2location *xdb.Searcher, DB *query.Query) error {
userAgent := r.UserAgent()
if userAgent == "" {
return errors2.New("user agent not found")
}
ip := utils.GetClientIP(r)
location, err := ip2location.SearchByStr(ip)
if err != nil {
return err
}
location = utils.RemoveZeroAndAdjust(location)
ua := useragent.New(userAgent)
isBot := ua.Bot()
browser, browserVersion := ua.Browser()
os := ua.OS()
mobile := ua.Mobile()
mozilla := ua.Mozilla()
platform := ua.Platform()
engine, engineVersion := ua.Engine()
var newIsBot int64 = 0
var newIsMobile int64 = 0
if isBot {
newIsBot = 1
}
if mobile {
newIsMobile = 1
}
userDevice := DB.ScaAuthUserDevice
device, err := userDevice.Where(userDevice.UserID.Eq(userId), userDevice.IP.Eq(ip), userDevice.Agent.Eq(userAgent)).First()
if err != nil && !errors2.Is(err, gorm.ErrRecordNotFound) {
return err
}
newDevice := &model.ScaAuthUserDevice{
UserID: userId,
Bot: newIsBot,
Agent: userAgent,
Browser: browser,
BrowserVersion: browserVersion,
EngineName: engine,
EngineVersion: engineVersion,
IP: ip,
Location: location,
OperatingSystem: os,
Mobile: newIsMobile,
Mozilla: mozilla,
Platform: platform,
}
if device == nil {
// 创建新的设备记录
err = DB.ScaAuthUserDevice.Create(newDevice)
if err != nil {
return err
}
return nil
} else {
resultInfo, err := userDevice.Where(userDevice.ID.Eq(device.ID)).Updates(newDevice)
if err != nil || resultInfo.RowsAffected == 0 {
return errors2.New("update device failed")
}
return nil
}
}

View File

@@ -0,0 +1,62 @@
package user
import (
"context"
"encoding/json"
"github.com/ArtisanCloud/PowerWeChat/v3/src/basicService/qrCode/response"
"net/http"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/utils"
"time"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetWechatOffiaccountQrcodeLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetWechatOffiaccountQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWechatOffiaccountQrcodeLogic {
return &GetWechatOffiaccountQrcodeLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetWechatOffiaccountQrcodeLogic) GetWechatOffiaccountQrcode(r *http.Request, req *types.OAuthWechatRequest) (resp string, err error) {
ip := utils.GetClientIP(r)
key := constant.UserQrcodePrefix + ip
// 从Redis获取二维码数据
qrcode := l.svcCtx.RedisClient.Get(l.ctx, key).Val()
if qrcode != "" {
data := new(response.ResponseQRCodeCreate)
if err = json.Unmarshal([]byte(qrcode), data); err != nil {
return "", err
}
return data.Url, nil
}
// 生成临时二维码
data, err := l.svcCtx.WechatOfficial.QRCode.Temporary(l.ctx, req.ClientId, 7*24*3600)
if err != nil {
return "", err
}
// 序列化数据并存储到Redis
serializedData, err := json.Marshal(data)
if err != nil {
return "", err
}
if err = l.svcCtx.RedisClient.Set(l.ctx, key, serializedData, time.Hour*24*7).Err(); err != nil {
return "", err
}
return data.Url, nil
}

View File

@@ -0,0 +1,105 @@
package user
import (
"context"
"errors"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/model"
constant2 "schisandra-album-cloud-microservices/common/constant"
errors2 "schisandra-album-cloud-microservices/common/errors"
"schisandra-album-cloud-microservices/common/i18n"
"schisandra-album-cloud-microservices/common/random_name"
utils2 "schisandra-album-cloud-microservices/common/utils"
"strconv"
"github.com/yitter/idgenerator-go/idgen"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type PhoneLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewPhoneLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PhoneLoginLogic {
return &PhoneLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, req *types.PhoneLoginRequest) (resp *types.LoginResponse, err error) {
if !utils2.IsPhone(req.Phone) {
return nil, errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "captcha.verificationFailure"))
}
code := l.svcCtx.RedisClient.Get(l.ctx, constant2.UserSmsRedisPrefix+req.Phone).Val()
if code == "" {
return nil, errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.captchaExpired"))
}
if req.Captcha != code {
return nil, errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.captchaError"))
}
authUser := l.svcCtx.DB.ScaAuthUser
userInfo, err := authUser.Where(authUser.Phone.Eq(req.Phone)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
tx := l.svcCtx.DB.Begin()
defer func() {
if recover() != nil || err != nil {
_ = tx.Rollback()
}
}()
if userInfo == nil {
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
avatar := utils2.GenerateAvatar(uidStr)
name := randomname.GenerateName()
male := constant2.Male
user := &model.ScaAuthUser{
UID: uidStr,
Phone: req.Phone,
Avatar: avatar,
Nickname: name,
Gender: male,
}
err = tx.ScaAuthUser.Create(user)
if err != nil {
_ = tx.Rollback()
return nil, err
}
_, err = l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant2.User)
if err != nil {
_ = tx.Rollback()
return nil, err
}
data, err := HandleLoginJWT(user, l.svcCtx, req.AutoLogin, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return data, nil
} else {
data, err := HandleLoginJWT(userInfo, l.svcCtx, req.AutoLogin, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return data, nil
}
}

View File

@@ -0,0 +1,76 @@
package user
import (
"context"
"errors"
"net/http"
"schisandra-album-cloud-microservices/common/constant"
errors2 "schisandra-album-cloud-microservices/common/errors"
"schisandra-album-cloud-microservices/common/i18n"
utils2 "schisandra-album-cloud-microservices/common/utils"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type ResetPasswordLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetPasswordLogic {
return &ResetPasswordLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (err error) {
if !utils2.IsPhone(req.Phone) {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.phoneFormatError"))
}
if req.Password != req.Repassword {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.passwordNotMatch"))
}
if !utils2.IsPassword(req.Password) {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.passwordFormatError"))
}
code := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
if code == "" {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.captchaExpired"))
}
if req.Captcha != code {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.captchaError"))
}
// 验证码检查通过后立即删除或标记为已使用
if err = l.svcCtx.RedisClient.Del(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Err(); err != nil {
return errors2.New(http.StatusInternalServerError, err.Error())
}
authUser := l.svcCtx.DB.ScaAuthUser
userInfo, err := authUser.Where(authUser.Phone.Eq(req.Phone)).First()
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.userNotRegistered"))
}
if err != nil {
return errors2.New(http.StatusInternalServerError, err.Error())
}
encrypt, err := utils2.Encrypt(req.Password)
if err != nil {
return errors2.New(http.StatusInternalServerError, err.Error())
}
affected, err := authUser.Where(authUser.ID.Eq(userInfo.ID), authUser.Phone.Eq(req.Phone)).Update(authUser.Password, encrypt)
if err != nil {
return errors2.New(http.StatusInternalServerError, err.Error())
}
if affected.RowsAffected == 0 {
return errors2.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.resetPasswordError"))
}
return nil
}

View File

@@ -0,0 +1,121 @@
package user
import (
"context"
"errors"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/encrypt"
errors2 "schisandra-album-cloud-microservices/common/errors"
"schisandra-album-cloud-microservices/common/i18n"
"schisandra-album-cloud-microservices/common/random_name"
"schisandra-album-cloud-microservices/common/utils"
"strconv"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type WechatOffiaccountLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewWechatOffiaccountLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatOffiaccountLoginLogic {
return &WechatOffiaccountLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *WechatOffiaccountLoginLogic) WechatOffiaccountLogin(r *http.Request, req *types.WechatOffiaccountLoginRequest) (resp *types.LoginResponse, err error) {
decryptedClientId, err := encrypt.Decrypt(req.ClientId, l.svcCtx.Config.Encrypt.Key, l.svcCtx.Config.Encrypt.IV)
if err != nil {
return nil, err
}
clientId := l.svcCtx.RedisClient.Get(r.Context(), constant.UserClientPrefix+decryptedClientId).Val()
if clientId == "" {
return nil, errors2.New(http.StatusUnauthorized, i18n.FormatText(l.ctx, "login.loginFailed"))
}
Openid, err := encrypt.Decrypt(req.Openid, l.svcCtx.Config.Encrypt.Key, l.svcCtx.Config.Encrypt.IV)
if err != nil {
return nil, err
}
tx := l.svcCtx.DB.Begin()
userSocial := l.svcCtx.DB.ScaAuthUserSocial
socialUser, err := tx.ScaAuthUserSocial.Where(userSocial.OpenID.Eq(Openid), userSocial.Source.Eq(constant.OAuthSourceWechat)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
if socialUser == nil {
// 创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
avatar := utils.GenerateAvatar(uidStr)
name := randomname.GenerateName()
addUser := &model.ScaAuthUser{
UID: uidStr,
Avatar: avatar,
Username: Openid,
Nickname: name,
Gender: constant.Male,
}
err = tx.ScaAuthUser.Create(addUser)
if err != nil {
_ = tx.Rollback()
return nil, err
}
newSocialUser := &model.ScaAuthUserSocial{
UserID: uidStr,
OpenID: Openid,
Source: constant.OAuthSourceWechat,
}
err = tx.ScaAuthUserSocial.Create(newSocialUser)
if err != nil {
_ = tx.Rollback()
return nil, err
}
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
_ = tx.Rollback()
return nil, err
}
data, err := HandleLoginJWT(addUser, l.svcCtx, true, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, err
}
return data, nil
} else {
authUser := l.svcCtx.DB.ScaAuthUser
authUserInfo, err := tx.ScaAuthUser.Where(authUser.UID.Eq(socialUser.UserID)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
_ = tx.Rollback()
return nil, err
}
data, err := HandleLoginJWT(authUserInfo, l.svcCtx, true, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, err
}
return data, nil
}
}