好玩多了,chatGPT接入微信公众号方法总结(绝对可行)

6,558 阅读12分钟

chatGPT挺好用的。但是想用时就得打开网页或者其他工具插件才行?太不方便了。记得有次要给媳妇演示下它的强大,竟没带电脑竟啥事干不成。索性把chatGPT接入微信公众号,这样在公众号聊天窗口里发消息,chatGPT自动给我回复内容,且可以分享给好友邀好友一块儿体验,这太好玩儿了。

好玩多了,chatGPT接入微信公众号后可以随时随地使用它了。这里总结下chatGPT接入微信的方法。效果展示:

image.png

环境准备

1.微信公众号。测试的就行,也建议用测试的,很容易申请,无门槛且不限制次数。

2.chatGPT的访问api-key,这个需要科学上网申请个账号,获取api-key并保存好。

3.一台云服务器。各大云商有提供,包年很便宜,部署下Golang的后台服务。

具备以上条件就ok啦,接下来介绍下微信公众号接入方法。

我的首页里关注公众号可体验功能。测试号支持最多一百个用户,名额有限,目前还有剩余可以体验,欢迎交流。

实现思路

总体方案介绍

通过搭建后台云服务,实现微信公众号与chatGPT的连接。使用微信公众号开发平台提供的接口,主要是客服消息接口,用户在公众号聊天窗口发送问题内容,后台服务收到后,调用openai接口问答,并将chatGPT的结果发送到微信公众号窗口中进行展示。

接入微信公众平台

如何接入微信平台?这部分内容网上有很多,比较简单这里就不详细介绍了。

介绍几个我封装的接入微信公众号的golang接口:

// HandleWxLogin首次接入,成为开发者
func HandleWxLogin(c *gin.Context) {
	fmt.Printf("==>HandleWxLogin\n")
	echostr := c.DefaultQuery("echostr", "")
	if echostr != "" {
		fmt.Printf("==>echostr:%s\n", echostr)
		c.String(200, "%s", echostr)
		return
	}
 
}

 
// WxGetAccessToken 获取微信accesstoken
func WxGetAccessToken() string {
	url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%v&secret=%v", APPID, APPSECRET)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("获取微信token失败", err)
		return ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("微信token读取失败", err)
		return ""
	}
 
	token := token{}
	err = json.Unmarshal(body, &token)
	if err != nil {
		fmt.Println("微信token解析json失败", err)
		return ""
	}
 
	return token.AccessToken
}

客服回复消息接口

调用此封装的微信接口,向用户回复消息。比如用户在公众号聊天窗口发送问题后,通过向chatGPT的api接口发送请求,把chatGPT回复的内容,通过客服回复消息接口向用户应答。

微信客服回复接口封装

// 客服回复接口
func WxPostCustomTextMsg(accessToken string, touser string, content string) {
 
	url := "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + accessToken
 
	req:=&WxCustomTextMsg{ToUser:touser,MsgType:"text",Text:WxCustomText{Content:content}}
	jsonStr := req.ToJson()
	//fmt.Printf("WxPostCustomTextMsg:%#v\n", jsonStr)
	request ,_:= http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	request.Header.Set("Content-Type", "application/json")
    client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	fmt.Println(string(body))
}

需要注意的是,需要使用golang的协程异步处理,因为用户发送信息后若5秒内没响应,会报服务异常的。异步示例如下:

var wxReceiveFunc = func(msg wxapi.WxReceiveCommonMsg) error {
	fmt.Println("weixin msg received")
	fmt.Printf("%#v\n", msg)
	touser := msg.FromUserName
	content := msg.Content
	accessToken := wxapi.WxGetAccessToken()
 
	//异步请求chatAI,成功后调用客服接口回复
    go func(){
		resp:=chatapi.AskChatAI(content)
		if(resp !=""){
			wxapi.WxPostCustomTextMsg(accessToken,touser,resp)
		}else{
			wxapi.WxPostCustomTextMsg(accessToken,touser,"chatGPT服务异常")
		}
	}()
	
	return nil
}

chatGPT的API接口实现

package chatapi
import (
	"fmt"
	"time"
	"context"
	"github.com/otiai10/openaigo"
)
 
