updated

This commit is contained in:
landaiqing
2024-11-14 01:58:20 +08:00
parent c95d5fc041
commit 3b8e3df27a
83 changed files with 172212 additions and 343 deletions

View File

@@ -13,9 +13,8 @@ type Config struct {
}
Redis struct {
Host string
Type string
Pass string
Tls bool
DB int
}
Mongo struct {
Uri string

View File

@@ -0,0 +1,24 @@
package client
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"schisandra-album-cloud-microservices/app/core/api/common/utils"
"schisandra-album-cloud-microservices/app/core/api/internal/logic/client"
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
)
func GenerateClientIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
clientIP := utils.GetClientIP(r)
l := client.NewGenerateClientIdLogic(r.Context(), svcCtx)
resp, err := l.GenerateClientId(clientIP)
if err != nil || resp.Code == 500 {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"time"
client "schisandra-album-cloud-microservices/app/core/api/internal/handler/client"
user "schisandra-album-cloud-microservices/app/core/api/internal/handler/user"
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
@@ -14,6 +15,22 @@ import (
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/generate_client_id",
Handler: client.GenerateClientIdHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/client"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware},

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
package client
import (
"context"
"time"
"github.com/ccpwcn/kgo"
"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/internal/svc"
"schisandra-album-cloud-microservices/app/core/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GenerateClientIdLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGenerateClientIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateClientIdLogic {
return &GenerateClientIdLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GenerateClientIdLogic) GenerateClientId(clientIP string) (resp *types.Response, err error) {
clientId := l.svcCtx.RedisClient.Get(l.ctx, constant.UserClientPrefix+clientIP).Val()
if clientId != "" {
return response.Success(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
}

View File

@@ -2,14 +2,27 @@ package user
import (
"context"
"net/http"
"time"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/mssola/useragent"
"github.com/zeromicro/go-zero/core/logc"
"github.com/zeromicro/go-zero/core/logx"
"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"
"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/mongodb/collection"
"schisandra-album-cloud-microservices/app/core/api/repository/mongodb/model"
"schisandra-album-cloud-microservices/common/i18n"
"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"
"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 {
@@ -26,13 +39,160 @@ func NewAccountLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Acco
}
}
func (l *AccountLoginLogic) AccountLogin(req *types.AccountLoginRequest) (resp *types.LoginResponse, err error) {
// todo: add your logic here and delete this line
i18n.IsHasI18n(l.ctx)
text := i18n.FormatText(l.ctx, "user.name", "landaiqing")
collection.MustNewCollection[model.CommentImage](l.svcCtx, "comment_image")
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
}
var user *ent.ScaAuthUser
var query *ent.ScaAuthUserQuery
return &types.LoginResponse{
AccessToken: text,
}, nil
switch {
case utils.IsPhone(req.Account):
query = l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.PhoneEQ(req.Account), scaauthuser.DeletedEQ(0))
case utils.IsEmail(req.Account):
query = l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.EmailEQ(req.Account), scaauthuser.DeletedEQ(0))
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
}
user, err = query.First(l.ctx)
if err != nil {
if ent.IsNotFound(err) {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.notFoundAccount", "无效账号!")), nil
}
return nil, err
}
if !utils.Verify(user.Password, req.Password) {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.invalidPassword", "密码错误!")), nil
}
data, result := HandleUserLogin(user, l, req.AutoLogin, r, w, l.svcCtx.Ip2Region, l.svcCtx.MySQLClient)
if !result {
return response.ErrorWithMessage(i18n.FormatText(l.ctx, "login.loginFailed", "登录失败!")), nil
}
return response.Success(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) {
// 生成jwt token
accessToken := jwt.GenerateAccessToken(l.svcCtx.Config.Auth.AccessSecret, jwt.AccessJWTPayload{
UserID: user.UID,
})
var days time.Duration
if autoLogin {
days = 7 * 24 * time.Hour
} else {
days = time.Hour * 24
}
refreshToken := jwt.GenerateRefreshToken(l.svcCtx.Config.Auth.AccessSecret, jwt.RefreshJWTPayload{
UserID: user.UID,
}, days)
data := types.LoginResponse{
AccessToken: accessToken,
UID: user.UID,
Username: user.Username,
Nickname: user.Nickname,
Avatar: user.Avatar,
Status: user.Status,
}
redisToken := types2.RedisToken{
AccessToken: accessToken,
UID: user.UID,
}
err := l.svcCtx.RedisClient.SetEx(l.ctx, constant.UserTokenPrefix+user.UID, redisToken, days).Err()
if err != nil {
logc.Error(l.ctx, err)
return nil, false
}
sessionData := types3.SessionData{
RefreshToken: refreshToken,
UID: user.UID,
}
session, err := l.svcCtx.Session.Get(r, constant.SESSION_KEY)
if err != nil {
logc.Error(l.ctx, err)
return nil, false
}
session.Values[constant.SESSION_KEY] = sessionData
if err = session.Save(r, w); err != nil {
logc.Error(l.ctx, err)
return nil, false
}
// 记录用户登录设备
if !getUserLoginDevice(user.UID, r, ip2location, entClient, l.ctx) {
return nil, false
}
return &data, true
}
// getUserLoginDevice 获取用户登录设备
func getUserLoginDevice(userId string, r *http.Request, ip2location *xdb.Searcher, entClient *ent.Client, ctx context.Context) bool {
userAgent := r.Header.Get("User-Agent")
if userAgent == "" {
return false
}
ip := utils.GetClientIP(r)
location, err := ip2location.SearchByStr(ip)
if err != nil {
return false
}
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()
device, err := entClient.ScaAuthUserDevice.Query().
Where(scaauthuserdevice.UserID(userId), scaauthuserdevice.IP(ip), scaauthuserdevice.Agent(userAgent)).
Only(ctx)
// 如果有错误,表示设备不存在,执行插入
if ent.IsNotFound(err) {
// 创建新的设备记录
entClient.ScaAuthUserDevice.Create().
SetBot(isBot).
SetAgent(userAgent).
SetBrowser(browser).
SetBrowserVersion(browserVersion).
SetEngineName(engine).
SetEngineVersion(engineVersion).
SetIP(ip).
SetLocation(location).
SetOperatingSystem(os).
SetMobile(mobile).
SetMozilla(mozilla).
SetPlatform(platform).
SaveX(ctx)
} else if err == nil {
// 如果设备存在,执行更新
device.Update().
SetBot(isBot).
SetAgent(userAgent).
SetBrowser(browser).
SetBrowserVersion(browserVersion).
SetEngineName(engine).
SetEngineVersion(engineVersion).
SetIP(ip).
SetLocation(location).
SetOperatingSystem(os).
SetMobile(mobile).
SetMozilla(mozilla).
SetPlatform(platform).
SaveX(ctx)
} else {
logc.Error(ctx, err)
return false
}
return true
}

View File

@@ -3,10 +3,10 @@ package user
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"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 PhoneLoginLogic struct {
@@ -23,7 +23,7 @@ func NewPhoneLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PhoneL
}
}
func (l *PhoneLoginLogic) PhoneLogin(req *types.PhoneLoginRequest) (resp *types.LoginResponse, err error) {
func (l *PhoneLoginLogic) PhoneLogin(req *types.PhoneLoginRequest) (resp *types.Response, err error) {
// todo: add your logic here and delete this line
return

View File

@@ -3,10 +3,10 @@ package user
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"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 ResetPasswordLogic struct {
@@ -23,7 +23,7 @@ func NewResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Res
}
}
func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (resp string, err error) {
func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (resp *types.Response, err error) {
// todo: add your logic here and delete this line
return

View File

@@ -3,7 +3,7 @@ package middleware
import (
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
"schisandra-album-cloud-microservices/app/core/api/common/middleware"
)
type SecurityHeadersMiddleware struct {

View File

@@ -1,24 +1,31 @@
package svc
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/rbcervilla/redisstore/v9"
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/rest"
"go.mongodb.org/mongo-driver/v2/mongo"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
"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/ip2region"
"schisandra-album-cloud-microservices/app/core/api/repository/mongodb"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql"
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
"schisandra-album-cloud-microservices/app/core/api/repository/redis_session"
"schisandra-album-cloud-microservices/app/core/api/repository/redisx"
)
type ServiceContext struct {
Config config.Config
SecurityHeadersMiddleware rest.Middleware
MySQLClient *ent.Client
RedisClient *redis.Redis
RedisClient *redis.Client
MongoClient *mongo.Database
Session *redisstore.RedisStore
Ip2Region *xdb.Searcher
}
func NewServiceContext(c config.Config) *ServiceContext {
@@ -26,12 +33,9 @@ func NewServiceContext(c config.Config) *ServiceContext {
Config: c,
SecurityHeadersMiddleware: middleware.NewSecurityHeadersMiddleware().Handle,
MySQLClient: mysql.NewMySQL(c.Mysql.DataSource),
RedisClient: redis.MustNewRedis(redis.RedisConf{
Host: c.Redis.Host,
Pass: c.Redis.Pass,
Type: c.Redis.Type,
Tls: c.Redis.Tls,
}),
MongoClient: mongodb.NewMongoDB(c.Mongo.Uri, c.Mongo.Username, c.Mongo.Password, c.Mongo.AuthSource, c.Mongo.Database),
RedisClient: redisx.NewRedis(c.Redis.Host, c.Redis.Pass, c.Redis.DB),
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(),
}
}

View File

@@ -14,10 +14,10 @@ type AccountLoginRequest struct {
type LoginResponse struct {
AccessToken string `json:"access_token"`
UID string `json:"uid"`
Username string `json:"username,optional"`
Username string `json:"username,omitempty"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Status int64 `json:"status"`
Status int8 `json:"status"`
}
type PhoneLoginRequest struct {
@@ -32,3 +32,9 @@ type ResetPasswordRequest struct {
Password string `json:"password"`
Repassword string `json:"repassword"`
}
type Response struct {
Code int64 `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,optional"`
}