Golang实现发送微信模板消息(每日一句和天气预报)

前言

本文是基于https://www.cnblogs.com/connect/p/python-wechat-iciba.html 这篇博客写成的。该博客实现了用python将金山词霸的每日一句推送到微信公众测试号,我想既然python能实现,那么用Golang也可以。

后来又加了每天早晨定时给自己和女朋友发天气预报提醒,开始着手做,gogogo!

运行环境

  1. 阿里云Linux服务器
  2. Go开发环境

一、获取接口数据

1、每日一句接口

调用地址:http://open.iciba.com/dsapi/
请求方式:GET
请求参数:

参数必选类型说明
datestring格式为:2013-05-06;如果date为空,则默认取当天
typestring可选值为last和next;以date日期为准的,last返回前一天的,next返回后一天的

返回类型:JSON
JSON字段解释:

属性名属性值类型说明
sidstring每日一句ID
ttsstring音频地址
contentstring英文内容
notestring中文内容
lovestring每日一句喜欢个数
translationstring词霸小编
picturestring图片地址
picture2string大图片地址
captionstring标题
datelinestring时间
s_pvstring浏览数
sp_pvstring语音评测浏览数
tagsarray相关标签
fenxiang_imgstring合成图片,建议分享微博用的

返回示例:

{
    "sid": "3369",
    "tts": "http://news.iciba.com/admin/tts/2019-04-23-day.mp3",
    "content": "There is no such thing as a great talent without great will.",
    "note": "没有伟大的意志力,便没有雄才大略。",
    "love": "197",
    "translation": "小编的话:正如爱迪生所说一般,强者容易坚强。只有坚强的意志力才能给我们克服各种困难的勇气和决心。",
    "picture": "http://cdn.iciba.com/news/word/20190423.jpg",
    "picture2": "http://cdn.iciba.com/news/word/big_20190423b.jpg",
    "caption": "词霸每日一句",
    "dateline": "2019-04-23",
    "s_pv": "0",
    "sp_pv": "0",
    "tags": [
        {
            "id": null,
            "name": null
        }
    ],
    "fenxiang_img": "http://cdn.iciba.com/web/news/longweibo/imag/2019-04-23.jpg"
}

请求示例:

type sentence struct {
	Content     string `json:"content"`
	Note        string `json:"note"`
	Translation string `json:"translation"`
}

func getsen() (sentence, string) {
	resp, err := http.Get("http://open.iciba.com/dsapi/?date")
	sent := sentence{}
	if err != nil {
		fmt.Println("获取每日一句失败", err)
		return sent, ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取内容失败", err)
		return sent, ""
	}

	err = json.Unmarshal(body, &sent)
	if err != nil {
		fmt.Println("每日一句解析json失败")
		return sent, ""
	}
	fenxiangurl := gjson.Get(string(body), "fenxiang_img").String()
	fmt.Println(sent)
	return sent, fenxiangurl
}

这里使用了golang自带的http包发起了一次get请求,然后将返回的json数据解析出来,另外使用了gjon这个包,该包可以直接从json字符串中解析出需要的字段,十分方便。(其实这段代码我偷了个懒,如果内部有错误应该将错误return出去,大家不要学我啊 ~~o(>_<)o ~~)

2、获取天气预报接口

调用地址:https://www.tianqiapi.com/api
请求方式:GET
请求参数:

参数必选类型说明
versionstringv1(版本标识)
cityid以下参数3选1string101120201(城市编号,不要带CN, 以下参数3选1)
city3选1string青岛(城市名称,不要带市和区)
ip3选1string27.193.XX.XXX(IP地址)
callbackstringjsonp方式

返回类型:JSON
返回示例:

该接口会返回往后一周的天气预报,因为返回的参数过多,我只截取了当天的数据。如果需要其它数据可以自己请求一下。

请求示例代码:

func getweather(city string) (string, string, string, string) {
	url := fmt.Sprintf("https://www.tianqiapi.com/api?version=%s&city=%s", WeatherVersion, 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 "", "", "", ""
	}

	data := gjson.Get(string(body), "data").Array()
	thisday := data[0].String()
	day := gjson.Get(thisday, "day").Str 	//日期
	wea := gjson.Get(thisday, "wea").Str	//天气
	tem := gjson.Get(thisday, "tem").Str	//平均气温
	air_tips := gjson.Get(thisday, "air_tips").Str		//提示
	return day, wea, tem, air_tips
}

get请求获得天气数据,gjson包将当天的天气信息解析出来后返回。同样地,偷懒错误没return出去 ?。

二、微信公众平台接口测试帐号

通过上一步我们已经成功的获取到了数据,接下来申请一个微信公众平台测试帐号,其实正式帐号的操作也是一样的,但方便起见,我们直接用测试号。

