feat: Phase 3-5 管理后台前端+统计监控+部署配置
deploy-test / build-and-deploy-test (push) Successful in 2m41s

Phase 3: 管理后台前端 (Vue3+Vite+Element Plus)
- 登录页面/JWT认证/Pinia状态管理
- 首页大盘/站点管理/验证码日志/IP黑名单/套餐管理/系统设置
- 7个页面完整路由+API对接

Phase 4: 统计监控模块
- CaptchaLogAspect AOP自动记录
- RealtimeStatsService Redis实时计数
- HistoryStatsService 历史查询
- AnomalyDetectionService 异常检测

Phase 5: 部署和测试
- docker-compose.yml 5服务编排
- application-prod.yml 外部化配置
- CaptchaApiIntegrationTest 集成测试
- 前端 Dockerfile + nginx.conf
This commit is contained in:
abcv7
2026-08-26 13:44:08 +08:00
parent d1cd371061
commit 1164b51c64
67 changed files with 5510 additions and 829 deletions
@@ -0,0 +1,74 @@
package cloud.tianai.captcha.platform;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class CaptchaApiIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
private String jwtToken;
@BeforeEach
void login() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> request = new HttpEntity<>(
Map.of("username", "admin", "password", "admin"), headers);
var response = restTemplate.postForEntity("/api/auth/login", request, Map.class);
if (response.getBody() != null && response.getBody().get("data") != null) {
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) response.getBody().get("data");
if (data != null && data.get("token") != null) {
jwtToken = (String) data.get("token");
}
}
}
private HttpHeaders authHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if (jwtToken != null) {
headers.setBearerAuth(jwtToken);
}
return headers;
}
@Test
void contextLoads() {
assertNotNull(restTemplate);
}
@Test
void loginReturnsToken() {
assertNotNull(jwtToken, "JWT token should not be null after login");
}
@Test
void statsEndpointReturnsData() {
HttpEntity<Void> entity = new HttpEntity<>(authHeaders());
var response = restTemplate.exchange("/api/admin/stats", HttpMethod.GET, entity, Map.class);
assertEquals(200, response.getStatusCode().value());
assertNotNull(response.getBody());
}
@Test
void siteListEndpointReturnsData() {
HttpEntity<Void> entity = new HttpEntity<>(authHeaders());
var response = restTemplate.exchange("/api/admin/sites?page=0&size=10", HttpMethod.GET, entity, Map.class);
assertEquals(200, response.getStatusCode().value());
assertNotNull(response.getBody());
}
}
@@ -0,0 +1,41 @@
spring:
datasource:
url: jdbc:h2:mem:testdb;MODE=PostgreSQL
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
h2:
console:
enabled: false
data:
redis:
host: localhost
port: 6379
jwt:
secret: test-secret-key-must-be-at-least-256-bits-long-for-hs256
expiration: 86400000
captcha:
prefix: captcha
expire:
default: 120000
init-default-resource: false
local-cache-enabled: false
minio:
endpoint: http://localhost:9000
access-key: minioadmin
secret-key: minioadmin
bucket: captcha-resources
logging:
level:
cloud.tianai.captcha: DEBUG