Wavelog 3.2.4 常用 CLI 命令与工具详解

目标:把 Wavelog 3.2.4 在服务器上的命令行运维面一次讲全——mariadb 客户端直查数据库(含 6 条列名纠错实录)、mariadb-dump 备份与恢复演练(含 ERROR 1044 权限实录)、migrate 迁移端点、cron/run 手动触发与 429 限流对照实验、API v1 十二例与 API v2 十例 curl 全回显、PHP 运行时与日志自检。全部命令与输出均为 2026-11-01 在已部署实例上的实测回显。

WavelogWavelog自托管MariaDB2026/11/01

Wavelog 3.2.4 常用 CLI 命令与工具详解

目标:把 Wavelog 3.2.4 在服务器上的命令行运维面一次讲全——mariadb 客户端直查数据库(含 6 条列名纠错实录)、mariadb-dump 备份与恢复演练(含 ERROR 1044 权限实录)、migrate 迁移端点、cron/run 手动触发与 429 限流对照实验、API v1 十二例与 API v2 十例 curl 全回显、PHP 运行时与日志自检。全部命令与输出均为 2026-11-01 在已部署实例上的实测回显。

一、环境信息

本文所有命令实测于以下环境(版本复核日期 2026-11-01):

  • 应用版本:Wavelog 3.2.4(官方最新稳定版;实例部署过程见站内 《Wavelog 3.2.4 Docker Compose 部署指南》)
  • 技术栈:PHP 8.5.12 / Apache 2.4.69 (Debian) / MariaDB 11.8.9 / 数据库迁移版本 307 / ADIF 3.1.8
  • 栈内网络:10.89.59.0/24,数据库服务主机名 wavelog-324-db(10.89.59.10),应用服务 wavelog-324-main(10.89.59.20),Web 入口 http://127.0.0.1:8137/
  • 数据库坐标:库 wavelog,业务账号 wavelog / q1w2e3r4;数据库 root 初始口令为镜像随机生成(见部署篇一、)
  • Web 管理员:root / q1w2e3r4(呼号 BH1ABC,网格 OM89KK,user_type=99)
  • 本文使用的凭据(均为演示环境生成值,生成过程见三、):v1 API key wl1b5b5e4f61ee5;v2 API token wl2_03892f48d7e67050abbb5edcf64d7d9e624a0cf0(名称 ops-token);cron 访问令牌 3104a9e2d58faefc23760c9f7484786bb32b0933371f01ac729da4c27b3248a6

💡 视角约定:本文按「部署完成后的服务器」视角撰写——数据库命令用 mariadb 客户端连栈内主机名 wavelog-324-db(备份恢复演练在数据库机上以 localhost 执行),HTTP 命令在应用机上用 curl 打 127.0.0.1:8137 或容器内 localhost。四篇分工一句话:选型看 A1、部署看 B2、参数看 C1、命令看本文(A1:《Wavelog 介绍与选型指南》,C1:《Wavelog 3.2.4 配置参数详解》)。

二、工具概览

Wavelog 没有自带专门的管理 CLI 二进制,服务器上的命令行运维面由两类入口构成:数据库客户端直查 + curl 打 HTTP 端点。日常七类操作与对应工具:

