# 附录三:接入代码示例

# 调用勤策OpenAPI接口

# 前提条件

开始之前先联系企业专属客户经理申请企业OpenAPI调用授权信息openid和appkey,确认允许调用接口的授权范围和ip白名单等信息,确保账号可用。 表格中附件为调用勤策OpenAPI示例代码,包括java,python,C#,Go,NodeJS,php等语言。 openapi_tools-1.3.2.jaropenapi_tools-1.1.9.jar分别适配不同JDK版本。 demo-openapi.zip 为各种语言调用示例代码。

项目 名称 大小
JDK1.8 OpenAPI SDK openapi_tools-1.3.2.jar (opens new window) 43 KB
JDK1.7 OpenAPI SDK openapi_tools-1.1.9.jar (opens new window) 34 KB
JAVA开发工具包 demo-openapi.zip (opens new window) 1.61 MB

# 快速开始


// 引用JAVA开发工具包openapi_tools.jar
import com.alibaba.fastjson.JSONObject;
import com.waiqin365.openapi.tools.WQApiHandler;
import com.waiqin365.openapi.vo.WQOpenApi;
import com.waiqin365.openapi.vo.WQRequest;
import com.waiqin365.openapi.vo.WQResponse;
public class TestSample
{
	public static void main(String[] args)
	{
        // 企业所在数据中心服务地址
        String region = "https://openapi.qince.com";
        // 企业唯一授权令牌OPENID
        String openid = "847740919664405****";
        // 企业授权加密秘钥
        String appkey = "hgm*************";
        WQOpenApi wqOpenApi = new WQOpenApi(openid, appkey);
        wqOpenApi.setOpenurl(String.format("%s/api", region));
        WQRequest request = new WQRequest();
        request.setWqOpenApi(wqOpenApi);
        request.setModule("organization");
        request.setVersion("v1");
        request.setOperation("queryOrganization");
        JSONObject body = new JSONObject();
        request.setRequestdata(body.toJSONString());
        try
        {
            WQResponse res = WQApiHandler.handleOpenApi(request);
            log.info(request.getRequestUrlV2());
            log.info("return_code:" + res.getReturn_code());
            log.info("return_message:" + res.getReturn_msg());
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
      

/** 引用代码 demo-openapi.zip */
        String domain = "https://openapi.qince.com";
        String openid = "547416644250459****";
        String appkey = "ipA1CBM7RoXJb6****";
        Long msgId = java.util.UUID.randomUUID().getLeastSignificantBits() * -1;
        String timestamp = OpenAPIUtil.getTimestamp();
        String jsonData = "{}";
        String nonce = OpenAPIUtil.randomString();
        String digest = OpenAPIUtil.makeDigest(jsonData ,timestamp, nonce, appkey);
        String url = domain + "/api/organization/v1/queryOrganization/" + openid + "/" + timestamp + "/" + nonce + "/" + msgId;
        String resp = OpenAPIUtil.postJsonString(url, jsonData, digest);
        System.out.println("勤策响应结果:" + resp);
                  

# 引用代码demo-openapi.zip test_waiqin_openapi.py
domain = "https://openapi.qince.com"
openid = "547416644250459****"
appkey = "ipA1CBM7R********"
msg_id = uuid.uuid4().int & 0x7FFFFFFFFFFFFFFF  # least significant bits, positive
timestamp = get_timestamp()
json_data = "{}"
nonce = random_string()
digest = make_digest(json_data, timestamp, nonce, appkey)
url = f"{domain}/api/organization/v1/queryOrganization/{openid}/{timestamp}/{nonce}/{msg_id}"
resp = requests.post(url, data=json_data, headers={
    "Content-Type": "application/json; charset=UTF-8",
    "encrypt-version": "v2",
    "digest": digest
}, timeout=5)
print(f"勤策响应结果:{resp.text}")
      

/** 引用代码demo-openapi.zip test_waiqin_openapi.cs */
    static async Task Main(string[] args)
    {
        var msgId = Math.Abs(BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0));
        var timestamp = GetTimestamp();
        var jsonData = "{}";
        var nonce = RandomString();
        var digest = MakeDigest(jsonData, timestamp, nonce, Appkey);
        var url = $"{Domain}/api/organization/v1/queryOrganization/{Openid}/{timestamp}/{nonce}/{msgId}";
        using var client = new HttpClient();
        client.Timeout = TimeSpan.FromSeconds(5);
        var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
        var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
        request.Headers.Add("encrypt-version", "v2");
        request.Headers.Add("digest", digest);
        var response = await client.SendAsync(request);
        var resp = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"勤策响应结果:{resp}");
    }
      

// 引用代码demo-openapi.zip test_waiqin_openapi.go
const (
	domain    = "https://openapi.qince.com"
	openid    = "547416644250459****"
	appkey    = "ipA1CBM7R********"
	codeChars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz_0123456789"
)
func main() {
	msgId := uuid.New().ID()
	timestamp := getTimestamp()
	jsonData := "{}"
	nonce := randomString()
	digest := makeDigest(jsonData, timestamp, nonce, appkey)
	url := fmt.Sprintf("%s/api/organization/v1/queryOrganization/%s/%s/%s/%d", domain, openid, timestamp, nonce, msgId)
	req, _ := http.NewRequest("POST", url, strings.NewReader(jsonData))
	req.Header.Set("Content-Type", "application/json; charset=UTF-8")
	req.Header.Set("encrypt-version", "v2")
	req.Header.Set("digest", digest)
	client := &http.Client{Timeout: 5 * time.Second}
	resp, _ := client.Do(req)
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("勤策响应结果:%s\n", string(body))
}
      

/** 引用代码demo-openapi.zip test_waiqin_openapi.js */
const domain = "https://openapi.qince.com";
const openid = "547416644250459****";
const appkey = "ipA1CBM7R********";
const msgId = BigInt.asUintN(64, BigInt('0x' + uuidV4().replace(/-/g, '')));
const timestamp = getTimestamp();
const jsonData = "{}";
const nonce = randomString();
const digest = makeDigest(jsonData, timestamp, nonce, appkey);
const path = `/api/organization/v1/queryOrganization/${openid}/${timestamp}/${nonce}/${msgId}`;
const url = new URL(path, domain);
const options = {
    hostname: url.hostname,
    port: url.port,
    path: url.pathname,
    method: 'POST',
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json; charset=UTF-8',
        'encrypt-version': 'v2',
        'digest': digest,
        'Content-Length': Buffer.byteLength(jsonData)
    }
};
const req = http.request(options, (res) => {
    let body = '';
    res.on('data', (chunk) => body += chunk);
    res.on('end', () => {
        console.log(`勤策响应结果:${body}`);
    });
});
req.on('error', (e) => {
    console.error(`发送请求失败:${e.message}`);
});
req.write(jsonData);
req.end();
      

