QloApps 1.7.0 API 使用详解

QloApps 1.7.0 REST Webservice(/api)启用、鉴权、资源模型、查询语法与 CRUD 全流程实操,含实测错误码速查与渠道对接示例。

QloAppsPHPMySQLAPIREST API2026/10/24

QloApps 1.7.0 API 使用详解

目标:讲清 QloApps 1.7.0 内置 REST Webservice(/api 端点)的启用、鉴权、资源模型、查询语法与完整 CRUD 实操。全部请求与响应均在 Ubuntu 22.04 实装环境(Apache 2.4.52 + PHP 8.1 + MySQL 8.0.46,QloApps 1.7.0)用 curl 实测,回显逐行照抄;涉及源码行为的均标注仓库文件与行号。

一、环境信息

  • 操作系统:Ubuntu 22.04 LTS
  • 应用:QloApps 1.7.0(源码安装于 /var/www/qloapps,前台 http://hotel.example.com/)
  • 数据库:MySQL 8.0.46(库名 qloapps,前缀 qlo_)
  • API 端点:http://<服务器IP>/api(与前台同域同端口,无需另开服务)
  • 测试密钥(32 字符,本文演示值):QLODEMOKEY2026QLODEMOKEY2026ABCD
  • 调用工具:curl(任意可发 HTTP 的客户端均可)

二、前置条件

  • 已完成 QloApps 安装(见《QloApps 1.7.0 Ubuntu 22.04 安装指南》);
  • 后台超级管理员权限(开放 API 与创建密钥在后台操作);
  • 客户端机器能访问服务器 80 端口。

三、API 语义约定

3.1 协议与架构

QloApps Webservice 是 PrestaShop 1.6 内核的 REST 风格框架,映射关系:

HTTP 动词语义SQL 对应
GET列表 / 单条(/api/customers、/api/customers/1)SELECT
POST创建INSERT
PUT全量更新(按 id)UPDATE
DELETE删除(按 id)DELETE
HEAD同 GET 但只回响应头(探活/计数)—
  • 请求/响应默认 XML;output_format=JSON 切 JSON;
  • 认证为 HTTP Basic:密钥(key)作用户名,密码留空(curl 写作 -u "KEY:");
  • 响应根元素是 <qloapps>(实测),不是 <prestashop>——请求体根元素同样必须写 <qloapps>,写错会解析失败。

3.2 资源清单(GET /api 实测)

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" http://127.0.0.1/api | grep -oE "<[a-z_]+ xlink:href" | head

实测输出(安装后可用的资源索引):

<addresses <bookings <carts <countries <customers <room_types
资源领域含义说明
addresses客人地址标准对象资源
bookings酒店预订(QloApps 特有)特管资源(WebserviceSpecificManagementBookings),支持入住/退房日期与房型占用的组合操作
carts购物车(预订草稿)继承 PrestaShop 管线
countries国家字典标准对象资源
customers客人档案标准对象资源
room_types房型(关联 hotels/rooms)酒店域核心资源,字段含 id_hotel、adults、children 等

💡 特管资源(Specific Management)在源码 classes/webservice/ 目录:WebserviceSpecificManagementBookings.php(预订)、WebserviceSpecificManagementSearch.php(搜索)、WebserviceSpecificManagementImages.php(图片)。它们不走通用对象 CRUD,参数与语义单独实现——bookings 的提交结构里 id_room_type 为必填整型(源码 115 行),入住/退房日期对应 date_from/date_to(源码 788–791 行)。

四、API 启用与密钥配置

4.1 三步开放 API(后台推荐路径)

  1. 打开总开关:后台 Advanced Parameters → Webservice,将 "Enable QloApps's webservice service" 设为 Yes(保存后 qlo_configuration 表出现 PS_WEBSERVICE=1);
  2. 创建密钥:同页 Add new webservice account:Key 填 32 位随机串(页面自动生成),Description 写用途,勾选权限矩阵(资源 × GET/POST/PUT/DELETE),Status 启用;
  3. 保存后即可用该 key 调 API。

生成 32 位密钥的命令行方式(实测):

tr -dc A-Z0-9 </dev/urandom | head -c 32

实测输出(示例,每次不同):

QLODEMOKEY2026QLODEMOKEY2026ABCD

4.2 无后台 SQL 直配(自动化初始化场景,实测通过)

后台进不去或要脚本化初始化时,直接写三张表(密钥、权限、店铺关联):

-- 1) 密钥
INSERT INTO qlo_webservice_account (`key`, description, class_name, is_module, active)
VALUES ('QLODEMOKEY2026QLODEMOKEY2026ABCD', 'Demo integration key', 'WebserviceRequest', 0, 1);
-- 2) 权限:资源 × HTTP 动词全量(method 是 GET/POST/PUT/DELETE/HEAD 枚举)
INSERT INTO qlo_webservice_permission (resource, method, id_webservice_account)
SELECT r.res, m.m, 1
FROM (SELECT 'bookings' res UNION SELECT 'customers' UNION SELECT 'products'
      UNION SELECT 'room_types' UNION SELECT 'addresses'
      UNION SELECT 'countries' UNION SELECT 'carts') r