var (
	// CHATKEY 
	CHATKEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxx"
)
 
func AskChatAI(question string) string{
 
	fmt.Printf("\033[31mAsk chatGPT,\033[0m question:%s\n", question)
	begin := time.Now()
	client := openaigo.NewClient(CHATKEY)
    request := openaigo.CompletionRequestBody{
    Model:  "text-davinci-003",
    Prompt: []string{question},
	MaxTokens:2000,
   }
   ctx := context.Background()
   response, err := client.Completion(ctx, request)
   elapsed := time.Since(begin)
   //fmt.Println(response, err)
   if err != nil {
	 fmt.Printf("\033[31mError:\033[0m %v\nTime: ", err,elapsed)
	 return ""
   }else{
	 fmt.Printf("Answer:%+v\n\033[32mTime:\033[0m %v\n\n", response,elapsed)
	 return response.Choices[0].Text
   }
}

text-davinci-003介绍

使用text-davinci-003的模型的openai的接口,详细的介绍参照地址:

beta.openai.com/docs/api-re…

关于text-davinci-003和官方chatGPT的区别,参照下面文章,其实用起来差别不大。甚至text-davinci-003在写作创造方面更优秀。简单说chatGPT是个产品,text-davinci-003更面向开发者开发产品使用。

curl https://api.openai.com/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"model": "text-davinci-003", "prompt": "Say this is a test", "temperature": 0, "max_tokens": 7}'
{
  "id": "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7",
  "object": "text_completion",
  "created": 1589478378,
  "model": "text-davinci-003",
  "choices": [
    {
      "text": "\n\nThis is indeed a test",
      "index": 0,
      "logprobs": null,
      "finish_reason": "length"
    }
  ],
  "usage": {
    "prompt_tokens": 5,
    "completion_tokens": 7,
    "total_tokens": 12
  }
}

官方貌似只提供了python的demo和接口实现,其实模拟http调用使用其他语言都可以实现。感谢开源,这部分已经有现成的实现了。

github地址在这里:GitHub - otiai10/openaigo: OpenAI GPT-3 API Client for Go

模型text-davinci-003和官方chatGPT的区别,先来看看ChatGPT的架构和训练方法。

架构上,研究者们微调了GPT 3.5中的某个模型,得到了ChatGPT。

GPT 3.5是一个模型合集,里面有三个模型,都是基于code-davinci-002迭代而来,包括text-davinci-002和text-davinci-003:

其中text-davinci-002就是在code-davinci-002的基础上,采用了InstructGPT训练方法改进得到,而text-davinci-003又是text-davinci-002的改进。ChatGPT就是基于这几个模型之一做了微调,并同样采用了InstructGPT的训练方法。

训练上,InstructGPT采用了强化学习“秘方”,让语言模型不再埋头苦干,而是学会对人类的反馈“做出反应”。具体来说,研究人员先收集平时用户给GPT-3等模型输入的提示词,得到一个提示词数据集(用户同意的情况下)。

然后,基于这个提示词数据集,让模型输出几种不同的答案,并对这些答案进行排序,从而训练得到一个reward模型。

2020年,OpenAI推出了1750亿参数量的屠榜‘杀器’GPT-3,但基于大模型至今悬而未决的伦理和社会风险以及商业盈利等因素的考量,OpenAI将GPT-3以付费API的形式向公众开放。通过调用GPT-3的API,问答、语义检索、翻译、数学推理、创作小说等诸多玩法被玩家及尽探索。

最近,OpenAI推出了最新的文本生成模型:text-davinci-003。为了适配更多任务,OpenAI发布了不同功率以及能力的GPT-3模型,text-davinci-003是其中能力最为强大的模型,也是OpenAI的GPT-3.5系列模型之一[1],一经发布就引起了社区的广泛关注。

OpenAI提供四种主要型号的GPT-3模型,Davinci是功能最强大的模型,Ada是其中速度最快的。text-davinci-003作为Davinci系列的最新模型,通过处理更复杂的指令和生成更长形式的内容对现有模型做了重大改进,新模型的描述如下:

产生更高质量的写作,这将帮助您的应用程序提供更清晰、更具吸引力和更具吸引力的内容。

