1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-04 12:52:27 +08:00

Merge pull request #2 from silenceper/master

update into new
This commit is contained in:
cielu
2019-11-20 17:42:04 +08:00
committed by GitHub
11 changed files with 435 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
language: go
go:
- 1.13.x
- 1.12.x
- 1.11.x
- 1.10.x

116
device/authorize.go Normal file
View File

@@ -0,0 +1,116 @@
package device
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/util"
)
const (
// DeviceAdd 添加设备标识
DeviceAdd = iota
// DeviceUpgrade 更新设备标识
DeviceUpgrade
)
type reqDeviceAuthorize struct {
// 设备id的个数
DeviceNum string `json:"device_num"`
// 设备id的列表json的array格式其size必须等于device_num
DeviceList []ReqDevice `json:"device_list"`
// 请求操作的类型限定取值为0设备授权缺省值为0 1设备更新更新已授权设备的各属性值
OpType string `json:"op_type,omitempty"`
// 设备的产品编号(由微信硬件平台分配)。可在公众号设备功能管理页面查询。
//当 op_type 为0product_id 为1不要填写 product_id 字段(会引起不必要错误);
//当 op_typy 为0product_id 不为1必须填写 product_id 字段;
//当 op_type 为 1 时,不要填写 product_id 字段。
ProductID string `json:"product_id,omitempty"`
}
//ReqDevice 设备授权实体
type ReqDevice struct {
// 设备的 device id
ID string `json:"id"`
// 设备的mac地址 格式采用16进制串的方式长度为12字节
// 不需要0X前缀 1234567890AB
Mac string `json:"mac"`
// 支持以下四种连接协议:
// android classic bluetooth 1
// ios classic bluetooth 2
// ble 3
// wifi -- 4
// 一个设备可以支持多种连接类型,用符号"|"做分割,客户端优先选择靠前的连接方式(优先级按|关系的排序依次降低),举例:
// 1表示设备仅支持andiod classic bluetooth 1|2表示设备支持andiod 和ios 两种classic bluetooth但是客户端优先选择andriod classic bluetooth 协议如果andriod classic bluetooth协议连接失败再选择ios classic bluetooth协议进行连接
// 安卓平台不同时支持BLE和classic类型
ConnectProtocol string `json:"connect_protocol"`
//auth及通信的加密key第三方需要将key烧制在设备上128bit格式采用16进制串的方式长度为32字节不需要0X前缀 1234567890ABCDEF1234567890ABCDEF
AuthKey string `json:"auth_key"`
// 断开策略,目前支持: 1退出公众号页面时即断开连接 2退出公众号之后保持连接不断开
CloseStrategy string `json:"close_strategy"`
//连接策略32位整型按bit位置位目前仅第1bit和第3bit位有效bit置0为无效1为有效第2bit已被废弃且bit位可以按或置位如1|4=5各bit置位含义说明如下
//1第1bit置位在公众号对话页面不停的尝试连接设备
//4第3bit置位处于非公众号页面如主界面等微信自动连接。当用户切换微信到前台时可能尝试去连接设备连上后一定时间会断开
ConnStrategy string `json:"conn_strategy"`
// auth version设备和微信进行auth时会根据该版本号来确认auth buf和auth key的格式各version对应的auth buf及key的具体格式可以参看“客户端蓝牙外设协议”该字段目前支持取值
// 0不加密的version
// 1version 1
AuthVer string `json:"auth_ver"`
// 表示mac地址在厂商广播manufature data里含有mac地址的偏移取值如下
// -1在尾部、
// -2表示不包含mac地址 其他:非法偏移
ManuMacPos string `json:"manu_mac_pos"`
// 表示mac地址在厂商serial number里含有mac地址的偏移取值如下
// -1表示在尾部
// -2表示不包含mac地址 其他:非法偏移
SerMacPost string `json:"ser_mac_post"`
// 精简协议类型取值如下计步设备精简协议1 若该字段填1connect_protocol 必须包括3。非精简协议设备切勿填写该字段
BleSimpleProtocol string `json:"ble_simple_protocol,omitempty"`
}
//ResBaseInfo 授权回调实体
type ResBaseInfo struct {
BaseInfo struct {
DeviceType string `json:"device_type"`
DeviceID string `json:"device_id"`
} `json:"base_info"`
}
// 授权回调根信息
type resDeviceAuthorize struct {
util.CommonError
Resp []ResBaseInfo `json:"resp"`
}
// DeviceAuthorize 设备授权
func (d *Device) DeviceAuthorize(devices []ReqDevice, opType int, product string) (res []ResBaseInfo, err error) {
var accessToken string
accessToken, err = d.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriAuthorize, accessToken)
req := reqDeviceAuthorize{
DeviceNum: fmt.Sprintf("%d", len(devices)),
DeviceList: devices,
OpType: fmt.Sprintf("%d", opType),
ProductID: product,
}
var response []byte
response, err = util.PostJSON(uri, req)
if err != nil {
return nil, err
}
var result resDeviceAuthorize
err = json.Unmarshal(response, &result)
if err != nil {
return
}
if result.ErrCode != 0 {
err = fmt.Errorf("DeviceAuthorize Error , errcode=%d , errmsg=%s", result.ErrCode, result.ErrMsg)
return
}
res = result.Resp
return
}

