feat(v1): add learn/advanced profiles and interactive learning task card
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
package com.example.scaffold.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class LearningProfileInfo {
|
||||||
|
|
||||||
|
@Value("${spring.profiles.active:learn}")
|
||||||
|
private String activeProfile;
|
||||||
|
|
||||||
|
@Value("${app.enabled-modules:ioc,aop,mybatis,transaction,users}")
|
||||||
|
private String[] enabledModules;
|
||||||
|
|
||||||
|
@GetMapping("/api/profile")
|
||||||
|
public Map<String, Object> profileInfo() {
|
||||||
|
return Map.of(
|
||||||
|
"profile", activeProfile,
|
||||||
|
"enabledModules", Arrays.asList(enabledModules)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/resources/application-advanced.yml
Normal file
46
src/main/resources/application-advanced.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 高级配置 - 可插拔组件
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: advanced
|
||||||
|
|
||||||
|
# 数据库选择: h2 / mysql / postgresql
|
||||||
|
datasource:
|
||||||
|
driver-class-name: ${DB_DRIVER:org.h2.Driver}
|
||||||
|
url: ${DB_URL:jdbc:h2:file:~/h2/springboot_scaffold}
|
||||||
|
username: ${DB_USER:sa}
|
||||||
|
password: ${DB_PASS:}
|
||||||
|
|
||||||
|
# Redis 缓存
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:localhost}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
password: ${REDIS_PASS:}
|
||||||
|
database: ${REDIS_DB:0}
|
||||||
|
timeout: 3000ms
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 8
|
||||||
|
max-idle: 8
|
||||||
|
min-idle: 0
|
||||||
|
|
||||||
|
# 鉴权方案选择: none / jwt / satoken
|
||||||
|
auth:
|
||||||
|
type: ${AUTH_TYPE:none} # none | jwt | satoken
|
||||||
|
jwt:
|
||||||
|
secret: ${JWT_SECRET:your-secret-key}
|
||||||
|
expiration: ${JWT_EXPIRATION:86400000} # 24小时
|
||||||
|
satoken:
|
||||||
|
timeout: ${SA_TIMEOUT:86400} # 24小时
|
||||||
|
activity-timeout: ${SA_ACTIVITY_TIMEOUT:1800} # 30分钟
|
||||||
|
|
||||||
|
# 缓存配置
|
||||||
|
cache:
|
||||||
|
type: ${CACHE_TYPE:caffeine} # caffeine | redis
|
||||||
|
redis:
|
||||||
|
time-to-live: 600000 # 10分钟
|
||||||
|
|
||||||
|
# MyBatis 多数据库适配
|
||||||
|
mybatis:
|
||||||
|
configuration:
|
||||||
|
database-id: ${DB_TYPE:h2} # h2 | mysql | postgresql
|
||||||
20
src/main/resources/application-learn.yml
Normal file
20
src/main/resources/application-learn.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Learn profile: keep dependencies and runtime simple for first-round learning
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: learn
|
||||||
|
|
||||||
|
app:
|
||||||
|
profile: learn
|
||||||
|
enabled-modules:
|
||||||
|
- ioc
|
||||||
|
- aop
|
||||||
|
- mybatis
|
||||||
|
- transaction
|
||||||
|
- users
|
||||||
|
|
||||||
|
auth:
|
||||||
|
type: none
|
||||||
|
|
||||||
|
cache:
|
||||||
|
type: caffeine
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
spring.application.name=springboot-scaffold
|
spring.application.name=springboot-scaffold
|
||||||
server.port=8082
|
server.port=8082
|
||||||
|
|
||||||
# H2 Database
|
spring.profiles.active=${APP_PROFILE:learn}
|
||||||
|
|
||||||
|
# H2 Database (default for learning)
|
||||||
spring.h2.console.enabled=true
|
spring.h2.console.enabled=true
|
||||||
spring.datasource.url=jdbc:h2:file:~/h2/springboot_scaffold
|
spring.datasource.url=jdbc:h2:file:~/h2/springboot_scaffold
|
||||||
spring.datasource.driverClassName=org.h2.Driver
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
|
|||||||
@@ -41,6 +41,9 @@
|
|||||||
.status-item { background: white; padding: 20px 30px; border-radius: 10px; text-align: center; }
|
.status-item { background: white; padding: 20px 30px; border-radius: 10px; text-align: center; }
|
||||||
.status-item .value { font-size: 2em; font-weight: bold; color: #667eea; }
|
.status-item .value { font-size: 2em; font-weight: bold; color: #667eea; }
|
||||||
.status-item .label { color: #666; margin-top: 5px; }
|
.status-item .label { color: #666; margin-top: 5px; }
|
||||||
|
.lab { background:#fff7e6; border:1px solid #ffe58f; border-radius:10px; padding:16px; margin-bottom:20px; }
|
||||||
|
.lab h4 { margin-bottom:8px; color:#ad6800; }
|
||||||
|
.lab ul { margin-left:18px; color:#444; line-height:1.7; }
|
||||||
|
|
||||||
footer { text-align: center; padding: 30px; color: #666; margin-top: 40px; }
|
footer { text-align: center; padding: 30px; color: #666; margin-top: 40px; }
|
||||||
</style>
|
</style>
|
||||||
@@ -69,6 +72,21 @@
|
|||||||
<div class="value" id="orderCount">-</div>
|
<div class="value" id="orderCount">-</div>
|
||||||
<div class="label">订单数量</div>
|
<div class="label">订单数量</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<div class="value" id="activeProfile">-</div>
|
||||||
|
<div class="label">当前 Profile</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lab">
|
||||||
|
<h4>🧪 实验任务卡(事务模块)</h4>
|
||||||
|
<ul>
|
||||||
|
<li>目标:理解事务回滚与 REQUIRES_NEW 差异</li>
|
||||||
|
<li>步骤1:到 transaction.html 创建普通订单(rollback=false)</li>
|
||||||
|
<li>步骤2:再创建模拟失败订单(rollback=true)</li>
|
||||||
|
<li>预期:主事务回滚,但独立事务可保留日志/部分数据(取决于实现)</li>
|
||||||
|
<li>观察点:查看控制台事务日志(TransactionInterceptor TRACE)</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
@@ -78,6 +96,7 @@
|
|||||||
<a href="mybatis.html">💾 MyBatis</a>
|
<a href="mybatis.html">💾 MyBatis</a>
|
||||||
<a href="transaction.html">🔄 事务管理</a>
|
<a href="transaction.html">🔄 事务管理</a>
|
||||||
<a href="users.html">👥 用户管理</a>
|
<a href="users.html">👥 用户管理</a>
|
||||||
|
<a href="advanced.html">🚀 高级功能</a>
|
||||||
<a href="api.html">🔌 API 测试</a>
|
<a href="api.html">🔌 API 测试</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -142,6 +161,19 @@
|
|||||||
<a href="users.html" class="btn">开始学习 →</a>
|
<a href="users.html" class="btn">开始学习 →</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>🚀 高级功能</h3>
|
||||||
|
<p>Redis 缓存、分布式锁、多数据库、认证方案对比,从小白到高手的进阶之路。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li><span class="icon">✅</span>Redis 数据类型操作</li>
|
||||||
|
<li><span class="icon">✅</span>缓存穿透/击穿/雪崩</li>
|
||||||
|
<li><span class="icon">✅</span>分布式锁实现</li>
|
||||||
|
<li><span class="icon">✅</span>JWT vs Sa-Token</li>
|
||||||
|
<li><span class="icon">✅</span>多数据库切换</li>
|
||||||
|
</ul>
|
||||||
|
<a href="advanced.html" class="btn">进阶学习 →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>🔌 API 测试面板</h3>
|
<h3>🔌 API 测试面板</h3>
|
||||||
<p>在线测试所有 API 接口,查看请求响应,理解 RESTful API 工作原理。</p>
|
<p>在线测试所有 API 接口,查看请求响应,理解 RESTful API 工作原理。</p>
|
||||||
@@ -180,17 +212,19 @@
|
|||||||
// 加载状态数据
|
// 加载状态数据
|
||||||
async function loadStatus() {
|
async function loadStatus() {
|
||||||
try {
|
try {
|
||||||
const [beans, users, products, orders] = await Promise.all([
|
const [beans, users, products, orders, profile] = await Promise.all([
|
||||||
fetch('/api/learning/ioc/beans').then(r => r.json()),
|
fetch('/api/learning/ioc/beans').then(r => r.json()),
|
||||||
fetch('/api/users/count').then(r => r.json()),
|
fetch('/api/users/count').then(r => r.json()),
|
||||||
fetch('/api/products').then(r => r.json()),
|
fetch('/api/products').then(r => r.json()),
|
||||||
fetch('/api/orders').then(r => r.json())
|
fetch('/api/orders').then(r => r.json()),
|
||||||
|
fetch('/api/profile').then(r => r.json())
|
||||||
]);
|
]);
|
||||||
|
|
||||||
document.getElementById('beanCount').textContent = beans.total || '-';
|
document.getElementById('beanCount').textContent = beans.total || '-';
|
||||||
document.getElementById('userCount').textContent = users.count || 0;
|
document.getElementById('userCount').textContent = users.count || 0;
|
||||||
document.getElementById('productCount').textContent = products.length || 0;
|
document.getElementById('productCount').textContent = products.length || 0;
|
||||||
document.getElementById('orderCount').textContent = orders.length || 0;
|
document.getElementById('orderCount').textContent = orders.length || 0;
|
||||||
|
document.getElementById('activeProfile').textContent = profile.profile || '-';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载状态失败:', e);
|
console.error('加载状态失败:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user