处理更复杂的指令,这意味着您现在可以更有创意地利用它的功能。

更擅长生成较长形式的内容,让您可以承担以前难以完成的任务。

微信后台接口(Golang版本)

/**
 * Created by yangyongzhen on 2018/06/27
 * simple wxapi tool by golang
 */
package wxapi
 
import (
	"crypto/sha1"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"sort"
	"strings"
	"bytes"
 
	"github.com/gin-gonic/gin"
	"github.com/tidwall/gjson"
)
 
var (
	// APPID 公众号后台分配的APPID
	APPID = "xxxxxxxx"
	// APPSECRET 公众号后台分配的APPSECRET
	APPSECRET = "xxxxxxxxxxxxxxxxxxxx"
)
 
type token struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
}
 
// WxReceiveCommonMsg 接收普通消息
type WxReceiveCommonMsg struct {
	ToUserName   string //接收者 开发者 微信号
	FromUserName string //发送者 发送方帐号(一个OpenID)
	Content      string //文本内容
	CreateTime   int64  //创建时间
	MsgType      string //消息类型
	MsgId        int64  //消息id
	PicUrl       string //图片url
	MediaId      string //媒体id
	Event        string //事件类型,VIEW
	EventKey     string //事件KEY值,设置的跳转URL
	MenuId       string
	Format       string
	Recognition  string
	ThumbMediaId string //缩略图媒体ID
}
 
type WxCustomText struct{
	Content string `json:"content"`
}
type WxCustomTextMsg struct{
	ToUser string `json:"touser"`
	MsgType string `json:"msgtype"`
	Text WxCustomText `json:"text"`
}
 
func (msg *WxCustomTextMsg) ToJson() []byte {
	body, err := json.Marshal(msg)
	if err != nil {
		panic(err)
	}
	return body
}
 
// WxReceiveFunc (接收到消息之后,会将消息交于这个函数处理)
var WxReceiveFunc func(msg WxReceiveCommonMsg) error
 
// WxMakeSign 计算签名
func WxMakeSign(token, timestamp, nonce string) string {
	strs := []string{token, timestamp, nonce}
	sort.Strings(strs)
	sha := sha1.New()
	io.WriteString(sha, strings.Join(strs, ""))
	return fmt.Sprintf("%x", sha.Sum(nil))
}
 
// HandleWxLogin首次接入,成为开发者
func HandleWxLogin(c *gin.Context) {
	fmt.Printf("==>HandleWxLogin\n")
	echostr := c.DefaultQuery("echostr", "")
	if echostr != "" {
		fmt.Printf("==>echostr:%s\n", echostr)
		c.String(200, "%s", echostr)
		return
	}
 
}
 
// WxGetAccessToken 获取微信accesstoken
func WxGetAccessToken() string {
	url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%v&secret=%v", APPID, APPSECRET)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("获取微信token失败", err)
		return ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("微信token读取失败", err)
		return ""
	}
 
	token := token{}
	err = json.Unmarshal(body, &token)
	if err != nil {
		fmt.Println("微信token解析json失败", err)
		return ""
	}
 
	return token.AccessToken
}
 
// WxGetUserList 获取关注者列表
func WxGetUserList(accessToken string) []gjson.Result {
	url := "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + accessToken + "&next_openid="
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("获取关注列表失败", err)
		return nil
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取内容失败", err)
		return nil
	}
	flist := gjson.Get(string(body), "data.openid").Array()
	return flist
}
 
// WxPostTemplate 发送模板消息
func WxPostTemplate(accessToken string, reqdata string, fxurl string, templateid string, openid string) {
 
	url := "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken
 
	reqbody := "{\"touser\":\"" + openid + "\", \"template_id\":\"" + templateid + "\", \"url\":\"" + fxurl + "\", \"data\": " + reqdata + "}"
	fmt.Printf("WxPostTemplate:%#v\n", reqbody)
	resp, err := http.Post(url,
		"application/x-www-form-urlencoded",
		strings.NewReader(string(reqbody)))
	if err != nil {
		fmt.Println(err)
		return
	}
 
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	fmt.Println(string(body))
}
 
