✨ code migration
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/captcha/generate"
|
||||
"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 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 *types.Response, err error) {
|
||||
captcha, err := generate.GenerateRotateCaptcha(l.svcCtx.RotateCaptcha, l.svcCtx.RedisClient, l.ctx)
|
||||
if err != nil {
|
||||
return response.Error(), err
|
||||
}
|
||||
return response.SuccessWithData(captcha), nil
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/captcha/generate"
|
||||
"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 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 *types.Response, err error) {
|
||||
captcha, err := generate.GenerateSlideBasicCaptcha(l.svcCtx.SlideCaptcha, l.svcCtx.RedisClient, l.ctx)
|
||||
if err != nil {
|
||||
return response.Error(), err
|
||||
}
|
||||
return response.SuccessWithData(captcha), nil
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 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 *types.Response, 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 response.SuccessWithData(url), nil
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 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 *types.Response, 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 response.SuccessWithData(url), nil
|
||||
}
|
32
app/core/api/internal/logic/oauth/get_qq_oauth_url_logic.go
Normal file
32
app/core/api/internal/logic/oauth/get_qq_oauth_url_logic.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 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 *types.Response, 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 response.SuccessWithData(url), nil
|
||||
}
|
64
app/core/api/internal/logic/oauth/get_wechat_qrcode_logic.go
Normal file
64
app/core/api/internal/logic/oauth/get_wechat_qrcode_logic.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/basicService/qrCode/response"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
response2 "schisandra-album-cloud-microservices/app/core/api/common/response"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetWechatQrcodeLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetWechatQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWechatQrcodeLogic {
|
||||
return &GetWechatQrcodeLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetWechatQrcodeLogic) GetWechatQrcode(r *http.Request, req *types.OAuthWechatRequest) (resp *types.Response, err error) {
|
||||
ip := utils.GetClientIP(r) // 使用工具函数获取客户端IP
|
||||
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 response2.Error(), err
|
||||
}
|
||||
return response2.SuccessWithData(data.Url), nil
|
||||
}
|
||||
|
||||
// 生成临时二维码
|
||||
data, err := l.svcCtx.WechatPublic.QRCode.Temporary(l.ctx, req.Client_id, 7*24*3600)
|
||||
if err != nil {
|
||||
return response2.Error(), err
|
||||
}
|
||||
|
||||
// 序列化数据并存储到Redis
|
||||
serializedData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return response2.Error(), err
|
||||
}
|
||||
if err = l.svcCtx.RedisClient.Set(l.ctx, key, serializedData, time.Hour*24*7).Err(); err != nil {
|
||||
return response2.Error(), err
|
||||
}
|
||||
|
||||
return response2.SuccessWithData(data.Url), nil
|
||||
}
|
257
app/core/api/internal/logic/oauth/gitee_callback_logic.go
Normal file
257
app/core/api/internal/logic/oauth/gitee_callback_logic.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/yitter/idgenerator-go/idgen"
|
||||
|
||||
"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/logic/user"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthusersocial"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
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(w http.ResponseWriter, r *http.Request, req *types.OAuthCallbackRequest) error {
|
||||
// 获取 token
|
||||
tokenAuthUrl := l.GetGiteeTokenAuthUrl(req.Code)
|
||||
token, err := l.GetGiteeToken(tokenAuthUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if token == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
userInfo, err := l.GetGiteeUserInfo(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var giteeUser GiteeUser
|
||||
marshal, err := json.Marshal(userInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = json.Unmarshal(marshal, &giteeUser); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := l.svcCtx.MySQLClient.Tx(l.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Id := strconv.Itoa(giteeUser.ID)
|
||||
|
||||
socialUser, err := l.svcCtx.MySQLClient.ScaAuthUserSocial.Query().
|
||||
Where(scaauthusersocial.OpenID(Id),
|
||||
scaauthusersocial.Source(constant.OAuthSourceGitee),
|
||||
scaauthusersocial.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
|
||||
if err != nil && !ent.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if ent.IsNotFound(err) {
|
||||
// 创建用户
|
||||
uid := idgen.NextId()
|
||||
uidStr := strconv.FormatInt(uid, 10)
|
||||
|
||||
addUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Create().
|
||||
SetUID(uidStr).
|
||||
SetAvatar(giteeUser.AvatarURL).
|
||||
SetUsername(giteeUser.Login).
|
||||
SetNickname(giteeUser.Name).
|
||||
SetBlog(giteeUser.Blog).
|
||||
SetEmail(giteeUser.Email).
|
||||
SetDeleted(constant.NotDeleted).
|
||||
SetGender(constant.Male).
|
||||
Save(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if err = l.svcCtx.MySQLClient.ScaAuthUserSocial.Create().
|
||||
SetUserID(uidStr).
|
||||
SetOpenID(Id).
|
||||
SetSource(constant.OAuthSourceGitee).
|
||||
Exec(l.ctx); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if result := HandleOauthLoginResponse(addUser, l.svcCtx, r, w, l.ctx); !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
} else {
|
||||
sacAuthUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Query().
|
||||
Where(scaauthuser.UID(socialUser.UserID), scaauthuser.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if result := HandleOauthLoginResponse(sacAuthUser, l.svcCtx, r, w, l.ctx); !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleOauthLoginResponse 处理登录响应
|
||||
func HandleOauthLoginResponse(scaAuthUser *ent.ScaAuthUser, svcCtx *svc.ServiceContext, r *http.Request, w http.ResponseWriter, ctx context.Context) bool {
|
||||
data, result := user.HandleUserLogin(scaAuthUser, svcCtx, true, r, w, ctx)
|
||||
if !result {
|
||||
return false
|
||||
}
|
||||
responseData := response.SuccessWithData(data)
|
||||
formattedScript := fmt.Sprintf(Script, responseData, svcCtx.Config.Web.URL)
|
||||
|
||||
// 设置响应状态码和内容类型
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
// 写入响应内容
|
||||
if _, writeErr := w.Write([]byte(formattedScript)); writeErr != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
236
app/core/api/internal/logic/oauth/github_callback_logic.go
Normal file
236
app/core/api/internal/logic/oauth/github_callback_logic.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/yitter/idgenerator-go/idgen"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthusersocial"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
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(w http.ResponseWriter, r *http.Request, req *types.OAuthCallbackRequest) error {
|
||||
|
||||
// 获取 token
|
||||
tokenAuthUrl := l.GetTokenAuthUrl(req.Code)
|
||||
token, err := l.GetToken(tokenAuthUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if token == nil {
|
||||
|
||||
return errors.New("failed to get token")
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
userInfo, err := l.GetUserInfo(token)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if userInfo == nil {
|
||||
return errors.New("failed to get user info")
|
||||
}
|
||||
|
||||
// 处理用户信息
|
||||
userInfoBytes, err := json.Marshal(userInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var gitHubUser GitHubUser
|
||||
err = json.Unmarshal(userInfoBytes, &gitHubUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx, err := l.svcCtx.MySQLClient.Tx(l.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Id := strconv.Itoa(gitHubUser.ID)
|
||||
socialUser, err := l.svcCtx.MySQLClient.ScaAuthUserSocial.Query().
|
||||
Where(scaauthusersocial.OpenID(Id),
|
||||
scaauthusersocial.Source(constant.OAuthSourceGithub),
|
||||
scaauthusersocial.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
|
||||
if err != nil && !ent.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if ent.IsNotFound(err) {
|
||||
// 创建用户
|
||||
uid := idgen.NextId()
|
||||
uidStr := strconv.FormatInt(uid, 10)
|
||||
|
||||
addUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Create().
|
||||
SetUID(uidStr).
|
||||
SetAvatar(gitHubUser.AvatarURL).
|
||||
SetUsername(gitHubUser.Login).
|
||||
SetNickname(gitHubUser.Name).
|
||||
SetBlog(gitHubUser.Blog).
|
||||
SetEmail(gitHubUser.Email).
|
||||
SetDeleted(constant.NotDeleted).
|
||||
SetGender(constant.Male).
|
||||
Save(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if err = l.svcCtx.MySQLClient.ScaAuthUserSocial.Create().
|
||||
SetUserID(uidStr).
|
||||
SetOpenID(Id).
|
||||
SetSource(constant.OAuthSourceGithub).
|
||||
Exec(l.ctx); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if result := HandleOauthLoginResponse(addUser, l.svcCtx, r, w, l.ctx); !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
} else {
|
||||
sacAuthUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Query().
|
||||
Where(scaauthuser.UID(socialUser.UserID), scaauthuser.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if result := HandleOauthLoginResponse(sacAuthUser, l.svcCtx, r, w, l.ctx); !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
return 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
|
||||
}
|
252
app/core/api/internal/logic/oauth/qq_callback_logic.go
Normal file
252
app/core/api/internal/logic/oauth/qq_callback_logic.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/yitter/idgenerator-go/idgen"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthusersocial"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
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(w http.ResponseWriter, r *http.Request, req *types.OAuthCallbackRequest) 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
|
||||
}
|
||||
|
||||
// 处理用户信息
|
||||
userInfoBytes, err := json.Marshal(userInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var qqUserInfo QQUserInfo
|
||||
err = json.Unmarshal(userInfoBytes, &qqUserInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx, err := l.svcCtx.MySQLClient.Tx(l.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
socialUser, err := l.svcCtx.MySQLClient.ScaAuthUserSocial.Query().
|
||||
Where(scaauthusersocial.OpenID(authQQme.OpenID),
|
||||
scaauthusersocial.Source(constant.OAuthSourceQQ),
|
||||
scaauthusersocial.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
|
||||
if err != nil && !ent.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if ent.IsNotFound(err) {
|
||||
// 创建用户
|
||||
uid := idgen.NextId()
|
||||
uidStr := strconv.FormatInt(uid, 10)
|
||||
|
||||
addUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Create().
|
||||
SetUID(uidStr).
|
||||
SetAvatar(qqUserInfo.FigureurlQq1).
|
||||
SetUsername(authQQme.OpenID).
|
||||
SetNickname(qqUserInfo.Nickname).
|
||||
SetDeleted(constant.NotDeleted).
|
||||
SetGender(constant.Male).
|
||||
Save(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if err = l.svcCtx.MySQLClient.ScaAuthUserSocial.Create().
|
||||
SetUserID(uidStr).
|
||||
SetOpenID(authQQme.OpenID).
|
||||
SetSource(constant.OAuthSourceQQ).
|
||||
Exec(l.ctx); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if result := HandleOauthLoginResponse(addUser, l.svcCtx, r, w, l.ctx); !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
} else {
|
||||
sacAuthUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Query().
|
||||
Where(scaauthuser.UID(socialUser.UserID), scaauthuser.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if result := HandleOauthLoginResponse(sacAuthUser, l.svcCtx, r, w, l.ctx); !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
return 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
|
||||
}
|
201
app/core/api/internal/logic/oauth/wechat_callback_logic.go
Normal file
201
app/core/api/internal/logic/oauth/wechat_callback_logic.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ArtisanCloud/PowerLibs/v3/http/helper"
|
||||
"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"
|
||||
"github.com/yitter/idgenerator-go/idgen"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/i18n"
|
||||
randomname "schisandra-album-cloud-microservices/app/core/api/common/random_name"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/logic/user"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/logic/websocket"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"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/scaauthusersocial"
|
||||
)
|
||||
|
||||
type WechatCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewWechatCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatCallbackLogic {
|
||||
return &WechatCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *WechatCallbackLogic) WechatCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
_, err := l.svcCtx.WechatPublic.Server.VerifyURL(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rs, err := l.svcCtx.WechatPublic.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 {
|
||||
println(err.Error())
|
||||
return "error"
|
||||
}
|
||||
key := strings.TrimPrefix(msg.EventKey, "qrscene_")
|
||||
err = l.HandlerWechatLogin(msg.FromUserName, key, w, r)
|
||||
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 {
|
||||
println(err.Error())
|
||||
return "error"
|
||||
}
|
||||
return messages.NewText("ok")
|
||||
|
||||
case models.CALLBACK_EVENT_SCAN:
|
||||
msg := models.EventScan{}
|
||||
err = event.ReadMessage(&msg)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return "error"
|
||||
}
|
||||
err = l.HandlerWechatLogin(msg.FromUserName, msg.EventKey, w, r)
|
||||
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 {
|
||||
println(err.Error())
|
||||
return "error"
|
||||
}
|
||||
}
|
||||
return messages.NewText("ok")
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = helper.HttpResponseSend(rs, w)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandlerWechatLogin 处理微信登录
|
||||
func (l *WechatCallbackLogic) HandlerWechatLogin(openId string, clientId string, w http.ResponseWriter, r *http.Request) error {
|
||||
if openId == "" {
|
||||
return errors.New("openId is empty")
|
||||
}
|
||||
socialUser, err := l.svcCtx.MySQLClient.ScaAuthUserSocial.Query().
|
||||
Where(scaauthusersocial.OpenID(openId),
|
||||
scaauthusersocial.Source(constant.OAuthSourceWechat),
|
||||
scaauthusersocial.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
|
||||
if err != nil && !ent.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
tx, err := l.svcCtx.MySQLClient.Tx(l.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ent.IsNotFound(err) {
|
||||
// 创建用户
|
||||
uid := idgen.NextId()
|
||||
uidStr := strconv.FormatInt(uid, 10)
|
||||
avatar := utils.GenerateAvatar(uidStr)
|
||||
name := randomname.GenerateName()
|
||||
|
||||
addUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Create().
|
||||
SetUID(uidStr).
|
||||
SetAvatar(avatar).
|
||||
SetUsername(openId).
|
||||
SetNickname(name).
|
||||
SetDeleted(constant.NotDeleted).
|
||||
SetGender(constant.Male).
|
||||
Save(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if err = l.svcCtx.MySQLClient.ScaAuthUserSocial.Create().
|
||||
SetUserID(uidStr).
|
||||
SetOpenID(openId).
|
||||
SetSource(constant.OAuthSourceWechat).
|
||||
Exec(l.ctx); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
if res, err := l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User); !res || err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
data, result := user.HandleUserLogin(addUser, l.svcCtx, true, r, w, l.ctx)
|
||||
if !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
marshal, fault := json.Marshal(data)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
err = websocket.QrcodeWebSocketHandler.SendMessageToClient(clientId, marshal)
|
||||
if err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
} else {
|
||||
sacAuthUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Query().
|
||||
Where(scaauthuser.UID(socialUser.UserID), scaauthuser.Deleted(constant.NotDeleted)).
|
||||
First(l.ctx)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
data, result := user.HandleUserLogin(sacAuthUser, l.svcCtx, true, r, w, l.ctx)
|
||||
if !result {
|
||||
return tx.Rollback()
|
||||
}
|
||||
marshal, fault := json.Marshal(data)
|
||||
if fault != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
err = websocket.QrcodeWebSocketHandler.SendMessageToClient(clientId, marshal)
|
||||
if err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return tx.Rollback()
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
73
app/core/api/internal/logic/sms/send_sms_by_aliyun_logic.go
Normal file
73
app/core/api/internal/logic/sms/send_sms_by_aliyun_logic.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
gosms "github.com/pkg6/go-sms"
|
||||
"github.com/pkg6/go-sms/gateways"
|
||||
"github.com/pkg6/go-sms/gateways/aliyun"
|
||||
|
||||
"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/response"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
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) (resp *types.Response, err error) {
|
||||
|
||||
checkRotateData := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
|
||||
if !checkRotateData {
|
||||
return response.ErrorWithI18n(l.ctx, "captcha.verificationFailure", "验证码错误"), nil
|
||||
}
|
||||
isPhone := utils.IsPhone(req.Phone)
|
||||
if !isPhone {
|
||||
return response.ErrorWithI18n(l.ctx, "login.phoneFormatError", "手机号格式错误"), nil
|
||||
}
|
||||
val := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
|
||||
if val != "" {
|
||||
return response.ErrorWithI18n(l.ctx, "sms.smsSendTooFrequently", "验证码发送过于频繁,请稍后再试"), nil
|
||||
}
|
||||
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 response.ErrorWithI18n(l.ctx, "sms.smsSendFailed", "验证码发送失败"), wrong
|
||||
}
|
||||
_, 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 response.ErrorWithI18n(l.ctx, "sms.smsSendFailed", "验证码发送失败"), err
|
||||
}
|
||||
return response.Success(), nil
|
||||
}
|
66
app/core/api/internal/logic/sms/send_sms_by_smsbao_logic.go
Normal file
66
app/core/api/internal/logic/sms/send_sms_by_smsbao_logic.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/core/api/common/captcha/verify"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/response"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
|
||||
"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) (resp *types.Response, err error) {
|
||||
checkRotateData := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
|
||||
if !checkRotateData {
|
||||
return response.ErrorWithI18n(l.ctx, "captcha.verificationFailure", "验证码错误"), nil
|
||||
}
|
||||
isPhone := utils.IsPhone(req.Phone)
|
||||
if !isPhone {
|
||||
return response.ErrorWithI18n(l.ctx, "login.phoneFormatError", "手机号格式错误"), nil
|
||||
}
|
||||
val := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
|
||||
if val != "" {
|
||||
return response.ErrorWithI18n(l.ctx, "sms.smsSendTooFrequently", "验证码发送过于频繁,请稍后再试"), nil
|
||||
}
|
||||
sms := gosms.NewParser(gateways.Gateways{
|
||||
SmsBao: smsbao.SmsBao{
|
||||
User: l.svcCtx.Config.SMS.SMSBao.Username,
|
||||
Password: l.svcCtx.Config.SMS.SMSBao.Password,
|
||||
},
|
||||
})
|
||||
code := utils.GenValidateCode(6)
|
||||
wrong := l.svcCtx.RedisClient.Set(l.ctx, constant.UserSmsRedisPrefix+req.Phone, code, time.Minute).Err()
|
||||
if wrong != nil {
|
||||
return response.ErrorWithI18n(l.ctx, "sms.smsSendFailed", "验证码发送失败"), wrong
|
||||
}
|
||||
_, err = sms.Send(req.Phone, gosms.MapStringAny{
|
||||
"content": "您的验证码是:" + code + "。请不要把验证码泄露给其他人。",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return response.ErrorWithI18n(l.ctx, "sms.smsSendFailed", "验证码发送失败"), err
|
||||
}
|
||||
return response.Success(), nil
|
||||
}
|
50
app/core/api/internal/logic/sms/send_sms_by_test_logic.go
Normal file
50
app/core/api/internal/logic/sms/send_sms_by_test_logic.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"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/response"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
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) (resp *types.Response, err error) {
|
||||
checkRotateData := verify.VerifyRotateCaptcha(l.ctx, l.svcCtx.RedisClient, req.Angle, req.Key)
|
||||
if !checkRotateData {
|
||||
return response.ErrorWithI18n(l.ctx, "captcha.verificationFailure", "验证码错误"), nil
|
||||
}
|
||||
isPhone := utils.IsPhone(req.Phone)
|
||||
if !isPhone {
|
||||
return response.ErrorWithI18n(l.ctx, "login.phoneFormatError", "手机号格式错误"), nil
|
||||
}
|
||||
val := l.svcCtx.RedisClient.Get(l.ctx, constant.UserSmsRedisPrefix+req.Phone).Val()
|
||||
if val != "" {
|
||||
return response.ErrorWithI18n(l.ctx, "sms.smsSendTooFrequently", "验证码发送过于频繁,请稍后再试"), nil
|
||||
}
|
||||
code := utils.GenValidateCode(6)
|
||||
wrong := l.svcCtx.RedisClient.Set(l.ctx, constant.UserSmsRedisPrefix+req.Phone, code, time.Minute).Err()
|
||||
if wrong != nil {
|
||||
return response.ErrorWithI18n(l.ctx, "sms.smsSendFailed", "验证码发送失败"), wrong
|
||||
}
|
||||
return response.Success(), nil
|
||||
}
|
@@ -5,8 +5,6 @@ import (
|
||||
"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"
|
||||
|
||||
@@ -19,7 +17,6 @@ import (
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuser"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuserdevice"
|
||||
)
|
||||
|
||||
type AccountLoginLogic struct {
|
||||
@@ -116,87 +113,14 @@ func HandleUserLogin(user *ent.ScaAuthUser, svcCtx *svc.ServiceContext, autoLogi
|
||||
return nil, false
|
||||
}
|
||||
session.Values[constant.SESSION_KEY] = sessionData
|
||||
if err = session.Save(r, w); err != nil {
|
||||
logc.Error(ctx, err)
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
// 记录用户登录设备
|
||||
if !getUserLoginDevice(user.UID, r, svcCtx.Ip2Region, svcCtx.MySQLClient, ctx) {
|
||||
if !GetUserLoginDevice(user.UID, r, svcCtx.Ip2Region, svcCtx.MySQLClient, 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) {
|
||||
// 创建新的设备记录
|
||||
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).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else if err == nil {
|
||||
// 如果设备存在,执行更新
|
||||
err = 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).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
logc.Error(ctx, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
133
app/core/api/internal/logic/user/get_user_device_logic.go
Normal file
133
app/core/api/internal/logic/user/get_user_device_logic.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"github.com/mssola/useragent"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/core/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent"
|
||||
"schisandra-album-cloud-microservices/app/core/api/repository/mysql/ent/scaauthuserdevice"
|
||||
)
|
||||
|
||||
type GetUserDeviceLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetUserDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserDeviceLogic {
|
||||
return &GetUserDeviceLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetUserDeviceLogic) GetUserDevice(r *http.Request) error {
|
||||
session, err := l.svcCtx.Session.Get(r, constant.SESSION_KEY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sessionData, ok := session.Values[constant.SESSION_KEY]
|
||||
if !ok {
|
||||
return errors.New("User not found or device not found")
|
||||
}
|
||||
var data types.SessionData
|
||||
err = json.Unmarshal(sessionData.([]byte), &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := GetUserLoginDevice(data.UID, r, l.svcCtx.Ip2Region, l.svcCtx.MySQLClient, l.ctx)
|
||||
if !res {
|
||||
return errors.New("User not found or device not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
logx.Error(err)
|
||||
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) {
|
||||
// 创建新的设备记录
|
||||
err = entClient.ScaAuthUserDevice.Create().
|
||||
SetUserID(userId).
|
||||
SetBot(isBot).
|
||||
SetAgent(userAgent).
|
||||
SetBrowser(browser).
|
||||
SetBrowserVersion(browserVersion).
|
||||
SetEngineName(engine).
|
||||
SetEngineVersion(engineVersion).
|
||||
SetIP(ip).
|
||||
SetLocation(location).
|
||||
SetOperatingSystem(os).
|
||||
SetMobile(mobile).
|
||||
SetMozilla(mozilla).
|
||||
SetPlatform(platform).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else if err == nil {
|
||||
// 如果设备存在,执行更新
|
||||
err = device.Update().
|
||||
SetUserID(userId).
|
||||
SetBot(isBot).
|
||||
SetAgent(userAgent).
|
||||
SetBrowser(browser).
|
||||
SetBrowserVersion(browserVersion).
|
||||
SetEngineName(engine).
|
||||
SetEngineVersion(engineVersion).
|
||||
SetIP(ip).
|
||||
SetLocation(location).
|
||||
SetOperatingSystem(os).
|
||||
SetMobile(mobile).
|
||||
SetMozilla(mozilla).
|
||||
SetPlatform(platform).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
logx.Error(err)
|
||||
return false
|
||||
}
|
||||
}
|
@@ -43,7 +43,7 @@ func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, w http.ResponseWriter, req
|
||||
if req.Captcha != code {
|
||||
return response.ErrorWithI18n(l.ctx, "login.captchaError", "验证码错误"), nil
|
||||
}
|
||||
user, err := l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.Phone(req.Phone), scaauthuser.Deleted(0)).Only(l.ctx)
|
||||
user, err := l.svcCtx.MySQLClient.ScaAuthUser.Query().Where(scaauthuser.Phone(req.Phone), scaauthuser.Deleted(0)).First(l.ctx)
|
||||
tx, wrong := l.svcCtx.MySQLClient.Tx(l.ctx)
|
||||
if wrong != nil {
|
||||
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败"), err
|
||||
@@ -54,7 +54,7 @@ func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, w http.ResponseWriter, req
|
||||
avatar := utils.GenerateAvatar(uidStr)
|
||||
name := randomname.GenerateName()
|
||||
|
||||
addUser, wrong := l.svcCtx.MySQLClient.ScaAuthUser.Create().
|
||||
addUser, fault := l.svcCtx.MySQLClient.ScaAuthUser.Create().
|
||||
SetUID(uidStr).
|
||||
SetPhone(req.Phone).
|
||||
SetAvatar(avatar).
|
||||
@@ -62,7 +62,12 @@ func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, w http.ResponseWriter, req
|
||||
SetDeleted(constant.NotDeleted).
|
||||
SetGender(constant.Male).
|
||||
Save(l.ctx)
|
||||
if wrong != nil {
|
||||
if fault != nil {
|
||||
err = tx.Rollback()
|
||||
return response.ErrorWithI18n(l.ctx, "login.registerError", "注册失败"), err
|
||||
}
|
||||
_, err = l.svcCtx.CasbinEnforcer.AddRoleForUser(uidStr, constant.User)
|
||||
if err != nil {
|
||||
err = tx.Rollback()
|
||||
return response.ErrorWithI18n(l.ctx, "login.registerError", "注册失败"), err
|
||||
}
|
||||
@@ -72,15 +77,21 @@ func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, w http.ResponseWriter, req
|
||||
return response.ErrorWithI18n(l.ctx, "login.registerError", "注册失败"), err
|
||||
}
|
||||
err = tx.Commit()
|
||||
return response.SuccessWithData(data), err
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
return response.SuccessWithData(data), nil
|
||||
} else if err == nil {
|
||||
data, result := HandleUserLogin(user, l.svcCtx, req.AutoLogin, r, w, l.ctx)
|
||||
if !result {
|
||||
err = tx.Rollback()
|
||||
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败"), err
|
||||
}
|
||||
err = tx.Commit()
|
||||
return response.SuccessWithData(data), err
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
return response.SuccessWithData(data), nil
|
||||
} else {
|
||||
return response.ErrorWithI18n(l.ctx, "login.loginFailed", "登录失败"), nil
|
||||
}
|
||||
|
193
app/core/api/internal/logic/websocket/message_websocket_logic.go
Normal file
193
app/core/api/internal/logic/websocket/message_websocket_logic.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/lxzan/gws"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/jwt"
|
||||
"schisandra-album-cloud-microservices/app/core/api/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 {
|
||||
redis *redis.Client
|
||||
ctx context.Context
|
||||
gws.BuiltinEventHandler
|
||||
sessions *gws.ConcurrentMap[string, *gws.Conn] // 使用内置的ConcurrentMap存储连接, 可以减少锁冲突
|
||||
}
|
||||
|
||||
var MessageWebSocketHandler *MessageWebSocket
|
||||
|
||||
// InitializeWebSocketHandler 初始化WebSocketHandler
|
||||
func InitializeWebSocketHandler(svcCtx *svc.ServiceContext) {
|
||||
MessageWebSocketHandler = NewMessageWebSocket(svcCtx)
|
||||
}
|
||||
func (l *MessageWebsocketLogic) MessageWebsocket(w http.ResponseWriter, r *http.Request) error {
|
||||
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
|
||||
}
|
||||
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
|
||||
},
|
||||
})
|
||||
socket, err := upgrader.Upgrade(w, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
socket.ReadLoop() // 此处阻塞会使请求上下文不能顺利被GC
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMessageWebSocket 创建WebSocket实例
|
||||
func NewMessageWebSocket(svcCtx *svc.ServiceContext) *MessageWebSocket {
|
||||
return &MessageWebSocket{
|
||||
redis: svcCtx.RedisClient,
|
||||
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)
|
||||
// 订阅该用户的频道
|
||||
go c.subscribeUserChannel(clientId, c.redis)
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// SendMessageToClient 向指定客户端发送消息
|
||||
func (c *MessageWebSocket) 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)
|
||||
}
|
||||
|
||||
// SendMessageToUser 发送消息到指定用户的 Redis 频道
|
||||
func (c *MessageWebSocket) SendMessageToUser(clientId string, message []byte, redis *redis.Client, ctx context.Context) error {
|
||||
if _, ok := c.sessions.Load(clientId); ok {
|
||||
return redis.Publish(ctx, clientId, message).Err()
|
||||
} else {
|
||||
return redis.LPush(ctx, constant.CommentOfflineMessagePrefix+clientId, message).Err()
|
||||
}
|
||||
}
|
||||
|
||||
// 订阅用户频道
|
||||
func (c *MessageWebSocket) subscribeUserChannel(clientId string, redis *redis.Client) {
|
||||
conn, ok := c.sessions.Load(clientId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取离线消息
|
||||
messages, err := redis.LRange(c.ctx, constant.CommentOfflineMessagePrefix+clientId, 0, -1).Result()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading offline messages for user %s: %v\n", clientId, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 逐条发送离线消息
|
||||
for _, msg := range messages {
|
||||
if writeErr := conn.WriteMessage(gws.OpcodeText, []byte(msg)); writeErr != nil {
|
||||
fmt.Printf("Error writing offline message to user %s: %v\n", clientId, writeErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 清空离线消息列表
|
||||
if delErr := redis.Del(c.ctx, constant.CommentOfflineMessagePrefix+clientId); delErr.Err() != nil {
|
||||
fmt.Printf("Error clearing offline messages for user %s: %v\n", clientId, delErr.Err())
|
||||
return
|
||||
}
|
||||
|
||||
pubsub := redis.Subscribe(c.ctx, clientId)
|
||||
defer func() {
|
||||
if closeErr := pubsub.Close(); closeErr != nil {
|
||||
fmt.Printf("Error closing pubsub for user %s: %v\n", clientId, closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msg, waitErr := pubsub.ReceiveMessage(context.Background())
|
||||
if waitErr != nil {
|
||||
fmt.Printf("Error receiving message for user %s: %v\n", clientId, err)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := conn.WriteMessage(gws.OpcodeText, []byte(msg.Payload)); writeErr != nil {
|
||||
fmt.Printf("Error writing message to user %s: %v\n", clientId, writeErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
134
app/core/api/internal/logic/websocket/qrcode_websocket_logic.go
Normal file
134
app/core/api/internal/logic/websocket/qrcode_websocket_logic.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/lxzan/gws"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/constant"
|
||||
"schisandra-album-cloud-microservices/app/core/api/common/utils"
|
||||
"schisandra-album-cloud-microservices/app/core/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) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
socket.ReadLoop() // 此处阻塞会使请求上下文不能顺利被GC
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
Reference in New Issue
Block a user