Files
accounting-system/DEPLOYMENT_GUIDE.md
Invoice Master 05ea67144f feat: initial project setup
- 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
2026-02-04 20:14:34 +01:00

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 初始版本