feat(platform): 增强 Infrastructure 层可扩展性,添加 NuGet 发布脚本与 CI/CD
Some checks failed
Build and Push Docker / build (push) Failing after 16s
Publish NuGet Packages / build (push) Failing after 1h7m51s

## 主要变更

### Infrastructure 层重构
- `PlatformDbContext`: 构造函数改为接受泛型 `DbContextOptions`,支持派生上下文
- `TenantStore<TContext>`: 泛型化实现,支持不同的数据库上下文
- `Extensions`: 新增 `AddPlatformCore<TContext>` 扩展方法,简化服务注册

### 依赖调整
- 移除 Npgsql.EntityFrameworkCore.PostgreSQL 直接依赖,由使用方自行决定数据库提供程序

### CI/CD 集成
- 新增 `.gitea/workflows/publish-nuget.yml` Gitea Actions 工作流
- 新增 `push-platform-nuget.sh` 脚本,支持:
  - 从 git tag 自动获取版本号
  - HTTP/HTTPS 双模式支持
  - 独立 NuGet 配置文件
  - CI/CD 友好的环境变量配置

### 其他
- `NuGet.Config`: 新增 NuGet 配置文件
- `Fengling.Platform.Domain`: 添加 Items 文件夹占位
This commit is contained in:
movingsam 2026-02-27 13:58:09 +08:00
parent a04dc199c9
commit 7877f89d35
9 changed files with 385 additions and 8 deletions

View File

@ -0,0 +1,26 @@
name: Publish NuGet Package
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0'
- name: Publish NuGet packages
run: |
./push-platform-nuget.sh all
env:
GITEA_HOST: gitea.shtao1.cn
GITEA_ORG: fengling
GITEA_API_TOKEN: ${{ secrets.GITEA_API_TOKEN }}

View File

@ -14,4 +14,8 @@
<PackageReference Include="NetCorePal.Extensions.Primitives" />
</ItemGroup>
<ItemGroup>
<Folder Include="Items\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
namespace Fengling.Platform.Infrastructure;
public static class Extensions
{
public static IServiceCollection AddPlatformCore<TContext>(this IServiceCollection services,
Action<DbContextOptionsBuilder>? optionsAction = null,
Action<IServiceCollection>? serviceAction = null
)
where TContext : PlatformDbContext
{
if (optionsAction != null)
{
var isRegistry = services.Any(x => x.ImplementationType == typeof(TContext));
if (!isRegistry)
{
services.AddDbContext<TContext>(optionsAction);
}
}
services.AddScoped<ITenantStore, TenantStore<TContext>>();
services.AddScoped<ITenantManager, TenantManager>();
serviceAction?.Invoke(services);
return services;
}
}

View File

@ -13,7 +13,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" />
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore" />
<PackageReference Include="MediatR" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" />

View File

