
数据库灾备【免费下载链接】databasusPostgreSQL backup tool with Point-In-Time-Recovery and restore verification项目地址https://gitcode.com/gh_mirrors/po/databasus点击查看免费下载本文为 Databasus一款支持 PostgreSQL 时间点恢复与恢复验证的备份工具前端代码库的工程规范指南基于 frontend/AGENTS.md 展开。读完本文你将掌握该项目在 React 19 TypeScript Vite Ant Design TailwindCSS 技术栈下的组件编写结构、剪贴板安全访问、表单渐进披露、基于typeof en强类型约束的多语言字典体系以及 Feature-Sliced Design (FSD) v2.1 的分层导入规则与代码归属决策方法并了解每条规范在仓库源码与测试中的落点。技术栈基线AntD 5 是唯一组件库Databasus 前端由 React 19、TypeScript、Vite 驱动组件库限定为AntD 5 单一来源配套 TailwindCSS 工具类负责布局与间距。规范明确要求组件只用 AntD 原语Button、Input、Modal、Form、Table、Menu、Tabs等禁止引入 Mantine、MUI、Chakra、shadcn 或 Radix图标只用ant-design/icons禁止lucide-react、heroicons/react、react-icons或 FontAwesome。frontend/package.json 中的依赖清单印证了这一约束antd ^5.29.3、ant-design/icons ^5.6.1是仅有的 UI 依赖其余如cron-parser解析备份计划的 cron 表达式、recharts图表、i18next/react-i18next国际化都属于功能库而非组件库。这种单一组件库策略保证了视觉一致性与打包体积的可控。React 组件的标准内部结构规范为每个组件文件规定了固定的声明顺序核心代码骨架如下interface Props { someValue: SomeValue; } const someHelperFunction () { ... } export const ReactComponent ({ someValue }: Props): JSX.Element { // First put states const [someState, setSomeState] useState...(...) // Then place functions const loadSomeData async () { ... } // Then hooks useEffect(() { loadSomeData(); }); // Then calculated values const calculatedValue someValue.calculate(); return div ... /div }结构顺序Structure order分五层从文件顶层到 JSX 返回依次为Props 接口— 定义组件入参组件外的辅助函数— 纯工具函数组件主体内部再细分四段States— 所有useState声明普通函数— 事件处理器、异步操作、组件内格式化函数凡是不调用 React Hook 的函数都归入此段Hooks—useRef ref 变更、useCallback、useMemo、useEffect。注意被useCallback包裹的函数属于 Hook必须放在 Hooks 段而不是普通函数段计算值— 内联派生数据例如 AntDTable的columns数组。其中一条硬性规则值得单独强调所有 Hook包括每个useEffect必须位于所有普通函数定义之后。如果某个useEffect要读取某个 handler该 handler 必须定义在它上方——不能为了把 effect 集中在前面而把 handler 挪到 effect 之后。这条规则同时规避了依赖声明与函数定义顺序带来的阅读歧义。垂直间距Vertical Spacing函数体内部要求用垂直节奏来组织在逻辑上不同的步骤之间setup / 主工作 / return、独立分支、守卫语句前后各留一行空行函数体开头和结尾不留空行绝不允许连续两行空行不要在紧凑表达式、跨行的单条语句、或不超过 5 行的短函数内部插空行如果仅靠空行已经无法让人快速定位函数体结构正确做法是抽取提取函数/常量/文件而不是加注释。剪贴板操作ClipboardHelper 统一收口规范规定一切剪贴板操作必须走ClipboardHelperfrontend/src/shared/lib/ClipboardHelper.ts禁止直接调用navigator.clipboard。查看源码可以看到两条 API 的完整实现export class ClipboardHelper { static isClipboardApiAvailable(): boolean { return !!(navigator.clipboard window.isSecureContext); } static async copyToClipboard(text: string): Promisevoid { if (this.isClipboardApiAvailable()) { await navigator.clipboard.writeText(text); return; } // 非安全上下文如 HTTP下回退到隐藏 textarea execCommand(copy) const textarea document.createElement(textarea); textarea.value text; textarea.style.position fixed; textarea.style.opacity 0; document.body.appendChild(textarea); textarea.select(); document.execCommand(copy); document.body.removeChild(textarea); } // ... }按规范的使用模式复制直接调ClipboardHelper.copyToClipboard(text)。源码显示它先探测isClipboardApiAvailable()在非安全上下文HTTP 部署时isSecureContext为false自动回退到execCommand(copy)方案——这对自托管在内网 HTTP 地址下的 Databasus 实例是必要的兼容路径。粘贴先检查ClipboardHelper.isClipboardApiAvailable()。可用时调用ClipboardHelper.readFromClipboard()不可用时展示ClipboardPasteModalComponent位于shared/ui让用户通过文本输入框手动粘贴。navigator.clipboard.readText()同样依赖安全上下文与用户手势因此能读则读、不能读则兜底是这套交互的必然形态。表单的渐进披露Progressive Disclosure表单交互遵循两条渐进披露规则提交按钮Save、Update password等只在表单处于脏状态dirty时渲染——没有任何修改时页面上根本不存在提交入口依赖字段例如Confirm password只在它所依赖的字段已有值之后才渲染。这两条规则把未修改的表单和待提交的表单在视觉上区分开减少用户误触也符合备份工具这类低频但高风险操作的表单语境改密码、改存储凭证等。面向用户的文案排版英文字典frontend/src/shared/i18n/locales/en.ts中所有用户可见字符串标签、描述、通知、模态框正文、错误信息统一使用普通连字符-em dash—与 en dash–只允许出现在 Markdown 文档和代码注释中。其他语言则遵循各自语言的排版惯例规范明确指向 website/AGENTS.md 的 Translation quality 章节俄语保留其语法需要的«—»法语在: ; ! ?和%前加空格中文使用全角标点。该章节同时列出了每种语言的规范界面术语。i18n 体系以 en 为唯一键源的多语言字典界面通过i18nextreact-i18next提供6 种语言英语、俄语、西班牙语巴西葡语、简体中文、法语全部位于shared/i18n目录。frontend/src/shared/i18n/dictionaries.ts 是整个体系的中枢export const DICTIONARIES { en, ru, es, pt, zh, fr }; export type Locale keyof typeof DICTIONARIES; export const DEFAULT_LOCALE: Locale en;字典的强完整性约束en 是键集合的唯一事实来源source of truth。其他每种字典在类型上都是typeof en因此缺失一个键或多留一个键都会导致pnpm build失败——任何语言都不可能处于半翻译状态发布出去每个字典必须是单文件内的单个对象字面量。TypeScript 对多余键的检测只对原地书写的字面量生效如果通过变量传入嵌套对象或使用展开运算符spread类型检查就会失效键按业务域分组backups、databases、storages、status、errors、common等而不是按文件路径分组——组件移动位置不会牵连键名变更frontend/src/shared/i18n/dictionaries.test.ts 在测试层面再加一道闸把每种字典拍平后与英文逐键比对确保每条翻译的{{placeholders}}、Trans标签集合与英文一致code片段逐字节相同且没有空消息。源码中的正则值得参考// {{name}} and {{name, format}} both count as the placeholder name const getPlaceholders (text: string) uniqueSorted([...text.matchAll(/\{\{\s*([^}\s,])[^}]*\}\}/g)].map((m) m[1])); // bold, /bold and workspaceName/ all count as the Trans tag of that name const getTransTags (text: string) uniqueSorted([...text.matchAll(/\/?([A-Za-z][\w-]*)\s*\/?/g)].map((m) m[1]));也就是说占位符/标签与英文一致不仅靠 lint而是有 vitest 测试pnpm test持续验证。代码侧的强制规则这是本文档中约束最密集的一节逐条展开src中不允许任何用户可见字符串字面量。由i18next/no-literal-stringlint 规则强制且覆盖 SCREAMING_SNAKE 常量和默认参数值上游插件默认豁免这两类项目专门改写了规则——见下文eslint 配置一节。技术字符串保持字面量命令、代码块、连接串、cron 表达式、引擎与工具名、文件路径、localStorage键。个别确实需要豁免的技术字符串使用// eslint-disable-next-line i18next/no-literal-string -- reason逐条说明理由在 JSX 中则把它写成独立一行的表达式{pg_dump}以便指令生效。整个文件只有技术字符串时例如 shell 命令构造器允许在文件顶部一次性禁用并写明理由。frontend/eslint.config.js 是这套规则的真正实现。可以看到项目把插件的no-literal-string规则做了二次包装const i18nextLiteralStringOptions { mode: all, // 检查所有字符串字面量与模板而不只是 JSX 文本 should-validate-template: true, jsx-components: { exclude: [Trans] }, // 排除永不承载文案的属性className、data-*、href、mode、placement…… jsx-attributes: { exclude: [ /* 正则与白名单 */ ] }, words: { exclude: [ [\\s0-9!-/:-[-{-~], // 0 2 * * *、******** 等 (https?|wss?|s?ftp|s3|mongodb(\\srv)?|postgres(ql)?|mysql|mariadb|jdbc:\\w)://\\S*, // 连接串 \\.{0,2}/\\S*, // 绝对/相对路径 #[0-9a-fA-F]{3,8}, // CSS 颜色值 -?\\d(\\.\\d)?(px|rem|em|%|vh|vw|ms|s|fr), // CSS 尺寸 [\\w.-][\\w-](\\.[\\w-]), // 示例邮箱 [a-z]{2}-[A-Z]{2}, // BCP 47 语言标签 ], }, // ... };其中words.exclude的正则白名单非常贴切备份工具的业务postgres(ql)?、mysql、mariadb、mongodb(\\srv)?、s3等连接串前缀直接豁免cron 表达式如0 2 * * *由纯数字/标点正则豁免。这解释了为什么备份配置表单里可以直接写引擎名与调度表达式而不会被 lint 拦截。组件内取文案用const { t } useTranslation()来自react-i18next。组件之外的代码存键不存文本。模块级常量、标签表、解析器如果直接调t()会冻结在执行时激活的语言上。正确做法是存TranslationKey带参数的消息存LocalizedText{ key, params }由组件在渲染时翻译t(key)、translateLocalizedText(text, t)。状态同理state 里存错误或键不存翻译后的字符串。每个面向用户的 enum 都有标签表。形式为TYPE_LABEL_KEYS: RecordEnum, TranslationKey放在它标注的类型旁边并通过该 slice 的index.ts导出。永远不要用值拼键t(status.${value})——这样新增一个枚举成员时构建会直接失败而不是在运行时悄悄显示原始值。句子保持完整。带内联标记的句子是一个键通过Trans渲染Trans i18nKey... components{{ code: InlineCodeComponent /, bold: strong /, docsLink: a href{...} / }} /键在变量中时用shared/i18n提供的TransByKey因为Trans无法对整个键联合做类型检查。字典里写命名标签例如Run codepg_dump/code first。值插值到完整句子中Delete {{name}}?绝不把翻译碎片拼起来当插入的词在其他语言中会屈折变化时每个值各配一个整句键。用户在 Trans 句子中录入的值工作区、数据库、用户名是自闭合标签元素由组件提供字典写Delete workspaceName/?组件写components{{ workspaceName: strong{name}/strong }}。绝不能把它塞进values——Trans会展开其中的占位符语法。这一安全细节在 frontend/src/shared/i18n/createI18n.ts 中另有呼应react: { useSuspense: false, // 只有组件通过 components 提供的标签才变成元素 // 字典里孤立出现的 strong 之类会原样显示为文本 transKeepBasicHtmlNodesFor: [], // 先转义 values 再解析标签否则 strongx/strong 这样的值会变成元素 transDefaultProps: { shouldUnescape: true, tOptions: { interpolation: { escapeValue: true } }, }, },行内代码一律走InlineCodeComponentshared/ui不用裸code——仅等宽字体不足以把代码从周围文字中区分出来。计数句式写成一个字符串适配所有数量Members: {{count}}而不是{{count}} members。字典没有复数形式因为俄语的_few变体在typeof en下会成为游离键。翻译文本绝不当作 HTML 渲染禁用dangerouslySetInnerHTML。依赖语言的输出从 Hook 获取这样切换语言才能触发重渲染文本用useTranslation数字格式化用useLocale()返回的formatNumber替代toLocaleString()相对时间用formatRelativeTime替代 dayjs 的fromNow()。API 错误到达用户的唯一通道是translateApiError(error, t)。frontend/src/shared/i18n/translateApiError.ts 的实现清晰呈现了这个三层兜底export const translateApiError (error: unknown, t: TFunction): string { if (!(error instanceof ApiError)) { return t(errors.unknown); } const codeKey error.code ? API_ERROR_CODE_KEYS[error.code] : undefined; if (codeKey) { return t(codeKey, { status: error.status ?? }); // 已知错误码 - 翻译 } // 后端消息以小写开头user is already a member return error.message ? StringUtils.capitalizeFirstLetter(error.message) : t(errors.unknown); };已知错误码给翻译否则用后端消息最后兜底通用消息——绝不直接展示error.message。指向 databasus.com 页面的链接统一走getWebsitePageUrl(pageId, locale)配合WEBSITE_PAGES常量表在官网发布了对应语言版本时自动以所选语言打开页面。时钟制式与日期顺序读navigator.language是有意的shared/time/getUserTimeFormat.ts与shared/time/utils.ts刻意读取navigator.language12/24 小时制和日/月先后是区域性约定不是语言属性——英语世界两种时钟制式都有人用。浏览器报告的是用户所在区域而界面语言选择只决定用哪些词。因此这两处读取被明确保留但裸的toLocaleString()不被允许因为数字千分位应跟随所选语言由useLocale()提供的formatNumber处理。FSDFeature-Sliced Designv2.1 分层架构项目采用 FSD v2.1启用的层级为app/、pages/、widgets/、features/、entity/、shared/对照仓库可见 frontend/src 下正是entity/、features/、pages/、shared/、widgets/五个目录。导入方向依赖只能向下app → pages → widgets → features → entity → shared。模块只能从严格位于其下方的层级导入同层之间不同 slice 的横向导入被禁止。✅ 允许 某个 widget 导入 features/backups, entity/backups, shared/ui 某个 feature 导入 entity/databases, shared/api 某个 entity 导入 shared/lib ❌ 违规 某个 entity 导入 features/databases 向上entity 低于 features 某个 feature 导入 pages/AuthPageComponent 向上pages 高于 features features/users 导入 features/backups 同层跨 slice 导入 shared/lib 导入 entity/users 向上shared 是最底层一个实现细节值得注意项目没有配置路径别名上述路径为便于阅读而以src/为基准书写真实 import 全部是相对路径。新代码放哪里只在一个页面使用 → 留在该页面的pages/slice 内可复用基础设施、不含业务逻辑→shared/UI kit、utils、API client、路由常量、认证令牌、CRUD 辅助;在 2 个以上页面复用的用户交互 →features/在 2 个以上页面/feature 复用的领域模型 →entity/应用级 Provider、路由、主题 →app/。拿不准时就留在pages/直到出现第二个真实消费方再抽取。快速归属表场景单一使用两处及以上复用个人资料表单pages/profile/ui/ProfileForm.tsxfeatures/profile-form/数据库卡片pages/databases/ui/DatabaseCard.tsxentity/database/ui/DatabaseCard.tsx备份数据抓取pages/backup/api/fetch-backup.tsentity/backup/api/认证令牌 / 会话shared/auth/永远shared/auth/永远登录表单pages/login/ui/LoginForm.tsxfeatures/auth/CRUD 辅助shared/api/永远shared/api/永远日期格式化工具—shared/lib/format-date.ts模态框内容pages/[page]/ui/SomeModal.tsx—五条 MUST 规则只向下导入。禁止向上导入禁止同层跨 slice 导入对外 API 只通过index.ts暴露。外部消费方只能从 slice 的index.ts导入不能碰内部文件✅ features/users ❌ features/users/ui/AuthNavbarComponent按业务域命名文件而不是按技术角色// ❌ model/types.ts, model/utils.ts, lib/helpers.ts // ✅ model/user.ts, model/backup.ts, api/fetch-database.tsshared/中禁止业务逻辑。共享层只放基础设施领域计算放在entity/或更高层。model/中一个类型一个文件。每个代表领域实体、DTO、请求体或响应形状的interface/class/enum单独一个文件以类型命名。不要把FooFooStatusFooResponse这样的兄弟类型塞进同一个Foo.ts——拆开后各自仍从 slice 的index.ts再导出消费方依旧只依赖 slice 的公共 API// ❌ model/RestoreVerification.ts — 枚举 表统计 主接口挤在一个文件 // ✅ model/RestoreVerification.ts — interface RestoreVerification // ✅ model/RestoreVerificationTableStat.ts — interface RestoreVerificationTableStat // ✅ model/VerificationStatus.ts — enum VerificationStatus // ✅ model/VerificationTrigger.ts — enum VerificationTrigger例外与父类型紧耦合的小型纯形状辅助类型字面量联合别名、Pick可以留在父类型文件里。拆分适用于有自己身份的任何类型——即任何你合理地会单独去别处 import 的类型。Slice 内部的段Segmentsui/— 组件与样式model/— 状态、类型、领域逻辑、校验api/— 后端调用、请求函数、API 专属类型lib/— 仅供本 slice 使用的内部辅助config/— slice 级配置 / 特性开关app/与shared/只有段、没有 slice其内部各段之间允许互相导入。AVOID 清单过早创建 entity单一消费方 → 留在页面里把 CRUD 放进entity/——CRUD 是基础设施归shared/api/仅仅为了认证令牌 / DTO 创建userentity——那些属于shared/auth/或shared/api/为了将来复用而抽取单一使用的代码上帝 slice如user-management/同时覆盖认证 个人资料 密码——按聚焦职责拆分从一个 entity 导入另一个 entity 的ui/段——entity 的 UI 只允许被 features/widgets/pages 导入滥用x跨导入写法——那是最后手段不是常规工具。同层 slice 之间的共享代码四步降级策略当同层两个 slice 需要共享代码时按顺序尝试合并——如果两者总是同步变更它们本就是一个 slice把共享逻辑下沉到entity/——UI 留在 features/widgets在更高层组合IoC——父级 page/widget 导入两者通过 props/slots 接线x记法——仅限 entity 之间、显式且有文档说明的跨导入。最后手段。重构纪律格式化与 Lint 是收尾动作规范最后一条应用变更时不要忘记重构旧代码——可以缩短、提升可读性、改善质量公共逻辑可抽到函数、常量或文件。在frontend/根目录下每次50~100 行以上的较大改动之后必须执行pnpm format # Prettier 全量格式化含 import 排序插件 pnpm lint # ESLint 校验改动含 i18next/no-literal-string这两个脚本定义在 frontend/package.jsonformat实际是prettier --write **/*.{ts,tsx,js,jsx,json,css,md}配合trivago/prettier-plugin-sort-imports与prettier-plugin-tailwindcsslint是eslint .即上文分析的 frontend/eslint.config.js 规则集。此外还有pnpm testvitest 全量运行包含 i18n 字典一致性测试与pnpm buildtsc -b vite build由typeof en键完整性检查把关。小结frontend/AGENTS.md 所定义的这套规范把 Databasus 前端的质量约束拆成了四个可机器执行的层面tsc -b的类型系统字典键完整性、enum 标签表、ESLintno-literal-string全模式 连接串/cron 白名单、Vitest占位符与 Trans 标签跨语言一致性、Prettier格式与 import 排序。再加上 FSD 的分层导入纪律与拿不准就留在 pages/的克制原则共同支撑一个 6 语言、多备份引擎管理界面在长期演进中保持结构清晰与文案零遗漏。对于读者这套实践中最可迁移的三点是以源语言字典为typeof锚点强制全量翻译、用 Record 标签表代替运行时拼键、以及把 lint 规则的豁免做成显式正则白名单而非随手eslint-disable。赞分享数据库灾备【免费下载链接】databasusPostgreSQL backup tool with Point-In-Time-Recovery and restore verification项目地址https://gitcode.com/gh_mirrors/po/databasus点击查看免费下载相关推荐sourcekit-lsp代码补全深度解析提升开发效率的5个技巧sourcekit lsp代码补全深度解析提升开发效率的5个技巧 sourcekit lsp是Swift和C系列语言的Language Server Prot开发工具终极科普sptransformer-npu 是什么RNA剪接位点预测模型与昇腾NPU适配全解析终极科普sptransformer npu 是什么RNA剪接位点预测模型与昇腾NPU适配全解析 sptransformer npu 是一个面向 RNA 剪接人工智能生物信息学本地部署AscendRedisInsight 前端国际化i18n体系解析i18next react-i18next 的工程化实践RedisInsight 前端国际化i18n体系解析i18next react i18next 的工程化实践 RedisInsight 是 Redis数据库客户端桌面应用后端前端数据可视化上一篇Vibe-Trading Tushare stk_auction 开盘竞价成交接口实战从 9:25 全市场快照到竞价抢筹选股下一篇超写实人像生成新突破FLUX.1-Kontext专用LoRA模型实战解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考