feat: 完成云同步、语音控制与多平台扩展基础架构搭建
本次提交完成了项目核心基础架构升级: 1. 新增动态API中间件与权限控制系统,支持匿名/鉴权接口分离 2. 搭建云同步服务体系,包含认证、任务同步、安全策略等核心模块 3. 实现语音控制全链路,从STT/意图解析到命令执行 4. 新增任务类型、附件实体与相关仓储接口 5. 重构前端配置与代理规则,统一后端端口为5057 6. 新增多平台测试项目与CI脚本优化 7. 完善项目文档与代码注释规范 移除了旧版迁移文件与冗余代理配置,调整项目结构适配跨平台部署需求。
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Hua.Todo.Core.Entities;
|
||||
using Hua.Todo.Core.Interfaces;
|
||||
|
||||
namespace Hua.Todo.Application.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 附件仓储实现(EF Core)。
|
||||
/// </summary>
|
||||
public class AttachmentRepository : IAttachmentRepository
|
||||
{
|
||||
private readonly TodoDbContext _context;
|
||||
|
||||
/// <summary>
|
||||
/// 创建 <see cref="AttachmentRepository"/>。
|
||||
/// </summary>
|
||||
/// <param name="context">数据库上下文。</param>
|
||||
public AttachmentRepository(TodoDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<AttachmentEntity>> GetByTaskIdAsync(Guid taskId)
|
||||
{
|
||||
return await _context.Attachments
|
||||
.Where(a => a.TaskId == taskId)
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<AttachmentEntity?> GetByIdAsync(Guid id)
|
||||
{
|
||||
return await _context.Attachments
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<AttachmentEntity> AddAsync(AttachmentEntity attachment)
|
||||
{
|
||||
_context.Attachments.Add(attachment);
|
||||
await _context.SaveChangesAsync();
|
||||
return attachment;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteAsync(Guid id)
|
||||
{
|
||||
var attachment = await _context.Attachments.FindAsync(id);
|
||||
if (attachment != null)
|
||||
{
|
||||
_context.Attachments.Remove(attachment);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> GetCountByTaskIdAsync(Guid taskId)
|
||||
{
|
||||
return await _context.Attachments
|
||||
.CountAsync(a => a.TaskId == taskId);
|
||||
}
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hua.Todo.Application.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hua.Todo.Application.Migrations
|
||||
{
|
||||
[DbContext(typeof(TodoDbContext))]
|
||||
[Migration("20260616203619_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.AttachmentEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AttachmentType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("datetime('now')");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("FileSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("TaskId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("Attachments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.AuditLogEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientIp")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSuccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("TimestampUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TimestampUtc");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AuditLogs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.SecurityPolicyEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AllowPersist")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("AllowSync")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("IsTrustedDeviceOnly")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SecondFactorExpiryMinutes")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(30);
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SecurityPolicies", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.TaskEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double?>("AudioDuration")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("datetime('now')");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(5000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ExtraProperties")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsCompleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MeetingNotes")
|
||||
.HasMaxLength(20000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("ParentTaskId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<int>("TaskType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentTaskId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("T_Tasks", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("MustChangePassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordSalt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserSessionEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ExpiresAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("StepUpExpiresAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserSessions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.AttachmentEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.TaskEntity", "Task")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("TaskId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Task");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.SecurityPolicyEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.UserEntity", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.TaskEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.TaskEntity", "ParentTask")
|
||||
.WithMany("SubTasks")
|
||||
.HasForeignKey("ParentTaskId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Hua.Todo.Core.Entities.UserEntity", "User")
|
||||
.WithMany("Tasks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentTask");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserSessionEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.UserEntity", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.TaskEntity", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
|
||||
b.Navigation("SubTasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserEntity", b =>
|
||||
{
|
||||
b.Navigation("Tasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hua.Todo.Application.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AuditLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
TimestampUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
UserName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
EventType = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: false),
|
||||
ClientIp = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
|
||||
UserAgent = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
IsSuccess = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AuditLogs", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
UserName = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PasswordSalt = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Role = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
UpdatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
MustChangePassword = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SecurityPolicies",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
AllowPersist = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true),
|
||||
AllowSync = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true),
|
||||
SecondFactorExpiryMinutes = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 30),
|
||||
IsTrustedDeviceOnly = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SecurityPolicies", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SecurityPolicies_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "T_Tasks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Title = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
Priority = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 1),
|
||||
Code = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false),
|
||||
IsCompleted = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: false),
|
||||
ParentTaskId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
TaskType = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
|
||||
MeetingNotes = table.Column<string>(type: "TEXT", maxLength: 20000, nullable: true),
|
||||
AudioDuration = table.Column<double>(type: "REAL", nullable: true),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 5000, nullable: true),
|
||||
ExtraProperties = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"),
|
||||
CreatorId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: false),
|
||||
DeletionTime = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
DeleterId = table.Column<Guid>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_T_Tasks", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_T_Tasks_T_Tasks_ParentTaskId",
|
||||
column: x => x.ParentTaskId,
|
||||
principalTable: "T_Tasks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_T_Tasks_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserSessions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
ExpiresAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
StepUpExpiresAtUtc = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserSessions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserSessions_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Attachments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
TaskId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
FileName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false),
|
||||
FilePath = table.Column<string>(type: "TEXT", maxLength: 1024, nullable: false),
|
||||
FileSize = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
ContentType = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
|
||||
AttachmentType = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Attachments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Attachments_T_Tasks_TaskId",
|
||||
column: x => x.TaskId,
|
||||
principalTable: "T_Tasks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Attachments_TaskId",
|
||||
table: "Attachments",
|
||||
column: "TaskId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AuditLogs_TimestampUtc",
|
||||
table: "AuditLogs",
|
||||
column: "TimestampUtc");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AuditLogs_UserId",
|
||||
table: "AuditLogs",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SecurityPolicies_UserId",
|
||||
table: "SecurityPolicies",
|
||||
column: "UserId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_T_Tasks_ParentTaskId",
|
||||
table: "T_Tasks",
|
||||
column: "ParentTaskId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_T_Tasks_UserId",
|
||||
table: "T_Tasks",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_UserName",
|
||||
table: "Users",
|
||||
column: "UserName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserSessions_UserId",
|
||||
table: "UserSessions",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Attachments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AuditLogs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SecurityPolicies");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserSessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "T_Tasks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hua.Todo.Application.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hua.Todo.Application.Migrations
|
||||
{
|
||||
[DbContext(typeof(TodoDbContext))]
|
||||
partial class TodoDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.AttachmentEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AttachmentType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("datetime('now')");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("FileSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("TaskId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("Attachments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.AuditLogEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientIp")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSuccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("TimestampUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TimestampUtc");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AuditLogs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.SecurityPolicyEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AllowPersist")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("AllowSync")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("IsTrustedDeviceOnly")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SecondFactorExpiryMinutes")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(30);
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SecurityPolicies", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.TaskEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double?>("AudioDuration")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("datetime('now')");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(5000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ExtraProperties")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsCompleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MeetingNotes")
|
||||
.HasMaxLength(20000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("ParentTaskId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<int>("TaskType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentTaskId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("T_Tasks", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("MustChangePassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordSalt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserSessionEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ExpiresAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("StepUpExpiresAtUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserSessions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.AttachmentEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.TaskEntity", "Task")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("TaskId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Task");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.SecurityPolicyEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.UserEntity", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.TaskEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.TaskEntity", "ParentTask")
|
||||
.WithMany("SubTasks")
|
||||
.HasForeignKey("ParentTaskId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Hua.Todo.Core.Entities.UserEntity", "User")
|
||||
.WithMany("Tasks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentTask");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserSessionEntity", b =>
|
||||
{
|
||||
b.HasOne("Hua.Todo.Core.Entities.UserEntity", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.TaskEntity", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
|
||||
b.Navigation("SubTasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hua.Todo.Core.Entities.UserEntity", b =>
|
||||
{
|
||||
b.Navigation("Tasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Hua.Todo.Application.Data;
|
||||
using Hua.Todo.Core.Entities;
|
||||
using Hua.Todo.Core.Interfaces;
|
||||
|
||||
@@ -28,7 +27,7 @@ public class TaskRepository : ITaskRepository
|
||||
public async Task<List<TaskEntity>> GetAllAsync()
|
||||
{
|
||||
return await _context.Tasks
|
||||
.Include(t => t.SubTasks)
|
||||
.Include(t => t.SubTasks!)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -37,10 +36,10 @@ public class TaskRepository : ITaskRepository
|
||||
/// </summary>
|
||||
/// <param name="id">任务 ID。</param>
|
||||
/// <returns>匹配的任务实体;如果不存在则返回 null。</returns>
|
||||
public async Task<TaskEntity?> GetByIdAsync(int id)
|
||||
public async Task<TaskEntity?> GetByIdAsync(Guid id)
|
||||
{
|
||||
return await _context.Tasks
|
||||
.Include(t => t.SubTasks)
|
||||
.Include(t => t.SubTasks!)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ public class TaskRepository : ITaskRepository
|
||||
{
|
||||
return await _context.Tasks
|
||||
.Where(t => !t.IsCompleted)
|
||||
.OrderByDescending(t => t.CreatedAt)
|
||||
.OrderByDescending(t => t.CreationTime)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -64,7 +63,7 @@ public class TaskRepository : ITaskRepository
|
||||
{
|
||||
return await _context.Tasks
|
||||
.Where(t => t.IsCompleted)
|
||||
.OrderByDescending(t => t.UpdatedAt)
|
||||
.OrderByDescending(t => t.LastModificationTime)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -87,7 +86,7 @@ public class TaskRepository : ITaskRepository
|
||||
/// <returns>更新后的任务实体。</returns>
|
||||
public async Task<TaskEntity> UpdateAsync(TaskEntity taskEntity)
|
||||
{
|
||||
taskEntity.UpdatedAt = DateTime.UtcNow;
|
||||
taskEntity.LastModificationTime = DateTime.UtcNow;
|
||||
_context.Tasks.Update(taskEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
return taskEntity;
|
||||
@@ -98,7 +97,7 @@ public class TaskRepository : ITaskRepository
|
||||
/// </summary>
|
||||
/// <param name="id">要删除的任务 ID。</param>
|
||||
/// <returns>表示删除操作的任务。</returns>
|
||||
public async Task DeleteAsync(int id)
|
||||
public async Task DeleteAsync(Guid id)
|
||||
{
|
||||
var task = await _context.Tasks.FindAsync(id);
|
||||
if (task != null)
|
||||
@@ -113,11 +112,35 @@ public class TaskRepository : ITaskRepository
|
||||
/// </summary>
|
||||
/// <param name="parentTaskId">父任务 ID。</param>
|
||||
/// <returns>子任务实体的列表。</returns>
|
||||
public async Task<List<TaskEntity>> GetSubTasksAsync(int parentTaskId)
|
||||
public async Task<List<TaskEntity>> GetSubTasksAsync(Guid parentTaskId)
|
||||
{
|
||||
return await _context.Tasks
|
||||
.Where(t => t.ParentTaskId == parentTaskId)
|
||||
.OrderByDescending(t => t.CreatedAt)
|
||||
.OrderByDescending(t => t.CreationTime)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定用户的任务中最大的数字型 Code。
|
||||
/// </summary>
|
||||
/// <param name="userId">用户 ID。</param>
|
||||
/// <returns>最大数字 Code;若无任务或无可解析 Code 则返回 0。</returns>
|
||||
public async Task<int> GetMaxCodeAsync(Guid userId)
|
||||
{
|
||||
var codes = await _context.Tasks
|
||||
.AsNoTracking()
|
||||
.Where(t => t.UserId == userId)
|
||||
.Select(t => t.Code)
|
||||
.ToListAsync();
|
||||
|
||||
int max = 0;
|
||||
foreach (var code in codes)
|
||||
{
|
||||
if (int.TryParse(code, out var value) && value > max)
|
||||
{
|
||||
max = value;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
using Hua.Todo.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Hua.Todo.Application.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序数据库上下文(EF Core)。
|
||||
/// </summary>
|
||||
public class TodoDbContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建 <see cref="TodoDbContext"/>。
|
||||
/// </summary>
|
||||
/// <param name="options">数据库上下文配置。</param>
|
||||
public TodoDbContext(DbContextOptions<TodoDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 DbContext 行为,抑制开发期间模型变化导致的迁移验证警告。
|
||||
/// </summary>
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.ConfigureWarnings(w => w.Ignore(
|
||||
Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务集合。
|
||||
/// </summary>
|
||||
public DbSet<TaskEntity> Tasks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附件集合。
|
||||
/// </summary>
|
||||
public DbSet<AttachmentEntity> Attachments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户集合(云同步)。
|
||||
/// </summary>
|
||||
public DbSet<UserEntity> Users { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户会话集合(云同步)。
|
||||
/// </summary>
|
||||
public DbSet<UserSessionEntity> UserSessions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 安全策略集合(云同步)。
|
||||
/// </summary>
|
||||
public DbSet<SecurityPolicyEntity> SecurityPolicies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 审计日志集合(云同步)。
|
||||
/// </summary>
|
||||
public DbSet<AuditLogEntity> AuditLogs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置实体模型映射。
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">模型构建器。</param>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<TaskEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("T_Tasks");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.Id).ValueGeneratedOnAdd();
|
||||
entity.Property(e => e.Title).IsRequired().HasMaxLength(200);
|
||||
entity.Property(e => e.Priority).HasDefaultValue(TaskPriority.Medium).HasSentinel(TaskPriority.Low);
|
||||
entity.Property(e => e.IsCompleted).HasDefaultValue(false);
|
||||
|
||||
// ABP 审计字段
|
||||
entity.Property(e => e.CreationTime).HasDefaultValueSql("datetime('now')");
|
||||
entity.Property(e => e.LastModificationTime).IsRequired(false);
|
||||
entity.Property(e => e.CreatorId).IsRequired(false);
|
||||
entity.Property(e => e.LastModifierId).IsRequired(false);
|
||||
entity.Property(e => e.IsDeleted).HasDefaultValue(false);
|
||||
entity.Property(e => e.DeletionTime).IsRequired(false);
|
||||
entity.Property(e => e.DeleterId).IsRequired(false);
|
||||
entity.Property(e => e.ConcurrencyStamp).IsRequired(false);
|
||||
|
||||
// 用户隔离字段
|
||||
entity.Property(e => e.UserId).IsRequired();
|
||||
entity.Property(e => e.Code).HasMaxLength(32);
|
||||
|
||||
entity.Property(e => e.TaskType)
|
||||
.HasDefaultValue(TaskType.Normal)
|
||||
.HasConversion<int>();
|
||||
|
||||
entity.Property(e => e.MeetingNotes)
|
||||
.HasMaxLength(20000);
|
||||
|
||||
entity.Property(e => e.AudioDuration)
|
||||
.IsRequired(false);
|
||||
|
||||
entity.Property(e => e.Description)
|
||||
.HasMaxLength(5000);
|
||||
|
||||
entity.HasIndex(e => e.UserId);
|
||||
|
||||
entity.HasOne(e => e.ParentTask)
|
||||
.WithMany(e => e.SubTasks)
|
||||
.HasForeignKey(e => e.ParentTaskId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
entity.HasOne(e => e.User)
|
||||
.WithMany(e => e.Tasks)
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<AttachmentEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("Attachments");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.Id).ValueGeneratedOnAdd();
|
||||
|
||||
entity.Property(e => e.FileName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256);
|
||||
|
||||
entity.Property(e => e.FilePath)
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024);
|
||||
|
||||
entity.Property(e => e.ContentType)
|
||||
.HasMaxLength(128);
|
||||
|
||||
entity.Property(e => e.AttachmentType)
|
||||
.HasConversion<int>()
|
||||
.HasDefaultValue(AttachmentType.LocalFile);
|
||||
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("datetime('now')");
|
||||
|
||||
entity.HasOne(e => e.Task)
|
||||
.WithMany(e => e.Attachments)
|
||||
.HasForeignKey(e => e.TaskId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<UserEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("Users");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.UserName).IsUnique();
|
||||
entity.Property(e => e.UserName).IsRequired().HasMaxLength(64);
|
||||
entity.Property(e => e.PasswordHash).IsRequired();
|
||||
entity.Property(e => e.PasswordSalt).IsRequired();
|
||||
entity.Property(e => e.Role).HasMaxLength(32);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<UserSessionEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("UserSessions");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.UserId);
|
||||
entity.HasOne(e => e.User).WithMany().HasForeignKey(e => e.UserId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SecurityPolicyEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("SecurityPolicies");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.UserId).IsUnique();
|
||||
entity.Property(e => e.AllowPersist).HasDefaultValue(true);
|
||||
entity.Property(e => e.AllowSync).HasDefaultValue(true);
|
||||
entity.Property(e => e.SecondFactorExpiryMinutes).HasDefaultValue(30);
|
||||
entity.HasOne(e => e.User).WithMany().HasForeignKey(e => e.UserId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<AuditLogEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("AuditLogs");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.TimestampUtc);
|
||||
entity.HasIndex(e => e.UserId);
|
||||
entity.Property(e => e.EventType).HasMaxLength(64);
|
||||
entity.Property(e => e.Description).HasMaxLength(500);
|
||||
entity.Property(e => e.ClientIp).HasMaxLength(64);
|
||||
entity.Property(e => e.UserAgent).HasMaxLength(500);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user