Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.git
.codex
.agents
.worktrees

.env
.env.*
!.env.example

docker-data
logs
*.log

**/node_modules
**/dist
**/coverage
**/.vite

**/target/**
!backend/*/target/
!backend/*/target/*.jar

01-login-page.png
test-*.png
*-browser-qa*.png
review_diff.txt
44 changes: 28 additions & 16 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ VITE_API_PROXY_TARGET=http://localhost:8080
JWT_SECRET=local-dev-secret-32-bytes-minimum-value
GATEWAY_INTERNAL_SECRET=local-dev-gateway-secret-32-bytes
TOKEN_TTL_MINUTES=720
JAVA_TOOL_OPTIONS=-Xms64m -Xmx256m -XX:MaxMetaspaceSize=192m -XX:+UseSerialGC

GATEWAY_PORT=8080
AUTH_SERVICE_PORT=8081
Expand All @@ -29,19 +30,19 @@ AI_ADAPTER_SERVICE_PORT=8089
AUDIT_SERVICE_PORT=8091
MONITOR_SERVICE_PORT=8092

GATEWAY_HOST_PORT=8080
AUTH_SERVICE_HOST_PORT=8081
PRODUCT_SERVICE_HOST_PORT=8082
ACTIVITY_SERVICE_HOST_PORT=8083
RISK_SERVICE_HOST_PORT=8084
ORDER_SERVICE_HOST_PORT=8085
PAYMENT_SERVICE_HOST_PORT=8086
LEGACY_SERVICE_HOST_PORT=18080
INVENTORY_SERVICE_HOST_PORT=8087
SECKILL_SERVICE_HOST_PORT=8088
AI_ADAPTER_SERVICE_HOST_PORT=8089
AUDIT_SERVICE_HOST_PORT=8091
MONITOR_SERVICE_HOST_PORT=8092
GATEWAY_HOST_PORT=18080
AUTH_SERVICE_HOST_PORT=18081
PRODUCT_SERVICE_HOST_PORT=18082
ACTIVITY_SERVICE_HOST_PORT=18083
RISK_SERVICE_HOST_PORT=18084
ORDER_SERVICE_HOST_PORT=18085
PAYMENT_SERVICE_HOST_PORT=18086
LEGACY_SERVICE_HOST_PORT=18087
INVENTORY_SERVICE_HOST_PORT=18093
SECKILL_SERVICE_HOST_PORT=18088
AI_ADAPTER_SERVICE_HOST_PORT=18089
AUDIT_SERVICE_HOST_PORT=18091
MONITOR_SERVICE_HOST_PORT=18092

AUTH_SERVICE_URL=http://sg-auth-service:8081
PRODUCT_SERVICE_URL=http://sg-product-service:8082
Expand Down Expand Up @@ -137,14 +138,22 @@ ZIPKIN_PORT=9411
PROMETHEUS_PORT=9090

# Grafana
GRAFANA_IMAGE=grafana/grafana:11.5.2
GRAFANA_USER=0
GRAFANA_PORT=3000
GRAFANA_ADMIN_PASSWORD=admin

# Sentinel
SENTINEL_DASHBOARD_ADDR=192.168.101.130:8858
SENTINEL_DASHBOARD_PORT=8858

# Elasticsearch
ES_HOST=192.168.101.130
ES_PORT=9200

# Kibana
KIBANA_IMAGE=docker.elastic.co/kibana/kibana:8.15.3
KIBANA_ELASTICSEARCH_HOSTS=http://192.168.101.130:9200
KIBANA_PORT=5601

# MinIO
Expand All @@ -155,12 +164,15 @@ MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin

# Seata
SEATA_IP=192.168.101.130
SEATA_SERVER_ADDR=192.168.101.130:8091
SEATA_PORT=8091
SEATA_CONSOLE_PORT=7091
SEATA_STORE_MODE=file

# Mail Service
MAIL_USERNAME=3514938226@qq.com
MAIL_APP_PASSWORD=qxlicpedspikdaif
MAIL_FROM=SpikeGuard <noreply@SplikeGuard.com>
MAIL_USERNAME=your-email@qq.com
MAIL_APP_PASSWORD=change-me-mail-app-password
MAIL_FROM=SpikeGuard <noreply@example.com>
MAIL_HOST=smtp.qq.com
MAIL_PORT=465
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ backend/L*/
backend/*/L*/
.codex/
.agents/
.worktrees/
review_diff.txt
*-browser-qa*.png
01-login-page.png
test-*.png
frontend-mall/node_modules/
frontend-mall/dist/

