Files
test-repo/docs/.vitepress/theme/components/appendix/backend-layered-architecture/ControllerLayerDemo.vue
T
sanbuphy d35211071a style: update border-radius and padding values across components
- standardize border-radius from 8px to 6px for consistent styling
- adjust padding values from 1rem to 0.75rem for better visual hierarchy
- remove redundant overflow-y properties for cleaner code
2026-02-14 20:23:34 +08:00

341 lines
7.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="controller-layer-demo">
<div class="demo-header">
<h4>🎮 Controller 请求的"接待员"</h4>
<p class="subtitle">点击流程节点查看 Controller 如何接收和处理请求</p>
</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>
</div>
<div class="arrow-connector"> 请求到达</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>
</div>
<!-- Controller 职责总结 -->
<div class="controller-summary">
<h5>🎯 Controller 的核心职责</h5>
<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>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showDetails = ref('')
const toggleDetails = (section) => {
showDetails.value = showDetails.value === section ? '' : section
}
</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;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
color: #1a1a2e;
font-size: 18px;
}
.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-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;
}
.arrow-connector {
text-align: center;
padding: 8px;
font-size: 12px;
color: #909399;
font-weight: 500;
}
.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;
}
@media (max-width: 768px) {
.duty-grid {
grid-template-columns: repeat(2, 1fr);
}
.flow-step {
flex-direction: column;
gap: 8px;
}
.step-content {
width: 100%;
}
}
</style>