# 接收勤策推送

# 快速开始

项目 名称 大小
接收勤策推送示例 springboot-waiqin365.zip (opens new window) 112 KB
    1.将示例代码导入IDEA之后启动运行。
    2.在浏览器中输入,开始模拟请求数据:
    http://127.0.0.1:8080/waiqin365/mock
    3.在ProcessFactory中加入同步保存数据处理代码。建议接收到推送数据后保存入库完成即可同步返回,后面启动新线程ProcessSubThread处理业务逻辑

import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.security.MessageDigest;
@Controller
@RequestMapping(value = "/")
public class ReceiverController
{
	private static final Logger logger = LoggerFactory.getLogger(ReceiverController.class);
	public static String PUSH_SECRET = "xxxxxxx";
	@RequestMapping(value = "/receivce")
	@ResponseBody
	public JSONObject receivce(String msgId, String dataType, String dataId, String dataVersion, String dataFormat, String dataSource, String data, long timestamp,String digest, String status, @RequestParam(required = false) String statusTime, String tenantId)
	{
		logger.info("[NEW]===================================================");
		logger.info("msgId:      {}", msgId);
		logger.info("dataType:   {}", dataType);
		logger.info("dataId:     {}", dataId);
		logger.info("dataVersion:{}", dataVersion);
		logger.info("dataFormat: {}", dataFormat);
		logger.info("dataSource: {}", dataSource);
		logger.info("data:       {}", data);
		logger.info("timestamp:  {}", timestamp);
		logger.info("digest:     {}", digest);
		logger.info("status:     {}", status);
		logger.info("statusTime: {}", statusTime);
		logger.info("tenantId:   {}", tenantId);
		StringBuffer mdata = new StringBuffer();
mdata.append(data).append("|").append(PUSH_SECRET).append("|").append(timestamp);
		String ndigest = encodeMd5(mdata.toString());
		logger.info("Calcdigest: {}", ndigest);
		if(!ndigest.equals(digest))
		{
			JSONObject json = new JSONObject();
			json.put("return_code", "1");
			json.put("return_msg", "数据签名校验失败,请求不合法!");
			return json;
		}
		// TODO: add proccess code
		JSONObject json = new JSONObject();
		json.put("return_code", "0");
		return json;
	}
	public static synchronized String encodeMd5(String data)
	{
		MessageDigest digest = null;
		if (digest == null)
		{
			try
			{
				digest = MessageDigest.getInstance("MD5");
				digest.update(data.getBytes("UTF-8"));
				byte[] hash = digest.digest();
				StringBuffer buf = new StringBuffer(hash.length * 2);
				int i;
				for (i = 0; i < hash.length; i++)
				{
					if ((hash[i] & 0xff) < 0x10)
					{
						buf.append("0");
					}
					buf.append(Long.toString(hash[i] & 0xff, 16));
				}
				return buf.toString();
			}
			catch (Exception nsae)
			{
				System.err.println("Failed to load the MD5 MessageDigest. ");
			}
		}
		return null;
	}
}
      

