scan the QR code to log in on the WeChat public account

This commit is contained in:
landaiqing
2024-08-15 23:56:46 +08:00
parent 55759a33db
commit e8fbff7e7f
28 changed files with 1095 additions and 432 deletions

View File

@@ -2,6 +2,7 @@ package api
import (
"schisandra-cloud-album/api/captcha_api"
"schisandra-cloud-album/api/oauth_api"
"schisandra-cloud-album/api/sms_api"
"schisandra-cloud-album/api/user_api"
)
@@ -11,6 +12,7 @@ type Apis struct {
UserApi user_api.UserAPI
CaptchaApi captcha_api.CaptchaAPI
SmsApi sms_api.SmsAPI
OAuthApi oauth_api.OAuthAPI
}
// Api new函数实例化实例化完成后会返回结构体地指针类型

3
api/oauth_api/oauth.go Normal file
View File

@@ -0,0 +1,3 @@
package oauth_api
type OAuthAPI struct{}

281
api/oauth_api/oauth_api.go Normal file
View File

@@ -0,0 +1,281 @@
package oauth_api
import (
"encoding/json"
"errors"
"github.com/ArtisanCloud/PowerLibs/v3/fmt"
"github.com/ArtisanCloud/PowerLibs/v3/http/helper"
"github.com/ArtisanCloud/PowerWeChat/v3/src/basicService/qrCode/response"
"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"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
uuid "github.com/satori/go.uuid"
"github.com/yitter/idgenerator-go/idgen"
"gorm.io/gorm"
"schisandra-cloud-album/api/user_api/dto"
"schisandra-cloud-album/common/constant"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/common/redis"
"schisandra-cloud-album/common/result"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
"schisandra-cloud-album/service"
"schisandra-cloud-album/utils"
"strconv"
"strings"
"time"
)
var userService = service.Service.UserService
var userRoleService = service.Service.UserRoleService
var userSocialService = service.Service.UserSocialService
var rolePermissionService = service.Service.RolePermissionService
var permissionServiceService = service.Service.PermissionService
var roleService = service.Service.RoleService
// GenerateClientId 生成客户端ID
// @Summary 生成客户端ID
// @Description 生成客户端ID
// @Produce json
// @Success 200 {object} result.Result{data=string} "客户端ID"
// @Router /api/oauth/generate_client_id [get]
func (OAuthAPI) GenerateClientId(c *gin.Context) {
ip := c.ClientIP()
v1 := uuid.NewV1()
redis.Set(constant.UserLoginClientRedisKey+ip, v1.String(), 0)
result.OkWithData(v1.String(), c)
return
}
// CallbackNotify 微信回调验证
// @Summary 微信回调验证
// @Description 微信回调验证
// @Produce json
// @Success 200 {object} result.Result{data=string} "验证结果"
// @Router /api/oauth/callback_notify [POST]
func (OAuthAPI) CallbackNotify(c *gin.Context) {
rs, err := global.Wechat.Server.Notify(c.Request, func(event contract.EventInterface) interface{} {
fmt.Dump("event", event)
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_")
res := wechatLoginHandler(msg.FromUserName, key)
if !res {
return messages.NewText("登录失败")
}
return messages.NewText("登录成功")
case models.CALLBACK_EVENT_UNSUBSCRIBE:
msg := models.EventUnSubscribe{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
fmt.Dump(msg)
return messages.NewText("再见,我的宝!")
case models.CALLBACK_EVENT_SCAN:
msg := models.EventScan{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
res := wechatLoginHandler(msg.FromUserName, msg.EventKey)
if !res {
return messages.NewText("登录失败")
}
return messages.NewText("登录成功")
}
case models2.CALLBACK_MSG_TYPE_TEXT:
msg := models.MessageText{}
err := event.ReadMessage(&msg)
if err != nil {
println(err.Error())
return "error"
}
fmt.Dump(msg)
}
return messages.NewText("ok")
})
if err != nil {
panic(err)
}
err = helper.HttpResponseSend(rs, c.Writer)
if err != nil {
panic(err)
}
}
// CallbackVerify 微信回调验证
// @Summary 微信回调验证
// @Description 微信回调验证
// @Produce json
// @Success 200 {object} result.Result{data=string} "验证结果"
// @Router /api/oauth/callback_verify [get]
func (OAuthAPI) CallbackVerify(c *gin.Context) {
rs, err := global.Wechat.Server.VerifyURL(c.Request)
if err != nil {
panic(err)
}
err = helper.HttpResponseSend(rs, c.Writer)
}
// GetTempQrCode 获取临时二维码
// @Summary 获取临时二维码
// @Description 获取临时二维码
// @Produce json
// @Param client_id query string true "客户端ID"
// @Success 200 {object} result.Result{data=string} "临时二维码"
// @Router /api/oauth/get_temp_qrcode [get]
func (OAuthAPI) GetTempQrCode(c *gin.Context) {
clientId := c.Query("client_id")
if clientId == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
qrcode := redis.Get(constant.UserLoginQrcodeRedisKey + clientId).Val()
if qrcode != "" {
data := response.ResponseQRCodeCreate{}
err := json.Unmarshal([]byte(qrcode), &data)
if err != nil {
return
}
result.OK(ginI18n.MustGetMessage(c, "QRCodeGetSuccess"), data.Url, c)
return
}
data, err := global.Wechat.QRCode.Temporary(c.Request.Context(), clientId, 30*24*3600)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
serializedData, err := json.Marshal(data)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
wrong := redis.Set(constant.UserLoginQrcodeRedisKey+clientId, serializedData, time.Hour*24*30).Err()
if wrong != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "QRCodeGetFailed"), c)
return
}
result.OK(ginI18n.MustGetMessage(c, "QRCodeGetSuccess"), data.Url, c)
}
func wechatLoginHandler(openId string, clientId string) bool {
if openId == "" {
return false
}
authUserSocial, err := userSocialService.QueryUserSocialByOpenID(openId)
if errors.Is(err, gorm.ErrRecordNotFound) {
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
createUser := model.ScaAuthUser{
UID: &uidStr,
Username: &openId,
}
addUser, err := userService.AddUser(createUser)
if err != nil {
return false
}
wechat := enum.OAuthSourceWechat
userSocial := model.ScaAuthUserSocial{
UserID: &addUser.ID,
OpenID: &openId,
Source: &wechat,
}
wrong := userSocialService.AddUserSocial(userSocial)
if wrong != nil {
return false
}
userRole := model.ScaAuthUserRole{
UserID: addUser.ID,
RoleID: enum.User,
}
e := userRoleService.AddUserRole(userRole)
if e != nil {
return false
}
res := handelUserLogin(addUser, clientId)
if !res {
return false
}
return true
} else {
user, err := userService.QueryUserById(authUserSocial.UserID)
if err != nil {
return false
}
res := handelUserLogin(user, clientId)
if !res {
return false
}
return true
}
}
// handelUserLogin 处理用户登录
func handelUserLogin(user model.ScaAuthUser, clientId string) bool {
ids, err := userRoleService.GetUserRoleIdsByUserId(user.ID)
if err != nil {
return false
}
permissionIds := rolePermissionService.QueryPermissionIdsByRoleId(ids)
permissions, err := permissionServiceService.GetPermissionsByIds(permissionIds)
if err != nil {
return false
}
serializedPermissions, err := json.Marshal(permissions)
if err != nil {
return false
}
wrong := redis.Set(constant.UserAuthPermissionRedisKey+*user.UID, serializedPermissions, 0).Err()
if wrong != nil {
return false
}
roleList, err := roleService.GetRoleListByIds(ids)
if err != nil {
return false
}
serializedRoleList, err := json.Marshal(roleList)
if err != nil {
return false
}
er := redis.Set(constant.UserAuthRoleRedisKey+*user.UID, serializedRoleList, 0).Err()
if er != nil {
return false
}
accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: user.UID, RoleID: ids})
data := dto.ResponseData{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresAt: expiresAt,
UID: user.UID,
}
fail := redis.Set(constant.UserLoginTokenRedisKey+*user.UID, data, time.Hour*24*7).Err()
w := redis.Set(constant.UserLoginWechatRedisKey+clientId, data, time.Minute*5).Err()
if fail != nil || w != nil {
return false
}
return true
}