工具/入口角色本文章节
mariadb 客户端直查 users/options/cron/QSO 等 54 张表,排障与核对配置4.1
mariadb-dump逻辑备份与恢复演练4.2
curl → /index.php/migrate升级镜像后的数据库 schema 迁移4.3
curl → /index.php/cron/run(+ X-Wavelog-Auth 头)后台任务手动触发与限流观察4.4
curl → /index.php/api/*(v1,key 认证)旧版程序化日志接口:ADIF 导入导出、台站管理4.5
curl → /index.php/api/v2/*(v2,Bearer token + scopes)新版接口:结构化 CRUD 与统计4.6
php -m / php -i / 日志目录PHP 运行时自检与应用/Apache 日志检索4.7

💡 技术拓展:v1 与 v2 是 Wavelog 的两代 API,共存而非替代——

维度v1(/index.php/api/)v2(/index.php/api/v2/)
认证key(JSON body 或 URI 路径)Authorization: Bearer <token>
权限粒度账号级 rw 一把梭scopes 逐项授权(qso:write、radio:write…)
写入格式ADIF 标签串结构化 JSON 字段
响应形态业务 JSON 裸返回{"data":…,"meta":…} 信封
操作面导入/导出/查询GET/POST/PATCH/DELETE 全套

官方文档:v1 见 https://docs.wavelog.org/developer/api/ ,v2 见 https://docs.wavelog.org/developer/api-v2/ 。

三、前置条件

跟读本文实测前需要具备:

  1. 已完成部署并通过初始化向导的 Wavelog 3.2.4 实例(主线见 《Wavelog 3.2.4 Docker Compose 部署指南》);
  2. 服务器上有 mariadb-client(Ubuntu/Debian:apt install mariadb-client,自带 mariadb 与 mariadb-dump)与 curl;
  3. 管理员登录态——实测以 root 账号表单登录(凭据 q1w2e3r4):
login: 303 -> http://127.0.0.1:8137/dashboard
dashboard: 200 url=http://127.0.0.1:8137/index.php/dashboard
<title>Dashboard - Wavelog</title>
  1. 两枚 API 凭据在 Web UI 生成,实测生成即落表:

v1 key(Account → API,提交后 303 回跳):

api/generate: 303 -> http://127.0.0.1:8137/api
id	key	rights	status	creation_date
1	wl1b5b5e4f61ee5	rw	active	2026-11-01 09:41:31

v2 token(Account → API Tokens,勾选 scopes 后生成):

api_token/generate: 303 -> http://127.0.0.1:8137/api
id	token_name	scopes	status	expires_at	created_at
1	ops-token	qso:read,qso:write,station:read,statistic:read,lookup:read	active	2027-11-01 09:41:31	2026-11-01 09:41:31

解读:v1 key 是 wl1 前缀短串、权限只有 rw 一档;v2 token 是 wl2_ 前缀长串,scopes 逐项列出、默认一年过期(2027-11-01)。两枚凭据的完整值只在生成时展示一次,数据库里 v1 只存前缀(见 4.1.3),遗失只能重生成。

四、各工具详解

4.1 mariadb 客户端直查数据库

4.1.1 连接方式与全库 54 表速览

业务账号直连栈内数据库(-h 用栈内主机名或 IP 10.89.59.10 均可):

mariadb -h wavelog-324-db -uwavelog -pq1w2e3r4 wavelog -e "SHOW TABLES;"

实测全库 54 张表(输出节选照抄,空格分隔为原样):

TABLE_HRD_CONTACTS_V01 adif_modes api api_token bandedges bands bandxuser cat club_permissions config contest contest_qsos contest_session cron cwmacros dxcc_entities dxcc_exceptions dxcc_master dxcc_prefixes dxcc_temp dxpedition eQSL_images hams_of_note iota label_templates label_types lotw_certs lotw_users migrations notes oqrs paper_types pota_boundaries pota_directory primary_subdivisions qsl_images qsl_postcard_templates queries satellite satellitemode sota_directory station_logbooks station_logbooks_relationship station_profile themes thirdparty_logins timezones tle user_options users vuccgrids webadif wwff_directory

运维最常打交道的表:

表内容对应 UI/章节
TABLE_HRD_CONTACTS_V01QSO 主日志表(ADIF 列名体系)4.1.4
users / user_options账号与每用户偏好4.1.2
options全局动态配置C1 3.2
cron21 个后台任务定义C1 5.8
api / api_tokenv1 key / v2 token三、
station_profile / station_logbooks / station_logbooks_relationship台站档案、日志簿、二者关联4.5.5
migrationsschema 版本戳(当前 307)4.3
configLotW/eQSL 服务地址常量4.1.5
dxcc_* / *_directory / timezones参考数据(备份体积大头,见 4.2.1)4.2

4.1.2 users 表:照 Cloudlog 习惯写列名会报错(实录)

第一反应按常见命名查询,实测报错:

--------------
SELECT user_id, username, email, callsign, user_level FROM users
--------------

ERROR 1054 (42S22) at line 1: Unknown column 'username' in 'SELECT'

先看真实列名(SHOW COLUMNS FROM users,输出节选实测首尾):

user_id,user_name,user_password,user_email,user_type,clubstation,external_account,user_callsign,user_locator,user_firstname,user_lastname,user_timezone,user_lotw_name,user_lotw_password,user_eqsl_name,user_eqsl_password,user_eqsl_qth_nickname,active_station_logbook,user_language,user_clublog_name,user_clublog_password,user_clublog_callsign,user_measurement_base,user_date_format,user_stylesheet,...,login_attempts,slug,creation_date,last_modified

正确查询实测:

user_id	user_name	user_email	user_callsign	user_type	active_station_logbook	login_attempts
1	root	root@quzuo.net	BH1ABC	99	1	0

解读:user_type=99 即所有者级(对应 config.php auth_level[99]='Administrator',见 C1 5.2);login_attempts=0 可作爆破排查指标;active_station_logbook=1 指向当前活动日志簿——它直接决定 API 统计口径(4.5.5 有对照实验)。

4.1.3 api / api_token 表:v1 key 只落前缀,last_used_at 不存在(实录)

id	key_prefix	rights	status
1	wl1b5b5e	rw	active

v1 key 在库里只存 key_prefix(wl1b5b5e),完整值不可从库反查——这是凭据「展示一次」设计的存储侧证据。想查 token 最近使用时间时踩了第二个坑:

--------------
SELECT id, token_name, LEFT(scopes,60) AS scopes, status, last_used_at FROM api_token
--------------

ERROR 1054 (42S22) at line 1: Unknown column 'last_used_at' in 'SELECT'

api_token 实测可用列为 id/token_name/scopes/status/expires_at/created_at(三、生成时表头),3.2.4 没有记录 last_used_at。

4.1.4 cron / options / QSO 主表:还有三处列名坑(实录)

坑一:cron 表没有 name 列。 按任务名语义查询:

--------------
SELECT * FROM cron WHERE name IN ('master_run','access_token') OR name LIKE '%token%'
--------------

ERROR 1054 (42S22) at line 1: Unknown column 'name' in 'WHERE'

DESCRIBE cron 实测主键是字符串 id(varchar(191),任务名即主键),全结构见 C1 5.8。正确查法与 21 任务开关实测(SELECT id, enabled FROM cron):

clublog_download	0
clublog_upload	0
eqsl_sync	0
hrdlog_upload	0
lotw_lotw_upload	0
qrz_download	0
qrz_upload	0
sync_dcl	0
update_dxcc	1
update_lotw_users	1
update_pota_boundaries	1
update_update_clublog_scp	1
update_update_dok	1
update_update_hamqsl	1
update_update_hamsofnote	0
update_update_pota	1
update_update_sota	1
update_update_tle	0
update_update_wwff	1
update_version_check	0
vucc_grid_file	0

顺带验证了「cron 令牌存在哪」的错误猜想——令牌不落库(全盘搜索 options/user_options/api/api_token 均无命中,派生机制见 C1 5.9)。

坑二:没有 wavelog_options 表。 想当然加了库名前缀式表名:

--------------
SELECT * FROM wavelog_options
--------------

ERROR 1146 (42S02) at line 1: Table 'wavelog.wavelog_options' doesn't exist

真实表名就是 options(11 行全表见 C1 3.2)。

坑三:QSO 表主键不是 PRIMARY_KEY 也不是 id。 两次实测报错:

--------------
SELECT PRIMARY_KEY, COL_CALL, COL_QSO_DATE, COL_TIME_ON, COL_BAND, COL_MODE FROM TABLE_HRD_CONTACTS_V01 ORDER BY PRIMARY_KEY LIMIT 5
--------------

ERROR 1054 (42S22) at line 1: Unknown column 'PRIMARY_KEY' in 'SELECT'
--------------
SELECT id, COL_CALL, COL_BAND, COL_MODE, COL_QSO_DATE FROM TABLE_HRD_CONTACTS_V01 ORDER BY id
--------------

ERROR 1054 (42S22) at line 1: Unknown column 'id' in 'SELECT'

正确列名是 COL_PRIMARY_KEY,修正后实测:

COL_PRIMARY_KEY	COL_CALL	COL_QSO_DATE	COL_TIME_ON	COL_BAND	COL_MODE
1	JA1ABC	2026-11-01	2026-11-01 10:30:00	15m	CW
2	DL1ABC	2026-11-01	2026-11-01 09:15:00	20m	SSB
3	JA1ABC	2026-11-01	2026-11-01 08:00:00	40m	FM

QSO 表实测列名全清单(150+ 列,输出节选实测首尾):

COL_PRIMARY_KEY,COL_ADDRESS,COL_AGE,COL_A_INDEX,COL_ANT_AZ,COL_ANT_EL,COL_ANT_PATH,COL_ARRL_SECT,COL_BAND,COL_BAND_RX,COL_BIOGRAPHY,COL_CALL,COL_CHECK,COL_CLASS,COL_CNTY,...,COL_QSO_DATE,COL_QSO_DATE_OFF,...,COL_SUBMODE,COL_UKSMG,COL_USACA_COUNTIES,COL_VUCC_GRIDS,station_id,COL_WWFF_REF,COL_MY_WWFF_REF,COL_POTA_REF,COL_MY_POTA_REF,COL_MY_DARC_DOK,COL_QRZCOM_QSO_DOWNLOAD_DATE,COL_QRZCOM_QSO_DOWNLOAD_STATUS,COL_CLUBLOG_QSO_DOWNLOAD_DATE,COL_CLUBLOG_QSO_DOWNLOAD_STATUS,COL_DCL_QSLRDATE,COL_DCL_QSL_RCVD,COL_DCL_QSLSDATE,COL_DCL_QSL_SENT,creation_date,last_modified

💡 技术拓展:QSO 表列名 = ADIF 字段名加 COL_ 前缀(COL_CALL↔<CALL>、COL_QSO_DATE↔<QSO_DATE>),表名 TABLE_HRD_CONTACTS_V01 也是 ADIF v01 时代遗产(C1 5.6)。对着 ADIF 规范就能猜出列名,反之猜「通用命名」必踩坑。

纠错实录汇总(六条全部真实触发):

错误写法实测报错正确写法
users.username/email/callsign/user_level1054 Unknown column 'username'user_name/user_email/user_callsign/user_type
api_token.last_used_at1054 Unknown column 'last_used_at'3.2.4 无此列,用 expires_at/created_at
cron.name1054 Unknown column 'name'cron.id(varchar 主键)
表 wavelog_options1146 Table doesn't existoptions
TABLE_HRD_CONTACTS_V01.PRIMARY_KEY1054 Unknown column 'PRIMARY_KEY'COL_PRIMARY_KEY
TABLE_HRD_CONTACTS_V01.id1054 Unknown column 'id'COL_PRIMARY_KEY

⚠️ 注意:Wavelog 各表命名体系互不相同(users 带 user_ 前缀、QSO 表带 COL_ 前缀、cron/options 用通用名)。写任何统计/排障 SQL 前先 DESCRIBE,别继承 Cloudlog 时代或网上教程的旧列名。

4.1.5 config 与 user_options:两张容易和 options 混淆的表

config 表不是用户配置,是外部服务地址常量(实测全表 1 行,节选):

id	lotw_download_url	lotw_upload_url	lotw_rcvd_mark	lotw_login_url	eqsl_download_url	eqsl_rcvd_mark
1	https://lotw.arrl.org/lotwuser/lotwreport.adi	https://lotw.arrl.org/lotwuser/upload	Y	https://lotw.arrl.org/lotwuser/default	https://www.eqsl.cc/qslcard/DownloadInBox.cfm	Y

user_options 是每用户的界面/字段显示偏好(实测节选):

option_name	LEFT(option_value,40)
shown	1
utc_headermenu	true
icon	{"icon":"fas fa-dot-circle","color":"#ff
icon	{"icon":"fas fa-check-circle","color":"#
gridsquare	0
icon	{"icon":"fas fa-home","color":"#0000ff"}
oqrs_grouped_search_show_station_name	on
confirmed	false

三张「配置」表分工:config.php(文件层,C1 五)→ options(全局动态层)→ user_options(每用户偏好层);config 表则基本不动。

4.2 mariadb-dump 备份与恢复演练

4.2.1 全量逻辑备份

在数据库机上执行(业务账号即可,只读导出):

mariadb-dump --single-transaction -h wavelog-324-db -uwavelog -pq1w2e3r4 wavelog > backup-verify.sql; echo "exit=$?"

实测回显:

exit=0
-rw-r--r-- 1 root root 174102551 Nov  1 18:25 backup-verify.sql

备份文件头部(输出节选,实测首尾):

/*M!999999\- enable the sandbox mode */ 
-- MariaDB dump 10.20-11.8.9-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost    Database: wavelog
-- ------------------------------------------------------
-- Server version	11.8.9-MariaDB-ubu2404

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;