106
device/bind.go Normal file
View File

@@ -0,0 +1,106 @@
package device
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/util"
)
// ReqBind 设备绑定解绑共通实体
type ReqBind struct {
Ticket string `json:"ticket,omitempty"`
DeviceID string `json:"device_id"`
OpenID string `json:"openid"`
}
type resBind struct {
BaseResp util.CommonError `json:"base_resp"`
}
// Bind 设备绑定
func (d *Device) Bind(req ReqBind) (err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriBind, accessToken)
var response []byte
if response, err = util.PostJSON(uri, req); err != nil {
return
}
var result resBind
if err = json.Unmarshal(response, &result); err != nil {
return
}
if result.BaseResp.ErrCode != 0 {
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
return
}
return
}
// Unbind 设备解绑
func (d *Device) Unbind(req ReqBind) (err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriUnbind, accessToken)
var response []byte
if response, err = util.PostJSON(uri, req); err != nil {
return
}
var result resBind
if err = json.Unmarshal(response, &result); err != nil {
return
}
if result.BaseResp.ErrCode != 0 {
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
return
}
return
}
// CompelBind 强制绑定用户和设备
func (d *Device) CompelBind(req ReqBind) (err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriCompelBind, accessToken)
var response []byte
if response, err = util.PostJSON(uri, req); err != nil {
return
}
var result resBind
if err = json.Unmarshal(response, &result); err != nil {
return
}
if result.BaseResp.ErrCode != 0 {
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
return
}
return
}
// CompelUnbind 强制解绑用户和设备
func (d *Device) CompelUnbind(req ReqBind) (err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriCompelUnbind, accessToken)
var response []byte
if response, err = util.PostJSON(uri, req); err != nil {
return
}
var result resBind
if err = json.Unmarshal(response, &result); err != nil {
return
}
if result.BaseResp.ErrCode != 0 {
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
return
}
return
}

60
device/device.go Normal file
View File

