Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
carolmao committed Dec 31, 2021
1 parent 3988d27 commit 80610a8
Show file tree
Hide file tree
Showing 50 changed files with 2,549 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.idea
.DS_Store
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "client"]
path = client
url = [email protected]:WeixinCloud/wxcloudrun-wxcomponent-frontend.git
37 changes: 37 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
FROM node:12.22.1 as nodeBuilder

# 指定构建过程中的工作目录
WORKDIR /wxcloudrun-wxcomponent

# 将当前目录(dockerfile所在目录)下所有文件都拷贝到工作目录下
COPY . /wxcloudrun-wxcomponent/

RUN cd /wxcloudrun-wxcomponent/client && npm install --registry=https://registry.npm.taobao.org && npm run build

FROM golang:1.17.1-alpine3.14 as builder

# 指定构建过程中的工作目录
WORKDIR /wxcloudrun-wxcomponent

# 将当前目录(dockerfile所在目录)下所有文件都拷贝到工作目录下
COPY . /wxcloudrun-wxcomponent/

# 执行代码编译命令。操作系统参数为linux,编译后的二进制产物命名为main,并存放在当前目录下。
RUN GOOS=linux go build -o main .

# 选用运行时所用基础镜像(GO语言选择原则:尽量体积小、包含基础linux内容的基础镜像)
FROM alpine:3.13

# 指定运行时的工作目录
WORKDIR /wxcloudrun-wxcomponent

# 将构建产物/wxcloudrun-wxcomponent/main拷贝到运行时的工作目录中
COPY --from=builder /wxcloudrun-wxcomponent/main /wxcloudrun-wxcomponent/
COPY --from=builder /wxcloudrun-wxcomponent/comm/config/server.conf /wxcloudrun-wxcomponent/comm/config/
COPY --from=nodeBuilder /wxcloudrun-wxcomponent/client/dist /wxcloudrun-wxcomponent/client/dist

# 设置release模式
ENV GIN_MODE release

# 执行启动命令
CMD ["/wxcloudrun-wxcomponent/main"]
87 changes: 86 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,87 @@
# wxcloudrun-wxcomponent
微信云托管 微信第三方平台模版
微信云托管 微信第三方平台管理工具模版

## 功能介绍
此项目提供第三方平台的后端服务以及第三方平台管理工具。该镜像可一键部署到微信云托管,分钟级别即可完成第三方平台开发环境搭建以及第三方平台管理工具部署。

#### 第三方平台推送消息
微信第三方平台需要填写两个URL用于接受官方推送的消息,详情参考官方文档:[创建与配置第三方平台准备工作](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/operation/thirdparty/prepare.html)
- 授权事件URL: 本项目提供了接受官方推送并存入数据库的服务,对推送ticket、授权、解除授权的事件都做了相应处理。
- 消息与事件URL: 本项目提供了接受官方推送并存入数据库的服务,开发者可以读数据库查看推送消息,也可以在此基础上进行二次开发。
#### 第三方平台管理工具
- 授权帐号管理:可查看授权给第三方平台的公众号/小程序帐号信息。
- 第三方token获取:可一键获取component_verify_ticket、component_access_token、authorizer_access_token以及微信令牌,便于开发者进行调试。
- 第三方消息查看:可获取推送至授权事件URL和消息与事件URL的消息,便于开发者进行调试。
- 第三方授权页面生成:可一键生成PC版和H5版的授权页面,商家可扫码或者直接访问授权页面完成授权。

