sns及refreshaccesstoken测试通过
This commit is contained in:
commit
a519daf9f1
|
|
@ -0,0 +1,26 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
go.sum
|
||||
.auth_file
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Hugo Zhu
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
# DingTalk Open API golang SDK
|
||||
|
||||

|
||||
|
||||
Check out DingTalk Open API document at: https://ding-doc.dingtalk.com/
|
||||
|
||||
## Usage
|
||||
|
||||
Fetch the SDK
|
||||
```
|
||||
export GOPATH=`pwd`
|
||||
go get myschools.me/suguo/godingtalk
|
||||
```
|
||||
|
||||
### Example code to send a micro app message
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"myschools.me/suguo/godingtalk"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := godingtalk.NewDingTalkClient(os.Getenv("corpid"), os.Getenv("corpsecret"))
|
||||
c.RefreshAccessToken()
|
||||
err := c.SendAppMessage(os.Args[1], os.Args[2], os.Args[3])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Guide
|
||||
|
||||
Step-by-step Guide to use this SDK
|
||||
|
||||
http://hugozhu.myalert.info/2016/05/02/66-use-dingtalk-golang-sdk-to-send-message-on-pi.html
|
||||
|
||||
## Tools
|
||||
|
||||
**ding_alert** : Command line tool to send app/text/oa ... messages
|
||||
|
||||
```
|
||||
export GOPATH=`pwd`
|
||||
go get myschools.me/suguo/godingtalk/demo/ding_alert
|
||||
|
||||
export corpid=<组织的corpid 通过 https://oa.dingtalk.com 获取>
|
||||
export corpsecret=<组织的corpsecret 通过 https://oa.dingtalk.com 获取>
|
||||
|
||||
./bin/ding_alert
|
||||
Usage of ./bin/ding_alert:
|
||||
-agent string
|
||||
agent Id (default "22194403")
|
||||
-chat string
|
||||
chat id (default "chat6a93bc1ee3b7d660d372b1b877a9de62")
|
||||
-file string
|
||||
file path for media message
|
||||
-link string
|
||||
link url (default "http://hugozhu.myalert.info/dingtalk")
|
||||
-sender string
|
||||
sender id (default "011217462940")
|
||||
-text string
|
||||
text for link message (default "This is link text")
|
||||
-title string
|
||||
title for link message (default "This is link title")
|
||||
-touser string
|
||||
touser id (default "0420506555")
|
||||
-type string
|
||||
message type (app, text, image, voice, link, oa) (default "app")
|
||||
|
||||
```
|
||||
|
||||
**github**: Deliver Github webhook events to DingTalk, which can be deployed on Google AppEngine.
|
||||
|
||||
more info at: http://hugozhu.myalert.info/2016/05/15/67-use-free-google-cloud-service-to-deliver-github-webhook-events-to-dingtalk.html
|
||||
|
||||
```
|
||||
export GOPATH=`pwd`
|
||||
go get myschools.me/suguo/godingtalk/demo/github/appengine
|
||||
```
|
||||
|
||||
Modify `app.yaml`
|
||||
|
||||
```
|
||||
cd src/myschools.me/suguo/godingtalk/demo/github/appengine
|
||||
cat app.yaml
|
||||
application: github-alert-<random_number>
|
||||
version: 1
|
||||
runtime: go
|
||||
api_version: go1
|
||||
env_variables:
|
||||
CORP_ID: '<从 http://oa.dingtalk.com 获取>'
|
||||
CORP_SECRET: '<从 http://oa.dingtalk.com 获取>'
|
||||
GITHUB_WEBHOOK_SECRET: '<从 http://github.com/ 获取>'
|
||||
SENDER_ID: '<从 http://open.dingtalk.com 调用api获取>'
|
||||
CHAT_ID: '<从 http://open.dingtalk.com 调用api获取>'
|
||||
handlers:
|
||||
- url: /.*
|
||||
script: _go_app
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Author Kevin Zhu
|
||||
*
|
||||
* Direct questions, comments to <ipandtcp@gmail.com>
|
||||
*/
|
||||
|
||||
package godingtalk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Attendance struct {
|
||||
GmtModifed int64 `json:"gmtModified"` //: 1492594486000,
|
||||
IsLegal string `json:"isLegal"` //: "N",
|
||||
BaseCheckTime int64 `json:"baseCheckTime"` //: 1492568460000,
|
||||
ID int64 `json:"id"` //: 933202551,
|
||||
UserAddress string `json:"userAddress"` //: "北京市朝阳区崔各庄镇阿里中心.望京A座阿里巴巴绿地中心",
|
||||
UID string `json:"userId"` //: "manager7078",
|
||||
CheckType string `json:"checkType"` //: "OnDuty",
|
||||
TimeResult string `json:"timeResult"` //: "Normal",
|
||||
DeviceID string `json:"deviceId"` // :"cb7ace07d52fe9be14f4d8bec5e1ba79"
|
||||
CorpID string `json:"corpId"` //: "ding7536bfee6fb1fa5a35c2f4657eb6378f",
|
||||
SourceType string `json:"sourceType"` //: "USER",
|
||||
WorkDate int64 `json:"workDate"` //: 1492531200000,
|
||||
PlanCheckTime int64 `json:"planCheckTime"` //: 1492568497000,
|
||||
GmtCreate int64 `json:"gmtCreate"` //: 1492594486000,
|
||||
LocaltionMethod string `json:"locationMethod"` //: "MAP",
|
||||
LocationResult string `json:"locationResult"` //: "Outside",
|
||||
UserLongitude float64 `json:"userLongitude"` //: 116.486888,
|
||||
PlanID int `json:"planId"` //: 4550269081,
|
||||
GroupID int `json:"groupId"` //: 121325603,
|
||||
UserAccuracy int `json:"userAccuracy"` //: 65,
|
||||
UserCheckTime int64 `json:"userCheckTime"` //: 1492568497000,
|
||||
UserLatitude float64 `json:"userLatitude"` //: 39.999946,
|
||||
ProcInstID string `json:"procInstId"` //: "cb992267-9b70"
|
||||
ApproveID int `json:"approveId"` // string, `json:""`//关联的审批id
|
||||
ClassId int `json:"classId"` //考勤班次id,没有的话表示该次打卡不在排班内
|
||||
UserSsid string `json:"userSsid"` //用户打卡wifi SSID
|
||||
UserMacAddr string `json:"userMacAddr"` //用户打卡wifi Mac地址
|
||||
BaseAddress string `json:"baseAddress"` //基准地址
|
||||
BaseLongitude float32 `json:"baseLongitude"` // 基准经度
|
||||
BaseLatitude float32 `json:"baseLatitude"` // 基准纬度
|
||||
BaseAccuracy int `json:"baseAccuracy"` // 基准定位精度
|
||||
BaseSsid string `json:"baseSsid"` //基准wifi ssid
|
||||
BaseMacAddr string `json:"baseMacAddr"` //基准 Mac 地址
|
||||
OutsideRemark string `json:"outsideRemark"` //打卡备注
|
||||
}
|
||||
|
||||
type listAttendanceRecordResp struct {
|
||||
OAPIResponse
|
||||
Records []Attendance `json:"recordresult"`
|
||||
}
|
||||
|
||||
// 获取所有的打卡记录,该员工当天如果打卡10条,那么10条都将返回
|
||||
func (c *DingTalkClient) ListAttendanceRecord(ulist []string, dateFrom time.Time, dateTo time.Time) ([]Attendance, error) {
|
||||
var resp listAttendanceRecordResp
|
||||
if len(ulist) > 50 || len(ulist) < 1 {
|
||||
return nil, errors.New("Users can't more than 50 or less than 1")
|
||||
}
|
||||
|
||||
if !dateFrom.Before(dateTo) {
|
||||
return nil, errors.New("FromDate must before ToDate")
|
||||
}
|
||||
if time.Duration(dateTo.UnixNano()-dateFrom.UnixNano()).Hours() > float64(7*24) {
|
||||
return nil, errors.New("Can't more than 6 days at once")
|
||||
}
|
||||
|
||||
request := map[string]interface{}{
|
||||
"checkDateFrom": dateFrom.Format("2006-01-02 15:04:05"), // "yyyy-MM-dd hh:mm:ss",
|
||||
"checkDateTo": dateTo.Format("2006-01-02 15:04:05"), // "yyyy-MM-dd hh:mm:ss",
|
||||
"userIds": ulist, // 企业内的员工id列表,最多不能超过50个
|
||||
}
|
||||
return resp.Records, c.httpRPC("/attendance/listRecord", nil, request, &resp)
|
||||
}
|
||||
|
||||
type listAttendanceResultResp struct {
|
||||
OAPIResponse
|
||||
HasMore bool `json:"hasMore"`
|
||||
Records []Attendance `json:"recordresult"`
|
||||
}
|
||||
|
||||
// 即使员工在这期间打了多次,该接口也只会返回两条记录,包括上午的打卡结果和下午的打卡结果
|
||||
// 用户如果为空则获取所有用户
|
||||
func (c *DingTalkClient) ListAttendanceResult(ulist []string, dateFrom, dateTo time.Time, offset, lmt int64) (listAttendanceResultResp, error) {
|
||||
var resp listAttendanceResultResp
|
||||
if time.Duration(dateTo.UnixNano()-dateFrom.UnixNano()).Hours() > float64(7*24) {
|
||||
return resp, errors.New("Can't more than 7 days at once")
|
||||
}
|
||||
|
||||
if !dateFrom.Before(dateTo) {
|
||||
return resp, errors.New("FromDate must before ToDate")
|
||||
}
|
||||
|
||||
request := map[string]interface{}{
|
||||
"workDateFrom": dateFrom.Format("2006-01-02 15:04:05"), // "yyyy-MM-dd hh:mm:ss",
|
||||
"workDateTo": dateTo.Format("2006-01-02 15:04:05"), // "yyyy-MM-dd hh:mm:ss",
|
||||
"userIdList": ulist, // ["员工UserId列表"], 必填,与offset和limit配合使用,不传表示分页获取全员的数据
|
||||
"offset": offset, // 必填,第一次传0,如果还有多余数据,下次传之前的offset加上limit的值
|
||||
"limit": lmt, // 最多50
|
||||
}
|
||||
return resp, c.httpRPC("/attendance/list", nil, request, &resp)
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestListAttendanceRecord(t *testing.T) {
|
||||
dataFrom, _ := time.Parse("2006-01-02", "2018-03-06")
|
||||
dataTo, _ := time.Parse("2006-01-02", "2018-03-10")
|
||||
records, err := c.ListAttendanceRecord([]string{"085354234826136236"}, dataFrom, dataTo)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if len(records) > 0 {
|
||||
t.Logf("%+v\n", records)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAttendanceResult(t *testing.T) {
|
||||
dataFrom, _ := time.Parse("2006-01-02", "2018-03-06")
|
||||
dataTo, _ := time.Parse("2006-01-02", "2018-03-10")
|
||||
resp, err := c.ListAttendanceResult([]string{"085354234826136236"}, dataFrom, dataTo, 0, 2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if len(resp.Records) > 0 {
|
||||
t.Logf("%+v\n", resp.Records[0])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package godingtalk
|
||||
|
||||
import "time"
|
||||
|
||||
type Event struct {
|
||||
OAPIResponse
|
||||
Id string
|
||||
Location string
|
||||
Summary string
|
||||
Description string
|
||||
Start struct {
|
||||
DateTime string `json:"date_time"`
|
||||
}
|
||||
End struct {
|
||||
DateTime string `json:"date_time"`
|
||||
}
|
||||
}
|
||||
|
||||
type ListEventsResponse struct {
|
||||
OAPIResponse
|
||||
Success bool `json:"success"`
|
||||
Result struct {
|
||||
Events []Event `json:"items"`
|
||||
Summary string `json:"summary"`
|
||||
NextPageToken string `json:"next_page_token"`
|
||||
} `json:"result"`
|
||||
}
|
||||
type CalendarTime struct {
|
||||
TimeZone string `json:"time_zone"`
|
||||
Date string `json:"date_time"`
|
||||
}
|
||||
|
||||
type CalendarRequest struct {
|
||||
TimeMax CalendarTime `json:"time_max"`
|
||||
TimeMin CalendarTime `json:"time_min"`
|
||||
StaffId string `json:"user_id"`
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) ListEvents(staffid string, from time.Time, to time.Time) (events []Event, err error) {
|
||||
location := time.Now().Location().String()
|
||||
timeMin := CalendarTime{
|
||||
TimeZone: location,
|
||||
Date: from.Format("2006-01-02T15:04:05Z0700"),
|
||||
}
|
||||
timeMax := CalendarTime{
|
||||
TimeZone: location,
|
||||
Date: to.Format("2006-01-02T15:04:05Z0700"),
|
||||
}
|
||||
|
||||
data := CalendarRequest{
|
||||
TimeMax: timeMax,
|
||||
TimeMin: timeMin,
|
||||
StaffId: staffid,
|
||||
}
|
||||
var resp ListEventsResponse
|
||||
err = c.httpRPC("topapi/calendar/list", nil, data, &resp)
|
||||
events = resp.Result.Events
|
||||
return events, err
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package godingtalk
|
||||
|
||||
type Callback struct {
|
||||
OAPIResponse
|
||||
Token string
|
||||
AES_KEY string `json:"aes_key"`
|
||||
URL string
|
||||
Callbacks []string `json:"call_back_tag"`
|
||||
}
|
||||
|
||||
//RegisterCallback is 注册事件回调接口
|
||||
func (c *DingTalkClient) RegisterCallback(callbacks []string, token string, aes_key string, callbackURL string) error {
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"call_back_tag": callbacks,
|
||||
"token": token,
|
||||
"aes_key": aes_key,
|
||||
"url": callbackURL,
|
||||
}
|
||||
err := c.httpRPC("call_back/register_call_back", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
//UpdateCallback is 更新事件回调接口
|
||||
func (c *DingTalkClient) UpdateCallback(callbacks []string, token string, aes_key string, callbackURL string) error {
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"call_back_tag": callbacks,
|
||||
"token": token,
|
||||
"aes_key": aes_key,
|
||||
"url": callbackURL,
|
||||
}
|
||||
err := c.httpRPC("call_back/update_call_back", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
//DeleteCallback is 删除事件回调接口
|
||||
func (c *DingTalkClient) DeleteCallback() error {
|
||||
var data OAPIResponse
|
||||
err := c.httpRPC("call_back/delete_call_back", nil, nil, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
//ListCallback is 查询事件回调接口
|
||||
func (c *DingTalkClient) ListCallback() (Callback, error) {
|
||||
var data Callback
|
||||
err := c.httpRPC("call_back/get_call_back", nil, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegisterCallback(t *testing.T) {
|
||||
err := c.UpdateCallback([]string{"user_modify_org"}, "hello", "1234567890123456789012345678901234567890aes", "http://go.myalert.info:8888/dingtalk/callback/")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
err = c.DeleteCallback()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
err = c.RegisterCallback([]string{"user_add_org"}, "hello", "1234567890123456789012345678901234567890aes", "http://go.myalert.info:8888/dingtalk/callback/")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListCallback(t *testing.T) {
|
||||
data, err := c.ListCallback()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
t.Log(data)
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
OAPIResponse
|
||||
Userid string
|
||||
Name string
|
||||
Mobile string
|
||||
Tel string
|
||||
Remark string
|
||||
Order int
|
||||
IsAdmin bool
|
||||
IsBoss bool
|
||||
IsLeader bool
|
||||
Active bool
|
||||
Department []int
|
||||
Position string
|
||||
Email string
|
||||
OrgEmail string
|
||||
Avatar string
|
||||
Extattr interface{}
|
||||
}
|
||||
|
||||
type UserList struct {
|
||||
OAPIResponse
|
||||
HasMore bool
|
||||
Userlist []User
|
||||
}
|
||||
|
||||
type Department struct {
|
||||
OAPIResponse
|
||||
Id int
|
||||
Name string
|
||||
ParentId int
|
||||
Order int
|
||||
DeptPerimits string
|
||||
UserPerimits string
|
||||
OuterDept bool
|
||||
OuterPermitDepts string
|
||||
OuterPermitUsers string
|
||||
OrgDeptOwner string
|
||||
DeptManagerUseridList string
|
||||
}
|
||||
|
||||
type DepartmentList struct {
|
||||
OAPIResponse
|
||||
Departments []Department `json:"department"`
|
||||
}
|
||||
|
||||
// DepartmentList is 获取部门列表
|
||||
func (c *DingTalkClient) DepartmentList() (DepartmentList, error) {
|
||||
var data DepartmentList
|
||||
err := c.httpRPC("department/list", nil, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//DepartmentDetail is 获取部门详情
|
||||
func (c *DingTalkClient) DepartmentDetail(id int) (Department, error) {
|
||||
var data Department
|
||||
params := url.Values{}
|
||||
params.Add("id", fmt.Sprintf("%d", id))
|
||||
err := c.httpRPC("department/get", params, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//UserList is 获取部门成员
|
||||
func (c *DingTalkClient) UserList(departmentID, offset, size int) (UserList, error) {
|
||||
var data UserList
|
||||
if size > 100 {
|
||||
return data, fmt.Errorf("size 最大100")
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("department_id", fmt.Sprintf("%d", departmentID))
|
||||
params.Add("offset", fmt.Sprintf("%d", offset))
|
||||
params.Add("size", fmt.Sprintf("%d", size))
|
||||
err := c.httpRPC("user/list", params, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//CreateChat is
|
||||
func (c *DingTalkClient) CreateChat(name string, owner string, useridlist []string) (string, error) {
|
||||
var data struct {
|
||||
OAPIResponse
|
||||
Chatid string
|
||||
}
|
||||
request := map[string]interface{}{
|
||||
"name": name,
|
||||
"owner": owner,
|
||||
"useridlist": useridlist,
|
||||
}
|
||||
err := c.httpRPC("chat/create", nil, request, &data)
|
||||
return data.Chatid, err
|
||||
}
|
||||
|
||||
//UserInfoByCode 校验免登录码并换取用户身份
|
||||
func (c *DingTalkClient) UserInfoByCode(code string) (User, error) {
|
||||
var data User
|
||||
params := url.Values{}
|
||||
params.Add("code", code)
|
||||
err := c.httpRPC("user/getuserinfo", params, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//UserInfoByUserId 获取用户详情
|
||||
func (c *DingTalkClient) UserInfoByUserId(userid string) (User, error) {
|
||||
var data User
|
||||
params := url.Values{}
|
||||
params.Add("userid", userid)
|
||||
err := c.httpRPC("user/get", params, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//UseridByUnionId 通过UnionId获取玩家Userid
|
||||
func (c *DingTalkClient) UseridByUnionId(unionid string) (string, error) {
|
||||
var data struct {
|
||||
OAPIResponse
|
||||
UserID string `json:"userid"`
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("unionid", unionid)
|
||||
err := c.httpRPC("user/getUseridByUnionid", params, nil, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return data.UserID, err
|
||||
}
|
||||
|
||||
//UseridByMobile 通过手机号获取Userid
|
||||
func (c *DingTalkClient) UseridByMobile(mobile string) (string, error) {
|
||||
var data struct {
|
||||
OAPIResponse
|
||||
UserID string `json:"userid"`
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("mobile", mobile)
|
||||
err := c.httpRPC("user/get_by_mobile", params, nil, &data)
|
||||
return data.UserID, err
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package godingtalk
|
||||
|
||||
//DataMessage 服务端加密、解密消息
|
||||
type DataMessage struct {
|
||||
OAPIResponse
|
||||
Data string
|
||||
}
|
||||
|
||||
|
||||
//Encrypt is 服务端加密
|
||||
func (c *DingTalkClient) Encrypt(str string) (string, error) {
|
||||
var data DataMessage
|
||||
request := map[string]interface{}{
|
||||
"data": str,
|
||||
}
|
||||
err := c.httpRPC("encryption/encrypt", nil, request, &data)
|
||||
if err!=nil {
|
||||
return "", err
|
||||
}
|
||||
return data.Data, nil
|
||||
}
|
||||
|
||||
//Decrypt is 服务端解密
|
||||
func (c *DingTalkClient) Decrypt(str string) (string, error) {
|
||||
var data DataMessage
|
||||
request := map[string]interface{}{
|
||||
"data": str,
|
||||
}
|
||||
err := c.httpRPC("encryption/decrypt", nil, request, &data)
|
||||
if err!=nil {
|
||||
return "", err
|
||||
}
|
||||
return data.Data, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
str, err := c.Encrypt("Hello")
|
||||
if err!=nil {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Log(str)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
/**
|
||||
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.UeYQVr&treeId=172&articleId=104970&docType=1
|
||||
* TODO: not completed yet
|
||||
**/
|
||||
|
||||
//FileResponse is
|
||||
type FileResponse struct {
|
||||
OAPIResponse
|
||||
Code int
|
||||
Msg string
|
||||
UploadID string `json:"uploadid"`
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
func (f *FileResponse) getWriter() io.Writer {
|
||||
return f.Writer
|
||||
}
|
||||
|
||||
//CreateFile is to create a new file in Ding Space
|
||||
func (c *DingTalkClient) CreateFile(size int64) (file FileResponse, err error) {
|
||||
buf := bytes.Buffer{}
|
||||
file = FileResponse{
|
||||
Writer: &buf,
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Add("size", fmt.Sprintf("%d", size))
|
||||
err = c.httpRPC("file/upload/create", params, nil, &file)
|
||||
return file, err
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateFile(t *testing.T) {
|
||||
file, err := c.CreateFile(1024)
|
||||
t.Log(file, err)
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
//MediaResponse is
|
||||
type MediaResponse struct {
|
||||
OAPIResponse
|
||||
Type string
|
||||
MediaID string `json:"media_id"`
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
func (m *MediaResponse) getWriter() io.Writer {
|
||||
return m.Writer
|
||||
}
|
||||
|
||||
//UploadMedia is to upload media file to DingTalk
|
||||
func (c *DingTalkClient) UploadMedia(mediaType string, filename string, reader io.Reader) (media MediaResponse, err error) {
|
||||
upload := UploadFile{
|
||||
FieldName: "media",
|
||||
FileName: filename,
|
||||
Reader: reader,
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Add("type", mediaType)
|
||||
c.HTTPClient.Timeout = 120 * time.Second
|
||||
err = c.httpRPC("media/upload", params, upload, &media)
|
||||
return media, err
|
||||
}
|
||||
|
||||
//DownloadMedia is to download a media file from DingTalk
|
||||
func (c *DingTalkClient) DownloadMedia(mediaID string, write io.Writer) error {
|
||||
var data MediaResponse
|
||||
data.Writer = write
|
||||
params := url.Values{}
|
||||
params.Add("media_id", mediaID)
|
||||
c.HTTPClient.Timeout = 120 * time.Second
|
||||
err := c.httpRPC("media/get", params, nil, &data)
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//SendAppMessage is 发送企业会话消息
|
||||
func (c *DingTalkClient) SendAppMessage(agentID string, touser string, msg string) error {
|
||||
if agentID == "" {
|
||||
agentID = c.AgentID
|
||||
}
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"touser": touser,
|
||||
"agentid": agentID,
|
||||
"msgtype": "text",
|
||||
"text": map[string]interface{}{
|
||||
"content": msg,
|
||||
},
|
||||
}
|
||||
err := c.httpRPC("message/send", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
//SendAppOAMessage is 发送OA消息
|
||||
func (c *DingTalkClient) SendAppOAMessage(agentID string, touser string, msg OAMessage) error {
|
||||
if agentID == "" {
|
||||
agentID = c.AgentID
|
||||
}
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"touser": touser,
|
||||
"agentid": agentID,
|
||||
"msgtype": "oa",
|
||||
"oa": msg,
|
||||
}
|
||||
err := c.httpRPC("message/send", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
// ActionCardMessage
|
||||
func (c *DingTalkClient) SendOverAllActionCardMessage(agentID string, touser string, msg OverAllActionCardMessage) error {
|
||||
if agentID == "" {
|
||||
agentID = c.AgentID
|
||||
}
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"touser": touser,
|
||||
"agentid": agentID,
|
||||
"msgtype": "action_card",
|
||||
"action_card": msg,
|
||||
}
|
||||
err := c.httpRPC("message/send", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) SendIndependentActionCardMessage(agentID string, touser string, msg IndependentActionCardMessage) error {
|
||||
if agentID == "" {
|
||||
agentID = c.AgentID
|
||||
}
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"touser": touser,
|
||||
"agentid": agentID,
|
||||
"msgtype": "action_card",
|
||||
"action_card": msg,
|
||||
}
|
||||
err := c.httpRPC("message/send", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
//SendAppLinkMessage is 发送企业会话链接消息
|
||||
func (c *DingTalkClient) SendAppLinkMessage(agentID, touser string, title, text string, picUrl, url string) error {
|
||||
if agentID == "" {
|
||||
agentID = c.AgentID
|
||||
}
|
||||
var data OAPIResponse
|
||||
request := map[string]interface{}{
|
||||
"touser": touser,
|
||||
"agentid": agentID,
|
||||
"msgtype": "link",
|
||||
"link": map[string]string{
|
||||
"messageUrl": url,
|
||||
"picUrl": picUrl,
|
||||
"title": title,
|
||||
"text": text,
|
||||
},
|
||||
}
|
||||
err := c.httpRPC("message/send", nil, request, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
//SendTextMessage is 发送普通文本消息
|
||||
func (c *DingTalkClient) SendTextMessage(sender string, cid string, msg string) (data MessageResponse, err error) {
|
||||
request := map[string]interface{}{
|
||||
"chatid": cid,
|
||||
"sender": sender,
|
||||
"msgtype": "text",
|
||||
"text": map[string]interface{}{
|
||||
"content": msg,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("chat/send", nil, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//SendImageMessage is 发送图片消息
|
||||
func (c *DingTalkClient) SendImageMessage(sender string, cid string, mediaID string) (data MessageResponse, err error) {
|
||||
request := map[string]interface{}{
|
||||
"chatid": cid,
|
||||
"sender": sender,
|
||||
"msgtype": "image",
|
||||
"image": map[string]string{
|
||||
"media_id": mediaID,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("chat/send", nil, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//SendVoiceMessage is 发送语音消息
|
||||
func (c *DingTalkClient) SendVoiceMessage(sender string, cid string, mediaID string, duration string) (data MessageResponse, err error) {
|
||||
request := map[string]interface{}{
|
||||
"chatid": cid,
|
||||
"sender": sender,
|
||||
"msgtype": "voice",
|
||||
"voice": map[string]string{
|
||||
"media_id": mediaID,
|
||||
"duration": duration,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("chat/send", nil, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//SendFileMessage is 发送文件消息
|
||||
func (c *DingTalkClient) SendFileMessage(sender string, cid string, mediaID string) (data MessageResponse, err error) {
|
||||
request := map[string]interface{}{
|
||||
"chatid": cid,
|
||||
"sender": sender,
|
||||
"msgtype": "file",
|
||||
"file": map[string]string{
|
||||
"media_id": mediaID,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("chat/send", nil, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//SendLinkMessage is 发送链接消息
|
||||
func (c *DingTalkClient) SendLinkMessage(sender string, cid string, mediaID string, url string, title string, text string) (data MessageResponse, err error) {
|
||||
request := map[string]interface{}{
|
||||
"chatid": cid,
|
||||
"sender": sender,
|
||||
"msgtype": "link",
|
||||
"link": map[string]string{
|
||||
"messageUrl": url,
|
||||
"picUrl": mediaID,
|
||||
"title": title,
|
||||
"text": text,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("chat/send", nil, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
|
||||
// OverAllActionCardMessage 整体跳转ActionCard
|
||||
type OverAllActionCardMessage struct {
|
||||
Title string `json:"title"`
|
||||
MarkDown string `json:"markdown"`
|
||||
SingleTitle string `json:"single_title"`
|
||||
SingleUrl string `json:"single_url"`
|
||||
}
|
||||
|
||||
// IndependentActionCardMessage 独立跳转ActionCard
|
||||
type IndependentActionCardMessage struct {
|
||||
Title string `json:"title"`
|
||||
MarkDown string `json:"markdown"`
|
||||
BtnOrientation string `json:"btn_orientation"`
|
||||
BtnJsonList []ActionCardMessageBtnList `json:"btn_json_list"`
|
||||
}
|
||||
|
||||
type ActionCardMessageBtnList struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
ActionUrl string `json:"action_url,omitempty"`
|
||||
}
|
||||
|
||||
func (m *IndependentActionCardMessage) AppendBtnItem(title string, action_url string) {
|
||||
f := ActionCardMessageBtnList{Title: title, ActionUrl: action_url}
|
||||
|
||||
if m.BtnJsonList == nil {
|
||||
m.BtnJsonList = []ActionCardMessageBtnList{}
|
||||
}
|
||||
|
||||
m.BtnJsonList = append(m.BtnJsonList, f)
|
||||
}
|
||||
|
||||
//OAMessage is the Message for OA
|
||||
type OAMessage struct {
|
||||
URL string `json:"message_url"`
|
||||
PcURL string `json:"pc_message_url"`
|
||||
Head struct {
|
||||
BgColor string `json:"bgcolor,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
} `json:"head,omitempty"`
|
||||
Body struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Form []OAMessageForm `json:"form,omitempty"`
|
||||
Rich OAMessageRich `json:"rich,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
FileCount int `json:"file_count,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
} `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
type OAMessageForm struct {
|
||||
Key string `json:"key,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type OAMessageRich struct {
|
||||
Num string `json:"num,omitempty"`
|
||||
Unit string `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (m *OAMessage) AppendFormItem(key string, value string) {
|
||||
f := OAMessageForm{Key: key, Value: value}
|
||||
|
||||
if m.Body.Form == nil {
|
||||
m.Body.Form = []OAMessageForm{}
|
||||
}
|
||||
|
||||
m.Body.Form = append(m.Body.Form, f)
|
||||
}
|
||||
|
||||
//SendOAMessage is 发送OA消息
|
||||
func (c *DingTalkClient) SendOAMessage(sender string, cid string, msg OAMessage) (data MessageResponse, err error) {
|
||||
request := map[string]interface{}{
|
||||
"chatid": cid,
|
||||
"sender": sender,
|
||||
"msgtype": "oa",
|
||||
"oa": msg,
|
||||
}
|
||||
err = c.httpRPC("chat/send", nil, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//GetMessageReadList is 获取已读列表
|
||||
func (c *DingTalkClient) GetMessageReadList(messageID string, cursor int, size int) (data MessageReadListResponse, err error) {
|
||||
params := url.Values{}
|
||||
params.Add("messageId", messageID)
|
||||
params.Add("cursor", strconv.Itoa(cursor))
|
||||
params.Add("size", strconv.Itoa(size))
|
||||
err = c.httpRPC("chat/getReadList", params, nil, &data)
|
||||
return data, err
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type RobotAtList struct {
|
||||
AtMobiles []string `json:"atMobiles"`
|
||||
IsAtAll bool `json:"isAtAll"`
|
||||
}
|
||||
|
||||
type RobotOutgoingMessage struct {
|
||||
MessageType string `json:"msgtype"`
|
||||
Text struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
} `json:"text,omitempty"`
|
||||
MessageID string `json:"msgId"`
|
||||
CreatedAt int64 `json:"createAt"`
|
||||
ConversationID string `json:"conversationId"`
|
||||
ConversationType string `json:"conversationType"`
|
||||
ConversationTitle string `json:"conversationTitle"`
|
||||
SenderID string `json:"senderId"`
|
||||
SenderNick string `json:"senderNick"`
|
||||
SenderCorpID string `json:"senderCorpId"`
|
||||
SenderStaffID string `json:"senderStaffId"`
|
||||
ChatbotUserID string `json:"chatbotUserId"`
|
||||
AtUsers []struct {
|
||||
DingTalkID string `json:"dingtalkId,omitempty"`
|
||||
StaffID string `json:"staffId,omitempty"`
|
||||
} `json:"atUsers,omitempty"`
|
||||
}
|
||||
|
||||
//SendRobotTextMessage can send a text message to a group chat
|
||||
func (c *DingTalkClient) SendRobotTextMessage(accessToken string, msg string) (data MessageResponse, err error) {
|
||||
params := url.Values{}
|
||||
params.Add("access_token", accessToken)
|
||||
request := map[string]interface{}{
|
||||
"msgtype": "text",
|
||||
"text": map[string]interface{}{
|
||||
"content": msg,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("robot/send", params, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
//SendRobotMarkdownMessage can send a text message to a group chat
|
||||
func (c *DingTalkClient) SendRobotMarkdownMessage(accessToken string, title string, msg string) (data MessageResponse, err error) {
|
||||
params := url.Values{}
|
||||
params.Add("access_token", accessToken)
|
||||
request := map[string]interface{}{
|
||||
"msgtype": "markdown",
|
||||
"markdown": map[string]interface{}{
|
||||
"title": title,
|
||||
"text": msg,
|
||||
},
|
||||
}
|
||||
err = c.httpRPC("robot/send", params, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
// SendRobotTextAtMessage can send a text message and at user to a group chat
|
||||
func (c *DingTalkClient) SendRobotTextAtMessage(accessToken string, msg string, at *RobotAtList) (data OAPIResponse, err error) {
|
||||
params := url.Values{}
|
||||
params.Add("access_token", accessToken)
|
||||
request := map[string]interface{}{
|
||||
"msgtype": "text",
|
||||
"text": map[string]interface{}{
|
||||
"content": msg,
|
||||
},
|
||||
"at": at,
|
||||
}
|
||||
err = c.httpRPC("robot/send", params, request, &data)
|
||||
return data, err
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
//普通钉钉用户账号开放相关接口
|
||||
package godingtalk
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SnsUserInfoResponse struct {
|
||||
OAPIResponse
|
||||
|
||||
CorpInfo []struct {
|
||||
CorpName string `json:"corp_name"`
|
||||
IsAuth bool `json:"is_auth"`
|
||||
IsManager bool `json:"is_manager"`
|
||||
RightsLevel int `json:"rights_level"`
|
||||
} `json:"corp_info"`
|
||||
|
||||
UserInfo struct {
|
||||
MaskedMobile string `json:"marskedMobile"`
|
||||
Nick string `json:"nick"`
|
||||
OpenID string `json:"openid"`
|
||||
UnionID string `json:"unionid"`
|
||||
DingID string `json:"dingId"`
|
||||
} `json:"user_info"`
|
||||
}
|
||||
|
||||
//获取用户授权的个人信息
|
||||
func (c *DingTalkClient) SnsUserInfo(code string) (SnsUserInfoResponse, error) {
|
||||
ts := strconv.FormatInt(time.Now().UnixNano()/1000000, 10)
|
||||
params := url.Values{}
|
||||
params.Add("accessKey", c.AppKey)
|
||||
params.Add("timestamp", ts)
|
||||
params.Add("signature", encodeSHA256(ts, c.AppSecret))
|
||||
|
||||
body := struct {
|
||||
Code string `json:"tmp_auth_code"`
|
||||
}{code}
|
||||
|
||||
var data SnsUserInfoResponse
|
||||
err := c.httpRequest("sns/getuserinfo_bycode", params, body, &data)
|
||||
return data, err
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSnsUserInfo(t *testing.T) {
|
||||
userinfo, err := c.SnsUserInfo("f9f9ba22256136f29a7fb3dd5d26c24c")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
fmt.Println(userinfo)
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
r "math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
AES_ENCODE_KEY_LENGTH = 43
|
||||
)
|
||||
|
||||
var DefaultDingtalkCrypto *Crypto
|
||||
|
||||
type Crypto struct {
|
||||
Token string
|
||||
AesKey string
|
||||
SuiteKey string
|
||||
block cipher.Block
|
||||
bkey []byte
|
||||
}
|
||||
|
||||
func encodeSHA256(body, secret string) string {
|
||||
// 钉钉签名算法实现
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(body))
|
||||
sum := h.Sum(nil)
|
||||
return base64.StdEncoding.EncodeToString(sum)
|
||||
}
|
||||
|
||||
/*
|
||||
token 数据签名需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写
|
||||
aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey
|
||||
suiteKey 一般使用corpID
|
||||
*/
|
||||
func NewCrypto(token, aesKey, suiteKey string) (c *Crypto) {
|
||||
c = &Crypto{
|
||||
Token: token,
|
||||
AesKey: aesKey,
|
||||
SuiteKey: suiteKey,
|
||||
}
|
||||
if len(c.AesKey) != AES_ENCODE_KEY_LENGTH {
|
||||
panic("不合法的aeskey")
|
||||
}
|
||||
var err error
|
||||
c.bkey, err = base64.StdEncoding.DecodeString(aesKey + "=")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
c.block, err = aes.NewCipher(c.bkey)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
/*
|
||||
signature: 签名字符串
|
||||
timeStamp: 时间戳
|
||||
nonce: 随机字符串
|
||||
secretStr: 密文
|
||||
返回: 解密后的明文
|
||||
*/
|
||||
func (c *Crypto) DecryptMsg(signature, timeStamp, nonce, secretStr string) (string, error) {
|
||||
if !c.VerifySignature(c.Token, timeStamp, nonce, secretStr, signature) {
|
||||
return "", errors.New("签名不匹配")
|
||||
}
|
||||
decode, err := base64.StdEncoding.DecodeString(secretStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(decode) < aes.BlockSize {
|
||||
return "", errors.New("密文太短啦")
|
||||
}
|
||||
blockMode := cipher.NewCBCDecrypter(c.block, c.bkey[:c.block.BlockSize()])
|
||||
plantText := make([]byte, len(decode))
|
||||
blockMode.CryptBlocks(plantText, decode)
|
||||
plantText = PKCS7UnPadding(plantText)
|
||||
size := binary.BigEndian.Uint32(plantText[16 : 16+4])
|
||||
plantText = plantText[16+4:]
|
||||
cropid := plantText[size:]
|
||||
if string(cropid) != c.SuiteKey {
|
||||
return "", errors.New("CropID不正确")
|
||||
}
|
||||
return string(plantText[:size]), nil
|
||||
}
|
||||
|
||||
func PKCS7UnPadding(plantText []byte) []byte {
|
||||
length := len(plantText)
|
||||
unpadding := int(plantText[length-1])
|
||||
return plantText[:(length - unpadding)]
|
||||
}
|
||||
|
||||
/*
|
||||
replyMsg: 明文字符串
|
||||
timeStamp: 时间戳
|
||||
nonce: 随机字符串
|
||||
返回: 密文,签名字符串
|
||||
*/
|
||||
func (c *Crypto) EncryptMsg(replyMsg, timeStamp, nonce string) (string, string, error) {
|
||||
//原生消息体长度
|
||||
size := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(size, uint32(len(replyMsg)))
|
||||
replyMsg = c.RandomString(16) + string(size) + replyMsg + c.SuiteKey
|
||||
plantText := PKCS7Padding([]byte(replyMsg), c.block.BlockSize())
|
||||
if len(plantText)%aes.BlockSize != 0 {
|
||||
return "", "", errors.New("消息体大小不为16的倍数")
|
||||
}
|
||||
|
||||
blockMode := cipher.NewCBCEncrypter(c.block, c.bkey[:c.block.BlockSize()])
|
||||
ciphertext := make([]byte, len(plantText))
|
||||
blockMode.CryptBlocks(ciphertext, plantText)
|
||||
outStr := base64.StdEncoding.EncodeToString(ciphertext)
|
||||
sigStr := c.GenerateSignature(c.Token, timeStamp, nonce, string(outStr))
|
||||
return string(outStr), sigStr, nil
|
||||
}
|
||||
|
||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
// 数据签名
|
||||
func (c *Crypto) GenerateSignature(token, timeStamp, nonce, secretStr string) string {
|
||||
// 先将参数值进行排序
|
||||
params := make([]string, 0)
|
||||
params = append(params, token)
|
||||
params = append(params, secretStr)
|
||||
params = append(params, timeStamp)
|
||||
params = append(params, nonce)
|
||||
sort.Strings(params)
|
||||
return sha1Sign(params[0] + params[1] + params[2] + params[3])
|
||||
}
|
||||
|
||||
// 校验数据签名
|
||||
func (c *Crypto) VerifySignature(token, timeStamp, nonce, secretStr, sigture string) bool {
|
||||
return c.GenerateSignature(token, timeStamp, nonce, secretStr) == sigture
|
||||
}
|
||||
|
||||
func (c *Crypto) RandomString(n int, alphabets ...byte) string {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
var randby bool
|
||||
if num, err := rand.Read(bytes); num != n || err != nil {
|
||||
r.Seed(time.Now().UnixNano())
|
||||
randby = true
|
||||
}
|
||||
for i, b := range bytes {
|
||||
if len(alphabets) == 0 {
|
||||
if randby {
|
||||
bytes[i] = alphanum[r.Intn(len(alphanum))]
|
||||
} else {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
} else {
|
||||
if randby {
|
||||
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||
} else {
|
||||
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||
}
|
||||
}
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module myschools.me/suguo/godingtalk
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/google/go-github v17.0.0+incompatible
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/ipandtcp/godingtalk v0.0.0-20180410032244-ca3d6ac197fb
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
google.golang.org/api v0.10.0
|
||||
google.golang.org/appengine v1.6.4
|
||||
)
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//VERSION is SDK version
|
||||
VERSION = "0.3"
|
||||
)
|
||||
|
||||
//DingTalkClient is the Client to access DingTalk Open API
|
||||
type DingTalkClient struct {
|
||||
AppKey string
|
||||
AppSecret string
|
||||
|
||||
AgentID string
|
||||
PartnerID string
|
||||
AccessToken string
|
||||
HTTPClient *http.Client
|
||||
Cache Cache
|
||||
}
|
||||
|
||||
//Unmarshallable is
|
||||
type Unmarshallable interface {
|
||||
checkError() error
|
||||
getWriter() io.Writer
|
||||
}
|
||||
|
||||
//OAPIResponse is
|
||||
type OAPIResponse struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
func (data *OAPIResponse) checkError() (err error) {
|
||||
if data.ErrCode != 0 {
|
||||
err = fmt.Errorf("%d: %s", data.ErrCode, data.ErrMsg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (data *OAPIResponse) getWriter() io.Writer {
|
||||
return nil
|
||||
}
|
||||
|
||||
//MessageResponse is
|
||||
type MessageResponse struct {
|
||||
OAPIResponse
|
||||
MessageID string `json:"messageId"`
|
||||
}
|
||||
|
||||
//MessageResponse is
|
||||
type MessageReadListResponse struct {
|
||||
OAPIResponse
|
||||
NextCursor int64 `json:"next_cursor"`
|
||||
ReadUserIdList []string `json:"readUserIdList"`
|
||||
}
|
||||
|
||||
//AccessTokenResponse is
|
||||
type AccessTokenResponse struct {
|
||||
OAPIResponse
|
||||
AccessToken string `json:"access_token"`
|
||||
Expires int `json:"expires_in"`
|
||||
Created int64
|
||||
}
|
||||
|
||||
//CreatedAt is when the access token is generated
|
||||
func (e *AccessTokenResponse) CreatedAt() int64 {
|
||||
return e.Created
|
||||
}
|
||||
|
||||
//ExpiresIn is how soon the access token is expired
|
||||
func (e *AccessTokenResponse) ExpiresIn() int {
|
||||
return e.Expires
|
||||
}
|
||||
|
||||
//JsAPITicketResponse is
|
||||
type JsAPITicketResponse struct {
|
||||
OAPIResponse
|
||||
Ticket string
|
||||
Expires int `json:"expires_in"`
|
||||
Created int64
|
||||
}
|
||||
|
||||
//CreatedAt is when the ticket is generated
|
||||
func (e *JsAPITicketResponse) CreatedAt() int64 {
|
||||
return e.Created
|
||||
}
|
||||
|
||||
//ExpiresIn is how soon the ticket is expired
|
||||
func (e *JsAPITicketResponse) ExpiresIn() int {
|
||||
return e.Expires
|
||||
}
|
||||
|
||||
//NewDingTalkClient creates a DingTalkClient instance
|
||||
func NewDingTalkClient(appkey, appsecret string) *DingTalkClient {
|
||||
c := new(DingTalkClient)
|
||||
c.AppKey = appkey
|
||||
c.AppSecret = appsecret
|
||||
c.HTTPClient = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
c.Cache = NewFileCache(".auth_file")
|
||||
return c
|
||||
}
|
||||
|
||||
//RefreshAccessToken is to get a valid access token
|
||||
func (c *DingTalkClient) RefreshAccessToken() error {
|
||||
var data AccessTokenResponse
|
||||
err := c.Cache.Get(&data)
|
||||
if err == nil {
|
||||
c.AccessToken = data.AccessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("appkey", c.AppKey)
|
||||
params.Add("appsecret", c.AppSecret)
|
||||
|
||||
err = c.httpRPC("gettoken", params, nil, &data)
|
||||
if err == nil {
|
||||
c.AccessToken = data.AccessToken
|
||||
data.Expires = data.Expires | 7200
|
||||
data.Created = time.Now().Unix()
|
||||
err = c.Cache.Set(&data)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//GetJsAPITicket is to get a valid ticket for JS API
|
||||
func (c *DingTalkClient) GetJsAPITicket() (ticket string, err error) {
|
||||
var data JsAPITicketResponse
|
||||
cache := NewFileCache(".jsapi_ticket")
|
||||
err = cache.Get(&data)
|
||||
if err == nil {
|
||||
return data.Ticket, err
|
||||
}
|
||||
err = c.httpRPC("get_jsapi_ticket", nil, nil, &data)
|
||||
if err == nil {
|
||||
ticket = data.Ticket
|
||||
cache.Set(&data)
|
||||
}
|
||||
return ticket, err
|
||||
}
|
||||
|
||||
//GetConfig is to return config in json
|
||||
func (c *DingTalkClient) GetConfig(nonceStr string, timestamp string, url string) string {
|
||||
ticket, _ := c.GetJsAPITicket()
|
||||
config := map[string]string{
|
||||
"url": url,
|
||||
"nonceStr": nonceStr,
|
||||
"agentId": c.AgentID,
|
||||
"timeStamp": timestamp,
|
||||
"corpId": c.AppKey,
|
||||
"ticket": ticket,
|
||||
"signature": Sign(ticket, nonceStr, timestamp, url),
|
||||
}
|
||||
bytes, _ := json.Marshal(&config)
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
//Sign is 签名
|
||||
func Sign(ticket string, nonceStr string, timeStamp string, url string) string {
|
||||
s := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s", ticket, nonceStr, timeStamp, url)
|
||||
return sha1Sign(s)
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var c *DingTalkClient
|
||||
|
||||
func init() {
|
||||
c = NewDingTalkClient("dinguipztmzpv8sog933", "kUYbKHxNixdMhTmW6IrdTE-yVnWfQLs1C7RQIAsrlwz8BYlVmceFs-3JRBmU32rQ")
|
||||
err := c.RefreshAccessToken()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitWithAppKey(t *testing.T) {
|
||||
c1 := NewDingTalkClient("dinguipztmzpv8sog933", "kUYbKHxNixdMhTmW6IrdTE-yVnWfQLs1C7RQIAsrlwz8BYlVmceFs-3JRBmU32rQ")
|
||||
err := c1.RefreshAccessToken()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = c1.SendRobotTextMessage(c1.AccessToken, "Message sent successfully with appkey and appsecret")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarListApi(t *testing.T) {
|
||||
from := time.Now().AddDate(0, 0, -1)
|
||||
to := time.Now().AddDate(0, 0, 1)
|
||||
_, err := c.ListEvents("0420506555", from, to)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//for _, event := range events {
|
||||
// t.Logf("%v %v %v %v", event.Start, event.End, event.Summary, event.Description)
|
||||
//}
|
||||
}
|
||||
|
||||
func TestDepartmentApi(t *testing.T) {
|
||||
departments, err := c.DepartmentList()
|
||||
// t.Logf("%+v %+v", departments, err)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
d, err := c.DepartmentDetail(departments.Departments[0].Id)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if d.Id != departments.Departments[0].Id {
|
||||
t.Error("DepartmentDetail error")
|
||||
}
|
||||
|
||||
for _, department := range departments.Departments {
|
||||
t.Logf("dept: %v", department)
|
||||
list, err := c.UserList(department.Id, 0, 100)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
for _, user := range list.Userlist {
|
||||
t.Logf("\t\tuser: %v", user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsAPITicket(t *testing.T) {
|
||||
ticket, err := c.GetJsAPITicket()
|
||||
if err != nil || ticket == "" {
|
||||
t.Error("JsAPITicket error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateChat(t *testing.T) {
|
||||
// chatid, err := c.CreateChat("Test chat", "0420506555", []string{"0420506555"})
|
||||
// if err!=nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// t.Log("-----",chatid)
|
||||
}
|
||||
|
||||
func TestSendAppMessageApi(t *testing.T) {
|
||||
err := c.SendAppMessage("22194403", "0420506555", "测试消息,请忽略") //@all
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextMessage(t *testing.T) {
|
||||
data, err := c.SendTextMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "测试消息,来自双十一,请忽略")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if data.MessageID == "" {
|
||||
t.Error("Message id is empty")
|
||||
}
|
||||
}
|
||||
data2, _ := c.GetMessageReadList(data.MessageID, 0, 10)
|
||||
if len(data2.ReadUserIdList) == 0 {
|
||||
t.Error("Message Read List should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendOAMessage(t *testing.T) {
|
||||
msg := OAMessage{}
|
||||
msg.URL = "http://www.google.com/"
|
||||
msg.Head.Text = "头部标题"
|
||||
msg.Head.BgColor = "FFBBBBBB"
|
||||
msg.Body.Title = "正文标题"
|
||||
msg.Body.Content = "test content"
|
||||
_, err := c.SendOAMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", msg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func _TestDownloadAndUploadImage(t *testing.T) {
|
||||
f, err := os.Create("lADOHrf_oVxc.jpg")
|
||||
if err == nil {
|
||||
err = c.DownloadMedia("@lADOHrf_oVxc", f)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
f, _ = os.Open("lADOHrf_oVxc.jpg")
|
||||
defer f.Close()
|
||||
media, err := c.UploadMedia("image", "myfile.jpg", f)
|
||||
if media.MediaID == "" {
|
||||
t.Error("Upload File Failed")
|
||||
}
|
||||
t.Log("uploaded file mediaid:", media.MediaID)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = c.SendImageMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "@lADOHrf_oVxc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoiceMessage(t *testing.T) {
|
||||
// f, _ := os.Open("/Users/hugozhu/Downloads/BlackBerry_test2_AMR-NB_Mono_12.2kbps_8000Hz.amr")
|
||||
// defer f.Close()
|
||||
// media, err := c.UploadMedia("voice", "sample.amr", f)
|
||||
// if media.MediaID == "" {
|
||||
// t.Error("Upload File Failed")
|
||||
// }
|
||||
// t.Log("uploaded file mediaid:", media.MediaID)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
_, err := c.SendVoiceMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "@lATOHr53E84DALnDzml4wS0", "10")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRobotMessage(t *testing.T) {
|
||||
_, err := c.SendRobotTextMessage(os.Getenv("token"), "这是一条测试消息")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = c.SendRobotMarkdownMessage(os.Getenv("token"), "测试标题", "# 杭州天气\n这是一条测试消息\n"+
|
||||
"> 9度,西北风1级,空气良89,**相对温度**73%\n\n"+
|
||||
"> \n"+
|
||||
"> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRobotAtMessage(t *testing.T) {
|
||||
_, err := c.SendRobotTextAtMessage(os.Getenv("token"), "这是一条测试消息", &RobotAtList{
|
||||
IsAtAll: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Author Kevin Zhu
|
||||
*
|
||||
* Direct questions, comments to <ipandtcp@gmail.com>
|
||||
*/
|
||||
|
||||
package godingtalk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
topAPICreateProcInstMethod = "dingtalk.smartwork.bpms.processinstance.create"
|
||||
topAPIGetProcInstMethod = "dingtalk.smartwork.bpms.processinstance.get"
|
||||
topAPIListProcInstMethod = "dingtalk.smartwork.bpms.processinstance.list"
|
||||
)
|
||||
|
||||
type TopAPICreateProcInst struct {
|
||||
// 审批模板code
|
||||
ProcessCode string `json:"process_code"`
|
||||
// 发起人UID
|
||||
OriginatorUID string `json:"originator_user_id"`
|
||||
// 发起人所在部门
|
||||
DeptID int `json:"dept_id"`
|
||||
// 审批人列表
|
||||
Approvers []string `json:"approvers"`
|
||||
// 抄送人列表
|
||||
CCList []string `json:"cc_list"`
|
||||
//抄送时间,分为(START,FINISH,START_FINISH
|
||||
CCPosition string `json:"cc_position"`
|
||||
// 审批单内容, Name为审批模板中的列名, value 为该列的值
|
||||
FormCompntValues []ProcInstCompntValues `json:"form_component_values"`
|
||||
}
|
||||
|
||||
type ProcInst struct {
|
||||
ProcInstID string `json:"process_instance_id"`
|
||||
Title string `json:"title"`
|
||||
CreateTime string `json:"create_time"`
|
||||
FinishTime string `json:"finish_time"`
|
||||
OriginatorUID string `json:"originator_userid"`
|
||||
Status string `json:"status"`
|
||||
ApproverUIDS []string `json:"approver_userids"`
|
||||
CCUIDS []string `json:"cc_userids"`
|
||||
Result string `json:"result"`
|
||||
BusinessID string `json:"business_id"`
|
||||
FormCompntValues []ProcInstCompntValues `json:"form_component_values"` // 表单详情列表
|
||||
Tasks []_ProcInstTasks `json:"tasks"` // 任务列表
|
||||
OperationRecords []_ProcInstOperationRecords `json:"operation_records"` // 操作记录列表
|
||||
OriginatorDeptID string `json:"originator_dept_id"`
|
||||
OriginatorDeptName string `json:"originator_dept_name"`
|
||||
}
|
||||
|
||||
type ProcInstCompntValues struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
ExtValue string `json:"ext_value"`
|
||||
}
|
||||
|
||||
type _ProcInstOperationRecords struct {
|
||||
UID string `json:"userid"`
|
||||
Date string `json:"date"`
|
||||
Type string `json:"operation_type"`
|
||||
Result string `json:"operation_result"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type _ProcInstTasks struct {
|
||||
UID string `json:"userid"`
|
||||
Status string `json:"task_status"`
|
||||
Result string `json:"task_result"`
|
||||
CreateTime string `json:"create_time"`
|
||||
FinishTime string `json:"finish_time"`
|
||||
}
|
||||
|
||||
type topAPICreateProcInstResp struct {
|
||||
topAPIErrResponse
|
||||
OK struct {
|
||||
Errcode int `json:"ding_open_errcode"`
|
||||
ErrMsg string `json:"error_msg"`
|
||||
IsSuccess bool `jons:"is_success"`
|
||||
ProcInstID string `json:"process_instance_id"`
|
||||
} `json:"result"`
|
||||
RequestID string `json:"request_id"`
|
||||
}
|
||||
|
||||
// 发起审批
|
||||
func (c *DingTalkClient) TopAPICreateProcInst(data TopAPICreateProcInst) (string, error) {
|
||||
var resp topAPICreateProcInstResp
|
||||
values, err := json.Marshal(data.FormCompntValues)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("method", topAPICreateProcInstMethod)
|
||||
form.Add("cc_list", strings.Join(data.CCList, ","))
|
||||
form.Add("dept_id", strconv.Itoa(data.DeptID))
|
||||
form.Add("approvers", strings.Join(data.Approvers, ","))
|
||||
form.Add("cc_position", data.CCPosition)
|
||||
form.Add("process_code", data.ProcessCode)
|
||||
form.Add("originator_user_id", data.OriginatorUID)
|
||||
form.Add("form_component_values", string(values))
|
||||
if c.AgentID != "" {
|
||||
form.Add("agent_id", c.AgentID)
|
||||
}
|
||||
|
||||
return resp.OK.ProcInstID, c.topAPIRequest(form, &resp)
|
||||
}
|
||||
|
||||
type topAPIGetProcInstResp struct {
|
||||
Ok struct {
|
||||
ErrCode int `json:"ding_open_errcode"`
|
||||
ErrMsg string `json:"error_msg"`
|
||||
Success bool `json:"success"`
|
||||
ProcInst ProcInst `json:"process_instance"`
|
||||
} `json:"result"`
|
||||
RequestID string `json:"request_id"`
|
||||
topAPIErrResponse
|
||||
}
|
||||
|
||||
// 根据审批实例id获取单条审批实例详情
|
||||
func (c *DingTalkClient) TopAPIGetProcInst(pid string) (ProcInst, error) {
|
||||
var resp topAPIGetProcInstResp
|
||||
reqForm := url.Values{}
|
||||
reqForm.Add("process_instance_id", pid)
|
||||
reqForm.Add("method", topAPIGetProcInstMethod)
|
||||
err := c.topAPIRequest(reqForm, &resp)
|
||||
if err != nil {
|
||||
return resp.Ok.ProcInst, err
|
||||
}
|
||||
resp.Ok.ProcInst.ProcInstID = pid
|
||||
return resp.Ok.ProcInst, err
|
||||
}
|
||||
|
||||
type ListProcInst struct {
|
||||
ApproverUIDS []string `json:"approver_userid_list"`
|
||||
CCUIDS []string `json:"cc_userid_list"`
|
||||
FormCompntValues []ProcInstCompntValues `json:"form_component_values"`
|
||||
ProcInstID string `json:"process_instance_id"`
|
||||
Title string `json:"title"`
|
||||
CreateTime string `json:"create_time"`
|
||||
FinishTime string `json:"finish_time"`
|
||||
OriginatorUID string `json:"originator_userid"`
|
||||
Status string `json:"status"`
|
||||
BusinessID string `json:"business_id"`
|
||||
OriginatorDeptID string `json:"originator_dept_id"`
|
||||
ProcInstResult string `json:"process_instance_result"` // "agree",
|
||||
}
|
||||
|
||||
type TopAPIListProcInstResp struct {
|
||||
topAPIErrResponse
|
||||
OK struct {
|
||||
ErrCode int `json:"ding_open_errcode"`
|
||||
ErrMsg string `json:"error_msg"`
|
||||
Success bool `json:"success"`
|
||||
Result struct {
|
||||
List []ListProcInst `json:"list"`
|
||||
NextCursor int `json:"next_cursor"`
|
||||
} `json:"result"`
|
||||
} `json:"result"`
|
||||
RequestID string `json:"request_id"`
|
||||
}
|
||||
|
||||
// 获取审批实例列表
|
||||
// Note: processCode 官方不会检查错误,请保证processCode正确
|
||||
func (c *DingTalkClient) TopAPIListProcInst(processCode string, startTime, endTime time.Time, size, cursor int, useridList []string) (TopAPIListProcInstResp, error) {
|
||||
var resp TopAPIListProcInstResp
|
||||
if size > 10 {
|
||||
return resp, errors.New("Max size is 10")
|
||||
}
|
||||
|
||||
reqForm := url.Values{}
|
||||
reqForm.Add("process_code", processCode)
|
||||
reqForm.Add("start_time", strconv.FormatInt(startTime.UnixNano()/int64(time.Millisecond), 10))
|
||||
reqForm.Add("end_time", strconv.FormatInt(endTime.UnixNano()/int64(time.Millisecond), 10))
|
||||
reqForm.Add("size", strconv.Itoa(size))
|
||||
reqForm.Add("cursor", strconv.Itoa(cursor))
|
||||
reqForm.Add("userid_list", strings.Join(useridList, ","))
|
||||
reqForm.Add("method", topAPIListProcInstMethod)
|
||||
return resp, c.topAPIRequest(reqForm, &resp)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTopAPIProcInst(t *testing.T) {
|
||||
var compntValues []ProcInstCompntValues
|
||||
compntValues = append(compntValues, ProcInstCompntValues{Name: "单行输入框", Value: "单行输入框输入的内容"})
|
||||
|
||||
detailCompntValues := [][]ProcInstCompntValues{[]ProcInstCompntValues{ProcInstCompntValues{Name: "明细内单行输入框", Value: "明细内单行输入框的内容"}}}
|
||||
detailValues, _ := json.Marshal(detailCompntValues)
|
||||
compntValues = append(compntValues, ProcInstCompntValues{Name: "明细1", Value: string(detailValues)})
|
||||
|
||||
procInstData := TopAPICreateProcInst{
|
||||
Approvers: []string{"085354234826136236"},
|
||||
CCList: []string{"085354234826136236"},
|
||||
CCPosition: "START",
|
||||
DeptID: 4207088,
|
||||
OriginatorUID: "085354234826136236",
|
||||
ProcessCode: "PROC-FF6YHQ9WQ2-RWDT8XCUTV0U5IAT7JBM1-8MD0TNEJ-6",
|
||||
FormCompntValues: compntValues,
|
||||
}
|
||||
procInstID, err := c.TopAPICreateProcInst(procInstData)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Logf("%+v\n", procInstID)
|
||||
}
|
||||
|
||||
procInst, err := c.TopAPIGetProcInst(procInstID)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Logf("%+v\n", procInst)
|
||||
}
|
||||
|
||||
listResp, err := c.TopAPIListProcInst("PROC-FF6YHQ9WQ2-RWDT8XCUTV0U5IAT7JBM1-8MD0TNEJ-6", time.Now().AddDate(0, 0, -10), time.Now(), 10, 0, nil)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Logf("%+v\n", listResp)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Author Kevin Zhu
|
||||
*
|
||||
* Direct questions, comments to <ipandtcp@gmail.com>
|
||||
*/
|
||||
|
||||
package godingtalk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
topAPIMsgAsyncSendMethod = "dingtalk.corp.message.corpconversation.asyncsend"
|
||||
topAPIMsgGetResultMethod = "dingtalk.corp.message.corpconversation.getsendresult"
|
||||
topAPIMsgGetprogressMethod = "dingtalk.corp.message.corpconversation.getsendprogress"
|
||||
)
|
||||
|
||||
type topAPIMsgSendResponse struct {
|
||||
topAPIErrResponse
|
||||
OK struct {
|
||||
ErrCode int `json:"ding_open_errcode"`
|
||||
ErrMsg string `json:"error_msg"`
|
||||
Success bool `json:"success"`
|
||||
TaskID int `json:"task_id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// mgType 消息类型:text;iamge;voice;file;link;oa;markdown;action_card
|
||||
// userList 接收推送的UID 列表
|
||||
// deptList 接收推送的部门ID列表
|
||||
// toAll 是否发送给所有用户
|
||||
// msgContent 消息内容
|
||||
// If success return task_id, or is error is not nil when errored
|
||||
func (c *DingTalkClient) TopAPIMsgSend(msgType string, userList []string, deptList []int, toAll bool, msgContent interface{}) (int, error) {
|
||||
var resp topAPIMsgSendResponse
|
||||
if len(userList) > 20 || len(deptList) > 20 {
|
||||
return 0, errors.New("Can't more than 20 users or departments at once")
|
||||
}
|
||||
|
||||
mcontent, err := json.Marshal(msgContent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
toAllStr := "false"
|
||||
if toAll {
|
||||
toAllStr = "true"
|
||||
}
|
||||
|
||||
form := url.Values{
|
||||
"method": {topAPIMsgAsyncSendMethod},
|
||||
"agent_id": {c.AgentID},
|
||||
"userid_list": {strings.Join(userList, ",")},
|
||||
"to_all_user": {toAllStr},
|
||||
"msgtype": {msgType},
|
||||
"msgcontent": {string(mcontent)},
|
||||
}
|
||||
|
||||
if len(deptList) > 0 {
|
||||
var deptListStr string
|
||||
for _, dept := range deptList {
|
||||
deptListStr = strconv.Itoa(dept) + ","
|
||||
}
|
||||
deptListStr = string([]uint8(deptListStr)[0 : len(deptListStr)-1])
|
||||
form.Set("dept_id_list", deptListStr)
|
||||
}
|
||||
|
||||
return resp.OK.TaskID, c.topAPIRequest(form, &resp)
|
||||
}
|
||||
|
||||
type TopAPIMsgGetSendResult struct {
|
||||
topAPIErrResponse
|
||||
OK struct {
|
||||
ErrCode int `json:"ding_open_errcode"`
|
||||
ErrMsg string `json:"error_msg"`
|
||||
Success bool `json:"success"`
|
||||
SendResult struct {
|
||||
InvalidUserIDList []string `json:"invalid_user_id_list"`
|
||||
ForbiddenUserIDList []string `json:"forbidden_user_id_list"`
|
||||
FaildedUserIDList []string `json:"failed_user_id_list"`
|
||||
ReadUserIDLIst []string `json:"read_user_id_list"`
|
||||
UnreadUserIDList []string `json:"unread_user_id_list"`
|
||||
InvalidDeptIDList []int `json:"invalid_dept_id_list"`
|
||||
} `json:"send_result"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) TopAPIMsgGetSendResult(taskID int) (TopAPIMsgGetSendResult, error) {
|
||||
var resp TopAPIMsgGetSendResult
|
||||
form := url.Values{
|
||||
"method": {topAPIMsgGetResultMethod},
|
||||
"agent_id": {c.AgentID},
|
||||
"task_id": {strconv.Itoa(taskID)},
|
||||
}
|
||||
return resp, c.topAPIRequest(form, &resp)
|
||||
}
|
||||
|
||||
type TopAPIMsgGetSendProgress struct {
|
||||
topAPIErrResponse
|
||||
OK struct {
|
||||
ErrCode int `json:"ding_open_errcode"`
|
||||
ErrMsg string `json:"error_msg"`
|
||||
Success bool `json:"success"`
|
||||
Progress struct {
|
||||
Percent int `json:"progress_in_percent"`
|
||||
Status int `json:"status"`
|
||||
} `json:"progress"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) TopAPIMsgGetSendProgress(taskID int) (TopAPIMsgGetSendProgress, error) {
|
||||
var resp TopAPIMsgGetSendProgress
|
||||
form := url.Values{
|
||||
"method": {topAPIMsgGetprogressMethod},
|
||||
"agent_id": {c.AgentID},
|
||||
"task_id": {strconv.Itoa(taskID)},
|
||||
}
|
||||
return resp, c.topAPIRequest(form, &resp)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTopAPImsg(t *testing.T) {
|
||||
c.AgentID = "161271936"
|
||||
msg := OAMessage{}
|
||||
msg.URL = "http://www.google.com/"
|
||||
msg.Head.Text = "头部标题"
|
||||
msg.Head.BgColor = "FFBBBBBB"
|
||||
msg.Body.Title = "正文标题"
|
||||
msg.Body.Content = "test content"
|
||||
taskID, err := c.TopAPIMsgSend("oa", []string{"085354234826136236"}, nil, false, msg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%d\n", taskID)
|
||||
}
|
||||
|
||||
sendProgress, err := c.TopAPIMsgGetSendProgress(taskID)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%v\n", sendProgress.OK.Progress)
|
||||
}
|
||||
|
||||
sendResult, err := c.TopAPIMsgGetSendResult(taskID)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%v\n", sendResult.OK.SendResult)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Author Kevin Zhu
|
||||
*
|
||||
* Direct questions, comments to <ipandtcp@gmail.com>
|
||||
*/
|
||||
|
||||
package godingtalk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
topAPIRootURL = "https://eco.taobao.com/router/rest"
|
||||
formDataType = "application/x-www-form-urlencoded;charset=utf-8"
|
||||
)
|
||||
|
||||
type TopAPIResponse interface {
|
||||
checkError() error
|
||||
}
|
||||
|
||||
type topAPIErrResponse struct {
|
||||
ERR struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
SubCode string `json:"sub_code"`
|
||||
SubMsg string `json:"sub_msg"`
|
||||
RequestID string `json:"request_id"`
|
||||
} `json:"error_response"`
|
||||
}
|
||||
|
||||
func (data *topAPIErrResponse) checkError() (err error) {
|
||||
if data.ERR.Code != 0 || len(data.ERR.SubCode) != 0 {
|
||||
err = fmt.Errorf("%#v", data.ERR)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) topAPIRequest(requestForm url.Values, respData TopAPIResponse) error {
|
||||
requestForm.Set("v", "2.0")
|
||||
requestForm.Set("format", "json")
|
||||
requestForm.Set("simplify", "true")
|
||||
|
||||
err := c.RefreshAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestForm.Set("session", c.AccessToken)
|
||||
if requestForm.Get("timestamp") == "" {
|
||||
requestForm.Set("timestamp", time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
if c.PartnerID != "" {
|
||||
requestForm.Set("partner_id", c.PartnerID)
|
||||
}
|
||||
|
||||
v := bytes.NewBuffer([]byte(requestForm.Encode()))
|
||||
|
||||
req, _ := http.NewRequest("POST", topAPIRootURL, v)
|
||||
req.Header.Set("Content-Type", formDataType)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("Server error: " + resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err == nil {
|
||||
err := json.Unmarshal(buf, &respData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return respData.checkError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
const typeJSON = "application/json"
|
||||
|
||||
//UploadFile is for uploading a single file to DingTalk
|
||||
type UploadFile struct {
|
||||
FieldName string
|
||||
FileName string
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
//DownloadFile is for downloading a single file from DingTalk
|
||||
type DownloadFile struct {
|
||||
MediaID string
|
||||
FileName string
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) httpRPC(path string, params url.Values, requestData interface{}, responseData Unmarshallable) error {
|
||||
if c.AccessToken != "" {
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
if params.Get("access_token") == "" {
|
||||
params.Set("access_token", c.AccessToken)
|
||||
}
|
||||
}
|
||||
return c.httpRequest(path, params, requestData, responseData)
|
||||
}
|
||||
|
||||
func (c *DingTalkClient) httpRequest(path string, params url.Values, requestData interface{}, responseData Unmarshallable) error {
|
||||
client := c.HTTPClient
|
||||
var request *http.Request
|
||||
ROOT := os.Getenv("oapi_server")
|
||||
if ROOT == "" {
|
||||
ROOT = "oapi.dingtalk.com"
|
||||
}
|
||||
DEBUG := os.Getenv("debug") != ""
|
||||
url2 := "https://" + ROOT + "/" + path + "?" + params.Encode()
|
||||
// log.Println(url2)
|
||||
if requestData != nil {
|
||||
switch requestData.(type) {
|
||||
case UploadFile:
|
||||
var b bytes.Buffer
|
||||
w := multipart.NewWriter(&b)
|
||||
|
||||
uploadFile := requestData.(UploadFile)
|
||||
if uploadFile.Reader == nil {
|
||||
return errors.New("upload file is empty")
|
||||
}
|
||||
fw, err := w.CreateFormFile(uploadFile.FieldName, uploadFile.FileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(fw, uploadFile.Reader); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
request, _ = http.NewRequest("POST", url2, &b)
|
||||
request.Header.Set("Content-Type", w.FormDataContentType())
|
||||
default:
|
||||
d, _ := json.Marshal(requestData)
|
||||
if DEBUG {
|
||||
log.Printf("url: %s request: %s", url2, string(d))
|
||||
}
|
||||
request, _ = http.NewRequest("POST", url2, bytes.NewReader(d))
|
||||
request.Header.Set("Content-Type", typeJSON)
|
||||
}
|
||||
} else {
|
||||
if DEBUG {
|
||||
log.Printf("url: %s", url2)
|
||||
}
|
||||
request, _ = http.NewRequest("GET", url2, nil)
|
||||
}
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("Server error: " + resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if DEBUG {
|
||||
log.Printf("url: %s response content type: %s", url2, contentType)
|
||||
}
|
||||
pos := len(typeJSON)
|
||||
if len(contentType) >= pos && contentType[0:pos] == typeJSON {
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if DEBUG {
|
||||
log.Println(string(content))
|
||||
}
|
||||
if err == nil {
|
||||
json.Unmarshal(content, responseData)
|
||||
return responseData.checkError()
|
||||
}
|
||||
} else {
|
||||
io.Copy(responseData.getWriter(), resp.Body)
|
||||
return responseData.checkError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package godingtalk
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Expirable interface {
|
||||
CreatedAt() int64
|
||||
ExpiresIn() int
|
||||
}
|
||||
|
||||
type Cache interface {
|
||||
Set(data Expirable) error
|
||||
Get(data Expirable) error
|
||||
}
|
||||
|
||||
type FileCache struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func NewFileCache(path string) *FileCache {
|
||||
return &FileCache{
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FileCache) Set(data Expirable) error {
|
||||
bytes, err := json.Marshal(data)
|
||||
if err == nil {
|
||||
ioutil.WriteFile(c.Path, bytes, 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *FileCache) Get(data Expirable) error {
|
||||
bytes, err := ioutil.ReadFile(c.Path)
|
||||
if err == nil {
|
||||
err = json.Unmarshal(bytes, data)
|
||||
if err == nil {
|
||||
created := data.CreatedAt()
|
||||
expires := data.ExpiresIn()
|
||||
if err == nil && time.Now().Unix() > created+int64(expires-60) {
|
||||
err = errors.New("Data is already expired")
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type InMemoryCache struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func NewInMemoryCache() *InMemoryCache {
|
||||
return &InMemoryCache{}
|
||||
}
|
||||
|
||||
func (c *InMemoryCache) Set(data Expirable) error {
|
||||
bytes, err := json.Marshal(data)
|
||||
if err == nil {
|
||||
c.data = bytes
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *InMemoryCache) Get(data Expirable) error {
|
||||
err := json.Unmarshal(c.data, data)
|
||||
if err == nil {
|
||||
created := data.CreatedAt()
|
||||
expires := data.ExpiresIn()
|
||||
if err == nil && time.Now().Unix() > created+int64(expires-60) {
|
||||
err = errors.New("Data is already expired")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func sha1Sign(s string) string {
|
||||
// The pattern for generating a hash is `sha1.New()`,
|
||||
// `sha1.Write(bytes)`, then `sha1.Sum([]byte{})`.
|
||||
// Here we start with a new hash.
|
||||
h := sha1.New()
|
||||
|
||||
// `Write` expects bytes. If you have a string `s`,
|
||||
// use `[]byte(s)` to coerce it to bytes.
|
||||
h.Write([]byte(s))
|
||||
|
||||
// This gets the finalized hash result as a byte
|
||||
// slice. The argument to `Sum` can be used to append
|
||||
// to an existing byte slice: it usually isn't needed.
|
||||
bs := h.Sum(nil)
|
||||
|
||||
// SHA1 values are often printed in hex, for example
|
||||
// in git commits. Use the `%x` format verb to convert
|
||||
// a hash results to a hex string.
|
||||
return fmt.Sprintf("%x", bs)
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package godingtalk
|
||||
|
||||
import "testing"
|
||||
import "time"
|
||||
|
||||
type ExpiresData struct {
|
||||
Data string
|
||||
Expires int `json:"expires_in"`
|
||||
Created int64 `json:"created"`
|
||||
}
|
||||
|
||||
func (e *ExpiresData) CreatedAt() int64 {
|
||||
return e.Created
|
||||
}
|
||||
|
||||
func (e *ExpiresData) ExpiresIn() int {
|
||||
return e.Expires
|
||||
}
|
||||
|
||||
func TestFileCache(t *testing.T) {
|
||||
cache := NewFileCache(".test_cache")
|
||||
data := ExpiresData{
|
||||
Data: "Hello World!",
|
||||
Expires: 7200,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
cache.Set(&data)
|
||||
|
||||
var data2 ExpiresData
|
||||
cache.Get(&data2)
|
||||
t.Logf("%+v %+v", data, data2)
|
||||
|
||||
if data2.Created != data.Created {
|
||||
t.Errorf("FileCache error")
|
||||
}
|
||||
|
||||
data = ExpiresData{
|
||||
Data: "Hello World 2!",
|
||||
Expires: 0,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
cache.Set(&data)
|
||||
err := cache.Get(&data2)
|
||||
if err == nil {
|
||||
t.Error("FileCache error: err should not be nil")
|
||||
}
|
||||
t.Logf("%+v %+v", data, data2)
|
||||
}
|
||||
|
||||
func TestInMemoryCache(t *testing.T) {
|
||||
cache := NewInMemoryCache()
|
||||
data := ExpiresData{
|
||||
Data: "Hello World!",
|
||||
Expires: 7200,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
cache.Set(&data)
|
||||
|
||||
var data2 ExpiresData
|
||||
cache.Get(&data2)
|
||||
t.Logf("%+v %+v", data, data2)
|
||||
|
||||
if data2.Created != data.Created {
|
||||
t.Errorf("InMemoryCache error")
|
||||
}
|
||||
|
||||
data = ExpiresData{
|
||||
Data: "Hello World 2!",
|
||||
Expires: 0,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
cache.Set(&data)
|
||||
err := cache.Get(&data2)
|
||||
if err == nil {
|
||||
t.Error("InMemoryCache error: err should not be nil")
|
||||
}
|
||||
t.Logf("%+v %+v", data, data2)
|
||||
}
|
||||
Loading…
Reference in New Issue