提交 89286ebe 编写于 作者: wangxinxu's avatar wangxinxu

init

上级
流水线 #52128 已取消 于阶段
FROM golang:1.17.10 AS builder
# ENV GOPROXY https://goproxy.io
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN go build -o chatgpt-dingtalk .
FROM centos:centos7
RUN mkdir /app
WORKDIR /app
COPY --from=builder /app/ .
RUN chmod +x chatgpt-dingtalk && cp config.dev.json config.json && yum -y install vim net-tools telnet wget curl && yum clean all
CMD ./chatgpt-dingtalk
\ No newline at end of file
MIT License
Copyright (c) 2022 二丫讲梵
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
default: build
run:
GIN_MODE=release go run main.go
build:
go build -o chatgpt-dingtalk main.go
build-linux:
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o chatgpt-dingtalk main.go
build-linux-arm:
CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build -o chatgpt-dingtalk main.go
lint:
env GOGC=25 golangci-lint run --fix -j 8 -v ./...
\ No newline at end of file
<div align="center">
<h1>ChatGPT Dingtalk</h1>
[![Auth](https://img.shields.io/badge/Auth-eryajf-ff69b4)](https://github.com/eryajf)
[![Go Version](https://img.shields.io/github/go-mod/go-version/eryajf/chatgpt-dingtalk)](https://github.com/eryajf/chatgpt-dingtalk)
[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/eryajf/chatgpt-dingtalk)](https://github.com/eryajf/chatgpt-dingtalk/pulls)
[![GitHub Pull Requests](https://img.shields.io/github/stars/eryajf/chatgpt-dingtalk)](https://github.com/eryajf/chatgpt-dingtalk/stargazers)
[![HitCount](https://views.whatilearened.today/views/github/eryajf/chatgpt-dingtalk.svg)](https://github.com/eryajf/chatgpt-dingtalk)
[![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/eryajf/chatgpt-dingtalk)](https://hub.docker.com/r/eryajf/chatgpt-dingtalk)
[![Docker Pulls](https://img.shields.io/docker/pulls/eryajf/chatgpt-dingtalk)](https://hub.docker.com/r/eryajf/chatgpt-dingtalk)
[![GitHub license](https://img.shields.io/github/license/eryajf/chatgpt-dingtalk)](https://github.com/eryajf/chatgpt-dingtalk/blob/main/LICENSE)
<p> 🌉 在钉钉群聊中添加ChatGPT机器人 🌉</p>
<img src="https://camo.githubusercontent.com/82291b0fe831bfc6781e07fc5090cbd0a8b912bb8b8d4fec0696c881834f81ac/68747470733a2f2f70726f626f742e6d656469612f394575424971676170492e676966" width="800" height="3">
</div><br>
## 前言
最近ChatGPT异常火爆,本项目可以将GPT机器人集成到钉钉群聊中。
`感谢:`这个项目借鉴了[wechatbot](https://github.com/869413421/wechatbot.git),wechatbot是一个能够集成到个人微信的GPT机器人,如果需要,可以前去体验。
## 功能简介
* 支持在钉钉群聊中添加机器人,通过@机器人进行聊天交互。
* 提问增加上下文(可能不太理想),更接近官网效果。
## 使用前提
* 有openai账号,并且创建好api_key,注册相关事项可以参考[此文章](https://juejin.cn/post/7173447848292253704) 。访问[这里](https://beta.openai.com/account/api-keys),申请个人秘钥。
* 在钉钉开发者后台创建机器人,配置应用程序回调。
## 使用教程
### 第一步,先创建机器人
创建步骤参考文档:[企业内部开发机器人](https://open.dingtalk.com/document/robots/enterprise-created-chatbot),或者根据如下步骤进行配置。
1. 创建机器人。
![image_20221209_163616](https://cdn.staticaly.com/gh/eryajf/tu/main/img/image_20221209_163616.png)
步骤比较简单,这里就不赘述了。
2. 配置机器人回调接口。
![image_20221209_163652](https://cdn.staticaly.com/gh/eryajf/tu/main/img/image_20221209_163652.png)
创建完毕之后,点击机器人开发管理,然后配置将要部署的服务所在服务器的出口IP,以及将要给服务配置的域名。
3. 发布机器人。
![image_20221209_163709](https://cdn.staticaly.com/gh/eryajf/tu/main/img/image_20221209_163709.png)
点击版本管理与发布,然后点击上线,这个时候就能在钉钉的群里中添加这个机器人了。
4. 群聊添加机器人。
![image_20221209_163724](https://cdn.staticaly.com/gh/eryajf/tu/main/img/image_20221209_163724.png)
### 第二步,部署应用
你可以使用docker快速运行本项目。
`第一种:基于环境变量运行`
```sh
# 运行项目
$ docker run -itd --name chatgpt -p 8090:8090 -e APIKEY=换成你的key -e SESSIONTIMEOUT=60s -e MODEL=text-davinci-003 -e MAX_TOKENS=512 -e TEMPREATURE=0.9 -e SESSION_CLEAR_TOKEN=清空会话 --restart=always docker.mirrors.sjtug.sjtu.edu.cn/eryajf/chatgpt-dingtalk:latest
```
运行命令中映射的配置文件参考下边的配置文件说明。
`第二种:基于配置文件挂载运行`
```sh
# 复制配置文件,根据自己实际情况,调整配置里的内容
$ cp config.dev.json config.json # 其中 config.dev.json 从项目的根目录获取
# 运行项目
$ docker run -itd --name chatgpt -p 8090:8090 -v `pwd`/config.json:/app/config.json --restart=always docker.mirrors.sjtug.sjtu.edu.cn/eryajf/chatgpt-dingtalk:latest
```
其中配置文件参考下边的配置文件说明。
注意,不论通过上边哪种docker方式部署,都需要配置Nginx代理,当然你直接通过服务器外网IP也可以。
部署完成之后,通过Nginx代理本服务:
```nginx
server {
listen 80;
server_name chat.eryajf.net;
client_header_timeout 120s;
client_body_timeout 120s;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://localhost:8090;
}
}
```
部署完成之后,就可以在群里艾特机器人进行体验了。
Nginx配置完毕之后,可以先手动请求一下,通过服务日志输出判断服务是否正常可用:
```sh
$ curl --location --request POST 'http://chat.eryajf.net/' \
--header 'Content-type: application/json' \
--data-raw '{
"conversationId": "xxx",
"atUsers": [
{
"dingtalkId": "xxx",
"staffId":"xxx"
}
],
"chatbotCorpId": "dinge8a565xxxx",
"chatbotUserId": "$:LWCP_v1:$Cxxxxx",
"msgId": "msg0xxxxx",
"senderNick": "eryajf",
"isAdmin": true,
"senderStaffId": "user123",
"sessionWebhookExpiredTime": 1613635652738,
"createAt": 1613630252678,
"senderCorpId": "dinge8a565xxxx",
"conversationType": "2",
"senderId": "$:LWCP_v1:$Ff09GIxxxxx",
"conversationTitle": "机器人测试-TEST",
"isInAtList": true,
"sessionWebhook": "https://oapi.dingtalk.com/robot/sendBySession?session=xxxxx",
"text": {
"content": " 你好"
},
"msgtype": "text"
}'
```
如果手动请求没有问题,那么就可以在钉钉群里与机器人进行对话了。
效果如下:
![image_20221209_163739](https://cdn.staticaly.com/gh/eryajf/tu/main/img/image_20221209_163739.png)
---
如果你想通过命令行直接部署,可以直接下载release中的[压缩包](https://github.com/eryajf/chatgpt-dingtalk/releases) ,请根据自己系统以及架构选择合适的压缩包,下载之后直接解压运行。
下载之后,在本地解压,即可看到可执行程序,与配置文件:
```
$ tar xf chatgpt-dingtalk-v0.0.4-darwin-arm64.tar.gz
$ cd chatgpt-dingtalk-v0.0.4-darwin-arm64
$ cp config.dev.json # 根据情况调整配置文件内容
$ ./chatgpt-dingtalk # 直接运行
# 如果要守护在后台运行
$ nohup ./chatgpt-dingtalk &> run.log &
$ tail -f run.log
```
## 本地开发
```sh
# 获取项目
$ git clone https://github.com/eryajf/chatgpt-dingtalk.git
# 进入项目目录
$ cd chatgpt-dingtalk
# 复制配置文件,根据个人实际情况进行配置
$ cp config.dev.json config.json
# 启动项目
$ go run main.go
```
## 配置文件说明
```json
{
"api_key": "xxxxxxxxx", // openai api_key
"session_timeout": 60, // 会话超时时间,默认60,在会话时间内所有发送给机器人的信息会作为上下文
"max_tokens": 1024, // GPT响应字符数,最大2048,默认值512。值大小会影响接口响应速度,越大响应越慢。
"model": "text-davinci-003", // GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
"temperature": 0.9, // GPT热度,01,默认0.9。数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
"session_clear_token": "清空会话" // 会话清空口令,默认`清空会话`
}
```
## 常见问题
- Q: 钉钉群聊艾特机器人之后,没有回应,应用也没有任何输出
- A: 注意钉钉艾特群聊之后,会通过上文配置的回调IP与域名把请求发过来,如果这个环节有问题,那么是接收不到请求的,因此配置完成之后,建议通过curl验证下自己的服务。
- Q: 一切配置完毕之后,群聊艾特机器人没有反应,看应用输出内容为:回调参数为空,以至于无法正常解析,请检查原因
- A: 可能是创建的机器人有问题,建议重新走一遍创建机器人的流程,创建一个新的机器人再试试。
> 本项目曾在[2022-12-12](https://github.com/bonfy/github-trending/blob/master/2022/2022-12-12.md#go),[2022-12-18](https://github.com/bonfy/github-trending/blob/master/2022/2022-12-18.md#go),[2022-12-19](https://github.com/bonfy/github-trending/blob/master/2022/2022-12-19.md#go),[2022-12-20](https://github.com/bonfy/github-trending/blob/master/2022/2022-12-20.md#go),[2023-02-09](https://github.com/bonfy/github-trending/blob/master/2023-02-09.md#go),[2023-02-10](https://github.com/bonfy/github-trending/blob/master/2023-02-10.md#go),[2023-02-11](https://github.com/bonfy/github-trending/blob/master/2023-02-11.md#go),[2023-02-12](https://github.com/bonfy/github-trending/blob/master/2023-02-12.md#go),这些天里,登上GitHub Trending.
\ No newline at end of file
{
"api_key": "xxxxxxxxx",
"session_timeout": 180,
"max_tokens": 2000,
"model": "text-davinci-003",
"temperature": 0.9,
"session_clear_token": "清空会话"
}
\ No newline at end of file
package config
import (
"encoding/json"
"fmt"
"os"
"strconv"
"sync"
"time"
"github.com/eryajf/chatgpt-dingtalk/public/logger"
)
// Configuration 项目配置
type Configuration struct {
// gtp apikey
ApiKey string `json:"api_key"`
// 会话超时时间
SessionTimeout time.Duration `json:"session_timeout"`
// GPT请求最大字符数
MaxTokens uint `json:"max_tokens"`
// GPT模型
Model string `json:"model"`
// 热度
Temperature float64 `json:"temperature"`
// 自定义清空会话口令
SessionClearToken string `json:"session_clear_token"`
}
var config *Configuration
var once sync.Once
// LoadConfig 加载配置
func LoadConfig() *Configuration {
once.Do(func() {
// 从文件中读取
config = &Configuration{}
f, err := os.Open("config.json")
if err != nil {
logger.Danger("open config err: %v", err)
return
}
defer f.Close()
encoder := json.NewDecoder(f)
err = encoder.Decode(config)
if err != nil {
logger.Warning("decode config err: %v", err)
return
}
// 如果环境变量有配置,读取环境变量
ApiKey := os.Getenv("APIKEY")
SessionTimeout := os.Getenv("SESSION_TIMEOUT")
Model := os.Getenv("MODEL")
MaxTokens := os.Getenv("MAX_TOKENS")
Temperature := os.Getenv("TEMPREATURE")
SessionClearToken := os.Getenv("SESSION_CLEAR_TOKEN")
if ApiKey != "" {
config.ApiKey = ApiKey
}
if SessionTimeout != "" {
duration, err := strconv.ParseInt(SessionTimeout, 10, 64)
if err != nil {
logger.Danger(fmt.Sprintf("config session timeout err: %v ,get is %v", err, SessionTimeout))
return
}
config.SessionTimeout = time.Duration(duration) * time.Second
} else {
config.SessionTimeout = time.Duration(config.SessionTimeout) * time.Second
}
if Model != "" {
config.Model = Model
}
if MaxTokens != "" {
max, err := strconv.Atoi(MaxTokens)
if err != nil {
logger.Danger(fmt.Sprintf("config MaxTokens err: %v ,get is %v", err, MaxTokens))
return
}
config.MaxTokens = uint(max)
}
if Temperature != "" {
temp, err := strconv.ParseFloat(Temperature, 64)
if err != nil {
logger.Danger(fmt.Sprintf("config Temperature err: %v ,get is %v", err, Temperature))
return
}
config.Temperature = temp
}
if SessionClearToken != "" {
config.SessionClearToken = SessionClearToken
}
})
if config.ApiKey == "" {
logger.Danger("config err: api key required")
}
return config
}
module github.com/eryajf/chatgpt-dingtalk
go 1.17
require (
github.com/go-resty/resty/v2 v2.7.0
github.com/patrickmn/go-cache v2.1.0+incompatible
)
require golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
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=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
package gpt
import (
"crypto/tls"
"encoding/json"
"fmt"
"time"
"github.com/eryajf/chatgpt-dingtalk/config"
"github.com/eryajf/chatgpt-dingtalk/public/logger"
"github.com/go-resty/resty/v2"
)
const BASEURL = "https://api.openai.com/v1/"
// ChatGPTRequestBody 请求体
type ChatGPTRequestBody struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
MaxTokens uint `json:"max_tokens"`
Temperature float64 `json:"temperature"`
}
// ChatGPTResponseBody 响应体
type ChatGPTResponseBody struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Choices []ChoiceItem `json:"choices"`
Usage map[string]interface{} `json:"usage"`
}
type ChoiceItem struct {
Text string `json:"text"`
Index int `json:"index"`
Logprobs int `json:"logprobs"`
FinishReason string `json:"finish_reason"`
}
// Completions gtp文本模型回复
//curl https://api.openai.com/v1/completions
//-H "Content-Type: application/json"
//-H "Authorization: Bearer your chatGPT key"
//-d '{"model": "text-davinci-003", "prompt": "give me good song", "temperature": 0, "max_tokens": 7}'
func Completions(msg string) (string, error) {
cfg := config.LoadConfig()
requestBody := ChatGPTRequestBody{
Model: cfg.Model,
Prompt: msg,
MaxTokens: cfg.MaxTokens,
Temperature: cfg.Temperature,
}
client := resty.New().
SetRetryCount(2).
SetRetryWaitTime(1*time.Second).
SetTimeout(cfg.SessionTimeout).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+cfg.ApiKey).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
rsp, err := client.R().SetBody(requestBody).Post(BASEURL + "completions")
if err != nil {
return "", fmt.Errorf("request openai failed, err : %v", err)
}
if rsp.StatusCode() != 200 {
return "", fmt.Errorf("gtp api status code not equals 200, code is %d ,details: %v ", rsp.StatusCode(), string(rsp.Body()))
} else {
logger.Info(fmt.Sprintf("response gtp json string : %v", string(rsp.Body())))
}
gptResponseBody := &ChatGPTResponseBody{}
err = json.Unmarshal(rsp.Body(), gptResponseBody)
if err != nil {
return "", err
}
var reply string
if len(gptResponseBody.Choices) > 0 {
reply = gptResponseBody.Choices[0].Text
}
return reply, nil
}
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/eryajf/chatgpt-dingtalk/gpt"
"github.com/eryajf/chatgpt-dingtalk/public"
"github.com/eryajf/chatgpt-dingtalk/public/logger"
"github.com/eryajf/chatgpt-dingtalk/service"
)
var UserService service.UserServiceInterface
func init() {
UserService = service.NewUserService()
}
func main() {
Start()
}
func Start() {
// 定义一个处理器函数
handler := func(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
logger.Warning("read request body failed: %v\n", err.Error())
return
}
// TODO: 校验请求
if len(data) == 0 {
logger.Warning("回调参数为空,以至于无法正常解析,请检查原因")
return
} else {
var msgObj = new(public.ReceiveMsg)
err = json.Unmarshal(data, &msgObj)
if err != nil {
logger.Warning("unmarshal request body failed: %v\n", err)
}
logger.Info(fmt.Sprintf("dingtalk callback parameters: %#v", msgObj))
err = ProcessRequest(*msgObj)
if err != nil {
logger.Warning("process request failed: %v\n", err)
}
}
}
// 创建一个新的 HTTP 服务器
server := &http.Server{
Addr: ":8090",
Handler: http.HandlerFunc(handler),
}
// 启动服务器
logger.Info("Start Listen On ", server.Addr)
err := server.ListenAndServe()
if err != nil {
logger.Danger(err)
}
}
func ProcessRequest(rmsg public.ReceiveMsg) error {
atText := "@" + rmsg.SenderNick + "\n" + " "
if UserService.ClearUserSessionContext(rmsg.SenderID, rmsg.Text.Content) {
_, err := rmsg.ReplyText(atText + "上下文已经清空了,你可以问下一个问题啦。")
if err != nil {
logger.Warning("response user error: %v \n", err)
return err
}
} else {
requestText := getRequestText(rmsg)
// 获取问题的答案
reply, err := gpt.Completions(requestText)
if err != nil {
logger.Info("gpt request error: %v \n", err)
_, err = rmsg.ReplyText("机器人太累了,让她休息会儿,过一会儿再来请求。")
if err != nil {
logger.Warning("send message error: %v \n", err)
return err
}
logger.Info("request openai error: %v\n", err)
return err
}
if reply == "" {
logger.Warning("get gpt result falied: %v\n", err)
return nil
}
// 回复@我的用户
reply = strings.TrimSpace(reply)
reply = strings.Trim(reply, "\n")
UserService.SetUserSessionContext(rmsg.SenderID, requestText, reply)
replyText := atText + reply
_, err = rmsg.ReplyText(replyText)
if err != nil {
logger.Info("send message error: %v \n", err)
return err
}
}
return nil
}
// getRequestText 获取请求接口的文本,要做一些清洗
func getRequestText(rmsg public.ReceiveMsg) string {
// 1.去除空格以及换行
requestText := strings.TrimSpace(rmsg.Text.Content)
requestText = strings.Trim(rmsg.Text.Content, "\n")
// 2.替换掉当前用户名称
replaceText := "@" + rmsg.SenderNick
requestText = strings.TrimSpace(strings.ReplaceAll(rmsg.Text.Content, replaceText, ""))
if requestText == "" {
return ""
}
// 3.获取上下文,拼接在一起,如果字符长度超出4000,截取为4000。(GPT按字符长度算)
requestText = UserService.GetUserSessionContext(rmsg.SenderID) + requestText
if len(requestText) >= 4000 {
requestText = requestText[:4000]
}
// 4.返回请求文本
return requestText
}
package public
import (
"bytes"
"encoding/json"
"net/http"
)
// 接收的消息体
type ReceiveMsg struct {
ConversationID string `json:"conversationId"`
AtUsers []struct {
DingtalkID string `json:"dingtalkId"`
} `json:"atUsers"`
ChatbotUserID string `json:"chatbotUserId"`
MsgID string `json:"msgId"`
SenderNick string `json:"senderNick"`
IsAdmin bool `json:"isAdmin"`
SessionWebhookExpiredTime int64 `json:"sessionWebhookExpiredTime"`
CreateAt int64 `json:"createAt"`
ConversationType string `json:"conversationType"`
SenderID string `json:"senderId"`
ConversationTitle string `json:"conversationTitle"`
IsInAtList bool `json:"isInAtList"`
SessionWebhook string `json:"sessionWebhook"`
Text Text `json:"text"`
RobotCode string `json:"robotCode"`
Msgtype string `json:"msgtype"`
}
// 发送的消息体
type SendMsg struct {
Text Text `json:"text"`
Msgtype string `json:"msgtype"`
}
// 消息内容
type Text struct {
Content string `json:"content"`
}
// 发消息给钉钉
func (r ReceiveMsg) ReplyText(msg string) (statuscode int, err error) {
// 定义消息
msgtmp := &SendMsg{Text: Text{Content: msg}, Msgtype: "text"}
data, err := json.Marshal(msgtmp)
if err != nil {
return 0, err
}
req, err := http.NewRequest("POST", r.SessionWebhook, bytes.NewBuffer(data))
if err != nil {
return 0, err
}
req.Header.Add("Accept", "*/*")
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
return resp.StatusCode, nil
}
package logger
import (
"log"
"os"
"sync"
)
var Logger *log.Logger
var once sync.Once
func init() {
once.Do(func() {
Logger = log.New(os.Stdout, "INFO", log.Ldate|log.Ltime|log.Lshortfile)
})
}
// Info 详情
func Info(args ...interface{}) {
Logger.SetPrefix("[INFO]")
Logger.Println(args...)
}
// Danger 错误 为什么不命名为 error?避免和 error 类型重名
func Danger(args ...interface{}) {
Logger.SetPrefix("[ERROR]")
Logger.Fatal(args...)
}
// Warning 警告
func Warning(args ...interface{}) {
Logger.SetPrefix("[WARNING]")
Logger.Println(args...)
}
// DeBug debug
func DeBug(args ...interface{}) {
Logger.SetPrefix("[DeBug]")
Logger.Println(args...)
}
package service
import (
"strings"
"time"
"github.com/eryajf/chatgpt-dingtalk/config"
"github.com/patrickmn/go-cache"
)
// UserServiceInterface 用户业务接口
type UserServiceInterface interface {
GetUserSessionContext(userId string) string
SetUserSessionContext(userId string, question, reply string)
ClearUserSessionContext(userId string, msg string) bool
}
var _ UserServiceInterface = (*UserService)(nil)
// UserService 用戶业务
type UserService struct {
// 缓存
cache *cache.Cache
}
// ClearUserSessionContext 清空GTP上下文,接收文本中包含 SessionClearToken
func (s *UserService) ClearUserSessionContext(userId string, msg string) bool {
// 清空会话
if strings.Contains(msg, config.LoadConfig().SessionClearToken) {
s.cache.Delete(userId)
return true
}
return false
}
// NewUserService 创建新的业务层
func NewUserService() UserServiceInterface {
return &UserService{cache: cache.New(time.Second*config.LoadConfig().SessionTimeout, time.Minute*10)}
}
// GetUserSessionContext 获取用户会话上下文文本
func (s *UserService) GetUserSessionContext(userId string) string {
sessionContext, ok := s.cache.Get(userId)
if !ok {
return ""
}
return sessionContext.(string)
}
// SetUserSessionContext 设置用户会话上下文文本,question用户提问内容,GTP回复内容
func (s *UserService) SetUserSessionContext(userId string, question, reply string) {
value := question + "\n" + reply
s.cache.Set(userId, value, time.Second*config.LoadConfig().SessionTimeout)
}
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册