Binary file removed 01-login-page.png
Binary file not shown.
33 changes: 25 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# SpikeGuard

> **v1.1.0** — 中间件增强版本
> **v1.1.0** — 中间件增强 + 全链路联调稳定版(2026-07-03)

SpikeGuard(高并发智能秒杀风控平台)面向电商大促、票务抢购、限量商品发售、优惠券抢领和会员权益秒杀等场景,提供活动配置、库存预热、秒杀令牌、实时风控、Redis Lua 原子扣减、RabbitMQ 异步下单、AI 风控解释、分布式事务、链路追踪、监控告警、日志聚合和审计追踪。

支持邮箱验证码注册和商城游客预览模式(首页/活动列表无需登录可浏览)。
支持邮箱验证码注册和商城游客预览模式:商城首页与活动列表无需登录可浏览,活动详情、秒杀、订单和个人中心需登录后访问。

## 当前联调状态(2026-07-03)

- 工作分支:`codex/full-quality-optimization`
- Gateway:`http://192.168.101.130:18080`
- 管理后台:`http://127.0.0.1:5183`
- 商城前端:`http://127.0.0.1:5184`
- 演示数据:商品 20 条、秒杀活动 6 场、活动 SKU 20 条
- 测试账号由维护者另行提供;不要把真实账号密码提交到 Git。
- 最新验证覆盖:后端 `mvn test`、管理后台构建、商城构建、API 冒烟、双端浏览器冒烟。

## 技术栈

Expand All @@ -19,17 +29,19 @@ SpikeGuard(高并发智能秒杀风控平台)面向电商大促、票务抢
| --- | --- | --- |
| Nacos | 服务注册发现 + 配置中心 | v2.3.0 |
| Knife4j | API 文档聚合(/doc.html) | 4.x |
| Prometheus + Grafana | 指标监控与告警 | latest |
| Prometheus + Grafana | 指标监控与告警 | Prometheus latest, Grafana 11.5.2 |
| Zipkin | 分布式链路追踪 | 3 |
| Sentinel | 限流熔断 | — |
| Elasticsearch + Kibana | 日志聚合与检索 | 8.12.0 |
| Elasticsearch + Kibana | 日志聚合与检索 | Elasticsearch 8.12.0 / Kibana 8.15.3 |
| MinIO | 对象存储 | latest |
| Seata AT | 分布式事务 | 2.0.0 |

## 安全约定

仓库只提交 `.env.example`。真实 `.env`、数据库密码、Redis 密码、RabbitMQ 密码、虚拟机密码、GitHub Token 和模型 API Key 不得提交到仓库。所有配置文件都通过环境变量读取敏感值。

邮件配置请在本地 `.env` 中填写真实 QQ SMTP 信息;文档和示例文件统一使用 `MAIL_USERNAME=your-email@qq.com` 与 `MAIL_APP_PASSWORD=change-me-mail-app-password` 占位值。

## 快速启动

```powershell
Expand All @@ -39,6 +51,8 @@ docker compose config
docker compose up -d
```

Docker build context is constrained by `.dockerignore`; keep runtime volumes such as `docker-data/`, frontend `node_modules/`, and generated screenshots out of image builds.

后端:

```powershell
Expand All @@ -61,7 +75,7 @@ conda run -n opspilot uvicorn main:app --host 0.0.0.0 --port 8090 --reload
cd L:\ProjectSource\Project\SpikeGuard\frontend
npm install
npm run build
npm run dev
npm run dev -- --host 127.0.0.1 --port 5183
```

前端(商城):
Expand All @@ -70,10 +84,12 @@ npm run dev
cd L:\ProjectSource\Project\SpikeGuard\frontend-mall
npm install
npm run build
npm run dev
npm run dev -- --host 127.0.0.1 --port 5184
```

首次启动后,通过登录页的注册入口创建第一个用户。注册需邮箱验证码(通过 QQ SMTP 发送),系统会将首个注册用户授予管理员角色。商城首页和活动列表支持游客浏览,无需登录。生产环境请使用强密码并妥善保管运行时 `.env`。
本地 Vite 开发服务器默认通过同源 `/api` 代理访问 Gateway,保持 `VITE_API_BASE_URL` 为空即可;需要代理到虚拟机或远端 Gateway 时,设置 `VITE_API_PROXY_TARGET=http://<gateway-host>:18080`。

首次启动后,通过登录页的注册入口创建第一个用户。注册需邮箱验证码(通过 QQ SMTP 发送),系统会将首个注册用户授予管理员角色。商城首页和活动列表支持游客浏览,无需登录;活动详情、秒杀、订单和个人中心需要登录。生产环境请使用强密码并妥善保管运行时 `.env`。

## 系统架构

Expand Down Expand Up @@ -117,7 +133,7 @@ graph TB
| Kibana 日志 | `http://192.168.101.130:5601` |
| MinIO 控制台 | `http://192.168.101.130:9001` |
| Seata 控制台 | `http://192.168.101.130:7091` |
| Knife4j API 文档 | `http://localhost:8080/doc.html` |
| Knife4j API 文档 | `http://192.168.101.130:18080/doc.html`(VM)/ `http://localhost:8080/doc.html`(本地) |

## 项目结构

