fix: 修复重构本地闭环契约

This commit is contained in:
sunlei 2026-06-15 07:03:36 +08:00
parent cf89157fd6
commit fd22836c23
9 changed files with 505 additions and 112 deletions

View File

@ -12,12 +12,12 @@
| Domain | Tables | Owner Batch | Notes |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Admin Identity | `admin_user`, `admin_role`, `admin_permission`, `admin_menu`, `admin_department`, `admin_user_role`, `admin_role_permission`, `admin_role_menu` | Batch 2 | Login, menu, role, permission, department. |
| Platform Config | `platform_dict_group`, `platform_dict_item`, `platform_component_template`, `platform_setting` | Batch 2 | Dict and component template split from legacy Admin misc. |
| Blog Content | `blog_post`, `blog_taxonomy`, `blog_term`, `blog_post_term`, `blog_theme_profile`, `blog_import_job` | Batch 3 | Categories and tags use relation table. |
| Admin Identity | `admin_user`, `admin_role`, `admin_permission`, `admin_menu`, `admin_dept`, `admin_department`, `admin_user_role`, `admin_role_permission`, `admin_role_menu` | Batch 2 | Runtime login/menu/role/dept tables plus future permission and department boundary tables. |
| Platform Config | `admin_dict`, `admin_component`, `platform_dict_group`, `platform_dict_item`, `platform_component_template`, `platform_setting` | Batch 2 | Runtime Admin dict/component compatibility plus new platform dict/component template split. |
| Blog Content | `blog_post`, `blog_article`, `blog_taxonomy`, `blog_term`, `blog_post_term`, `blog_theme_profile`, `blog_theme_config`, `blog_import_job` | Batch 3 | `blog_article`/`blog_theme_config` keep current runtime compatibility; `blog_term` carries both runtime category/tag fields and nullable taxonomy planning fields. |
| WordPress Mirror | `wordpress_site`, `wordpress_auth_session`, `wordpress_remote_post`, `wordpress_remote_term`, `wordpress_sync_job`, `wordpress_sync_mapping` | Batch 3 | Remote state separate from local Blog content. |
| Asset | `asset_bucket`, `asset_object`, `asset_reference`, `asset_access_grant` | Batch 3 | MinIO object ownership and access grant. |
| System Event | `system_notice`, `system_event`, `system_event_dedupe`, `system_event_delivery` | Batch 2 | MySQL stores actionable events; Loki remains log query source. |
| System Event | `admin_notice`, `system_notice`, `system_event`, `system_event_dedupe`, `system_event_delivery` | Batch 2 | Admin notices and MySQL actionable events; Loki remains log query source. |
| Runtime Evidence | `runtime_evidence_index` | Batch 1 | Safe index only, no large logs or secrets. |
| QQBot Core | `qqbot_account`, `qqbot_account_ability`, `qqbot_connection_session`, `qqbot_capability_binding`, `qqbot_permission_policy`, `qqbot_allowlist`, `qqbot_blocklist`, `qqbot_command`, `qqbot_command_alias`, `qqbot_command_log`, `qqbot_config`, `qqbot_rule`, `qqbot_conversation`, `qqbot_message`, `qqbot_send_task`, `qqbot_send_log`, `qqbot_dedupe_event`, `qqbot_dedupe`, `qqbot_account_napcat`, `qqbot_napcat_container` | Batch 4 | Account, connection, permission, command, message, send queue, and transitional registered-entity compatibility tables until Batches 5-7 replace plugin/NapCat internals. |
| NapCat Runtime | `napcat_container`, `napcat_device_identity`, `napcat_account_binding`, `napcat_login_session`, `napcat_login_challenge`, `napcat_runtime_cleanup` | Batch 7 | Device identity and login challenge state. |

View File

