🏗️ 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,63 @@
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
Web struct {
URL string
}
Auth struct {
AccessSecret string
}
Encrypt struct {
Key string
IV string
}
Mysql struct {
DataSource string
MaxOpenConn int
MaxIdleConn int
}
Redis struct {
Host string
Pass string
DB int
}
Wechat struct {
AppID string
AppSecret string
Token string
AESKey string
}
OAuth struct {
Github struct {
ClientID string
ClientSecret string
RedirectURI string
}
QQ struct {
ClientID string
ClientSecret string
RedirectURI string
}
Gitee struct {
ClientID string
ClientSecret string
RedirectURI string
}
}
SMS struct {
Ali struct {
Host string
AccessKeyId string
AccessKeySecret string
Signature string
TemplateCode string
}
SMSBao struct {
Username string
Password string
}
}
}

View File

@@ -0,0 +1,21 @@
package captcha
import (
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/captcha"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
func GenerateRotateCaptchaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := captcha.NewGenerateRotateCaptchaLogic(r.Context(), svcCtx)
resp, err := l.GenerateRotateCaptcha()
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,21 @@
package captcha
import (
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/captcha"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
func GenerateSlideBasicCaptchaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := captcha.NewGenerateSlideBasicCaptchaLogic(r.Context(), svcCtx)
resp, err := l.GenerateSlideBasicCaptcha()
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,23 @@
package client
import (
"net/http"
"schisandra-album-cloud-microservices/common/utils"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/client"
"schisandra-album-cloud-microservices/app/auth/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 {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,21 @@
package oauth
import (
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
func GetGiteeOauthUrlHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := oauth.NewGetGiteeOauthUrlLogic(r.Context(), svcCtx)
resp, err := l.GetGiteeOauthUrl()
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,29 @@
package oauth
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func GetGithubOauthUrlHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OAuthRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := oauth.NewGetGithubOauthUrlLogic(r.Context(), svcCtx)
resp, err := l.GetGithubOauthUrl(&req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,29 @@
package oauth
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func GetQqOauthUrlHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OAuthRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := oauth.NewGetQqOauthUrlLogic(r.Context(), svcCtx)
resp, err := l.GetQqOauthUrl(&req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,29 @@
package oauth
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func GiteeCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OAuthCallbackRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := oauth.NewGiteeCallbackLogic(r.Context(), svcCtx)
data, err := l.GiteeCallback(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.OkHTML(w, data)
}
}
}

View File

@@ -0,0 +1,28 @@
package oauth
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"schisandra-album-cloud-microservices/common/xhttp"
)
func GithubCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OAuthCallbackRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := oauth.NewGithubCallbackLogic(r.Context(), svcCtx)
data, err := l.GithubCallback(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.OkHTML(w, data)
}
}
}

View File

@@ -0,0 +1,28 @@
package oauth
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"schisandra-album-cloud-microservices/common/xhttp"
)
func QqCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OAuthCallbackRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := oauth.NewQqCallbackLogic(r.Context(), svcCtx)
data, err := l.QqCallback(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.OkHTML(w, data)
}
}
}

View File

@@ -0,0 +1,21 @@
package oauth
import (
"github.com/ArtisanCloud/PowerLibs/v3/http/helper"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/common/xhttp"
)
func WechatOffiaccountCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := oauth.NewWechatOffiaccountCallbackLogic(r.Context(), svcCtx)
res, err := l.WechatOffiaccountCallback(r)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
_ = helper.HttpResponseSend(res, w)
}
}
}

View File

@@ -0,0 +1,21 @@
package oauth
import (
"github.com/ArtisanCloud/PowerLibs/v3/http/helper"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/oauth"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/common/xhttp"
)
func WechatOffiaccountCallbackVerifyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := oauth.NewWechatOffiaccountCallbackVerifyLogic(r.Context(), svcCtx)
res, err := l.WechatOffiaccountCallbackVerify(r)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
_ = helper.HttpResponseSend(res, w)
}
}
}

View File

@@ -0,0 +1,206 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
package handler
import (
"net/http"
"time"
captcha "schisandra-album-cloud-microservices/app/auth/api/internal/handler/captcha"
client "schisandra-album-cloud-microservices/app/auth/api/internal/handler/client"
oauth "schisandra-album-cloud-microservices/app/auth/api/internal/handler/oauth"
sms "schisandra-album-cloud-microservices/app/auth/api/internal/handler/sms"
token "schisandra-album-cloud-microservices/app/auth/api/internal/handler/token"
user "schisandra-album-cloud-microservices/app/auth/api/internal/handler/user"
websocket "schisandra-album-cloud-microservices/app/auth/api/internal/handler/websocket"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/rotate/generate",
Handler: captcha.GenerateRotateCaptchaHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/slide/generate",
Handler: captcha.GenerateSlideBasicCaptchaHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/captcha"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/generate",
Handler: client.GenerateClientIdHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/client"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/gitee/callback",
Handler: oauth.GiteeCallbackHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/gitee/url",
Handler: oauth.GetGiteeOauthUrlHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/github/callback",
Handler: oauth.GithubCallbackHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/github/url",
Handler: oauth.GetGithubOauthUrlHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/qq/callback",
Handler: oauth.QqCallbackHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/qq/url",
Handler: oauth.GetQqOauthUrlHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/wechat/offiaccount/callback",
Handler: oauth.WechatOffiaccountCallbackHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/wechat/offiaccount/callback",
Handler: oauth.WechatOffiaccountCallbackVerifyHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/oauth"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/ali/send",
Handler: sms.SendSmsByAliyunHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/smsbao/send",
Handler: sms.SendSmsBySmsbaoHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/test/send",
Handler: sms.SendSmsByTestHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/sms"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.CasbinVerifyMiddleware, serverCtx.NonceMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/token/refresh",
Handler: token.RefreshTokenHandler(serverCtx),
},
}...,
),
rest.WithSignature(serverCtx.Config.Signature),
rest.WithPrefix("/api/auth"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/login",
Handler: user.AccountLoginHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/phone/login",
Handler: user.PhoneLoginHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/reset/password",
Handler: user.ResetPasswordHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/wechat/offiaccount/login",
Handler: user.WechatOffiaccountLoginHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/wechat/offiaccount/qrcode",
Handler: user.GetWechatOffiaccountQrcodeHandler(serverCtx),
},
}...,
),
rest.WithSignature(serverCtx.Config.Signature),
rest.WithPrefix("/api/user"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(1048576),
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/message",
Handler: websocket.MessageWebsocketHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/qrcode",
Handler: websocket.QrcodeWebsocketHandler(serverCtx),
},
},
rest.WithPrefix("/api/ws"),
)
}

View File