const crypto = require('crypto');
const http = require('http');
const url = require('url');
const PUSH_SECRET = 'xxxxxx';
const PORT = 8080;
function md5(str) {
    return crypto.createHash('md5').update(str, 'utf8').digest('hex');
}
const server = http.createServer((req, res) => {
    const query = url.parse(req.url, true).query;
    const msgId = query.msgId || '';
    const dataType = query.dataType || '';
    const dataId = query.dataId || '';
    const dataVersion = query.dataVersion || '';
    const dataFormat = query.dataFormat || '';
    const dataSource = query.dataSource || '';
    const data = query.data || '';
    const timestamp = query.timestamp || '';
    const digest = query.digest || '';
    const status = query.status || '';
    const statusTime = query.statusTime || '';
    const tenantId = query.tenantId || '';
    console.log('[NEW]=======================================================');
    console.log('msgId:      ' + msgId);
    console.log('dataType:   ' + dataType);
    console.log('dataId:     ' + dataId);
    console.log('dataVersion:' + dataVersion);
    console.log('dataFormat: ' + dataFormat);
    console.log('dataSource: ' + dataSource);
    console.log('data:       ' + data);
    console.log('timestamp:  ' + timestamp);
    console.log('digest:     ' + digest);
    console.log('status:     ' + status);
    console.log('statusTime: ' + statusTime);
    console.log('tenantId:   ' + tenantId);
    const mdata = data + '|' + PUSH_SECRET + '|' + timestamp;
    const ndigest = md5(mdata);
    console.log('Calcdigest: ' + ndigest);
    res.setHeader('Content-Type', 'application/json; charset=UTF-8');
    if (ndigest !== digest) {
        res.end(JSON.stringify({
            return_code: '1',
            return_msg: '数据签名校验失败,请求不合法!'
        }));
        return;
    }
    res.end(JSON.stringify({ return_code: '0' }));
});
server.listen(PORT, () => {
    console.log(`Push message server running on port ${PORT}`);
});