feat: complete login
This commit is contained in:
112
backend/app/http/auth.go
Normal file
112
backend/app/http/auth.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/app/models"
|
||||||
|
"quyun/database/fields"
|
||||||
|
"quyun/database/schemas/public/model"
|
||||||
|
"quyun/providers/jwt"
|
||||||
|
"quyun/providers/wechat"
|
||||||
|
|
||||||
|
"github.com/go-jet/jet/v2/qrm"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatePrefix = "sns_basic_auth"
|
||||||
|
salt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @provider
|
||||||
|
type auth struct {
|
||||||
|
wechat *wechat.Client
|
||||||
|
jwt *jwt.JWT
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Router /auth/login [get]
|
||||||
|
// @Bind code query
|
||||||
|
// @Bind state query
|
||||||
|
// @Bind redirect query
|
||||||
|
func (ctl *auth) Login(ctx fiber.Ctx, code, state, redirect string) error {
|
||||||
|
log.Debugf("code: %s, state: %s", code, state)
|
||||||
|
|
||||||
|
// get the openid
|
||||||
|
token, err := ctl.wechat.AuthorizeCode2Token(code)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get openid")
|
||||||
|
}
|
||||||
|
log.Debugf("tokenInfo %+v", token)
|
||||||
|
|
||||||
|
authUserInfo, err := ctl.wechat.AuthorizeUserInfo(token.AccessToken, token.Openid)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get user info")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Auth User Info: %+v", authUserInfo)
|
||||||
|
|
||||||
|
user, err := models.Users.GetUserByOpenID(ctx.Context(), token.Openid)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, qrm.ErrNoRows) {
|
||||||
|
// Create User
|
||||||
|
model := &model.Users{
|
||||||
|
Status: fields.UserStatusOk,
|
||||||
|
OpenID: token.GetOpenID(),
|
||||||
|
Username: fmt.Sprintf("u_%s", gonanoid.MustGenerate(salt, 8)),
|
||||||
|
Avatar: nil,
|
||||||
|
}
|
||||||
|
if err := models.Users.Create(ctx.Context(), model); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create user")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.Wrap(err, "failed to get user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtToken, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{UserID: user.ID}))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create token")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Cookie(&fiber.Cookie{
|
||||||
|
Name: "token",
|
||||||
|
Value: jwtToken,
|
||||||
|
Expires: time.Now().Add(6 * time.Hour),
|
||||||
|
HTTPOnly: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return ctx.Redirect().To(redirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Router /auth/wechat [get]
|
||||||
|
// @Bind redirect query
|
||||||
|
func (ctl *auth) Wechat(ctx fiber.Ctx, redirect string) error {
|
||||||
|
log.Debugf("%s, query: %v", ctx.OriginalURL(), ctx.Queries())
|
||||||
|
|
||||||
|
// 添加 redirect 参数
|
||||||
|
u, err := url.Parse(string(ctx.Request().URI().FullURI()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query := u.Query()
|
||||||
|
query.Set("redirect", redirect)
|
||||||
|
u.RawQuery = query.Encode()
|
||||||
|
u.Path = "/auth/login"
|
||||||
|
fullUrl := u.String()
|
||||||
|
|
||||||
|
log.Debug("redirect_uri: ", fullUrl)
|
||||||
|
|
||||||
|
to, err := ctl.wechat.ScopeAuthorizeURL(
|
||||||
|
wechat.ScopeAuthorizeURLWithRedirectURI(fullUrl),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get wechat auth url")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Redirect().To(to.String())
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// @provider
|
||||||
type pays struct {
|
type pays struct {
|
||||||
wepay *wepay.Client
|
wepay *wepay.Client
|
||||||
job *job.Job
|
job *job.Job
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ type posts struct {
|
|||||||
// @Router /posts [get]
|
// @Router /posts [get]
|
||||||
// @Bind pagination query
|
// @Bind pagination query
|
||||||
// @Bind query query
|
// @Bind query query
|
||||||
func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery) (*requests.Pager, error) {
|
// @Bind user local
|
||||||
|
func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
|
||||||
cond := models.Posts.BuildConditionWithKey(query.Keyword)
|
cond := models.Posts.BuildConditionWithKey(query.Keyword)
|
||||||
return models.Posts.List(ctx.Context(), pagination, cond)
|
return models.Posts.List(ctx.Context(), pagination, cond)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"quyun/providers/job"
|
||||||
|
"quyun/providers/jwt"
|
||||||
|
"quyun/providers/wechat"
|
||||||
"quyun/providers/wepay"
|
"quyun/providers/wepay"
|
||||||
|
|
||||||
"go.ipao.vip/atom"
|
"go.ipao.vip/atom"
|
||||||
@@ -10,6 +13,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Provide(opts ...opt.Option) error {
|
func Provide(opts ...opt.Option) error {
|
||||||
|
if err := container.Container.Provide(func(
|
||||||
|
jwt *jwt.JWT,
|
||||||
|
wechat *wechat.Client,
|
||||||
|
) (*auth, error) {
|
||||||
|
obj := &auth{
|
||||||
|
jwt: jwt,
|
||||||
|
wechat: wechat,
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := container.Container.Provide(func(
|
||||||
|
job *job.Job,
|
||||||
|
wepay *wepay.Client,
|
||||||
|
) (*pays, error) {
|
||||||
|
obj := &pays{
|
||||||
|
job: job,
|
||||||
|
wepay: wepay,
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := container.Container.Provide(func(
|
if err := container.Container.Provide(func(
|
||||||
wepay *wepay.Client,
|
wepay *wepay.Client,
|
||||||
) (*posts, error) {
|
) (*posts, error) {
|
||||||
@@ -22,10 +51,12 @@ func Provide(opts ...opt.Option) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := container.Container.Provide(func(
|
if err := container.Container.Provide(func(
|
||||||
|
auth *auth,
|
||||||
pays *pays,
|
pays *pays,
|
||||||
posts *posts,
|
posts *posts,
|
||||||
) (contracts.HttpRoute, error) {
|
) (contracts.HttpRoute, error) {
|
||||||
obj := &Routes{
|
obj := &Routes{
|
||||||
|
auth: auth,
|
||||||
pays: pays,
|
pays: pays,
|
||||||
posts: posts,
|
posts: posts,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import (
|
|||||||
_ "go.ipao.vip/atom/contracts"
|
_ "go.ipao.vip/atom/contracts"
|
||||||
. "go.ipao.vip/atom/fen"
|
. "go.ipao.vip/atom/fen"
|
||||||
"quyun/app/requests"
|
"quyun/app/requests"
|
||||||
|
"quyun/database/schemas/public/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider contracts.HttpRoute atom.GroupRoutes
|
// @provider contracts.HttpRoute atom.GroupRoutes
|
||||||
type Routes struct {
|
type Routes struct {
|
||||||
log *log.Entry `inject:"false"`
|
log *log.Entry `inject:"false"`
|
||||||
|
auth *auth
|
||||||
pays *pays
|
pays *pays
|
||||||
posts *posts
|
posts *posts
|
||||||
}
|
}
|
||||||
@@ -28,6 +30,19 @@ func (r *Routes) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Routes) Register(router fiber.Router) {
|
func (r *Routes) Register(router fiber.Router) {
|
||||||
|
// 注册路由组: auth
|
||||||
|
router.Get("/auth/login", Func3(
|
||||||
|
r.auth.Login,
|
||||||
|
QueryParam[string]("code"),
|
||||||
|
QueryParam[string]("state"),
|
||||||
|
QueryParam[string]("redirect"),
|
||||||
|
))
|
||||||
|
|
||||||
|
router.Get("/auth/wechat", Func1(
|
||||||
|
r.auth.Wechat,
|
||||||
|
QueryParam[string]("redirect"),
|
||||||
|
))
|
||||||
|
|
||||||
// 注册路由组: pays
|
// 注册路由组: pays
|
||||||
router.Get("/pay/callback/:channel", Func1(
|
router.Get("/pay/callback/:channel", Func1(
|
||||||
r.pays.Callback,
|
r.pays.Callback,
|
||||||
@@ -35,10 +50,11 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
))
|
))
|
||||||
|
|
||||||
// 注册路由组: posts
|
// 注册路由组: posts
|
||||||
router.Get("/posts", DataFunc2(
|
router.Get("/posts", DataFunc3(
|
||||||
r.posts.List,
|
r.posts.List,
|
||||||
Query[requests.Pagination]("pagination"),
|
Query[requests.Pagination]("pagination"),
|
||||||
Query[ListQuery]("query"),
|
Query[ListQuery]("query"),
|
||||||
|
Local[*model.Users]("user"),
|
||||||
))
|
))
|
||||||
|
|
||||||
router.Get("/show/:id", DataFunc1(
|
router.Get("/show/:id", DataFunc1(
|
||||||
|
|||||||
57
backend/app/middlewares/mid_auth.go
Normal file
57
backend/app/middlewares/mid_auth.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"quyun/app/models"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
"github.com/gofiber/fiber/v3/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Middlewares) Auth(ctx fiber.Ctx) error {
|
||||||
|
if strings.HasPrefix(ctx.Path(), "/admin/") {
|
||||||
|
return ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(ctx.Path(), "/auth/") {
|
||||||
|
return ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl := string(ctx.Request().URI().FullURI())
|
||||||
|
u, err := url.Parse(fullUrl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query := u.Query()
|
||||||
|
query.Set("redirect", fullUrl)
|
||||||
|
u.RawQuery = query.Encode()
|
||||||
|
u.Path = "/auth/wechat"
|
||||||
|
fullUrl = u.String()
|
||||||
|
|
||||||
|
// check cookie exists
|
||||||
|
cookie := ctx.Cookies("token")
|
||||||
|
log.Infof("cookie: %s", cookie)
|
||||||
|
if cookie == "" {
|
||||||
|
log.Infof("auth redirect_uri: %s", fullUrl)
|
||||||
|
return ctx.Redirect().To(fullUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err := f.jwt.Parse(cookie)
|
||||||
|
if err != nil {
|
||||||
|
// remove cookie
|
||||||
|
ctx.ClearCookie("token")
|
||||||
|
return ctx.Redirect().To(fullUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := models.Users.GetByID(ctx.Context(), jwt.UserID)
|
||||||
|
if err != nil {
|
||||||
|
// remove cookie
|
||||||
|
ctx.ClearCookie("token")
|
||||||
|
return ctx.Redirect().To(fullUrl)
|
||||||
|
}
|
||||||
|
ctx.Locals("user", user)
|
||||||
|
|
||||||
|
return ctx.Next()
|
||||||
|
}
|
||||||
@@ -2,8 +2,13 @@ package middlewares
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *Middlewares) DebugMode(c fiber.Ctx) error {
|
func (f *Middlewares) DebugMode(ctx fiber.Ctx) error {
|
||||||
return c.Next()
|
log.Infof("c.Path: %s", ctx.Path())
|
||||||
|
log.Infof("Request Method: %s", ctx.Method())
|
||||||
|
log.Infof("FullURL: %s", ctx.Request().URI().FullURI())
|
||||||
|
|
||||||
|
return ctx.Next()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"quyun/providers/jwt"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type Middlewares struct {
|
type Middlewares struct {
|
||||||
log *log.Entry `inject:"false"`
|
log *log.Entry `inject:"false"`
|
||||||
|
jwt *jwt.JWT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Middlewares) Prepare() error {
|
func (f *Middlewares) Prepare() error {
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"quyun/providers/jwt"
|
||||||
|
|
||||||
"go.ipao.vip/atom/container"
|
"go.ipao.vip/atom/container"
|
||||||
"go.ipao.vip/atom/opt"
|
"go.ipao.vip/atom/opt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Provide(opts ...opt.Option) error {
|
func Provide(opts ...opt.Option) error {
|
||||||
if err := container.Container.Provide(func() (*Middlewares, error) {
|
if err := container.Container.Provide(func(
|
||||||
obj := &Middlewares{}
|
jwt *jwt.JWT,
|
||||||
|
) (*Middlewares, error) {
|
||||||
|
obj := &Middlewares{
|
||||||
|
jwt: jwt,
|
||||||
|
}
|
||||||
if err := obj.Prepare(); err != nil {
|
if err := obj.Prepare(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,3 +227,23 @@ func (m *usersModel) PostList(ctx context.Context, userId int64, pagination *req
|
|||||||
Pagination: *pagination,
|
Pagination: *pagination,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserIDByOpenID
|
||||||
|
func (m *usersModel) GetUserByOpenID(ctx context.Context, openID string) (*model.Users, error) {
|
||||||
|
tbl := table.Users
|
||||||
|
|
||||||
|
stmt := tbl.
|
||||||
|
SELECT(tbl.AllColumns).
|
||||||
|
WHERE(
|
||||||
|
tbl.OpenID.EQ(String(openID)),
|
||||||
|
)
|
||||||
|
m.log.Infof("sql: %s", stmt.DebugSql())
|
||||||
|
|
||||||
|
var user model.Users
|
||||||
|
if err := stmt.QueryContext(ctx, db, &user); err != nil {
|
||||||
|
m.log.Errorf("error querying user by OpenID: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"quyun/app/errorx"
|
"quyun/app/errorx"
|
||||||
appHttp "quyun/app/http"
|
appHttp "quyun/app/http"
|
||||||
"quyun/app/jobs"
|
"quyun/app/jobs"
|
||||||
|
"quyun/app/middlewares"
|
||||||
"quyun/app/models"
|
"quyun/app/models"
|
||||||
"quyun/app/service"
|
"quyun/app/service"
|
||||||
_ "quyun/docs"
|
_ "quyun/docs"
|
||||||
@@ -32,8 +33,8 @@ import (
|
|||||||
|
|
||||||
func defaultProviders() container.Providers {
|
func defaultProviders() container.Providers {
|
||||||
return service.Default(container.Providers{
|
return service.Default(container.Providers{
|
||||||
wechat.DefaultProvider(),
|
|
||||||
ali.DefaultProvider(),
|
ali.DefaultProvider(),
|
||||||
|
wechat.DefaultProvider(),
|
||||||
wepay.DefaultProvider(),
|
wepay.DefaultProvider(),
|
||||||
http.DefaultProvider(),
|
http.DefaultProvider(),
|
||||||
postgres.DefaultProvider(),
|
postgres.DefaultProvider(),
|
||||||
@@ -53,6 +54,7 @@ func Command() atom.Option {
|
|||||||
With(
|
With(
|
||||||
jobs.Provide,
|
jobs.Provide,
|
||||||
models.Provide,
|
models.Provide,
|
||||||
|
middlewares.Provide,
|
||||||
).
|
).
|
||||||
WithProviders(
|
WithProviders(
|
||||||
appHttp.Providers(),
|
appHttp.Providers(),
|
||||||
@@ -66,10 +68,11 @@ type Service struct {
|
|||||||
|
|
||||||
Initials []contracts.Initial `group:"initials"`
|
Initials []contracts.Initial `group:"initials"`
|
||||||
|
|
||||||
App *app.Config
|
App *app.Config
|
||||||
Job *job.Job
|
Job *job.Job
|
||||||
Http *http.Service
|
Http *http.Service
|
||||||
Routes []contracts.HttpRoute `group:"routes"`
|
Middlewares *middlewares.Middlewares
|
||||||
|
Routes []contracts.HttpRoute `group:"routes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Serve(cmd *cobra.Command, args []string) error {
|
func Serve(cmd *cobra.Command, args []string) error {
|
||||||
@@ -82,6 +85,9 @@ func Serve(cmd *cobra.Command, args []string) error {
|
|||||||
svc.Http.Engine.Get("/swagger/*", swagger.HandlerDefault)
|
svc.Http.Engine.Get("/swagger/*", swagger.HandlerDefault)
|
||||||
}
|
}
|
||||||
svc.Http.Engine.Use(errorx.Middleware)
|
svc.Http.Engine.Use(errorx.Middleware)
|
||||||
|
svc.Http.Engine.Use(svc.Middlewares.DebugMode)
|
||||||
|
svc.Http.Engine.Use(svc.Middlewares.Auth)
|
||||||
|
|
||||||
svc.Http.Engine.Use(favicon.New(favicon.Config{
|
svc.Http.Engine.Use(favicon.New(favicon.Config{
|
||||||
Data: []byte{},
|
Data: []byte{},
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Password = "xixi0202"
|
|||||||
|
|
||||||
[JWT]
|
[JWT]
|
||||||
ExpiresTime = "168h"
|
ExpiresTime = "168h"
|
||||||
SigningKey = "Key"
|
SigningKey = "xixi@0202"
|
||||||
|
|
||||||
[HashIDs]
|
[HashIDs]
|
||||||
Salt = "Salt"
|
Salt = "Salt"
|
||||||
@@ -37,11 +37,11 @@ AppID = "wx47649361b6eba174"
|
|||||||
AppSecret = "e9cdf19b006cd294a9dae7ad8ae08b72"
|
AppSecret = "e9cdf19b006cd294a9dae7ad8ae08b72"
|
||||||
Token = "W8Xhw5TivYBgY"
|
Token = "W8Xhw5TivYBgY"
|
||||||
EncodingAesKey = "OlgPgMvsl92zy5oErtEzRcziRT2txoN3jgEHV6RQZMY"
|
EncodingAesKey = "OlgPgMvsl92zy5oErtEzRcziRT2txoN3jgEHV6RQZMY"
|
||||||
DevMode = true
|
DevMode = false
|
||||||
|
|
||||||
[WeChat.Pay]
|
[WeChat.Pay]
|
||||||
NotifyURL="https://www.baidu.com/go.php"
|
NotifyURL="https://www.baidu.com/go.php"
|
||||||
MechID = "1702644947"
|
MchID = "1702644947"
|
||||||
SerialNo = "4563EC584A35BC84FB27AA4100C934C9A91D59CA"
|
SerialNo = "4563EC584A35BC84FB27AA4100C934C9A91D59CA"
|
||||||
MechName = "佳芃(北京)企业管理咨询有限公司"
|
MechName = "佳芃(北京)企业管理咨询有限公司"
|
||||||
ApiV3Key="5UBDkxVDY44AKafkqN6YgYxgtkXP6Mw6"
|
ApiV3Key="5UBDkxVDY44AKafkqN6YgYxgtkXP6Mw6"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ CREATE TABLE users(
|
|||||||
status int2 NOT NULL DEFAULT 0,
|
status int2 NOT NULL DEFAULT 0,
|
||||||
open_id varchar(128) NOT NULL UNIQUE,
|
open_id varchar(128) NOT NULL UNIQUE,
|
||||||
username varchar(128) NOT NULL,
|
username varchar(128) NOT NULL,
|
||||||
avatar varchar(128)
|
avatar text
|
||||||
);
|
);
|
||||||
|
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ require (
|
|||||||
github.com/aliyun/credentials-go v1.4.5
|
github.com/aliyun/credentials-go v1.4.5
|
||||||
github.com/go-jet/jet/v2 v2.13.0
|
github.com/go-jet/jet/v2 v2.13.0
|
||||||
github.com/go-pay/gopay v1.5.110
|
github.com/go-pay/gopay v1.5.110
|
||||||
|
github.com/go-pay/util v0.0.4
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4
|
github.com/gofiber/fiber/v3 v3.0.0-beta.4
|
||||||
github.com/gofiber/utils/v2 v2.0.0-beta.7
|
github.com/gofiber/utils/v2 v2.0.0-beta.7
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||||
@@ -86,7 +87,6 @@ require (
|
|||||||
github.com/go-pay/crypto v0.0.1 // indirect
|
github.com/go-pay/crypto v0.0.1 // indirect
|
||||||
github.com/go-pay/errgroup v0.0.3 // indirect
|
github.com/go-pay/errgroup v0.0.3 // indirect
|
||||||
github.com/go-pay/smap v0.0.2 // indirect
|
github.com/go-pay/smap v0.0.2 // indirect
|
||||||
github.com/go-pay/util v0.0.4 // indirect
|
|
||||||
github.com/go-pay/xlog v0.0.3 // indirect
|
github.com/go-pay/xlog v0.0.3 // indirect
|
||||||
github.com/go-pay/xtime v0.0.2 // indirect
|
github.com/go-pay/xtime v0.0.2 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
|||||||
11
backend/pkg/oauth/contracts.go
Normal file
11
backend/pkg/oauth/contracts.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package oauth
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type OAuthInfo interface {
|
||||||
|
GetOpenID() string
|
||||||
|
GetUnionID() string
|
||||||
|
GetAccessToken() string
|
||||||
|
GetRefreshToken() string
|
||||||
|
GetExpiredAt() time.Time
|
||||||
|
}
|
||||||
39
backend/pkg/oauth/wechat.go
Normal file
39
backend/pkg/oauth/wechat.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package oauth
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
var _ OAuthInfo = (*WechatOAuthInfo)(nil)
|
||||||
|
|
||||||
|
type WechatOAuthInfo struct {
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
OpenID string `json:"openid,omitempty"`
|
||||||
|
UnionID string `json:"unionid,omitempty"`
|
||||||
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
ExpiresIn int64 `json:"expires_in,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessToken implements OAuthInfo.
|
||||||
|
func (w *WechatOAuthInfo) GetAccessToken() string {
|
||||||
|
return w.AccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExpiredAt implements OAuthInfo.
|
||||||
|
func (w *WechatOAuthInfo) GetExpiredAt() time.Time {
|
||||||
|
return time.Now().Add(time.Duration(w.ExpiresIn) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOpenID implements OAuthInfo.
|
||||||
|
func (w *WechatOAuthInfo) GetOpenID() string {
|
||||||
|
return w.OpenID
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRefreshToken implements OAuthInfo.
|
||||||
|
func (w *WechatOAuthInfo) GetRefreshToken() string {
|
||||||
|
return w.RefreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnionID implements OAuthInfo.
|
||||||
|
func (w *WechatOAuthInfo) GetUnionID() string {
|
||||||
|
return w.UnionID
|
||||||
|
}
|
||||||
@@ -5,18 +5,6 @@ import (
|
|||||||
"go.ipao.vip/atom/opt"
|
"go.ipao.vip/atom/opt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Provide(opts ...opt.Option) error {
|
|
||||||
o := opt.New(opts...)
|
|
||||||
var config Config
|
|
||||||
if err := o.UnmarshalConfig(&config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return container.Container.Provide(func() (*Config, error) {
|
|
||||||
return &config, nil
|
|
||||||
}, o.DiOptions()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
const DefaultPrefix = "WeChat"
|
const DefaultPrefix = "WeChat"
|
||||||
|
|
||||||
func DefaultProvider() container.ProviderContainer {
|
func DefaultProvider() container.ProviderContainer {
|
||||||
@@ -28,6 +16,28 @@ func DefaultProvider() container.ProviderContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Provide(opts ...opt.Option) error {
|
||||||
|
o := opt.New(opts...)
|
||||||
|
var config Config
|
||||||
|
if err := o.UnmarshalConfig(&config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return container.Container.Provide(func() (*Config, *Client, error) {
|
||||||
|
httpClient := DefaultClient
|
||||||
|
if config.DevMode {
|
||||||
|
httpClient = httpClient.DevMode()
|
||||||
|
}
|
||||||
|
return &config, New(
|
||||||
|
WithAppID(config.AppID),
|
||||||
|
WithAppSecret(config.AppSecret),
|
||||||
|
WithAESKey(config.EncodingAESKey),
|
||||||
|
WithToken(config.Token),
|
||||||
|
WithClient(httpClient),
|
||||||
|
), nil
|
||||||
|
}, o.DiOptions()...)
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppID string
|
AppID string
|
||||||
AppSecret string
|
AppSecret string
|
||||||
|
|||||||
59
backend/providers/wechat/errors.go
Normal file
59
backend/providers/wechat/errors.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
// -1 系统繁忙,此时请开发者稍候再试
|
||||||
|
// 0 请求成功
|
||||||
|
// 40001 AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性
|
||||||
|
// 40002 请确保grant_type字段值为client_credential
|
||||||
|
// 40164 调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。
|
||||||
|
// 40243 AppSecret已被冻结,请登录MP解冻后再次调用。
|
||||||
|
// 89503 此IP调用需要管理员确认,请联系管理员
|
||||||
|
// 89501 此IP正在等待管理员确认,请联系管理员
|
||||||
|
// 89506 24小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用
|
||||||
|
// 89507 1小时内该IP被管理员拒绝调用一次,1小时内不可再使用该IP调用
|
||||||
|
// 10003 redirect_uri域名与后台配置不一致
|
||||||
|
// 10004 此公众号被封禁
|
||||||
|
// 10005 此公众号并没有这些scope的权限
|
||||||
|
// 10006 必须关注此测试号
|
||||||
|
// 10009 操作太频繁了,请稍后重试
|
||||||
|
// 10010 scope不能为空
|
||||||
|
// 10011 redirect_uri不能为空
|
||||||
|
// 10012 appid不能为空
|
||||||
|
// 10013 state不能为空
|
||||||
|
// 10015 公众号未授权第三方平台,请检查授权状态
|
||||||
|
// 10016 不支持微信开放平台的Appid,请使用公众号Appid
|
||||||
|
func translateError(errCode int, msg string) error {
|
||||||
|
if errCode == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := map[int]error{
|
||||||
|
0: nil,
|
||||||
|
-1: errors.New("系统繁忙,此时请开发者稍候再试"),
|
||||||
|
40001: errors.New("AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性"),
|
||||||
|
40002: errors.New("请确保grant_type字段值为client_credential"),
|
||||||
|
40164: errors.New("调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置"),
|
||||||
|
40243: errors.New("AppSecret已被冻结,请登录MP解冻后再次调用"),
|
||||||
|
89503: errors.New("此IP调用需要管理员确认,请联系管理员"),
|
||||||
|
89501: errors.New("此IP正在等待管理员确认,请联系管理员"),
|
||||||
|
89506: errors.New("24小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用"),
|
||||||
|
89507: errors.New("1小时内该IP被管理员拒绝调用一次,1小时内不可再使用该IP调用"),
|
||||||
|
10003: errors.New("redirect_uri域名与后台配置不一致"),
|
||||||
|
10004: errors.New("此公众号被封禁"),
|
||||||
|
10005: errors.New("此公众号并没有这些scope的权限"),
|
||||||
|
10006: errors.New("必须关注此测试号"),
|
||||||
|
10009: errors.New("操作太频繁了,请稍后重试"),
|
||||||
|
10010: errors.New("scope不能为空"),
|
||||||
|
10011: errors.New("redirect_uri不能为空"),
|
||||||
|
10012: errors.New("appid不能为空"),
|
||||||
|
10013: errors.New("state不能为空"),
|
||||||
|
10015: errors.New("公众号未授权第三方平台,请检查授权状态"),
|
||||||
|
10016: errors.New("不支持微信开放平台的Appid,请使用公众号Appid"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, ok := errs[errCode]; ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return errors.New(msg)
|
||||||
|
}
|
||||||
14
backend/providers/wechat/funcs.go
Normal file
14
backend/providers/wechat/funcs.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
import "math/rand"
|
||||||
|
|
||||||
|
// RandomString generate random size string
|
||||||
|
func randomString(size int) (string, error) {
|
||||||
|
// generate size string [0-9a-zA-Z]
|
||||||
|
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
b := make([]byte, size)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = chars[rand.Intn(len(chars))]
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
76
backend/providers/wechat/options.go
Normal file
76
backend/providers/wechat/options.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options func(*Client)
|
||||||
|
|
||||||
|
func WithAppID(appID string) Options {
|
||||||
|
return func(we *Client) {
|
||||||
|
we.appID = appID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAppSecret sets the app secret
|
||||||
|
func WithAppSecret(appSecret string) Options {
|
||||||
|
return func(we *Client) {
|
||||||
|
we.appSecret = appSecret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithToken sets the token
|
||||||
|
func WithToken(token string) Options {
|
||||||
|
return func(we *Client) {
|
||||||
|
we.token = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAESKey sets the AES key
|
||||||
|
func WithAESKey(aesKey string) Options {
|
||||||
|
return func(we *Client) {
|
||||||
|
we.aesKey = aesKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClient sets the http client
|
||||||
|
func WithClient(client *req.Client) Options {
|
||||||
|
return func(we *Client) {
|
||||||
|
we.client = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScopeAuthorizeURLOptions func(url.Values)
|
||||||
|
|
||||||
|
func ScopeAuthorizeURLWithScope(scope AuthScope) ScopeAuthorizeURLOptions {
|
||||||
|
return func(v url.Values) {
|
||||||
|
v.Set("scope", scope.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScopeAuthorizeURLWithRedirectURI(uri string) ScopeAuthorizeURLOptions {
|
||||||
|
return func(v url.Values) {
|
||||||
|
v.Set("redirect_uri", uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScopeAuthorizeURLWithState(state string) ScopeAuthorizeURLOptions {
|
||||||
|
return func(v url.Values) {
|
||||||
|
v.Set("state", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScopeAuthorizeURLWithForcePopup() ScopeAuthorizeURLOptions {
|
||||||
|
return func(v url.Values) {
|
||||||
|
v.Set("forcePopup", "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithVerifySiteKeyPair(key, value string) Options {
|
||||||
|
return func(we *Client) {
|
||||||
|
we.verifyKey = key
|
||||||
|
we.verifyValue = value
|
||||||
|
}
|
||||||
|
}
|
||||||
16
backend/providers/wechat/response.go
Normal file
16
backend/providers/wechat/response.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
ErrCode int `json:"errcode,omitempty"`
|
||||||
|
ErrMsg string `json:"errmsg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ErrorResponse) Error() error {
|
||||||
|
return translateError(r.ErrCode, r.ErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessTokenResponse struct {
|
||||||
|
ErrorResponse
|
||||||
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
|
ExpiresIn int `json:"expires_in,omitempty"` // seconds
|
||||||
|
}
|
||||||
245
backend/providers/wechat/wechat.go
Normal file
245
backend/providers/wechat/wechat.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/pkg/oauth"
|
||||||
|
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BaseURL = "https://api.weixin.qq.com/"
|
||||||
|
|
||||||
|
var DefaultClient = req.
|
||||||
|
NewClient().
|
||||||
|
SetBaseURL(BaseURL).
|
||||||
|
SetCommonHeader("Content-Type", "application/json")
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeBase = "snsapi_base"
|
||||||
|
ScopeUserInfo = "snsapi_userinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthScope string
|
||||||
|
|
||||||
|
func (s AuthScope) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
client *req.Client
|
||||||
|
|
||||||
|
appID string
|
||||||
|
appSecret string
|
||||||
|
token string
|
||||||
|
aesKey string
|
||||||
|
|
||||||
|
verifyKey string
|
||||||
|
verifyValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(options ...Options) *Client {
|
||||||
|
we := &Client{
|
||||||
|
client: DefaultClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(we)
|
||||||
|
}
|
||||||
|
|
||||||
|
return we
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) VerifySite(key string) (string, error) {
|
||||||
|
if key == we.verifyKey {
|
||||||
|
return we.verifyValue, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("verify failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) Verify(signature, timestamp, nonce string) error {
|
||||||
|
params := []string{signature, timestamp, nonce, we.token}
|
||||||
|
sort.Strings(params)
|
||||||
|
str := strings.Join(params, "")
|
||||||
|
hash := sha1.Sum([]byte(str))
|
||||||
|
hashStr := hex.EncodeToString(hash[:])
|
||||||
|
|
||||||
|
if hashStr == signature {
|
||||||
|
return errors.New("Signature verification failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) wrapParams(params map[string]string) map[string]string {
|
||||||
|
if params == nil {
|
||||||
|
params = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
params["appid"] = we.appID
|
||||||
|
params["secret"] = we.appSecret
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) GetAccessToken() (*AccessTokenResponse, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"grant_type": "client_credential",
|
||||||
|
}
|
||||||
|
|
||||||
|
var data ErrorResponse
|
||||||
|
resp, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/cgi-bin/token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "call /cgi-bin/token failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ErrCode != 0 {
|
||||||
|
return nil, data.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var token AccessTokenResponse
|
||||||
|
if err := resp.Unmarshal(&token); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "parse response failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScopeAuthorizeURL
|
||||||
|
func (we *Client) ScopeAuthorizeURL(opts ...ScopeAuthorizeURLOptions) (*url.URL, error) {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("appid", we.appID)
|
||||||
|
params.Add("response_type", "code")
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Get("scope") == "" {
|
||||||
|
params.Add("scope", ScopeBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse("https://open.weixin.qq.com/connect/oauth2/authorize")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "parse url failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Fragment = "wechat_redirect"
|
||||||
|
u.RawQuery = url.Values(params).Encode()
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ oauth.OAuthInfo = (*AuthorizeAccessToken)(nil)
|
||||||
|
|
||||||
|
type AuthorizeAccessToken struct {
|
||||||
|
ErrorResponse
|
||||||
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
|
ExpiresIn int64 `json:"expires_in,omitempty"`
|
||||||
|
IsSnapshotuser int64 `json:"is_snapshotuser,omitempty"`
|
||||||
|
Openid string `json:"openid,omitempty"`
|
||||||
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Unionid string `json:"unionid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessToken implements oauth.OAuthInfo.
|
||||||
|
func (a *AuthorizeAccessToken) GetAccessToken() string {
|
||||||
|
return a.AccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExpiredAt implements oauth.OAuthInfo.
|
||||||
|
func (a *AuthorizeAccessToken) GetExpiredAt() time.Time {
|
||||||
|
return time.Now().Add(time.Duration(a.ExpiresIn) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOpenID implements oauth.OAuthInfo.
|
||||||
|
func (a *AuthorizeAccessToken) GetOpenID() string {
|
||||||
|
return a.Openid
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRefreshToken implements oauth.OAuthInfo.
|
||||||
|
func (a *AuthorizeAccessToken) GetRefreshToken() string {
|
||||||
|
return a.RefreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnionID implements oauth.OAuthInfo.
|
||||||
|
func (a *AuthorizeAccessToken) GetUnionID() string {
|
||||||
|
return a.Unionid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) AuthorizeCode2Token(code string) (*AuthorizeAccessToken, error) {
|
||||||
|
params := we.wrapParams(map[string]string{
|
||||||
|
"code": code,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
})
|
||||||
|
|
||||||
|
var data AuthorizeAccessToken
|
||||||
|
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/access_token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "call /sns/oauth2/access_token failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Error(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) AuthorizeRefreshAccessToken(accessToken string) (*AuthorizeAccessToken, error) {
|
||||||
|
params := we.wrapParams(map[string]string{
|
||||||
|
"refresh_token": accessToken,
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
})
|
||||||
|
|
||||||
|
var data AuthorizeAccessToken
|
||||||
|
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/refresh_token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "call /sns/oauth2/refresh_token failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Error(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizeUserInfo struct {
|
||||||
|
ErrorResponse
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
Headimgurl string `json:"headimgurl,omitempty"`
|
||||||
|
Nickname string `json:"nickname,omitempty"`
|
||||||
|
Openid string `json:"openid,omitempty"`
|
||||||
|
Privilege []string `json:"privilege,omitempty"`
|
||||||
|
Province string `json:"province,omitempty"`
|
||||||
|
Sex int64 `json:"sex,omitempty"`
|
||||||
|
Unionid string `json:"unionid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *Client) AuthorizeUserInfo(accessToken, openID string) (*AuthorizeUserInfo, error) {
|
||||||
|
params := (map[string]string{
|
||||||
|
"access_token": accessToken,
|
||||||
|
"openid": openID,
|
||||||
|
})
|
||||||
|
|
||||||
|
var data AuthorizeUserInfo
|
||||||
|
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/userinfo")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "call /sns/userinfo failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Error(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
86
backend/providers/wechat/wechat_test.go
Normal file
86
backend/providers/wechat/wechat_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WechatAppID = "wx45745a8c51091ae0"
|
||||||
|
WechatAppSecret = "2ab33bc79d9b47efa4abef19d66e1977"
|
||||||
|
WechatToken = "W8Xhw5TivYBgY"
|
||||||
|
WechatAesKey = "F6AqCxAV4W1eCrY6llJ2zapphKK49CQN3RgtPDrjhnI"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClient() *Client {
|
||||||
|
return New(
|
||||||
|
WithAppID(WechatAppID),
|
||||||
|
WithAppSecret(WechatAppSecret),
|
||||||
|
WithAESKey(WechatAesKey),
|
||||||
|
WithToken(WechatToken),
|
||||||
|
WithClient(DefaultClient.DevMode()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWechatClient_GetAccessToken(t *testing.T) {
|
||||||
|
Convey("Test GetAccessToken", t, func() {
|
||||||
|
token, err := getClient().GetAccessToken()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(token.AccessToken, ShouldNotBeEmpty)
|
||||||
|
So(token.ExpiresIn, ShouldBeGreaterThan, 0)
|
||||||
|
|
||||||
|
t.Log("Access Token:", token.AccessToken)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_ScopeAuthorizeURL(t *testing.T) {
|
||||||
|
Convey("Test ScopeAuthorizeURL", t, func() {
|
||||||
|
url, err := getClient().ScopeAuthorizeURL(
|
||||||
|
ScopeAuthorizeURLWithScope(ScopeBase),
|
||||||
|
ScopeAuthorizeURLWithRedirectURI("https://qvyun.mp.jdwan.com/"),
|
||||||
|
)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(url, ShouldNotBeEmpty)
|
||||||
|
t.Log("URL:", url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_AuthorizeCode2Token(t *testing.T) {
|
||||||
|
code := "011W1sll2Xv4Ae4OjUnl2I7jvd2W1slX"
|
||||||
|
|
||||||
|
Convey("Test AuthorizeCode2Token", t, func() {
|
||||||
|
token, err := getClient().AuthorizeCode2Token(code)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
t.Logf("token: %+v", token)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_AuthorizeRefreshAccessToken(t *testing.T) {
|
||||||
|
token := "86_m_EAHq0RKlo6RzzGAsY8gVmiCqHqIiAJufxhm8mK8imyIW6yoE4NTcIr2vaukp7dexPWId0JWP1iZWYaLpXT_MJv1N7YQW8Qt3zOZDpJY90"
|
||||||
|
|
||||||
|
Convey("Test AuthorizeCode2Token", t, func() {
|
||||||
|
token, err := getClient().AuthorizeRefreshAccessToken(token)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
t.Logf("token: %+v", token)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_AuthorizeUserInfo(t *testing.T) {
|
||||||
|
token := "86_ZxJa8mIwbml5mDlHHbIUle_UKW8LA75nOuB0wqiome8AX5LlMWU8JwRKMZykdLEjDnKX8EJavz5GeQn3T1ot7TwpULp8imQvNIgFIjC4er8"
|
||||||
|
openID := "oMLa5tyJ2vRHa-HI4CMEkHztq3eU"
|
||||||
|
|
||||||
|
Convey("Test AuthorizeUserInfo", t, func() {
|
||||||
|
user, err := getClient().AuthorizeUserInfo(token, openID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
t.Logf("user: %+v", user)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user