@@ -0,0 +1,25 @@
package sms
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/sms"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func SendSmsByAliyunHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SmsSendRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := sms.NewSendSmsByAliyunLogic(r.Context(), svcCtx)
err := l.SendSmsByAliyun(&req)
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
}
}

View File

@@ -0,0 +1,25 @@
package sms
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/sms"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func SendSmsBySmsbaoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SmsSendRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := sms.NewSendSmsBySmsbaoLogic(r.Context(), svcCtx)
err := l.SendSmsBySmsbao(&req)
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
}
}

View File

@@ -0,0 +1,25 @@
package sms
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/sms"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func SendSmsByTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SmsSendRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := sms.NewSendSmsByTestLogic(r.Context(), svcCtx)
err := l.SendSmsByTest(&req)
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
}
}

View File

@@ -0,0 +1,21 @@
package token
import (
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/token"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
func RefreshTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := token.NewRefreshTokenLogic(r.Context(), svcCtx)
resp, err := l.RefreshToken(r)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,29 @@
package user
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func AccountLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AccountLoginRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := user.NewAccountLoginLogic(r.Context(), svcCtx)
resp, err := l.AccountLogin(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,28 @@
package user
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"schisandra-album-cloud-microservices/common/xhttp"
)
func GetWechatOffiaccountQrcodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OAuthWechatRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := user.NewGetWechatOffiaccountQrcodeLogic(r.Context(), svcCtx)
resp, err := l.GetWechatOffiaccountQrcode(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,29 @@
package user
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func PhoneLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PhoneLoginRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := user.NewPhoneLoginLogic(r.Context(), svcCtx)
resp, err := l.PhoneLogin(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,24 @@
package user
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
func ResetPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ResetPasswordRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := user.NewResetPasswordLogic(r.Context(), svcCtx)
err := l.ResetPassword(&req)
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
}
}

View File

@@ -0,0 +1,28 @@
package user
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"schisandra-album-cloud-microservices/common/xhttp"
)
func WechatOffiaccountLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.WechatOffiaccountLoginRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := user.NewWechatOffiaccountLoginLogic(r.Context(), svcCtx)
resp, err := l.WechatOffiaccountLogin(r, &req)
if err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
} else {
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,15 @@
package websocket
import (
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/websocket"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
func MessageWebsocketHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := websocket.NewMessageWebsocketLogic(r.Context(), svcCtx)
l.MessageWebsocket(w, r)
}
}

View File

@@ -0,0 +1,15 @@
package websocket
import (
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/websocket"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
func QrcodeWebsocketHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := websocket.NewQrcodeWebsocketLogic(r.Context(), svcCtx)
l.QrcodeWebsocket(w, r)
}
}

View File

@@ -0,0 +1,33 @@
package captcha
import (
"context"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/common/captcha/generate"
"schisandra-album-cloud-microservices/common/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GenerateRotateCaptchaLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGenerateRotateCaptchaLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateRotateCaptchaLogic {
return &GenerateRotateCaptchaLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GenerateRotateCaptchaLogic) GenerateRotateCaptcha() (resp map[string]interface{}, err error) {
captcha, err := generate.GenerateRotateCaptcha(l.svcCtx.RotateCaptcha, l.svcCtx.RedisClient, l.ctx)
if err != nil {
return nil, errors.New(http.StatusInternalServerError, err.Error())
}
return captcha, nil
}

View File

@@ -0,0 +1,33 @@
package captcha
import (
"context"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/common/captcha/generate"
"schisandra-album-cloud-microservices/common/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GenerateSlideBasicCaptchaLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGenerateSlideBasicCaptchaLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateSlideBasicCaptchaLogic {
return &GenerateSlideBasicCaptchaLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GenerateSlideBasicCaptchaLogic) GenerateSlideBasicCaptcha() (resp map[string]interface{}, err error) {
captcha, err := generate.GenerateSlideBasicCaptcha(l.svcCtx.SlideCaptcha, l.svcCtx.RedisClient, l.ctx)
if err != nil {
return nil, errors.New(http.StatusInternalServerError, err.Error())
}
return captcha, nil
}

View File

@@ -0,0 +1,41 @@
package client
import (
"context"
"net/http"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/errors"
"time"
"github.com/ccpwcn/kgo"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
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 string, err error) {
clientId := l.svcCtx.RedisClient.Get(l.ctx, constant.UserClientPrefix+clientIP).Val()
if clientId != "" {
return clientId, nil
}
simpleUuid := kgo.SimpleUuid()
if err = l.svcCtx.RedisClient.SetEx(l.ctx, constant.UserClientPrefix+clientIP, simpleUuid, time.Hour*24*7).Err(); err != nil {
return "", errors.New(http.StatusInternalServerError, err.Error())
}
return simpleUuid, nil
}

View File

@@ -0,0 +1,28 @@
package oauth
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
type GetGiteeOauthUrlLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetGiteeOauthUrlLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetGiteeOauthUrlLogic {
return &GetGiteeOauthUrlLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetGiteeOauthUrlLogic) GetGiteeOauthUrl() (resp string, err error) {
clientID := l.svcCtx.Config.OAuth.Gitee.ClientID
redirectURI := l.svcCtx.Config.OAuth.Gitee.RedirectURI
url := "https://gitee.com/oauth/authorize?client_id=" + clientID + "&redirect_uri=" + redirectURI + "&response_type=code"
return url, nil
}

View File

@@ -0,0 +1,29 @@
package oauth
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type GetGithubOauthUrlLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetGithubOauthUrlLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetGithubOauthUrlLogic {
return &GetGithubOauthUrlLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetGithubOauthUrlLogic) GetGithubOauthUrl(req *types.OAuthRequest) (resp string, err error) {
clientId := l.svcCtx.Config.OAuth.Github.ClientID
redirectUrl := l.svcCtx.Config.OAuth.Github.RedirectURI
url := "https://github.com/login/oauth/authorize?client_id=" + clientId + "&redirect_uri=" + redirectUrl + "&state=" + req.State
return url, nil
}

View File

@@ -0,0 +1,29 @@
package oauth
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type GetQqOauthUrlLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetQqOauthUrlLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetQqOauthUrlLogic {
return &GetQqOauthUrlLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetQqOauthUrlLogic) GetQqOauthUrl(req *types.OAuthRequest) (resp string, err error) {
clientId := l.svcCtx.Config.OAuth.QQ.ClientID
redirectURI := l.svcCtx.Config.OAuth.QQ.RedirectURI
url := "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" + clientId + "&redirect_uri=" + redirectURI + "&state=" + req.State
return url, nil
}

View File

