feat: 全系统优化 — 并发控制 + 冗余清理 + 数据流修复 + 全面测试

核心修复:
- 状态机加 SELECT FOR UPDATE 行锁,消除并发竞态
- hss_md_staff 加 role 列,登录从数据库读取真实角色
- 申请重复校验排除自身,全流程 20 步闭环通过
- 派单 SQL 修复 + 支付状态机过渡 + 完成服务 plan_item_id 修复

并发控制新增:
- RedisLockService (SET NX PX + Lua 安全解锁)
- RateLimiterService (Redis 滑动窗口 + API 拦截器)
- TransactionIsolationConfig (SERIALIZABLE for 支付回调)
- MqttPublisher (异步队列 + JDK TCP 探测)
- ObjectStorageService (AWS SigV4 预签名, 纯 JDK)

冗余清理:
- 删除 6 个死代码文件 (~620 行)
- hutool-all → JDK MessageDigest, 去 MapStruct, 去 jsr310
- haversine 提取到 GeoUtil, count/round 提取到 JdbcUtil
- 创建 platform layout 组件

前端修复:
- 登录页移除角色选择器, 由后端 JWT 返回
- 移除 ClientOnly 包裹, 页面正常渲染
- SPA fallback Nginx 配置修复

Docker: 运行时镜像 eclipse-temurin:17-jre-jammy (缩小 ~300MB)

文档: 新增系统实现与修复报告.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 11:48:07 +08:00
parent 7d92322b99
commit 01e1034cc1
387 changed files with 6220 additions and 12952 deletions

View File

@@ -1,20 +1,22 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import { usePlatformAuth } from '~/composables/usePlatformAuth'
import { useApi } from '~/composables/useApi'
definePageMeta({ layout: false })
const { isLoggedIn, user, logout, switchRole, getAuthHeaders, ROLES } = usePlatformAuth()
const ROLE_LABELS: Record<string,string> = { ADMIN:'系统管理员', RECEPTIONIST:'受理员', ASSESSOR:'评估员', PLANNER:'方案制定员', DISPATCHER:'调度员', STAFF:'服务人员', SETTLER:'结算员', SUPERVISOR:'监管员', REVIEWER:'复核员' }
const { isLoggedIn, user, logout } = usePlatformAuth()
const roleLabel = computed(() => ROLE_LABELS[user.value?.userRole || ''] || user.value?.userRole || '')
const { get, post } = useApi()
const stats = ref<Record<string,any>>({})
const recentOrders = ref<any[]>([])
const activeMenu = ref('dashboard')
if (!isLoggedIn.value) { await navigateTo('/platform/login') }
onMounted(async () => {
if (!isLoggedIn.value) { await navigateTo('/platform/login'); return }
try { stats.value = await get<any>('/admin/dashboard').catch(() => ({})) } catch {}
})
@@ -57,10 +59,7 @@ const roleMenus: Record<string, string[]> = {
<div class="w-7 h-7 rounded-full bg-primary-50 text-primary flex items-center justify-center text-xs font-bold">{{ user?.userName?.charAt(0) }}</div>
<div class="min-w-0">
<div class="text-xs font-medium truncate">{{ user?.userName }}</div>
<select @change="switchRole(($event.target as HTMLSelectElement).value)"
class="text-xs text-text-secondary bg-transparent border-none outline-none cursor-pointer">
<option v-for="r in ROLES" :key="r.key" :value="r.key" :selected="user?.userRole === r.key">{{ r.label }}</option>
</select>
<div class="text-xs text-text-secondary">{{ roleLabel }}</div>
</div>
</div>
<button @click="logout(); navigateTo('/platform/login')"
@@ -74,16 +73,13 @@ const roleMenus: Record<string, string[]> = {
<div class="lg:hidden bg-white border-b px-4 py-3 flex items-center justify-between">
<NuxtLink to="/platform" class="font-bold text-primary text-sm">智慧医养平台</NuxtLink>
<div class="flex items-center gap-2">
<select @change="switchRole(($event.target as HTMLSelectElement).value)"
class="text-xs border rounded-lg px-2 py-1 outline-none">
<option v-for="r in ROLES" :key="r.key" :value="r.key" :selected="user?.userRole === r.key">{{ r.label }}</option>
</select>
<span class="text-xs text-text-secondary">{{ roleLabel }}</span>
<button @click="logout(); navigateTo('/platform/login')" class="text-xs text-red-500">退出</button>
</div>
</div>
<div class="p-4 lg:p-8">
<h2 class="text-xl font-bold mb-6">工作台 {{ ROLES.find(r=>r.key===user?.userRole)?.label || user?.userRole }}</h2>
<h2 class="text-xl font-bold mb-6">工作台 {{ ROLE_LABELS[user?.userRole] || user?.userRole }}</h2>
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div class="bg-white rounded-xl p-4 shadow-sm border"><div class="text-2xl font-bold font-mono text-primary">{{ stats.todayOrders || '--' }}</div><div class="text-xs text-text-secondary mt-1">今日工单</div></div>
@@ -108,7 +104,7 @@ const roleMenus: Record<string, string[]> = {
<div class="bg-white rounded-2xl shadow-sm border p-6">
<h3 class="font-bold mb-4">当前角色权限</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between py-2 border-b border-gray-50"><span class="text-text-secondary">角色</span><span class="font-medium">{{ ROLES.find(r=>r.key===user?.userRole)?.label }}</span></div>
<div class="flex justify-between py-2 border-b border-gray-50"><span class="text-text-secondary">角色</span><span class="font-medium">{{ ROLE_LABELS[user?.userRole] || user?.userRole }}</span></div>
<div class="flex justify-between py-2 border-b border-gray-50"><span class="text-text-secondary">可操作模块</span><span class="font-medium">{{ (roleMenus[user?.userRole||'']||['dashboard']).join(', ') }}</span></div>
<div class="flex justify-between py-2"><span class="text-text-secondary">数据范围</span><span class="font-medium">本机构</span></div>
</div>