mirror of
https://github.com/razertory/gpt-wework
synced 2025-10-28 01:12:25 +08:00
init
This commit is contained in:
57
README.md
57
README.md
@@ -1,2 +1,59 @@
|
|||||||
# gpt-wework
|
# gpt-wework
|
||||||
企业微信(客服)能力下的 GPT-3 微信机器人
|
企业微信(客服)能力下的 GPT-3 微信机器人
|
||||||
|
|
||||||
|
|
||||||
|
目前越来越多的人开始用 GPT-3 相关的产品协助自己的工作和学习,在微信上也有不少接入API的机器人。
|
||||||
|
不过目前而言,想拥有微信原生的体验,多数是基于浏览器模式下的微信号。这种做法有两个限制
|
||||||
|
|
||||||
|
用的微信号是能够登陆网页版微信的,这种号会越来越少,
|
||||||
|
一不注意就有封的风险,Kotaro 的朋友圈就已经出现号被封的情况了。
|
||||||
|
|
||||||
|
|
||||||
|
在各种尝试和实验下,我上线了一种基于企业微信客服的做法。
|
||||||
|
- 能够媲美原生的体验:私聊,分享,翻译等等。后期考虑加入群聊,如果有需要
|
||||||
|
- 技术上和微信解耦
|
||||||
|
|
||||||
|
|
||||||
|
体验在公众号后台回复 「客服」可以直接体验
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 教程
|
||||||
|
#### 注册并登陆企业微信后台
|
||||||
|
应用管理 - 微信客服
|
||||||
|

