Skip to content
Merged

Dev #100

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
```
DFApp/ ← 仓库根目录
├── AGENTS.md ← 本文件
├── DFApp.Web/ ← 后端项目
├── src/DFApp.Web/ ← 后端项目
├── DFApp.LotteryProxy/ ← 彩票代理服务
├── test/DFApp.Web.Tests/ ← 单元测试
├── client/ ← 前端项目(Vue 3)
Expand All @@ -55,7 +55,7 @@ DFApp/ ← 仓库根目录
```

### 后端结构(轻量级单体架构)
- `DFApp.Web/` ← 唯一后端项目
- `src/DFApp.Web/` ← 唯一后端项目
- `Domain/` - 实体和自定义基类
- `Services/` - 应用服务
- `Controllers/` - API 控制器(路由模式:`/api/app/{kebab-case-entity}`)
Expand Down
81 changes: 81 additions & 0 deletions sql/16-add-missing-concurrency-stamp.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
-- =============================================================
-- 为缺失 ConcurrencyStamp 列的表补充该列
-- 幂等安全版:可安全地重复执行
--
-- 说明:
-- AppRssWordSegment、AppRssSubscriptionDownloads、AppMediaExternalLinkMediaIds
-- 三个表的对应实体(RssWordSegment、RssSubscriptionDownload、
-- MediaExternalLinkMediaIds)继承自 EntityBase<long>,后者定义了
-- ConcurrencyStamp 列,但建表时遗漏了该列,导致查询时报错:
-- "no such column: ConcurrencyStamp"
--
-- 幂等性原理:
-- SQLite 不支持 ALTER TABLE ADD COLUMN IF NOT EXISTS 语法,
-- 通过 pragma_table_info 检查列是否存在,仅对缺失的列生成
-- ALTER TABLE 语句,使用 .output 重定向 + .read 执行的方式。
-- =============================================================

.bail on
.headers off

-- ============================================================
-- 第一部分:检查当前状态
-- ============================================================

SELECT '=== ConcurrencyStamp 列迁移前状态检查 ===';

SELECT 'AppRssWordSegment:' ||
CASE WHEN (SELECT COUNT(*) FROM pragma_table_info('AppRssWordSegment') WHERE name = 'ConcurrencyStamp') > 0 THEN ' 已存在' ELSE ' 缺失' END;

SELECT 'AppRssSubscriptionDownloads:' ||
CASE WHEN (SELECT COUNT(*) FROM pragma_table_info('AppRssSubscriptionDownloads') WHERE name = 'ConcurrencyStamp') > 0 THEN ' 已存在' ELSE ' 缺失' END;

SELECT 'AppMediaExternalLinkMediaIds:' ||
CASE WHEN (SELECT COUNT(*) FROM pragma_table_info('AppMediaExternalLinkMediaIds') WHERE name = 'ConcurrencyStamp') > 0 THEN ' 已存在' ELSE ' 缺失' END;


-- ============================================================
-- 第二部分:动态生成并执行 ALTER TABLE 语句
-- 仅对不存在的列生成 ALTER TABLE,已存在的列自动跳过
-- ============================================================

.output /tmp/_migration_16_steps.sql

-- AppRssWordSegment(RssWordSegment : CreationAuditedEntity<long> → EntityBase<long>)
SELECT 'ALTER TABLE "AppRssWordSegment" ADD COLUMN "ConcurrencyStamp" TEXT NOT NULL DEFAULT '''';'
WHERE (SELECT COUNT(*) FROM pragma_table_info('AppRssWordSegment') WHERE name = 'ConcurrencyStamp') = 0;

-- AppRssSubscriptionDownloads(RssSubscriptionDownload : CreationAuditedEntity<long> → EntityBase<long>)
SELECT 'ALTER TABLE "AppRssSubscriptionDownloads" ADD COLUMN "ConcurrencyStamp" TEXT NOT NULL DEFAULT '''';'
WHERE (SELECT COUNT(*) FROM pragma_table_info('AppRssSubscriptionDownloads') WHERE name = 'ConcurrencyStamp') = 0;

-- AppMediaExternalLinkMediaIds(MediaExternalLinkMediaIds : EntityBase<long>)
SELECT 'ALTER TABLE "AppMediaExternalLinkMediaIds" ADD COLUMN "ConcurrencyStamp" TEXT NOT NULL DEFAULT '''';'
WHERE (SELECT COUNT(*) FROM pragma_table_info('AppMediaExternalLinkMediaIds') WHERE name = 'ConcurrencyStamp') = 0;

.output stdout

-- 执行动态生成的 ALTER TABLE 语句
-- 如果所有列都已存在,临时文件为空,.read 不会执行任何操作
.read /tmp/_migration_16_steps.sql

-- 清理临时文件
.shell rm -f /tmp/_migration_16_steps.sql


-- ============================================================
-- 第三部分:迁移后验证
-- ============================================================

SELECT '=== 迁移后验证 ===';

SELECT 'AppRssWordSegment:' ||
CASE WHEN (SELECT COUNT(*) FROM pragma_table_info('AppRssWordSegment') WHERE name = 'ConcurrencyStamp') > 0 THEN ' ✅ConcurrencyStamp' ELSE ' ❌ConcurrencyStamp缺失' END;

SELECT 'AppRssSubscriptionDownloads:' ||
CASE WHEN (SELECT COUNT(*) FROM pragma_table_info('AppRssSubscriptionDownloads') WHERE name = 'ConcurrencyStamp') > 0 THEN ' ✅ConcurrencyStamp' ELSE ' ❌ConcurrencyStamp缺失' END;

SELECT 'AppMediaExternalLinkMediaIds:' ||
CASE WHEN (SELECT COUNT(*) FROM pragma_table_info('AppMediaExternalLinkMediaIds') WHERE name = 'ConcurrencyStamp') > 0 THEN ' ✅ConcurrencyStamp' ELSE ' ❌ConcurrencyStamp缺失' END;

SELECT '✅ ConcurrencyStamp 列迁移完成(幂等安全,可重复执行)';
11 changes: 11 additions & 0 deletions src/DFApp.Web/Controllers/ExternalLinkController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ public async Task<IActionResult> GetAsync([FromRoute] long id)
return Success(result);
}

/// <summary>
/// 生成外链(后台任务)
/// </summary>
[HttpGet("external-link")]
[Permission(DFAppPermissions.Medias.Create)]
public async Task<IActionResult> GetExternalLinkAsync()
{
var result = await _externalLinkService.GetExternalLink();
return Success(result);
}

/// <summary>
/// 分页获取外链列表
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/DFApp.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public async static Task<int> Main(string[] args)

// 注册密码哈希服务(无状态,使用 Transient)
builder.Services.AddTransient<IPasswordHasher, PasswordHasher>();
builder.Services.AddTransient<DFApp.Web.Mapping.LotteryMapper>();

// 注册油价刷新器(无状态,使用 Transient)
builder.Services.AddTransient<GasolinePriceRefresher>();
Expand Down Expand Up @@ -232,7 +233,7 @@ public async static Task<int> Main(string[] args)
});
});

Quartz.Logging.LogProvider.IsDisabled = true;
// Quartz.Logging.LogProvider.IsDisabled = true;
builder.Services.AddQuartz(q =>
{
// GasolinePriceRefreshJob — 每晚21:00执行
Expand Down
Loading