From 2b8b4213e20556d64b14f6bcf32460451decfc07 Mon Sep 17 00:00:00 2001 From: likingcode Date: Sun, 8 Mar 2026 14:40:30 +0000 Subject: [PATCH] feat(v1): add learn/advanced profiles and interactive learning task card --- .../scaffold/config/LearningProfileInfo.java | 26 +++++++++++ src/main/resources/application-advanced.yml | 46 +++++++++++++++++++ src/main/resources/application-learn.yml | 20 ++++++++ src/main/resources/application.properties | 4 +- src/main/resources/static/index.html | 38 ++++++++++++++- 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/scaffold/config/LearningProfileInfo.java create mode 100644 src/main/resources/application-advanced.yml create mode 100644 src/main/resources/application-learn.yml diff --git a/src/main/java/com/example/scaffold/config/LearningProfileInfo.java b/src/main/java/com/example/scaffold/config/LearningProfileInfo.java new file mode 100644 index 0000000..4dad1f3 --- /dev/null +++ b/src/main/java/com/example/scaffold/config/LearningProfileInfo.java @@ -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 profileInfo() { + return Map.of( + "profile", activeProfile, + "enabledModules", Arrays.asList(enabledModules) + ); + } +} diff --git a/src/main/resources/application-advanced.yml b/src/main/resources/application-advanced.yml new file mode 100644 index 0000000..15f0542 --- /dev/null +++ b/src/main/resources/application-advanced.yml @@ -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 \ No newline at end of file diff --git a/src/main/resources/application-learn.yml b/src/main/resources/application-learn.yml new file mode 100644 index 0000000..b9309f7 --- /dev/null +++ b/src/main/resources/application-learn.yml @@ -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 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 21224e8..ce2658f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,9 @@ spring.application.name=springboot-scaffold server.port=8082 -# H2 Database +spring.profiles.active=${APP_PROFILE:learn} + +# H2 Database (default for learning) spring.h2.console.enabled=true spring.datasource.url=jdbc:h2:file:~/h2/springboot_scaffold spring.datasource.driverClassName=org.h2.Driver diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index d137181..7492a70 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -41,6 +41,9 @@ .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 .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; } @@ -69,6 +72,21 @@
-
订单数量
+
+
-
+
当前 Profile
+
+ + +
+

🧪 实验任务卡(事务模块)

+
    +
  • 目标:理解事务回滚与 REQUIRES_NEW 差异
  • +
  • 步骤1:到 transaction.html 创建普通订单(rollback=false)
  • +
  • 步骤2:再创建模拟失败订单(rollback=true)
  • +
  • 预期:主事务回滚,但独立事务可保留日志/部分数据(取决于实现)
  • +
  • 观察点:查看控制台事务日志(TransactionInterceptor TRACE)
  • +
@@ -142,6 +161,19 @@ 开始学习 → +
+

🚀 高级功能

+

Redis 缓存、分布式锁、多数据库、认证方案对比,从小白到高手的进阶之路。

+
    +
  • Redis 数据类型操作
  • +
  • 缓存穿透/击穿/雪崩
  • +
  • 分布式锁实现
  • +
  • JWT vs Sa-Token
  • +
  • 多数据库切换
  • +
+ 进阶学习 → +
+

🔌 API 测试面板

在线测试所有 API 接口,查看请求响应,理解 RESTful API 工作原理。

@@ -180,17 +212,19 @@ // 加载状态数据 async function loadStatus() { 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/users/count').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('userCount').textContent = users.count || 0; document.getElementById('productCount').textContent = products.length || 0; document.getElementById('orderCount').textContent = orders.length || 0; + document.getElementById('activeProfile').textContent = profile.profile || '-'; } catch (e) { console.error('加载状态失败:', e); }