feat: 删除无用的测试报告,添加房间状态快照表的数据库初始化脚本及集成方案
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
2026-01-30T16:54:47: Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'node-cron' imported from R:\nodejsROOT\bls\rcu-action\dist\index.js
|
||||
2026-01-30T16:54:47: at Object.getPackageJSONURL (node:internal/modules/package_json_reader:316:9)
|
||||
2026-01-30T16:54:47: at packageResolve (node:internal/modules/esm/resolve:768:81)
|
||||
2026-01-30T16:54:47: at moduleResolve (node:internal/modules/esm/resolve:858:18)
|
||||
2026-01-30T16:54:47: at defaultResolve (node:internal/modules/esm/resolve:990:11)
|
||||
2026-01-30T16:54:47: at #cachedDefaultResolve (node:internal/modules/esm/loader:737:20)
|
||||
2026-01-30T16:54:47: at ModuleLoader.resolve (node:internal/modules/esm/loader:714:38)
|
||||
2026-01-30T16:54:47: at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:293:38)
|
||||
2026-01-30T16:54:47: at #link (node:internal/modules/esm/module_job:208:49)
|
||||
2026-01-30T16:54:47: at process.processTicksAndRejections (node:internal/process/task_queues:103:5) {
|
||||
2026-01-30T16:54:47: code: 'ERR_MODULE_NOT_FOUND'
|
||||
2026-01-30T16:54:47: }
|
||||
2026-01-30T16:56:12: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763372054,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:12: {"level":"error","message":"Service bootstrap failed","timestamp":1769763372055,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:12: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763372929,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:12: {"level":"error","message":"Service bootstrap failed","timestamp":1769763372929,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:13: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763373801,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:13: {"level":"error","message":"Service bootstrap failed","timestamp":1769763373801,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:14: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763374671,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:14: {"level":"error","message":"Service bootstrap failed","timestamp":1769763374671,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:15: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763375539,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:15: {"level":"error","message":"Service bootstrap failed","timestamp":1769763375539,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:16: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763376418,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:16: {"level":"error","message":"Service bootstrap failed","timestamp":1769763376419,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:17: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763377290,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:17: {"level":"error","message":"Service bootstrap failed","timestamp":1769763377291,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:18: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763378161,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:18: {"level":"error","message":"Service bootstrap failed","timestamp":1769763378162,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:19: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763379035,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:19: {"level":"error","message":"Service bootstrap failed","timestamp":1769763379035,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:19: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763379920,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:19: {"level":"error","message":"Service bootstrap failed","timestamp":1769763379921,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:20: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763380801,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:20: {"level":"error","message":"Service bootstrap failed","timestamp":1769763380802,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:21: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763381675,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:21: {"level":"error","message":"Service bootstrap failed","timestamp":1769763381675,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:22: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763382560,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:22: {"level":"error","message":"Service bootstrap failed","timestamp":1769763382561,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:23: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763383432,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:23: {"level":"error","message":"Service bootstrap failed","timestamp":1769763383433,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:24: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763384307,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:24: {"level":"error","message":"Service bootstrap failed","timestamp":1769763384307,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
2026-01-30T16:56:25: {"level":"error","message":"Error initializing schema and table:","timestamp":1769763385185,"context":{"errno":-4058,"code":"ENOENT","syscall":"open","path":"R:\\nodejsROOT\\bls\\scripts\\init_db.sql"}}
|
||||
2026-01-30T16:56:25: {"level":"error","message":"Service bootstrap failed","timestamp":1769763385185,"context":{"error":"ENOENT: no such file or directory, open 'R:\\nodejsROOT\\bls\\scripts\\init_db.sql'"}}
|
||||
@@ -92,3 +92,5 @@ ACK (待补充)
|
||||
- 例如:`dev_type=35` (名称: TimeCtrl), `addr=14`, `loop=21` -> `[35TimeCtrl-14-21]`
|
||||
- 最后将找到的或生成的 `loop_name` 写入 `rcu_action_events` 表。
|
||||
- 注意,所有查库操作都要通过内存缓存来实现。
|
||||
|
||||
|
||||
|
||||
141
docs/room_status_moment.sql
Normal file
141
docs/room_status_moment.sql
Normal file
@@ -0,0 +1,141 @@
|
||||
-- ============================================================================
|
||||
-- 数据库初始化脚本
|
||||
-- 描述:创建 log_platform 库(逻辑参考)、room_status 模式及 room_status_moment 分区表
|
||||
-- 对应项目需求:project.md #L57-65
|
||||
-- ============================================================================
|
||||
|
||||
-- 注意:在 PostgreSQL 中,CREATE DATABASE 不能在事务块中执行。
|
||||
-- 通常建议先手动创建数据库,然后再执行后续脚本。
|
||||
-- CREATE DATABASE log_platform;
|
||||
|
||||
-- 切换到 log_platform 数据库后执行以下内容:
|
||||
|
||||
-- 1. 创建模式
|
||||
CREATE SCHEMA IF NOT EXISTS room_status;
|
||||
|
||||
-- 2. 创建主表 (使用声明式分区)
|
||||
-- 根据需求 L57-65,考虑后期十万级以上数据的扩展,按 hotel_id 进行 LIST 分区
|
||||
CREATE TABLE IF NOT EXISTS room_status.room_status_moment (
|
||||
-- 基础标识字段
|
||||
guid UUID NOT NULL,
|
||||
ts_ms INT8 NOT NULL DEFAULT (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000)::BIGINT,
|
||||
hotel_id INT2 NOT NULL,
|
||||
room_id TEXT NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
|
||||
-- 设备状态字段
|
||||
sys_lock_status INT2,
|
||||
online_status INT2,
|
||||
launcher_version TEXT,
|
||||
app_version TEXT,
|
||||
config_version TEXT,
|
||||
register_ts_ms INT8,
|
||||
upgrade_ts_ms INT8,
|
||||
config_ts_ms INT8,
|
||||
ip TEXT,
|
||||
|
||||
-- 房间业务状态字段
|
||||
pms_status INT2,
|
||||
power_state INT2,
|
||||
cardless_state INT2,
|
||||
service_mask INT8,
|
||||
insert_card INT2,
|
||||
bright_g INT2,
|
||||
agreement_ver TEXT,
|
||||
|
||||
-- 空调相关
|
||||
air_address TEXT[],
|
||||
air_state INT2[],
|
||||
air_model INT2[],
|
||||
air_speed INT2[],
|
||||
air_set_temp INT2[],
|
||||
air_now_temp INT2[],
|
||||
air_solenoid_valve INT2[],
|
||||
|
||||
-- 能耗相关
|
||||
elec_address TEXT[],
|
||||
elec_voltage DOUBLE PRECISION[],
|
||||
elec_ampere DOUBLE PRECISION[],
|
||||
elec_power DOUBLE PRECISION[],
|
||||
elec_phase DOUBLE PRECISION[],
|
||||
elec_energy DOUBLE PRECISION[],
|
||||
elec_sum_energy DOUBLE PRECISION[],
|
||||
|
||||
-- 节能与外设
|
||||
carbon_state INT2,
|
||||
dev_loops JSONB,
|
||||
energy_carbon_sum DOUBLE PRECISION,
|
||||
energy_nocard_sum DOUBLE PRECISION,
|
||||
external_device JSONB DEFAULT '{}',
|
||||
faulty_device_count JSONB DEFAULT '{}',
|
||||
|
||||
-- 约束:分区表的主键必须包含分区键 (hotel_id)
|
||||
PRIMARY KEY (hotel_id, room_id, device_id, guid)
|
||||
) PARTITION BY LIST (hotel_id);
|
||||
|
||||
-- 3. 创建索引 (针对高频查询字段)
|
||||
-- 注意:在分区表上创建索引会自动在所有子表上创建对应的索引
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_hotel_room ON room_status.room_status_moment (hotel_id, room_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_device_id ON room_status.room_status_moment (device_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_sys_lock ON room_status.room_status_moment (sys_lock_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_online ON room_status.room_status_moment (online_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_pms ON room_status.room_status_moment (pms_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_power ON room_status.room_status_moment (power_state);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_cardless ON room_status.room_status_moment (cardless_state);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_insert_card ON room_status.room_status_moment (insert_card);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_status_moment_carbon ON room_status.room_status_moment (carbon_state);
|
||||
|
||||
-- 3.1 唯一索引 (支持 UPSERT)
|
||||
-- 必须在 (hotel_id, room_id, device_id) 上建立唯一约束,才能使用 ON CONFLICT
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_room_status_unique_device
|
||||
ON room_status.room_status_moment (hotel_id, room_id, device_id);
|
||||
|
||||
-- 4. 示例:创建第一个分区 (hotel_id = 1)
|
||||
-- 实际部署时,可根据 hotel_id 动态创建分区
|
||||
CREATE TABLE IF NOT EXISTS room_status.room_status_moment_h1
|
||||
PARTITION OF room_status.room_status_moment
|
||||
FOR VALUES IN (1);
|
||||
|
||||
-- 5. 添加表和字段注释
|
||||
COMMENT ON TABLE room_status.room_status_moment IS '房间即时状态表 - 记录设备及房间业务的最新实时状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.guid IS '主键 guid uuid 32位无符号UUID';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.ts_ms IS '最后更新时间';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.hotel_id IS '酒店';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.room_id IS '房间';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.device_id IS '设备编号';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.sys_lock_status IS '系统锁状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.online_status IS '设备在线状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.launcher_version IS '设备launcher版本';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.app_version IS '设备App版本';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.config_version IS '设备配置版本';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.register_ts_ms IS '最后一次注册时间';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.upgrade_ts_ms IS '最后一次升级时间';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.config_ts_ms IS '最后一次下发配置时间';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.ip IS '当前公网IP地址';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.pms_status IS 'PMS状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.power_state IS '取电状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.cardless_state IS '有、无人状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.service_mask IS '服务状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.insert_card IS '插卡状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_address IS '空调地址';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_state IS '空调状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_model IS '空调模型';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_speed IS '空调风速';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_set_temp IS '空调设置温度';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_now_temp IS '房间当前温度';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.air_solenoid_valve IS '空调电磁阀状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_address IS '能耗表地址';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_voltage IS '能耗表电压';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_ampere IS '能耗表电流';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_power IS '能耗表功率';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_phase IS '当前相位';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_energy IS '能耗表能耗';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.elec_sum_energy IS '能耗表累计能耗';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.carbon_state IS '碳达人状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.bright_g IS '光亮值';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.agreement_ver IS '协议版本';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.dev_loops IS '回路状态';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.energy_carbon_sum IS '碳达人节能累计';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.energy_nocard_sum IS '无卡节能累计';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.external_device IS '外设设备管理(数组)';
|
||||
COMMENT ON COLUMN room_status.room_status_moment.faulty_device_count IS '故障设备数量';
|
||||
45
docs/测试报告.md
45
docs/测试报告.md
@@ -1,45 +0,0 @@
|
||||
# 测试报告
|
||||
|
||||
## 基本信息
|
||||
- 运行时间: 2026-01-29
|
||||
- 运行方式: 控制台启动 `npm run dev`,运行约 60 秒后 Ctrl + C 终止
|
||||
- 测试目标: 验证 Kafka 消费与入库链路,定位无入库原因
|
||||
|
||||
## 控制台关键日志
|
||||
```
|
||||
{"level":"error","message":"Message processing failed","timestamp":1769734880590,"context":{"error":"[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"hotel_id\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n }\n]","type":"PARSE_ERROR","stack":"ZodError: ...","rawPayload":"{\"ts_ms\":1769692878011,\"hotel_id\":\"2147\",\"room_id\":\"8209\",\"device_id\":\"099008129081\",\"direction\":\"上报\",\"cmd_word\":\"36\",\"frame_id\":52496,...}","validationIssues":[{"expected":"number","code":"invalid_type","path":["hotel_id"],"message":"Invalid input: expected number, received string"}]}}
|
||||
```
|
||||
|
||||
## 结论
|
||||
- 数据未入库的直接原因: Kafka 消息在解析阶段触发 Zod 校验失败,`hotel_id` 为字符串类型而非文档要求的 Number,导致 `PARSE_ERROR`,数据库插入流程未执行。
|
||||
|
||||
## 与文档格式的一致性检查
|
||||
对照 [kafka_format.md](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/docs/kafka_format.md):
|
||||
- `hotel_id`: 文档要求 Number,但实测为字符串 (示例: `"2147"`),不一致。
|
||||
- `cmd_word`: 文档要求 `"0x36"`/`"0x0F"`,实测为 `"36"`,不一致。
|
||||
- `control_list`: 文档要求 Array/可选,但实测为 `null`,不一致。
|
||||
- 其余关键字段如 `ts_ms`, `room_id`, `device_id`, `direction`, `udp_raw` 均存在。
|
||||
|
||||
## 已增强的控制台错误输出
|
||||
为了便于定位异常,以下模块已经增加详细错误输出到 PowerShell 控制台:
|
||||
- Kafka 处理异常: 输出 `type`, `stack`, `rawPayload`, `validationIssues`, `dbContext`
|
||||
- 数据库插入异常: 输出 `schema`, `table`, `rowsLength`
|
||||
- Redis 入队与重试异常: 输出详细错误信息
|
||||
|
||||
相关改动文件:
|
||||
- [index.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/index.js)
|
||||
- [databaseManager.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/db/databaseManager.js)
|
||||
- [errorQueue.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/redis/errorQueue.js)
|
||||
|
||||
## 建议修改方向
|
||||
以下为解决无入库问题的可选方案,由你决定是否执行:
|
||||
1. 上游严格按文档输出:
|
||||
- `hotel_id` 改为 Number
|
||||
- `cmd_word` 改为 `"0x36"` / `"0x0F"`
|
||||
- `control_list` 用 `[]` 或省略字段,避免 `null`
|
||||
2. 下游放宽校验并做类型转换:
|
||||
- 将 `hotel_id` 支持字符串并转换为 Number
|
||||
- 继续兼容 `cmd_word = "36"` 的写法
|
||||
- `control_list/device_list/fault_list` 接受 `null` 并转为空数组
|
||||
|
||||
当前代码已兼容 `cmd_word="36"` 和 `control_list=null`,但 `hotel_id` 仍按文档严格要求 Number。
|
||||
Reference in New Issue
Block a user