@ -15,16 +15,27 @@ $schema = Join-Path $root "sql\refactor-v3\00-full-schema.sql"
$seed = Join-Path $root "sql\refactor-v3\01-seed-core.sql"
$verify = Join-Path $root "sql\refactor-v3\99-verify.sql"
function Invoke-Mysql {
param(
[Parameter(Mandatory = $true)][string[]]$Arguments
)
mysql --default-character-set=utf8mb4 @Arguments
if ($LASTEXITCODE -ne 0) {
throw "mysql failed with exit code $LASTEXITCODE"
}
}
function Invoke-MysqlSource {
param(
[Parameter(Mandatory = $true)][string]$Path
)
$sourcePath = (Resolve-Path -LiteralPath $Path).Path.Replace("\", "/")
mysql -h $HostName -P $Port -u $User $Database --execute="source $sourcePath"
Invoke-Mysql -Arguments @("-h", $HostName, "-P", "$Port", "-u", $User, $Database, "--execute=source $sourcePath")
}
mysql -h $HostName -P $Port -u $User -e "DROP DATABASE IF EXISTS ``$Database``; CREATE DATABASE ``$Database`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
Invoke-Mysql -Arguments @("-h", $HostName, "-P", "$Port", "-u", $User, "-e", "DROP DATABASE IF EXISTS ``$Database``; CREATE DATABASE ``$Database`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;")
Invoke-MysqlSource -Path $schema
Invoke-MysqlSource -Path $seed
Invoke-MysqlSource -Path $verify

View File

@ -1,5 +1,5 @@
param(
[string]$BaseUrl = "http://127.0.0.1:5320"
[string]$BaseUrl = "http://127.0.0.1:48085"
)
$ErrorActionPreference = "Stop"

View File

@ -1,22 +1,147 @@
CREATE TABLE IF NOT EXISTS admin_user (
CREATE TABLE IF NOT EXISTS admin_menu (
id BIGINT NOT NULL PRIMARY KEY,
username VARCHAR(64) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_admin_user_username (username)
pid BIGINT NOT NULL DEFAULT 0,
name VARCHAR(120) NOT NULL,
path VARCHAR(255) NULL,
component VARCHAR(255) NULL,
redirect VARCHAR(255) NULL,
auth_code VARCHAR(120) NULL,
type VARCHAR(32) NOT NULL DEFAULT 'menu',
meta LONGTEXT NULL,
status INT NOT NULL DEFAULT 1,
sort INT NOT NULL DEFAULT 0,
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY uk_admin_menu_name (name),
KEY idx_admin_menu_pid (pid),
KEY idx_admin_menu_path (path),
KEY idx_admin_menu_auth_code (auth_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_role (
id BIGINT NOT NULL PRIMARY KEY,
role_key VARCHAR(64) NOT NULL,
role_name VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_admin_role_key (role_key)
role_code VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
remark VARCHAR(255) NOT NULL DEFAULT '',
status INT NOT NULL DEFAULT 1,
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY uk_admin_role_code (role_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_user (
id BIGINT NOT NULL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
real_name VARCHAR(255) NOT NULL,
avatar VARCHAR(1024) NOT NULL DEFAULT '',
dept_id BIGINT NULL,
home_path VARCHAR(255) NOT NULL DEFAULT '',
timezone VARCHAR(255) NOT NULL DEFAULT 'Asia/Shanghai',
status INT NOT NULL DEFAULT 1,
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY uk_admin_user_username (username),
KEY idx_admin_user_dept_id (dept_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_dept (
id BIGINT NOT NULL PRIMARY KEY,
pid BIGINT NOT NULL DEFAULT 0,
name VARCHAR(255) NOT NULL,
status INT NOT NULL DEFAULT 1,
remark VARCHAR(255) NOT NULL DEFAULT '',
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_admin_dept_pid (pid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_dict (
id BIGINT NOT NULL PRIMARY KEY,
dict_code VARCHAR(255) NOT NULL,
label VARCHAR(255) NOT NULL,
value VARCHAR(255) NOT NULL,
children_code VARCHAR(255) NULL,
sort INT NOT NULL DEFAULT 0,
status INT NOT NULL DEFAULT 1,
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY uk_admin_dict_code_value (dict_code, value),
KEY idx_admin_dict_code (dict_code),
KEY idx_admin_dict_children_code (children_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_component (
id BIGINT NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL DEFAULT '',
type INT NOT NULL,
component_type INT NOT NULL,
image MEDIUMTEXT NOT NULL,
template MEDIUMTEXT NOT NULL,
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_admin_component_type (type),
KEY idx_admin_component_component_type (component_type),
KEY idx_admin_component_deleted (is_deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_notice (
id BIGINT NOT NULL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content LONGTEXT NOT NULL,
summary TEXT NULL,
level INT NOT NULL DEFAULT 1,
status INT NOT NULL DEFAULT 1,
severity VARCHAR(16) NOT NULL DEFAULT 'info',
source VARCHAR(64) NOT NULL DEFAULT 'system',
event_type VARCHAR(120) NOT NULL DEFAULT 'system.event',
dedupe_key VARCHAR(255) NULL,
occurrence_count INT NOT NULL DEFAULT 1,
notify_role_code VARCHAR(64) NOT NULL DEFAULT 'super',
metadata JSON NULL,
is_top TINYINT NOT NULL DEFAULT 0,
notify_users TEXT NULL,
created_by BIGINT NULL,
is_deleted TINYINT NOT NULL DEFAULT 0,
active_dedupe_key VARCHAR(255) GENERATED ALWAYS AS (CASE WHEN is_deleted = 0 AND dedupe_key IS NOT NULL THEN dedupe_key ELSE NULL END) VIRTUAL,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
first_seen_at DATETIME NULL,
last_seen_at DATETIME NULL,
UNIQUE KEY uk_admin_notice_active_dedupe_key (active_dedupe_key),
KEY idx_admin_notice_status (status),
KEY idx_admin_notice_severity (severity),
KEY idx_admin_notice_source_event (source, event_type),
KEY idx_admin_notice_dedupe_key (dedupe_key),
KEY idx_admin_notice_notify_role (notify_role_code),
KEY idx_admin_notice_is_top (is_top),
KEY idx_admin_notice_is_deleted (is_deleted),
KEY idx_admin_notice_create_time (create_time),
KEY idx_admin_notice_last_seen (last_seen_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_user_role (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id),
KEY idx_admin_user_role_role_id (role_id),
CONSTRAINT fk_admin_user_role_user FOREIGN KEY (user_id) REFERENCES admin_user (id) ON DELETE CASCADE,
CONSTRAINT fk_admin_user_role_role FOREIGN KEY (role_id) REFERENCES admin_role (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_role_menu (
role_id BIGINT NOT NULL,
menu_id BIGINT NOT NULL,
PRIMARY KEY (role_id, menu_id),
KEY idx_admin_role_menu_menu_id (menu_id),
CONSTRAINT fk_admin_role_menu_role FOREIGN KEY (role_id) REFERENCES admin_role (id) ON DELETE CASCADE,
CONSTRAINT fk_admin_role_menu_menu FOREIGN KEY (menu_id) REFERENCES admin_menu (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_permission (
@ -29,21 +154,6 @@ CREATE TABLE IF NOT EXISTS admin_permission (
UNIQUE KEY uk_admin_permission_key (permission_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_menu (
id BIGINT NOT NULL PRIMARY KEY,
parent_id BIGINT NULL,
menu_key VARCHAR(128) NOT NULL,
title VARCHAR(128) NOT NULL,
path VARCHAR(255) NOT NULL,
component VARCHAR(255) NULL,
sort_no INT NOT NULL DEFAULT 0,
status VARCHAR(32) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_admin_menu_key (menu_key),
KEY idx_admin_menu_parent (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_department (
id BIGINT NOT NULL PRIMARY KEY,
parent_id BIGINT NULL,
@ -55,15 +165,6 @@ CREATE TABLE IF NOT EXISTS admin_department (
KEY idx_admin_department_parent (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_user_role (
id BIGINT NOT NULL PRIMARY KEY,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_admin_user_role (user_id, role_id),
KEY idx_admin_user_role_role (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_role_permission (
id BIGINT NOT NULL PRIMARY KEY,
role_id BIGINT NOT NULL,
@ -73,15 +174,6 @@ CREATE TABLE IF NOT EXISTS admin_role_permission (
KEY idx_admin_role_permission_permission (permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_role_menu (
id BIGINT NOT NULL PRIMARY KEY,
role_id BIGINT NOT NULL,
menu_id BIGINT NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_admin_role_menu (role_id, menu_id),
KEY idx_admin_role_menu_menu (menu_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS platform_dict_group (
id BIGINT NOT NULL PRIMARY KEY,
group_key VARCHAR(128) NOT NULL,
@ -142,6 +234,30 @@ CREATE TABLE IF NOT EXISTS blog_post (
KEY idx_blog_post_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS blog_article (
id BIGINT NOT NULL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL DEFAULT '',
status VARCHAR(32) NOT NULL DEFAULT 'draft',
excerpt TEXT NULL,
content_markdown MEDIUMTEXT NULL,
content_html MEDIUMTEXT NULL,
cover TEXT NULL,
author_name VARCHAR(255) NOT NULL DEFAULT 'KwiTsukasa',
category_items TEXT NULL,
tag_items TEXT NULL,
views INT NOT NULL DEFAULT 0,
comments INT NOT NULL DEFAULT 0,
is_deleted TINYINT NOT NULL DEFAULT 0,
publish_time DATETIME NULL,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_blog_article_slug (slug),
KEY idx_blog_article_status (status),
KEY idx_blog_article_publish_time (publish_time),
KEY idx_blog_article_is_deleted (is_deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS blog_taxonomy (
id BIGINT NOT NULL PRIMARY KEY,
taxonomy_key VARCHAR(64) NOT NULL,
@ -153,13 +269,20 @@ CREATE TABLE IF NOT EXISTS blog_taxonomy (
CREATE TABLE IF NOT EXISTS blog_term (
id BIGINT NOT NULL PRIMARY KEY,
taxonomy_id BIGINT NOT NULL,
taxonomy_id BIGINT NULL,
kind VARCHAR(32) NOT NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
term_name VARCHAR(128) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_blog_term_slug (taxonomy_id, slug),
KEY idx_blog_term_taxonomy (taxonomy_id)
term_name VARCHAR(128) NULL,
description TEXT NULL,
parent_id VARCHAR(64) NULL,
is_deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_blog_term_taxonomy (taxonomy_id),
KEY idx_blog_term_kind_slug (kind, slug),
KEY idx_blog_term_parent_id (parent_id),
KEY idx_blog_term_is_deleted (is_deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS blog_post_term (
@ -182,6 +305,14 @@ CREATE TABLE IF NOT EXISTS blog_theme_profile (
UNIQUE KEY uk_blog_theme_profile_key (profile_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS blog_theme_config (
id VARCHAR(64) NOT NULL PRIMARY KEY,
config LONGTEXT NOT NULL,
source VARCHAR(255) NOT NULL DEFAULT 'local',
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS blog_import_job (
id BIGINT NOT NULL PRIMARY KEY,
source_key VARCHAR(64) NOT NULL,

View File

@ -1,67 +1,214 @@
INSERT INTO admin_role (
id,
role_code,
name,
remark,
status
) VALUES (
2041700000000010001,
'super',
'超级管理员',
'拥有所有后台权限',
1
) ON DUPLICATE KEY UPDATE
name = VALUES(name),
remark = VALUES(remark),
status = VALUES(status),
is_deleted = 0;
INSERT INTO admin_dept (
id,
pid,
name,
status,
remark
) VALUES (
2041700000000200001,
0,
'KT 总部',
1,
'根部门'
) ON DUPLICATE KEY UPDATE
pid = VALUES(pid),
name = VALUES(name),
status = VALUES(status),
remark = VALUES(remark),
is_deleted = 0;
INSERT INTO admin_user (
id,
username,
password_hash,
nickname,
password,
real_name,
avatar,
dept_id,
home_path,
timezone,
status
) VALUES (
1000000000000000001,
2041700000000000002,
'admin',
'!disabled-refactor-v3-password-hash!',
'Administrator',
'password_unset'
'123456',
'Admin',
'',
2041700000000200001,
'/workspace',
'Asia/Shanghai',
1
) ON DUPLICATE KEY UPDATE
nickname = VALUES(nickname),
status = VALUES(status);
INSERT INTO admin_role (
id,
role_key,
role_name,
status
) VALUES (
1000000000000000002,
'super_admin',
'Super Administrator',
'enabled'
) ON DUPLICATE KEY UPDATE
role_name = VALUES(role_name),
status = VALUES(status);
INSERT INTO admin_user_role (
id,
user_id,
role_id
) VALUES (
1000000000000000003,
1000000000000000001,
1000000000000000002
) ON DUPLICATE KEY UPDATE
role_id = VALUES(role_id);
password = VALUES(password),
real_name = VALUES(real_name),
avatar = VALUES(avatar),
dept_id = VALUES(dept_id),
home_path = VALUES(home_path),
timezone = VALUES(timezone),
status = VALUES(status),
is_deleted = 0;
INSERT INTO admin_menu (
id,
parent_id,
menu_key,
title,
pid,
name,
path,
component,
sort_no,
status
) VALUES (
1000000000000000004,
NULL,
'dashboard',
redirect,
auth_code,
type,
meta,
status,
sort
) VALUES
(
2041700000000100001,
0,
'Dashboard',
'/dashboard',
'dashboard/workspace/index',
10,
'enabled'
) ON DUPLICATE KEY UPDATE
title = VALUES(title),
NULL,
'/workspace',
NULL,
'catalog',
'{"order":-1,"title":"page.dashboard.title"}',
1,
0
),
(
2041700000000100102,
2041700000000100001,
'Workspace',
'/workspace',
'/dashboard/workspace/index',
NULL,
NULL,
'menu',
'{"icon":"carbon:workspace","title":"page.dashboard.workspace"}',
1,
0
),
(
2041700000000100400,
0,
'QqBot',
'/qqbot',
NULL,
'/qqbot/dashboard',
NULL,
'catalog',
'{"icon":"lucide:bot","order":110,"title":"QQBot 管理"}',
1,
110
),
(
2041700000000100402,
2041700000000100400,
'QqBotAccount',
'/qqbot/account',
'/qqbot/account/list',
NULL,
'QqBot:Account:List',
'menu',
'{"icon":"lucide:radio-receiver","title":"账号连接"}',
1,
1
),
(
2041700000000100408,
2041700000000100400,
'QqBotCommand',
'/qqbot/command',
'/qqbot/command/list',
NULL,
'QqBot:Command:List',
'menu',
'{"icon":"lucide:square-terminal","title":"在线命令"}',
1,
3
),
(
2041700000000100409,
2041700000000100400,
'QqBotPlugin',
'/qqbot/plugin',
'/qqbot/plugin/list',
NULL,
'QqBot:Plugin:List',
'menu',
'{"icon":"lucide:plug","title":"插件能力"}',
1,
4
)
ON DUPLICATE KEY UPDATE
pid = VALUES(pid),
path = VALUES(path),
component = VALUES(component),
status = VALUES(status);
redirect = VALUES(redirect),
auth_code = VALUES(auth_code),
type = VALUES(type),
meta = VALUES(meta),
status = VALUES(status),
sort = VALUES(sort),
is_deleted = 0;
INSERT INTO admin_dict (
id,
dict_code,
label,
value,
children_code,
sort,
status
) VALUES
(
2041700000000300001,
'COMPONENT_TYPE',
'图表',
'1',
'CHART',
1,
1
),
(
2041700000000300101,
'CHART',
'未分类',
'-1',
NULL,
0,
1
)
ON DUPLICATE KEY UPDATE
label = VALUES(label),
children_code = VALUES(children_code),
sort = VALUES(sort),
status = VALUES(status),
is_deleted = 0;
INSERT IGNORE INTO admin_user_role (user_id, role_id)
VALUES (2041700000000000002, 2041700000000010001);
INSERT IGNORE INTO admin_role_menu (role_id, menu_id)
SELECT 2041700000000010001, id
FROM admin_menu
WHERE is_deleted = 0;
INSERT INTO platform_setting (
id,
@ -121,6 +268,9 @@ INSERT INTO qqbot_command (
id,
operation_key,
command_key,
code,
name,
aliases,
plugin_key,
enabled,
cooldown_seconds
@ -128,11 +278,17 @@ INSERT INTO qqbot_command (
1000000000000000201,
'bangdream.song.search',
'bangdream_song',
'bd',
'BangDream 查歌',
'["查曲","bd","bangdream","bandori","邦邦","邦邦查歌"]',
'bangdream',
1,
2
) ON DUPLICATE KEY UPDATE
operation_key = VALUES(operation_key),
code = VALUES(code),
name = VALUES(name),
aliases = VALUES(aliases),
plugin_key = VALUES(plugin_key),
enabled = VALUES(enabled),
cooldown_seconds = VALUES(cooldown_seconds);

View File

@ -2,6 +2,7 @@ SELECT 'admin_user' AS table_name, COUNT(*) AS row_count FROM admin_user;
SELECT 'admin_role' AS table_name, COUNT(*) AS row_count FROM admin_role;
SELECT 'admin_menu' AS table_name, COUNT(*) AS row_count FROM admin_menu;
SELECT 'platform_setting' AS table_name, COUNT(*) AS row_count FROM platform_setting;
SELECT 'admin_dict' AS table_name, COUNT(*) AS row_count FROM admin_dict;
SELECT 'qqbot_command' AS table_name, COUNT(*) AS row_count FROM qqbot_command;
SELECT 'qqbot_plugin' AS table_name, COUNT(*) AS row_count FROM qqbot_plugin;
SELECT 'napcat_device_identity' AS table_name, COUNT(*) AS row_count FROM napcat_device_identity;
@ -9,7 +10,9 @@ SELECT 'napcat_device_identity' AS table_name, COUNT(*) AS row_count FROM napcat
SELECT 'seed_admin_user' AS check_name, COUNT(*) AS matched_rows
FROM admin_user
WHERE username = 'admin'
AND status = 'password_unset';
AND password <> ''
AND status = 1
AND is_deleted = 0;
SELECT 'seed_platform_schema_version' AS check_name, COUNT(*) AS matched_rows
FROM platform_setting
@ -40,6 +43,12 @@ WHERE table_schema = DATABASE()
AND table_name = 'platform_setting'
AND index_name = 'uk_platform_setting_key';
SELECT 'index_admin_dict_code_value' AS check_name, COUNT(*) AS matched_rows
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'admin_dict'
AND index_name = 'uk_admin_dict_code_value';
SELECT 'index_qqbot_command_key' AS check_name, COUNT(*) AS matched_rows
FROM information_schema.statistics
WHERE table_schema = DATABASE()

View File

@ -44,7 +44,10 @@ import { RuntimeModule } from './runtime';
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_DATABASE'),
synchronize: configService.get<string>('DB_SYNC') === 'true',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
entities: [
__dirname + '/**/*.entity{.ts,.js}',
__dirname + '/**/*.entities{.ts,.js}',
],
subscribers: [__dirname + '/**/*.subscriber{.ts,.js}'],
};
},

View File

@ -105,6 +105,15 @@ describe('QQBot plugin platform API contract', () => {
expect(source).toContain('QqbotPluginPlatformModule');
});
it('loads plural entity files into the TypeORM datasource', () => {
const source = readFileSync(
join(__dirname, '../../../../src/app.module.ts'),
'utf8',
);
expect(source).toContain('*.entities{.ts,.js}');
});
it('exposes plugin-platform management routes', () => {
expect(routeKey).toBeDefined();
expect(

View File

@ -53,4 +53,78 @@ describe('refactor v3 schema skeleton', () => {
expect(verify).toContain('qqbot_plugin');
expect(verify).toContain('napcat_device_identity');
});
it('declares bootstrap tables required before the API can finish startup', () => {
const sql = readFileSync(
join(root, 'sql/refactor-v3/00-full-schema.sql'),
'utf8',
);
const verify = readFileSync(
join(root, 'sql/refactor-v3/99-verify.sql'),
'utf8',
);
expect(sql).toContain('CREATE TABLE IF NOT EXISTS admin_dict');
expect(verify).toContain('admin_dict');
});
it('declares blog tables compatible with current runtime entities', () => {
const sql = readFileSync(
join(root, 'sql/refactor-v3/00-full-schema.sql'),
'utf8',
);
const blogTermColumns = sql.match(
/CREATE TABLE IF NOT EXISTS blog_term \(([\s\S]*?)\) ENGINE=/,
)?.[1];
expect(sql).toContain('CREATE TABLE IF NOT EXISTS blog_article');
expect(sql).toContain('CREATE TABLE IF NOT EXISTS blog_theme_config');
expect(sql).toContain('content_markdown');
expect(sql).toContain('content_html');
expect(sql).toContain('category_items');
expect(sql).toContain('tag_items');
expect(sql).toContain('author_name');
expect(sql).toContain('publish_time');
expect(sql).toContain('is_deleted');
expect(blogTermColumns).toContain('kind');
expect(blogTermColumns).toContain('name');
expect(blogTermColumns).toContain('description');
expect(blogTermColumns).toContain('parent_id');
});
it('seeds qqbot command rows with required command code and aliases', () => {
const seed = readFileSync(
join(root, 'sql/refactor-v3/01-seed-core.sql'),
'utf8',
);
const commandColumns = seed.match(
/INSERT INTO qqbot_command \(([\s\S]*?)\) VALUES/,
)?.[1];
expect(commandColumns).toContain('code');
expect(commandColumns).toContain('aliases');
expect(seed).toContain('["查曲","bd","bangdream","bandori","邦邦","邦邦查歌"]');
});
it('runs dry-run SQL sources with utf8mb4 and fails on native mysql errors', () => {
const dryRunScript = readFileSync(
join(root, 'scripts/refactor-v3/db-dry-run.ps1'),
'utf8',
);
expect(dryRunScript).toContain('--default-character-set=utf8mb4');
expect(dryRunScript).toContain('$LASTEXITCODE');
expect(dryRunScript).toContain('throw');
});
it('uses the Nest listen port as the local smoke default base URL', () => {
const localSmokeScript = readFileSync(
join(root, 'scripts/refactor-v3/local-smoke.ps1'),
'utf8',
);
const main = readFileSync(join(root, 'src/main.ts'), 'utf8');
expect(main).toContain('app.listen(48085)');
expect(localSmokeScript).toContain('http://127.0.0.1:48085');
});
});