2026-02-06 03:34:50 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="monolith-demo">
|
|
|
|
|
|
<div class="demo-header">
|
2026-02-14 12:14:07 +08:00
|
|
|
|
<span class="icon">🏢</span>
|
|
|
|
|
|
<span class="title">单体架构演示</span>
|
|
|
|
|
|
<span class="subtitle">观察单体应用如何处理请求</span>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="monolith-diagram">
|
|
|
|
|
|
<div class="monolith-box" :class="{ crashed: hasCrashed }">
|
|
|
|
|
|
<div class="monolith-header">单体应用进程</div>
|
|
|
|
|
|
<div class="modules-container">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="module in modules"
|
|
|
|
|
|
:key="module.name"
|
|
|
|
|
|
class="module-box"
|
|
|
|
|
|
:class="{ active: activeModule === module.name, crashed: crashedModule === module.name }"
|
|
|
|
|
|
@click="triggerModule(module.name)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="module-icon">{{ module.icon }}</div>
|
|
|
|
|
|
<div class="module-name">{{ module.name }}</div>
|
|
|
|
|
|
<div class="module-status" :class="module.status">{{ module.statusText }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="shared-db">
|
|
|
|
|
|
<div class="db-icon">🗄️</div>
|
|
|
|
|
|
<div class="db-label">共享数据库</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="request-flow">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="req in requests"
|
|
|
|
|
|
:key="req.id"
|
|
|
|
|
|
class="flow-request"
|
|
|
|
|
|
:class="req.status"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="req-type">{{ req.type }}</span>
|
|
|
|
|
|
<span class="req-arrow">→</span>
|
|
|
|
|
|
<span class="req-target">{{ req.target }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="controls">
|
|
|
|
|
|
<button class="control-btn" @click="simulateNormalRequest">正常请求</button>
|
|
|
|
|
|
<button class="control-btn danger" @click="simulateCrash">模拟模块故障</button>
|
|
|
|
|
|
<button class="control-btn" @click="reset">重置</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-14 12:14:07 +08:00
|
|
|
|
<div class="info-box">
|
|
|
|
|
|
<span class="icon">💡</span>
|
|
|
|
|
|
<strong>核心思想:</strong>所有模块在同一个进程中运行,内存共享,但一个模块崩溃可能导致整个进程挂掉(雪崩效应)。
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
const modules = ref([
|
|
|
|
|
|
{ name: '用户模块', icon: '👤', status: 'healthy', statusText: '健康' },
|
|
|
|
|
|
{ name: '订单模块', icon: '📦', status: 'healthy', statusText: '健康' },
|
|
|
|
|
|
{ name: '支付模块', icon: '💳', status: 'healthy', statusText: '健康' },
|
|
|
|
|
|
{ name: '库存模块', icon: '🏭', status: 'healthy', statusText: '健康' }
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const requests = ref([])
|
|
|
|
|
|
const hasCrashed = ref(false)
|
|
|
|
|
|
const crashedModule = ref(null)
|
|
|
|
|
|
const activeModule = ref(null)
|
|
|
|
|
|
const requestId = ref(0)
|
|
|
|
|
|
|
|
|
|
|
|
const simulateNormalRequest = () => {
|
|
|
|
|
|
const targets = ['用户模块', '订单模块', '支付模块', '库存模块']
|
|
|
|
|
|
const target = targets[Math.floor(Math.random() * targets.length)]
|
|
|
|
|
|
|
|
|
|
|
|
activeModule.value = target
|
|
|
|
|
|
requestId.value++
|
|
|
|
|
|
|
|
|
|
|
|
requests.value.push({
|
|
|
|
|
|
id: requestId.value,
|
|
|
|
|
|
type: 'GET',
|
|
|
|
|
|
target: target,
|
|
|
|
|
|
status: 'active'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
activeModule.value = null
|
|
|
|
|
|
if (requests.value.length > 5) {
|
|
|
|
|
|
requests.value.shift()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1500)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const simulateCrash = () => {
|
|
|
|
|
|
const targetModule = '订单模块'
|
|
|
|
|
|
hasCrashed.value = true
|
|
|
|
|
|
crashedModule.value = targetModule
|
|
|
|
|
|
|
|
|
|
|
|
const module = modules.value.find(m => m.name === targetModule)
|
|
|
|
|
|
if (module) {
|
|
|
|
|
|
module.status = 'crashed'
|
|
|
|
|
|
module.statusText = '已崩溃'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cascade effect - other modules become unavailable
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
modules.value.forEach(m => {
|
|
|
|
|
|
if (m.name !== targetModule) {
|
|
|
|
|
|
m.status = 'affected'
|
|
|
|
|
|
m.statusText = '受影响'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}, 500)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const reset = () => {
|
|
|
|
|
|
hasCrashed.value = false
|
|
|
|
|
|
crashedModule.value = null
|
|
|
|
|
|
activeModule.value = null
|
|
|
|
|
|
requests.value = []
|
|
|
|
|
|
|
|
|
|
|
|
modules.value.forEach(m => {
|
|
|
|
|
|
m.status = 'healthy'
|
|
|
|
|
|
m.statusText = '健康'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.monolith-demo {
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
margin: 0.5rem 0;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.demo-header {
|
2026-02-14 12:14:07 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
padding-bottom: 0.4rem;
|
2026-02-14 12:14:07 +08:00
|
|
|
|
border-bottom: 1px solid var(--vp-c-divider);
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 12:14:07 +08:00
|
|
|
|
.demo-header .icon {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 1rem;
|
2026-02-14 12:14:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.demo-header .title {
|
|
|
|
|
|
font-weight: bold;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.9rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 12:14:07 +08:00
|
|
|
|
.demo-header .subtitle {
|
2026-02-06 03:34:50 +08:00
|
|
|
|
color: var(--vp-c-text-2);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
margin-left: 0.4rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.monolith-diagram {
|
|
|
|
|
|
display: flex;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
align-items: flex-start;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
margin-bottom: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.monolith-box {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border: 2px solid var(--vp-c-brand);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 0.5rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.monolith-box.crashed {
|
|
|
|
|
|
border-color: var(--vp-c-danger);
|
|
|
|
|
|
background: rgba(239, 68, 68, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.monolith-header {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--vp-c-brand);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
padding-bottom: 0.25rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
border-bottom: 1px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modules-container {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-box {
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 0.4rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
text-align: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-box:hover {
|
|
|
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-box.active {
|
|
|
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
|
background: rgba(102, 126, 234, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-box.crashed {
|
|
|
|
|
|
border-color: var(--vp-c-danger);
|
|
|
|
|
|
background: rgba(239, 68, 68, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-icon {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
margin-bottom: 0.1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-name {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.65rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
margin-bottom: 0.1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-status {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.55rem;
|
|
|
|
|
|
padding: 0.05rem 0.25rem;
|
|
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
display: inline-block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-status.healthy {
|
|
|
|
|
|
background: rgba(34, 197, 94, 0.2);
|
|
|
|
|
|
color: #16a34a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-status.crashed {
|
|
|
|
|
|
background: rgba(239, 68, 68, 0.2);
|
|
|
|
|
|
color: #dc2626;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.module-status.affected {
|
|
|
|
|
|
background: rgba(245, 158, 11, 0.2);
|
|
|
|
|
|
color: #d97706;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.shared-db {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
padding: 0.4rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border: 1px dashed var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 4px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.db-icon {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.db-label {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.65rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.request-flow {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
width: 100px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.25rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-request {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
padding: 0.3rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 0.6rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-request.active {
|
|
|
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
|
background: rgba(102, 126, 234, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.req-type {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--vp-c-brand);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.req-arrow {
|
|
|
|
|
|
color: var(--vp-c-text-3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.req-target {
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
|
|
display: flex;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.4rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
justify-content: center;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
margin-bottom: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-btn {
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.3rem 0.6rem;
|
|
|
|
|
|
border-radius: 4px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
font-size: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-btn:hover {
|
|
|
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-btn.danger {
|
|
|
|
|
|
border-color: var(--vp-c-danger);
|
|
|
|
|
|
color: var(--vp-c-danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-btn.danger:hover {
|
|
|
|
|
|
background: rgba(239, 68, 68, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.monolith-diagram {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.request-flow {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-14 12:14:07 +08:00
|
|
|
|
|
|
|
|
|
|
.info-box {
|
|
|
|
|
|
background: var(--vp-c-bg-alt);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 0.75rem;
|
2026-02-14 12:14:07 +08:00
|
|
|
|
color: var(--vp-c-text-2);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
margin-top: 0.5rem;
|
2026-02-14 12:14:07 +08:00
|
|
|
|
display: flex;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
gap: 0.2rem;
|
2026-02-14 12:14:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-box .icon {
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-box strong {
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</style>
|