1、每日一句模板

  1. 手机上确认登录
  2. 找到 新增测试模板 ,添加模板消息在这里插入图片描述
    填写模板标题 《每日一句》,填写如下模板内容

{{content.DATA}}
{{note.DATA}}
{{translation.DATA}}

注意:后面的.DATA必须保留,前面是你定义的字段。在这里插入图片描述
提交保存之后,记住该模板ID,一会儿会用到在这里插入图片描述

  1. 找到测试号信息,记住 appidappsecret,一会儿会用到在这里插入图片描述
  2. 找到测试号二维码。手机扫描此二维码,关注之后,你的昵称会出现在右侧列表里,记住该微信号,一会儿会用到(注:此微信号非你真实的微信号,而是你的微信在关注了该测试号后分配的在该号下的唯一ID)在这里插入图片描述

2、天气预报模板

和每日一句的添加方法一样,区别在于第3步中新增测试模板使用的模板不同:

{{city.DATA}}
{{day.DATA}}
{{wea.DATA}}
{{tem1.DATA}}
{{air_tips.DATA}}
在这里插入图片描述

三、发送微信模板消息的程序

//发送每日一句,将json字符串拼接好后调用templatepost函数发送模板
func everydaysen() {
	req, fxurl := getsen()
	if req.Content == "" {
		return
	}
	access_token := getaccesstoken()
	if access_token == "" {
		return
	}

	flist := getflist(access_token)  //获取公众号关注人列表
	if flist == nil {
		return
	}

	reqdata := "{\"content\":{\"value\":\"" + req.Content + "\", \"color\":\"#0000CD\"}, \"note\":{\"value\":\"" + req.Note + "\"}, \"translation\":{\"value\":\"" + req.Translation + "\"}}"
	for _, v := range flist {
		templatepost(access_token, reqdata, fxurl, SentTemplateID, v.Str)
	}
}

//发送天气预报
func weather() {
	access_token := getaccesstoken()
	if access_token == "" {
		return
	}

	flist := getflist(access_token)
	if flist == nil {
		return
	}

	var city string
	for _, v := range flist {
		switch v.Str {
		case "oeZ6P5kyGsLKn3sIGRVfpb8oT4mg":
			city = "青岛"
			go sendweather(access_token, city, v.Str)
		case "oeZ6P5jvFNh2y_h_2UcaoTXBaC2o":
			city = "西安"
			go sendweather(access_token, city, v.Str)
		default:
		}
	}
	fmt.Println("weather is ok")
}

//获取微信accesstoken
func getaccesstoken() 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
}

//获取关注人列表
func getflist(access_token string) []gjson.Result {
	url := "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + access_token + "&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
}

//发送模板消息代码
func templatepost(access_token string, reqdata string, fxurl string, templateid string, openid string) {
	url := "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token

	reqbody := "{\"touser\":\"" + openid + "\", \"template_id\":\"" + templateid + "\", \"url\":\"" + fxurl + "\", \"data\": " + reqdata + "}"

	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))
}

//拼接json字符串,调用templatepost函数发送天气模板
func sendweather(access_token, city, openid string) {
	day, wea, tem, air_tips := getweather(city)
	if day == "" || wea == "" || tem == ""|| air_tips == "" {
		return
	}
	reqdata := "{\"city\":{\"value\":\"城市:" + city + "\", \"color\":\"#0000CD\"}, \"day\":{\"value\":\"" + day + "\"}, \"wea\":{\"value\":\"天气:" + wea + "\"}, \"tem1\":{\"value\":\"平均温度:" + tem + "\"}, \"air_tips\":{\"value\":\"tips:" + air_tips + "\"}}"
	//fmt.Println(reqdata)
	templatepost(access_token, reqdata, "", WeatTemplateID, openid)
}

在发送模板消息之前要先获取微信 accesstoken关注人列表 ,再遍历所有关注人,给每位关注人发模板消息,或者给某位特定的人发送。

四、设置定时发送

func main() {
	spec := "0 0 12 * * *" // 每天12:00
	spec1 := "0 0 7 * * *" // 每天早晨7:00
	c := cron.New()
	c.AddFunc(spec, everydaysen)
	c.AddFunc(spec1, weather)
	c.Start()
	fmt.Println("开启定时任务")
	select {}
	//weather()
	//everydaysen()
}

使用了 github.com/robfig/cron 包的定时任务,使每天早晨7点和中午12点发送天气预报和每日一句。

spec := “0 0 12 * * *”
这里对应的分别是 “ 秒 分 时 日 月 周 ” 和linux里的crontab定时任务差不多。

在阿里云Linux服务器上后台启动该程序既可。

程序运行结果截图:
在这里插入图片描述
完美解决 ,好吧,并不完美,其实代码还是有很大的优化空间,由于时间紧凑,就不优化了,知道就行,是吧O(∩_∩)O~