feat 增加mock数据别名
This commit is contained in:
parent
47cd3b338e
commit
7469ded0de
19
admin.html
19
admin.html
@ -137,6 +137,11 @@
|
||||
<el-button size="mini" type="primary" @click="openMockFileDialogForCreate">新增 Mock 文件</el-button>
|
||||
</div>
|
||||
<el-table :data="pagedMockFiles" border>
|
||||
<el-table-column label="别名" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.alias || "-" }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Mock 文件路径" min-width="320">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.filePath }}</span>
|
||||
@ -266,7 +271,7 @@
|
||||
<el-option
|
||||
v-for="item in mockFiles"
|
||||
:key="item.filePath"
|
||||
:label="item.filePath"
|
||||
:label="item.alias ? item.alias + ' (' + item.filePath + ')' : item.filePath"
|
||||
:value="item.filePath"
|
||||
></el-option>
|
||||
</el-select>
|
||||
@ -307,10 +312,16 @@
|
||||
width="760px"
|
||||
>
|
||||
<el-form :model="mockFileDialog.form" label-width="180px">
|
||||
<el-form-item label="别名">
|
||||
<el-input
|
||||
v-model="mockFileDialog.form.alias"
|
||||
placeholder="可选:用于展示,支持任意文本"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Mock 文件路径">
|
||||
<el-input
|
||||
v-model="mockFileDialog.form.filePath"
|
||||
placeholder="mock/new-api.json"
|
||||
placeholder="mock/test123.json(路径仅英文和数字)"
|
||||
:disabled="mockFileDialog.mode === 'edit'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
@ -398,6 +409,7 @@
|
||||
mode: "create",
|
||||
form: {
|
||||
filePath: "mock/",
|
||||
alias: "",
|
||||
content: "",
|
||||
},
|
||||
},
|
||||
@ -548,6 +560,7 @@
|
||||
getDefaultMockFileForm: function () {
|
||||
return {
|
||||
filePath: "mock/",
|
||||
alias: "",
|
||||
content: "",
|
||||
};
|
||||
},
|
||||
@ -560,6 +573,7 @@
|
||||
this.mockFileDialog.mode = "edit";
|
||||
this.mockFileDialog.form = {
|
||||
filePath: item.filePath || "mock/",
|
||||
alias: String(item.alias || ""),
|
||||
content: String(item.content || ""),
|
||||
};
|
||||
this.mockFileDialog.visible = true;
|
||||
@ -589,6 +603,7 @@
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
filePath: this.mockFileDialog.form.filePath,
|
||||
alias: this.mockFileDialog.form.alias,
|
||||
content: this.mockFileDialog.form.content,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"routes": {
|
||||
"/api1/account/bind": "mock/basic-error.json",
|
||||
"#/api1/account/bind": "mock/basic-error.json",
|
||||
"/api2/admin/banner/list": "mock/banner.txt",
|
||||
"/api2/home/ad/list": "mock/basic-error.json"
|
||||
},
|
||||
"routeStatuses": {
|
||||
"/api1/account/bind": 500,
|
||||
"#/api1/account/bind": 500,
|
||||
"/api2/admin/banner/list": 200,
|
||||
"/api2/home/ad/list": 200
|
||||
},
|
||||
|
||||
53
index.api.ts
53
index.api.ts
@ -49,6 +49,7 @@ interface ApiListItem {
|
||||
|
||||
interface MockFileItem {
|
||||
filePath: string;
|
||||
alias: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
@ -60,6 +61,7 @@ interface RouteRow {
|
||||
|
||||
interface MockFileRow {
|
||||
file_path: string;
|
||||
alias: string;
|
||||
}
|
||||
|
||||
// 存储当前的路由配置
|
||||
@ -186,9 +188,15 @@ async function initDatabase(): Promise<void> {
|
||||
`);
|
||||
await dbRun(`
|
||||
CREATE TABLE IF NOT EXISTS mock_files (
|
||||
file_path TEXT PRIMARY KEY
|
||||
file_path TEXT PRIMARY KEY,
|
||||
alias TEXT NOT NULL DEFAULT ''
|
||||
)
|
||||
`);
|
||||
await dbRun("ALTER TABLE mock_files ADD COLUMN alias TEXT NOT NULL DEFAULT ''").catch(
|
||||
() => {
|
||||
// ignore when column already exists
|
||||
},
|
||||
);
|
||||
// 清理不该出现在 mock 列表中的系统文件
|
||||
await dbRun(
|
||||
"DELETE FROM mock_files WHERE file_path = ? OR file_path LIKE ?",
|
||||
@ -246,19 +254,31 @@ async function loadApiListFromDb(): Promise<ApiListItem[]> {
|
||||
return rows.map((row) => ({ route: row.route, name: row.name }));
|
||||
}
|
||||
|
||||
async function upsertMockFilePathToDb(filePath: string): Promise<void> {
|
||||
await dbRun("INSERT OR IGNORE INTO mock_files(file_path) VALUES(?)", [filePath]);
|
||||
async function upsertMockFilePathToDb(filePath: string, alias?: string): Promise<void> {
|
||||
if (typeof alias === "string") {
|
||||
await dbRun(
|
||||
"INSERT INTO mock_files(file_path, alias) VALUES(?, ?) ON CONFLICT(file_path) DO UPDATE SET alias = excluded.alias",
|
||||
[filePath, alias],
|
||||
);
|
||||
return;
|
||||
}
|
||||
await dbRun("INSERT OR IGNORE INTO mock_files(file_path, alias) VALUES(?, '')", [
|
||||
filePath,
|
||||
]);
|
||||
}
|
||||
|
||||
async function removeMockFilePathFromDb(filePath: string): Promise<void> {
|
||||
await dbRun("DELETE FROM mock_files WHERE file_path = ?", [filePath]);
|
||||
}
|
||||
|
||||
async function loadMockFilePathsFromDb(): Promise<string[]> {
|
||||
async function loadMockFilePathsFromDb(): Promise<MockFileRow[]> {
|
||||
const rows = await dbAll<MockFileRow>(
|
||||
"SELECT file_path FROM mock_files ORDER BY file_path ASC",
|
||||
"SELECT file_path, alias FROM mock_files ORDER BY file_path ASC",
|
||||
);
|
||||
return rows.map((row) => row.file_path);
|
||||
return rows.map((row) => ({
|
||||
file_path: row.file_path,
|
||||
alias: String(row.alias || ""),
|
||||
}));
|
||||
}
|
||||
|
||||
// 加载配置文件
|
||||
@ -417,6 +437,12 @@ function normalizeMockFilePath(filePath: string): string {
|
||||
if (path.isAbsolute(relative)) {
|
||||
throw new Error("filePath must be a relative path");
|
||||
}
|
||||
const innerPath = relative.replace(/^mock\//, "");
|
||||
if (!/^[A-Za-z0-9/.]+$/.test(innerPath)) {
|
||||
throw new Error(
|
||||
"filePath only allows English letters, numbers, '/', and '.'",
|
||||
);
|
||||
}
|
||||
return relative;
|
||||
}
|
||||
|
||||
@ -452,8 +478,9 @@ function walkMockFiles(dir: string, baseDir: string, result: string[]): void {
|
||||
async function loadMockFiles(): Promise<MockFileItem[]> {
|
||||
let files = await loadMockFilePathsFromDb();
|
||||
files = files.filter(
|
||||
(filePath) =>
|
||||
filePath !== "mock/api-list.json" && !filePath.endsWith(".sqlite3"),
|
||||
(item) =>
|
||||
item.file_path !== "mock/api-list.json" &&
|
||||
!item.file_path.endsWith(".sqlite3"),
|
||||
);
|
||||
if (files.length === 0 && fs.existsSync(MOCK_DIR)) {
|
||||
const fsFiles: string[] = [];
|
||||
@ -462,15 +489,17 @@ async function loadMockFiles(): Promise<MockFileItem[]> {
|
||||
for (const filePath of fsFiles) {
|
||||
await upsertMockFilePathToDb(filePath);
|
||||
}
|
||||
files = fsFiles;
|
||||
files = fsFiles.map((filePath) => ({ file_path: filePath, alias: "" }));
|
||||
}
|
||||
|
||||
const result: MockFileItem[] = [];
|
||||
for (const filePath of files) {
|
||||
for (const item of files) {
|
||||
const filePath = item.file_path;
|
||||
const fullPath = path.join(__dirname, filePath);
|
||||
if (!fs.existsSync(fullPath)) continue;
|
||||
result.push({
|
||||
filePath,
|
||||
alias: String(item.alias || ""),
|
||||
content: fs.readFileSync(fullPath, "utf-8"),
|
||||
});
|
||||
}
|
||||
@ -878,6 +907,7 @@ const proxyServer = http.createServer(async (clientReq, clientRes) => {
|
||||
.then(async (bodyText) => {
|
||||
const body = bodyText ? JSON.parse(bodyText) : {};
|
||||
const filePath = String(body.filePath || "").trim();
|
||||
const alias = String(body.alias || "");
|
||||
const fullPath = resolveMockFullPath(filePath);
|
||||
const normalizedPath = normalizeMockFilePath(filePath);
|
||||
|
||||
@ -885,12 +915,13 @@ const proxyServer = http.createServer(async (clientReq, clientRes) => {
|
||||
const content = String(body.content || "");
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
fs.writeFileSync(fullPath, content, "utf-8");
|
||||
await upsertMockFilePathToDb(normalizedPath);
|
||||
await upsertMockFilePathToDb(normalizedPath, alias);
|
||||
clientRes.writeHead(200, { "Content-Type": "application/json" });
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
filePath: normalizedPath,
|
||||
alias,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user