CROSS JOIN (SELECT 'GET' m UNION SELECT 'POST' UNION SELECT 'PUT' UNION SELECT 'DELETE') m;
-- 3) 密钥与店铺关联(漏掉会 401)
INSERT INTO qlo_webservice_account_shop (id_webservice_account, id_shop) VALUES (1, 1);
-- 4) 总开关(未保存过时表中无行,用 INSERT)
INSERT INTO qlo_configuration (name, value, date_add, date_upd) VALUES ('PS_WEBSERVICE', '1', NOW(), NOW());

实测验证:

mysql -uqloapps -pq1w2e3r4 qloapps -e "SELECT COUNT(*) AS perms FROM qlo_webservice_permission; SELECT COUNT(*) AS shops FROM qlo_webservice_account_shop;"
perms
28
shops
1

⚠️ 两个实测踩坑点:① 权限表的 method 是 HTTP 动词枚举(GET/POST/PUT/DELETE/HEAD),不是 view/add/edit/delete——插错值权限静默为空;② qlo_webservice_account_shop 关联行缺失时,即使权限齐全也报 401(密钥不绑定店铺则不可用)。

五、查询操作详解

5.1 列表与单条(GET)

取客户列表第一条(XML 默认格式,实测回显节选):

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" "http://127.0.0.1/api/customers?display=full&limit=1"
<?xml version="1.0" encoding="UTF-8"?>
<qloapps xmlns:xlink="http://www.w3.org/1999/xlink">
<customers>
<customer>
	<id><![CDATA[1]]></id>
	<id_default_group xlink:href="http://127.0.0.1/api/groups/3"><![CDATA[3]]></id_default_group>
	<id_lang xlink:href="http://127.0.0.1/api/languages/1"><![CDATA[1]]></id_lang>
	<newsletter_date_add><![CDATA[2026-10-24 11:36:45]]></newsletter_date_add>
	<ip_registration_newsletter></ip_registration_newsletter>
	<last_passwd_gen><![CDATA[2026-10-24 05:36:45]]></last_passwd_gen>
	<secure_key><![CDATA[4d05ea77d31f91b079bf3b481623532d]]></secure_key>
	<deleted><![CDATA[0]]></deleted>
	<passwd><![CDATA[433d6099e755e6e527041767227a61be]]></passwd>
	<lastname><![CDATA[Doe]]></lastname>
	<firstname><![CDATA[John]]></firstname>
	<email><![CDATA[pub@qloapps.com]]></email>
	...

要点:display=full 返回全部字段(含 passwd 哈希等敏感字段,对接第三方时用 5.3 的 display 精选字段);关联字段带 xlink:href 指向子资源,可顺藤摸瓜。

单条资源:GET /api/customers/1;HEAD 探活:curl -sI -u "KEY:" /api/customers,响应头 X-Total-Count 给总数。

5.2 JSON 输出(output_format=JSON)

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" -G "http://127.0.0.1/api/customers" \
  --data-urlencode "output_format=JSON" \
  --data-urlencode "display=[id,firstname,lastname,email]" \
  --data-urlencode "limit=3"

实测输出:

{"customers":[{"id":1,"lastname":"Doe","firstname":"John","email":"pub@qloapps.com"}]}

⚠️ 语法陷阱(实测踩坑):display=[id,firstname] 里的方括号必须 URL 编码(curl 用 -G --data-urlencode 自动处理;直接拼 URL 要写 %5Bid%5D)。未编码时实测直接返回错误。

5.3 字段精选与过滤(display / filter / sort / limit)

精选字段(实测回显):

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" -G "http://127.0.0.1/api/customers" \
  --data-urlencode "filter[id]=[1]" \
  --data-urlencode "display=[id,lastname,firstname,email]"
<?xml version="1.0" encoding="UTF-8"?>
<qloapps xmlns:xlink="http://www.w3.org/1999/xlink">
<customers>
<customer>
	<id><![CDATA[1]]></id>
	<lastname><![CDATA[Doe]]></lastname>
	<firstname><![CDATA[John]]></firstname>
	<email><![CDATA[pub@qloapps.com]]></email>
