20260905172534
This commit is contained in:
1 parent
1b01a31252
commit
7b00eeb88c
711 files changed
+2849
-959
No files matched your search
@@ -19,7 +19,6 @@ BEGIN TRANSACTION;
|
||||
CREATE TABLE dbo.s_log_login (
|
||||
b_id bigint NOT NULL,
|
||||
b_user_id bigint NULL,
|
||||
b_account nvarchar(50) NULL,
|
||||
b_login_type varchar(20) NOT NULL, -- 登录类型:password/token/oidc/ldap
|
||||
b_login_result int NOT NULL CONSTRAINT DF_s_log_login_result DEFAULT (0),
|
||||
b_fail_reason nvarchar(200) NULL,
|
||||
@@ -39,7 +38,6 @@ BEGIN TRANSACTION;
|
||||
);
|
||||
|
||||
CREATE INDEX IX_s_log_login_user ON dbo.s_log_login (b_user_id, b_occurdatetime DESC);
|
||||
CREATE INDEX IX_s_log_login_account ON dbo.s_log_login (b_account, b_occurdatetime DESC);
|
||||
CREATE INDEX IX_s_log_login_result ON dbo.s_log_login (b_login_result, b_occurdatetime DESC);
|
||||
END
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
中文账号写入会因代码页转换变为 '?'(b_user.b_account 本身是 nvarchar,账号支持中文,见
|
||||
100_initialize_fms_new.sql)。改为 nvarchar(50) 与用户表对齐。
|
||||
|
||||
已废弃(2026-09-05):s_log_login / s_log_technical 的 b_account 列已按《FMS新系统核心表结构设计.md》
|
||||
从结构中移除(账号即 b_user.b_id,见 b_user_id 列),详见 203_drop_log_account.sql。
|
||||
本脚本仅针对历史 schema 保留幂等逻辑:列已不存在时自动跳过,无副作用。
|
||||
|
||||
可重复执行:仅在列存在且当前类型为 varchar 时才 ALTER。
|
||||
s_log_login.b_account 上有索引 IX_s_log_login_account,SQL Server 修改列类型前需先删除、改完重建。
|
||||
*/
|
||||
|
||||
@@ -432,7 +432,6 @@ BEGIN
|
||||
CREATE TABLE dbo.s_log_technical (
|
||||
b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一)
|
||||
b_user_id bigint NULL,
|
||||
b_account nvarchar(50) NULL,
|
||||
b_sourcetype varchar(20) NULL,
|
||||
b_loglevel varchar(16) NOT NULL,
|
||||
b_logcategory varchar(64) NULL,
|
||||
@@ -459,7 +458,6 @@ BEGIN
|
||||
CREATE TABLE dbo.s_log_login (
|
||||
b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一)
|
||||
b_user_id bigint NULL,
|
||||
b_account nvarchar(50) NULL,
|
||||
b_login_type varchar(20) NOT NULL,
|
||||
b_login_result int NOT NULL CONSTRAINT DF_s_log_login_result DEFAULT (0),
|
||||
b_fail_reason nvarchar(200) NULL,
|
||||
@@ -478,7 +476,6 @@ BEGIN
|
||||
CONSTRAINT PK_s_log_login PRIMARY KEY (b_id)
|
||||
);
|
||||
CREATE INDEX IX_s_log_login_user ON dbo.s_log_login (b_user_id, b_occurdatetime DESC);
|
||||
CREATE INDEX IX_s_log_login_account ON dbo.s_log_login (b_account, b_occurdatetime DESC);
|
||||
CREATE INDEX IX_s_log_login_result ON dbo.s_log_login (b_login_result, b_occurdatetime DESC);
|
||||
END;
|
||||
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
-- ============================================================================
|
||||
-- 200_rebuild_business_keys.sql
|
||||
-- 目的: 按《FMS新系统核心表结构设计.md》(2026-09-04) 将 FMS-NEW 元数据表
|
||||
-- 从 bigint 雪花ID 方案重构为【字符串业务键】方案。
|
||||
-- 目标库: FMS-NEW (118.89.70.199:1433), 执行前已备份:
|
||||
-- /var/opt/mssql/data/FMS-NEW_pre_refactor_20260905.bak
|
||||
-- 数据迁移范围:
|
||||
-- 迁移 : b_user(3) / s_module(2) / s_field+view/edit/query(6字段配置)
|
||||
-- / s_menu(5) / s_i18n_type(3) / s_i18n(8)
|
||||
-- 不迁移: s_log_* 日志数据 / s_power 权限数据(结构保留,建空表)
|
||||
-- 删除 : s_role / s_user_role / s_role_power / s_role_scope (文档不含,数据空)
|
||||
-- 视图 v_b_user / v_s_log_login / v_s_log_technical / v_s_log_audit (文档不含)
|
||||
-- 说明: 旧表先改名 *_pre200 -> 建新表 -> 迁移数据 -> 删除 *_pre200。
|
||||
-- 逻辑全部在一个事务内, 失败自动回滚。
|
||||
-- ============================================================================
|
||||
|
||||
IF DB_NAME() <> N'FMS-NEW'
|
||||
THROW 51000, N'200_rebuild_business_keys can only run against FMS-NEW.', 1;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 1. 删除文档中不存在的对象: 旧视图 + RBAC 表
|
||||
-- ---------------------------------------------------------------------------
|
||||
IF OBJECT_ID(N'dbo.v_s_log_audit', N'V') IS NOT NULL DROP VIEW dbo.v_s_log_audit;
|
||||
IF OBJECT_ID(N'dbo.v_s_log_technical', N'V') IS NOT NULL DROP VIEW dbo.v_s_log_technical;
|
||||
IF OBJECT_ID(N'dbo.v_s_log_login', N'V') IS NOT NULL DROP VIEW dbo.v_s_log_login;
|
||||
IF OBJECT_ID(N'dbo.v_b_user', N'V') IS NOT NULL DROP VIEW dbo.v_b_user;
|
||||
|
||||
IF OBJECT_ID(N'dbo.s_role_power', N'U') IS NOT NULL DROP TABLE dbo.s_role_power;
|
||||
IF OBJECT_ID(N'dbo.s_user_role', N'U') IS NOT NULL DROP TABLE dbo.s_user_role;
|
||||
IF OBJECT_ID(N'dbo.s_role_scope', N'U') IS NOT NULL DROP TABLE dbo.s_role_scope;
|
||||
IF OBJECT_ID(N'dbo.s_role', N'U') IS NOT NULL DROP TABLE dbo.s_role;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 2. 旧表改名留档 *_pre200 (数据保留至迁移完成)
|
||||
-- ---------------------------------------------------------------------------
|
||||
EXEC sys.sp_rename N'dbo.b_user', N'b_user_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_module', N's_module_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_field', N's_field_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_field_group', N's_field_group_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_field_view', N's_field_view_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_field_edit', N's_field_edit_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_field_query', N's_field_query_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_autocode', N's_autocode_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_relation', N's_relation_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_menu', N's_menu_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_power', N's_power_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_i18n_type', N's_i18n_type_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_i18n', N's_i18n_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_log_technical', N's_log_technical_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_log_login', N's_log_login_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_log_audit', N's_log_audit_pre200', N'OBJECT';
|
||||
EXEC sys.sp_rename N'dbo.s_log_audit_field', N's_log_audit_field_pre200', N'OBJECT';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 3. 按设计文档创建新表
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- 3.1 用户表
|
||||
CREATE TABLE dbo.b_user (
|
||||
b_id varchar(50) not null primary key, -- 用户账号本身
|
||||
b_name nvarchar(100) null,
|
||||
b_bz nvarchar(400) null,
|
||||
b_password nvarchar(255) not null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_logintime tinyint not null default 0
|
||||
);
|
||||
|
||||
-- 3.2 模块
|
||||
CREATE TABLE dbo.s_module (
|
||||
b_id varchar(50) not null primary key, -- 模块编码本身, 创建后不可修改
|
||||
b_parent_id varchar(50) null,
|
||||
b_name nvarchar(200) not null,
|
||||
b_i18n varchar(150) null,
|
||||
b_module_type varchar(20) not null,
|
||||
b_viewtable varchar(128) null,
|
||||
b_savetable varchar(128) null,
|
||||
b_keyfield varchar(50) null,
|
||||
b_orderfield varchar(500) null,
|
||||
b_delete_policy varchar(20) null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_xh int not null default 0,
|
||||
b_bz nvarchar(2000) null,
|
||||
b_edit_mode varchar(10) null
|
||||
);
|
||||
CREATE INDEX ix_s_module_parent ON dbo.s_module (b_parent_id, b_xh, b_canuse, b_id);
|
||||
CREATE INDEX ix_s_module_order ON dbo.s_module (b_xh, b_id);
|
||||
CREATE INDEX ix_s_module_savetable ON dbo.s_module (b_savetable);
|
||||
|
||||
-- 3.3 字段与布局
|
||||
CREATE TABLE dbo.s_field (
|
||||
b_module_id varchar(50) not null,
|
||||
b_field varchar(50) not null,
|
||||
b_name nvarchar(200) not null,
|
||||
b_i18n varchar(150) null,
|
||||
b_type varchar(30) not null,
|
||||
b_dbtype varchar(30) not null,
|
||||
b_length int null,
|
||||
b_precision int null,
|
||||
b_scale int null,
|
||||
b_nullable tinyint not null default 1,
|
||||
b_default_value nvarchar(1000) null,
|
||||
b_db_default nvarchar(1000) null,
|
||||
b_writemode varchar(20) not null default 'direct',
|
||||
b_options nvarchar(max) null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_xh int not null default 0, -- 字段定义顺序:同步时按视图列序重排(10 起步)
|
||||
primary key (b_module_id, b_field)
|
||||
);
|
||||
CREATE INDEX ix_s_field_module_order ON dbo.s_field (b_module_id, b_xh, b_field);
|
||||
|
||||
CREATE TABLE dbo.s_field_group (
|
||||
b_id varchar(50) not null, -- 分组编码本身, 模块内唯一, 创建后不可修改
|
||||
b_module_id varchar(50) not null,
|
||||
b_parent_id varchar(50) null, -- 父分组编码, NULL 表示根分组
|
||||
b_title nvarchar(200) not null,
|
||||
b_i18n varchar(150) null,
|
||||
b_xh int not null default 0,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_colspan int not null default 48,
|
||||
b_layout_direction varchar(10) null default 'vertical',
|
||||
primary key (b_module_id, b_id)
|
||||
);
|
||||
CREATE INDEX ix_s_field_group_parent ON dbo.s_field_group (b_module_id, b_parent_id, b_xh, b_id);
|
||||
CREATE INDEX ix_s_field_group_order ON dbo.s_field_group (b_module_id, b_xh, b_id);
|
||||
|
||||
CREATE TABLE dbo.s_field_view (
|
||||
b_module_id varchar(50) not null,
|
||||
b_field varchar(50) not null,
|
||||
b_group_id varchar(50) null,
|
||||
b_visible tinyint not null default 1,
|
||||
b_xh int not null default 0,
|
||||
b_width int null,
|
||||
primary key (b_module_id, b_field)
|
||||
);
|
||||
CREATE INDEX ix_s_field_view_order ON dbo.s_field_view (b_module_id, b_xh, b_field);
|
||||
|
||||
CREATE TABLE dbo.s_field_edit (
|
||||
b_module_id varchar(50) not null,
|
||||
b_field varchar(50) not null,
|
||||
b_group_id varchar(50) null,
|
||||
b_visible tinyint not null default 1,
|
||||
b_required tinyint not null default 0,
|
||||
b_readonly tinyint not null default 0,
|
||||
b_disabled tinyint not null default 0,
|
||||
b_xh int not null default 0,
|
||||
b_width int null,
|
||||
b_placeholder nvarchar(400) null,
|
||||
b_colspan int not null default 12,
|
||||
b_height int null,
|
||||
primary key (b_module_id, b_field)
|
||||
);
|
||||
CREATE INDEX ix_s_field_edit_order ON dbo.s_field_edit (b_module_id, b_xh, b_field);
|
||||
|
||||
CREATE TABLE dbo.s_field_query (
|
||||
b_module_id varchar(50) not null,
|
||||
b_field varchar(50) not null,
|
||||
b_component varchar(30) null,
|
||||
b_condition_no int not null default 1,
|
||||
b_operator varchar(20) not null,
|
||||
b_default_value nvarchar(1000) null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_xh int not null default 0,
|
||||
primary key (b_module_id, b_field, b_condition_no)
|
||||
);
|
||||
CREATE INDEX ix_s_field_query_order ON dbo.s_field_query (b_module_id, b_xh, b_field, b_condition_no);
|
||||
|
||||
-- 3.4 自动编码
|
||||
CREATE TABLE dbo.s_autocode (
|
||||
b_module_id varchar(50) not null,
|
||||
b_field varchar(50) not null,
|
||||
b_prefix nvarchar(200) not null default N'',
|
||||
b_dateformat varchar(30) not null default 'yyyyMM',
|
||||
b_separator nvarchar(20) not null default N'-',
|
||||
b_seqwidth int not null default 5,
|
||||
b_resettype varchar(10) not null default 'month',
|
||||
b_startvalue bigint not null default 1,
|
||||
b_currentperiod varchar(8) null,
|
||||
b_currentvalue bigint not null default 0,
|
||||
b_canuse tinyint not null default 1,
|
||||
primary key (b_module_id, b_field)
|
||||
);
|
||||
|
||||
-- 3.5 模块关系
|
||||
CREATE TABLE dbo.s_relation (
|
||||
b_module_id varchar(50) not null,
|
||||
b_target_module_id varchar(50) not null,
|
||||
b_relation_type varchar(20) not null,
|
||||
b_field varchar(50) not null,
|
||||
b_target_field varchar(50) not null,
|
||||
b_foreign_side varchar(10) null,
|
||||
b_junction_module_id varchar(50) null,
|
||||
b_junction_field varchar(50) null,
|
||||
b_junction_target_field varchar(50) null,
|
||||
b_ownership varchar(20) not null default 'reference',
|
||||
b_owner_side varchar(10) null,
|
||||
b_delete_policy varchar(20) null,
|
||||
b_order_module_id varchar(50) null,
|
||||
b_order_field varchar(50) null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_xh int not null default 0,
|
||||
primary key (b_module_id, b_field, b_target_module_id, b_target_field)
|
||||
);
|
||||
CREATE INDEX ix_s_relation_module ON dbo.s_relation (b_module_id, b_canuse, b_xh, b_field, b_target_module_id);
|
||||
CREATE INDEX ix_s_relation_target ON dbo.s_relation (b_target_module_id);
|
||||
|
||||
-- 3.6 菜单
|
||||
CREATE TABLE dbo.s_menu (
|
||||
b_id varchar(50) not null primary key, -- 菜单编码本身
|
||||
b_parent_id varchar(50) null,
|
||||
b_name nvarchar(200) not null,
|
||||
b_i18n varchar(150) null,
|
||||
b_menu_type varchar(20) not null,
|
||||
b_route varchar(200) null,
|
||||
b_module_id varchar(50) null,
|
||||
b_icon varchar(50) null,
|
||||
b_xh int not null default 0,
|
||||
b_canuse tinyint not null default 1
|
||||
);
|
||||
CREATE INDEX ix_s_menu_parent ON dbo.s_menu (b_parent_id, b_xh, b_canuse, b_id);
|
||||
CREATE INDEX ix_s_menu_order ON dbo.s_menu (b_xh, b_id);
|
||||
CREATE INDEX ix_s_menu_module ON dbo.s_menu (b_module_id);
|
||||
CREATE INDEX ix_s_menu_route ON dbo.s_menu (b_route, b_canuse, b_id);
|
||||
|
||||
-- 3.7 权限(结构保留, 数据不迁移)
|
||||
CREATE TABLE dbo.s_power (
|
||||
b_id varchar(50) not null primary key, -- 权限编码本身
|
||||
b_name nvarchar(200) not null,
|
||||
b_i18n varchar(150) null,
|
||||
b_power_type varchar(20) not null,
|
||||
b_resource varchar(128) null,
|
||||
b_module_id varchar(50) null,
|
||||
b_field varchar(50) null,
|
||||
b_operation varchar(30) not null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_xh int not null default 0
|
||||
);
|
||||
CREATE INDEX ix_s_power_module ON dbo.s_power (b_module_id, b_field, b_canuse);
|
||||
CREATE INDEX ix_s_power_resource ON dbo.s_power (b_power_type, b_resource, b_canuse);
|
||||
|
||||
-- 3.8 多语言
|
||||
CREATE TABLE dbo.s_i18n_type (
|
||||
b_id varchar(20) not null primary key, -- 语言编码本身
|
||||
b_name nvarchar(200) not null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_default tinyint not null default 0,
|
||||
b_xh int not null default 0
|
||||
);
|
||||
|
||||
CREATE TABLE dbo.s_i18n (
|
||||
b_id varchar(200) not null primary key, -- b_key + ':' + b_locale
|
||||
b_key varchar(150) not null,
|
||||
b_locale varchar(20) not null,
|
||||
b_value nvarchar(1000) not null,
|
||||
b_i18ntype varchar(20) null,
|
||||
b_canuse tinyint not null default 1,
|
||||
b_module_id varchar(50) null,
|
||||
unique (b_key, b_locale)
|
||||
);
|
||||
CREATE INDEX ix_s_i18n_module_key ON dbo.s_i18n (b_module_id, b_key, b_locale);
|
||||
CREATE INDEX ix_s_i18n_locale ON dbo.s_i18n (b_locale, b_canuse);
|
||||
|
||||
-- 3.9 日志(仅结构, 数据不迁移)
|
||||
CREATE TABLE dbo.s_log_technical (
|
||||
b_id uniqueidentifier not null,
|
||||
b_user_id varchar(50) null,
|
||||
b_sourcetype varchar(20) null,
|
||||
b_loglevel varchar(16) not null,
|
||||
b_logcategory varchar(64) null,
|
||||
b_operation varchar(100) null,
|
||||
b_module_id varchar(50) null,
|
||||
b_request_id varchar(64) null,
|
||||
b_trace_id varchar(64) null,
|
||||
b_server_node varchar(128) null,
|
||||
b_duration_ms bigint null,
|
||||
b_result_code varchar(50) null,
|
||||
b_error_code varchar(50) null,
|
||||
b_error_message nvarchar(4000) null,
|
||||
b_exception_text nvarchar(max) null,
|
||||
b_context_text nvarchar(max) null,
|
||||
b_occurdatetime datetime2 default sysutcdatetime(),
|
||||
primary key nonclustered (b_id)
|
||||
);
|
||||
|
||||
CREATE TABLE dbo.s_log_login (
|
||||
b_id uniqueidentifier not null,
|
||||
b_user_id varchar(50) null,
|
||||
b_login_type varchar(20) not null,
|
||||
b_login_result tinyint not null default 0,
|
||||
b_fail_reason nvarchar(400) null,
|
||||
b_ip varchar(50) null,
|
||||
b_ip_location nvarchar(400) null,
|
||||
b_browser varchar(100) null,
|
||||
b_browser_version varchar(50) null,
|
||||
b_os varchar(100) null,
|
||||
b_os_version varchar(50) null,
|
||||
b_device_type varchar(20) null,
|
||||
b_user_agent nvarchar(max) null,
|
||||
b_session_id varchar(50) null,
|
||||
b_request_id varchar(64) null,
|
||||
b_trace_id varchar(64) null,
|
||||
b_occurdatetime datetime2 default sysutcdatetime(),
|
||||
primary key nonclustered (b_id)
|
||||
);
|
||||
|
||||
CREATE TABLE dbo.s_log_audit (
|
||||
b_id uniqueidentifier not null,
|
||||
b_event_code varchar(50) not null,
|
||||
b_module_id varchar(50) null,
|
||||
b_data_id varchar(50) null,
|
||||
b_business_no varchar(50) null,
|
||||
b_operation varchar(100) null,
|
||||
b_operator_id varchar(50) null,
|
||||
b_operator_name nvarchar(200) null,
|
||||
b_source_type varchar(20) not null default 'user',
|
||||
b_visibility varchar(20) not null default 'internal',
|
||||
b_request_id varchar(64) null,
|
||||
b_trace_id varchar(64) null,
|
||||
b_payload_text nvarchar(max) null,
|
||||
b_occurdatetime datetime2 default sysutcdatetime(),
|
||||
primary key nonclustered (b_id)
|
||||
);
|
||||
|
||||
CREATE TABLE dbo.s_log_audit_field (
|
||||
b_id uniqueidentifier not null,
|
||||
b_event_id uniqueidentifier not null,
|
||||
b_module_id varchar(50) null,
|
||||
b_data_id varchar(50) null,
|
||||
b_field varchar(50) not null,
|
||||
b_field_label nvarchar(200) null,
|
||||
b_before_value nvarchar(max) null,
|
||||
b_after_value nvarchar(max) null,
|
||||
b_before_display nvarchar(1000) null,
|
||||
b_after_display nvarchar(1000) null,
|
||||
b_visibility varchar(20) not null default 'internal',
|
||||
primary key nonclustered (b_id)
|
||||
);
|
||||
|
||||
CREATE CLUSTERED INDEX cx_s_log_technical_occur ON dbo.s_log_technical (b_occurdatetime, b_id);
|
||||
CREATE CLUSTERED INDEX cx_s_log_login_occur ON dbo.s_log_login (b_occurdatetime, b_id);
|
||||
CREATE CLUSTERED INDEX cx_s_log_audit_occur ON dbo.s_log_audit (b_occurdatetime, b_id);
|
||||
CREATE CLUSTERED INDEX cx_s_log_audit_field_event ON dbo.s_log_audit_field (b_event_id, b_id);
|
||||
CREATE INDEX ix_s_log_technical_trace ON dbo.s_log_technical (b_trace_id, b_occurdatetime);
|
||||
CREATE INDEX ix_s_log_login_user ON dbo.s_log_login (b_user_id, b_occurdatetime);
|
||||
CREATE INDEX ix_s_log_audit_module ON dbo.s_log_audit (b_module_id, b_data_id, b_occurdatetime);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 4. 数据迁移 (旧 *_pre200 -> 新表)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- 4.1 b_user: b_account -> b_id, 丢弃业务系统冗余列
|
||||
INSERT INTO dbo.b_user (b_id, b_name, b_bz, b_password, b_canuse, b_logintime)
|
||||
SELECT b_account, b_name, b_bz, b_password, b_canuse, b_logintime
|
||||
FROM dbo.b_user_pre200;
|
||||
|
||||
-- 4.2 s_module: b_code -> b_id, 父模块与录入/更新人引用改业务键
|
||||
INSERT INTO dbo.s_module (
|
||||
b_id, b_parent_id, b_name, b_i18n, b_module_type, b_viewtable, b_savetable,
|
||||
b_keyfield, b_orderfield, b_delete_policy, b_canuse, b_xh, b_bz,
|
||||
b_edit_mode
|
||||
)
|
||||
SELECT
|
||||
m.b_code, p.b_code, m.b_name, m.b_i18n, m.b_module_type, m.b_viewtable, m.b_savetable,
|
||||
m.b_keyfield, m.b_orderfield, m.b_delete_policy, m.b_canuse, m.b_xh, m.b_bz,
|
||||
m.b_edit_mode
|
||||
FROM dbo.s_module_pre200 m
|
||||
LEFT JOIN dbo.s_module_pre200 p ON p.b_id = m.b_parent_id;
|
||||
|
||||
-- 4.3 s_field: b_module_id 引用改业务键
|
||||
INSERT INTO dbo.s_field (
|
||||
b_module_id, b_field, b_name, b_i18n, b_type, b_dbtype, b_length,
|
||||
b_precision, b_scale, b_nullable, b_default_value, b_db_default, b_writemode,
|
||||
b_options, b_canuse
|
||||
)
|
||||
SELECT
|
||||
mo.b_code, f.b_field, f.b_name, f.b_i18n, f.b_type, f.b_dbtype, f.b_length,
|
||||
f.b_precision, f.b_scale, f.b_nullable, f.b_default_value, f.b_db_default, f.b_writemode,
|
||||
f.b_options, f.b_canuse
|
||||
FROM dbo.s_field_pre200 f
|
||||
JOIN dbo.s_module_pre200 mo ON mo.b_id = f.b_module_id;
|
||||
|
||||
-- 4.4 s_field_view / s_field_edit / s_field_query: b_field_id -> b_field
|
||||
INSERT INTO dbo.s_field_view (b_module_id, b_field, b_group_id, b_visible, b_xh, b_width)
|
||||
SELECT mo.b_code, f.b_field, NULL, v.b_visible, v.b_xh, v.b_width
|
||||
FROM dbo.s_field_view_pre200 v
|
||||
JOIN dbo.s_module_pre200 mo ON mo.b_id = v.b_module_id
|
||||
JOIN dbo.s_field_pre200 f ON f.b_id = v.b_field_id AND f.b_module_id = v.b_module_id;
|
||||
|
||||
INSERT INTO dbo.s_field_edit (b_module_id, b_field, b_group_id, b_visible, b_required, b_readonly, b_disabled, b_xh, b_width, b_placeholder, b_colspan, b_height)
|
||||
SELECT mo.b_code, f.b_field, NULL, e.b_visible, e.b_required, e.b_readonly, e.b_disabled, e.b_xh, e.b_width, e.b_placeholder, e.b_colspan, e.b_height
|
||||
FROM dbo.s_field_edit_pre200 e
|
||||
JOIN dbo.s_module_pre200 mo ON mo.b_id = e.b_module_id
|
||||
JOIN dbo.s_field_pre200 f ON f.b_id = e.b_field_id AND f.b_module_id = e.b_module_id;
|
||||
|
||||
INSERT INTO dbo.s_field_query (b_module_id, b_field, b_component, b_condition_no, b_operator, b_default_value, b_canuse, b_xh)
|
||||
SELECT mo.b_code, f.b_field, q.b_component, q.b_condition_no, q.b_operator, q.b_default_value, q.b_canuse, q.b_xh
|
||||
FROM dbo.s_field_query_pre200 q
|
||||
JOIN dbo.s_module_pre200 mo ON mo.b_id = q.b_field_module_id
|
||||
JOIN dbo.s_field_pre200 f ON f.b_id = q.b_field_id AND f.b_module_id = q.b_field_module_id;
|
||||
|
||||
-- s_field_group / s_autocode / s_relation: 无有效数据, 保持空表
|
||||
-- (原 s_field_group 仅存的 1 行数据其 b_module_id 已不存在, 属孤儿数据, 不迁移)
|
||||
|
||||
-- 4.5 s_menu: b_code -> b_id, 父菜单/绑定模块引用改业务键
|
||||
INSERT INTO dbo.s_menu (b_id, b_parent_id, b_name, b_i18n, b_menu_type, b_route, b_module_id, b_icon, b_xh, b_canuse)
|
||||
SELECT
|
||||
m.b_code, p.b_code, m.b_name, m.b_i18n, m.b_menu_type, m.b_route, mo.b_code,
|
||||
NULLIF(LEFT(CONVERT(nvarchar(max), m.b_icon), 50), N''), m.b_xh, m.b_canuse
|
||||
FROM dbo.s_menu_pre200 m
|
||||
LEFT JOIN dbo.s_menu_pre200 p ON p.b_id = m.b_parent_id
|
||||
LEFT JOIN dbo.s_module_pre200 mo ON mo.b_id = m.b_module_id;
|
||||
|
||||
-- 4.6 s_i18n_type: b_code -> b_id
|
||||
INSERT INTO dbo.s_i18n_type (b_id, b_name, b_canuse, b_default, b_xh)
|
||||
SELECT b_code, b_name, b_canuse, b_default, b_xh
|
||||
FROM dbo.s_i18n_type_pre200;
|
||||
|
||||
-- 4.7 s_i18n: b_id = b_key + ':' + b_locale, 模块引用改业务键(0 -> NULL)
|
||||
INSERT INTO dbo.s_i18n (b_id, b_key, b_locale, b_value, b_i18ntype, b_canuse, b_module_id)
|
||||
SELECT
|
||||
i.b_key + N':' + i.b_locale, i.b_key, i.b_locale, i.b_value, i.b_i18ntype, i.b_canuse,
|
||||
CASE WHEN i.b_module_id = 0 THEN NULL ELSE mo.b_code END
|
||||
FROM dbo.s_i18n_pre200 i
|
||||
LEFT JOIN dbo.s_module_pre200 mo ON mo.b_id = i.b_module_id;
|
||||
|
||||
-- s_power / s_log_* : 按需求不迁移数据
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 5. 删除 *_pre200 旧表
|
||||
-- ---------------------------------------------------------------------------
|
||||
DROP TABLE dbo.b_user_pre200;
|
||||
DROP TABLE dbo.s_module_pre200;
|
||||
DROP TABLE dbo.s_field_pre200;
|
||||
DROP TABLE dbo.s_field_group_pre200;
|
||||
DROP TABLE dbo.s_field_view_pre200;
|
||||
DROP TABLE dbo.s_field_edit_pre200;
|
||||
DROP TABLE dbo.s_field_query_pre200;
|
||||
DROP TABLE dbo.s_autocode_pre200;
|
||||
DROP TABLE dbo.s_relation_pre200;
|
||||
DROP TABLE dbo.s_menu_pre200;
|
||||
DROP TABLE dbo.s_power_pre200;
|
||||
DROP TABLE dbo.s_i18n_type_pre200;
|
||||
DROP TABLE dbo.s_i18n_pre200;
|
||||
DROP TABLE dbo.s_log_technical_pre200;
|
||||
DROP TABLE dbo.s_log_login_pre200;
|
||||
DROP TABLE dbo.s_log_audit_pre200;
|
||||
DROP TABLE dbo.s_log_audit_field_pre200;
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
|
||||
-- 验证输出
|
||||
SELECT DB_NAME() AS database_name,
|
||||
(SELECT COUNT(*) FROM sys.tables WHERE is_ms_shipped = 0) AS user_table_count,
|
||||
(SELECT COUNT(*) FROM dbo.b_user) AS user_count,
|
||||
(SELECT COUNT(*) FROM dbo.s_module) AS module_count,
|
||||
(SELECT COUNT(*) FROM dbo.s_field) AS field_count,
|
||||
(SELECT COUNT(*) FROM dbo.s_menu) AS menu_count,
|
||||
(SELECT COUNT(*) FROM dbo.s_i18n) AS i18n_count;
|
||||
@@ -0,0 +1,45 @@
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
-- ============================================================================
|
||||
-- 201_add_bf_files.sql
|
||||
-- 目的: 在 FMS-NEW 补建文件表 bf_files(旧 FMS 系统已有,代码 FileService 仍依赖)。
|
||||
-- 参考旧库 FMS.dbo.bf_files 列结构;将"引用类"列改为字符串业务键:
|
||||
-- mx_moduleid -> varchar(50) 引用 s_module.b_id(模块编码)
|
||||
-- mx_inputuser_id / mx_updateuser_id / mx_shuser_id
|
||||
-- -> varchar(50) 引用 b_user.b_id(账号)
|
||||
-- father 仍为 bigint,引用本表 subid(文件树父节点)。
|
||||
-- 主键 subid 为应用层雪花 bigint,由 FileService/IdGenerator 生成。
|
||||
-- ============================================================================
|
||||
|
||||
IF DB_NAME() <> N'FMS-NEW'
|
||||
THROW 51000, N'201_add_bf_files can only run against FMS-NEW.', 1;
|
||||
|
||||
IF OBJECT_ID(N'dbo.bf_files', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.bf_files (
|
||||
subid bigint not null, -- 雪花主键(应用层生成)
|
||||
father bigint null, -- 父节点(引用同表 subid)
|
||||
mx_xh int null default 0,
|
||||
mx_filename nvarchar(500) null,
|
||||
mx_filesize varchar(50) null,
|
||||
mx_fileext varchar(50) null,
|
||||
mx_mapfilename nvarchar(500) null,
|
||||
mx_moduleid varchar(50) null, -- 引用 s_module.b_id(模块编码)
|
||||
mx_cutfilename nvarchar(500) null,
|
||||
mx_cutfilesize varchar(50) null,
|
||||
mx_cate_id varchar(50) null,
|
||||
mx_bz nvarchar(500) null,
|
||||
mx_orderid varchar(50) null,
|
||||
mx_sh int not null default 0,
|
||||
mx_shdatetime datetime2 null,
|
||||
mx_shuser_id varchar(50) null, -- 引用 b_user.b_id(账号)
|
||||
mx_inputuser_id varchar(50) null, -- 引用 b_user.b_id(账号)
|
||||
mx_inputdatetime datetime2 null default sysutcdatetime(),
|
||||
mx_updateuser_id varchar(50) null,
|
||||
mx_updatedatetime datetime2 null,
|
||||
CONSTRAINT PK_bf_files PRIMARY KEY (subid)
|
||||
);
|
||||
CREATE INDEX ix_bf_files_module ON dbo.bf_files (mx_moduleid);
|
||||
CREATE INDEX ix_bf_files_father ON dbo.bf_files (father);
|
||||
END;
|
||||
@@ -0,0 +1,26 @@
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
-- ============================================================================
|
||||
-- 202_add_bf_files_cateid.sql
|
||||
-- 目的: 在 FMS-NEW 补建文件分类表 bf_files_cateid(旧 FMS 系统已有,
|
||||
-- 前端 fms-file 组件 FileListPanel / useFileCategory 仍按该表加载分类)。
|
||||
-- 参考旧库 G3HY2025.dbo.bf_files_cateid 列结构,并按字符串业务键/状态列
|
||||
-- 统一规范:b_id 即分类编码(varchar,被 bf_files.mx_cate_id 引用)。
|
||||
-- ============================================================================
|
||||
|
||||
IF DB_NAME() <> N'FMS-NEW'
|
||||
THROW 51000, N'202_add_bf_files_cateid can only run against FMS-NEW.', 1;
|
||||
|
||||
IF OBJECT_ID(N'dbo.bf_files_cateid', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.bf_files_cateid (
|
||||
b_id varchar(50) not null, -- 分类编码(字符串业务键),被 bf_files.mx_cate_id 引用
|
||||
b_name nvarchar(200) not null, -- 分类名称
|
||||
b_files_type varchar(50) null, -- 用途标识(如 file_sh 附件),可选
|
||||
b_xh int not null default 0,
|
||||
b_canuse tinyint not null default 1, -- 启用
|
||||
b_bz nvarchar(500) null, -- 备注
|
||||
CONSTRAINT PK_bf_files_cateid PRIMARY KEY (b_id)
|
||||
);
|
||||
END;
|
||||
@@ -0,0 +1,30 @@
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
-- ============================================================================
|
||||
-- 203_drop_log_account.sql
|
||||
-- 目的: 从日志表移除冗余的 b_account 列,与《FMS新系统核心表结构设计.md》对齐:
|
||||
-- s_log_technical: 删除 b_account,保留 b_user_id
|
||||
-- s_log_login: 删除 b_account,保留 b_user_id
|
||||
-- 新结构中账号即 b_user.b_id(字符串业务键),日志通过 b_user_id 记录用户;
|
||||
-- b_account 列系历史建表脚本(100/200)多余引入,数据库中无其索引依赖。
|
||||
-- ============================================================================
|
||||
|
||||
IF DB_NAME() <> N'FMS-NEW'
|
||||
THROW 51000, N'203_drop_log_account can only run against FMS-NEW.', 1;
|
||||
|
||||
-- 若历史库中残留依赖 b_account 的索引,先显式删除
|
||||
IF OBJECT_ID(N'dbo.IX_s_log_login_account', N'INDEX') IS NOT NULL
|
||||
DROP INDEX IX_s_log_login_account ON dbo.s_log_login;
|
||||
|
||||
IF OBJECT_ID(N'dbo.s_log_login', N'U') IS NOT NULL
|
||||
AND COL_LENGTH(N'dbo.s_log_login', N'b_account') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.s_log_login DROP COLUMN b_account;
|
||||
END;
|
||||
|
||||
IF OBJECT_ID(N'dbo.s_log_technical', N'U') IS NOT NULL
|
||||
AND COL_LENGTH(N'dbo.s_log_technical', N'b_account') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.s_log_technical DROP COLUMN b_account;
|
||||
END;
|
||||
@@ -0,0 +1,69 @@
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
-- ============================================================================
|
||||
-- 204_add_s_field_xh.sql
|
||||
-- 目的: 给 s_field 增加排序列 b_xh(对齐 s_field_group/view/edit/query 的 b_xh 惯例)。
|
||||
-- 此前 s_field 无顺序列,前端只能按 b_field 字母排序,模块「同步字段」后
|
||||
-- 保存再加载,顺序会被字母序打乱,无法按数据库视图/表的列顺序持久化。
|
||||
-- 目标库: FMS-NEW
|
||||
-- 说明: 1) 已存在 s_field 的库执行本脚本(新增列并回填初值)。
|
||||
-- 2) b_xh 初值按各模块 b_viewtable 的列顺序(sys.columns.column_id)赋序,
|
||||
-- 步长 10;视图里找不到的字段保持 0(排在末位,按 b_field 兜底)。
|
||||
-- 3) 全新库在 200_rebuild_business_keys.sql 建表定义中已含 b_xh,
|
||||
-- 此处 COL_LENGTH 判断将跳过 ADD,仅执行回填与建索引(幂等)。
|
||||
-- 4) 批次按 GO 拆分:同一批次内 ALTER 加列后引用新列会在解析期
|
||||
-- 报 Invalid column name,必须拆批执行(sqlcmd / JDBC 均按 GO 分批)。
|
||||
-- ============================================================================
|
||||
|
||||
IF DB_NAME() <> N'FMS-NEW'
|
||||
THROW 51000, N'204_add_s_field_xh can only run against FMS-NEW.', 1;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF COL_LENGTH(N'dbo.s_field', N'b_xh') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.s_field
|
||||
ADD b_xh int NOT NULL CONSTRAINT DF_s_field_xh DEFAULT (0);
|
||||
END;
|
||||
GO
|
||||
|
||||
-- 按模块查询视图的列顺序给既有字段行赋序(10 起步,与其它配置表一致)
|
||||
UPDATE f
|
||||
SET f.b_xh = ranked.xh
|
||||
FROM dbo.s_field f
|
||||
JOIN (
|
||||
SELECT f.b_module_id,
|
||||
f.b_field,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY f.b_module_id
|
||||
ORDER BY c.column_id
|
||||
) * 10 AS xh
|
||||
FROM dbo.s_field f
|
||||
JOIN dbo.s_module m ON m.b_id = f.b_module_id
|
||||
JOIN sys.columns c ON c.object_id = OBJECT_ID(m.b_viewtable)
|
||||
AND c.name = f.b_field
|
||||
) ranked
|
||||
ON ranked.b_module_id = f.b_module_id
|
||||
AND ranked.b_field = f.b_field;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.ix_s_field_module_order', N'I') IS NULL
|
||||
BEGIN
|
||||
CREATE INDEX ix_s_field_module_order
|
||||
ON dbo.s_field (b_module_id, b_xh, b_field);
|
||||
END;
|
||||
GO
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
GO
|
||||
|
||||
-- 验证输出
|
||||
SELECT m.b_id AS module_id,
|
||||
m.b_viewtable,
|
||||
f.b_xh,
|
||||
f.b_field
|
||||
FROM dbo.s_module m
|
||||
JOIN dbo.s_field f ON f.b_module_id = m.b_id
|
||||
ORDER BY m.b_id, f.b_xh, f.b_field;
|
||||
GO
|
||||
@@ -46,7 +46,7 @@ public class AuthService {
|
||||
|
||||
if (users.isEmpty()) {
|
||||
loginLogService.recordLogin(
|
||||
normalizedOrgId, normalizedUserId, null,
|
||||
normalizedOrgId, normalizedUserId,
|
||||
false, "账号不存在", null, request);
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -55,24 +55,27 @@ public class AuthService {
|
||||
String storedPassword = getString(account, "b_password");
|
||||
if (!passwordMatches(storedPassword, password)) {
|
||||
loginLogService.recordLogin(
|
||||
normalizedOrgId, normalizedUserId, null,
|
||||
normalizedOrgId, normalizedUserId,
|
||||
false, "密码错误", null, request);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String accountId = getString(account, "b_account");
|
||||
long userPrimaryKey = getLong(account, "b_id");
|
||||
// 字符串业务键方案:b_user.b_id 即用户账号本身
|
||||
String accountId = getString(account, "b_id");
|
||||
if (accountId == null || accountId.isBlank()) {
|
||||
throw new IllegalStateException("登录用户缺少有效的 b_id");
|
||||
}
|
||||
String name = getString(account, "b_name");
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
String token = jwtUtils.generateToken(accountId, normalizedOrgId, sessionId);
|
||||
activeSessionRegistry.activate(normalizedOrgId, accountId, sessionId);
|
||||
|
||||
loginLogService.recordLogin(
|
||||
normalizedOrgId, accountId, userPrimaryKey,
|
||||
normalizedOrgId, accountId,
|
||||
true, null, sessionId, request);
|
||||
|
||||
Map<String, Object> user = new LinkedHashMap<>();
|
||||
user.put("id", userPrimaryKey);
|
||||
user.put("id", accountId);
|
||||
user.put("account", accountId);
|
||||
user.put("name", name);
|
||||
|
||||
@@ -88,21 +91,6 @@ public class AuthService {
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
|
||||
private long getLong(Map<String, Object> row, String column) {
|
||||
Object value = getValue(row, column);
|
||||
if (value instanceof Number number) {
|
||||
return number.longValue();
|
||||
}
|
||||
if (value != null) {
|
||||
try {
|
||||
return Long.parseLong(value.toString());
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Report the same configuration error below.
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("登录用户缺少有效的" + column);
|
||||
}
|
||||
|
||||
private Object getValue(Map<String, Object> row, String column) {
|
||||
for (Map.Entry<String, Object> entry : row.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(column)) {
|
||||
|
||||
@@ -65,18 +65,14 @@ public class DataSaveService {
|
||||
throw new BusinessException("count 必须是 1 到 1000 之间的整数");
|
||||
}
|
||||
|
||||
final long moduleIdValue;
|
||||
try {
|
||||
moduleIdValue = Long.parseLong(moduleId);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new BusinessException("moduleId 必须是有效的数字");
|
||||
}
|
||||
// 字符串业务键方案:moduleId 即模块编码(如 's_i18n_type'),非 bigint
|
||||
String normalizedModuleId = moduleId.trim();
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
boolean originalAutoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false);
|
||||
try {
|
||||
AutoCodeRule rule = loadAutoCodeRule(connection, moduleIdValue);
|
||||
AutoCodeRule rule = loadAutoCodeRule(connection, normalizedModuleId);
|
||||
DbUtils.TableMetadata targetTable = dbUtils.loadTableMetadata(connection, rule.saveTable());
|
||||
DbUtils.ColumnMetadata targetColumn = dbUtils.getColumn(targetTable, rule.fieldName());
|
||||
if (targetColumn == null) {
|
||||
@@ -119,14 +115,16 @@ public class DataSaveService {
|
||||
}
|
||||
|
||||
long lastValue = nextValue - 1;
|
||||
// s_autocode 主键为 (b_module_id, b_field)
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE dbo.s_module_auto_code "
|
||||
+ "SET b_current_period = ?, b_current_value = ? "
|
||||
+ "WHERE b_id = ?"
|
||||
"UPDATE dbo.s_autocode "
|
||||
+ "SET b_currentperiod = ?, b_currentvalue = ? "
|
||||
+ "WHERE b_module_id = ? AND b_field = ?"
|
||||
)) {
|
||||
statement.setString(1, period);
|
||||
statement.setLong(2, lastValue);
|
||||
statement.setLong(3, rule.id());
|
||||
statement.setString(3, rule.moduleId());
|
||||
statement.setString(4, rule.fieldName());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
connection.commit();
|
||||
@@ -142,18 +140,19 @@ public class DataSaveService {
|
||||
}
|
||||
}
|
||||
|
||||
private AutoCodeRule loadAutoCodeRule(Connection connection, long moduleId) throws SQLException {
|
||||
String sql = "SELECT TOP 1 r.b_id, r.b_prefix, r.b_date_format, "
|
||||
+ "r.b_separator, r.b_sequence_width, r.b_reset_type, "
|
||||
+ "r.b_start_value, r.b_current_period, r.b_current_value, "
|
||||
+ "m.b_savetable, f.b_field "
|
||||
+ "FROM dbo.s_module_auto_code r WITH (UPDLOCK, HOLDLOCK, ROWLOCK) "
|
||||
private AutoCodeRule loadAutoCodeRule(Connection connection, String moduleId) throws SQLException {
|
||||
// 字符串业务键方案:s_autocode 主键为 (b_module_id, b_field),
|
||||
// 字段名直接存于 s_autocode.b_field,不再经 s_module_field 中间表。
|
||||
String sql = "SELECT TOP 1 r.b_prefix, r.b_dateformat, r.b_separator, "
|
||||
+ "r.b_seqwidth, r.b_resettype, r.b_startvalue, "
|
||||
+ "r.b_currentperiod, r.b_currentvalue, r.b_field, m.b_savetable "
|
||||
+ "FROM dbo.s_autocode r WITH (UPDLOCK, HOLDLOCK, ROWLOCK) "
|
||||
+ "INNER JOIN dbo.s_module m ON m.b_id = r.b_module_id "
|
||||
+ "INNER JOIN dbo.s_module_field f ON f.b_id = r.b_field_id "
|
||||
+ "WHERE r.b_module_id = ? AND r.b_canuse = 1 "
|
||||
+ "AND m.b_module_type = 'data' AND f.b_canuse = 1";
|
||||
+ "AND m.b_module_type = 'data' "
|
||||
+ "ORDER BY r.b_field";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, moduleId);
|
||||
statement.setString(1, moduleId);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
if (!resultSet.next()) {
|
||||
throw new BusinessException("当前模块未配置启用的自动编码规则");
|
||||
@@ -163,16 +162,19 @@ public class DataSaveService {
|
||||
if (saveTable == null || saveTable.isBlank()) {
|
||||
throw new BusinessException("模块未配置保存表,无法生成自动编号");
|
||||
}
|
||||
if (fieldName == null || fieldName.isBlank()) {
|
||||
throw new BusinessException("自动编码规则缺少字段配置");
|
||||
}
|
||||
return new AutoCodeRule(
|
||||
resultSet.getLong("b_id"),
|
||||
moduleId,
|
||||
resultSet.getString("b_prefix"),
|
||||
resultSet.getString("b_date_format"),
|
||||
resultSet.getString("b_dateformat"),
|
||||
resultSet.getString("b_separator"),
|
||||
resultSet.getInt("b_sequence_width"),
|
||||
resultSet.getString("b_reset_type"),
|
||||
resultSet.getLong("b_start_value"),
|
||||
resultSet.getString("b_current_period"),
|
||||
resultSet.getLong("b_current_value"),
|
||||
resultSet.getInt("b_seqwidth"),
|
||||
resultSet.getString("b_resettype"),
|
||||
resultSet.getLong("b_startvalue"),
|
||||
resultSet.getString("b_currentperiod"),
|
||||
resultSet.getLong("b_currentvalue"),
|
||||
saveTable,
|
||||
fieldName
|
||||
);
|
||||
@@ -353,7 +355,7 @@ public class DataSaveService {
|
||||
}
|
||||
|
||||
private record AutoCodeRule(
|
||||
long id,
|
||||
String moduleId,
|
||||
String prefix,
|
||||
String dateFormat,
|
||||
String separator,
|
||||
@@ -394,29 +396,42 @@ public class DataSaveService {
|
||||
tableName,
|
||||
metadataCache
|
||||
);
|
||||
DbUtils.ColumnMetadata keyColumn = dbUtils.getColumn(table, keyField);
|
||||
if (keyColumn == null) {
|
||||
throw new BusinessException("主键字段不存在: " + tableName + "." + keyField);
|
||||
// 字符串业务键方案支持复合主键:key_field 可用逗号分隔多个列,
|
||||
// 例如 s_field 传 "b_module_id,b_field";单列主键传 "b_id"。
|
||||
List<DbUtils.ColumnMetadata> keyColumns = new ArrayList<>();
|
||||
for (String name : keyField.split(",")) {
|
||||
String trimmed = name.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
DbUtils.ColumnMetadata column = dbUtils.getColumn(table, trimmed);
|
||||
if (column == null) {
|
||||
throw new BusinessException("主键字段不存在: " + tableName + "." + trimmed);
|
||||
}
|
||||
keyColumns.add(column);
|
||||
}
|
||||
if (keyColumns.isEmpty()) {
|
||||
throw new BusinessException("主键字段不能为空: " + tableName);
|
||||
}
|
||||
|
||||
executeRows(
|
||||
connection,
|
||||
table,
|
||||
keyColumn,
|
||||
keyColumns,
|
||||
"delete",
|
||||
rows(request, "deletes")
|
||||
);
|
||||
executeRows(
|
||||
connection,
|
||||
table,
|
||||
keyColumn,
|
||||
keyColumns,
|
||||
"update",
|
||||
rows(request, "updates")
|
||||
);
|
||||
executeRows(
|
||||
connection,
|
||||
table,
|
||||
keyColumn,
|
||||
keyColumns,
|
||||
"insert",
|
||||
rows(request, "inserts")
|
||||
);
|
||||
@@ -445,7 +460,7 @@ public class DataSaveService {
|
||||
private void executeRows(
|
||||
Connection connection,
|
||||
DbUtils.TableMetadata table,
|
||||
DbUtils.ColumnMetadata keyColumn,
|
||||
List<DbUtils.ColumnMetadata> keyColumns,
|
||||
String action,
|
||||
List<Map<String, Object>> rows
|
||||
) {
|
||||
@@ -453,8 +468,8 @@ public class DataSaveService {
|
||||
try {
|
||||
switch (action) {
|
||||
case "delete" -> dbUtils.delete(connection, table, rows.get(index));
|
||||
case "update" -> dbUtils.update(connection, table, keyColumn, rows.get(index));
|
||||
case "insert" -> dbUtils.insert(connection, table, keyColumn, rows.get(index));
|
||||
case "update" -> dbUtils.update(connection, table, keyColumns, rows.get(index));
|
||||
case "insert" -> dbUtils.insert(connection, table, null, rows.get(index));
|
||||
default -> throw new IllegalArgumentException("不支持的保存操作: " + action);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
|
||||
@@ -132,14 +132,16 @@ public class FileService {
|
||||
""";
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
ps.setLong(1, subid);
|
||||
// father 自引用 bf_files.subid,仍为 bigint
|
||||
ps.setObject(2, toLongOrNull(father));
|
||||
ps.setString(3, filename);
|
||||
ps.setString(4, filesize);
|
||||
ps.setString(5, fileext);
|
||||
ps.setString(6, mapfilename);
|
||||
ps.setObject(7, toLongOrNull(moduleId));
|
||||
ps.setObject(8, toLongOrNull(cateId));
|
||||
ps.setObject(9, toLongOrNull(userId));
|
||||
// 字符串业务键:模块编码 / 分类编码 / 用户账号均按字符串存储
|
||||
ps.setString(7, moduleId);
|
||||
ps.setString(8, cateId);
|
||||
ps.setString(9, userId);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -3,7 +3,7 @@ package cn.g3soft.fmsapi.service;
|
||||
import cn.g3soft.fmsapi.database.OrgContext;
|
||||
import cn.g3soft.fmsapi.utils.ClientInfo;
|
||||
import cn.g3soft.fmsapi.utils.ClientInfoExtractor;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
|
||||
import cn.g3soft.fmsapi.utils.UuidV7Generator;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,7 +37,6 @@ public class LoginLogService {
|
||||
public void recordLogin(
|
||||
String orgId,
|
||||
String account,
|
||||
Long userId,
|
||||
boolean success,
|
||||
String failReason,
|
||||
String sessionId,
|
||||
@@ -50,9 +49,10 @@ public class LoginLogService {
|
||||
ClientInfo info = clientInfoExtractor.extract(request);
|
||||
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("b_id", IdGenerator.nextId());
|
||||
row.put("b_user_id", userId);
|
||||
row.put("b_account", account);
|
||||
// 字符串业务键方案:主键为业务层生成的 UUIDv7
|
||||
row.put("b_id", UuidV7Generator.nextUuid().toString());
|
||||
// b_user_id 引用 b_user.b_id(账号字符串,即稳定用户标识);仅登录成功时存在
|
||||
row.put("b_user_id", success ? account : null);
|
||||
row.put("b_login_type", LOGIN_TYPE_PASSWORD);
|
||||
row.put("b_occurdatetime", Timestamp.from(java.time.Instant.now()));
|
||||
row.put("b_ip", info.ip());
|
||||
|
||||
@@ -2,7 +2,6 @@ package cn.g3soft.fmsapi.utils;
|
||||
|
||||
import cn.g3soft.fmsapi.database.OrgDataSourceManager;
|
||||
import cn.g3soft.fmsapi.exception.BusinessException;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -166,8 +165,9 @@ public class DbUtils {
|
||||
String account
|
||||
) throws SQLException {
|
||||
try (Connection connection = dataSourceManager.getConnection(orgId)) {
|
||||
String sql = "SELECT [b_id], [b_account], [b_name], [b_password] "
|
||||
+ "FROM [b_user] WHERE [b_account] = ? AND [b_canuse] = 1";
|
||||
// 字符串业务键方案:b_user.b_id 即用户账号本身,不再有独立 b_account
|
||||
String sql = "SELECT [b_id], [b_name], [b_password] "
|
||||
+ "FROM [b_user] WHERE [b_id] = ? AND [b_canuse] = 1";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, account);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
@@ -244,23 +244,12 @@ public class DbUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean shouldAssignKey(Object keyValue) {
|
||||
if (keyValue == null) {
|
||||
return true;
|
||||
}
|
||||
if (keyValue instanceof Number number) {
|
||||
return number.longValue() < 0;
|
||||
}
|
||||
if (keyValue instanceof String text && !text.isBlank()) {
|
||||
try {
|
||||
return Long.parseLong(text) < 0;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用新增:不再自动生成主键(字符串业务键方案下,主键由请求方提供,
|
||||
* 例如 s_module.b_id=模块编码、s_field 的 (b_module_id,b_field))。
|
||||
* 需要数据库生成主键的场景(如未来 bigint 业务表、bf_files 文件表)
|
||||
* 由调用方先取号(IdGenerator / 文件服务 subid)再随行写入。
|
||||
*/
|
||||
public int insert(
|
||||
Connection connection,
|
||||
TableMetadata table,
|
||||
@@ -268,9 +257,6 @@ public class DbUtils {
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
Map<String, Object> normalizedRow = normalizeRow(row);
|
||||
if (keyColumn != null && shouldAssignKey(normalizedRow.get(keyColumn.normalizedName()))) {
|
||||
normalizedRow.put(keyColumn.normalizedName(), IdGenerator.nextId());
|
||||
}
|
||||
LinkedHashMap<ColumnMetadata, Object> values = writableValues(
|
||||
table,
|
||||
normalizedRow,
|
||||
@@ -293,13 +279,34 @@ public class DbUtils {
|
||||
ColumnMetadata keyColumn,
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
return update(connection, table, List.of(keyColumn), row);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用修改:支持单列或多列业务键定位(字符串业务键方案下,
|
||||
* 如 s_field 以 (b_module_id, b_field) 为键)。主键值从 row 中读取。
|
||||
*/
|
||||
public int update(
|
||||
Connection connection,
|
||||
TableMetadata table,
|
||||
List<ColumnMetadata> keyColumns,
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
if (keyColumns == null || keyColumns.isEmpty()) {
|
||||
throw new BusinessException("更新操作必须指定主键字段");
|
||||
}
|
||||
Map<String, Object> normalizedRow = normalizeRow(row);
|
||||
Object keyValue = requiredKeyValue(normalizedRow, keyColumn, table.requestedName());
|
||||
for (ColumnMetadata keyColumn : keyColumns) {
|
||||
requiredKeyValue(normalizedRow, keyColumn, table.requestedName());
|
||||
}
|
||||
LinkedHashMap<ColumnMetadata, Object> values = writableValues(
|
||||
table,
|
||||
normalizedRow,
|
||||
keyColumn.normalizedName()
|
||||
null
|
||||
);
|
||||
for (ColumnMetadata keyColumn : keyColumns) {
|
||||
values.remove(keyColumn);
|
||||
}
|
||||
if (values.isEmpty()) {
|
||||
throw new BusinessException("没有可修改的字段: " + table.requestedName());
|
||||
}
|
||||
@@ -307,12 +314,17 @@ public class DbUtils {
|
||||
String setSql = values.keySet().stream()
|
||||
.map(column -> quoteColumn(column) + " = ?")
|
||||
.collect(Collectors.joining(", "));
|
||||
String whereSql = keyColumns.stream()
|
||||
.map(column -> quoteColumn(column) + " = ?")
|
||||
.collect(Collectors.joining(" AND "));
|
||||
String sql = "UPDATE " + table.quotedName()
|
||||
+ " SET " + setSql
|
||||
+ " WHERE " + quoteColumn(keyColumn) + " = ?";
|
||||
+ " WHERE " + whereSql;
|
||||
|
||||
List<Object> parameters = new ArrayList<>(values.values());
|
||||
parameters.add(keyValue);
|
||||
for (ColumnMetadata keyColumn : keyColumns) {
|
||||
parameters.add(normalizedRow.get(keyColumn.normalizedName()));
|
||||
}
|
||||
return executeWrite(connection, sql, parameters, "修改");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.g3soft.fmsapi.utils;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 业务层 UUIDv7 生成器(RFC 9562 时间有序版本)。
|
||||
* <p>UUID 主键不再依赖雪花 bigint:日志表 s_log_* 使用 uniqueidentifier 主键,
|
||||
* 主键非聚集、按 b_occurdatetime 建立聚集索引,因此只需按时间大致有序即可。
|
||||
* <p>生成结果满足 UUIDv7 布局:
|
||||
* <pre>
|
||||
* 0 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | unix_ts_ms |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | unix_ts_ms | ver | rand_a |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* |var| rand_b |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* </pre>
|
||||
*/
|
||||
public final class UuidV7Generator {
|
||||
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private UuidV7Generator() {
|
||||
}
|
||||
|
||||
public static UUID nextUuid() {
|
||||
long unixTsMs = System.currentTimeMillis() & 0xFFFFFFFFFFFFL;
|
||||
long mostSigBits = (unixTsMs << 16) | (RANDOM.nextLong() & 0x0FFFL);
|
||||
mostSigBits = (mostSigBits & ~0xF000L) | 0x7000L;
|
||||
long leastSigBits = (RANDOM.nextLong() & 0x3FFFFFFFFFFFFFFFL) | 0x8000000000000000L;
|
||||
return new UUID(mostSigBits, leastSigBits);
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,7 @@ class AuthServiceTests {
|
||||
"FMS",
|
||||
"g3soft"
|
||||
)).thenReturn(List.of(Map.of(
|
||||
"b_id", 828084517703744L,
|
||||
"b_account", "g3soft",
|
||||
"b_id", "g3soft",
|
||||
"b_name", "G3Soft",
|
||||
"b_password", ""
|
||||
)));
|
||||
@@ -64,7 +63,7 @@ class AuthServiceTests {
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.orElseThrow()).containsEntry("token", "token");
|
||||
Map<?, ?> user = (Map<?, ?>) result.orElseThrow().get("user");
|
||||
assertThat(user.get("id")).isEqualTo(828084517703744L);
|
||||
assertThat(user.get("id")).isEqualTo("g3soft");
|
||||
assertThat(user.get("account")).isEqualTo("g3soft");
|
||||
assertThat(user.get("name")).isEqualTo("G3Soft");
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
@@ -54,3 +54,4 @@ cn\g3soft\fmsapi\utils\ClientInfoExtractor.class
|
||||
cn\g3soft\fmsapi\exception\AuthenticationException.class
|
||||
cn\g3soft\fmsapi\database\OrgDatabaseProperties.class
|
||||
cn\g3soft\fmsapi\service\DataSaveService$AutoCodeFormat.class
|
||||
cn\g3soft\fmsapi\utils\UuidV7Generator.class
|
||||
+1
@@ -42,3 +42,4 @@ D:\workspace\code\fms\fms-api\src\main\java\cn\g3soft\fmsapi\utils\snowflake\cor
|
||||
D:\workspace\code\fms\fms-api\src\main\java\cn\g3soft\fmsapi\utils\snowflake\idgen\DefaultIdGenerator.java
|
||||
D:\workspace\code\fms\fms-api\src\main\java\cn\g3soft\fmsapi\utils\snowflake\idgen\IdGenerator.java
|
||||
D:\workspace\code\fms\fms-api\src\main\java\cn\g3soft\fmsapi\utils\StringUtils.java
|
||||
D:\workspace\code\fms\fms-api\src\main\java\cn\g3soft\fmsapi\utils\UuidV7Generator.java
|
||||
Binary file not shown.
Reference in new issue
Block a user