签入版本
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
export declare const SITE_URL: string;
|
||||
export declare const SITE_DESCRIPTION: string;
|
||||
export declare function stripNumberPrefix(title: string): string;
|
||||
export declare function extractDescription(md: string): string;
|
||||
export declare function extractFaqs(md: string): { q: string; a: string }[];
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* SEO/GEO 共享工具(纯 ESM,零依赖)。
|
||||
*
|
||||
* 同时被两类调用方 import:
|
||||
* 1. VitePress 配置(.ts,esbuild 打包时内联本文件)
|
||||
* 2. scripts/generate-llms-txt.mjs(Node 直接运行)
|
||||
*
|
||||
* 注意:SITE_URL 是全站唯一 URL 真相源。
|
||||
* docs 子域当前 TLS 证书为腾讯 CDN 默认证书(*.cdn.myqcloud.com,校验失败),
|
||||
* 故暂用 http;证书修复后只需改这里一处。
|
||||
*/
|
||||
|
||||
export const SITE_URL = "http://docs.tornado.antdvue.djangoadmin.cn";
|
||||
|
||||
export const SITE_DESCRIPTION =
|
||||
"DjangoAdmin Tornado+AntDesign 企业级中台解决方案开发者文档";
|
||||
|
||||
/**
|
||||
* 剥离 H1/标题中的章节编号前缀,用于 <title> 与 JSON-LD headline。
|
||||
* 例:"5.10 字典模块" → "字典模块";"K. 贡献指南" → "贡献指南"。
|
||||
* 页内 H1 与侧边栏编号保持不变,仅影响 SEO 输出。
|
||||
*/
|
||||
export function stripNumberPrefix(title) {
|
||||
return String(title)
|
||||
.replace(/^(?:\d+(?:\.\d+)+|[A-Z]\.)\s+/, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从页面 Markdown 提取 meta description:
|
||||
* 1. 首选 `:::tip xxx` 容器内的首句(141/143 页的标准 opener)
|
||||
* 2. 回退到 H1 后的第一个普通段落
|
||||
* 输出压缩为单行、去除行内 Markdown,按中文句末标点截断(≤100 字符)。
|
||||
*/
|
||||
export function extractDescription(md) {
|
||||
const src = String(md);
|
||||
const fm = src.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
const body = fm ? src.slice(fm[0].length) : src;
|
||||
|
||||
let text = "";
|
||||
// frontmatter description 显式声明优先(无 H1 页面的补齐手段)
|
||||
const fmDesc = fm && fm[1].match(/^description:\s*(.+)$/m);
|
||||
if (fmDesc) {
|
||||
text = fmDesc[1].trim().replace(/^["']|["']$/g, "");
|
||||
}
|
||||
const tip = !text && body.match(/^:::\s*tip[^\n]*\n([\s\S]*?)\n:::/m);
|
||||
if (tip) {
|
||||
text = tip[1];
|
||||
}
|
||||
if (!text) {
|
||||
const afterH1 = body.match(/^# (?!#)(.+)$/m);
|
||||
if (afterH1) {
|
||||
const rest = body.slice((afterH1.index ?? 0) + afterH1[0].length);
|
||||
const para = rest
|
||||
.split(/\n{2,}/)
|
||||
.find(
|
||||
(p) =>
|
||||
p.trim() &&
|
||||
!/^\s*#/.test(p) &&
|
||||
!/^\s*:::/.test(p) &&
|
||||
!/^\s*```/.test(p) &&
|
||||
!/^\s*\|/.test(p),
|
||||
);
|
||||
text = para || "";
|
||||
}
|
||||
}
|
||||
|
||||
text = text
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.trim();
|
||||
|
||||
if (!text) return "";
|
||||
|
||||
// 按中文句末标点截取首句,上限 100 字符;截断时避免切碎词
|
||||
const m = text.match(/^.{1,100}?(?:[。!?!?]|$)/);
|
||||
let out = m ? m[0] : text.slice(0, 100);
|
||||
if (out.length < text.length && !/[。!?!?]$/.test(out)) {
|
||||
out = out.replace(/[^\s,、;:,;:]+$/, "");
|
||||
}
|
||||
return out.trim() || text.slice(0, 100);
|
||||
}
|
||||
|
||||
function cleanText(s) {
|
||||
return s
|
||||
.replace(/^:::[^\n]*$/gm, "")
|
||||
.replace(/^```[^\n]*$/gm, "")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取页面中的 Q/A 对(用于 FAQPage JSON-LD),支持两种站内格式:
|
||||
* A. `### Q:问题` 标题式(如 zh/quickstart/faq.md)
|
||||
* B. `**Q:问题**` 加粗式(如 zh/appendix/faq.md)
|
||||
* 返回 [{ q, a }];无匹配返回 []。
|
||||
*/
|
||||
export function extractFaqs(md) {
|
||||
const body = String(md).replace(/^---[\s\S]*?\n---\s*/, "");
|
||||
let faqs = [];
|
||||
|
||||
// 格式 A:按 `### Q:` 切块,答案截断到下一个标题
|
||||
for (const part of body.split(/(?=^###\s*Q[::])/m)) {
|
||||
const m = part.match(/^###\s*Q[::]\s*([^\n]+)\n+([\s\S]*)/);
|
||||
if (!m) continue;
|
||||
let a = m[2];
|
||||
const cut = a.search(/^#{1,4}\s/m);
|
||||
if (cut >= 0) a = a.slice(0, cut);
|
||||
faqs.push({ q: cleanText(m[1]), a: cleanText(a) });
|
||||
}
|
||||
|
||||
// 格式 B:按 `**Q:` 切块
|
||||
if (!faqs.length) {
|
||||
for (const part of body.split(/(?=^\*\*Q[::])/m)) {
|
||||
const m = part.match(/^\*\*Q[::]\s*([^*]+)\*\*\s*\n+([\s\S]*)/);
|
||||
if (!m) continue;
|
||||
let a = m[2];
|
||||
const cut = a.search(/^(?:#{1,4}\s|\*\*Q)/m);
|
||||
if (cut >= 0) a = a.slice(0, cut);
|
||||
faqs.push({ q: cleanText(m[1]), a: cleanText(a) });
|
||||
}
|
||||
}
|
||||
|
||||
return faqs.filter((f) => f.q && f.a);
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
import { enConfig } from "./en";
|
||||
import { createSeoHooks } from "./seo";
|
||||
import { sharedConfig } from "./shared";
|
||||
import { zhConfig } from "./zh";
|
||||
|
||||
const seo = createSeoHooks(zhConfig.themeConfig?.sidebar);
|
||||
|
||||
export default defineConfig({
|
||||
...sharedConfig,
|
||||
ignoreDeadLinks: [
|
||||
(link) => link.includes('localhost'),
|
||||
],
|
||||
|
||||
// SEO:页面级 title/description、canonical/OG/JSON-LD、sitemap+lastmod
|
||||
transformPageData: seo.transformPageData,
|
||||
transformHead: seo.transformHead,
|
||||
sitemap: seo.sitemap,
|
||||
|
||||
locales: {
|
||||
root: { label: "简体中文", lang: "zh-CN", link: "/", ...zhConfig },
|
||||
// root: { label: "English", lang: "en-US", link: "/", ...enConfig },
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* SEO/GEO 构建期钩子(仅 Node 构建时运行,不进客户端 bundle):
|
||||
* - transformPageData:每页独立 title(去编号前缀)与 description(取 :::tip 首句)
|
||||
* - transformHead:canonical / Open Graph / twitter 卡片 / JSON-LD 结构化数据
|
||||
* - sitemap.transformItems:注入 lastmod(git 最后提交时间)并过滤 404
|
||||
*
|
||||
* 由 config/index.ts 通过 createSeoHooks(zhConfig.themeConfig?.sidebar) 挂载。
|
||||
* 依赖 extract.mjs(纯 ESM,同被 scripts/generate-llms-txt.mjs 复用)。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
import {
|
||||
SITE_URL,
|
||||
extractDescription,
|
||||
extractFaqs,
|
||||
stripNumberPrefix,
|
||||
} from "./extract.mjs";
|
||||
|
||||
/** 与 vitepress HeadConfig 同构:[tag, attrs, innerHTML?] */
|
||||
type HeadTuple = [string, Record<string, any>, string?];
|
||||
|
||||
interface SideItem {
|
||||
text?: string;
|
||||
link?: string;
|
||||
items?: SideItem[];
|
||||
}
|
||||
|
||||
interface PageDataLike {
|
||||
relativePath?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
frontmatter?: Record<string, any>;
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
interface TransformCtx {
|
||||
pageData?: PageDataLike;
|
||||
title?: string;
|
||||
description?: string;
|
||||
content?: string;
|
||||
siteConfig?: { root?: string; srcDir?: string };
|
||||
}
|
||||
|
||||
const ORG = {
|
||||
"@type": "Organization",
|
||||
name: "DjangoAdmin",
|
||||
url: "https://www.djangoadmin.cn",
|
||||
logo: `${SITE_URL}/logo.png`,
|
||||
sameAs: [
|
||||
"https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue",
|
||||
"https://www.djangoadmin.cn",
|
||||
],
|
||||
};
|
||||
|
||||
/** 相对路径 → 站内路由(cleanUrls 关闭,正文页带 .html) */
|
||||
function routeOf(relativePath: string): string {
|
||||
if (relativePath === "index.md") return "/";
|
||||
if (relativePath === "404.md") return "/404.html";
|
||||
return `/${relativePath.replace(/\.md$/, ".html")}`;
|
||||
}
|
||||
|
||||
/** 去掉侧边栏文案前导 emoji/符号,保留 "1.1 项目介绍" 这类编号文字 */
|
||||
function cleanLabel(s?: string): string {
|
||||
return (s || "").replace(/^[^\p{L}\p{N}]+/u, "").trim();
|
||||
}
|
||||
|
||||
/** 从侧边栏构建 route → 面包屑文案链(含分组小节) */
|
||||
function buildCrumbMap(sidebar: unknown): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>();
|
||||
const groups: SideItem[] = Array.isArray(sidebar)
|
||||
? sidebar
|
||||
: ((sidebar as Record<string, SideItem[]>)?.["/zh/"] ?? []);
|
||||
const walk = (items: SideItem[], trail: string[]) => {
|
||||
for (const item of items) {
|
||||
if (item.link && item.link.startsWith("/")) {
|
||||
map.set(item.link, [...trail, cleanLabel(item.text)]);
|
||||
} else if (item.items?.length) {
|
||||
walk(item.items, [...trail, cleanLabel(item.text)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const group of groups) {
|
||||
walk(group.items ?? [], [cleanLabel(group.text)].filter(Boolean));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 从渲染后的 HTML 提取 H1 纯文本(JSON-LD headline 须与页面 H1 一致) */
|
||||
function h1OfHtml(html?: string): string {
|
||||
const m = (html || "").match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
||||
if (!m) return "";
|
||||
return m[1]
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/** 从 Markdown 源提取 H1 纯文本(去除行内 markdown 标记) */
|
||||
function h1OfMd(md: string): string {
|
||||
// 围栏感知:跳过 ``` 代码块内的 # 注释,取真正的页面 H1
|
||||
let inFence = false;
|
||||
for (const line of md.split("\n")) {
|
||||
if (line.startsWith("```")) { inFence = !inFence; continue; }
|
||||
if (!inFence && /^# (?!#)/.test(line)) {
|
||||
return line
|
||||
.replace(/^# /, "")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function createSeoHooks(sidebar: unknown) {
|
||||
const crumbs = buildCrumbMap(sidebar);
|
||||
const projectDirs: string[] = [];
|
||||
let gitDates: Map<string, string> | null = null;
|
||||
|
||||
const readMd = (relativePath: string, srcDir?: string): string => {
|
||||
try {
|
||||
return fs.readFileSync(
|
||||
path.resolve(srcDir || process.cwd(), relativePath),
|
||||
"utf8",
|
||||
);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 项目内每个文件最后一次 git 提交的 ISO 时间。
|
||||
* 单次 git log 调用构建全量映射(key = 相对路径,与 pageData.relativePath 一致);
|
||||
* git 不可用时静默降级(sitemap 无 lastmod、JSON-LD 无 dateModified)。
|
||||
*/
|
||||
const getGitDates = (root: string): Map<string, string> => {
|
||||
if (gitDates) return gitDates;
|
||||
gitDates = new Map<string, string>();
|
||||
try {
|
||||
const out = execSync(
|
||||
"git log --format=:::%cI --name-only --relative -n 500 -- .",
|
||||
{
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
let current = "";
|
||||
for (const line of out.split(/\r?\n/)) {
|
||||
if (line.startsWith(":::")) {
|
||||
current = line.slice(3).trim();
|
||||
continue;
|
||||
}
|
||||
const file = line.trim();
|
||||
if (file && current && !gitDates.has(file)) {
|
||||
gitDates.set(file, current);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 降级:无 lastmod / dateModified
|
||||
}
|
||||
return gitDates;
|
||||
};
|
||||
|
||||
/** 每页独立 title(去 "5.10 " 编号前缀,关键词前置)与 description(:::tip 首句) */
|
||||
function transformPageData(pageData: PageDataLike, ctx?: any): Record<string, unknown> {
|
||||
const srcDir: string | undefined = ctx?.siteConfig?.srcDir;
|
||||
const root: string | undefined = ctx?.siteConfig?.root;
|
||||
if (root && !projectDirs.includes(root)) projectDirs.push(root);
|
||||
|
||||
const out: Record<string, unknown> = {};
|
||||
const rel = pageData.relativePath;
|
||||
if (rel && !pageData.frontmatter?.description) {
|
||||
const desc = extractDescription(readMd(rel, srcDir));
|
||||
if (desc) out.description = desc;
|
||||
}
|
||||
if (pageData.title) {
|
||||
out.title = stripNumberPrefix(pageData.title);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 构建期注入 canonical / OG / twitter / JSON-LD */
|
||||
function transformHead(ctx: TransformCtx): HeadTuple[] {
|
||||
const rel = ctx?.pageData?.relativePath || "";
|
||||
if (!rel || rel === "404.md") return [];
|
||||
|
||||
const route = routeOf(rel);
|
||||
const url = `${SITE_URL}${route}`;
|
||||
const title = ctx.pageData?.title || ctx.title || "";
|
||||
const desc = ctx.description || "";
|
||||
const isHome = rel === "index.md";
|
||||
const image = `${SITE_URL}/logo.png`;
|
||||
|
||||
const heads: HeadTuple[] = [
|
||||
["link", { rel: "canonical", href: url }],
|
||||
["meta", { property: "og:type", content: isHome ? "website" : "article" }],
|
||||
["meta", { property: "og:site_name", content: "DjangoAdmin" }],
|
||||
["meta", { property: "og:locale", content: "zh_CN" }],
|
||||
["meta", { property: "og:title", content: title }],
|
||||
["meta", { property: "og:description", content: desc }],
|
||||
["meta", { property: "og:url", content: url }],
|
||||
["meta", { property: "og:image", content: image }],
|
||||
["meta", { property: "twitter:title", content: title }],
|
||||
["meta", { property: "twitter:description", content: desc }],
|
||||
["meta", { property: "twitter:url", content: url }],
|
||||
["meta", { property: "twitter:image", content: image }],
|
||||
];
|
||||
|
||||
// ---- JSON-LD ----
|
||||
const root = ctx.siteConfig?.root || projectDirs[0] || process.cwd();
|
||||
const srcDir = ctx.siteConfig?.srcDir || root;
|
||||
const lastMod = getGitDates(root).get(rel);
|
||||
const blocks: Record<string, any>[] = [];
|
||||
|
||||
if (isHome) {
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
name: "DjangoAdmin 开发文档",
|
||||
url: `${SITE_URL}/`,
|
||||
inLanguage: "zh-CN",
|
||||
description: desc,
|
||||
});
|
||||
blocks.push({ "@context": "https://schema.org", ...ORG });
|
||||
} else {
|
||||
const md = readMd(rel, srcDir);
|
||||
const article: Record<string, any> = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: h1OfMd(md) || h1OfHtml(ctx.content) || title,
|
||||
description: desc,
|
||||
url,
|
||||
inLanguage: "zh-CN",
|
||||
mainEntityOfPage: { "@type": "WebPage", "@id": url },
|
||||
publisher: ORG,
|
||||
};
|
||||
if (lastMod) article.dateModified = lastMod;
|
||||
blocks.push(article);
|
||||
|
||||
const trail = crumbs.get(route);
|
||||
if (trail?.length) {
|
||||
const items: Record<string, any>[] = [
|
||||
{ "@type": "ListItem", position: 1, name: "首页", item: `${SITE_URL}/` },
|
||||
];
|
||||
trail.forEach((name, i) => {
|
||||
const node: Record<string, any> = {
|
||||
"@type": "ListItem",
|
||||
position: i + 2,
|
||||
name,
|
||||
};
|
||||
if (i === trail.length - 1) node.item = url;
|
||||
items.push(node);
|
||||
});
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items,
|
||||
});
|
||||
}
|
||||
|
||||
const faqs = extractFaqs(md);
|
||||
if (faqs.length) {
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: faqs.map((f) => ({
|
||||
"@type": "Question",
|
||||
name: f.q,
|
||||
acceptedAnswer: { "@type": "Answer", text: f.a },
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 转义 "<",防止内容中出现 </script> 破坏注入
|
||||
const json = JSON.stringify(blocks).replace(/</g, "\\u003c");
|
||||
heads.push(["script", { type: "application/ld+json" }, json]);
|
||||
return heads;
|
||||
}
|
||||
|
||||
/** sitemap.xml:过滤 404/README,注入 git lastmod */
|
||||
const sitemap = {
|
||||
hostname: SITE_URL,
|
||||
transformItems(items: { url: string; lastmod?: string }[]) {
|
||||
const root = projectDirs[0] || process.cwd();
|
||||
const dates = getGitDates(root);
|
||||
const out: { url: string; lastmod?: string }[] = [];
|
||||
for (const item of items) {
|
||||
// ⚠ vitepress 传入的 item.url 是相对路径("404.html"、"zh/x.html"、""),
|
||||
// 无前导斜杠也无域名——hostname 由 SitemapStream 事后拼接。
|
||||
if (/(^|\/)404\.html$/.test(item.url)) continue; // 404 页不收录
|
||||
if (/(^|\/)README\.html$/.test(item.url)) continue; // 仓库 README 非文档页
|
||||
const p = item.url.replace(/^\//, "");
|
||||
const rel =
|
||||
p === "" || p === "index.html" ? "index.md" : p.replace(/\.html$/, ".md");
|
||||
const lastmod = dates.get(rel);
|
||||
out.push(lastmod ? { ...item, lastmod } : item);
|
||||
}
|
||||
return out;
|
||||
},
|
||||
};
|
||||
|
||||
return { transformPageData, transformHead, sitemap };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig, HeadConfig } from "vitepress";
|
||||
|
||||
export const META_IMAGE = "/social.png";
|
||||
export const META_IMAGE = "/logo.png";
|
||||
|
||||
const rControl = /[\u0000-\u001f]/g;
|
||||
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g;
|
||||
@@ -23,6 +23,8 @@ export const slugify = (str: string): string =>
|
||||
|
||||
export const sharedConfig = defineConfig({
|
||||
title: "小蚂蚁云",
|
||||
// 页面 <title> 后缀:「环境准备 | DjangoAdmin 开发文档」(index.md frontmatter 可覆盖)
|
||||
titleTemplate: "DjangoAdmin 开发文档",
|
||||
appearance: "light",
|
||||
|
||||
markdown: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { DefaultTheme, LocaleSpecificConfig } from "vitepress";
|
||||
|
||||
export const META_URL = "https://www.djangoadmin.cn";
|
||||
export const META_TITLE = "企业级中台解决方案";
|
||||
export const META_DESCRIPTION = "企业级中台解决方案";
|
||||
export const META_URL = "http://docs.tornado.antdvue.djangoadmin.cn";
|
||||
export const META_TITLE = "DjangoAdmin Tornado+AntDesign 开发文档";
|
||||
export const META_DESCRIPTION =
|
||||
"DjangoAdmin Tornado+AntDesign 企业级中台解决方案开发者文档";
|
||||
|
||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
description: META_DESCRIPTION,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
head:
|
||||
- - meta
|
||||
- name: robots
|
||||
content: noindex
|
||||
---
|
||||
|
||||
# 页面不存在
|
||||
|
||||
抱歉,您访问的页面不存在或已被移动。
|
||||
|
||||
[返回首页](/) · [浏览文档](/zh/introduction)
|
||||
@@ -5,7 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs": "vitepress dev .",
|
||||
"docs:build": "vitepress build .",
|
||||
"docs:llms": "node scripts/generate-llms-txt.mjs",
|
||||
"docs:build": "node scripts/generate-llms-txt.mjs && vitepress build .",
|
||||
"docs:preview": "vitepress preview ."
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# DjangoAdmin Tornado+AntDesign 开发文档
|
||||
|
||||
> DjangoAdmin Tornado+AntDesign 企业级中台解决方案开发者文档。内置 RBAC 权限体系、代码生成器等开箱即用功能,支持五种数据库一键切换。本文档共 138 篇,按章节组织。
|
||||
|
||||
## 1. 了解项目
|
||||
|
||||
- [更新日志](http://docs.tornado.antdvue.djangoadmin.cn/zh/changelog.html): 更新日志是项目演进的完整记录,帮助开发者和企业了解每个版本的能力变化与技术演进方向。
|
||||
- [新手入门指南](http://docs.tornado.antdvue.djangoadmin.cn/zh/course.html): 详细的安装步骤请参考 环境准备 章节。
|
||||
- [项目介绍](http://docs.tornado.antdvue.djangoadmin.cn/zh/introduction.html): DjangoAdmin 是企业级中台解决方案,后端基于 Tornado + SQLAlchemy,前端基于 Vue3 + AntDesign,内置 RBAC 权限、代码生成器等开箱即用功能,
|
||||
- [项目目录结构](http://docs.tornado.antdvue.djangoadmin.cn/zh/struct.html): + 保持一致性:目录结构一旦确定,应保持一致性,避免随意更改。
|
||||
- [为什么选择我们?](http://docs.tornado.antdvue.djangoadmin.cn/zh/why.html): 本框架提供开箱即用的企业级中台完整解决方案,免去从零搭建成本,助力中大型企业构建统一的数字化基座。
|
||||
|
||||
## 2. 快速入门
|
||||
|
||||
- [后端启动](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/backend.html): 启动后端前,请确保已完成 环境准备 章节中的所有软件安装。
|
||||
- [数据库初始化](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/database.html): 数据库初始化前,请确保已完成以下准备工作: 1. 目标数据库服务已启动(如 MySQL 8.0+) 2. .env 文件中的 DB_DRIVER 和数据库连接信息已正确配置 3. Python
|
||||
- [Docker 快速部署](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/docker.html): Docker 部署适合快速体验和生产环境。
|
||||
- [环境变量速查表](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/env-vars.html): 1. .env.example 是环境变量模板文件,已提交到版本控制。
|
||||
- [环境准备](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/environment.html): 1. 部分软件安装后需要设置本地环境变量,便于在终端中使用命令。
|
||||
- [常见问题FAQ](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/faq.html): 遇到问题时,建议先查看终端日志输出和浏览器控制台(F12)的错误信息,通常能快速定位问题原因。
|
||||
- [第一个接口调试](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/first-api.html): 开始调试前,请确保已完成以下步骤: 1. 后端服务已启动(参见 后端启动) 2. 数据库已初始化(参见 数据库初始化) 3. Redis 服务已启动
|
||||
- [前端启动](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume1/frontend.html): 启动前端前,请确保已完成 环境准备 章节中的 Node.js 和 pnpm 安装,并已启动后端服务(参见 后端启动)。
|
||||
|
||||
## 3. 模块开发实战
|
||||
|
||||
- [完整代码清单](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/code-list.html): 本页汇总友链(Link)模块涉及的全部文件代码,开发时可快速查阅与复制对照。
|
||||
- [示例模块(Example)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/example.html): src/modules/example/ 是一个完整的示例模块,演示了标准 CRUD 模块的六文件结构和开发模式。
|
||||
- [常见问题与排错](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/faq.html): 本页汇总模块开发过程中的常见问题与排错方法,开发受阻时按症状索引即可定位。
|
||||
- [前端 API 层](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/fe-api.html): 前端 API 层封装后端接口的 HTTP 请求,每个业务模块对应一个 API 文件,本页讲解基于 Axios 封装的编写规范。
|
||||
- [前端路由与菜单](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/fe-router.html): 本页讲解前端路由与菜单机制——路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
- [前端页面视图](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/fe-view.html): 前端页面视图是用户直接交互的界面,本页讲解 Vue3 + AntDesign 下一个完整 CRUD 页面的文件组成与编写方式。
|
||||
- [请求处理器(Handler)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/handler.html): Handler 层是 HTTP 接口层,负责接收请求、校验权限与记录操作日志,编写新接口从本页开始。
|
||||
- [数据模型(Model)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/model.html): BaseModel 是所有业务模型的基类,提供6个通用审计字段和 to_dict() 序列化方法。
|
||||
- [概述与目标](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/overview.html): 其中 id、create_user、create_time、update_user、update_time、is_delete 由基类 BaseModel 自动提供,业务字段仅需定义 name、
|
||||
- [权限节点配置](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/permission.html): 实际开发中,查询类权限(page、detail、list)通常合并为一个节点 sys:position:list,减少配置工作量。
|
||||
- [Repository 层](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/repository.html): Repository 层是数据访问层,封装全部数据库查询操作,本页讲解基于 BaseRepository 基类的通用 CRUD 用法。
|
||||
- [路由注册](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/router.html): 新模块只需两步: 1. 在 src/api/v1/ 下创建路由文件,定义 routes 列表 2. 在 router.py 的 _RESOURCE_MODULES 中追加模块导入 路由前缀
|
||||
- [Pydantic Schema](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/schema.html): Schema 层负责请求数据校验,本页讲解基于 Pydantic v2 BaseModel 与 Field 的表单类定义规范。
|
||||
- [Service 层](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume2/service.html): repo_cls 是 类属性,值为 Repository 类(不是实例)。
|
||||
|
||||
## 4. 架构设计
|
||||
|
||||
- [核心基类体系](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/base-classes.html): 四大基类各司其职,覆盖从数据模型到请求校验再到业务逻辑的完整链路。
|
||||
- [缓存架构设计](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/cache.html): 采用两级缓存架构:第一级为进程内字典缓存(dict_util),第二级为 Redis 分布式缓存。
|
||||
- [数据库设计](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/database-design.html): 所有业务表继承 base_model 基类,自动获得 6 个通用字段(id / create_user / create_time / update_user / update_time /
|
||||
- [目录结构设计](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/directory.html): 目录结构遵循「按功能分组、按职责分层」的原则。
|
||||
- [扩展性设计](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/extensibility.html): 遵循开闭原则(OCP):对扩展开放,对修改关闭。
|
||||
- [分层架构设计](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/layer-arch.html): 采用经典的分层架构,核心原则是:依赖单向、职责单一、接口隔离。
|
||||
- [整体架构图](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/overview.html): 本页从全局视角阐述系统整体技术架构,涵盖前端、后端、数据库与基础设施等核心组成,理解系统全貌时从本页开始。
|
||||
- [请求完整链路](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/request-flow.html): 一个典型的 API 请求经过以下阶段:浏览器 → Nginx → Tornado → Handler 生命周期(set_default_headers → prepare →
|
||||
- [安全架构设计](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/security.html): 纵深防御:认证、授权、限流、参数校验多层防护,单一防线失效时仍有其他机制兜底 最小权限:用户仅能访问已授权接口,角色权限按需分配,遵循"够用即可"原则 数据不落地:敏感信息(Token、
|
||||
- [技术选型说明](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/tech-stack.html): Tornado 是 Python 生态中成熟的异步 Web 框架,基于自有事件循环实现非阻塞 I/O。
|
||||
- [Tornado IOLoop 与异步模型](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume3/tornado-async.html): Tornado 基于单线程事件循环(IOLoop),Handler 方法默认为协程(async def)。
|
||||
|
||||
## 5. 开发指南
|
||||
|
||||
- [API 接口速查表](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/api-cheatsheet.html): API 接口速查表,按模块分组列出所有 RESTful 接口的请求方法、路径、说明和权限标识。
|
||||
- [文章管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/article.html): 文章管理模块支持富文本编辑、分类关联、封面图片上传、图集管理等功能。
|
||||
- [分类管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/category.html): 分类管理模块用于管理树形分类数据,如文章分类、产品分类等。
|
||||
- [行政区划管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/city.html): 行政区划管理模块提供省市区三级行政区划数据的查询和管理能力,支持树形结构展示和下拉选择。
|
||||
- [公告与链接管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/content.html): 公告、链接、城市是三个简单 CRUD 内容模块,均继承 BaseService,提供标准的增删改查接口。
|
||||
- [部门/职级/岗位管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/dept.html): 部门、职级、岗位是组织架构管理的三个核心模块。
|
||||
- [文件模板管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/file-template.html): 文件模板管理模块提供文件导入导出模板的管理能力,用于维护各种业务场景的 Excel/Word 等模板文件。
|
||||
- [定时任务日志](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/job-log.html): 定时任务日志模块记录定时任务的执行历史,包括执行时间、执行结果、耗时等信息。
|
||||
- [定时任务](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/job.html): 定时任务模块基于 APScheduler 实现,支持 Cron 表达式调度、任务执行日志、动态启停等功能。
|
||||
- [菜单管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/menu.html): 菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。
|
||||
- [参数管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/param.html): 参数管理模块用于管理动态系统参数,与系统配置模块类似但更侧重于业务参数的灵活配置。
|
||||
- [角色管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/role.html): 角色是 RBAC 权限模型的核心,连接用户和菜单(权限)。
|
||||
- [用户管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/business/user.html): 用户管理是系统的核心模块,属于复杂业务模块。
|
||||
- [认证流程实战](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/auth-flow.html): 登录 → Token 获取 → 权限校验 → Token 刷新 → 退出登录的完整认证链路实战说明。
|
||||
- [验证码机制](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/captcha.html): 验证码是防止自动化攻击的重要安全手段。
|
||||
- [系统配置管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/config.html): 系统配置模块用于管理动态的键值对配置项,与数据字典类似但更偏向系统级参数。
|
||||
- [CORS 跨域配置](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/cors.html): CORS(Cross-Origin Resource Sharing)跨域资源共享是前后端分离架构必须处理的问题。
|
||||
- [演示模式](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/demo-mode.html): 演示模式用于在线体验环境,开启后所有写操作(新增、编辑、删除等)将被拦截,返回"演示环境,暂无操作权限"提示。
|
||||
- [数据字典使用指南](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/dict-guide.html): 数据字典的完整使用流程,从新增字典类型到在代码中使用字典数据的实操指南。
|
||||
- [字典模块](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/dict.html): 数据字典用于管理系统中的枚举值和配置项,如状态、类型、性别等。
|
||||
- [异常与响应处理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/exception.html): 定义了统一的响应格式和全局异常处理机制。
|
||||
- [优雅关闭](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/graceful-shutdown.html): 优雅关闭确保应用在收到终止信号时,能够完成正在处理的请求、关闭数据库连接、停止定时任务调度器后再退出,避免请求中断和资源泄漏。
|
||||
- [用户认证(JWT)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/jwt.html): JWT(JSON Web Token)是的核心认证机制。
|
||||
- [登录日志](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/login-log.html): 登录日志记录每次登录尝试(成功/失败),包含登录账号、IP地址、浏览器信息、登录时间等。
|
||||
- [登录防爆破](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/login-protect.html): 登录防爆破机制基于 Redis 实现同一 IP + 用户名维度的连续失败计数与锁定,防止暴力破解密码。
|
||||
- [操作日志](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/operation-log.html): 操作日志装饰器自动记录写请求(POST/PUT/DELETE/PATCH)的操作日志,无需手动埋点。
|
||||
- [权限管理(RBAC)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/rbac.html): 采用基于角色的访问控制(RBAC)模型,通过用户-角色-菜单三层关系实现细粒度的权限管理。
|
||||
- [链路追踪(TraceID)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/trace-id.html): 链路追踪通过为每个请求分配唯一的 TraceID,将同一请求的所有日志串联起来,方便问题排查和性能分析。
|
||||
- [参数验证(Pydantic)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/core/validator.html): 使用 Pydantic v2 进行请求参数验证。
|
||||
- [API 层开发](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/api.html): 前端 API 层位于 src/api/ 目录,封装所有与后端的 HTTP 请求。
|
||||
- [前端构建](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/build.html): 前端使用 Vite 构建,支持开发服务器和生产构建。
|
||||
- [公共组件库](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/components.html): 项目提供丰富的公共组件,位于 ui/src/components/ 目录,基于 AntDesign 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。
|
||||
- [BasicForm 表单](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/form.html): BasicForm 位于 src/components/Form/src/BasicForm.vue,基于 AntDesign 的 a-form 二次封装,通过 schemas 配置化定义表单字段,
|
||||
- [Hooks 组合式函数](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/hooks.html): 项目封装了一系列组合式函数(Hooks),位于 src/hooks/ 和 src/utils/ 目录,提供权限检查、异步 loading、防重复提交、实时时钟、项目配置等可复用逻辑。
|
||||
- [国际化](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/i18n.html): 当前项目仅支持中文界面,AntDesign 使用中文语言包(zh-cn),所有界面文本直接硬编码在组件中。
|
||||
- [Modal 弹窗](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/modal.html): 项目提供 BasicModal 组件(src/components/Modal/src/BasicModal.vue)和原生 a-modal 两种弹窗用法。
|
||||
- [权限指令 v-perm](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/perm-directive.html): v-perm 和 v-perms 是项目自定义的 Vue 指令,用于按钮级别的权限控制,无权限时自动从 DOM 中移除元素。
|
||||
- [路由与菜单](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/router.html): 前端路由基于 Vue Router 4 实现动态路由生成,菜单数据从后端获取,通过 generator-routers.ts 递归生成路由表,路由守卫控制登录状态和权限。
|
||||
- [前端环境与结构](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/setup.html): 前端基于 Vue3 + AntDesign 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
|
||||
- [Store 状态管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/store.html): 前端使用 Pinia 进行状态管理,核心模块包括 user(用户信息与权限)、asyncRoute(动态路由与菜单)、projectSetting(项目配置)、designSetting(主题设计)等。
|
||||
- [样式与主题](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/style.html): 前端样式方案采用 Tailwind CSS + SCSS + AntDesign 主题覆盖,支持深色模式切换。
|
||||
- [BasicTable 表格](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/table.html): BasicTable 位于 src/components/Table/src/Table.vue,基于 AntDesign 的 a-table 二次封装,提供列定义、数据请求、分页、操作列、多选、
|
||||
- [工具函数](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/utils.html): 前端工具函数位于 src/utils/ 目录,提供认证管理、本地存储、日期格式化、树结构操作、文件下载等通用能力。
|
||||
- [页面视图开发](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/frontend/view.html): 页面视图位于 src/views/ 目录,每个业务模块遵循 index.vue(列表页)+ edit.vue(编辑弹窗)的标准模式,辅以 columns.ts(列定义)和
|
||||
- [接口响应规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/integration/api-contract.html): 后端所有接口统一返回 R.ok() / R.failed() 格式的 JSON 响应,前端通过 VAxios 的 transformRequestData 拦截器统一解包处理。
|
||||
- [字典数据联调](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/integration/dict-sync.html): 字典数据通过后端字典 API 获取,前端在下拉选择器中渲染选项。
|
||||
- [权限节点联动](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/integration/perm-sync.html): 前后端权限通过统一的权限节点字符串联动,后端 @permission_required 校验权限,前端 v-perm 指令和 TableAction 的 auth 属性控制按钮显示。
|
||||
- [代理配置](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/integration/proxy.html): 开发环境通过 Vite 代理转发 API 请求到后端,生产环境通过 Nginx 代理。
|
||||
- [文件上传联调](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/integration/upload-sync.html): 文件上传涉及前端 Upload 组件、后端上传接口和 URL 拼接三个环节,需要前后端协同配置。
|
||||
- [请求上下文(ContextVar)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/context.html): src/core/context.py 使用 Python 标准库 contextvars 管理请求级别的上下文信息,包括当前用户 ID、用户名、姓名和链路追踪 ID。
|
||||
- [数据库会话](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/db-session.html): 数据库会话通过 scoped_session 实现线程级隔离,Repository 层的写操作自动 commit,BaseHandler.on_finish() 兜底关闭会话。
|
||||
- [字典缓存工具](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/dict-util.html): src/common/utils/dict_util.py 提供业务字典的缓存读取能力,从 Redis 缓存中获取字典数据,缓存未命中时从数据库加载。
|
||||
- [导入导出](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/excel.html): 基于 openpyxl 实现 Excel 导入导出功能,支持通过 file_template 模块管理导入模板配置。
|
||||
- [通用 Helper 函数](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/helpers.html): src/common/utils/helpers.py 提供项目中广泛使用的通用工具函数,包括 ID 解析、OS/浏览器检测、参数脱敏等。
|
||||
- [IP 归属地解析](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/ip-region.html): src/common/utils/ip2region.py 基于 ip2region 离线数据库,提供 IP 地址到省市区的归属地查询能力,无需网络请求。
|
||||
- [日志系统](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/logger.html): 通过 Python 标准库 logging 实现结构化日志,支持控制台 + 文件双输出,日志文件按大小自动轮转。
|
||||
- [密码工具](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/password.html): 采用 bcrypt 双重加盐方案存储密码,源码位于 src/core/password.py。
|
||||
- [权限缓存](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/perm-cache.html): 通过 Redis 实现用户权限和菜单的缓存,采用用户级缓存 + 全局版本号双层策略。
|
||||
- [统一响应封装(R 对象)](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/response.html): src/core/response.py 提供统一的 API 响应封装,所有接口返回格式为 {code, data, msg, ok}。
|
||||
- [富文本过滤](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/rich-text.html): 富文本编辑器内容存在存储型 XSS 风险,通过 bleach 库实现 HTML 白名单清洗,在落盘前统一过滤危险内容。
|
||||
- [文件上传完整流程](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/upload-guide.html): 富文本编辑器(如 TinyMCE、CKEditor、WangEditor 等)的图片上传同样使用此接口。
|
||||
- [文件上传](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume4/tools/upload.html): 提供本地文件上传功能,支持图片和通用文件上传,内置扩展名白名单、大小限制、魔术字节校验和路径遍历防护。
|
||||
|
||||
## 6. 代码生成器
|
||||
|
||||
- [CLI 命令行使用](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/cli.html): 代码生成器 CLI 模式的使用方法,包括单表生成、批量生成、预览模式和配置导出。
|
||||
- [代码生成引擎](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/code-engine.html): 代码生成引擎的工作原理,包括配置解析、模板渲染、文件写入和自动注册的完整流程。
|
||||
- [配置文件详解](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/config.html): 代码生成器 JSON 配置文件的格式说明,包括模块名称、字段属性、字典编码等配置项的详细解释。
|
||||
- [实战案例](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/demo.html): 以 tornado_example 演示表为完整案例,演示从建表到生成到使用的全流程。
|
||||
- [常见问题](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/faq.html): 代码生成器使用过程中常见问题的原因分析和解决方案。
|
||||
- [概述与架构](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/overview.html): 代码生成器的整体架构、核心模块和生成流程概述。
|
||||
- [表结构解析引擎](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/parser.html): 表结构解析引擎的工作原理,从数据库读取表元数据并智能推断字段用途的过程。
|
||||
- [自定义模板](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/templates.html): 代码生成器模板的 Jinja2 语法说明,包括模板变量、字段属性和自定义模板的开发方法。
|
||||
- [Web 管理界面](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume5/web-ui.html): 代码生成器 Web 管理界面的使用方法,通过可视化界面操作单表生成和批量生成。
|
||||
|
||||
## 7. 运维部署
|
||||
|
||||
- [备份与恢复](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/backup.html): - 3 份数据副本(1 份生产 + 2 份备份) - 2 种不同存储介质(如本地磁盘 + 远程存储) - 1 份异地备份(防机房级灾难)
|
||||
- [跨库数据迁移](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/db-cross-migrate.html): 项目提供 scripts/migrate_db.py 脚本,支持在不同数据库之间迁移数据(如 MySQL → PostgreSQL / SQL Server / SQLite / Oracle)。
|
||||
- [数据库迁移](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/db-migrate.html): - 结构迁移:Alembic(自动生成 DDL 差异) - 数据迁移:scripts/migrate_db.py(跨库数据泵) - 初始化:scripts/init_db.py(全新安装一键建库)
|
||||
- [部署 Checklist](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/deploy-checklist.html): 从服务器准备到应用上线的完整部署操作清单,适用于生产环境首次部署和后续版本更新。
|
||||
- [Docker容器化](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/docker.html): - 环境一致性:消除"在我机器上能跑"的问题 - 快速部署:一条命令启动完整服务 - 资源隔离:应用与宿主机环境解耦
|
||||
- [环境规划](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/env-plan.html): 各环境之间保持代码一致,仅通过环境变量(.env)区分配置。
|
||||
- [健康检查与监控](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/monitoring.html): `` 1. 应用层:健康检查端点、业务指标 2. 系统层:CPU/内存/磁盘/网络 3. 中间件:MySQL/Redis 连接状态 4. 日志:集中式日志收集与分析 ``
|
||||
- [Nginx 反向代理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/nginx.html): `` 客户端 → Nginx(80/443) ├─ / → 前端静态资源(dist/) ├─ /api/v1/... → Tornado 后端(8041) └─ /api/file/ →
|
||||
- [性能调优](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/performance.html): 1. 先测量,后优化:用数据说话,避免盲目调参 2. 瓶颈优先:找到最大瓶颈,集中优化 3. 渐进式:每次只调整一个参数,观察效果
|
||||
- [安全加固](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/security-hardening.html): - 最小权限:只开放必要的端口和权限 - 纵深防御:多层安全措施叠加 - 默认安全:配置默认值应为安全选项
|
||||
- [Supervisor进程管理](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/supervisor.html): - Linux 服务器直接部署(非 Docker) - 需要进程自动重启和日志轮转 - 管理多个相关进程
|
||||
- [故障排查手册](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/troubleshooting.html): 1. 查看日志:tail -f logs/supervisor.out.log 或 docker-compose logs -f 2. 检查配置:.env 文件、数据库连接、Redis 连接 3.
|
||||
- [版本升级流程](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume6/upgrade.html): 1. 先备份,后升级 2. 先测试环境,后生产环境 3. 每次升级可回滚 4. 低峰期执行升级
|
||||
|
||||
## 8. 规范标准
|
||||
|
||||
- [API响应规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/api-response.html): - 所有接口必须通过 R.ok() 或 R.failed() 返回,禁止直接返回 dict - 成功响应 code=0,失败响应 code=1 - 响应体始终包含 code、data、msg、ok
|
||||
- [代码审查清单](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/code-review.html): - 每次提交必须经过至少一人审查 - 审查关注点:正确性 > 安全性 > 可维护性 > 性能 - 审查意见应具体、可操作,避免主观评价
|
||||
- [删除规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/deletion.html): - is_delete = 0:正常记录(默认值) - is_delete = 1:已删除记录 - 所有查询方法自动过滤 is_delete = 0,业务层无需关心
|
||||
- [文档规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/doc-standard.html): - 文档与代码同步更新 - 使用中文编写(项目约定) - 示例代码可直接运行
|
||||
- [前端 API 规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/fe-api.html): - 每个后端模块对应一个 API 文件 - 函数命名统一使用 名词 + 动词 格式(与后端保持一致) - 请求路径使用相对路径(/position/page),不含 /api/v1 前缀(由
|
||||
- [前端页面规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/fe-component.html): - Vue3(Composition API + <script setup>) - AntDesign - 自定义组件(BasicTable / BasicForm / TableAction /
|
||||
- [Git提交规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/git.html): `` <type>(<scope>): <subject> <body> <footer> ``
|
||||
- [Handler 层规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/handler.html): - Handler 只做请求分发,不含业务逻辑 - 通过装饰器控制权限和演示模式 - 委托 Service 处理业务逻辑 - 返回 R 封装的响应
|
||||
- [分页规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/pagination.html): - 前端传递 pageNo(页码,从 1 开始)和 pageSize(每页条数) - 后端返回 {records, total, size, current, pages} 结构 - 默认每页 10
|
||||
- [Pydantic Schema规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/pydantic.html): - 请求体参数必须声明为 Pydantic 模型(Form 类) - 字段约束使用 Field(...) 定义 - 自定义校验使用 @field_validator - 唯一性校验在 Schema
|
||||
- [Repository层规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/repository.html): - Repository 只做数据访问原语,不处理 R 封装/序列化/业务逻辑 - 软删除由基类自动处理,子类无需关心 - 查询方法自动跳过 None 值条件
|
||||
- [Service层规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/service.html): BaseService 采用模板方法模式:基类定义流程骨架,子类通过声明差异点和覆盖钩子定制行为。
|
||||
- [测试规范](http://docs.tornado.antdvue.djangoadmin.cn/zh/volume7/testing.html): 1. 契约测试(Contract):验证路由、权限、表名等契约不变 2. 单元测试(Unit):验证单个函数/方法的逻辑正确性 3. 集成测试(Integration):验证模块间协作、真实数据库操作
|
||||
|
||||
## 附录
|
||||
|
||||
- [常见问题汇总](http://docs.tornado.antdvue.djangoadmin.cn/zh/faq.html): 本页汇总了项目各模块的常见问题及解决方案,按场景分类索引。
|
||||
- [免责声明](http://docs.tornado.antdvue.djangoadmin.cn/zh/other/declare.html): 使用本产品前必读的法律声明,明确产品性质、使用限制与责任划分,一旦使用本产品即视为接受本声明全部条款。
|
||||
- [广告赞助](http://docs.tornado.antdvue.djangoadmin.cn/zh/other/sponsor.html): 1. 投放广告不得与官方全系产品存在结构性冲突和竞争 2. 广告内容需符合国家法律法规,不得包含虚假、误导性信息 3. 广告素材需经官方审核通过后方可上线,审核周期 1-3 个工作日 4.
|
||||
@@ -0,0 +1,33 @@
|
||||
# DjangoAdmin Tornado+AntDesign 开发文档
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# 显式放行生成式引擎 / AI 爬虫(GEO)
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Allow: /
|
||||
|
||||
User-agent: ClaudeBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-Web
|
||||
Allow: /
|
||||
|
||||
User-agent: anthropic-ai
|
||||
Allow: /
|
||||
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Allow: /
|
||||
|
||||
User-agent: Bytespider
|
||||
Allow: /
|
||||
|
||||
User-agent: bingbot
|
||||
Allow: /
|
||||
|
||||
Sitemap: http://docs.tornado.antdvue.djangoadmin.cn/sitemap.xml
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 生成 public/llms.txt(llmstxt.org 标准)——AI 爬虫的站点摘要索引。
|
||||
*
|
||||
* 数据源:zh 目录下全部 Markdown 的 H1 + :::tip 概述首句(与 meta
|
||||
* description 同源,复用 .vitepress/config/extract.mjs)。按侧边栏章节分组。
|
||||
*
|
||||
* 用法:node scripts/generate-llms-txt.mjs
|
||||
* 已挂入 package.json 的 docs:build 前置步骤。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
SITE_URL,
|
||||
SITE_DESCRIPTION,
|
||||
extractDescription,
|
||||
} from "../.vitepress/config/extract.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ZH = path.join(ROOT, "zh");
|
||||
|
||||
/** 顶层目录 → 侧边栏章节名(与 .vitepress/config/zh.ts 保持一致) */
|
||||
const SECTIONS = [
|
||||
["", "1. 了解项目"],
|
||||
["volume1", "2. 快速入门"],
|
||||
["volume2", "3. 模块开发实战"],
|
||||
["volume3", "4. 架构设计"],
|
||||
["volume4", "5. 开发指南"],
|
||||
["volume5", "6. 代码生成器"],
|
||||
["volume6", "7. 运维部署"],
|
||||
["volume7", "8. 规范标准"],
|
||||
["other", "附录"],
|
||||
];
|
||||
|
||||
function walk(dir, out = []) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(p, out);
|
||||
else if (entry.name.endsWith(".md")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 提取首个 H1 文本 */
|
||||
function h1Of(md) {
|
||||
// 围栏感知:跳过 ``` 代码块内的 # 注释,取真正的页面 H1
|
||||
let inFence = false;
|
||||
for (const line of md.split("\n")) {
|
||||
if (line.startsWith("```")) { inFence = !inFence; continue; }
|
||||
if (!inFence && line.startsWith("# ") && !line.startsWith("## ")) {
|
||||
return line.slice(2).trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/** H1 编号排序键:"5.10 x" → [5,10];无编号排最后 */
|
||||
function sortKey(h1) {
|
||||
const m = h1.match(/^(\d+(?:\.\d+)*)/);
|
||||
if (m) return m[1].split(".").map(Number);
|
||||
const letter = h1.match(/^([A-Z])\./);
|
||||
if (letter) return [1000 + letter[1].charCodeAt(0)];
|
||||
return [9999];
|
||||
}
|
||||
|
||||
function cmpKey(a, b) {
|
||||
const len = Math.max(a.length, b.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const x = a[i] ?? 0;
|
||||
const y = b[i] ?? 0;
|
||||
if (x !== y) return x - y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const files = walk(ZH);
|
||||
const groups = new Map(SECTIONS.map(([dir, label]) => [label, []]));
|
||||
|
||||
for (const file of files) {
|
||||
const rel = path.relative(ZH, file).replace(/\\/g, "/");
|
||||
const topDir = rel.includes("/") ? rel.split("/")[0] : "";
|
||||
const base = rel.split("/").pop();
|
||||
const label = (topDir === "" && base === "faq.md") ? "附录"
|
||||
: SECTIONS.find(([dir]) => dir === topDir)?.[1] ?? "附录";
|
||||
|
||||
const md = fs.readFileSync(file, "utf8");
|
||||
const h1 = h1Of(md);
|
||||
if (!h1) continue;
|
||||
const desc = extractDescription(md);
|
||||
const url = `${SITE_URL}/zh/${rel.replace(/\.md$/, ".html")}`;
|
||||
groups.get(label).push({ h1, desc, url, key: sortKey(h1) });
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"# DjangoAdmin Tornado+AntDesign 开发文档",
|
||||
"",
|
||||
`> ${SITE_DESCRIPTION}。内置 RBAC 权限体系、代码生成器等开箱即用功能,支持五种数据库一键切换。本文档共 ${files.length} 篇,按章节组织。`,
|
||||
"",
|
||||
];
|
||||
|
||||
// 按章节顺序输出,合并重复 label(appendix/other 同为「附录」,只输出一次)
|
||||
const printed = new Set();
|
||||
for (const [, label] of SECTIONS) {
|
||||
if (printed.has(label)) continue;
|
||||
const items = groups.get(label);
|
||||
if (!items?.length) continue;
|
||||
printed.add(label);
|
||||
|
||||
lines.push(`## ${label}`, "");
|
||||
items.sort((a, b) => cmpKey(a.key, b.key));
|
||||
for (const { h1, desc, url } of items) {
|
||||
lines.push(desc ? `- [${h1}](${url}): ${desc}` : `- [${h1}](${url})`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
const out = path.join(ROOT, "public", "llms.txt");
|
||||
fs.writeFileSync(out, lines.join("\n"), "utf8");
|
||||
console.log(
|
||||
`llms.txt: ${files.length} 页 → ${path.relative(ROOT, out)} (${printed.size} 个章节)`,
|
||||
);
|
||||
@@ -1,3 +1,5 @@
|
||||
# 新手入门指南
|
||||
|
||||
:::warning 特别提醒
|
||||
官方精心制作本教程,目的在于方便用户快速掌握软件产品的使用和部署。通过此教程,刚入行的开发者也可以快速掌握并投入产品研发。本地部署时务必请耐心阅读文档操作。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 项目介绍
|
||||
|
||||
:::tip 概述
|
||||
DjangoAdmin 是企业级中台解决方案,后端基于 Tornado + SQLAlchemy,前端基于 Vue3 + AntDesign,内置 RBAC 权限、代码生成器等开箱即用功能,支持五种数据库一键切换。
|
||||
:::
|
||||
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<img src="/logo.png" height="150" alt="logo"/>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 免责声明
|
||||
|
||||
:::tip 概述
|
||||
使用本产品前必读的法律声明,明确产品性质、使用限制与责任划分,一旦使用本产品即视为接受本声明全部条款。
|
||||
:::
|
||||
|
||||
:::danger 重要提示
|
||||
任何用户在使用本团队研发的系列软件产品(以下简称「本产品」)前,请仔细阅读并透彻理解本声明。选择不使用本产品,可随时停止使用;一旦使用本产品,即视为对本声明全部内容的认可和接受。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 项目目录结构
|
||||
|
||||
## 目录说明
|
||||
|
||||
在软件开发过程中,优秀的软件目录结构对于项目的组织、开发、维护和扩展至关重要,合理的目录结构能够显著提升开发效率和代码可维护性。
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 完整代码清单
|
||||
|
||||
:::tip 概述
|
||||
本页汇总友链(Link)模块涉及的全部文件代码,开发时可快速查阅与复制对照。
|
||||
:::
|
||||
|
||||
本章汇总友链(Link)模块涉及的全部文件,便于开发时快速查阅和复制。
|
||||
|
||||
## 后端文件清单
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 常见问题与排错
|
||||
|
||||
:::tip 概述
|
||||
本页汇总模块开发过程中的常见问题与排错方法,开发受阻时按症状索引即可定位。
|
||||
:::
|
||||
|
||||
本章汇总模块开发过程中的常见问题和解决方案。
|
||||
|
||||
## 后端问题
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 前端 API 层
|
||||
|
||||
:::tip 概述
|
||||
前端 API 层封装后端接口的 HTTP 请求,每个业务模块对应一个 API 文件,本页讲解基于 Axios 封装的编写规范。
|
||||
:::
|
||||
|
||||
前端 API 层负责封装后端接口的 HTTP 请求。每个业务模块对应一个 API 文件,使用项目封装的 Axios 工具发送请求。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 前端路由与菜单
|
||||
|
||||
:::tip 概述
|
||||
本页讲解前端路由与菜单机制——路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
:::
|
||||
|
||||
前端路由定义页面的访问路径,菜单是路由的可视化展示。项目的路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
|
||||
## 路由配置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 前端页面视图
|
||||
|
||||
:::tip 概述
|
||||
前端页面视图是用户直接交互的界面,本页讲解 Vue3 + AntDesign 下一个完整 CRUD 页面的文件组成与编写方式。
|
||||
:::
|
||||
|
||||
前端页面视图是用户直接交互的界面。项目使用 Vue3 + AntDesign + Vite 构建。一个完整的 CRUD 页面由 4 个文件组成:
|
||||
|
||||
```
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 请求处理器(Handler)
|
||||
|
||||
:::tip 概述
|
||||
Handler 层是 HTTP 接口层,负责接收请求、校验权限与记录操作日志,编写新接口从本页开始。
|
||||
:::
|
||||
|
||||
Handler 层是 HTTP 接口层,负责接收请求、校验权限、记录操作日志,然后委托给 Service 层处理。每个业务模块对应一个 `handlers.py` 文件,所有 Handler 继承 `BaseHandler`。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Repository 层
|
||||
|
||||
:::tip 概述
|
||||
Repository 层是数据访问层,封装全部数据库查询操作,本页讲解基于 BaseRepository 基类的通用 CRUD 用法。
|
||||
:::
|
||||
|
||||
Repository 层是数据访问层,封装所有数据库查询操作。项目通过 `BaseRepository` 基类提供通用 CRUD 能力,子类通常只需声明类名,无需编写任何代码。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Pydantic Schema
|
||||
|
||||
:::tip 概述
|
||||
Schema 层负责请求数据校验,本页讲解基于 Pydantic v2 BaseModel 与 Field 的表单类定义规范。
|
||||
:::
|
||||
|
||||
Schema 层负责请求数据的校验。项目使用 Pydantic v2 的 `BaseModel` 定义表单类,通过 `Field` 约束字段格式,Tornado 在接收请求体时自动执行校验。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 整体架构图
|
||||
|
||||
:::tip 概述
|
||||
本页从全局视角阐述系统整体技术架构,涵盖前端、后端、数据库与基础设施等核心组成,理解系统全貌时从本页开始。
|
||||
:::
|
||||
|
||||
本章从全局视角阐述系统的整体技术架构,涵盖前端、后端、数据库及基础设施等核心组成部分。
|
||||
|
||||
## 架构总览
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 文章管理
|
||||
|
||||
:::tip 文章管理
|
||||
文章管理模块支持富文本编辑、分类关联、封面图片上传、图集管理等功能。适用于新闻、公告、帮助文档等内容管理场景。本模块是典型的「带文件处理」业务模块,涵盖了封面迁移、图集处理、富文本 XSS 清洗等核心机制。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分类管理
|
||||
|
||||
:::tip 分类管理
|
||||
分类管理模块用于管理树形分类数据,如文章分类、产品分类等。通过 `parent_id` 字段实现无限级分类。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 公告与链接管理
|
||||
|
||||
:::tip 公告/链接/城市
|
||||
公告、链接、城市是三个简单 CRUD 内容模块,均继承 BaseService,提供标准的增删改查接口。适用于系统公告、友情链接、城市数据等场景。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 部门/职级/岗位管理
|
||||
|
||||
:::tip 部门/职级/岗位
|
||||
部门、职级、岗位是组织架构管理的三个核心模块。部门采用树形结构,职级和岗位为简单 CRUD 模块,均继承 `BaseService`。用户表通过 `dept_id`、`position_id`、`level_id` 三个外键关联这三个模块。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 定时任务
|
||||
|
||||
:::tip 定时任务
|
||||
定时任务模块基于 APScheduler 实现,支持 Cron 表达式调度、任务执行日志、动态启停等功能。包含 job(任务定义)、job_log(执行日志)、scheduler(调度器)三个子模块。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 菜单管理
|
||||
|
||||
:::tip 菜单管理
|
||||
菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。支持目录/菜单(type=0)和权限按钮节点(type=1)两种类型,通过树形结构组织。权限节点由系统根据前端勾选自动生成,采用 `sys:module:action` 三段式命名。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 参数管理
|
||||
|
||||
:::tip 参数管理
|
||||
参数管理模块用于管理动态系统参数,与系统配置模块类似但更侧重于业务参数的灵活配置。支持按参数键查询、修改,无需重启服务即可生效。本模块是典型的「BaseService 简单模块」,通过声明差异点即可获得完整 CRUD 能力。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 角色管理
|
||||
|
||||
:::tip 角色管理
|
||||
角色是 RBAC 权限模型的核心,连接用户和菜单(权限)。通过为角色分配菜单权限,再将角色分配给用户,实现灵活的权限管理。角色删除时需清理关联的用户角色和角色菜单数据,并即时失效受影响用户的权限缓存。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 用户管理
|
||||
|
||||
:::tip 用户管理
|
||||
用户管理是系统的核心模块,属于复杂业务模块。包含用户 CRUD、角色关联、密码加密、行政区划、Excel 导入导出等功能。与简单 CRUD 模块不同,用户管理使用自定义 service 函数而非继承 BaseService 的类方法。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 验证码机制
|
||||
|
||||
:::tip 验证码
|
||||
验证码是防止自动化攻击的重要安全手段。使用 Pillow 生成图片验证码,存储在 Redis 中,支持防刷限流和万能验证码(演示模式)。源码位于 `src/common/utils/captcha.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 系统配置管理
|
||||
|
||||
:::tip 系统配置
|
||||
系统配置模块用于管理动态的键值对配置项,与数据字典类似但更偏向系统级参数。支持分组管理,可通过接口动态修改,无需重启服务。模块位于 `src/modules/config/` 和 `src/modules/config_item/`,路由位于 `src/api/v1/config.py` 和 `config_item.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 演示模式
|
||||
|
||||
:::tip 演示模式
|
||||
演示模式用于在线体验环境,开启后所有写操作(新增、编辑、删除等)将被拦截,返回"演示环境,暂无操作权限"提示。通过 `TORNADO_DEMO` 环境变量控制。装饰器位于 `src/common/middleware/access_decorators.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 字典模块
|
||||
|
||||
:::tip 数据字典
|
||||
数据字典用于管理系统中的枚举值和配置项,如状态、类型、性别等。通过字典编码统一管理,前端可通过 `useDictStore` 获取字典选项,避免硬编码。后端读取工具位于 `src/common/utils/dict_util.py`,内置进程内 TTL 缓存。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 异常与响应处理
|
||||
|
||||
:::tip 全局异常与响应
|
||||
定义了统一的响应格式和全局异常处理机制。所有 API 返回 `{code, data, msg, ok}` 标准格式,异常被捕获后也转换为相同格式。响应函数位于 `src/core/response.py`,异常类型位于 `src/core/exceptions.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 用户认证(JWT)
|
||||
|
||||
:::tip JWT认证
|
||||
JWT(JSON Web Token)是的核心认证机制。用户登录成功后获取 Token,后续请求通过 `Authorization: Bearer <token>` 头携带 Token 进行身份验证。项目内置了 Token 生成、解析、刷新和黑名单管理功能,源码位于 `src/core/jwt.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 登录日志
|
||||
|
||||
:::tip 登录日志
|
||||
登录日志记录每次登录尝试(成功/失败),包含登录账号、IP地址、浏览器信息、登录时间等。支持 IP 地理位置解析(ip2region)和 User-Agent 解析。登录逻辑位于 `src/modules/auth/service.py`,日志记录位于 `src/modules/login_log/service.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 操作日志
|
||||
|
||||
:::tip 操作日志
|
||||
操作日志装饰器自动记录写请求(POST/PUT/DELETE/PATCH)的操作日志,无需手动埋点。源码位于 `src/common/middleware/operation_log.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 权限管理(RBAC)
|
||||
|
||||
:::tip RBAC权限
|
||||
采用基于角色的访问控制(RBAC)模型,通过用户-角色-菜单三层关系实现细粒度的权限管理。权限控制贯穿前后端:后端通过 `@permission_required` 装饰器校验(`src/common/middleware/access_decorators.py`),前端通过 `v-perm` 指令控制按钮显隐。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 参数验证(Pydantic)
|
||||
|
||||
:::tip 参数验证
|
||||
使用 Pydantic v2 进行请求参数验证。所有请求体参数通过 Pydantic BaseModel 定义,Tornado 自动解析和验证 JSON 请求体,验证失败返回标准格式错误信息。BaseSchema 基类位于 `src/core/base_schema.py`。
|
||||
:::
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 为什么选择我们?
|
||||
|
||||
:::tip 核心价值
|
||||
企业级数据中台不应从零搭建。本框架提供开箱即用的完整解决方案,为中大型企业构建统一的数字化基座。
|
||||
本框架提供开箱即用的企业级中台完整解决方案,免去从零搭建成本,助力中大型企业构建统一的数字化基座。
|
||||
:::
|
||||
|
||||
## 解决什么问题?
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
//
|
||||
// themeConfig: {
|
||||
// editLink: {
|
||||
// pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue",
|
||||
// pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue",
|
||||
// text: "Suggest changes to this page",
|
||||
// },
|
||||
//
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export declare const SITE_URL: string;
|
||||
export declare const SITE_DESCRIPTION: string;
|
||||
export declare function stripNumberPrefix(title: string): string;
|
||||
export declare function extractDescription(md: string): string;
|
||||
export declare function extractFaqs(md: string): { q: string; a: string }[];
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* SEO/GEO 共享工具(纯 ESM,零依赖)。
|
||||
*
|
||||
* 同时被两类调用方 import:
|
||||
* 1. VitePress 配置(.ts,esbuild 打包时内联本文件)
|
||||
* 2. scripts/generate-llms-txt.mjs(Node 直接运行)
|
||||
*
|
||||
* 注意:SITE_URL 是全站唯一 URL 真相源。
|
||||
* docs 子域当前 TLS 证书为腾讯 CDN 默认证书(*.cdn.myqcloud.com,校验失败),
|
||||
* 故暂用 http;证书修复后只需改这里一处。
|
||||
*/
|
||||
|
||||
export const SITE_URL = "http://docs.tornado.elevue.djangoadmin.cn";
|
||||
|
||||
export const SITE_DESCRIPTION =
|
||||
"DjangoAdmin Tornado+ElementPlus 企业级中台解决方案开发者文档";
|
||||
|
||||
/**
|
||||
* 剥离 H1/标题中的章节编号前缀,用于 <title> 与 JSON-LD headline。
|
||||
* 例:"5.10 字典模块" → "字典模块";"K. 贡献指南" → "贡献指南"。
|
||||
* 页内 H1 与侧边栏编号保持不变,仅影响 SEO 输出。
|
||||
*/
|
||||
export function stripNumberPrefix(title) {
|
||||
return String(title)
|
||||
.replace(/^(?:\d+(?:\.\d+)+|[A-Z]\.)\s+/, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从页面 Markdown 提取 meta description:
|
||||
* 1. 首选 `:::tip xxx` 容器内的首句(141/143 页的标准 opener)
|
||||
* 2. 回退到 H1 后的第一个普通段落
|
||||
* 输出压缩为单行、去除行内 Markdown,按中文句末标点截断(≤100 字符)。
|
||||
*/
|
||||
export function extractDescription(md) {
|
||||
const src = String(md);
|
||||
const fm = src.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
const body = fm ? src.slice(fm[0].length) : src;
|
||||
|
||||
let text = "";
|
||||
// frontmatter description 显式声明优先(无 H1 页面的补齐手段)
|
||||
const fmDesc = fm && fm[1].match(/^description:\s*(.+)$/m);
|
||||
if (fmDesc) {
|
||||
text = fmDesc[1].trim().replace(/^["']|["']$/g, "");
|
||||
}
|
||||
const tip = !text && body.match(/^:::\s*tip[^\n]*\n([\s\S]*?)\n:::/m);
|
||||
if (tip) {
|
||||
text = tip[1];
|
||||
}
|
||||
if (!text) {
|
||||
const afterH1 = body.match(/^# (?!#)(.+)$/m);
|
||||
if (afterH1) {
|
||||
const rest = body.slice((afterH1.index ?? 0) + afterH1[0].length);
|
||||
const para = rest
|
||||
.split(/\n{2,}/)
|
||||
.find(
|
||||
(p) =>
|
||||
p.trim() &&
|
||||
!/^\s*#/.test(p) &&
|
||||
!/^\s*:::/.test(p) &&
|
||||
!/^\s*```/.test(p) &&
|
||||
!/^\s*\|/.test(p),
|
||||
);
|
||||
text = para || "";
|
||||
}
|
||||
}
|
||||
|
||||
text = text
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.trim();
|
||||
|
||||
if (!text) return "";
|
||||
|
||||
// 按中文句末标点截取首句,上限 100 字符;截断时避免切碎词
|
||||
const m = text.match(/^.{1,100}?(?:[。!?!?]|$)/);
|
||||
let out = m ? m[0] : text.slice(0, 100);
|
||||
if (out.length < text.length && !/[。!?!?]$/.test(out)) {
|
||||
out = out.replace(/[^\s,、;:,;:]+$/, "");
|
||||
}
|
||||
return out.trim() || text.slice(0, 100);
|
||||
}
|
||||
|
||||
function cleanText(s) {
|
||||
return s
|
||||
.replace(/^:::[^\n]*$/gm, "")
|
||||
.replace(/^```[^\n]*$/gm, "")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取页面中的 Q/A 对(用于 FAQPage JSON-LD),支持两种站内格式:
|
||||
* A. `### Q:问题` 标题式(如 zh/quickstart/faq.md)
|
||||
* B. `**Q:问题**` 加粗式(如 zh/appendix/faq.md)
|
||||
* 返回 [{ q, a }];无匹配返回 []。
|
||||
*/
|
||||
export function extractFaqs(md) {
|
||||
const body = String(md).replace(/^---[\s\S]*?\n---\s*/, "");
|
||||
let faqs = [];
|
||||
|
||||
// 格式 A:按 `### Q:` 切块,答案截断到下一个标题
|
||||
for (const part of body.split(/(?=^###\s*Q[::])/m)) {
|
||||
const m = part.match(/^###\s*Q[::]\s*([^\n]+)\n+([\s\S]*)/);
|
||||
if (!m) continue;
|
||||
let a = m[2];
|
||||
const cut = a.search(/^#{1,4}\s/m);
|
||||
if (cut >= 0) a = a.slice(0, cut);
|
||||
faqs.push({ q: cleanText(m[1]), a: cleanText(a) });
|
||||
}
|
||||
|
||||
// 格式 B:按 `**Q:` 切块
|
||||
if (!faqs.length) {
|
||||
for (const part of body.split(/(?=^\*\*Q[::])/m)) {
|
||||
const m = part.match(/^\*\*Q[::]\s*([^*]+)\*\*\s*\n+([\s\S]*)/);
|
||||
if (!m) continue;
|
||||
let a = m[2];
|
||||
const cut = a.search(/^(?:#{1,4}\s|\*\*Q)/m);
|
||||
if (cut >= 0) a = a.slice(0, cut);
|
||||
faqs.push({ q: cleanText(m[1]), a: cleanText(a) });
|
||||
}
|
||||
}
|
||||
|
||||
return faqs.filter((f) => f.q && f.a);
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
import { enConfig } from "./en";
|
||||
import { createSeoHooks } from "./seo";
|
||||
import { sharedConfig } from "./shared";
|
||||
import { zhConfig } from "./zh";
|
||||
|
||||
const seo = createSeoHooks(zhConfig.themeConfig?.sidebar);
|
||||
|
||||
export default defineConfig({
|
||||
...sharedConfig,
|
||||
ignoreDeadLinks: [
|
||||
(link) => link.includes('localhost'),
|
||||
],
|
||||
|
||||
// SEO:页面级 title/description、canonical/OG/JSON-LD、sitemap+lastmod
|
||||
transformPageData: seo.transformPageData,
|
||||
transformHead: seo.transformHead,
|
||||
sitemap: seo.sitemap,
|
||||
|
||||
locales: {
|
||||
root: { label: "简体中文", lang: "zh-CN", link: "/", ...zhConfig },
|
||||
// root: { label: "English", lang: "en-US", link: "/", ...enConfig },
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* SEO/GEO 构建期钩子(仅 Node 构建时运行,不进客户端 bundle):
|
||||
* - transformPageData:每页独立 title(去编号前缀)与 description(取 :::tip 首句)
|
||||
* - transformHead:canonical / Open Graph / twitter 卡片 / JSON-LD 结构化数据
|
||||
* - sitemap.transformItems:注入 lastmod(git 最后提交时间)并过滤 404
|
||||
*
|
||||
* 由 config/index.ts 通过 createSeoHooks(zhConfig.themeConfig?.sidebar) 挂载。
|
||||
* 依赖 extract.mjs(纯 ESM,同被 scripts/generate-llms-txt.mjs 复用)。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
import {
|
||||
SITE_URL,
|
||||
extractDescription,
|
||||
extractFaqs,
|
||||
stripNumberPrefix,
|
||||
} from "./extract.mjs";
|
||||
|
||||
/** 与 vitepress HeadConfig 同构:[tag, attrs, innerHTML?] */
|
||||
type HeadTuple = [string, Record<string, any>, string?];
|
||||
|
||||
interface SideItem {
|
||||
text?: string;
|
||||
link?: string;
|
||||
items?: SideItem[];
|
||||
}
|
||||
|
||||
interface PageDataLike {
|
||||
relativePath?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
frontmatter?: Record<string, any>;
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
interface TransformCtx {
|
||||
pageData?: PageDataLike;
|
||||
title?: string;
|
||||
description?: string;
|
||||
content?: string;
|
||||
siteConfig?: { root?: string; srcDir?: string };
|
||||
}
|
||||
|
||||
const ORG = {
|
||||
"@type": "Organization",
|
||||
name: "DjangoAdmin",
|
||||
url: "https://www.djangoadmin.cn",
|
||||
logo: `${SITE_URL}/logo.png`,
|
||||
sameAs: [
|
||||
"https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue",
|
||||
"https://www.djangoadmin.cn",
|
||||
],
|
||||
};
|
||||
|
||||
/** 相对路径 → 站内路由(cleanUrls 关闭,正文页带 .html) */
|
||||
function routeOf(relativePath: string): string {
|
||||
if (relativePath === "index.md") return "/";
|
||||
if (relativePath === "404.md") return "/404.html";
|
||||
return `/${relativePath.replace(/\.md$/, ".html")}`;
|
||||
}
|
||||
|
||||
/** 去掉侧边栏文案前导 emoji/符号,保留 "1.1 项目介绍" 这类编号文字 */
|
||||
function cleanLabel(s?: string): string {
|
||||
return (s || "").replace(/^[^\p{L}\p{N}]+/u, "").trim();
|
||||
}
|
||||
|
||||
/** 从侧边栏构建 route → 面包屑文案链(含分组小节) */
|
||||
function buildCrumbMap(sidebar: unknown): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>();
|
||||
const groups: SideItem[] = Array.isArray(sidebar)
|
||||
? sidebar
|
||||
: ((sidebar as Record<string, SideItem[]>)?.["/zh/"] ?? []);
|
||||
const walk = (items: SideItem[], trail: string[]) => {
|
||||
for (const item of items) {
|
||||
if (item.link && item.link.startsWith("/")) {
|
||||
map.set(item.link, [...trail, cleanLabel(item.text)]);
|
||||
} else if (item.items?.length) {
|
||||
walk(item.items, [...trail, cleanLabel(item.text)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const group of groups) {
|
||||
walk(group.items ?? [], [cleanLabel(group.text)].filter(Boolean));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 从渲染后的 HTML 提取 H1 纯文本(JSON-LD headline 须与页面 H1 一致) */
|
||||
function h1OfHtml(html?: string): string {
|
||||
const m = (html || "").match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
||||
if (!m) return "";
|
||||
return m[1]
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/** 从 Markdown 源提取 H1 纯文本(去除行内 markdown 标记) */
|
||||
function h1OfMd(md: string): string {
|
||||
// 围栏感知:跳过 ``` 代码块内的 # 注释,取真正的页面 H1
|
||||
let inFence = false;
|
||||
for (const line of md.split("\n")) {
|
||||
if (line.startsWith("```")) { inFence = !inFence; continue; }
|
||||
if (!inFence && /^# (?!#)/.test(line)) {
|
||||
return line
|
||||
.replace(/^# /, "")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function createSeoHooks(sidebar: unknown) {
|
||||
const crumbs = buildCrumbMap(sidebar);
|
||||
const projectDirs: string[] = [];
|
||||
let gitDates: Map<string, string> | null = null;
|
||||
|
||||
const readMd = (relativePath: string, srcDir?: string): string => {
|
||||
try {
|
||||
return fs.readFileSync(
|
||||
path.resolve(srcDir || process.cwd(), relativePath),
|
||||
"utf8",
|
||||
);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 项目内每个文件最后一次 git 提交的 ISO 时间。
|
||||
* 单次 git log 调用构建全量映射(key = 相对路径,与 pageData.relativePath 一致);
|
||||
* git 不可用时静默降级(sitemap 无 lastmod、JSON-LD 无 dateModified)。
|
||||
*/
|
||||
const getGitDates = (root: string): Map<string, string> => {
|
||||
if (gitDates) return gitDates;
|
||||
gitDates = new Map<string, string>();
|
||||
try {
|
||||
const out = execSync(
|
||||
"git log --format=:::%cI --name-only --relative -n 500 -- .",
|
||||
{
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
let current = "";
|
||||
for (const line of out.split(/\r?\n/)) {
|
||||
if (line.startsWith(":::")) {
|
||||
current = line.slice(3).trim();
|
||||
continue;
|
||||
}
|
||||
const file = line.trim();
|
||||
if (file && current && !gitDates.has(file)) {
|
||||
gitDates.set(file, current);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 降级:无 lastmod / dateModified
|
||||
}
|
||||
return gitDates;
|
||||
};
|
||||
|
||||
/** 每页独立 title(去 "5.10 " 编号前缀,关键词前置)与 description(:::tip 首句) */
|
||||
function transformPageData(pageData: PageDataLike, ctx?: any): Record<string, unknown> {
|
||||
const srcDir: string | undefined = ctx?.siteConfig?.srcDir;
|
||||
const root: string | undefined = ctx?.siteConfig?.root;
|
||||
if (root && !projectDirs.includes(root)) projectDirs.push(root);
|
||||
|
||||
const out: Record<string, unknown> = {};
|
||||
const rel = pageData.relativePath;
|
||||
if (rel && !pageData.frontmatter?.description) {
|
||||
const desc = extractDescription(readMd(rel, srcDir));
|
||||
if (desc) out.description = desc;
|
||||
}
|
||||
if (pageData.title) {
|
||||
out.title = stripNumberPrefix(pageData.title);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 构建期注入 canonical / OG / twitter / JSON-LD */
|
||||
function transformHead(ctx: TransformCtx): HeadTuple[] {
|
||||
const rel = ctx?.pageData?.relativePath || "";
|
||||
if (!rel || rel === "404.md") return [];
|
||||
|
||||
const route = routeOf(rel);
|
||||
const url = `${SITE_URL}${route}`;
|
||||
const title = ctx.pageData?.title || ctx.title || "";
|
||||
const desc = ctx.description || "";
|
||||
const isHome = rel === "index.md";
|
||||
const image = `${SITE_URL}/logo.png`;
|
||||
|
||||
const heads: HeadTuple[] = [
|
||||
["link", { rel: "canonical", href: url }],
|
||||
["meta", { property: "og:type", content: isHome ? "website" : "article" }],
|
||||
["meta", { property: "og:site_name", content: "DjangoAdmin" }],
|
||||
["meta", { property: "og:locale", content: "zh_CN" }],
|
||||
["meta", { property: "og:title", content: title }],
|
||||
["meta", { property: "og:description", content: desc }],
|
||||
["meta", { property: "og:url", content: url }],
|
||||
["meta", { property: "og:image", content: image }],
|
||||
["meta", { property: "twitter:title", content: title }],
|
||||
["meta", { property: "twitter:description", content: desc }],
|
||||
["meta", { property: "twitter:url", content: url }],
|
||||
["meta", { property: "twitter:image", content: image }],
|
||||
];
|
||||
|
||||
// ---- JSON-LD ----
|
||||
const root = ctx.siteConfig?.root || projectDirs[0] || process.cwd();
|
||||
const srcDir = ctx.siteConfig?.srcDir || root;
|
||||
const lastMod = getGitDates(root).get(rel);
|
||||
const blocks: Record<string, any>[] = [];
|
||||
|
||||
if (isHome) {
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
name: "DjangoAdmin 开发文档",
|
||||
url: `${SITE_URL}/`,
|
||||
inLanguage: "zh-CN",
|
||||
description: desc,
|
||||
});
|
||||
blocks.push({ "@context": "https://schema.org", ...ORG });
|
||||
} else {
|
||||
const md = readMd(rel, srcDir);
|
||||
const article: Record<string, any> = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: h1OfMd(md) || h1OfHtml(ctx.content) || title,
|
||||
description: desc,
|
||||
url,
|
||||
inLanguage: "zh-CN",
|
||||
mainEntityOfPage: { "@type": "WebPage", "@id": url },
|
||||
publisher: ORG,
|
||||
};
|
||||
if (lastMod) article.dateModified = lastMod;
|
||||
blocks.push(article);
|
||||
|
||||
const trail = crumbs.get(route);
|
||||
if (trail?.length) {
|
||||
const items: Record<string, any>[] = [
|
||||
{ "@type": "ListItem", position: 1, name: "首页", item: `${SITE_URL}/` },
|
||||
];
|
||||
trail.forEach((name, i) => {
|
||||
const node: Record<string, any> = {
|
||||
"@type": "ListItem",
|
||||
position: i + 2,
|
||||
name,
|
||||
};
|
||||
if (i === trail.length - 1) node.item = url;
|
||||
items.push(node);
|
||||
});
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items,
|
||||
});
|
||||
}
|
||||
|
||||
const faqs = extractFaqs(md);
|
||||
if (faqs.length) {
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: faqs.map((f) => ({
|
||||
"@type": "Question",
|
||||
name: f.q,
|
||||
acceptedAnswer: { "@type": "Answer", text: f.a },
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 转义 "<",防止内容中出现 </script> 破坏注入
|
||||
const json = JSON.stringify(blocks).replace(/</g, "\\u003c");
|
||||
heads.push(["script", { type: "application/ld+json" }, json]);
|
||||
return heads;
|
||||
}
|
||||
|
||||
/** sitemap.xml:过滤 404/README,注入 git lastmod */
|
||||
const sitemap = {
|
||||
hostname: SITE_URL,
|
||||
transformItems(items: { url: string; lastmod?: string }[]) {
|
||||
const root = projectDirs[0] || process.cwd();
|
||||
const dates = getGitDates(root);
|
||||
const out: { url: string; lastmod?: string }[] = [];
|
||||
for (const item of items) {
|
||||
// ⚠ vitepress 传入的 item.url 是相对路径("404.html"、"zh/x.html"、""),
|
||||
// 无前导斜杠也无域名——hostname 由 SitemapStream 事后拼接。
|
||||
if (/(^|\/)404\.html$/.test(item.url)) continue; // 404 页不收录
|
||||
if (/(^|\/)README\.html$/.test(item.url)) continue; // 仓库 README 非文档页
|
||||
const p = item.url.replace(/^\//, "");
|
||||
const rel =
|
||||
p === "" || p === "index.html" ? "index.md" : p.replace(/\.html$/, ".md");
|
||||
const lastmod = dates.get(rel);
|
||||
out.push(lastmod ? { ...item, lastmod } : item);
|
||||
}
|
||||
return out;
|
||||
},
|
||||
};
|
||||
|
||||
return { transformPageData, transformHead, sitemap };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig, HeadConfig } from "vitepress";
|
||||
|
||||
export const META_IMAGE = "/social.png";
|
||||
export const META_IMAGE = "/logo.png";
|
||||
|
||||
const rControl = /[\u0000-\u001f]/g;
|
||||
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g;
|
||||
@@ -23,6 +23,8 @@ export const slugify = (str: string): string =>
|
||||
|
||||
export const sharedConfig = defineConfig({
|
||||
title: "小蚂蚁云",
|
||||
// 页面 <title> 后缀:「环境准备 | DjangoAdmin 开发文档」(index.md frontmatter 可覆盖)
|
||||
titleTemplate: "DjangoAdmin 开发文档",
|
||||
appearance: "light",
|
||||
|
||||
markdown: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { DefaultTheme, LocaleSpecificConfig } from "vitepress";
|
||||
|
||||
export const META_URL = "https://www.djangoadmin.cn";
|
||||
export const META_TITLE = "企业级中台解决方案";
|
||||
export const META_DESCRIPTION = "企业级中台解决方案";
|
||||
export const META_URL = "http://docs.tornado.elevue.djangoadmin.cn";
|
||||
export const META_TITLE = "DjangoAdmin Tornado+ElementPlus 开发文档";
|
||||
export const META_DESCRIPTION =
|
||||
"DjangoAdmin Tornado+ElementPlus 企业级中台解决方案开发者文档";
|
||||
|
||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
description: META_DESCRIPTION,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
head:
|
||||
- - meta
|
||||
- name: robots
|
||||
content: noindex
|
||||
---
|
||||
|
||||
# 页面不存在
|
||||
|
||||
抱歉,您访问的页面不存在或已被移动。
|
||||
|
||||
[返回首页](/) · [浏览文档](/zh/introduction)
|
||||
@@ -5,7 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs": "vitepress dev .",
|
||||
"docs:build": "vitepress build .",
|
||||
"docs:llms": "node scripts/generate-llms-txt.mjs",
|
||||
"docs:build": "node scripts/generate-llms-txt.mjs && vitepress build .",
|
||||
"docs:preview": "vitepress preview ."
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# DjangoAdmin Tornado+ElementPlus 开发文档
|
||||
|
||||
> DjangoAdmin Tornado+ElementPlus 企业级中台解决方案开发者文档。内置 RBAC 权限体系、代码生成器等开箱即用功能,支持五种数据库一键切换。本文档共 138 篇,按章节组织。
|
||||
|
||||
## 1. 了解项目
|
||||
|
||||
- [更新日志](http://docs.tornado.elevue.djangoadmin.cn/zh/changelog.html): 更新日志是项目演进的完整记录,帮助开发者和企业了解每个版本的能力变化与技术演进方向。
|
||||
- [新手入门指南](http://docs.tornado.elevue.djangoadmin.cn/zh/course.html): 详细的安装步骤请参考 环境准备 章节。
|
||||
- [项目介绍](http://docs.tornado.elevue.djangoadmin.cn/zh/introduction.html): DjangoAdmin 是企业级中台解决方案,后端基于 Tornado + SQLAlchemy,前端基于 Vue3 + ElementPlus,内置 RBAC 权限、代码生成器等开箱即用功能,
|
||||
- [项目目录结构](http://docs.tornado.elevue.djangoadmin.cn/zh/struct.html): + 保持一致性:目录结构一旦确定,应保持一致性,避免随意更改。
|
||||
- [为什么选择我们?](http://docs.tornado.elevue.djangoadmin.cn/zh/why.html): 本框架提供开箱即用的企业级中台完整解决方案,免去从零搭建成本,助力中大型企业构建统一的数字化基座。
|
||||
|
||||
## 2. 快速入门
|
||||
|
||||
- [后端启动](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/backend.html): 启动后端前,请确保已完成 环境准备 章节中的所有软件安装。
|
||||
- [数据库初始化](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/database.html): 数据库初始化前,请确保已完成以下准备工作: 1. 目标数据库服务已启动(如 MySQL 8.0+) 2. .env 文件中的 DB_DRIVER 和数据库连接信息已正确配置 3. Python
|
||||
- [Docker 快速部署](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/docker.html): Docker 部署适合快速体验和生产环境。
|
||||
- [环境变量速查表](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/env-vars.html): 1. .env.example 是环境变量模板文件,已提交到版本控制。
|
||||
- [环境准备](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/environment.html): 1. 部分软件安装后需要设置本地环境变量,便于在终端中使用命令。
|
||||
- [常见问题FAQ](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/faq.html): 遇到问题时,建议先查看终端日志输出和浏览器控制台(F12)的错误信息,通常能快速定位问题原因。
|
||||
- [第一个接口调试](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/first-api.html): 开始调试前,请确保已完成以下步骤: 1. 后端服务已启动(参见 后端启动) 2. 数据库已初始化(参见 数据库初始化) 3. Redis 服务已启动
|
||||
- [前端启动](http://docs.tornado.elevue.djangoadmin.cn/zh/volume1/frontend.html): 启动前端前,请确保已完成 环境准备 章节中的 Node.js 和 pnpm 安装,并已启动后端服务(参见 后端启动)。
|
||||
|
||||
## 3. 模块开发实战
|
||||
|
||||
- [完整代码清单](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/code-list.html): 本页汇总友链(Link)模块涉及的全部文件代码,开发时可快速查阅与复制对照。
|
||||
- [示例模块(Example)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/example.html): src/modules/example/ 是一个完整的示例模块,演示了标准 CRUD 模块的六文件结构和开发模式。
|
||||
- [常见问题与排错](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/faq.html): 本页汇总模块开发过程中的常见问题与排错方法,开发受阻时按症状索引即可定位。
|
||||
- [前端 API 层](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/fe-api.html): 前端 API 层封装后端接口的 HTTP 请求,每个业务模块对应一个 API 文件,本页讲解基于 Axios 封装的编写规范。
|
||||
- [前端路由与菜单](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/fe-router.html): 本页讲解前端路由与菜单机制——路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
- [前端页面视图](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/fe-view.html): 前端页面视图是用户直接交互的界面,本页讲解 Vue3 + ElementPlus 下一个完整 CRUD 页面的文件组成与编写方式。
|
||||
- [请求处理器(Handler)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/handler.html): Handler 层是 HTTP 接口层,负责接收请求、校验权限与记录操作日志,编写新接口从本页开始。
|
||||
- [数据模型(Model)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/model.html): 数据模型是模块开发的第一步,讲解如何用 SQLAlchemy ORM 与基类定义数据库表结构,新建模块时从本页开始。
|
||||
- [概述与目标](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/overview.html): 其中 id、create_user、create_time、update_user、update_time、is_delete 由基类 BaseModel 自动提供,业务字段仅需定义 name、
|
||||
- [权限节点配置](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/permission.html): 实际开发中,查询类权限(page、detail、list)通常合并为一个节点 sys:position:list,减少配置工作量。
|
||||
- [Repository 层](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/repository.html): Repository 层是数据访问层,封装全部数据库查询操作,本页讲解基于 BaseRepository 基类的通用 CRUD 用法。
|
||||
- [路由注册](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/router.html): 新模块只需两步: 1. 在 src/api/v1/ 下创建路由文件,定义 routes 列表 2. 在 router.py 的 _RESOURCE_MODULES 中追加模块导入 路由前缀
|
||||
- [Pydantic Schema](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/schema.html): Schema 层负责请求数据校验,本页讲解基于 Pydantic v2 BaseModel 与 Field 的表单类定义规范。
|
||||
- [Service 层](http://docs.tornado.elevue.djangoadmin.cn/zh/volume2/service.html): repo_cls 是 类属性,值为 Repository 类(不是实例)。
|
||||
|
||||
## 4. 架构设计
|
||||
|
||||
- [核心基类体系](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/base-classes.html): 四大基类各司其职,覆盖从数据模型到请求校验再到业务逻辑的完整链路。
|
||||
- [缓存架构设计](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/cache.html): 采用两级缓存架构:第一级为进程内字典缓存(dict_util),第二级为 Redis 分布式缓存。
|
||||
- [数据库设计](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/database-design.html): 所有业务表继承 base_model 基类,自动获得 6 个通用字段(id / create_user / create_time / update_user / update_time /
|
||||
- [目录结构设计](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/directory.html): 目录结构遵循「按功能分组、按职责分层」的原则。
|
||||
- [扩展性设计](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/extensibility.html): 遵循开闭原则(OCP):对扩展开放,对修改关闭。
|
||||
- [分层架构设计](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/layer-arch.html): 采用经典的分层架构,核心原则是:依赖单向、职责单一、接口隔离。
|
||||
- [整体架构图](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/overview.html): 本页从全局视角阐述系统整体技术架构,涵盖前端、后端、数据库与基础设施等核心组成,理解系统全貌时从本页开始。
|
||||
- [请求完整链路](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/request-flow.html): 一个典型的 API 请求经过以下阶段:浏览器 → Nginx → Tornado → Handler 生命周期(set_default_headers → prepare →
|
||||
- [安全架构设计](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/security.html): 纵深防御:认证、授权、限流、参数校验多层防护,单一防线失效时仍有其他机制兜底 最小权限:用户仅能访问已授权接口,角色权限按需分配,遵循"够用即可"原则 数据不落地:敏感信息(Token、
|
||||
- [技术选型说明](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/tech-stack.html): Tornado 是 Python 生态中成熟的异步 Web 框架,基于自有事件循环实现非阻塞 I/O。
|
||||
- [Tornado IOLoop 与异步模型](http://docs.tornado.elevue.djangoadmin.cn/zh/volume3/tornado-async.html): Tornado 基于单线程事件循环(IOLoop),Handler 方法默认为协程(async def)。
|
||||
|
||||
## 5. 开发指南
|
||||
|
||||
- [API 接口速查表](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/api-cheatsheet.html): API 接口速查表,按模块分组列出所有 RESTful 接口的请求方法、路径、说明和权限标识。
|
||||
- [文章管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/article.html): 文章管理模块支持富文本编辑、分类关联、封面图片上传、图集管理等功能。
|
||||
- [分类管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/category.html): 分类管理模块用于管理树形分类数据,如文章分类、产品分类等。
|
||||
- [行政区划管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/city.html): 行政区划管理模块提供省市区三级行政区划数据的查询和管理能力,支持树形结构展示和下拉选择。
|
||||
- [公告与链接管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/content.html): 公告、链接、城市是三个简单 CRUD 内容模块,均继承 BaseService,提供标准的增删改查接口。
|
||||
- [部门/职级/岗位管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/dept.html): 部门、职级、岗位是组织架构管理的三个核心模块。
|
||||
- [文件模板管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/file-template.html): 文件模板管理模块提供文件导入导出模板的管理能力,用于维护各种业务场景的 Excel/Word 等模板文件。
|
||||
- [定时任务日志](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/job-log.html): 定时任务日志模块记录定时任务的执行历史,包括执行时间、执行结果、耗时等信息。
|
||||
- [定时任务](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/job.html): 定时任务模块基于 APScheduler 实现,支持 Cron 表达式调度、任务执行日志、动态启停等功能。
|
||||
- [菜单管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/menu.html): 菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。
|
||||
- [参数管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/param.html): 参数管理模块用于管理动态系统参数,与系统配置模块类似但更侧重于业务参数的灵活配置。
|
||||
- [角色管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/role.html): 角色是 RBAC 权限模型的核心,连接用户和菜单(权限)。
|
||||
- [用户管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/business/user.html): 用户管理是系统的核心模块,属于复杂业务模块。
|
||||
- [认证流程实战](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/auth-flow.html): 登录 → Token 获取 → 权限校验 → Token 刷新 → 退出登录的完整认证链路实战说明。
|
||||
- [验证码机制](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/captcha.html): 验证码是防止自动化攻击的重要安全手段。
|
||||
- [系统配置管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/config.html): 系统配置模块用于管理动态的键值对配置项,与数据字典类似但更偏向系统级参数。
|
||||
- [CORS 跨域配置](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/cors.html): CORS(Cross-Origin Resource Sharing)跨域资源共享是前后端分离架构必须处理的问题。
|
||||
- [演示模式](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/demo-mode.html): 演示模式用于在线体验环境,开启后所有写操作(新增、编辑、删除等)将被拦截,返回"演示环境,暂无操作权限"提示。
|
||||
- [数据字典使用指南](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/dict-guide.html): 数据字典的完整使用流程,从新增字典类型到在代码中使用字典数据的实操指南。
|
||||
- [字典模块](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/dict.html): 数据字典用于管理系统中的枚举值和配置项,如状态、类型、性别等。
|
||||
- [异常与响应处理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/exception.html): 定义了统一的响应格式和全局异常处理机制。
|
||||
- [优雅关闭](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/graceful-shutdown.html): 优雅关闭确保应用在收到终止信号时,能够完成正在处理的请求、关闭数据库连接、停止定时任务调度器后再退出,避免请求中断和资源泄漏。
|
||||
- [用户认证(JWT)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/jwt.html): JWT(JSON Web Token)是的核心认证机制。
|
||||
- [登录日志](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/login-log.html): 登录日志记录每次登录尝试(成功/失败),包含登录账号、IP地址、浏览器信息、登录时间等。
|
||||
- [登录防爆破](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/login-protect.html): 登录防爆破机制基于 Redis 实现同一 IP + 用户名维度的连续失败计数与锁定,防止暴力破解密码。
|
||||
- [操作日志](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/operation-log.html): 操作日志装饰器自动记录写请求(POST/PUT/DELETE/PATCH)的操作日志,无需手动埋点。
|
||||
- [权限管理(RBAC)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/rbac.html): 采用基于角色的访问控制(RBAC)模型,通过用户-角色-菜单三层关系实现细粒度的权限管理。
|
||||
- [链路追踪(TraceID)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/trace-id.html): 链路追踪通过为每个请求分配唯一的 TraceID,将同一请求的所有日志串联起来,方便问题排查和性能分析。
|
||||
- [参数验证(Pydantic)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/core/validator.html): 使用 Pydantic v2 进行请求参数验证。
|
||||
- [API 层开发](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/api.html): 前端 API 层位于 src/api/ 目录,封装所有与后端的 HTTP 请求。
|
||||
- [前端构建](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/build.html): 前端使用 Vite 构建,支持开发服务器和生产构建。
|
||||
- [公共组件库](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/components.html): 项目提供丰富的公共组件,位于 ui/src/components/ 目录,基于 ElementPlus 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。
|
||||
- [BasicForm 表单](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/form.html): BasicForm 位于 src/components/Form/src/BasicForm.vue,基于 ElementPlus 的 el-form 二次封装,通过 schemas
|
||||
- [Hooks 组合式函数](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/hooks.html): 项目封装了一系列组合式函数(Hooks),位于 src/hooks/ 和 src/utils/ 目录,提供权限检查、异步 loading、防重复提交、实时时钟、项目配置等可复用逻辑。
|
||||
- [国际化](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/i18n.html): 当前项目仅支持中文界面,ElementPlus 使用中文语言包(zh-cn),所有界面文本直接硬编码在组件中。
|
||||
- [Modal 弹窗](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/modal.html): 项目提供 BasicModal 组件(src/components/Modal/src/BasicModal.vue)和原生 el-dialog 两种弹窗用法。
|
||||
- [权限指令 v-perm](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/perm-directive.html): v-perm 和 v-perms 是项目自定义的 Vue 指令,用于按钮级别的权限控制,无权限时自动从 DOM 中移除元素。
|
||||
- [路由与菜单](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/router.html): 前端路由基于 Vue Router 4 实现动态路由生成,菜单数据从后端获取,通过 generator-routers.ts 递归生成路由表,路由守卫控制登录状态和权限。
|
||||
- [前端环境与结构](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/setup.html): 前端基于 Vue3 + ElementPlus 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
|
||||
- [Store 状态管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/store.html): 前端使用 Pinia 进行状态管理,核心模块包括 user(用户信息与权限)、asyncRoute(动态路由与菜单)、projectSetting(项目配置)、designSetting(主题设计)等。
|
||||
- [样式与主题](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/style.html): 前端样式方案采用 Tailwind CSS + SCSS + ElementPlus 主题覆盖,支持深色模式切换。
|
||||
- [BasicTable 表格](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/table.html): BasicTable 位于 src/components/Table/src/Table.vue,基于 ElementPlus 的 el-table 二次封装,提供列定义、数据请求、分页、操作列、
|
||||
- [工具函数](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/utils.html): 前端工具函数位于 src/utils/ 目录,提供认证管理、本地存储、日期格式化、树结构操作、文件下载等通用能力。
|
||||
- [页面视图开发](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/frontend/view.html): 页面视图位于 src/views/ 目录,每个业务模块遵循 index.vue(列表页)+ edit.vue(编辑弹窗)的标准模式,辅以 columns.ts(列定义)和
|
||||
- [接口响应规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/integration/api-contract.html): 后端所有接口统一返回 R.ok() / R.failed() 格式的 JSON 响应,前端通过 VAxios 的 transformRequestData 拦截器统一解包处理。
|
||||
- [字典数据联调](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/integration/dict-sync.html): 字典数据通过后端字典 API 获取,前端在下拉选择器中渲染选项。
|
||||
- [权限节点联动](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/integration/perm-sync.html): 前后端权限通过统一的权限节点字符串联动,后端 @permission_required 校验权限,前端 v-perm 指令和 TableAction 的 auth 属性控制按钮显示。
|
||||
- [代理配置](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/integration/proxy.html): 开发环境通过 Vite 代理转发 API 请求到后端,生产环境通过 Nginx 代理。
|
||||
- [文件上传联调](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/integration/upload-sync.html): 文件上传涉及前端 Upload 组件、后端上传接口和 URL 拼接三个环节,需要前后端协同配置。
|
||||
- [请求上下文(ContextVar)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/context.html): src/core/context.py 使用 Python 标准库 contextvars 管理请求级别的上下文信息,包括当前用户 ID、用户名、姓名和链路追踪 ID。
|
||||
- [数据库会话](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/db-session.html): 数据库会话通过 scoped_session 实现线程级隔离,Repository 层的写操作自动 commit,BaseHandler.on_finish() 兜底关闭会话。
|
||||
- [字典缓存工具](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/dict-util.html): src/common/utils/dict_util.py 提供业务字典的缓存读取能力,从 Redis 缓存中获取字典数据,缓存未命中时从数据库加载。
|
||||
- [导入导出](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/excel.html): 基于 openpyxl 实现 Excel 导入导出功能,支持通过 file_template 模块管理导入模板配置。
|
||||
- [通用 Helper 函数](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/helpers.html): src/common/utils/helpers.py 提供项目中广泛使用的通用工具函数,包括 ID 解析、OS/浏览器检测、参数脱敏等。
|
||||
- [IP 归属地解析](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/ip-region.html): src/common/utils/ip2region.py 基于 ip2region 离线数据库,提供 IP 地址到省市区的归属地查询能力,无需网络请求。
|
||||
- [日志系统](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/logger.html): 通过 Python 标准库 logging 实现结构化日志,支持控制台 + 文件双输出,日志文件按大小自动轮转。
|
||||
- [密码工具](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/password.html): 采用 bcrypt 双重加盐方案存储密码,源码位于 src/core/password.py。
|
||||
- [权限缓存](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/perm-cache.html): 通过 Redis 实现用户权限和菜单的缓存,采用用户级缓存 + 全局版本号双层策略。
|
||||
- [统一响应封装(R 对象)](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/response.html): src/core/response.py 提供统一的 API 响应封装,所有接口返回格式为 {code, data, msg, ok}。
|
||||
- [富文本过滤](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/rich-text.html): 富文本编辑器内容存在存储型 XSS 风险,通过 bleach 库实现 HTML 白名单清洗,在落盘前统一过滤危险内容。
|
||||
- [文件上传完整流程](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/upload-guide.html): 富文本编辑器(如 TinyMCE、CKEditor、WangEditor 等)的图片上传同样使用此接口。
|
||||
- [文件上传](http://docs.tornado.elevue.djangoadmin.cn/zh/volume4/tools/upload.html): 提供本地文件上传功能,支持图片和通用文件上传,内置扩展名白名单、大小限制、魔术字节校验和路径遍历防护。
|
||||
|
||||
## 6. 代码生成器
|
||||
|
||||
- [CLI 命令行使用](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/cli.html): 代码生成器 CLI 模式的使用方法,包括单表生成、批量生成、预览模式和配置导出。
|
||||
- [代码生成引擎](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/code-engine.html): 代码生成引擎的工作原理,包括配置解析、模板渲染、文件写入和自动注册的完整流程。
|
||||
- [配置文件详解](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/config.html): 代码生成器 JSON 配置文件的格式说明,包括模块名称、字段属性、字典编码等配置项的详细解释。
|
||||
- [实战案例](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/demo.html): 以 tornado_example 演示表为完整案例,演示从建表到生成到使用的全流程。
|
||||
- [常见问题](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/faq.html): 代码生成器使用过程中常见问题的原因分析和解决方案。
|
||||
- [概述与架构](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/overview.html): 代码生成器的整体架构、核心模块和生成流程概述。
|
||||
- [表结构解析引擎](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/parser.html): 表结构解析引擎的工作原理,从数据库读取表元数据并智能推断字段用途的过程。
|
||||
- [自定义模板](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/templates.html): 代码生成器模板的 Jinja2 语法说明,包括模板变量、字段属性和自定义模板的开发方法。
|
||||
- [Web 管理界面](http://docs.tornado.elevue.djangoadmin.cn/zh/volume5/web-ui.html): 代码生成器 Web 管理界面的使用方法,通过可视化界面操作单表生成和批量生成。
|
||||
|
||||
## 7. 运维部署
|
||||
|
||||
- [备份与恢复](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/backup.html): - 3 份数据副本(1 份生产 + 2 份备份) - 2 种不同存储介质(如本地磁盘 + 远程存储) - 1 份异地备份(防机房级灾难)
|
||||
- [跨库数据迁移](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/db-cross-migrate.html): 项目提供 scripts/migrate_db.py 脚本,支持在不同数据库之间迁移数据(如 MySQL → PostgreSQL / SQL Server / SQLite / Oracle)。
|
||||
- [数据库迁移](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/db-migrate.html): - 结构迁移:Alembic(自动生成 DDL 差异) - 数据迁移:scripts/migrate_db.py(跨库数据泵) - 初始化:scripts/init_db.py(全新安装一键建库)
|
||||
- [部署 Checklist](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/deploy-checklist.html): 从服务器准备到应用上线的完整部署操作清单,适用于生产环境首次部署和后续版本更新。
|
||||
- [Docker容器化](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/docker.html): - 环境一致性:消除"在我机器上能跑"的问题 - 快速部署:一条命令启动完整服务 - 资源隔离:应用与宿主机环境解耦
|
||||
- [环境规划](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/env-plan.html): 各环境之间保持代码一致,仅通过环境变量(.env)区分配置。
|
||||
- [健康检查与监控](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/monitoring.html): `` 1. 应用层:健康检查端点、业务指标 2. 系统层:CPU/内存/磁盘/网络 3. 中间件:MySQL/Redis 连接状态 4. 日志:集中式日志收集与分析 ``
|
||||
- [Nginx 反向代理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/nginx.html): `` 客户端 → Nginx(80/443) ├─ / → 前端静态资源(dist/) ├─ /api/v1/... → Tornado 后端(8041) └─ /api/file/ →
|
||||
- [性能调优](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/performance.html): 1. 先测量,后优化:用数据说话,避免盲目调参 2. 瓶颈优先:找到最大瓶颈,集中优化 3. 渐进式:每次只调整一个参数,观察效果
|
||||
- [安全加固](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/security-hardening.html): - 最小权限:只开放必要的端口和权限 - 纵深防御:多层安全措施叠加 - 默认安全:配置默认值应为安全选项
|
||||
- [Supervisor进程管理](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/supervisor.html): - Linux 服务器直接部署(非 Docker) - 需要进程自动重启和日志轮转 - 管理多个相关进程
|
||||
- [故障排查手册](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/troubleshooting.html): 1. 查看日志:tail -f logs/supervisor.out.log 或 docker-compose logs -f 2. 检查配置:.env 文件、数据库连接、Redis 连接 3.
|
||||
- [版本升级流程](http://docs.tornado.elevue.djangoadmin.cn/zh/volume6/upgrade.html): 1. 先备份,后升级 2. 先测试环境,后生产环境 3. 每次升级可回滚 4. 低峰期执行升级
|
||||
|
||||
## 8. 规范标准
|
||||
|
||||
- [API响应规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/api-response.html): - 所有接口必须通过 R.ok() 或 R.failed() 返回,禁止直接返回 dict - 成功响应 code=0,失败响应 code=1 - 响应体始终包含 code、data、msg、ok
|
||||
- [代码审查清单](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/code-review.html): - 每次提交必须经过至少一人审查 - 审查关注点:正确性 > 安全性 > 可维护性 > 性能 - 审查意见应具体、可操作,避免主观评价
|
||||
- [删除规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/deletion.html): - is_delete = 0:正常记录(默认值) - is_delete = 1:已删除记录 - 所有查询方法自动过滤 is_delete = 0,业务层无需关心
|
||||
- [文档规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/doc-standard.html): - 文档与代码同步更新 - 使用中文编写(项目约定) - 示例代码可直接运行
|
||||
- [前端 API 规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/fe-api.html): - 每个后端模块对应一个 API 文件 - 函数命名统一使用 名词 + 动词 格式(与后端保持一致) - 请求路径使用相对路径(/position/page),不含 /api/v1 前缀(由
|
||||
- [前端页面规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/fe-component.html): - Vue3(Composition API + <script setup>) - ElementPlus - 自定义组件(BasicTable / BasicForm / TableAction
|
||||
- [Git提交规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/git.html): `` <type>(<scope>): <subject> <body> <footer> ``
|
||||
- [Handler 层规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/handler.html): - Handler 只做请求分发,不含业务逻辑 - 通过装饰器控制权限和演示模式 - 委托 Service 处理业务逻辑 - 返回 R 封装的响应
|
||||
- [分页规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/pagination.html): - 前端传递 pageNo(页码,从 1 开始)和 pageSize(每页条数) - 后端返回 {records, total, size, current, pages} 结构 - 默认每页 10
|
||||
- [Pydantic Schema规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/pydantic.html): - 请求体参数必须声明为 Pydantic 模型(Form 类) - 字段约束使用 Field(...) 定义 - 自定义校验使用 @field_validator - 唯一性校验在 Schema
|
||||
- [Repository层规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/repository.html): - Repository 只做数据访问原语,不处理 R 封装/序列化/业务逻辑 - 软删除由基类自动处理,子类无需关心 - 查询方法自动跳过 None 值条件
|
||||
- [Service层规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/service.html): BaseService 采用模板方法模式:基类定义流程骨架,子类通过声明差异点和覆盖钩子定制行为。
|
||||
- [测试规范](http://docs.tornado.elevue.djangoadmin.cn/zh/volume7/testing.html): 1. 契约测试(Contract):验证路由、权限、表名等契约不变 2. 单元测试(Unit):验证单个函数/方法的逻辑正确性 3. 集成测试(Integration):验证模块间协作、真实数据库操作
|
||||
|
||||
## 附录
|
||||
|
||||
- [常见问题汇总](http://docs.tornado.elevue.djangoadmin.cn/zh/faq.html): 本页汇总了项目各模块的常见问题及解决方案,按场景分类索引。
|
||||
- [免责声明](http://docs.tornado.elevue.djangoadmin.cn/zh/other/declare.html): 使用本产品前必读的法律声明,明确产品性质、使用限制与责任划分,一旦使用本产品即视为接受本声明全部条款。
|
||||
- [广告赞助](http://docs.tornado.elevue.djangoadmin.cn/zh/other/sponsor.html): 1. 投放广告不得与官方全系产品存在结构性冲突和竞争 2. 广告内容需符合国家法律法规,不得包含虚假、误导性信息 3. 广告素材需经官方审核通过后方可上线,审核周期 1-3 个工作日 4.
|
||||
@@ -0,0 +1,33 @@
|
||||
# DjangoAdmin Tornado+ElementPlus 开发文档
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# 显式放行生成式引擎 / AI 爬虫(GEO)
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Allow: /
|
||||
|
||||
User-agent: ClaudeBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-Web
|
||||
Allow: /
|
||||
|
||||
User-agent: anthropic-ai
|
||||
Allow: /
|
||||
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Allow: /
|
||||
|
||||
User-agent: Bytespider
|
||||
Allow: /
|
||||
|
||||
User-agent: bingbot
|
||||
Allow: /
|
||||
|
||||
Sitemap: http://docs.tornado.elevue.djangoadmin.cn/sitemap.xml
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 生成 public/llms.txt(llmstxt.org 标准)——AI 爬虫的站点摘要索引。
|
||||
*
|
||||
* 数据源:zh 目录下全部 Markdown 的 H1 + :::tip 概述首句(与 meta
|
||||
* description 同源,复用 .vitepress/config/extract.mjs)。按侧边栏章节分组。
|
||||
*
|
||||
* 用法:node scripts/generate-llms-txt.mjs
|
||||
* 已挂入 package.json 的 docs:build 前置步骤。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
SITE_URL,
|
||||
SITE_DESCRIPTION,
|
||||
extractDescription,
|
||||
} from "../.vitepress/config/extract.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ZH = path.join(ROOT, "zh");
|
||||
|
||||
/** 顶层目录 → 侧边栏章节名(与 .vitepress/config/zh.ts 保持一致) */
|
||||
const SECTIONS = [
|
||||
["", "1. 了解项目"],
|
||||
["volume1", "2. 快速入门"],
|
||||
["volume2", "3. 模块开发实战"],
|
||||
["volume3", "4. 架构设计"],
|
||||
["volume4", "5. 开发指南"],
|
||||
["volume5", "6. 代码生成器"],
|
||||
["volume6", "7. 运维部署"],
|
||||
["volume7", "8. 规范标准"],
|
||||
["other", "附录"],
|
||||
];
|
||||
|
||||
function walk(dir, out = []) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(p, out);
|
||||
else if (entry.name.endsWith(".md")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 提取首个 H1 文本 */
|
||||
function h1Of(md) {
|
||||
// 围栏感知:跳过 ``` 代码块内的 # 注释,取真正的页面 H1
|
||||
let inFence = false;
|
||||
for (const line of md.split("\n")) {
|
||||
if (line.startsWith("```")) { inFence = !inFence; continue; }
|
||||
if (!inFence && line.startsWith("# ") && !line.startsWith("## ")) {
|
||||
return line.slice(2).trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/** H1 编号排序键:"5.10 x" → [5,10];无编号排最后 */
|
||||
function sortKey(h1) {
|
||||
const m = h1.match(/^(\d+(?:\.\d+)*)/);
|
||||
if (m) return m[1].split(".").map(Number);
|
||||
const letter = h1.match(/^([A-Z])\./);
|
||||
if (letter) return [1000 + letter[1].charCodeAt(0)];
|
||||
return [9999];
|
||||
}
|
||||
|
||||
function cmpKey(a, b) {
|
||||
const len = Math.max(a.length, b.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const x = a[i] ?? 0;
|
||||
const y = b[i] ?? 0;
|
||||
if (x !== y) return x - y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const files = walk(ZH);
|
||||
const groups = new Map(SECTIONS.map(([dir, label]) => [label, []]));
|
||||
|
||||
for (const file of files) {
|
||||
const rel = path.relative(ZH, file).replace(/\\/g, "/");
|
||||
const topDir = rel.includes("/") ? rel.split("/")[0] : "";
|
||||
const base = rel.split("/").pop();
|
||||
const label = (topDir === "" && base === "faq.md") ? "附录"
|
||||
: SECTIONS.find(([dir]) => dir === topDir)?.[1] ?? "附录";
|
||||
|
||||
const md = fs.readFileSync(file, "utf8");
|
||||
const h1 = h1Of(md);
|
||||
if (!h1) continue;
|
||||
const desc = extractDescription(md);
|
||||
const url = `${SITE_URL}/zh/${rel.replace(/\.md$/, ".html")}`;
|
||||
groups.get(label).push({ h1, desc, url, key: sortKey(h1) });
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"# DjangoAdmin Tornado+ElementPlus 开发文档",
|
||||
"",
|
||||
`> ${SITE_DESCRIPTION}。内置 RBAC 权限体系、代码生成器等开箱即用功能,支持五种数据库一键切换。本文档共 ${files.length} 篇,按章节组织。`,
|
||||
"",
|
||||
];
|
||||
|
||||
// 按章节顺序输出,合并重复 label(appendix/other 同为「附录」,只输出一次)
|
||||
const printed = new Set();
|
||||
for (const [, label] of SECTIONS) {
|
||||
if (printed.has(label)) continue;
|
||||
const items = groups.get(label);
|
||||
if (!items?.length) continue;
|
||||
printed.add(label);
|
||||
|
||||
lines.push(`## ${label}`, "");
|
||||
items.sort((a, b) => cmpKey(a.key, b.key));
|
||||
for (const { h1, desc, url } of items) {
|
||||
lines.push(desc ? `- [${h1}](${url}): ${desc}` : `- [${h1}](${url})`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
const out = path.join(ROOT, "public", "llms.txt");
|
||||
fs.writeFileSync(out, lines.join("\n"), "utf8");
|
||||
console.log(
|
||||
`llms.txt: ${files.length} 页 → ${path.relative(ROOT, out)} (${printed.size} 个章节)`,
|
||||
);
|
||||
@@ -1,3 +1,5 @@
|
||||
# 新手入门指南
|
||||
|
||||
:::warning 特别提醒
|
||||
官方精心制作本教程,目的在于方便用户快速掌握软件产品的使用和部署。通过此教程,刚入行的开发者也可以快速掌握并投入产品研发。本地部署时务必请耐心阅读文档操作。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 项目介绍
|
||||
|
||||
:::tip 概述
|
||||
DjangoAdmin 是企业级中台解决方案,后端基于 Tornado + SQLAlchemy,前端基于 Vue3 + ElementPlus,内置 RBAC 权限、代码生成器等开箱即用功能,支持五种数据库一键切换。
|
||||
:::
|
||||
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<img src="/logo.png" height="150" alt="logo"/>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 免责声明
|
||||
|
||||
:::tip 概述
|
||||
使用本产品前必读的法律声明,明确产品性质、使用限制与责任划分,一旦使用本产品即视为接受本声明全部条款。
|
||||
:::
|
||||
|
||||
:::danger 重要提示
|
||||
任何用户在使用本团队研发的系列软件产品(以下简称「本产品」)前,请仔细阅读并透彻理解本声明。选择不使用本产品,可随时停止使用;一旦使用本产品,即视为对本声明全部内容的认可和接受。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 项目目录结构
|
||||
|
||||
## 目录说明
|
||||
|
||||
在软件开发过程中,优秀的软件目录结构对于项目的组织、开发、维护和扩展至关重要,合理的目录结构能够显著提升开发效率和代码可维护性。
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 完整代码清单
|
||||
|
||||
:::tip 概述
|
||||
本页汇总友链(Link)模块涉及的全部文件代码,开发时可快速查阅与复制对照。
|
||||
:::
|
||||
|
||||
本章汇总友链(Link)模块涉及的全部文件,便于开发时快速查阅和复制。
|
||||
|
||||
## 后端文件清单
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 常见问题与排错
|
||||
|
||||
:::tip 概述
|
||||
本页汇总模块开发过程中的常见问题与排错方法,开发受阻时按症状索引即可定位。
|
||||
:::
|
||||
|
||||
本章汇总模块开发过程中的常见问题和解决方案。
|
||||
|
||||
## 后端问题
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 前端 API 层
|
||||
|
||||
:::tip 概述
|
||||
前端 API 层封装后端接口的 HTTP 请求,每个业务模块对应一个 API 文件,本页讲解基于 Axios 封装的编写规范。
|
||||
:::
|
||||
|
||||
前端 API 层负责封装后端接口的 HTTP 请求。每个业务模块对应一个 API 文件,使用项目封装的 Axios 工具发送请求。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 前端路由与菜单
|
||||
|
||||
:::tip 概述
|
||||
本页讲解前端路由与菜单机制——路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
:::
|
||||
|
||||
前端路由定义页面的访问路径,菜单是路由的可视化展示。项目的路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
|
||||
## 路由配置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 前端页面视图
|
||||
|
||||
:::tip 概述
|
||||
前端页面视图是用户直接交互的界面,本页讲解 Vue3 + ElementPlus 下一个完整 CRUD 页面的文件组成与编写方式。
|
||||
:::
|
||||
|
||||
前端页面视图是用户直接交互的界面。项目使用 Vue3 + ElementPlus + Vite 构建。一个完整的 CRUD 页面由 4 个文件组成:
|
||||
|
||||
```
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 请求处理器(Handler)
|
||||
|
||||
:::tip 概述
|
||||
Handler 层是 HTTP 接口层,负责接收请求、校验权限与记录操作日志,编写新接口从本页开始。
|
||||
:::
|
||||
|
||||
Handler 层是 HTTP 接口层,负责接收请求、校验权限、记录操作日志,然后委托给 Service 层处理。每个业务模块对应一个 `handlers.py` 文件,所有 Handler 继承 `BaseHandler`。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 数据模型(Model)
|
||||
|
||||
:::tip 概述
|
||||
数据模型是模块开发的第一步,讲解如何用 SQLAlchemy ORM 与基类定义数据库表结构,新建模块时从本页开始。
|
||||
:::
|
||||
|
||||
数据模型是模块开发的第一步,用于定义数据库表结构。项目使用 `SQLAlchemy ORM`,模型类通过继承 `BaseModel` 自动获得通用字段和方法。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Repository 层
|
||||
|
||||
:::tip 概述
|
||||
Repository 层是数据访问层,封装全部数据库查询操作,本页讲解基于 BaseRepository 基类的通用 CRUD 用法。
|
||||
:::
|
||||
|
||||
Repository 层是数据访问层,封装所有数据库查询操作。项目通过 `BaseRepository` 基类提供通用 CRUD 能力,子类通常只需声明类名,无需编写任何代码。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Pydantic Schema
|
||||
|
||||
:::tip 概述
|
||||
Schema 层负责请求数据校验,本页讲解基于 Pydantic v2 BaseModel 与 Field 的表单类定义规范。
|
||||
:::
|
||||
|
||||
Schema 层负责请求数据的校验。项目使用 Pydantic v2 的 `BaseModel` 定义表单类,通过 `Field` 约束字段格式,Tornado 在接收请求体时自动执行校验。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 整体架构图
|
||||
|
||||
:::tip 概述
|
||||
本页从全局视角阐述系统整体技术架构,涵盖前端、后端、数据库与基础设施等核心组成,理解系统全貌时从本页开始。
|
||||
:::
|
||||
|
||||
本章从全局视角阐述系统的整体技术架构,涵盖前端、后端、数据库及基础设施等核心组成部分。
|
||||
|
||||
## 架构总览
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 文章管理
|
||||
|
||||
:::tip 文章管理
|
||||
文章管理模块支持富文本编辑、分类关联、封面图片上传、图集管理等功能。适用于新闻、公告、帮助文档等内容管理场景。本模块是典型的「带文件处理」业务模块,涵盖了封面迁移、图集处理、富文本 XSS 清洗等核心机制。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分类管理
|
||||
|
||||
:::tip 分类管理
|
||||
分类管理模块用于管理树形分类数据,如文章分类、产品分类等。通过 `parent_id` 字段实现无限级分类。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 公告与链接管理
|
||||
|
||||
:::tip 公告/链接/城市
|
||||
公告、链接、城市是三个简单 CRUD 内容模块,均继承 BaseService,提供标准的增删改查接口。适用于系统公告、友情链接、城市数据等场景。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 部门/职级/岗位管理
|
||||
|
||||
:::tip 部门/职级/岗位
|
||||
部门、职级、岗位是组织架构管理的三个核心模块。部门采用树形结构,职级和岗位为简单 CRUD 模块,均继承 `BaseService`。用户表通过 `dept_id`、`position_id`、`level_id` 三个外键关联这三个模块。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 定时任务
|
||||
|
||||
:::tip 定时任务
|
||||
定时任务模块基于 APScheduler 实现,支持 Cron 表达式调度、任务执行日志、动态启停等功能。包含 job(任务定义)、job_log(执行日志)、scheduler(调度器)三个子模块。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 菜单管理
|
||||
|
||||
:::tip 菜单管理
|
||||
菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。支持目录/菜单(type=0)和权限按钮节点(type=1)两种类型,通过树形结构组织。权限节点由系统根据前端勾选自动生成,采用 `sys:module:action` 三段式命名。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 参数管理
|
||||
|
||||
:::tip 参数管理
|
||||
参数管理模块用于管理动态系统参数,与系统配置模块类似但更侧重于业务参数的灵活配置。支持按参数键查询、修改,无需重启服务即可生效。本模块是典型的「BaseService 简单模块」,通过声明差异点即可获得完整 CRUD 能力。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 角色管理
|
||||
|
||||
:::tip 角色管理
|
||||
角色是 RBAC 权限模型的核心,连接用户和菜单(权限)。通过为角色分配菜单权限,再将角色分配给用户,实现灵活的权限管理。角色删除时需清理关联的用户角色和角色菜单数据,并即时失效受影响用户的权限缓存。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 用户管理
|
||||
|
||||
:::tip 用户管理
|
||||
用户管理是系统的核心模块,属于复杂业务模块。包含用户 CRUD、角色关联、密码加密、行政区划、Excel 导入导出等功能。与简单 CRUD 模块不同,用户管理使用自定义 service 函数而非继承 BaseService 的类方法。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 验证码机制
|
||||
|
||||
:::tip 验证码
|
||||
验证码是防止自动化攻击的重要安全手段。使用 Pillow 生成图片验证码,存储在 Redis 中,支持防刷限流和万能验证码(演示模式)。源码位于 `src/common/utils/captcha.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 系统配置管理
|
||||
|
||||
:::tip 系统配置
|
||||
系统配置模块用于管理动态的键值对配置项,与数据字典类似但更偏向系统级参数。支持分组管理,可通过接口动态修改,无需重启服务。模块位于 `src/modules/config/` 和 `src/modules/config_item/`,路由位于 `src/api/v1/config.py` 和 `config_item.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 演示模式
|
||||
|
||||
:::tip 演示模式
|
||||
演示模式用于在线体验环境,开启后所有写操作(新增、编辑、删除等)将被拦截,返回"演示环境,暂无操作权限"提示。通过 `TORNADO_DEMO` 环境变量控制。装饰器位于 `src/common/middleware/access_decorators.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 字典模块
|
||||
|
||||
:::tip 数据字典
|
||||
数据字典用于管理系统中的枚举值和配置项,如状态、类型、性别等。通过字典编码统一管理,前端可通过 `useDictStore` 获取字典选项,避免硬编码。后端读取工具位于 `src/common/utils/dict_util.py`,内置进程内 TTL 缓存。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 异常与响应处理
|
||||
|
||||
:::tip 全局异常与响应
|
||||
定义了统一的响应格式和全局异常处理机制。所有 API 返回 `{code, data, msg, ok}` 标准格式,异常被捕获后也转换为相同格式。响应函数位于 `src/core/response.py`,异常类型位于 `src/core/exceptions.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 用户认证(JWT)
|
||||
|
||||
:::tip JWT认证
|
||||
JWT(JSON Web Token)是的核心认证机制。用户登录成功后获取 Token,后续请求通过 `Authorization: Bearer <token>` 头携带 Token 进行身份验证。项目内置了 Token 生成、解析、刷新和黑名单管理功能,源码位于 `src/core/jwt.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 登录日志
|
||||
|
||||
:::tip 登录日志
|
||||
登录日志记录每次登录尝试(成功/失败),包含登录账号、IP地址、浏览器信息、登录时间等。支持 IP 地理位置解析(ip2region)和 User-Agent 解析。登录逻辑位于 `src/modules/auth/service.py`,日志记录位于 `src/modules/login_log/service.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 操作日志
|
||||
|
||||
:::tip 操作日志
|
||||
操作日志装饰器自动记录写请求(POST/PUT/DELETE/PATCH)的操作日志,无需手动埋点。源码位于 `src/common/middleware/operation_log.py`。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 权限管理(RBAC)
|
||||
|
||||
:::tip RBAC权限
|
||||
采用基于角色的访问控制(RBAC)模型,通过用户-角色-菜单三层关系实现细粒度的权限管理。权限控制贯穿前后端:后端通过 `@permission_required` 装饰器校验(`src/common/middleware/access_decorators.py`),前端通过 `v-perm` 指令控制按钮显隐。
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 参数验证(Pydantic)
|
||||
|
||||
:::tip 参数验证
|
||||
使用 Pydantic v2 进行请求参数验证。所有请求体参数通过 Pydantic BaseModel 定义,Tornado 自动解析和验证 JSON 请求体,验证失败返回标准格式错误信息。BaseSchema 基类位于 `src/core/base_schema.py`。
|
||||
:::
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 为什么选择我们?
|
||||
|
||||
:::tip 核心价值
|
||||
企业级数据中台不应从零搭建。本框架提供开箱即用的完整解决方案,为中大型企业构建统一的数字化基座。
|
||||
本框架提供开箱即用的企业级中台完整解决方案,免去从零搭建成本,助力中大型企业构建统一的数字化基座。
|
||||
:::
|
||||
|
||||
## 解决什么问题?
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
//
|
||||
// themeConfig: {
|
||||
// editLink: {
|
||||
// pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue",
|
||||
// pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue",
|
||||
// text: "Suggest changes to this page",
|
||||
// },
|
||||
//
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export declare const SITE_URL: string;
|
||||
export declare const SITE_DESCRIPTION: string;
|
||||
export declare function stripNumberPrefix(title: string): string;
|
||||
export declare function extractDescription(md: string): string;
|
||||
export declare function extractFaqs(md: string): { q: string; a: string }[];
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* SEO/GEO 共享工具(纯 ESM,零依赖)。
|
||||
*
|
||||
* 同时被两类调用方 import:
|
||||
* 1. VitePress 配置(.ts,esbuild 打包时内联本文件)
|
||||
* 2. scripts/generate-llms-txt.mjs(Node 直接运行)
|
||||
*
|
||||
* 注意:SITE_URL 是全站唯一 URL 真相源。
|
||||
* docs 子域当前 TLS 证书为腾讯 CDN 默认证书(*.cdn.myqcloud.com,校验失败),
|
||||
* 故暂用 http;证书修复后只需改这里一处。
|
||||
*/
|
||||
|
||||
export const SITE_URL = "http://docs.tornado.naivevue.djangoadmin.cn";
|
||||
|
||||
export const SITE_DESCRIPTION =
|
||||
"DjangoAdmin Tornado+NaiveUI 企业级中台解决方案开发者文档";
|
||||
|
||||
/**
|
||||
* 剥离 H1/标题中的章节编号前缀,用于 <title> 与 JSON-LD headline。
|
||||
* 例:"5.10 字典模块" → "字典模块";"K. 贡献指南" → "贡献指南"。
|
||||
* 页内 H1 与侧边栏编号保持不变,仅影响 SEO 输出。
|
||||
*/
|
||||
export function stripNumberPrefix(title) {
|
||||
return String(title)
|
||||
.replace(/^(?:\d+(?:\.\d+)+|[A-Z]\.)\s+/, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从页面 Markdown 提取 meta description:
|
||||
* 1. 首选 `:::tip xxx` 容器内的首句(141/143 页的标准 opener)
|
||||
* 2. 回退到 H1 后的第一个普通段落
|
||||
* 输出压缩为单行、去除行内 Markdown,按中文句末标点截断(≤100 字符)。
|
||||
*/
|
||||
export function extractDescription(md) {
|
||||
const src = String(md);
|
||||
const fm = src.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
const body = fm ? src.slice(fm[0].length) : src;
|
||||
|
||||
let text = "";
|
||||
// frontmatter description 显式声明优先(无 H1 页面的补齐手段)
|
||||
const fmDesc = fm && fm[1].match(/^description:\s*(.+)$/m);
|
||||
if (fmDesc) {
|
||||
text = fmDesc[1].trim().replace(/^["']|["']$/g, "");
|
||||
}
|
||||
const tip = !text && body.match(/^:::\s*tip[^\n]*\n([\s\S]*?)\n:::/m);
|
||||
if (tip) {
|
||||
text = tip[1];
|
||||
}
|
||||
if (!text) {
|
||||
const afterH1 = body.match(/^# (?!#)(.+)$/m);
|
||||
if (afterH1) {
|
||||
const rest = body.slice((afterH1.index ?? 0) + afterH1[0].length);
|
||||
const para = rest
|
||||
.split(/\n{2,}/)
|
||||
.find(
|
||||
(p) =>
|
||||
p.trim() &&
|
||||
!/^\s*#/.test(p) &&
|
||||
!/^\s*:::/.test(p) &&
|
||||
!/^\s*```/.test(p) &&
|
||||
!/^\s*\|/.test(p),
|
||||
);
|
||||
text = para || "";
|
||||
}
|
||||
}
|
||||
|
||||
text = text
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.trim();
|
||||
|
||||
if (!text) return "";
|
||||
|
||||
// 按中文句末标点截取首句,上限 100 字符;截断时避免切碎词
|
||||
const m = text.match(/^.{1,100}?(?:[。!?!?]|$)/);
|
||||
let out = m ? m[0] : text.slice(0, 100);
|
||||
if (out.length < text.length && !/[。!?!?]$/.test(out)) {
|
||||
out = out.replace(/[^\s,、;:,;:]+$/, "");
|
||||
}
|
||||
return out.trim() || text.slice(0, 100);
|
||||
}
|
||||
|
||||
function cleanText(s) {
|
||||
return s
|
||||
.replace(/^:::[^\n]*$/gm, "")
|
||||
.replace(/^```[^\n]*$/gm, "")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取页面中的 Q/A 对(用于 FAQPage JSON-LD),支持两种站内格式:
|
||||
* A. `### Q:问题` 标题式(如 zh/quickstart/faq.md)
|
||||
* B. `**Q:问题**` 加粗式(如 zh/appendix/faq.md)
|
||||
* 返回 [{ q, a }];无匹配返回 []。
|
||||
*/
|
||||
export function extractFaqs(md) {
|
||||
const body = String(md).replace(/^---[\s\S]*?\n---\s*/, "");
|
||||
let faqs = [];
|
||||
|
||||
// 格式 A:按 `### Q:` 切块,答案截断到下一个标题
|
||||
for (const part of body.split(/(?=^###\s*Q[::])/m)) {
|
||||
const m = part.match(/^###\s*Q[::]\s*([^\n]+)\n+([\s\S]*)/);
|
||||
if (!m) continue;
|
||||
let a = m[2];
|
||||
const cut = a.search(/^#{1,4}\s/m);
|
||||
if (cut >= 0) a = a.slice(0, cut);
|
||||
faqs.push({ q: cleanText(m[1]), a: cleanText(a) });
|
||||
}
|
||||
|
||||
// 格式 B:按 `**Q:` 切块
|
||||
if (!faqs.length) {
|
||||
for (const part of body.split(/(?=^\*\*Q[::])/m)) {
|
||||
const m = part.match(/^\*\*Q[::]\s*([^*]+)\*\*\s*\n+([\s\S]*)/);
|
||||
if (!m) continue;
|
||||
let a = m[2];
|
||||
const cut = a.search(/^(?:#{1,4}\s|\*\*Q)/m);
|
||||
if (cut >= 0) a = a.slice(0, cut);
|
||||
faqs.push({ q: cleanText(m[1]), a: cleanText(a) });
|
||||
}
|
||||
}
|
||||
|
||||
return faqs.filter((f) => f.q && f.a);
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
import { enConfig } from "./en";
|
||||
import { createSeoHooks } from "./seo";
|
||||
import { sharedConfig } from "./shared";
|
||||
import { zhConfig } from "./zh";
|
||||
|
||||
const seo = createSeoHooks(zhConfig.themeConfig?.sidebar);
|
||||
|
||||
export default defineConfig({
|
||||
...sharedConfig,
|
||||
ignoreDeadLinks: [
|
||||
(link) => link.includes('localhost'),
|
||||
],
|
||||
|
||||
// SEO:页面级 title/description、canonical/OG/JSON-LD、sitemap+lastmod
|
||||
transformPageData: seo.transformPageData,
|
||||
transformHead: seo.transformHead,
|
||||
sitemap: seo.sitemap,
|
||||
|
||||
locales: {
|
||||
root: { label: "简体中文", lang: "zh-CN", link: "/", ...zhConfig },
|
||||
// root: { label: "English", lang: "en-US", link: "/", ...enConfig },
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* SEO/GEO 构建期钩子(仅 Node 构建时运行,不进客户端 bundle):
|
||||
* - transformPageData:每页独立 title(去编号前缀)与 description(取 :::tip 首句)
|
||||
* - transformHead:canonical / Open Graph / twitter 卡片 / JSON-LD 结构化数据
|
||||
* - sitemap.transformItems:注入 lastmod(git 最后提交时间)并过滤 404
|
||||
*
|
||||
* 由 config/index.ts 通过 createSeoHooks(zhConfig.themeConfig?.sidebar) 挂载。
|
||||
* 依赖 extract.mjs(纯 ESM,同被 scripts/generate-llms-txt.mjs 复用)。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
import {
|
||||
SITE_URL,
|
||||
extractDescription,
|
||||
extractFaqs,
|
||||
stripNumberPrefix,
|
||||
} from "./extract.mjs";
|
||||
|
||||
/** 与 vitepress HeadConfig 同构:[tag, attrs, innerHTML?] */
|
||||
type HeadTuple = [string, Record<string, any>, string?];
|
||||
|
||||
interface SideItem {
|
||||
text?: string;
|
||||
link?: string;
|
||||
items?: SideItem[];
|
||||
}
|
||||
|
||||
interface PageDataLike {
|
||||
relativePath?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
frontmatter?: Record<string, any>;
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
interface TransformCtx {
|
||||
pageData?: PageDataLike;
|
||||
title?: string;
|
||||
description?: string;
|
||||
content?: string;
|
||||
siteConfig?: { root?: string; srcDir?: string };
|
||||
}
|
||||
|
||||
const ORG = {
|
||||
"@type": "Organization",
|
||||
name: "DjangoAdmin",
|
||||
url: "https://www.djangoadmin.cn",
|
||||
logo: `${SITE_URL}/logo.png`,
|
||||
sameAs: [
|
||||
"https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue",
|
||||
"https://www.djangoadmin.cn",
|
||||
],
|
||||
};
|
||||
|
||||
/** 相对路径 → 站内路由(cleanUrls 关闭,正文页带 .html) */
|
||||
function routeOf(relativePath: string): string {
|
||||
if (relativePath === "index.md") return "/";
|
||||
if (relativePath === "404.md") return "/404.html";
|
||||
return `/${relativePath.replace(/\.md$/, ".html")}`;
|
||||
}
|
||||
|
||||
/** 去掉侧边栏文案前导 emoji/符号,保留 "1.1 项目介绍" 这类编号文字 */
|
||||
function cleanLabel(s?: string): string {
|
||||
return (s || "").replace(/^[^\p{L}\p{N}]+/u, "").trim();
|
||||
}
|
||||
|
||||
/** 从侧边栏构建 route → 面包屑文案链(含分组小节) */
|
||||
function buildCrumbMap(sidebar: unknown): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>();
|
||||
const groups: SideItem[] = Array.isArray(sidebar)
|
||||
? sidebar
|
||||
: ((sidebar as Record<string, SideItem[]>)?.["/zh/"] ?? []);
|
||||
const walk = (items: SideItem[], trail: string[]) => {
|
||||
for (const item of items) {
|
||||
if (item.link && item.link.startsWith("/")) {
|
||||
map.set(item.link, [...trail, cleanLabel(item.text)]);
|
||||
} else if (item.items?.length) {
|
||||
walk(item.items, [...trail, cleanLabel(item.text)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const group of groups) {
|
||||
walk(group.items ?? [], [cleanLabel(group.text)].filter(Boolean));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 从渲染后的 HTML 提取 H1 纯文本(JSON-LD headline 须与页面 H1 一致) */
|
||||
function h1OfHtml(html?: string): string {
|
||||
const m = (html || "").match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
||||
if (!m) return "";
|
||||
return m[1]
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/** 从 Markdown 源提取 H1 纯文本(去除行内 markdown 标记) */
|
||||
function h1OfMd(md: string): string {
|
||||
// 围栏感知:跳过 ``` 代码块内的 # 注释,取真正的页面 H1
|
||||
let inFence = false;
|
||||
for (const line of md.split("\n")) {
|
||||
if (line.startsWith("```")) { inFence = !inFence; continue; }
|
||||
if (!inFence && /^# (?!#)/.test(line)) {
|
||||
return line
|
||||
.replace(/^# /, "")
|
||||
.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function createSeoHooks(sidebar: unknown) {
|
||||
const crumbs = buildCrumbMap(sidebar);
|
||||
const projectDirs: string[] = [];
|
||||
let gitDates: Map<string, string> | null = null;
|
||||
|
||||
const readMd = (relativePath: string, srcDir?: string): string => {
|
||||
try {
|
||||
return fs.readFileSync(
|
||||
path.resolve(srcDir || process.cwd(), relativePath),
|
||||
"utf8",
|
||||
);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 项目内每个文件最后一次 git 提交的 ISO 时间。
|
||||
* 单次 git log 调用构建全量映射(key = 相对路径,与 pageData.relativePath 一致);
|
||||
* git 不可用时静默降级(sitemap 无 lastmod、JSON-LD 无 dateModified)。
|
||||
*/
|
||||
const getGitDates = (root: string): Map<string, string> => {
|
||||
if (gitDates) return gitDates;
|
||||
gitDates = new Map<string, string>();
|
||||
try {
|
||||
const out = execSync(
|
||||
"git log --format=:::%cI --name-only --relative -n 500 -- .",
|
||||
{
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
let current = "";
|
||||
for (const line of out.split(/\r?\n/)) {
|
||||
if (line.startsWith(":::")) {
|
||||
current = line.slice(3).trim();
|
||||
continue;
|
||||
}
|
||||
const file = line.trim();
|
||||
if (file && current && !gitDates.has(file)) {
|
||||
gitDates.set(file, current);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 降级:无 lastmod / dateModified
|
||||
}
|
||||
return gitDates;
|
||||
};
|
||||
|
||||
/** 每页独立 title(去 "5.10 " 编号前缀,关键词前置)与 description(:::tip 首句) */
|
||||
function transformPageData(pageData: PageDataLike, ctx?: any): Record<string, unknown> {
|
||||
const srcDir: string | undefined = ctx?.siteConfig?.srcDir;
|
||||
const root: string | undefined = ctx?.siteConfig?.root;
|
||||
if (root && !projectDirs.includes(root)) projectDirs.push(root);
|
||||
|
||||
const out: Record<string, unknown> = {};
|
||||
const rel = pageData.relativePath;
|
||||
if (rel && !pageData.frontmatter?.description) {
|
||||
const desc = extractDescription(readMd(rel, srcDir));
|
||||
if (desc) out.description = desc;
|
||||
}
|
||||
if (pageData.title) {
|
||||
out.title = stripNumberPrefix(pageData.title);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 构建期注入 canonical / OG / twitter / JSON-LD */
|
||||
function transformHead(ctx: TransformCtx): HeadTuple[] {
|
||||
const rel = ctx?.pageData?.relativePath || "";
|
||||
if (!rel || rel === "404.md") return [];
|
||||
|
||||
const route = routeOf(rel);
|
||||
const url = `${SITE_URL}${route}`;
|
||||
const title = ctx.pageData?.title || ctx.title || "";
|
||||
const desc = ctx.description || "";
|
||||
const isHome = rel === "index.md";
|
||||
const image = `${SITE_URL}/logo.png`;
|
||||
|
||||
const heads: HeadTuple[] = [
|
||||
["link", { rel: "canonical", href: url }],
|
||||
["meta", { property: "og:type", content: isHome ? "website" : "article" }],
|
||||
["meta", { property: "og:site_name", content: "DjangoAdmin" }],
|
||||
["meta", { property: "og:locale", content: "zh_CN" }],
|
||||
["meta", { property: "og:title", content: title }],
|
||||
["meta", { property: "og:description", content: desc }],
|
||||
["meta", { property: "og:url", content: url }],
|
||||
["meta", { property: "og:image", content: image }],
|
||||
["meta", { property: "twitter:title", content: title }],
|
||||
["meta", { property: "twitter:description", content: desc }],
|
||||
["meta", { property: "twitter:url", content: url }],
|
||||
["meta", { property: "twitter:image", content: image }],
|
||||
];
|
||||
|
||||
// ---- JSON-LD ----
|
||||
const root = ctx.siteConfig?.root || projectDirs[0] || process.cwd();
|
||||
const srcDir = ctx.siteConfig?.srcDir || root;
|
||||
const lastMod = getGitDates(root).get(rel);
|
||||
const blocks: Record<string, any>[] = [];
|
||||
|
||||
if (isHome) {
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
name: "DjangoAdmin 开发文档",
|
||||
url: `${SITE_URL}/`,
|
||||
inLanguage: "zh-CN",
|
||||
description: desc,
|
||||
});
|
||||
blocks.push({ "@context": "https://schema.org", ...ORG });
|
||||
} else {
|
||||
const md = readMd(rel, srcDir);
|
||||
const article: Record<string, any> = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
headline: h1OfMd(md) || h1OfHtml(ctx.content) || title,
|
||||
description: desc,
|
||||
url,
|
||||
inLanguage: "zh-CN",
|
||||
mainEntityOfPage: { "@type": "WebPage", "@id": url },
|
||||
publisher: ORG,
|
||||
};
|
||||
if (lastMod) article.dateModified = lastMod;
|
||||
blocks.push(article);
|
||||
|
||||
const trail = crumbs.get(route);
|
||||
if (trail?.length) {
|
||||
const items: Record<string, any>[] = [
|
||||
{ "@type": "ListItem", position: 1, name: "首页", item: `${SITE_URL}/` },
|
||||
];
|
||||
trail.forEach((name, i) => {
|
||||
const node: Record<string, any> = {
|
||||
"@type": "ListItem",
|
||||
position: i + 2,
|
||||
name,
|
||||
};
|
||||
if (i === trail.length - 1) node.item = url;
|
||||
items.push(node);
|
||||
});
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items,
|
||||
});
|
||||
}
|
||||
|
||||
const faqs = extractFaqs(md);
|
||||
if (faqs.length) {
|
||||
blocks.push({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: faqs.map((f) => ({
|
||||
"@type": "Question",
|
||||
name: f.q,
|
||||
acceptedAnswer: { "@type": "Answer", text: f.a },
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 转义 "<",防止内容中出现 </script> 破坏注入
|
||||
const json = JSON.stringify(blocks).replace(/</g, "\\u003c");
|
||||
heads.push(["script", { type: "application/ld+json" }, json]);
|
||||
return heads;
|
||||
}
|
||||
|
||||
/** sitemap.xml:过滤 404/README,注入 git lastmod */
|
||||
const sitemap = {
|
||||
hostname: SITE_URL,
|
||||
transformItems(items: { url: string; lastmod?: string }[]) {
|
||||
const root = projectDirs[0] || process.cwd();
|
||||
const dates = getGitDates(root);
|
||||
const out: { url: string; lastmod?: string }[] = [];
|
||||
for (const item of items) {
|
||||
// ⚠ vitepress 传入的 item.url 是相对路径("404.html"、"zh/x.html"、""),
|
||||
// 无前导斜杠也无域名——hostname 由 SitemapStream 事后拼接。
|
||||
if (/(^|\/)404\.html$/.test(item.url)) continue; // 404 页不收录
|
||||
if (/(^|\/)README\.html$/.test(item.url)) continue; // 仓库 README 非文档页
|
||||
const p = item.url.replace(/^\//, "");
|
||||
const rel =
|
||||
p === "" || p === "index.html" ? "index.md" : p.replace(/\.html$/, ".md");
|
||||
const lastmod = dates.get(rel);
|
||||
out.push(lastmod ? { ...item, lastmod } : item);
|
||||
}
|
||||
return out;
|
||||
},
|
||||
};
|
||||
|
||||
return { transformPageData, transformHead, sitemap };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig, HeadConfig } from "vitepress";
|
||||
|
||||
export const META_IMAGE = "/social.png";
|
||||
export const META_IMAGE = "/logo.png";
|
||||
|
||||
const rControl = /[\u0000-\u001f]/g;
|
||||
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g;
|
||||
@@ -23,6 +23,8 @@ export const slugify = (str: string): string =>
|
||||
|
||||
export const sharedConfig = defineConfig({
|
||||
title: "小蚂蚁云",
|
||||
// 页面 <title> 后缀:「环境准备 | DjangoAdmin 开发文档」(index.md frontmatter 可覆盖)
|
||||
titleTemplate: "DjangoAdmin 开发文档",
|
||||
appearance: "light",
|
||||
|
||||
markdown: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { DefaultTheme, LocaleSpecificConfig } from "vitepress";
|
||||
|
||||
export const META_URL = "https://www.djangoadmin.cn";
|
||||
export const META_TITLE = "企业级中台解决方案";
|
||||
export const META_DESCRIPTION = "企业级中台解决方案";
|
||||
export const META_URL = "http://docs.tornado.naivevue.djangoadmin.cn";
|
||||
export const META_TITLE = "DjangoAdmin Tornado+NaiveUI 开发文档";
|
||||
export const META_DESCRIPTION =
|
||||
"DjangoAdmin Tornado+NaiveUI 企业级中台解决方案开发者文档";
|
||||
|
||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
description: META_DESCRIPTION,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
head:
|
||||
- - meta
|
||||
- name: robots
|
||||
content: noindex
|
||||
---
|
||||
|
||||
# 页面不存在
|
||||
|
||||
抱歉,您访问的页面不存在或已被移动。
|
||||
|
||||
[返回首页](/) · [浏览文档](/zh/introduction)
|
||||
@@ -5,7 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs": "vitepress dev .",
|
||||
"docs:build": "vitepress build .",
|
||||
"docs:llms": "node scripts/generate-llms-txt.mjs",
|
||||
"docs:build": "node scripts/generate-llms-txt.mjs && vitepress build .",
|
||||
"docs:preview": "vitepress preview ."
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# DjangoAdmin Tornado+NaiveUI 开发文档
|
||||
|
||||
> DjangoAdmin Tornado+NaiveUI 企业级中台解决方案开发者文档。内置 RBAC 权限体系、代码生成器等开箱即用功能,支持五种数据库一键切换。本文档共 138 篇,按章节组织。
|
||||
|
||||
## 1. 了解项目
|
||||
|
||||
- [更新日志](http://docs.tornado.naivevue.djangoadmin.cn/zh/changelog.html): 更新日志是项目演进的完整记录,帮助开发者和企业了解每个版本的能力变化与技术演进方向。
|
||||
- [新手入门指南](http://docs.tornado.naivevue.djangoadmin.cn/zh/course.html): 详细的安装步骤请参考 环境准备 章节。
|
||||
- [项目介绍](http://docs.tornado.naivevue.djangoadmin.cn/zh/introduction.html): DjangoAdmin 是企业级中台解决方案,后端基于 Tornado + SQLAlchemy,前端基于 Vue3 + NaiveUI,内置 RBAC 权限、代码生成器等开箱即用功能,
|
||||
- [项目目录结构](http://docs.tornado.naivevue.djangoadmin.cn/zh/struct.html): + 保持一致性:目录结构一旦确定,应保持一致性,避免随意更改。
|
||||
- [为什么选择我们?](http://docs.tornado.naivevue.djangoadmin.cn/zh/why.html): 本框架提供开箱即用的企业级中台完整解决方案,免去从零搭建成本,助力中大型企业构建统一的数字化基座。
|
||||
|
||||
## 2. 快速入门
|
||||
|
||||
- [后端启动](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/backend.html): 启动后端前,请确保已完成 环境准备 章节中的所有软件安装。
|
||||
- [数据库初始化](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/database.html): 数据库初始化前,请确保已完成以下准备工作: 1. 目标数据库服务已启动(如 MySQL 8.0+) 2. .env 文件中的 DB_DRIVER 和数据库连接信息已正确配置 3. Python
|
||||
- [Docker 快速部署](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/docker.html): Docker 部署适合快速体验和生产环境。
|
||||
- [环境变量速查表](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/env-vars.html): 1. .env.example 是环境变量模板文件,已提交到版本控制。
|
||||
- [环境准备](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/environment.html): 1. 部分软件安装后需要设置本地环境变量,便于在终端中使用命令。
|
||||
- [常见问题FAQ](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/faq.html): 遇到问题时,建议先查看终端日志输出和浏览器控制台(F12)的错误信息,通常能快速定位问题原因。
|
||||
- [第一个接口调试](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/first-api.html): 开始调试前,请确保已完成以下步骤: 1. 后端服务已启动(参见 后端启动) 2. 数据库已初始化(参见 数据库初始化) 3. Redis 服务已启动
|
||||
- [前端启动](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume1/frontend.html): 启动前端前,请确保已完成 环境准备 章节中的 Node.js 和 pnpm 安装,并已启动后端服务(参见 后端启动)。
|
||||
|
||||
## 3. 模块开发实战
|
||||
|
||||
- [完整代码清单](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/code-list.html): 本页汇总友链(Link)模块涉及的全部文件代码,开发时可快速查阅与复制对照。
|
||||
- [示例模块(Example)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/example.html): src/modules/example/ 是一个完整的示例模块,演示了标准 CRUD 模块的六文件结构和开发模式。
|
||||
- [常见问题与排错](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/faq.html): 本页汇总模块开发过程中的常见问题与排错方法,开发受阻时按症状索引即可定位。
|
||||
- [前端 API 层](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/fe-api.html): 前端 API 层封装后端接口的 HTTP 请求,每个业务模块对应一个 API 文件,本页讲解基于 Axios 封装的编写规范。
|
||||
- [前端路由与菜单](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/fe-router.html): 本页讲解前端路由与菜单机制——路由和菜单由后端动态返回,前端只需注册路由组件映射。
|
||||
- [前端页面视图](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/fe-view.html): 前端页面视图是用户直接交互的界面,本页讲解 Vue3 + NaiveUI 下一个完整 CRUD 页面的文件组成与编写方式。
|
||||
- [请求处理器(Handler)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/handler.html): Handler 层是 HTTP 接口层,负责接收请求、校验权限与记录操作日志,编写新接口从本页开始。
|
||||
- [数据模型(Model)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/model.html): BaseModel 是所有业务模型的基类,提供6个通用审计字段和 to_dict() 序列化方法。
|
||||
- [概述与目标](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/overview.html): 其中 id、create_user、create_time、update_user、update_time、is_delete 由基类 BaseModel 自动提供,业务字段仅需定义 name、
|
||||
- [权限节点配置](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/permission.html): 实际开发中,查询类权限(page、detail、list)通常合并为一个节点 sys:position:list,减少配置工作量。
|
||||
- [Repository 层](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/repository.html): Repository 层是数据访问层,封装全部数据库查询操作,本页讲解基于 BaseRepository 基类的通用 CRUD 用法。
|
||||
- [路由注册](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/router.html): 新模块只需两步: 1. 在 src/api/v1/ 下创建路由文件,定义 routes 列表 2. 在 router.py 的 _RESOURCE_MODULES 中追加模块导入 路由前缀
|
||||
- [Pydantic Schema](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/schema.html): Schema 层负责请求数据校验,本页讲解基于 Pydantic v2 BaseModel 与 Field 的表单类定义规范。
|
||||
- [Service 层](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume2/service.html): repo_cls 是 类属性,值为 Repository 类(不是实例)。
|
||||
|
||||
## 4. 架构设计
|
||||
|
||||
- [核心基类体系](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/base-classes.html): 四大基类各司其职,覆盖从数据模型到请求校验再到业务逻辑的完整链路。
|
||||
- [缓存架构设计](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/cache.html): 采用两级缓存架构:第一级为进程内字典缓存(dict_util),第二级为 Redis 分布式缓存。
|
||||
- [数据库设计](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/database-design.html): 所有业务表继承 base_model 基类,自动获得 6 个通用字段(id / create_user / create_time / update_user / update_time /
|
||||
- [目录结构设计](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/directory.html): 目录结构遵循「按功能分组、按职责分层」的原则。
|
||||
- [扩展性设计](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/extensibility.html): 遵循开闭原则(OCP):对扩展开放,对修改关闭。
|
||||
- [分层架构设计](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/layer-arch.html): 采用经典的分层架构,核心原则是:依赖单向、职责单一、接口隔离。
|
||||
- [整体架构图](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/overview.html): 本页从全局视角阐述系统整体技术架构,涵盖前端、后端、数据库与基础设施等核心组成,理解系统全貌时从本页开始。
|
||||
- [请求完整链路](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/request-flow.html): 一个典型的 API 请求经过以下阶段:浏览器 → Nginx → Tornado → Handler 生命周期(set_default_headers → prepare →
|
||||
- [安全架构设计](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/security.html): 纵深防御:认证、授权、限流、参数校验多层防护,单一防线失效时仍有其他机制兜底 最小权限:用户仅能访问已授权接口,角色权限按需分配,遵循"够用即可"原则 数据不落地:敏感信息(Token、
|
||||
- [技术选型说明](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/tech-stack.html): Tornado 是 Python 生态中成熟的异步 Web 框架,基于自有事件循环实现非阻塞 I/O。
|
||||
- [Tornado IOLoop 与异步模型](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume3/tornado-async.html): Tornado 基于单线程事件循环(IOLoop),Handler 方法默认为协程(async def)。
|
||||
|
||||
## 5. 开发指南
|
||||
|
||||
- [API 接口速查表](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/api-cheatsheet.html): API 接口速查表,按模块分组列出所有 RESTful 接口的请求方法、路径、说明和权限标识。
|
||||
- [文章管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/article.html): 文章管理模块支持富文本编辑、分类关联、封面图片上传、图集管理等功能。
|
||||
- [分类管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/category.html): 分类管理模块用于管理树形分类数据,如文章分类、产品分类等。
|
||||
- [行政区划管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/city.html): 行政区划管理模块提供省市区三级行政区划数据的查询和管理能力,支持树形结构展示和下拉选择。
|
||||
- [公告与链接管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/content.html): 公告、链接、城市是三个简单 CRUD 内容模块,均继承 BaseService,提供标准的增删改查接口。
|
||||
- [部门/职级/岗位管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/dept.html): 部门、职级、岗位是组织架构管理的三个核心模块。
|
||||
- [文件模板管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/file-template.html): 文件模板管理模块提供文件导入导出模板的管理能力,用于维护各种业务场景的 Excel/Word 等模板文件。
|
||||
- [定时任务日志](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/job-log.html): 定时任务日志模块记录定时任务的执行历史,包括执行时间、执行结果、耗时等信息。
|
||||
- [定时任务](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/job.html): 定时任务模块基于 APScheduler 实现,支持 Cron 表达式调度、任务执行日志、动态启停等功能。
|
||||
- [菜单管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/menu.html): 菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。
|
||||
- [参数管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/param.html): 参数管理模块用于管理动态系统参数,与系统配置模块类似但更侧重于业务参数的灵活配置。
|
||||
- [角色管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/role.html): 角色是 RBAC 权限模型的核心,连接用户和菜单(权限)。
|
||||
- [用户管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/business/user.html): 用户管理是系统的核心模块,属于复杂业务模块。
|
||||
- [认证流程实战](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/auth-flow.html): 登录 → Token 获取 → 权限校验 → Token 刷新 → 退出登录的完整认证链路实战说明。
|
||||
- [验证码机制](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/captcha.html): 验证码是防止自动化攻击的重要安全手段。
|
||||
- [系统配置管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/config.html): 系统配置模块用于管理动态的键值对配置项,与数据字典类似但更偏向系统级参数。
|
||||
- [CORS 跨域配置](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/cors.html): CORS(Cross-Origin Resource Sharing)跨域资源共享是前后端分离架构必须处理的问题。
|
||||
- [演示模式](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/demo-mode.html): 演示模式用于在线体验环境,开启后所有写操作(新增、编辑、删除等)将被拦截,返回"演示环境,暂无操作权限"提示。
|
||||
- [数据字典使用指南](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/dict-guide.html): 数据字典的完整使用流程,从新增字典类型到在代码中使用字典数据的实操指南。
|
||||
- [字典模块](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/dict.html): 数据字典用于管理系统中的枚举值和配置项,如状态、类型、性别等。
|
||||
- [异常与响应处理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/exception.html): 定义了统一的响应格式和全局异常处理机制。
|
||||
- [优雅关闭](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/graceful-shutdown.html): 优雅关闭确保应用在收到终止信号时,能够完成正在处理的请求、关闭数据库连接、停止定时任务调度器后再退出,避免请求中断和资源泄漏。
|
||||
- [用户认证(JWT)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/jwt.html): JWT(JSON Web Token)是的核心认证机制。
|
||||
- [登录日志](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/login-log.html): 登录日志记录每次登录尝试(成功/失败),包含登录账号、IP地址、浏览器信息、登录时间等。
|
||||
- [登录防爆破](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/login-protect.html): 登录防爆破机制基于 Redis 实现同一 IP + 用户名维度的连续失败计数与锁定,防止暴力破解密码。
|
||||
- [操作日志](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/operation-log.html): 操作日志装饰器自动记录写请求(POST/PUT/DELETE/PATCH)的操作日志,无需手动埋点。
|
||||
- [权限管理(RBAC)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/rbac.html): 采用基于角色的访问控制(RBAC)模型,通过用户-角色-菜单三层关系实现细粒度的权限管理。
|
||||
- [链路追踪(TraceID)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/trace-id.html): 链路追踪通过为每个请求分配唯一的 TraceID,将同一请求的所有日志串联起来,方便问题排查和性能分析。
|
||||
- [参数验证(Pydantic)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/core/validator.html): 使用 Pydantic v2 进行请求参数验证。
|
||||
- [API 层开发](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/api.html): 前端 API 层位于 src/api/ 目录,封装所有与后端的 HTTP 请求。
|
||||
- [前端构建](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/build.html): 前端使用 Vite 构建,支持开发服务器和生产构建。
|
||||
- [公共组件库](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/components.html): 项目提供丰富的公共组件,位于 ui/src/components/ 目录,基于 NaiveUI 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。
|
||||
- [BasicForm 表单](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/form.html): BasicForm 位于 src/components/Form/src/BasicForm.vue,基于 NaiveUI 的 n-form 二次封装,通过 schemas 配置化定义表单字段,
|
||||
- [Hooks 组合式函数](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/hooks.html): 项目封装了一系列组合式函数(Hooks),位于 src/hooks/ 和 src/utils/ 目录,提供权限检查、异步 loading、防重复提交、实时时钟、项目配置等可复用逻辑。
|
||||
- [国际化](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/i18n.html): 当前项目仅支持中文界面,NaiveUI 默认使用中文,所有界面文本直接硬编码在组件中。
|
||||
- [Modal 弹窗](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/modal.html): 项目提供 BasicModal 组件(src/components/Modal/src/BasicModal.vue)和 useModal hook 管理弹窗状态。
|
||||
- [权限指令 v-perm](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/perm-directive.html): v-perm 和 v-perms 是项目自定义的 Vue 指令,用于按钮级别的权限控制,无权限时自动从 DOM 中移除元素。
|
||||
- [路由与菜单](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/router.html): 前端路由基于 Vue Router 4 实现动态路由生成,菜单数据从后端获取,通过 generator-routers.ts 递归生成路由表,路由守卫控制登录状态和权限。
|
||||
- [前端环境与结构](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/setup.html): 前端基于 Vue3 + NaiveUI 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
|
||||
- [Store 状态管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/store.html): 前端使用 Pinia 进行状态管理,核心模块包括 user(用户信息与权限)、asyncRoute(动态路由与菜单)、projectSetting(项目配置)、designSetting(主题设计)等。
|
||||
- [样式与主题](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/style.html): 前端样式方案采用 Tailwind CSS + SCSS + NaiveUI 主题覆盖,支持深色模式切换。
|
||||
- [BasicTable 表格](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/table.html): BasicTable 位于 src/components/Table/src/Table.vue,基于 NaiveUI 的 n-data-table 二次封装,提供列定义、数据请求、分页、操作列、
|
||||
- [工具函数](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/utils.html): 前端工具函数位于 src/utils/ 目录,提供认证管理、本地存储、日期格式化、树结构操作、文件下载等通用能力。
|
||||
- [页面视图开发](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/frontend/view.html): 页面视图位于 src/views/ 目录,每个业务模块遵循 index.vue(列表页)+ edit.vue(编辑弹窗)的标准模式,辅以 columns.ts(列定义)和
|
||||
- [接口响应规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/integration/api-contract.html): 后端所有接口统一返回 R.ok() / R.failed() 格式的 JSON 响应,前端通过 VAxios 的 transformRequestData 拦截器统一解包处理。
|
||||
- [字典数据联调](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/integration/dict-sync.html): 字典数据通过后端字典 API 获取,前端在下拉选择器中渲染选项。
|
||||
- [权限节点联动](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/integration/perm-sync.html): 前后端权限通过统一的权限节点字符串联动,后端 @permission_required 校验权限,前端 v-perm 指令和 TableAction 的 auth 属性控制按钮显示。
|
||||
- [代理配置](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/integration/proxy.html): 开发环境通过 Vite 代理转发 API 请求到后端,生产环境通过 Nginx 代理。
|
||||
- [文件上传联调](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/integration/upload-sync.html): 文件上传涉及前端 Upload 组件、后端上传接口和 URL 拼接三个环节,需要前后端协同配置。
|
||||
- [请求上下文(ContextVar)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/context.html): src/core/context.py 使用 Python 标准库 contextvars 管理请求级别的上下文信息,包括当前用户 ID、用户名、姓名和链路追踪 ID。
|
||||
- [数据库会话](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/db-session.html): 数据库会话通过 scoped_session 实现线程级隔离,Repository 层的写操作自动 commit,BaseHandler.on_finish() 兜底关闭会话。
|
||||
- [字典缓存工具](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/dict-util.html): src/common/utils/dict_util.py 提供业务字典的缓存读取能力,从 Redis 缓存中获取字典数据,缓存未命中时从数据库加载。
|
||||
- [导入导出](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/excel.html): 基于 openpyxl 实现 Excel 导入导出功能,支持通过 file_template 模块管理导入模板配置。
|
||||
- [通用 Helper 函数](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/helpers.html): src/common/utils/helpers.py 提供项目中广泛使用的通用工具函数,包括 ID 解析、OS/浏览器检测、参数脱敏等。
|
||||
- [IP 归属地解析](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/ip-region.html): src/common/utils/ip2region.py 基于 ip2region 离线数据库,提供 IP 地址到省市区的归属地查询能力,无需网络请求。
|
||||
- [日志系统](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/logger.html): 通过 Python 标准库 logging 实现结构化日志,支持控制台 + 文件双输出,日志文件按大小自动轮转。
|
||||
- [密码工具](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/password.html): 采用 bcrypt 双重加盐方案存储密码,源码位于 src/core/password.py。
|
||||
- [权限缓存](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/perm-cache.html): 通过 Redis 实现用户权限和菜单的缓存,采用用户级缓存 + 全局版本号双层策略。
|
||||
- [统一响应封装(R 对象)](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/response.html): src/core/response.py 提供统一的 API 响应封装,所有接口返回格式为 {code, data, msg, ok}。
|
||||
- [富文本过滤](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/rich-text.html): 富文本编辑器内容存在存储型 XSS 风险,通过 bleach 库实现 HTML 白名单清洗,在落盘前统一过滤危险内容。
|
||||
- [文件上传完整流程](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/upload-guide.html): 富文本编辑器(如 TinyMCE、CKEditor、WangEditor 等)的图片上传同样使用此接口。
|
||||
- [文件上传](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume4/tools/upload.html): 提供本地文件上传功能,支持图片和通用文件上传,内置扩展名白名单、大小限制、魔术字节校验和路径遍历防护。
|
||||
|
||||
## 6. 代码生成器
|
||||
|
||||
- [CLI 命令行使用](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/cli.html): 代码生成器 CLI 模式的使用方法,包括单表生成、批量生成、预览模式和配置导出。
|
||||
- [代码生成引擎](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/code-engine.html): 代码生成引擎的工作原理,包括配置解析、模板渲染、文件写入和自动注册的完整流程。
|
||||
- [配置文件详解](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/config.html): 代码生成器 JSON 配置文件的格式说明,包括模块名称、字段属性、字典编码等配置项的详细解释。
|
||||
- [实战案例](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/demo.html): 以 tornado_example 演示表为完整案例,演示从建表到生成到使用的全流程。
|
||||
- [常见问题](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/faq.html): 代码生成器使用过程中常见问题的原因分析和解决方案。
|
||||
- [概述与架构](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/overview.html): 代码生成器的整体架构、核心模块和生成流程概述。
|
||||
- [表结构解析引擎](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/parser.html): 表结构解析引擎的工作原理,从数据库读取表元数据并智能推断字段用途的过程。
|
||||
- [自定义模板](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/templates.html): 代码生成器模板的 Jinja2 语法说明,包括模板变量、字段属性和自定义模板的开发方法。
|
||||
- [Web 管理界面](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume5/web-ui.html): 代码生成器 Web 管理界面的使用方法,通过可视化界面操作单表生成和批量生成。
|
||||
|
||||
## 7. 运维部署
|
||||
|
||||
- [备份与恢复](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/backup.html): - 3 份数据副本(1 份生产 + 2 份备份) - 2 种不同存储介质(如本地磁盘 + 远程存储) - 1 份异地备份(防机房级灾难)
|
||||
- [跨库数据迁移](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/db-cross-migrate.html): 项目提供 scripts/migrate_db.py 脚本,支持在不同数据库之间迁移数据(如 MySQL → PostgreSQL / SQL Server / SQLite / Oracle)。
|
||||
- [数据库迁移](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/db-migrate.html): - 结构迁移:Alembic(自动生成 DDL 差异) - 数据迁移:scripts/migrate_db.py(跨库数据泵) - 初始化:scripts/init_db.py(全新安装一键建库)
|
||||
- [部署 Checklist](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/deploy-checklist.html): 从服务器准备到应用上线的完整部署操作清单,适用于生产环境首次部署和后续版本更新。
|
||||
- [Docker容器化](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/docker.html): - 环境一致性:消除"在我机器上能跑"的问题 - 快速部署:一条命令启动完整服务 - 资源隔离:应用与宿主机环境解耦
|
||||
- [环境规划](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/env-plan.html): 各环境之间保持代码一致,仅通过环境变量(.env)区分配置。
|
||||
- [健康检查与监控](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/monitoring.html): `` 1. 应用层:健康检查端点、业务指标 2. 系统层:CPU/内存/磁盘/网络 3. 中间件:MySQL/Redis 连接状态 4. 日志:集中式日志收集与分析 ``
|
||||
- [Nginx 反向代理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/nginx.html): `` 客户端 → Nginx(80/443) ├─ / → 前端静态资源(dist/) ├─ /api/v1/... → Tornado 后端(8041) └─ /api/file/ →
|
||||
- [性能调优](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/performance.html): 1. 先测量,后优化:用数据说话,避免盲目调参 2. 瓶颈优先:找到最大瓶颈,集中优化 3. 渐进式:每次只调整一个参数,观察效果
|
||||
- [安全加固](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/security-hardening.html): - 最小权限:只开放必要的端口和权限 - 纵深防御:多层安全措施叠加 - 默认安全:配置默认值应为安全选项
|
||||
- [Supervisor进程管理](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/supervisor.html): - Linux 服务器直接部署(非 Docker) - 需要进程自动重启和日志轮转 - 管理多个相关进程
|
||||
- [故障排查手册](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/troubleshooting.html): 1. 查看日志:tail -f logs/supervisor.out.log 或 docker-compose logs -f 2. 检查配置:.env 文件、数据库连接、Redis 连接 3.
|
||||
- [版本升级流程](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume6/upgrade.html): 1. 先备份,后升级 2. 先测试环境,后生产环境 3. 每次升级可回滚 4. 低峰期执行升级
|
||||
|
||||
## 8. 规范标准
|
||||
|
||||
- [API响应规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/api-response.html): - 所有接口必须通过 R.ok() 或 R.failed() 返回,禁止直接返回 dict - 成功响应 code=0,失败响应 code=1 - 响应体始终包含 code、data、msg、ok
|
||||
- [代码审查清单](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/code-review.html): - 每次提交必须经过至少一人审查 - 审查关注点:正确性 > 安全性 > 可维护性 > 性能 - 审查意见应具体、可操作,避免主观评价
|
||||
- [删除规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/deletion.html): - is_delete = 0:正常记录(默认值) - is_delete = 1:已删除记录 - 所有查询方法自动过滤 is_delete = 0,业务层无需关心
|
||||
- [文档规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/doc-standard.html): - 文档与代码同步更新 - 使用中文编写(项目约定) - 示例代码可直接运行
|
||||
- [前端 API 规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/fe-api.html): - 每个后端模块对应一个 API 文件 - 函数命名统一使用 名词 + 动词 格式(与后端保持一致) - 请求路径使用相对路径(/position/page),不含 /api/v1 前缀(由
|
||||
- [前端页面规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/fe-component.html): - Vue3(Composition API + <script setup>) - NaiveUI - 自定义组件(BasicTable / BasicForm / TableAction /
|
||||
- [Git提交规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/git.html): `` <type>(<scope>): <subject> <body> <footer> ``
|
||||
- [Handler 层规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/handler.html): - Handler 只做请求分发,不含业务逻辑 - 通过装饰器控制权限和演示模式 - 委托 Service 处理业务逻辑 - 返回 R 封装的响应
|
||||
- [分页规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/pagination.html): - 前端传递 pageNo(页码,从 1 开始)和 pageSize(每页条数) - 后端返回 {records, total, size, current, pages} 结构 - 默认每页 10
|
||||
- [Pydantic Schema规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/pydantic.html): - 请求体参数必须声明为 Pydantic 模型(Form 类) - 字段约束使用 Field(...) 定义 - 自定义校验使用 @field_validator - 唯一性校验在 Schema
|
||||
- [Repository层规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/repository.html): - Repository 只做数据访问原语,不处理 R 封装/序列化/业务逻辑 - 软删除由基类自动处理,子类无需关心 - 查询方法自动跳过 None 值条件
|
||||
- [Service层规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/service.html): BaseService 采用模板方法模式:基类定义流程骨架,子类通过声明差异点和覆盖钩子定制行为。
|
||||
- [测试规范](http://docs.tornado.naivevue.djangoadmin.cn/zh/volume7/testing.html): 1. 契约测试(Contract):验证路由、权限、表名等契约不变 2. 单元测试(Unit):验证单个函数/方法的逻辑正确性 3. 集成测试(Integration):验证模块间协作、真实数据库操作
|
||||
|
||||
## 附录
|
||||
|
||||
- [常见问题汇总](http://docs.tornado.naivevue.djangoadmin.cn/zh/faq.html): 本页汇总了项目各模块的常见问题及解决方案,按场景分类索引。
|
||||
- [免责声明](http://docs.tornado.naivevue.djangoadmin.cn/zh/other/declare.html): 使用本产品前必读的法律声明,明确产品性质、使用限制与责任划分,一旦使用本产品即视为接受本声明全部条款。
|
||||
- [广告赞助](http://docs.tornado.naivevue.djangoadmin.cn/zh/other/sponsor.html): 1. 投放广告不得与官方全系产品存在结构性冲突和竞争 2. 广告内容需符合国家法律法规,不得包含虚假、误导性信息 3. 广告素材需经官方审核通过后方可上线,审核周期 1-3 个工作日 4.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user