fix: 按评审报告止血支付漏洞并加固授权链路

废除浏览器回跳自助结算,通用 webhook 强制密钥鉴权,Waffo 支持验签与未知事件忽略;重构订单状态机/激活占座/SQLite WAL,并补备份、readyz、限流与前端导出/登录页安全修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-12 21:26:58 +08:00
1 parent 8cc2c83f9d
commit bac72e6945
24 files changed
+682 -391

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@
"pinia": "^2.1.7",
"element-plus": "^2.5.0",
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.5",
"axios": "^1.7.9",
"echarts": "^5.4.3",
"vue-echarts": "^6.6.8",
"dayjs": "^1.11.10"
+37 -25
View File
@@ -1,5 +1,6 @@
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ElMessage } from 'element-plus'
import router from '@/router'
import { useUserStore } from '@/stores/user'
// 创建 axios 实例 // Logan.Li
@@ -11,6 +12,26 @@ const request = axios.create({
},
})
let handling401 = false
// 统一强制重新登录,避免并发 401 叠加弹窗 // Logan.Li
async function forceReLogin() {
if (handling401) return Promise.reject(new Error('relogin'))
handling401 = true
try {
const userStore = useUserStore()
userStore.logout()
ElMessage.warning('登录状态已过期,请重新登录')
const redirect = router.currentRoute.value.fullPath
await router.push({ path: '/login', query: redirect && redirect !== '/login' ? { redirect } : {} })
} finally {
setTimeout(() => {
handling401 = false
}, 1000)
}
return Promise.reject(new Error('relogin'))
}
// 请求拦截器:注入 Bearer token // Logan.Li
request.interceptors.request.use(
(config) => {
@@ -26,6 +47,10 @@ request.interceptors.request.use(
// 响应拦截器:统一处理 code 与 HTTP 错误 // Logan.Li
request.interceptors.response.use(
(response) => {
// 文件下载(blob)直接放行,避免被业务码拦截 // Logan.Li
if (['blob', 'arraybuffer'].includes(response.config.responseType)) {
return response
}
const res = response.data
// code 为 0 或 200 视为成功 // Logan.Li
@@ -33,53 +58,40 @@ request.interceptors.response.use(
return res
}
// 401:登录态失效,引导重新登录 // Logan.Li
// 401:登录态失效 // Logan.Li
if (res.code === 401) {
ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
const userStore = useUserStore()
userStore.logout()
window.location.href = '/login'
})
return Promise.reject(new Error(res.message || '未授权'))
return forceReLogin()
}
// 其他业务错误:仅错误用 ElMessage 提示(常规操作不打扰) // Logan.Li
ElMessage.error(res.message || '请求失败')
return Promise.reject(new Error(res.message || 'Error'))
},
(error) => {
let message = '网络错误'
async (error) => {
if (error.response?.status === 401) {
return forceReLogin()
}
let message = '网络错误'
if (error.response) {
switch (error.response.status) {
case 400:
message = error.response.data?.message || '请求参数错误'
break
case 401:
message = '未授权,请重新登录'
{
const userStore = useUserStore()
userStore.logout()
setTimeout(() => {
window.location.href = '/login'
}, 1500)
}
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求的资源不存在'
break
case 429:
message = '请求过于频繁,请稍后再试'
break
case 500:
message = '服务器内部错误'
break
default:
message = `连接错误 ${error.response.status}`
message = error.response.data?.message || `连接错误 ${error.response.status}`
}
} else if (error.request) {
message = '网络连接失败,请检查网络'
+2 -1
View File
@@ -1,6 +1,7 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
@@ -17,7 +18,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.use(createPinia())
app.use(router)
app.use(ElementPlus, { locale: undefined }) // 默认中文
app.use(ElementPlus, { locale: zhCn })
// 从 localStorage 恢复登录状态 // Logan.Li
const userStore = useUserStore()
+2 -10
View File
@@ -41,8 +41,6 @@
</el-button>
</el-form-item>
</el-form>
<div class="login-tip">默认账号:admin / admin</div>
</div>
</div>
</template>
@@ -60,8 +58,8 @@ const userStore = useUserStore()
const loginFormRef = ref(null)
const loading = ref(false)
const loginForm = reactive({
username: 'admin',
password: 'admin',
username: '',
password: '',
})
const loginRules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
@@ -117,10 +115,4 @@ async function handleLogin() {
color: #909399;
font-size: 14px;
}
.login-tip {
text-align: center;
color: #c0c4cc;
font-size: 12px;
margin-top: 16px;
}
</style>