// 客服回复接口
func WxPostCustomTextMsg(accessToken string, touser string, content string) {
 
	url := "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + accessToken
 
	req:=&WxCustomTextMsg{ToUser:touser,MsgType:"text",Text:WxCustomText{Content:content}}
	jsonStr := req.ToJson()
	//fmt.Printf("WxPostCustomTextMsg:%#v\n", jsonStr)
	request ,_:= http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	request.Header.Set("Content-Type", "application/json")
    client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	fmt.Println(string(body))
}
 
 
 
// ReceiveCommonMsg
func ReceiveCommonMsg(msgData []byte) (WxReceiveCommonMsg, error) {
 
	fmt.Printf("received weixin msgData:\n%s\n", msgData)
	msg := WxReceiveCommonMsg{}
	err := xml.Unmarshal(msgData, &msg)
	if WxReceiveFunc == nil {
		return msg, err
	}
	err = WxReceiveFunc(msg)
	return msg, err
}
 
// HandleWxPostRecv 处理微信公众号前端发起的消息事件
func HandleWxPostRecv(c *gin.Context) {
	fmt.Printf("==>HandleWxPostRecv Enter\n")
	data, err := c.GetRawData()
	if err != nil {
		log.Fatalln(err)
	}
	ReceiveCommonMsg(data)
}
 
// WxCreateMenu 创建菜单
func WxCreateMenu(accessToken, menustr string) (string, error) {
 
	url := "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken
	fmt.Printf("WxCreateMenu:%s\n", menustr)
	resp, err := http.Post(url,
		"application/x-www-form-urlencoded",
		strings.NewReader(menustr))
	if err != nil {
		fmt.Println(err)
		return "", err
	}
 
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return "", err
	}
 
	fmt.Println(string(body))
	return string(body), nil
 
}
 
// WxDelMenu 删除菜单
func WxDelMenu(accessToken string) (string, error) {
	url := "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=" + accessToken
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("删除菜单失败", err)
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取内容失败", err)
		return "", err
	}
 
	fmt.Println(string(body))
	return string(body), nil
 
}
 
// WxGetUserInfo 根据用户openid获取基本信息
func WxGetUserInfo(accessToken, openid string) (string, error) {
	url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN", accessToken, openid)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("获取信息失败", err)
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取内容失败", err)
		return "", err
	}
 
	fmt.Println(string(body))
	return string(body), nil
 
}
 
func init() {
}

main.go代码实现

package main
 
import (
	"fmt"
	"net/http"
	"weixin2/apis"
	"weixin2/wxapi"
	_"weixin2/convert"
	"weixin2/chatapi"
 
	"github.com/gin-gonic/gin"
	"github.com/jasonlvhit/gocron"
)
 
var wxReceiveFunc = func(msg wxapi.WxReceiveCommonMsg) error {
	fmt.Println("weixin msg received")
	fmt.Printf("%#v\n", msg)
	touser := msg.FromUserName
	content := msg.Content
	accessToken := wxapi.WxGetAccessToken()
 
	go func(){
		resp:=chatapi.AskChatAI(content)
		if(resp !=""){
			wxapi.WxPostCustomTextMsg(accessToken,touser,resp)
		}else{
			wxapi.WxPostCustomTextMsg(accessToken,touser,"chatGPT服务异常")
		}
	}()
	return nil
}
 
func main() {
	// 定时每天早上7点 公众号发送天气预报
	gocron.Every(1).Day().At("07:00").Do(apis.SendWeather)
	// 定时每天早上九点 公众号发送每日一句
	gocron.Every(1).Day().At("10:00").Do(apis.SendExponent)
 
	fmt.Println("开启定时触发任务")
 
	gocron.Start()
 
	wxapi.WxReceiveFunc = wxReceiveFunc
 
	router := gin.Default()
	router.GET("/", wxapi.HandleWxLogin)
	router.POST("/", wxapi.HandleWxPostRecv)
 
	//运行的端口
	router.Run(":8000")
}

至于里面的每日发送是啥?

哈哈,为了好玩。我让每日7点给我发送一份天气预报。每日12点发个当前大盘点位,关注下股市大盘。至于大盘咨询的获取的实现,调用的第三方的接口。这里也给出下。