## 目录结构
```
.
├── Dockerfile
├── README.md
├── api // 后端api
│ ├── admin // 管理工具,需管理员登录
│ ├── authpage // 授权页面,无鉴权
│ └── wxcallback // 接收微信消息
├── client // 前端
│ ├── dist // 打包结果
│ ├── index.html
│ ├── node_modules
│ ├── package.json
│ ├── src // 源代码
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── yarn.lock
├── comm // 后端公共模块
│ ├── config // 配置
│ ├── encrypt // 加密
│ ├── errno // 错误码
│ ├── httputils // http
│ ├── inits // 初始化
│ ├── log // 日志
│ ├── utils // 其他工具
│ └── wx // 微信相关
├── container.config.json // 微信云托管配置
├── db // 数据库相关
│ ├── dao
│ ├── init.go
│ └── model
├── go.mod
├── go.sum
├── main.go
├── middleware // 中间件
│ ├── jwt.go // jwt
│ ├── log.go // 日志
│ └── wxsource.go // 判断是否为微信来源
└── routers // 路由
└── routers.go
```

## 其他说明
#### 本地调试
服务启动前会从环境变量中读取数据库配置,自行写入环境变量后运行一下代码,即可在本地启动服务。
```
go run main
```

#### 判断微信来源
服务部署在微信云托管时,微信推送消息走内网,无需加解密,判断header中是否有x-wx-source即可。

#### 数据表
```
+-----------------------+
| Tables_in_wxcomponent |
+-----------------------+
| authorizers |
| comm |
| user |
| wxcallback_biz |
| wxcallback_component |
+-----------------------+
```
- authorizers: 授权账号信息
- comm: 存储ticket、第三方信息等
- user: 用户表
- wxcallback_biz: 推送给消息与事件URL的消息
- wxcallback_component: 推送给授权事件URL的消息
81 changes: 81 additions & 0 deletions api/admin/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package admin

// 系统鉴权,登录,人员管理

import (
"net/http"
"strconv"

"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/errno"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/log"
"github.com/astaxie/beego/validation"
"github.com/gin-gonic/gin"

"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/utils"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/db/dao"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/db/model"
)

func checkAuth(req model.UserRecord) (int32, error) {
record, err := dao.GetUserRecord(req.Username, req.Password)
if err != nil {
log.Error(err)
return 0, err
}
if len(record) > 0 {
return record[0].ID, nil
}
return 0, err
}

func authHandler(c *gin.Context) {
var req model.UserRecord
if err := c.ShouldBindJSON(&req); err != nil {
log.Error(err.Error())
c.JSON(http.StatusOK, errno.ErrInvalidParam.WithData(err.Error()))
return
}

valid := validation.Validation{}
ok, _ := valid.Valid(&req)

if !ok {
for _, err := range valid.Errors {
log.Debug(err.Key + " " + err.Message)
}
log.Error(valid.Errors)
c.JSON(http.StatusOK, errno.ErrAuthErr.WithData(valid.Errors))
return
}

ID, err := checkAuth(req)
if err != nil {
log.Error(err.Error())
c.JSON(http.StatusOK, errno.ErrAuthErr.WithData(err.Error()))
return
}

token, _err := utils.GenerateToken(strconv.Itoa(int(ID)), req.Username)
if _err != nil {
log.Error(_err.Error())
c.JSON(http.StatusOK, errno.ErrAuthErr.WithData(_err.Error()))
return
}

c.JSON(http.StatusOK, errno.OK.WithData(gin.H{"jwt": token}))

}

func refreshAuthHandler(c *gin.Context) {
jwt, _ := c.Get("jwt")
id, userName := jwt.(*utils.Claims).ID, jwt.(*utils.Claims).UserName
log.Debugf("id: %s name: %s", id, userName)
token, err := utils.GenerateToken(id, userName)
if err != nil {
log.Error(err.Error())
c.JSON(http.StatusOK, errno.ErrAuthErr.WithData(err.Error()))
return
}

c.JSON(http.StatusOK, errno.OK.WithData(gin.H{"jwt": token}))
}
135 changes: 135 additions & 0 deletions api/admin/authorizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package admin

import (
"encoding/json"
"net/http"
"strconv"
"sync"
"time"

"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/errno"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/httputils"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/log"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/wx"
wxbase "github.com/WeixinCloud/wxcloudrun-wxcomponent/comm/wx/base"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/db/dao"
"github.com/WeixinCloud/wxcloudrun-wxcomponent/db/model"
"github.com/gin-gonic/gin"
)

