feat: update documentation and component demos for backend layered architecture

- Add new LanguageScopeDemo component for backend languages overview
- Refactor and simplify existing demo components (ControllerLayerDemo, DtoFlowDemo, DependencyDirectionDemo)
- Update .gitignore to exclude .claude/skills directory
- Modify backend-related sections in documentation from "后端与全栈" to "后端开发"
- Add new backend layered architecture demo components (CleanArchitectureDemo, DependencyDirectionDemo)
- Improve documentation structure and content for stage-3 core skills
- Fix component initialization timing in CompileVsInterpretDemo and RateLimiterDemo
- Add design style prompt reference in frontend documentation
This commit is contained in:
sanbuphy
2026-03-01 12:28:47 +08:00
parent d8eb93663d
commit dc8b5773f1
22 changed files with 2660 additions and 5288 deletions
@@ -1,192 +1,70 @@
<template>
<div class="controller-layer-demo">
<div class="demo-header">
<h4>🎮 Controller 请求的"接待员"</h4>
<p class="subtitle">
点击流程节点查看 Controller 如何接收和处理请求
</p>
<div class="controller-demo">
<div class="header">
<div class="title">Controller 请求的"接待员"</div>
<div class="subtitle">点击流程节点查看详情</div>
</div>
<div class="flow-container">
<!-- 请求发起 -->
<div class="flow-step">
<div class="step-icon">
🌐
</div>
<div class="step-content">
<div class="step-title">
客户端发起请求
</div>
<div class="step-code">
POST /api/users/register
Content-Type: application/json
{
"username": "张三",
"email": "zhangsan@example.com",
"password": "123456"
}
</div>
<div class="flow">
<div class="step">
<div class="step-label">客户端发起请求</div>
<pre class="step-code">POST /api/users/register
Content-Type: application/json
{ "username": "张三", "email": "zhangsan@example.com", "password": "123456" }</pre>
</div>
<div class="arrow"> 请求到达</div>
<div :class="['step', 'clickable', { active: detail === 'ctrl' }]" @click="toggle('ctrl')">
<div class="step-label accent">Controller 接收并解析请求</div>
<pre class="step-code">@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity&lt;UserDTO&gt; register(
@RequestBody @Valid UserRegisterRequest request) {
UserDTO user = userService.register(request);
return ResponseEntity.ok(user);
}
}</pre>
</div>
<div class="arrow"> 参数校验 + 调用</div>
<div :class="['step', 'clickable', { active: detail === 'valid' }]" @click="toggle('valid')">
<div class="step-label warn">参数校验Controller 的职责之一</div>
<pre class="step-code">public class UserRegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20) private String username;
@Email(message = "邮箱格式不正确") private String email;
@Size(min = 6, message = "密码至少6位") private String password;
}</pre>
<div v-if="detail === 'valid'" class="detail-box">
<strong>为什么校验要放在 Controller</strong>
<ul>
<li>第一道防线尽早拦截非法请求</li>
<li>减轻下游压力Service 层可以假设数据已清洗</li>
<li>关注点分离Service 专注于业务不处理格式验证</li>
</ul>
</div>
</div>
<div class="arrow-connector">
请求到达
</div>
<div class="arrow"> 返回结果</div>
<!-- Controller 接收 -->
<div
class="flow-step controller-step"
:class="{ active: showDetails === 'controller' }"
@click="toggleDetails('controller')"
>
<div class="step-icon">
🎮
</div>
<div class="step-content">
<div class="step-title">
Controller 接收并解析请求
</div>
<div class="step-code">
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity&lt;UserDTO&gt; register(
@RequestBody @Valid UserRegisterRequest request
) {
// 调用 Service 处理业务
UserDTO user = userService.register(request);
return ResponseEntity.ok(user);
}
}
</div>
</div>
</div>
<div class="arrow-connector">
参数校验 + 调用
</div>
<!-- 校验逻辑 -->
<div
class="flow-step validation-step"
:class="{ active: showDetails === 'validation' }"
@click="toggleDetails('validation')"
>
<div class="step-icon">
</div>
<div class="step-content">
<div class="step-title">
参数校验Controller 的职责之一
</div>
<div class="step-code">
public class UserRegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Size(min = 6, message = "密码至少6位")
private String password;
}
</div>
<div
v-if="showDetails === 'validation'"
class="detail-panel"
>
<h5>为什么校验要放在 Controller</h5>
<ul>
<li>🛡 第一道防线尽早拦截非法请求</li>
<li>📦 减轻下游压力Service 层可以假设数据已清洗</li>
<li>🔧 关注点分离Service 专注于业务不处理格式验证</li>
</ul>
</div>
</div>
</div>
<div class="arrow-connector">
返回结果
</div>
<!-- 响应返回 -->
<div class="flow-step">
<div class="step-icon">
📤
</div>
<div class="step-content">
<div class="step-title">
Controller 封装响应返回给客户端
</div>
<div class="step-code">
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 200,
"message": "注册成功",
"data": {
"id": 10001,
"username": "张三",
"email": "zhangsan@example.com",
"createdAt": "2024-01-15T10:30:00Z"
}
}
</div>
</div>
<div class="step">
<div class="step-label">Controller 封装响应返回</div>
<pre class="step-code">HTTP/1.1 200 OK
{ "code": 200, "message": "注册成功",
"data": { "id": 10001, "username": "张三", "email": "zhangsan@example.com" } }</pre>
</div>
</div>
<!-- Controller 职责总结 -->
<div class="controller-summary">
<h5>🎯 Controller 的核心职责</h5>
<div class="duties">
<div class="duties-title">Controller 的核心职责</div>
<div class="duty-grid">
<div class="duty-item">
<div class="duty-icon">
📡
</div>
<div class="duty-title">
接收请求
</div>
<div class="duty-desc">
映射 HTTP 请求到方法
</div>
</div>
<div class="duty-item">
<div class="duty-icon">
</div>
<div class="duty-title">
参数校验
</div>
<div class="duty-desc">
基础格式和必填校验
</div>
</div>
<div class="duty-item">
<div class="duty-icon">
🔄
</div>
<div class="duty-title">
调用 Service
</div>
<div class="duty-desc">
将请求转发给业务层
</div>
</div>
<div class="duty-item">
<div class="duty-icon">
📦
</div>
<div class="duty-title">
封装响应
</div>
<div class="duty-desc">
统一响应格式返回
</div>
<div class="duty" v-for="d in duties" :key="d.name">
<div class="duty-name">{{ d.name }}</div>
<div class="duty-desc">{{ d.desc }}</div>
</div>
</div>
</div>
@@ -196,196 +74,60 @@
<script setup>
import { ref } from 'vue'
const showDetails = ref('')
const detail = ref('')
const toggle = (s) => { detail.value = detail.value === s ? '' : s }
const toggleDetails = (section) => {
showDetails.value = showDetails.value === section ? '' : section
}
const duties = [
{ name: '接收请求', desc: '映射 HTTP 请求到方法' },
{ name: '参数校验', desc: '基础格式和必填校验' },
{ name: '调用 Service', desc: '将请求转发给业务层' },
{ name: '封装响应', desc: '统一响应格式返回' }
]
</script>
<style scoped>
.controller-layer-demo {
padding: 24px;
background: linear-gradient(135deg, #f0f7ff 0%, #e6f0ff 100%);
border-radius: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.controller-demo { padding: 20px; background: var(--vp-c-bg-soft); border-radius: 12px; }
.header { text-align: center; margin-bottom: 20px; }
.title { font-size: 16px; font-weight: 600; color: var(--vp-c-text-1); }
.subtitle { font-size: 13px; color: var(--vp-c-text-3); margin-top: 4px; }
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.flow { display: flex; flex-direction: column; gap: 8px; }
.arrow { text-align: center; color: var(--vp-c-text-3); font-size: 12px; }
.demo-header h4 {
margin: 0 0 8px 0;
color: #1a1a2e;
font-size: 18px;
.step {
padding: 14px; border-radius: 8px;
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
}
.step.clickable { cursor: pointer; transition: all .2s; }
.step.clickable:hover { box-shadow: 0 2px 8px rgba(0,0,0,.06); }
.step.active { border-color: var(--vp-c-brand-1); box-shadow: 0 0 0 2px var(--vp-c-brand-soft); }
.subtitle {
margin: 0;
color: #666;
font-size: 13px;
}
.flow-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.flow-step {
display: flex;
gap: 16px;
padding: 16px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
cursor: pointer;
}
.flow-step:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.flow-step.active {
border: 2px solid #409eff;
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.2);
}
.controller-step {
border-left: 4px solid #67c23a;
}
.validation-step {
border-left: 4px solid #e6a23c;
}
.step-icon {
font-size: 24px;
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-title {
font-weight: 600;
color: #303133;
margin-bottom: 8px;
font-size: 14px;
}
.step-label { font-weight: 600; font-size: 13px; color: var(--vp-c-text-1); margin-bottom: 8px; }
.step-label.accent { color: #10b981; }
.step-label.warn { color: #f59e0b; }
.step-code {
background: #f8f9fa;
padding: 12px;
border-radius: 6px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 11px;
color: #333;
white-space: pre-wrap;
line-height: 1.5;
margin: 0; padding: 10px; border-radius: 6px; overflow-x: auto;
background: var(--vp-c-bg-soft); font-size: 11px; line-height: 1.5;
color: var(--vp-c-text-2); font-family: var(--vp-font-family-mono);
}
.arrow-connector {
text-align: center;
padding: 8px;
font-size: 12px;
color: #909399;
font-weight: 500;
.detail-box {
margin-top: 12px; padding: 12px; border-radius: 6px;
background: var(--vp-c-brand-soft); border-left: 3px solid var(--vp-c-brand-1);
font-size: 12px; color: var(--vp-c-text-1); line-height: 1.6;
}
.detail-box ul { margin: 8px 0 0; padding-left: 18px; }
.detail-box li { margin: 4px 0; }
.detail-panel {
margin-top: 12px;
padding: 16px;
background: #f0f7ff;
border-radius: 6px;
border-left: 4px solid #409eff;
}
.detail-panel h5 {
margin: 0 0 12px 0;
color: #1a1a2e;
font-size: 14px;
}
.detail-panel ul {
margin: 0;
padding-left: 20px;
}
.detail-panel li {
margin: 6px 0;
color: #606266;
font-size: 12px;
line-height: 1.6;
}
.controller-summary {
margin-top: 24px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.controller-summary h5 {
margin: 0 0 16px 0;
color: #1a1a2e;
font-size: 15px;
text-align: center;
}
.duty-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.duty-item {
text-align: center;
padding: 16px 12px;
background: #f8f9fa;
border-radius: 6px;
transition: all 0.3s ease;
}
.duty-item:hover {
background: #e6f7ff;
transform: translateY(-2px);
}
.duty-icon {
font-size: 28px;
margin-bottom: 8px;
}
.duty-title {
font-weight: 600;
color: #303133;
font-size: 13px;
margin-bottom: 4px;
}
.duty-desc {
color: #909399;
font-size: 11px;
}
.duties { margin-top: 20px; padding: 16px; border-radius: 8px; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); }
.duties-title { text-align: center; font-weight: 600; font-size: 14px; color: var(--vp-c-text-1); margin-bottom: 12px; }
.duty-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.duty { text-align: center; padding: 12px 8px; background: var(--vp-c-bg-soft); border-radius: 6px; }
.duty-name { font-weight: 600; font-size: 13px; color: var(--vp-c-text-1); margin-bottom: 4px; }
.duty-desc { font-size: 11px; color: var(--vp-c-text-3); }
@media (max-width: 768px) {
.duty-grid {
grid-template-columns: repeat(2, 1fr);
}
.flow-step {
flex-direction: column;
gap: 8px;
}
.step-content {
width: 100%;
}
.duty-grid { grid-template-columns: repeat(2, 1fr); }
}
</style>