feat(interactive): add participatory verification lab for debugging and repair validation

This commit is contained in:
likingcode
2026-03-10 09:35:04 +08:00
parent 76a243b2de
commit 4bfb2fa250
5 changed files with 260 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
package com.example.scaffold.controller;
import com.example.scaffold.mapper.ProductMapper;
import com.example.scaffold.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/verify")
public class VerificationController {
private final JdbcTemplate jdbcTemplate;
private final UserService userService;
private final ProductMapper productMapper;
@Value("${spring.profiles.active:learn}")
private String activeProfile;
@Value("${auth.type:none}")
private String authType;
@Value("${spring.datasource.url:unknown}")
private String datasourceUrl;
public VerificationController(JdbcTemplate jdbcTemplate, UserService userService, ProductMapper productMapper) {
this.jdbcTemplate = jdbcTemplate;
this.userService = userService;
this.productMapper = productMapper;
}
@GetMapping("/overview")
public Map<String, Object> overview() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("timestamp", Instant.now().toString());
result.put("profile", activeProfile);
result.put("authType", authType);
result.put("datasourceUrl", datasourceUrl);
result.put("checks", Map.of(
"database", checkDatabase(),
"users", checkUsers(),
"products", checkProducts(),
"h2Console", checkH2ConsoleHint(),
"auth", checkAuthMode()
));
return result;
}
@GetMapping("/database")
public Map<String, Object> checkDatabase() {
Map<String, Object> r = new LinkedHashMap<>();
try {
Integer one = jdbcTemplate.queryForObject("SELECT 1", Integer.class);
r.put("ok", one != null && one == 1);
r.put("message", "数据库连接正常SELECT 1 返回成功");
} catch (Exception e) {
r.put("ok", false);
r.put("message", "数据库连接失败: " + e.getMessage());
}
r.put("hint", "如果这里失败,优先检查 DataSource、H2 文件锁或 profile 配置");
return r;
}
@GetMapping("/users")
public Map<String, Object> checkUsers() {
Map<String, Object> r = new LinkedHashMap<>();
try {
long count = userService.count();
r.put("ok", true);
r.put("count", count);
r.put("message", "用户服务可用");
} catch (Exception e) {
r.put("ok", false);
r.put("message", "用户服务异常: " + e.getMessage());
}
r.put("hint", "如果用户接口异常,说明 Service / JPA / 数据源链路可能有问题");
return r;
}
@GetMapping("/products")
public Map<String, Object> checkProducts() {
Map<String, Object> r = new LinkedHashMap<>();
try {
int size = productMapper.findAll().size();
r.put("ok", true);
r.put("count", size);
r.put("message", "MyBatis 产品查询可用");
} catch (Exception e) {
r.put("ok", false);
r.put("message", "MyBatis 查询异常: " + e.getMessage());
}
r.put("hint", "如果这里失败但 users 正常,优先排查 Mapper / SQL / MyBatis 配置");
return r;
}
@GetMapping("/h2")
public Map<String, Object> checkH2ConsoleHint() {
Map<String, Object> r = new LinkedHashMap<>();
r.put("ok", true);
r.put("consolePath", "/h2-console");
r.put("expectedJdbc", datasourceUrl);
r.put("message", "H2 控制台入口已暴露;若跳转异常,优先检查 X-Forwarded-Proto / forward-headers 配置");
return r;
}
@GetMapping("/auth")
public Map<String, Object> checkAuthMode() {
Map<String, Object> r = new LinkedHashMap<>();
r.put("ok", true);
r.put("authType", authType);
r.put("profile", activeProfile);
r.put("message", switch (authType) {
case "jwt" -> "当前是 JWT 模式,更适合验证 token / 401 / 403 链路";
case "satoken" -> "当前是 Sa-Token 模式,可测试登录态与会话行为";
default -> "当前是 none 模式,适合先学 Spring 核心功能";
});
return r;
}
}

View File

@@ -73,6 +73,7 @@
<div id="profileBanner" class="profile-banner">正在读取 profile...</div>
<div class="tools">
<button class="btn btn-primary" onclick="copyCurl()">复制当前示例 cURLGET /api/users</button>
<a class="btn btn-primary" href="verify-lab.html">进入修复验证实验室</a>
</div>
<div class="tabs">

View File

@@ -33,6 +33,7 @@
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="advanced.html">🚀 高级功能</a>
<a href="verify-lab.html">🩺 修复验证实验室</a>
<a href="api.html">🔌 API 测试</a>
<a href="auth-lab.html" class="active">🔐 鉴权实验室</a>
</div>

View File

