using AgriculturalPlatform.Api.Models;
using Microsoft.EntityFrameworkCore;
namespace AgriculturalPlatform.Api.Data;
///
/// 兼容已存在的数据库:EnsureCreated 不会为已存在的表补列,
/// 这里通过 information_schema 检测后逐列执行 ALTER TABLE,
/// 为 Farmers 表补充四级行政区划等新列(兼容 MySQL 5.7 / 8.0 各版本)。
///
public static class SchemaMigrator
{
public static async Task EnsureColumnsAsync(AppDbContext db)
{
var existing = new HashSet(
await db.Database
.SqlQuery($"SELECT COLUMN_NAME AS Value FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'Farmers'")
.ToListAsync(),
StringComparer.OrdinalIgnoreCase);
(string Name, string Ddl)[] columns =
[
("Province", "Province NVARCHAR(50) NULL"),
("County", "County NVARCHAR(50) NULL"),
("Township", "Township NVARCHAR(50) NULL"),
("GroupName", "GroupName NVARCHAR(50) NULL"),
("FarmerType", "FarmerType NVARCHAR(20) NULL"),
("IdCardFrontUrl", "IdCardFrontUrl NVARCHAR(500) NULL"),
("IdCardBackUrl", "IdCardBackUrl NVARCHAR(500) NULL"),
("AvatarUrl", "AvatarUrl NVARCHAR(500) NULL"),
("PinyinInitials", "PinyinInitials VARCHAR(50) NULL"),
];
foreach (var col in columns)
{
if (existing.Contains(col.Name)) continue;
await db.Database.ExecuteSqlRawAsync($"ALTER TABLE Farmers ADD COLUMN {col.Ddl}");
}
// 历史数据的新列为 NULL,EF 映射为不可空 string 读取会抛 DBNull 异常,统一刷新为空串(幂等)
await db.Database.ExecuteSqlRawAsync(
"UPDATE Farmers SET Province = COALESCE(Province,''), County = COALESCE(County,''), " +
"Township = COALESCE(Township,''), GroupName = COALESCE(GroupName,''), " +
"FarmerType = COALESCE(FarmerType,'农户'), " +
"IdCardFrontUrl = COALESCE(IdCardFrontUrl,''), IdCardBackUrl = COALESCE(IdCardBackUrl,''), " +
"AvatarUrl = COALESCE(AvatarUrl,''), PinyinInitials = COALESCE(PinyinInitials,'')");
// Users 表补充 RoleId 列(RBAC 角色外键)
var userCols = new HashSet(
await db.Database
.SqlQuery($"SELECT COLUMN_NAME AS Value FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'Users'")
.ToListAsync(),
StringComparer.OrdinalIgnoreCase);
if (!userCols.Contains("RoleId"))
await db.Database.ExecuteSqlRawAsync("ALTER TABLE Users ADD COLUMN RoleId INT NULL");
// PurchaseOrders 表补充 BoxCount 列(容器/框数合计)
var poCols = new HashSet(
await db.Database
.SqlQuery($"SELECT COLUMN_NAME AS Value FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'PurchaseOrders'")
.ToListAsync(),
StringComparer.OrdinalIgnoreCase);
if (!poCols.Contains("BoxCount"))
await db.Database.ExecuteSqlRawAsync("ALTER TABLE PurchaseOrders ADD COLUMN BoxCount INT NOT NULL DEFAULT 0");
}
///
/// 确保 RBAC/数据字典/多磅/公告等新增表存在(兼容旧库 EnsureCreated 不会补建新表)。
///
public static async Task EnsureSystemTablesAsync(AppDbContext db)
{
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS SysDicts (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
Code VARCHAR(64) NOT NULL,
Remark VARCHAR(500) NULL,
IsSystem TINYINT(1) NOT NULL DEFAULT 0,
Sort INT NOT NULL DEFAULT 0,
CreatedAt DATETIME(6) NOT NULL,
UNIQUE KEY uk_dicts_code (Code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS SysDictItems (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
DictId INT NOT NULL,
Label VARCHAR(100) NOT NULL,
Value VARCHAR(64) NOT NULL,
Ext VARCHAR(255) NULL,
IsDefault TINYINT(1) NOT NULL DEFAULT 0,
Enabled TINYINT(1) NOT NULL DEFAULT 1,
Sort INT NOT NULL DEFAULT 0,
UNIQUE KEY uk_dictitems (DictId, Value),
KEY idx_dictitems_dict (DictId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS SysMenus (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
ParentId INT NULL,
Name VARCHAR(100) NOT NULL,
Path VARCHAR(200) NOT NULL DEFAULT '',
Component VARCHAR(200) NOT NULL DEFAULT '',
Icon VARCHAR(50) NOT NULL DEFAULT '',
Type VARCHAR(20) NOT NULL DEFAULT 'menu',
Permission VARCHAR(100) NOT NULL DEFAULT '',
Visible TINYINT(1) NOT NULL DEFAULT 1,
Sort INT NOT NULL DEFAULT 0,
CreatedAt DATETIME(6) NOT NULL,
KEY idx_menus_permission (Permission)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS SysRoles (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
Code VARCHAR(100) NOT NULL,
Description VARCHAR(500) NULL,
IsSystem TINYINT(1) NOT NULL DEFAULT 0,
CreatedAt DATETIME(6) NOT NULL,
UNIQUE KEY uk_roles_code (Code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS SysRoleMenus (
RoleId INT NOT NULL,
MenuId INT NOT NULL,
PRIMARY KEY (RoleId, MenuId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS PurchaseWeighs (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
PurchaseOrderId INT NOT NULL,
SortNo INT NOT NULL DEFAULT 1,
Grade VARCHAR(20) NOT NULL DEFAULT '',
PriceType VARCHAR(20) NOT NULL DEFAULT 'Manual',
UnitPrice DECIMAL(18,2) NOT NULL DEFAULT 0,
GrossWeight DECIMAL(18,2) NOT NULL DEFAULT 0,
TareWeight DECIMAL(18,2) NOT NULL DEFAULT 0,
NetWeight DECIMAL(18,2) NOT NULL DEFAULT 0,
BoxCount INT NOT NULL DEFAULT 0,
Amount DECIMAL(18,2) NOT NULL DEFAULT 0,
CreatedAt DATETIME(6) NOT NULL,
KEY idx_weighs_order (PurchaseOrderId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS PurchaseTransfers (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
PurchaseOrderId INT NOT NULL,
TransferNo VARCHAR(32) NOT NULL,
FromFarmer VARCHAR(100) NOT NULL DEFAULT '',
ToFarmer VARCHAR(100) NOT NULL DEFAULT '',
ToIdCard VARCHAR(20) NOT NULL DEFAULT '',
Reason VARCHAR(500) NOT NULL DEFAULT '',
OperatorId INT NULL,
CreatedAt DATETIME(6) NOT NULL,
KEY idx_transfers_order (PurchaseOrderId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS Announcements (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Title VARCHAR(200) NOT NULL,
Content LONGTEXT NOT NULL,
Type VARCHAR(20) NOT NULL DEFAULT 'notice',
ScopeOrgIds VARCHAR(500) NOT NULL DEFAULT '',
IsPinned TINYINT(1) NOT NULL DEFAULT 0,
PublisherId INT NULL,
PublishedAt DATETIME(6) NOT NULL,
CreatedAt DATETIME(6) NOT NULL,
KEY idx_announcements_type (Type, PublishedAt)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS AnnouncementReads (
Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
AnnouncementId INT NOT NULL,
UserId INT NOT NULL,
ReadAt DATETIME(6) NOT NULL,
UNIQUE KEY uk_ann_read (AnnouncementId, UserId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
// 幂等系统数据种子(兼容旧库:DbSeeder 仅在空库执行,老库需要补齐角色/菜单/字典/公告)
await EnsureSystemSeedAsync(db);
}
///
/// 为已有数据的旧库补齐 RBAC/数据字典/公告等系统种子数据(幂等,按表是否为空判断)。
///
public static async Task EnsureSystemSeedAsync(AppDbContext db)
{
// 全新库由 DbSeeder 负责完整种子,这里仅补齐旧库(已有用户数据)缺失的系统数据
if (!await db.Users.AnyAsync()) return;
var now = DateTime.Now;
// ---------- 角色 ----------
var roles = new List();
if (!await db.SysRoles.AnyAsync())
{
roles =
[
new() { Name = "超级管理员", Code = "super_admin", Description = "系统内置:拥有全部权限", IsSystem = true, CreatedAt = now },
new() { Name = "公司管理员", Code = "company_admin", Description = "管理本公司及下属收购站业务", IsSystem = true, CreatedAt = now },
new() { Name = "收购站员工", Code = "station_staff", Description = "负责过磅称重、收购单录入", IsSystem = true, CreatedAt = now },
new() { Name = "收购个体", Code = "individual", Description = "个体收购户", IsSystem = true, CreatedAt = now },
];
db.SysRoles.AddRange(roles);
await db.SaveChangesAsync();
}
else
{
roles = await db.SysRoles.ToListAsync();
}
// ---------- 菜单 / 权限 ----------
if (!await db.SysMenus.AnyAsync())
{
var menus = new List
{
new() { Name = "首页", Path = "/home", Component = "views/Home.vue", Icon = "HomeFilled", Type = "menu", Permission = "home:view", Sort = 1, CreatedAt = now },
new() { Name = "收购业务", Path = "/weighing", Icon = "Van", Type = "directory", Sort = 2, CreatedAt = now },
new() { Name = "过磅称重", Path = "/weighing", Component = "views/weighing/WeighingList.vue", Icon = "Odometer", Type = "menu", Permission = "weighing:list", Sort = 1, CreatedAt = now },
new() { Name = "触摸屏过磅", Path = "/weighing/touch", Component = "views/weighing/WeighingTouch.vue", Icon = "Iphone", Type = "menu", Permission = "weighing:touch", Sort = 2, CreatedAt = now },
new() { Name = "电子支付", Path = "/payments", Component = "views/payments/PaymentList.vue", Icon = "Wallet", Type = "menu", Permission = "payment:list", Sort = 3, CreatedAt = now },
new() { Name = "反向开票", Path = "/invoices", Component = "views/invoices/InvoiceList.vue", Icon = "Tickets", Type = "menu", Permission = "invoice:list", Sort = 4, CreatedAt = now },
new() { Name = "基础资料", Path = "/base", Icon = "FolderOpened", Type = "directory", Sort = 3, CreatedAt = now },
new() { Name = "农户管理", Path = "/farmers", Component = "views/farmers/FarmerList.vue", Icon = "User", Type = "menu", Permission = "farmer:list", Sort = 1, CreatedAt = now },
new() { Name = "品种管理", Path = "/products", Component = "views/products/ProductList.vue", Icon = "Goods", Type = "menu", Permission = "product:list", Sort = 2, CreatedAt = now },
new() { Name = "统计报表", Path = "/reports", Icon = "DataAnalysis", Type = "directory", Sort = 4, CreatedAt = now },
new() { Name = "收购报表", Path = "/reports/purchase", Component = "views/reports/ReportPurchase.vue", Icon = "TrendCharts", Type = "menu", Permission = "report:purchase", Sort = 1, CreatedAt = now },
new() { Name = "付款报表", Path = "/reports/payment", Component = "views/reports/ReportPayment.vue", Icon = "Money", Type = "menu", Permission = "report:payment", Sort = 2, CreatedAt = now },
new() { Name = "开票报表", Path = "/reports/invoice", Component = "views/reports/ReportInvoice.vue", Icon = "Document", Type = "menu", Permission = "report:invoice", Sort = 3, CreatedAt = now },
new() { Name = "系统管理", Path = "/system", Icon = "Setting", Type = "directory", Sort = 5, CreatedAt = now },
new() { Name = "组织管理", Path = "/orgs", Component = "views/orgs/OrgList.vue", Icon = "OfficeBuilding", Type = "menu", Permission = "system:org:list", Sort = 1, CreatedAt = now },
new() { Name = "用户管理", Path = "/users", Component = "views/users/UserList.vue", Icon = "Avatar", Type = "menu", Permission = "system:user:list", Sort = 2, CreatedAt = now },
new() { Name = "角色权限", Path = "/system/roles", Component = "views/system/RoleList.vue", Icon = "UserFilled", Type = "menu", Permission = "system:role:list", Sort = 3, CreatedAt = now },
new() { Name = "菜单管理", Path = "/system/menus", Component = "views/system/MenuList.vue", Icon = "Menu", Type = "menu", Permission = "system:menu:list", Sort = 4, CreatedAt = now },
new() { Name = "数据字典", Path = "/system/dicts", Component = "views/system/DictList.vue", Icon = "Notebook", Type = "menu", Permission = "system:dict:list", Sort = 5, CreatedAt = now },
new() { Name = "通知公告", Path = "/notify", Icon = "Bell", Type = "directory", Sort = 6, CreatedAt = now },
new() { Name = "公告管理", Path = "/notify/announcements", Component = "views/notify/AnnouncementList.vue", Icon = "Bell", Type = "menu", Permission = "announcement:list", Sort = 1, CreatedAt = now },
new() { Name = "关于系统", Path = "/about", Component = "views/about/About.vue", Icon = "InfoFilled", Type = "menu", Permission = "about:view", Sort = 7, CreatedAt = now },
};
db.SysMenus.AddRange(menus);
await db.SaveChangesAsync();
SetChild(menus, "/weighing", "/weighing");
SetChild(menus, "/weighing/touch", "/weighing");
SetChild(menus, "/payments", "/weighing");
SetChild(menus, "/invoices", "/weighing");
SetChild(menus, "/farmers", "/base");
SetChild(menus, "/products", "/base");
SetChild(menus, "/reports/purchase", "/reports");
SetChild(menus, "/reports/payment", "/reports");
SetChild(menus, "/reports/invoice", "/reports");
SetChild(menus, "/orgs", "/system");
SetChild(menus, "/users", "/system");
SetChild(menus, "/system/roles", "/system");
SetChild(menus, "/system/menus", "/system");
SetChild(menus, "/system/dicts", "/system");
SetChild(menus, "/notify/announcements", "/notify");
await db.SaveChangesAsync();
// 角色-菜单分配:超级管理员全部;公司管理员除系统管理外全部;收购站员工常用菜单
var allMenuIds = menus.Select(m => m.Id).ToArray();
var admin = roles.First(r => r.Code == "super_admin");
var company = roles.First(r => r.Code == "company_admin");
var station = roles.First(r => r.Code == "station_staff");
db.SysRoleMenus.AddRange(allMenuIds.Select(mid => new SysRoleMenu { RoleId = admin.Id, MenuId = mid }));
db.SysRoleMenus.AddRange(menus.Where(m => m.Type == "menu" && !m.Permission.StartsWith("system:"))
.Select(m => new SysRoleMenu { RoleId = company.Id, MenuId = m.Id }));
db.SysRoleMenus.AddRange(menus.Where(m => m.Type == "menu" && m.Permission is "home:view" or "weighing:list" or "weighing:touch" or "farmer:list" or "about:view")
.Select(m => new SysRoleMenu { RoleId = station.Id, MenuId = m.Id }));
await db.SaveChangesAsync();
}
// ---------- 菜单层级修复:过磅称重/触摸屏过磅 归入 收购业务 目录(幂等,兼容已有数据的库) ----------
var weighingDir = await db.SysMenus.FirstOrDefaultAsync(m => m.Type == "directory" && m.Path == "/weighing");
if (weighingDir != null)
{
var moveTargets = await db.SysMenus
.Where(m => m.Type == "menu" && (m.Path == "/weighing" || m.Path == "/weighing/touch"))
.ToListAsync();
var menuChanged = false;
foreach (var m in moveTargets)
{
if (m.ParentId != weighingDir.Id)
{
m.ParentId = weighingDir.Id;
menuChanged = true;
}
}
if (menuChanged) await db.SaveChangesAsync();
}
// ---------- 已有用户按用户名绑定角色 ----------
var roleByUser = new Dictionary
{
["admin"] = "super_admin",
["company"] = "company_admin",
["station1"] = "station_staff",
["station2"] = "station_staff",
["individual"] = "individual",
};
var users = await db.Users.Where(u => u.RoleId == null).ToListAsync();
foreach (var u in users)
{
if (roleByUser.TryGetValue(u.Username, out var code))
{
var role = roles.FirstOrDefault(r => r.Code == code);
if (role != null) u.RoleId = role.Id;
}
}
if (users.Count > 0) await db.SaveChangesAsync();
// ---------- 数据字典 ----------
if (!await db.SysDicts.AnyAsync())
{
var dicts = new List
{
new() { Name = "收购等级", Code = "purchase_grade", Remark = "收购单磅次等级", IsSystem = true, Sort = 1, CreatedAt = now,
Items =
{
new() { Label = "一等", Value = "一等", IsDefault = true, Sort = 1 },
new() { Label = "二等", Value = "二等", Sort = 2 },
new() { Label = "三等", Value = "三等", Sort = 3 },
new() { Label = "级外", Value = "级外", Sort = 4 },
} },
new() { Name = "价格类型", Code = "price_type", Remark = "过磅计价方式", IsSystem = true, Sort = 2, CreatedAt = now,
Items =
{
new() { Label = "固定价格", Value = "Fixed", Ext = "按产品默认价", IsDefault = true, Sort = 1 },
new() { Label = "现场议价", Value = "Manual", Ext = "与农户协商价", Sort = 2 },
new() { Label = "价格区间", Value = "Range", Ext = "区间内浮动价", Sort = 3 },
} },
new() { Name = "票据格式", Code = "receipt_format", Remark = "小票/票据打印格式", IsSystem = true, Sort = 3, CreatedAt = now,
Items =
{
new() { Label = "58mm 热敏小票", Value = "thermal_58", Ext = "58mm", IsDefault = true, Sort = 1 },
new() { Label = "80mm 热敏小票", Value = "thermal_80", Ext = "80mm", Sort = 2 },
new() { Label = "三等分针式票据", Value = "dotmatrix_3part", Ext = "241mm×139.7mm/联", Sort = 3 },
new() { Label = "A5 激光打印", Value = "laser_a5", Ext = "A5", Sort = 4 },
} },
new() { Name = "公告类型", Code = "announcement_type", Remark = "通知公告类型", IsSystem = true, Sort = 4, CreatedAt = now,
Items =
{
new() { Label = "通知", Value = "notice", Sort = 1 },
new() { Label = "公告", Value = "announce", Sort = 2 },
new() { Label = "价格行情", Value = "price", Sort = 3 },
new() { Label = "政策法规", Value = "policy", Sort = 4 },
} },
};
db.SysDicts.AddRange(dicts);
await db.SaveChangesAsync();
}
// ---------- 系统图标字典(幂等补齐,兼容已有数据的库) ----------
if (!await db.SysDicts.AnyAsync(d => d.Code == "system_logo"))
{
db.SysDicts.Add(new SysDict
{
Name = "系统图标", Code = "system_logo", Remark = "主标题系统图标,可选大部分农产品图标", IsSystem = true, Sort = 5, CreatedAt = now,
Items =
{
new() { Label = "苹果", Value = "Apple", IsDefault = true, Sort = 1 },
new() { Label = "葡萄", Value = "Grape", Sort = 2 },
new() { Label = "西瓜", Value = "Watermelon", Sort = 3 },
new() { Label = "樱桃", Value = "Cherry", Sort = 4 },
new() { Label = "梨", Value = "Pear", Sort = 5 },
new() { Label = "桃", Value = "Peach", Sort = 6 },
new() { Label = "橙子", Value = "Orange", Sort = 7 },
new() { Label = "农作物", Value = "Food", Sort = 8 },
new() { Label = "时蔬", Value = "Dish", Sort = 9 },
new() { Label = "牛奶", Value = "Milk", Sort = 10 },
new() { Label = "冰淇淋", Value = "IceCream", Sort = 11 },
new() { Label = "生鲜礼盒", Value = "TakeawayBox", Sort = 12 },
}
});
await db.SaveChangesAsync();
}
// ---------- 通知公告(演示数据) ----------
if (!await db.Announcements.AnyAsync())
{
var publisher = await db.Users.FirstOrDefaultAsync(u => u.Username == "admin");
var adminRole = roles.FirstOrDefault(r => r.Code == "super_admin");
db.Announcements.AddRange(
new Announcement { Title = "欢迎使用农易富农产品收购交易平台", Content = "系统已完成升级,新增数据字典、角色权限、通知公告等功能。默认账号 admin / 123456。", Type = "announce", IsPinned = true, PublisherId = publisher?.Id ?? (adminRole != null ? db.Users.FirstOrDefault()?.Id : null), PublishedAt = now, CreatedAt = now },
new Announcement { Title = "关于2026年夏季收购价格调整的通知", Content = "自8月15日起,一等品收购价调整为每公斤3.6元,请各收购站及时更新价格配置。", Type = "price", PublisherId = publisher?.Id, PublishedAt = now.AddHours(-3), CreatedAt = now.AddHours(-3) },
new Announcement { Title = "电子秤串口连接使用说明", Content = "请在收购单新建界面点击电子秤区域连接串口,支持标准ASCII输出协议(如 ST,GS,+00123.45kg)。无串口设备时可使用手动输入模式。", Type = "notice", PublisherId = publisher?.Id, PublishedAt = now.AddDays(-1), CreatedAt = now.AddDays(-1) }
);
await db.SaveChangesAsync();
}
}
private static void SetChild(List menus, string childPath, string parentPath)
{
var parent = menus.FirstOrDefault(m => m.Type == "directory" && m.Path == parentPath);
if (parent is null) return;
foreach (var child in menus.Where(m => m.Type != "directory" && m.Path == childPath && m.ParentId == null))
child.ParentId = parent.Id;
}
///
/// 确保 Regions(行政区划)表存在。
/// 表结构与 scripts/areacity/import-regions.mjs 导入脚本一致;
/// 数据由该脚本导入(全国省市区乡镇四级,约 4.2 万条)。
///
public static async Task EnsureRegionsTableAsync(AppDbContext db)
{
await db.Database.ExecuteSqlRawAsync("""
CREATE TABLE IF NOT EXISTS Regions (
Id INT NOT NULL PRIMARY KEY,
Pid INT NOT NULL,
Deep INT NOT NULL,
Name VARCHAR(100) NOT NULL,
ExtName VARCHAR(100) NOT NULL,
Pinyin VARCHAR(200) NOT NULL DEFAULT '',
PinyinPrefix VARCHAR(10) NOT NULL DEFAULT '',
Hot INT NOT NULL DEFAULT 0,
KEY idx_regions_pid (Pid),
KEY idx_regions_deep (Deep)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
""");
// 老库补充 Hot 热度列(幂等)
var hotExists = await db.Database.SqlQuery(
$"SELECT COUNT(*) AS Value FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'Regions' AND COLUMN_NAME = 'Hot'")
.FirstOrDefaultAsync();
if (hotExists == 0)
await db.Database.ExecuteSqlRawAsync("ALTER TABLE Regions ADD COLUMN Hot INT NOT NULL DEFAULT 0");
// 常用省份热度值(值越大越靠前,幂等可重复执行)
(string Name, int Hot)[] hotProvinces =
[
("四川省", 20), ("广西壮族自治区", 19), ("陕西省", 18), ("重庆市", 17), ("安徽省", 16),
("云南省", 15), ("贵州省", 14), ("河南省", 13), ("山东省", 12), ("湖北省", 11), ("湖南省", 10),
("甘肃省", 9), ("新疆维吾尔自治区", 8), ("江西省", 7), ("福建省", 6), ("广东省", 5),
("河北省", 4), ("山西省", 3), ("江苏省", 2), ("浙江省", 1),
];
foreach (var p in hotProvinces)
await db.Database.ExecuteSqlRawAsync(
"UPDATE Regions SET Hot = {0} WHERE Deep = 0 AND ExtName = {1}", p.Hot, p.Name);
}
}