View File

@@ -13,6 +13,27 @@ type PhoneLoginRequest struct {
Captcha string `json:"captcha"`
}
// AccountLoginRequest 账号登录请求
type AccountLoginRequest struct {
Account string `json:"account"`
Password string `json:"password"`
}
// AddUserRequest 新增用户请求
type AddUserRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Phone string `json:"phone"`
}
// ResetPasswordRequest 重置密码请求
type ResetPasswordRequest struct {
Phone string `json:"phone"`
Captcha string `json:"captcha"`
Password string `json:"password"`
Repassword string `json:"repassword"`
}
// ResponseData 返回数据
type ResponseData struct {
AccessToken string `json:"access_token"`

View File

@@ -60,7 +60,11 @@ func (UserAPI) QueryUserByUsername(c *gin.Context) {
// @Router /api/auth/user/query_by_uuid [get]
func (UserAPI) QueryUserByUuid(c *gin.Context) {
uuid := c.Query("uuid")
user := userService.QueryUserByUuid(uuid)
user, err := userService.QueryUserByUuid(&uuid)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c)
return
}
if reflect.DeepEqual(user, model.ScaAuthUser{}) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c)
return
@@ -100,16 +104,81 @@ func (UserAPI) QueryUserByPhone(c *gin.Context) {
result.OkWithData(user, c)
}
// AddUser 添加用户
// @Summary 添加用户
// @Tags 鉴权模块
// @Param user body dto.AddUserRequest true "用户信息"
// @Success 200 {string} json
// @Router /api/user/add [post]
func (UserAPI) AddUser(c *gin.Context) {
addUserRequest := dto.AddUserRequest{}
err := c.ShouldBindJSON(&addUserRequest)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
username := userService.QueryUserByUsername(addUserRequest.Username)
if !reflect.DeepEqual(username, model.ScaAuthUser{}) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "UsernameExists"), c)
return
}
phone := userService.QueryUserByPhone(addUserRequest.Phone)
if !reflect.DeepEqual(phone, model.ScaAuthUser{}) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneExists"), c)
return
}
encrypt, err := utils.Encrypt(addUserRequest.Password)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "AddUserError"), c)
return
}
uid := idgen.NextId()
uidStr := strconv.FormatInt(uid, 10)
user := model.ScaAuthUser{
UID: &uidStr,
Username: &addUserRequest.Username,
Password: &encrypt,
Phone: &addUserRequest.Phone,
}
addUser, err := userService.AddUser(user)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "AddUserError"), c)
return
}
userRole := model.ScaAuthUserRole{
UserID: addUser.ID,
RoleID: enum.User,
}
e := userRoleService.AddUserRole(userRole)
if e != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "AddUserRoleError"), c)
return
}
result.OkWithMessage(ginI18n.MustGetMessage(c, "AddUserSuccess"), c)
return
}
// AccountLogin 账号登录
// @Summary 账号登录
// @Tags 鉴权模块
// @Param account query string true "账号"
// @Param password query string true "密码"
// @Param user body dto.AccountLoginRequest true "用户信息"
// @Success 200 {string} json
// @Router /api/user/login [post]
func (UserAPI) AccountLogin(c *gin.Context) {
account := c.PostForm("account")
password := c.PostForm("password")
accountLoginRequest := dto.AccountLoginRequest{}
err := c.ShouldBindJSON(&accountLoginRequest)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
account := accountLoginRequest.Account
password := accountLoginRequest.Password
if account == "" || password == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "AccountAndPasswordNotEmpty"), c)
return
}
isPhone := utils.IsPhone(account)
if isPhone {
user := userService.QueryUserByPhone(account)
@@ -117,9 +186,9 @@ func (UserAPI) AccountLogin(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotRegister"), c)
return
} else {
verify := utils.Verify(password, *user.Password)
verify := utils.Verify(*user.Password, password)
if verify {
result.OkWithData(user, c)
handelUserLogin(user, c)
return
} else {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c)
@@ -134,9 +203,9 @@ func (UserAPI) AccountLogin(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "EmailNotRegister"), c)
return
} else {
verify := utils.Verify(password, *user.Password)
verify := utils.Verify(*user.Password, password)
if verify {
result.OkWithData(user, c)
handelUserLogin(user, c)
return
} else {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c)
@@ -151,17 +220,18 @@ func (UserAPI) AccountLogin(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "UsernameNotRegister"), c)
return
} else {
verify := utils.Verify(password, *user.Password)
verify := utils.Verify(*user.Password, password)
if verify {
result.OkWithData(user, c)
handelUserLogin(user, c)
return
} else {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c)
return
}
}
}
result.FailWithMessage(ginI18n.MustGetMessage(c, "AccountErrorFormat"), c)
return
}
// PhoneLogin 手机号登录/注册
@@ -218,55 +288,7 @@ func (UserAPI) PhoneLogin(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
ids, err := userRoleService.GetUserRoleIdsByUserId(addUser.ID)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
permissionIds := rolePermissionService.QueryPermissionIdsByRoleId(ids)
permissions, err := permissionServiceService.GetPermissionsByIds(permissionIds)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
serializedPermissions, err := json.Marshal(permissions)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
wrong := redis.Set(constant.UserAuthPermissionRedisKey+*addUser.UID, serializedPermissions, 0).Err()
if wrong != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
roleList, err := roleService.GetRoleListByIds(ids)
if err != nil {
return
}
serializedRoleList, err := json.Marshal(roleList)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
er := redis.Set(constant.UserAuthRoleRedisKey+*addUser.UID, serializedRoleList, 0).Err()
if er != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: addUser.UID, RoleID: ids})
data := dto.ResponseData{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresAt: expiresAt,
UID: addUser.UID,
}
fail := redis.Set(constant.UserLoginTokenRedisKey+*addUser.UID, data, time.Hour*24*7).Err()
if fail != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
return
}
result.OkWithData(data, c)
handelUserLogin(addUser, c)
return
}
} else {
@@ -279,6 +301,73 @@ func (UserAPI) PhoneLogin(c *gin.Context) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c)
return
} else {
handelUserLogin(user, c)
return
}
}
}
}
// RefreshHandler 刷新token
// @Summary 刷新token
// @Tags 鉴权模块
// @Param refresh_token query string true "刷新token"
// @Success 200 {string} json
// @Router /api/token/refresh [post]
func (UserAPI) RefreshHandler(c *gin.Context) {
request := dto.RefreshTokenRequest{}
err := c.ShouldBindJSON(&request)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
refreshToken := request.RefreshToken
if refreshToken == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
plaintext, err := aes.AesCtrDecryptByHex(refreshToken, []byte(global.CONFIG.Encrypt.Key), []byte(global.CONFIG.Encrypt.IV))
if err != nil {
global.LOG.Error(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
return
}
parseRefreshToken, isUpd, err := utils.ParseToken(string(plaintext))
if err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
return
}
if isUpd {
accessTokenString, err := utils.GenerateAccessToken(utils.JWTPayload{UserID: parseRefreshToken.UserID, RoleID: parseRefreshToken.RoleID})
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
return
}
wrong := redis.Get(constant.UserLoginTokenRedisKey + *parseRefreshToken.UserID).Err()
if wrong != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
return
}
data := dto.ResponseData{
AccessToken: accessTokenString,
RefreshToken: refreshToken,
UID: parseRefreshToken.UserID,
}
fail := redis.Set("user:login:token:"+*parseRefreshToken.UserID, data, time.Hour*24*7).Err()
if fail != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
return
}
result.OkWithData(data, c)
return
}
}
// handelUserLogin 处理用户登录
func handelUserLogin(user model.ScaAuthUser, c *gin.Context) {
ids, err := userRoleService.GetUserRoleIdsByUserId(user.ID)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c)
@@ -330,64 +419,58 @@ func (UserAPI) PhoneLogin(c *gin.Context) {
result.OkWithData(data, c)
return
}
}
}
}
// RefreshHandler 刷新token
// @Summary 刷新token
// ResetPassword 重置密码
// @Summary 重置密码
// @Tags 鉴权模块
// @Param refresh_token query string true "刷新token"
// @Param user body dto.ResetPasswordRequest true "用户信息"
// @Success 200 {string} json
// @Router /api/auth/token/refresh [post]
func (UserAPI) RefreshHandler(c *gin.Context) {
request := dto.RefreshTokenRequest{}
err := c.ShouldBindJSON(&request)
// @Router /api/user/reset_password [post]
func (UserAPI) ResetPassword(c *gin.Context) {
resetPasswordRequest := dto.ResetPasswordRequest{}
err := c.ShouldBindJSON(&resetPasswordRequest)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
refreshToken := request.RefreshToken
if refreshToken == "" {
phone := resetPasswordRequest.Phone
captcha := resetPasswordRequest.Captcha
password := resetPasswordRequest.Password
repassword := resetPasswordRequest.Repassword
if phone == "" || captcha == "" || password == "" || repassword == "" {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c)
return
}
plaintext, err := aes.AesCtrDecryptByHex(refreshToken, []byte(global.CONFIG.Encrypt.Key), []byte(global.CONFIG.Encrypt.IV))
if err != nil {
global.LOG.Error(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
isPhone := utils.IsPhone(phone)
if !isPhone {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c)
return
}
parseRefreshToken, isUpd, err := utils.ParseToken(string(plaintext))
if err != nil {
global.LOG.Errorln(err)
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
code := redis.Get(constant.UserLoginSmsRedisKey + phone)
if code == nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaExpired"), c)
return
} else {
if captcha != code.Val() {
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c)
return
}
if isUpd {
accessTokenString, err := utils.GenerateAccessToken(utils.JWTPayload{UserID: parseRefreshToken.UserID, RoleID: parseRefreshToken.RoleID})
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
}
user := userService.QueryUserByPhone(phone)
if reflect.DeepEqual(user, model.ScaAuthUser{}) {
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotRegister"), c)
return
}
wrong := redis.Get(constant.UserLoginTokenRedisKey + *parseRefreshToken.UserID).Err()
encrypt, err := utils.Encrypt(password)
if err != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "ResetPasswordError"), c)
return
}
wrong := userService.UpdateUser(phone, encrypt)
if wrong != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
result.FailWithMessage(ginI18n.MustGetMessage(c, "ResetPasswordError"), c)
return
}
data := dto.ResponseData{
AccessToken: accessTokenString,
RefreshToken: refreshToken,
UID: parseRefreshToken.UserID,
}
fail := redis.Set("user:login:token:"+*parseRefreshToken.UserID, data, time.Hour*24*7).Err()
if fail != nil {
result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c)
result.OkWithMessage(ginI18n.MustGetMessage(c, "ResetPasswordSuccess"), c)
return
}
result.OkWithData(data, c)
return
}
}