@ -8,12 +8,12 @@ using Microsoft.EntityFrameworkCore;
namespace Fengling.Platform.Infrastructure;
public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> options)
public class PlatformDbContext(DbContextOptions options)
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
{
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<AccessLog> AccessLogs => Set<AccessLog>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{

View File

@ -1,8 +1,9 @@
namespace Fengling.Platform.Infrastructure;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Microsoft.AspNetCore.Identity;
namespace Fengling.Platform.Infrastructure;
public interface ITenantManager
{
Task<Tenant?> FindByIdAsync(long? tenantId, CancellationToken cancellationToken = default);

View File

@ -4,12 +4,13 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
public class TenantStore : ITenantStore
public class TenantStore<TContext> : ITenantStore
where TContext : PlatformDbContext
{
private readonly PlatformDbContext _context;
private readonly TContext _context;
private readonly DbSet<Tenant> _tenants;
public TenantStore(PlatformDbContext context)
public TenantStore(TContext context)
{
_context = context;
_tenants = context.Tenants;

8
NuGet.Config Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="gitea" value="https://gitea.shtao1.cn/api/packages/fengling/nuget/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

311
push-platform-nuget.sh Executable file
View File

@ -0,0 +1,311 @@
#!/bin/bash
# =============================================================================
# Fengling.Platform NuGet 包上传脚本
# 用于上传 Fengling.Platform.Domain 和 Fengling.Platform.Infrastructure 到 Gitea NuGet
# 支持 CI/CD 集成,自动从 git tag 获取版本
# =============================================================================
# =========================== 环境变量配置 ===========================
# CI/CD 时通过环境变量传入,本地可修改默认值
## TODO: 请在这里填入你的 Gitea API Token或通过环境变量 GITEA_API_TOKEN 传入
#export GITEA_HOST="${GITEA_HOST:-gitea.shtao1.cn}" # Gitea 域名
#export GITEA_ORG="${GITEA_ORG:-fengling}" # 组织名称
#export GITEA_API_TOKEN="${GITEA_API_TOKEN:-}" # Gitea API Token (必填)
#export GITEA_USE_HTTPS="${GITEA_USE_HTTPS:-true}" # 是否使用 HTTPS (外网用 true)
# CI/CD 时通过环境变量传入,本地可修改默认值
export GITEA_HOST="${GITEA_HOST:-gitea.shtao1.cn}" # Gitea 地址 (内网)
export GITEA_ORG="${GITEA_ORG:-fengling}" # 组织名称
export GITEA_API_TOKEN="${GITEA_API_TOKEN:-}" # Gitea API Token (必填)
export GITEA_USE_HTTPS="${GITEA_USE_HTTPS:-true}" # 是否使用 HTTPS (内网用 false)
# =========================== 内部变量 ===========================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NUGET_SOURCE_NAME="gitea"
# 根据是否使用 HTTPS 构建 URL
if [ "$GITEA_USE_HTTPS" = "true" ]; then
NUGET_SOURCE_URL="https://${GITEA_HOST}/api/packages/${GITEA_ORG}/nuget/index.json"
else
NUGET_SOURCE_URL="http://${GITEA_HOST}/api/packages/${GITEA_ORG}/nuget"
fi
# 输出目录
NUPKG_DIR="${SCRIPT_DIR}/nupkg"
if [ "$GITEA_USE_HTTPS" = "true" ]; then
NUGET_SOURCE_URL="https://${GITEA_HOST}/api/packages/${GITEA_ORG}/nuget/index.json"
else
NUPKG_DIR="${SCRIPT_DIR}/nupkg"
NUGET_SOURCE_URL="http://${GITEA_HOST}/api/packages/${GITEA_ORG}/nuget"
fi
# NuGet 配置文件路径
NUGET_CONFIG_DIR="${SCRIPT_DIR}/.nuget"
NUGET_CONFIG_FILE="${NUGET_CONFIG_DIR}/NuGet.Config"
# =========================== 版本获取 ===========================
get_version_from_git() {
# 优先使用环境变量中的版本
if [ -n "$PACKAGE_VERSION" ]; then
echo "$PACKAGE_VERSION"
return
fi
# 尝试从 git tag 获取版本
local git_dir="${SCRIPT_DIR}/.git"
if [ -d "$git_dir" ]; then
local latest_tag=$(git -C "$SCRIPT_DIR" describe --tags --abbrev=0 2>/dev/null)
if [ -n "$latest_tag" ]; then
# 去掉 v 前缀 (如 v1.0.0 -> 1.0.0)
echo "${latest_tag#v}"
return
fi
fi
# 默认版本
echo "1.0.0"
}
# =========================== 颜色输出 ===========================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
if [ "$DEBUG" = "true" ]; then
echo -e "${BLUE}[DEBUG]${NC} $1"
fi
}
# =========================== 检查配置 ===========================
check_config() {
if [ -z "$GITEA_API_TOKEN" ]; then
log_error "请设置环境变量 GITEA_API_TOKEN"
echo ""
echo "设置方式:"
echo " export GITEA_API_TOKEN=你的GiteaToken"
echo ""
echo "或运行时传入:"
echo " GITEA_API_TOKEN=你的Token $0 all"
echo ""
echo "CI/CD 示例 (GitHub Actions):"
echo " env:"
echo " GITEA_API_TOKEN: \${{ secrets.GITEA_API_TOKEN }}"
echo " GITEA_ORG: fengling"
exit 1
fi
log_info "Gitea: ${GITEA_HOST}, Org: ${GITEA_ORG}, HTTPS: ${GITEA_USE_HTTPS}"
}
# =========================== 创建 NuGet 配置 ===========================
create_nuget_config() {
# 创建 .nuget 目录
mkdir -p "${NUGET_CONFIG_DIR}"
# 创建 NuGet.Config启用 HTTP 支持
cat > "${NUGET_CONFIG_FILE}" << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="allowInsecureConnections" value="true" />
</config>
<packageSources>
<clear />
</packageSources>
</configuration>
EOF
log_info "已创建 NuGet 配置文件: ${NUGET_CONFIG_FILE}"
}
# =========================== 还原并构建包 ===========================
restore_and_build() {
# 获取版本
PACKAGE_VERSION=$(get_version_from_git)
log_info "包版本: ${PACKAGE_VERSION}"
log_info "开始构建 NuGet 包..."
local projects=(
"${SCRIPT_DIR}/Fengling.Platform.Domain/Fengling.Platform.Domain.csproj"
"${SCRIPT_DIR}/Fengling.Platform.Infrastructure/Fengling.Platform.Infrastructure.csproj"
)
for project in "${projects[@]}"; do
if [ ! -f "$project" ]; then
log_error "项目文件不存在: $project"
exit 1
fi
local project_name=$(basename $(dirname $project))
log_info "正在构建: ${project_name}"
# 还原依赖
dotnet restore "$project" --configfile "${NUGET_CONFIG_FILE}"
if [ $? -ne 0 ]; then
log_error "还原失败: $project"
exit 1
fi
# 构建并打包
dotnet build "$project" -c Release --no-restore
if [ $? -ne 0 ]; then
log_error "构建失败: $project"
exit 1
fi
dotnet pack "$project" -c Release --no-build -p:PackageVersion=${PACKAGE_VERSION} -o "${NUPKG_DIR}"
if [ $? -ne 0 ]; then
log_error "打包失败: $project"
exit 1
fi
done
log_info "NuGet 包构建完成!"
}
# =========================== 配置 NuGet 源 ===========================
configure_nuget_source() {
log_info "配置 NuGet 源: ${NUGET_SOURCE_URL}"
# 使用 configfile 参数添加源
dotnet nuget add source \
--name "${NUGET_SOURCE_NAME}" \
--username "movingsam" \
--password "${GITEA_API_TOKEN}" \
--store-password-in-clear-text \
"${NUGET_SOURCE_URL}" \
--configfile "${NUGET_CONFIG_FILE}"
if [ $? -eq 0 ]; then
log_info "NuGet 源配置成功!"
else
log_error "NuGet 源配置失败"
exit 1
fi
}
# =========================== 上传包 ===========================
push_packages() {
log_info "开始上传 NuGet 包..."
if [ ! -d "$NUPKG_DIR" ]; then
log_error "nupkg 目录不存在,请先运行构建"
exit 1
fi
# 上传所有 nupkg 文件
for nupkg in "${NUPKG_DIR}"/*.nupkg; do
[ -f "$nupkg" ] || continue
local filename=$(basename $nupkg)
log_info "上传: $filename"
dotnet nuget push "$nupkg" \
--source "${NUGET_SOURCE_URL}" \
--api-key "${GITEA_API_TOKEN}" \
--configfile "${NUGET_CONFIG_FILE}" \
--skip-duplicate
if [ $? -eq 0 ]; then
log_info "上传成功: $filename"
else
log_warn "上传失败或包已存在: $filename"
fi
done
}
# =========================== 显示帮助 ===========================
show_help() {
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " all 执行全部步骤 (构建 -> 配置源 -> 上传)"
echo " build 仅构建 NuGet 包"
echo " config 仅配置 NuGet 源"
echo " push 仅上传包 (需要先构建)"
echo " clean 清理构建产物"
echo " help 显示帮助"
echo ""
echo "环境变量:"
echo " GITEA_HOST Gitea 地址 (默认: 192.168.100.120:8418)"
echo " GITEA_ORG 组织名称 (默认: fengling)"
echo " GITEA_API_TOKEN Gitea API Token (必填)"
echo " GITEA_USE_HTTPS 是否使用 HTTPS (默认: false, 内网用 false)"
echo " PACKAGE_VERSION 包版本 (自动从 git tag 获取)"
echo ""
echo "示例:"
echo " # 方式1: 设置环境变量"
echo " export GITEA_API_TOKEN=your_token"
echo " $0 all"
echo ""
echo " # 方式2: 运行时传入环境变量"
echo " GITEA_API_TOKEN=your_token $0 all"
echo ""
echo " # 使用 HTTPS (外网)"
echo " GITEA_API_TOKEN=your_token GITEA_USE_HTTPS=true GITEA_HOST=gitea.shtao1.cn $0 all"
}
# =========================== 清理 ===========================
clean() {
log_info "清理构建产物..."
rm -rf "${NUPKG_DIR}"
rm -rf "${NUGET_CONFIG_DIR}"
log_info "清理完成"
}
# =========================== 主程序 ===========================
main() {
local command="${1:-all}"
check_config
create_nuget_config
case "$command" in
all)
restore_and_build
configure_nuget_source
push_packages
log_info "全部完成!"
;;
build)
restore_and_build
;;
config)
configure_nuget_source
;;
push)
push_packages
;;
clean)
clean
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"