@@ -0,0 +1,262 @@
package oauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/xhttp"
"strconv"
"time"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type GiteeCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
type GiteeUser struct {
AvatarURL string `json:"avatar_url"`
Bio string `json:"bio"`
Blog string `json:"blog"`
CreatedAt time.Time `json:"created_at"`
Email string `json:"email"`
EventsURL string `json:"events_url"`
Followers int `json:"followers"`
FollowersURL string `json:"followers_url"`
Following int `json:"following"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Login string `json:"login"`
Name string `json:"name"`
OrganizationsURL string `json:"organizations_url"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsURL string `json:"received_events_url"`
Remark string `json:"remark"`
ReposURL string `json:"repos_url"`
Stared int `json:"stared"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
Watched int `json:"watched"`
Weibo interface{} `json:"weibo"`
}
type Token struct {
AccessToken string `json:"access_token"`
}
var Script = `
<script>
window.opener.postMessage('%s', '%s');
window.close();
</script>
`
func NewGiteeCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GiteeCallbackLogic {
return &GiteeCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GiteeCallbackLogic) GiteeCallback(r *http.Request, req *types.OAuthCallbackRequest) (string, error) {
// 获取 token
tokenAuthUrl := l.GetGiteeTokenAuthUrl(req.Code)
token, err := l.GetGiteeToken(tokenAuthUrl)
if err != nil {
return "", err
}
if token == nil {
return "", errors.New("get gitee token failed")
}
// 获取用户信息
userInfo, err := l.GetGiteeUserInfo(token)
if err != nil {
return "", err
}
if userInfo == nil {
return "", errors.New("get gitee user info failed")
}
var giteeUser GiteeUser
marshal, err := json.Marshal(userInfo)
if err != nil {
return "", err
}
if err = json.Unmarshal(marshal, &giteeUser); err != nil {
return "", err
}
Id := strconv.Itoa(giteeUser.ID)
tx := l.svcCtx.DB.Begin()
userSocial := l.svcCtx.DB.ScaAuthUserSocial
socialUser, err := tx.ScaAuthUserSocial.Where(userSocial.OpenID.Eq(Id), userSocial.Source.Eq(constant.OAuthSourceGitee)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return "", err
}
if socialUser == nil {
// 创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
addUser := &model.ScaAuthUser{
UID: uidStr,
Avatar: giteeUser.AvatarURL,
Username: giteeUser.Login,
Nickname: giteeUser.Name,
Blog: giteeUser.Blog,
Email: giteeUser.Email,
Gender: constant.Male,
}
err = tx.ScaAuthUser.Create(addUser)
if err != nil {
_ = tx.Rollback()
return "", err
}
gitee := constant.OAuthSourceGitee
newSocialUser := &model.ScaAuthUserSocial{
UserID: uidStr,
OpenID: Id,
Source: gitee,
}
err = tx.ScaAuthUserSocial.Create(newSocialUser)
if err != nil {
_ = tx.Rollback()
return "", err
}
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
_ = tx.Rollback()
return "", err
}
data, err := HandleOauthLoginResponse(addUser, l.svcCtx, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return "", err
}
if err = tx.Commit(); err != nil {
return "", 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 "", err
}
data, err := HandleOauthLoginResponse(authUserInfo, l.svcCtx, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return "", err
}
if err = tx.Commit(); err != nil {
return "", err
}
return data, nil
}
}
// HandleOauthLoginResponse 处理登录响应
func HandleOauthLoginResponse(scaAuthUser *model.ScaAuthUser, svcCtx *svc.ServiceContext, r *http.Request, ctx context.Context) (string, error) {
data, err := user.HandleLoginJWT(scaAuthUser, svcCtx, true, r, ctx)
if err != nil {
return "", err
}
marshalData, err := json.Marshal(xhttp.BaseResponse[*types.LoginResponse]{
Data: data,
Code: 200,
Msg: "success",
})
if err != nil {
return "", err
}
formattedScript := fmt.Sprintf(Script, marshalData, svcCtx.Config.Web.URL)
return formattedScript, nil
}
// GetGiteeTokenAuthUrl 获取Gitee token
func (l *GiteeCallbackLogic) GetGiteeTokenAuthUrl(code string) string {
clientId := l.svcCtx.Config.OAuth.Gitee.ClientID
clientSecret := l.svcCtx.Config.OAuth.Gitee.ClientSecret
redirectURI := l.svcCtx.Config.OAuth.Gitee.RedirectURI
return fmt.Sprintf(
"https://gitee.com/oauth/token?grant_type=authorization_code&code=%s&client_id=%s&redirect_uri=%s&client_secret=%s",
code, clientId, redirectURI, clientSecret,
)
}
// GetGiteeToken 获取 token
func (l *GiteeCallbackLogic) GetGiteeToken(url string) (*Token, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodPost, url, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 token并返回
var token Token
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
// GetGiteeUserInfo 获取用户信息
func (l *GiteeCallbackLogic) GetGiteeUserInfo(token *Token) (map[string]interface{}, error) {
// 形成请求
var userInfoUrl = "https://gitee.com/api/v5/user" // github用户信息获取接口
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}

View File

@@ -0,0 +1,244 @@
package oauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"strconv"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type GithubCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
type GitHubUser struct {
AvatarURL string `json:"avatar_url"`
Bio interface{} `json:"bio"`
Blog string `json:"blog"`
Company interface{} `json:"company"`
CreatedAt string `json:"created_at"`
Email string `json:"email"`
EventsURL string `json:"events_url"`
Followers int `json:"followers"`
FollowersURL string `json:"followers_url"`
Following int `json:"following"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
GravatarID string `json:"gravatar_id"`
Hireable interface{} `json:"hireable"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Location interface{} `json:"location"`
Login string `json:"login"`
Name string `json:"name"`
NodeID string `json:"node_id"`
NotificationEmail interface{} `json:"notification_email"`
OrganizationsURL string `json:"organizations_url"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsURL string `json:"received_events_url"`
ReposURL string `json:"repos_url"`
SiteAdmin bool `json:"site_admin"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
TwitterUsername interface{} `json:"twitter_username"`
Type string `json:"type"`
UpdatedAt string `json:"updated_at"`
URL string `json:"url"`
}
func NewGithubCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GithubCallbackLogic {
return &GithubCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GithubCallbackLogic) GithubCallback(r *http.Request, req *types.OAuthCallbackRequest) (string, error) {
// 获取 token
tokenAuthUrl := l.GetTokenAuthUrl(req.Code)
token, err := l.GetToken(tokenAuthUrl)
if err != nil {
return "", err
}
if token == nil {
return "", errors.New("get github token failed")
}
// 获取用户信息
userInfo, err := l.GetUserInfo(token)
if err != nil {
return "", err
}
if userInfo == nil {
return "", errors.New("get github user info failed")
}
// 处理用户信息
userInfoBytes, err := json.Marshal(userInfo)
if err != nil {
return "", err
}
var gitHubUser GitHubUser
err = json.Unmarshal(userInfoBytes, &gitHubUser)
if err != nil {
return "", err
}
Id := strconv.Itoa(gitHubUser.ID)
tx := l.svcCtx.DB.Begin()
userSocial := l.svcCtx.DB.ScaAuthUserSocial
socialUser, err := tx.ScaAuthUserSocial.Where(userSocial.OpenID.Eq(Id), userSocial.Source.Eq(constant.OAuthSourceGithub)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return "", err
}
if socialUser == nil {
// 创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
male := constant.Male
addUser := &model.ScaAuthUser{
UID: uidStr,
Avatar: gitHubUser.AvatarURL,
Username: gitHubUser.Login,
Nickname: gitHubUser.Name,
Blog: gitHubUser.Blog,
Email: gitHubUser.Email,
Gender: male,
}
err = tx.ScaAuthUser.Create(addUser)
if err != nil {
_ = tx.Rollback()
return "", err
}
githubUser := constant.OAuthSourceGithub
newSocialUser := &model.ScaAuthUserSocial{
UserID: uidStr,
OpenID: Id,
Source: githubUser,
}
err = tx.ScaAuthUserSocial.Create(newSocialUser)
if err != nil {
_ = tx.Rollback()
return "", err
}
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
_ = tx.Rollback()
return "", err
}
data, err := HandleOauthLoginResponse(addUser, l.svcCtx, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return "", err
}
if err = tx.Commit(); err != nil {
return "", 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 "", err
}
data, err := HandleOauthLoginResponse(authUserInfo, l.svcCtx, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return "", err
}
if err = tx.Commit(); err != nil {
return "", err
}
return data, nil
}
}
// GetTokenAuthUrl 通过code获取token认证url
func (l *GithubCallbackLogic) GetTokenAuthUrl(code string) string {
clientId := l.svcCtx.Config.OAuth.Github.ClientID
clientSecret := l.svcCtx.Config.OAuth.Github.ClientSecret
return fmt.Sprintf(
"https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s",
clientId, clientSecret, code,
)
}
// GetToken 获取 token
func (l *GithubCallbackLogic) GetToken(url string) (*Token, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 token并返回
var token Token
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
// GetUserInfo 获取用户信息
func (l *GithubCallbackLogic) GetUserInfo(token *Token) (map[string]interface{}, error) {
// 形成请求
var userInfoUrl = "https://api.github.com/user" // github用户信息获取接口
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}

View File

@@ -0,0 +1,266 @@
package oauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"strconv"
"strings"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
)
type QqCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
type AuthQQme struct {
ClientID string `json:"client_id"`
OpenID string `json:"openid"`
}
type QQToken struct {
AccessToken string `json:"access_token"`
ExpireIn string `json:"expire_in"`
RefreshToken string `json:"refresh_token"`
}
type QQUserInfo struct {
City string `json:"city"`
Figureurl string `json:"figureurl"`
Figureurl1 string `json:"figureurl_1"`
Figureurl2 string `json:"figureurl_2"`
FigureurlQq string `json:"figureurl_qq"`
FigureurlQq1 string `json:"figureurl_qq_1"`
FigureurlQq2 string `json:"figureurl_qq_2"`
Gender string `json:"gender"`
GenderType int `json:"gender_type"`
IsLost int `json:"is_lost"`
IsYellowVip string `json:"is_yellow_vip"`
IsYellowYearVip string `json:"is_yellow_year_vip"`
Level string `json:"level"`
Msg string `json:"msg"`
Nickname string `json:"nickname"`
Province string `json:"province"`
Ret int `json:"ret"`
Vip string `json:"vip"`
Year string `json:"year"`
YellowVipLevel string `json:"yellow_vip_level"`
}
func NewQqCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QqCallbackLogic {
return &QqCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QqCallbackLogic) QqCallback(r *http.Request, req *types.OAuthCallbackRequest) (string, error) {
tokenAuthUrl := l.GetQQTokenAuthUrl(req.Code)
token, err := l.GetQQToken(tokenAuthUrl)
if err != nil {
return "", err
}
if token == nil {
return "", errors.New("get qq token failed")
}
// 通过 token 获取 openid
authQQme, err := l.GetQQUserOpenID(token)
if err != nil {
return "", err
}
// 通过 token 和 openid 获取用户信息
userInfo, err := l.GetQQUserUserInfo(token, authQQme.OpenID)
if err != nil {
return "", err
}
if userInfo == nil {
return "", errors.New("get qq user info failed")
}
// 处理用户信息
userInfoBytes, err := json.Marshal(userInfo)
if err != nil {
return "", err
}
var qqUserInfo QQUserInfo
err = json.Unmarshal(userInfoBytes, &qqUserInfo)
if err != nil {
return "", err
}
tx := l.svcCtx.DB.Begin()
userSocial := l.svcCtx.DB.ScaAuthUserSocial
socialUser, err := tx.ScaAuthUserSocial.Where(userSocial.OpenID.Eq(authQQme.OpenID), userSocial.Source.Eq(constant.OAuthSourceQQ)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return "", err
}
if socialUser == nil {
// 创建用户
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
male := constant.Male
avatarUrl := strings.Replace(qqUserInfo.FigureurlQq1, "http://", "https://", 1)
addUser := &model.ScaAuthUser{
UID: uidStr,
Avatar: avatarUrl,
Username: authQQme.OpenID,
Nickname: qqUserInfo.Nickname,
Gender: male,
}
err = tx.ScaAuthUser.Create(addUser)
if err != nil {
_ = tx.Rollback()
return "", err
}
githubUser := constant.OAuthSourceQQ
newSocialUser := &model.ScaAuthUserSocial{
UserID: uidStr,
OpenID: authQQme.OpenID,
Source: githubUser,
}
err = tx.ScaAuthUserSocial.Create(newSocialUser)
if err != nil {
_ = tx.Rollback()
return "", err
}
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
_ = tx.Rollback()
return "", err
}
data, err := HandleOauthLoginResponse(addUser, l.svcCtx, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return "", err
}
if err = tx.Commit(); err != nil {
return "", 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 "", err
}
data, err := HandleOauthLoginResponse(authUserInfo, l.svcCtx, r, l.ctx)
if err != nil {
_ = tx.Rollback()
return "", err
}
if err = tx.Commit(); err != nil {
return "", err
}
return data, nil
}
}
// GetQQTokenAuthUrl 通过code获取token认证url
func (l *QqCallbackLogic) GetQQTokenAuthUrl(code string) string {
clientId := l.svcCtx.Config.OAuth.QQ.ClientID
clientSecret := l.svcCtx.Config.OAuth.QQ.ClientSecret
redirectURI := l.svcCtx.Config.OAuth.QQ.RedirectURI
return fmt.Sprintf(
"https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&fmt=json",
clientId, clientSecret, code, redirectURI,
)
}
// GetQQToken 获取 token
func (l *QqCallbackLogic) GetQQToken(url string) (*QQToken, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil {
return nil, err
}
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 token并返回
var token QQToken
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
// GetQQUserOpenID 获取用户 openid
func (l *QqCallbackLogic) GetQQUserOpenID(token *QQToken) (*AuthQQme, error) {
// 形成请求
var userInfoUrl = "https://graph.qq.com/oauth2.0/me?access_token=" + token.AccessToken + "&fmt=json"
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 AuthQQme并返回
var authQQme AuthQQme
if err = json.NewDecoder(res.Body).Decode(&authQQme); err != nil {
return nil, err
}
return &authQQme, nil
}
// GetQQUserUserInfo 获取用户信息
func (l *QqCallbackLogic) GetQQUserUserInfo(token *QQToken, openId string) (map[string]interface{}, error) {
clientId := l.svcCtx.Config.OAuth.QQ.ClientID
// 形成请求
var userInfoUrl = "https://graph.qq.com/user/get_user_info?access_token=" + token.AccessToken + "&oauth_consumer_key=" + clientId + "&openid=" + openId
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}

View File

@@ -0,0 +1,125 @@
package oauth
import (
"context"
"encoding/json"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/contract"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/messages"
models2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models"
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount/server/handlers/models"
"net/http"
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/websocket"
"schisandra-album-cloud-microservices/common/encrypt"
"schisandra-album-cloud-microservices/common/i18n"
"schisandra-album-cloud-microservices/common/xhttp"
"strings"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
type WechatOffiaccountCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
type MessageData struct {
Openid string `json:"openid"`
ClientId string `json:"client_id"`
}
func NewWechatOffiaccountCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatOffiaccountCallbackLogic {
return &WechatOffiaccountCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *WechatOffiaccountCallbackLogic) WechatOffiaccountCallback(r *http.Request) (*http.Response, error) {
rs, err := l.svcCtx.WechatOfficial.Server.Notify(r, func(event contract.EventInterface) interface{} {
switch event.GetMsgType() {
case models2.CALLBACK_MSG_TYPE_EVENT:
switch event.GetEvent() {
case models.CALLBACK_EVENT_SUBSCRIBE:
msg := models.EventSubscribe{}
err := event.ReadMessage(&msg)
if err != nil {
logx.Error(err.Error())
return "error"
}
key := strings.TrimPrefix(msg.EventKey, "qrscene_")
err = l.SendMessage(msg.FromUserName, key)
if err != nil {
return messages.NewText(i18n.FormatText(l.ctx, "login.loginFailed"))
}
return messages.NewText(i18n.FormatText(l.ctx, "login.loginSuccess"))
case models.CALLBACK_EVENT_UNSUBSCRIBE:
msg := models.EventUnSubscribe{}
err := event.ReadMessage(&msg)
if err != nil {
logx.Error(err.Error())
return "error"
}
return messages.NewText("ok")
case models.CALLBACK_EVENT_SCAN:
msg := models.EventScan{}
err := event.ReadMessage(&msg)
if err != nil {
logx.Error(err.Error())
return "error"
}
err = l.SendMessage(msg.FromUserName, msg.EventKey)
if err != nil {
return messages.NewText(i18n.FormatText(l.ctx, "login.loginFailed"))
}
return messages.NewText(i18n.FormatText(l.ctx, "login.loginSuccess"))
}
case models2.CALLBACK_MSG_TYPE_TEXT:
msg := models.MessageText{}
err := event.ReadMessage(&msg)
if err != nil {
logx.Error(err.Error())
return "error"
}
}
return messages.NewText("ok")
})
if err != nil {
return nil, err
}
return rs, nil
}
// SendMessage 发送消息到客户端
func (l *WechatOffiaccountCallbackLogic) SendMessage(openId string, clientId string) error {
encryptClientId, err := encrypt.Encrypt(clientId, l.svcCtx.Config.Encrypt.Key, l.svcCtx.Config.Encrypt.IV)
if err != nil {
return err
}
encryptOpenId, err := encrypt.Encrypt(openId, l.svcCtx.Config.Encrypt.Key, l.svcCtx.Config.Encrypt.IV)
if err != nil {
return err
}
messageData := MessageData{
Openid: encryptOpenId,
ClientId: encryptClientId,
}
jsonData, err := json.Marshal(xhttp.BaseResponse[MessageData]{
Code: 200,
Data: messageData,
Msg: "success",
})
if err != nil {
return err
}
err = websocket.QrcodeWebSocketHandler.SendMessageToClient(clientId, jsonData)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,31 @@
package oauth
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
type WechatOffiaccountCallbackVerifyLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewWechatOffiaccountCallbackVerifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatOffiaccountCallbackVerifyLogic {
return &WechatOffiaccountCallbackVerifyLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *WechatOffiaccountCallbackVerifyLogic) WechatOffiaccountCallbackVerify(r *http.Request) (*http.Response, error) {
rs, err := l.svcCtx.WechatOfficial.Server.VerifyURL(r)
if err != nil {
return nil, err
}
return rs, nil
}

View File

@@ -0,0 +1,74 @@
package sms
import (
"context"
gosms "github.com/pkg6/go-sms"
"github.com/pkg6/go-sms/gateways"
"github.com/pkg6/go-sms/gateways/aliyun"
"net/http"
"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/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 SendSmsByAliyunLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSendSmsByAliyunLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsByAliyunLogic {
return &SendSmsByAliyunLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SendSmsByAliyunLogic) SendSmsByAliyun(req *types.SmsSendRequest) error {
checkRotateData := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
if !checkRotateData {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, i18n.FormatText(l.ctx, "sms.verificationFailure")))
}
isPhone := utils.IsPhone(req.Phone)
if !isPhone {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.phoneFormatError"))
}
val := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
if val != "" {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "sms.smsSendTooFrequently"))
}
sms := gosms.NewParser(gateways.Gateways{
ALiYun: aliyun.ALiYun{
Host: l.svcCtx.Config.SMS.Ali.Host,
AccessKeyId: l.svcCtx.Config.SMS.Ali.AccessKeyId,
AccessKeySecret: l.svcCtx.Config.SMS.Ali.AccessKeySecret,
},
})
code := utils.GenValidateCode(6)
wrong := l.svcCtx.RedisClient.Set(l.ctx, constant.UserSmsRedisPrefix+req.Phone, code, time.Minute).Err()
if wrong != nil {
return errors.New(http.StatusInternalServerError, wrong.Error())
}
_, err := sms.Send(req.Phone, gosms.MapStringAny{
"content": "您的验证码是:****。请不要把验证码泄露给其他人。",
"template": l.svcCtx.Config.SMS.Ali.TemplateCode,
"signName": l.svcCtx.Config.SMS.Ali.Signature,
"data": gosms.MapStrings{
"code": code,
},
}, nil)
if err != nil {
return errors.New(http.StatusInternalServerError, err.Error())
}
return nil
}

View File

@@ -0,0 +1,68 @@
package sms
import (
"context"
"net/http"
"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"
utils2 "schisandra-album-cloud-microservices/common/utils"
"time"
gosms "github.com/pkg6/go-sms"
"github.com/pkg6/go-sms/gateways"
"github.com/pkg6/go-sms/gateways/smsbao"
"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 SendSmsBySmsbaoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSendSmsBySmsbaoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsBySmsbaoLogic {
return &SendSmsBySmsbaoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SendSmsBySmsbaoLogic) SendSmsBySmsbao(req *types.SmsSendRequest) (err error) {
checkRotateData := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
if !checkRotateData {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "captcha.verificationFailure"))
}
isPhone := utils2.IsPhone(req.Phone)
if !isPhone {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.phoneFormatError"))
}
val := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
if val != "" {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "sms.smsSendTooFrequently"))
}
sms := gosms.NewParser(gateways.Gateways{
SmsBao: smsbao.SmsBao{
User: l.svcCtx.Config.SMS.SMSBao.Username,
Password: l.svcCtx.Config.SMS.SMSBao.Password,
},
})
code := utils2.GenValidateCode(6)
wrong := l.svcCtx.RedisClient.Set(l.ctx, constant.UserSmsRedisPrefix+req.Phone, code, time.Minute).Err()
if wrong != nil {
return errors.New(http.StatusInternalServerError, wrong.Error())
}
_, err = sms.Send(req.Phone, gosms.MapStringAny{
"content": "您的验证码是:" + code + "。请不要把验证码泄露给其他人。",
}, nil)
if err != nil {
return errors.New(http.StatusInternalServerError, err.Error())
}
return nil
}

View File

@@ -0,0 +1,52 @@
package sms
import (
"context"
"net/http"
"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/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 SendSmsByTestLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSendSmsByTestLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsByTestLogic {
return &SendSmsByTestLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SendSmsByTestLogic) SendSmsByTest(req *types.SmsSendRequest) (err error) {
checkRotateData := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
if !checkRotateData {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "captcha.verificationFailure"))
}
isPhone := utils.IsPhone(req.Phone)
if !isPhone {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.phoneFormatError"))
}
val := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
if val != "" {
return errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "sms.smsSendTooFrequently"))
}
code := utils.GenValidateCode(6)
wrong := l.svcCtx.RedisClient.Set(l.ctx, constant.UserSmsRedisPrefix+req.Phone, code, time.Minute).Err()
if wrong != nil {
return errors.New(http.StatusInternalServerError, wrong.Error())
}
return nil
}

View File

@@ -0,0 +1,85 @@
package token
import (
"context"
"encoding/json"
"net/http"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/errors"
jwt2 "schisandra-album-cloud-microservices/common/jwt"
"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 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.RefreshTokenResponse, err error) {
userId := r.Header.Get(constant.UID_HEADER_KEY)
if userId == "" {
return nil, errors.New(http.StatusForbidden, "user id is empty")
}
// 从redis中获取refresh token
tokenData := l.svcCtx.RedisClient.Get(l.ctx, constant.UserTokenPrefix+userId).Val()
if tokenData == "" {
return nil, errors.New(http.StatusForbidden, "refresh token is empty")
}
redisTokenData := types.RedisToken{}
err = json.Unmarshal([]byte(tokenData), &redisTokenData)
if err != nil {
return nil, err
}
// 判断是否已经被吊销
if redisTokenData.Revoked {
return nil, errors.New(http.StatusForbidden, "refresh token is revoked")
}
// 判断是否是同一个设备
if redisTokenData.AllowAgent != r.UserAgent() {
return nil, errors.New(http.StatusForbidden, "refresh token is not allowed for this agent")
}
// 判断refresh token是否在有效期内
refreshToken, result := jwt2.ParseRefreshToken(l.svcCtx.Config.Auth.AccessSecret, redisTokenData.RefreshToken)
if !result {
return nil, errors.New(http.StatusForbidden, "refresh token is invalid")
}
// 生成新的access token
accessToken, expireAt := jwt2.GenerateAccessToken(l.svcCtx.Config.Auth.AccessSecret, jwt2.AccessJWTPayload{
UserID: refreshToken.UserID,
Type: constant.JWT_TYPE_ACCESS,
})
// 更新redis中的access token
redisToken := types.RedisToken{
AccessToken: accessToken,
RefreshToken: redisTokenData.RefreshToken,
UID: refreshToken.UserID,
Revoked: false,
GeneratedAt: redisTokenData.GeneratedAt,
AllowAgent: redisTokenData.AllowAgent,
GeneratedIP: redisTokenData.GeneratedIP,
UpdatedAt: time.Now().Format(constant.TimeFormat),
}
err = l.svcCtx.RedisClient.Set(l.ctx, constant.UserTokenPrefix+refreshToken.UserID, redisToken, time.Hour*24*7).Err()
if err != nil {
return nil, err
}
token := &types.RefreshTokenResponse{
AccessToken: accessToken,
ExpireAt: expireAt,
}
return token, nil
}

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

View File

@@ -0,0 +1,126 @@
package websocket
import (
"context"
"fmt"
"net/http"
"schisandra-album-cloud-microservices/common/jwt"
"time"
"github.com/lxzan/gws"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
type MessageWebsocketLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewMessageWebsocketLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MessageWebsocketLogic {
return &MessageWebsocketLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
type MessageWebSocket struct {
ctx context.Context
gws.BuiltinEventHandler
sessions *gws.ConcurrentMap[string, *gws.Conn] // 使用内置的ConcurrentMap存储连接, 可以减少锁冲突
}
var MessageWebSocketHandler = NewMessageWebSocket()
func (l *MessageWebsocketLogic) MessageWebsocket(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Sec-Websocket-Protocol")
accessToken, res := jwt.ParseAccessToken(l.svcCtx.Config.Auth.AccessSecret, token)
if !res {
return
}
upgrader := gws.NewUpgrader(MessageWebSocketHandler, &gws.ServerOption{
HandshakeTimeout: 5 * time.Second, // 握手超时时间
ReadBufferSize: 1024, // 读缓冲区大小
ParallelEnabled: true, // 开启并行消息处理
Recovery: gws.Recovery, // 开启异常恢复
CheckUtf8Enabled: false, // 关闭UTF8校验
PermessageDeflate: gws.PermessageDeflate{
Enabled: true, // 开启压缩
},
Authorize: func(r *http.Request, session gws.SessionStorage) bool {
var clientId = r.URL.Query().Get("user_id")
if clientId == "" {
return false
}
if accessToken.UserID != clientId {
return false
}
//token := r.URL.Query().Get("token")
//if token == "" {
// return false
//}
//accessToken, res := jwt.ParseAccessToken(l.svcCtx.Config.Auth.AccessSecret, token)
//if !res || accessToken.UserID != clientId {
// return false
//}
session.Store("user_id", clientId)
return true
},
SubProtocols: []string{token},
})
socket, err := upgrader.Upgrade(w, r)
if err != nil {
panic(err)
}
go func() {
socket.ReadLoop()
}()
}
// NewMessageWebSocket 创建WebSocket实例
func NewMessageWebSocket() *MessageWebSocket {
return &MessageWebSocket{
ctx: context.Background(),
sessions: gws.NewConcurrentMap[string, *gws.Conn](64, 128),
}
}
// OnOpen 连接建立
func (c *MessageWebSocket) OnOpen(socket *gws.Conn) {
clientId := MustLoad[string](socket.Session(), "user_id")
c.sessions.Store(clientId, socket)
// 订阅该用户的频道
fmt.Printf("websocket client %s connected\n", clientId)
}
// OnClose 关闭连接
func (c *MessageWebSocket) OnClose(socket *gws.Conn, err error) {
name := MustLoad[string](socket.Session(), "user_id")
sharding := c.sessions.GetSharding(name)
c.sessions.Delete(name)
sharding.Lock()
defer sharding.Unlock()
fmt.Printf("websocket closed, name=%s, msg=%s\n", name, err.Error())
}
// OnPing 处理客户端的Ping消息
func (c *MessageWebSocket) OnPing(socket *gws.Conn, payload []byte) {
_ = socket.SetDeadline(time.Now().Add(PingInterval + HeartbeatWaitTimeout))
_ = socket.WritePong(payload)
}
// OnPong 处理客户端的Pong消息
func (c *MessageWebSocket) OnPong(_ *gws.Conn, _ []byte) {}
// OnMessage 接受消息
func (c *MessageWebSocket) OnMessage(socket *gws.Conn, message *gws.Message) {
defer message.Close()
clientId := MustLoad[string](socket.Session(), "user_id")
if conn, ok := c.sessions.Load(clientId); ok {
_ = conn.WriteMessage(gws.OpcodeText, message.Bytes())
}
// fmt.Printf("received message from client %s\n", message.Data)
}

View File

@@ -0,0 +1,133 @@
package websocket
import (
"context"
"fmt"
"net/http"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/utils"
"time"
"github.com/lxzan/gws"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
type QrcodeWebsocketLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewQrcodeWebsocketLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QrcodeWebsocketLogic {
return &QrcodeWebsocketLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
const (
PingInterval = 5 * time.Second // 客户端心跳间隔
HeartbeatWaitTimeout = 10 * time.Second // 心跳等待超时时间
)
type QrcodeWebSocket struct {
gws.BuiltinEventHandler
sessions *gws.ConcurrentMap[string, *gws.Conn] // 使用内置的ConcurrentMap存储连接, 可以减少锁冲突
}
var QrcodeWebSocketHandler = NewWebSocket()
func (l *QrcodeWebsocketLogic) QrcodeWebsocket(w http.ResponseWriter, r *http.Request) {
upgrader := gws.NewUpgrader(QrcodeWebSocketHandler, &gws.ServerOption{
HandshakeTimeout: 5 * time.Second, // 握手超时时间
ReadBufferSize: 1024, // 读缓冲区大小
ParallelEnabled: true, // 开启并行消息处理
Recovery: gws.Recovery, // 开启异常恢复
CheckUtf8Enabled: false, // 关闭UTF8校验
PermessageDeflate: gws.PermessageDeflate{
Enabled: true, // 开启压缩
},
Authorize: func(r *http.Request, session gws.SessionStorage) bool {
var clientId = r.URL.Query().Get("client_id")
if clientId == "" {
return false
}
ip := utils.GetClientIP(r)
exists := l.svcCtx.RedisClient.Get(l.ctx, constant.UserClientPrefix+ip).Val()
if clientId != exists {
return false
}
session.Store("client_id", clientId)
return true
},
})
socket, err := upgrader.Upgrade(w, r)
if err != nil {
panic(err)
}
go func() {
socket.ReadLoop() // 此处阻塞会使请求上下文不能顺利被GC
}()
}
// MustLoad 从session中加载数据
func MustLoad[T any](session gws.SessionStorage, key string) (v T) {
if value, exist := session.Load(key); exist {
v = value.(T)
}
return
}
// NewWebSocket 创建WebSocket实例
func NewWebSocket() *QrcodeWebSocket {
return &QrcodeWebSocket{
sessions: gws.NewConcurrentMap[string, *gws.Conn](64, 128),
}
}
// OnOpen 连接建立
func (c *QrcodeWebSocket) OnOpen(socket *gws.Conn) {
clientId := MustLoad[string](socket.Session(), "client_id")
c.sessions.Store(clientId, socket)
fmt.Printf("websocket client %s connected\n", clientId)
}
// OnClose 关闭连接
func (c *QrcodeWebSocket) OnClose(socket *gws.Conn, err error) {
name := MustLoad[string](socket.Session(), "client_id")
sharding := c.sessions.GetSharding(name)
c.sessions.Delete(name)
sharding.Lock()
defer sharding.Unlock()
fmt.Printf("websocket closed, name=%s, err=%s\n", name, err.Error())
}
// OnPing 处理客户端的Ping消息
func (c *QrcodeWebSocket) OnPing(socket *gws.Conn, payload []byte) {
_ = socket.SetDeadline(time.Now().Add(PingInterval + HeartbeatWaitTimeout))
_ = socket.WritePong(payload)
}
// OnPong 处理客户端的Pong消息
func (c *QrcodeWebSocket) OnPong(_ *gws.Conn, _ []byte) {}
// OnMessage 接受消息
func (c *QrcodeWebSocket) OnMessage(socket *gws.Conn, message *gws.Message) {
defer message.Close()
clientId := MustLoad[string](socket.Session(), "client_id")
if conn, ok := c.sessions.Load(clientId); ok {
_ = conn.WriteMessage(gws.OpcodeText, message.Bytes())
}
}
// SendMessageToClient 向指定客户端发送消息
func (c *QrcodeWebSocket) SendMessageToClient(clientId string, message []byte) error {
conn, ok := c.sessions.Load(clientId)
if ok {
return conn.WriteMessage(gws.OpcodeText, message)
}
return fmt.Errorf("client %s not found", clientId)
}

View File

@@ -0,0 +1,20 @@
package middleware
import (
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
)
type AuthorizationMiddleware struct {
}
func NewAuthorizationMiddleware() *AuthorizationMiddleware {
return &AuthorizationMiddleware{}
}
func (m *AuthorizationMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.AuthorizationMiddleware(w, r)
next(w, r)
}
}

View File

@@ -0,0 +1,24 @@
package middleware
import (
"github.com/casbin/casbin/v2"
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
)
type CasbinVerifyMiddleware struct {
casbin *casbin.SyncedCachedEnforcer
}
func NewCasbinVerifyMiddleware(casbin *casbin.SyncedCachedEnforcer) *CasbinVerifyMiddleware {
return &CasbinVerifyMiddleware{
casbin: casbin,
}
}
func (m *CasbinVerifyMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.CasbinMiddleware(w, r, m.casbin)
next(w, r)
}
}

View File

@@ -0,0 +1,24 @@
package middleware
import (
"github.com/redis/go-redis/v9"
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
)
type NonceMiddleware struct {
RedisClient *redis.Client
}
func NewNonceMiddleware(redisClient *redis.Client) *NonceMiddleware {
return &NonceMiddleware{
RedisClient: redisClient,
}
}
func (m *NonceMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.NonceMiddleware(w, r, m.RedisClient)
next(w, r)
}
}

View File

@@ -0,0 +1,20 @@
package middleware
import (
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
)
type SecurityHeadersMiddleware struct {
}
func NewSecurityHeadersMiddleware() *SecurityHeadersMiddleware {
return &SecurityHeadersMiddleware{}
}
func (m *SecurityHeadersMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.SecurityHeadersMiddleware(r)
next(w, r)
}
}

View File

@@ -0,0 +1,56 @@
package svc
import (
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount"
"github.com/casbin/casbin/v2"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/redis/go-redis/v9"
"github.com/wenlng/go-captcha/v2/rotate"
"github.com/wenlng/go-captcha/v2/slide"
"github.com/zeromicro/go-zero/rest"
"schisandra-album-cloud-microservices/app/auth/api/internal/config"
"schisandra-album-cloud-microservices/app/auth/api/internal/middleware"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql"
"schisandra-album-cloud-microservices/app/auth/api/model/mysql/query"
"schisandra-album-cloud-microservices/common/captcha/initialize"
"schisandra-album-cloud-microservices/common/casbinx"
"schisandra-album-cloud-microservices/common/ip2region"
"schisandra-album-cloud-microservices/common/redisx"
"schisandra-album-cloud-microservices/common/wechat_official"
)
type ServiceContext struct {
Config config.Config
SecurityHeadersMiddleware rest.Middleware
CasbinVerifyMiddleware rest.Middleware
AuthorizationMiddleware rest.Middleware
NonceMiddleware rest.Middleware
DB *query.Query
RedisClient *redis.Client
Ip2Region *xdb.Searcher
CasbinEnforcer *casbin.SyncedCachedEnforcer
WechatOfficial *officialAccount.OfficialAccount
RotateCaptcha rotate.Captcha
SlideCaptcha slide.Captcha
}
func NewServiceContext(c config.Config) *ServiceContext {
redisClient := redisx.NewRedis(c.Redis.Host, c.Redis.Pass, c.Redis.DB)
db, queryDB := mysql.NewMySQL(c.Mysql.DataSource, c.Mysql.MaxOpenConn, c.Mysql.MaxIdleConn, redisClient)
casbinEnforcer := casbinx.NewCasbin(db)
return &ServiceContext{
Config: c,
SecurityHeadersMiddleware: middleware.NewSecurityHeadersMiddleware().Handle,
CasbinVerifyMiddleware: middleware.NewCasbinVerifyMiddleware(casbinEnforcer).Handle,
AuthorizationMiddleware: middleware.NewAuthorizationMiddleware().Handle,
NonceMiddleware: middleware.NewNonceMiddleware(redisClient).Handle,
DB: queryDB,
RedisClient: redisClient,
Ip2Region: ip2region.NewIP2Region(),
CasbinEnforcer: casbinEnforcer,
WechatOfficial: wechat_official.NewWechatPublic(c.Wechat.AppID, c.Wechat.AppSecret, c.Wechat.Token, c.Wechat.AESKey, c.Redis.Host, c.Redis.Pass, c.Redis.DB),
RotateCaptcha: initialize.NewRotateCaptcha(),
SlideCaptcha: initialize.NewSlideCaptcha(),
}
}

View File

@@ -0,0 +1,22 @@
package types
import "encoding/json"
type RedisToken struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
UID string `json:"uid"`
Revoked bool `json:"revoked" default:"false"`
AllowAgent string `json:"allow_agent"`
GeneratedAt string `json:"generated_at"`
UpdatedAt string `json:"updated_at"`
GeneratedIP string `json:"generated_ip"`
}
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,79 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
package types
type AccountLoginRequest struct {
Account string `json:"account"`
Password string `json:"password"`
AutoLogin bool `json:"auto_login"`
Angle int64 `json:"angle"`
Key string `json:"key"`
}
type LoginResponse struct {
AccessToken string `json:"access_token"`
ExpireAt int64 `json:"expire_at"`
UID string `json:"uid"`
Username string `json:"username,omitempty"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Status int64 `json:"status"`
}
type OAuthCallbackRequest struct {
Code string `form:"code"`
}
type OAuthRequest struct {
State string `form:"state"`
}
type OAuthWechatRequest struct {
ClientId string `json:"client_id"`
}
type PhoneLoginRequest struct {
Phone string `json:"phone"`
Captcha string `json:"captcha"`
AutoLogin bool `json:"auto_login"`
}
type RefreshTokenResponse struct {
AccessToken string `json:"access_token"`
ExpireAt int64 `json:"expire_at"`
}
type ResetPasswordRequest struct {
Phone string `json:"phone"`
Captcha string `json:"captcha"`
Password string `json:"password"`
Repassword string `json:"repassword"`
}
type RotateCaptchaResponse struct {
Key string `json:"key"`
Image string `json:"image"`
Thumb string `json:"thumb"`
}
type SlideCaptchaResponse struct {
Key string `json:"key"`
Image string `json:"image"`
Thumb string `json:"thumb"`
ThumbWidth int64 `json:"thumb_width"`
ThumbHeight int64 `json:"thumb_height"`
ThumbX int64 `json:"thumb_x"`
ThumbY int64 `json:"thumb_y"`
}
type SmsSendRequest struct {
Phone string `json:"phone"`
Angle int64 `json:"angle"`
Key string `json:"key"`
}
type WechatOffiaccountLoginRequest struct {
Openid string `json:"openid"`
ClientId string `json:"client_id"`
}