一、系统概览
本模块在 FastAdmin/ThinkPHP 网站中完成用户、设备、证书模板、历史记录和通讯日志的闭环。管理员负责创建用户、绑定设备及交付设备凭证;用户只能查看自己绑定的设备、编辑自己的模板和历史记录;只有已启用且已绑定有效用户的设备才能调用 API。
管理员创建并绑定设备
设备携带 Key/Secret 鉴权
读取用户模板及字段
提交历史 JSON
用户修改并打印证书
面向设备端、桌面端和 Android 开发人员的接入与字段构建说明。
本模块在 FastAdmin/ThinkPHP 网站中完成用户、设备、证书模板、历史记录和通讯日志的闭环。管理员负责创建用户、绑定设备及交付设备凭证;用户只能查看自己绑定的设备、编辑自己的模板和历史记录;只有已启用且已绑定有效用户的设备才能调用 API。
| 字段 | 类型 | 必填 | 规则 |
|---|---|---|---|
record_no | string | 是 | 用户范围内唯一,最长 100 字符;重复提交同一编号时更新原记录 |
title | string | 否 | 记录标题,最长 150 字符;省略时使用记录编号 |
template_id | integer | 是 | 必须来自模板列表,并属于设备绑定的用户 |
status | string | 否 | draft 或 completed |
client_time | string | 否 | 设备端时间,建议 ISO 8601,最长 40 字符 |
data | object | 是 | 证书业务字段组成的 JSON 对象,整个 JSON 最大 2 MB |
RecordNumber。[RecordNumber],设备提交 data.RecordNumber。customer.name 对应 [customer.name]。data 中的扩展字段。| JSON 字段 | 字段含义 | HTML 写法 |
|---|---|---|
RecordNumber | 记录编号 | [RecordNumber] |
RecordTime | 检定日期 | [RecordTime] |
EntrustingUnit | 送检/委托单位 | [EntrustingUnit] |
StationName | 站点名称 | [StationName] |
Manufacturer | 制造厂商 | [Manufacturer] |
Model | 型号规格 | [Model] |
MachineNo | 出厂编号 | [MachineNo] |
Temperature | 环境温度 | [Temperature] |
Humidity | 环境湿度 | [Humidity] |
AtmosphericPressure | 大气压力 | [AtmosphericPressure] |
Inspector | 检定员 | [Inspector] |
Verifier | 核验员 | [Verifier] |
VerificationConclusion | 检定结论 | [VerificationConclusion] |
Organization | 用户所属单位 | [Organization] |
RealName | 用户真实姓名 | [RealName] |
CloudUrl | 云端数据地址 | [CloudUrl] |
StdName | 标准器名称 | [StdName] |
StdModel | 标准器型号规格 | [StdModel] |
StdSerialNo | 标准器编号 | [StdSerialNo] |
StdCertNo | 标准器证书编号 | [StdCertNo] |
StdValidDate | 标准器有效期 | [StdValidDate] |
StdAccuracy | 标准器准确度等级 | [StdAccuracy] |
Value1 | 第1次测量值 | [Value1] |
Value2 | 第2次测量值 | [Value2] |
Value3 | 第3次测量值 | [Value3] |
Value4 | 第4次测量值 | [Value4] |
Value5 | 第5次测量值 | [Value5] |
Value6 | 第6次测量值 | [Value6] |
Standard1 | 第1次标准值 | [Standard1] |
Standard2 | 第2次标准值 | [Standard2] |
Standard3 | 第3次标准值 | [Standard3] |
Standard4 | 第4次标准值 | [Standard4] |
Standard5 | 第5次标准值 | [Standard5] |
Standard6 | 第6次标准值 | [Standard6] |
Error1 | 第1次误差 | [Error1] |
Error2 | 第2次误差 | [Error2] |
Error3 | 第3次误差 | [Error3] |
Error4 | 第4次误差 | [Error4] |
Error5 | 第5次误差 | [Error5] |
Error6 | 第6次误差 | [Error6] |
MaxBasicError | 基本误差最大值 | [MaxBasicError] |
MaxRepeatability | 重复性最大值 | [MaxRepeatability] |
ValidTo | 有效期至 | [ValidTo] |
{
"record_no": "CNG-20260803-0001",
"title": "一号加气机检定记录",
"template_id": 8,
"status": "completed",
"client_time": "2026-08-03T14:30:00+08:00",
"data": {
"RecordNumber": "CNG-20260803-0001",
"RecordTime": "2026-08-03",
"EntrustingUnit": "示例送检单位",
"Manufacturer": "示例制造商",
"Model": "HC-CNG-01",
"MachineNo": "M20260803001",
"Temperature": "25.2℃",
"Humidity": "51%",
"VerificationConclusion": "合格"
}
}
本地地址为 http://127.0.0.1,正式环境替换为 HTTPS 域名。所有设备接口都携带以下请求头:
Content-Type: application/json; charset=utf-8
X-Device-Key: dev_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Device-Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
| 方法 | 地址 | 用途 |
|---|---|---|
| GET/POST | /api/device/ping | 验证设备凭证和绑定状态 |
| GET | /api/device/templates | 获取当前用户的启用模板、模板 ID 和变量说明 |
| POST | /api/device/history | 新增或更新历史记录 |
统一响应包含 code、msg、time 和 data。HTTP 401 表示凭证错误,403 表示绑定用户无效,413 表示内容过大,422 表示字段或模板校验失败。
使用系统自带的 HttpClient 和 System.Text.Json。实际项目中应复用单例 HttpClient,不要为每次请求重新创建。
using System.Net.Http.Json;
using System.Text.Json;
public sealed class CertificateApiClient
{
private readonly HttpClient _http;
public CertificateApiClient(string baseUrl, string apiKey, string apiSecret)
{
_http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/") };
_http.DefaultRequestHeaders.Add("X-Device-Key", apiKey);
_http.DefaultRequestHeaders.Add("X-Device-Secret", apiSecret);
}
public async Task PingAsync(CancellationToken token = default)
{
using var response = await _http.GetAsync("api/device/ping", token);
return await ReadResponseAsync(response, token);
}
public async Task GetTemplatesAsync(CancellationToken token = default)
{
using var response = await _http.GetAsync("api/device/templates", token);
return await ReadResponseAsync(response, token);
}
public async Task SubmitAsync(
long templateId,
string recordNo,
object certificateData,
CancellationToken token = default)
{
var request = new
{
record_no = recordNo,
title = $"检测记录 {recordNo}",
template_id = templateId,
status = "completed",
client_time = DateTimeOffset.Now.ToString("O"),
data = certificateData
};
using var response = await _http.PostAsJsonAsync(
"api/device/history", request, cancellationToken: token);
return await ReadResponseAsync(response, token);
}
private static async Task ReadResponseAsync(
HttpResponseMessage response, CancellationToken token)
{
var json = await response.Content.ReadAsStringAsync(token);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(
$"API {response.StatusCode}: {json}");
return JsonDocument.Parse(json);
}
}
// 调用示例
var api = new CertificateApiClient(
"https://hbhcyq.com",
"dev_xxx",
"secret_xxx");
await api.PingAsync();
var result = await api.SubmitAsync(8, "CNG-20260803-0001", new
{
RecordNumber = "CNG-20260803-0001",
RecordTime = "2026-08-03",
EntrustingUnit = "示例送检单位",
Model = "HC-CNG-01",
VerificationConclusion = "合格"
});
以下示例使用 Java 标准库 HttpClient。JSON 示例直接构建字符串;正式项目建议使用 Jackson 或 Gson 从对象序列化,避免手工拼接用户数据。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
public final class CertificateApiClient {
private final HttpClient http = HttpClient.newHttpClient();
private final String baseUrl;
private final String apiKey;
private final String apiSecret;
public CertificateApiClient(String baseUrl, String apiKey, String apiSecret) {
this.baseUrl = baseUrl.replaceAll("/+$", "");
this.apiKey = apiKey;
this.apiSecret = apiSecret;
}
private HttpRequest.Builder request(String path) {
return HttpRequest.newBuilder(URI.create(baseUrl + path))
.header("X-Device-Key", apiKey)
.header("X-Device-Secret", apiSecret)
.header("Content-Type", "application/json; charset=utf-8");
}
public String ping() throws Exception {
HttpRequest req = request("/api/device/ping").GET().build();
return send(req);
}
public String getTemplates() throws Exception {
HttpRequest req = request("/api/device/templates").GET().build();
return send(req);
}
public String submit(long templateId, String recordNo) throws Exception {
String safeRecordNo = escapeJson(recordNo);
String json = String.format(
"{" +
"\"record_no\":\"%s\"," +
"\"title\":\"设备检测记录\"," +
"\"template_id\":%d," +
"\"status\":\"completed\"," +
"\"client_time\":\"%s\"," +
"\"data\":{" +
"\"RecordNumber\":\"%s\"," +
"\"RecordTime\":\"2026-08-03\"," +
"\"Model\":\"HC-CNG-01\"," +
"\"VerificationConclusion\":\"合格\"}" +
"}",
safeRecordNo, templateId, OffsetDateTime.now(), safeRecordNo);
HttpRequest req = request("/api/device/history")
.POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
.build();
return send(req);
}
private static String escapeJson(String value) {
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}
private String send(HttpRequest request) throws Exception {
HttpResponse response = http.send(
request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException(
"API " + response.statusCode() + ": " + response.body());
}
return response.body();
}
}
// 调用示例
CertificateApiClient api = new CertificateApiClient(
"https://hbhcyq.com", "dev_xxx", "secret_xxx");
System.out.println(api.ping());
System.out.println(api.getTemplates());
System.out.println(api.submit(8L, "CNG-20260803-0001"));
Android 如果使用 OkHttp,Header 和 JSON 结构完全相同;网络请求必须放在线程池或协程中,不能在主线程执行。
record_no 会更新原记录,因此重试不会重复创建。服务端会校验模板是否属于设备绑定用户。不要使用其他账号看到的模板 ID,否则返回 HTTP 422。
设备接入、接口调试或业务合作中遇到问题,可通过以下方式联系。