|
||||||
|
|
||||||
|
#### 配置应用服务器
|
||||||
|
填写项目所在服务器的 host 以及 [main.go](./main.go) 的
|
||||||
|
`/wechat/check`
|
||||||
|
|
||||||
|
相关的参数参考[wechat.go](./service/wechat.go) 上方的参数
|
||||||
|
```
|
||||||
|
// 验证企业微信回调的token
|
||||||
|
var token = "token"
|
||||||
|
|
||||||
|
// 验证企业微信回调的key
|
||||||
|
var encodingAesKey = "encodingAesKey"
|
||||||
|
|
||||||
|
// 企业微信企业id 这个参数在企业微信后台的企业信息页
|
||||||
|
var corpid = "corpid"
|
||||||
|
|
||||||
|
// 企业微信secret 这个参数需要通过企业微信app发送
|
||||||
|
var corpsecret = "corpsecret"
|
||||||
|
```
|
||||||
|
注意,只有这些参数和企业微信`接收事件服务器`一致的时候,才能验证通过
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 配置机器人
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
40
go.mod
Normal file
40
go.mod
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
module gpt-wework
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-contrib/cors v1.4.0
|
||||||
|
github.com/gin-gonic/gin v1.8.1
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
|
github.com/sashabaranov/go-gpt3 v0.0.0-20221202105456-0f9f4aa343ad
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.9.7 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/hbagdi/go-unsplash v0.0.0-20220920231246-14121bdf6d94 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.0.2 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
|
golang.org/x/net v0.6.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.5.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
golang.org/x/text v0.7.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
135
go.sum
Normal file
135
go.sum
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||||
|
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||||
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||||
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/hbagdi/go-unsplash v0.0.0-20220920231246-14121bdf6d94 h1:dhBDGgWOBSG58zpXDZ8x7yS8pdlY5GII+GAgsPV/oXI=
|
||||||
|
github.com/hbagdi/go-unsplash v0.0.0-20220920231246-14121bdf6d94/go.mod h1:DEzhU5CxSdknL3hUXTco1n5AO2BZHs4KeJo5ADWU0Iw=
|
||||||
|
github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
|
||||||
|
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/sashabaranov/go-gpt3 v0.0.0-20221202105456-0f9f4aa343ad h1:xuO52/X2TArHVbDISrxXasZrDhdsSGABAR5sJrePVGU=
|
||||||
|
github.com/sashabaranov/go-gpt3 v0.0.0-20221202105456-0f9f4aa343ad/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
|
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
|
||||||
|
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
19
main.go
Normal file
19
main.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gpt-wework/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/ping", Ping)
|
||||||
|
r.GET("/wechat/check", service.CheckWeixinSign)
|
||||||
|
r.POST("/wechat/check", service.TalkWeixin)
|
||||||
|
r.Run(":8888")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ping(c *gin.Context) {
|
||||||
|
c.Data(500, "text/plain;charset=utf-8", []byte("ff"))
|
||||||
|
}
|
||||||
79
service/gpt.go
Normal file
79
service/gpt.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
gogpt "github.com/sashabaranov/go-gpt3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 停顿符,用来阻止 GPT-3 补充联想内容
|
||||||
|
var stop = "*#06#"
|
||||||
|
|
||||||
|
// openai key
|
||||||
|
var apiKey = "apiKey"
|
||||||
|
|
||||||
|
// openai orgId
|
||||||
|
var orgId = "orgId"
|
||||||
|
|
||||||
|
type ChatGPT struct {
|
||||||
|
client *gogpt.Client
|
||||||
|
ctx context.Context
|
||||||
|
userId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ask(question string) (string, error) {
|
||||||
|
k, orgId := apiKey, orgId
|
||||||
|
chat := NewGPT(k, orgId)
|
||||||
|
defer chat.Close()
|
||||||
|
answer, err := chat.Chat(question)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
}
|
||||||
|
return answer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatGPT) Chat(question string) (answer string, err error) {
|
||||||
|
q := question + stop
|
||||||
|
req := gogpt.CompletionRequest{
|
||||||
|
Model: gogpt.GPT3TextDavinci003,
|
||||||
|
MaxTokens: 1200,
|
||||||
|
Prompt: q,
|
||||||
|
Temperature: 0,
|
||||||
|
TopP: 0.99,
|
||||||
|
N: 1,
|
||||||
|
Stop: []string{stop},
|
||||||
|
FrequencyPenalty: 0,
|
||||||
|
PresencePenalty: 0.5,
|
||||||
|
User: c.userId,
|
||||||
|
}
|
||||||
|
resp, err := c.client.CreateCompletion(c.ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
answer = resp.Choices[0].Text
|
||||||
|
for len(answer) > 0 {
|
||||||
|
if answer[0] == '\n' {
|
||||||
|
answer = answer[1:]
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp.Choices[0].Text, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGPT(ApiKey, UserId string) *ChatGPT {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
return &ChatGPT{
|
||||||
|
client: gogpt.NewClient(ApiKey),
|
||||||
|
ctx: ctx,
|
||||||
|
userId: UserId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (c *ChatGPT) Close() {
|
||||||
|
c.ctx.Done()
|
||||||
|
}
|
||||||
315
service/util.go
Normal file
315
service/util.go
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ValidateSignatureError int = -40001
|
||||||
|
ParseXmlError int = -40002
|
||||||
|
ComputeSignatureError int = -40003
|
||||||
|
IllegalAesKey int = -40004
|
||||||
|
ValidateCorpidError int = -40005
|
||||||
|
EncryptAESError int = -40006
|
||||||
|
DecryptAESError int = -40007
|
||||||
|
IllegalBuffer int = -40008
|
||||||
|
EncodeBase64Error int = -40009
|
||||||
|
DecodeBase64Error int = -40010
|
||||||
|
GenXmlError int = -40010
|
||||||
|
ParseJsonError int = -40012
|
||||||
|
GenJsonError int = -40013
|
||||||
|
IllegalProtocolType int = -40014
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtocolType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
XmlType ProtocolType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type CryptError struct {
|
||||||
|
ErrCode int
|
||||||
|
ErrMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCryptError(err_code int, err_msg string) *CryptError {
|
||||||
|
return &CryptError{ErrCode: err_code, ErrMsg: err_msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WXBizMsg4Recv struct {
|
||||||
|
Tousername string `xml:"ToUserName"`
|
||||||
|
Encrypt string `xml:"Encrypt"`
|
||||||
|
Agentid string `xml:"AgentID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CDATA struct {
|
||||||
|
Value string `xml:",cdata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WXBizMsg4Send struct {
|
||||||
|
XMLName xml.Name `xml:"xml"`
|
||||||
|
Encrypt CDATA `xml:"Encrypt"`
|
||||||
|
Signature CDATA `xml:"MsgSignature"`
|
||||||
|
Timestamp string `xml:"TimeStamp"`
|
||||||
|
Nonce CDATA `xml:"Nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWXBizMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizMsg4Send {
|
||||||
|
return &WXBizMsg4Send{Encrypt: CDATA{Value: encrypt}, Signature: CDATA{Value: signature}, Timestamp: timestamp, Nonce: CDATA{Value: nonce}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolProcessor interface {
|
||||||
|
parse(src_data []byte) (*WXBizMsg4Recv, *CryptError)
|
||||||
|
serialize(msg_send *WXBizMsg4Send) ([]byte, *CryptError)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WXBizMsgCrypt struct {
|
||||||
|
token string
|
||||||
|
encoding_aeskey string
|
||||||
|
receiver_id string
|
||||||
|
protocol_processor ProtocolProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmlProcessor struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *XmlProcessor) parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) {
|
||||||
|
var msg4_recv WXBizMsg4Recv
|
||||||
|
err := xml.Unmarshal(src_data, &msg4_recv)
|
||||||
|
if nil != err {
|
||||||
|
return nil, NewCryptError(ParseXmlError, "xml to msg fail")
|
||||||
|
}
|
||||||
|
return &msg4_recv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *XmlProcessor) serialize(msg4_send *WXBizMsg4Send) ([]byte, *CryptError) {
|
||||||
|
xml_msg, err := xml.Marshal(msg4_send)
|
||||||
|
if nil != err {
|
||||||
|
return nil, NewCryptError(GenXmlError, err.Error())
|
||||||
|
}
|
||||||
|
return xml_msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt {
|
||||||
|
var protocol_processor ProtocolProcessor
|
||||||
|
if protocol_type != XmlType {
|
||||||
|
panic("unsupport protocal")
|
||||||
|
} else {
|
||||||
|
protocol_processor = new(XmlProcessor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) randString(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte {
|
||||||
|
padding := block_size - (len(plaintext) % block_size)
|
||||||
|
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(plaintext)
|
||||||
|
buffer.Write(padtext)
|
||||||
|
return buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) {
|
||||||
|
plaintext_len := len(plaintext)
|
||||||
|
if nil == plaintext || plaintext_len == 0 {
|
||||||
|
return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero")
|
||||||
|
}
|
||||||
|
if plaintext_len%block_size != 0 {
|
||||||
|
return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size")
|
||||||
|
}
|
||||||
|
padding_len := int(plaintext[plaintext_len-1])
|
||||||
|
return plaintext[:plaintext_len-padding_len], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) {
|
||||||
|
aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, NewCryptError(DecodeBase64Error, err.Error())
|
||||||
|
}
|
||||||
|
const block_size = 32
|
||||||
|
pad_msg := self.pKCS7Padding(plaintext, block_size)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(aeskey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewCryptError(EncryptAESError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := make([]byte, len(pad_msg))
|
||||||
|
iv := aeskey[:aes.BlockSize]
|
||||||
|
|
||||||
|
mode := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
|
||||||
|
mode.CryptBlocks(ciphertext, pad_msg)
|
||||||
|
base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext)))
|
||||||
|
base64.StdEncoding.Encode(base64_msg, ciphertext)
|
||||||
|
|
||||||
|
return base64_msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) {
|
||||||
|
aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, NewCryptError(DecodeBase64Error, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg)
|
||||||
|
if nil != err {
|
||||||
|
return nil, NewCryptError(DecodeBase64Error, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(aeskey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewCryptError(DecryptAESError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(encrypt_msg) < aes.BlockSize {
|
||||||
|
return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := aeskey[:aes.BlockSize]
|
||||||
|
|
||||||
|
if len(encrypt_msg)%aes.BlockSize != 0 {
|
||||||
|
return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size")
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
|
||||||
|
mode.CryptBlocks(encrypt_msg, encrypt_msg)
|
||||||
|
|
||||||
|
return encrypt_msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string {
|
||||||
|
sort_arr := []string{self.token, timestamp, nonce, data}
|
||||||
|
sort.Strings(sort_arr)
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for _, value := range sort_arr {
|
||||||
|
buffer.WriteString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
sha := sha1.New()
|
||||||
|
sha.Write(buffer.Bytes())
|
||||||
|
signature := fmt.Sprintf("%x", sha.Sum(nil))
|
||||||
|
return string(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) {
|
||||||
|
const block_size = 32
|
||||||
|
plaintext, err := self.pKCS7Unpadding(plaintext, block_size)
|
||||||
|
if nil != err {
|
||||||
|
return nil, 0, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text_len := uint32(len(plaintext))
|
||||||
|
if text_len < 20 {
|
||||||
|
return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1")
|
||||||
|
}
|
||||||
|
random := plaintext[:16]
|
||||||
|
msg_len := binary.BigEndian.Uint32(plaintext[16:20])
|
||||||
|
if text_len < (20 + msg_len) {
|
||||||
|
return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := plaintext[20 : 20+msg_len]
|
||||||
|
receiver_id := plaintext[20+msg_len:]
|
||||||
|
|
||||||
|
return random, msg_len, msg, receiver_id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) {
|
||||||
|
signature := self.calSignature(timestamp, nonce, echostr)
|
||||||
|
|
||||||
|
if strings.Compare(signature, msg_signature) != 0 {
|
||||||
|
return nil, NewCryptError(ValidateSignatureError, "signature not equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, err := self.cbcDecrypter(echostr)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, msg, receiver_id, err := self.ParsePlainText(plaintext)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 {
|
||||||
|
fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id))
|
||||||
|
return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) {
|
||||||
|
rand_str := self.randString(16)
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(rand_str)
|
||||||
|
|
||||||
|
msg_len_buf := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg)))
|
||||||
|
buffer.Write(msg_len_buf)
|
||||||
|
buffer.WriteString(reply_msg)
|
||||||
|
buffer.WriteString(self.receiver_id)
|
||||||
|
|
||||||
|
tmp_ciphertext, err := self.cbcEncrypter(buffer.String())
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ciphertext := string(tmp_ciphertext)
|
||||||
|
|
||||||
|
signature := self.calSignature(timestamp, nonce, ciphertext)
|
||||||
|
|
||||||
|
msg4_send := NewWXBizMsg4Send(ciphertext, signature, timestamp, nonce)
|
||||||
|
return self.protocol_processor.serialize(msg4_send)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) {
|
||||||
|
msg4_recv, crypt_err := self.protocol_processor.parse(post_data)
|
||||||
|
if nil != crypt_err {
|
||||||
|
return nil, crypt_err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt)
|
||||||
|
|
||||||
|
if strings.Compare(signature, msg_signature) != 0 {
|
||||||
|
return nil, NewCryptError(ValidateSignatureError, "signature not equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt)
|
||||||
|
if nil != crypt_err {
|
||||||
|
return nil, crypt_err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext)
|
||||||
|
if nil != crypt_err {
|
||||||
|
return nil, crypt_err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 {
|
||||||
|
return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
299
service/wechat.go
Normal file
299
service/wechat.go
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 验证企业微信回调的token
|
||||||
|
var token = "token"
|
||||||
|
|
||||||
|
// 验证企业微信回调的key
|
||||||
|
var encodingAesKey = "encodingAesKey"
|
||||||
|
|
||||||
|
// 企业微信企业id
|
||||||
|
var corpid = "corpid"
|
||||||
|
|
||||||
|
// 企业微信secret
|
||||||
|
var corpsecret = "corpsecret"
|
||||||
|
|
||||||
|
// 企业微信的重试缓存,如果服务器延迟低,可以去掉该变量以及 isRetry 逻辑
|
||||||
|
var retryCache = cache.New(60*time.Minute, 10*time.Minute)
|
||||||
|
|
||||||
|
// 企业微信 token 缓存,请求频次过高可能有一些额外的问题
|
||||||
|
var tokenCache = cache.New(5*time.Minute, 5*time.Minute)
|
||||||
|
|
||||||
|
type WeixinUserAskMsg struct {
|
||||||
|
ToUserName string `xml:"ToUserName"`
|
||||||
|
CreateTime int64 `xml:"CreateTime"`
|
||||||
|
MsgType string `xml:"MsgType"`
|
||||||
|
Event string `xml:"Event"`
|
||||||
|
Token string `xml:"Token"`
|
||||||
|
OpenKfId string `xml:"OpenKfId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessToken struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MsgRet struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
NextCursor string `json:"next_cursor"`
|
||||||
|
MsgList []Msg `json:"msg_list"`
|
||||||
|
}
|
||||||
|
type Msg struct {
|
||||||
|
Msgid string `json:"msgid"`
|
||||||
|
SendTime int64 `json:"send_time"`
|
||||||
|
Origin int `json:"origin"`
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Event struct {
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
Scene string `json:"scene"`
|
||||||
|
OpenKfid string `json:"open_kfid"`
|
||||||
|
ExternalUserid string `json:"external_userid"`
|
||||||
|
WelcomeCode string `json:"welcome_code"`
|
||||||
|
} `json:"event"`
|
||||||
|
Text struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"text"`
|
||||||
|
OpenKfid string `json:"open_kfid"`
|
||||||
|
ExternalUserid string `json:"external_userid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplyMsg struct {
|
||||||
|
Touser string `json:"touser,omitempty"`
|
||||||
|
OpenKfid string `json:"open_kfid,omitempty"`
|
||||||
|
Msgid string `json:"msgid,omitempty"`
|
||||||
|
Msgtype string `json:"msgtype,omitempty"`
|
||||||
|
Text struct {
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
} `json:"text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TalkWeixin(c *gin.Context) {
|
||||||
|
token := token
|
||||||
|
receiverId := corpid
|
||||||
|
encodingAeskey := encodingAesKey
|
||||||
|
verifyMsgSign := c.Query("msg_signature")
|
||||||
|
verifyTimestamp := c.Query("timestamp")
|
||||||
|
verifyNonce := c.Query("nonce")
|
||||||
|
crypt := NewWXBizMsgCrypt(token, encodingAeskey, receiverId, 1)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
|
||||||
|
data, _ := crypt.DecryptMsg(verifyMsgSign, verifyTimestamp, verifyNonce, bodyBytes)
|
||||||
|
var weixinUserAskMsg WeixinUserAskMsg
|
||||||
|
err := xml.Unmarshal([]byte(string(data)), &weixinUserAskMsg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("err: " + err.Error())
|
||||||
|
}
|
||||||
|
accessToken, err := accessToken()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, "ok")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgToken := weixinUserAskMsg.Token
|
||||||
|
msgRet, err := getMsgs(accessToken, msgToken)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, "ok")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go handleMsgRet(msgRet)
|
||||||
|
c.JSON(200, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TalkToUser(external_userid, open_kfid, ask, content string) {
|
||||||
|
reply := ReplyMsg{
|
||||||
|
Touser: external_userid,
|
||||||
|
OpenKfid: open_kfid,
|
||||||
|
Msgtype: "text",
|
||||||
|
Text: struct {
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
}{Content: content},
|
||||||
|
}
|
||||||
|
atoken, err := accessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callTalk(reply, atoken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMsgRet(msgRet MsgRet) {
|
||||||
|
fmt.Println(msgRet)
|
||||||
|
size := len(msgRet.MsgList)
|
||||||
|
if size < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current := msgRet.MsgList[size-1]
|
||||||
|
userId := current.ExternalUserid
|
||||||
|
kfId := current.OpenKfid
|
||||||
|
content := current.Text.Content
|
||||||
|
if content == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isRetry(userId, content) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret, err := Ask(content)
|
||||||
|
if err != nil {
|
||||||
|
TalkToUser(userId, kfId, content, "服务器火爆")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TalkToUser(userId, kfId, content, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRetry(userId, content string) bool {
|
||||||
|
data := md5.Sum([]byte(content))
|
||||||
|
s := string(data[:])
|
||||||
|
var base = "retry:user_id:%s:content:%s"
|
||||||
|
key := fmt.Sprintf(base, userId, s)
|
||||||
|
_, found := retryCache.Get(key)
|
||||||
|
if found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
retryCache.Set(key, "1", 1*time.Minute)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMsgs(accessToken, msgToken string) (MsgRet, error) {
|
||||||
|
var msgRet MsgRet
|
||||||
|
url := "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=" + accessToken
|
||||||
|
method := "POST"
|
||||||
|
payload := strings.NewReader(fmt.Sprintf(`{"token" : "%s"}`, msgToken))
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(method, url, payload)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return msgRet, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return msgRet, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return msgRet, err
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(string(body)), &msgRet)
|
||||||
|
return msgRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessToken() (string, error) {
|
||||||
|
var tokenCacheKey = "tokenCache"
|
||||||
|
data, found := tokenCache.Get(tokenCacheKey)
|
||||||
|
if found {
|
||||||
|
return fmt.Sprintf("%v", data), nil
|
||||||
|
}
|
||||||
|
urlBase := "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
|
||||||
|
url := fmt.Sprintf(urlBase, corpid, corpsecret)
|
||||||
|
method := "GET"
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(method, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s := string(body)
|
||||||
|
var accessToken AccessToken
|
||||||
|
json.Unmarshal([]byte(s), &accessToken)
|
||||||
|
token := accessToken.AccessToken
|
||||||
|
tokenCache.Set(tokenCacheKey, token, 5*time.Minute)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckWeixinSign(c *gin.Context) {
|
||||||
|
token := token
|
||||||
|
receiverId := corpid
|
||||||
|
encodingAeskey := encodingAesKey
|
||||||
|
wxcpt := NewWXBizMsgCrypt(token, encodingAeskey, receiverId, 1)
|
||||||
|
/*
|
||||||
|
------------使用示例一:验证回调URL---------------
|
||||||
|
*企业开启回调模式时,企业微信会向验证url发送一个get请求
|
||||||
|
假设点击验证时,企业收到类似请求:
|
||||||
|
* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D
|
||||||
|
* HTTP/1.1 Host: qy.weixin.qq.com
|
||||||
|
|
||||||
|
接收到该请求时,企业应
|
||||||
|
1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr),
|
||||||
|
这一步注意作URL解码。
|
||||||
|
2.验证消息体签名的正确性
|
||||||
|
3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信
|
||||||
|
第2,3步可以用企业微信提供的库函数VerifyURL来实现。
|
||||||
|
|
||||||
|
*/
|
||||||
|
// 解析出url上的参数值如下:
|
||||||
|
// verifyMsgSign := HttpUtils.ParseUrl("msg_signature")
|
||||||
|
verifyMsgSign := c.Query("msg_signature")
|
||||||
|
// verifyTimestamp := HttpUtils.ParseUrl("timestamp")
|
||||||
|
verifyTimestamp := c.Query("timestamp")
|
||||||
|
// verifyNonce := HttpUtils.ParseUrl("nonce")
|
||||||
|
verifyNonce := c.Query("nonce")
|
||||||
|
// verifyEchoStr := HttpUtils.ParseUrl("echoStr")
|
||||||
|
verifyEchoStr := c.Query("echostr")
|
||||||
|
echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr)
|
||||||
|
if nil != cryptErr {
|
||||||
|
panic(111)
|
||||||
|
}
|
||||||
|
c.Data(200, "text/plain;charset=utf-8", []byte(echoStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func callTalk(reply ReplyMsg, accessToken string) error {
|
||||||
|
url := "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=" + accessToken
|
||||||
|
method := "POST"
|
||||||
|
data, err := json.Marshal(reply)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqBody := string(data)
|
||||||
|
fmt.Println(reqBody)
|
||||||
|
payload := strings.NewReader(reqBody)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(method, url, payload)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s := string(body)
|
||||||
|
fmt.Println(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
0
staic/1.png
Normal file
0
staic/1.png
Normal file
BIN
staic/2.png
Normal file
BIN
staic/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
staic/3.jpeg
Normal file
BIN
staic/3.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
BIN
staic/4.png
Normal file
BIN
staic/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
BIN
staic/5.png
Normal file
BIN
staic/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
staic/6png
Normal file
BIN
staic/6png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Reference in New Issue
Block a user