type getAuthorizerListReq struct {
ComponentAppid string `json:"component_appid"`
Offset int `json:"offset"`
Count int `json:"count"`
}

type authorizerInfo struct {
AuthorizerAppid string `json:"authorizer_appid"`
RefreshToken string `json:"refresh_token"`
AuthTime int64 `json:"auth_time"`
}
type getAuthorizerListResp struct {
TotalCount int `json:"total_count"`
List []authorizerInfo `json:list`
}

func pullAuthorizerListHandler(c *gin.Context) {
go func() {
count := 100
offset := 0
total := 0
now := time.Now()
for {
var resp getAuthorizerListResp
if err := getAuthorizerList(offset, count, &resp); err != nil {
log.Error(err)
return
}
if total == 0 {
total = resp.TotalCount
}
// 插入数据库
length := len(resp.List)
records := make([]model.Authorizer, length)
var wg sync.WaitGroup
wg.Add(length)
for i, info := range resp.List {
go constructAuthorizerRecord(info, &records[i], &wg)
}
wg.Wait()
dao.BatchCreateOrUpdateAuthorizerRecord(&records)

if length < count {
break
}
offset += count
}

// 删除记录
if err := dao.ClearAuthorizerRecordsBefore(now); err != nil {
log.Error(err)
return
}
}()
c.JSON(http.StatusOK, errno.OK)
}

func constructAuthorizerRecord(info authorizerInfo, record *model.Authorizer, wg *sync.WaitGroup) error {
defer wg.Done()
record.Appid = info.AuthorizerAppid
record.AuthTime = time.Unix(info.AuthTime, 0)
record.RefreshToken = info.RefreshToken
var appinfo wx.AuthorizerInfoResp

if err := wx.GetAuthorizerInfo(record.Appid, &appinfo); err != nil {
log.Errorf("GetAuthorizerInfo fail %v", err)
return err
}
record.AppType = appinfo.AuthorizerInfo.AppType
record.ServiceType = appinfo.AuthorizerInfo.ServiceType.Id
record.NickName = appinfo.AuthorizerInfo.NickName
record.UserName = appinfo.AuthorizerInfo.UserName
record.HeadImg = appinfo.AuthorizerInfo.HeadImg
record.QrcodeUrl = appinfo.AuthorizerInfo.QrcodeUrl
record.PrincipalName = appinfo.AuthorizerInfo.PrincipalName
record.FuncInfo = appinfo.AuthorizationInfo.StrFuncInfo
record.VerifyInfo = appinfo.AuthorizerInfo.VerifyInfo.Id
return nil
}

func getAuthorizerList(offset, count int, resp *getAuthorizerListResp) error {
req := getAuthorizerListReq{
ComponentAppid: wxbase.GetAppid(),
Offset: offset,
Count: count,
}
_, respbody, err := httputils.PostWxJson("/cgi-bin/component/api_get_authorizer_list", req, true)
if err != nil {
return err
}
if err := json.Unmarshal(respbody, &resp); err != nil {
log.Errorf("Unmarshal err, %v", err)
return err
}
return nil
}

func getAuthorizerListHandler(c *gin.Context) {
offset, err := strconv.Atoi(c.DefaultQuery("offset", "0"))
if err != nil {
c.JSON(http.StatusOK, errno.ErrInvalidParam.WithData(err.Error()))
return
}
limit, err := strconv.Atoi(c.DefaultQuery("limit", "10"))
if err != nil {
c.JSON(http.StatusOK, errno.ErrInvalidParam.WithData(err.Error()))
return
}
appid := c.DefaultQuery("appid", "")
records, total, err := dao.GetAuthorizerRecords(appid, offset, limit)
if err != nil {
c.JSON(http.StatusOK, errno.ErrSystemError.WithData(err.Error()))
return
}
c.JSON(http.StatusOK, errno.OK.WithData(gin.H{"total": total, "records": records}))
}
Loading

0 comments on commit 80610a8

Please sign in to comment.