diff --git a/admin/.dockerignore b/admin/.dockerignore new file mode 100644 index 0000000..8617652 --- /dev/null +++ b/admin/.dockerignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.vscode/ diff --git a/admin/Dockerfile b/admin/Dockerfile new file mode 100644 index 0000000..5f61423 --- /dev/null +++ b/admin/Dockerfile @@ -0,0 +1,16 @@ +# 构建阶段 +FROM node:20-alpine AS build +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# 运行阶段 - 使用 nginx 托管静态文件 +FROM nginx:alpine AS runtime +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/admin/nginx.conf b/admin/nginx.conf new file mode 100644 index 0000000..0edab48 --- /dev/null +++ b/admin/nginx.conf @@ -0,0 +1,21 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Vue Router 历史模式支持 + location / { + try_files $uri $uri/ /index.html; + } + + # API 代理到后台服务 + location /api/ { + proxy_pass http://api:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Authorization $http_authorization; + proxy_pass_header Authorization; + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b05df81 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,70 @@ +version: "3.8" + +services: + # MySQL 数据库 + mysql: + image: mysql:8.0 + container_name: huangyan-mysql + environment: + MYSQL_ROOT_PASSWORD: huangyan123 + MYSQL_DATABASE: huangyan_parking + MYSQL_CHARSET: utf8mb4 + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + + # Redis 缓存 + redis: + image: redis:7-alpine + container_name: huangyan-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # 后台 API 服务 + api: + build: + context: ./server + dockerfile: Dockerfile + container_name: huangyan-api + ports: + - "5000:5000" + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ConnectionStrings__MySQL=Server=mysql;Port=3306;Database=huangyan_parking;User=root;Password=huangyan123; + - ConnectionStrings__Redis=redis:6379 + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + restart: unless-stopped + + # 管理后台前端 + admin: + build: + context: ./admin + dockerfile: Dockerfile + container_name: huangyan-admin + ports: + - "3001:80" + depends_on: + - api + restart: unless-stopped + +volumes: + mysql_data: + redis_data: diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..85afe14 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,5 @@ +**/bin/ +**/obj/ +**/tests/ +**/.vs/ +*.user diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..06a6488 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,24 @@ +# 构建阶段 +FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build +WORKDIR /src + +# 复制项目文件并还原依赖 +COPY HuangyanParking.slnx . +COPY src/HuangyanParking.Domain/HuangyanParking.Domain.csproj src/HuangyanParking.Domain/ +COPY src/HuangyanParking.Infrastructure/HuangyanParking.Infrastructure.csproj src/HuangyanParking.Infrastructure/ +COPY src/HuangyanParking.Api/HuangyanParking.Api.csproj src/HuangyanParking.Api/ +RUN dotnet restore src/HuangyanParking.Api/HuangyanParking.Api.csproj + +# 复制源码并发布 +COPY src/ src/ +RUN dotnet publish src/HuangyanParking.Api/HuangyanParking.Api.csproj -c Release -o /app/publish --no-restore + +# 运行阶段 +FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview AS runtime +WORKDIR /app +COPY --from=build /app/publish . + +ENV ASPNETCORE_URLS=http://+:5000 +EXPOSE 5000 + +ENTRYPOINT ["dotnet", "HuangyanParking.Api.dll"] diff --git a/server/src/HuangyanParking.Api/Controllers/AdminController.cs b/server/src/HuangyanParking.Api/Controllers/AdminController.cs index c426fca..a765fbb 100644 --- a/server/src/HuangyanParking.Api/Controllers/AdminController.cs +++ b/server/src/HuangyanParking.Api/Controllers/AdminController.cs @@ -86,7 +86,10 @@ public class AdminController : ControllerBase { var popup = await _db.PopupContents.FirstOrDefaultAsync(p => p.Type == type); if (popup is null) - return NotFound(ApiResponse.Fail("弹窗内容不存在")); + { + // 不存在时返回空内容,方便前端首次加载 + popup = new PopupContent { Type = type, Content = "" }; + } return Ok(ApiResponse.Ok(popup)); } @@ -345,7 +348,9 @@ public class AdminController : ControllerBase { var agreement = await _db.Agreements.FirstOrDefaultAsync(a => a.Type == type); if (agreement is null) - return NotFound(ApiResponse.Fail("协议内容不存在")); + { + agreement = new Agreement { Type = type, Content = "" }; + } return Ok(ApiResponse.Ok(agreement)); } @@ -380,7 +385,9 @@ public class AdminController : ControllerBase { var rule = await _db.PointsRules.FirstOrDefaultAsync(r => r.IsActive); if (rule is null) - return NotFound(ApiResponse.Fail("积分规则不存在")); + { + rule = new PointsRule { ChargeAmount = 0, PointsValue = 0, IsActive = true }; + } return Ok(ApiResponse.Ok(rule)); } diff --git a/server/src/HuangyanParking.Api/Program.cs b/server/src/HuangyanParking.Api/Program.cs index 143b4dd..3230559 100644 --- a/server/src/HuangyanParking.Api/Program.cs +++ b/server/src/HuangyanParking.Api/Program.cs @@ -69,6 +69,26 @@ builder.Services.AddControllers(); var app = builder.Build(); +// 自动迁移数据库(开发/首次部署时创建表结构) +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + + // 初始化默认管理员账号(如果不存在) + if (!db.Admins.Any()) + { + var adminAuth = scope.ServiceProvider.GetRequiredService(); + db.Admins.Add(new HuangyanParking.Domain.Entities.Admin + { + Username = "admin", + PasswordHash = adminAuth.HashPassword("admin123"), + CreatedAt = DateTime.UtcNow + }); + db.SaveChanges(); + } +} + // 中间件管道 app.UseSerilogRequestLogging(); app.UseAuthentication();