20260802220544

This commit is contained in:
oneao committed 2026-08-02 22:05:44 +08:00
1 parent c00329b5a4
commit 4fc9040a8a
60 files changed
+6273 -908

No files matched your search

+3
View File
@@ -3,3 +3,6 @@ src/main/resources/ip2region.xdb
# 多机构数据库连接配置(含敏感信息,已约定不入库)
config/dbconfigs/
# 本地文件存储目录(不入库)
data/files/
@@ -0,0 +1,24 @@
/* 为合作伙伴分类树增加多语言键。*/
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
IF OBJECT_ID(N'dbo.b_othercompany_bmfl', N'U') IS NOT NULL
BEGIN
IF NOT EXISTS (
SELECT 1 FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.b_othercompany_bmfl') AND name = 'b_i18n'
)
BEGIN
ALTER TABLE dbo.b_othercompany_bmfl
ADD b_i18n varchar(150) NULL;
END;
EXEC sys.sp_executesql N'
UPDATE dbo.b_othercompany_bmfl
SET b_i18n = ''category.base_othercompany.'' + CAST(b_id AS varchar(30))
WHERE b_i18n IS NULL OR b_i18n = '''''';
END;
COMMIT TRANSACTION;
@@ -0,0 +1,13 @@
/* 为编辑配置增加列宽字段,支持可编辑表格保存栏位设置。 */
SET NOCOUNT ON;
IF NOT EXISTS (
SELECT 1
FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.s_module_field_edit')
AND name = N'b_width'
)
BEGIN
ALTER TABLE dbo.s_module_field_edit
ADD b_width int NULL;
END;
@@ -0,0 +1,63 @@
/* 为"其他数据"可编辑表格的行拖拽排序提供排序字段。
1) b_port / b_city / b_oceanline 补 b_xh 列(b_money 已有);
2) 按 b_code 顺序为 b_xh 为空的存量数据初始化排序值(幂等,不覆盖已有排序);
3) 对应数据模块默认排序改为 b_xh ASC,使拖拽后的顺序在刷新后保持。 */
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
/* ================= 1. b_port ================= */
IF COL_LENGTH(N'dbo.b_port', N'b_xh') IS NULL
BEGIN
EXEC sys.sp_executesql N'ALTER TABLE dbo.b_port ADD b_xh int NULL;';
END;
EXEC sys.sp_executesql N'
WITH numbered AS (
SELECT b_id, b_xh, ROW_NUMBER() OVER (ORDER BY b_code ASC, b_id ASC) AS row_no
FROM dbo.b_port
WHERE b_xh IS NULL
)
UPDATE numbered SET b_xh = row_no * 10;';
/* ================= 2. b_city ================= */
IF COL_LENGTH(N'dbo.b_city', N'b_xh') IS NULL
BEGIN
EXEC sys.sp_executesql N'ALTER TABLE dbo.b_city ADD b_xh int NULL;';
END;
EXEC sys.sp_executesql N'
WITH numbered AS (
SELECT b_id, b_xh, ROW_NUMBER() OVER (ORDER BY b_code ASC, b_id ASC) AS row_no
FROM dbo.b_city
WHERE b_xh IS NULL
)
UPDATE numbered SET b_xh = row_no * 10;';
/* ================= 3. b_oceanline ================= */
IF COL_LENGTH(N'dbo.b_oceanline', N'b_xh') IS NULL
BEGIN
EXEC sys.sp_executesql N'ALTER TABLE dbo.b_oceanline ADD b_xh int NULL;';
END;
EXEC sys.sp_executesql N'
WITH numbered AS (
SELECT b_id, b_xh, ROW_NUMBER() OVER (ORDER BY b_code ASC, b_id ASC) AS row_no
FROM dbo.b_oceanline
WHERE b_xh IS NULL
)
UPDATE numbered SET b_xh = row_no * 10;';
/* ================= 4. 模块默认排序切到 b_xh =================
base_otherdata 页面下的所有数据模块统一按 b_xh 排序,
拖拽后的顺序在刷新后才会保持。 */
UPDATE m
SET m.b_orderfield = N'b_xh ASC'
FROM dbo.s_module m
INNER JOIN dbo.s_module p
ON p.b_id = m.b_parent_id
AND p.b_code = N'base_otherdata'
WHERE m.b_module_type = N'data';
COMMIT TRANSACTION;
@@ -0,0 +1,19 @@
/* 移除其他数据分类树的多语言列。此前通过 025 添加,现决定暂不做多语言。*/
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
IF OBJECT_ID(N'dbo.b_other_bmfl', N'U') IS NOT NULL
BEGIN
IF EXISTS (
SELECT 1 FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.b_other_bmfl') AND name = 'b_i18n'
)
BEGIN
ALTER TABLE dbo.b_other_bmfl
DROP COLUMN b_i18n;
END;
END;
COMMIT TRANSACTION;
@@ -0,0 +1,19 @@
/* 移除合作伙伴分类树的多语言列。此前通过 027 添加,现决定暂不做多语言。*/
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
IF OBJECT_ID(N'dbo.b_othercompany_bmfl', N'U') IS NOT NULL
BEGIN
IF EXISTS (
SELECT 1 FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.b_othercompany_bmfl') AND name = 'b_i18n'
)
BEGIN
ALTER TABLE dbo.b_othercompany_bmfl
DROP COLUMN b_i18n;
END;
END;
COMMIT TRANSACTION;
@@ -0,0 +1,384 @@
/* 032_rebuild_otherdata.sql — 重做"其他数据"子系统迁移
*
* 背景:此前用 OtherDataMigration 工具迁移产生错误结果——
* 1. 分类树被连跑 7 次产生重复分支;
* 2. 只有 b_port 一个数据模块,城市/币种/航线模块缺失;
* 3. b_port 真实数据未迁入(旧 b_id 含中文港口名,b_code 列 SQL_Latin1 排序规则存不下,
* 插入时中文转 '?' 触发 UQ_b_port_b_code 唯一索引冲突而整表回滚)。
*
* 本脚本(前置:026、030 已应用,b_other_bmfl 已无 b_level/b_path/b_i18n 列):
* 1. 清空并重建分类树(基础信息/财务信息/物流信息 + 4 个数据节点,模块编码沿用表名 b_*);
* 2. 移除 022 的 mock 占位数据;
* 3. b_port.b_code 排序规则改为 Chinese_PRC_CI_AS,迁入 193 条真实港口数据;
* 4. 补建 b_city / b_money / b_oceanline 数据模块(字段/列表/查询/编辑配置);
* 5. 回填 b_city / b_oceanline 迁移数据的 b_xh 排序值。
*
* 幂等:可重复执行。
*/
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
/* =========================================================================
1. 清理错乱的分类树
========================================================================= */
DELETE FROM dbo.b_other_bmfl;
/* =========================================================================
2. 移除 022 mock 占位数据(按预留 ID 段精确删除)
========================================================================= */
DELETE FROM dbo.b_port WHERE b_id BETWEEN 920000000000110001 AND 920000000000110005;
DELETE FROM dbo.b_city WHERE b_id BETWEEN 920000000000110101 AND 920000000000110104;
DELETE FROM dbo.b_money WHERE b_id BETWEEN 920000000000110201 AND 920000000000110204;
DELETE FROM dbo.b_oceanline WHERE b_id BETWEEN 920000000000110301 AND 920000000000110304;
/* =========================================================================
3. b_port.b_code 排序规则改为 Chinese_PRC_CI_AS(容纳中文港口代码)
(需先删依赖该列的唯一索引,改完再重建)
========================================================================= */
IF EXISTS (
SELECT 1 FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.b_port')
AND name = N'b_code'
AND collation_name <> N'Chinese_PRC_CI_AS'
)
BEGIN
IF EXISTS (
SELECT 1 FROM sys.key_constraints
WHERE parent_object_id = OBJECT_ID(N'dbo.b_port')
AND name = N'UQ_b_port_b_code'
)
ALTER TABLE dbo.b_port DROP CONSTRAINT [UQ_b_port_b_code];
ALTER TABLE dbo.b_port ALTER COLUMN b_code varchar(50) COLLATE Chinese_PRC_CI_AS NOT NULL;
IF NOT EXISTS (
SELECT 1 FROM sys.key_constraints
WHERE parent_object_id = OBJECT_ID(N'dbo.b_port')
AND name = N'UQ_b_port_b_code'
)
ALTER TABLE dbo.b_port ADD CONSTRAINT [UQ_b_port_b_code] UNIQUE NONCLUSTERED (b_code);
END;
GO
/* =========================================================================
4. 迁移 b_port 真实数据(193 行,含中文代码)
========================================================================= */
INSERT INTO dbo.b_port (
b_id, b_name_c, b_name_e, b_country, b_oceanline, b_edicode, b_bz, b_tz,
b_inputdatetime, b_code, b_xh
)
SELECT 920000000000110005 + ROW_NUMBER() OVER (ORDER BY LTRIM(RTRIM(old.b_id))),
old.b_name_c, old.b_name_e, old.b_country, old.b_oceanline, old.b_edicode,
old.b_bz, old.b_tz,
SYSDATETIME(),
LTRIM(RTRIM(old.b_id)),
ROW_NUMBER() OVER (ORDER BY LTRIM(RTRIM(old.b_id))) * 10
FROM G3HY2025.dbo.b_port old
WHERE NOT EXISTS (
SELECT 1 FROM dbo.b_port new WHERE new.b_code = LTRIM(RTRIM(old.b_id))
);
/* =========================================================================
5. 补建数据模块 b_city / b_money / b_oceanline
========================================================================= */
DECLARE @PageId bigint = 920000000000001100;
DECLARE @DataId bigint;
DECLARE @DataCode varchar(100);
/* ---- 5.1 b_city(城市) ---- */
SET @DataCode = 'b_city';
SET @DataId = 920000000000001102;
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
BEGIN
INSERT INTO dbo.s_module (
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type, b_route,
b_viewtable, b_savetable, b_keyfield, b_orderfield, b_canmenu, b_canuse, b_xh
) VALUES (
@DataId, @DataCode, @PageId, N'城市', 'module.b_city', 'data', NULL,
'b_city', 'b_city', 'b_id', 'b_xh ASC', 0, 1, 20
);
END
DECLARE @CityFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
INSERT INTO @CityFields VALUES
(920000000000010201, 'b_id', N'系统编号', 'input', 10),
(920000000000010202, 'b_code', N'城市代码', 'input', 20),
(920000000000010203, 'b_name_c', N'中文名称', 'input', 30),
(920000000000010204, 'b_name_e', N'英文名称', 'input', 40),
(920000000000010205, 'b_country_name_c', N'国家(中)', 'input', 50),
(920000000000010206, 'b_country_name_e', N'国家(英)', 'input', 60),
(920000000000010207, 'b_bz', N'备注', 'input', 70);
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
SELECT source.b_id, @DataId, source.b_field, source.b_name,
'field.b_city.' + source.b_field, source.b_type, 1
FROM @CityFields source
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field t WHERE t.b_id = source.b_id
);
DECLARE @CityList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
INSERT INTO @CityList VALUES
(920000000000020201, 'b_code', 10, 120),
(920000000000020202, 'b_name_c', 20, 150),
(920000000000020203, 'b_name_e', 30, 180),
(920000000000020204, 'b_country_name_c', 40, 140),
(920000000000020205, 'b_country_name_e', 50, 140);
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
SELECT source.b_id, @DataId, f.b_id, 'field.b_city.' + f.b_field, 1, source.b_xh, source.b_width
FROM @CityList source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_id = source.b_id
);
DECLARE @CityQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
INSERT INTO @CityQuery VALUES
(920000000000030201, 'b_code', 10),
(920000000000030202, 'b_name_c', 20);
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
FROM @CityQuery source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_id = source.b_id
);
DECLARE @CityEdit TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_visible int NOT NULL, b_xh int NOT NULL);
INSERT INTO @CityEdit VALUES
(920000000000050201, 'b_id', 0, 10),
(920000000000050202, 'b_code', 1, 20),
(920000000000050203, 'b_name_c', 1, 30),
(920000000000050204, 'b_name_e', 1, 40),
(920000000000050205, 'b_country_name_c', 1, 50),
(920000000000050206, 'b_country_name_e', 1, 60),
(920000000000050207, 'b_bz', 1, 70);
INSERT INTO dbo.s_module_field_edit (b_id, b_module_id, b_field_id, b_required, b_readonly, b_disabled, b_xh, b_visible)
SELECT source.b_id, @DataId, f.b_id, 0, 0, 0, source.b_xh, source.b_visible
FROM @CityEdit source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_edit t WHERE t.b_id = source.b_id
);
/* ---- 5.2 b_money(币种) ---- */
SET @DataCode = 'b_money';
SET @DataId = 920000000000001103;
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
BEGIN
INSERT INTO dbo.s_module (
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type, b_route,
b_viewtable, b_savetable, b_keyfield, b_orderfield, b_canmenu, b_canuse, b_xh
) VALUES (
@DataId, @DataCode, @PageId, N'币种', 'module.b_money', 'data', NULL,
'b_money', 'b_money', 'b_id', 'b_xh ASC', 0, 1, 30
);
END
DECLARE @MoneyFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
INSERT INTO @MoneyFields VALUES
(920000000000010301, 'b_id', N'系统编号', 'input', 10),
(920000000000010302, 'b_code', N'币种代码', 'input', 20),
(920000000000010303, 'b_name', N'名称', 'input', 30),
(920000000000010304, 'b_edicode', N'EDI代码', 'input', 40),
(920000000000010305, 'b_symbol', N'符号', 'input', 50),
(920000000000010306, 'b_hl', N'汇率', 'number', 60),
(920000000000010307, 'b_isconvert', N'可兑换', 'checkbox', 70),
(920000000000010308, 'b_canuse', N'启用', 'checkbox', 80),
(920000000000010309, 'b_xh', N'排序', 'number', 90),
(920000000000010310, 'b_bz', N'备注', 'input', 100);
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
SELECT source.b_id, @DataId, source.b_field, source.b_name,
'field.b_money.' + source.b_field, source.b_type, 1
FROM @MoneyFields source
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field t WHERE t.b_id = source.b_id
);
DECLARE @MoneyList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
INSERT INTO @MoneyList VALUES
(920000000000020301, 'b_code', 10, 120),
(920000000000020302, 'b_name', 20, 150),
(920000000000020303, 'b_edicode', 30, 120),
(920000000000020304, 'b_symbol', 40, 80),
(920000000000020305, 'b_hl', 50, 120),
(920000000000020306, 'b_canuse', 60, 80);
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
SELECT source.b_id, @DataId, f.b_id, 'field.b_money.' + f.b_field, 1, source.b_xh, source.b_width
FROM @MoneyList source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_id = source.b_id
);
DECLARE @MoneyQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
INSERT INTO @MoneyQuery VALUES
(920000000000030301, 'b_code', 10),
(920000000000030302, 'b_name', 20);
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
FROM @MoneyQuery source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_id = source.b_id
);
DECLARE @MoneyEdit TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_visible int NOT NULL, b_xh int NOT NULL);
INSERT INTO @MoneyEdit VALUES
(920000000000050301, 'b_id', 0, 10),
(920000000000050302, 'b_code', 1, 20),
(920000000000050303, 'b_name', 1, 30),
(920000000000050304, 'b_edicode', 1, 40),
(920000000000050305, 'b_symbol', 1, 50),
(920000000000050306, 'b_hl', 1, 60),
(920000000000050307, 'b_isconvert', 1, 70),
(920000000000050308, 'b_canuse', 1, 80),
(920000000000050309, 'b_xh', 1, 90),
(920000000000050310, 'b_bz', 1, 100);
INSERT INTO dbo.s_module_field_edit (b_id, b_module_id, b_field_id, b_required, b_readonly, b_disabled, b_xh, b_visible)
SELECT source.b_id, @DataId, f.b_id, 0, 0, 0, source.b_xh, source.b_visible
FROM @MoneyEdit source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_edit t WHERE t.b_id = source.b_id
);
/* ---- 5.3 b_oceanline(航线) ---- */
SET @DataCode = 'b_oceanline';
SET @DataId = 920000000000001104;
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
BEGIN
INSERT INTO dbo.s_module (
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type, b_route,
b_viewtable, b_savetable, b_keyfield, b_orderfield, b_canmenu, b_canuse, b_xh
) VALUES (
@DataId, @DataCode, @PageId, N'航线', 'module.b_oceanline', 'data', NULL,
'b_oceanline', 'b_oceanline', 'b_id', 'b_xh ASC', 0, 1, 40
);
END
DECLARE @OceanFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
INSERT INTO @OceanFields VALUES
(920000000000010401, 'b_id', N'系统编号', 'input', 10),
(920000000000010402, 'b_code', N'航线代码', 'input', 20),
(920000000000010403, 'b_name', N'名称', 'input', 30),
(920000000000010404, 'b_edicode', N'EDI代码', 'input', 40),
(920000000000010405, 'b_bz', N'备注', 'input', 50);
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
SELECT source.b_id, @DataId, source.b_field, source.b_name,
'field.b_oceanline.' + source.b_field, source.b_type, 1
FROM @OceanFields source
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field t WHERE t.b_id = source.b_id
);
DECLARE @OceanList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
INSERT INTO @OceanList VALUES
(920000000000020401, 'b_code', 10, 120),
(920000000000020402, 'b_name', 20, 180),
(920000000000020403, 'b_edicode', 30, 120);
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
SELECT source.b_id, @DataId, f.b_id, 'field.b_oceanline.' + f.b_field, 1, source.b_xh, source.b_width
FROM @OceanList source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_id = source.b_id
);
DECLARE @OceanQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
INSERT INTO @OceanQuery VALUES
(920000000000030401, 'b_code', 10),
(920000000000030402, 'b_name', 20);
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
FROM @OceanQuery source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_id = source.b_id
);
DECLARE @OceanEdit TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_visible int NOT NULL, b_xh int NOT NULL);
INSERT INTO @OceanEdit VALUES
(920000000000050401, 'b_id', 0, 10),
(920000000000050402, 'b_code', 1, 20),
(920000000000050403, 'b_name', 1, 30),
(920000000000050404, 'b_edicode', 1, 40),
(920000000000050405, 'b_bz', 1, 50);
INSERT INTO dbo.s_module_field_edit (b_id, b_module_id, b_field_id, b_required, b_readonly, b_disabled, b_xh, b_visible)
SELECT source.b_id, @DataId, f.b_id, 0, 0, 0, source.b_xh, source.b_visible
FROM @OceanEdit source
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
WHERE NOT EXISTS (
SELECT 1 FROM dbo.s_module_field_edit t WHERE t.b_id = source.b_id
);
/* =========================================================================
6. 回填 b_city / b_oceanline 迁移数据的 b_xh(b_money 旧库自带 b_xh)
========================================================================= */
WITH numbered AS (
SELECT b_id, b_xh, ROW_NUMBER() OVER (ORDER BY b_code ASC, b_id ASC) AS row_no
FROM dbo.b_city WHERE b_xh IS NULL
)
UPDATE numbered SET b_xh = row_no * 10;
WITH numbered AS (
SELECT b_id, b_xh, ROW_NUMBER() OVER (ORDER BY b_code ASC, b_id ASC) AS row_no
FROM dbo.b_oceanline WHERE b_xh IS NULL
)
UPDATE numbered SET b_xh = row_no * 10;
/* =========================================================================
7. 重建分类树(引用设计:category 仅结构,module 引用 s_module.b_code)
========================================================================= */
DECLARE @CatBasic bigint = 930000000000000101;
DECLARE @CatFinance bigint = 930000000000000102;
DECLARE @CatLogistics bigint = 930000000000000103;
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @CatBasic)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@CatBasic, NULL, N'基础信息', 'category', NULL, 10, 1);
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @CatFinance)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@CatFinance, NULL, N'财务信息', 'category', NULL, 20, 1);
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @CatLogistics)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@CatLogistics, NULL, N'物流信息', 'category', NULL, 30, 1);
DECLARE @NodePort bigint = 930000000000000201;
DECLARE @NodeCity bigint = 930000000000000202;
DECLARE @NodeMoney bigint = 930000000000000203;
DECLARE @NodeOceanline bigint = 930000000000000204;
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodePort)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@NodePort, @CatBasic, N'港口', 'module', 'b_port', 10, 1);
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodeCity)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@NodeCity, @CatBasic, N'城市', 'module', 'b_city', 20, 1);
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodeMoney)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@NodeMoney, @CatFinance, N'币种', 'module', 'b_money', 10, 1);
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodeOceanline)
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_node_type, b_module_code, b_xh, b_canuse)
VALUES (@NodeOceanline, @CatLogistics, N'航线', 'module', 'b_oceanline', 10, 1);
COMMIT TRANSACTION;
+9 -5
View File
@@ -61,11 +61,15 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>com.github.ua-parser</groupId>
@@ -0,0 +1,68 @@
package cn.g3soft.fmsapi.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "fms.file")
public class FileStorageProperties {
private String storageType;
private Local local = new Local();
private Oss oss = new Oss();
public String getStorageType() {
return storageType;
}
public void setStorageType(String storageType) {
this.storageType = storageType;
}
public Local getLocal() {
return local;
}
public void setLocal(Local local) {
this.local = local;
}
public Oss getOss() {
return oss;
}
public void setOss(Oss oss) {
this.oss = oss;
}
public static class Local {
private String storageDir;
private String urlPrefix;
public String getStorageDir() {
return storageDir;
}
public void setStorageDir(String storageDir) {
this.storageDir = storageDir;
}
public String getUrlPrefix() {
return urlPrefix;
}
public void setUrlPrefix(String urlPrefix) {
this.urlPrefix = urlPrefix;
}
}
public static class Oss {
private String g3ossUrl;
public String getG3ossUrl() {
return g3ossUrl;
}
public void setG3ossUrl(String g3ossUrl) {
this.g3ossUrl = g3ossUrl;
}
}
}
@@ -79,6 +79,7 @@ public class JwtAuthFilter extends OncePerRequestFilter {
}
OrgContext.setOrgId(claims.orgId());
OrgContext.setUserId(claims.userId());
try {
filterChain.doFilter(request, response);
} finally {
@@ -0,0 +1,112 @@
package cn.g3soft.fmsapi.controller;
import cn.g3soft.fmsapi.config.FileStorageProperties;
import cn.g3soft.fmsapi.service.FileService;
import cn.g3soft.fmsapi.utils.ApiResponse;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private FileStorageProperties props;
@Resource
private FileService fileService;
/**
* 返回存储模式配置
*/
@PostMapping("/config")
public ApiResponse<Map<String, Object>> config() {
Map<String, Object> cfg = new LinkedHashMap<>();
cfg.put("storageType", props.getStorageType().toLowerCase());
return ApiResponse.success(cfg);
}
/**
* 本地上传(支持多文件,FormData key: "files")
*/
@PostMapping("/upload")
public ApiResponse<List<Map<String, Object>>> upload(
@RequestParam("files") List<MultipartFile> files,
@RequestParam("father") String father,
@RequestParam("moduleId") String moduleId,
@RequestParam(value = "cateId", required = false) String cateId
) throws IOException, SQLException {
return ApiResponse.success(fileService.localUpload(files, father, moduleId, cateId));
}
/**
* OSS 上传凭证(bucket = 机构码)
*/
@PostMapping("/upload-credential")
public ApiResponse<?> uploadCredential() throws IOException, InterruptedException {
return ApiResponse.success(fileService.ossCredential());
}
/**
* 保存文件记录到 DB(OSS 直传后回调)
*/
@PostMapping("/save-record")
public ApiResponse<Map<String, Object>> saveRecord(@RequestBody Map<String, Object> body)
throws SQLException {
return ApiResponse.success(fileService.saveRecord(body));
}
/**
* 删除文件
*/
@PostMapping("/delete")
public ApiResponse<Void> delete(@RequestBody Map<String, Object> body)
throws SQLException, IOException, InterruptedException {
String subid = String.valueOf(body.get("subid"));
fileService.delete(subid);
return ApiResponse.success(null);
}
/**
* 本地文件访问(路径含子目录: {orgId}/{yyyy}/{MM}/{dd}/{file})
*/
@GetMapping("/static/{fileName:.+}")
public void serveFile(@PathVariable String fileName, HttpServletResponse response) throws IOException {
Path dir = Path.of(props.getLocal().getStorageDir()).toAbsolutePath().normalize();
Path file = dir.resolve(fileName).normalize();
// 路径穿越防护
if (!file.startsWith(dir) || !Files.isRegularFile(file)) {
response.setStatus(404);
return;
}
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "inline; filename*=UTF-8''"
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8));
response.setContentLengthLong(Files.size(file));
try (OutputStream out = response.getOutputStream()) {
Files.copy(file, out);
}
}
}
@@ -3,10 +3,13 @@ package cn.g3soft.fmsapi.database;
public final class OrgContext {
private static final ThreadLocal<String> ORG_ID_HOLDER = new ThreadLocal<>();
private static final ThreadLocal<String> USER_ID_HOLDER = new ThreadLocal<>();
private OrgContext() {
}
// -- orgId --
public static void setOrgId(String orgId) {
if (orgId == null || orgId.isBlank()) {
throw new IllegalArgumentException("机构码不能为空");
@@ -26,7 +29,26 @@ public final class OrgContext {
return orgId;
}
// -- userId --
public static void setUserId(String userId) {
USER_ID_HOLDER.set(userId);
}
public static String getUserId() {
return USER_ID_HOLDER.get();
}
public static String requireUserId() {
String userId = getUserId();
if (userId == null) {
throw new IllegalStateException("当前请求未设置用户上下文");
}
return userId;
}
public static void clear() {
ORG_ID_HOLDER.remove();
USER_ID_HOLDER.remove();
}
}
@@ -28,9 +28,12 @@ public class OrgDataSourceFactory {
dataSource.setTestWhileIdle(poolProperties.isTestWhileIdle());
dataSource.setTestOnBorrow(poolProperties.isTestOnBorrow());
dataSource.setTestOnReturn(poolProperties.isTestOnReturn());
dataSource.setConnectionErrorRetryAttempts(0);
dataSource.setBreakAfterAcquireFailure(true);
dataSource.setFailFast(true);
dataSource.setConnectionErrorRetryAttempts(3);
dataSource.setBreakAfterAcquireFailure(false);
dataSource.setFailFast(false);
// 定期 ping 空闲连接,对抗云 NAT/防火墙回收空闲 TCP 会话(默认 120s)
dataSource.setKeepAlive(true);
dataSource.setKeepAliveBetweenTimeMillis(120_000);
dataSource.setTimeBetweenEvictionRunsMillis(
poolProperties.getTimeBetweenEvictionRuns().toMillis()
);
@@ -0,0 +1,267 @@
package cn.g3soft.fmsapi.service;
import cn.g3soft.fmsapi.config.FileStorageProperties;
import cn.g3soft.fmsapi.database.OrgContext;
import cn.g3soft.fmsapi.exception.BusinessException;
import cn.g3soft.fmsapi.utils.DbUtils;
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.sql.DataSource;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Duration;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Service
public class FileService {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
@Resource
private FileStorageProperties props;
@Resource
private DbUtils dbUtils;
@Resource
private DataSource dataSource;
/**
* 本地上传:逐个写磁盘 → 写 DB → 返回元数据列表
*/
public List<Map<String, Object>> localUpload(List<MultipartFile> files, String father, String moduleId, String cateId)
throws IOException, SQLException {
List<Map<String, Object>> results = new ArrayList<>();
for (MultipartFile file : files) {
results.add(uploadOne(file, father, moduleId, cateId));
}
return results;
}
private Map<String, Object> uploadOne(MultipartFile file, String father, String moduleId, String cateId)
throws IOException, SQLException {
long subid = IdGenerator.nextId();
String originalName = file.getOriginalFilename();
String ext = extension(originalName);
String fileName = UUID.randomUUID().toString().replace("-", "") + (ext.isEmpty() ? "" : "." + ext);
// 机构码/年/月/日/文件名
String orgId = OrgContext.requireOrgId();
LocalDate today = LocalDate.now();
String subPath = String.format("%s/%04d/%02d/%02d/%s",
orgId, today.getYear(), today.getMonthValue(), today.getDayOfMonth(), fileName);
Path baseDir = Path.of(props.getLocal().getStorageDir()).toAbsolutePath().normalize();
Path target = baseDir.resolve(subPath);
Files.createDirectories(target.getParent());
Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
String mapFilename = props.getLocal().getUrlPrefix() + "/" + subPath;
insertFileRecord(subid, father, moduleId, cateId,
originalName, String.valueOf(file.getSize()), ext, mapFilename);
Map<String, Object> meta = new LinkedHashMap<>();
meta.put("subid", String.valueOf(subid));
meta.put("mx_filename", originalName);
meta.put("mx_filesize", String.valueOf(file.getSize()));
meta.put("mx_fileext", ext);
meta.put("mx_mapfilename", mapFilename);
meta.put("father", father);
meta.put("mx_moduleid", moduleId);
meta.put("mx_cate_id", cateId);
return meta;
}
/**
* 保存文件记录到 DB(OSS 模式:前端直传后回调写入)
*/
public Map<String, Object> saveRecord(Map<String, Object> meta) throws SQLException {
long subid = Long.parseLong(String.valueOf(meta.get("subid")));
String father = String.valueOf(meta.get("father"));
String moduleId = String.valueOf(meta.get("mx_moduleid"));
String cateId = meta.containsKey("mx_cate_id") ? String.valueOf(meta.get("mx_cate_id")) : null;
String filename = String.valueOf(meta.get("mx_filename"));
String filesize = String.valueOf(meta.get("mx_filesize"));
String fileext = String.valueOf(meta.get("mx_fileext"));
String mapfilename = String.valueOf(meta.get("mx_mapfilename"));
insertFileRecord(subid, father, moduleId, cateId,
filename, filesize, fileext, mapfilename);
Map<String, Object> result = new LinkedHashMap<>();
result.put("subid", String.valueOf(subid));
result.put("message", "记录已保存");
return result;
}
private void insertFileRecord(long subid, String father, String moduleId, String cateId,
String filename, String filesize, String fileext, String mapfilename)
throws SQLException {
String userId = OrgContext.getUserId();
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
String sql = """
INSERT INTO [bf_files] (
[subid], [father], [mx_filename], [mx_filesize], [mx_fileext],
[mx_mapfilename], [mx_moduleid], [mx_cate_id], [mx_xh], [mx_sh],
[mx_inputuser_id], [mx_inputdatetime]
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, SYSDATETIME())
""";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setLong(1, subid);
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.executeUpdate();
}
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
private static Long toLongOrNull(String s) {
if (s == null || s.isBlank()) return null;
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
return null;
}
}
/**
* OSS 凭证:调 g3oss,bucket = 当前机构码
*/
@SuppressWarnings("unchecked")
public Map<String, Object> ossCredential() throws IOException, InterruptedException {
String orgId = OrgContext.requireOrgId();
String url = props.getOss().getG3ossUrl() + "/upload?orgid=" + orgId + "&bucket=" + orgId;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(10))
.GET()
.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new BusinessException("获取 OSS 上传凭证失败: " + response.statusCode());
}
return OBJECT_MAPPER.readValue(response.body(), Map.class);
}
/**
* 删除文件:查 bf_files → 删存储 → 删 DB 行
*/
public void delete(String subid) throws SQLException, IOException, InterruptedException {
List<Map<String, Object>> rows = dbUtils.loadData("bf_files", "subid = " + subid, null, null);
if (rows.isEmpty()) {
return;
}
String mapfilename = (String) rows.get(0).get("mx_mapfilename");
// 删存储文件
if ("aliyun-oss".equalsIgnoreCase(props.getStorageType())) {
deleteOssFile(mapfilename);
} else {
deleteLocalFile(mapfilename);
}
// 删 DB 行
deleteDbRow(subid);
}
private void deleteLocalFile(String mapfilename) throws IOException {
if (mapfilename == null) return;
// mapfilename: /api/file/static/{orgId}/{yyyy}/{MM}/{dd}/{file}
String prefix = props.getLocal().getUrlPrefix() + "/";
if (!mapfilename.startsWith(prefix)) return;
String relativePath = mapfilename.substring(prefix.length());
Path file = Path.of(props.getLocal().getStorageDir()).toAbsolutePath().normalize().resolve(relativePath);
Files.deleteIfExists(file);
}
private void deleteOssFile(String mapfilename) throws IOException, InterruptedException {
if (mapfilename == null || mapfilename.isBlank()) {
return;
}
// 从 URL 提取 objectName(bucket 直接用机构码)
String objectName = mapfilename;
if (objectName.contains("://")) {
objectName = objectName.substring(objectName.indexOf("://") + 3);
}
// 跳过 host + bucket 两段
int firstSlash = objectName.indexOf('/');
if (firstSlash > 0) {
objectName = objectName.substring(firstSlash + 1);
int secondSlash = objectName.indexOf('/');
if (secondSlash > 0) {
objectName = objectName.substring(secondSlash + 1);
}
}
String bucket = OrgContext.requireOrgId();
String url = props.getOss().getG3ossUrl()
+ "/delete?bucketName=" + bucket + "&objectName=" + objectName;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(10))
.GET()
.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new BusinessException("OSS 删除文件失败: " + response.statusCode());
}
}
private void deleteDbRow(String subid) throws SQLException {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
String sql = "DELETE FROM [bf_files] WHERE [subid] = ?";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, Long.parseLong(subid));
statement.executeUpdate();
}
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
private String extension(String name) {
if (name == null || !name.contains(".")) {
return "";
}
return name.substring(name.lastIndexOf('.') + 1).toLowerCase();
}
}
@@ -20,7 +20,14 @@ fms:
max-wait: 5000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-borrow: true
test-on-return: false
time-between-eviction-runs: 60s
min-evictable-idle-time: 5m
time-between-eviction-runs: 30s
min-evictable-idle-time: 30s
file:
storage-type: local
local:
storage-dir: ./data/files
url-prefix: /api/file/static
oss:
g3oss-url: http://g3oss.g3soft.cn:8082
@@ -20,7 +20,14 @@ fms:
max-wait: 5000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-borrow: true
test-on-return: false
time-between-eviction-runs: 60s
min-evictable-idle-time: 5m
time-between-eviction-runs: 30s
min-evictable-idle-time: 30s
file:
storage-type: local
local:
storage-dir: ./data/files
url-prefix: /api/file/static
oss:
g3oss-url: http://g3oss.g3soft.cn:8082
File diff suppressed because it is too large. Load diff
+5 -4
View File
@@ -62,11 +62,11 @@ cd fms-api/tools/migration
export JAVA_HOME=/path/to/jdk-21 # Linux/Mac
set JAVA_HOME=D:\devtool\jdk\jdk-21 # Windows
# 编译
javac -cp "%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java
# 编译(源文件含中文注释,需指定 UTF-8 编码)
javac -encoding UTF-8 -cp "%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java
# 运行(以 v_b_othercompany 为例)
java -cp ".;%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync v_b_othercompany
# 运行(参数为 s_module.b_viewtable 的值;本例模块注册的是表名 b_port,非视图名 v_b_port)
java -cp ".;%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync b_port
```
## 输出示例
@@ -110,3 +110,4 @@ java -cp ".;%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4
| --- | --- | --- | --- |
| `v_b_othercompany` | 2026-07-30 | 50 | 已同步 |
| `v_b_contact` | 2026-07-30 | 36 | 已同步 |
| `b_port` | 2026-08-01 | 13 | 已同步(`s_module.b_viewtable='b_port'` 表名,非 `v_b_port`;更新字段名 8 条,列表/编辑显示各 8 条) |