View File

@@ -6,4 +6,7 @@ const (
UserLoginCaptchaRedisKey = "user:login:captcha:"
UserAuthRoleRedisKey = "user:auth:role:"
UserAuthPermissionRedisKey = "user:auth:permission:"
UserLoginClientRedisKey = "user:login:client:"
UserLoginQrcodeRedisKey = "user:login:qrcode:"
UserLoginWechatRedisKey = "user:wechat:token:"
)

View File

@@ -0,0 +1,7 @@
package enum
const (
OAuthSourceWechat = "wechat"
OAuthSourceQQ = "qq"
OAuthSourceWeibo = "weibo"
)

8
config/conf_wechat.go Normal file
View File

@@ -0,0 +1,8 @@
package config
type Wechat struct {
AppID string `json:"app-id"`
AppSecret string `json:"app-secret"`
Token string `json:"token"`
AESKey string `json:"aes-key"`
}

View File

@@ -8,4 +8,5 @@ type Config struct {
SMS SMS `yaml:"sms"`
JWT JWT `yaml:"jwt"`
Encrypt Encrypt `yaml:"encrypt"`
Wechat Wechat `yaml:"wechat"`
}

33
core/wechat.go Normal file
View File

@@ -0,0 +1,33 @@
package core
import (
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount"
"os"
"schisandra-cloud-album/global"
)
func InitWechat() {
OfficialAccountApp, err := officialAccount.NewOfficialAccount(&officialAccount.UserConfig{
AppID: "wx55251c2f83b9fc25",
Secret: "d511800cd53d248afe1260bb8aeed230",
Token: "LDQ20020618xxx",
AESKey: global.CONFIG.Wechat.AESKey,
//Log: officialAccount.Log{
// Level: "debug",
// File: "./wechat.log",
//},
ResponseType: os.Getenv("response_type"),
HttpDebug: true,
Debug: true,
Cache: kernel.NewRedisClient(&kernel.UniversalOptions{
Addrs: []string{global.CONFIG.Redis.Addr()},
Password: global.CONFIG.Redis.Password,
DB: 0,
}),
})
if err != nil {
panic(err)
}
global.Wechat = OfficialAccountApp
}