@@ -99,6 +99,7 @@
<a href="users.html">👥 用户管理</a>
<a href="advanced.html">🚀 高级功能</a>
<a href="auth-lab.html">🔐 鉴权实验室</a>
<a href="verify-lab.html">🩺 修复验证实验室</a>
<a href="api.html">🔌 API 测试</a>
</div>
@@ -188,6 +189,18 @@
<a href="auth-lab.html" class="btn">开始实验 →</a>
</div>
<div class="card">
<h3>🩺 修复验证实验室</h3>
<p>自己点检查,不靠口头确认。直接验证 profile、H2、用户服务、MyBatis 和鉴权模式。</p>
<ul class="feature-list">
<li><span class="icon"></span>一键总览检查</li>
<li><span class="icon"></span>数据库链路验证</li>
<li><span class="icon"></span>用户服务验证</li>
<li><span class="icon"></span>MyBatis 查询验证</li>
</ul>
<a href="verify-lab.html" class="btn">开始验证 →</a>
</div>
<div class="card">
<h3>🔌 API 测试面板</h3>
<p>在线测试所有 API 接口,查看请求响应,理解 RESTful API 工作原理。</p>

View File

@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修复验证实验室 - Spring Boot</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:#f5f5f5; }
.container { max-width:1200px; margin:0 auto; padding:20px; }
.header { background:linear-gradient(135deg,#667eea,#764ba2); color:#fff; padding:28px 20px; border-radius:12px; text-align:center; margin-bottom:20px; }
.nav { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-bottom:20px; }
.nav a { padding:10px 18px; background:#fff; border-radius:20px; text-decoration:none; color:#333; }
.nav a.active { background:#667eea; color:#fff; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:16px; border-radius:8px; margin-bottom:20px; }
.grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap:16px; }
.card { background:#fff; border-radius:12px; padding:18px; box-shadow:0 2px 10px rgba(0,0,0,.08); }
.card h3 { margin-bottom:12px; color:#333; }
.result { background:#111827; color:#d1d5db; padding:12px; border-radius:8px; min-height:160px; white-space:pre-wrap; font-family:monospace; margin-top:10px; }
button { padding:10px 14px; background:#667eea; color:#fff; border:none; border-radius:6px; cursor:pointer; margin-right:8px; margin-bottom:8px; }
.status-pass { color:#16a34a; font-weight:700; }
.status-fail { color:#dc2626; font-weight:700; }
.hint { color:#666; line-height:1.7; margin-top:8px; }
.summary { background:#fff; border-radius:12px; padding:18px; margin-bottom:20px; box-shadow:0 2px 10px rgba(0,0,0,.08); }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🩺 修复验证实验室</h1>
<p>不是“我说修好了”,而是你自己点检查,看到哪里坏、为什么坏、修没修好。</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="auth-lab.html">🔐 鉴权实验室</a>
<a href="api.html">🔌 API 测试</a>
<a href="verify-lab.html" class="active">🩺 修复验证实验室</a>
</div>
<div class="lab">
<strong>参与式验证任务卡</strong>
<ul style="padding-left:20px;line-height:1.8;margin-top:8px;">
<li>先点“一键跑总览”,确认当前 profile / auth / datasource 状态</li>
<li>再分别点数据库、用户服务、MyBatis 产品查询,感受“哪个环节坏了就在哪一步暴露”</li>
<li>最后打开 H2 Console验证修复后跳转是不是仍保持 https</li>
<li>如果某一步失败,不要只看失败,要看返回里的 hint —— 那是排查方向</li>
</ul>
</div>
<div class="summary">
<button onclick="runOverview()">一键跑总览</button>
<button onclick="openH2()">打开 H2 Console</button>
<div id="summaryResult" class="result">点击按钮开始验证...</div>
</div>
<div class="grid">
<div class="card">
<h3>数据库链路</h3>
<button onclick="runCheck('database','dbResult')">检查数据库</button>
<div class="hint">验证 DataSource / JDBC / H2 是否可用。</div>
<div id="dbResult" class="result"></div>
</div>
<div class="card">
<h3>用户服务链路</h3>
<button onclick="runCheck('users','userResult')">检查用户服务</button>
<div class="hint">验证 JPA / Service / 用户数据查询是否正常。</div>
<div id="userResult" class="result"></div>
</div>
<div class="card">
<h3>MyBatis 链路</h3>
<button onclick="runCheck('products','productResult')">检查产品查询</button>
<div class="hint">验证 MyBatis Mapper / SQL / 数据库查询是否正常。</div>
<div id="productResult" class="result"></div>
</div>
<div class="card">
<h3>鉴权模式</h3>
<button onclick="runCheck('auth','authResult')">检查鉴权模式</button>
<div class="hint">对照当前 profile理解 why learn=none / advanced=jwt。</div>
<div id="authResult" class="result"></div>
</div>
</div>
</div>
<script>
async function runOverview() {
const el = document.getElementById('summaryResult');
el.textContent = '检查中...';
try {
const res = await fetch('/api/verify/overview');
const data = await res.json();
el.textContent = JSON.stringify(data, null, 2);
} catch (e) {
el.textContent = '总览检查失败: ' + e.message;
}
}
async function runCheck(name, resultId) {
const el = document.getElementById(resultId);
el.textContent = '检查中...';
try {
const res = await fetch('/api/verify/' + name);
const data = await res.json();
const badge = data.ok ? '✅ PASS' : '❌ FAIL';
el.textContent = badge + '\n\n' + JSON.stringify(data, null, 2);
} catch (e) {
el.textContent = '检查失败: ' + e.message;
}
}
function openH2() {
window.open('/h2-console', '_blank');
}
runOverview();
</script>
</body>
</html>