API 文档
完整的API接口说明和使用示例
接口地址与说明 同步 / 异步 · HMAC-SHA256 签名校验
同步识别(等待识别完成再返回)
POST https://api.lijinbu.cn/v1/recognize
异步识别(推荐,用 requestId 轮询结果)
POST https://api.lijinbu.cn/v1/recognize/async
GET https://api.lijinbu.cn/v1/recognize/result/{requestId}
域名 `api.lijinbu.cn` 仅为示例,请替换为你自己的网关域名;路径 `/v1/...` 即为本服务的对外接口路径。
请求头(全部必填)
| Header 名称 | 是否必填 | 说明 |
|---|---|---|
| Content-Type | 是 | 固定为 multipart/form-data,用于上传图片文件 |
| X-API-Key | 是 | 控制台生成的 API Key,用于标识调用方身份 |
| X-Timestamp | 是 | 当前 Unix 时间戳(秒),与服务端时间误差需在 5 分钟以内 |
| X-Signature | 是 | 使用 HMAC-SHA256 计算得到的签名,算法见下方“签名生成”章节 |
签名生成(HMAC-SHA256)
签名规则:先计算上传文件的 MD5 值,得到 file_md5;
然后拼接字符串 sign_string = timestamp + api_key + file_md5,
最后使用 api_secret 做 HMAC-SHA256 计算,十六进制输出即为 X-Signature。
时间戳有效期为 ±5 分钟:如果 |server_time - timestamp| > 300,
后端会直接认为签名无效并返回 401。
JavaScript 示例
const crypto = require('crypto');
function generateSignature(timestamp, apiKey, apiSecret, fileMd5) {
const signString = timestamp + apiKey + fileMd5;
const signature = crypto
.createHmac('sha256', apiSecret)
.update(signString)
.digest('hex');
return signature;
}
// 使用示例
const timestamp = Math.floor(Date.now() / 1000).toString();
const fileMd5 = 'your_file_md5_hash';
const signature = generateSignature(timestamp, apiKey, apiSecret, fileMd5);
Python 示例
import hmac
import hashlib
import time
def generate_signature(timestamp, api_key, api_secret, file_md5):
sign_string = f"{timestamp}{api_key}{file_md5}"
signature = hmac.new(
api_secret.encode('utf-8'),
sign_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
# 使用示例
timestamp = str(int(time.time()))
file_md5 = 'your_file_md5_hash'
signature = generate_signature(timestamp, api_key, api_secret, file_md5)
请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | File | 是 | 图片文件(JPG、PNG),最大10MB |
注意:当前版本中,请求头中的 X-API-Key、X-Signature、X-Timestamp 均为必填,并且签名必须验证通过才能调用接口。
同步识别接口调用示例
同步接口:POST /v1/recognize,服务端会等待识别完成后一次性返回完整结果。
cURL(命令行直接可用)
curl -X POST https://api.lijinbu.cn/v1/recognize \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: $(date +%s)" \
-H "X-Signature: your_generated_signature" \
-F "file=@/path/to/image.jpg"
Java(OkHttp 示例,可直接使用)
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("image/jpeg");
File file = new File("/path/to/image.jpg");
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(),
RequestBody.create(file, mediaType))
.build();
long timestamp = System.currentTimeMillis() / 1000;
String fileMd5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(new java.io.FileInputStream(file));
String signString = timestamp + "your_api_key" + fileMd5;
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
mac.init(new javax.crypto.spec.SecretKeySpec("your_api_secret".getBytes("UTF-8"), "HmacSHA256"));
String signature = javax.xml.bind.DatatypeConverter.printHexBinary(
mac.doFinal(signString.getBytes("UTF-8"))
).toLowerCase();
Request request = new Request.Builder()
.url("https://api.lijinbu.cn/v1/recognize")
.addHeader("X-API-Key", "your_api_key")
.addHeader("X-Timestamp", String.valueOf(timestamp))
.addHeader("X-Signature", signature)
.post(body)
.build();
try (Response resp = client.newCall(request).execute()) {
String json = resp.body().string();
System.out.println(json);
}
Python(requests 示例,可直接使用)
import time
import hashlib
import hmac
import requests
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://api.lijinbu.cn"
IMAGE_PATH = "/path/to/image.jpg"
def md5_file(path: str) -> str:
md5 = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
md5.update(chunk)
return md5.hexdigest()
def generate_signature(timestamp: str, api_key: str, api_secret: str, file_md5: str) -> str:
sign_string = f"{timestamp}{api_key}{file_md5}"
return hmac.new(
api_secret.encode("utf-8"),
sign_string.encode("utf-8"),
hashlib.sha256,
).hexdigest()
timestamp = str(int(time.time()))
file_md5 = md5_file(IMAGE_PATH)
signature = generate_signature(timestamp, API_KEY, API_SECRET, file_md5)
headers = {
"X-API-Key": API_KEY,
"X-Timestamp": timestamp,
"X-Signature": signature,
}
with open(IMAGE_PATH, "rb") as f:
files = {"file": f}
resp = requests.post(f"{BASE_URL}/v1/recognize", headers=headers, files=files, timeout=30)
print(resp.status_code, resp.json())
Node.js(axios + form-data 示例)
import axios from "axios";
import FormData from "form-data";
import fs from "fs";
import crypto from "crypto";
const API_KEY = "your_api_key";
const API_SECRET = "your_api_secret";
const BASE_URL = "https://api.lijinbu.cn";
const IMAGE_PATH = "/path/to/image.jpg";
function md5File(path) {
const hash = crypto.createHash("md5");
const data = fs.readFileSync(path);
hash.update(data);
return hash.digest("hex");
}
function generateSignature(timestamp, apiKey, apiSecret, fileMd5) {
const signString = `${timestamp}${apiKey}${fileMd5}`;
return crypto
.createHmac("sha256", apiSecret)
.update(signString)
.digest("hex");
}
async function main() {
const timestamp = Math.floor(Date.now() / 1000).toString();
const fileMd5 = md5File(IMAGE_PATH);
const signature = generateSignature(timestamp, API_KEY, API_SECRET, fileMd5);
const form = new FormData();
form.append("file", fs.createReadStream(IMAGE_PATH));
const resp = await axios.post(`${BASE_URL}/v1/recognize`, form, {
headers: {
"X-API-Key": API_KEY,
"X-Timestamp": timestamp,
"X-Signature": signature,
...form.getHeaders(),
},
timeout: 30000,
});
console.log(resp.data);
}
main().catch(console.error);
Go(net/http 示例)
package main
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"time"
)
const (
apiKey = "your_api_key"
apiSecret = "your_api_secret"
baseURL = "https://api.lijinbu.cn"
imagePath = "/path/to/image.jpg"
)
func md5File(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func generateSignature(timestamp, apiKey, apiSecret, fileMd5 string) string {
signString := timestamp + apiKey + fileMd5
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(signString))
return hex.EncodeToString(mac.Sum(nil))
}
func main() {
fileMd5, err := md5File(imagePath)
if err != nil {
panic(err)
}
timestamp := fmt.Sprintf("%d", time.Now().Unix())
signature := generateSignature(timestamp, apiKey, apiSecret, fileMd5)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, err := os.Open(imagePath)
if err != nil {
panic(err)
}
defer file.Close()
part, err := writer.CreateFormFile("file", filepath.Base(file.Name()))
if err != nil {
panic(err)
}
if _, err := io.Copy(part, file); err != nil {
panic(err)
}
writer.Close()
req, _ := http.NewRequest("POST", baseURL+"/v1/recognize", body)
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("X-Signature", signature)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
异步识别(推荐)
同步接口会一直等待识别完成再返回;如果你希望更稳定(避免超时),推荐用异步接口:
先提交任务拿到 requestId,再轮询查询结果。
1)提交异步任务
POST
/v1/recognize/async
curl -X POST https://api.lijinbu.cn/v1/recognize/async \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: $(date +%s)" \
-H "X-Signature: your_generated_signature" \
-F "file=@/path/to/image.jpg"
返回示例(立即返回,status=pending):
{
"code": 200,
"message": "success",
"data": {
"requestId": "a1b2c3d4e5f6",
"identifyRecordId": 123456,
"status": "pending"
}
}
2)轮询查询结果
GET
/v1/recognize/result/{requestId}
curl -X GET https://api.lijinbu.cn/v1/recognize/result/a1b2c3d4e5f6 \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: $(date +%s)" \
-H "X-Signature: your_generated_signature"
如果仍在识别中(status=pending):
{
"code": 200,
"message": "success",
"data": {
"requestId": "a1b2c3d4e5f6",
"identifyRecordId": 123456,
"status": "pending"
}
}
识别完成(status=success):
{
"code": 200,
"message": "success",
"data": {
"requestId": "a1b2c3d4e5f6",
"identifyRecordId": 123456,
"status": "success",
"records": [
{ "name": "张三", "money": 500, "remark": "" }
],
"total": 500,
"recordCount": 1
}
}
异步识别多语言调用示例(提交 + 轮询)
import time
import hashlib
import hmac
import requests
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://api.lijinbu.cn"
IMAGE_PATH = "/path/to/image.jpg"
def md5_file(path: str) -> str:
md5 = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
md5.update(chunk)
return md5.hexdigest()
def generate_signature(timestamp: str, api_key: str, api_secret: str, payload: str) -> str:
sign_string = f"{timestamp}{api_key}{payload}"
return hmac.new(
api_secret.encode("utf-8"),
sign_string.encode("utf-8"),
hashlib.sha256,
).hexdigest()
# 1. 提交异步任务
timestamp = str(int(time.time()))
file_md5 = md5_file(IMAGE_PATH)
signature = generate_signature(timestamp, API_KEY, API_SECRET, file_md5)
headers = {
"X-API-Key": API_KEY,
"X-Timestamp": timestamp,
"X-Signature": signature,
}
with open(IMAGE_PATH, "rb") as f:
files = {"file": f}
resp = requests.post(f"{BASE_URL}/v1/recognize/async",
headers=headers,
files=files).json()
request_id = resp["data"]["requestId"]
# 2. 轮询查询结果
while True:
time.sleep(2)
ts = str(int(time.time()))
poll_sig = generate_signature(ts, API_KEY, API_SECRET, request_id)
poll_headers = {
"X-API-Key": API_KEY,
"X-Timestamp": ts,
"X-Signature": poll_sig,
}
r = requests.get(f"{BASE_URL}/v1/recognize/result/{request_id}",
headers=poll_headers).json()
data = r["data"]
status = data["status"]
print("status:", status)
if status == "pending":
continue
elif status == "success":
print("records:", data["records"])
print("total:", data["total"])
else:
print("failed:", data.get("errorMsg"))
break
OkHttpClient client = new OkHttpClient();
File file = new File("/path/to/image.jpg");
MediaType mediaType = MediaType.parse("image/jpeg");
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(),
RequestBody.create(file, mediaType))
.build();
// 1. 提交异步任务
long ts = System.currentTimeMillis() / 1000;
String fileMd5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(new java.io.FileInputStream(file));
String signString = ts + "your_api_key" + fileMd5;
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
mac.init(new javax.crypto.spec.SecretKeySpec("your_api_secret".getBytes("UTF-8"), "HmacSHA256"));
String signature = javax.xml.bind.DatatypeConverter.printHexBinary(
mac.doFinal(signString.getBytes("UTF-8"))
).toLowerCase();
Request asyncReq = new Request.Builder()
.url("https://api.lijinbu.cn/v1/recognize/async")
.addHeader("X-API-Key", "your_api_key")
.addHeader("X-Timestamp", String.valueOf(ts))
.addHeader("X-Signature", signature)
.post(body)
.build();
String requestId;
try (Response resp = client.newCall(asyncReq).execute()) {
String json = resp.body().string();
// 解析 JSON 获取 requestId(略)
requestId = "...";
}
// 2. 轮询查询结果
while (true) {
Thread.sleep(2000);
long pollTs = System.currentTimeMillis() / 1000;
String pollSignString = pollTs + "your_api_key" + requestId;
javax.crypto.Mac pollMac = javax.crypto.Mac.getInstance("HmacSHA256");
pollMac.init(new javax.crypto.spec.SecretKeySpec("your_api_secret".getBytes("UTF-8"), "HmacSHA256"));
String pollSignature = javax.xml.bind.DatatypeConverter.printHexBinary(
pollMac.doFinal(pollSignString.getBytes("UTF-8"))
).toLowerCase();
Request pollReq = new Request.Builder()
.url("https://api.lijinbu.cn/v1/recognize/result/" + requestId)
.addHeader("X-API-Key", "your_api_key")
.addHeader("X-Timestamp", String.valueOf(pollTs))
.addHeader("X-Signature", pollSignature)
.get()
.build();
try (Response resp = client.newCall(pollReq).execute()) {
String json = resp.body().string();
// 解析 JSON,判断 data.status 是否为 pending/success/failed
}
}
import axios from "axios";
import FormData from "form-data";
import fs from "fs";
import crypto from "crypto";
const API_KEY = "your_api_key";
const API_SECRET = "your_api_secret";
const BASE_URL = "https://api.lijinbu.cn";
const IMAGE_PATH = "/path/to/image.jpg";
function md5File(path) {
const hash = crypto.createHash("md5");
const data = fs.readFileSync(path);
hash.update(data);
return hash.digest("hex");
}
function generateSignature(timestamp, apiKey, apiSecret, payload) {
const signString = `${timestamp}${apiKey}${payload}`;
return crypto.createHmac("sha256", apiSecret).update(signString).digest("hex");
}
// 1. 提交异步任务
const ts = Math.floor(Date.now() / 1000).toString();
const fileMd5 = md5File(IMAGE_PATH);
const sig = generateSignature(ts, API_KEY, API_SECRET, fileMd5);
const form = new FormData();
form.append("file", fs.createReadStream(IMAGE_PATH));
const asyncResp = await axios.post(`${BASE_URL}/v1/recognize/async`, form, {
headers: {
"X-API-Key": API_KEY,
"X-Timestamp": ts,
"X-Signature": sig,
...form.getHeaders(),
},
});
const requestId = asyncResp.data.data.requestId;
// 2. 轮询查询结果
while (true) {
await new Promise((r) => setTimeout(r, 2000));
const pollTs = Math.floor(Date.now() / 1000).toString();
const pollSig = generateSignature(pollTs, API_KEY, API_SECRET, requestId);
const pollResp = await axios.get(
`${BASE_URL}/v1/recognize/result/${requestId}`,
{
headers: {
"X-API-Key": API_KEY,
"X-Timestamp": pollTs,
"X-Signature": pollSig,
},
}
);
const data = pollResp.data.data;
console.log("status:", data.status);
if (data.status === "pending") continue;
if (data.status === "success") {
console.log("records:", data.records);
console.log("total:", data.total);
} else {
console.log("failed:", data.errorMsg);
}
break;
}
const apiKey = "your_api_key"
const apiSecret = "your_api_secret"
const baseURL = "https://api.lijinbu.cn"
// 1. 提交异步任务(参考同步示例构造 multipart 请求到 /v1/recognize/async,签名 payload 为文件 MD5)
// 返回 JSON 中 data.requestId 即为异步查询 ID
requestId := "..."
// 2. 轮询查询结果
for {
time.Sleep(2 * time.Second)
ts := fmt.Sprintf("%d", time.Now().Unix())
payload := requestId
signString := ts + apiKey + payload
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(signString))
sig := hex.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest("GET",
fmt.Sprintf("%s/v1/recognize/result/%s", baseURL, requestId), nil)
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("X-Timestamp", ts)
req.Header.Set("X-Signature", sig)
resp, _ := http.DefaultClient.Do(req)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println(string(body))
// 根据 JSON 中的 data.status 判断 pending/success/failed
}
响应示例
成功响应
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"name": "张三",
"money": 500,
"remark": "备注信息"
},
{
"name": "李四",
"money": 300,
"remark": ""
}
],
"total": 800,
"recordCount": 2,
"status": "success",
"requestId": 123456
}
}
错误响应
{
"code": 400,
"message": "Invalid signature",
"data": null
}
错误码说明
| 错误码 | 说明 |
|---|---|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 签名验证失败 |
| 403 | 点数不足或余额不足 |
| 500 | 服务器内部错误 |
响应字段说明
| 字段名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 状态码(200表示成功) |
| message | String | 响应消息 |
| data.records | Array | 识别结果数组,每个元素包含 name(姓名)、money(金额)、remark(备注) |
| data.total | Number | 总金额 |
| data.recordCount | Integer | 识别记录数量 |
| data.status | String | 识别状态(success/failed/pending) |
| data.requestId | Long | 识别记录ID,可用于查询详情 |