解读:日志库只有 3 条 QSO,dump 却有 174MB——体积大头是参考数据表(pota_boundaries 的 GeoJSON、sota_directory/wwff_directory/dxcc_*/timezones 等,见 4.1.1 表清单)。这决定了 Wavelog 备份的两个实操结论:备份文件按百 MB 级预留空间与传输窗口;恢复演练必须做,因为「小业务数据 + 大参考数据」的库最容易在迁移时只拷了前者。

💡 技术拓展:MariaDB 10.20+ 起官方工具改名 mariadb-dump(dump 头 MariaDB dump 10.20-11.8.9 即该工具版本),mysqldump 为兼容别名,两者在本实例等价。备份方式选型:

方式工具特点适用
逻辑备份(本文)mariadb-dump --single-transaction一致性快照不锁 InnoDB、可跨版本、恢复慢个人/俱乐部库,日备
物理备份mariadb-backup快、需同版本环境大库增量
文件级直接拷数据目录必须停库否则损坏不建议

4.2.2 恢复演练第一回合:业务账号建库被拒(ERROR 1044 实录)

把 dump 导入新库 wavelog_restore 做还原验证,用业务账号执行:

--------------
CREATE DATABASE IF NOT EXISTS wavelog_restore CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
--------------

ERROR 1044 (42000) at line 1: Access denied for user 'wavelog'@'%' to database 'wavelog_restore'
ERROR 1044 (42000): Access denied for user 'wavelog'@'%' to database 'wavelog_restore'
restore exit=1

后续核对语句连锁失败(表根本没建起来):

src_tables	54
restore_tables	0
src_qso	3
--------------
SELECT 'restore_qso', COUNT(*) FROM wavelog_restore.TABLE_HRD_CONTACTS_V01
--------------

ERROR 1142 (42000) at line 1: SELECT command denied to user 'wavelog'@'localhost' for table `wavelog_restore`.`TABLE_HRD_CONTACTS_V01`

连清理也被拒:

--------------
DROP DATABASE wavelog_restore
--------------

ERROR 1044 (42000) at line 1: Access denied for user 'wavelog'@'%' to database 'wavelog_restore'
drop exit=1

