12 KiB
关于我们模块 + 数据库重置 + COS 清理 实施计划
Context(背景与目标)
用户提出三项需求:
- 数据库重置:清空所有业务数据,仅保留 admin 账户,重置后保留种子数据(分类/平台/标签/示例项目/轮播图/合作伙伴由 DbSeeder 自动补回)。
- COS 资源全删:清空腾讯云 COS 存储桶内所有应用上传的文件(保留
backups/下的数据库备份)。 - 关于我们模块:
- 前台 About 页去掉"联系我们"模块
- 后台新增"关于我们"管理模块:控制前台是否显示该页 + 全部内容可编辑(内联所见即所得,编辑样式与前台一致)
当前 About 页 100% 硬编码(About.vue),无任何后端存储。本计划从零构建内容存储 + 后台内联编辑 + 前台动态渲染。
关键设计决策(经用户确认 + 架构验证)
- 存储方式:全部 About 内容作为单个 JSON 字符串存入 SystemSettings 表(Key=
AboutContent),与现有 PlatformName/ShowPartners 等配置一致。避免新建表 + 改 seeder 补丁的复杂度。 - 编辑交互:使用无边框
el-input/el-textarea(autosize),通过 hover/focus 样式提示可编辑性。不使用contenteditable(Vue 响应性陷阱:光标跳动)。 - 数组排序:上下箭头按钮,不引入拖拽库。
- ShowAbout 控制:隐藏导航链接 + 路由守卫拦截直接访问(重定向到首页)。
- 重置安全:要求输入 "RESET" 确认 + 重置前自动创建一次数据库备份 + 操作日志记录。
- COS 清理范围:仅删除已知上传前缀(avatars/banners/logos/projects/webgl/packages/common/icons/videos/categories/platforms/partners),保留
backups/。 - AboutContent 不进 PublicSettingsDto:public-settings 仅加
showAbout(小布尔值,供导航/路由用);AboutContent 由独立端点加载,仅 About 页消费。
实施步骤
阶段 A:后端基础设施
A1. FileStorageService 增加批量删除能力
文件:d:\TraeProject\MilliSim4\backend\MilliSim.Api\Services\Infrastructure\FileStorageService.cs
- 接口
IFileStorageService新增:Task<List<string>> ListAllObjectsAsync()— 分页列出桶内所有对象 key(每页 1000,循环至结束)Task<int> DeleteObjectsAsync(IEnumerable<string> keys)— 批量删除(DeleteMultiObjectRequest,每批最多 1000)
- 实现:复用私有
GetStorageConfigAsync()+GetCosXml(config);Local 模式则遍历wwwroot/uploads目录删除文件 - 不暴露到控制器单独使用,仅供 factory-reset 调用
A2. AboutContent DTO + 端点
文件:d:\TraeProject\MilliSim4\backend\MilliSim.Api\Common\Dtos\Dtos.cs
新增 AboutContentDto:
public class AboutContentDto
{
public string HeroTag { get; set; } = "关于我们";
public string HeroTitle { get; set; } = "";
public string HeroDesc { get; set; } = "";
public string IntroTitle { get; set; } = "公司简介";
public List<string> IntroParagraphs { get; set; } = new();
public List<AboutStatDto> Stats { get; set; } = new();
public string MissionTitle { get; set; } = "使命与愿景";
public List<AboutMissionCardDto> MissionCards { get; set; } = new();
public string TimelineTitle { get; set; } = "发展历程";
public List<AboutTimelineItemDto> Timeline { get; set; } = new();
}
public class AboutStatDto { public string Number { get; set; } = ""; public string Label { get; set; } = ""; }
public class AboutMissionCardDto { public string Title { get; set; } = ""; public string Icon { get; set; } = "rocket"; public string Desc { get; set; } = ""; }
public class AboutTimelineItemDto { public string Year { get; set; } = ""; public string Title { get; set; } = ""; public string Desc { get; set; } = ""; }
public class AboutSettingsDto
{
public bool ShowAbout { get; set; } = true;
public AboutContentDto Content { get; set; } = new();
}
PublicSettingsDto 仅新增 public bool ShowAbout { get; set; } = true;(不加 AboutContent)。
A3. SystemService 关于我们读写
文件:d:\TraeProject\MilliSim4\backend\MilliSim.Api\Services\SystemService.cs
GetAboutSettingsAsync()→ 读 SystemSettings 中ShowAbout+AboutContent(JSON 反序列化),返回AboutSettingsDtoUpdateAboutSettingsAsync(AboutSettingsDto dto)→ 序列化 Content 为 JSON 存入AboutContentkey,ShowAbout存 bool 字符串GetPublicSettingsAsync()中加入ShowAbout读取
A4. SystemController 端点
文件:d:\TraeProject\MilliSim4\backend\MilliSim.Api\Controllers\SystemController.cs
GET /api/system/about→[AllowAnonymous]返回AboutSettingsDto(前台 About 页消费)PUT /api/system/about→[Authorize(Policy="Admin")]保存
A5. 工厂重置端点
文件:d:\TraeProject\MilliSim4\backend\MilliSim.Api\Services\SystemService.cs + SystemController.cs
POST /api/system/factory-reset→[Authorize(Policy="Admin")],body 含ConfirmText(必须等于 "RESET")- 逻辑:
- 校验
ConfirmText == "RESET",否则返回 400 - 先调
CreateBackupAsync()创建备份(保留安全网) SET FOREIGN_KEY_CHECKS=0- 按依赖顺序 DELETE FROM 所有 21 张表(Users 仅删
Username != 'admin',并清空 admin 的 Avatar/UpdatedAt 字段) SET FOREIGN_KEY_CHECKS=1- 调
DbSeeder.SeedAsync(db)重新补种子(admin 已存在跳过;分类/平台/标签/项目/轮播图/合作伙伴/设置按"为空才补"逻辑重新填入;AboutContent/ShowAbout 默认值也会被补入) - 调
_fileStorage.ListAllObjectsAsync()列出所有对象,过滤掉backups/前缀,调DeleteObjectsAsync()批量删除 - 记操作日志
- 返回
{ TablesCleared, ObjectsDeleted, BackupId }
- 校验
A6. DbSeeder 补种子
文件:d:\TraeProject\MilliSim4\backend\MilliSim.Api\Data\DbSeeder.cs
- 在
SeedAsync的 SystemSettings 补种子段加入:ShowAbout= "true"AboutContent= 默认 JSON(当前 About.vue 的硬编码内容序列化)
- 仅在 key 不存在时插入(与现有逻辑一致)
阶段 B:前端 — 关于我们动态化 + 去掉联系模块
B1. 类型 + API + Store
文件:
d:\TraeProject\MilliSim4\frontend\src\types\index.ts:加AboutContent/AboutStat/AboutMissionCard/AboutTimelineItem接口;PublicSettings加showAbout: booleand:\TraeProject\MilliSim4\frontend\src\api\index.ts:systemApi.getAbout()GET/system/about;systemApi.updateAbout(payload)PUT;systemApi.factoryReset(confirmText)POSTd:\TraeProject\MilliSim4\frontend\src\stores\system.ts:settings加showAbout(默认 true);加aboutContentref +loadAbout()方法(About 页 onMounted 调用)
B2. About.vue 动态化
文件:d:\TraeProject\MilliSim4\frontend\src\views\frontend\About.vue
- 去掉"联系我们"section(原 L101-130)+ 相关 CTA
- script:
onMounted调systemStore.loadAbout()获取内容;用computed映射 store 中的 aboutContent - template:所有硬编码文本替换为
{{ about.heroTag }}等绑定;timeline 用v-for="item in about.timeline" - 无数据时显示骨架屏/loading
B3. 路由 + 导航守卫
文件:
d:\TraeProject\MilliSim4\frontend\src\router\index.ts:/about路由meta.requiresAbout: true;beforeEach守卫中if (to.meta.requiresAbout && !systemStore.settings.showAbout) return '/',并先await systemStore.ensureLoaded()d:\TraeProject\MilliSim4\frontend\src\layouts\FrontendLayout.vue:nav 菜单"关于我们"项加v-if="systemStore.settings.showAbout";footer 链接同理
阶段 C:后台 — 关于我们内联编辑页
C1. 新建 AboutSettings.vue
文件:d:\TraeProject\MilliSim4\frontend\src\views\admin\AboutSettings.vue(新建)
布局完全复刻前台 About.vue 结构,但所有文字改为可编辑:
- 顶部工具栏:
ShowAboutel-switch + "保存" el-button(loading 状态) - Hero 区:3 个无边框 el-input(tag/title/desc),inherit 前台字体样式
- 公司简介:
el-textarea :autosize× N 段(可增删),4 个统计卡片(number + label 各一输入),每卡右上角 hover 显示删除按钮 + 上下移按钮 - 使命与愿景:3 张卡片(title + icon 选择 + desc textarea),增删/排序
- 发展历程:时间线项(year + title + desc),增删/排序
- 编辑态样式:
.editable:hover { outline: 1px dashed var(--color-primary); }.editable:focus { outline: 2px solid var(--color-primary); background: var(--color-primary-light-9); } - 数组操作按钮:hover 时浮现"上移/下移/删除","添加"按钮在列表末尾
onMounted调systemApi.getAbout()加载;保存调systemApi.updateAbout();保存后systemStore.load()刷新前台状态- 图标选择用 el-select 下拉(预设 SvgIcon 列表:rocket/globe/heart/target/users/star 等)
C2. 路由 + 菜单
文件:
d:\TraeProject\MilliSim4\frontend\src\router\index.ts:加/admin/about-settings路由(admin auth)d:\TraeProject\MilliSim4\frontend\src\layouts\AdminLayout.vue:侧栏菜单加入"关于我们"项(icon:info或building)
阶段 D:后台 — 工厂重置入口
D1. Settings.vue 危险操作卡片
文件:d:\TraeProject\MilliSim4\frontend\src\views\admin\Settings.vue
- 在页面末尾加"危险操作"卡片:
- 标题 + 警告说明("将清空所有业务数据并删除 COS 资源,仅保留 admin 账户,操作前会自动创建备份")
- el-input 要求输入 "RESET" 才能激活按钮
- el-button danger "恢复出厂设置"(disabled 直到输入正确)
- 二次确认弹窗
showActionConfirm - 调
systemApi.factoryReset(confirmText);成功后ElMessage.success+ 提示重新登录(admin 密码不变)
阶段 E:编译验证 + 重启
- 杀掉后端进程(PID 13788/22680)→
dotnet build→ 重启后端 - 前端 Vite HMR 自动加载,确认无编译错误
- 浏览器测试场景
验证步骤
后端验证
dotnet build0 错误 0 警告- 启动后端,确认
PendingUploads+ 新增设置 key(AboutContent/ShowAbout)建表/补种子成功(查日志) - Swagger 确认新端点存在:GET/PUT
/api/system/about、POST/api/system/factory-reset
前端验证
- Vite HMR 无编译错误
- 访问
/about确认内容从后端加载(非硬编码) - 关闭 ShowAbout 后导航不显示"关于我们",直接访问
/about重定向到首页 - 后台
/admin/about-settings编辑任意文字 → 保存 → 刷新前台确认生效 - 数组增删/排序正常
- 后台"恢复出厂设置"输入 RESET → 执行 → 确认数据库清空 + COS 清空 + admin 可登录 + 种子数据恢复
假设与决策
- 不删 admin 账户:DbSeeder 对 admin 用户按 username 幂等,重置后保留原密码
admin123。 - 保留 backups/ 前缀:工厂重置不删
backups/下的数据库备份文件,作为最后安全网。 - 重置前自动备份:调用现有
CreateBackupAsync,即使重置失败也有回滚依据。 - AboutContent 默认值:取当前 About.vue 硬编码内容作为默认 JSON,保证未配置时前台显示不变。
- 不引入拖拽库:数组排序用上下箭头按钮,与现有 SortOrder 字段约定一致。
- PublicSettingsDto 不含 AboutContent:避免每次应用启动加载大 JSON;AboutContent 由 About 页独立加载。
- factory-reset 同步删 COS:DB 清空后立即删 COS,保证一致性;COS 删除失败不回滚 DB(DB 已是干净状态,COS 残留可手动清理,记录日志)。