@@ -0,0 +1,60 @@
package device
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/context"
"github.com/silenceper/wechat/util"
)
const (
uriAuthorize = "https://api.weixin.qq.com/device/authorize_device"
uriQRCode = "https://api.weixin.qq.com/device/create_qrcode"
uriVerifyQRCode = "https://api.weixin.qq.com/device/verify_qrcode"
uriBind = "https://api.weixin.qq.com/device/bind"
uriUnbind = "https://api.weixin.qq.com/device/unbind"
uriCompelBind = "https://api.weixin.qq.com/device/compel_bind"
uriCompelUnbind = "https://api.weixin.qq.com/device/compel_unbind"
uriState = "https://api.weixin.qq.com/device/get_stat"
)
//Device struct
type Device struct {
*context.Context
}
//NewDevice 实例
func NewDevice(context *context.Context) *Device {
device := new(Device)
device.Context = context
return device
}
// ResDeviceState 设备状态响应实体
type ResDeviceState struct {
util.CommonError
Status int `json:"status"`
StatusInfo string `json:"status_info"`
}
// State 设备状态查询
func (d *Device) State(device string) (res ResDeviceState, err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s&device_id=%s", uriState, accessToken, device)
var response []byte
if response, err = util.HTTPGet(uri); err != nil {
return
}
if err = json.Unmarshal(response, &res); err != nil {
return
}
if res.ErrCode != 0 {
err = fmt.Errorf("DeviceState Error , errcode=%d , errmsg=%s", res.ErrCode, res.ErrMsg)
return
}
return
}

9
device/message.go Normal file
View File

@@ -0,0 +1,9 @@
package device
//MsgDevice 设备消息响应
type MsgDevice struct {
DeviceType string
DeviceID string
SessionID string
OpenID string
}

76
device/qrcode.go Normal file
View File

@@ -0,0 +1,76 @@
package device
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/util"
)
//ResCreateQRCode 获取二维码的返回实体
type ResCreateQRCode struct {
util.CommonError
DeviceNum int `json:"device_num"`
CodeList []struct {
DeviceID string `json:"device_id"`
Ticket string `json:"ticket"`
} `json:"code_list"`
}
// CreateQRCode 获取设备二维码
func (d *Device) CreateQRCode(devices []string) (res ResCreateQRCode, err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriQRCode, accessToken)
req := map[string]interface{}{
"device_num": len(devices),
"device_id_list": devices,
}
var response []byte
if response, err = util.PostJSON(uri, req); err != nil {
return
}
if err = json.Unmarshal(response, &res); err != nil {
return
}
if res.ErrCode != 0 {
err = fmt.Errorf("DeviceCreateQRCode Error , errcode=%d , errmsg=%s", res.ErrCode, res.ErrMsg)
return
}
return
}
//ResVerifyQRCode 验证授权结果实体
type ResVerifyQRCode struct {
util.CommonError
DeviceType string `json:"device_type"`
DeviceID string `json:"device_id"`
Mac string `json:"mac"`
}
// VerifyQRCode 验证设备二维码
func (d *Device) VerifyQRCode(ticket string) (res ResVerifyQRCode, err error) {
var accessToken string
if accessToken, err = d.GetAccessToken(); err != nil {
return
}
uri := fmt.Sprintf("%s?access_token=%s", uriVerifyQRCode, accessToken)
req := map[string]interface{}{
"ticket": ticket,
}
fmt.Println(req)
var response []byte
if response, err = util.PostJSON(uri, req); err != nil {
return
}
if err = json.Unmarshal(response, &res); err != nil {
return
}
if res.ErrCode != 0 {
err = fmt.Errorf("DeviceCreateQRCode Error , errcode=%d , errmsg=%s", res.ErrCode, res.ErrMsg)
return
}
return
}

View File

@@ -13,6 +13,7 @@ const (
addNewsURL = "https://api.weixin.qq.com/cgi-bin/material/add_news"
addMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/add_material"
delMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/del_material"
getMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/get_material"
)
//Material 素材管理
@@ -36,6 +37,33 @@ type Article struct {
ShowCoverPic int `json:"show_cover_pic"`
Content string `json:"content"`
ContentSourceURL string `json:"content_source_url"`
URL string `json:"url"`
DownURL string `json:"down_url"`
}
// GetNews 获取/下载永久素材
func (material *Material) GetNews(id string) ([]*Article, error) {
accessToken, err := material.GetAccessToken()
if err != nil {
return nil, err
}
uri := fmt.Sprintf("%s?access_token=%s", getMaterialURL, accessToken)
var req struct {
MediaID string `json:"media_id"`
}
req.MediaID = id
responseBytes, err := util.PostJSON(uri, req)
var res struct {
NewsItem []*Article `json:"news_item"`
}
err = json.Unmarshal(responseBytes, &res)
if err != nil {
return nil, err
}
return res.NewsItem, nil
}
//reqArticles 永久性图文素材请求信息