定位:wavelog 业务账号是镜像初始化时按「仅 wavelog 库」授权创建的(部署篇),对新库无 CREATE/DROP/SELECT 权限——这是最小权限的正常表现,不是缺陷。

4.2.3 恢复演练第二回合:root 重跑成功 + 行数对照

改用数据库 root 账号(初始随机口令见部署篇一、)重跑建库与导入:

create exit=0
restore exit=0

核对脚本实测(源库 vs 恢复库):

src_tables	54
restore_tables	54
src_qso	3
restore_qso	3
src_options	11
restore_options	11

恢复库内抽查 QSO 明细时又踩一次 4.1.4 的 id 列坑(实录见彼处),换 COL_PRIMARY_KEY 后与 4.1.4 回显一致。随后给 wavelog 账号 GRANT 恢复库可见权限,用业务账号复核表数量:

54

演练收尾,root 删除恢复库与授权:

cleanup exit=0

恢复验证四步清单(本文实测顺序):表数量对照 → 关键表行数对照(业务表 + 配置表各选一个)→ 明细抽查(记得用对列名)→ 以生产账号复核权限可见性。

4.3 migrate:升级后的数据库迁移

Wavelog 的 schema 迁移是一个 HTTP 端点,命令行用 curl 触发:

curl -s -w '\nHTTP_CODE=%{http_code}\n' http://127.0.0.1:8137/index.php/migrate

实测回显:

{"status":"success","version":"307"}
HTTP_CODE=200

解读:version=307 与 migrations 表版本戳一致(安装向导阶段同端点回显见部署篇 4.5)。该操作幂等——schema 已在目标版本时重放只返回 success、不改动数据,实测安装后重跑即此结果。运维时机:换新版镜像(如 3.2.4 → 后续版本)拉起后跑一次本命令,即完成「代码随镜像、结构随 migrate」的升级闭环。

⚠️ 注意:迁移前先做 4.2 备份;虽属可重放操作,生产仍建议在反代层限制 /index.php/migrate 的外部访问,只允许本机或运维网段触发。

4.4 cron/run:后台任务触发与 429 限流

4.4.1 调度入口是 cron.d,不是用户 crontab(实录)

按直觉先查 wavelog 用户的 crontab:

crontab -l -u wavelog
no crontab for wavelog

真正的调度片段在系统级 cron.d(实测 cat /etc/cron.d/wavelog):

* * * * * wavelog /usr/bin/curl --silent http://localhost/index.php/cron/run >/dev/null 2>&1

每分钟以 wavelog 身份 curl 一次 cron/run,「哪个任务到期该跑」由应用内 cron 表(4.1.4 的 21 任务)决定——系统 cron 只管敲门,不管敲哪间房。Apache 访问日志实测尾部(每分钟一条 200):

127.0.0.1 - - [01/Nov/2026:10:20:01 +0000] "GET /index.php/cron/run HTTP/1.1" 200 1766 "-" "curl/8.14.1"
127.0.0.1 - - [01/Nov/2026:10:21:01 +0000] "GET /index.php/cron/run HTTP/1.1" 200 1766 "-" "curl/8.14.1"
127.0.0.1 - - [01/Nov/2026:10:22:02 +0000] "GET /index.php/cron/run HTTP/1.1" 200 1766 "-" "curl/8.14.1"
127.0.0.1 - - [01/Nov/2026:10:23:01 +0000] "GET /index.php/cron/run HTTP/1.1" 200 1766 "-" "curl/8.14.1"
127.0.0.1 - - [01/Nov/2026:10:24:01 +0000] "GET /index.php/cron/run HTTP/1.1" 200 1766 "-" "curl/8.14.1"
127.0.0.1 - - [01/Nov/2026:10:25:01 +0000] "GET /index.php/cron/run HTTP/1.1" 200 1766 "-" "curl/8.14.1"

排查「同步任务没跑」的顺序:cron.d 在不在 → 访问日志有没有每分钟的 200 → 应用日志与 cron 表 last_run/next_run。

4.4.2 手动触发:21 任务 due/skipped 全回显

运维排障时常需要立刻催一次任务,带 cron 令牌手动触发(令牌派生机制见 C1 5.9——由 encryption_key HMAC-SHA256 派生、不落库):

curl -s -w '\nHTTP_CODE=%{http_code}\n' \
  -H 'X-Wavelog-Auth: 3104a9e2d58faefc23760c9f7484786bb32b0933371f01ac729da4c27b3248a6' \
  http://127.0.0.1:8137/index.php/cron/run

实测回显(21 任务全清单):

CRON: clublog_download is disabled. skipped..
CRON: clublog_upload is disabled. skipped..
CRON: eqsl_sync is disabled. skipped..
CRON: hrdlog_upload is disabled. skipped..
CRON: lotw_lotw_upload is disabled. skipped..
CRON: qrz_download is disabled. skipped..
CRON: qrz_upload is disabled. skipped..
CRON: sync_dcl is disabled. skipped..
CRON: update_dxcc -> is due: false -> Next Run: 2027-01-01 00:20:00
CRON: update_lotw_users -> is due: false -> Next Run: 2026-11-02 01:10:00
CRON: update_pota_boundaries -> is due: false -> Next Run: 2026-12-01 00:30:00
CRON: update_update_clublog_scp -> is due: false -> Next Run: 2026-11-08 00:00:00
CRON: update_update_dok -> is due: false -> Next Run: 2026-12-01 00:00:00
CRON: update_update_hamqsl -> is due: false -> Next Run: 2026-11-01 10:00:00
CRON: update_update_hamsofnote is disabled. skipped..
CRON: update_update_pota -> is due: false -> Next Run: 2026-12-01 00:15:00
CRON: update_update_sota -> is due: false -> Next Run: 2026-12-01 00:05:00
CRON: update_update_tle is disabled. skipped..
CRON: update_update_wwff -> is due: false -> Next Run: 2026-12-01 00:10:00
CRON: update_version_check is disabled. skipped..
CRON: vucc_grid_file is disabled. skipped..

HTTP_CODE=200

解读:输出只有两种形态——disabled. skipped..(cron 表 enabled=0,与 4.1.4 查询的 9 个 0 值任务一一对应)和 is due: false -> Next Run: <时间>(启用但未到 expression 周期)。Next Run 由表达式推算,例如 update_dxcc 表达式 20 0 1 */2 *(每 2 个月的 1 日 00:20,C1 5.8)对应下次执行 2027-01-01 00:20:00。

