{"id":180,"date":"2025-12-29T19:00:49","date_gmt":"2025-12-29T11:00:49","guid":{"rendered":"https:\/\/erishen.cn\/?p=180"},"modified":"2026-01-04T22:32:23","modified_gmt":"2026-01-04T14:32:23","slug":"%e4%bb%8e%e9%9b%b6%e6%9e%84%e5%bb%ba%e5%85%a8%e6%a0%88%e6%8a%80%e6%9c%af%e7%9f%a5%e8%af%86%e5%ba%93%e5%b9%b3%e5%8f%b0%ef%bc%9amonorepo-next-js-14-fastapi-%e5%ae%9e%e8%b7%b5","status":"publish","type":"post","link":"https:\/\/erishen.cn\/?p=180","title":{"rendered":"\u4ece\u96f6\u6784\u5efa\u5168\u6808\u6280\u672f\u77e5\u8bc6\u5e93\u5e73\u53f0\uff1aMonorepo + Next.js 14 + FastAPI \u5b9e\u8df5"},"content":{"rendered":"\n<div class=\"quote-block-wrapper\"><blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u57fa\u4e8e Turborepo + Next.js 14 + FastAPI \u7684\u5168\u6808\u5f00\u53d1\u5b9e\u8df5\uff0c\u9002\u5408\u4e2d\u5c0f\u578b\u9879\u76ee\u7684\u67b6\u6784\u8bbe\u8ba1\u548c\u90e8\u7f72\u65b9\u6848\u3002<\/p>\n<\/blockquote><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"630\" src=\"https:\/\/erishen.cn\/wordpress\/wp-content\/uploads\/2025\/12\/cover-modern.png\" alt=\"\" class=\"wp-image-198\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E8%83%8C%E6%99%AF%E4%B8%8E%E5%AE%9A%E4%BD%8D\">\u9879\u76ee\u80cc\u666f\u4e0e\u5b9a\u4f4d<\/h2>\n\n\n\n<p>\u5728\u591a\u5e74\u7684\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u6211\u53d1\u73b0\u81ea\u5df1\u79ef\u7d2f\u7684\u6280\u672f\u77e5\u8bc6\u70b9\u5f80\u5f80\u96f6\u6563\u5206\u5e03\u5728\u5404\u4e2a\u9879\u76ee\u548c\u5b66\u4e60\u7b14\u8bb0\u4e2d\u3002\u4e3a\u4e86\u7cfb\u7edf\u5316\u68b3\u7406\u8fd9\u4e9b\u77e5\u8bc6\uff0c\u6211\u51b3\u5b9a\u6784\u5efa\u4e00\u4e2a\u6280\u672f\u77e5\u8bc6\u5e93\u5e73\u53f0\u3002<\/p>\n\n\n\n<p><strong>\u6838\u5fc3\u9700\u6c42<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7edf\u4e00\u7ba1\u7406\u5b66\u4e60\u7b14\u8bb0\u548c\u6280\u672f\u6587\u6863<\/li>\n\n\n\n<li>\u652f\u6301\u5728\u7ebf\u7f16\u8f91\u548c\u5b9e\u65f6\u9884\u89c8<\/li>\n\n\n\n<li>\u63d0\u4f9b\u4f18\u96c5\u7684\u6587\u6863\u5c55\u793a\u4f53\u9a8c<\/li>\n\n\n\n<li>\u4f5c\u4e3a\u4e00\u4e2a\u6280\u672f\u5b9e\u8df5\u9879\u76ee\uff0c\u63a2\u7d22\u5168\u6808\u5f00\u53d1\u6700\u4f73\u5b9e\u8df5<\/li>\n<\/ul>\n\n\n\n<p>\u8fd9\u4e2a\u9879\u76ee\u4e0d\u4ec5\u6ee1\u8db3\u4e86\u4e2a\u4eba\u77e5\u8bc6\u7ba1\u7406\u7684\u9700\u6c42\uff0c\u4e5f\u5e0c\u671b\u901a\u8fc7\u5f00\u6e90\u5206\u4eab\uff0c\u7ed9\u5176\u4ed6\u5f00\u53d1\u8005\u63d0\u4f9b\u53c2\u8003\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E5%AE%9A%E4%BD%8D\">\u9879\u76ee\u5b9a\u4f4d<\/h3>\n\n\n\n<p>\u8fd9\u662f\u4e00\u4e2a<strong>\u4e2d\u5c0f\u578b\u5168\u6808\u9879\u76ee<\/strong>\uff0c\u91cd\u70b9\u5c55\u793a\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Monorepo \u67b6\u6784\u5728\u975e\u8d85\u5927\u89c4\u6a21\u9879\u76ee\u4e2d\u7684\u5e94\u7528<\/li>\n\n\n\n<li>Next.js 14 + FastAPI \u7684\u6280\u672f\u9009\u578b\u548c\u5f00\u53d1\u5b9e\u8df5<\/li>\n\n\n\n<li>Docker \u5bb9\u5668\u5316\u90e8\u7f72\u5230\u751f\u4ea7\u73af\u5883\u7684\u5b8c\u6574\u6d41\u7a0b<\/li>\n\n\n\n<li>\u6280\u672f\u77e5\u8bc6\u5e93\u7cfb\u7edf\u7684\u8bbe\u8ba1\u4e0e\u5b9e\u73b0<\/li>\n<\/ul>\n\n\n\n<p><strong>\u9002\u5408\u573a\u666f<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4e2a\u4eba\u6280\u672f\u535a\u5ba2\u548c\u77e5\u8bc6\u5e93<\/li>\n\n\n\n<li>\u5c0f\u56e2\u961f\u7684\u5168\u6808\u9879\u76ee\u67b6\u6784\u53c2\u8003<\/li>\n\n\n\n<li>\u5b66\u4e60 Next.js 14 \u548c FastAPI \u7684\u5b9e\u8df5\u6848\u4f8b<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B%E4%B8%8E%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1\">\u6280\u672f\u9009\u578b\u4e0e\u67b6\u6784\u8bbe\u8ba1<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-monorepo\">\u4e3a\u4ec0\u4e48\u9009\u62e9 Monorepo\uff1f<\/h3>\n\n\n\n<p>\u5728\u9879\u76ee\u521d\u671f\uff0c\u6211\u9762\u4e34\u4e00\u4e2a\u5173\u952e\u51b3\u7b56\uff1a\u662f\u91c7\u7528\u4f20\u7edf\u7684\u591a\u4ed3\u5e93\uff08Multi-repo\uff09\u67b6\u6784\uff0c\u8fd8\u662f\u9009\u62e9 Monorepo\uff1f<\/p>\n\n\n\n<p>\u6700\u7ec8\u9009\u62e9&nbsp;<strong>Turborepo + Monorepo<\/strong>&nbsp;\u67b6\u6784\uff0c\u4e3b\u8981\u57fa\u4e8e\u4ee5\u4e0b\u8003\u91cf\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">\u7ef4\u5ea6<\/th><th class=\"has-text-align-left\" data-align=\"left\">Multi-repo<\/th><th class=\"has-text-align-left\" data-align=\"left\">Monorepo<\/th><\/tr><\/thead><tbody><tr><td>\u4ee3\u7801\u5171\u4eab<\/td><td>\u9700\u8981\u53d1\u5e03 npm \u5305<\/td><td>workspace \u76f4\u63a5\u5f15\u7528<\/td><\/tr><tr><td>\u7248\u672c\u7ba1\u7406<\/td><td>\u5404\u4ed3\u5e93\u72ec\u7acb\u7248\u672c<\/td><td>\u7edf\u4e00\u7248\u672c\u63a7\u5236<\/td><\/tr><tr><td>CI\/CD<\/td><td>\u591a\u4e2a\u6d41\u6c34\u7ebf<\/td><td>\u5355\u4e00\u6784\u5efa\u6d41\u7a0b<\/td><\/tr><tr><td>\u4f9d\u8d56\u7ba1\u7406<\/td><td>\u5bb9\u6613\u7248\u672c\u51b2\u7a81<\/td><td>\u7edf\u4e00\u4f9d\u8d56\u7ba1\u7406<\/td><\/tr><tr><td>\u8de8\u5305\u6d4b\u8bd5<\/td><td>\u56f0\u96be<\/td><td>\u7b80\u5355\u76f4\u63a5<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E6%A0%88\">\u6838\u5fc3\u6280\u672f\u6808<\/h3>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code>\u251c\u2500\u2500 \u524d\u7aef\u6846\u67b6\u5c42\n\u2502   \u251c\u2500\u2500 Next.js 14 (App Router)   # React \u5168\u6808\u6846\u67b6\n\u2502   \u251c\u2500\u2500 React 18                  # UI \u5e93\n\u2502   \u2514\u2500\u2500 TypeScript 5              # \u7c7b\u578b\u5b89\u5168\n\u2502\n\u251c\u2500\u2500 \u540e\u7aef\u6846\u67b6\u5c42\n\u2502   \u251c\u2500\u2500 FastAPI                  # Python \u5f02\u6b65 Web \u6846\u67b6\n\u2502   \u251c\u2500\u2500 Python 3.10+             # \u8fd0\u884c\u65f6\n\u2502   \u251c\u2500\u2500 Pydantic                 # \u6570\u636e\u9a8c\u8bc1\n\u2502   \u251c\u2500\u2500 SQLAlchemy               # ORM\n\u2502   \u251c\u2500\u2500 Alembic                  # \u6570\u636e\u5e93\u8fc1\u79fb\n\u2502   \u2514\u2500\u2500 Uvicorn                  # ASGI \u670d\u52a1\u5668\n\u2502\n\u251c\u2500\u2500 \u6570\u636e\u5b58\u50a8\n\u2502   \u251c\u2500\u2500 MySQL 8.0               # \u5173\u7cfb\u578b\u6570\u636e\u5e93\n\u2502   \u251c\u2500\u2500 Redis                   # \u7f13\u5b58\u4e0e\u4f1a\u8bdd\n\u2502   \u2514\u2500\u2500 Supabase                # \u4e91\u6570\u636e\u5e93 + \u8ba4\u8bc1\n\u2502\n\u251c\u2500\u2500 \u5185\u5bb9\u6e32\u67d3\n\u2502   \u251c\u2500\u2500 next-mdx-remote         # MDX\/Markdown \u6e32\u67d3\n\u2502   \u2514\u2500\u2500 @tailwindcss\/typography # \u6587\u6863\u6837\u5f0f\u63d2\u4ef6\n\u2502\n\u251c\u2500\u2500 \u6784\u5efa\u5de5\u5177\n\u2502   \u251c\u2500\u2500 Turborepo             # Monorepo \u6784\u5efa\u5de5\u5177\n\u2502   \u251c\u2500\u2500 pnpm                  # \u9ad8\u6548\u7684\u5305\u7ba1\u7406\u5668\n\u2502   \u2514\u2500\u2500 Docker                # \u5bb9\u5668\u5316\u90e8\u7f72\n\u2502\n\u251c\u2500\u2500 \u6837\u5f0f\u65b9\u6848\n\u2502   \u251c\u2500\u2500 Tailwind CSS             # \u539f\u5b50\u5316 CSS\n\u2502   \u2514\u2500\u2500 styled-components        # CSS-in-JS\n\u2502\n\u251c\u2500\u2500 \u5f00\u53d1\u5de5\u5177\n\u2502   \u251c\u2500\u2500 ESLint + Prettier       # \u4ee3\u7801\u89c4\u8303\n\u2502   \u251c\u2500\u2500 Storybook               # \u7ec4\u4ef6\u5f00\u53d1\n\u2502   \u2514\u2500\u2500 Changeset               # \u7248\u672c\u7ba1\u7406\n\u2502\n\u2514\u2500\u2500 \u90e8\u7f72\u5e73\u53f0\n    \u251c\u2500\u2500 Vercel                   # Serverless \u524d\u7aef\u90e8\u7f72\n    \u251c\u2500\u2500 Docker + Linux           # \u540e\u7aef\u5bb9\u5668\u5316\u90e8\u7f72\n    \u2514\u2500\u2500 Nginx                    # \u53cd\u5411\u4ee3\u7406\n<\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E6%9E%B6%E6%9E%84\">\u9879\u76ee\u67b6\u6784<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                    \u7528\u6237\u8bbf\u95ee\u5c42                                 \n\u2502  web.erishen.cn (\u524d\u7aef)  \u2502  admin.erishen.cn (\u540e\u53f0)       \n\u2502  (Interview Web)          \u2502  (Interview Admin)            \n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                           \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                    API \u7f51\u5173\u5c42                             \n\u2502              api.erishen.cn (FastAPI)                     \n\u2502  - \u5546\u54c1\u7ba1\u7406 API (Items API)                                \n\u2502  - \u8ba4\u8bc1 API (Auth API)                                    \n\u2502  - \u6587\u6863 API (Docs API)                                     \n\u2502  - Redis API (\u7f13\u5b58\u670d\u52a1)                                     \n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                           \u2193\n        \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n        \u2193                                     \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   MySQL 8.0     \u2502               \u2502     Redis       \n\u2502   \u5546\u54c1\/\u6587\u6863\u6570\u636e   \u2502               \u2502    \u7f13\u5b58\/\u4f1a\u8bdd      \n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #F8F8F2\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502                    \u7528\u6237\u8bbf\u95ee\u5c42                                 <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  web.erishen.<\/span><span style=\"color: #50FA7B\">cn<\/span><span style=\"color: #F8F8F2\"> (\u524d\u7aef)  \u2502  admin.erishen.<\/span><span style=\"color: #50FA7B\">cn<\/span><span style=\"color: #F8F8F2\"> (\u540e\u53f0)       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  (Interview Web)          \u2502  (Interview Admin)            <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">                           \u2193<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502                    API \u7f51\u5173\u5c42                             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502              api.erishen.<\/span><span style=\"color: #50FA7B\">cn<\/span><span style=\"color: #F8F8F2\"> (FastAPI)                     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> \u5546\u54c1\u7ba1\u7406 <\/span><span style=\"color: #50FA7B\">API<\/span><span style=\"color: #F8F8F2\"> (Items API)                                <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> \u8ba4\u8bc1 <\/span><span style=\"color: #50FA7B\">API<\/span><span style=\"color: #F8F8F2\"> (Auth API)                                    <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> \u6587\u6863 <\/span><span style=\"color: #50FA7B\">API<\/span><span style=\"color: #F8F8F2\"> (Docs API)                                     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> Redis <\/span><span style=\"color: #50FA7B\">API<\/span><span style=\"color: #F8F8F2\"> (\u7f13\u5b58\u670d\u52a1)                                     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">                           \u2193<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        \u2193                                     \u2193<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502   MySQL <\/span><span style=\"color: #BD93F9\">8.0<\/span><span style=\"color: #F8F8F2\">     \u2502               \u2502     Redis       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502   \u5546\u54c1<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">\u6587\u6863\u6570\u636e   \u2502               \u2502    \u7f13\u5b58<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">\u4f1a\u8bdd      <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84\">\u9879\u76ee\u7ed3\u6784<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502              Interview (\u524d\u7aef Monorepo)                       \n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502  apps\/                                                     \n\u2502  \u251c\u2500\u2500 web\/              # \u4e3b\u5e94\u7528 (\u7aef\u53e3 3000)              \n\u2502  \u2502   \u2514\u2500\u2500 src\/                                            \n\u2502  \u2502       \u251c\u2500\u2500 app\/                                           \n\u2502  \u2502       \u2502   \u251c\u2500\u2500 docs\/                 # \u6587\u6863\u5c55\u793a\u7cfb\u7edf     \n\u2502  \u2502       \u2502   \u2502   \u251c\u2500\u2500 page.tsx         # \u6587\u6863\u5217\u8868         \n\u2502  \u2502       \u2502   \u2502   \u2514\u2500\u2500 &#91;slug&#93;\/page.tsx  # \u6587\u6863\u8be6\u60c5        \n\u2502  \u2502       \u2502   \u251c\u2500\u2500 api-integration\/     # API \u96c6\u6210\u6f14\u793a     \n\u2502  \u2502       \u2502   \u2514\u2500\u2500 ...                                         \n\u2502  \u2502       \u2514\u2500\u2500 lib\/                                           \n\u2502  \u2502           \u2514\u2500\u2500 docs.ts              # \u6587\u6863\u52a0\u8f7d\u5de5\u5177     \n\u2502  \u2514\u2500\u2500 admin\/            # \u7ba1\u7406\u540e\u53f0 (\u7aef\u53e3 3003)          \n\u2502                                                             \n\u2502  packages\/                                                  \n\u2502  \u251c\u2500\u2500 ui\/               # \u5171\u4eab UI \u7ec4\u4ef6\u5e93                  \n\u2502  \u251c\u2500\u2500 api-client\/       # API \u5ba2\u6237\u7aef                      \n\u2502  \u251c\u2500\u2500 utils\/            # \u5de5\u5177\u51fd\u6570                         \n\u2502  \u251c\u2500\u2500 types\/            # \u7c7b\u578b\u5b9a\u4e49                         \n\u2502  \u251c\u2500\u2500 config\/           # \u914d\u7f6e\u6587\u4ef6                         \n\u2502  \u2514\u2500\u2500 constants\/        # \u5e38\u91cf\u5b9a\u4e49                        \n\u2502                                                             \n\u2502  docs\/                 # \u77e5\u8bc6\u5e93\u6587\u6863 (Markdown \u6e90\u6587\u4ef6)      \n\u2502  \u251c\u2500\u2500 README.md                      # \u6587\u6863\u5bfc\u822a           \n\u2502  \u251c\u2500\u2500 frontend.md                    # \u524d\u7aef\u57fa\u7840\u77e5\u8bc6       \n\u2502  \u251c\u2500\u2500 frontend-extended.md           # \u524d\u7aef\u6269\u5c55\u77e5\u8bc6       \n\u2502  \u251c\u2500\u2500 dynamic-programming.md         # \u52a8\u6001\u89c4\u5212           \n\u2502  \u251c\u2500\u2500 case1.md                      # \u7efc\u5408\u9898\u5e93           \n\u2502  \u2514\u2500\u2500 ...                                                       \n\u2502                                                             \n\u2502  scripts\/              # \u5de5\u5177\u811a\u672c                          \n\u2502  turbo.json           # Turborepo \u914d\u7f6e                    \n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502              fastapi-web (\u540e\u7aef\u670d\u52a1)                           \n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502  app\/                                                       \n\u2502  \u251c\u2500\u2500 main.py                 # FastAPI \u5e94\u7528\u5165\u53e3             \n\u2502  \u251c\u2500\u2500 routers\/                # API \u8def\u7531\u5c42               \n\u2502  \u2502   \u251c\u2500\u2500 items.py        # \u5546\u54c1 API                   \n\u2502  \u2502   \u251c\u2500\u2500 auth.py         # \u8ba4\u8bc1 API                   \n\u2502  \u2502   \u251c\u2500\u2500 docs.py         # \u6587\u6863 API                   \n\u2502  \u2502   \u251c\u2500\u2500 redis.py        # Redis API                  \n\u2502  \u2502   \u2514\u2500\u2500 system.py      # \u7cfb\u7edf API                   \n\u2502  \u251c\u2500\u2500 config.py               # \u914d\u7f6e\u7ba1\u7406                   \n\u2502  \u251c\u2500\u2500 crud.py                # \u6570\u636e\u5e93 CRUD \u64cd\u4f5c             \n\u2502  \u251c\u2500\u2500 models.py              # \u6570\u636e\u6a21\u578b\u5c42 (SQLAlchemy)       \n\u2502  \u2502   \u251c\u2500\u2500 Item            # \u5546\u54c1\u6a21\u578b                   \n\u2502  \u2502   \u2514\u2500\u2500 DocLog         # \u6587\u6863\u65e5\u5fd7\u6a21\u578b                \n\u2502  \u251c\u2500\u2500 schemas.py             # Pydantic \u6570\u636e\u9a8c\u8bc1           \n\u2502  \u251c\u2500\u2500 security.py            # \u5b89\u5168\u5de5\u5177\uff08JWT\u3001\u8ba4\u8bc1\uff09       \n\u2502  \u251c\u2500\u2500 security_headers.py     # \u5b89\u5168\u54cd\u5e94\u5934                  \n\u2502  \u251c\u2500\u2500 middleware.py         # \u4e2d\u95f4\u4ef6\uff08\u65e5\u5fd7\u3001\u9650\u6d41\uff09       \n\u2502  \u251c\u2500\u2500 ip_filter.py          # IP \u9ed1\u540d\u5355\/\u767d\u540d\u5355\u8fc7\u6ee4        \n\u2502  \u251c\u2500\u2500 path_protection.py    # \u654f\u611f\u8def\u5f84\u4fdd\u62a4               \n\u2502  \u251c\u2500\u2500 redis_client.py       # Redis \u5ba2\u6237\u7aef                \n\u2502  \u251c\u2500\u2500 database.py           # \u6570\u636e\u5e93\u8fde\u63a5                  \n\u2502  \u251c\u2500\u2500 exceptions.py         # \u5f02\u5e38\u5904\u7406                    \n\u2502  \u2514\u2500\u2500 factory.py           # \u5e94\u7528\u5de5\u5382                    \n\u2502                                                             \n\u2502  alembic\/                # \u6570\u636e\u5e93\u8fc1\u79fb                       \n\u2502  tests\/                  # \u6d4b\u8bd5\u7528\u4f8b                        \n\u2502  requirements.txt         # Python \u4f9d\u8d56                     \n\u2502  Dockerfile              # \u5bb9\u5668\u6784\u5efa\u914d\u7f6e                    \n\u2502  docker-compose.yml      # Docker Compose \u914d\u7f6e              \n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #F8F8F2\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502              Interview (\u524d\u7aef Monorepo)                       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  apps<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                                                     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 web<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">              <\/span><span style=\"color: #6272A4\"># \u4e3b\u5e94\u7528 (\u7aef\u53e3 3000)              <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u2514\u2500\u2500 src<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                                            <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u251c\u2500\u2500 app<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                                           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u2502   \u251c\u2500\u2500 docs<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                 <\/span><span style=\"color: #6272A4\"># \u6587\u6863\u5c55\u793a\u7cfb\u7edf     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u2502   \u2502   \u251c\u2500\u2500 page.tsx         <\/span><span style=\"color: #6272A4\"># \u6587\u6863\u5217\u8868         <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u2502   \u2502   \u2514\u2500\u2500 &#91;slug&#93;<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">page.tsx  <\/span><span style=\"color: #6272A4\"># \u6587\u6863\u8be6\u60c5        <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u2502   \u251c\u2500\u2500 api<\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\">integration<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">     <\/span><span style=\"color: #6272A4\"># API \u96c6\u6210\u6f14\u793a     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u2502   \u2514\u2500\u2500 <\/span><span style=\"color: #BD93F9\">...<\/span><span style=\"color: #F8F8F2\">                                         <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502       \u2514\u2500\u2500 lib<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                                           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502           \u2514\u2500\u2500 docs.ts              <\/span><span style=\"color: #6272A4\"># \u6587\u6863\u52a0\u8f7d\u5de5\u5177     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2514\u2500\u2500 admin<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #6272A4\"># \u7ba1\u7406\u540e\u53f0 (\u7aef\u53e3 3003)          <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502                                                             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  packages<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                                                  <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 ui<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">               <\/span><span style=\"color: #6272A4\"># \u5171\u4eab UI \u7ec4\u4ef6\u5e93                  <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 api<\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\">client<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">       <\/span><span style=\"color: #6272A4\"># API \u5ba2\u6237\u7aef                      <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 utils<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #6272A4\"># \u5de5\u5177\u51fd\u6570                         <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 types<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #6272A4\"># \u7c7b\u578b\u5b9a\u4e49                         <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 config<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">           <\/span><span style=\"color: #6272A4\"># \u914d\u7f6e\u6587\u4ef6                         <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2514\u2500\u2500 constants<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #6272A4\"># \u5e38\u91cf\u5b9a\u4e49                        <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502                                                             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  docs<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                 <\/span><span style=\"color: #6272A4\"># \u77e5\u8bc6\u5e93\u6587\u6863 (Markdown \u6e90\u6587\u4ef6)      <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 <\/span><span style=\"color: #BD93F9\">README<\/span><span style=\"color: #F8F8F2\">.md                      <\/span><span style=\"color: #6272A4\"># \u6587\u6863\u5bfc\u822a           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 frontend.md                    <\/span><span style=\"color: #6272A4\"># \u524d\u7aef\u57fa\u7840\u77e5\u8bc6       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 frontend<\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\">extended.md           <\/span><span style=\"color: #6272A4\"># \u524d\u7aef\u6269\u5c55\u77e5\u8bc6       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 dynamic<\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\">programming.md         <\/span><span style=\"color: #6272A4\"># \u52a8\u6001\u89c4\u5212           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 case1.md                      <\/span><span style=\"color: #6272A4\"># \u7efc\u5408\u9898\u5e93           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2514\u2500\u2500 <\/span><span style=\"color: #BD93F9\">...<\/span><span style=\"color: #F8F8F2\">                                                       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502                                                             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  scripts<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">              <\/span><span style=\"color: #6272A4\"># \u5de5\u5177\u811a\u672c                          <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  turbo.json           <\/span><span style=\"color: #6272A4\"># Turborepo \u914d\u7f6e                    <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502              fastapi<\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\">web (\u540e\u7aef\u670d\u52a1)                           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  app<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                                                       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 main.py                 <\/span><span style=\"color: #6272A4\"># FastAPI \u5e94\u7528\u5165\u53e3             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 routers<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                <\/span><span style=\"color: #6272A4\"># API \u8def\u7531\u5c42               <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u251c\u2500\u2500 items.py        <\/span><span style=\"color: #6272A4\"># \u5546\u54c1 API                   <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u251c\u2500\u2500 auth.py         <\/span><span style=\"color: #6272A4\"># \u8ba4\u8bc1 API                   <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u251c\u2500\u2500 docs.py         <\/span><span style=\"color: #6272A4\"># \u6587\u6863 API                   <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u251c\u2500\u2500 redis.py        <\/span><span style=\"color: #6272A4\"># Redis API                  <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u2514\u2500\u2500 system.py      <\/span><span style=\"color: #6272A4\"># \u7cfb\u7edf API                   <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 config.py               <\/span><span style=\"color: #6272A4\"># \u914d\u7f6e\u7ba1\u7406                   <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 crud.py                <\/span><span style=\"color: #6272A4\"># \u6570\u636e\u5e93 CRUD \u64cd\u4f5c             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 models.py              <\/span><span style=\"color: #6272A4\"># \u6570\u636e\u6a21\u578b\u5c42 (SQLAlchemy)       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u251c\u2500\u2500 Item            <\/span><span style=\"color: #6272A4\"># \u5546\u54c1\u6a21\u578b                   <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2502   \u2514\u2500\u2500 DocLog         <\/span><span style=\"color: #6272A4\"># \u6587\u6863\u65e5\u5fd7\u6a21\u578b                <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 schemas.py             <\/span><span style=\"color: #6272A4\"># Pydantic \u6570\u636e\u9a8c\u8bc1           <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 security.py            <\/span><span style=\"color: #6272A4\"># \u5b89\u5168\u5de5\u5177\uff08JWT\u3001\u8ba4\u8bc1\uff09       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 security_headers.py     <\/span><span style=\"color: #6272A4\"># \u5b89\u5168\u54cd\u5e94\u5934                  <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 middleware.py         <\/span><span style=\"color: #6272A4\"># \u4e2d\u95f4\u4ef6\uff08\u65e5\u5fd7\u3001\u9650\u6d41\uff09       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 ip_filter.py          <\/span><span style=\"color: #6272A4\"># IP \u9ed1\u540d\u5355\/\u767d\u540d\u5355\u8fc7\u6ee4        <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 path_protection.py    <\/span><span style=\"color: #6272A4\"># \u654f\u611f\u8def\u5f84\u4fdd\u62a4               <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 redis_client.py       <\/span><span style=\"color: #6272A4\"># Redis \u5ba2\u6237\u7aef                <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 database.py           <\/span><span style=\"color: #6272A4\"># \u6570\u636e\u5e93\u8fde\u63a5                  <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u251c\u2500\u2500 exceptions.py         <\/span><span style=\"color: #6272A4\"># \u5f02\u5e38\u5904\u7406                    <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  \u2514\u2500\u2500 factory.py           <\/span><span style=\"color: #6272A4\"># \u5e94\u7528\u5de5\u5382                    <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502                                                             <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  alembic<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                <\/span><span style=\"color: #6272A4\"># \u6570\u636e\u5e93\u8fc1\u79fb                       <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  tests<\/span><span style=\"color: #FF79C6\">\/<\/span><span style=\"color: #F8F8F2\">                  <\/span><span style=\"color: #6272A4\"># \u6d4b\u8bd5\u7528\u4f8b                        <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  requirements.txt         <\/span><span style=\"color: #6272A4\"># Python \u4f9d\u8d56                     <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  Dockerfile              <\/span><span style=\"color: #6272A4\"># \u5bb9\u5668\u6784\u5efa\u914d\u7f6e                    <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2502  docker<\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\">compose.yml      <\/span><span style=\"color: #6272A4\"># Docker Compose \u914d\u7f6e              <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E6%A0%B8%E5%BF%83%E5%8A%9F%E8%83%BD%E6%A8%A1%E5%9D%97\">\u6838\u5fc3\u529f\u80fd\u6a21\u5757<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1-web-%E5%BA%94%E7%94%A8%E5%89%8D%E7%AB%AF%E7%9F%A5%E8%AF%86%E5%BA%93\">1. Web \u5e94\u7528\uff08\u524d\u7aef\u77e5\u8bc6\u5e93\uff09<\/h3>\n\n\n\n<p>Web \u5e94\u7528\u662f\u9879\u76ee\u7684\u4e3b\u5165\u53e3\uff0c\u63d0\u4f9b\u5b8c\u6574\u7684\u9762\u8bd5\u77e5\u8bc6\u5e93\u6d4f\u89c8\u548c\u641c\u7d22\u529f\u80fd\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u77e5\u8bc6\u5e93\u5bfc\u822a<\/strong>\uff1a\u7ed3\u6784\u5316\u7684\u6587\u6863\u5206\u7c7b\u5c55\u793a<\/li>\n\n\n\n<li><strong>\u6587\u6863\u6e32\u67d3<\/strong>\uff1a\u57fa\u4e8e&nbsp;<code>next-mdx-remote<\/code>&nbsp;\u7684 Markdown \u5b9e\u65f6\u6e32\u67d3<\/li>\n\n\n\n<li><strong>\u81ea\u52a8\u52a0\u8f7d<\/strong>\uff1a\u4ece Admin API \u6216\u672c\u5730\u6587\u4ef6\u7cfb\u7edf\u83b7\u53d6\u6587\u6863<\/li>\n\n\n\n<li><strong>\u4ee3\u7801\u9ad8\u4eae<\/strong>\uff1a\u4f18\u96c5\u7684\u4ee3\u7801\u5757\u5c55\u793a\u548c\u8bed\u6cd5\u9ad8\u4eae<\/li>\n\n\n\n<li><strong>\u641c\u7d22\u529f\u80fd<\/strong>\uff1a\u57fa\u4e8e\u5173\u952e\u8bcd\u7684\u5feb\u901f\u68c0\u7d22<\/li>\n\n\n\n<li><strong>\u54cd\u5e94\u5f0f\u8bbe\u8ba1<\/strong>\uff1a\u9002\u914d\u79fb\u52a8\u7aef\u548c\u684c\u9762\u7aef<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%96%87%E6%A1%A3%E5%B1%95%E7%A4%BA%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0\">\u6587\u6863\u5c55\u793a\u7cfb\u7edf\u5b9e\u73b0<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E6%96%87%E6%A1%A3%E5%8A%A0%E8%BD%BD%E9%80%BB%E8%BE%91\">\u6587\u6863\u52a0\u8f7d\u903b\u8f91<\/h4>\n\n\n\n<p>\u6587\u6863\u5185\u5bb9\u901a\u8fc7 Admin API \u83b7\u53d6\uff0c\u6784\u5efa\u65f6\u56de\u9000\u5230\u672c\u5730\u6587\u4ef6\u7cfb\u7edf\uff1a<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ src\/lib\/docs.ts\nimport fs from 'fs';\nimport path from 'path';\n\nexport interface Doc {\n  slug: string;\n  title: string;\n  description?: string;\n}\n\nconst DOCS_DIR = path.join(process.cwd(), '..\/..\/docs');\nconst ADMIN_API_URL = process.env.NEXT_PUBLIC_ADMIN_URL || 'http:\/\/localhost:3003';\nconst DOCS_API_ENDPOINT = `${ADMIN_API_URL}\/api\/docs-public`;\n\n\/\/ \u4ece Admin API \u83b7\u53d6\u6587\u6863\u5217\u8868\uff08\u751f\u4ea7\u73af\u5883\uff09\nasync function fetchDocsFromAdmin(): Promise&lt;Doc[]> {\n  try {\n    const response = await fetch(DOCS_API_ENDPOINT, {\n      cache: 'no-store',\n      headers: {\n        'Referer': process.env.NEXT_PUBLIC_WEB_URL || 'http:\/\/localhost:3000',\n        'Origin': process.env.NEXT_PUBLIC_WEB_URL || 'http:\/\/localhost:3000',\n      },\n    });\n    const data = await response.json();\n    return data.success ? data.docs : [];\n  } catch (error) {\n    console.error('&#91;Docs API&#93; Error fetching docs from Admin:', error);\n    return [];\n  }\n}\n\n\/\/ \u4ece\u672c\u5730\u6587\u4ef6\u7cfb\u7edf\u83b7\u53d6\u6587\u6863\u5217\u8868\uff08\u6784\u5efa\u65f6\u56de\u9000\uff09\nfunction getLocalDocs(): Doc[] {\n  if (!fs.existsSync(DOCS_DIR)) {\n    return [];\n  }\n\n  const files = fs.readdirSync(DOCS_DIR);\n  return files\n    .filter(file => file.endsWith('.md'))\n    .map(file => {\n      const slug = file.replace(\/\\.md$\/, '');\n      const content = fs.readFileSync(path.join(DOCS_DIR, file), 'utf-8');\n      const titleMatch = content.match(\/^#\\s+(.+)$\/m);\n      return {\n        slug,\n        title: titleMatch ? titleMatch&#91;1&#93; : slug,\n        description: content.match(\/^> (.+)$\/m)?.&#91;1&#93;\n      };\n    });\n}\n\n\/\/ \u83b7\u53d6\u6240\u6709\u6587\u6863\uff08\u751f\u4ea7\u73af\u5883\u4f18\u5148 Admin API\uff0c\u6784\u5efa\u65f6\u6216\u5f00\u53d1\u73af\u5883\u4f18\u5148\u672c\u5730\uff09\nexport async function getAllDocs(): Promise&lt;Doc[]> {\n  const isProduction = process.env.NODE_ENV === 'production';\n  const isBuildTime = process.env.NEXT_PHASE?.includes('build');\n\n  \/\/ \u6784\u5efa\u65f6\u6216\u5f00\u53d1\u73af\u5883\uff1a\u4f18\u5148\u672c\u5730\u6587\u4ef6\n  if (isBuildTime || !isProduction) {\n    const localDocs = getLocalDocs();\n    if (localDocs.length > 0) return localDocs;\n    return await fetchDocsFromAdmin();\n  }\n\n  \/\/ \u751f\u4ea7\u73af\u5883\uff1a\u4f18\u5148\u4ece Admin API\uff0c\u5931\u8d25\u5219\u964d\u7ea7\u5230\u672c\u5730\u6587\u4ef6\n  const adminDocs = await fetchDocsFromAdmin();\n  if (adminDocs.length > 0) return adminDocs;\n  return getLocalDocs();\n}\n\n\/\/ \u83b7\u53d6\u5355\u4e2a\u6587\u6863\nexport async function getDocBySlug(slug: string): Promise&lt;string | null> {\n  const isProduction = process.env.NODE_ENV === 'production';\n  const isBuildTime = process.env.NEXT_PHASE?.includes('build');\n\n  async function fetchDocFromAdmin(slug: string): Promise&lt;string | null> {\n    try {\n      const url = new URL(DOCS_API_ENDPOINT);\n      url.searchParams.set('slug', slug);\n      const response = await fetch(url.toString(), { cache: 'no-store' });\n      const data = await response.json();\n      return data.success ? data.doc.content : null;\n    } catch (error) {\n      console.error(`&#91;Docs API&#93; Error fetching doc ${slug}:`, error);\n      return null;\n    }\n  }\n\n  function getLocalDocBySlug(slug: string): string | null {\n    const filePath = path.join(DOCS_DIR, `${slug}.md`);\n    return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : null;\n  }\n\n  \/\/ \u6784\u5efa\u65f6\u6216\u5f00\u53d1\u73af\u5883\uff1a\u4f18\u5148\u672c\u5730\u6587\u4ef6\n  if (isBuildTime || !isProduction) {\n    const localDoc = getLocalDocBySlug(slug);\n    if (localDoc) return localDoc;\n    return await fetchDocFromAdmin(slug);\n  }\n\n  \/\/ \u751f\u4ea7\u73af\u5883\uff1a\u4f18\u5148\u4ece Admin API\n  const adminDoc = await fetchDocFromAdmin(slug);\n  if (adminDoc) return adminDoc;\n  return getLocalDocBySlug(slug);\n}\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ src\/lib\/docs.ts<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> fs <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">fs<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> path <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">path<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">export<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">interface<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">Doc<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  slug<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  title<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  description<\/span><span style=\"color: #F286C4\">?:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> DOCS_DIR <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> path.<\/span><span style=\"color: #62E884\">join<\/span><span style=\"color: #F6F6F4\">(process.<\/span><span style=\"color: #62E884\">cwd<\/span><span style=\"color: #F6F6F4\">(), <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">..\/..\/docs<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> ADMIN_API_URL <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NEXT_PUBLIC_ADMIN_URL<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">||<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">http:\/\/localhost:3003<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> DOCS_API_ENDPOINT <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #E7EE98\">`<\/span><span style=\"color: #F286C4\">${<\/span><span style=\"color: #F6F6F4\">ADMIN_API_URL<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #E7EE98\">\/api\/docs-public`<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u4ece Admin API \u83b7\u53d6\u6587\u6863\u5217\u8868\uff08\u751f\u4ea7\u73af\u5883\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetchDocsFromAdmin<\/span><span style=\"color: #F6F6F4\">()<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">Promise<\/span><span style=\"color: #F6F6F4\">&lt;<\/span><span style=\"color: #FFB86C; font-style: italic\">Doc<\/span><span style=\"color: #F6F6F4\">[]&gt; {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">try<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> response <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetch<\/span><span style=\"color: #F6F6F4\">(DOCS_API_ENDPOINT, {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      cache<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">no-store<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      headers<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">Referer<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NEXT_PUBLIC_WEB_URL<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">||<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">http:\/\/localhost:3000<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">Origin<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NEXT_PUBLIC_WEB_URL<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">||<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">http:\/\/localhost:3000<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      },<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    });<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> data <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> response.<\/span><span style=\"color: #62E884\">json<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> data.success <\/span><span style=\"color: #F286C4\">?<\/span><span style=\"color: #F6F6F4\"> data.docs <\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> [];<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  } <\/span><span style=\"color: #F286C4\">catch<\/span><span style=\"color: #F6F6F4\"> (error) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    console.<\/span><span style=\"color: #62E884\">error<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">&#91;Docs API&#93; Error fetching docs from Admin:<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">, error);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> [];<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u4ece\u672c\u5730\u6587\u4ef6\u7cfb\u7edf\u83b7\u53d6\u6587\u6863\u5217\u8868\uff08\u6784\u5efa\u65f6\u56de\u9000\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getLocalDocs<\/span><span style=\"color: #F6F6F4\">()<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">Doc<\/span><span style=\"color: #F6F6F4\">[] {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (<\/span><span style=\"color: #F286C4\">!<\/span><span style=\"color: #F6F6F4\">fs.<\/span><span style=\"color: #62E884\">existsSync<\/span><span style=\"color: #F6F6F4\">(DOCS_DIR)) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> [];<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> files <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> fs.<\/span><span style=\"color: #62E884\">readdirSync<\/span><span style=\"color: #F6F6F4\">(DOCS_DIR);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> files<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    .<\/span><span style=\"color: #62E884\">filter<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">file<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=&gt;<\/span><span style=\"color: #F6F6F4\"> file.<\/span><span style=\"color: #62E884\">endsWith<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">.md<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">))<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    .<\/span><span style=\"color: #62E884\">map<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">file<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=&gt;<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> slug <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> file.<\/span><span style=\"color: #62E884\">replace<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #EE6666\">\/<\/span><span style=\"color: #E7EE98\">\\.md<\/span><span style=\"color: #F286C4\">$<\/span><span style=\"color: #EE6666\">\/<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&#39;&#39;<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> content <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> fs.<\/span><span style=\"color: #62E884\">readFileSync<\/span><span style=\"color: #F6F6F4\">(path.<\/span><span style=\"color: #62E884\">join<\/span><span style=\"color: #F6F6F4\">(DOCS_DIR, file), <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">utf-8<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> titleMatch <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> content.<\/span><span style=\"color: #62E884\">match<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #EE6666\">\/<\/span><span style=\"color: #F286C4\">^<\/span><span style=\"color: #E7EE98\">#<\/span><span style=\"color: #BF9EEE\">\\s<\/span><span style=\"color: #F286C4\">+<\/span><span style=\"color: #FFB86C\">(<\/span><span style=\"color: #BF9EEE\">.<\/span><span style=\"color: #F286C4\">+<\/span><span style=\"color: #FFB86C\">)<\/span><span style=\"color: #F286C4\">$<\/span><span style=\"color: #EE6666\">\/<\/span><span style=\"color: #F286C4\">m<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        slug,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        title<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> titleMatch <\/span><span style=\"color: #F286C4\">?<\/span><span style=\"color: #F6F6F4\"> titleMatch&#91;<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> slug,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        description<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> content.<\/span><span style=\"color: #62E884\">match<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #EE6666\">\/<\/span><span style=\"color: #F286C4\">^<\/span><span style=\"color: #E7EE98\">&gt; <\/span><span style=\"color: #FFB86C\">(<\/span><span style=\"color: #BF9EEE\">.<\/span><span style=\"color: #F286C4\">+<\/span><span style=\"color: #FFB86C\">)<\/span><span style=\"color: #F286C4\">$<\/span><span style=\"color: #EE6666\">\/<\/span><span style=\"color: #F286C4\">m<\/span><span style=\"color: #F6F6F4\">)?.&#91;<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      };<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    });<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u83b7\u53d6\u6240\u6709\u6587\u6863\uff08\u751f\u4ea7\u73af\u5883\u4f18\u5148 Admin API\uff0c\u6784\u5efa\u65f6\u6216\u5f00\u53d1\u73af\u5883\u4f18\u5148\u672c\u5730\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">export<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getAllDocs<\/span><span style=\"color: #F6F6F4\">()<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">Promise<\/span><span style=\"color: #F6F6F4\">&lt;<\/span><span style=\"color: #FFB86C; font-style: italic\">Doc<\/span><span style=\"color: #F6F6F4\">[]&gt; {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> isProduction <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NODE_ENV<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">===<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">production<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> isBuildTime <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NEXT_PHASE<\/span><span style=\"color: #F6F6F4\">?.<\/span><span style=\"color: #62E884\">includes<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">build<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #7B7F8B\">\/\/ \u6784\u5efa\u65f6\u6216\u5f00\u53d1\u73af\u5883\uff1a\u4f18\u5148\u672c\u5730\u6587\u4ef6<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (isBuildTime <\/span><span style=\"color: #F286C4\">||<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">!<\/span><span style=\"color: #F6F6F4\">isProduction) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> localDocs <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getLocalDocs<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (localDocs.length <\/span><span style=\"color: #F286C4\">&gt;<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">) <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> localDocs;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetchDocsFromAdmin<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #7B7F8B\">\/\/ \u751f\u4ea7\u73af\u5883\uff1a\u4f18\u5148\u4ece Admin API\uff0c\u5931\u8d25\u5219\u964d\u7ea7\u5230\u672c\u5730\u6587\u4ef6<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> adminDocs <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetchDocsFromAdmin<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (adminDocs.length <\/span><span style=\"color: #F286C4\">&gt;<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">) <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> adminDocs;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getLocalDocs<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u83b7\u53d6\u5355\u4e2a\u6587\u6863<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">export<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getDocBySlug<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">slug<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\">)<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">Promise<\/span><span style=\"color: #F6F6F4\">&lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">|<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">null<\/span><span style=\"color: #F6F6F4\">&gt; {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> isProduction <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NODE_ENV<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">===<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">production<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> isBuildTime <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> process.env.<\/span><span style=\"color: #BF9EEE\">NEXT_PHASE<\/span><span style=\"color: #F6F6F4\">?.<\/span><span style=\"color: #62E884\">includes<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">build<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetchDocFromAdmin<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">slug<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\">)<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">Promise<\/span><span style=\"color: #F6F6F4\">&lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">|<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">null<\/span><span style=\"color: #F6F6F4\">&gt; {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">try<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> url <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4; font-weight: bold\">new<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">URL<\/span><span style=\"color: #F6F6F4\">(DOCS_API_ENDPOINT);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      url.searchParams.<\/span><span style=\"color: #62E884\">set<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">slug<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">, slug);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> response <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetch<\/span><span style=\"color: #F6F6F4\">(url.<\/span><span style=\"color: #62E884\">toString<\/span><span style=\"color: #F6F6F4\">(), { cache<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">no-store<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\"> });<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> data <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> response.<\/span><span style=\"color: #62E884\">json<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> data.success <\/span><span style=\"color: #F286C4\">?<\/span><span style=\"color: #F6F6F4\"> data.doc.content <\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">null<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    } <\/span><span style=\"color: #F286C4\">catch<\/span><span style=\"color: #F6F6F4\"> (error) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      console.<\/span><span style=\"color: #62E884\">error<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #E7EE98\">`&#91;Docs API&#93; Error fetching doc <\/span><span style=\"color: #F286C4\">${<\/span><span style=\"color: #F6F6F4\">slug<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #E7EE98\">:`<\/span><span style=\"color: #F6F6F4\">, error);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">null<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getLocalDocBySlug<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">slug<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\">)<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">|<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">null<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> filePath <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> path.<\/span><span style=\"color: #62E884\">join<\/span><span style=\"color: #F6F6F4\">(DOCS_DIR, <\/span><span style=\"color: #E7EE98\">`<\/span><span style=\"color: #F286C4\">${<\/span><span style=\"color: #F6F6F4\">slug<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #E7EE98\">.md`<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> fs.<\/span><span style=\"color: #62E884\">existsSync<\/span><span style=\"color: #F6F6F4\">(filePath) <\/span><span style=\"color: #F286C4\">?<\/span><span style=\"color: #F6F6F4\"> fs.<\/span><span style=\"color: #62E884\">readFileSync<\/span><span style=\"color: #F6F6F4\">(filePath, <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">utf-8<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">) <\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">null<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #7B7F8B\">\/\/ \u6784\u5efa\u65f6\u6216\u5f00\u53d1\u73af\u5883\uff1a\u4f18\u5148\u672c\u5730\u6587\u4ef6<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (isBuildTime <\/span><span style=\"color: #F286C4\">||<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">!<\/span><span style=\"color: #F6F6F4\">isProduction) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> localDoc <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getLocalDocBySlug<\/span><span style=\"color: #F6F6F4\">(slug);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (localDoc) <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> localDoc;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetchDocFromAdmin<\/span><span style=\"color: #F6F6F4\">(slug);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #7B7F8B\">\/\/ \u751f\u4ea7\u73af\u5883\uff1a\u4f18\u5148\u4ece Admin API<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> adminDoc <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">fetchDocFromAdmin<\/span><span style=\"color: #F6F6F4\">(slug);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (adminDoc) <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> adminDoc;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getLocalDocBySlug<\/span><span style=\"color: #F6F6F4\">(slug);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E6%96%87%E6%A1%A3%E5%88%97%E8%A1%A8%E9%A1%B5%E9%9D%A2\">\u6587\u6863\u5217\u8868\u9875\u9762<\/h4>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ src\/app\/docs\/page.tsx\nimport { getAllDocs } from '@\/lib\/docs';\nimport Link from 'next\/link';\n\nexport default async function DocsPage() {\n  const docs = await getAllDocs();\n\n  \/\/ \u6309\u5206\u7c7b\u5c55\u793a\n  const coreDocs = docs.filter(doc => &#91;'frontend', 'frontend-extended'&#93;.includes(doc.slug));\n  const algorithmDocs = docs.filter(doc => \n    &#91;'dynamic-programming', 'min-path-sum-explained'&#93;.includes(doc.slug)\n  );\n\n  return (\n    &lt;div className=\"container mx-auto\">\n      &lt;h1>\ud83d\udcda \u524d\u7aef\u9762\u8bd5\u77e5\u8bc6\u5e93&lt;\/h1>\n\n      &lt;section>\n        &lt;h2>\ud83c\udfaf \u6838\u5fc3\u57fa\u7840\u77e5\u8bc6&lt;\/h2>\n        {coreDocs.map(doc => (\n          &lt;Link key={doc.slug} href={`\/docs\/${doc.slug}`}>\n            &lt;Card title={doc.title} description={doc.description} \/>\n          &lt;\/Link>\n        ))}\n      &lt;\/section>\n\n      &lt;section>\n        &lt;h2>\ud83e\uddee \u7b97\u6cd5\u4e0e\u6570\u636e\u7ed3\u6784&lt;\/h2>\n        {algorithmDocs.map(doc => (\n          &lt;Link key={doc.slug} href={`\/docs\/${doc.slug}`}>\n            &lt;Card title={doc.title} description={doc.description} \/>\n          &lt;\/Link>\n        ))}\n      &lt;\/section>\n    &lt;\/div>\n  );\n}\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ src\/app\/docs\/page.tsx<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> { getAllDocs } <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">@\/lib\/docs<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> Link <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">next\/link<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">export<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">default<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">DocsPage<\/span><span style=\"color: #F6F6F4\">() {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> docs <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getAllDocs<\/span><span style=\"color: #F6F6F4\">();<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #7B7F8B\">\/\/ \u6309\u5206\u7c7b\u5c55\u793a<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> coreDocs <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> docs.<\/span><span style=\"color: #62E884\">filter<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">doc<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=&gt;<\/span><span style=\"color: #F6F6F4\"> &#91;<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">frontend<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">frontend-extended<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">&#93;.<\/span><span style=\"color: #62E884\">includes<\/span><span style=\"color: #F6F6F4\">(doc.slug));<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> algorithmDocs <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> docs.<\/span><span style=\"color: #62E884\">filter<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">doc<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=&gt;<\/span><span style=\"color: #F6F6F4\"> <\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    &#91;<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">dynamic-programming<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">min-path-sum-explained<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">&#93;.<\/span><span style=\"color: #62E884\">includes<\/span><span style=\"color: #F6F6F4\">(doc.slug)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  );<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> (<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    &lt;<\/span><span style=\"color: #F286C4\">div<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">className<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">container mx-auto<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      &lt;<\/span><span style=\"color: #F286C4\">h1<\/span><span style=\"color: #F6F6F4\">&gt;\ud83d\udcda \u524d\u7aef\u9762\u8bd5\u77e5\u8bc6\u5e93&lt;\/<\/span><span style=\"color: #F286C4\">h1<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      &lt;<\/span><span style=\"color: #F286C4\">section<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        &lt;<\/span><span style=\"color: #F286C4\">h2<\/span><span style=\"color: #F6F6F4\">&gt;\ud83c\udfaf \u6838\u5fc3\u57fa\u7840\u77e5\u8bc6&lt;\/<\/span><span style=\"color: #F286C4\">h2<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">{<\/span><span style=\"color: #F6F6F4\">coreDocs.<\/span><span style=\"color: #62E884\">map<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">doc<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=&gt;<\/span><span style=\"color: #F6F6F4\"> (<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">          &lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">Link<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">key<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">doc.slug<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">href<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #E7EE98\">`\/docs\/<\/span><span style=\"color: #F286C4\">${<\/span><span style=\"color: #F6F6F4\">doc.slug<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #E7EE98\">`<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            &lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">Card<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">title<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">doc.title<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">description<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">doc.description<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> \/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">          &lt;\/<\/span><span style=\"color: #97E1F1; font-style: italic\">Link<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        ))<\/span><span style=\"color: #F286C4\">}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      &lt;\/<\/span><span style=\"color: #F286C4\">section<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      &lt;<\/span><span style=\"color: #F286C4\">section<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        &lt;<\/span><span style=\"color: #F286C4\">h2<\/span><span style=\"color: #F6F6F4\">&gt;\ud83e\uddee \u7b97\u6cd5\u4e0e\u6570\u636e\u7ed3\u6784&lt;\/<\/span><span style=\"color: #F286C4\">h2<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">{<\/span><span style=\"color: #F6F6F4\">algorithmDocs.<\/span><span style=\"color: #62E884\">map<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">doc<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=&gt;<\/span><span style=\"color: #F6F6F4\"> (<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">          &lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">Link<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">key<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">doc.slug<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">href<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #E7EE98\">`\/docs\/<\/span><span style=\"color: #F286C4\">${<\/span><span style=\"color: #F6F6F4\">doc.slug<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #E7EE98\">`<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            &lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">Card<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">title<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">doc.title<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">description<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">doc.description<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> \/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">          &lt;\/<\/span><span style=\"color: #97E1F1; font-style: italic\">Link<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        ))<\/span><span style=\"color: #F286C4\">}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      &lt;\/<\/span><span style=\"color: #F286C4\">section<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    &lt;\/<\/span><span style=\"color: #F286C4\">div<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  );<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E6%96%87%E6%A1%A3%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2\">\u6587\u6863\u8be6\u60c5\u9875\u9762<\/h4>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ src\/app\/docs\/&#91;slug&#93;\/page.tsx\nimport { getDocBySlug } from '@\/lib\/docs';\nimport { MDXRemote } from 'next-mdx-remote\/rsc';\n\nexport default async function DocDetailPage({ params }: { params: { slug: string } }) {\n  const content = await getDocBySlug(params.slug);\n\n  if (!content) return &lt;div>\u6587\u6863\u672a\u627e\u5230&lt;\/div>;\n\n  return (\n    &lt;article className=\"prose prose-slate prose-lg max-w-none\">\n      &lt;MDXRemote source={content} \/>\n    &lt;\/article>\n  );\n}\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ src\/app\/docs\/&#91;slug&#93;\/page.tsx<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> { getDocBySlug } <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">@\/lib\/docs<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> { MDXRemote } <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">next-mdx-remote\/rsc<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">export<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">default<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">function<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">DocDetailPage<\/span><span style=\"color: #F6F6F4\">({ <\/span><span style=\"color: #FFB86C; font-style: italic\">params<\/span><span style=\"color: #F6F6F4\"> }<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> { params<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> { slug<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">string<\/span><span style=\"color: #F6F6F4\"> } }) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">const<\/span><span style=\"color: #F6F6F4\"> content <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">getDocBySlug<\/span><span style=\"color: #F6F6F4\">(params.slug);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> (<\/span><span style=\"color: #F286C4\">!<\/span><span style=\"color: #F6F6F4\">content) <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> &lt;<\/span><span style=\"color: #F286C4\">div<\/span><span style=\"color: #F6F6F4\">&gt;\u6587\u6863\u672a\u627e\u5230&lt;\/<\/span><span style=\"color: #F286C4\">div<\/span><span style=\"color: #F6F6F4\">&gt;;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> (<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    &lt;<\/span><span style=\"color: #F286C4\">article<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">className<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">prose prose-slate prose-lg max-w-none<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      &lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">MDXRemote<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">source<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">content<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> \/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    &lt;\/<\/span><span style=\"color: #F286C4\">article<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  );<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-admin-%E5%BA%94%E7%94%A8%E6%8A%80%E6%9C%AF%E6%BC%94%E7%A4%BA%E5%B9%B3%E5%8F%B0\">2. Admin \u5e94\u7528\uff08\u6280\u672f\u6f14\u793a\u5e73\u53f0\uff09<\/h3>\n\n\n\n<p>Admin \u5e94\u7528\u662f\u4e00\u4e2a\u4f01\u4e1a\u7ea7\u6280\u672f\u6f14\u793a\u5e73\u53f0\uff0c\u7528\u4e8e\u5c55\u793a\u548c\u9a8c\u8bc1\u591a\u79cd\u524d\u7aef\/\u540e\u7aef\u6280\u672f\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u6587\u6863\u7f16\u8f91\u5668<\/strong>\uff1a\u5728\u7ebf\u7f16\u8f91&nbsp;<code>docs\/<\/code>&nbsp;\u76ee\u5f55\u4e0b\u7684 Markdown \u6587\u6863\n<ul class=\"wp-block-list\">\n<li>\u5b9e\u65f6\u6587\u6863\u5217\u8868\u5c55\u793a<\/li>\n\n\n\n<li>\u5728\u7ebf Markdown \u7f16\u8f91<\/li>\n\n\n\n<li>\u521b\u5efa\u65b0\u6587\u6863<\/li>\n\n\n\n<li>\u5b9e\u65f6\u9884\u89c8\u6548\u679c<\/li>\n\n\n\n<li>\u901a\u8fc7 Admin API \u5411 Web \u5e94\u7528\u63d0\u4f9b\u6587\u6863\u5185\u5bb9<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u53cc\u8ba4\u8bc1\u7cfb\u7edf<\/strong>\uff1aNextAuth.js \u548c Passport.js \u4e24\u79cd\u8ba4\u8bc1\u65b9\u5f0f\u5bf9\u6bd4\u6f14\u793a<\/li>\n\n\n\n<li><strong>\u5b89\u5168\u9a8c\u8bc1<\/strong>\uff1aCSRF \u4fdd\u62a4\u3001Lusca \u5b89\u5168\u4e2d\u95f4\u4ef6<\/li>\n\n\n\n<li><strong>\u7f13\u5b58\u6f14\u793a<\/strong>\uff1aRedis \u8fde\u63a5\u548c\u7f13\u5b58\u64cd\u4f5c<\/li>\n\n\n\n<li><strong>API \u96c6\u6210<\/strong>\uff1aFastAPI \u670d\u52a1\u4ee3\u7406\u548c\u8de8\u670d\u52a1\u901a\u4fe1<\/li>\n\n\n\n<li><strong>\u7ba1\u7406\u540e\u53f0\u6a21\u677f<\/strong>\uff1aDashboard UI \u548c\u7edf\u8ba1\u6570\u636e\u5c55\u793a<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-fastapi-%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1\">3. FastAPI \u540e\u7aef\u670d\u52a1<\/h3>\n\n\n\n<p>FastAPI \u540e\u7aef\u63d0\u4f9b\u5b8c\u6574\u7684 RESTful API \u670d\u52a1\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u5546\u54c1\u7ba1\u7406 API<\/strong>\uff1a\u5b8c\u6574\u7684 CRUD \u64cd\u4f5c\n<ul class=\"wp-block-list\">\n<li>\u5546\u54c1\u5217\u8868\u67e5\u8be2\uff08\u5206\u9875\u3001\u641c\u7d22\uff09<\/li>\n\n\n\n<li>\u5546\u54c1\u8be6\u60c5\u83b7\u53d6<\/li>\n\n\n\n<li>\u5546\u54c1\u521b\u5efa\u548c\u66f4\u65b0\uff08\u9700\u7ba1\u7406\u5458\u6743\u9650\uff09<\/li>\n\n\n\n<li>\u5546\u54c1\u5220\u9664\uff08\u9700\u7ba1\u7406\u5458\u6743\u9650\uff09<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u8ba4\u8bc1 API<\/strong>\uff1aJWT \u8ba4\u8bc1\u548c\u7528\u6237\u7ba1\u7406\n<ul class=\"wp-block-list\">\n<li>\u767b\u5f55\u8ba4\u8bc1<\/li>\n\n\n\n<li>Token \u9a8c\u8bc1<\/li>\n\n\n\n<li>\u6743\u9650\u63a7\u5236<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u6587\u6863 API<\/strong>\uff1a\u77e5\u8bc6\u5e93\u6587\u6863\u670d\u52a1\n<ul class=\"wp-block-list\">\n<li>\u6587\u6863\u5217\u8868\u548c\u8be6\u60c5<\/li>\n\n\n\n<li>\u6587\u6863\u64cd\u4f5c\u65e5\u5fd7<\/li>\n\n\n\n<li>\u7f16\u8f91\u6743\u9650\u63a7\u5236<\/li>\n\n\n\n<li>\u5b9e\u65f6\u9884\u89c8<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Redis API<\/strong>\uff1a\u7f13\u5b58\u64cd\u4f5c\u6f14\u793a\n<ul class=\"wp-block-list\">\n<li>\u952e\u503c\u5b58\u50a8<\/li>\n\n\n\n<li>\u7f13\u5b58\u7ba1\u7406<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u7cfb\u7edf API<\/strong>\uff1a\u5065\u5eb7\u68c0\u67e5\u548c\u7cfb\u7edf\u4fe1\u606f<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E8%AE%A1%E5%88%92%E4%B8%AD%E7%9A%84%E5%8A%9F%E8%83%BD\">\u8ba1\u5212\u4e2d\u7684\u529f\u80fd<\/h4>\n\n\n\n<p>\u4ee5\u4e0b\u529f\u80fd\u5df2\u89c4\u5212\u5b9e\u73b0\uff0c\u5f53\u524d\u5904\u4e8e\u5f00\u53d1\u9636\u6bb5\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u5b9e\u65f6\u901a\u4fe1<\/strong>\uff1aWebSocket \u652f\u6301\uff08\u8ba1\u5212\u4e2d\uff09\n<ul class=\"wp-block-list\">\n<li>\u5065\u5eb7\u5ea6\u8bc4\u5206\u7cfb\u7edf<\/li>\n\n\n\n<li>\u5728\u7ebf\u72b6\u6001\u63a8\u9001<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u5b89\u5168\u673a\u5236<\/strong>\uff1a\n<ul class=\"wp-block-list\">\n<li>JWT \u4ee4\u724c\u8ba4\u8bc1<\/li>\n\n\n\n<li>\u901f\u7387\u9650\u5236\uff08\u9632\u6b62 DDoS\uff09<\/li>\n\n\n\n<li>\u53ef\u7591\u8bbf\u95ee\u68c0\u6d4b\u548c\u544a\u8b66<\/li>\n\n\n\n<li>CORS \u8de8\u57df\u914d\u7f6e<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u6027\u80fd\u4f18\u5316<\/strong>\uff1a\n<ul class=\"wp-block-list\">\n<li>Redis \u7f13\u5b58\u5c42<\/li>\n\n\n\n<li>\u6570\u636e\u5e93\u8fde\u63a5\u6c60<\/li>\n\n\n\n<li>\u5f02\u6b65 I\/O \u5904\u7406<\/li>\n\n\n\n<li>Gzip \u538b\u7f29\u54cd\u5e94<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fastapi-%E5%AE%9E%E7%8E%B0%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B\">FastAPI \u5b9e\u73b0\u4ee3\u7801\u793a\u4f8b<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E8%B7%AF%E7%94%B1%E5%AE%9A%E4%B9%89%E5%95%86%E5%93%81-api\">\u8def\u7531\u5b9a\u4e49\uff08\u5546\u54c1 API\uff09<\/h4>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># app\/routers\/items.py\nfrom fastapi import APIRouter, HTTPException, Depends, Query\nfrom sqlalchemy.orm import Session\nfrom typing import List, Optional\nfrom .. import crud, schemas\nfrom ..database import get_db\nfrom ..security import get_current_user, get_admin_user\n\nrouter = APIRouter(\n    prefix=\"\/items\",\n    tags=&#91;\"\u5546\u54c1\u7ba1\u7406\"&#93;,\n    responses={404: {\"description\": \"\u5546\u54c1\u672a\u627e\u5230\"}}\n)\n\n@router.get(\"\/\", response_model=List&#91;schemas.Item&#93;)\ndef read_items(\n    skip: int = Query(0, ge=0, description=\"\u8df3\u8fc7\u7684\u8bb0\u5f55\u6570\"),\n    limit: int = Query(10, ge=1, le=100, description=\"\u8fd4\u56de\u7684\u8bb0\u5f55\u6570\"),\n    db: Session = Depends(get_db),\n    current_user: Optional&#91;dict&#93; = Depends(lambda: None)  # \u516c\u5f00\u8bbf\u95ee\uff0c\u65e0\u9700\u8ba4\u8bc1\n):\n    \"\"\"\u83b7\u53d6\u5546\u54c1\u5217\u8868\uff08\u516c\u5f00\u8bbf\u95ee\uff09\"\"\"\n    items = crud.get_items(db, skip=skip, limit=limit)\n    return items\n\n@router.get(\"\/search\", response_model=List&#91;schemas.Item&#93;)\ndef search_items(\n    keyword: str = Query(..., min_length=1, description=\"\u641c\u7d22\u5173\u952e\u8bcd\"),\n    skip: int = Query(0, ge=0),\n    limit: int = Query(10, ge=1, le=100),\n    db: Session = Depends(get_db),\n    current_user: Optional&#91;dict&#93; = Depends(lambda: None)  # \u516c\u5f00\u8bbf\u95ee\uff0c\u65e0\u9700\u8ba4\u8bc1\n):\n    \"\"\"\u641c\u7d22\u5546\u54c1\uff08\u516c\u5f00\u8bbf\u95ee\uff09\"\"\"\n    items = crud.search_items(db, keyword=keyword, skip=skip, limit=limit)\n    return items\n\n@router.get(\"\/{item_id}\", response_model=schemas.Item)\ndef read_item(\n    item_id: int,\n    db: Session = Depends(get_db),\n    current_user: Optional&#91;dict&#93; = Depends(lambda: None)  # \u516c\u5f00\u8bbf\u95ee\uff0c\u65e0\u9700\u8ba4\u8bc1\n):\n    \"\"\"\u83b7\u53d6\u5355\u4e2a\u5546\u54c1\uff08\u516c\u5f00\u8bbf\u95ee\uff09\"\"\"\n    db_item = crud.get_item(db, item_id=item_id)\n    if db_item is None:\n        raise HTTPException(status_code=404, detail=\"\u5546\u54c1\u672a\u627e\u5230\")\n    return db_item\n\n@router.post(\"\/\", response_model=schemas.Item, status_code=201)\ndef create_item(\n    item: schemas.ItemCreate,\n    db: Session = Depends(get_db),\n    admin_user: dict = Depends(get_admin_user)  # \u9700\u8981\u7ba1\u7406\u5458\u6743\u9650\n):\n    \"\"\"\u521b\u5efa\u65b0\u5546\u54c1\"\"\"\n    return crud.create_item(db=db, item=item)\n\n@router.put(\"\/{item_id}\", response_model=schemas.Item)\ndef update_item(\n    item_id: int,\n    item: schemas.ItemUpdate,\n    db: Session = Depends(get_db),\n    admin_user: dict = Depends(get_admin_user)  # \u9700\u8981\u7ba1\u7406\u5458\u6743\u9650\n):\n    \"\"\"\u66f4\u65b0\u5546\u54c1\u4fe1\u606f\"\"\"\n    db_item = crud.update_item(db=db, item_id=item_id, item=item)\n    if db_item is None:\n        raise HTTPException(status_code=404, detail=\"\u5546\u54c1\u672a\u627e\u5230\")\n    return db_item\n\n@router.delete(\"\/{item_id}\")\ndef delete_item(\n    item_id: int,\n    db: Session = Depends(get_db),\n    admin_user: dict = Depends(get_admin_user)  # \u9700\u8981\u7ba1\u7406\u5458\u6743\u9650\n):\n    \"\"\"\u5220\u9664\u5546\u54c1\"\"\"\n    success = crud.delete_item(db=db, item_id=item_id)\n    if not success:\n        raise HTTPException(status_code=404, detail=\"\u5546\u54c1\u672a\u627e\u5230\")\n    return {\"message\": \"\u5546\u54c1\u5220\u9664\u6210\u529f\"}\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\"># app\/routers\/items.py<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> fastapi <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> APIRouter, HTTPException, Depends, Query<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> sqlalchemy.orm <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> Session<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> typing <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> List, Optional<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> .. <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> crud, schemas<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> ..database <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> get_db<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> ..security <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> get_current_user, get_admin_user<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">router <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> APIRouter(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">prefix<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/items<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">tags<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u7ba1\u7406<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">responses<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">{<\/span><span style=\"color: #BF9EEE\">404<\/span><span style=\"color: #F6F6F4\">: {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">description<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u672a\u627e\u5230<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">}}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #62E884\">@router.get<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">response_model<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">List&#91;schemas.Item&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">read_items<\/span><span style=\"color: #F6F6F4\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">skip<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Query(<\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">ge<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u8df3\u8fc7\u7684\u8bb0\u5f55\u6570<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">limit<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Query(<\/span><span style=\"color: #BF9EEE\">10<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">ge<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">le<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u8fd4\u56de\u7684\u8bb0\u5f55\u6570<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Session <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_db),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">current_user<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(<\/span><span style=\"color: #F286C4\">lambda<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">)  <\/span><span style=\"color: #7B7F8B\"># \u516c\u5f00\u8bbf\u95ee\uff0c\u65e0\u9700\u8ba4\u8bc1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u83b7\u53d6\u5546\u54c1\u5217\u8868\uff08\u516c\u5f00\u8bbf\u95ee\uff09&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    items <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> crud.get_items(db, <\/span><span style=\"color: #FFB86C; font-style: italic\">skip<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">skip, <\/span><span style=\"color: #FFB86C; font-style: italic\">limit<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">limit)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> items<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #62E884\">@router.get<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/search<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">response_model<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">List&#91;schemas.Item&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">search_items<\/span><span style=\"color: #F6F6F4\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">keyword<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Query(<\/span><span style=\"color: #BF9EEE\">...<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">min_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u641c\u7d22\u5173\u952e\u8bcd<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">skip<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Query(<\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">ge<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">limit<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Query(<\/span><span style=\"color: #BF9EEE\">10<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">ge<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">le<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Session <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_db),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">current_user<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(<\/span><span style=\"color: #F286C4\">lambda<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">)  <\/span><span style=\"color: #7B7F8B\"># \u516c\u5f00\u8bbf\u95ee\uff0c\u65e0\u9700\u8ba4\u8bc1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u641c\u7d22\u5546\u54c1\uff08\u516c\u5f00\u8bbf\u95ee\uff09&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    items <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> crud.search_items(db, <\/span><span style=\"color: #FFB86C; font-style: italic\">keyword<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">keyword, <\/span><span style=\"color: #FFB86C; font-style: italic\">skip<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">skip, <\/span><span style=\"color: #FFB86C; font-style: italic\">limit<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">limit)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> items<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #62E884\">@router.get<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/<\/span><span style=\"color: #BF9EEE\">{item_id}<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">response_model<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">schemas.Item)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">read_item<\/span><span style=\"color: #F6F6F4\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">item_id<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Session <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_db),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">current_user<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(<\/span><span style=\"color: #F286C4\">lambda<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">)  <\/span><span style=\"color: #7B7F8B\"># \u516c\u5f00\u8bbf\u95ee\uff0c\u65e0\u9700\u8ba4\u8bc1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u83b7\u53d6\u5355\u4e2a\u5546\u54c1\uff08\u516c\u5f00\u8bbf\u95ee\uff09&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    db_item <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> crud.get_item(db, <\/span><span style=\"color: #FFB86C; font-style: italic\">item_id<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">item_id)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> db_item <\/span><span style=\"color: #F286C4\">is<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">raise<\/span><span style=\"color: #F6F6F4\"> HTTPException(<\/span><span style=\"color: #FFB86C; font-style: italic\">status_code<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">404<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">detail<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u672a\u627e\u5230<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> db_item<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #62E884\">@router.post<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">response_model<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">schemas.Item, <\/span><span style=\"color: #FFB86C; font-style: italic\">status_code<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">201<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">create_item<\/span><span style=\"color: #F6F6F4\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">item<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> schemas.ItemCreate,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Session <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_db),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">admin_user<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_admin_user)  <\/span><span style=\"color: #7B7F8B\"># \u9700\u8981\u7ba1\u7406\u5458\u6743\u9650<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u521b\u5efa\u65b0\u5546\u54c1&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> crud.create_item(<\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">db, <\/span><span style=\"color: #FFB86C; font-style: italic\">item<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">item)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #62E884\">@router.put<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/<\/span><span style=\"color: #BF9EEE\">{item_id}<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">response_model<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">schemas.Item)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">update_item<\/span><span style=\"color: #F6F6F4\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">item_id<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">item<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> schemas.ItemUpdate,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Session <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_db),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">admin_user<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_admin_user)  <\/span><span style=\"color: #7B7F8B\"># \u9700\u8981\u7ba1\u7406\u5458\u6743\u9650<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u66f4\u65b0\u5546\u54c1\u4fe1\u606f&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    db_item <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> crud.update_item(<\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">db, <\/span><span style=\"color: #FFB86C; font-style: italic\">item_id<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">item_id, <\/span><span style=\"color: #FFB86C; font-style: italic\">item<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">item)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> db_item <\/span><span style=\"color: #F286C4\">is<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">raise<\/span><span style=\"color: #F6F6F4\"> HTTPException(<\/span><span style=\"color: #FFB86C; font-style: italic\">status_code<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">404<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">detail<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u672a\u627e\u5230<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> db_item<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #62E884\">@router.delete<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/<\/span><span style=\"color: #BF9EEE\">{item_id}<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">delete_item<\/span><span style=\"color: #F6F6F4\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">item_id<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Session <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_db),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">admin_user<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Depends(get_admin_user)  <\/span><span style=\"color: #7B7F8B\"># \u9700\u8981\u7ba1\u7406\u5458\u6743\u9650<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u5220\u9664\u5546\u54c1&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    success <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> crud.delete_item(<\/span><span style=\"color: #FFB86C; font-style: italic\">db<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">db, <\/span><span style=\"color: #FFB86C; font-style: italic\">item_id<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">item_id)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">not<\/span><span style=\"color: #F6F6F4\"> success:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">raise<\/span><span style=\"color: #F6F6F4\"> HTTPException(<\/span><span style=\"color: #FFB86C; font-style: italic\">status_code<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">404<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">detail<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u672a\u627e\u5230<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">message<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u5220\u9664\u6210\u529f<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"pydantic-%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81\">Pydantic \u6570\u636e\u9a8c\u8bc1<\/h4>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># app\/schemas.py\nfrom pydantic import BaseModel, Field\nfrom typing import Union, Optional\nfrom datetime import datetime\n\nclass ItemBase(BaseModel):\n    name: str = Field(..., min_length=1, max_length=100, description=\"\u5546\u54c1\u540d\u79f0\")\n    price: float = Field(..., gt=0, description=\"\u5546\u54c1\u4ef7\u683c\uff0c\u5fc5\u987b\u5927\u4e8e0\")\n    is_offer: Union&#91;bool, None&#93; = Field(default=None, description=\"\u662f\u5426\u4e3a\u7279\u4ef7\u5546\u54c1\")\n    description: Optional&#91;str&#93; = Field(None, max_length=1000, description=\"\u5546\u54c1\u63cf\u8ff0\")\n\nclass ItemCreate(ItemBase):\n    pass\n\nclass ItemUpdate(BaseModel):\n    name: Optional&#91;str&#93; = Field(None, min_length=1, max_length=100)\n    price: Optional&#91;float&#93; = Field(None, gt=0)\n    is_offer: Optional&#91;bool&#93; = None\n    description: Optional&#91;str&#93; = Field(None, max_length=1000)\n\nclass Item(ItemBase):\n    id: int\n    created_at: Optional&#91;datetime&#93; = None\n    updated_at: Optional&#91;datetime&#93; = None\n\n    class Config:\n        from_attributes = True  # Pydantic v2 \u8bed\u6cd5\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\"># app\/schemas.py<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> pydantic <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> BaseModel, Field<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> typing <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> Union, Optional<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> datetime <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> datetime<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">class<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1\">ItemBase<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #97E1F1; font-style: italic\">BaseModel<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    name: <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #BF9EEE\">...<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">min_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">max_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u540d\u79f0<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    price: <\/span><span style=\"color: #97E1F1; font-style: italic\">float<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #BF9EEE\">...<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">gt<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u4ef7\u683c\uff0c\u5fc5\u987b\u5927\u4e8e0<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    is_offer: Union&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">bool<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #FFB86C; font-style: italic\">default<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u662f\u5426\u4e3a\u7279\u4ef7\u5546\u54c1<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    description: Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">max_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1000<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u63cf\u8ff0<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">class<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1\">ItemCreate<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #97E1F1; font-style: italic\">ItemBase<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">pass<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">class<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1\">ItemUpdate<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #97E1F1; font-style: italic\">BaseModel<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    name: Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">min_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">max_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    price: Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">float<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">gt<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    is_offer: Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">bool<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">None<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    description: Optional&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Field(<\/span><span style=\"color: #BF9EEE\">None<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">max_length<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">1000<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">class<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1\">Item<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #97E1F1; font-style: italic\">ItemBase<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #97E1F1\">id<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    created_at: Optional&#91;datetime&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">None<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    updated_at: Optional&#91;datetime&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">None<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">class<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1\">Config<\/span><span style=\"color: #F6F6F4\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        from_attributes <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">True<\/span><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #7B7F8B\"># Pydantic v2 \u8bed\u6cd5<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-%E5%85%B1%E4%BA%AB%E7%BB%84%E4%BB%B6%E5%BA%93packagesui\">3. \u5171\u4eab\u7ec4\u4ef6\u5e93\uff08packages\/ui\uff09<\/h3>\n\n\n\n<p>\u5728 Monorepo \u67b6\u6784\u4e2d\uff0c\u5171\u4eab\u7ec4\u4ef6\u5e93\u662f\u63d0\u5347\u5f00\u53d1\u6548\u7387\u7684\u5173\u952e\uff1a<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ \u4efb\u610f\u5e94\u7528\u4e2d\u5bfc\u5165\u5171\u4eab\u7ec4\u4ef6\nimport { Button, Card, Input } from \"@interview\/ui\";\n\n\/\/ \u5e26\u7c7b\u578b\u63d0\u793a\u548c\u81ea\u52a8\u8865\u5168\n&lt;Button variant=\"primary\" size=\"large\">\n  \u70b9\u51fb\u6211\n&lt;\/Button>\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u4efb\u610f\u5e94\u7528\u4e2d\u5bfc\u5165\u5171\u4eab\u7ec4\u4ef6<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> { Button, Card, Input } <\/span><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">@interview\/ui<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u5e26\u7c7b\u578b\u63d0\u793a\u548c\u81ea\u52a8\u8865\u5168<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">&lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">Button<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">variant<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">primary<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">size<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">large<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  \u70b9\u51fb\u6211<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">&lt;\/<\/span><span style=\"color: #97E1F1; font-style: italic\">Button<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E7%9F%A5%E8%AF%86%E5%BA%93%E5%86%85%E5%AE%B9%E4%BD%93%E7%B3%BB\">\u77e5\u8bc6\u5e93\u5185\u5bb9\u4f53\u7cfb<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%96%87%E6%A1%A3%E7%BB%93%E6%9E%84\">\u6587\u6863\u7ed3\u6784<\/h3>\n\n\n\n<p>\u6574\u4e2a\u77e5\u8bc6\u5e93\u6587\u6863\u6309\u96be\u5ea6\u548c\u9886\u57df\u8fdb\u884c\u5206\u7c7b\uff0c\u5f62\u6210\u5b8c\u6574\u7684\u5b66\u4e60\u8def\u5f84\uff1a<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>docs\/\n\u251c\u2500\u2500 README.md                      # \u5bfc\u822a\u7d22\u5f15\n\u251c\u2500\u2500 frontend.md                    # \u57fa\u7840\u77e5\u8bc6 (70KB)\n\u251c\u2500\u2500 frontend-extended.md           # \u6269\u5c55\u77e5\u8bc6 (46KB)\n\u251c\u2500\u2500 dynamic-programming.md         # \u52a8\u6001\u89c4\u5212 (18KB)\n\u251c\u2500\u2500 min-path-sum-explained.md      # \u6700\u5c0f\u8def\u5f84\u548c\u8be6\u89e3\n\u251c\u2500\u2500 frontend-algorithms-practical.md  # \u5b9e\u9645\u5de5\u4f5c\u7b97\u6cd5\n\u251c\u2500\u2500 case1.md                      # \u7efc\u5408\u9898\u5e93 (\u4f18\u5316\u7248)\n\u251c\u2500\u2500 styled-components-guide.md     # styled-components \u6307\u5357\n\u251c\u2500\u2500 REDIS_USAGE.md                 # Redis \u4f7f\u7528\u6307\u5357\n\u2514\u2500\u2500 PACKAGES_VS_SHARED.md          # \u5305\u4e0e\u5171\u4eab\u4ee3\u7801\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #F6F6F4\">docs<\/span><span style=\"color: #F286C4\">\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 <\/span><span style=\"color: #BF9EEE\">README<\/span><span style=\"color: #F6F6F4\">.md                      # \u5bfc\u822a\u7d22\u5f15<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 frontend.md                    # <\/span><span style=\"color: #62E884\">\u57fa\u7840\u77e5\u8bc6<\/span><span style=\"color: #F6F6F4\"> (70KB)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 frontend<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">extended.md           # <\/span><span style=\"color: #62E884\">\u6269\u5c55\u77e5\u8bc6<\/span><span style=\"color: #F6F6F4\"> (46KB)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 dynamic<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">programming.md         # <\/span><span style=\"color: #62E884\">\u52a8\u6001\u89c4\u5212<\/span><span style=\"color: #F6F6F4\"> (18KB)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 min<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">path<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">sum<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">explained.md      # \u6700\u5c0f\u8def\u5f84\u548c\u8be6\u89e3<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 frontend<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">algorithms<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">practical.md  # \u5b9e\u9645\u5de5\u4f5c\u7b97\u6cd5<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 case1.md                      # <\/span><span style=\"color: #62E884\">\u7efc\u5408\u9898\u5e93<\/span><span style=\"color: #F6F6F4\"> (\u4f18\u5316\u7248)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 styled<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">components<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">guide.md     # styled<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">components \u6307\u5357<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 <\/span><span style=\"color: #BF9EEE\">REDIS_USAGE<\/span><span style=\"color: #F6F6F4\">.md                 # Redis \u4f7f\u7528\u6307\u5357<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2514\u2500\u2500 <\/span><span style=\"color: #BF9EEE\">PACKAGES_VS_SHARED<\/span><span style=\"color: #F6F6F4\">.md          # \u5305\u4e0e\u5171\u4eab\u4ee3\u7801<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%9C%80%E6%96%B0%E4%BC%98%E5%8C%96%E5%86%85%E5%AE%B9\">\u6700\u65b0\u4f18\u5316\u5185\u5bb9<\/h3>\n\n\n\n<p><strong>case1.md \u7efc\u5408\u9898\u5e93\u4f18\u5316\u4eae\u70b9<\/strong>\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u683c\u5f0f\u4fee\u6b63<\/strong>\uff1a\u4fee\u6b63\u7f16\u53f7\u9519\u8bef\uff0c\u7edf\u4e00\u7684\u6587\u6863\u7ed3\u6784<\/li>\n\n\n\n<li><strong>\u4ee3\u7801\u793a\u4f8b<\/strong>\uff1a\u6bcf\u4e2a\u95ee\u9898\u90fd\u6dfb\u52a0\u4e86\u8be6\u7ec6\u7684 TypeScript \u4ee3\u7801<\/li>\n\n\n\n<li><strong>\u5bf9\u6bd4\u8868\u683c<\/strong>\uff1a\u591a\u79cd\u6280\u672f\u65b9\u6848\u7684\u6a2a\u5411\u5bf9\u6bd4\uff08\u5982 LRU vs LFU\u3001gRPC vs REST\uff09<\/li>\n\n\n\n<li><strong>\u5b9e\u6218\u6848\u4f8b<\/strong>\uff1a\u7b2c 8 \u9898\u63d0\u4f9b 3 \u4e2a\u5b8c\u6574\u7684 STAR \u6cd5\u5219\u6848\u4f8b<\/li>\n\n\n\n<li><strong>\u5b8c\u6574\u5b9e\u73b0<\/strong>\uff1aLRU\/LFU \u7f13\u5b58\u7684\u5b8c\u6574 TypeScript \u5b9e\u73b0<\/li>\n\n\n\n<li><strong>\u8d28\u91cf\u76d1\u63a7<\/strong>\uff1aWebSocket \u5065\u5eb7\u5ea6\u8bc4\u5206\u7cfb\u7edf<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E5%AD%A6%E4%B9%A0%E8%B7%AF%E5%BE%84%E6%8E%A8%E8%8D%90\">\u5b66\u4e60\u8def\u5f84\u63a8\u8350<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E5%88%9D%E5%AD%A6%E8%80%85%E8%B7%AF%E5%BE%84\">\u521d\u5b66\u8005\u8def\u5f84<\/h4>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code>\u524d\u7aef\u57fa\u7840\u77e5\u8bc6 \u2192 \u5b9e\u9645\u5de5\u4f5c\u4e2d\u7684\u7b97\u6cd5 \u2192 \u52a8\u6001\u89c4\u5212\u5165\u95e8<\/code><\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E8%BF%9B%E9%98%B6%E5%BC%80%E5%8F%91%E8%80%85%E8%B7%AF%E5%BE%84\">\u8fdb\u9636\u5f00\u53d1\u8005\u8def\u5f84<\/h4>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code>\u524d\u7aef\u6269\u5c55\u77e5\u8bc6 \u2192 \u6700\u5c0f\u8def\u5f84\u548c\u8be6\u89e3 \u2192 \u6df1\u5165\u7279\u5b9a\u6280\u672f\u6808<\/code><\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E9%9D%A2%E8%AF%95%E5%86%B2%E5%88%BA%E8%B7%AF%E5%BE%84\">\u9762\u8bd5\u51b2\u523a\u8def\u5f84<\/h4>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code>\u6838\u5fc3\u77e5\u8bc6\u70b9 \u2192 \u7b97\u6cd5\u57fa\u7840 \u2192 \u6df1\u5ea6\u62d3\u5c55 \u2192 \u5b9e\u6218\u7ecf\u9a8c \u2192 \u7efc\u5408\u9898\u5e93<\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E6%8A%80%E6%9C%AF%E4%BA%AE%E7%82%B9%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5\">\u6280\u672f\u4eae\u70b9\u4e0e\u6700\u4f73\u5b9e\u8df5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1-%E5%89%8D%E7%AB%AFturborepo-%E6%9E%84%E5%BB%BA%E4%BC%98%E5%8C%96\">1. \u524d\u7aef\uff1aTurborepo \u6784\u5efa\u4f18\u5316<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ turbo.json\n{\n  \"pipeline\": {\n    \"build\": {\n      \"dependsOn\": &#91;\"^build\"&#93;,\n      \"outputs\": &#91;\".next\/**\", \"!.next\/cache\/**\"&#93;\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    }\n  }\n}\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ turbo.json<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">pipeline<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">build<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">dependsOn<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> &#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">^build<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">outputs<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> &#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">.next\/**<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">!.next\/cache\/**<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    },<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">dev<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">cache<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">false<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">persistent<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u4f18\u52bf<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u667a\u80fd\u7f13\u5b58\u673a\u5236\uff0c\u907f\u514d\u91cd\u590d\u6784\u5efa<\/li>\n\n\n\n<li>\u5e76\u884c\u6267\u884c\u4efb\u52a1\uff0c\u63d0\u5347\u6784\u5efa\u901f\u5ea6<\/li>\n\n\n\n<li>\u4f9d\u8d56\u5173\u7cfb\u81ea\u52a8\u5206\u6790<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-%E5%90%8E%E7%AB%AFfastapi-%E5%BC%82%E6%AD%A5%E6%9E%B6%E6%9E%84\">2. \u540e\u7aef\uff1aFastAPI \u5f02\u6b65\u67b6\u6784<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># app\/factory.py\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom .routers import items, system, auth, redis, doc_logs\n\ndef create_app():\n    \"\"\"\u521b\u5efa FastAPI \u5e94\u7528\u5b9e\u4f8b\"\"\"\n\n    app = FastAPI(\n        title=\"FastAPI Web API\",\n        description=\"\u5168\u6808\u6f14\u793a\u5e73\u53f0\u540e\u7aef\u670d\u52a1\",\n        version=\"1.0.0\",\n        docs_url=\"\/docs\",\n        redoc_url=\"\/redoc\"\n    )\n\n    # CORS \u914d\u7f6e\n    # \u6ce8\u610f\uff1aallow_origins=&#91;\"*\"&#93; \u4ec5\u7528\u4e8e\u5f00\u53d1\u73af\u5883\uff0c\u751f\u4ea7\u73af\u5883\u8bf7\u6307\u5b9a\u5177\u4f53\u57df\u540d\n    app.add_middleware(\n        CORSMiddleware,\n        allow_origins=&#91;\"*\"&#93;,  # \u751f\u4ea7\u73af\u5883\u8bf7\u6539\u4e3a &#91;\"https:\/\/web.erishen.cn\", ...&#93;\n        allow_credentials=True,\n        allow_methods=&#91;\"*\"&#93;,\n        allow_headers=&#91;\"*\"&#93;,\n    )\n\n    # \u8def\u7531\u6ce8\u518c\n    app.include_router(items.router, prefix=\"\/items\", tags=&#91;\"\u5546\u54c1\u7ba1\u7406\"&#93;)\n    app.include_router(auth.router, prefix=\"\/auth\", tags=&#91;\"\u8ba4\u8bc1\"&#93;)\n    app.include_router(docs.router, prefix=\"\/docs\", tags=&#91;\"\u6587\u6863\"&#93;)\n    app.include_router(redis.router, prefix=\"\/redis\", tags=&#91;\"Redis\"&#93;)\n    app.include_router(system.router, tags=&#91;\"\u7cfb\u7edf\"&#93;)\n\n    return app\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\"># app\/factory.py<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> fastapi <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> FastAPI<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> fastapi.middleware.cors <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> CORSMiddleware<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> .routers <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> items, system, auth, redis, doc_logs<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">create_app<\/span><span style=\"color: #F6F6F4\">():<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\">&quot;&quot;&quot;\u521b\u5efa FastAPI \u5e94\u7528\u5b9e\u4f8b&quot;&quot;&quot;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> FastAPI(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">title<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">FastAPI Web API<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">description<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5168\u6808\u6f14\u793a\u5e73\u53f0\u540e\u7aef\u670d\u52a1<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">version<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">1.0.0<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">docs_url<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/docs<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">redoc_url<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/redoc<\/span><span style=\"color: #DEE492\">&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    )<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># CORS \u914d\u7f6e<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># \u6ce8\u610f\uff1aallow_origins=&#91;&quot;*&quot;&#93; \u4ec5\u7528\u4e8e\u5f00\u53d1\u73af\u5883\uff0c\u751f\u4ea7\u73af\u5883\u8bf7\u6307\u5b9a\u5177\u4f53\u57df\u540d<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app.add_middleware(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        CORSMiddleware,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">allow_origins<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">*<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;,  <\/span><span style=\"color: #7B7F8B\"># \u751f\u4ea7\u73af\u5883\u8bf7\u6539\u4e3a &#91;&quot;https:\/\/web.erishen.cn&quot;, ...&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">allow_credentials<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">True<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">allow_methods<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">*<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #FFB86C; font-style: italic\">allow_headers<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">*<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    )<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># \u8def\u7531\u6ce8\u518c<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app.include_router(items.router, <\/span><span style=\"color: #FFB86C; font-style: italic\">prefix<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/items<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">tags<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u5546\u54c1\u7ba1\u7406<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app.include_router(auth.router, <\/span><span style=\"color: #FFB86C; font-style: italic\">prefix<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/auth<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">tags<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u8ba4\u8bc1<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app.include_router(docs.router, <\/span><span style=\"color: #FFB86C; font-style: italic\">prefix<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/docs<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">tags<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u6587\u6863<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app.include_router(redis.router, <\/span><span style=\"color: #FFB86C; font-style: italic\">prefix<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\/redis<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">tags<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Redis<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    app.include_router(system.router, <\/span><span style=\"color: #FFB86C; font-style: italic\">tags<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">\u7cfb\u7edf<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> app<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u4f18\u52bf<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u539f\u751f\u5f02\u6b65\u652f\u6301\uff0c\u9ad8\u5e76\u53d1\u6027\u80fd<\/li>\n\n\n\n<li>\u81ea\u52a8\u751f\u6210 OpenAPI \u6587\u6863\uff08Swagger\uff09<\/li>\n\n\n\n<li>\u7c7b\u578b\u6ce8\u89e3\u9a71\u52a8\u7684\u6570\u636e\u9a8c\u8bc1\uff08Pydantic\uff09<\/li>\n\n\n\n<li>\u5185\u7f6e\u4f9d\u8d56\u6ce8\u5165\u7cfb\u7edf<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-%E5%90%8E%E7%AB%AF%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1\">3. \u540e\u7aef\uff1a\u5206\u5c42\u67b6\u6784\u8bbe\u8ba1<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>app\/\n\u251c\u2500\u2500 routers\/                # API \u8def\u7531\u5c42\n\u2502   \u251c\u2500\u2500 items.py        # \u5546\u54c1 API\n\u2502   \u251c\u2500\u2500 auth.py         # \u8ba4\u8bc1 API\n\u2502   \u251c\u2500\u2500 docs.py         # \u6587\u6863 API\n\u2502   \u251c\u2500\u2500 redis.py        # Redis API\n\u2502   \u2514\u2500\u2500 system.py      # \u7cfb\u7edf API\n\u251c\u2500\u2500 config.py               # \u914d\u7f6e\u7ba1\u7406\n\u251c\u2500\u2500 crud.py                # \u6570\u636e\u5e93 CRUD \u64cd\u4f5c\n\u251c\u2500\u2500 models.py              # \u6570\u636e\u6a21\u578b\u5c42 (SQLAlchemy)\n\u2502   \u251c\u2500\u2500 Item            # \u5546\u54c1\u6a21\u578b\n\u2502   \u2514\u2500\u2500 DocLog         # \u6587\u6863\u65e5\u5fd7\u6a21\u578b\n\u251c\u2500\u2500 schemas.py             # Pydantic \u6570\u636e\u9a8c\u8bc1\n\u251c\u2500\u2500 security.py            # \u5b89\u5168\u5de5\u5177\uff08JWT\u3001\u8ba4\u8bc1\uff09\n\u251c\u2500\u2500 security_headers.py     # \u5b89\u5168\u54cd\u5e94\u5934\n\u251c\u2500\u2500 middleware.py         # \u4e2d\u95f4\u4ef6\uff08\u65e5\u5fd7\u3001\u9650\u6d41\uff09\n\u251c\u2500\u2500 ip_filter.py          # IP \u9ed1\u540d\u5355\/\u767d\u540d\u5355\u8fc7\u6ee4\n\u251c\u2500\u2500 path_protection.py    # \u654f\u611f\u8def\u5f84\u4fdd\u62a4\n\u251c\u2500\u2500 redis_client.py       # Redis \u5ba2\u6237\u7aef\n\u251c\u2500\u2500 database.py           # \u6570\u636e\u5e93\u8fde\u63a5\n\u251c\u2500\u2500 exceptions.py         # \u5f02\u5e38\u5904\u7406\n\u2514\u2500\u2500 factory.py           # \u5e94\u7528\u5de5\u5382\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #F6F6F4\">app<\/span><span style=\"color: #F286C4\">\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 routers<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">                <\/span><span style=\"color: #7B7F8B\"># API \u8def\u7531\u5c42<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u251c\u2500\u2500 items.py        <\/span><span style=\"color: #7B7F8B\"># \u5546\u54c1 API<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u251c\u2500\u2500 auth.py         <\/span><span style=\"color: #7B7F8B\"># \u8ba4\u8bc1 API<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u251c\u2500\u2500 docs.py         <\/span><span style=\"color: #7B7F8B\"># \u6587\u6863 API<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u251c\u2500\u2500 redis.py        <\/span><span style=\"color: #7B7F8B\"># Redis API<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u2514\u2500\u2500 system.py      <\/span><span style=\"color: #7B7F8B\"># \u7cfb\u7edf API<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 config.py               <\/span><span style=\"color: #7B7F8B\"># \u914d\u7f6e\u7ba1\u7406<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 crud.py                <\/span><span style=\"color: #7B7F8B\"># \u6570\u636e\u5e93 CRUD \u64cd\u4f5c<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 models.py              <\/span><span style=\"color: #7B7F8B\"># \u6570\u636e\u6a21\u578b\u5c42 (SQLAlchemy)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u251c\u2500\u2500 Item            <\/span><span style=\"color: #7B7F8B\"># \u5546\u54c1\u6a21\u578b<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2502   \u2514\u2500\u2500 DocLog         <\/span><span style=\"color: #7B7F8B\"># \u6587\u6863\u65e5\u5fd7\u6a21\u578b<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 schemas.py             <\/span><span style=\"color: #7B7F8B\"># Pydantic \u6570\u636e\u9a8c\u8bc1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 security.py            <\/span><span style=\"color: #7B7F8B\"># \u5b89\u5168\u5de5\u5177\uff08JWT\u3001\u8ba4\u8bc1\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 security_headers.py     <\/span><span style=\"color: #7B7F8B\"># \u5b89\u5168\u54cd\u5e94\u5934<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 middleware.py         <\/span><span style=\"color: #7B7F8B\"># \u4e2d\u95f4\u4ef6\uff08\u65e5\u5fd7\u3001\u9650\u6d41\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 ip_filter.py          <\/span><span style=\"color: #7B7F8B\"># IP \u9ed1\u540d\u5355\/\u767d\u540d\u5355\u8fc7\u6ee4<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 path_protection.py    <\/span><span style=\"color: #7B7F8B\"># \u654f\u611f\u8def\u5f84\u4fdd\u62a4<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 redis_client.py       <\/span><span style=\"color: #7B7F8B\"># Redis \u5ba2\u6237\u7aef<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 database.py           <\/span><span style=\"color: #7B7F8B\"># \u6570\u636e\u5e93\u8fde\u63a5<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u251c\u2500\u2500 exceptions.py         <\/span><span style=\"color: #7B7F8B\"># \u5f02\u5e38\u5904\u7406<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">\u2514\u2500\u2500 factory.py           <\/span><span style=\"color: #7B7F8B\"># \u5e94\u7528\u5de5\u5382<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u4f18\u52bf<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u804c\u8d23\u5206\u79bb\uff0c\u6613\u4e8e\u7ef4\u62a4\u548c\u6d4b\u8bd5<\/li>\n\n\n\n<li>\u4e1a\u52a1\u903b\u8f91\u72ec\u7acb\u4e8e API \u63a5\u53e3<\/li>\n\n\n\n<li>\u53ef\u590d\u7528\u7684\u670d\u52a1\u5c42<\/li>\n\n\n\n<li>\u7075\u6d3b\u7684\u4e2d\u95f4\u4ef6\u6269\u5c55<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"4-%E5%AE%89%E5%85%A8%E6%9C%BA%E5%88%B6%E5%A4%9A%E5%B1%82%E7%BA%A7%E9%98%B2%E6%8A%A4\">4. \u5b89\u5168\u673a\u5236\uff1a\u591a\u5c42\u7ea7\u9632\u62a4<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># app\/middleware.py\nfrom fastapi import Request, Response\nfrom .security import is_suspicious_request\n\nasync def security_middleware(request: Request, call_next):\n    # 1. \u53ef\u7591\u8bbf\u95ee\u68c0\u6d4b\n    if is_suspicious_request(request):\n        logger.warning(f\"\u26a0\ufe0f  \u53ef\u7591\u8bbf\u95ee\u68c0\u6d4b: IP={request.client.host}\")\n        return Response(status_code=403)\n\n    # 2. \u901f\u7387\u9650\u5236\n    if await rate_limiter.is_exceeded(request):\n        logger.warning(f\"\ud83d\udeab  \u901f\u7387\u9650\u5236: IP={request.client.host}\")\n        return Response(\n            content=\"Too many requests\",\n            status_code=429\n        )\n\n    # 3. \u65e5\u5fd7\u8bb0\u5f55\n    logger.info(f\"{request.method} {request.url.path}\")\n    response = await call_next(request)\n    return response\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\"># app\/middleware.py<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> fastapi <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> Request, Response<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> .security <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> is_suspicious_request<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">security_middleware<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">request<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> Request, <\/span><span style=\"color: #FFB86C; font-style: italic\">call_next<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># 1. \u53ef\u7591\u8bbf\u95ee\u68c0\u6d4b<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> is_suspicious_request(request):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        logger.warning(<\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;\u26a0\ufe0f  \u53ef\u7591\u8bbf\u95ee\u68c0\u6d4b: IP=<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">request.client.host<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> Response(<\/span><span style=\"color: #FFB86C; font-style: italic\">status_code<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">403<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># 2. \u901f\u7387\u9650\u5236<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> rate_limiter.is_exceeded(request):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        logger.warning(<\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;\ud83d\udeab  \u901f\u7387\u9650\u5236: IP=<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">request.client.host<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> Response(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #FFB86C; font-style: italic\">content<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Too many requests<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #FFB86C; font-style: italic\">status_code<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">429<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        )<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># 3. \u65e5\u5fd7\u8bb0\u5f55<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    logger.info(<\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">request.method<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\"> <\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">request.url.path<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    response <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> call_next(request)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> response<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u9632\u62a4\u63aa\u65bd<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 JWT \u4ee4\u724c\u8ba4\u8bc1<\/li>\n\n\n\n<li>\u2705 \u901f\u7387\u9650\u5236\uff08\u9632\u6b62\u6ee5\u7528\uff09<\/li>\n\n\n\n<li>\u2705 \u53ef\u7591\u8bbf\u95ee\u68c0\u6d4b<\/li>\n\n\n\n<li>\u2705 CORS \u8de8\u57df\u63a7\u5236<\/li>\n\n\n\n<li>\u2705 SQL \u6ce8\u5165\u9632\u62a4\uff08ORM\uff09<\/li>\n\n\n\n<li>\u2705 XSS \u9632\u62a4\uff08\u8f93\u5165\u9a8c\u8bc1\uff09<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-mdxmarkdown-%E6%B8%B2%E6%9F%93%E6%96%B9%E6%A1%88\">2. MDX\/Markdown \u6e32\u67d3\u65b9\u6848<\/h3>\n\n\n\n<p>\u4f7f\u7528&nbsp;<code>next-mdx-remote<\/code>&nbsp;+&nbsp;<code>@tailwindcss\/typography<\/code>&nbsp;\u5b9e\u73b0\u4f18\u96c5\u7684\u6587\u6863\u6e32\u67d3\uff1a<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ \u5b89\u88c5\u4f9d\u8d56\npnpm add next-mdx-remote @tailwindcss\/typography\n\n\/\/ Tailwind \u914d\u7f6e\n\/\/ tailwind.config.js\nmodule.exports = {\n  plugins: &#91;require('@tailwindcss\/typography')&#93;,\n};\n\n\/\/ \u4f7f\u7528 Tailwind Typography \u7c7b\n&lt;article className=\"prose prose-slate prose-lg max-w-none\">\n  &lt;MDXRemote source={content} \/>\n&lt;\/article>\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u5b89\u88c5\u4f9d\u8d56<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">pnpm add next<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">mdx<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">remote @<\/span><span style=\"color: #62E884; font-style: italic\">tailwindcss<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #62E884; font-style: italic\">typography<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ Tailwind \u914d\u7f6e<\/span><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ tailwind.config.js<\/span><\/span>\n<span class=\"line\"><span style=\"color: #97E1F1; font-style: italic\">module<\/span><span style=\"color: #F6F6F4\">.<\/span><span style=\"color: #97E1F1; font-style: italic\">exports<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  plugins<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> &#91;<\/span><span style=\"color: #62E884\">require<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #E7EE98\">@tailwindcss\/typography<\/span><span style=\"color: #DEE492\">&#39;<\/span><span style=\"color: #F6F6F4\">)&#93;,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">};<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">\/\/ \u4f7f\u7528 Tailwind Typography \u7c7b<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">&lt;<\/span><span style=\"color: #F286C4\">article<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">className<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">prose prose-slate prose-lg max-w-none<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  &lt;<\/span><span style=\"color: #97E1F1; font-style: italic\">MDXRemote<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884; font-style: italic\">source<\/span><span style=\"color: #F286C4\">={<\/span><span style=\"color: #F6F6F4\">content<\/span><span style=\"color: #F286C4\">}<\/span><span style=\"color: #F6F6F4\"> \/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">&lt;\/<\/span><span style=\"color: #F286C4\">article<\/span><span style=\"color: #F6F6F4\">&gt;<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u652f\u6301\u7684 Markdown \u7279\u6027<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 \u6807\u9898\uff08H1-H6\uff09<\/li>\n\n\n\n<li>\u2705 \u4ee3\u7801\u5757\u548c\u8bed\u6cd5\u9ad8\u4eae<\/li>\n\n\n\n<li>\u2705 \u8868\u683c<\/li>\n\n\n\n<li>\u2705 \u5f15\u7528\u5757<\/li>\n\n\n\n<li>\u2705 \u5217\u8868\uff08\u6709\u5e8f\/\u65e0\u5e8f\uff09<\/li>\n\n\n\n<li>\u2705 \u7c97\u4f53\u3001\u659c\u4f53<\/li>\n\n\n\n<li>\u2705 \u94fe\u63a5\u548c\u56fe\u7247<\/li>\n\n\n\n<li>\u2705 \u5206\u9694\u7ebf<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-workspace-%E5%8D%8F%E8%AE%AE\">3. Workspace \u534f\u8bae<\/h3>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>{\n  \"dependencies\": {\n    \"@interview\/ui\": \"workspace:*\",\n    \"@interview\/utils\": \"workspace:*\"\n  }\n}\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #F6F6F4\">{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">dependencies<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">@interview\/ui<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">workspace:*<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">@interview\/utils<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">workspace:*<\/span><span style=\"color: #DEE492\">&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p>\u4f7f\u7528&nbsp;<code>workspace:*<\/code>&nbsp;\u534f\u8bae\u53ef\u4ee5\u5b9e\u73b0\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5f00\u53d1\u65f6\u5b9e\u65f6\u5f15\u7528\u6e90\u7801<\/li>\n\n\n\n<li>\u6784\u5efa\u65f6\u81ea\u52a8\u94fe\u63a5<\/li>\n\n\n\n<li>\u7248\u672c\u7edf\u4e00\u7ba1\u7406<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"4-%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E7%AE%A1%E7%90%86\">4. \u73af\u5883\u53d8\u91cf\u7ba1\u7406<\/h3>\n\n\n\n<p>\u9879\u76ee\u652f\u6301\u591a\u73af\u5883\u914d\u7f6e\uff1a<\/p>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em># .env.example          # \u6a21\u677f\u6587\u4ef6<\/em>\n<em># .env.local            # \u672c\u5730\u5f00\u53d1\uff08\u4e0d\u63d0\u4ea4\uff09<\/em>\n<em># .env.vercel           # Vercel \u90e8\u7f72<\/em><\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"5-%E7%BB%84%E4%BB%B6%E5%BC%80%E5%8F%91%E6%B5%81%E7%A8%8B\">5. \u7ec4\u4ef6\u5f00\u53d1\u6d41\u7a0b<\/h3>\n\n\n\n<p>\u4f7f\u7528 Storybook \u8fdb\u884c\u7ec4\u4ef6\u5f00\u53d1\uff1a<\/p>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em># \u542f\u52a8 Storybook<\/em>\npnpm storybook\n\n<em># \u6784\u5efa\u9759\u6001\u6587\u6863<\/em>\npnpm build-storybook<\/code><\/pre><\/div>\n\n\n\n<p><strong>\u4f18\u52bf<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7ec4\u4ef6\u72ec\u7acb\u5f00\u53d1<\/li>\n\n\n\n<li>\u5b9e\u65f6\u9884\u89c8\u6548\u679c<\/li>\n\n\n\n<li>\u6587\u6863\u81ea\u52a8\u751f\u6210<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E5%BC%80%E5%8F%91%E4%BD%93%E9%AA%8C%E4%BC%98%E5%8C%96\">\u5f00\u53d1\u4f53\u9a8c\u4f18\u5316<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1-%E7%BB%9F%E4%B8%80%E7%9A%84%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83\">1. \u7edf\u4e00\u7684\u4ee3\u7801\u89c4\u8303<\/h3>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em>\/\/ .eslintrc.js<\/em>\nmodule.exports = {\n  extends: &#91;\"@interview\/eslint-config\"]\n}<\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8%E4%BF%9D%E9%9A%9C\">2. \u7c7b\u578b\u5b89\u5168\u4fdd\u969c<\/h3>\n\n\n\n<p>TypeScript \u914d\u7f6e\u652f\u6301\u8def\u5f84\u522b\u540d\uff1a<\/p>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em>\/\/ tsconfig.json<\/em>\n{\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@\/*\": &#91;\".\/src\/*\"],\n      \"@interview\/ui\": &#91;\".\/packages\/ui\/src\"]\n    }\n  }\n}<\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-%E5%AE%9E%E7%94%A8%E5%B7%A5%E5%85%B7%E8%84%9A%E6%9C%AC\">3. \u5b9e\u7528\u5de5\u5177\u811a\u672c<\/h3>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em># \u6e05\u7406\u7aef\u53e3\u5360\u7528<\/em>\npnpm kill-ports\n\n<em># \u6e05\u7406\u6570\u636e\u5e93\u8fde\u63a5<\/em>\npnpm clean-connections<\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E9%83%A8%E7%BD%B2%E4%B8%8E%E8%BF%90%E7%BB%B4\">\u90e8\u7f72\u4e0e\u8fd0\u7ef4<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E5%89%8D%E7%AB%AF%E9%83%A8%E7%BD%B2vercel\">\u524d\u7aef\u90e8\u7f72\uff1aVercel<\/h3>\n\n\n\n<p>\u9879\u76ee\u914d\u7f6e\u4e86 Vercel \u81ea\u52a8\u90e8\u7f72\uff1a<\/p>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em>\/\/ vercel.json<\/em>\n{\n  \"buildCommand\": \"pnpm build\",\n  \"installCommand\": \"pnpm install\"\n}<\/code><\/pre><\/div>\n\n\n\n<p><strong>\u90e8\u7f72\u73af\u5883<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Web \u5e94\u7528\uff1a<a href=\"https:\/\/web.erishen.cn\/\">https:\/\/web.erishen.cn<\/a><\/li>\n\n\n\n<li>Admin \u5e94\u7528\uff1a<a href=\"https:\/\/admin.erishen.cn\/\">https:\/\/admin.erishen.cn<\/a><\/li>\n<\/ul>\n\n\n\n<p><strong>\u90e8\u7f72\u7279\u70b9<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 \u81ea\u52a8\u5316 CI\/CD\uff08Git \u63a8\u9001\u81ea\u52a8\u90e8\u7f72\uff09<\/li>\n\n\n\n<li>\u2705 Serverless \u51fd\u6570\uff08\u6309\u9700\u4ed8\u8d39\uff09<\/li>\n\n\n\n<li>\u2705 \u5168\u7403 CDN \u52a0\u901f<\/li>\n\n\n\n<li>\u2705 \u81ea\u52a8 HTTPS \u8bc1\u4e66<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E5%90%8E%E7%AB%AF%E9%83%A8%E7%BD%B2docker--linux\">\u540e\u7aef\u90e8\u7f72\uff1aDocker + Linux<\/h3>\n\n\n\n<p>FastAPI \u540e\u7aef\u91c7\u7528 Docker \u5bb9\u5668\u5316\u90e8\u7f72\u5230 Linux \u670d\u52a1\u5668\uff1a<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># docker-compose.prod.yml\nservices:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    container_name: fastapi-web-app\n    network_mode: host\n    restart: always\n    environment:\n      - APP_ENV=production\n      - PORT=8086\n      - DATABASE_URL=${DATABASE_URL}\n      - REDIS_URL=${REDIS_URL}\n    volumes:\n      - .\/app:\/app\/app\n      - .\/logs:\/app\/logs\n    healthcheck:\n      test: &#91;\"CMD\", \"curl\", \"-f\", \"http:\/\/localhost:8086\/health\"&#93;\n      interval: 30s\n      retries: 3\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\"># docker-compose.prod.yml<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">services:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">  app:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    build:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      context: .<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      dockerfile: Dockerfile<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    container_name: fastapi<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">web<\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\">app<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    network_mode: host<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    restart: always<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    environment:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">APP_ENV<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">production<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">PORT<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">8086<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">DATABASE_URL<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #EE6666; font-style: italic; text-decoration: underline\">$<\/span><span style=\"color: #F6F6F4\">{<\/span><span style=\"color: #BF9EEE\">DATABASE_URL<\/span><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">REDIS_URL<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #EE6666; font-style: italic; text-decoration: underline\">$<\/span><span style=\"color: #F6F6F4\">{<\/span><span style=\"color: #BF9EEE\">REDIS_URL<\/span><span style=\"color: #F6F6F4\">}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    volumes:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> .<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">app:<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">app<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">app<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> .<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">logs:<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">app<\/span><span style=\"color: #F286C4\">\/<\/span><span style=\"color: #F6F6F4\">logs<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    healthcheck:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      test: &#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">CMD<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">curl<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">-f<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">http:\/\/localhost:8086\/health<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      interval: <\/span><span style=\"color: #EE6666; font-style: italic; text-decoration: underline\">30s<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">      retries: <\/span><span style=\"color: #BF9EEE\">3<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u90e8\u7f72\u73af\u5883<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>FastAPI \u670d\u52a1\uff1a<a href=\"https:\/\/api.erishen.cn\/\">https:\/\/api.erishen.cn<\/a><\/li>\n<\/ul>\n\n\n\n<p><strong>\u90e8\u7f72\u7279\u70b9<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 Docker \u5bb9\u5668\u5316\uff08\u73af\u5883\u4e00\u81f4\u6027\uff09<\/li>\n\n\n\n<li>\u2705 \u5bb9\u5668\u81ea\u52a8\u91cd\u542f\uff08<code>restart: always<\/code>\uff09<\/li>\n\n\n\n<li>\u2705 \u5065\u5eb7\u68c0\u67e5\uff08\u81ea\u52a8\u6062\u590d\uff09<\/li>\n\n\n\n<li>\u2705 Nginx \u53cd\u5411\u4ee3\u7406<\/li>\n\n\n\n<li>\u2705 SSL\/TLS \u52a0\u5bc6\u901a\u4fe1<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1\">\u6570\u636e\u5e93\u8bbe\u8ba1<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"mysql-%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93\">MySQL \u5173\u7cfb\u578b\u6570\u636e\u5e93<\/h4>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>-- \u5546\u54c1\u8868\nCREATE TABLE items (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    name VARCHAR(100) NOT NULL,\n    description TEXT,\n    price FLOAT NOT NULL,\n    is_offer INT DEFAULT 0,  -- 0=False, 1=True\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n);\n\n-- \u6587\u6863\u64cd\u4f5c\u65e5\u5fd7\u8868\nCREATE TABLE doc_logs (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    action VARCHAR(50) NOT NULL,  -- \u64cd\u4f5c\u7c7b\u578b: create\/update\/delete\n    doc_slug VARCHAR(100) NOT NULL,\n    user_id VARCHAR(100),\n    user_email VARCHAR(100),\n    user_name VARCHAR(100),\n    auth_method VARCHAR(50),  -- \u8ba4\u8bc1\u65b9\u5f0f: nextauth\/passport\n    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    details TEXT\n);\n\n-- \u7d22\u5f15\u4f18\u5316\nCREATE INDEX idx_items_name ON items(name);\nCREATE INDEX idx_doc_logs_slug ON doc_logs(doc_slug);\nCREATE INDEX idx_doc_logs_user ON doc_logs(user_id);\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\">-- \u5546\u54c1\u8868<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">CREATE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">TABLE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">items<\/span><span style=\"color: #F6F6F4\"> (<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    id <\/span><span style=\"color: #F286C4\">INT<\/span><span style=\"color: #F6F6F4\"> AUTO_INCREMENT <\/span><span style=\"color: #F286C4\">PRIMARY KEY<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">name<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">) <\/span><span style=\"color: #F286C4\">NOT NULL<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">description<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">TEXT<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    price <\/span><span style=\"color: #F286C4\">FLOAT<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">NOT NULL<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    is_offer <\/span><span style=\"color: #F286C4\">INT<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">DEFAULT<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">0<\/span><span style=\"color: #F6F6F4\">,  <\/span><span style=\"color: #7B7F8B\">-- 0=False, 1=True<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    created_at <\/span><span style=\"color: #F286C4\">TIMESTAMP<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">DEFAULT<\/span><span style=\"color: #F6F6F4\"> CURRENT_TIMESTAMP,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    updated_at <\/span><span style=\"color: #F286C4\">TIMESTAMP<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">DEFAULT<\/span><span style=\"color: #F6F6F4\"> CURRENT_TIMESTAMP <\/span><span style=\"color: #F286C4\">ON<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">UPDATE<\/span><span style=\"color: #F6F6F4\"> CURRENT_TIMESTAMP<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">-- \u6587\u6863\u64cd\u4f5c\u65e5\u5fd7\u8868<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">CREATE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">TABLE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">doc_logs<\/span><span style=\"color: #F6F6F4\"> (<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    id <\/span><span style=\"color: #F286C4\">INT<\/span><span style=\"color: #F6F6F4\"> AUTO_INCREMENT <\/span><span style=\"color: #F286C4\">PRIMARY KEY<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">action<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">50<\/span><span style=\"color: #F6F6F4\">) <\/span><span style=\"color: #F286C4\">NOT NULL<\/span><span style=\"color: #F6F6F4\">,  <\/span><span style=\"color: #7B7F8B\">-- \u64cd\u4f5c\u7c7b\u578b: create\/update\/delete<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    doc_slug <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">) <\/span><span style=\"color: #F286C4\">NOT NULL<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    user_id <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    user_email <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    user_name <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">100<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    auth_method <\/span><span style=\"color: #F286C4\">VARCHAR<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE\">50<\/span><span style=\"color: #F6F6F4\">),  <\/span><span style=\"color: #7B7F8B\">-- \u8ba4\u8bc1\u65b9\u5f0f: nextauth\/passport<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">timestamp<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">TIMESTAMP<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">DEFAULT<\/span><span style=\"color: #F6F6F4\"> CURRENT_TIMESTAMP,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    details <\/span><span style=\"color: #F286C4\">TEXT<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #7B7F8B\">-- \u7d22\u5f15\u4f18\u5316<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">CREATE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">INDEX<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">idx_items_name<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">ON<\/span><span style=\"color: #F6F6F4\"> items(<\/span><span style=\"color: #F286C4\">name<\/span><span style=\"color: #F6F6F4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">CREATE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">INDEX<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">idx_doc_logs_slug<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">ON<\/span><span style=\"color: #F6F6F4\"> doc_logs(doc_slug);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">CREATE<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">INDEX<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">idx_doc_logs_user<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">ON<\/span><span style=\"color: #F6F6F4\"> doc_logs(user_id);<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"redis-%E7%BC%93%E5%AD%98%E7%AD%96%E7%95%A5\">Redis \u7f13\u5b58\u7b56\u7565<\/h4>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"\u590d\u5236\" class=\"code-block-pro-copy-button\"><div class=\"code-toolbar\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\" data-toolbar-order=\"copy\" data-prismjs-copy><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># app\/redis_client.py\nfrom redis import Redis\nfrom json import dumps, loads\n\nclass RedisClient:\n    def __init__(self):\n        self.redis = Redis(\n            host=os.getenv(\"REDIS_HOST\", \"localhost\"),\n            port=int(os.getenv(\"REDIS_PORT\", \"6379\")),\n            password=os.getenv(\"REDIS_PASSWORD\"),\n            decode_responses=True\n        )\n\n    # \u7f13\u5b58\u5546\u54c1\u5217\u8868\uff08TTL 5 \u5206\u949f\uff09\n    async def cache_products(self, products: list):\n        await self.redis.setex(\n            \"products:list\",\n            300,\n            dumps(products)\n        )\n\n    # \u7f13\u5b58\u7528\u6237\u4f1a\u8bdd\uff08TTL 30 \u5206\u949f\uff09\n    async def cache_session(self, user_id: int, session: dict):\n        await self.redis.setex(\n            f\"session:{user_id}\",\n            1800,\n            dumps(session)\n        )\n\n    # \u7f13\u5b58\u6587\u6863\u5185\u5bb9\uff08TTL 10 \u5206\u949f\uff09\n    async def cache_doc(self, slug: str, content: str):\n        await self.redis.setex(\n            f\"doc:{slug}\",\n            600,\n            content\n        )\n<\/textarea><\/pre><\/div><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><div class=\"code-toolbar\"><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\" data-toolbar-order=\"copy\" data-prismjs-copy><code><span class=\"line\"><span style=\"color: #7B7F8B\"># app\/redis_client.py<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> redis <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> Redis<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">from<\/span><span style=\"color: #F6F6F4\"> json <\/span><span style=\"color: #F286C4\">import<\/span><span style=\"color: #F6F6F4\"> dumps, loads<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F286C4\">class<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1\">RedisClient<\/span><span style=\"color: #F6F6F4\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">__init__<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">.redis <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> Redis(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #FFB86C; font-style: italic\">host<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">os.getenv(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">REDIS_HOST<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">localhost<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #FFB86C; font-style: italic\">port<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\">(os.getenv(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">REDIS_PORT<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">6379<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #FFB86C; font-style: italic\">password<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">os.getenv(<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">REDIS_PASSWORD<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #FFB86C; font-style: italic\">decode_responses<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">True<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        )<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># \u7f13\u5b58\u5546\u54c1\u5217\u8868\uff08TTL 5 \u5206\u949f\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">cache_products<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">products<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">list<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">.redis.setex(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">products:list<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #BF9EEE\">300<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            dumps(products)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        )<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># \u7f13\u5b58\u7528\u6237\u4f1a\u8bdd\uff08TTL 30 \u5206\u949f\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">cache_session<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">user_id<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">int<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">session<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">dict<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">.redis.setex(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;session:<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">user_id<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #BF9EEE\">1800<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            dumps(session)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        )<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># \u7f13\u5b58\u6587\u6863\u5185\u5bb9\uff08TTL 10 \u5206\u949f\uff09<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">async<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">cache_doc<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">slug<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">content<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #F286C4\">await<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE; font-style: italic\">self<\/span><span style=\"color: #F6F6F4\">.redis.setex(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;doc:<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">slug<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #BF9EEE\">600<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">            content<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F6F6F4\">        )<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div><\/div>\n\n\n\n<p><strong>\u7f13\u5b58\u7b56\u7565<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5546\u54c1\u5217\u8868\uff1a5 \u5206\u949f\uff08\u70ed\u70b9\u6570\u636e\uff09<\/li>\n\n\n\n<li>\u7528\u6237\u4f1a\u8bdd\uff1a30 \u5206\u949f<\/li>\n\n\n\n<li>\u6587\u6863\u5185\u5bb9\uff1a10 \u5206\u949f<\/li>\n\n\n\n<li>\u7cfb\u7edf\u914d\u7f6e\uff1a1 \u5c0f\u65f6<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E5%90%AF%E5%8A%A8\">\u5f00\u53d1\u73af\u5883\u542f\u52a8<\/h3>\n\n\n\n<div class=\"code-toolbar\"><pre class=\"wp-block-code\" data-toolbar-order=\"copy\" data-prismjs-copy><code><em># \u5b89\u88c5\u4f9d\u8d56<\/em>\npnpm install\n\n<em># \u542f\u52a8\u6240\u6709\u5e94\u7528<\/em>\npnpm dev\n\n<em># \u542f\u52a8\u5355\u4e2a\u5e94\u7528<\/em>\npnpm dev --filter=@interview\/web\n\n<em># \u8bbf\u95ee\u6587\u6863\u5217\u8868<\/em>\nopen http:\/\/localhost:3000\/docs<\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E6%94%B6%E7%9B%8A%E4%B8%8E%E5%8F%8D%E6%80%9D\">\u9879\u76ee\u6536\u76ca\u4e0e\u53cd\u601d<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%94%B6%E7%9B%8A\">\u6536\u76ca<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u5168\u6808\u6280\u672f\u6c89\u6dc0<\/strong>\uff1a\u7cfb\u7edf\u68b3\u7406\u4e86\u524d\u540e\u7aef\u77e5\u8bc6\u4f53\u7cfb<\/li>\n\n\n\n<li><strong>\u5de5\u7a0b\u5316\u5b9e\u8df5<\/strong>\uff1a\u638c\u63e1\u4e86 Monorepo + Docker \u5b8c\u6574\u67b6\u6784<\/li>\n\n\n\n<li><strong>\u5b66\u4e60\u6548\u7387<\/strong>\uff1a\u5feb\u901f\u67e5\u627e\u6280\u672f\u76f8\u5173\u77e5\u8bc6\u70b9<\/li>\n\n\n\n<li><strong>\u5206\u4eab\u4ef7\u503c<\/strong>\uff1a\u5e2e\u52a9\u4ed6\u4eba\u5b66\u4e60\u6210\u957f<\/li>\n\n\n\n<li><strong>\u6587\u6863\u7cfb\u7edf<\/strong>\uff1a\u4f18\u96c5\u7684 Markdown \u5c55\u793a\u4f53\u9a8c<\/li>\n\n\n\n<li><strong>\u5b9e\u6218\u7ecf\u9a8c<\/strong>\uff1a\u5b8c\u6210\u5b8c\u6574\u7684\u4ea7\u54c1\u5f00\u53d1\u6d41\u7a0b<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E5%8F%8D%E6%80%9D%E4%B8%8E%E6%94%B9%E8%BF%9B\">\u53cd\u601d\u4e0e\u6539\u8fdb<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6587\u6863\u7ef4\u62a4\u6210\u672c<\/strong>\uff1a\u5185\u5bb9\u66f4\u65b0\u9700\u8981\u6301\u7eed\u6295\u5165<\/li>\n\n\n\n<li><strong>\u6027\u80fd\u4f18\u5316<\/strong>\uff1a\u5927\u6587\u6863\u52a0\u8f7d\u4f53\u9a8c\u6709\u5f85\u4f18\u5316\uff08\u53ef\u8003\u8651\u5206\u5757\u52a0\u8f7d\uff09<\/li>\n\n\n\n<li><strong>\u793e\u533a\u53c2\u4e0e<\/strong>\uff1a\u7f3a\u4e4f\u4e92\u52a8\u673a\u5236\u548c\u7528\u6237\u53cd\u9988<\/li>\n\n\n\n<li><strong>\u641c\u7d22\u529f\u80fd<\/strong>\uff1a\u53ef\u4ee5\u589e\u52a0\u5168\u6587\u641c\u7d22\u548c\u667a\u80fd\u63a8\u8350<\/li>\n\n\n\n<li><strong>\u540e\u7aef\u76d1\u63a7<\/strong>\uff1a\u9700\u8981\u6dfb\u52a0\u6027\u80fd\u76d1\u63a7\u548c\u544a\u8b66\u7cfb\u7edf<\/li>\n\n\n\n<li><strong>\u5bb9\u5668\u7f16\u6392<\/strong>\uff1a\u672a\u6765\u53ef\u8003\u8651 Kubernetes \u96c6\u7fa4\u90e8\u7f72<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E6%9C%AA%E6%9D%A5%E8%A7%84%E5%88%92\">\u672a\u6765\u89c4\u5212<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E5%89%8D%E7%AB%AF%E4%BC%98%E5%8C%96\">\u524d\u7aef\u4f18\u5316<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u6dfb\u52a0\u5168\u6587\u641c\u7d22\u529f\u80fd\uff08ElasticSearch\uff09<\/li>\n\n\n\n<li>[ ] \u652f\u6301\u6587\u6863\u8bc4\u8bba\u548c\u4e92\u52a8<\/li>\n\n\n\n<li>[ ] \u5b9e\u73b0\u6df1\u8272\u6a21\u5f0f<\/li>\n\n\n\n<li>[ ] \u6dfb\u52a0\u9605\u8bfb\u8fdb\u5ea6\u548c\u4e66\u7b7e\u529f\u80fd<\/li>\n\n\n\n<li>[ ] \u652f\u6301\u5bfc\u51fa PDF<\/li>\n\n\n\n<li>[ ] \u6570\u636e\u57cb\u70b9\uff0c\u7528\u6237\u8bbf\u95ee\u4fe1\u606f\u8bb0\u5f55<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E5%90%8E%E7%AB%AF%E4%BC%98%E5%8C%96\">\u540e\u7aef\u4f18\u5316<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] WebSocket \u5b9e\u65f6\u901a\u4fe1\u529f\u80fd\uff08\u8ba1\u5212\u4e2d\uff09<\/li>\n\n\n\n<li>[ ] \u5065\u5eb7\u5ea6\u8bc4\u5206\u7cfb\u7edf\uff08\u8ba1\u5212\u4e2d\uff09<\/li>\n\n\n\n<li>[ ] \u6dfb\u52a0\u6027\u80fd\u76d1\u63a7\uff08Prometheus + Grafana\uff09<\/li>\n\n\n\n<li>[ ] \u5b9e\u73b0\u5206\u5e03\u5f0f\u8ffd\u8e2a\uff08Jaeger\uff09<\/li>\n\n\n\n<li>[ ] \u6dfb\u52a0\u6d88\u606f\u961f\u5217\uff08RabbitMQ\/Celery\uff09<\/li>\n\n\n\n<li>[ ] \u4f18\u5316\u6570\u636e\u5e93\u67e5\u8be2\uff08\u8bfb\u5199\u5206\u79bb\uff09<\/li>\n\n\n\n<li>[ ] \u5b9e\u73b0 GraphQL API\uff08\u8ba1\u5212\u4e2d\uff09<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"%E8%BF%90%E7%BB%B4%E4%BC%98%E5%8C%96\">\u8fd0\u7ef4\u4f18\u5316<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] CI\/CD \u81ea\u52a8\u5316\u6d41\u7a0b\u4f18\u5316<\/li>\n\n\n\n<li>[ ] \u5bb9\u5668\u7f16\u6392\u8fc1\u79fb\u5230 Kubernetes<\/li>\n\n\n\n<li>[ ] \u81ea\u52a8\u5316\u6d4b\u8bd5\u8986\u76d6\u7387\u63d0\u5347<\/li>\n\n\n\n<li>[ ] \u7070\u5ea6\u53d1\u5e03\u548c\u56de\u6eda\u673a\u5236<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E5%B1%80%E9%99%90%E6%80%A7\">\u9879\u76ee\u5c40\u9650\u6027<\/h3>\n\n\n\n<p>\u4e3a\u4e86\u4fdd\u6301\u6587\u7ae0\u7684\u771f\u5b9e\u6027\uff0c\u8fd9\u91cc\u4e5f\u8bf4\u660e\u4e00\u4e0b\u5f53\u524d\u9879\u76ee\u7684<strong>\u5c40\u9650\u6027<\/strong>\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u9879\u76ee\u89c4\u6a21\u8f83\u5c0f<\/strong>\uff1a\u8fd9\u4e0d\u662f\u4e00\u4e2a\u8d85\u5927\u89c4\u6a21\u7684\u7cfb\u7edf\uff0cAPI \u6570\u91cf\u548c\u4e1a\u52a1\u590d\u6742\u5ea6\u6709\u9650<\/li>\n\n\n\n<li><strong>\u7f3a\u5c11\u5355\u5143\u6d4b\u8bd5<\/strong>\uff1a\u5f53\u524d\u9879\u76ee\u8fd8\u6ca1\u6709\u5b8c\u6574\u7684\u5355\u5143\u6d4b\u8bd5\u8986\u76d6<\/li>\n\n\n\n<li><strong>\u672a\u5b9e\u73b0\u6240\u6709\u8ba1\u5212\u529f\u80fd<\/strong>\uff1aWebSocket\u3001\u6027\u80fd\u76d1\u63a7\u7b49\u529f\u80fd\u4ecd\u5728\u89c4\u5212\u4e2d<\/li>\n\n\n\n<li><strong>\u6027\u80fd\u6570\u636e\u7f3a\u5931<\/strong>\uff1a\u6ca1\u6709\u505a\u8fc7\u9ad8\u5e76\u53d1\u538b\u529b\u6d4b\u8bd5\u548c\u6027\u80fd\u57fa\u51c6\u6d4b\u8bd5<\/li>\n\n\n\n<li><strong>\u56e2\u961f\u534f\u4f5c\u573a\u666f\u672a\u9a8c\u8bc1<\/strong>\uff1a\u4e3b\u8981\u662f\u4e2a\u4eba\u9879\u76ee\uff0c\u591a\u56e2\u961f\u534f\u4f5c\u7684\u573a\u666f\u672a\u5145\u5206\u9a8c\u8bc1<\/li>\n<\/ol>\n\n\n\n<p>\u8fd9\u4e2a\u6848\u4f8b\u66f4\u9002\u5408\u4f5c\u4e3a<strong>\u4e2d\u5c0f\u578b\u9879\u76ee\u7684\u67b6\u6784\u53c2\u8003<\/strong>\uff0c\u800c\u4e0d\u662f\u8d85\u5927\u89c4\u6a21\u7cfb\u7edf\u7684\u89e3\u51b3\u65b9\u6848\u3002\u5982\u679c\u4f60\u7684\u9879\u76ee\u89c4\u6a21\u66f4\u5927\uff0c\u9700\u8981\u8003\u8651\u66f4\u590d\u6742\u7684\u67b6\u6784\u8bbe\u8ba1\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E6%80%BB%E7%BB%93\">\u603b\u7ed3<\/h2>\n\n\n\n<p>\u8fd9\u4e2a\u9879\u76ee\u662f\u6211\u7684\u4e00\u4e2a\u5168\u6808\u5f00\u53d1\u5b9e\u8df5\u6848\u4f8b\uff0c\u901a\u8fc7\u6784\u5efa\u6280\u672f\u77e5\u8bc6\u5e93\uff0c\u6211\u603b\u7ed3\u4e86\u4ee5\u4e0b\u51e0\u70b9\u7ecf\u9a8c\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Monorepo \u9002\u5408\u4e2d\u5c0f\u56e2\u961f<\/strong>\uff1a\u4ee3\u7801\u5171\u4eab\u3001\u7edf\u4e00\u4f9d\u8d56\uff0c\u4f46\u5728\u8d85\u5927\u89c4\u6a21\u9879\u76ee\u65f6\u8981\u8c28\u614e\u8bc4\u4f30<\/li>\n\n\n\n<li><strong>\u6280\u672f\u9009\u578b\u8981\u52a1\u5b9e<\/strong>\uff1aNext.js 14 + FastAPI \u5bf9\u4e8e\u4e2d\u5c0f\u578b\u9879\u76ee\u662f\u9ad8\u6548\u7684\u9009\u62e9<\/li>\n\n\n\n<li><strong>\u5de5\u7a0b\u5316\u5f88\u91cd\u8981<\/strong>\uff1aTurborepo\u3001Docker\u3001CI\/CD \u80fd\u663e\u8457\u63d0\u5347\u5f00\u53d1\u6548\u7387\u548c\u90e8\u7f72\u53ef\u9760\u6027<\/li>\n\n\n\n<li><strong>\u4ece\u7b80\u5355\u5f00\u59cb\u8fed\u4ee3<\/strong>\uff1a\u5148\u5b9e\u73b0\u6838\u5fc3\u529f\u80fd\uff0c\u518d\u9010\u6b65\u5b8c\u5584\uff08\u5982\u540e\u7eed\u8ba1\u5212\u6dfb\u52a0\u7684 WebSocket\uff09<\/li>\n\n\n\n<li><strong>\u5b89\u5168\u610f\u8bc6\u8981\u8d2f\u7a7f\u59cb\u7ec8<\/strong>\uff1a\u5373\u4f7f\u5c0f\u578b\u9879\u76ee\uff0c\u4e5f\u8981\u505a\u597d\u57fa\u7840\u5b89\u5168\u9632\u62a4<\/li>\n<\/ul>\n\n\n\n<p>\u9879\u76ee\u5df2\u90e8\u7f72\u5e76\u7a33\u5b9a\u8fd0\u884c\uff0c\u5305\u542b\u4e86\u4ece\u5f00\u53d1\u5230\u90e8\u7f72\u7684\u5b8c\u6574\u6d41\u7a0b\u3002\u901a\u8fc7\u8fd9\u4e2a\u6848\u4f8b\uff0c\u6211\u5e0c\u671b\u5c55\u793a\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5982\u4f55\u7528\u73b0\u4ee3\u5316\u6280\u672f\u6808\u5feb\u901f\u6784\u5efa\u5168\u6808\u5e94\u7528<\/li>\n\n\n\n<li>\u4e2d\u5c0f\u578b\u9879\u76ee\u7684\u67b6\u6784\u8bbe\u8ba1\u601d\u8def<\/li>\n\n\n\n<li>Docker \u5bb9\u5668\u5316\u90e8\u7f72\u7684\u5b9e\u9645\u64cd\u4f5c<\/li>\n<\/ul>\n\n\n\n<p>\u8fd9\u4e2a\u9879\u76ee\u8fd8\u6709\u5f88\u5927\u7684\u6539\u8fdb\u7a7a\u95f4\uff08\u5982\u5355\u5143\u6d4b\u8bd5\u3001\u6027\u80fd\u76d1\u63a7\u3001WebSocket \u529f\u80fd\u7b49\uff09\uff0c\u4f46\u4f5c\u4e3a\u4e00\u4e2a\u53ef\u7528\u7cfb\u7edf\u7684\u5b9e\u73b0\u6848\u4f8b\uff0c\u5e0c\u671b\u80fd\u7ed9\u5176\u4ed6\u5f00\u53d1\u8005\u4e00\u4e9b\u53c2\u8003\u3002\u5982\u679c\u4f60\u5bf9\u9879\u76ee\u6709\u4efb\u4f55\u5efa\u8bae\u6216\u60f3\u6cd5\uff0c\u6b22\u8fce\u4ea4\u6d41\u8ba8\u8bba\uff01<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80\">\u9879\u76ee\u5730\u5740<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u524d\u7aef\u9879\u76ee\uff1a<a href=\"https:\/\/github.com\/erishen\/interview\">https:\/\/github.com\/erishen\/interview<\/a><\/li>\n\n\n\n<li>\u540e\u7aef\u9879\u76ee\uff1a<a href=\"https:\/\/github.com\/erishen\/fastapi-web\">https:\/\/github.com\/erishen\/fastapi-web<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E5%9C%A8%E7%BA%BF%E4%BD%93%E9%AA%8C\">\u5728\u7ebf\u4f53\u9a8c<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83c\udf10 Web \u5e94\u7528\uff1a<a href=\"https:\/\/web.erishen.cn\/\">https:\/\/web.erishen.cn<\/a><\/li>\n\n\n\n<li>\ud83d\udee0\ufe0f \u7ba1\u7406\u540e\u53f0\uff1a<a href=\"https:\/\/admin.erishen.cn\/\">https:\/\/admin.erishen.cn<\/a><\/li>\n\n\n\n<li>\ud83d\udcda \u6587\u6863\u5217\u8868\uff1a<a href=\"https:\/\/web.erishen.cn\/docs\">https:\/\/web.erishen.cn\/docs<\/a><\/li>\n\n\n\n<li>\ud83d\udd0c API \u670d\u52a1\uff1a<a href=\"https:\/\/api.erishen.cn\/\">https:\/\/api.erishen.cn<\/a><\/li>\n\n\n\n<li>\ud83d\udcd6 API \u6587\u6863\uff1a<a href=\"https:\/\/api.erishen.cn\/docs\">https:\/\/api.erishen.cn\/docs<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%E6%8A%80%E6%9C%AF%E6%A0%88\">\u6280\u672f\u6808<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u524d\u7aef\uff1aNext.js 14 + React 18 + TypeScript 5 + Turborepo<\/li>\n\n\n\n<li>\u540e\u7aef\uff1aFastAPI + Python 3.10 + Pydantic v2 + SQLAlchemy<\/li>\n\n\n\n<li>\u6570\u636e\u5e93\uff1aMySQL 8.0 + Redis<\/li>\n\n\n\n<li>\u90e8\u7f72\uff1aVercel + Docker + Nginx<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>\u57fa\u4e8e Turborepo + Next.js 14 + FastAPI \u7684\u5168\u6808\u5f00\u53d1\u5b9e\u8df5\uff0c\u9002\u5408\u4e2d\u5c0f\u578b\u9879\u76ee\u7684\u67b6\u6784\u8bbe [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-180","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/erishen.cn\/index.php?rest_route=\/wp\/v2\/posts\/180","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/erishen.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/erishen.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/erishen.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/erishen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=180"}],"version-history":[{"count":10,"href":"https:\/\/erishen.cn\/index.php?rest_route=\/wp\/v2\/posts\/180\/revisions"}],"predecessor-version":[{"id":199,"href":"https:\/\/erishen.cn\/index.php?rest_route=\/wp\/v2\/posts\/180\/revisions\/199"}],"wp:attachment":[{"href":"https:\/\/erishen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/erishen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/erishen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}