1. 概述
澜湄联盟官网对外开放数据接口,供第三方系统调用获取联盟相关数据。
当前状态:默认无需鉴权 — 可直接调用下方接口,无需 AppId、签名 Header。
后期如需启用鉴权,服务端配置
open.api.auth-enabled=true 即可。
| 项目 | 说明 |
|---|---|
| 接口前缀 | /api/open/v1/** |
| Base URL | https://your-domain.com(请替换为实际域名) |
| 认证方式 | 默认关闭;可配置 auth-enabled: true 启用 HMAC 签名 |
| 权限控制 | 鉴权开启后,后台为每个应用勾选 Scope |
| 响应格式 | JSON,统一 { code, message, data } 包装 |
架构示意(鉴权开启时)
第三方系统
│ X-App-Id + X-Sign
▼
OpenApiAuthInterceptor(签名 / 时间戳 / Nonce / 限流 / IP)
▼
@OpenScope 权限校验
▼
Open Controller
▼
Portal Service(业务数据层)
2. 管理后台使用
鉴权开启后,需在管理后台创建应用并分配权限。入口:系统管理 → 开放接口。
2.1 创建应用
- 进入 系统管理 → 开放接口
- 点击 创建应用,填写应用名称
- 勾选 数据权限(Scope)
- 保存后弹窗展示 AppId 和 AppSecret(仅展示一次,请立即复制保存)
2.2 应用配置项
| 字段 | 说明 |
|---|---|
| 应用名称 | 合作方名称,如「XX 旅游平台」 |
| 状态 | 启用 / 禁用 |
| 限流/分钟 | 默认 100 次/分钟 |
| IP 白名单 | 逗号分隔,留空不限制 |
| 过期时间 | 留空表示永不过期 |
| 数据权限 | Scope 多选,可随时调整 |
3. 鉴权开关
当前环境默认 不鉴权,以下签名章节供后期启用时参考。
open:
api:
auth-enabled: true # 或环境变量 OPEN_API_AUTH_ENABLED=true
4. 签名认证(auth-enabled=true 时生效)
4.1 请求 Header
| Header | 说明 |
|---|---|
X-App-Id |
应用 AppId,如 lm_a1b2c3d4e5f6 |
X-Timestamp |
Unix 时间戳(秒),与服务端时差 ≤ 300 秒 |
X-Nonce |
随机字符串,16~32 位,不可重复使用 |
X-Sign |
HMAC-SHA256 签名,十六进制小写 |
4.2 签名算法
signContent = appId + "\n"
+ timestamp + "\n"
+ nonce + "\n"
+ METHOD + "\n"
+ path + "\n"
+ sortedQueryString + "\n"
+ sha256(body)
sign = HMAC-SHA256(appSecret, signContent).toHexLowerCase()
- path:不含域名,如
/api/open/v1/regions/countries - sortedQueryString:Query 参数按 key 字典序,URL 编码后用
&连接;无参数时为空字符串 - body:GET 为空字符串的 SHA256;POST/PUT 为 raw body 的 SHA256
5. 接口列表
5.1 快速开始(当前无需鉴权)
直接 GET 请求即可,示例(请将域名替换为实际地址):
GET /api/open/v1/regions/countries?lang=zh-CN
GET /api/open/v1/regions/children?parentId=1&lang=zh-CN
GET /api/open/v1/cms/category?lang=zh-CN
GET /api/open/v1/cms/tag?lang=zh-CN
5.2 连通性测试
GET /api/open/v1/ping
鉴权开启时用于验证 AppId 与签名是否正确。
5.3 地区数据 已开放
鉴权开启时需 Scope:region:read
| 方法 | 路径 | 参数 | 说明 |
|---|---|---|---|
| GET | /api/open/v1/regions/countries |
lang(默认 zh-CN) |
六国国家列表(level=1) |
| GET | /api/open/v1/regions/children |
parentId(必填)、lang |
子级地区(省州等) |
响应示例 — countries
{
"code": 200,
"message": "success",
"data": [
{ "id": 1, "code": "CN", "name": "中国", "sort": 1 },
{ "id": 2, "code": "TH", "name": "泰国", "sort": 2 }
]
}
响应示例 — children
{
"code": 200,
"message": "success",
"data": [
{ "id": 10, "code": "110000", "name": "北京市", "parentId": 1, "level": 2, "sort": 1 }
]
}
5.4 CMS 分类 已开放
鉴权开启时需 Scope:cms:category:read
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/open/v1/cms/category |
分类树,支持 lang |
| GET | /api/open/v1/cms/category/slug/{slug} |
按 slug 获取分类详情 + i18n |
字段说明 — category 节点
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 分类 ID |
parentId |
number | 父级 ID,0 表示顶级 |
slug |
string | URL 标识,可为空 |
name |
string | 分类名称(按 lang 参数回填,fallback:请求语言 → en → 任意可用语言) |
sort |
number | 排序值,升序 |
status |
number | 状态,1=启用(接口仅返回启用分类) |
children |
array | 子分类列表,树形嵌套;无子级时为 [] |
createTime |
string | 创建时间 |
updateTime |
string | 更新时间 |
deleted |
null | 逻辑删除标记,正常数据为 null |
响应示例 — category 树(完整数据,lang=zh-CN)
GET /api/open/v1/cms/category?lang=zh-CN
{
"code": 200,
"message": "Success",
"data": [
{
"id": 1,
"deleted": null,
"createTime": "2026-05-16 09:28:29",
"updateTime": "2026-07-05 17:06:11",
"parentId": 0,
"slug": "news",
"sort": 0,
"status": 1,
"name": "中国文旅",
"children": []
},
{
"id": 2,
"deleted": null,
"createTime": "2026-06-01 17:03:31",
"updateTime": "2026-07-01 20:45:42",
"parentId": 0,
"slug": "other_news",
"sort": 0,
"status": 1,
"name": "国际视野",
"children": []
},
{
"id": 9,
"deleted": null,
"createTime": "2026-07-05 17:06:23",
"updateTime": "2026-07-05 17:06:23",
"parentId": 0,
"slug": "",
"sort": 0,
"status": 1,
"name": "柬埔寨文旅",
"children": []
},
{
"id": 11,
"deleted": null,
"createTime": "2026-07-05 17:06:44",
"updateTime": "2026-07-05 17:06:44",
"parentId": 0,
"slug": "老挝文旅",
"sort": 0,
"status": 1,
"name": "老挝文旅",
"children": []
},
{
"id": 12,
"deleted": null,
"createTime": "2026-07-05 17:06:55",
"updateTime": "2026-07-05 17:06:55",
"parentId": 0,
"slug": "缅甸文旅",
"sort": 0,
"status": 1,
"name": "Category-12",
"children": []
},
{
"id": 13,
"deleted": null,
"createTime": "2026-07-05 17:07:02",
"updateTime": "2026-07-05 17:07:02",
"parentId": 0,
"slug": "泰国文旅",
"sort": 0,
"status": 1,
"name": "Category-13",
"children": []
},
{
"id": 14,
"deleted": null,
"createTime": "2026-07-05 17:07:12",
"updateTime": "2026-07-05 17:07:12",
"parentId": 0,
"slug": "越南文旅",
"sort": 0,
"status": 1,
"name": "Category-14",
"children": []
}
]
}
5.5 CMS 标签 已开放
鉴权开启时需 Scope:cms:tag:read
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/open/v1/cms/tag |
标签列表,支持 lang |
| GET | /api/open/v1/cms/tag/slug/{slug} |
按 slug 获取标签详情 + i18n |
字段说明 — tag 节点
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 标签 ID |
slug |
string | URL 标识,可为空 |
name |
string | 标签名称(按 lang 参数回填,fallback:请求语言 → en → 任意可用语言) |
sort |
number | 排序值,升序 |
status |
number | 状态,1=启用(接口仅返回启用标签) |
createTime |
string | 创建时间 |
updateTime |
string | 更新时间 |
deleted |
null | 逻辑删除标记,正常数据为 null |
响应示例 — tag 列表(lang=zh-CN)
GET /api/open/v1/cms/tag?lang=zh-CN
{
"code": 200,
"message": "Success",
"data": [
{
"id": 1,
"deleted": null,
"createTime": "2026-07-05 10:00:00",
"updateTime": "2026-07-05 10:00:00",
"slug": "tag1",
"sort": 0,
"status": 1,
"name": "标签一"
},
{
"id": 2,
"deleted": null,
"createTime": "2026-07-05 10:00:00",
"updateTime": "2026-07-05 10:00:00",
"slug": "tag2",
"sort": 1,
"status": 1,
"name": "标签二"
},
{
"id": 3,
"deleted": null,
"createTime": "2026-07-05 10:00:00",
"updateTime": "2026-07-05 10:00:00",
"slug": "policy_updates",
"sort": 2,
"status": 1,
"name": "政策动态"
}
]
}
6. Scope 权限一览(鉴权开启时生效)
| Scope | 说明 | 状态 |
|---|---|---|
region:read |
地区/国家 | 已开放 |
cms:category:read |
CMS 分类 | 已开放 |
cms:tag:read |
CMS 标签 | 已开放 |
cms:article:read |
CMS 文章 | 待扩展 |
cms:page:read |
CMS 单页 | 待扩展 |
biz:org:read |
商务机构 | 待扩展 |
biz:resource:read |
商务资源 | 待扩展 |
media:read |
媒体名录 | 待扩展 |
material:read |
素材库 | 待扩展 |
data:report:read |
数据报告 | 待扩展 |
product:read |
商城商品 | 待扩展 |
search:read |
统一搜索 | 待扩展 |
7. 错误码
| Code | 说明 |
|---|---|
| 200 | 成功 |
| 1400 | 请求参数错误 |
| 1601 | 应用不存在 |
| 1602 | 应用已禁用 |
| 1603 | 应用已过期 |
| 1604 | 签名无效 |
| 1605 | 时间戳过期 |
| 1606 | Nonce 重复使用 |
| 1607 | IP 不在白名单 |
| 1608 | 超出限流 |
| 1609 | 无对应 Scope 权限 |
8. 代码示例
8.1 直接调用(当前,无需鉴权)
# curl
curl "https://your-domain.com/api/open/v1/regions/countries?lang=zh-CN"
# Python
import requests
r = requests.get("https://your-domain.com/api/open/v1/regions/countries", params={"lang": "zh-CN"})
print(r.json())
8.2 Python 签名示例(鉴权开启时)
import hashlib, hmac, time, uuid, urllib.parse, requests
APP_ID = "lm_xxxxxxxxxxxx"
APP_SECRET = "your-secret-here"
BASE = "https://your-domain.com"
def sha256_hex(s):
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def build_query(params):
items = []
for key in sorted(params.keys()):
val = params[key]
if val is None: continue
items.append(f"{urllib.parse.quote(key, safe='')}={urllib.parse.quote(str(val), safe='')}")
return "&".join(items)
def sign(method, path, params, body=""):
ts = str(int(time.time()))
nonce = uuid.uuid4().hex
content = "\n".join([APP_ID, ts, nonce, method.upper(), path, build_query(params), sha256_hex(body)])
sig = hmac.new(APP_SECRET.encode(), content.encode(), hashlib.sha256).hexdigest()
return ts, nonce, sig
path = "/api/open/v1/regions/countries"
params = {"lang": "zh-CN"}
ts, nonce, sig = sign("GET", path, params)
r = requests.get(BASE + path, params=params, headers={
"X-App-Id": APP_ID, "X-Timestamp": ts, "X-Nonce": nonce, "X-Sign": sig,
})
print(r.json())
8.3 curl 签名示例(鉴权开启时)
curl -G "https://your-domain.com/api/open/v1/regions/countries" \
-H "X-App-Id: lm_xxxxxxxxxxxx" \
-H "X-Timestamp: 1719900000" \
-H "X-Nonce: abc123def4567890" \
-H "X-Sign: <computed_hmac>" \
--data-urlencode "lang=zh-CN"
9. 服务端配置(运维参考)
open:
api:
auth-enabled: ${OPEN_API_AUTH_ENABLED:false}
encrypt-key: ${OPEN_API_ENCRYPT_KEY:lm-open-api-key!}
timestamp-skew-seconds: 300
nonce-ttl-seconds: 600