- Java 包 cn.huijing.game→cn.wanxiang.game:126 .java + 15 目录 git mv(保留历史)+ Application scanBasePackages/@MapperScan + yaml type-aliases - SDK HuijingGameSDK→WanxiangGameSDK + postMessage channel huijing-game-sdk→wanxiang-game-sdk(契约 + 前端 host/sdk/inject/bridge) - 契约 contracts/ 同步(sdk-interface.d.ts / runtime.yaml / game-package.schema / $id URL / V3 SQL 注释) - 主 agent 独立验证全绿:后端集成编译+55 单测(cn.wanxiang)、前端 build+/browse 冒烟(WanxiangGameSDK+demo 911 像素+遥测 200)、残留 huijing=0、Flyway 5 迁移 diff-identical、yudao cn.iocoder.yudao 2167 文件未误伤 - 保留 yudao 框架 cn.iocoder.yudao + Maven groupId cn.iocoder.cloud Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { fileURLToPath, URL } from 'node:url'
|
||
import type { IncomingMessage } from 'node:http'
|
||
import { defineConfig, type Connect, type PluginOption } from 'vite'
|
||
import vue from '@vitejs/plugin-vue'
|
||
// 自写 mock 调度器(DOM-free,dev/preview 两端复用)。严格按 contracts/api-schemas 返回数据。
|
||
import { dispatch, MOCK_PREFIXES } from './src/mock/index.ts'
|
||
|
||
/**
|
||
* 读取请求体(POST/PUT 等),解析为 JSON。
|
||
* connect 中间件层面手动收 chunk,避免依赖 body-parser(零额外依赖)。
|
||
*/
|
||
function readBody(req: IncomingMessage): Promise<unknown> {
|
||
return new Promise((resolve) => {
|
||
const chunks: Buffer[] = []
|
||
req.on('data', (c: Buffer) => chunks.push(c))
|
||
req.on('end', () => {
|
||
if (chunks.length === 0) {
|
||
resolve(undefined)
|
||
return
|
||
}
|
||
const raw = Buffer.concat(chunks).toString('utf-8')
|
||
try {
|
||
resolve(raw ? JSON.parse(raw) : undefined)
|
||
} catch {
|
||
// 非 JSON 体(如 sendBeacon 偶发)直接透传原文,handler 自行兜底
|
||
resolve(raw)
|
||
}
|
||
})
|
||
req.on('error', () => resolve(undefined))
|
||
})
|
||
}
|
||
|
||
/** 判断 url 是否命中 mock 前缀(/app-api、/admin-api) */
|
||
function isMockUrl(url: string | undefined): boolean {
|
||
if (!url) return false
|
||
return MOCK_PREFIXES.some((p) => url === p || url.startsWith(p + '/') || url.startsWith(p + '?'))
|
||
}
|
||
|
||
/**
|
||
* connect 中间件:拦截 API 请求 → 调 dispatch → 写 JSON 包络。
|
||
* 未命中 mock 路由则调用 next() 放行(不误吞静态资源/HMR 等)。
|
||
*/
|
||
const mockMiddleware: Connect.NextHandleFunction = (req, res, next) => {
|
||
const url = req.url
|
||
if (!isMockUrl(url)) {
|
||
next()
|
||
return
|
||
}
|
||
void (async () => {
|
||
try {
|
||
const body = await readBody(req)
|
||
const result = await dispatch(req.method || 'GET', url as string, body)
|
||
if (result === null) {
|
||
// 命中前缀但无对应路由:返回 404 包络,便于排查“契约里没有的端点”被误调
|
||
res.statusCode = 404
|
||
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
||
res.end(JSON.stringify({ code: 404, data: null, msg: `mock 未匹配: ${req.method} ${url}` }))
|
||
return
|
||
}
|
||
res.statusCode = 200
|
||
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
||
res.end(JSON.stringify(result))
|
||
} catch (err) {
|
||
// 兜底:mock 自身异常也返回包络,不让请求悬挂
|
||
res.statusCode = 200
|
||
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
||
res.end(JSON.stringify({ code: 1, data: null, msg: (err as Error)?.message || 'mock error' }))
|
||
}
|
||
})()
|
||
}
|
||
|
||
/**
|
||
* 自写 mock vite 插件。
|
||
* 关键:同时 hook configureServer(dev) 与 configurePreviewServer(preview),
|
||
* 两端都把 mockMiddleware 前置注入 connect 栈 → `vite` 与 `vite preview` 都能命中 mock(验收要求)。
|
||
*/
|
||
function mockPlugin(): PluginOption {
|
||
return {
|
||
name: 'wanxiang-local-mock',
|
||
// 开发服务器(vite dev)
|
||
configureServer(server: { middlewares: Connect.Server }) {
|
||
server.middlewares.use(mockMiddleware)
|
||
},
|
||
// 预览服务器(vite preview)——验收闭环在 preview 跑,必须生效
|
||
configurePreviewServer(server: { middlewares: Connect.Server }) {
|
||
server.middlewares.use(mockMiddleware)
|
||
},
|
||
}
|
||
}
|
||
|
||
// https://vite.dev/config/
|
||
export default defineConfig({
|
||
plugins: [vue(), mockPlugin()],
|
||
resolve: {
|
||
alias: {
|
||
// '@' → src(供 B 工位用 @/ 绝对导入;A1 自身用相对导入,避免依赖 tsconfig paths)
|
||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||
},
|
||
},
|
||
})
|