├── cmd ├── data │ └── .gitignore ├── parseDocLink.go ├── apiconfig.go └── build.go ├── .gitignore ├── go.mod ├── .github ├── workflows │ ├── ci.yaml │ └── golangci-lint.yml └── FUNDING.yml ├── LICENSE ├── apis ├── qrcode │ ├── example_qrcode_test.go │ ├── qrcode.go │ └── qrcode_test.go ├── auth │ ├── example_auth_test.go │ ├── auth.go │ └── auth_test.go ├── template_message │ ├── example_template_message_test.go │ ├── template_message.go │ └── template_message_test.go ├── subscribe_notification │ ├── example_subscribe_notification_test.go │ ├── subscribe_notification.go │ └── subscribe_notification_test.go ├── content_security │ ├── example_content_security_test.go │ ├── content_security.go │ └── content_security_test.go └── data_caching │ ├── example_data_caching_test.go │ ├── data_caching.go │ └── data_caching_test.go ├── test └── test.go ├── microapp.go ├── doc └── apilist.md ├── README.md ├── go.sum └── client.go /cmd/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | book/ 4 | *tmp.go 5 | TODO -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fastwego/microapp 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.6.0 7 | github.com/faabiosr/cachego v0.16.1 8 | github.com/iancoleman/strcase v0.1.2 9 | ) 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: golang-ci 3 | jobs: 4 | checks: 5 | name: run-test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@main 9 | 10 | - name: Use Go 1.15 11 | uses: cedrickring/golang-action@1.6.0 12 | env: 13 | GO111MODULE: "on" 14 | 15 | - name: Use Go 1.14 16 | uses: cedrickring/golang-action/go1.14@1.6.0 17 | env: 18 | GO111MODULE: "on" 19 | 20 | - name: Use Go 1.13 21 | uses: cedrickring/golang-action/go1.13@1.6.0 22 | env: 23 | GO111MODULE: "on" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://pic2.zhimg.com/80/v2-6b84f07b309b6e1ad1b3141c3e9d34dc_1440w.png'] 13 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: [pull_request] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: golangci-lint 10 | uses: golangci/golangci-lint-action@v1 11 | with: 12 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 13 | version: v1.29 14 | 15 | # Optional: working directory, useful for monorepos 16 | # working-directory: somedir 17 | 18 | # Optional: golangci-lint command line arguments. 19 | # args: --issues-exit-code=0 20 | 21 | # Optional: show only new issues if it's a pull request. The default value is `false`. 22 | # only-new-issues: true -------------------------------------------------------------------------------- /apis/qrcode/example_qrcode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package qrcode_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/fastwego/microapp" 21 | "github.com/fastwego/microapp/apis/qrcode" 22 | ) 23 | 24 | func ExampleCreateQRCode() { 25 | var ctx *microapp.MicroApp 26 | 27 | payload := []byte("{}") 28 | resp, err := qrcode.CreateQRCode(ctx, payload) 29 | 30 | fmt.Println(resp, err) 31 | } 32 | -------------------------------------------------------------------------------- /apis/auth/example_auth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth_test 16 | 17 | import ( 18 | "fmt" 19 | "net/url" 20 | 21 | "github.com/fastwego/microapp" 22 | "github.com/fastwego/microapp/apis/auth" 23 | ) 24 | 25 | func ExampleCode2Session() { 26 | var ctx *microapp.MicroApp 27 | 28 | params := url.Values{} 29 | resp, err := auth.Code2Session(ctx, params) 30 | 31 | fmt.Println(resp, err) 32 | } 33 | -------------------------------------------------------------------------------- /apis/template_message/example_template_message_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package template_message_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/fastwego/microapp" 21 | "github.com/fastwego/microapp/apis/template_message" 22 | ) 23 | 24 | func ExampleSend() { 25 | var ctx *microapp.MicroApp 26 | 27 | payload := []byte("{}") 28 | resp, err := template_message.Send(ctx, payload) 29 | 30 | fmt.Println(resp, err) 31 | } 32 | -------------------------------------------------------------------------------- /apis/subscribe_notification/example_subscribe_notification_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package subscribe_notification_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/fastwego/microapp" 21 | "github.com/fastwego/microapp/apis/subscribe_notification" 22 | ) 23 | 24 | func ExampleNotify() { 25 | var ctx *microapp.MicroApp 26 | 27 | payload := []byte("{}") 28 | resp, err := subscribe_notification.Notify(ctx, payload) 29 | 30 | fmt.Println(resp, err) 31 | } 32 | -------------------------------------------------------------------------------- /apis/content_security/example_content_security_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package content_security_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/fastwego/microapp" 21 | "github.com/fastwego/microapp/apis/content_security" 22 | ) 23 | 24 | func ExampleTextAntiDirty() { 25 | var ctx *microapp.MicroApp 26 | 27 | payload := []byte("{}") 28 | resp, err := content_security.TextAntiDirty(ctx, payload) 29 | 30 | fmt.Println(resp, err) 31 | } 32 | 33 | func ExampleImage() { 34 | var ctx *microapp.MicroApp 35 | 36 | payload := []byte("{}") 37 | resp, err := content_security.Image(ctx, payload) 38 | 39 | fmt.Println(resp, err) 40 | } 41 | -------------------------------------------------------------------------------- /apis/template_message/template_message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package template_message 模板消息 16 | package template_message 17 | 18 | import ( 19 | "bytes" 20 | 21 | "github.com/fastwego/microapp" 22 | ) 23 | 24 | const ( 25 | apiSend = "/api/apps/game/template/send" 26 | ) 27 | 28 | /* 29 | 发送模版消息 30 | 31 | 提示 本接口在服务器端调用 目前只有今日头条支持,抖音和 lite 接入中 32 | 33 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/model-news/send 34 | 35 | POST https://developer.toutiao.com/api/apps/game/template/send 36 | */ 37 | func Send(ctx *microapp.MicroApp, payload []byte) (resp []byte, err error) { 38 | return ctx.Client.HTTPPost(apiSend, bytes.NewReader(payload), "application/json;charset=utf-8") 39 | } 40 | -------------------------------------------------------------------------------- /apis/data_caching/example_data_caching_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package data_caching_test 16 | 17 | import ( 18 | "fmt" 19 | "net/url" 20 | 21 | "github.com/fastwego/microapp" 22 | "github.com/fastwego/microapp/apis/data_caching" 23 | ) 24 | 25 | func ExampleSetUserStorage() { 26 | var ctx *microapp.MicroApp 27 | 28 | payload := []byte("{}") 29 | params := url.Values{} 30 | resp, err := data_caching.SetUserStorage(ctx, payload, params) 31 | 32 | fmt.Println(resp, err) 33 | } 34 | 35 | func ExampleRemoveUserStorage() { 36 | var ctx *microapp.MicroApp 37 | 38 | payload := []byte("{}") 39 | params := url.Values{} 40 | resp, err := data_caching.RemoveUserStorage(ctx, payload, params) 41 | 42 | fmt.Println(resp, err) 43 | } 44 | -------------------------------------------------------------------------------- /apis/qrcode/qrcode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package qrcode 二维码 16 | package qrcode 17 | 18 | import ( 19 | "bytes" 20 | 21 | "github.com/fastwego/microapp" 22 | ) 23 | 24 | const ( 25 | apiCreateQRCode = "/api/apps/qrcode" 26 | ) 27 | 28 | /* 29 | createQRCode 30 | 31 | 获取小程序/小游戏的二维码。该二维码可通过任意 app 扫码打开,能跳转到开发者指定的对应字节系 app 内拉起小程序/小游戏,并传入开发者指定的参数。通过该接口生成的二维码,永久有效,暂无数量限制。 32 | 33 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/qr-code/create-qr-code 34 | 35 | POST https://developer.toutiao.com/api/apps/qrcode 36 | */ 37 | func CreateQRCode(ctx *microapp.MicroApp, payload []byte) (resp []byte, err error) { 38 | return ctx.Client.HTTPPost(apiCreateQRCode, bytes.NewReader(payload), "application/json;charset=utf-8") 39 | } 40 | -------------------------------------------------------------------------------- /apis/auth/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package auth 登录 16 | package auth 17 | 18 | import ( 19 | "net/url" 20 | 21 | "github.com/fastwego/microapp" 22 | ) 23 | 24 | const ( 25 | apiCode2Session = "/api/apps/jscode2session" 26 | ) 27 | 28 | /* 29 | code2Session 30 | 31 | 通过login接口获取到登录凭证后,开发者可以通过服务器发送请求的方式获取 session_key 和 openId。 32 | 33 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/log-in/code-2-session 34 | 35 | GET https://developer.toutiao.com/api/apps/jscode2session 36 | */ 37 | func Code2Session(ctx *microapp.MicroApp, params url.Values) (resp []byte, err error) { 38 | params.Add("appid", ctx.Config.AppId) 39 | params.Add("secret", ctx.Config.AppSecret) 40 | return ctx.Client.HTTPGet(apiCode2Session + "?" + params.Encode()) 41 | } 42 | -------------------------------------------------------------------------------- /apis/subscribe_notification/subscribe_notification.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package subscribe_notification 订阅消息 16 | package subscribe_notification 17 | 18 | import ( 19 | "bytes" 20 | 21 | "github.com/fastwego/microapp" 22 | ) 23 | 24 | const ( 25 | apiNotify = "/api/apps/subscribe_notification/developer/v1/notify" 26 | ) 27 | 28 | /* 29 | 订阅消息推送 30 | 31 | 用户产生了订阅模板消息的行为后,可以通过这个接口发送模板消息给用户,功能参考订阅消息能力。 32 | 33 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/subscribe-notification/notify 34 | 35 | POST https://developer.toutiao.com/api/apps/subscribe_notification/developer/v1/notify 36 | */ 37 | func Notify(ctx *microapp.MicroApp, payload []byte) (resp []byte, err error) { 38 | return ctx.Client.HTTPPost(apiNotify, bytes.NewReader(payload), "application/json;charset=utf-8") 39 | } 40 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package test 模拟服务器 测试 16 | package test 17 | 18 | import ( 19 | "net/http" 20 | "net/http/httptest" 21 | "sync" 22 | 23 | "github.com/fastwego/microapp" 24 | ) 25 | 26 | var MockMicroApp *microapp.MicroApp 27 | var MockSvr *httptest.Server 28 | var MockSvrHandler *http.ServeMux 29 | var onceSetup sync.Once 30 | 31 | // 初始化测试环境 32 | func Setup() { 33 | onceSetup.Do(func() { 34 | 35 | MockMicroApp = microapp.New(microapp.Config{ 36 | AppId: "APPID", 37 | AppSecret: "SECRET", 38 | }) 39 | 40 | // Mock Server 41 | MockSvrHandler = http.NewServeMux() 42 | MockSvr = httptest.NewServer(MockSvrHandler) 43 | microapp.ServerUrl = MockSvr.URL // 拦截发往服务器的请求 44 | 45 | // Mock access token 46 | MockSvrHandler.HandleFunc("/api/apps/token", func(w http.ResponseWriter, r *http.Request) { 47 | _, _ = w.Write([]byte(`{"access_token":"ACCESS_TOKEN","expires_in":7200}`)) 48 | }) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /apis/data_caching/data_caching.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package data_caching 数据缓存 16 | package data_caching 17 | 18 | import ( 19 | "bytes" 20 | "net/url" 21 | 22 | "github.com/fastwego/microapp" 23 | ) 24 | 25 | const ( 26 | apiSetUserStorage = "/api/apps/set_user_storage" 27 | apiRemoveUserStorage = "/api/apps/remove_user_storage" 28 | ) 29 | 30 | /* 31 | setUserStorage 32 | 33 | 以 key-value 形式存储用户数据到小程序平台的云存储服务。若开发者无内部存储服务则可接入,免费且无需申请。一般情况下只存储用户的基本信息,禁止写入大量不相干信息。 34 | 35 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/data-caching/set-user-storage 36 | 37 | POST https://developer.toutiao.com/api/apps/set_user_storage 38 | */ 39 | func SetUserStorage(ctx *microapp.MicroApp, payload []byte, params url.Values) (resp []byte, err error) { 40 | return ctx.Client.HTTPPost(apiSetUserStorage+"?"+params.Encode(), bytes.NewReader(payload), "application/json;charset=utf-8") 41 | } 42 | 43 | /* 44 | removeUserStorage 45 | 46 | 删除存储到字节跳动的云存储服务的 key-value 数据。当开发者不需要该用户信息时,需要删除,以免占用过大的存储空间。 47 | 48 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/data-caching/remove-user-storage 49 | 50 | POST https://developer.toutiao.com/api/apps/remove_user_storage 51 | */ 52 | func RemoveUserStorage(ctx *microapp.MicroApp, payload []byte, params url.Values) (resp []byte, err error) { 53 | return ctx.Client.HTTPPost(apiRemoveUserStorage+"?"+params.Encode(), bytes.NewReader(payload), "application/json;charset=utf-8") 54 | } 55 | -------------------------------------------------------------------------------- /apis/template_message/template_message_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package template_message 16 | 17 | import ( 18 | "net/http" 19 | "os" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/fastwego/microapp" 24 | "github.com/fastwego/microapp/test" 25 | ) 26 | 27 | func TestMain(m *testing.M) { 28 | test.Setup() 29 | os.Exit(m.Run()) 30 | } 31 | 32 | func TestSend(t *testing.T) { 33 | mockResp := map[string][]byte{ 34 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 35 | } 36 | var resp []byte 37 | test.MockSvrHandler.HandleFunc(apiSend, func(w http.ResponseWriter, r *http.Request) { 38 | w.Write([]byte(resp)) 39 | }) 40 | 41 | type args struct { 42 | ctx *microapp.MicroApp 43 | payload []byte 44 | } 45 | tests := []struct { 46 | name string 47 | args args 48 | wantResp []byte 49 | wantErr bool 50 | }{ 51 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | resp = mockResp[tt.name] 56 | gotResp, err := Send(tt.args.ctx, tt.args.payload) 57 | //fmt.Println(string(gotResp), err) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("Send() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 63 | t.Errorf("Send() gotResp = %v, want %v", gotResp, tt.wantResp) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /apis/qrcode/qrcode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package qrcode 16 | 17 | import ( 18 | "net/http" 19 | "os" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/fastwego/microapp" 24 | "github.com/fastwego/microapp/test" 25 | ) 26 | 27 | func TestMain(m *testing.M) { 28 | test.Setup() 29 | os.Exit(m.Run()) 30 | } 31 | 32 | func TestCreateQRCode(t *testing.T) { 33 | mockResp := map[string][]byte{ 34 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 35 | } 36 | var resp []byte 37 | test.MockSvrHandler.HandleFunc(apiCreateQRCode, func(w http.ResponseWriter, r *http.Request) { 38 | w.Write([]byte(resp)) 39 | }) 40 | 41 | type args struct { 42 | ctx *microapp.MicroApp 43 | payload []byte 44 | } 45 | tests := []struct { 46 | name string 47 | args args 48 | wantResp []byte 49 | wantErr bool 50 | }{ 51 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | resp = mockResp[tt.name] 56 | gotResp, err := CreateQRCode(tt.args.ctx, tt.args.payload) 57 | //fmt.Println(string(gotResp), err) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("CreateQRCode() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 63 | t.Errorf("CreateQRCode() gotResp = %v, want %v", gotResp, tt.wantResp) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /apis/subscribe_notification/subscribe_notification_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package subscribe_notification 16 | 17 | import ( 18 | "net/http" 19 | "os" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/fastwego/microapp" 24 | "github.com/fastwego/microapp/test" 25 | ) 26 | 27 | func TestMain(m *testing.M) { 28 | test.Setup() 29 | os.Exit(m.Run()) 30 | } 31 | 32 | func TestNotify(t *testing.T) { 33 | mockResp := map[string][]byte{ 34 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 35 | } 36 | var resp []byte 37 | test.MockSvrHandler.HandleFunc(apiNotify, func(w http.ResponseWriter, r *http.Request) { 38 | w.Write([]byte(resp)) 39 | }) 40 | 41 | type args struct { 42 | ctx *microapp.MicroApp 43 | payload []byte 44 | } 45 | tests := []struct { 46 | name string 47 | args args 48 | wantResp []byte 49 | wantErr bool 50 | }{ 51 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | resp = mockResp[tt.name] 56 | gotResp, err := Notify(tt.args.ctx, tt.args.payload) 57 | //fmt.Println(string(gotResp), err) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("Notify() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 63 | t.Errorf("Notify() gotResp = %v, want %v", gotResp, tt.wantResp) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /apis/auth/auth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "net/http" 19 | "net/url" 20 | "os" 21 | "reflect" 22 | "testing" 23 | 24 | "github.com/fastwego/microapp" 25 | "github.com/fastwego/microapp/test" 26 | ) 27 | 28 | func TestMain(m *testing.M) { 29 | test.Setup() 30 | os.Exit(m.Run()) 31 | } 32 | 33 | func TestCode2Session(t *testing.T) { 34 | mockResp := map[string][]byte{ 35 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 36 | } 37 | var resp []byte 38 | test.MockSvrHandler.HandleFunc(apiCode2Session, func(w http.ResponseWriter, r *http.Request) { 39 | w.Write([]byte(resp)) 40 | }) 41 | 42 | type args struct { 43 | ctx *microapp.MicroApp 44 | 45 | params url.Values 46 | } 47 | tests := []struct { 48 | name string 49 | args args 50 | wantResp []byte 51 | wantErr bool 52 | }{ 53 | {name: "case1", args: args{ctx: test.MockMicroApp, params: url.Values{}}, wantResp: mockResp["case1"], wantErr: false}, 54 | } 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | resp = mockResp[tt.name] 58 | gotResp, err := Code2Session(tt.args.ctx, tt.args.params) 59 | //fmt.Println(string(gotResp), err) 60 | if (err != nil) != tt.wantErr { 61 | t.Errorf("Code2Session() error = %v, wantErr %v", err, tt.wantErr) 62 | return 63 | } 64 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 65 | t.Errorf("Code2Session() gotResp = %v, want %v", gotResp, tt.wantResp) 66 | } 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /microapp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | 字节小程序开发 SDK 17 | 18 | See: https://microapp.bytedance.com/ 19 | */ 20 | package microapp 21 | 22 | import ( 23 | "log" 24 | "os" 25 | 26 | "github.com/faabiosr/cachego" 27 | "github.com/faabiosr/cachego/file" 28 | ) 29 | 30 | // GetAccessTokenFunc 获取 access_token 方法接口 31 | type GetAccessTokenFunc func(ctx *MicroApp) (accessToken string, err error) 32 | 33 | // NoticeAccessTokenExpireFunc 通知中控 刷新 access_token 34 | type NoticeAccessTokenExpireFunc func(ctx *MicroApp) (err error) 35 | 36 | /* 37 | MicroApp 实例 38 | */ 39 | type MicroApp struct { 40 | Config Config 41 | Client Client 42 | Logger *log.Logger 43 | Cache cachego.Cache 44 | GetAccessTokenHandler GetAccessTokenFunc 45 | NoticeAccessTokenExpireHandler NoticeAccessTokenExpireFunc 46 | } 47 | 48 | /* 49 | 小程序配置 50 | */ 51 | type Config struct { 52 | AppId string 53 | AppSecret string 54 | } 55 | 56 | /* 57 | 创建小程序实例 58 | */ 59 | func New(config Config) (microapp *MicroApp) { 60 | instance := MicroApp{ 61 | Config: config, 62 | Cache: file.New(os.TempDir()), 63 | GetAccessTokenHandler: GetAccessToken, 64 | NoticeAccessTokenExpireHandler: NoticeAccessTokenExpire, 65 | } 66 | 67 | instance.Client = Client{Ctx: &instance} 68 | instance.Logger = log.New(os.Stdout, "[fastwego/microapp] ", log.LstdFlags|log.Llongfile) 69 | 70 | return &instance 71 | } 72 | -------------------------------------------------------------------------------- /doc/apilist.md: -------------------------------------------------------------------------------- 1 | - 登录(auth) 2 | - [code2Session](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/log-in/code-2-session) 3 | - [Code2Session (/api/apps/jscode2session)](https://pkg.go.dev/github.com/fastwego/microapp/apis/auth?tab=doc#Code2Session) 4 | - 数据缓存(data_caching) 5 | - [setUserStorage](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/data-caching/set-user-storage) 6 | - [SetUserStorage (/api/apps/set_user_storage)](https://pkg.go.dev/github.com/fastwego/microapp/apis/data_caching?tab=doc#SetUserStorage) 7 | - [removeUserStorage](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/data-caching/remove-user-storage) 8 | - [RemoveUserStorage (/api/apps/remove_user_storage)](https://pkg.go.dev/github.com/fastwego/microapp/apis/data_caching?tab=doc#RemoveUserStorage) 9 | - 二维码(qrcode) 10 | - [createQRCode](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/qr-code/create-qr-code) 11 | - [CreateQRCode (/api/apps/qrcode)](https://pkg.go.dev/github.com/fastwego/microapp/apis/qrcode?tab=doc#CreateQRCode) 12 | - 模板消息(template_message) 13 | - [发送模版消息](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/model-news/send) 14 | - [Send (/api/apps/game/template/send)](https://pkg.go.dev/github.com/fastwego/microapp/apis/template_message?tab=doc#Send) 15 | - 内容安全(content_security) 16 | - [内容安全检测](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/content-security/content-security-detect) 17 | - [TextAntiDirty (/api/v2/tags/text/antidirt)](https://pkg.go.dev/github.com/fastwego/microapp/apis/content_security?tab=doc#TextAntiDirty) 18 | - [图片检测](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/content-security/picture-detect) 19 | - [Image (/api/v2/tags/image/)](https://pkg.go.dev/github.com/fastwego/microapp/apis/content_security?tab=doc#Image) 20 | - 订阅消息(subscribe_notification) 21 | - [订阅消息推送](https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/subscribe-notification/notify) 22 | - [Notify (/api/apps/subscribe_notification/developer/v1/notify)](https://pkg.go.dev/github.com/fastwego/microapp/apis/subscribe_notification?tab=doc#Notify) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastwego/microapp 2 | 3 | A fast [microapp](https://microapp.bytedance.com/) development sdk written in Golang 4 | 5 | [![GoDoc](https://pkg.go.dev/badge/github.com/fastwego/microapp?status.svg)](https://pkg.go.dev/github.com/fastwego/microapp?tab=doc) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/fastwego/microapp)](https://goreportcard.com/report/github.com/fastwego/microapp) 7 | 8 | ## 快速开始 & demo 9 | 10 | ```shell script 11 | go get github.com/fastwego/microapp 12 | ``` 13 | 14 | ```go 15 | // 创建字节小程序实例 16 | app := microapp.New(microapp.Config{ 17 | AppId: viper.GetString("APPID"), 18 | AppSecret: viper.GetString("SECRET"), 19 | }) 20 | 21 | // 调用 api 22 | payload := []byte(`{ 23 | "tasks": [ 24 | { 25 | "content": "要检测的文本" 26 | } 27 | ] 28 | }`) 29 | resp, err := content_security.TextAntiDirty(app, payload) 30 | fmt.Println(string(resp), err) 31 | ``` 32 | 33 | 34 | 完整演示项目: 35 | 36 | [https://github.com/fastwego/microapp-demo](https://github.com/fastwego/microapp-demo) 37 | 38 | 接口列表: 39 | 40 | - 小程序 [doc/apilist.md](doc/apilist.md) 41 | 42 | ## 框架特点 43 | 44 | ### 快速 45 | 46 | 「快」作为框架设计的核心理念,体现在方方面面: 47 | 48 | - 使用 Go 语言,开发快、编译快、部署快、运行快,轻松服务海量用户 49 | - 丰富的[文档](https://pkg.go.dev/github.com/fastwego/microapp) 和 [演示代码](https://github.com/fastwego/microapp-demo) ,快速上手,5 分钟即可搭建一套完整的字节小程序服务 50 | - 独立清晰的模块划分,快速熟悉整个框架,没有意外,一切都是你期望的样子 51 | - 甚至连框架自身的大部分代码也是自动生成的,维护更新快到超乎想象 52 | 53 | ### 符合直觉 54 | 55 | 作为第三方开发框架,尽可能贴合官方文档和设计,不引入新的概念,不给开发者添加学习负担 56 | 57 | ### 官方文档就是最好的文档 58 | 59 | 每个接口的注释都附带官方文档的链接,让你随时翻阅,省时省心 60 | 61 | ### 完备的单元测试 62 | 63 | 100% 覆盖每一个接口,让你每一次调用都信心满满 64 | 65 | ### 详细的日志 66 | 67 | 每个关键环节都为你完整记录,Debug 倍轻松,你可以自由定义日志输出,甚至可以关闭日志 68 | 69 | 70 | ### 支持服务集群 71 | 72 | 单台服务器支撑不住访问流量/想提高服务可用性? 73 | 74 | 只需 [设置 GetAccessTokenFunc 方法](https://pkg.go.dev/github.com/fastwego/microapp/?tab=doc#example-MicroApp.GetAccessTokenHandler) ,从中控服务获取 AccessToken,即可解决多实例刷新冲突/覆盖的问题 75 | 76 | ### 活跃的开发者社区 77 | 78 | FastWeGo 是一套完整的 Go 开发框架,包括支持微信、飞书、钉钉、字节小程序服务,拥有庞大的开发者用户群体 79 | 80 | 你遇到的所有问题几乎都可以在社区找到解决方案 81 | 82 | 83 | ## 参与贡献 84 | 85 | 欢迎提交 pr/issue 或者 文档,一起让 Go 开发更快更好! 86 | 87 | Faster we go together! 88 | 89 | [加入开发者交流群](https://github.com/fastwego/fastwego.dev#%E5%BC%80%E5%8F%91%E8%80%85%E4%BA%A4%E6%B5%81%E7%BE%A4) 90 | -------------------------------------------------------------------------------- /apis/content_security/content_security.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package content_security 内容安全 16 | package content_security 17 | 18 | import ( 19 | "bytes" 20 | "net/http" 21 | 22 | "github.com/fastwego/microapp" 23 | ) 24 | 25 | const ( 26 | apiTextAntiDirty = "/api/v2/tags/text/antidirt" 27 | apiImage = "/api/v2/tags/image/" 28 | ) 29 | 30 | /* 31 | 内容安全检测 32 | 33 | 检测一段文本是否包含违法违规内容。 34 | 35 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/content-security/content-security-detect 36 | 37 | POST https://developer.toutiao.com/api/v2/tags/text/antidirt 38 | */ 39 | func TextAntiDirty(ctx *microapp.MicroApp, payload []byte) (resp []byte, err error) { 40 | req, err := http.NewRequest(http.MethodPost, microapp.ServerUrl+apiTextAntiDirty, bytes.NewReader(payload)) 41 | if err != nil { 42 | return 43 | } 44 | 45 | var accessToken string 46 | accessToken, err = ctx.GetAccessTokenHandler(ctx) 47 | if err != nil { 48 | return 49 | } 50 | req.Header.Add("X-Token", accessToken) 51 | req.Header.Add("Content-Type", "application/json;charset=utf-8") 52 | 53 | return ctx.Client.HTTPDo(req) 54 | } 55 | 56 | /* 57 | 图片检测 58 | 59 | 检测图片是否包含违法违规内容。 60 | 61 | See: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/content-security/picture-detect 62 | 63 | POST https://developer.toutiao.com/api/v2/tags/image/ 64 | */ 65 | func Image(ctx *microapp.MicroApp, payload []byte) (resp []byte, err error) { 66 | 67 | req, err := http.NewRequest(http.MethodPost, microapp.ServerUrl+apiImage, bytes.NewReader(payload)) 68 | if err != nil { 69 | return 70 | } 71 | 72 | var accessToken string 73 | accessToken, err = ctx.GetAccessTokenHandler(ctx) 74 | if err != nil { 75 | return 76 | } 77 | req.Header.Add("X-Token", accessToken) 78 | req.Header.Add("Content-Type", "application/json;charset=utf-8") 79 | 80 | return ctx.Client.HTTPDo(req) 81 | } 82 | -------------------------------------------------------------------------------- /apis/content_security/content_security_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package content_security 16 | 17 | import ( 18 | "net/http" 19 | "os" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/fastwego/microapp" 24 | "github.com/fastwego/microapp/test" 25 | ) 26 | 27 | func TestMain(m *testing.M) { 28 | test.Setup() 29 | os.Exit(m.Run()) 30 | } 31 | 32 | func TestTextAntiDirty(t *testing.T) { 33 | mockResp := map[string][]byte{ 34 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 35 | } 36 | var resp []byte 37 | test.MockSvrHandler.HandleFunc(apiTextAntiDirty, func(w http.ResponseWriter, r *http.Request) { 38 | w.Write([]byte(resp)) 39 | }) 40 | 41 | type args struct { 42 | ctx *microapp.MicroApp 43 | payload []byte 44 | } 45 | tests := []struct { 46 | name string 47 | args args 48 | wantResp []byte 49 | wantErr bool 50 | }{ 51 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | resp = mockResp[tt.name] 56 | gotResp, err := TextAntiDirty(tt.args.ctx, tt.args.payload) 57 | //fmt.Println(string(gotResp), err) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("TextAntiDirty() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 63 | t.Errorf("TextAntiDirty() gotResp = %v, want %v", gotResp, tt.wantResp) 64 | } 65 | }) 66 | } 67 | } 68 | func TestImage(t *testing.T) { 69 | mockResp := map[string][]byte{ 70 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 71 | } 72 | var resp []byte 73 | test.MockSvrHandler.HandleFunc(apiImage, func(w http.ResponseWriter, r *http.Request) { 74 | w.Write([]byte(resp)) 75 | }) 76 | 77 | type args struct { 78 | ctx *microapp.MicroApp 79 | payload []byte 80 | } 81 | tests := []struct { 82 | name string 83 | args args 84 | wantResp []byte 85 | wantErr bool 86 | }{ 87 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 88 | } 89 | for _, tt := range tests { 90 | t.Run(tt.name, func(t *testing.T) { 91 | resp = mockResp[tt.name] 92 | gotResp, err := Image(tt.args.ctx, tt.args.payload) 93 | //fmt.Println(string(gotResp), err) 94 | if (err != nil) != tt.wantErr { 95 | t.Errorf("Image() error = %v, wantErr %v", err, tt.wantErr) 96 | return 97 | } 98 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 99 | t.Errorf("Image() gotResp = %v, want %v", gotResp, tt.wantResp) 100 | } 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /apis/data_caching/data_caching_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package data_caching 16 | 17 | import ( 18 | "net/http" 19 | "net/url" 20 | "os" 21 | "reflect" 22 | "testing" 23 | 24 | "github.com/fastwego/microapp" 25 | "github.com/fastwego/microapp/test" 26 | ) 27 | 28 | func TestMain(m *testing.M) { 29 | test.Setup() 30 | os.Exit(m.Run()) 31 | } 32 | 33 | func TestSetUserStorage(t *testing.T) { 34 | mockResp := map[string][]byte{ 35 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 36 | } 37 | var resp []byte 38 | test.MockSvrHandler.HandleFunc(apiSetUserStorage, func(w http.ResponseWriter, r *http.Request) { 39 | w.Write([]byte(resp)) 40 | }) 41 | 42 | type args struct { 43 | ctx *microapp.MicroApp 44 | payload []byte 45 | 46 | params url.Values 47 | } 48 | tests := []struct { 49 | name string 50 | args args 51 | wantResp []byte 52 | wantErr bool 53 | }{ 54 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 55 | } 56 | for _, tt := range tests { 57 | t.Run(tt.name, func(t *testing.T) { 58 | resp = mockResp[tt.name] 59 | gotResp, err := SetUserStorage(tt.args.ctx, tt.args.payload, tt.args.params) 60 | //fmt.Println(string(gotResp), err) 61 | if (err != nil) != tt.wantErr { 62 | t.Errorf("SetUserStorage() error = %v, wantErr %v", err, tt.wantErr) 63 | return 64 | } 65 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 66 | t.Errorf("SetUserStorage() gotResp = %v, want %v", gotResp, tt.wantResp) 67 | } 68 | }) 69 | } 70 | } 71 | func TestRemoveUserStorage(t *testing.T) { 72 | mockResp := map[string][]byte{ 73 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 74 | } 75 | var resp []byte 76 | test.MockSvrHandler.HandleFunc(apiRemoveUserStorage, func(w http.ResponseWriter, r *http.Request) { 77 | w.Write([]byte(resp)) 78 | }) 79 | 80 | type args struct { 81 | ctx *microapp.MicroApp 82 | payload []byte 83 | 84 | params url.Values 85 | } 86 | tests := []struct { 87 | name string 88 | args args 89 | wantResp []byte 90 | wantErr bool 91 | }{ 92 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 93 | } 94 | for _, tt := range tests { 95 | t.Run(tt.name, func(t *testing.T) { 96 | resp = mockResp[tt.name] 97 | gotResp, err := RemoveUserStorage(tt.args.ctx, tt.args.payload, tt.args.params) 98 | //fmt.Println(string(gotResp), err) 99 | if (err != nil) != tt.wantErr { 100 | t.Errorf("RemoveUserStorage() error = %v, wantErr %v", err, tt.wantErr) 101 | return 102 | } 103 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 104 | t.Errorf("RemoveUserStorage() gotResp = %v, want %v", gotResp, tt.wantResp) 105 | } 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cmd/parseDocLink.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "io/ioutil" 20 | "log" 21 | "net/http" 22 | "net/url" 23 | "regexp" 24 | "strings" 25 | 26 | "github.com/PuerkitoBio/goquery" 27 | ) 28 | 29 | const ServerUrl = `https://microapp.bytedance.com` 30 | 31 | var apiUniqMap = map[string]bool{} 32 | 33 | func run() { 34 | 35 | file, err := ioutil.ReadFile("./cmd/data/doc_links.html") 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | pattern := `href="(/docs/zh-CN/mini-app/develop/server/.+)"` 41 | reg := regexp.MustCompile(pattern) 42 | matched := reg.FindAllStringSubmatch(string(file), -1) 43 | 44 | for _, match := range matched { 45 | 46 | link := ServerUrl + match[1] 47 | resp, err := http.Get(link) 48 | if err != nil { 49 | continue 50 | } 51 | 52 | // Load the HTML document 53 | doc, err := goquery.NewDocumentFromReader(resp.Body) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | _NAME_ := doc.Find(".markdown-render-content h1").First().Text() 59 | 60 | _REQUEST_ := strings.TrimSpace(doc.Find(".markdown-render-content h2#请求地址").Next().Text()) 61 | //fmt.Println(_REQUEST_) 62 | 63 | if _REQUEST_ == "" || _NAME_ == "" { 64 | fmt.Println("Error") 65 | continue 66 | } 67 | 68 | _DESCRIPTION_ := doc.Find(".markdown-render-content h1").Next().Text() 69 | 70 | _SEE_ := link 71 | _FUNCNAME_ := _NAME_ 72 | 73 | //GetParams: []Param{ 74 | // {Name: `appid`, Type: `string`}, 75 | // {Name: `redirect_uri`, Type: `string`}, 76 | //}, 77 | 78 | _GET_PARAMS_ := "" 79 | fields := strings.Fields(_REQUEST_) 80 | parse, _ := url.Parse(fields[1]) 81 | for param_name, _ := range parse.Query() { 82 | if param_name == "access_token" { 83 | continue 84 | } 85 | _GET_PARAMS_ += "\t\t{Name: `" + param_name + "`, Type: `string`},\n" 86 | } 87 | if _GET_PARAMS_ != "" { 88 | _GET_PARAMS_ = `GetParams: []Param{ 89 | ` + _GET_PARAMS_ + "\t}," 90 | } 91 | 92 | if _, ok := apiUniqMap[_REQUEST_]; !ok { 93 | apiUniqMap[_REQUEST_] = true 94 | } else { 95 | continue 96 | } 97 | 98 | tpl := strings.ReplaceAll(itemTpl, "_NAME_", _NAME_) 99 | tpl = strings.ReplaceAll(tpl, "_DESCRIPTION_", strings.TrimSpace(_DESCRIPTION_)) 100 | tpl = strings.ReplaceAll(tpl, "_REQUEST_", strings.TrimSpace(_REQUEST_)) 101 | tpl = strings.ReplaceAll(tpl, "_SEE_", _SEE_) 102 | tpl = strings.ReplaceAll(tpl, "_FUNCNAME_", _FUNCNAME_) 103 | tpl = strings.ReplaceAll(tpl, "_GET_PARAMS_", _GET_PARAMS_) 104 | 105 | fmt.Println(tpl) 106 | 107 | } 108 | 109 | } 110 | 111 | var itemTpl = `{ 112 | Name: "_NAME_", 113 | Description: "_DESCRIPTION_", 114 | Request: "_REQUEST_", 115 | See: "_SEE_", 116 | FuncName: "_FUNCNAME_", 117 | _GET_PARAMS_ 118 | },` 119 | -------------------------------------------------------------------------------- /cmd/apiconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | type Param struct { 18 | Name string 19 | Type string 20 | } 21 | 22 | type Api struct { 23 | Name string 24 | Description string 25 | Request string 26 | See string 27 | FuncName string 28 | GetParams []Param 29 | } 30 | 31 | type ApiGroup struct { 32 | Name string 33 | Apis []Api 34 | Package string 35 | } 36 | 37 | var apiConfig = []ApiGroup{ 38 | { 39 | Name: `登录`, 40 | Package: `auth`, 41 | Apis: []Api{ 42 | { 43 | Name: "code2Session", 44 | Description: "通过login接口获取到登录凭证后,开发者可以通过服务器发送请求的方式获取 session_key 和 openId。", 45 | Request: "GET https://developer.toutiao.com/api/apps/jscode2session", 46 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/log-in/code-2-session", 47 | FuncName: "Code2Session", 48 | GetParams: []Param{ 49 | {Name: "appid", Type: "string"}, 50 | }, 51 | }, 52 | }, 53 | }, 54 | { 55 | Name: `数据缓存`, 56 | Package: `data_caching`, 57 | Apis: []Api{ 58 | { 59 | Name: "setUserStorage", 60 | Description: "以 key-value 形式存储用户数据到小程序平台的云存储服务。若开发者无内部存储服务则可接入,免费且无需申请。一般情况下只存储用户的基本信息,禁止写入大量不相干信息。", 61 | Request: "POST https://developer.toutiao.com/api/apps/set_user_storage", 62 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/data-caching/set-user-storage", 63 | FuncName: "SetUserStorage", 64 | GetParams: []Param{ 65 | {Name: "openid", Type: "string"}, 66 | {Name: "signature", Type: "string"}, 67 | {Name: "sig_method", Type: "string"}, 68 | }, 69 | }, 70 | { 71 | Name: "removeUserStorage", 72 | Description: "删除存储到字节跳动的云存储服务的 key-value 数据。当开发者不需要该用户信息时,需要删除,以免占用过大的存储空间。", 73 | Request: "POST https://developer.toutiao.com/api/apps/remove_user_storage", 74 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/data-caching/remove-user-storage", 75 | FuncName: "RemoveUserStorage", 76 | GetParams: []Param{ 77 | {Name: "openid", Type: "string"}, 78 | {Name: "signature", Type: "string"}, 79 | {Name: "sig_method", Type: "string"}, 80 | }, 81 | }}, 82 | }, 83 | { 84 | Name: `二维码`, 85 | Package: `qrcode`, 86 | Apis: []Api{{ 87 | Name: "createQRCode", 88 | Description: "获取小程序/小游戏的二维码。该二维码可通过任意 app 扫码打开,能跳转到开发者指定的对应字节系 app 内拉起小程序/小游戏,并传入开发者指定的参数。通过该接口生成的二维码,永久有效,暂无数量限制。", 89 | Request: "POST https://developer.toutiao.com/api/apps/qrcode", 90 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/qr-code/create-qr-code", 91 | FuncName: "CreateQRCode", 92 | }}, 93 | }, 94 | { 95 | Name: `模板消息`, 96 | Package: `template_message`, 97 | Apis: []Api{{ 98 | Name: "发送模版消息", 99 | Description: "提示 本接口在服务器端调用 目前只有今日头条支持,抖音和 lite 接入中", 100 | Request: "POST https://developer.toutiao.com/api/apps/game/template/send", 101 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/model-news/send", 102 | FuncName: "Send", 103 | }}, 104 | }, 105 | { 106 | Name: `内容安全`, 107 | Package: `content_security`, 108 | Apis: []Api{{ 109 | Name: "内容安全检测", 110 | Description: "检测一段文本是否包含违法违规内容。", 111 | Request: "POST https://developer.toutiao.com/api/v2/tags/text/antidirt", 112 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/content-security/content-security-detect", 113 | FuncName: "TextAntiDirty", 114 | }, 115 | { 116 | Name: "图片检测", 117 | Description: "检测图片是否包含违法违规内容。", 118 | Request: "POST https://developer.toutiao.com/api/v2/tags/image/", 119 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/content-security/picture-detect", 120 | FuncName: "Image", 121 | }}, 122 | }, 123 | { 124 | Name: `订阅消息`, 125 | Package: `subscribe_notification`, 126 | Apis: []Api{ 127 | { 128 | Name: "订阅消息推送", 129 | Description: "用户产生了订阅模板消息的行为后,可以通过这个接口发送模板消息给用户,功能参考订阅消息能力。", 130 | Request: "POST https://developer.toutiao.com/api/apps/subscribe_notification/developer/v1/notify", 131 | See: "https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/subscribe-notification/notify", 132 | FuncName: "Notify", 133 | }}, 134 | }, 135 | } 136 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94= 2 | github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 3 | github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= 4 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 5 | github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 6 | github.com/faabiosr/cachego v0.16.1 h1:8Ec0pvCA0tmzF9wYGRjTl1X8MZg/6N/+Jvx3m5/aOTM= 7 | github.com/faabiosr/cachego v0.16.1/go.mod h1:L2EomlU3/rUWjzFavY9Fwm8B4zZmX2X6u8kTMkETrwI= 8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 9 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 10 | github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 13 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 14 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 15 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 16 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 17 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 18 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 19 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 22 | github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U= 23 | github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= 24 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 25 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 26 | github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 27 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 28 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 29 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 30 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 31 | github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= 32 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 33 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 34 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= 35 | go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 38 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 39 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 40 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= 41 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 42 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 45 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 52 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 53 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 54 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 56 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 57 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 58 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 59 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 60 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 61 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 62 | gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= 63 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 64 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 65 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 66 | gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 67 | gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= 68 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 69 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 70 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 71 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package microapp 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "net/http" 25 | "net/url" 26 | "sync" 27 | "time" 28 | ) 29 | 30 | var ( 31 | ServerUrl = "https://developer.toutiao.com" // api 服务器地址 32 | UserAgent = "fastwego/microapp" 33 | ErrorAccessTokenExpire = errors.New("access token expire") 34 | ErrorSystemBusy = errors.New("system busy") 35 | ) 36 | 37 | /* 38 | HttpClient 用于向接口发送请求 39 | */ 40 | type Client struct { 41 | Ctx *MicroApp 42 | } 43 | 44 | // HTTPGet GET 请求 45 | func (client *Client) HTTPGet(uri string) (resp []byte, err error) { 46 | 47 | req, err := http.NewRequest(http.MethodGet, ServerUrl+uri, nil) 48 | if err != nil { 49 | return 50 | } 51 | 52 | return client.HTTPDo(req) 53 | } 54 | 55 | //HTTPPost POST 请求 56 | func (client *Client) HTTPPost(uri string, payload io.Reader, contentType string) (resp []byte, err error) { 57 | 58 | req, err := http.NewRequest(http.MethodPost, ServerUrl+uri, payload) 59 | if err != nil { 60 | return 61 | } 62 | 63 | req.Header.Add("Content-Type", contentType) 64 | 65 | return client.HTTPDo(req) 66 | } 67 | 68 | //HTTPDo 执行 请求 69 | func (client *Client) HTTPDo(req *http.Request) (resp []byte, err error) { 70 | 71 | var body, body2 []byte 72 | if req.Body != nil { 73 | 74 | body, err = ioutil.ReadAll(req.Body) 75 | if err != nil { 76 | return 77 | } 78 | 79 | body2 = make([]byte, len(body)) 80 | copy(body2, body) 81 | req.Body = ioutil.NopCloser(bytes.NewReader(body)) 82 | } 83 | 84 | req.Header.Add("User-Agent", UserAgent) 85 | 86 | if client.Ctx.Logger != nil { 87 | client.Ctx.Logger.Printf("%s %s Headers %v", req.Method, req.URL.String(), req.Header) 88 | } 89 | 90 | response, err := http.DefaultClient.Do(req) 91 | if err != nil { 92 | return 93 | } 94 | defer response.Body.Close() 95 | 96 | resp, err = responseFilter(response) 97 | 98 | // 发现 access_token 过期 99 | if err == ErrorAccessTokenExpire { 100 | 101 | // 主动 通知 access_token 过期 102 | err = client.Ctx.NoticeAccessTokenExpireHandler(client.Ctx) 103 | if err != nil { 104 | return 105 | } 106 | 107 | // 通知到位后 access_token 会被刷新,那么可以 retry 了 108 | var accessToken string 109 | accessToken, err = client.Ctx.GetAccessTokenHandler(client.Ctx) 110 | if err != nil { 111 | return 112 | } 113 | 114 | // 换新 115 | q := req.URL.Query() 116 | if q.Get("access_token") != "" { 117 | q.Set("access_token", accessToken) 118 | req.URL.RawQuery = q.Encode() 119 | } else if req.Header.Get("X-Token") != "" { 120 | req.Header.Set("X-Token", accessToken) 121 | } else { 122 | if len(body2) > 0 { 123 | jsonData := map[string]interface{}{} 124 | err = json.Unmarshal(body2, &jsonData) 125 | if err != nil { 126 | return 127 | } 128 | if _, ok := jsonData["access_token"]; ok { 129 | jsonData["access_token"] = accessToken 130 | body2, err = json.Marshal(jsonData) 131 | if err != nil { 132 | return 133 | } 134 | } 135 | } 136 | } 137 | 138 | if client.Ctx.Logger != nil { 139 | client.Ctx.Logger.Printf("%v retry %s %s Headers %v", ErrorAccessTokenExpire, req.Method, req.URL.String(), req.Header) 140 | } 141 | 142 | req.Body = ioutil.NopCloser(bytes.NewReader(body2)) 143 | req.ContentLength = int64(len(body2)) 144 | response, err = http.DefaultClient.Do(req) 145 | if err != nil { 146 | fmt.Println(err) 147 | return 148 | } 149 | resp, err = responseFilter(response) 150 | } else if err == ErrorSystemBusy { 151 | 152 | if client.Ctx.Logger != nil { 153 | client.Ctx.Logger.Printf("%v : retry %s %s Headers %v", ErrorSystemBusy, req.Method, req.URL.String(), req.Header) 154 | } 155 | 156 | req.Body = ioutil.NopCloser(bytes.NewReader(body2)) 157 | req.ContentLength = int64(len(body2)) 158 | response, err = http.DefaultClient.Do(req) 159 | if err != nil { 160 | return 161 | } 162 | 163 | resp, err = responseFilter(response) 164 | } 165 | 166 | return 167 | } 168 | 169 | /* 170 | 筛查 api 服务器响应,判断以下错误: 171 | 172 | - http 状态码 不为 200 173 | 174 | - 接口响应错误码 errcode 不为 0 175 | */ 176 | func responseFilter(response *http.Response) (resp []byte, err error) { 177 | if response.StatusCode != http.StatusOK { 178 | 179 | if response.StatusCode == 401 { // 401 Unauthorized 180 | err = ErrorAccessTokenExpire 181 | return 182 | } 183 | 184 | err = fmt.Errorf("Status %s", response.Status) 185 | return 186 | } 187 | 188 | resp, err = ioutil.ReadAll(response.Body) 189 | if err != nil { 190 | return 191 | } 192 | 193 | errorResponse := struct { 194 | Errcode int64 `json:"errcode"` 195 | Errmsg string `json:"errmsg"` 196 | }{} 197 | err = json.Unmarshal(resp, &errorResponse) 198 | if err != nil { 199 | return 200 | } 201 | 202 | if errorResponse.Errcode == 40002 { // bad access_token 203 | err = ErrorAccessTokenExpire 204 | return 205 | } 206 | // -1 系统繁忙,此时请开发者稍候再试 207 | if errorResponse.Errcode == -1 { 208 | err = ErrorSystemBusy 209 | return 210 | } 211 | 212 | if errorResponse.Errcode != 0 { 213 | err = errors.New(string(resp)) 214 | return 215 | } 216 | return 217 | } 218 | 219 | // 防止多个 goroutine 并发刷新冲突 220 | var refreshAccessTokenLock sync.Mutex 221 | 222 | /* 223 | 从 公众号实例 的 AccessToken 管理器 获取 access_token 224 | 225 | 如果没有 access_token 或者 已过期,那么刷新 226 | 227 | 获得新的 access_token 后 过期时间设置为 0.9 * expiresIn 提供一定冗余 228 | */ 229 | func GetAccessToken(ctx *MicroApp) (accessToken string, err error) { 230 | accessToken, err = ctx.Cache.Fetch(ctx.Config.AppId) 231 | if accessToken != "" { 232 | return 233 | } 234 | 235 | refreshAccessTokenLock.Lock() 236 | defer refreshAccessTokenLock.Unlock() 237 | 238 | accessToken, err = ctx.Cache.Fetch(ctx.Config.AppId) 239 | if accessToken != "" { 240 | return 241 | } 242 | 243 | accessToken, expiresIn, err := refreshAccessToken(ctx.Config.AppId, ctx.Config.AppSecret) 244 | if err != nil { 245 | return 246 | } 247 | 248 | // 本地缓存 access_token 249 | d := time.Duration(expiresIn) * time.Second 250 | _ = ctx.Cache.Save(ctx.Config.AppId, accessToken, d) 251 | 252 | if ctx.Logger != nil { 253 | ctx.Logger.Printf("%s %s %d\n", "refreshAccessToken", accessToken, expiresIn) 254 | } 255 | 256 | return 257 | } 258 | 259 | /* 260 | NoticeAccessTokenExpire 只需将本地存储的 access_token 删除,即完成了 access_token 已过期的 主动通知 261 | 262 | retry 请求的时候,会发现本地没有 access_token ,从而触发refresh 263 | */ 264 | func NoticeAccessTokenExpire(ctx *MicroApp) (err error) { 265 | if ctx.Logger != nil { 266 | ctx.Logger.Println("NoticeAccessTokenExpire") 267 | } 268 | 269 | err = ctx.Cache.Delete(ctx.Config.AppId) 270 | return 271 | } 272 | 273 | /* 274 | 从服务器获取新的 AccessToken 275 | 276 | See: https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html 277 | */ 278 | func refreshAccessToken(appid string, secret string) (accessToken string, expiresIn int, err error) { 279 | params := url.Values{} 280 | params.Add("appid", appid) 281 | params.Add("secret", secret) 282 | params.Add("grant_type", "client_credential") 283 | url := ServerUrl + "/api/apps/token?" + params.Encode() 284 | 285 | response, err := http.Get(url) 286 | if err != nil { 287 | return 288 | } 289 | 290 | defer response.Body.Close() 291 | if response.StatusCode != http.StatusOK { 292 | err = fmt.Errorf("GET %s RETURN %s", url, response.Status) 293 | return 294 | } 295 | 296 | resp, err := ioutil.ReadAll(response.Body) 297 | if err != nil { 298 | return 299 | } 300 | 301 | var result = struct { 302 | AccessToken string `json:"access_token"` 303 | ExpiresIn int `json:"expires_in"` 304 | Errcode float64 `json:"errcode"` 305 | Errmsg string `json:"errmsg"` 306 | }{} 307 | 308 | err = json.Unmarshal(resp, &result) 309 | if err != nil { 310 | err = fmt.Errorf("Unmarshal error %s", string(resp)) 311 | return 312 | } 313 | 314 | if result.AccessToken == "" { 315 | err = fmt.Errorf("%s", string(resp)) 316 | return 317 | } 318 | 319 | return result.AccessToken, result.ExpiresIn, nil 320 | } 321 | -------------------------------------------------------------------------------- /cmd/build.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FastWeGo 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "io/ioutil" 21 | "net/url" 22 | "os" 23 | "path" 24 | "regexp" 25 | "strings" 26 | 27 | "github.com/iancoleman/strcase" 28 | ) 29 | 30 | var buildType = "microapp" 31 | 32 | func main() { 33 | var pkgFlag string 34 | flag.StringVar(&pkgFlag, "package", "default", "") 35 | 36 | flag.StringVar(&buildType, "type", "microapp", "") 37 | flag.Parse() 38 | 39 | for _, group := range apiConfig { 40 | 41 | //if group.Package == pkgFlag { 42 | build(group) 43 | //} 44 | } 45 | 46 | if pkgFlag == "apilist" { 47 | apilist() 48 | } 49 | } 50 | 51 | func apilist() { 52 | for _, group := range apiConfig { 53 | fmt.Printf("- %s(%s)\n", group.Name, group.Package) 54 | for _, api := range group.Apis { 55 | split := strings.Split(api.Request, " ") 56 | parse, _ := url.Parse(split[1]) 57 | 58 | if api.FuncName == "" { 59 | api.FuncName = strcase.ToCamel(path.Base(parse.Path)) 60 | } 61 | 62 | godocLink := fmt.Sprintf("https://pkg.go.dev/github.com/fastwego/microapp/apis/%s?tab=doc#%s", group.Package, api.FuncName) 63 | 64 | fmt.Printf("\t- [%s](%s) \n\t\t- [%s (%s)](%s)\n", api.Name, api.See, api.FuncName, parse.Path, godocLink) 65 | } 66 | } 67 | } 68 | 69 | func build(group ApiGroup) { 70 | var funcs []string 71 | var consts []string 72 | var testFuncs []string 73 | var exampleFuncs []string 74 | 75 | for _, api := range group.Apis { 76 | tpl := postFuncTpl 77 | _FUNC_NAME_ := "" 78 | _GET_PARAMS_ := "" 79 | _GET_SUFFIX_PARAMS_ := "" 80 | _UPLOAD_ := "media" 81 | _FIELD_NAME_ := "" 82 | _FIELDS_ := "" 83 | _PAYLOAD_ := "" 84 | switch { 85 | case strings.Contains(api.Request, "GET http"): 86 | tpl = getFuncTpl 87 | case strings.Contains(api.Request, "POST http"): 88 | tpl = postFuncTpl 89 | case strings.Contains(api.Request, "POST(@media"): 90 | tpl = postUploadFuncTpl 91 | _UPLOAD_ = "media" 92 | 93 | pattern := `POST\(@media\|field=(\S+)\) http` 94 | reg := regexp.MustCompile(pattern) 95 | matched := reg.FindAllStringSubmatch(api.Request, -1) 96 | if matched != nil { 97 | _FIELD_NAME_ = matched[0][1] 98 | _PAYLOAD_ = ", payload []byte" 99 | } 100 | } 101 | if len(api.GetParams) > 0 { 102 | _GET_PARAMS_ = `, params url.Values` 103 | //if strings.Contains(api.Request, "POST") { 104 | // _GET_PARAMS_ = `, ` + _GET_PARAMS_ 105 | //} 106 | _GET_SUFFIX_PARAMS_ = `+ "?" + params.Encode()` 107 | } 108 | 109 | split := strings.Split(api.Request, " ") 110 | parseUrl, _ := url.Parse(split[1]) 111 | 112 | if api.FuncName == "" { 113 | _FUNC_NAME_ = strcase.ToCamel(path.Base(parseUrl.Path)) 114 | } else { 115 | _FUNC_NAME_ = api.FuncName 116 | } 117 | 118 | tpl = strings.ReplaceAll(tpl, "_TITLE_", api.Name) 119 | tpl = strings.ReplaceAll(tpl, "_DESCRIPTION_", api.Description) 120 | tpl = strings.ReplaceAll(tpl, "_REQUEST_", api.Request) 121 | tpl = strings.ReplaceAll(tpl, "_SEE_", api.See) 122 | tpl = strings.ReplaceAll(tpl, "_FUNC_NAME_", _FUNC_NAME_) 123 | tpl = strings.ReplaceAll(tpl, "_UPLOAD_", _UPLOAD_) 124 | tpl = strings.ReplaceAll(tpl, "_GET_PARAMS_", _GET_PARAMS_) 125 | tpl = strings.ReplaceAll(tpl, "_GET_SUFFIX_PARAMS_", _GET_SUFFIX_PARAMS_) 126 | if _FIELD_NAME_ != "" { 127 | _FIELDS_ = strings.ReplaceAll(fieldTpl, "_FIELD_NAME_", _FIELD_NAME_) 128 | } 129 | tpl = strings.ReplaceAll(tpl, "_FIELDS_", _FIELDS_) 130 | tpl = strings.ReplaceAll(tpl, "_PAYLOAD_", _PAYLOAD_) 131 | 132 | funcs = append(funcs, tpl) 133 | 134 | tpl = strings.ReplaceAll(constTpl, "_FUNC_NAME_", _FUNC_NAME_) 135 | tpl = strings.ReplaceAll(tpl, "_API_PATH_", parseUrl.Path) 136 | 137 | consts = append(consts, tpl) 138 | 139 | // TestFunc 140 | _TEST_ARGS_STRUCT_ := "" 141 | switch { 142 | case strings.Contains(api.Request, "GET http"): 143 | _TEST_ARGS_STRUCT_ = `ctx *microapp.MicroApp, ` + _GET_PARAMS_ 144 | case strings.Contains(api.Request, "POST http"): 145 | _TEST_ARGS_STRUCT_ = `ctx *microapp.MicroApp, payload []byte` 146 | if _GET_PARAMS_ != "" { 147 | _TEST_ARGS_STRUCT_ += `,` + _GET_PARAMS_ 148 | } 149 | case strings.Contains(api.Request, "POST(@media"): 150 | _TEST_ARGS_STRUCT_ = `ctx *microapp.MicroApp, ` + _UPLOAD_ + ` string` + _PAYLOAD_ + _GET_PARAMS_ 151 | } 152 | _TEST_ARGS_STRUCT_ = strings.ReplaceAll(_TEST_ARGS_STRUCT_, ",", "\n") 153 | 154 | _TEST_FUNC_SIGNATURE_ := "" 155 | _EXAMPLE_ARGS_STMT_ := "" 156 | if strings.TrimSpace(_TEST_ARGS_STRUCT_) != "" { 157 | signatures := strings.Split(_TEST_ARGS_STRUCT_, "\n") 158 | paramNames := []string{} 159 | exampleStmt := []string{} 160 | for _, signature := range signatures { 161 | signature = strings.TrimSpace(signature) 162 | tmp := strings.Split(signature, " ") 163 | //fmt.Println(tmp) 164 | if len(tmp[0]) > 0 { 165 | paramNames = append(paramNames, "tt.args."+tmp[0]) 166 | 167 | switch tmp[1] { 168 | case `[]byte`: 169 | exampleStmt = append(exampleStmt, tmp[0]+" := []byte(\"{}\")") 170 | case `string`: 171 | exampleStmt = append(exampleStmt, tmp[0]+" := \"\"") 172 | case `url.Values`: 173 | exampleStmt = append(exampleStmt, tmp[0]+" := url.Values{}") 174 | } 175 | } 176 | } 177 | _TEST_FUNC_SIGNATURE_ = strings.Join(paramNames, ",") 178 | _EXAMPLE_ARGS_STMT_ = strings.Join(exampleStmt, "\n") 179 | } 180 | 181 | tpl = strings.ReplaceAll(testFuncTpl, "_FUNC_NAME_", _FUNC_NAME_) 182 | tpl = strings.ReplaceAll(tpl, "_TEST_ARGS_STRUCT_", _TEST_ARGS_STRUCT_) 183 | tpl = strings.ReplaceAll(tpl, "_TEST_FUNC_SIGNATURE_", _TEST_FUNC_SIGNATURE_) 184 | testFuncs = append(testFuncs, tpl) 185 | 186 | //Example 187 | tpl = strings.ReplaceAll(exampleFuncTpl, "_FUNC_NAME_", _FUNC_NAME_) 188 | tpl = strings.ReplaceAll(tpl, "_PACKAGE_", path.Base(group.Package)) 189 | tpl = strings.ReplaceAll(tpl, "_TEST_FUNC_SIGNATURE_", strings.ReplaceAll(_TEST_FUNC_SIGNATURE_, "tt.args.", "")) 190 | tpl = strings.ReplaceAll(tpl, "_EXAMPLE_ARGS_STMT_", _EXAMPLE_ARGS_STMT_) 191 | exampleFuncs = append(exampleFuncs, tpl) 192 | 193 | } 194 | 195 | fileContent := fmt.Sprintf(fileTpl, path.Base(group.Package), group.Name, path.Base(group.Package), strings.Join(consts, ``), strings.Join(funcs, ``)) 196 | 197 | filename := "./../apis/" + group.Package + "/" + path.Base(group.Package) + ".go" 198 | 199 | _ = os.MkdirAll(path.Dir(filename), 0644) 200 | ioutil.WriteFile(filename, []byte(fileContent), 0644) 201 | 202 | // output Test 203 | testFileContent := fmt.Sprintf(testFileTpl, path.Base(group.Package), strings.Join(testFuncs, ``)) 204 | //fmt.Println(testFileContent) 205 | filename = "./../apis/" + group.Package + "/" + path.Base(group.Package) + "_test.go" 206 | ioutil.WriteFile(filename, []byte(testFileContent), 0644) 207 | 208 | // output example 209 | exampleFileContent := fmt.Sprintf(exampleFileTpl, path.Base(group.Package), strings.Join(exampleFuncs, ``)) 210 | //fmt.Println(testFileContent) 211 | filename = "./../apis/" + group.Package + "/example_" + path.Base(group.Package) + "_test.go" 212 | ioutil.WriteFile(filename, []byte(exampleFileContent), 0644) 213 | 214 | } 215 | 216 | var constTpl = ` 217 | api_FUNC_NAME_ = "_API_PATH_"` 218 | var commentTpl = ` 219 | /* 220 | _TITLE_ 221 | 222 | _DESCRIPTION_ 223 | 224 | See: _SEE_ 225 | 226 | _REQUEST_ 227 | */` 228 | var postFuncTpl = commentTpl + ` 229 | func _FUNC_NAME_(ctx *microapp.MicroApp, payload []byte_GET_PARAMS_) (resp []byte, err error) { 230 | return ctx.Client.HTTPPost(api_FUNC_NAME__GET_SUFFIX_PARAMS_, bytes.NewReader(payload), "application/json;charset=utf-8") 231 | } 232 | ` 233 | var getFuncTpl = commentTpl + ` 234 | func _FUNC_NAME_(ctx *microapp.MicroApp_GET_PARAMS_) (resp []byte, err error) { 235 | return ctx.Client.HTTPGet(api_FUNC_NAME__GET_SUFFIX_PARAMS_) 236 | } 237 | ` 238 | var postUploadFuncTpl = commentTpl + ` 239 | func _FUNC_NAME_(ctx *microapp.MicroApp, _UPLOAD_ string_PAYLOAD__GET_PARAMS_) (resp []byte, err error) { 240 | r, w := io.Pipe() 241 | m := multipart.NewWriter(w) 242 | go func() { 243 | defer w.Close() 244 | defer m.Close() 245 | 246 | part, err := m.CreateFormFile("_UPLOAD_", path.Base(_UPLOAD_)) 247 | if err != nil { 248 | return 249 | } 250 | file, err := os.Open(_UPLOAD_) 251 | if err != nil { 252 | return 253 | } 254 | defer file.Close() 255 | if _, err = io.Copy(part, file); err != nil { 256 | return 257 | } 258 | 259 | _FIELDS_ 260 | }() 261 | return ctx.Client.HTTPPost(api_FUNC_NAME__GET_SUFFIX_PARAMS_, r, m.FormDataContentType()) 262 | } 263 | ` 264 | 265 | var fieldTpl = ` 266 | // field 267 | err = m.WriteField("_FIELD_NAME_", string(payload)) 268 | if err != nil { 269 | return 270 | } 271 | ` 272 | 273 | var fileTpl = `// Package %s %s 274 | package %s 275 | 276 | const ( 277 | %s 278 | ) 279 | %s` 280 | 281 | var testFileTpl = `package %s 282 | 283 | func TestMain(m *testing.M) { 284 | test.Setup() 285 | os.Exit(m.Run()) 286 | } 287 | 288 | %s 289 | ` 290 | 291 | var testFuncTpl = ` 292 | func Test_FUNC_NAME_(t *testing.T) { 293 | mockResp := map[string][]byte{ 294 | "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), 295 | } 296 | var resp []byte 297 | test.MockSvrHandler.HandleFunc(api_FUNC_NAME_, func(w http.ResponseWriter, r *http.Request) { 298 | w.Write([]byte(resp)) 299 | }) 300 | 301 | type args struct { 302 | _TEST_ARGS_STRUCT_ 303 | } 304 | tests := []struct { 305 | name string 306 | args args 307 | wantResp []byte 308 | wantErr bool 309 | }{ 310 | {name: "case1", args: args{ctx: test.MockMicroApp}, wantResp: mockResp["case1"], wantErr: false}, 311 | } 312 | for _, tt := range tests { 313 | t.Run(tt.name, func(t *testing.T) { 314 | resp = mockResp[tt.name] 315 | gotResp, err := _FUNC_NAME_(_TEST_FUNC_SIGNATURE_) 316 | //fmt.Println(string(gotResp), err) 317 | if (err != nil) != tt.wantErr { 318 | t.Errorf("_FUNC_NAME_() error = %v, wantErr %v", err, tt.wantErr) 319 | return 320 | } 321 | if !reflect.DeepEqual(gotResp, tt.wantResp) { 322 | t.Errorf("_FUNC_NAME_() gotResp = %v, want %v", gotResp, tt.wantResp) 323 | } 324 | }) 325 | } 326 | }` 327 | 328 | var exampleFileTpl = `package %s_test 329 | 330 | %s 331 | ` 332 | var exampleFuncTpl = ` 333 | func Example_FUNC_NAME_() { 334 | var ctx *microapp.MicroApp 335 | 336 | _EXAMPLE_ARGS_STMT_ 337 | resp, err := _PACKAGE_._FUNC_NAME_(_TEST_FUNC_SIGNATURE_) 338 | 339 | fmt.Println(resp, err) 340 | } 341 | 342 | ` 343 | --------------------------------------------------------------------------------