diff --git a/.github/workflows/template-test.yml b/.github/workflows/template-test.yml
index 1418c231..bc5cab11 100644
--- a/.github/workflows/template-test.yml
+++ b/.github/workflows/template-test.yml
@@ -22,6 +22,7 @@ jobs:
matrix:
framework: [ 'net10.0', 'net8.0', 'net9.0' ]
aspire: [ 'true', 'false' ]
+ use-admin: [ 'true', 'false' ]
scenario:
# Test all databases with RabbitMQ
- { database: 'MySql', messagequeue: 'RabbitMQ' }
@@ -38,7 +39,7 @@ jobs:
- { database: 'MySql', messagequeue: 'NATS' }
- { database: 'MySql', messagequeue: 'AzureServiceBus' }
- { database: 'MySql', messagequeue: 'AmazonSQS' }
- name: ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}
+ name: ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}
steps:
- uses: actions/checkout@v3
@@ -68,53 +69,53 @@ jobs:
- name: Install template locally
run: dotnet new install .
- - name: Generate project from template - ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}
+ - name: Generate project from template - ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}
run: |
- mkdir -p test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}
+ mkdir -p test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}
if [ "${{ matrix.aspire }}" == "true" ]; then
- dotnet new netcorepal-web -n TestProject -F ${{ matrix.framework }} -D ${{ matrix.scenario.database }} -M ${{ matrix.scenario.messagequeue }} --UseAspire
+ dotnet new netcorepal-web -n TestProject -F ${{ matrix.framework }} -D ${{ matrix.scenario.database }} -M ${{ matrix.scenario.messagequeue }} --UseAspire --UseAdmin ${{ matrix.use-admin }}
else
- dotnet new netcorepal-web -n TestProject -F ${{ matrix.framework }} -D ${{ matrix.scenario.database }} -M ${{ matrix.scenario.messagequeue }}
+ dotnet new netcorepal-web -n TestProject -F ${{ matrix.framework }} -D ${{ matrix.scenario.database }} -M ${{ matrix.scenario.messagequeue }} --UseAdmin ${{ matrix.use-admin }}
fi
- name: Restore dependencies for generated project
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
# Restore dependencies, but allow missing preview packages
if ! dotnet restore --ignore-failed-sources; then
- echo "❌ RESTORE FAILED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}"
+ echo "❌ RESTORE FAILED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}"
exit 1
fi
- name: Build generated project
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
# Build the project - this must succeed
if ! dotnet build --no-restore --configuration Release --verbosity minimal; then
- echo "❌ BUILD FAILED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}"
+ echo "❌ BUILD FAILED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}"
echo "Build logs:"
dotnet build --no-restore --configuration Release --verbosity normal
exit 1
fi
- echo "✅ BUILD SUCCEEDED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}"
+ echo "✅ BUILD SUCCEEDED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}"
- name: Run tests for generated project
if: ${{ inputs.include-tests }}
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
# Run tests - this must succeed
if ! dotnet test --no-build --configuration Release \
--verbosity normal \
--logger "console;verbosity=normal"; then
- echo "❌ TESTS FAILED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}"
+ echo "❌ TESTS FAILED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}"
exit 1
fi
- echo "✅ TESTS SUCCEEDED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}"
+ echo "✅ TESTS SUCCEEDED for ${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}"
- name: Verify project structure and templates
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
echo "Project structure verification:"
ls -la src/
echo "Framework verification in csproj:"
@@ -141,10 +142,87 @@ jobs:
echo "✅ Aspire projects correctly not generated"
fi
fi
+ if [ "${{ matrix.use-admin }}" == "true" ]; then
+ echo "Verifying Admin files are present when UseAdmin=true"
+ if [ -d "src/TestProject.Domain/AggregatesModel/UserAggregate" ]; then
+ echo "✅ UserAggregate found"
+ else
+ echo "❌ UserAggregate not found when UseAdmin=true"
+ exit 1
+ fi
+ if [ -d "src/TestProject.Domain/AggregatesModel/RoleAggregate" ]; then
+ echo "✅ RoleAggregate found"
+ else
+ echo "❌ RoleAggregate not found when UseAdmin=true"
+ exit 1
+ fi
+ if [ -d "src/TestProject.Domain/AggregatesModel/DeptAggregate" ]; then
+ echo "✅ DeptAggregate found"
+ else
+ echo "❌ DeptAggregate not found when UseAdmin=true"
+ exit 1
+ fi
+ if [ -d "src/TestProject.Web/Endpoints/Identity/Admin" ]; then
+ echo "✅ Admin endpoints directory found"
+ else
+ echo "❌ Admin endpoints directory not found when UseAdmin=true"
+ exit 1
+ fi
+ if [ -d "src/frontend" ]; then
+ echo "✅ Frontend directory found"
+ else
+ echo "❌ Frontend directory not found when UseAdmin=true"
+ exit 1
+ fi
+ if [ -d "src/TestProject.Web/AppPermissions" ]; then
+ echo "✅ AppPermissions directory found"
+ else
+ echo "❌ AppPermissions directory not found when UseAdmin=true"
+ exit 1
+ fi
+ else
+ echo "Verifying Admin files are absent when UseAdmin=false"
+ if [ -d "src/TestProject.Domain/AggregatesModel/UserAggregate" ]; then
+ echo "❌ UserAggregate found when it should be excluded"
+ exit 1
+ else
+ echo "✅ UserAggregate correctly excluded"
+ fi
+ if [ -d "src/TestProject.Domain/AggregatesModel/RoleAggregate" ]; then
+ echo "❌ RoleAggregate found when it should be excluded"
+ exit 1
+ else
+ echo "✅ RoleAggregate correctly excluded"
+ fi
+ if [ -d "src/TestProject.Domain/AggregatesModel/DeptAggregate" ]; then
+ echo "❌ DeptAggregate found when it should be excluded"
+ exit 1
+ else
+ echo "✅ DeptAggregate correctly excluded"
+ fi
+ if [ -d "src/TestProject.Web/Endpoints/Identity/Admin" ]; then
+ echo "❌ Admin endpoints directory found when it should be excluded"
+ exit 1
+ else
+ echo "✅ Admin endpoints directory correctly excluded"
+ fi
+ if [ -d "src/frontend" ]; then
+ echo "❌ Frontend directory found when it should be excluded"
+ exit 1
+ else
+ echo "✅ Frontend directory correctly excluded"
+ fi
+ if [ -d "src/TestProject.Web/AppPermissions" ]; then
+ echo "❌ AppPermissions directory found when it should be excluded"
+ exit 1
+ else
+ echo "✅ AppPermissions directory correctly excluded"
+ fi
+ fi
- name: Verify solution file format
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
echo "Solution file format verification for framework: ${{ matrix.framework }}"
if [ "${{ matrix.framework }}" == "net8.0" ]; then
# For net8.0, expect .sln file and no .slnx file
@@ -182,16 +260,16 @@ jobs:
- name: Verify Dockerfile template substitution
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
echo "Dockerfile framework verification:"
grep -E "(aspnet|sdk):" src/TestProject.Web/Dockerfile
- name: Build Docker image for generated project
if: ${{ inputs.include-docker-build }}
run: |
- cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}/TestProject
+ cd test-projects/${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}/TestProject
# Build Docker image - this must succeed for Docker tests
- CONFIG_NAME="${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}"
+ CONFIG_NAME="${{ matrix.scenario.database }}-${{ matrix.scenario.messagequeue }}-${{ matrix.framework }}-aspire-${{ matrix.aspire }}-admin-${{ matrix.use-admin }}"
IMAGE_TAG="testproject-$(echo "$CONFIG_NAME" | tr '[:upper:]' '[:lower:]'):latest"
echo "Building Docker image with tag: $IMAGE_TAG"
if ! docker build -f src/TestProject.Web/Dockerfile -t "$IMAGE_TAG" .; then
@@ -438,4 +516,4 @@ jobs:
- name: Uninstall template
run: dotnet new uninstall .
- if: always()
+ if: always()
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 4c46a271..512fcbff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,8 @@ x86/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
+# Exception: allow frontend scripts bin directory
+!template/src/frontend/scripts/**/bin/
[Oo]bj/
[Ll]og/
[Ll]ogs/
@@ -197,9 +199,10 @@ PublishScripts/
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
+# Only ignore NuGet packages folders at project root level, not frontend workspace packages
+/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
+!/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
@@ -387,3 +390,8 @@ FodyWeavers.xsd
.idea/
*.sln.iml
/.vs
+
+# Internal packages build outputs (generated by unbuild --stub, contains absolute paths)
+src/frontend/internal/**/dist/
+src/frontend/packages/**/dist/
+src/frontend/scripts/**/dist/
diff --git a/README.md b/README.md
index a9ea2609..95b28f5f 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ dotnet new list
| `--MessageQueue` | `-M` | 消息队列提供程序 | `RabbitMQ`, `Kafka`, `AzureServiceBus`, `AmazonSQS`, `NATS`, `RedisStreams`, `Pulsar` | `RabbitMQ` |
| `--UseAspire` | `-U` | 启用 Aspire Dashboard 支持 | `true`, `false` | `false` |
| `--IncludeCopilotInstructions` | `-I` | 是否包含 Copilot 指令文件 | `true`, `false` | `true` |
+| `--UseAdmin` | | 是否包含 Admin 功能(用户、角色、部门管理、权限系统、前端) | `true`, `false` | `false` |
#### 使用示例
@@ -168,6 +169,19 @@ dotnet new netcorepal-web -n My.Project.Name -I false
> **提示:** 创建项目后,请根据选择的数据库和消息队列配置,使用对应的基础设施初始化脚本来启动所需的服务。详细说明请参考生成项目中的 `scripts/README.md` 文件。
+### 关于 Admin 功能
+
+当启用 `--UseAdmin` 选项时,模板会包含以下功能:
+
++ **用户管理**:用户的创建、更新、删除、查询等功能
++ **角色管理**:角色的创建、更新、删除、权限分配等功能
++ **部门管理**:部门的创建、更新、删除、树形结构管理等功能
++ **权限系统**:基于角色的权限控制(RBAC),支持权限码定义和验证
++ **JWT 认证**:完整的 JWT Token 认证和刷新 Token 机制
++ **前端界面**:基于 Vue 3 + Vben Admin 的管理后台界面
+
+启用 Admin 功能后,系统会自动创建默认的管理员账户(用户名:`admin`,密码:`123456`)。
+
### 关于 Aspire Dashboard
当启用 `--UseAspire` 选项时,模板会生成以下额外的项目:
diff --git a/scripts/gen-all-migrations.ps1 b/scripts/gen-all-migrations.ps1
new file mode 100644
index 00000000..b428265e
--- /dev/null
+++ b/scripts/gen-all-migrations.ps1
@@ -0,0 +1,111 @@
+# Generate migration scripts for all database + dotnet version combinations
+# Output to template/src/ABC.Template.Infrastructure/MigrationsAll/Database-Framework directories
+
+$ErrorActionPreference = "Stop"
+$TEMPLATE_ROOT = $PSScriptRoot | Split-Path -Parent
+$INFRA_MIGRATIONS_PATH = Join-Path $TEMPLATE_ROOT "template\src\ABC.Template.Infrastructure\MigrationsAll"
+$TMP_ROOT = Join-Path $env:TEMP "netcorepal-migrations-$(New-Guid)"
+
+# Database types
+$DATABASES = @("MySql", "SqlServer", "PostgreSQL", "Sqlite", "GaussDB", "DMDB")
+# Dotnet versions - check which SDKs are installed
+$ALL_FRAMEWORKS = @("net8.0", "net9.0", "net10.0")
+$INSTALLED_SDKS = dotnet --list-sdks | ForEach-Object { if ($_ -match '(\d+\.\d+\.\d+)') { $matches[1] } }
+$FRAMEWORKS = @()
+foreach ($fw in $ALL_FRAMEWORKS) {
+ $sdkVersion = if ($fw -eq "net8.0") { "8.0" } elseif ($fw -eq "net9.0") { "9.0" } else { "10.0" }
+ $hasSdk = $INSTALLED_SDKS | Where-Object { $_.StartsWith($sdkVersion) }
+ if ($hasSdk) {
+ $FRAMEWORKS += $fw
+ Write-Host "Found SDK for $fw, will generate migrations"
+ } else {
+ Write-Host "Warning: SDK for $fw not found, skipping"
+ }
+}
+if ($FRAMEWORKS.Count -eq 0) {
+ Write-Host "Error: No compatible SDKs found. Please install .NET 8.0, 9.0, or 10.0 SDK."
+ exit 1
+}
+
+# 1. Clear MigrationsAll directory before generation
+Write-Host "==> Clearing $INFRA_MIGRATIONS_PATH ..."
+if (Test-Path $INFRA_MIGRATIONS_PATH) {
+ Remove-Item -Recurse -Force $INFRA_MIGRATIONS_PATH
+}
+New-Item -ItemType Directory -Path $INFRA_MIGRATIONS_PATH -Force | Out-Null
+
+$MIGRATION_NAME = "Init"
+
+# Generate migrations for both UseAdmin=true and UseAdmin=false
+$USE_ADMIN_VALUES = @($false, $true)
+
+foreach ($db in $DATABASES) {
+ foreach ($fw in $FRAMEWORKS) {
+ foreach ($useAdmin in $USE_ADMIN_VALUES) {
+ $adminSuffix = if ($useAdmin) { "-admin" } else { "" }
+ $PRJ_DIR = Join-Path $TMP_ROOT "${db}-${fw}${adminSuffix}"
+ $OUTDIR = Join-Path $INFRA_MIGRATIONS_PATH "${db}-${fw}${adminSuffix}"
+
+ Write-Host "==> Cleaning $PRJ_DIR ..."
+ if (Test-Path $PRJ_DIR) {
+ Remove-Item -Recurse -Force $PRJ_DIR
+ }
+
+ $adminParam = if ($useAdmin) { "--UseAdmin true" } else { "" }
+ Write-Host "==> Generating $db $fw project with UseAdmin=$useAdmin..."
+ if ($useAdmin) {
+ dotnet new netcorepal-web -n ABC.Template -F $fw -D $db -M RabbitMQ --UseAdmin true --output $PRJ_DIR
+ } else {
+ dotnet new netcorepal-web -n ABC.Template -F $fw -D $db -M RabbitMQ --output $PRJ_DIR
+ }
+
+ $INFRA_DIR = Join-Path $PRJ_DIR "src\ABC.Template.Infrastructure"
+
+ # Recursively clean the Migrations directory generated by dotnet new to avoid template pollution
+ $MIGRATIONS_DIR = Join-Path $INFRA_DIR "Migrations"
+ if (Test-Path $MIGRATIONS_DIR) {
+ Remove-Item -Recurse -Force $MIGRATIONS_DIR
+ }
+
+ Write-Host "==> Running dotnet restore..."
+ Push-Location $PRJ_DIR
+ try {
+ dotnet restore --ignore-failed-sources
+ } finally {
+ Pop-Location
+ }
+
+ Write-Host "==> Running dotnet build..."
+ Push-Location $PRJ_DIR
+ try {
+ dotnet build --no-restore
+ } finally {
+ Pop-Location
+ }
+
+ Write-Host "==> Cleaning target migration directory..."
+ if (Test-Path $OUTDIR) {
+ Remove-Item -Recurse -Force $OUTDIR
+ }
+
+ Write-Host "==> Generating migration $MIGRATION_NAME..."
+ Push-Location $INFRA_DIR
+ try {
+ dotnet ef migrations add $MIGRATION_NAME --output-dir Migrations
+ } finally {
+ Pop-Location
+ }
+
+ New-Item -ItemType Directory -Path $OUTDIR -Force | Out-Null
+ $GENERATED_MIGRATIONS = Join-Path $INFRA_DIR "Migrations"
+ Copy-Item -Path "$GENERATED_MIGRATIONS\*" -Destination $OUTDIR -Recurse -Force
+ Write-Host "==> $db $fw UseAdmin=$useAdmin migration copied to $OUTDIR"
+ }
+ }
+}
+
+Write-Host "==> Cleaning temporary directory..."
+if (Test-Path $TMP_ROOT) {
+ Remove-Item -Recurse -Force $TMP_ROOT
+}
+Write-Host "All migrations generated successfully!"
diff --git a/scripts/gen-all-migrations.sh b/scripts/gen-all-migrations.sh
index 197ae9a3..b29437bc 100755
--- a/scripts/gen-all-migrations.sh
+++ b/scripts/gen-all-migrations.sh
@@ -12,38 +12,53 @@ DATABASES=(MySql SqlServer PostgreSQL Sqlite GaussDB DMDB)
FRAMEWORKS=(net8.0 net9.0 net10.0)
TEMPLATE_ROOT="$(pwd)"
-INFRA_MIGRATIONS_PATH="$TEMPLATE_ROOT/template/src/ABC.Template.Infrastructure/Migrations"
+INFRA_MIGRATIONS_PATH="$TEMPLATE_ROOT/template/src/ABC.Template.Infrastructure/MigrationsAll"
TMP_ROOT="$(mktemp -d /tmp/netcorepal-migrations-XXXXXXXX)"
-# 1. 生成前清空 Migrations 目录
+# 1. 生成前清空 MigrationsAll 目录
echo "==> 清空 $INFRA_MIGRATIONS_PATH ..."
rm -rf "$INFRA_MIGRATIONS_PATH"
mkdir -p "$INFRA_MIGRATIONS_PATH"
MIGRATION_NAME=Init
+# Generate migrations for both UseAdmin=true and UseAdmin=false
for db in "${DATABASES[@]}"; do
for fw in "${FRAMEWORKS[@]}"; do
- PRJ_DIR="$TMP_ROOT/${db}-${fw}"
- OUTDIR="$INFRA_MIGRATIONS_PATH/${db}-${fw}"
- echo "==> 清理 $PRJ_DIR ..."
- rm -rf "$PRJ_DIR"
- echo "==> dotnet new 生成 $db $fw 项目..."
- dotnet new netcorepal-web -n ABC.Template -F $fw -D $db -M RabbitMQ --output "$PRJ_DIR"
- INFRA_DIR="$PRJ_DIR/src/ABC.Template.Infrastructure"
- # 递归清理 dotnet new 生成的 Migrations 目录,避免模板污染
- rm -rf "$INFRA_DIR/Migrations"
- echo "==> dotnet restore..."
- (cd "$PRJ_DIR" && dotnet restore)
- echo "==> dotnet build..."
- (cd "$PRJ_DIR" && dotnet build --no-restore)
- echo "==> 清理目标迁移目录..."
- rm -rf "$OUTDIR"
- echo "==> 生成迁移 $MIGRATION_NAME..."
- (cd "$INFRA_DIR" && dotnet ef migrations add $MIGRATION_NAME --output-dir Migrations)
- mkdir -p "$OUTDIR"
- cp -r "$INFRA_DIR"/Migrations/* "$OUTDIR"/
- echo "==> $db $fw 迁移已拷贝到 $OUTDIR"
+ for useAdmin in false true; do
+ if [ "$useAdmin" = "true" ]; then
+ ADMIN_SUFFIX="-admin"
+ ADMIN_PARAM="--UseAdmin true"
+ else
+ ADMIN_SUFFIX=""
+ ADMIN_PARAM=""
+ fi
+
+ PRJ_DIR="$TMP_ROOT/${db}-${fw}${ADMIN_SUFFIX}"
+ OUTDIR="$INFRA_MIGRATIONS_PATH/${db}-${fw}${ADMIN_SUFFIX}"
+ echo "==> 清理 $PRJ_DIR ..."
+ rm -rf "$PRJ_DIR"
+ echo "==> dotnet new 生成 $db $fw 项目 (UseAdmin=$useAdmin)..."
+ if [ "$useAdmin" = "true" ]; then
+ dotnet new netcorepal-web -n ABC.Template -F $fw -D $db -M RabbitMQ --UseAdmin true --output "$PRJ_DIR"
+ else
+ dotnet new netcorepal-web -n ABC.Template -F $fw -D $db -M RabbitMQ --output "$PRJ_DIR"
+ fi
+ INFRA_DIR="$PRJ_DIR/src/ABC.Template.Infrastructure"
+ # 递归清理 dotnet new 生成的 Migrations 目录,避免模板污染
+ rm -rf "$INFRA_DIR/Migrations"
+ echo "==> dotnet restore..."
+ (cd "$PRJ_DIR" && dotnet restore)
+ echo "==> dotnet build..."
+ (cd "$PRJ_DIR" && dotnet build --no-restore)
+ echo "==> 清理目标迁移目录..."
+ rm -rf "$OUTDIR"
+ echo "==> 生成迁移 $MIGRATION_NAME..."
+ (cd "$INFRA_DIR" && dotnet ef migrations add $MIGRATION_NAME --output-dir Migrations)
+ mkdir -p "$OUTDIR"
+ cp -r "$INFRA_DIR"/Migrations/* "$OUTDIR"/
+ echo "==> $db $fw UseAdmin=$useAdmin 迁移已拷贝到 $OUTDIR"
+ done
done
done
diff --git a/template/.gitignore b/template/.gitignore
index 7af8161b..d2ed0a3d 100644
--- a/template/.gitignore
+++ b/template/.gitignore
@@ -28,6 +28,8 @@ x86/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
+# Exception: allow frontend scripts bin directory
+!src/frontend/scripts/**/bin/
[Oo]bj/
[Ll]og/
[Ll]ogs/
@@ -197,9 +199,10 @@ PublishScripts/
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
+# Only ignore NuGet packages folders at project root level, not frontend workspace packages
+/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
+!/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
@@ -388,3 +391,8 @@ FodyWeavers.xsd
.idea/
*.sln.iml
/.vs
+
+# Internal packages build outputs (generated by unbuild --stub, contains absolute paths)
+src/frontend/internal/**/dist/
+src/frontend/packages/**/dist/
+src/frontend/scripts/**/dist/
diff --git a/template/.template.config/template.json b/template/.template.config/template.json
index 4c3784d4..39b3e5c8 100644
--- a/template/.template.config/template.json
+++ b/template/.template.config/template.json
@@ -1,303 +1,673 @@
{
- "$schema": "http://json.schemastore.org/template",
- "author": "NetCorePal",
- "classifications": [ "Web" ],
- "tags": {
- "language": "C#",
- "type": "project"
+ "$schema": "http://json.schemastore.org/template",
+ "author": "NetCorePal",
+ "classifications": [ "Web" ],
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "identity": "NetCorePal.Web",
+ "name": "NetCorePal Cloud Web Application",
+ "shortName": "netcorepal-web",
+ "sourceName": "ABC.Template",
+ "preferNameDirectory": true,
+ "symbols": {
+ "Framework": {
+ "type": "parameter",
+ "description": "The target framework for the project",
+ "datatype": "choice",
+ "choices": [
+ {
+ "choice": "net8.0",
+ "description": "Target .NET 8.0"
+ },
+ {
+ "choice": "net9.0",
+ "description": "Target .NET 9.0"
+ },
+ {
+ "choice": "net10.0",
+ "description": "Target .NET 10.0"
+ }
+ ],
+ "defaultValue": "net10.0",
+ "replaces": "NETCOREPAL_FRAMEWORK"
},
- "identity": "NetCorePal.Web",
- "name": "NetCorePal Cloud Web Application",
- "shortName": "netcorepal-web",
- "sourceName": "ABC.Template",
- "preferNameDirectory": true,
- "symbols": {
- "Framework": {
- "type": "parameter",
- "description": "The target framework for the project",
- "datatype": "choice",
- "choices": [
- {
- "choice": "net8.0",
- "description": "Target .NET 8.0"
- },
- {
- "choice": "net9.0",
- "description": "Target .NET 9.0"
- },
- {
- "choice": "net10.0",
- "description": "Target .NET 10.0"
- }
- ],
- "defaultValue": "net10.0",
- "replaces": "NETCOREPAL_FRAMEWORK"
- },
- "Database": {
- "type": "parameter",
- "description": "The database provider to use",
- "datatype": "choice",
- "choices": [
- {
- "choice": "SqlServer",
- "description": "Use Microsoft SQL Server"
- },
- {
- "choice": "MySql",
- "description": "Use MySQL with Pomelo provider"
- },
- {
- "choice": "PostgreSQL",
- "description": "Use PostgreSQL"
- },
- {
- "choice": "Sqlite",
- "description": "Use SQLite"
- },
- {
- "choice": "GaussDB",
- "description": "Use GaussDB"
- },
- {
- "choice": "DMDB",
- "description": "Use DMDB"
- },
- {
- "choice": "MongoDB",
- "description": "Use MongoDB"
- }
- ],
- "defaultValue": "MySql"
- },
- "MessageQueue": {
- "type": "parameter",
- "description": "The message queue provider to use with CAP",
- "datatype": "choice",
- "choices": [
- {
- "choice": "RabbitMQ",
- "description": "Use RabbitMQ"
- },
- {
- "choice": "Kafka",
- "description": "Use Apache Kafka"
- },
- {
- "choice": "AzureServiceBus",
- "description": "Use Azure Service Bus"
- },
- {
- "choice": "AmazonSQS",
- "description": "Use Amazon SQS"
- },
- {
- "choice": "NATS",
- "description": "Use NATS"
- },
- {
- "choice": "RedisStreams",
- "description": "Use Redis Streams"
- },
- {
- "choice": "Pulsar",
- "description": "Use Apache Pulsar"
- }
- ],
- "defaultValue": "RabbitMQ"
- },
- "UseAspire": {
- "type": "parameter",
- "datatype": "bool",
- "defaultValue": "false",
- "description": "Enable Aspire Dashboard support for observability and orchestration"
- },
- "IncludeCopilotInstructions": {
- "type": "parameter",
- "datatype": "bool",
- "defaultValue": "true",
- "description": "Include Copilot instructions files (.github directory) in the generated template"
- },
- "SdkVersion": {
- "type": "generated",
- "generator": "switch",
- "dataType": "string",
- "parameters": {
- "cases": [
- { "condition": "(Framework == \"net8.0\")", "value": "8.0.100" },
- { "condition": "(Framework == \"net9.0\")", "value": "9.0.100" },
- { "condition": "(Framework == \"net10.0\")", "value": "10.0.100" }
- ],
- "defaultValue": "9.0.100"
- },
- "replaces": "NETCOREPAL_SDK_VERSION"
- },
- "DockerFramework": {
- "type": "generated",
- "generator": "switch",
- "dataType": "string",
- "parameters": {
- "cases": [
- { "condition": "(Framework == \"net8.0\")", "value": "8.0" },
- { "condition": "(Framework == \"net9.0\")", "value": "9.0" },
- { "condition": "(Framework == \"net10.0\")", "value": "10.0" }
- ],
- "defaultValue": "9.0"
- },
- "replaces": "NETCOREPAL_DOCKER_FRAMEWORK"
- },
- "UseSqlServer": {
- "type": "computed",
- "value": "(Database == \"SqlServer\")"
- },
- "UseMySql": {
- "type": "computed",
- "value": "(Database == \"MySql\")"
- },
- "UsePostgreSQL": {
- "type": "computed",
- "value": "(Database == \"PostgreSQL\")"
- },
- "UseSqlite": {
- "type": "computed",
- "value": "(Database == \"Sqlite\")"
- },
- "UseGaussDB": {
- "type": "computed",
- "value": "(Database == \"GaussDB\")"
- },
- "UseDMDB": {
- "type": "computed",
- "value": "(Database == \"DMDB\")"
- },
- "UseMongoDB": {
- "type": "computed",
- "value": "(Database == \"MongoDB\")"
- },
- "UseRabbitMQ": {
- "type": "computed",
- "value": "(MessageQueue == \"RabbitMQ\")"
- },
- "UseKafka": {
- "type": "computed",
- "value": "(MessageQueue == \"Kafka\")"
- },
- "UseAzureServiceBus": {
- "type": "computed",
- "value": "(MessageQueue == \"AzureServiceBus\")"
- },
- "UseAmazonSQS": {
- "type": "computed",
- "value": "(MessageQueue == \"AmazonSQS\")"
- },
- "UseNATS": {
- "type": "computed",
- "value": "(MessageQueue == \"NATS\")"
- },
- "UseRedisStreams": {
- "type": "computed",
- "value": "(MessageQueue == \"RedisStreams\")"
- },
- "UsePulsar": {
- "type": "computed",
- "value": "(MessageQueue == \"Pulsar\")"
+ "Database": {
+ "type": "parameter",
+ "description": "The database provider to use",
+ "datatype": "choice",
+ "choices": [
+ {
+ "choice": "SqlServer",
+ "description": "Use Microsoft SQL Server"
+ },
+ {
+ "choice": "MySql",
+ "description": "Use MySQL with Pomelo provider"
+ },
+ {
+ "choice": "PostgreSQL",
+ "description": "Use PostgreSQL"
+ },
+ {
+ "choice": "Sqlite",
+ "description": "Use SQLite"
+ },
+ {
+ "choice": "GaussDB",
+ "description": "Use GaussDB"
+ },
+ {
+ "choice": "DMDB",
+ "description": "Use DMDB"
+ },
+ {
+ "choice": "MongoDB",
+ "description": "Use MongoDB"
}
+ ],
+ "defaultValue": "MySql"
},
- "sources": [
+ "MessageQueue": {
+ "type": "parameter",
+ "description": "The message queue provider to use with CAP",
+ "datatype": "choice",
+ "choices": [
{
- "modifiers": [
- { "condition": "(!IncludeCopilotInstructions)", "exclude": [ ".github/**" ] },
- { "condition": "(Framework == \"net8.0\")", "exclude": [ "*.slnx" ] },
- { "condition": "(Framework == \"net9.0\" || Framework == \"net10.0\")", "exclude": [ "*.sln" ] },
- { "condition": "(!UseAspire)", "exclude": [ "src/ABC.Template.AppHost/**", "src/ABC.Template.ServiceDefaults/**", "test/ABC.Template.TestAppHost/**", "test/ABC.Template.AppHost.Tests/**" ] },
-
- // SqlServer
- { "condition": "(Database == \"SqlServer\" && Framework == \"net8.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net8.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"SqlServer\" && Framework == \"net9.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net9.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"SqlServer\" && Framework == \"net10.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net10.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database != \"SqlServer\" || Framework != \"net8.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net8.0/**" ] },
- { "condition": "(Database != \"SqlServer\" || Framework != \"net9.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net9.0/**" ] },
- { "condition": "(Database != \"SqlServer\" || Framework != \"net10.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net10.0/**" ] },
+ "choice": "RabbitMQ",
+ "description": "Use RabbitMQ"
+ },
+ {
+ "choice": "Kafka",
+ "description": "Use Apache Kafka"
+ },
+ {
+ "choice": "AzureServiceBus",
+ "description": "Use Azure Service Bus"
+ },
+ {
+ "choice": "AmazonSQS",
+ "description": "Use Amazon SQS"
+ },
+ {
+ "choice": "NATS",
+ "description": "Use NATS"
+ },
+ {
+ "choice": "RedisStreams",
+ "description": "Use Redis Streams"
+ },
+ {
+ "choice": "Pulsar",
+ "description": "Use Apache Pulsar"
+ }
+ ],
+ "defaultValue": "RabbitMQ"
+ },
+ "UseAspire": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "false",
+ "description": "Enable Aspire Dashboard support for observability and orchestration"
+ },
+ "IncludeCopilotInstructions": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "true",
+ "description": "Include Copilot instructions files (.github directory) in the generated template"
+ },
+ "UseAdmin": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "false",
+ "description": "Include Admin functionality (user, role, department management, permissions, and frontend)"
+ },
+ "SdkVersion": {
+ "type": "generated",
+ "generator": "switch",
+ "dataType": "string",
+ "parameters": {
+ "cases": [
+ {
+ "condition": "(Framework == \"net8.0\")",
+ "value": "8.0.100"
+ },
+ {
+ "condition": "(Framework == \"net9.0\")",
+ "value": "9.0.100"
+ },
+ {
+ "condition": "(Framework == \"net10.0\")",
+ "value": "10.0.100"
+ }
+ ],
+ "defaultValue": "9.0.100"
+ },
+ "replaces": "NETCOREPAL_SDK_VERSION"
+ },
+ "DockerFramework": {
+ "type": "generated",
+ "generator": "switch",
+ "dataType": "string",
+ "parameters": {
+ "cases": [
+ {
+ "condition": "(Framework == \"net8.0\")",
+ "value": "8.0"
+ },
+ {
+ "condition": "(Framework == \"net9.0\")",
+ "value": "9.0"
+ },
+ {
+ "condition": "(Framework == \"net10.0\")",
+ "value": "10.0"
+ }
+ ],
+ "defaultValue": "9.0"
+ },
+ "replaces": "NETCOREPAL_DOCKER_FRAMEWORK"
+ },
+ "UseSqlServer": {
+ "type": "computed",
+ "value": "(Database == \"SqlServer\")"
+ },
+ "UseMySql": {
+ "type": "computed",
+ "value": "(Database == \"MySql\")"
+ },
+ "UsePostgreSQL": {
+ "type": "computed",
+ "value": "(Database == \"PostgreSQL\")"
+ },
+ "UseSqlite": {
+ "type": "computed",
+ "value": "(Database == \"Sqlite\")"
+ },
+ "UseGaussDB": {
+ "type": "computed",
+ "value": "(Database == \"GaussDB\")"
+ },
+ "UseDMDB": {
+ "type": "computed",
+ "value": "(Database == \"DMDB\")"
+ },
+ "UseMongoDB": {
+ "type": "computed",
+ "value": "(Database == \"MongoDB\")"
+ },
+ "UseRabbitMQ": {
+ "type": "computed",
+ "value": "(MessageQueue == \"RabbitMQ\")"
+ },
+ "UseKafka": {
+ "type": "computed",
+ "value": "(MessageQueue == \"Kafka\")"
+ },
+ "UseAzureServiceBus": {
+ "type": "computed",
+ "value": "(MessageQueue == \"AzureServiceBus\")"
+ },
+ "UseAmazonSQS": {
+ "type": "computed",
+ "value": "(MessageQueue == \"AmazonSQS\")"
+ },
+ "UseNATS": {
+ "type": "computed",
+ "value": "(MessageQueue == \"NATS\")"
+ },
+ "UseRedisStreams": {
+ "type": "computed",
+ "value": "(MessageQueue == \"RedisStreams\")"
+ },
+ "UsePulsar": {
+ "type": "computed",
+ "value": "(MessageQueue == \"Pulsar\")"
+ }
+ },
+ "sources": [
+ {
+ "modifiers": [
+ {
+ "condition": "(!IncludeCopilotInstructions)",
+ "exclude": [ ".github/**" ]
+ },
+ {
+ "condition": "(Framework == \"net8.0\")",
+ "exclude": [ "*.slnx" ]
+ },
+ {
+ "condition": "(Framework == \"net9.0\" || Framework == \"net10.0\")",
+ "exclude": [ "*.sln" ]
+ },
+ {
+ "condition": "(!UseAspire)",
+ "exclude": [ "src/ABC.Template.AppHost/**", "src/ABC.Template.ServiceDefaults/**", "test/ABC.Template.TestAppHost/**", "test/ABC.Template.AppHost.Tests/**" ]
+ },
+ {
+ "condition": "(!UseAdmin)",
+ "exclude": [
+ "src/frontend/**",
+ "src/ABC.Template.Domain/AggregatesModel/UserAggregate/**",
+ "src/ABC.Template.Domain/AggregatesModel/RoleAggregate/**",
+ "src/ABC.Template.Domain/AggregatesModel/DeptAggregate/**",
+ "src/ABC.Template.Domain/DomainEvents/RoleEvents.cs",
+ "src/ABC.Template.Domain/DomainEvents/DeptEvents/**",
+ "src/ABC.Template.Domain/ErrorCodes.cs",
+ "src/ABC.Template.Infrastructure/EntityConfigurations/UserEntityTypeConfiguration.cs",
+ "src/ABC.Template.Infrastructure/EntityConfigurations/RoleEntityTypeConfiguration.cs",
+ "src/ABC.Template.Infrastructure/EntityConfigurations/DeptEntityTypeConfiguration.cs",
+ "src/ABC.Template.Infrastructure/Repositories/UserRepository.cs",
+ "src/ABC.Template.Infrastructure/Repositories/RoleRepository.cs",
+ "src/ABC.Template.Infrastructure/Repositories/DeptRepository.cs",
+ "src/ABC.Template.Web/AppPermissions/**",
+ "src/ABC.Template.Web/Application/Commands/Identity/**",
+ "src/ABC.Template.Web/Application/Queries/UserQuery.cs",
+ "src/ABC.Template.Web/Application/Queries/RoleQuery.cs",
+ "src/ABC.Template.Web/Application/Queries/DeptQuery.cs",
+ "src/ABC.Template.Web/Application/DomainEventHandlers/DeptInfoChangedDomainEventHandlerForUpdateUserDeptName.cs",
+ "src/ABC.Template.Web/Application/DomainEventHandlers/RoleInfoChangedDomainEventHandlerForUpdateUserRoleName.cs",
+ "src/ABC.Template.Web/Endpoints/Identity/**",
+ "src/ABC.Template.Web/Utils/SeedDatabaseExtension.cs",
+ "src/ABC.Template.Web/Utils/PasswordHasher.cs",
+ "src/ABC.Template.Web/Utils/TokenGenerator.cs",
+ "src/ABC.Template.Web/Extensions/ServiceCollectionExtensions.cs",
+ "test/ABC.Template.Web.Tests/UserTests.cs",
+ "test/ABC.Template.Web.Tests/RoleTests.cs",
+ "test/ABC.Template.Web.Tests/DeptTests.cs",
+ "test/ABC.Template.Web.Tests/Fixtures/AuthenticatedTestBase.cs"
+ ]
+ },
+ {
+ "description": "Include turbo-run bin directory which is otherwise excluded by default",
+ "condition": "(UseAdmin)",
+ "include": [
+ "src/frontend/scripts/turbo-run/bin/**"
+ ]
+ },
+ // SqlServer
+ {
+ "condition": "(Database == \"SqlServer\" && Framework == \"net8.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net8.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"SqlServer\" && Framework == \"net8.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net8.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"SqlServer\" && Framework == \"net9.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net9.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"SqlServer\" && Framework == \"net9.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net9.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"SqlServer\" && Framework == \"net10.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net10.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"SqlServer\" && Framework == \"net10.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net10.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database != \"SqlServer\" || Framework != \"net8.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net8.0/**" ]
+ },
+ {
+ "condition": "(Database != \"SqlServer\" || Framework != \"net8.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net8.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"SqlServer\" || Framework != \"net9.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net9.0/**" ]
+ },
+ {
+ "condition": "(Database != \"SqlServer\" || Framework != \"net9.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net9.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"SqlServer\" || Framework != \"net10.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net10.0/**" ]
+ },
+ {
+ "condition": "(Database != \"SqlServer\" || Framework != \"net10.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/SqlServer-net10.0-admin/**" ]
+ },
- // MySql
- { "condition": "(Database == \"MySql\" && Framework == \"net8.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net8.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"MySql\" && Framework == \"net9.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net9.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"MySql\" && Framework == \"net10.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net10.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database != \"MySql\" || Framework != \"net8.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net8.0/**" ] },
- { "condition": "(Database != \"MySql\" || Framework != \"net9.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net9.0/**" ] },
- { "condition": "(Database != \"MySql\" || Framework != \"net10.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net10.0/**" ] },
+ // MySql
+ {
+ "condition": "(Database == \"MySql\" && Framework == \"net8.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net8.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"MySql\" && Framework == \"net8.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net8.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"MySql\" && Framework == \"net9.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net9.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"MySql\" && Framework == \"net9.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net9.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"MySql\" && Framework == \"net10.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net10.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"MySql\" && Framework == \"net10.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net10.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database != \"MySql\" || Framework != \"net8.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net8.0/**" ]
+ },
+ {
+ "condition": "(Database != \"MySql\" || Framework != \"net8.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net8.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"MySql\" || Framework != \"net9.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net9.0/**" ]
+ },
+ {
+ "condition": "(Database != \"MySql\" || Framework != \"net9.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net9.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"MySql\" || Framework != \"net10.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net10.0/**" ]
+ },
+ {
+ "condition": "(Database != \"MySql\" || Framework != \"net10.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/MySql-net10.0-admin/**" ]
+ },
- // PostgreSQL
- { "condition": "(Database == \"PostgreSQL\" && Framework == \"net8.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net8.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"PostgreSQL\" && Framework == \"net9.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net9.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"PostgreSQL\" && Framework == \"net10.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net10.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database != \"PostgreSQL\" || Framework != \"net8.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net8.0/**" ] },
- { "condition": "(Database != \"PostgreSQL\" || Framework != \"net9.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net9.0/**" ] },
- { "condition": "(Database != \"PostgreSQL\" || Framework != \"net10.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net10.0/**" ] },
+ // PostgreSQL
+ {
+ "condition": "(Database == \"PostgreSQL\" && Framework == \"net8.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net8.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"PostgreSQL\" && Framework == \"net8.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net8.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"PostgreSQL\" && Framework == \"net9.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net9.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"PostgreSQL\" && Framework == \"net9.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net9.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"PostgreSQL\" && Framework == \"net10.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net10.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"PostgreSQL\" && Framework == \"net10.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net10.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database != \"PostgreSQL\" || Framework != \"net8.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net8.0/**" ]
+ },
+ {
+ "condition": "(Database != \"PostgreSQL\" || Framework != \"net8.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net8.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"PostgreSQL\" || Framework != \"net9.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net9.0/**" ]
+ },
+ {
+ "condition": "(Database != \"PostgreSQL\" || Framework != \"net9.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net9.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"PostgreSQL\" || Framework != \"net10.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net10.0/**" ]
+ },
+ {
+ "condition": "(Database != \"PostgreSQL\" || Framework != \"net10.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/PostgreSQL-net10.0-admin/**" ]
+ },
- // Sqlite
- { "condition": "(Database == \"Sqlite\" && Framework == \"net8.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net8.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"Sqlite\" && Framework == \"net9.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net9.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"Sqlite\" && Framework == \"net10.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net10.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database != \"Sqlite\" || Framework != \"net8.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net8.0/**" ] },
- { "condition": "(Database != \"Sqlite\" || Framework != \"net9.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net9.0/**" ] },
- { "condition": "(Database != \"Sqlite\" || Framework != \"net10.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net10.0/**" ] },
+ // Sqlite
+ {
+ "condition": "(Database == \"Sqlite\" && Framework == \"net8.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net8.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"Sqlite\" && Framework == \"net8.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net8.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"Sqlite\" && Framework == \"net9.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net9.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"Sqlite\" && Framework == \"net9.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net9.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"Sqlite\" && Framework == \"net10.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net10.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"Sqlite\" && Framework == \"net10.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net10.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database != \"Sqlite\" || Framework != \"net8.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net8.0/**" ]
+ },
+ {
+ "condition": "(Database != \"Sqlite\" || Framework != \"net8.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net8.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"Sqlite\" || Framework != \"net9.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net9.0/**" ]
+ },
+ {
+ "condition": "(Database != \"Sqlite\" || Framework != \"net9.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net9.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"Sqlite\" || Framework != \"net10.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net10.0/**" ]
+ },
+ {
+ "condition": "(Database != \"Sqlite\" || Framework != \"net10.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/Sqlite-net10.0-admin/**" ]
+ },
- // GaussDB
- { "condition": "(Database == \"GaussDB\" && Framework == \"net8.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net8.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"GaussDB\" && Framework == \"net9.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net9.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"GaussDB\" && Framework == \"net10.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net10.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database != \"GaussDB\" || Framework != \"net8.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net8.0/**" ] },
- { "condition": "(Database != \"GaussDB\" || Framework != \"net9.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net9.0/**" ] },
- { "condition": "(Database != \"GaussDB\" || Framework != \"net10.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net10.0/**" ] },
+ // GaussDB
+ {
+ "condition": "(Database == \"GaussDB\" && Framework == \"net8.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net8.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"GaussDB\" && Framework == \"net8.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net8.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"GaussDB\" && Framework == \"net9.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net9.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"GaussDB\" && Framework == \"net9.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net9.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"GaussDB\" && Framework == \"net10.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net10.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"GaussDB\" && Framework == \"net10.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net10.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database != \"GaussDB\" || Framework != \"net8.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net8.0/**" ]
+ },
+ {
+ "condition": "(Database != \"GaussDB\" || Framework != \"net8.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net8.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"GaussDB\" || Framework != \"net9.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net9.0/**" ]
+ },
+ {
+ "condition": "(Database != \"GaussDB\" || Framework != \"net9.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net9.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"GaussDB\" || Framework != \"net10.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net10.0/**" ]
+ },
+ {
+ "condition": "(Database != \"GaussDB\" || Framework != \"net10.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/GaussDB-net10.0-admin/**" ]
+ },
- // DMDB
- { "condition": "(Database == \"DMDB\" && Framework == \"net8.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net8.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"DMDB\" && Framework == \"net9.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net9.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database == \"DMDB\" && Framework == \"net10.0\")", "rename": {
- "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0": "src/ABC.Template.Infrastructure/Migrations"
- } },
- { "condition": "(Database != \"DMDB\" || Framework != \"net8.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net8.0/**" ] },
- { "condition": "(Database != \"DMDB\" || Framework != \"net9.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net9.0/**" ] },
- { "condition": "(Database != \"DMDB\" || Framework != \"net10.0\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0/**" ] },
- { "condition": "(Database != \"DMDB\")", "exclude": [ "src/ABC.Template.Infrastructure/MyDmDateTimeOffsetTypeMapping.cs" ] },
+ // DMDB
+ {
+ "condition": "(Database == \"DMDB\" && Framework == \"net8.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net8.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"DMDB\" && Framework == \"net8.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net8.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"DMDB\" && Framework == \"net9.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net9.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"DMDB\" && Framework == \"net9.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net9.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"DMDB\" && Framework == \"net10.0\" && UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database == \"DMDB\" && Framework == \"net10.0\" && !UseAdmin)",
+ "rename": {
+ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0": "src/ABC.Template.Infrastructure/Migrations"
+ }
+ },
+ {
+ "condition": "(Database != \"DMDB\" || Framework != \"net8.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net8.0/**" ]
+ },
+ {
+ "condition": "(Database != \"DMDB\" || Framework != \"net8.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net8.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"DMDB\" || Framework != \"net9.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net9.0/**" ]
+ },
+ {
+ "condition": "(Database != \"DMDB\" || Framework != \"net9.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net9.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"DMDB\" || Framework != \"net10.0\" || UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0/**" ]
+ },
+ {
+ "condition": "(Database != \"DMDB\" || Framework != \"net10.0\" || !UseAdmin)",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/**" ]
+ },
+ {
+ "condition": "(Database != \"DMDB\")",
+ "exclude": [ "src/ABC.Template.Infrastructure/MyDmDateTimeOffsetTypeMapping.cs" ]
+ },
- // MongoDB
- { "condition": "(Database == \"MongoDB\")", "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll" ] }
- ]
+ // MongoDB
+ {
+ "condition": "(Database == \"MongoDB\")",
+ "exclude": [ "src/ABC.Template.Infrastructure/MigrationsAll" ]
}
- ]
+ ]
+ }
+ ]
}
diff --git a/template/Directory.Packages.props b/template/Directory.Packages.props
index e84680b3..63bc3e02 100644
--- a/template/Directory.Packages.props
+++ b/template/Directory.Packages.props
@@ -140,6 +140,9 @@
+
+
+
diff --git a/template/src/ABC.Template.AppHost/ABC.Template.AppHost.csproj b/template/src/ABC.Template.AppHost/ABC.Template.AppHost.csproj
index 40d3c21d..ddec8719 100644
--- a/template/src/ABC.Template.AppHost/ABC.Template.AppHost.csproj
+++ b/template/src/ABC.Template.AppHost/ABC.Template.AppHost.csproj
@@ -35,6 +35,9 @@
+
+
+
diff --git a/template/src/ABC.Template.AppHost/Program.cs b/template/src/ABC.Template.AppHost/Program.cs
index 610d65e7..23199955 100644
--- a/template/src/ABC.Template.AppHost/Program.cs
+++ b/template/src/ABC.Template.AppHost/Program.cs
@@ -94,7 +94,7 @@
//#endif
// Add web project with infrastructure dependencies
-builder.AddProject("web")
+var web = builder.AddProject("web")
.WithExternalHttpEndpoints()
.WithHttpHealthCheck("/health")
.WithReference(redis)
@@ -135,4 +135,16 @@
//#endif
;
+//#if (UseAdmin)
+// Add frontend project
+var frontend = builder.AddJavaScriptApp("frontend", "../frontend")
+ .WithPnpm()
+ .WithDeveloperCertificateTrust(true)
+ .WithHttpEndpoint(port: 5666, env: "VITE_PORT", name: "http", isProxied: false)
+ .WithExternalHttpEndpoints()
+ .WithEnvironment("VITE_API_BASE", web.GetEndpoint("http"))
+ .WithReference(web)
+ .WaitFor(web);
+//#endif
+
await builder.Build().RunAsync();
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/DeliverAggregate/DeliverRecord.cs b/template/src/ABC.Template.Domain/AggregatesModel/DeliverAggregate/DeliverRecord.cs
deleted file mode 100644
index 56410d39..00000000
--- a/template/src/ABC.Template.Domain/AggregatesModel/DeliverAggregate/DeliverRecord.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using ABC.Template.Domain.AggregatesModel.OrderAggregate;
-
-namespace ABC.Template.Domain.AggregatesModel.DeliverAggregate;
-
-public partial record DeliverRecordId : IGuidStronglyTypedId;
-
-public class DeliverRecord : Entity, IAggregateRoot
-{
- protected DeliverRecord() { }
-
-
- public DeliverRecord(OrderId orderId)
- {
- this.OrderId = orderId;
- }
-
- public OrderId OrderId { get; private set; } = default!;
-}
-
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/DeptAggregate/Dept.cs b/template/src/ABC.Template.Domain/AggregatesModel/DeptAggregate/Dept.cs
new file mode 100644
index 00000000..76f657ef
--- /dev/null
+++ b/template/src/ABC.Template.Domain/AggregatesModel/DeptAggregate/Dept.cs
@@ -0,0 +1,195 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using ABC.Template.Domain.DomainEvents.DeptEvents;
+using ABC.Template.Domain;
+
+namespace ABC.Template.Domain.AggregatesModel.DeptAggregate;
+
+///
+/// 部门ID(强类型ID)
+///
+public partial record DeptId : IInt64StronglyTypedId;
+
+///
+/// 部门聚合根
+/// 用于管理企业部门的层级结构
+///
+public class Dept : Entity, IAggregateRoot
+{
+ ///
+ /// 部门名称
+ ///
+ public string Name { get; private set; } = string.Empty;
+
+ ///
+ /// 备注
+ ///
+ public string Remark { get; private set; } = string.Empty;
+
+ ///
+ /// 上级部门ID
+ ///
+ public DeptId ParentId { get; private set; } = default!;
+
+ ///
+ /// 状态(0=禁用,1=启用)
+ ///
+ public int Status { get; private set; } = 1;
+
+ ///
+ /// 创建时间
+ ///
+ public DateTimeOffset CreatedAt { get; init; }
+
+ ///
+ /// 是否删除
+ ///
+ public Deleted IsDeleted { get; private set; } = new Deleted(false);
+
+ ///
+ /// 删除时间
+ ///
+ public DeletedTime DeletedAt { get; private set; } = new DeletedTime(DateTimeOffset.UtcNow);
+
+ ///
+ /// 更新时间
+ ///
+ public UpdateTime UpdateTime { get; private set; } = new UpdateTime(DateTimeOffset.UtcNow);
+
+ ///
+ /// 子部门(不映射到数据库,用于内存中的树形结构)
+ ///
+ [NotMapped]
+ public virtual ICollection Children { get; } = [];
+
+ protected Dept()
+ {
+ }
+
+ ///
+ /// 创建部门
+ ///
+ /// 部门名称
+ /// 备注
+ /// 上级部门ID
+ /// 状态(0=禁用,1=启用)
+ public Dept(string name, string remark, DeptId parentId, int status)
+ {
+ CreatedAt =DateTimeOffset.UtcNow;
+ Name = name;
+ Remark = remark;
+ ParentId = parentId;
+ Status = status;
+ }
+
+ ///
+ /// 更新部门信息
+ ///
+ /// 部门名称
+ /// 备注
+ /// 上级部门ID
+ /// 状态(0=禁用,1=启用)
+ public void UpdateInfo(string name, string remark, DeptId parentId, int status)
+ {
+ Name = name;
+ Remark = remark;
+ ParentId = parentId;
+ Status = status;
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+
+ AddDomainEvent(new DeptInfoChangedDomainEvent(this));
+ }
+
+ ///
+ /// 激活部门
+ ///
+ public void Activate()
+ {
+ if (Status == 1)
+ {
+ throw new KnownException("部门已经是激活状态", ErrorCodes.DeptAlreadyActivated);
+ }
+
+ Status = 1;
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+
+ ///
+ /// 停用部门
+ ///
+ public void Deactivate()
+ {
+ if (Status == 0)
+ {
+ throw new KnownException("部门已经被停用", ErrorCodes.DeptAlreadyDeactivated);
+ }
+
+ Status = 0;
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+
+ ///
+ /// 软删除部门
+ ///
+ public void SoftDelete()
+ {
+ if (IsDeleted)
+ {
+ throw new KnownException("部门已经被删除", ErrorCodes.DeptAlreadyDeleted);
+ }
+
+ IsDeleted = true;
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+
+ ///
+ /// 添加子部门
+ ///
+ /// 子部门
+ public void AddChild(Dept child)
+ {
+ if (child == null)
+ {
+ throw new KnownException("子部门不能为空", ErrorCodes.ChildDeptCannotBeEmpty);
+ }
+
+ Children.Add(child);
+ }
+
+ ///
+ /// 移除子部门
+ ///
+ /// 子部门
+ public void RemoveChild(Dept child)
+ {
+ if (child == null)
+ {
+ throw new KnownException("子部门不能为空", ErrorCodes.ChildDeptCannotBeEmpty);
+ }
+
+ Children.Remove(child);
+ }
+
+ ///
+ /// 获取所有子部门(包括子级的子级)
+ ///
+ /// 所有子部门
+ public IEnumerable GetAllChildren()
+ {
+ var result = new List();
+ foreach (var child in Children)
+ {
+ result.Add(child);
+ result.AddRange(child.GetAllChildren());
+ }
+ return result;
+ }
+
+ ///
+ /// 获取部门层级路径
+ ///
+ /// 层级路径
+ public string GetPath()
+ {
+ return Name;
+ }
+}
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/OrderAggregate/Order.cs b/template/src/ABC.Template.Domain/AggregatesModel/OrderAggregate/Order.cs
deleted file mode 100644
index 570b8201..00000000
--- a/template/src/ABC.Template.Domain/AggregatesModel/OrderAggregate/Order.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using ABC.Template.Domain.DomainEvents;
-using NetCorePal.Extensions.Domain;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using NetCorePal.Extensions.Primitives;
-
-namespace ABC.Template.Domain.AggregatesModel.OrderAggregate;
-
-public partial record OrderId : IGuidStronglyTypedId;
-
-///
-/// 聚合根
-///
-public partial class Order : Entity, IAggregateRoot
-{
- ///
- /// 受保护的默认构造函数,用以作为EF Core的反射入口
- ///
- protected Order()
- {
- }
-
- public Order(string name, int count)
- {
- this.Name = name;
- this.Count = count;
- this.AddDomainEvent(new OrderCreatedDomainEvent(this));
- }
-
- public bool Paid { get; private set; } = false;
-
- public string Name { get; private set; } = string.Empty;
-
- public int Count { get; private set; }
-
- public RowVersion RowVersion { get; private set; } = new RowVersion();
-
- public UpdateTime UpdateTime { get; private set; } = new UpdateTime(DateTimeOffset.UtcNow);
-
- public void OrderPaid()
- {
- if (Paid)
- {
- throw new KnownException("Order has been paid");
- }
- else
- {
- this.Paid = true;
- this.AddDomainEvent(new OrderPaidDomainEvent(this));
- }
- }
-}
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/RoleAggregate/Role.cs b/template/src/ABC.Template.Domain/AggregatesModel/RoleAggregate/Role.cs
new file mode 100644
index 00000000..3bf2f420
--- /dev/null
+++ b/template/src/ABC.Template.Domain/AggregatesModel/RoleAggregate/Role.cs
@@ -0,0 +1,88 @@
+using ABC.Template.Domain.DomainEvents.RoleEvents;
+using ABC.Template.Domain;
+
+namespace ABC.Template.Domain.AggregatesModel.RoleAggregate;
+
+public partial record RoleId : IGuidStronglyTypedId;
+
+public class Role : Entity, IAggregateRoot
+{
+ protected Role()
+ {
+ }
+
+ public string Name { get; private set; } = string.Empty;
+ public string Description { get; private set; } = string.Empty;
+ public DateTimeOffset CreatedAt { get; init; }
+ public bool IsActive { get; private set; } = true;
+ public Deleted IsDeleted { get; private set; } = new Deleted(false);
+ public DeletedTime DeletedAt { get; private set; } = new DeletedTime(DateTimeOffset.UtcNow);
+
+ public virtual ICollection Permissions { get; init; } = [];
+
+ public Role(string name, string description, IEnumerable permissions)
+ {
+ CreatedAt =DateTimeOffset.UtcNow;
+ Name = name;
+ Description = description;
+ Permissions = new List(permissions);
+ IsActive = true;
+ }
+
+ public void UpdateRoleInfo(string name, string description)
+ {
+ Name = name;
+ Description = description;
+ AddDomainEvent(new RoleInfoChangedDomainEvent(this));
+ }
+
+ public void UpdateRolePermissions(IEnumerable newPermissions)
+ {
+ var currentPermissionMap = Permissions.ToDictionary(p => p.PermissionCode);
+ var newPermissionMap = newPermissions.ToDictionary(p => p.PermissionCode);
+
+ var permissionsToRemove = currentPermissionMap.Keys.Except(newPermissionMap.Keys).ToList();
+ foreach (var permissionCode in permissionsToRemove)
+ {
+ Permissions.Remove(currentPermissionMap[permissionCode]);
+ }
+
+ var permissionsToAdd = newPermissionMap.Keys.Except(currentPermissionMap.Keys).ToList();
+ foreach (var permissionCode in permissionsToAdd)
+ {
+ Permissions.Add(newPermissionMap[permissionCode]);
+ }
+
+ AddDomainEvent(new RolePermissionChangedDomainEvent(this));
+ }
+
+ public void Deactivate()
+ {
+ if (!IsActive)
+ {
+ throw new KnownException("角色已经被停用", ErrorCodes.RoleAlreadyDeactivated);
+ }
+
+ IsActive = false;
+ }
+
+ public void Activate()
+ {
+ if (IsActive)
+ {
+ throw new KnownException("角色已经是激活状态", ErrorCodes.RoleAlreadyActivated);
+ }
+
+ IsActive = true;
+ }
+
+ public void SoftDelete()
+ {
+ IsDeleted = true;
+ }
+
+ public bool HasPermission(string permissionCode)
+ {
+ return Permissions.Any(p => p.PermissionCode == permissionCode);
+ }
+}
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/RoleAggregate/RolePermission.cs b/template/src/ABC.Template.Domain/AggregatesModel/RoleAggregate/RolePermission.cs
new file mode 100644
index 00000000..d8e739ef
--- /dev/null
+++ b/template/src/ABC.Template.Domain/AggregatesModel/RoleAggregate/RolePermission.cs
@@ -0,0 +1,27 @@
+namespace ABC.Template.Domain.AggregatesModel.RoleAggregate;
+
+public class RolePermission
+{
+ protected RolePermission()
+ {
+ }
+
+ public RoleId RoleId { get; internal set; } = default!;
+ public string PermissionCode { get; private set; } = string.Empty;
+ public string PermissionName { get; private set; } = string.Empty;
+ public string PermissionDescription { get; private set; } = string.Empty;
+
+
+ public RolePermission(string permissionCode, string permissionName, string permissionDescription)
+ {
+ PermissionCode = permissionCode;
+ PermissionName = permissionName;
+ PermissionDescription = permissionDescription;
+ }
+
+ public void UpdatePermissionInfo(string permissionName, string permissionDescription)
+ {
+ PermissionName = permissionName;
+ PermissionDescription = permissionDescription;
+ }
+}
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/User.cs b/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/User.cs
new file mode 100644
index 00000000..4ba598f8
--- /dev/null
+++ b/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/User.cs
@@ -0,0 +1,176 @@
+using ABC.Template.Domain.AggregatesModel.RoleAggregate;
+using ABC.Template.Domain.AggregatesModel.DeptAggregate;
+
+namespace ABC.Template.Domain.AggregatesModel.UserAggregate;
+
+public partial record UserId : IInt64StronglyTypedId;
+
+public class User : Entity, IAggregateRoot
+{
+ protected User()
+ {
+ }
+
+ public string Name { get; private set; } = string.Empty;
+ public string Email { get; private set; } = string.Empty;
+ public string Phone { get; private set; } = string.Empty;
+ public string RealName { get; private set; } = string.Empty;
+ public int Status { get; private set; }
+ public string PasswordHash { get; private set; } = string.Empty;
+ public bool IsActive { get; private set; } = true;
+ public DateTimeOffset CreatedAt { get; init; }
+ public DateTimeOffset? LastLoginTime { get; private set; }
+ public UpdateTime UpdateTime { get; private set; } = new UpdateTime(DateTimeOffset.UtcNow);
+ public Deleted IsDeleted { get; private set; } = new Deleted(false);
+ public DeletedTime DeletedAt { get; private set; } = new DeletedTime(DateTimeOffset.UtcNow);
+ public string Gender { get; private set; } = string.Empty;
+ public int Age { get; private set; }
+ public DateTimeOffset BirthDate { get; private set; } = default!;
+
+ public virtual ICollection Roles { get; } = [];
+
+ public virtual UserDept Dept { get; private set; } = default!;
+
+ public ICollection RefreshTokens { get; } = [];
+
+ public User(string name, string phone, string password, IEnumerable roles, string realName, int status, string email, string gender, DateTimeOffset birthDate)
+ {
+ CreatedAt =DateTimeOffset.UtcNow;
+ Name = name;
+ Phone = phone;
+ PasswordHash = password;
+ RealName = realName;
+ Status = status;
+ Email = email;
+ Gender = gender;
+ Age = CalculateAge(birthDate);
+ BirthDate = birthDate;
+ foreach (var userRole in roles)
+ {
+ Roles.Add(userRole);
+ }
+ }
+
+ public void SoftDelete()
+ {
+ IsDeleted = true;
+ }
+
+ public void PasswordReset(string password)
+ {
+ PasswordHash = password;
+ }
+
+ public void SetUserRefreshToken(string refreshToken)
+ {
+ var refreshTokenInfo = new UserRefreshToken(refreshToken);
+ RefreshTokens.Add(refreshTokenInfo);
+ }
+
+ public void UpdateLastLoginTime(DateTimeOffset loginTime)
+ {
+ LastLoginTime = loginTime;
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+
+ public static int CalculateAge(DateTimeOffset birthDate)
+ {
+ var today =DateTimeOffset.UtcNow.Date;
+ int age = today.Year - birthDate.Year;
+ if (birthDate.Date > today.AddYears(-age))
+ {
+ age--;
+ }
+ return age;
+ }
+
+ public void UpdateUserInfo(string name, string phone, string realName, int status, string email, string gender, DateTimeOffset birthDate)
+ {
+ Name = name;
+ Phone = phone;
+ RealName = realName;
+ Status = status;
+ Email = email;
+ Gender = gender;
+ Age = CalculateAge(birthDate);
+ BirthDate = birthDate;
+ }
+
+ public void UpdateRoleInfo(RoleId roleId, string roleName)
+ {
+ var savedRole = Roles.FirstOrDefault(r => r.RoleId == roleId);
+ savedRole?.UpdateRoleInfo(roleName);
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+
+ public void UpdatePassword(string newPasswordHash)
+ {
+ if (!string.IsNullOrEmpty(newPasswordHash))
+ {
+ PasswordHash = newPasswordHash;
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+ }
+
+ public void UpdateRoles(IEnumerable rolesToBeAssigned)
+ {
+ var currentRoleMap = Roles.ToDictionary(r => r.RoleId);
+ var targetRoleMap = rolesToBeAssigned.ToDictionary(r => r.RoleId);
+
+ var roleIdsToRemove = currentRoleMap.Keys.Except(targetRoleMap.Keys);
+ foreach (var roleId in roleIdsToRemove)
+ {
+ Roles.Remove(currentRoleMap[roleId]);
+ }
+
+ var roleIdsToAdd = targetRoleMap.Keys.Except(currentRoleMap.Keys);
+ foreach (var roleId in roleIdsToAdd)
+ {
+ var targetRole = targetRoleMap[roleId];
+ Roles.Add(targetRole);
+ }
+ }
+
+ ///
+ /// 分配部门
+ ///
+ /// 部门
+ public void AssignDept(UserDept dept)
+ {
+ ArgumentNullException.ThrowIfNull(dept);
+
+ Dept = dept;
+ }
+
+ ///
+ /// 更新部门名称
+ ///
+ /// 新的部门名称
+ public void UpdateDeptName(string deptName)
+ {
+ if (Dept == null)
+ {
+ return;
+ }
+
+ Dept.UpdateDeptName(deptName);
+ UpdateTime = new UpdateTime(DateTimeOffset.UtcNow);
+ }
+
+ ///
+ /// 撤销所有未过期的刷新令牌(用于退出登录)
+ ///
+ public void RevokeAllRefreshTokens()
+ {
+ var now = DateTimeOffset.UtcNow;
+ foreach (var token in RefreshTokens)
+ {
+ // 只撤销未过期且未使用的令牌
+ if (!token.IsRevoked && !token.IsUsed && token.ExpiresTime > now)
+ {
+ token.Revoke();
+ }
+ }
+ UpdateTime = new UpdateTime(now);
+ }
+}
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/UserDept.cs b/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/UserDept.cs
new file mode 100644
index 00000000..1420b383
--- /dev/null
+++ b/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/UserDept.cs
@@ -0,0 +1,82 @@
+using ABC.Template.Domain.AggregatesModel.DeptAggregate;
+using ABC.Template.Domain;
+
+namespace ABC.Template.Domain.AggregatesModel.UserAggregate;
+
+///
+/// 用户部门关系实体
+/// 表示用户与部门的一对一关系
+///
+public class UserDept
+{
+ ///
+ /// 用户ID
+ ///
+ public UserId UserId { get; private set; } = default!;
+
+ ///
+ /// 部门ID
+ ///
+ public DeptId DeptId { get; private set; } = default!;
+
+ ///
+ /// 部门名称
+ ///
+ public string DeptName { get; private set; } = string.Empty;
+
+ ///
+ /// 分配时间
+ ///
+ public DateTimeOffset AssignedAt { get; init; }
+
+ protected UserDept()
+ {
+ }
+
+ ///
+ /// 创建用户部门关系
+ ///
+ /// 用户ID
+ /// 部门ID
+ /// 部门名称
+ public UserDept(UserId userId, DeptId deptId, string deptName)
+ {
+ UserId = userId;
+ DeptId = deptId;
+ AssignedAt = DateTimeOffset.UtcNow;
+ DeptName = deptName;
+ }
+
+ ///
+ /// 更新部门名称
+ ///
+ /// 新的部门名称
+ public void UpdateDeptName(string deptName)
+ {
+ if (string.IsNullOrWhiteSpace(deptName))
+ {
+ throw new KnownException("部门名称不能为空", ErrorCodes.DeptNameCannotBeEmpty);
+ }
+
+ DeptName = deptName;
+ }
+
+//#if (UseMongoDB)
+ ///
+ /// 更新部门信息(部门ID和部门名称)
+ /// MongoDB 下 UpdateUser 直接操作 UserDepts 集合时使用,用于就地更新已有 UserDept。
+ ///
+ /// 新的部门ID
+ /// 新的部门名称
+ public void UpdateDept(DeptId deptId, string deptName)
+ {
+ if (string.IsNullOrWhiteSpace(deptName))
+ {
+ throw new KnownException("部门名称不能为空", ErrorCodes.DeptNameCannotBeEmpty);
+ }
+
+ DeptId = deptId;
+ DeptName = deptName;
+ }
+//#endif
+}
diff --git a/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/UserRefreshToken.cs b/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/UserRefreshToken.cs
new file mode 100644
index 00000000..28db55df
--- /dev/null
+++ b/template/src/ABC.Template.Domain/AggregatesModel/UserAggregate/UserRefreshToken.cs
@@ -0,0 +1,41 @@
+using ABC.Template.Domain;
+
+namespace ABC.Template.Domain.AggregatesModel.UserAggregate;
+
+public partial record UserRefreshTokenId : IInt64StronglyTypedId;
+
+public class UserRefreshToken : Entity
+{
+ protected UserRefreshToken()
+ {
+ }
+
+ public UserRefreshToken(string token)
+ {
+ Token = token;
+ CreatedTime =DateTimeOffset.UtcNow;
+ ExpiresTime = CreatedTime.AddDays(1);
+ }
+
+ public UserId UserId { get; private set; } = null!;
+ public string Token { get; private set; } = string.Empty;
+ public DateTimeOffset CreatedTime { get; init; }
+ public DateTimeOffset ExpiresTime { get; init; }
+ public bool IsUsed { get; private set; }
+ public bool IsRevoked { get; private set; }
+
+ public void Use()
+ {
+ if (IsUsed ||
+ IsRevoked ||
+ ExpiresTime
+/// 部门信息变更领域事件
+///
+public record DeptInfoChangedDomainEvent(Dept Dept) : IDomainEvent;
diff --git a/template/src/ABC.Template.Domain/DomainEvents/OrderDomainEvents.cs b/template/src/ABC.Template.Domain/DomainEvents/OrderDomainEvents.cs
deleted file mode 100644
index 2e7f5478..00000000
--- a/template/src/ABC.Template.Domain/DomainEvents/OrderDomainEvents.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using ABC.Template.Domain.AggregatesModel.OrderAggregate;
-
-namespace ABC.Template.Domain.DomainEvents;
-
-public record OrderCreatedDomainEvent(Order Order) : IDomainEvent;
-
-public record OrderPaidDomainEvent(Order Order) : IDomainEvent;
diff --git a/template/src/ABC.Template.Domain/DomainEvents/RoleEvents.cs b/template/src/ABC.Template.Domain/DomainEvents/RoleEvents.cs
new file mode 100644
index 00000000..56d33cee
--- /dev/null
+++ b/template/src/ABC.Template.Domain/DomainEvents/RoleEvents.cs
@@ -0,0 +1,6 @@
+using ABC.Template.Domain.AggregatesModel.RoleAggregate;
+
+namespace ABC.Template.Domain.DomainEvents.RoleEvents;
+
+public record RoleInfoChangedDomainEvent(Role Role) : IDomainEvent;
+public record RolePermissionChangedDomainEvent(Role Role) : IDomainEvent;
diff --git a/template/src/ABC.Template.Domain/ErrorCodes.cs b/template/src/ABC.Template.Domain/ErrorCodes.cs
new file mode 100644
index 00000000..b617b88a
--- /dev/null
+++ b/template/src/ABC.Template.Domain/ErrorCodes.cs
@@ -0,0 +1,114 @@
+namespace ABC.Template.Domain;
+
+///
+/// 错误码定义
+///
+public sealed class ErrorCodes
+{
+ private ErrorCodes()
+ {
+
+ }
+
+ #region 用户相关错误 (100xxx)
+
+ ///
+ /// 未找到账户
+ ///
+ public const int AccountNotFound = 100001;
+
+ ///
+ /// 用户名或密码错误
+ ///
+ public const int UserNameOrPasswordError = 100002;
+
+ ///
+ /// 未找到用户
+ ///
+ public const int UserNotFound = 100003;
+
+ ///
+ /// 无效的用户身份
+ ///
+ public const int InvalidUserIdentity = 100004;
+
+ ///
+ /// 无效的用户
+ ///
+ public const int InvalidUser = 100005;
+
+ ///
+ /// 无效的令牌
+ ///
+ public const int InvalidToken = 100006;
+
+ ///
+ /// 无效的刷新令牌
+ ///
+ public const int InvalidRefreshToken = 100007;
+
+ #endregion
+
+ #region 角色相关错误 (110xxx)
+
+ ///
+ /// 未找到角色
+ ///
+ public const int RoleNotFound = 110001;
+
+ ///
+ /// 不能删除管理员角色
+ ///
+ public const int CannotDeleteAdminRole = 110002;
+
+ ///
+ /// 角色已经被停用
+ ///
+ public const int RoleAlreadyDeactivated = 110003;
+
+ ///
+ /// 角色已经是激活状态
+ ///
+ public const int RoleAlreadyActivated = 110004;
+
+ #endregion
+
+ #region 部门相关错误 (120xxx)
+
+ ///
+ /// 未找到部门
+ ///
+ public const int DeptNotFound = 120001;
+
+ ///
+ /// 该部门下存在子部门,无法删除
+ ///
+ public const int DeptHasChildrenCannotDelete = 120002;
+
+ ///
+ /// 部门已经是激活状态
+ ///
+ public const int DeptAlreadyActivated = 120003;
+
+ ///
+ /// 部门已经被停用
+ ///
+ public const int DeptAlreadyDeactivated = 120004;
+
+ ///
+ /// 部门已经被删除
+ ///
+ public const int DeptAlreadyDeleted = 120005;
+
+ ///
+ /// 部门名称不能为空
+ ///
+ public const int DeptNameCannotBeEmpty = 120006;
+
+ ///
+ /// 子部门不能为空
+ ///
+ public const int ChildDeptCannotBeEmpty = 120007;
+
+ #endregion
+}
diff --git a/template/src/ABC.Template.Infrastructure/ApplicationDbContext.cs b/template/src/ABC.Template.Infrastructure/ApplicationDbContext.cs
index 28ff925c..a44333c2 100644
--- a/template/src/ABC.Template.Infrastructure/ApplicationDbContext.cs
+++ b/template/src/ABC.Template.Infrastructure/ApplicationDbContext.cs
@@ -1,11 +1,14 @@
-using ABC.Template.Domain.AggregatesModel.OrderAggregate;
using MediatR;
using Microsoft.EntityFrameworkCore;
//#if (UseDMDB)
using Microsoft.EntityFrameworkCore.Storage;
//#endif
using NetCorePal.Extensions.DistributedTransactions.CAP.Persistence;
-using ABC.Template.Domain.AggregatesModel.DeliverAggregate;
+//#if (UseAdmin)
+using ABC.Template.Domain.AggregatesModel.UserAggregate;
+using ABC.Template.Domain.AggregatesModel.RoleAggregate;
+using ABC.Template.Domain.AggregatesModel.DeptAggregate;
+//#endif
namespace ABC.Template.Infrastructure;
@@ -25,7 +28,7 @@ public partial class ApplicationDbContext(DbContextOptions
, IDMDBCapDataStorage
//#elif (UseMongoDB)
, IMongoDBCapDataStorage
- //#endif
+//#endif
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -39,13 +42,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
-//#if (UseDMDB)
+ //#if (UseDMDB)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ReplaceService();
base.OnConfiguring(optionsBuilder);
}
-//#endif
+ //#endif
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
@@ -53,6 +56,12 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura
base.ConfigureConventions(configurationBuilder);
}
- public DbSet Orders => Set();
- public DbSet DeliverRecords => Set();
+ //#if (UseAdmin)
+ public DbSet Users => Set();
+ public DbSet Roles => Set();
+ public DbSet RolePermissions => Set();
+ public DbSet UserRoles => Set();
+ public DbSet Depts => Set();
+ public DbSet UserDepts => Set();
+ //#endif
}
diff --git a/template/src/ABC.Template.Infrastructure/DesignTimeApplicationDbContextFactory.cs b/template/src/ABC.Template.Infrastructure/DesignTimeApplicationDbContextFactory.cs
index e06d2f72..5e57a7b0 100644
--- a/template/src/ABC.Template.Infrastructure/DesignTimeApplicationDbContextFactory.cs
+++ b/template/src/ABC.Template.Infrastructure/DesignTimeApplicationDbContextFactory.cs
@@ -52,6 +52,8 @@ public ApplicationDbContext CreateDbContext(string[] args)
{
b.MigrationsAssembly(typeof(DesignTimeApplicationDbContextFactory).Assembly.FullName);
});
+
+ options.UseMongoDB("mongodb://localhost:27017/any", "any");
});
var provider = services.BuildServiceProvider();
diff --git a/template/src/ABC.Template.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs b/template/src/ABC.Template.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs
deleted file mode 100644
index d5d026f8..00000000
--- a/template/src/ABC.Template.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using ABC.Template.Domain.AggregatesModel.DeliverAggregate;
-
-namespace ABC.Template.Infrastructure.EntityConfigurations
-{
- internal class DeliverRecordConfiguration : IEntityTypeConfiguration
- {
- public void Configure(EntityTypeBuilder builder)
- {
- builder.ToTable("deliverrecord");
- builder.HasKey(t => t.Id);
- builder.Property(t => t.Id).UseGuidVersion7ValueGenerator();
- }
- }
-
-}
diff --git a/template/src/ABC.Template.Infrastructure/EntityConfigurations/DeptEntityTypeConfiguration.cs b/template/src/ABC.Template.Infrastructure/EntityConfigurations/DeptEntityTypeConfiguration.cs
new file mode 100644
index 00000000..8f858190
--- /dev/null
+++ b/template/src/ABC.Template.Infrastructure/EntityConfigurations/DeptEntityTypeConfiguration.cs
@@ -0,0 +1,47 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using ABC.Template.Domain.AggregatesModel.DeptAggregate;
+
+namespace ABC.Template.Infrastructure.EntityConfigurations;
+
+///
+/// 部门实体类型配置
+///
+internal class DeptEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("dept");
+
+ builder.HasKey(d => d.Id);
+ builder.Property(t => t.Id).UseSnowFlakeValueGenerator();
+
+ builder.Property(d => d.Name)
+ .IsRequired()
+ .HasMaxLength(100);
+
+ builder.Property(d => d.Remark)
+ .HasMaxLength(500);
+
+ builder.Property(d => d.Status)
+ .IsRequired();
+
+ builder.Property(d => d.CreatedAt)
+ .IsRequired();
+
+ builder.Property(d => d.IsDeleted)
+ .IsRequired();
+
+ builder.Property(d => d.DeletedAt);
+
+ builder.Property(d => d.UpdateTime);
+
+ // 索引
+ builder.HasIndex(d => d.ParentId);
+ builder.HasIndex(d => d.Status);
+ builder.HasIndex(d => d.IsDeleted);
+
+ // 软删除过滤器
+ builder.HasQueryFilter(d => !d.IsDeleted);
+ }
+}
\ No newline at end of file
diff --git a/template/src/ABC.Template.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs b/template/src/ABC.Template.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs
deleted file mode 100644
index 18d9d005..00000000
--- a/template/src/ABC.Template.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using ABC.Template.Domain.AggregatesModel.OrderAggregate;
-
-namespace ABC.Template.Infrastructure.EntityConfigurations
-{
- internal class OrderEntityTypeConfiguration : IEntityTypeConfiguration
- {
- public void Configure(EntityTypeBuilder builder)
- {
- builder.ToTable("order");
- builder.HasKey(t => t.Id);
- builder.Property(t => t.Id).UseGuidVersion7ValueGenerator();
- builder.Property(b => b.Name).HasMaxLength(100);
- builder.Property(b => b.Count);
- builder.Property(b => b.Paid);
- }
- }
-
-}
diff --git a/template/src/ABC.Template.Infrastructure/EntityConfigurations/RoleEntityTypeConfiguration.cs b/template/src/ABC.Template.Infrastructure/EntityConfigurations/RoleEntityTypeConfiguration.cs
new file mode 100644
index 00000000..c09e997f
--- /dev/null
+++ b/template/src/ABC.Template.Infrastructure/EntityConfigurations/RoleEntityTypeConfiguration.cs
@@ -0,0 +1,48 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using ABC.Template.Domain.AggregatesModel.RoleAggregate;
+
+namespace ABC.Template.Infrastructure.EntityConfigurations;
+
+internal class RoleEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("role");
+ builder.HasKey(t => t.Id);
+ builder.Property(t => t.Id).UseGuidVersion7ValueGenerator();
+
+ builder.Property(b => b.Name).HasMaxLength(50).IsRequired();
+ builder.Property(b => b.Description).HasMaxLength(200);
+ builder.Property(b => b.CreatedAt);
+ builder.Property(b => b.IsActive);
+ builder.Property(b => b.IsDeleted);
+
+ builder.HasIndex(b => b.Name).IsUnique();
+
+ builder.HasMany(r => r.Permissions).WithOne().HasForeignKey(rp => rp.RoleId);
+ builder.Navigation(e => e.Permissions).AutoInclude();
+
+ builder.HasQueryFilter(b => !b.IsDeleted);
+ }
+}
+
+internal class RolePermissionEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("role_permission");
+
+ builder.HasKey(t => new { t.RoleId, t.PermissionCode });
+
+ builder.Property(b => b.RoleId);
+ builder.Property(b => b.PermissionCode).HasMaxLength(100).IsRequired();
+ builder.Property(b => b.PermissionName).HasMaxLength(100);
+ builder.Property(b => b.PermissionDescription).HasMaxLength(200);
+
+ builder.HasOne()
+ .WithMany(r => r.Permissions)
+ .HasForeignKey(rp => rp.RoleId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
\ No newline at end of file
diff --git a/template/src/ABC.Template.Infrastructure/EntityConfigurations/UserEntityTypeConfiguration.cs b/template/src/ABC.Template.Infrastructure/EntityConfigurations/UserEntityTypeConfiguration.cs
new file mode 100644
index 00000000..618218ba
--- /dev/null
+++ b/template/src/ABC.Template.Infrastructure/EntityConfigurations/UserEntityTypeConfiguration.cs
@@ -0,0 +1,113 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using ABC.Template.Domain.AggregatesModel.UserAggregate;
+
+namespace ABC.Template.Infrastructure.EntityConfigurations;
+
+internal class UserEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("user");
+ builder.HasKey(t => t.Id);
+ builder.Property(t => t.Id).UseSnowFlakeValueGenerator();
+
+ builder.Property(b => b.Name).HasMaxLength(50).IsRequired();
+ builder.Property(b => b.Email).HasMaxLength(100).IsRequired();
+ builder.Property(b => b.PasswordHash).HasMaxLength(255).IsRequired();
+ builder.Property(b => b.Phone).HasMaxLength(20);
+ builder.Property(b => b.RealName).HasMaxLength(50);
+ builder.Property(b => b.Gender).HasMaxLength(10);
+ builder.Property(b => b.Age);
+ builder.Property(b => b.BirthDate);
+ builder.Property(b => b.IsActive);
+ builder.Property(b => b.CreatedAt);
+ builder.Property(b => b.LastLoginTime);
+ builder.Property(b => b.UpdateTime);
+
+ builder.HasIndex(b => b.Name);
+ builder.HasIndex(b => b.Email);
+
+ builder.HasMany(au => au.Roles)
+ .WithOne()
+ .HasForeignKey(aur => aur.UserId)
+ .OnDelete(DeleteBehavior.ClientCascade);
+ builder.Navigation(au => au.Roles).AutoInclude();
+
+ builder.HasMany(u => u.RefreshTokens)
+ .WithOne()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ builder.HasOne(au => au.Dept)
+ .WithOne()
+ .HasForeignKey(ud => ud.UserId)
+ .OnDelete(DeleteBehavior.ClientCascade);
+ builder.Navigation(au => au.Dept).AutoInclude();
+ }
+}
+
+internal class UserDeptEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("user_dept");
+
+ builder.HasKey(ud => ud.UserId);
+
+//#if (UseMongoDB)
+ // MongoDB 要求主键必须映射到 _id 元素
+ builder.Property(ud => ud.UserId)
+ .HasElementName("_id");
+ builder.Property(ud => ud.DeptId);
+ builder.Property(ud => ud.DeptName).HasMaxLength(100);
+ builder.Property(ud => ud.AssignedAt)
+ .IsRequired();
+
+ // 注意:不能为主键字段(映射到 _id)创建索引,因为 MongoDB 的 _id 字段已经有默认的唯一索引
+ builder.HasIndex(ud => ud.DeptId);
+//#else
+ builder.Property(ud => ud.UserId);
+ builder.Property(ud => ud.DeptId);
+ builder.Property(ud => ud.DeptName).HasMaxLength(100);
+ builder.Property(ud => ud.AssignedAt)
+ .IsRequired();
+
+ // 索引
+ builder.HasIndex(ud => ud.UserId);
+ builder.HasIndex(ud => ud.DeptId);
+//#endif
+ }
+}
+
+internal class UserRoleEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("user_role");
+
+ builder.HasKey(t => new { t.UserId, t.RoleId });
+
+ builder.Property(b => b.UserId);
+ builder.Property(b => b.RoleId);
+ builder.Property(b => b.RoleName).HasMaxLength(50).IsRequired();
+
+ builder.HasOne()
+ .WithMany(u => u.Roles)
+ .HasForeignKey(ur => ur.UserId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
+
+internal class UserRefreshTokenConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("user_refresh_token");
+ builder.HasKey(x => x.Id);
+ builder.Property(x => x.Id).UseSnowFlakeValueGenerator();
+ builder.Property(x => x.Token).HasMaxLength(500).IsRequired();
+ builder.Property(x => x.CreatedTime).IsRequired();
+ builder.Property(x => x.ExpiresTime).IsRequired();
+ }
+}
\ No newline at end of file
diff --git a/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/20260122055126_Init.Designer.cs b/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/20260122055126_Init.Designer.cs
new file mode 100644
index 00000000..a3e7de1a
--- /dev/null
+++ b/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/20260122055126_Init.Designer.cs
@@ -0,0 +1,433 @@
+//
+using System;
+using ABC.Template.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace ABC.Template.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260122055126_Init")]
+ partial class Init
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Dm:ValueGenerationStrategy", DmValueGenerationStrategy.IdentityColumn)
+ .HasAnnotation("ProductVersion", "9.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.DeptAggregate.Dept", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeletedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("IsDeleted")
+ .HasColumnType("BIT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.Property("ParentId")
+ .HasColumnType("BIGINT");
+
+ b.Property("Remark")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("NVARCHAR2(500)");
+
+ b.Property("Status")
+ .HasColumnType("INT");
+
+ b.Property("UpdateTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsDeleted");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Status");
+
+ b.ToTable("dept", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.RoleAggregate.Role", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("CHAR(36)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeletedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("NVARCHAR2(200)");
+
+ b.Property("IsActive")
+ .HasColumnType("BIT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("BIT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("role", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.RoleAggregate.RolePermission", b =>
+ {
+ b.Property("RoleId")
+ .HasColumnType("CHAR(36)");
+
+ b.Property("PermissionCode")
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.Property("PermissionDescription")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("NVARCHAR2(200)");
+
+ b.Property("PermissionName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.HasKey("RoleId", "PermissionCode");
+
+ b.ToTable("role_permission", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.User", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("Age")
+ .HasColumnType("INT");
+
+ b.Property("BirthDate")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("CreatedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeletedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.Property("Gender")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("NVARCHAR2(10)");
+
+ b.Property("IsActive")
+ .HasColumnType("BIT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("BIT");
+
+ b.Property("LastLoginTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("NVARCHAR2(255)");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("NVARCHAR2(20)");
+
+ b.Property("RealName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.Property("Status")
+ .HasColumnType("INT");
+
+ b.Property("UpdateTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Email");
+
+ b.HasIndex("Name");
+
+ b.ToTable("user", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserDept", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("BIGINT");
+
+ b.Property("AssignedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeptId")
+ .HasColumnType("BIGINT");
+
+ b.Property("DeptName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.HasKey("UserId");
+
+ b.HasIndex("DeptId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_dept", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserRefreshToken", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("CreatedTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("ExpiresTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("IsRevoked")
+ .HasColumnType("BIT");
+
+ b.Property("IsUsed")
+ .HasColumnType("BIT");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("NVARCHAR2(500)");
+
+ b.Property("UserId")
+ .HasColumnType("BIGINT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_refresh_token", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("BIGINT");
+
+ b.Property("RoleId")
+ .HasColumnType("CHAR(36)");
+
+ b.Property("RoleName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.ToTable("user_role", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
+ {
+ b.Property("Key")
+ .HasMaxLength(128)
+ .HasColumnType("NVARCHAR2(128)");
+
+ b.Property("Instance")
+ .HasMaxLength(256)
+ .HasColumnType("NVARCHAR2(256)");
+
+ b.Property("LastLockTime")
+ .HasColumnType("TIMESTAMP");
+
+ b.HasKey("Key");
+
+ b.ToTable("CAPLock", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("Added")
+ .HasColumnType("TIMESTAMP");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TIMESTAMP");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("NVARCHAR2(200)");
+
+ b.Property("Retries")
+ .HasColumnType("INT");
+
+ b.Property("StatusName")
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("NVARCHAR2(40)");
+
+ b.Property("Version")
+ .HasMaxLength(20)
+ .HasColumnType("NVARCHAR2(20)");
+
+ b.HasKey("Id");
+
+ b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName");
+
+ b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName");
+
+ b.ToTable("CAPPublishedMessage", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.ReceivedMessage", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("Added")
+ .HasColumnType("TIMESTAMP");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TIMESTAMP");
+
+ b.Property("Group")
+ .HasMaxLength(200)
+ .HasColumnType("NVARCHAR2(200)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(400)
+ .HasColumnType("NVARCHAR2(400)");
+
+ b.Property("Retries")
+ .HasColumnType("INT");
+
+ b.Property("StatusName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.Property("Version")
+ .HasMaxLength(20)
+ .HasColumnType("NVARCHAR2(20)");
+
+ b.HasKey("Id");
+
+ b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName")
+ .HasDatabaseName("IX_ExpiresAt_StatusName1");
+
+ b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName")
+ .HasDatabaseName("IX_Version_ExpiresAt_StatusName1");
+
+ b.ToTable("CAPReceivedMessage", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.RoleAggregate.RolePermission", b =>
+ {
+ b.HasOne("ABC.Template.Domain.AggregatesModel.RoleAggregate.Role", null)
+ .WithMany("Permissions")
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserDept", b =>
+ {
+ b.HasOne("ABC.Template.Domain.AggregatesModel.UserAggregate.User", null)
+ .WithOne("Dept")
+ .HasForeignKey("ABC.Template.Domain.AggregatesModel.UserAggregate.UserDept", "UserId")
+ .OnDelete(DeleteBehavior.ClientCascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserRefreshToken", b =>
+ {
+ b.HasOne("ABC.Template.Domain.AggregatesModel.UserAggregate.User", null)
+ .WithMany("RefreshTokens")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserRole", b =>
+ {
+ b.HasOne("ABC.Template.Domain.AggregatesModel.UserAggregate.User", null)
+ .WithMany("Roles")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.RoleAggregate.Role", b =>
+ {
+ b.Navigation("Permissions");
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.User", b =>
+ {
+ b.Navigation("Dept")
+ .IsRequired();
+
+ b.Navigation("RefreshTokens");
+
+ b.Navigation("Roles");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/20260122055126_Init.cs b/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/20260122055126_Init.cs
new file mode 100644
index 00000000..3e54f7ab
--- /dev/null
+++ b/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/20260122055126_Init.cs
@@ -0,0 +1,308 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace ABC.Template.Infrastructure.Migrations
+{
+ ///
+ public partial class Init : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "CAPLock",
+ columns: table => new
+ {
+ Key = table.Column(type: "NVARCHAR2(128)", maxLength: 128, nullable: false),
+ Instance = table.Column(type: "NVARCHAR2(256)", maxLength: 256, nullable: true),
+ LastLockTime = table.Column(type: "TIMESTAMP", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CAPLock", x => x.Key);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CAPPublishedMessage",
+ columns: table => new
+ {
+ Id = table.Column(type: "BIGINT", nullable: false),
+ Version = table.Column(type: "NVARCHAR2(20)", maxLength: 20, nullable: true),
+ Name = table.Column(type: "NVARCHAR2(200)", maxLength: 200, nullable: false),
+ Content = table.Column(type: "TEXT", nullable: true),
+ Retries = table.Column(type: "INT", nullable: true),
+ Added = table.Column(type: "TIMESTAMP", nullable: false),
+ ExpiresAt = table.Column(type: "TIMESTAMP", nullable: true),
+ StatusName = table.Column(type: "NVARCHAR2(40)", maxLength: 40, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CAPPublishedMessage", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CAPReceivedMessage",
+ columns: table => new
+ {
+ Id = table.Column(type: "BIGINT", nullable: false),
+ Version = table.Column(type: "NVARCHAR2(20)", maxLength: 20, nullable: true),
+ Name = table.Column(type: "NVARCHAR2(400)", maxLength: 400, nullable: false),
+ Group = table.Column(type: "NVARCHAR2(200)", maxLength: 200, nullable: true),
+ Content = table.Column(type: "TEXT", nullable: true),
+ Retries = table.Column(type: "INT", nullable: true),
+ Added = table.Column(type: "TIMESTAMP", nullable: false),
+ ExpiresAt = table.Column(type: "TIMESTAMP", nullable: true),
+ StatusName = table.Column(type: "NVARCHAR2(50)", maxLength: 50, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CAPReceivedMessage", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "dept",
+ columns: table => new
+ {
+ Id = table.Column(type: "BIGINT", nullable: false),
+ Name = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false),
+ Remark = table.Column(type: "NVARCHAR2(500)", maxLength: 500, nullable: false),
+ ParentId = table.Column(type: "BIGINT", nullable: false),
+ Status = table.Column(type: "INT", nullable: false),
+ CreatedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ IsDeleted = table.Column(type: "BIT", nullable: false),
+ DeletedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ UpdateTime = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_dept", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "role",
+ columns: table => new
+ {
+ Id = table.Column(type: "CHAR(36)", nullable: false),
+ Name = table.Column(type: "NVARCHAR2(50)", maxLength: 50, nullable: false),
+ Description = table.Column(type: "NVARCHAR2(200)", maxLength: 200, nullable: false),
+ CreatedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ IsActive = table.Column(type: "BIT", nullable: false),
+ IsDeleted = table.Column(type: "BIT", nullable: false),
+ DeletedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_role", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "user",
+ columns: table => new
+ {
+ Id = table.Column(type: "BIGINT", nullable: false),
+ Name = table.Column(type: "NVARCHAR2(50)", maxLength: 50, nullable: false),
+ Email = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false),
+ Phone = table.Column(type: "NVARCHAR2(20)", maxLength: 20, nullable: false),
+ RealName = table.Column(type: "NVARCHAR2(50)", maxLength: 50, nullable: false),
+ Status = table.Column(type: "INT", nullable: false),
+ PasswordHash = table.Column(type: "NVARCHAR2(255)", maxLength: 255, nullable: false),
+ IsActive = table.Column(type: "BIT", nullable: false),
+ CreatedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ LastLoginTime = table.Column(type: "DATETIME WITH TIME ZONE", nullable: true),
+ UpdateTime = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ IsDeleted = table.Column(type: "BIT", nullable: false),
+ DeletedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ Gender = table.Column(type: "NVARCHAR2(10)", maxLength: 10, nullable: false),
+ Age = table.Column(type: "INT", nullable: false),
+ BirthDate = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_user", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "role_permission",
+ columns: table => new
+ {
+ RoleId = table.Column(type: "CHAR(36)", nullable: false),
+ PermissionCode = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false),
+ PermissionName = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false),
+ PermissionDescription = table.Column(type: "NVARCHAR2(200)", maxLength: 200, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_role_permission", x => new { x.RoleId, x.PermissionCode });
+ table.ForeignKey(
+ name: "FK_role_permission_role_RoleId",
+ column: x => x.RoleId,
+ principalTable: "role",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "user_dept",
+ columns: table => new
+ {
+ UserId = table.Column(type: "BIGINT", nullable: false),
+ DeptId = table.Column(type: "BIGINT", nullable: false),
+ DeptName = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false),
+ AssignedAt = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_user_dept", x => x.UserId);
+ table.ForeignKey(
+ name: "FK_user_dept_user_UserId",
+ column: x => x.UserId,
+ principalTable: "user",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "user_refresh_token",
+ columns: table => new
+ {
+ Id = table.Column(type: "BIGINT", nullable: false),
+ UserId = table.Column(type: "BIGINT", nullable: false),
+ Token = table.Column(type: "NVARCHAR2(500)", maxLength: 500, nullable: false),
+ CreatedTime = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ ExpiresTime = table.Column(type: "DATETIME WITH TIME ZONE", nullable: false),
+ IsUsed = table.Column(type: "BIT", nullable: false),
+ IsRevoked = table.Column(type: "BIT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_user_refresh_token", x => x.Id);
+ table.ForeignKey(
+ name: "FK_user_refresh_token_user_UserId",
+ column: x => x.UserId,
+ principalTable: "user",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "user_role",
+ columns: table => new
+ {
+ UserId = table.Column(type: "BIGINT", nullable: false),
+ RoleId = table.Column(type: "CHAR(36)", nullable: false),
+ RoleName = table.Column(type: "NVARCHAR2(50)", maxLength: 50, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_user_role", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_user_role_user_UserId",
+ column: x => x.UserId,
+ principalTable: "user",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ExpiresAt_StatusName",
+ table: "CAPPublishedMessage",
+ columns: new[] { "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Version_ExpiresAt_StatusName",
+ table: "CAPPublishedMessage",
+ columns: new[] { "Version", "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ExpiresAt_StatusName1",
+ table: "CAPReceivedMessage",
+ columns: new[] { "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Version_ExpiresAt_StatusName1",
+ table: "CAPReceivedMessage",
+ columns: new[] { "Version", "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_dept_IsDeleted",
+ table: "dept",
+ column: "IsDeleted");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_dept_ParentId",
+ table: "dept",
+ column: "ParentId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_dept_Status",
+ table: "dept",
+ column: "Status");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_role_Name",
+ table: "role",
+ column: "Name",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_Email",
+ table: "user",
+ column: "Email");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_Name",
+ table: "user",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_dept_DeptId",
+ table: "user_dept",
+ column: "DeptId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_dept_UserId",
+ table: "user_dept",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_refresh_token_UserId",
+ table: "user_refresh_token",
+ column: "UserId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "CAPLock");
+
+ migrationBuilder.DropTable(
+ name: "CAPPublishedMessage");
+
+ migrationBuilder.DropTable(
+ name: "CAPReceivedMessage");
+
+ migrationBuilder.DropTable(
+ name: "dept");
+
+ migrationBuilder.DropTable(
+ name: "role_permission");
+
+ migrationBuilder.DropTable(
+ name: "user_dept");
+
+ migrationBuilder.DropTable(
+ name: "user_refresh_token");
+
+ migrationBuilder.DropTable(
+ name: "user_role");
+
+ migrationBuilder.DropTable(
+ name: "role");
+
+ migrationBuilder.DropTable(
+ name: "user");
+ }
+ }
+}
diff --git a/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/ApplicationDbContextModelSnapshot.cs b/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/ApplicationDbContextModelSnapshot.cs
new file mode 100644
index 00000000..b9696fbd
--- /dev/null
+++ b/template/src/ABC.Template.Infrastructure/MigrationsAll/DMDB-net10.0-admin/ApplicationDbContextModelSnapshot.cs
@@ -0,0 +1,430 @@
+//
+using System;
+using ABC.Template.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace ABC.Template.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Dm:ValueGenerationStrategy", DmValueGenerationStrategy.IdentityColumn)
+ .HasAnnotation("ProductVersion", "9.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.DeptAggregate.Dept", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeletedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("IsDeleted")
+ .HasColumnType("BIT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.Property("ParentId")
+ .HasColumnType("BIGINT");
+
+ b.Property("Remark")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("NVARCHAR2(500)");
+
+ b.Property("Status")
+ .HasColumnType("INT");
+
+ b.Property("UpdateTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsDeleted");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Status");
+
+ b.ToTable("dept", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.RoleAggregate.Role", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("CHAR(36)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeletedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("NVARCHAR2(200)");
+
+ b.Property("IsActive")
+ .HasColumnType("BIT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("BIT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("role", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.RoleAggregate.RolePermission", b =>
+ {
+ b.Property("RoleId")
+ .HasColumnType("CHAR(36)");
+
+ b.Property("PermissionCode")
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.Property("PermissionDescription")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("NVARCHAR2(200)");
+
+ b.Property("PermissionName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.HasKey("RoleId", "PermissionCode");
+
+ b.ToTable("role_permission", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.User", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("Age")
+ .HasColumnType("INT");
+
+ b.Property("BirthDate")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("CreatedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeletedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.Property("Gender")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("NVARCHAR2(10)");
+
+ b.Property("IsActive")
+ .HasColumnType("BIT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("BIT");
+
+ b.Property("LastLoginTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("NVARCHAR2(255)");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("NVARCHAR2(20)");
+
+ b.Property("RealName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.Property("Status")
+ .HasColumnType("INT");
+
+ b.Property("UpdateTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Email");
+
+ b.HasIndex("Name");
+
+ b.ToTable("user", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserDept", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("BIGINT");
+
+ b.Property("AssignedAt")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("DeptId")
+ .HasColumnType("BIGINT");
+
+ b.Property("DeptName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("NVARCHAR2(100)");
+
+ b.HasKey("UserId");
+
+ b.HasIndex("DeptId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_dept", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserRefreshToken", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("CreatedTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("ExpiresTime")
+ .HasColumnType("DATETIME WITH TIME ZONE");
+
+ b.Property("IsRevoked")
+ .HasColumnType("BIT");
+
+ b.Property("IsUsed")
+ .HasColumnType("BIT");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("NVARCHAR2(500)");
+
+ b.Property("UserId")
+ .HasColumnType("BIGINT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_refresh_token", (string)null);
+ });
+
+ modelBuilder.Entity("ABC.Template.Domain.AggregatesModel.UserAggregate.UserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("BIGINT");
+
+ b.Property("RoleId")
+ .HasColumnType("CHAR(36)");
+
+ b.Property("RoleName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("NVARCHAR2(50)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.ToTable("user_role", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
+ {
+ b.Property("Key")
+ .HasMaxLength(128)
+ .HasColumnType("NVARCHAR2(128)");
+
+ b.Property("Instance")
+ .HasMaxLength(256)
+ .HasColumnType("NVARCHAR2(256)");
+
+ b.Property("LastLockTime")
+ .HasColumnType("TIMESTAMP");
+
+ b.HasKey("Key");
+
+ b.ToTable("CAPLock", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("BIGINT");
+
+ b.Property("Added")
+ .HasColumnType("TIMESTAMP");
+
+ b.Property