- Add .NET 8 backend with Clean Architecture - Add React + Vite + TypeScript frontend - Implement authentication with JWT - Implement Azure Blob Storage client - Implement OCR integration - Implement supplier matching service - Implement voucher generation - Implement Fortnox provider - Add unit and integration tests - Add Docker Compose configuration
27 KiB
27 KiB
Invoice Master - 部署指南
版本: v3.0
目标平台: Azure
运行时: .NET 8
日期: 2026-02-03
1. 架构概览
1.1 多会计系统架构部署图
┌─────────────────────────────────────────────────────────────────────────────┐
│ Azure Sweden Central │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Azure Container Apps Environment │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────┐ │ │
│ │ │ Frontend App │ │ Backend API │ │ Worker │ │ │
│ │ │ (Static Web) │ │ (FastAPI) │ │ (Background) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ Vercel/Azure │ │ CPU: 1 vCPU │ │ CPU: 0.5 │ │ │
│ │ │ Static Web │ │ Memory: 2 GiB │ │ Memory: 1GiB │ │ │
│ │ │ │ │ Replicas: 1-5 │ │ Replicas: 1-3│ │ │
│ │ └─────────────────┘ └─────────────────┘ └───────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Azure Application Gateway (WAF) │ │ │
│ │ │ SSL Termination │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ │
│ │ PostgreSQL Flexible │ │ Azure Cache for Redis │ │
│ │ Server │ │ │ │
│ │ - SKU: Standard_B1ms │ │ - SKU: Basic C1 │ │
│ │ - Storage: 32 GB │ │ - Memory: 1 GB │ │
│ │ - Backup: 7 days │ │ │ │
│ │ - accounting_ │ │ - Multi-provider cache │ │
│ │ connections table │ │ │ │
│ └─────────────────────────┘ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Azure Blob Storage │ │
│ │ - Tier: Hot │ │
│ │ - Redundancy: LRS │ │
│ │ - Invoice PDFs (multi-tenant) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Azure Key Vault │ │
│ │ - Encryption Keys │ │
│ │ - Fortnox Client Credentials │ │
│ │ - Visma Client Credentials (future) │ │
│ │ - Hogia Client Credentials (future) │ │
│ │ - Provider-specific configs │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
1.2 多会计系统配置
┌─────────────────────────────────────────────────────────────────┐
│ Key Vault Secrets Structure │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Global Secrets: │
│ ├── jwt-secret │
│ ├── encryption-key │
│ ├── db-password │
│ └── ocr-api-key │
│ │
│ Provider-Specific Secrets: │
│ ├── fortnox-client-id │
│ ├── fortnox-client-secret │
│ ├── fortnox-redirect-uri │
│ ├── visma-client-id (future) │
│ ├── visma-client-secret (future) │
│ └── visma-redirect-uri (future) │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 前置要求
2.1 工具安装
# Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Terraform
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# kubectl (可选)
az aks install-cli
2.2 Azure 登录
# 登录 Azure
az login
# 设置订阅
az account set --subscription "Your Subscription Name"
# 创建 Service Principal (用于 Terraform)
az ad sp create-for-rbac --name "invoice-master-terraform" --role Contributor \
--scopes /subscriptions/{subscription-id}
3. 基础设施部署
3.1 Terraform 配置
cd infrastructure/terraform
# 初始化
terraform init
# 创建变量文件
cat > terraform.tfvars <<EOF
# 基础配置
environment = "production"
location = "swedencentral"
resource_group_name = "rg-invoice-master-prod"
# 数据库
db_admin_username = "dbadmin"
db_admin_password = "YourSecurePassword123!"
db_sku = "Standard_B1ms"
db_storage_mb = 32768
# Redis
redis_sku = "Basic"
redis_family = "C"
redis_capacity = 1
# Container Apps
ca_cpu = "1.0"
ca_memory = "2.0Gi"
ca_min_replicas = 1
ca_max_replicas = 5
# 域名
domain_name = "app.invoice-master.app"
EOF
# 计划
terraform plan
# 应用
terraform apply
3.2 Terraform 模块结构
infrastructure/terraform/
├── main.tf # 主配置
├── variables.tf # 变量定义
├── outputs.tf # 输出
├── providers.tf # 提供商配置
├── backend.tf # 远程状态
├── terraform.tfvars # 变量值 (gitignore)
│
└── modules/
├── database/ # PostgreSQL 模块
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
│
├── cache/ # Redis 模块
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
│
├── storage/ # Blob Storage 模块
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
│
├── container_apps/ # Container Apps 模块
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
│
└── keyvault/ # Key Vault 模块
├── main.tf
├── variables.tf
└── outputs.tf
3.3 主要资源清单
| 资源 | 名称 | SKU/配置 |
|---|---|---|
| Resource Group | rg-invoice-master-prod | Sweden Central |
| PostgreSQL | psql-invoice-master-prod | Standard_B1ms |
| Redis | redis-invoice-master-prod | Basic C1 |
| Blob Storage | stinvoicemasterprod | Standard LRS |
| Container Apps Env | cae-invoice-master-prod | Consumption |
| Backend App | ca-invoice-master-api | 1 CPU, 2Gi |
| Worker App | ca-invoice-master-worker | 0.5 CPU, 1Gi |
| Key Vault | kv-invoice-master-prod | Standard |
| Log Analytics | log-invoice-master-prod | Per GB2018 |
| Application Insights | ai-invoice-master-prod | Web |
4. 应用部署
4.1 构建 .NET 应用
4.1.1 本地发布
cd backend/src/InvoiceMaster.API
# 发布 Release 版本
dotnet publish -c Release -o ./publish \
--runtime linux-x64 \
--self-contained false
# 或者自包含(无需运行时)
dotnet publish -c Release -o ./publish \
--runtime linux-x64 \
--self-contained true \
-p:PublishSingleFile=true
4.1.2 Docker 构建
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["src/InvoiceMaster.API/InvoiceMaster.API.csproj", "src/InvoiceMaster.API/"]
COPY ["src/InvoiceMaster.Core/InvoiceMaster.Core.csproj", "src/InvoiceMaster.Core/"]
COPY ["src/InvoiceMaster.Application/InvoiceMaster.Application.csproj", "src/InvoiceMaster.Application/"]
COPY ["src/InvoiceMaster.Infrastructure/InvoiceMaster.Infrastructure.csproj", "src/InvoiceMaster.Infrastructure/"]
COPY ["src/InvoiceMaster.Integrations/InvoiceMaster.Integrations.csproj", "src/InvoiceMaster.Integrations/"]
RUN dotnet restore "src/InvoiceMaster.API/InvoiceMaster.API.csproj"
COPY . .
WORKDIR "/src/src/InvoiceMaster.API"
RUN dotnet build "InvoiceMaster.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "InvoiceMaster.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "InvoiceMaster.API.dll"]
# 构建并推送镜像
docker build -t invoice-master-api:latest -f Dockerfile .
docker tag invoice-master-api:latest {acr-name}.azurecr.io/invoice-master-api:v1.0.0
# 推送到 ACR
az acr login --name {acr-name}
docker push {acr-name}.azurecr.io/invoice-master-api:v1.0.0
4.2 配置环境变量
在 Azure Key Vault 中创建以下 secrets:
全局 Secrets
| Secret Name | 说明 |
|---|---|
db-password |
PostgreSQL 密码 |
jwt-secret |
JWT 签名密钥 |
encryption-key |
AES-256 加密密钥 |
ocr-api-key |
Invoice Master API Key |
Provider-Specific Secrets
| Secret Name | 说明 | 状态 |
|---|---|---|
fortnox-client-id |
Fortnox OAuth Client ID | Required |
fortnox-client-secret |
Fortnox OAuth Client Secret | Required |
fortnox-redirect-uri |
Fortnox OAuth Redirect URI | Required |
visma-client-id |
Visma OAuth Client ID | Future |
visma-client-secret |
Visma OAuth Client Secret | Future |
visma-redirect-uri |
Visma OAuth Redirect URI | Future |
# 示例: 添加 global secrets
az keyvault secret set --vault-name kv-invoice-master-prod \
--name "jwt-secret" \
--value "your-256-bit-secret-key-here"
# 示例: 添加 Fortnox secrets
az keyvault secret set --vault-name kv-invoice-master-prod \
--name "fortnox-client-id" \
--value "your-fortnox-client-id"
az keyvault secret set --vault-name kv-invoice-master-prod \
--name "fortnox-client-secret" \
--value "your-fortnox-client-secret"
4.3 部署到 Container Apps
# 更新后端应用
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--image {acr-name}.azurecr.io/invoice-master-api:v1.0.0 \
--set-env-vars "ENVIRONMENT=production"
4.4 数据库迁移 (EF Core)
# 方法 1: 使用 EF Core CLI (推荐开发环境)
cd backend/src/InvoiceMaster.Infrastructure
# 添加迁移
dotnet ef migrations add InitialCreate \
--startup-project ../InvoiceMaster.API
# 更新数据库
dotnet ef database update \
--startup-project ../InvoiceMaster.API \
--connection "Host=psql-invoice-master-prod.postgres.database.azure.com;Database=invoice_master;Username=dbadmin;Password=$DB_PASSWORD"
# 生成 SQL 脚本 (生产环境推荐)
dotnet ef migrations script \
--startup-project ../InvoiceMaster.API \
--output migration.sql
# 执行 SQL 脚本
psql "Host=psql-invoice-master-prod.postgres.database.azure.com;Database=invoice_master;Username=dbadmin" \
-f migration.sql
4.4.1 生产环境迁移最佳实践
# 使用临时 Container App Job 运行迁移
az containerapp job create \
--name db-migration-job \
--resource-group rg-invoice-master-prod \
--environment cae-invoice-master-prod \
--image {acr-name}.azurecr.io/invoice-master-api:v1.0.0 \
--command "dotnet ef database update" \
--env-vars "ConnectionStrings__DefaultConnection=secretref:db-connection-string"
4.5 添加新的会计系统 Provider
当需要添加新的会计系统时:
# 1. 在 Key Vault 中添加新 Provider 的 credentials
az keyvault secret set \
--vault-name kv-invoice-master-prod \
--name "visma-client-id" \
--value "your-visma-client-id"
# 2. 更新 Container Apps 环境变量
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--set-env-vars "VISMA_CLIENT_ID=secretref:visma-client-id"
# 3. 重启应用
az containerapp revision restart \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod
5. 前端部署
5.1 构建
cd frontend
# 安装依赖
npm install
# 构建生产版本
npm run build
5.2 部署到 Azure Static Web Apps
# 使用 SWA CLI
npm install -g @azure/static-web-apps-cli
# 部署
swa deploy ./dist \
--env production \
--app-name stapp-invoice-master-prod \
--resource-group rg-invoice-master-prod
5.3 或部署到 Vercel
# 安装 Vercel CLI
npm i -g vercel
# 部署
vercel --prod
6. 域名和 SSL
6.1 配置自定义域名
# Container Apps 自定义域名
az containerapp hostname add \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--hostname api.invoice-master.app
# 绑定证书 (Managed Certificate)
az containerapp hostname bind \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--hostname api.invoice-master.app \
--environment cae-invoice-master-prod \
--validation-method CNAME
6.2 DNS 配置
在 DNS 提供商处添加以下记录:
| 类型 | 主机 | 值 |
|---|---|---|
| CNAME | api | ca-invoice-master-api.{region}.azurecontainerapps.io |
| CNAME | app | {static-web-app-url} |
7. 监控配置
7.1 Application Insights
# 获取连接字符串
APP_INSIGHTS_CONN=$(az monitor app-insights component show \
--app ai-invoice-master-prod \
--resource-group rg-invoice-master-prod \
--query connectionString -o tsv)
# 配置到 Container Apps
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--set-env-vars "APPLICATIONINSIGHTS_CONNECTION_STRING=$APP_INSIGHTS_CONN"
7.2 多会计系统监控
// 查看各 Provider API 调用情况
AppRequests
| where TimeGenerated > ago(1h)
| where OperationName contains "accounting"
| summarize count(), avg(DurationMs) by Provider = tostring(CustomDimensions.provider)
| order by count_ desc
// 查看 Provider 错误率
AppExceptions
| where TimeGenerated > ago(1h)
| where ProblemId contains "fortnox" or ProblemId contains "visma"
| summarize count() by Provider = tostring(CustomDimensions.provider), bin(TimeGenerated, 5m)
| render timechart
7.3 告警规则
# 创建 CPU 使用率告警
az monitor metrics alert create \
--name "High CPU Alert" \
--resource-group rg-invoice-master-prod \
--scopes $(az containerapp show --name ca-invoice-master-api --resource-group rg-invoice-master-prod --query id -o tsv) \
--condition "avg cpu percentage > 80" \
--window-size 5m \
--evaluation-frequency 1m \
--action $(az monitor action-group show --name ag-invoice-master --resource-group rg-invoice-master-prod --query id -o tsv)
# 创建 Provider API 错误告警
az monitor metrics alert create \
--name "Provider API Errors" \
--resource-group rg-invoice-master-prod \
--scopes $(az monitor app-insights component show --name ai-invoice-master-prod --resource-group rg-invoice-master-prod --query id -o tsv) \
--condition "count exceptions > 10" \
--window-size 5m \
--evaluation-frequency 1m
8. 备份策略
8.1 数据库备份
# 配置自动备份 (已在 Terraform 中配置)
# 手动备份
az postgres flexible-server backup create \
--name manual-backup-$(date +%Y%m%d) \
--server-name psql-invoice-master-prod \
--resource-group rg-invoice-master-prod
# 备份 accounting_connections 表 (关键)
pg_dump "host=psql-invoice-master-prod.postgres.database.azure.com user=dbadmin dbname=invoice_master sslmode=require" \
--table=accounting_connections \
--table=invoices \
--table=users > critical_tables_backup.sql
8.2 Blob Storage 备份
# 启用版本控制
az storage account blob-service-properties update \
--account-name stinvoicemasterprod \
--enable-versioning true
# 配置生命周期策略
az storage account management-policy create \
--account-name stinvoicemasterprod \
--policy @lifecycle-policy.json
9. 安全加固
9.1 网络安全
# 配置防火墙规则
az postgres flexible-server firewall-rule create \
--name AllowContainerApps \
--server-name psql-invoice-master-prod \
--resource-group rg-invoice-master-prod \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
# 启用私有链接 (可选)
9.2 Key Vault 访问策略
# 授予 Container Apps 访问 Key Vault 的权限
az keyvault set-policy \
--name kv-invoice-master-prod \
--object-id $(az containerapp show --name ca-invoice-master-api --resource-group rg-invoice-master-prod --query identity.principalId -o tsv) \
--secret-permissions get list
# 为新的 Provider 添加 secrets 时,确保权限已配置
9.3 多租户数据隔离
-- 验证数据隔离
SELECT provider, COUNT(*) as connection_count
FROM accounting_connections
GROUP BY provider;
-- 检查用户数据访问权限
SELECT u.email, ac.provider, ac.company_name
FROM users u
JOIN accounting_connections ac ON u.id = ac.user_id
WHERE u.email = 'test@example.com';
10. 回滚策略
10.1 应用回滚
# 回滚到上一个版本
az containerapp revision list \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--revision-suffix {previous-revision}
10.2 数据库回滚
# 从备份恢复
az postgres flexible-server restore \
--name psql-invoice-master-prod-restored \
--resource-group rg-invoice-master-prod \
--source-server psql-invoice-master-prod \
--point-in-time "2026-02-03T10:00:00Z"
# 回滚特定 Provider 的数据 (谨慎操作)
DELETE FROM accounting_connections WHERE provider = 'fortnox';
10.3 Provider 配置回滚
# 如果新 Provider 配置出错,快速禁用
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--set-env-vars "ENABLED_PROVIDERS=fortnox"
11. CI/CD 配置
11.1 GitHub Actions 工作流
# .github/workflows/deploy.yml
name: Deploy to Azure
on:
push:
branches: [main]
jobs:
deploy-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build and Push
run: |
az acr build --registry ${{ secrets.ACR_NAME }} \
--image invoice-master-api:${{ github.sha }} \
./backend
- name: Deploy
run: |
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--image ${{ secrets.ACR_NAME }}.azurecr.io/invoice-master-api:${{ github.sha }}
- name: Run Migrations
run: |
# 使用临时容器运行 EF Core 迁移
az containerapp job create \
--name migration-job \
--resource-group rg-invoice-master-prod \
--image ${{ secrets.ACR_NAME }}.azurecr.io/invoice-master-api:${{ github.sha }} \
--command "dotnet ef database update --no-build"
11.2 多环境部署
# 不同环境使用不同配置
- name: Deploy to Staging
if: github.ref == 'refs/heads/develop'
run: |
az containerapp update \
--name ca-invoice-master-api-staging \
--resource-group rg-invoice-master-staging
- name: Deploy to Production
if: github.ref == 'refs/heads/main'
run: |
az containerapp update \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod
12. 成本估算
12.1 基础资源成本
| 资源 | 每月估算 (SEK) |
|---|---|
| PostgreSQL (B1ms) | ~150 |
| Redis (Basic C1) | ~100 |
| Container Apps | ~200-500 |
| Blob Storage | ~50-200 |
| Key Vault | ~30 |
| Application Insights | ~100 |
| 基础总计 | ~630-1,080 SEK |
12.2 多会计系统额外成本
| 项目 | 每月估算 (SEK) | 说明 |
|---|---|---|
| 额外的 API 调用 | ~0-100 | 取决于 Provider 数量 |
| 额外的 Redis 缓存 | ~0-50 | Provider token 缓存 |
| 额外的日志存储 | ~0-100 | 多 Provider 日志 |
| 额外总计 | ~0-250 SEK |
13. 故障排除
13.1 常见问题
问题: Container App 无法启动
# 查看日志
az containerapp logs show \
--name ca-invoice-master-api \
--resource-group rg-invoice-master-prod \
--follow
问题: Provider 认证失败
# 检查 Key Vault secrets
az keyvault secret list --vault-name kv-invoice-master-prod
# 验证特定 Provider 配置
curl https://api.invoice-master.app/api/v1/accounting/providers \
-H "Authorization: Bearer $TOKEN"
问题: 数据库连接失败
# 测试连接
psql "host=psql-invoice-master-prod.postgres.database.azure.com port=5432 dbname=invoice_master user=dbadmin sslmode=require"
# 检查连接表
psql $DB_URL -c "SELECT provider, COUNT(*) FROM accounting_connections GROUP BY provider;"
13.2 Provider 特定故障排除
Fortnox OAuth 问题:
# 检查 Fortnox 连接状态
curl https://api.invoice-master.app/api/v1/accounting/fortnox/connection \
-H "Authorization: Bearer $TOKEN"
# 重新授权
# 引导用户访问: https://api.invoice-master.app/api/v1/accounting/fortnox/auth/url
13.3 健康检查
# API 健康检查
curl https://api.invoice-master.app/health
# 详细健康检查 (包含 Provider 状态)
curl https://api.invoice-master.app/health/detailed \
-H "Authorization: Bearer $ADMIN_TOKEN"
# 预期响应:
# {
# "status": "healthy",
# "providers": {
# "fortnox": { "status": "connected", "latency_ms": 150 },
# "visma": { "status": "not_configured" }
# }
# }
14. 维护窗口
| 任务 | 频率 | 时间 |
|---|---|---|
| 安全更新 | 每周 | 周日 02:00-04:00 |
| 数据库备份验证 | 每月 | 第一个周日 |
| Provider API 健康检查 | 每周 | 周一 |
| 性能审查 | 每季度 | - |
| 证书续期检查 | 每月 | - |
| Provider SDK 更新检查 | 每月 | - |
15. 扩展检查清单
添加新 Provider 时的部署检查清单
- 在 Key Vault 中添加 Provider credentials
- 更新 Container Apps 环境变量
- 更新 DNS/防火墙规则 (如需要)
- 测试 Provider 连接
- 更新监控告警规则
- 更新文档
- 通知团队
16. 联系信息
| 角色 | 联系方式 |
|---|---|
| 技术负责人 | tech@example.com |
| 运维团队 | ops@example.com |
| 紧急联系 | +46-xxx-xxx-xxxx |
| Provider 支持 | providers@example.com |
文档历史:
| 版本 | 日期 | 作者 | 变更 |
|---|---|---|---|
| 3.0 | 2026-02-03 | Claude Code | 重构为 .NET 8 + EF Core 部署 |
| 2.0 | 2026-02-03 | Claude Code | 更新为多会计系统架构 |
| 1.0 | 2026-02-03 | Claude Code | 初始版本 |