Skip to content

Commit 3c37dfe

Browse files
committed
0.4.0 版本发布,支持服务器IP直接访问
1 parent 306741b commit 3c37dfe

File tree

14 files changed

+793
-400
lines changed

14 files changed

+793
-400
lines changed

README.md

Lines changed: 47 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
可在微信 **安全使用(通过企业微信中转到微信,无封号风险)** 的 ChatGPT 个人助手应用,
44

5-
- 主要能力
5+
- 主要能力会话(支持上下文)
66
- 第一个为自定义 prompt
77
<p align="center">
88
<a href="https://github.com/whyiyhw/chatgpt-wechat" target="_blank" rel="noopener noreferrer">
@@ -16,26 +16,16 @@
1616
<img width="400" src="./doc/image27.png" alt="image27" />
1717
</a>
1818
</p>
19-
- 可选能力(图片识别-小猿搜题目青春版
19+
- 可选能力(图片识别-小猿搜题 青春版
2020
- [点击查看示例](./doc/image25.jpg)
2121

2222
## 使用前提条件
23-
- 云服务器 1h2g 就够了
24-
- 域名(备案/不备案)都可以,
2523
- 需要去注册一个个人[企业微信](https://work.weixin.qq.com/)
26-
- 阿里云开通云函数服务(新人一年88的额度,绝对够用)
27-
- 其它腾讯云,华为云 云函数都适用,需要自行修改 js 文件。
28-
29-
(如果你有 已经备案好的域名,下面的服务与函数区域可以不用管)
30-
31-
- 为什么会需要云函数做中转?
32-
- 因为我不想去备案,已经备案的,可以直接域名解析到服务器,无视云函数这一步,后续我会在后端直接支持企业微信的回调认证
33-
34-
- 为什么需要一台服务器,完全用云函数是否可行?
35-
- 目前来看,完全用同步的web函数不可能,3秒的执行时间会导致你等不到 openai 响应就整个函数会被kill掉。除非你在web函数中再触发异步函数去执行请求与获取响应,整个下来,费用跟调试成本都比较高,我假设大家都有自己的服务器,是真的不如把自己的服务器利用起来,后续有时间也会尝试用异步函数试试~
36-
37-
## 效果如图
38-
24+
- 云服务器 1h2g
25+
- 如果是自己注册的企业微信,那么其实是不需要域名的,直接ip访问
26+
- 如果是企业微信已经关联了备案主体,那么需要开一个备案的二级域名解析到服务器,nginx 做下转发就行
27+
- 其它情况,如我域名没备案,但是我就是想用这个域名解析到我的服务器,
28+
- 那就就可以考虑下面这种 [云函数/网关转发思路,点击查看](./doc/cloudfc.md)
3929

4030
## 如何使用本项目代码?
4131

@@ -53,80 +43,24 @@
5343
- 可以在详情页看到 企业可信IP的配置,把你服务器的公网IP 填入就好,如果没有这个配置项,就说明是老应用,无需处理,这步跳过
5444
![image21.png](./doc/image21.png)
5545

56-
### 2. 点击启用消息
57-
58-
会进入验证步骤, 先不验证 url 我们可以 拿到 Token 跟 EncodingAESKey
59-
![image2.png](./doc/image2.png)
60-
61-
### 3. 访问 [阿里云函数计算 fc ](https://fcnext.console.aliyun.com/cn-hangzhou/services)
62-
63-
创建一个新的服务与函数 **重点** 需要选择中国大陆以外的区域,如香港/日本
64-
![image2.png](./doc/image16.png)
65-
66-
登录 [阿里云函数计算 fc](https://fcnext.console.aliyun.com/cn-hangzhou/services) ,创建一个新的 Node.js v16/v14 的服务,服务名可以根据你的需要填写,可以填写 ChatGPT .
67-
68-
![image4.png](./doc/image4.png)
69-
70-
再创建一个函数,函数名也可以随意
71-
72-
![image5.png](./doc/image5.png)
73-
74-
### 4. 复制本项目下的 aliyunfc/index.js 的源码内容,并粘贴到 webide 当中
75-
76-
然后点击顶部的 deploy ,完成第一次部署。
77-
78-
![image6.png](./doc/image6.png)
79-
80-
### 5. 安装所需依赖
81-
82-
这个开发过程中,我们使用了企业微信开放平台官方提供的 SDK,以及 axios 来完成调用。在webide中开启终端,安装 `axios``@wecom/crypto` 还有 `xmlreader`
83-
84-
```shell
85-
npm i axios
86-
npm i @wecom/crypto
87-
npm i xmlreader
88-
```
89-
90-
![image7.png](./doc/image7.png)
91-
92-
安装完成后,点击上方的部署,使其生效。
93-
94-
### 5.5 将自己的域名 cname 解析到 云函数
95-
96-
- 进入 fc -> 高级功能 -> 域名管理
97-
![image17.png](./doc/image17.png)
98-
99-
- 我们可以找到这个cname 的值
100-
101-
- 接下来复制这个值去购买域名的服务商,下配置 cname
102-
103-
![image18.png](./doc/image18.png)
104-
105-
- 配置好后,等待1分钟,点击保存,我们就成功的把未备案域名 解析到了香港的云函数上
106-
107-
### 6. 配置环境变量
108-
109-
接下来我们回到函数管理来配置环境变量,你需要配置两个个环境变量 `aes_key``aes_token` `aes_key` 填写你第二步获取到的 EncodingAESKey,`aes_token` 填写你第二步获取到的 Token。
110-
![image8.png](./doc/image8.png)
111-
112-
配置完成,点确认后,再次点击上方的 **Deploy** 按钮部署,使这些环境变量生效。这个时候去 企业微信里面,
113-
![image12.png](./doc/image12.png)
114-
填入函数的 url , 点击保存, 验证就通过了.
115-
![image19.png](./doc/image19.png)
116-
117-
**url 就是你刚刚 cname 解析过去的自己的域名**
118-
119-
### 7. 获取 OpenAI 的 KEY ,并配置环境变量
46+
### 2. 获取 OpenAI 的 KEY
12047

12148
访问 [Account API Keys - OpenAI API](https://platform.openai.com/account/api-keys) ,点击 `Create new secret key` ,创建一个新的 key ,并保存备用。
12249
![image10.png](./doc/image10.png)
12350

124-
### 8. 在自购服务器上 部署 golang 服务,并开启对外的网络端口
51+
### 3. 点击启用消息
52+
53+
会进入验证步骤, 先不验证 url 我们可以 拿到 Token 跟 EncodingAESKey
54+
![image2.png](./doc/image2.png)
55+
56+
### 4. 在自购服务器上 部署 golang 服务,并开启对外的网络端口
12557
- 前提条件,需要有一个自己的服务器,或者云服务器
12658
- 执行 docker -v 是否有版本号?
12759
- 执行 docker-compose -v 是否有版本号?
60+
![image29](./doc/image29.png)
61+
12862
- 确认这两个软件都安装后
129-
-
63+
13064
```shell
13165
# 进入chat 后端目录
13266
cd ./chat
@@ -135,14 +69,33 @@ cd ./chat
13569
cp ./service/chat/api/etc/chat-api.yaml.bak ./service/chat/api/etc/chat-api.yaml
13670
vim ./service/chat/api/etc/chat-api.yaml
13771
```
138-
- 修改这三个配置项
139-
![image20.png](./doc/image20.png)
72+
73+
- 修改这5个配置项
74+
![image25.png](./doc/image25.png)
14075

14176
- 前两个是企业微信 的配置
142-
- corpSecret 就是 步骤一中的 Secret
14377
- 访问 企业微信-管理员页面 , 可在 我的企业 > 企业信息 > 底部 看到 CorpID
78+
- corpSecret 就是 步骤一中的 Secret
79+
- Token 跟 EncodingAESKey 可以在步骤二中拿到
14480

14581
- 最后一个 是 openAPI 生成 KEY 的值
82+
---
83+
#### 3.1 重点,因为 openai 对于大陆地区的封锁,如果你的服务器在国内,这边提供了两个方案
84+
1. 自建 代理服务器,然后在 chat-api.yaml 中配置代理服务器的地址,相关的参数在 `chat-api.yaml.complete.bak`
85+
```yaml
86+
Proxy: # 代理配置 (可选)
87+
Enable: false # 是否启用代理,默认为 false(可选)
88+
Socket5: "127.0.0.1:1080" # 代理地址 默认为 127.0.0.1:1080(可选)
89+
```
90+
如何自建代理,点击查看 [自建代理](./doc/proxy.md)
91+
92+
2. 使用 cf 自建反向域名代理,然后用的代理域名替换掉,OpenAi 的 Host 即可
93+
```yaml
94+
OpenAi: # openai配置
95+
Key: "xxxxxxxxxxxxxxxxxxxxx" # openai key
96+
Host: "https://api.openai.com" # openai host (可选,使用cf进行反向代理时,修改可用)
97+
```
98+
如何自建反向域名代理,点击查看 [自建反向域名代理](./doc/cf.md)
14699
147100
```shell
148101
# 修改好后生成集成应用镜像
@@ -151,37 +104,12 @@ sudo docker-compose build
151104
# 启动集成应用
152105
sudo docker-compose up -d
153106
```
154-
- 应用启动成功后 我们需要去拿 req_host 和 req_token
155-
- `req_host` 就是部署服务器的 `http://{host}:8888/api/msg/push` `{host}` 就是你服务器的ip
156-
- `req_token` 就是自己注册一个账号,调用登录api获取到的 token ,集体步骤如下
157-
- 调用注册api
158-
```shell
159-
curl --location 'localhost:8888/api/user/register' \
160-
--header 'Content-Type: application/json' \
161-
--data '{"email": "[email protected]","name": "admin","password": "admin123"}'
162-
```
163-
- 调用登录api
164-
```shell
165-
curl --location 'localhost:8888/api/user/login' \
166-
--header 'Content-Type: application/json' \
167-
--data '{"email": "[email protected]","password": "admin123"}'
168-
```
169-
- 登录API 响应
170-
```json
171-
{
172-
"code":200,
173-
"msg":"成功",
174-
"data":{
175-
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDM1Njk0MzgsImlhdCI6MTY3NzY0OTQzOCwidXNlcklkIjoxfQ.mjRJcu3WNaqAYHB1RbG3qoBezzbEsW6weq8amOvGAaU"
176-
}
177-
}
178-
```
179-
- 所以 `req_token` 就是 `data.token` 的值
107+
- 最后在 企业微信的配置中,把 服务器地址 `http://{host}:8887` 填入,如下图
108+
![image26.png](./doc/image26.png)
180109

181-
- 最后把 `req_host``req_token` 配置到阿里云函数云的环境变量中
182110
- 🎉🎉 你的机器人就配置好了
183111

184-
### 9. 正式布发布与微信打通
112+
### 5. 正式布发布与微信打通
185113

186114
可在 我的企业 > 微信插件 > 下方找到 一个邀请关注二维码,
187115
![image13.png](./doc/image13.png)
@@ -194,18 +122,19 @@ curl --location 'localhost:8888/api/user/login' \
194122

195123
![image99.png](./doc/image99.png)
196124

197-
## FAQ
125+
- 如果需要企业自定义方案,也可以wx我 `whyiyhwxy`
198126

199-
## [版本更新日志,点击查看](./doc/CHANGELOG.md)
127+
## changelog [版本更新日志,点击查看](./doc/CHANGELOG.md)
200128

201129
### feature 版本 考虑与执行中
202130
- [x] 单服务-多应用支持 2023-03-05
203131
- [x] 新增代理设置 2023-03-05
204132
- [x] 支持最新的 gpt3.5 与模型可自行切换
205133
- [x] 支持 prompt 自定义配置
206134
- [x] 命令式动态调整对话参数
207-
- [x] 系统设置&预定义模板
208-
- [ ] 支持 openapi 对话 token 累计功能, 余额不足时,支持 token 更换
135+
- [x] 系统设置&预定义模板 2023-03-17
136+
- [x] 支持服务端直接对接企业微信,无需云函数中转 2023-03-18
137+
- [ ] 支持 openapi 对话 token 累计功能, 余额不足时,支持 token 更换
209138
- [ ] 支持作图功能(可选)
210139
- [ ] 支持英语语音输入(可选)
211140
- [ ] 支持特定角色对话-如雅思口语练习(可选)

chat/common/wecom/wecom.go

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
package wecom
22

3-
import "github.com/xen0n/go-workwx"
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"strings"
9+
"time"
10+
11+
"github.com/golang-jwt/jwt/v4"
12+
"github.com/xen0n/go-workwx"
13+
)
14+
15+
var Token string
416

517
func SendToUser(agentID int64, userID string, msg string, corpID string, corpSecret string) {
618

@@ -17,7 +29,7 @@ func SendToUser(agentID int64, userID string, msg string, corpID string, corpSec
1729
//3. 企业微信会话发送文字的字符上限
1830
//(1)会话消息目前支持2000(不确定)?字符,1个汉字=2字符,1个英文、符号=1个字符。
1931
if len(rs) > 850 {
20-
msgs := SplitMsg(rs, 850)
32+
msgs := splitMsg(rs, 850)
2133
for _, v := range msgs {
2234
_ = app.SendTextMessage(&recipient, v, false)
2335
}
@@ -28,7 +40,7 @@ func SendToUser(agentID int64, userID string, msg string, corpID string, corpSec
2840
}()
2941
}
3042

31-
func SplitMsg(rs []rune, i int) []string {
43+
func splitMsg(rs []rune, i int) []string {
3244
var msgs []string
3345
for len(rs) > i {
3446
msgs = append(msgs, string(rs[:i]))
@@ -37,3 +49,112 @@ func SplitMsg(rs []rune, i int) []string {
3749
msgs = append(msgs, string(rs))
3850
return msgs
3951
}
52+
53+
type dummyRxMessageHandler struct{}
54+
55+
var _ workwx.RxMessageHandler = dummyRxMessageHandler{}
56+
57+
// OnIncomingMessage 一条消息到来时的回调。
58+
func (dummyRxMessageHandler) OnIncomingMessage(msg *workwx.RxMessage) error {
59+
// You can do much more!
60+
fmt.Printf("incoming message: %s\n", msg)
61+
62+
if msg.MsgType == workwx.MessageTypeText {
63+
message, ok := msg.Text()
64+
if ok {
65+
realLogic("openai", message.GetContent(), msg.FromUserID, msg.AgentID)
66+
}
67+
}
68+
if msg.MsgType == workwx.MessageTypeEvent {
69+
if string(msg.Event) == "enter_agent" {
70+
realLogic("openai", "#welcome", msg.FromUserID, msg.AgentID)
71+
}
72+
}
73+
if msg.MsgType == workwx.MessageTypeImage {
74+
p, ok := msg.Image()
75+
if ok {
76+
realLogic("openai", "#image:"+p.GetPicURL(), msg.FromUserID, msg.AgentID)
77+
}
78+
}
79+
80+
return nil
81+
}
82+
83+
func XmlServe(pToken, pEncodingAESKey, accessSecret string, accessExpire int64) {
84+
pAddr := "[::]:8887"
85+
86+
// build a json web token
87+
iat := time.Now().Unix()
88+
claims := make(jwt.MapClaims)
89+
claims["exp"] = iat + accessExpire
90+
claims["iat"] = iat
91+
claims["userId"] = 1
92+
token := jwt.New(jwt.SigningMethodHS256)
93+
token.Claims = claims
94+
Token, _ = token.SignedString([]byte(accessSecret))
95+
96+
hh, err := workwx.NewHTTPHandler(pToken, pEncodingAESKey, dummyRxMessageHandler{})
97+
if err != nil {
98+
panic(err)
99+
}
100+
mux := http.NewServeMux()
101+
mux.Handle("/", hh)
102+
103+
err = http.ListenAndServe(pAddr, mux)
104+
if err != nil {
105+
panic(err)
106+
}
107+
}
108+
109+
func realLogic(channel, msg, userID string, agentID int64) {
110+
url := "http://localhost:8888/api/msg/push"
111+
method := "POST"
112+
113+
type ChatReq struct {
114+
Channel string `json:"channel"`
115+
MSG string `json:"msg"`
116+
UserID string `json:"user_id"`
117+
AgentID int64 `json:"agent_id"`
118+
}
119+
120+
r := ChatReq{
121+
Channel: channel,
122+
MSG: msg,
123+
UserID: userID,
124+
AgentID: agentID,
125+
}
126+
127+
b, _ := json.Marshal(r)
128+
129+
payload := strings.NewReader(string(b))
130+
131+
client := &http.Client{}
132+
req, err := http.NewRequest(method, url, payload)
133+
134+
if err != nil {
135+
fmt.Println(err)
136+
return
137+
}
138+
139+
req.Header.Add("Content-Type", "application/json")
140+
req.Header.Add("Authorization", "Bearer "+Token)
141+
142+
res, err := client.Do(req)
143+
if err != nil {
144+
fmt.Println(err)
145+
return
146+
}
147+
defer func(Body io.ReadCloser) {
148+
err := Body.Close()
149+
if err != nil {
150+
151+
}
152+
}(res.Body)
153+
154+
body, err := io.ReadAll(res.Body)
155+
if err != nil {
156+
fmt.Println(err)
157+
return
158+
}
159+
fmt.Println(string(body))
160+
}

chat/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ services:
77
dockerfile: Dockerfile
88
ports:
99
- "8888:8888"
10+
- "8887:8887"
1011
privileged: true
1112
restart: always
1213
networks:

0 commit comments

Comments
 (0)