View File

@@ -56,38 +56,6 @@ const docTemplate = `{
}
}
},
"/api/auth/user/login": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "账号登录",
"parameters": [
{
"type": "string",
"description": "账号",
"name": "account",
"in": "query",
"required": true
},
{
"type": "string",
"description": "密码",
"name": "password",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/auth/user/query_by_phone": {
"get": {
"tags": [
@@ -163,33 +131,6 @@ const docTemplate = `{
}
}
},
"/api/auth/user/register": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "用户注册",
"parameters": [
{
"description": "用户信息",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ScaAuthUser"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/captcha/rotate/check": {
"post": {
"description": "验证旋转验证码",
@@ -414,7 +355,7 @@ const docTemplate = `{
"tags": [
"短信验证码"
],
"summary": "发送短信验证码",
"summary": "短信宝发送短信验证码",
"parameters": [
{
"type": "string",
@@ -426,74 +367,163 @@ const docTemplate = `{
],
"responses": {}
}
},
"/api/sms/test/send": {
"get": {
"description": "发送测试短信验证码",
"produces": [
"application/json"
],
"tags": [
"短信验证码"
],
"summary": "发送测试短信验证码",
"parameters": [
{
"type": "string",
"description": "手机号",
"name": "phone",
"in": "query",
"required": true
}
],
"responses": {}
}
},
"/api/token/refresh": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "刷新token",
"parameters": [
{
"type": "string",
"description": "刷新token",
"name": "refresh_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/user/add": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "添加用户",
"parameters": [
{
"description": "用户信息",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AddUserRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/user/login": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "账号登录",
"parameters": [
{
"description": "用户信息",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AccountLoginRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/user/phone_login": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "手机号登录/注册",
"parameters": [
{
"type": "string",
"description": "手机号",
"name": "phone",
"in": "query",
"required": true
},
{
"type": "string",
"description": "验证码",
"name": "captcha",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"dto.ScaAuthUser": {
"dto.AccountLoginRequest": {
"type": "object",
"properties": {
"avatar": {
"description": "头像",
"account": {
"type": "string"
},
"blog": {
"description": "博客",
"password": {
"type": "string"
}
}
},
"company": {
"description": "公司",
"type": "string"
},
"created_by": {
"description": "创建人",
"type": "string"
},
"created_time": {
"description": "创建时间",
"type": "string"
},
"email": {
"description": "邮箱",
"type": "string"
},
"gender": {
"description": "性别",
"type": "string"
},
"introduce": {
"description": "介绍",
"type": "string"
},
"location": {
"description": "地址",
"type": "string"
},
"nickname": {
"description": "昵称",
"dto.AddUserRequest": {
"type": "object",
"properties": {
"password": {
"type": "string"
},
"phone": {
"description": "电话",
"type": "string"
},
"status": {
"description": "状态 0 正常 1 封禁",
"type": "integer"
},
"update_by": {
"description": "更新人",
"type": "string"
},
"update_time": {
"description": "更新时间",
"type": "string"
},
"username": {
"description": "用户名",
"type": "string"
},
"uuid": {
"description": "唯一ID",
"type": "string"
}
}

View File

@@ -45,38 +45,6 @@
}
}
},
"/api/auth/user/login": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "账号登录",
"parameters": [
{
"type": "string",
"description": "账号",
"name": "account",
"in": "query",
"required": true
},
{
"type": "string",
"description": "密码",
"name": "password",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/auth/user/query_by_phone": {
"get": {
"tags": [
@@ -152,33 +120,6 @@
}
}
},
"/api/auth/user/register": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "用户注册",
"parameters": [
{
"description": "用户信息",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.ScaAuthUser"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/captcha/rotate/check": {
"post": {
"description": "验证旋转验证码",
@@ -403,7 +344,7 @@
"tags": [
"短信验证码"
],
"summary": "发送短信验证码",
"summary": "短信宝发送短信验证码",
"parameters": [
{
"type": "string",
@@ -415,74 +356,163 @@
],
"responses": {}
}
},
"/api/sms/test/send": {
"get": {
"description": "发送测试短信验证码",
"produces": [
"application/json"
],
"tags": [
"短信验证码"
],
"summary": "发送测试短信验证码",
"parameters": [
{
"type": "string",
"description": "手机号",
"name": "phone",
"in": "query",
"required": true
}
],
"responses": {}
}
},
"/api/token/refresh": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "刷新token",
"parameters": [
{
"type": "string",
"description": "刷新token",
"name": "refresh_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/user/add": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "添加用户",
"parameters": [
{
"description": "用户信息",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AddUserRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/user/login": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "账号登录",
"parameters": [
{
"description": "用户信息",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AccountLoginRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/api/user/phone_login": {
"post": {
"tags": [
"鉴权模块"
],
"summary": "手机号登录/注册",
"parameters": [
{
"type": "string",
"description": "手机号",
"name": "phone",
"in": "query",
"required": true
},
{
"type": "string",
"description": "验证码",
"name": "captcha",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"model.ScaAuthUser": {
"dto.AccountLoginRequest": {
"type": "object",
"properties": {
"avatar": {
"description": "头像",
"account": {
"type": "string"
},
"blog": {
"description": "博客",
"password": {
"type": "string"
}
}
},
"company": {
"description": "公司",
"type": "string"
},
"created_by": {
"description": "创建人",
"type": "string"
},
"created_time": {
"description": "创建时间",
"type": "string"
},
"email": {
"description": "邮箱",
"type": "string"
},
"gender": {
"description": "性别",
"type": "string"
},
"introduce": {
"description": "介绍",
"type": "string"
},
"location": {
"description": "地址",
"type": "string"
},
"nickname": {
"description": "昵称",
"dto.AddUserRequest": {
"type": "object",
"properties": {
"password": {
"type": "string"
},
"phone": {
"description": "电话",
"type": "string"
},
"status": {
"description": "状态 0 正常 1 封禁",
"type": "integer"
},
"update_by": {
"description": "更新人",
"type": "string"
},
"update_time": {
"description": "更新时间",
"type": "string"
},
"username": {
"description": "用户名",
"type": "string"
},
"uuid": {
"description": "唯一ID",
"type": "string"
}
}

View File

@@ -1,53 +1,18 @@
definitions:
model.ScaAuthUser:
dto.AccountLoginRequest:
properties:
avatar:
description: 头像
account:
type: string
blog:
description: 博客
password:
type: string
company:
description: 公司
type: string
created_by:
description: 创建人
type: string
created_time:
description: 创建时间
type: string
email:
description: 邮箱
type: string
gender:
description: 性别
type: string
introduce:
description: 介绍
type: string
location:
description: 地址
type: string
nickname:
description: 昵称
type: object
dto.AddUserRequest:
properties:
password:
type: string
phone:
description: 电话
type: string
status:
description: 状态 0 正常 1 封禁
type: integer
update_by:
description: 更新人
type: string
update_time:
description: 更新时间
type: string
username:
description: 用户名
type: string
uuid:
description: 唯一ID
type: string
type: object
info:
@@ -79,27 +44,6 @@ paths:
summary: 删除用户
tags:
- 鉴权模块
/api/auth/user/login:
post:
parameters:
- description: 账号
in: query
name: account
required: true
type: string
- description: 密码
in: query
name: password
required: true
type: string
responses:
"200":
description: OK
schema:
type: string
summary: 账号登录
tags:
- 鉴权模块
/api/auth/user/query_by_phone:
get:
parameters:
@@ -148,23 +92,6 @@ paths:
summary: 根据uuid查询用户
tags:
- 鉴权模块
/api/auth/user/register:
post:
parameters:
- description: 用户信息
in: body
name: user
required: true
schema:
$ref: '#/definitions/model.ScaAuthUser'
responses:
"200":
description: OK
schema:
type: string
summary: 用户注册
tags:
- 鉴权模块
/api/captcha/rotate/check:
post:
description: 验证旋转验证码
@@ -319,7 +246,93 @@ paths:
produces:
- application/json
responses: {}
summary: 发送短信验证码
summary: 短信宝发送短信验证码
tags:
- 短信验证码
/api/sms/test/send:
get:
description: 发送测试短信验证码
parameters:
- description: 手机号
in: query
name: phone
required: true
type: string
produces:
- application/json
responses: {}
summary: 发送测试短信验证码
tags:
- 短信验证码
/api/token/refresh:
post:
parameters:
- description: 刷新token
in: query
name: refresh_token
required: true
type: string
responses:
"200":
description: OK
schema:
type: string
summary: 刷新token
tags:
- 鉴权模块
/api/user/add:
post:
parameters:
- description: 用户信息
in: body
name: user
required: true
schema:
$ref: '#/definitions/dto.AddUserRequest'
responses:
"200":
description: OK
schema:
type: string
summary: 添加用户
tags:
- 鉴权模块
/api/user/login:
post:
parameters:
- description: 用户信息
in: body
name: user
required: true
schema:
$ref: '#/definitions/dto.AccountLoginRequest'
responses:
"200":
description: OK
schema:
type: string
summary: 账号登录
tags:
- 鉴权模块
/api/user/phone_login:
post:
parameters:
- description: 手机号
in: query
name: phone
required: true
type: string
- description: 验证码
in: query
name: captcha
required: true
type: string
responses:
"200":
description: OK
schema:
type: string
summary: 手机号登录/注册
tags:
- 鉴权模块
swagger: "2.0"

View File

@@ -1,6 +1,7 @@
package global
import (
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount"
"github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
"github.com/wenlng/go-captcha/v2/click"
@@ -22,4 +23,5 @@ var (
RotateCaptcha rotate.Captcha
SlideRegionCaptcha slide.Captcha
REDIS *redis.Client
Wechat *officialAccount.OfficialAccount
)

10
go.mod
View File

@@ -25,10 +25,14 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/ArtisanCloud/PowerLibs/v3 v3.2.5 // indirect
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 // indirect
github.com/ArtisanCloud/PowerWeChat/v3 v3.2.38 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bytedance/sonic v1.12.0 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -56,13 +60,19 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg6/go-requests v0.2.2 // indirect
github.com/pkg6/go-sms v0.1.2 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/wumansgy/goEncrypt v1.1.0 // indirect
github.com/yitter/idgenerator-go v1.3.3 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.19.0 // indirect

20
go.sum
View File

@@ -1,5 +1,11 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/ArtisanCloud/PowerLibs/v3 v3.2.5 h1:W3NKBTnh4d5RBzondNo++QdZ99HPYs5TQKMH2gngKvk=
github.com/ArtisanCloud/PowerLibs/v3 v3.2.5/go.mod h1:XFRnJA+D0b0IoeSk2ceZzBp9qxatMHOGtWdZCa/r/3U=
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 h1:P+erNlErr+X2v7Et+yTWaTfIRhw+HfpAPdvNIEwk9Gw=
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7/go.mod h1:VZQNCvcK/rldF3QaExiSl1gJEAkyc5/I8RLOd3WFZq4=
github.com/ArtisanCloud/PowerWeChat/v3 v3.2.38 h1:f2gDqq4s3FR3wEdaMJM/Cr3gRhSG3usjQhhbM3Tj+eU=
github.com/ArtisanCloud/PowerWeChat/v3 v3.2.38/go.mod h1:9CbKc6nODhoM8TVjoXqujrAr7zrTBUlr0Z7daFJVAJI=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -17,6 +23,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@@ -115,8 +123,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg6/go-requests v0.2.2 h1:wL0aFmyybM/Wuqj8xQa3sNL5ioAL97hQZ78TJovltbM=
github.com/pkg6/go-requests v0.2.2/go.mod h1:/rcVm8Itd2djtxDVxjRnHURChV86TB4ooZnP+IBZBmg=
github.com/pkg6/go-sms v0.1.2 h1:HZQlBkRVF9xQHhyCMB3kXY/kltfvuNgMTKuN/DoSg7w=
@@ -127,6 +139,8 @@ github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -162,6 +176,12 @@ github.com/wumansgy/goEncrypt v1.1.0/go.mod h1:dWgF7mi5Ujmt8V5EoyRqjH6XtZ8wmNQyT
github.com/yitter/idgenerator-go v1.3.3 h1:i6rzmpbCL0vlmr/tuW5+lSQzNuDG9vYBjIYRvnRcHE8=
github.com/yitter/idgenerator-go v1.3.3/go.mod h1:VVjbqFjGUsIkaXVkXEdmx1LiXUL3K1NvyxWPJBPbBpE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@@ -41,3 +41,14 @@ PhoneAndCaptchaNotEmpty = "phone number and captcha can not be empty!"
PhoneErrorFormat = "phone number error format!"
RegisterUserError = "register user error!"
LoginExpired = "login expired!, please try again!"
AccountAndPasswordNotEmpty = "account and password can not be empty!"
UserNotRegister = "user not register!"
AccountErrorFormat = "account error format!"
AddUserError = "add user error!"
AddUserRoleError = "add user role error!"
AddUserSuccess = "add user success!"
ResetPasswordError = "reset password error!"
ResetPasswordSuccess = "reset password success!"
QRCodeGetFailed = "qr code get failed!"
QRCodeGetSuccess = "qr code get successfully!"
QRCodeExpired = "qr code expired!"

View File

@@ -41,3 +41,15 @@ PhoneAndCaptchaNotEmpty = "手机号和验证码不能为空!"
PhoneErrorFormat = "手机号格式错误!"
RegisterUserError = "注册用户错误!"
LoginExpired = "登录已过期!,请重新登录!"
AccountAndPasswordNotEmpty = "账号和密码不能为空!"
UserNotRegister = "用户未注册!"
AccountErrorFormat = "账号格式错误!"
AddUserError = "添加用户错误!"
AddUserRoleError = "添加用户角色错误!"
AddUserSuccess = "添加用户成功!"
ResetPasswordError = "重置密码错误!"
ResetPasswordSuccess = "重置密码成功!"
QRCodeGetFailed = "获取二维码失败!"
QRCodeGetSuccess = "获取二维码成功!"
QRCodeExpired = "二维码已过期!"

View File

@@ -15,6 +15,7 @@ func main() {
core.InitRedis() // 初始化redis
core.InitCaptcha() // 初始化验证码
core.InitIDGenerator() // 初始化ID生成器
core.InitWechat() // 初始化微信
// 命令行参数绑定
option := cmd.Parse()
if cmd.IsStopWeb(&option) {

View File

@@ -9,14 +9,13 @@ const TableNameScaAuthUserSocial = "sca_auth_user_social"
// ScaAuthUserSocial 社会用户信息表
type ScaAuthUserSocial struct {
ID int64 `gorm:"column:id;type:bigint(20);primaryKey;comment:主键ID" json:"id"` // 主键ID
UserID int64 `gorm:"column:user_id;type:bigint(20);not null;comment:用户ID" json:"user_id"` // 用户ID
UserID *int64 `gorm:"column:user_id;type:bigint(20);not null;comment:用户ID" json:"user_id"` // 用户ID
UUID *string `gorm:"column:uuid;type:varchar(255);comment:第三方系统的唯一ID" json:"uuid"` // 第三方系统的唯一ID
Source *string `gorm:"column:source;type:varchar(255);comment:第三方用户来源" json:"source"` // 第三方用户来源
AccessToken *string `gorm:"column:access_token;type:varchar(255);comment:用户的授权令牌" json:"access_token"` // 用户的授权令牌
ExpireIn *int64 `gorm:"column:expire_in;type:int(11);comment:第三方用户的授权令牌的有效期" json:"expire_in"` // 第三方用户的授权令牌的有效期
RefreshToken *string `gorm:"column:refresh_token;type:varchar(255);comment:刷新令牌" json:"refresh_token"` // 刷新令牌
OpenID *string `gorm:"column:open_id;type:varchar(255);comment:第三方用户的 open id" json:"open_id"` // 第三方用户的 open id
UID *string `gorm:"column:uid;type:varchar(255);comment:第三方用户的 ID" json:"uid"` // 第三方用户的 ID
AccessCode *string `gorm:"column:access_code;type:varchar(255);comment:个别平台的授权信息" json:"access_code"` // 个别平台的授权信息
UnionID *string `gorm:"column:union_id;type:varchar(255);comment:第三方用户的 union id" json:"union_id"` // 第三方用户的 union id
Scope *string `gorm:"column:scope;type:varchar(255);comment:第三方用户授予的权限" json:"scope"` // 第三方用户授予的权限
@@ -27,13 +26,13 @@ type ScaAuthUserSocial struct {
Code *string `gorm:"column:code;type:varchar(255);comment:用户的授权code" json:"code"` // 用户的授权code
OauthToken *string `gorm:"column:oauth_token;type:varchar(255);comment:Twitter平台用户的附带属性" json:"oauth_token"` // Twitter平台用户的附带属性
OauthTokenSecret *string `gorm:"column:oauth_token_secret;type:varchar(255);comment:Twitter平台用户的附带属性" json:"oauth_token_secret"` // Twitter平台用户的附带属性
Status *string `gorm:"column:status;type:varchar(255);comment:状态 0正常 1 封禁" json:"status"` // 状态 0正常 1 封禁
Status *string `gorm:"column:status;type:varchar(255);default:0;comment:状态 0正常 1 封禁" json:"status"` // 状态 0正常 1 封禁
ExtJSON *string `gorm:"column:ext_json;type:varchar(255);comment:额外字段" json:"ext_json"` // 额外字段
CreatedBy *string `gorm:"column:created_by;type:varchar(32);comment:创建人" json:"created_by"` // 创建人
CreatedTime *time.Time `gorm:"column:created_time;type:datetime;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_time"` // 创建时间
UpdateBy *string `gorm:"column:update_by;type:varchar(32);comment:更新人" json:"update_by"` // 更新人
UpdateTime *time.Time `gorm:"column:update_time;type:datetime;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_time"` // 更新时间
Deleted *int64 `gorm:"column:deleted;type:int(11);comment:是否删除" json:"deleted"` // 是否删除
Deleted *int64 `gorm:"column:deleted;type:int(11);default:0;comment:是否删除" json:"deleted"` // 是否删除
}
// TableName ScaAuthUserSocial's table name

View File

@@ -0,0 +1,16 @@
package modules
import (
"github.com/gin-gonic/gin"
"schisandra-cloud-album/api"
)
var oauth = api.Api.OAuthApi
func OauthRouter(router *gin.RouterGroup) {
group := router.Group("/oauth")
group.GET("/generate_client_id", oauth.GenerateClientId)
group.GET("/get_temp_qrcode", oauth.GetTempQrCode)
//group.GET("/callback", oauth.CallbackVerify)
group.POST("/callback", oauth.CallbackNotify)
}

View File

@@ -14,6 +14,8 @@ func UserRouter(router *gin.RouterGroup) {
{
userGroup.POST("/login", userApi.AccountLogin)
userGroup.POST("/phone_login", userApi.PhoneLogin)
userGroup.POST("/add", userApi.AddUser)
userGroup.POST("/reset_password", userApi.ResetPassword)
}
authGroup := router.Group("auth").Use(middleware.JWTAuthMiddleware())
{

View File

@@ -26,5 +26,6 @@ func InitRouter() *gin.Engine {
modules.UserRouter(publicGroup) // 注册鉴权路由
modules.CaptchaRouter(publicGroup) // 注册验证码路由
modules.SmsRouter(publicGroup) // 注册短信验证码路由
modules.OauthRouter(publicGroup) // 注册oauth路由
return router
}

View File

@@ -6,6 +6,7 @@ import (
"schisandra-cloud-album/service/role_service"
"schisandra-cloud-album/service/user_role_service"
"schisandra-cloud-album/service/user_service"
"schisandra-cloud-album/service/user_social_service"
)
// Services 统一导出的service
@@ -15,6 +16,7 @@ type Services struct {
UserRoleService user_role_service.UserRoleService
RolePermissionService role_permission_service.RolePermissionService
PermissionService permission_service.PermissionService
UserSocialService user_social_service.UserSocialService
}
// Service new函数实例化实例化完成后会返回结构体地指针类型

View File

@@ -1,7 +1,6 @@
package user_service
import (
"gorm.io/gorm"
"schisandra-cloud-album/common/enum"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
@@ -22,10 +21,21 @@ func (UserService) QueryUserByUsername(username string) model.ScaAuthUser {
}
// QueryUserByUuid 根据用户uuid查询用户
func (UserService) QueryUserByUuid(uuid string) model.ScaAuthUser {
func (UserService) QueryUserByUuid(uuid *string) (model.ScaAuthUser, error) {
authUser := model.ScaAuthUser{}
global.DB.Where("uuid = ? and deleted = 0", uuid).First(&authUser)
return authUser
if err := global.DB.Where("uid = ? and deleted = 0", uuid).First(&authUser).Error; err != nil {
return model.ScaAuthUser{}, err
}
return authUser, nil
}
// QueryUserById 根据用户id查询用户
func (UserService) QueryUserById(id *int64) (model.ScaAuthUser, error) {
authUser := model.ScaAuthUser{}
if err := global.DB.Where("id = ? and deleted = 0", id).First(&authUser).Error; err != nil {
return model.ScaAuthUser{}, err
}
return authUser, nil
}
// AddUser 添加用户
@@ -42,15 +52,14 @@ func (UserService) AddUser(user model.ScaAuthUser) (model.ScaAuthUser, error) {
}
// UpdateUser 更新用户
func (UserService) UpdateUser(user model.ScaAuthUser) *gorm.DB {
authUser := model.ScaAuthUser{}
return global.DB.Model(&authUser).Where("uuid = ?", user.UID).Updates(user)
func (UserService) UpdateUser(phone string, password string) error {
return global.DB.Model(&model.ScaAuthUser{}).Where("phone = ? and deleted = 0", phone).Updates(&model.ScaAuthUser{Password: &password}).Error
}
// DeleteUser 删除用户
func (UserService) DeleteUser(uuid string) error {
authUser := model.ScaAuthUser{}
return global.DB.Model(&authUser).Where("uuid = ?", uuid).Updates(&model.ScaAuthUser{Deleted: &enum.DELETED}).Error
return global.DB.Model(&authUser).Where("uid = ?", uuid).Updates(&model.ScaAuthUser{Deleted: &enum.DELETED}).Error
}
// QueryUserByPhone 根据手机号查询用户

View File

@@ -0,0 +1,3 @@
package user_social_service
type UserSocialService struct{}

View File

@@ -0,0 +1,31 @@
package user_social_service
import (
"errors"
"gorm.io/gorm"
"schisandra-cloud-album/global"
"schisandra-cloud-album/model"
)
// AddUserSocial 添加社会化登录用户信息
func (UserSocialService) AddUserSocial(user model.ScaAuthUserSocial) error {
result := global.DB.Create(&user)
if result.Error != nil {
return result.Error
}
return nil
}
// QueryUserSocialByOpenID 根据openID查询用户信息
func (UserSocialService) QueryUserSocialByOpenID(openID string) (model.ScaAuthUserSocial, error) {
var user model.ScaAuthUserSocial
result := global.DB.Where("open_id = ? and deleted = 0", openID).First(&user)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return model.ScaAuthUserSocial{}, result.Error
}
return model.ScaAuthUserSocial{}, result.Error
}
return user, nil
}

29
wechat/info.log Normal file
View File

@@ -0,0 +1,29 @@
{"L":"INFO","timestamp":"2024-08-15T16:56:15+08:00","M":"Server response created:","content":"269685512388607999"}
{"L":"INFO","timestamp":"2024-08-15T16:58:26+08:00","M":"GET https://api.weixin.qq.com/cgi-bin/token?appid=&grant_type=client_credential&neededText=&secret= request header: { Accept:*/*} "}
{"L":"INFO","timestamp":"2024-08-15T16:58:27+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 74\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 08:58:27 GMT\r\nLogicret: 41002\r\nRetkey: 11\r\n\r\n{\"errcode\":41002,\"errmsg\":\"appid missing rid: 66bdc333-1a4b473f-4fe5f5c6\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:00:28+08:00","M":"GET https://api.weixin.qq.com/cgi-bin/token?appid=&grant_type=client_credential&neededText=&secret= request header: { Accept:*/*} "}
{"L":"INFO","timestamp":"2024-08-15T17:00:28+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 74\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:00:28 GMT\r\nLogicret: 41002\r\nRetkey: 11\r\n\r\n{\"errcode\":41002,\"errmsg\":\"appid missing rid: 66bdc3ac-59196e03-5ed1ff84\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:08:50+08:00","M":"GET https://api.weixin.qq.com/cgi-bin/token?appid=&grant_type=client_credential&neededText=&secret= request header: { Accept:*/*} "}
{"L":"INFO","timestamp":"2024-08-15T17:08:50+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 74\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:08:50 GMT\r\nLogicret: 41002\r\nRetkey: 11\r\n\r\n{\"errcode\":41002,\"errmsg\":\"appid missing rid: 66bdc5a2-10088a26-2e3835d6\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:19:18+08:00","M":"GET https://api.weixin.qq.com/cgi-bin/token?appid=wx55251c2f83b9fc25&grant_type=client_credential&neededText=&secret=d511800cd53d248afe1260bb8aeed230 request header: { Accept:*/*} "}
{"L":"INFO","timestamp":"2024-08-15T17:19:18+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 173\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:19:18 GMT\r\n\r\n{\"access_token\":\"83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT\",\"expires_in\":7200}"}
{"L":"INFO","timestamp":"2024-08-15T17:19:18+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Content-Type:application/jsonAccept:*/*} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:19:18+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:19:18 GMT\r\n\r\n{\"ticket\":\"gQFJ8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyb1Fobk5TbmxjbkYxUW1sQk5DY3IAAgQWyL1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02oQhnNSnlcnF1QmlBNCcr\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:20:32+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Accept:*/*Content-Type:application/json} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:20:32+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:20:32 GMT\r\n\r\n{\"ticket\":\"gQFX8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAycXpEVk1IbmxjbkYxUndsQjFDY0kAAgRgyL1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02qzDVMHnlcnF1RwlB1CcI\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:20:38+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Content-Type:application/jsonAccept:*/*} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:20:38+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:20:38 GMT\r\n\r\n{\"ticket\":\"gQFs8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyYlpseE1YbmxjbkYxUkNsQjFDYzcAAgRmyL1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02bZlxMXnlcnF1RClB1Cc7\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:21:32+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Accept:*/*Content-Type:application/json} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:21:32+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:21:32 GMT\r\n\r\n{\"ticket\":\"gQGv8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAydnlNSk5tbmxjbkYxU3NsQnhDY0oAAgScyL1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02vyMJNmnlcnF1SslBxCcJ\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:22:21+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Content-Type:application/jsonAccept:*/*} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:22:21+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:22:21 GMT\r\n\r\n{\"ticket\":\"gQHJ8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyaE8xaU5obmxjbkYxVGRsQmhDY1cAAgTNyL1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02hO1iNhnlcnF1TdlBhCcW\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:22:39+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Content-Type:application/jsonAccept:*/*} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:22:39+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:22:39 GMT\r\n\r\n{\"ticket\":\"gQHz8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyX3k1Yk5XbmxjbkYxVHZsQjFDYzMAAgTfyL1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02_y5bNWnlcnF1TvlB1Cc3\"}"}
{"L":"INFO","timestamp":"2024-08-15T17:24:17+08:00","M":"POST https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=83_HgqCjNIkKPGe_jIVDhx5LSmwI8Q7Am7-Acr2FeDNla7HeXGu-A-rFCgHSrA9d2sH9eUCvo7g_0KkE6tlcpfoX75Dn8wkmmwgA-EfVwiPUOA1uMY0vy0Hf9u-6acCHMaAAAJOT&debug=1 request header: { Content-Type:application/jsonAccept:*/*} request body:{\"action_info\":{\"scene\":{\"scene_str\":\"7cd96062-5ade-11ef-9e17-902e16172287\"}},\"action_name\":\"QR_STR_SCENE\",\"expire_seconds\":2592000}\n"}
{"L":"INFO","timestamp":"2024-08-15T17:24:17+08:00","M":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 192\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Thu, 15 Aug 2024 09:24:17 GMT\r\n\r\n{\"ticket\":\"gQGL8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyWndrRE5GbmxjbkYxUjFtQnhDYzcAAgRByb1mAwQAjScA\",\"expire_seconds\":2592000,\"url\":\"http:\\/\\/weixin.qq.com\\/q\\/02ZwkDNFnlcnF1R1mBxCc7\"}"}
{"L":"INFO","timestamp":"2024-08-15T21:46:59+08:00","M":"Server response created:","content":"2932968073927229439"}
{"L":"INFO","timestamp":"2024-08-15T21:47:37+08:00","M":"Server response created:","content":"6631222197266362111"}
{"L":"INFO","timestamp":"2024-08-15T21:47:48+08:00","M":"Server response created:","content":"3904152095992776479"}
{"L":"INFO","timestamp":"2024-08-15T21:48:35+08:00","M":"Server response created:","content":"7016009957992470331"}
{"L":"INFO","timestamp":"2024-08-15T21:51:45+08:00","M":"Server response created:","content":"4905816075205062651"}
{"L":"INFO","timestamp":"2024-08-15T23:24:38+08:00","M":"Server response created:","content":"3390456157377093054"}