4.4.3 429 限流:与令牌无关的 30 秒锁(对照实验实录)

紧接着再触发一次(不带任何头):

no-token HTTP_CODE=429

响应体实测:

CRON: rate limit active, skipping. Try again in a few seconds.

HTTP_CODE=429

第一反应是「429 因为没带令牌」——对照实验证伪了这个猜想。同一 30 秒窗口内:

场景实测结果
窗口外、无头触发no-header HTTP_CODE=200(4.4.2 即此情形,全量执行)
窗口内、带正确令牌with-token HTTP_CODE=429
窗口内、带错误令牌bad-token HTTP_CODE=429
窗口内、X-Wavelog-Auth 空值X-Wavelog-Auth empty HTTP_CODE=429

结论:cron/run 主入口不校验令牌,只受 30 秒缓存限流锁约束(源码实测与机制解读见 C1 5.9:锁键 cron_run_rate_limit 跑在 cache_adapter 上)。手动触发返回 429 不是故障,等窗口过期即可;每分钟 cron.d 的节奏恰好贴着安全线,这也是官方不建议把系统 cron 加密到亚分钟的原因。

4.4.4 子端点验令牌:X-Wavelog-Auth 的真实用场

令牌防的是直接打各同步子端点。实测 cronauth_allowed 在源码中的调用点:

/var/www/html/application/controllers/Hrdlog.php:26:		if (!cronauth_allowed(3)) {
/var/www/html/application/controllers/Clublog.php:31:		if (!cronauth_allowed(3)) {
/var/www/html/application/controllers/Lotw.php:178:		if (!cronauth_allowed(3)) {
/var/www/html/application/controllers/Eqsl.php:628:		if (!cronauth_allowed(3)) {
/var/www/html/application/controllers/Qrz.php:74:		if (!cronauth_allowed(3)) {
/var/www/html/application/controllers/Qrz.php:398:		if (!cronauth_allowed(3)) {
/var/www/html/application/controllers/Update.php:20:		if (!cronauth_allowed(99)) {

即业务同步类端点要求 3 级、数据更新类(Update)要求 99 级,凭 X-Wavelog-Auth 头或管理员会话放行。安装向导最后一步就是带头手动拉了一次 DXCC 数据(部署篇 4.5):

curl -s -H "X-Wavelog-Auth: 3104a9e2d58faefc23760c9f7484786bb32b0933371f01ac729da4c27b3248a6" \
  http://127.0.0.1:8137/index.php/update/dxcc

⚠️ 注意:令牌 = HMAC(encryption_key),改 key 等于换令牌——如果外部监控系统(非本实例 cron.d)直接调子端点,换 key 后必须同步更新其配置(C1 7.2)。

4.5 API v1:key 认证与 12 例接口实测

v1 端点族挂 /index.php/api/ 下,key 有两种携带形态:POST 时放 JSON body 的 "key" 字段,GET 类放 URI 路径段(/api/statistics/<key>)。以下 12 例按调用顺序照抄实测回显(时间戳 2026-11-01 09:44–09:45 UTC)。

4.5.1 version:正反例

curl -s -w '\nHTTP_CODE=%{http_code}\n' -X POST http://127.0.0.1:8137/index.php/api/version \
  -H 'Content-Type: application/json' -d '{"key":"wl1b5b5e4f61ee5"}'
--- v1 version (ok) ---
{"status":"ok","version":"3.2.4"}
HTTP_CODE=200
--- v1 version (bad) ---
{"status":"failed","reason":"missing or invalid api key"}
HTTP_CODE=401

4.5.2 statistics 与 station_info(key 在 URI)

--- v1 statistics (key in URI) ---
{"Today":0,"total_qsos":0,"month_qsos":0,"year_qsos":0}
HTTP_CODE=201
--- v1 station_info (key in URI) ---
[{"station_id":"1","station_profile_name":"Beijing","station_gridsquare":"OM89KK","station_callsign":"BH1ABC","station_active":"1","station_uuid":"49780306-d5d8-11f1-a360-569a53790263","station_city":"Beijing","station_dxcc":"27","station_cq":"1","station_itu":"1","station_country":"BELARUS", "...":"..."}]
HTTP_CODE=200

解读:此刻库内还没有 QSO,statistics 全 0;station_info 返回安装向导创建的唯一台站(空字段已折叠,输出节选)。注意 statistics 返回 201 而非 200——v1 的部分 GET 端点也回 201,写监控脚本按 2xx 判定,别硬编码 200。

4.5.3 create_station:JSON 数组建台站

请求体(station.json 原文):

[
  {
    "station_profile_name": "Beijing Home QTH",
    "station_callsign": "BH1ABC",
    "station_gridsquare": "OM89KK",
    "station_city": "Beijing",
    "station_dxcc": "27",
    "station_cnty": "China",
    "station_cq": "24",
    "station_itu": "44"
  }
]
--- v1 create_station ---
{"status":"success","message":"1 locations imported."}
HTTP_CODE=201
--- v1 station_info after create ---
[{"station_id":"1","station_profile_name":"Beijing","station_active":"1","...":"..."},{"station_id":"2","station_profile_name":"Beijing Home QTH","station_gridsquare":"OM89KK","station_callsign":"BH1ABC","station_active":"0","station_city":"Beijing","station_dxcc":"27","station_cnty":"China","station_cq":"24","station_itu":"44","...":"..."}]
HTTP_CODE=200

解读:新台站 station_id=2 入库但 station_active=0——API 建的台站默认不激活,这个细节在 4.5.5 会引出统计口径的坑。

4.5.4 qso:ADIF 导入(400 实录 → 修正 → 201 → 重复 400)

首试构造了一段缺日期字段的 ADIF 串(<CALL:6>DL1ABC<EOM><BAND:3>20m<EOM><MODE:3>SSB<EOM>...,无 <QSO_DATE>):

--- v1 qso (first) ---
{"status":"abort","type":"adif","string":"","adif_count":1,"adif_errors":1,"messages":["","You tried to import a QSO without valid date. This QSO wasn&#039;t imported. It&#039;s invalid. Call: DL1ABC, Mode:  Band: <br>"]}
HTTP_CODE=400

服务端应用日志同步留痕(application/logs/log-2026-11-01.php 实测内容):

<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>

ERROR - 2026-11-01 09:44:18 --> Trying to import QSO with invalid date:  for station_id 2. Call: DL1ABC Mode:  Band: 
ERROR - 2026-11-01 09:44:18 --> Trying to import QSO with invalid date:  for station_id 2. Call: DL1ABC Mode:  Band: 

补齐日期后重跑(完整 ADIF 串:<CALL:6>DL1ABC<EOM><BAND:3>20m<EOM><MODE:3>SSB<EOM><FREQ:7>14.2360<EOM><QSO_DATE:8>20261101<EOM><TIME_ON:6>091500<EOM><RST_SENT:3>599<EOM><RST_RCVD:3>599<EOM><COMMENT:9>api test<EOM>,POST 到 /index.php/api/qso,station_profile_id=2):

--- v1 qso first import ---
{"status":"created","type":"adif","string":"","adif_count":1,"adif_errors":0,"messages":[""]}
HTTP_CODE=201
--- v1 qso duplicate import ---
{"status":"abort","type":"adif","string":"","adif_count":1,"adif_errors":1,"messages":["","Date\/Time: 2026-11-01 09:15:00 Callsign: DL1ABC Band: 20m Duplicate for BH1ABC<br>"]}
HTTP_CODE=400

错误 key 的对照:

--- v1 qso (badkey) ---
{"status":"failed","reason":"missing or wrong api key"}
HTTP_CODE=401

解读:qso 端点三连——格式错(缺必填字段)400 + adif_errors 计数、重复导入 400 + Duplicate 明细、key 错 401。去重键是「时间+呼号+波段」组合;400 与 401 的响应体结构完全不同(status:abort/messages vs status:failed/reason),脚本按此分流。

💡 技术拓展:ADIF 标签语法 <字段名:长度>值,如 <CALL:6>DL1ABC;记录头以 <EOH> 结束、记录体以 <EOR> 结束。长度前缀必须准确(<QSO_DATE:8>20261101),长度错了后续字段全部错位——这是手工拼 ADIF 最常见的翻车点,能用库生成就别手拼。

4.5.5 export、check、radio 与统计口径实验

导出(get_contacts_adif,fetchfromid 增量语义):

--- v1 get_contacts_adif (export) ---
{"status":"successful","message":"No new QSOs available.","lastfetchedid":0,"exported_qsos":0,"adif":null}
HTTP_CODE=200

导入两条后重跑(输出节选,ADIF 正文只留头尾):

{"status":"successful","message":"Export successful","lastfetchedid":"2","exported_qsos":2,"adif":"Wavelog ADIF export\n<ADIF_VER:5>3.1.8\n<PROGRAMID:7>Wavelog\r\n<PROGRAMVERSION:5>3.2.4\r\n<EOH>\n\n<BAND:3>15m\r\n<CALL:6>JA1ABC\r\n...<EOR>\r\n\r\n<BAND:3>20m\r\n<CALL:6>DL1ABC\r\n...<EOR>\r\n\r\n"}
HTTP_CODE=200

呼号查重(logbook_check_callsign,按公共日志簿 slug 查询;slug 由 station_logbooks.public_slug 提供,实测 UPDATE station_logbooks SET public_slug='beijing-demo', public_search=1 WHERE logbook_id=1; 等价 Web UI 的 Logbooks 编辑):

--- v1 logbook_check_callsign (check) ---
{"callsign":"DL1ABC","result":"Not Found"}
HTTP_CODE=201

(导入 JA1ABC 后查 JA1ABC:{"callsign":"JA1ABC","result":"Found"};DL1ABC 因导入落在 station 2 仍 Not Found——查询作用域是公共日志簿关联的 station 1。)

电台上报(radio):

--- v1 radio (radio) ---
{"status":"success"}
HTTP_CODE=200

统计口径实验:导入 station 2 两条后 statistics 纹丝不动:

--- v1 statistics after import ---
{"Today":0,"total_qsos":0,"month_qsos":0,"year_qsos":0}
HTTP_CODE=201

查关联表实测:

logbook_relation_id	station_logbook_id	station_location_id	modified	creation_date	last_modified
1	1	1	2026-11-01 09:40:33	2026-11-01 09:40:34	2026-11-01 09:40:34
station_id	station_active
1	1
2	0

把 QSO 导入 station 1(active)后:

--- import to station 1 (active) ---
{"status":"created","type":"adif","string":"","adif_count":1,"adif_errors":0,"messages":[""]}
HTTP_CODE=201
--- statistics ---
{"Today":1,"total_qsos":1,"month_qsos":1,"year_qsos":1}
HTTP_CODE=201

结论:v1 statistics 只统计活动台站/活动日志簿(users.active_station_logbook=1,4.1.2)口径;create_station 建的新台站默认 inactive,往它导数据会出现「导入成功但统计为 0」的假丢失。数据在库里(4.1.4 的 QSO 抽查 3 行可证),切回活动台站即可见。

v1 十二例小结:

端点方法实测结果HTTP
/api/versionPOST body keyok / bad key 401200 / 401
/api/statistics/<key>GET URI key全 0 → 口径实验见 4.5.5201
/api/station_info/<key>GET URI key1 台站 → 2 台站200
/api/create_station/<key>POST JSON 数组1 locations imported.201
/api/qsoPOST ADIF 串缺日期 400 / 成功 201 / 重复 400 / 坏 key 401400/201/401
/api/get_contacts_adifPOST空导出 → 导出 2 条200
/api/logbook_check_callsignPOSTNot Found / Found201
/api/radioPOST{"status":"success"}200

4.6 API v2:Bearer token、scopes 与 CRUD 实测

v2 端点族挂 /index.php/api/v2/,统一 Authorization: Bearer <token>,响应是 {"data":…,"meta":…} 信封。以下 10 例 + 4 例修正实测于 09:44–09:45 UTC。

4.6.1 服务状态与未认证探测

--- v2 root (no auth) ---
{"data":{"name":"Wavelog API","status":"ok"},"meta":{"timestamp":"2026-11-01T09:44:18+00:00","resource":"status","method":"GET"}}
HTTP_CODE=200
--- v2 token/whoami no-auth ---
{"error":{"code":"unauthorized","message":"Missing API token"}}
HTTP_CODE=401

解读:/api/v2/ 状态端点免认证,可作监控探活;业务端点缺 token 直接 401 unauthorized。带 token 查自身:

curl -s -H "Authorization: Bearer wl2_03892f48d7e67050abbb5edcf64d7d9e624a0cf0" \
  http://127.0.0.1:8137/index.php/api/v2/token
--- v2 GET token ---
{"data":{"id":2,"name":"ops-token","owner":"BH1ABC","user_id":1,"scopes":["qso:read","qso:write","station:read","statistic:read","lookup:read"],"expires_at":"2027-11-01 09:41:42"},"meta":{"timestamp":"2026-11-01T09:44:18+00:00","resource":"token","method":"GET"}}
HTTP_CODE=200

(token 行 id 与三、生成时不同,系演练中重建;scopes 与有效期与生成时一致。)

4.6.2 station 与 qso 读取

--- v2 GET station ---
{"data":[{"id":1,"uuid":"49780306-d5d8-11f1-a360-569a53790263","name":"Beijing","callsign":"BH1ABC","gridsquare":"OM89KK","city":"Beijing","dxcc":27,"cq":1,"itu":1,"active":true,"clublog_upload":"on","...":"..."},{"id":2,"uuid":"cf0c1033-d5d8-11f1-a360-569a53790263","name":"Beijing Home QTH","callsign":"BH1ABC","gridsquare":"OM89KK","city":"Beijing","dxcc":27,"cq":24,"itu":44,"cnty":"China","active":false,"clublog_upload":"disabled","...":"..."}],"meta":{"timestamp":"2026-11-01T09:44:18+00:00","resource":"station","method":"GET"}}
HTTP_CODE=200
--- v2 GET qso?per_page=3 ---
{"data":[],"meta":{"timestamp":"2026-11-01T09:44:19+00:00","resource":"qso","method":"GET","page":1,"per_page":3,"count":0,"total":0,"total_pages":0,"has_more":false}}
HTTP_CODE=200

解读:v2 的 station 视图直接给出 active 布尔与 uuid;qso 列表带分页 meta(page/per_page/count/total/has_more)。此刻 v1 已导入的 2 条 QSO 在 v2 列表仍为空——与 4.5.5 同一口径(活动日志簿)。

4.6.3 写入:结构化 JSON 建 QSO

--- v2 POST qso ---
{"data":{"id":1,"station_id":2,"qso_date":"2026-11-01 10:30:00","mode":"CW","freq":"21020000","call":"JA1ABC","band":"15m","rst_sent":"599","rst_rcvd":"579","cqz":25,"...":"..."},"meta":{"timestamp":"2026-11-01T09:44:19+00:00","resource":"qso","method":"POST"}}
HTTP_CODE=201

请求体是普通 JSON({"station_profile_id":2,"call":"JA1ABC","band":"15m","mode":"CW","qso_date":"20261101","time_on":"103000","rst_sent":"599","rst_rcvd":"579"}),不用拼 ADIF 长度前缀——v2 写 QSO 比 v1 省心之处。随后统计与查询:

--- v2 GET statistic ---
{"data":{"qso":{"total":1,"activity":{"today":1,"month":1,"year":1},"breakdown":{"by_band":[{"band":"15m","count":1}],"by_mode":[{"mode":"CW","count":1}]},"dxcc":{"worked":1,"confirmed":0,"available":340,"deleted":{"worked":0,"confirmed":0,"confirmed_paper":0,"confirmed_lotw":0}}}},"meta":{"timestamp":"2026-11-01T09:44:19+00:00","resource":"statistic","method":"GET","profile":"qso","admin":true}}
HTTP_CODE=200
--- v2 GET lookup?callsign=DL1ABC ---
{"data":{"callsign":"DL1ABC","dxcc":"FEDERAL REPUBLIC OF GERMANY","dxcc_id":"230","dxcc_cqz":"14","cont":"EU","workedBefore":false,"lotw_member":false,"...":"..."},"meta":{"timestamp":"2026-11-01T09:44:19+00:00","resource":"lookup","method":"GET","detail":"basic"}}
HTTP_CODE=200
--- v2 GET qso ---
{"data":[{"id":1,"station_id":2,"qso_date":"2026-11-01 10:30:00","mode":"CW","call":"JA1ABC","band":"15m","rst_sent":"599","rst_rcvd":"579","cqz":25,"...":"..."}],"meta":{"timestamp":"2026-11-01T09:44:19+00:00","resource":"qso","method":"GET","page":1,"per_page":50,"count":1,"total":1,"total_pages":1,"has_more":false}}
HTTP_CODE=200

(超长字段以 "...":"..." 折叠,输出节选;statistic 的 total:1 同样只算活动口径。)

4.6.4 scope 边界:radio 403、PATCH 200、DELETE 403

POST radio 被拒(ops-token 没申请 radio scope):

--- v2 POST radio ---
{"error":{"code":"insufficient_scope","message":"Token is missing the required scope: radio:write","details":{"required_scope":"radio:write"}}}
HTTP_CODE=403

修改与删除(09:45:21 UTC 实测):

--- v2 PATCH qso/1 ---
{"data":{"id":1,"station_id":2,"qso_date":"2026-11-01 10:30:00","mode":"CW","call":"JA1ABC","band":"15m","comment":"updated via v2 API","...":"..."},"meta":{"timestamp":"2026-11-01T09:45:21+00:00","resource":"qso","method":"PATCH"}}
HTTP_CODE=200
--- v2 DELETE qso/1 (expect insufficient_scope) ---
{"error":{"code":"insufficient_scope","message":"Token is missing the required scope: qso:delete","details":{"required_scope":"qso:delete"}}}
HTTP_CODE=403
--- v2 GET qso/1 ---
{"data":{"id":1,"station_id":2,"qso_date":"2026-11-01 10:30:00","mode":"CW","call":"JA1ABC","band":"15m","comment":"updated via v2 API","...":"..."},"meta":{"timestamp":"2026-11-01T09:45:21+00:00","resource":"qso","method":"GET"}}
HTTP_CODE=200
--- v2 bad token ---
{"error":{"code":"invalid_token","message":"Invalid or revoked API token"}}
HTTP_CODE=401

解读:v2 的权限模型在实测里完整现形——qso:write 覆盖 PATCH 但不覆盖 DELETE(删除要 qso:delete);错误分类清晰:缺 scope 是 403 insufficient_scope(带 details.required_scope,脚本可自动提示补哪个 scope),token 被吊销/无效是 401 invalid_token,没带是 401 unauthorized。PATCH 后立即 GET 回读,comment 已更新,写读一致。

4.7 PHP 运行时与日志自检

4.7.1 php -m 与 php -i

php -m | grep -E 'apcu|curl|gd|mbstring|mysqli|openssl|session'

实测(关键模块全部就位):

apcu
curl
gd
json
mbstring
mysqli
openssl
session
php -i | grep -E 'max_execution_time|memory_limit|post_max_size|upload_max_filesize'

实测生效值:

max_execution_time => 0 => 0
memory_limit => 256M => 256M
post_max_size => 64M => 64M
upload_max_filesize => 64M => 64M

解读:max_execution_time=0 保证大 ADIF 导入不被 PHP 时限掐断;memory_limit=256M 与上传 64M 是镜像默认,调法见 C1 3.4/六。mysqli/apcu 缺失会分别导致数据库连不上与缓存层报错(cron 限流锁依赖缓存,C1 5.4)。

4.7.2 应用日志与 Apache 日志

ls -l /var/www/html/application/logs/

实测(运行一天后):

total 24
drwxrwxr-x 1 wavelog  www-data  4096 Nov  1 09:44 .
-rw-rw-r-- 1 wavelog  www-data   114 Oct 21 19:05 index.html
-rw-r--r-- 1 www-data www-data  1323 Nov  1 09:40 installer_debug.log
-rw-r--r-- 1 www-data www-data   310 Nov  1 09:44 log-2026-11-01.php

log-2026-11-01.php 内容见 4.5.4(两条 ERROR 与 API 400 一一对应);installer_debug.log 是向导留档。Apache 侧实测:

lrwxrwxrwx 1 www-data www-data 11 Oct 21 15:47 /var/log/apache2/access.log -> /dev/stdout

解读:access log 符号链接到 /dev/stdout,容器化形态下日志从标准输出走(docker logs 即入口,部署篇五);排障三层各司其职——接口错误看响应体、应用错误看 application/logs/、请求流水看 access 日志。配置文件规模自检(wc -l,呼应 C1 一、):

  948 /var/www/html/application/config/docker/config.php
   98 /var/www/html/application/config/docker/database.php
 1046 total

五、注意事项

  • ⚠️ 先 DESCRIBE 再写 SQL:本文 4.1 实测踩中 6 处列名/表名坑(username、name、PRIMARY_KEY、id、last_used_at、wavelog_options),网上旧教程的 Cloudlog 式列名在 Wavelog 3.2.4 会直接报错。
  • ⚠️ 业务账号没有 DDL:wavelog 账号仅对 wavelog 库有权限,建库/恢复/授权必须数据库 root(ERROR 1044 实录见 4.2.2);恢复库验证完及时 DROP 并回收 GRANT。
  • ⚠️ 备份体积与业务数据量不成正比:3 条 QSO 的库 dump 出 174MB(参考数据表占绝对大头,4.2.1),容量规划与恢复演练都按这个量级来。
  • ⚠️ 429 是限流不是鉴权:cron/run 30 秒锁窗口内带对令牌也 429(4.4.3 对照实验);别为此重启服务,等窗口过期即可。外部系统直接调同步子端点才需要 X-Wavelog-Auth 令牌。
  • ⚠️ v1 key 走 URI 形态会进访问日志:/api/statistics/<key> 的 key 明文出现在 access log(4.4.1 日志同款位置);生产优先 JSON body 形态并上 HTTPS。
  • ⚠️ v2 token 按最小 scope 签发并设过期:ops-token 实测无 qso:delete/radio:write,误删/误控被 403 兜住(4.6.4);吊销即时生效(401 invalid_token)。
  • ⚠️ 统计口径绑定活动日志簿:往 inactive 台站导数据会出现「导入 201 但 statistics 为 0」的假丢失(4.5.5),排障先查 station_active 与 station_logbooks_relationship。
  • ⚠️ 改 encryption_key 的连带影响:cron 令牌随 key 变化(不落库、HMAC 派生,C1 5.9),外部 crontab/监控若直连子端点需同步换令牌。

六、常见问题

Q1:手动触发 cron/run 返回 429,任务是不是挂了?

不是。响应体 CRON: rate limit active, skipping. 是 30 秒限流锁(4.4.3 对照实验:窗口内带正确令牌同样 429,窗口外无头 200)。等 30 秒再触发;cron.d 每分钟一敲已贴安全线。

Q2:crontab -l -u wavelog 查不到任务?

Wavelog 用系统级片段 /etc/cron.d/wavelog(4.4.1 实测 no crontab for wavelog + cron.d 行原文),不在任何用户 crontab 里。

Q3:恢复备份时报 ERROR 1044?

业务账号无建库权限(4.2.2 实录)。用数据库 root 建库导入,或先 GRANT 再导;恢复验证按 4.2.3 四步清单走。

Q4:SQL 查询各种 Unknown column / Table doesn't exist?

对照 4.1 纠错汇总表:users 列带 user_ 前缀、cron 主键是 id、QSO 主键是 COL_PRIMARY_KEY、配置表叫 options。统一办法:SHOW TABLES; + DESCRIBE <表>; 先行。

Q5:v1 导入 QSO 返回 400 You tried to import a QSO without valid date?

ADIF 串缺 <QSO_DATE>/<TIME_ON>(4.5.4 实录)。补齐后 201 created;服务端 application/logs/ 有同时间戳 ERROR 留痕可交叉核对。

Q6:v2 返回 403 insufficient_scope?

token 缺对应 scope,响应体 details.required_scope 直接点名要哪个(radio:write、qso:delete 实测两例,4.6.4)。到 Web UI 重建 token 时勾上所需 scope。

Q7:v1/v2 的 401 怎么区分原因?

v1:missing or invalid api key(version 探测)/missing or wrong api key(qso 写入)——key 错或已删。v2:unauthorized(没带 Bearer)vs invalid_token(带了但被吊销/无效,4.6.4 实测)。

Q8:导入明明返回 201,statistics 却是 0?

导到了 inactive 台站。4.5.5 实验:station 2 两条 → 统计 0;station 1 一条 → 统计 1。数据没丢(4.1.4 QSO 抽查 3 行),切活动台站或改 station_profile_id。

Q9:v1 导出返回 No new QSOs available.?

fetchfromid 增量语义:该 id 之后无新 QSO 时即此响应(4.5.5 首测)。全量导出把 fetchfromid 置 0。

Q10:升级新版镜像后要做点什么?

curl http://127.0.0.1:8137/index.php/migrate 跑一次 schema 迁移(4.3,幂等、回显目标版本号);迁移前备份(4.2),迁移后核对 migrations 版本戳与界面正常。

七、参考链接

Wavelog 3.2.4 常用 CLI 命令与工具详解 - Quzuo