diff --git a/.gitignore b/.gitignore index 0b4d796..f29054f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .vs/ *.user appsettings.Local.json +src/ElectionSystem.Cms/App_Data/ +src/ElectionSystem.Cms/wwwroot/media/ diff --git a/ElectionSystem.slnx b/ElectionSystem.slnx index 81415ae..c515442 100644 --- a/ElectionSystem.slnx +++ b/ElectionSystem.slnx @@ -1,6 +1,10 @@ + + + + diff --git a/docs/architecture.md b/docs/architecture.md index 572baaf..920615d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -18,6 +18,8 @@ dotnet user-secrets set "ConnectionStrings:ElectionDatabase" "Server=localhost;P 生产环境请以 `ConnectionStrings__ElectionDatabase` 环境变量注入。迁移由运维明确执行,不在应用启动时自动运行。 +CMS 使用独立的 `ElectionSystem.Cms` Orchard Core 主机及数据库;具体初始化和内容模型见 [cms-setup.md](cms-setup.md)。 + ## 当前数据库对象 初始迁移仅创建 `identity_users`、`organization_units` 和 `audit_entries`。审计元数据只记录受影响的字段名,不能用于保存线上匿名选票内容或关联信息。 diff --git a/docs/cms-setup.md b/docs/cms-setup.md new file mode 100644 index 0000000..c0f607d --- /dev/null +++ b/docs/cms-setup.md @@ -0,0 +1,33 @@ +# CMS 初始化与安全边界 + +## 运行模式 + +`ElectionSystem.Cms` 是独立的 Orchard Core 3.0.1 Headless CMS 主机。它仅管理公开内容、媒体、审核和发布历史,不是选举业务服务,也不能访问匿名选票、已投状态、线下计票或内部审计表。 + +首次启动时运行 `dotnet run --project src/ElectionSystem.Cms` 并访问 CMS 主机根路径,在初始化页面选择 **Headless** 配方和 **MySql** 提供程序,再输入连接字符串。生产环境应使用独立数据库,例如 `election_cms`;不要与 `election` 业务数据库共用表、账户或迁移历史。 + +请在部署前由数据库管理员创建 CMS 专用数据库及最小权限账户。Orchard Core 会验证连接后创建自己的表,但不会创建数据库;本仓库不会自动创建数据库或自动完成 CMS 初始化。 + +初始化完成后,在 **Configuration → Features** 启用 `ElectionSystem.Cms.PublicContent`。该模块的数据迁移会自动创建下列固定公开栏目和公共元数据字段;停用模块不会删除已有公开内容。 + +## 首批内容类型 + +在 CMS 的 Content Definition 中创建以下类型,并统一启用 Title、HTML/Markdown 正文、SEO、Taxonomy、Autoroute、Publish Later、Archive Later 和版本历史: + +| 类型 | 必需字段 | 限制 | +| --- | --- | --- | +| ElectionNotice | 活动外部标识、适用受众、生效时间 | 不含选民名单或参与状态 | +| PolicyDocument | 规则版本、生效日期、附件 | 历史版本不能原地覆盖 | +| CandidateDisclosure | 活动外部标识、候选人公开资料、公示期 | 不含联系方式、内部审核意见 | +| VoterGuide | 引导类别、适用范围 | 不透露任何个体资格结果 | +| ResultAnnouncement | 结果发布版本标识、统计口径 | 只能引用 Election API 已发布结果 | +| Faq | 问题、答案、排序 | 不接收个人申诉材料 | +| DownloadAsset | 文件、版本、适用范围 | 受文件安全与下载策略控制 | + +固定栏目以独立内容类型呈现,不能由编辑任意改名或删除;每项内容还带有“适用受众”和活动外部标识。后续将启用 Taxonomies 作为可扩展专题标签,但标签不能改变固定栏目或绕过发布审核。 + +## 发布与权限 + +配置 CMS 编辑、审核员、发布员、媒体管理员和选举联络员角色。发布流程必须是草稿、提交审核、退回或通过、定时/立即发布、下线/归档;回滚创建新版本并保留审计。 + +CMS 到公开站的集成只能是只读公开内容接口。结果、参与率和实时票数由 `ElectionSystem.Api` 根据活动公开策略裁决后提供;CMS 不得直接查询选举 MySQL 数据库。 diff --git a/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/ElectionSystem.Cms.PublicContent.csproj b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/ElectionSystem.Cms.PublicContent.csproj new file mode 100644 index 0000000..05b1a34 --- /dev/null +++ b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/ElectionSystem.Cms.PublicContent.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + true + enable + enable + + + + + + + + + + + + + + + diff --git a/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Manifest.cs b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Manifest.cs new file mode 100644 index 0000000..d84b4ae --- /dev/null +++ b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Manifest.cs @@ -0,0 +1,10 @@ +using OrchardCore.Modules.Manifest; + +[assembly: Module( + Name = "ElectionSystem.Cms.PublicContent", + Author = "Election System", + Website = "https://localhost", + Version = "1.0.0", + Description = "Fixed public-information sections and metadata for the election CMS.", + Category = "Content Management" +)] diff --git a/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Migrations.cs b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Migrations.cs new file mode 100644 index 0000000..b68ea95 --- /dev/null +++ b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Migrations.cs @@ -0,0 +1,51 @@ +using OrchardCore.ContentManagement.Metadata; +using OrchardCore.ContentManagement.Metadata.Settings; +using OrchardCore.Data.Migration; + +namespace ElectionSystem.Cms.PublicContent; + +public sealed class Migrations(IContentDefinitionManager contentDefinitionManager) : DataMigration +{ + public async Task CreateAsync() + { + await contentDefinitionManager.AlterPartDefinitionAsync("ElectionPublicMetadataPart", part => part + .WithDescription("公开内容的活动外部标识、适用范围和更新时间。不得保存选民身份、投票状态或计票明细。") + .WithField("ElectionReference", field => field + .OfType("TextField") + .WithDisplayName("选举活动外部标识")) + .WithField("AudienceScope", field => field + .OfType("MultiTextField") + .WithDisplayName("适用受众")) + .WithField("EffectiveFromUtc", field => field + .OfType("DateTimeField") + .WithDisplayName("生效时间(UTC)")) + .WithField("EffectiveToUtc", field => field + .OfType("DateTimeField") + .WithDisplayName("失效时间(UTC)"))); + + await CreatePublicContentTypeAsync("ElectionNotice", "选举公告"); + await CreatePublicContentTypeAsync("PolicyDocument", "规则与政策"); + await CreatePublicContentTypeAsync("CandidateDisclosure", "候选人公示"); + await CreatePublicContentTypeAsync("VoterGuide", "选民指南"); + await CreatePublicContentTypeAsync("ResultAnnouncement", "结果公告"); + await CreatePublicContentTypeAsync("PublicFaq", "常见问题"); + await CreatePublicContentTypeAsync("DownloadAsset", "资料下载"); + await CreatePublicContentTypeAsync("ArchiveRecord", "历史归档"); + + return 1; + } + + private Task CreatePublicContentTypeAsync(string name, string displayName) => + contentDefinitionManager.AlterTypeDefinitionAsync(name, type => type + .WithDisplayName(displayName) + .WithCategory("信息公开") + .Creatable() + .Draftable() + .Versionable() + .Securable() + .WithPart("TitlePart") + .WithPart("HtmlBodyPart") + .WithPart("AutoroutePart") + .WithPart("SeoMetaPart") + .WithPart("ElectionPublicMetadataPart")); +} diff --git a/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Startup.cs b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Startup.cs new file mode 100644 index 0000000..1912dd6 --- /dev/null +++ b/src/ElectionSystem.Cms.Modules/ElectionSystem.Cms.PublicContent/Startup.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Modules; + +namespace ElectionSystem.Cms.PublicContent; + +public sealed class Startup : StartupBase +{ + public override void ConfigureServices(IServiceCollection services) + { + } + +} diff --git a/src/ElectionSystem.Cms/ElectionSystem.Cms.csproj b/src/ElectionSystem.Cms/ElectionSystem.Cms.csproj new file mode 100644 index 0000000..d5751d2 --- /dev/null +++ b/src/ElectionSystem.Cms/ElectionSystem.Cms.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + election-system-cms-e1af6d0c-fc30-4a68-900f-38477ebdd3c7 + + + + + + + + + + + + diff --git a/src/ElectionSystem.Cms/Program.cs b/src/ElectionSystem.Cms/Program.cs new file mode 100644 index 0000000..741cbd0 --- /dev/null +++ b/src/ElectionSystem.Cms/Program.cs @@ -0,0 +1,10 @@ +using OrchardCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddOrchardCms(); + +var app = builder.Build(); + +app.UseOrchardCore(); +app.Run(); diff --git a/src/ElectionSystem.Cms/Properties/launchSettings.json b/src/ElectionSystem.Cms/Properties/launchSettings.json new file mode 100644 index 0000000..5076c5e --- /dev/null +++ b/src/ElectionSystem.Cms/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5065", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7247;http://localhost:5065", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/ElectionSystem.Cms/appsettings.Development.json b/src/ElectionSystem.Cms/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/ElectionSystem.Cms/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/ElectionSystem.Cms/appsettings.json b/src/ElectionSystem.Cms/appsettings.json new file mode 100644 index 0000000..a683581 --- /dev/null +++ b/src/ElectionSystem.Cms/appsettings.json @@ -0,0 +1,13 @@ +{ + "OrchardCore_Setup": { + "DefaultCulture": "zh-CN", + "SupportedCultures": [ "zh-CN", "en" ] + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}