版本:v1.0
最后更新:2026-06-30 测试环境-基础URL:https://api.zjmjyy.com/api/api-platform
本文档仅开放 [接口一:任务大厅列表] [接口二:任务详情]对外接口免登录 ,
但必须通过 签名验证。每个请求需携带 3 个自定义 Header,服务端会校验合法性。
平台其它所有接口都需要登录
联系平台管理员获取以下凭证(由平台分配并录入 api_platform_info 表):
| 参数 | 说明 | 示例 |
|---|---|---|
appKey |
平台身份标识 | (填你的 appKey) |
appPublicKey |
签名密钥(机密,切勿泄露) | (填你的 appPublicKey) |
| Header 名称 | 必填 | 说明 |
|---|---|---|
X-App-Key |
是 | 平台分配的身份标识 |
X-Timestamp |
是 | 当前时间戳(毫秒),有效期 ±5 分钟 |
X-Sign |
是 | 请求签名,生成算法见下方 |
Authorization |
否 | 小程序端使用,携带此 Header 时跳过验签 |
签名原串 = 所有 Query 参数按 key 字典序拼接 "k1=v1&k2=v2&...×tamp={ts}&key={appPublicKey}"
签名结果 = MD5(签名原串)
步骤详解:
? 后面的部分,不含路径参数)TreeMap)key1=value1&key2=value2&... 的形式(值为空则跳过该参数)timestamp={时间戳}&key={appPublicKey}以任务大厅列表接口为例,假设请求参数为 current=1&size=10:
步骤1: 原始参数 → current=1, size=10
步骤2: 字典序排列 → current=1, size=10 (本身就是升序)
步骤3: 拼接参数字符串 → "current=1&size=10&"
步骤4: 追加timestamp和key →
"current=1&size=10×tamp=1751289600000&key=70c1b93ae1df463809ed3b41156e4791"
步骤5: MD5 → "3f8c5d2a1b9e4f6c8d0a2b4c6e8f0a1b"
最终请求:
GET /task/visitor/hall?current=1&size=10 HTTP/1.1
Host: 121.199.7.40:9998
X-App-Key: 填你的 appKey
X-Timestamp: 1751289600000
X-Sign: 3f8c5d2a1b9e4f6c8d0a2b4c6e8f0a1b
String 自然排序(字典序/ASCII 序),Java 用 TreeMap 即可保证null 或空字符串 "" 的参数不参与签名@PathVariable(如 /{taskId} 中的 taskId)不在 Query String 中,不参与签名请求已过期Authorization Header(JWT Token),则直接放行,不触发验签| 项目 | 内容 |
|---|---|
| 接口路径 | /task/visitor/hall |
| 请求方式 | GET |
| 认证 | 免登录 + 签名验证 |
| Content-Type | 无需(GET 请求) |
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
current |
int | 否 | 页码,默认 1 |
size |
int | 否 | 每页条数,默认 10 |
occupationCode |
String | 否 | 职业编码 |
lng |
Double | 否 | 经度 |
lat |
Double | 否 | 纬度 |
provinceId |
String | 否 | 省 ID |
cityId |
String | 否 | 市 ID |
areaId |
String | 否 | 区 ID |
streetId |
String | 否 | 街道 ID |
titleFuzzy |
String | 否 | 任务标题模糊搜索 |
timeRange |
String | 否 | 时间范围:LastWeek(最近一周)、LastHalfYear(最近半年)、不传或 ALL(不限) |
sortByType |
int | 否 | 排序类型:1=创建时间倒序,2=距离升序 |
sortField |
String | 否 | 自定义排序字段(如 wage=工资待遇) |
sortOrder |
String | 否 | 排序规则:asc=升序,desc=降序 |
返回分页结构,records 中每个元素字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
taskId |
String | 任务 ID |
bizId |
String | 业务身份 ID |
bizType |
Integer | 业务域:0=个人,1=一般企业 |
bizName |
String | 业务身份名称 |
workTimeLimit |
Integer | 是否限制作业时间:0=不限制,1=限制 |
title |
String | 任务标题 |
description |
String | 任务描述 |
taskAddress |
String | 任务工作地址 |
provinceId / provinceName |
String | 省 ID / 名称 |
cityId / cityName |
String | 市 ID / 名称 |
areaId / areaName |
String | 区 ID / 名称 |
streetId / streetName |
String | 街道 ID / 名称 |
lat / lng |
Double | 纬度 / 经度 |
contactPhone |
String | 联系人手机号 |
email |
String | 联系人邮箱 |
settlementUnit |
Integer | 结算单位:1=元/小时,2=元/天,3=元/次,4=元/件 |
occupationCode |
String | 职业编码 |
occupationName |
String | 职业名称 |
wage |
BigDecimal | 佣金 |
requiredPersonnel |
Integer | 需要人数 |
welfare |
String | 任务福利 |
taskType |
Integer | 任务类型:1=撮合,2=代发 |
taskStatus |
Integer | 任务状态:1=代付款,2=待开始,3=已超时,4=进行中,5=已完成,6=已取消 |
taskCycleStartTime |
Date | 任务周期起始时间(yyyy-MM-dd HH:mm:ss) |
taskCycleEndTime |
Date | 任务周期结束时间(yyyy-MM-dd HH:mm:ss) |
certificateRequired |
Integer | 是否需要证书:0=不需要,1=需要 |
certificate |
String | 证书列表,多个逗号分隔 |
insureMethod |
Integer | 保险方式:1=发单方购买,2=双方线下承保 |
insureEffectType |
Integer | 保险购买方式:0=暂不购买,2=按打卡购买,3=按任务周期购买 |
insuranceType |
String | 险种类别 |
distance |
String | 距离(米) |
isPersonal |
Integer | 发单方身份:0=个人,1=企业 |
applyCount |
Integer | 报名人数 |
taskNumber |
String | 任务编号 |
onDutyTime / offDutyTime |
String | 到场时间 / 离场时间 |
gmtCreated |
Date | 创建时间(yyyy-MM-dd HH:mm:ss) |
createTime |
String | 创建时间字符串(兼容字段) |
curl -X GET \
'https://api.zjmjyy.com/api/api-platform/task/visitor/hall?current=1&size=10' \
-H 'X-App-Key: 填你的 appKey' \
-H 'X-Timestamp: 1751289600000' \
-H 'X-Sign: 3f8c5d2a1b9e4f6c8d0a2b4c6e8f0a1b'
{
"code": 200,
"msg": "操作成功",
"data": {
"current": 1,
"size": 10,
"total": 156,
"pages": 16,
"records": [
{
"taskId": "TASK20260601001",
"bizId": "BIZ001",
"bizType": 1,
"bizName": "XX建筑公司",
"title": "招电工-日结",
"description": "需要持证电工,工作地点XX工地",
"taskAddress": "浙江省杭州市余杭区XX路XX号",
"provinceId": "330000",
"provinceName": "浙江省",
"cityId": "330100",
"cityName": "杭州市",
"areaId": "330110",
"areaName": "余杭区",
"lat": 30.2500,
"lng": 120.1300,
"occupationCode": "OCC001",
"occupationName": "电工",
"wage": 350.00,
"requiredPersonnel": 5,
"taskType": 1,
"taskStatus": 4,
"taskCycleStartTime": "2026-06-15 08:00:00",
"taskCycleEndTime": "2026-07-15 18:00:00",
"settlementUnit": 1,
"distance": "1250.50",
"isPersonal": 1,
"applyCount": 12,
"gmtCreated": "2026-06-01 10:30:00"
}
]
}
}
| 项目 | 内容 |
|---|---|
| 接口路径 | /task/visitor/{taskId} |
| 请求方式 | GET |
| 认证 | 免登录 + 签名验证 |
| Content-Type | 无需(GET 请求) |
| 参数名 | 类型 | 位置 | 必填 | 说明 |
|---|---|---|---|---|
taskId |
String | Path | 是 | 任务 ID(从任务大厅列表获取) |
⚠️ 重要:
taskId是路径参数(@PathVariable),不参与签名计算。
当该接口没有 Query 参数时,签名原串为:timestamp={ts}&key={appPublicKey}
| 字段 | 类型 | 说明 |
|---|---|---|
taskId |
String | 任务 ID |
bizId |
String | 业务身份 ID |
bizType |
Integer | 业务域:0=个人,1=企业 |
bizName |
String | 业务身份名称 |
title |
String | 任务标题 |
description |
String | 任务描述 |
workTimeLimit |
Integer | 是否限制作业时间:0=不限制,1=限制 |
onDutyTime / offDutyTime |
String | 到场时间 / 离场时间 |
taskAddress |
String | 任务工作地址 |
provinceId / provinceName |
String | 省 ID / 名称 |
cityId / cityName |
String | 市 ID / 名称 |
areaId / areaName |
String | 区 ID / 名称 |
streetId / streetName |
String | 街道 ID / 名称 |
lat / lng |
Double | 纬度 / 经度 |
contactPhone |
String | 联系人手机号 |
contact |
String | 联系人 |
welfare |
String | 任务福利 |
occupationCode |
String | 职业编码 |
occupationName |
String | 职业名称 |
settlementUnit |
Integer | 结算单位:1=元/小时,2=元/天,3=元/次,4=元/件 |
taskCycle |
Integer | 任务周期:1=长期,2=短期 |
taskCycleStartTime |
Date | 任务周期起始时间(yyyy-MM-dd) |
taskCycleEndTime |
Date | 任务周期结束时间(yyyy-MM-dd) |
wage |
BigDecimal | 佣金 |
requiredPersonnel |
Integer | 需要人数 |
taskType |
Integer | 任务类型:1=撮合,2=代发 |
taskStatus |
Integer | 任务状态:1=代付款,2=待开始,3=已超时,4=进行中,5=已完成,6=已取消 |
taskCompletionTime |
Date | 任务完成时间 |
taskActualStartTime |
Date | 任务实际开始时间 |
taskCancellationTime |
Date | 任务取消时间 |
taskTimeout |
Date | 任务超时时间 |
certificateRequired |
Integer | 是否需要证书:0=不需要,1=需要 |
certificate |
String | 证书列表 |
sexLimit |
Integer | 性别限制:1=不限,2=男,3=女 |
ageLimit |
Integer | 是否限制年龄:0=不限制,1=限制 |
minAge / maxAge |
Integer | 最小/最大年龄限制 |
insureMethod |
Integer | 保险方式:1=发单方购买,2=双方线下承保 |
insureEffectType |
Integer | 保险购买方式:0=暂不购买,2=按打卡购买,3=按任务周期购买 |
insuranceType |
String | 险种类别 |
isFaceCheckInRequired |
Integer | 是否需要人脸打卡:0=不需要,1=需要 |
checkInRangeLimit |
Integer | 是否限制打卡范围:0=不限,1=限制 |
checkRange |
Long | 打卡范围(米) |
distance |
String | 距离(米) |
applyCount |
Integer | 报名人数 |
isPersonal |
Integer | 发单方身份:0=个人,1=企业 |
taskNumber |
String | 任务编号 |
days |
Integer | 任务天数 |
taskCycleEndTimeOver |
Boolean | 是否已过任务周期结束时间 |
hasDevice |
Boolean | 是否包含打卡机 |
attendanceGroupId |
String | 打卡组 ID |
companyIntro |
String | 公司简介 |
jobRequirements |
String | 招聘条件 |
curl -X GET \
'https://api.zjmjyy.com/api/api-platform/task/visitor/TASK20260601001' \
-H 'X-App-Key: 填你的 appKey' \
-H 'X-Timestamp: 1751289600000' \
-H 'X-Sign: 8a2d4e6f1b3c5d7e9f0a2b4c6d8e0f1a'
{
"code": 200,
"msg": "操作成功",
"data": {
"taskId": "TASK20260601001",
"bizId": "BIZ001",
"bizType": 1,
"bizName": "XX建筑公司",
"title": "招电工-日结",
"description": "需要持证电工,工作地点XX工地,每天8小时",
"workTimeLimit": 1,
"onDutyTime": "08:00",
"offDutyTime": "18:00",
"taskAddress": "浙江省杭州市余杭区XX路XX号",
"provinceId": "330000",
"provinceName": "浙江省",
"cityId": "330100",
"cityName": "杭州市",
"areaId": "330110",
"areaName": "余杭区",
"lat": 30.2500,
"lng": 120.1300,
"contactPhone": "138****1234",
"contact": "张经理",
"occupationCode": "OCC001",
"occupationName": "电工",
"settlementUnit": 1,
"taskCycle": 2,
"taskCycleStartTime": "2026-06-15",
"taskCycleEndTime": "2026-07-15",
"wage": 350.00,
"requiredPersonnel": 5,
"taskType": 1,
"taskStatus": 4,
"sexLimit": 1,
"ageLimit": 1,
"minAge": 25,
"maxAge": 55,
"certificateRequired": 1,
"certificate": "电工证",
"insureMethod": 1,
"insureEffectType": 2,
"isPersonal": 1,
"applyCount": 12,
"distance": "1250.50",
"companyIntro": "XX建筑公司成立于2000年...",
"jobRequirements": "持有有效电工证,3年以上工作经验"
}
}
所有接口统一返回:
{
"code": 200,
"msg": "操作成功",
"data": { ... }
}
| 字段 | 类型 | 说明 |
|---|---|---|
code |
int | 状态码,200 表示成功 |
msg |
String | 提示信息 |
data |
Object | 业务数据 |
分页响应 data 额外包含:
| 字段 | 类型 | 说明 |
|---|---|---|
current |
int | 当前页码 |
size |
int | 每页条数 |
total |
int | 总记录数 |
pages |
int | 总页数 |
records |
Array | 数据列表 |
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
缺少验签参数 |
请求未携带 X-App-Key、X-Timestamp 或 X-Sign |
确保三个 Header 全部传入且值非空 |
请求已过期 |
X-Timestamp 与服务器时间差超过 ±5 分钟 |
生成新时间戳,重新计算签名后重试 |
时间戳格式错误 |
X-Timestamp 不是合法的长整型毫秒值 |
传入正确的毫秒时间戳,如 1751289600000 |
无效的AppKey |
X-App-Key 在平台不存在或已停用 |
检查 appKey 是否正确,联系管理员确认 |
签名验证失败 |
X-Sign 与服务端计算的不一致 |
检查签名算法实现,确认参数排序、拼接方式、MD5 结果 |
以下是完整的 Java 签名工具代码,第三方可直接复制使用:
import java.security.MessageDigest;
import java.util.*;
public class ApiSignUtil {
/**
* 构建请求签名
*
* @param params 请求参数 Map(query 参数,key=参数名,value=参数值)
* @param timestamp 时间戳(毫秒)
* @param secret 平台分配的 appPublicKey
* @return 32位小写MD5签名字符串
*/
public static String buildSign(Map<String, String> params, long timestamp, String secret) {
// 1. 参数字典序排列(TreeMap 自动排序)
TreeMap<String, String> sorted = new TreeMap<>(params);
// 2. 拼接签名原串:k1=v1&k2=v2...×tamp=xxx&key=secret
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sorted.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
sb.append("timestamp=").append(timestamp);
sb.append("&key=").append(secret);
// 3. MD5 → 32位小写十六进制
return md5(sb.toString());
}
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(input.getBytes("UTF-8"));
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder(digest.length * 2);
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 error", e);
}
}
// ────────── 使用示例 ──────────
public static void main(String[] args) {
String appKey = "AK_eaf449b6fb9f21";
String secret = "70c1b93ae1df463809ed3b41156e4791";
// 示例1:任务大厅列表
Map<String, String> hallParams = new LinkedHashMap<>();
hallParams.put("current", "1");
hallParams.put("size", "10");
hallParams.put("occupationCode", "OCC001");
long ts = System.currentTimeMillis();
String sign = buildSign(hallParams, ts, secret);
System.out.println("X-App-Key : " + appKey);
System.out.println("X-Timestamp : " + ts);
System.out.println("X-Sign : " + sign);
// 示例2:任务详情(无Query参数)
Map<String, String> emptyParams = new LinkedHashMap<>();
long ts2 = System.currentTimeMillis();
String sign2 = buildSign(emptyParams, ts2, secret);
System.out.println("\n[任务详情] X-Timestamp: " + ts2);
System.out.println("[任务详情] X-Sign : " + sign2);
}
}