| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8"/>
- <title>秒杀系统时序图</title>
- <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
- <style>
- html,body{margin:0;height:100%;background:#f5f5f5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;}
- .wrap{display:flex;flex-direction:column;height:100%;}
- .toolbar{flex:0 0 50px;display:flex;align-items:center;justify-content:center;background:#fff;box-shadow:0 2px 4px rgba(0,0,0,.08);}
- .toolbar button{margin:0 6px;padding:6px 14px;border:0;border-radius:4px;background:#2196f3;color:#fff;cursor:pointer;}
- .viewport{flex:1;overflow:hidden;position:relative;background:#fafafa;}
- #mermaid-svg{position:absolute;top:0;left:0;cursor:grab;transition:none;}
- #mermaid-svg.grabbing{cursor:grabbing;}
- </style>
- </head>
- <body>
- <div class="wrap">
- <div class="toolbar">
- <button onclick="zoom(0.9)">缩小</button>
- <button onclick="zoom(1.1)">放大</button>
- <button onclick="reset()">重置</button>
- <button onclick="toggleDrag()">拖拽开关</button>
- </div>
- <div class="viewport" id="viewport">
- <div id="mermaid-svg"></div>
- </div>
- </div>
- <script>
- /* ---------- 1. 渲染时序图 ---------- */
- mermaid.initialize({
- startOnLoad: false,
- theme: 'default',
- sequence: { diagramMarginX: 50, diagramMarginY: 10, actorMargin: 50, width: 150, height: 65 }
- });
- const code = `
- sequenceDiagram
- participant U as 用户
- participant SC as SeckillController
- participant SGA as SeckillGuardAspect
- participant UC as UserContext
- participant RS as RedissonClient
- participant RSD as RedisStockDao
- participant LU as Lua脚本
- participant SMP as SeckillMessageProducer
- participant SSS as StockSyncService
- participant SIS as StockInitializerService
- participant SJ as StockSyncJob
- participant SMC as SeckillMessageConsumer
- U->>SC: 发起秒杀请求
- SC->>SGA: 调用被@SeckillGuard注解的方法
- SGA->>SGA: 检查系统配置和灰度规则
- alt 非灰度用户或未启用新逻辑
- SGA->>SC: 降级到旧逻辑
- SC-->>U: 返回旧逻辑处理结果
- else 灰度用户且启用新逻辑
- SGA->>RS: 用户限流检查(RAtomicLong.incrementAndGet)
- RS-->>SGA: 返回用户访问次数
- alt 超过用户限制
- SGA-->>U: 抛出用户限流异常
- else 未超过用户限制
- SGA->>RS: 获取信号量许可(RSemaphore.tryAcquire)
- RS-->>SGA: 返回许可获取结果
- alt 未获取到许可(系统繁忙)
- SGA-->>U: 抛出系统繁忙异常
- else 获取到许可
- SGA->>RSD: 检查库存是否存在(existsStock)
- RSD-->>SGA: 返回库存存在状态
- alt 库存不存在
- SGA->>SIS: 初始化库存(checkAndInitializeStock)
- SIS-->>SGA: 返回初始化结果
- end
- SGA->>UC: 生成订单号(UUID)
- SGA->>RSD: 执行库存扣减(decrStock)
- RSD->>LU: 执行Lua脚本
- LU-->>RSD: 返回剩余库存
- RSD-->>SGA: 返回扣减结果
- alt 库存不足
- SGA->>RS: 释放信号量许可
- SGA-->>U: 抛出库存不足异常
- else 库存充足
- SGA->>UC: 设置订单Token(setOrderToken)
- SGA->>SC: 执行业务逻辑(proceed)
- SC-->>SGA: 返回业务执行结果
- alt 业务执行成功
- SGA->>SMP: 发送秒杀成功消息
- SGA->>SMP: 发送库存回滚消息
- SGA->>SSS: 记录库存变化
- SGA->>RS: 释放信号量许可
- SGA-->>U: 返回秒杀成功结果
- else 业务执行失败
- SGA->>RSD: 回滚库存(incrStock)
- SGA->>RS: 释放信号量许可
- SGA-->>U: 抛出业务异常
- end
- end
- end
- end
- end
- `;
- mermaid.render('mermaid-svg', code).then(({ svg }) => {
- document.getElementById('mermaid-svg').innerHTML = svg;
- reset(); // 初始化居中
- });
- /* ---------- 2. 无溢出缩放 + 丝滑拖拽 ---------- */
- let scale = 1, translateX = 0, translateY = 0;
- let dragOn = false, dragging = false, startX, startY;
- const svgRoot = () => document.querySelector('#mermaid-svg svg');
- const viewport = document.getElementById('viewport');
- function applyTransform() {
- const el = svgRoot();
- if (!el) return;
- el.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
- el.style.transformOrigin = '0 0';
- }
- function zoom(factor, anchorX, anchorY) {
- const el = svgRoot();
- if (!el) return;
- const rect = el.getBoundingClientRect();
- anchorX = anchorX ?? rect.left + rect.width / 2;
- anchorY = anchorY ?? rect.top + rect.height / 2;
- const newScale = Math.max(0.3, Math.min(5, scale * factor));
- const dx = (anchorX - rect.left) * (1 - newScale / scale);
- const dy = (anchorY - rect.top) * (1 - newScale / scale);
- scale = newScale;
- translateX += dx;
- translateY += dy;
- applyTransform();
- }
- function reset() {
- scale = 1; translateX = 0; translateY = 0;
- const el = svgRoot();
- if (!el) return;
- // 居中
- const vRect = viewport.getBoundingClientRect();
- const sRect = el.getBoundingClientRect();
- translateX = (vRect.width - sRect.width) / 2;
- translateY = 20;
- applyTransform();
- }
- function toggleDrag() {
- dragOn = !dragOn;
- viewport.style.cursor = dragOn ? 'grab' : 'default';
- }
- /* 滚轮缩放以鼠标位置为锚点 */
- viewport.addEventListener('wheel', e => {
- e.preventDefault();
- zoom(e.deltaY < 0 ? 1.1 : 0.9, e.clientX, e.clientY);
- });
- /* 拖拽 */
- viewport.addEventListener('mousedown', e => {
- if (!dragOn) return;
- dragging = true;
- startX = e.clientX - translateX;
- startY = e.clientY - translateY;
- svgRoot()?.classList.add('grabbing');
- });
- window.addEventListener('mousemove', e => {
- if (!dragging) return;
- translateX = e.clientX - startX;
- translateY = e.clientY - startY;
- applyTransform();
- });
- window.addEventListener('mouseup', () => {
- dragging = false;
- svgRoot()?.classList.remove('grabbing');
- });
- </script>
- </body>
- </html>
|