add: Go版新增图片类型的推送消息

This commit is contained in:
fcbhank 2021-08-17 01:55:55 +08:00
parent 6747568425
commit cce2cd3d9b
4 changed files with 296 additions and 176 deletions

5
.gitignore vendored
View File

@ -1,3 +1,6 @@
demo.php demo.php
show.php show.php
.DS_Store .DS_Store
.idea
go-wecomchan/wecomchan
go-wecomchan/wecomchan.exe

View File

@ -1,5 +1,8 @@
FROM golang:1.16.5-alpine3.13 as gobuilder FROM golang:1.16.5-alpine3.13 as gobuilder
# 替换为国内源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
ENV GO111MODULE="on" ENV GO111MODULE="on"
ENV GOPROXY="https://goproxy.cn,direct" ENV GOPROXY="https://goproxy.cn,direct"
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0

View File

@ -18,15 +18,14 @@ docker构建镜像使用需要安装docker不依赖golang以及网络。
修改的sendkey企业微信公司ID 等默认值为你的企业中的相关信息,如不设置运行时和打包后都可通过环境变量传入。 修改的sendkey企业微信公司ID 等默认值为你的企业中的相关信息,如不设置运行时和打包后都可通过环境变量传入。
```golang ```golang
var SENDKEY string = GetEnvDefault("SENDKEY", "set_a_sendkey") var Sendkey = GetEnvDefault("SENDKEY", "set_a_sendkey")
var WECOM_CID string = GetEnvDefault("WECOM_CID", "企业微信公司ID") var WecomCid = GetEnvDefault("WECOM_CID", "企业微信公司ID")
var WECOM_SECRET string = GetEnvDefault("WECOM_SECRET", "企业微信应用Secret") var WecomSecret = GetEnvDefault("WECOM_SECRET", "企业微信应用Secret")
var WECOM_AID string = GetEnvDefault("WECOM_AID", "企业微信应用ID") var WecomAid = GetEnvDefault("WECOM_AID", "企业微信应用ID")
var WECOM_TOUID string = GetEnvDefault("WECOM_TOUID", "@all") var WecomToUid = GetEnvDefault("WECOM_TOUID", "@all")
var REDIS_ADDR string = GetEnvDefault("REDIS_ADDR", "localhost:6379") var RedisStat = GetEnvDefault("REDIS_STAT", "OFF")
var REDIS_STAT string = GetEnvDefault("REDIS_STAT", "OFF") var RedisAddr = GetEnvDefault("REDIS_ADDR", "localhost:6379")
var REDIS_PASSWORD string = GetEnvDefault("REDIS_PASSWORD", "") var RedisPassword = GetEnvDefault("REDIS_PASSWORD", "")
``` ```
## 直接使用 ## 直接使用
@ -49,10 +48,12 @@ var REDIS_PASSWORD string = GetEnvDefault("REDIS_PASSWORD", "")
新增打包好的镜像可以直接使用 新增打包好的镜像可以直接使用
`docker pull aozakiaoko/go-wecomchan` - v1_推送文本:`docker pull aozakiaoko/go-wecomchan`
Docker Hub 地址为:[https://hub.docker.com/r/aozakiaoko/go-wecomchan](https://hub.docker.com/r/aozakiaoko/go-wecomchan) Docker Hub 地址为:[https://hub.docker.com/r/aozakiaoko/go-wecomchan](https://hub.docker.com/r/aozakiaoko/go-wecomchan)
- v2_推送文本or图片:`docker pull fcbhank/go-wecomchan`
Docker Hub 地址为:[https://hub.docker.com/r/fcbhank/go-wecomchan](https://hub.docker.com/r/fcbhank/go-wecomchan)
1. 构建镜像 1. 构建镜像
`docker build -t go-wecomchan .` `docker build -t go-wecomchan .`
@ -70,10 +71,12 @@ docker run -dit -e SENDKEY=set_a_sendkey \
-e REDIS_STAT=ON \ -e REDIS_STAT=ON \
-e REDIS_ADDR="localhost:6379" \ -e REDIS_ADDR="localhost:6379" \
-e REDIS_PASSWORD="" \ -e REDIS_PASSWORD="" \
-p 8080:8080 aozakiaoko/go-wecomchan # v1 aozakiaoko/go-wecomchan
# v2 fcbhank/go-wecomchan
-p 8080:8080 go-wecomchan
``` ```
如不使用redis不要传入最后三个关于redis的环境变量 如不使用redis不要传入最后三个关于redis的环境变量(REDIS_STAT|REDIS_ADDR|REDIS_PASSWORD)
4. 环境变量说明 4. 环境变量说明
@ -84,9 +87,9 @@ docker run -dit -e SENDKEY=set_a_sendkey \
|WECOM_SECRET|企业微信应用Secret| |WECOM_SECRET|企业微信应用Secret|
|WECOM_AID|企业微信应用ID| |WECOM_AID|企业微信应用ID|
|WECOM_TOUID|需要发送给的人,详见[企业微信官方文档](https://work.weixin.qq.com/api/doc/90000/90135/90236#%E6%96%87%E6%9C%AC%E6%B6%88%E6%81%AF)| |WECOM_TOUID|需要发送给的人,详见[企业微信官方文档](https://work.weixin.qq.com/api/doc/90000/90135/90236#%E6%96%87%E6%9C%AC%E6%B6%88%E6%81%AF)|
|REDIS_STAT|是否启用redis换缓存token,ON-启用 OFF或空-不启用|
|REDIS_ADDR|redis服务器地址如不启用redis缓存可不设置| |REDIS_ADDR|redis服务器地址如不启用redis缓存可不设置|
|REDIS_STAT|是否启用redis换缓存token| |REDIS_PASSWORD|redis的连接密码如不启用redis缓存可不设置|
|REDIS_PASSWORD|redis的连接密码|
## 使用docker-compose 部署 ## 使用docker-compose 部署
@ -95,9 +98,20 @@ docker run -dit -e SENDKEY=set_a_sendkey \
`docker-compose up -d` `docker-compose up -d`
## 调用方式 ## 调用方式
- v1_推送文本
访问 `http://localhost:8080/wecomchan?sendkey=你配置的sendkey&&msg=需要发送的消息&&msg_type=text` 访问 `http://localhost:8080/wecomchan?sendkey=你配置的sendkey&&msg=需要发送的消息&&msg_type=text`
- v2_推送文本or图片
```bash
# 推送文本消息
curl --location --request GET 'http://localhost:8080/wecomchan?sendkey={你的sendkey}&msg={你的文本消息}&msg_type=text'
# 推送图片消息
curl --location --request POST 'http://localhost:8080/wecomchan?sendkey={你的sendkey}&msg_type=image' \
--form 'media=@"test.jpg"'
```
## 后续预计添加 ## 后续预计添加
* [x] Dockerfile 打包镜像(不依赖网络环境) * [x] Dockerfile 打包镜像(不依赖网络环境)

View File

@ -1,159 +1,259 @@
package main package main
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "mime/multipart"
"os" "net/http"
"reflect" "os"
"time" "reflect"
"time"
"github.com/go-redis/redis/v8"
) "github.com/go-redis/redis/v8"
)
var SENDKEY string = GetEnvDefault("SENDKEY", "set_a_sendkey")
var WECOM_CID string = GetEnvDefault("WECOM_CID", "企业微信公司ID") /*------------------------------- 环境变量配置 begin -------------------------------*/
var WECOM_SECRET string = GetEnvDefault("WECOM_SECRET", "企业微信应用Secret")
var WECOM_AID string = GetEnvDefault("WECOM_AID", "企业微信应用ID") var Sendkey = GetEnvDefault("SENDKEY", "set_a_sendkey")
var WECOM_TOUID string = GetEnvDefault("WECOM_TOUID", "@all") var WecomCid = GetEnvDefault("WECOM_CID", "企业微信公司ID")
var REDIS_ADDR string = GetEnvDefault("REDIS_ADDR", "localhost:6379") var WecomSecret = GetEnvDefault("WECOM_SECRET", "企业微信应用Secret")
var REDIS_STAT string = GetEnvDefault("REDIS_STAT", "OFF") var WecomAid = GetEnvDefault("WECOM_AID", "企业微信应用ID")
var REDIS_PASSWORD string = GetEnvDefault("REDIS_PASSWORD", "") var WecomToUid = GetEnvDefault("WECOM_TOUID", "@all")
var ctx = context.Background() var RedisStat = GetEnvDefault("REDIS_STAT", "OFF")
var RedisAddr = GetEnvDefault("REDIS_ADDR", "localhost:6379")
func GetEnvDefault(key, defVal string) string { var RedisPassword = GetEnvDefault("REDIS_PASSWORD", "")
val, ex := os.LookupEnv(key) var ctx = context.Background()
if !ex {
return defVal /*------------------------------- 环境变量配置 end -------------------------------*/
}
return val /*------------------------------- 企业微信服务端API begin -------------------------------*/
}
var GetTokenApi = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
func praser_json(json_str string) map[string]interface{} { var SendMessageApi = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s"
var wecom_response map[string]interface{} var UploadMediaApi = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s"
if string(json_str) != "" {
err := json.Unmarshal([]byte(string(json_str)), &wecom_response) /*------------------------------- 企业微信服务端API end -------------------------------*/
if err != nil {
log.Println("生成json字符串错误") type Msg struct {
} Content string `json:"content"`
} }
return wecom_response type Pic struct {
} MediaId string `json:"media_id"`
}
func get_token(corpid, app_secret string) string { type JsonData struct {
resp, err := http.Get("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpid + "&corpsecret=" + app_secret) ToUser string `json:"touser"`
if err != nil { AgentId string `json:"agentid"`
log.Println(err) MsgType string `json:"msgtype"`
} DuplicateCheckInterval int `json:"duplicate_check_interval"`
defer resp.Body.Close() Text Msg `json:"text"`
resp_data, err := ioutil.ReadAll(resp.Body) Image Pic `json:"image"`
if err != nil { }
log.Println(err)
} // GetEnvDefault 获取配置信息,未获取到则取默认值
token_response := praser_json(string(resp_data)) func GetEnvDefault(key, defVal string) string {
return token_response["access_token"].(string) val, ex := os.LookupEnv(key)
} if !ex {
return defVal
func redis_client() *redis.Client { }
rdb := redis.NewClient(&redis.Options{ return val
Addr: REDIS_ADDR, }
Password: REDIS_PASSWORD, // no password set
DB: 0, // use default DB // ParseJson 将json字符串解析为map
}) func ParseJson(jsonStr string) map[string]interface{} {
return rdb var wecomResponse map[string]interface{}
} if string(jsonStr) != "" {
err := json.Unmarshal([]byte(string(jsonStr)), &wecomResponse)
func post_msg(text_msg, msg_type, post_url string) string { if err != nil {
type msg struct { log.Println("生成json字符串错误")
Content string `json:"content"` }
} }
type JsonData struct { return wecomResponse
Touser string `json:"touser"` }
Agentid string `json:"agentid"`
Msgtype string `json:"msgtype"` // GetRemoteToken 从企业微信服务端API获取access_token存在redis服务则缓存
Text msg `json:"text"` func GetRemoteToken(corpId, appSecret string) string {
Duplicate_check_interval int `json:"duplicate_check_interval"` getTokenUrl := fmt.Sprintf(GetTokenApi, corpId, appSecret)
} log.Println("getTokenUrl==>", getTokenUrl)
post_data := JsonData{ resp, err := http.Get(getTokenUrl)
Touser: WECOM_TOUID, if err != nil {
Agentid: WECOM_AID, log.Println(err)
Msgtype: msg_type, }
Duplicate_check_interval: 600, defer resp.Body.Close()
Text: msg{Content: text_msg}, respData, err := ioutil.ReadAll(resp.Body)
} if err != nil {
log.Println(err)
post_json, _ := json.Marshal(post_data) }
log.Println(string(post_json)) tokenResponse := ParseJson(string(respData))
msg_req, err := http.NewRequest("POST", post_url, bytes.NewBuffer(post_json)) log.Println("企业微信获取access_token接口返回==>", tokenResponse)
if err != nil { accessToken := tokenResponse["access_token"].(string)
log.Println(err)
} if RedisStat == "ON" {
msg_req.Header.Set("Content-Type", "application/json") log.Println("prepare to set redis key")
client := &http.Client{} rdb := RedisClient()
resp, err := client.Do(msg_req) // access_token有效时间为7200秒(2小时)
if err != nil { set, err := rdb.SetNX(ctx, "access_token", accessToken, 7000*time.Second).Result()
panic(err) log.Println(set)
} if err != nil {
defer msg_req.Body.Close() log.Println(err)
body, _ := ioutil.ReadAll(resp.Body) }
return string(body) }
} return accessToken
}
func IsZero(v interface{}) (bool, error) {
t := reflect.TypeOf(v) // RedisClient redis客户端
if !t.Comparable() { func RedisClient() *redis.Client {
return false, fmt.Errorf("type is not comparable: %v", t) rdb := redis.NewClient(&redis.Options{
} Addr: RedisAddr,
return v == reflect.Zero(t).Interface(), nil Password: RedisPassword, // no password set
} DB: 0, // use default DB
})
func main() { return rdb
var access_token string }
if REDIS_STAT == "ON" {
log.Println("从redis获取token") // PostMsg 推送消息
rdb := redis_client() func PostMsg(postData JsonData, postUrl string) string {
vals, err := rdb.Get(ctx, "access_token").Result() postJson, _ := json.Marshal(postData)
if err == redis.Nil { log.Println("postJson ", string(postJson))
log.Println("access_token does not exist") log.Println("postUrl ", postUrl)
} msgReq, err := http.NewRequest("POST", postUrl, bytes.NewBuffer(postJson))
access_token = string(vals) if err != nil {
} log.Println(err)
if access_token == "" { }
access_token = get_token(WECOM_CID, WECOM_SECRET) msgReq.Header.Set("Content-Type", "application/json")
} client := &http.Client{}
wecom_chan := func(res http.ResponseWriter, req *http.Request) { resp, err := client.Do(msgReq)
post_url := "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + access_token if err != nil {
req.ParseForm() log.Fatalln("企业微信发送应用消息接口报错==>", err)
sendkey := req.FormValue("sendkey") }
if sendkey != SENDKEY { defer msgReq.Body.Close()
log.Panicln("sendkey 错误,请检查") body, _ := ioutil.ReadAll(resp.Body)
} mediaResp := ParseJson(string(body))
msg := req.FormValue("msg") log.Println("企业微信发送应用消息接口返回==>", mediaResp)
msg_type := req.FormValue("msg_type") return string(body)
post_status := post_msg(msg, msg_type, post_url) }
log.Println(post_status)
post_response := praser_json(string(post_status)) // CheckOrUploadMedia 核对消息类型如果为图片则上传临时素材并返回mediaId
log.Println(post_response) func CheckOrUploadMedia(msgType string, req *http.Request, accessToken string) string {
errcode := post_response["errcode"] if msgType != "image" {
ok, err := IsZero(errcode) log.Println("消息类型不是图片")
if err != nil { return ""
fmt.Printf("%v", err) }
} else {
if ok && REDIS_STAT == "ON" { // 企业微信图片上传不能大于2M
log.Println("pre to set redis key") _ = req.ParseMultipartForm(2 << 20)
rdb := redis_client() imgFile, imgHeader, err := req.FormFile("media")
set, err := rdb.SetNX(ctx, "access_token", access_token, 7000*time.Second).Result() log.Printf("文件大小==>%d字节", imgHeader.Size)
log.Println(set) if err != nil {
if err != nil { log.Fatalln("图片文件出错==>", err)
log.Println(err) return ""
} }
} buf := new(bytes.Buffer)
} writer := multipart.NewWriter(buf)
} if createFormFile, err := writer.CreateFormFile("media", imgHeader.Filename); err == nil {
http.HandleFunc("/wecomchan", wecom_chan) readAll, _ := ioutil.ReadAll(imgFile)
log.Fatal(http.ListenAndServe(":8080", nil)) createFormFile.Write(readAll)
} }
writer.Close()
uploadMediaUrl := fmt.Sprintf(UploadMediaApi, accessToken, msgType)
log.Println("uploadMediaUrl==>", uploadMediaUrl)
newRequest, _ := http.NewRequest("POST", uploadMediaUrl, buf)
newRequest.Header.Set("Content-Type", writer.FormDataContentType())
log.Println("Content-Type ", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(newRequest)
respData, _ := ioutil.ReadAll(resp.Body)
mediaResp := ParseJson(string(respData))
log.Println("企业微信上传临时素材接口返回==>", mediaResp)
if err != nil {
log.Fatalln("上传临时素材出错==>", err)
return ""
} else {
return mediaResp["media_id"].(string)
}
}
// IsZero 判断企业微信服务端是否正常响应
func IsZero(v interface{}) (bool, error) {
t := reflect.TypeOf(v)
if !t.Comparable() {
return false, fmt.Errorf("type is not comparable: %v", t)
}
return v == reflect.Zero(t).Interface(), nil
}
// 获取企业微信的access_token
func getAccessToken() string {
accessToken := ""
if RedisStat == "ON" {
log.Println("尝试从redis获取token")
rdb := RedisClient()
value, err := rdb.Get(ctx, "access_token").Result()
if err == redis.Nil {
log.Println("access_token does not exist, need get it from remote API")
}
accessToken = value
}
if accessToken == "" {
log.Println("get access_token from remote API")
accessToken = GetRemoteToken(WecomCid, WecomSecret)
} else {
log.Println("get access_token from redis")
}
return accessToken
}
// InitJsonData 初始化Json公共部分数据
func InitJsonData(msgType string) JsonData {
return JsonData{
ToUser: WecomToUid,
AgentId: WecomAid,
MsgType: msgType,
DuplicateCheckInterval: 600,
}
}
// 主函数入口
func main() {
// 设置日志内容显示文件名和行号
log.SetFlags(log.LstdFlags | log.Lshortfile)
accessToken := getAccessToken()
wecomChan := func(res http.ResponseWriter, req *http.Request) {
_ = req.ParseForm()
sendkey := req.FormValue("sendkey")
if sendkey != Sendkey {
log.Panicln("sendkey 错误,请检查")
}
msgContent := req.FormValue("msg")
msgType := req.FormValue("msg_type")
log.Println("mes_type=", msgType)
mediaId := CheckOrUploadMedia(msgType, req, accessToken)
log.Println("企业微信上传临时素材接口返回的media_id==>", mediaId)
// 准备发送应用消息所需参数
postData := InitJsonData(msgType)
postData.Text = Msg{
Content: msgContent,
}
postData.Image = Pic{
MediaId: mediaId,
}
sendMessageUrl := fmt.Sprintf(SendMessageApi, accessToken)
postStatus := PostMsg(postData, sendMessageUrl)
postResponse := ParseJson(postStatus)
errcode := postResponse["errcode"]
_, err := IsZero(errcode)
if err != nil {
log.Printf("%v", err)
}
res.Header().Set("Content-type", "application/json")
_, _ = res.Write([]byte(postStatus))
}
http.HandleFunc("/wecomchan", wecomChan)
log.Fatal(http.ListenAndServe(":8080", nil))
}