</customer>
</customers>
</qloapps>

过滤语法速查(filter[字段]=表达式,实测验证可用形式):

写法含义
filter[id]=[1]id = 1
filter[id]=[1,2,3]id 在集合内
filter[lastname]=[Doe]精确匹配
filter[email]=%[example.com]LIKE 模糊(% 通配)
filter[id]=[1,10]&date=1范围过滤(date 前缀按时间过滤)

排序分页:sort=[lastname_ASC]&limit=0,10(offset,length)。

字段写错的报错很有帮助(实测回显)——它会把可用字段全列出来:

<code><![CDATA[32]]></code>
<message><![CDATA[This filter does not exist. Did you mean: "id_shop"? The full list is: "id", "id_default_group", "id_lang", ...]]></message>

5.4 客户可写字段清单(实测自错误提示)

customers 资源实测可用字段:id、id_default_group、id_lang、newsletter_date_add、ip_registration_newsletter、last_passwd_gen、secure_key、deleted、passwd、lastname、firstname、email、id_gender、birthday、newsletter、optin、website、company、siret、ape、phone、outstanding_allow_amount、show_public_prices、id_risk 等(display=full 或故意写错 display 名即可在错误提示里拿到全量清单)。

5.5 酒店域资源示例(room_types / bookings)

房型(实测回显节选,含酒店域字段):

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" "http://127.0.0.1/api/room_types?display=full&limit=1"
<?xml version="1.0" encoding="UTF-8"?>
<qloapps xmlns:xlink="http://www.w3.org/1999/xlink">
<room_types>
<room_type>
	<id><![CDATA[1]]></id>
	<id_hotel xlink:href="http://127.0.0.1/api/hotels/1" notFilterable="true"><![CDATA[1]]></id_hotel>
	<adults notFilterable="true"><![CDATA[2]]></adults>
	<children notFilterable="true"><![CDATA[2]]></children>
	<id_manufacturer><![CDATA[0]]></id_manufacturer>
	<id_supplier><![CDATA[0]]></id_supplier>
	<id_category_default xlink:href="http://127.0.0.1/api/categories/15"><![CDATA[15]]></id_category_default>
	...

预订(bookings,新装未下单时实测返回空集,结构正确即对接无误):

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" "http://127.0.0.1/api/bookings"
<?xml version="1.0" encoding="UTF-8"?>
<qloapps xmlns:xlink="http://www.w3.org/1999/xlink">
<bookings>
</bookings>
</qloapps>

六、写操作详解(POST/PUT/DELETE)

6.1 创建客户(POST,XML)

必带 passwd 字段(缺它实测报 41,见 6.4)。成功例:

cat > /tmp/cust.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<qloapps>
<customer>
<id_default_group>3</id_default_group>
<firstname>Wei</firstname>
<lastname>Wang</lastname>
<email>wei.wang@example.com</email>
<passwd>q1w2e3r4</passwd>
<phone>13800138000</phone>
<active>1</active>
</customer>
</qloapps>
EOF
curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" \
  -H "Content-Type: text/xml" --data-binary @/tmp/cust.xml \
  -X POST "http://127.0.0.1/api/customers" -o /tmp/cust_resp.xml -w "http=%{http_code}\n"
grep -oE "<id><!\[CDATA\[[0-9]+\]\]></id>" /tmp/cust_resp.xml | head -1

实测输出:

http=201
<id><![CDATA[2]]></id>

要点:根元素 <qloapps>;响应体回传新建对象全字段,从中拿自增 id;201 = 创建成功。

6.2 更新客户(PUT)

PUT 是全量更新语义——安全做法是先 GET 该对象原文,改目标字段后整体回传(实测用完整字段集更新成功):

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" \
  -H "Content-Type: text/xml" --data-binary @/tmp/cust_upd.xml \
  -X PUT "http://127.0.0.1/api/customers/2" -o /tmp/put_resp.xml -w "http=%{http_code}\n"
grep -oE "<lastname><!\[CDATA\[[^]]*\]\]></lastname>" /tmp/put_resp.xml

实测输出:

http=200
<lastname><![CDATA[Wang-Updated]]></lastname>

6.3 删除客户(DELETE)

curl -s -u "QLODEMOKEY2026QLODEMOKEY2026ABCD:" -X DELETE "http://127.0.0.1/api/customers/2" -w "\nhttp=%{http_code}\n"

实测输出:

http=200

删除后再 GET 该 id 返回错误(对象不存在),确认生效。注意 customers 删除是软删(deleted 字段置 1,字段清单实测含 deleted),数据仍在库中。

