diff --git a/api/api.go b/api/api.go index 2437f40..6649789 100644 --- a/api/api.go +++ b/api/api.go @@ -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函数实例化,实例化完成后会返回结构体地指针类型 diff --git a/api/oauth_api/oauth.go b/api/oauth_api/oauth.go new file mode 100644 index 0000000..517eb32 --- /dev/null +++ b/api/oauth_api/oauth.go @@ -0,0 +1,3 @@ +package oauth_api + +type OAuthAPI struct{} diff --git a/api/oauth_api/oauth_api.go b/api/oauth_api/oauth_api.go new file mode 100644 index 0000000..162d0da --- /dev/null +++ b/api/oauth_api/oauth_api.go @@ -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 +} diff --git a/api/user_api/dto/request_dto.go b/api/user_api/dto/request_dto.go index eda15cf..ae00067 100644 --- a/api/user_api/dto/request_dto.go +++ b/api/user_api/dto/request_dto.go @@ -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"` diff --git a/api/user_api/user_api.go b/api/user_api/user_api.go index c6cf33d..17065a8 100644 --- a/api/user_api/user_api.go +++ b/api/user_api/user_api.go @@ -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,55 +301,7 @@ func (UserAPI) PhoneLogin(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c) return } else { - ids, err := userRoleService.GetUserRoleIdsByUserId(user.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+*user.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+*user.UID, serializedRoleList, 0).Err() - if er != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) - return - } - 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() - if fail != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) - return - } - result.OkWithData(data, c) + handelUserLogin(user, c) return } } @@ -341,7 +315,7 @@ func (UserAPI) PhoneLogin(c *gin.Context) { // @Tags 鉴权模块 // @Param refresh_token query string true "刷新token" // @Success 200 {string} json -// @Router /api/auth/token/refresh [post] +// @Router /api/token/refresh [post] func (UserAPI) RefreshHandler(c *gin.Context) { request := dto.RefreshTokenRequest{} err := c.ShouldBindJSON(&request) @@ -391,3 +365,112 @@ func (UserAPI) RefreshHandler(c *gin.Context) { 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) + 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+*user.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+*user.UID, serializedRoleList, 0).Err() + if er != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + 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() + if fail != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + result.OkWithData(data, c) + return +} + +// ResetPassword 重置密码 +// @Summary 重置密码 +// @Tags 鉴权模块 +// @Param user body dto.ResetPasswordRequest true "用户信息" +// @Success 200 {string} json +// @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 + } + 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 + } + isPhone := utils.IsPhone(phone) + if !isPhone { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c) + return + } + 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 + } + } + user := userService.QueryUserByPhone(phone) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotRegister"), c) + return + } + 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, "ResetPasswordError"), c) + return + } + result.OkWithMessage(ginI18n.MustGetMessage(c, "ResetPasswordSuccess"), c) + return +} diff --git a/common/constant/redis_key.go b/common/constant/redis_key.go index 2a7c5a5..9243008 100644 --- a/common/constant/redis_key.go +++ b/common/constant/redis_key.go @@ -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:" ) diff --git a/common/enum/oauth_source.go b/common/enum/oauth_source.go new file mode 100644 index 0000000..fc955ca --- /dev/null +++ b/common/enum/oauth_source.go @@ -0,0 +1,7 @@ +package enum + +const ( + OAuthSourceWechat = "wechat" + OAuthSourceQQ = "qq" + OAuthSourceWeibo = "weibo" +) diff --git a/config/conf_wechat.go b/config/conf_wechat.go new file mode 100644 index 0000000..1c514b1 --- /dev/null +++ b/config/conf_wechat.go @@ -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"` +} diff --git a/config/config.go b/config/config.go index 69aaa5a..6e10c90 100644 --- a/config/config.go +++ b/config/config.go @@ -8,4 +8,5 @@ type Config struct { SMS SMS `yaml:"sms"` JWT JWT `yaml:"jwt"` Encrypt Encrypt `yaml:"encrypt"` + Wechat Wechat `yaml:"wechat"` } diff --git a/core/wechat.go b/core/wechat.go new file mode 100644 index 0000000..6865f91 --- /dev/null +++ b/core/wechat.go @@ -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 +} diff --git a/docs/docs.go b/docs/docs.go index 7c1e0e5..9d558ee 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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" } } diff --git a/docs/swagger.json b/docs/swagger.json index c5caaa5..f147c67 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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" } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c710b55..15fe106 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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" diff --git a/global/global.go b/global/global.go index 1688795..48cf83a 100644 --- a/global/global.go +++ b/global/global.go @@ -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 ) diff --git a/go.mod b/go.mod index 2e4a745..fbe79c8 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index eeb855c..10c9e5d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/i18n/language/en.toml b/i18n/language/en.toml index e4e6623..960e799 100644 --- a/i18n/language/en.toml +++ b/i18n/language/en.toml @@ -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!" diff --git a/i18n/language/zh.toml b/i18n/language/zh.toml index e47187a..7a4e828 100644 --- a/i18n/language/zh.toml +++ b/i18n/language/zh.toml @@ -41,3 +41,15 @@ PhoneAndCaptchaNotEmpty = "手机号和验证码不能为空!" PhoneErrorFormat = "手机号格式错误!" RegisterUserError = "注册用户错误!" LoginExpired = "登录已过期!,请重新登录!" +AccountAndPasswordNotEmpty = "账号和密码不能为空!" +UserNotRegister = "用户未注册!" +AccountErrorFormat = "账号格式错误!" +AddUserError = "添加用户错误!" +AddUserRoleError = "添加用户角色错误!" +AddUserSuccess = "添加用户成功!" +ResetPasswordError = "重置密码错误!" +ResetPasswordSuccess = "重置密码成功!" +QRCodeGetFailed = "获取二维码失败!" +QRCodeGetSuccess = "获取二维码成功!" +QRCodeExpired = "二维码已过期!" + diff --git a/main.go b/main.go index 3e3b33d..5cb89bf 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ func main() { core.InitRedis() // 初始化redis core.InitCaptcha() // 初始化验证码 core.InitIDGenerator() // 初始化ID生成器 + core.InitWechat() // 初始化微信 // 命令行参数绑定 option := cmd.Parse() if cmd.IsStopWeb(&option) { diff --git a/model/sca_auth_user_social.go b/model/sca_auth_user_social.go index 42db95f..5df4943 100644 --- a/model/sca_auth_user_social.go +++ b/model/sca_auth_user_social.go @@ -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 diff --git a/router/modules/oauth_router.go b/router/modules/oauth_router.go new file mode 100644 index 0000000..1588cba --- /dev/null +++ b/router/modules/oauth_router.go @@ -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) +} diff --git a/router/modules/user_router.go b/router/modules/user_router.go index 2216383..91d312e 100644 --- a/router/modules/user_router.go +++ b/router/modules/user_router.go @@ -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()) { diff --git a/router/router.go b/router/router.go index 23a833e..4689bfe 100644 --- a/router/router.go +++ b/router/router.go @@ -26,5 +26,6 @@ func InitRouter() *gin.Engine { modules.UserRouter(publicGroup) // 注册鉴权路由 modules.CaptchaRouter(publicGroup) // 注册验证码路由 modules.SmsRouter(publicGroup) // 注册短信验证码路由 + modules.OauthRouter(publicGroup) // 注册oauth路由 return router } diff --git a/service/service.go b/service/service.go index cd285dd..298d737 100644 --- a/service/service.go +++ b/service/service.go @@ -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函数实例化,实例化完成后会返回结构体地指针类型 diff --git a/service/user_service/user_service.go b/service/user_service/user_service.go index 78a2410..b4c0b2a 100644 --- a/service/user_service/user_service.go +++ b/service/user_service/user_service.go @@ -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 根据手机号查询用户 diff --git a/service/user_social_service/user_social.go b/service/user_social_service/user_social.go new file mode 100644 index 0000000..4e310d4 --- /dev/null +++ b/service/user_social_service/user_social.go @@ -0,0 +1,3 @@ +package user_social_service + +type UserSocialService struct{} diff --git a/service/user_social_service/user_social_service.go b/service/user_social_service/user_social_service.go new file mode 100644 index 0000000..76ea5a8 --- /dev/null +++ b/service/user_social_service/user_social_service.go @@ -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 +} diff --git a/wechat/info.log b/wechat/info.log new file mode 100644 index 0000000..442057f --- /dev/null +++ b/wechat/info.log @@ -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"}