作者 tangxuhui

添加 金蝶k3cloud接口调用工具包

... ... @@ -11,6 +11,7 @@ require (
github.com/go-pg/pg/v10 v10.9.0
github.com/go-redis/redis v6.15.7+incompatible
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/linmadan/egglib-go v0.0.0-20210313060205-8b5e456b11f7
github.com/moul/http2curl v1.0.0 // indirect
... ... @@ -19,6 +20,7 @@ require (
github.com/sergi/go-diff v1.2.0 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.13.0 // indirect
github.com/valyala/fasthttp v1.32.0 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
... ...
... ... @@ -162,6 +162,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
... ... @@ -351,6 +353,12 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/tal-tech/go-zero v1.0.27 h1:QMIbaTxibMc/OsO5RTAuKZ8ndbl2dGN6pITQEtp2x/A=
github.com/tal-tech/go-zero v1.0.27/go.mod h1:JtNXlsh/CgeIHyQnt5C5M2IcSevW7V0NAnqO93TQgm8=
github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tiptok/egglib-go v0.0.0-20220120032512-24dfab2b4987 h1:0e2hOSL+//5AL7e1r3xCGEugsOPsw2POAm82VZvWLe4=
github.com/tiptok/egglib-go v0.0.0-20220120032512-24dfab2b4987/go.mod h1:xl9i83IKNUkwlobRF6XLKn1RRbZsT+7yhCicpTGWTKc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
... ...
package k3cloud
import (
"errors"
"io"
)
//初始化默认的客户端
type Client struct {
HttpClient
}
/*NewClient 新建客户端,
默认执行登录操作,填充cookie
*/
func NewClient(apihost string, acctID string, username string, password string, lcid ...int) (*Client, error) {
client := &Client{
HttpClient{
HostUrl: apihost,
},
}
_, err := client.Login(acctID, username, password, lcid...)
if err != nil {
return nil, err
}
return client, nil
}
/*
Login 登陆验证接口
acctID 账套Id,
username 密码
password 用户登陆名
lcid 语言id,选择哪种语言访问,参考:中文2052,英文1033,繁体307
*/
func (c *Client) Login(acctID string, username string, password string, lcid ...int) (*ReponseValidateUser, error) {
api := "/Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc"
lcidTemp := 2052
if len(lcid) > 0 {
lcidTemp = lcid[0]
}
param := []interface{}{acctID, username, password, lcidTemp}
resp, err := c.PostRequest(api, param)
if err != nil {
return nil, err
}
defer resp.Body.Close()
cookies := resp.Cookies()
//填充调用接口时需要的cookie
c.HttpClient.Cookie = cookies
var loginReturn ReponseValidateUser
err = c.DecodeJSON(resp.Body, &loginReturn)
if err != nil {
return nil, err
}
if loginReturn.LoginResultType != 1 {
return &loginReturn, errors.New(loginReturn.Message)
}
return &loginReturn, nil
}
// ExecuteBillQuery 单据查询
func (c *Client) ExecuteBillQuery(param RequestExecuteBillQuery) (*QueryResult, error) {
api := "/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc"
paramFomat := []interface{}{param}
resp, err := c.PostRequest(api, paramFomat)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var dataByte []byte
dataByte, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
queryResult := newQueryResult(dataByte, param.FieldKeys)
return queryResult, queryResult.Error()
}
... ...
package k3cloud
import (
"testing"
)
var (
acctID = "20211118121754866"
// username = "Administrator"
// password = "stxcs@888"
username = "18559023318"
password = "stx@123456"
hostUrl = "https://tianlian.test.ik3cloud.com/k3cloud"
)
func TestLogin(t *testing.T) {
client := Client{HttpClient{HostUrl: hostUrl}}
result, err := client.Login(acctID, username, password)
if err != nil {
t.Error(err)
}
t.Logf("%+v \n", *result)
}
func TestExecuteBillQuery(t *testing.T) {
client, err := NewClient(hostUrl, acctID, username, password)
if err != nil {
t.Error(err)
return
}
//参见 物料元数据
// _ = [][2]string{{"FMATERIALID", "表主键"}, {"FDocumentStatus", "数据状态"}, {"FForbidStatus", "禁用状态"}, {"FName", "名称"}, {"FNumber", "编码"}, {"FDescription", "描述"},
// {"FCreateDate", "创建日期"}, {"FModifyDate", "修改日期"}, {"FMnemonicCode", "助记码"}, {"FSpecification", "规格型号"}, {"FForbidDate", "禁用日期"},
// {"FApproveDate", "审核日期"}, {"FOldNumber", "旧物料编码"}, {"FMaterialGroup", "物料分组"}, {"FPLMMaterialId", "PLM物料内码"}, {"FMaterialSRC", "物料来源"},
// {"FIsSalseByNet", "是否网销"}, {"FIsAutoAllocate", "自动分配"}, {"FSPUID", "SPU信息"}, {"FPinYin", "拼音"}, {"FDSMatchByLot", "按批号匹配供需"},
// {"FForbidReson", "禁用原因"}, {"FRefStatus", "已使用"}}
result, err := client.ExecuteBillQuery(RequestExecuteBillQuery{
FormId: "BD_MATERIAL", //物料
FieldKeys: "FMATERIALID,FName,FNumber,FUseOrgId,FModifyDate,FCreateDate,FApproveDate", //查询的字段
TopRowCount: 5,
})
t.Logf("result buf===> %s \n", string(result.Buf))
if err != nil {
t.Error(err)
return
}
t.Logf("tomap===>%v \n", result.ToMapString())
}
... ...
package k3cloud
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/google/uuid"
)
//全局使用的http请求
type HttpClient struct {
HostUrl string
Cookie []*http.Cookie
}
//WrapPostData 包装发送的数据
func (hClient *HttpClient) WrapPostData(param interface{}) (*postData, error) {
data := postData{
Format: 1,
Useragent: "ApiClient",
Rid: uuid.NewString(),
Timestamp: time.Now().Format("2006-01-02"),
V: "1.0",
Parameters: param,
}
return &data, nil
}
func (hClient *HttpClient) PostRequest(api string, param interface{}) (*http.Response, error) {
data, err := hClient.WrapPostData(param)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s%s", hClient.HostUrl, api)
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
err = encoder.Encode(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, url, &buf)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
for i := range hClient.Cookie {
req.AddCookie(hClient.Cookie[i])
}
client := http.Client{
Timeout: 60 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, err
}
func (hClient *HttpClient) DecodeJSON(r io.Reader, v interface{}) error {
defer io.Copy(io.Discard, r)
return json.NewDecoder(r).Decode(v)
}
func (hClient *HttpClient) InvokeApi(api string, param interface{}, respData interface{}) error {
resp, err := hClient.PostRequest(api, param)
if err != nil {
return err
}
err = hClient.DecodeJSON(resp.Body, respData)
return err
}
... ...
package k3cloud
import (
"encoding/json"
"log"
"testing"
"github.com/tidwall/gjson"
)
func TestDecodeJson1(t *testing.T) {
//
str := `[[{"Result":{"ResponseStatus":{"ErrorCode":500,"IsSuccess":false,"Errors":[{"FieldName":null,"Message":"元数据中标识为FUseOrg的字段不存在","DIndex":0}],"SuccessEntitys":[],"SuccessMessages":[],"MsgCode":9}}}]]`
jResult := gjson.Parse(str)
arr1 := jResult.Array()
if len(arr1) == 0 {
return
}
arr2 := arr1[0].Array()
if len(arr2) == 0 {
return
}
var errResult QueryError
if !arr2[0].IsObject() {
return
}
rw := arr2[0].Raw
err := json.Unmarshal([]byte(rw), &errResult)
if err != nil {
return
}
log.Println(errResult)
}
func TestDecodeJson2(t *testing.T) {
str := `[["xxx","abc",2345]]`
jResult := gjson.Parse(str)
jResult.ForEach(func(key, value1 gjson.Result) bool {
value1.ForEach(func(key, value2 gjson.Result) bool {
log.Println(key.Int(), value2.String())
return true
})
return true
})
}
... ...
package k3cloud
import (
"encoding/json"
"strings"
"github.com/tidwall/gjson"
)
//QueryError 单据查询错误信息
type QueryError struct {
Result struct {
ResponseStatus struct {
ErrorCode int `json:"ErrorCode"`
IsSuccess bool `json:"IsSuccess"`
Errors []struct {
FieldName interface{} `json:"FieldName"`
Message string `json:"Message"`
DIndex int `json:"DIndex"`
} `json:"Errors"`
} `json:"ResponseStatus"`
} `json:"Result"`
}
func (qe QueryError) Error() string {
str := ""
for _, errMsg := range qe.Result.ResponseStatus.Errors {
str += errMsg.Message + ";"
}
return str
}
/*QueryResult 单据查询结果
当接口调用失败时得到的json 结构对应[][]QueryError,如:
[[{"Result":{"ResponseStatus":{"Errors":[{"Message":"元数据中标识为FUseOrg的字段不存在"}]}}}]]
当接口调用成功的得到的json结构对应[][]interface{},如:
[["xxx","abc",2345]]
*/
type QueryResult struct {
FieldKeys []string //对应的键名 ,注意数据的顺序
Buf []byte //原始的数据byte
}
func newQueryResult(buf []byte, keys string) *QueryResult {
return &QueryResult{
Buf: buf,
FieldKeys: strings.Split(keys, ","),
}
}
func (result *QueryResult) ToMapString() []map[string]string {
if result.IsError() {
return nil
}
jResult := gjson.ParseBytes(result.Buf)
mapResult := []map[string]string{}
var mapTemp map[string]string
for _, arr1 := range jResult.Array() {
mapTemp = make(map[string]string)
for index2, item := range arr1.Array() {
if index2 > len(result.FieldKeys) {
continue
}
keyName := result.FieldKeys[index2]
mapTemp[keyName] = item.String()
}
mapResult = append(mapResult, mapTemp)
}
return mapResult
}
func (result *QueryResult) ToMap() []map[string]interface{} {
if result.IsError() {
return nil
}
jResult := gjson.ParseBytes(result.Buf)
mapResult := []map[string]interface{}{}
var mapTemp map[string]interface{}
for _, arr1 := range jResult.Array() {
mapTemp = make(map[string]interface{})
for index2, item := range arr1.Array() {
if index2 > len(result.FieldKeys) {
continue
}
keyName := result.FieldKeys[index2]
mapTemp[keyName] = item.Value()
}
mapResult = append(mapResult, mapTemp)
}
return mapResult
}
func (result *QueryResult) Error() error {
var (
errMsg QueryError
err error
)
jResult := gjson.ParseBytes(result.Buf)
for _, arr1 := range jResult.Array() {
for _, item := range arr1.Array() {
if !item.IsObject() {
return nil
}
err = json.Unmarshal([]byte(item.Raw), &errMsg)
if err != nil {
return err
}
}
}
return errMsg
}
//TODO 将结果解析为结构体
// func (result *QueryResult) ToStruct(v interface{}) error {
// return nil
// }
func (result *QueryResult) IsError() bool {
jResult := gjson.ParseBytes(result.Buf)
for _, arr1 := range jResult.Array() {
for _, item := range arr1.Array() {
if item.IsObject() {
return true
}
}
}
return false
}
... ...
package k3cloud
//postData 请求接口发送的数据结构
type postData struct {
Format int `json:"format"`
Useragent string `json:"useragent"`
Rid string `json:"rid"`
Parameters interface{} `json:"parameters"`
Timestamp string `json:"timestamp"`
V string `json:"v"`
}
//登录验证请求数据结构 /Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc
type (
// RequestValidateUser struct {
// AcctID string `json:"acctID"` //账套Id,
// Username string `json:"username"` //密码
// Password string `json:"password"` //用户登陆名
// Lcid int `json:"lcid"` //语言id,选择哪种语言访问,参考:中文2052,英文1033,繁体307
// }
ReponseValidateUser struct {
/*
云通行证未绑定Cloud账号 EntryCloudUnBind = -6
需要表单处理 DealWithForm = -5
登录警告 Wanning = -4
密码验证不通过(强制的) PWInvalid_Required = -3
密码验证不通过(可选的) PWInvalid_Optional = -2
登录失败 Failure = -1
用户或密码错误 PWError = 0,
登录成功 Success = 1
*/
Message string `json:"Message"`
MessageCode string `json:"MessageCode"`
LoginResultType int `json:"LoginResultType"`
//...其他
}
)
//表单数据查询 /K3Cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc
type (
RequestExecuteBillQuery struct {
FormId string `json:"FormId"` //必录,查询表单元数据唯一标识
FieldKeys string `json:"FieldKeys"` //必录,待查询表单的字段列表
Limit int `json:"Limit"` //非必录,分页取数每页允许获取的数据,最大不能超过2000
StartRow int `json:"StartRow"` //非必录,分页取数开始行索引,从0开始,例如每页10行数据,第2页开始是10,第3页开始是20
FilterString string `json:"FilterString"` //非必录,过滤条件
OrderString string `json:"OrderString"` //非必录,排序条件
TopRowCount int `json:"TopRowCount"` //非必录,返回总行数
}
)
... ...