View File

@@ -3,8 +3,8 @@ package message
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/util"
"github.com/silenceper/wechat/context"
"github.com/silenceper/wechat/util"
)
const (
@@ -139,14 +139,14 @@ type MediaMiniprogrampage struct {
}
//Send 发送客服消息
func (manager *Manager) Send(msg *CustomerMessage) error {
accessToken,err:=manager.Context.GetAccessToken()
if err!=nil {
func (manager *Manager) Send(msg *CustomerMessage) error {
accessToken, err := manager.Context.GetAccessToken()
if err != nil {
return err
}
uri := fmt.Sprintf("%s?access_token=%s", customerSendMessage, accessToken)
response, err := util.PostJSON(uri, msg)
var result util.CommonError
var result util.CommonError
err = json.Unmarshal(response, &result)
if err != nil {
return err

View File

@@ -1,6 +1,10 @@
package message
import "encoding/xml"
import (
"encoding/xml"
"github.com/silenceper/wechat/device"
)
// MsgType 基本消息类型
type MsgType string
@@ -148,10 +152,13 @@ type MixMessage struct {
UnionID string `xml:"UnionId"`
// 内容审核相关
IsRisky bool `xml:"isrisky"`
ExtraInfoJSON string `xml:"extra_info_json"`
TraceID string `xml:"trace_id"`
StatusCode int `xml:"status_code"`
IsRisky bool `xml:"isrisky"`
ExtraInfoJSON string `xml:"extra_info_json"`
TraceID string `xml:"trace_id"`
StatusCode int `xml:"status_code"`
//设备相关
device.MsgDevice
}
//EventPic 发图事件推送
@@ -175,22 +182,32 @@ type ResponseEncryptedXMLMsg struct {
Nonce string `xml:"Nonce" json:"Nonce"`
}
// CDATA 使用该类型,在序列化为 xml 文本时文本会被解析器忽略
type CDATA string
// MarshalXML 实现自己的序列化方法
func (c CDATA) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(struct {
string `xml:",cdata"`
}{string(c)}, start)
}
// CommonToken 消息中通用的结构
type CommonToken struct {
XMLName xml.Name `xml:"xml"`
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
ToUserName CDATA `xml:"ToUserName"`
FromUserName CDATA `xml:"FromUserName"`
CreateTime int64 `xml:"CreateTime"`
MsgType MsgType `xml:"MsgType"`
}
//SetToUserName set ToUserName
func (msg *CommonToken) SetToUserName(toUserName string) {
func (msg *CommonToken) SetToUserName(toUserName CDATA) {
msg.ToUserName = toUserName
}
//SetFromUserName set FromUserName
func (msg *CommonToken) SetFromUserName(fromUserName string) {
func (msg *CommonToken) SetFromUserName(fromUserName CDATA) {
msg.FromUserName = fromUserName
}

View File

@@ -3,12 +3,12 @@ package message
//Text 文本消息
type Text struct {
CommonToken
Content string `xml:"Content"`
Content CDATA `xml:"Content"`
}
//NewText 初始化文本消息
func NewText(content string) *Text {
text := new(Text)
text.Content = content
text.Content = CDATA(content)
return text
}

View File

@@ -1,6 +1,7 @@
package wechat
import (
"github.com/silenceper/wechat/device"
"net/http"
"sync"
@@ -111,3 +112,8 @@ func (wc *Wechat) GetQR() *qr.QR {
func (wc *Wechat) GetMiniProgram() *miniprogram.MiniProgram {
return miniprogram.NewMiniProgram(wc.Context)
}
// GetDevice 获取智能设备的实例
func (wc *Wechat) GetDevice() *device.Device {
return device.NewDevice(wc.Context)
}