大盘信息获取接口实现:

package apis
 
import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"
	"weixin2/util"
	"weixin2/wxapi"
)
 
var (
	LastPoint string //上次点数
	LastState string //上次状态
	LastTime  string //上次时间
	State     string //当前状态
)
 
type ExponentInfo struct {
	CurrentPoints string // 当前点数
	Gszzl         string //涨跌幅度
	Turnover      string //成交额(万元)
	Name          string //名称
}
 
// 查询指数
func QueryBroadMarket(code string) (exponentInfo ExponentInfo, err error) {
	res, err := http.Get("http://hq.sinajs.cn/list=" + code)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		return
	}
	if res.StatusCode != http.StatusOK {
		return exponentInfo, fmt.Errorf("http statusCode:%d", res.StatusCode)
	}
 
	result, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return exponentInfo, err
	}
	base := util.ParseStr(string(result))
	//exponentInfo.Name = BroadMarketName[code]
	exponentInfo.Name = util.ConvertToString(base[0], "gbk", "utf-8")
	currentPoints := strings.Split(base[1], ".")
	exponentInfo.CurrentPoints = currentPoints[0]
	exponentInfo.Gszzl = base[3]
	if len(base[5]) > 5 {
		base[5] = base[5][0 : len(base[5])-4]
		base[5] += "亿"
	}
	exponentInfo.Turnover = base[5]
	return
}
 
//  ExponentTplID 模板ID
// {{title.DATA}} {{date.DATA}} {{curpoint.DATA}} {{turnover.DATA}} {{gszzl.DATA}} {{state.DATA}} {{lastpoint.DATA}} {{laststate.DATA}} {{tips.DATA}}
var ExponentTplID = "RIzqpuRxwlV1IWucjE186Uvu949N0Uly9H3CoTMGskc"
 
// ExponentTpl 结构
type ExponentTpl struct {
	Title   string `json:"title"`
	Content string `json:"content"`
	Note    string `json:"note"`
}
 
// AfterTime 判断一个时间在现在之前还是之后
// layout 解析的时间格式,
// judeTime 需要判断的时间
// bool =true  在当前时候之后,false在当前时间之前
func AfterTime(tpl, judeTime string) (bool, error) {
	stringToTime, err := time.Parse(tpl, judeTime)
	if err != nil {
		return false, err
	}
	beforeOrAfter := stringToTime.After(time.Now())
	if beforeOrAfter == true {
		return true, nil
	} else {
		return false, nil
	}
}
 
//SendExponent 发送每日大盘
func SendExponent() {
 
	info, err := QueryBroadMarket("s_sh000001")
	if err != nil {
		fmt.Printf("QueryBroadMarket error,err=%s\n", err.Error())
		return
	}
	accessToken := wxapi.WxGetAccessToken()
	if accessToken == "" {
		return
	}
 
	flist := wxapi.WxGetUserList(accessToken)
	if flist == nil {
		return
	}
	title := "上证指数"
	timeStr := time.Now().Format("2006-01-02 15:04:05")
	tips := "tips:市场有风险,入市需谨慎"
 
	lastflt, err := strconv.ParseFloat(LastPoint, 32/64)
	curflt, err := strconv.ParseFloat(info.CurrentPoints, 32/64)
	if curflt > lastflt {
		State = "上升 " + strconv.FormatFloat(curflt-lastflt, 'f', 2, 32)
	} else {
		State = "下降 " + strconv.FormatFloat(curflt-lastflt, 'f', 2, 32)
	}
 
	reqdata := "{\"title\":{\"value\":\"" + title + "\", \"color\":\"#0000CD\"},\"date\":{\"value\":\"" + timeStr + "\"},\"curpoint\":{\"value\":\"" + "当前点数:" + info.CurrentPoints + "\"}, \"turnover\":{\"value\":\"" + "成交额:" + info.Turnover + "\"},\"gszzl\":{\"value\":\"" + "估值:" + info.Gszzl + "\"},\"state\":{\"value\":\"" + "今日状态:" + State + "\"},\"lastpoint\":{\"value\":\"" + "昨日点数:" + LastPoint + "\"},\"laststate\":{\"value\":\"" + "昨日状态:" + LastState + "\"},\"tips\":{\"value\":\"" + tips + "\"}}"
	for _, v := range flist {
		wxapi.WxPostTemplate(accessToken, reqdata, "blog.csdn.net/qq8864", ExponentTplID, v.Str)
	}
 
	now := time.Now()
	if now.Hour() > 13 {
		LastTime = timeStr
		LastPoint = info.CurrentPoints
		LastState = State
	}
 
}