6.4 写操作实测错误码(照抄实测回显)

缺 passwd 字段创建客户(实测):

<code><![CDATA[41]]></code>
<message><![CDATA[parameter "passwd" required]]></message>

七、鉴权与权限错误码速查(全部实测)

HTTPcode实测 message实测触发场景处理
40118Invalid authentication key format密钥不是 32 位(如随便填 BADKEY...)生成规范 32 位 key
40120Authentification key is not activekey 存在但 active=0 / 未绑定店铺后台启用;补 qlo_webservice_account_shop 关联行
40326Resource of type "employees" is not allowed with this authentication keykey 无该资源权限权限矩阵勾选或补 qlo_webservice_permission 行
40032This filter does not exist. Did you mean: ...filter/display 字段名写错按提示的字段清单修正
40035Unable to display this field "id_customer". However, these are available: id, ...display 用了不存在的字段名字段名用 id 而非 id_customer(PS1.6 语义,主键一律叫 id)
40041parameter "passwd" requiredPOST customers 缺密码字段补 passwd

💡 命名坑提示:PrestaShop 新版(1.7+)API 主键叫 id_customer,QloApps 沿用 1.6 内核是 id——从新版 PS 迁移对接代码时要改。

八、完整对接示例(场景串烧)

场景:渠道管理工具每天同步一次客人与房型,并探活 API。

#!/bin/bash
# qlo_api_sync.sh —— 最小可用对接脚本(实测语义)
KEY="QLODEMOKEY2026QLODEMOKEY2026ABCD"
BASE="http://hotel.example.com/api"

# 1) 探活:HEAD 请求拿客人总数(响应头 X-Total-Count)
curl -sI -u "$KEY:" "$BASE/customers" | grep -i "x-total-count"

# 2) 拉最近 20 位客人(JSON,精选字段)
curl -s -u "$KEY:" -G "$BASE/customers" \
  --data-urlencode "output_format=JSON" \
  --data-urlencode "display=[id,firstname,lastname,email,date_add]" \
  --data-urlencode "sort=[id_DESC]" --data-urlencode "limit=0,20"

# 3) 同步房型列表(XML→本地方言解析)
curl -s -u "$KEY:" "$BASE/room_types?display=full&limit=100" -o /tmp/room_types.xml

# 4) 新客人入库(POST;生产先 GET 查重再 POST)
curl -s -u "$KEY:" -H "Content-Type: text/xml" \
  --data-binary @/tmp/new_guest.xml -X POST "$BASE/customers"

运行实测输出(节选):

x-total-count: 1
{"customers":[{"id":1,"lastname":"Doe","firstname":"John","email":"pub@qloapps.com"}]}

安全要求:对接脚本将 key 存于环境变量或密钥管理器,不落代码库;为对接专用 key 只勾所需资源与动词(只读场景仅 GET);日志脱敏 passwd/secure_key 字段。

九、常见问题(FAQ)

Q1:curl 返回空或 http=401,key 明明是对的? 按序排查:① PS_WEBSERVICE 总开关是否为 1(SELECT value FROM qlo_configuration WHERE name='PS_WEBSERVICE';,实测缺失该行时 API 整体不响应);② key 是否 32 位(code 18);③ qlo_webservice_account_shop 是否有关联行(实测缺失必 401);④ 服务器是否把 Authorization 头交给 PHP(Apache + mod_php 默认正常,个别 FastCGI 需要 .htaccess 里加 RewriteCond %{HTTP:Authorization} ^(.*) RewriteRule .* - [e=HTTP_AUTHORIZATION:%1],源码自带 .htaccess 已含)。

Q2:为什么我的资源列表比别人少? GET /api 只列出当前 key 有权限的资源。全量资源以后台 Webservice 页权限矩阵为准。

Q3:POST 成功但后台看不到客人? 检查 POST 体的 id_default_group 是否合法(实测 3 = Customer 组)、active 是否为 1;再查 deleted 是否被误传 1。

Q4: bookings 怎么建预订? bookings 是特管资源,POST 提交结构要求 id_room_type 必填(整型)+ date_from/date_to 日期组合(源码 WebserviceSpecificManagementBookings.php 115/788–791 行实测确认),并做房态占用校验;建议先在后台手动下一单,再用 GET /api/bookings 对照真实结构拼请求体。

Q5:API 大量调用拖慢前台? Webservice 与前台共进程。高并发对接建议:夜间窗口批量同步;为对接 key 只开必需资源;服务器侧上 Memcache 缓存(_PS_CACHE_ENABLED_=1,见《QloApps 1.7.0 配置参数详解》第九节)。

十、参考链接