Expand All @@ -138,4 +154,5 @@ docker compose config
cd backend; L:\Develop\apache-maven-3.9.12\bin\mvn.cmd clean package
cd ..\ai-service; conda run -n opspilot python -m compileall .
cd ..\frontend; npm install; npm run build
cd ..\frontend-mall; npm install; npm run build
```
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ spring:
server-addr: ${NACOS_SERVER_ADDR:192.168.101.130:8848}
namespace: ${NACOS_NAMESPACE:}
file-extension: yaml
import-check:
enabled: false
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_ADDR:192.168.101.130:8858}
Expand Down Expand Up @@ -56,7 +58,8 @@ management:
sampling:
probability: 1.0
zipkin:
base-url: http://${ZIPKIN_HOST:192.168.101.130}:9411
tracing:
endpoint: http://${ZIPKIN_HOST:192.168.101.130}:9411/api/v2/spans

springdoc:
api-docs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ spring:
server-addr: ${NACOS_SERVER_ADDR:192.168.101.130:8848}
namespace: ${NACOS_NAMESPACE:}
file-extension: yaml
import-check:
enabled: false
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_ADDR:192.168.101.130:8858}
Expand Down Expand Up @@ -42,7 +44,8 @@ management:
sampling:
probability: 1.0
zipkin:
base-url: http://${ZIPKIN_HOST:192.168.101.130}:9411
tracing:
endpoint: http://${ZIPKIN_HOST:192.168.101.130}:9411/api/v2/spans

spikeguard:
ai:
Expand Down
5 changes: 4 additions & 1 deletion backend/sg-audit-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ spring:
server-addr: ${NACOS_SERVER_ADDR:192.168.101.130:8848}
namespace: ${NACOS_NAMESPACE:}
file-extension: yaml
import-check:
enabled: false
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_ADDR:192.168.101.130:8858}
Expand Down Expand Up @@ -42,7 +44,8 @@ management:
sampling:
probability: 1.0
zipkin:
base-url: http://${ZIPKIN_HOST:192.168.101.130}:9411
tracing:
endpoint: http://${ZIPKIN_HOST:192.168.101.130}:9411/api/v2/spans

springdoc:
api-docs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,18 @@ public AuthResponse register(RegisterRequest request) {
throw new BusinessException(ErrorCode.CONFLICT, "username already exists");
});

// 如果提供了 email 和 code,则验证
String email = request.email();
if (email != null && !email.isBlank()) {
if (request.code() == null || request.code().isBlank()) {
throw new BusinessException(ErrorCode.BAD_REQUEST, "验证码不能为空");
}
var vcRecord = verificationCodeRepository
.findTopByEmailAndCodeAndUsedFalseOrderByCreatedAtDesc(email, request.code())
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "验证码无效"));
if (vcRecord.expiresAt().isBefore(LocalDateTime.now())) {
throw new BusinessException(ErrorCode.BAD_REQUEST, "验证码已过期");
}
verificationCodeRepository.markUsed(vcRecord.id());
String code = request.code();
if (email == null || email.isBlank() || code == null || code.isBlank()) {
throw new BusinessException(ErrorCode.BAD_REQUEST, "邮箱和验证码不能为空");
}
var vcRecord = verificationCodeRepository
.findTopByEmailAndCodeAndUsedFalseOrderByCreatedAtDesc(email, code)
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "验证码无效"));
if (vcRecord.expiresAt().isBefore(LocalDateTime.now())) {
throw new BusinessException(ErrorCode.BAD_REQUEST, "验证码已过期");
}
verificationCodeRepository.markUsed(vcRecord.id());

Set<String> roles = userRepository.isEmpty() ? Set.of("ADMIN", "USER") : Set.of("USER");
String nickname = request.nickname() == null || request.nickname().isBlank() ? request.username() : request.nickname();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,27 @@ public void sendVerificationCode(String toEmail, String code) {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(fromAddress);
helper.setTo(toEmail);
helper.setSubject("SpikeGuard 邮箱验证码");
helper.setSubject("SpikeGuard verification code");
helper.setText(buildHtmlContent(code), true);
mailSender.send(message);
log.info("Verification code sent to {}", toEmail);
} catch (MessagingException e) {
log.error("Failed to send verification code to {}: {}", toEmail, e.getMessage());
throw new RuntimeException("邮件发送失败", e);
throw new RuntimeException("Failed to send verification email", e);
}
}

private String buildHtmlContent(String code) {
return """
<div style="max-width:480px;margin:0 auto;padding:32px;font-family:sans-serif;">
<h2 style="color:#FF6B35;">SpikeGuard 验证码</h2>
<p>您的验证码为:</p>
<h2 style="color:#FF6B35;">SpikeGuard verification code</h2>
<p>Your verification code is:</p>
<div style="font-size:36px;font-weight:bold;letter-spacing:8px;
text-align:center;padding:20px;background:#FFF5F0;
border-radius:8px;color:#FF4444;margin:16px 0;">
%s
</div>
<p style="color:#999;font-size:13px;">验证码 5 分钟内有效,请勿泄露给他人。</p>
<p style="color:#999;font-size:13px;">This code is valid for 5 minutes. Do not share it with anyone.</p>
</div>
""".formatted(code);
}
Expand Down
5 changes: 4 additions & 1 deletion backend/sg-auth-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ spring:
server-addr: ${NACOS_SERVER_ADDR:192.168.101.130:8848}
namespace: ${NACOS_NAMESPACE:}
file-extension: yaml
import-check:
enabled: false
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_ADDR:192.168.101.130:8858}
Expand Down Expand Up @@ -53,7 +55,8 @@ management:
sampling:
probability: 1.0
zipkin:
base-url: http://${ZIPKIN_HOST:192.168.101.130}:9411
tracing:
endpoint: http://${ZIPKIN_HOST:192.168.101.130}:9411/api/v2/spans

spikeguard:
security:
Expand Down
Loading