每日天气接口实现:

/**
每日天气api接口
*/
package apis
 
import (
	"fmt"
	"github.com/tidwall/gjson"
	"io/ioutil"
	"net/http"
	"weixin2/wxapi"
)
 
var (
	WeatTemplateID = "AXCDf6fetE2-ebF7Di8izW9avSN4cFWvnfcAQDSW7-k" //天气模板ID
 
	WeatherApiVersion = "v6"
	WeatherAPPID      = "45849795"
	WeatherAppSECRET  = "T5xyVwLB"
)
 
// GetWeather 获取天气
func GetWeather(city string) (string, string, string, string) {
	url := fmt.Sprintf("https://tianqiapi.com/api?version=%s&appid=%s&appsecret=%s&city=%s", WeatherApiVersion, WeatherAPPID, WeatherAppSECRET, city)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("获取天气失败", err)
		return "", "", "", ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取内容失败", err)
		return "", "", "", ""
	}
	fmt.Println(string(body))
	data := string(body)
	day := gjson.Get(data, "date").Str
	wea := gjson.Get(data, "wea").Str
	tem := gjson.Get(data, "tem").Str
	//tem2 := gjson.Get(thisday, "tem2").Str
	airTips := gjson.Get(data, "air_tips").Str
	return day, wea, tem, airTips
}
 
// PostWeather 发送天气
func PostWeather(accessToken, city, openid string) {
	day, wea, tem, airTips := GetWeather(city)
	if day == "" || wea == "" || tem == "" || airTips == "" {
		return
	}
	reqdata := "{\"city\":{\"value\":\"城市:" + city + "\", \"color\":\"#0000CD\"}, \"day\":{\"value\":\"" + day + "\"}, \"wea\":{\"value\":\"天气:" + wea + "\"}, \"tem1\":{\"value\":\"平均温度:" + tem + "\"}, \"air_tips\":{\"value\":\"tips:" + airTips + "\"}}"
	//fmt.Println(reqdata)
	wxapi.WxPostTemplate(accessToken, reqdata, "http://yangqq.xyz", WeatTemplateID, openid)
}
 
//SendWeather 向公众号所有用户发送天气预报
func SendWeather() {
	accessToken := wxapi.WxGetAccessToken()
	if accessToken == "" {
		return
	}
	flist := wxapi.WxGetUserList(accessToken)
	if flist == nil {
		return
	}
	city := "郑州"
	for _, v := range flist {
		go PostWeather(accessToken, city, v.Str)
	}
}

其他资源

ChatGPT API版介绍及使用讲解。 - 哔哩哔哩

ChatGPT又添劲敌?OpenAI核心员工创业,新模型获一片叫好

【NLP】万字拆解!追溯ChatGPT各项能力的起源_qq62985c01d4e12的技术博客_51CTO博客

GitHub - transitive-bullshit/chatgpt-api: Node.js client for the unofficial ChatGPT API. 🔥 GitHub - malaohu/wechat-chatGPT: 实现微信公众号被动返回接口的ChatGPT

github.com/869413421/w…

github.com/eatmoreappl…

GitHub - hktalent/ChatGPT-API: ChatGPT-API for go

GitHub - otiai10/openaigo: OpenAI GPT-3 API Client for Go

微信公众平台开发概述 | 微信开放文档

text-davinci-003和ChatGPT之间的不同点 - 知乎

OpenAI | GPT-3新模型Davinci,将AI写作提升到新水平!网友惊呼:GPT-4要来了?

OpenAI-Davinci,一个新的GPT-3模型,让AI写作更上一层楼!网友惊呼:GPT-4要_内容_能力_时间

原文链接:blog.csdn.net/yyz_1987/ar…