{"id":110,"date":"2025-12-15T10:45:11","date_gmt":"2025-12-15T02:45:11","guid":{"rendered":"https:\/\/zcc609.online\/?p=110"},"modified":"2025-12-15T10:45:11","modified_gmt":"2025-12-15T02:45:11","slug":"%e5%8c%bf%e5%90%8d%e8%ae%ba%e5%9d%9b%e6%8a%80%e6%9c%af%e6%96%87%e6%a1%a3","status":"publish","type":"post","link":"https:\/\/zcc609.online\/?p=110","title":{"rendered":"\u533f\u540d\u8bba\u575b\u6280\u672f\u6587\u6863"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">\u4e00\u3001\u9879\u76ee\u6982\u8ff0<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1.1 \u9879\u76ee\u7b80\u4ecb<\/h3>\n\n\n\n<p>\u533f\u540d\u8bba\u575b\u662f\u4e00\u4e2a\u652f\u6301\u7528\u6237\u6ce8\u518c\u767b\u5f55\u548c\u533f\u540d\u53d1\u5e16\u7684\u793e\u533a\u5e73\u53f0\uff0c\u63d0\u4f9b\u5e16\u5b50\u53d1\u5e03\u3001\u56de\u590d\u3001\u70b9\u8d5e\u3001\u6536\u85cf\u3001\u79c1\u4fe1\u3001\u901a\u77e5\u7b49\u5b8c\u6574\u793e\u4ea4\u529f\u80fd\uff0c\u5e76\u914d\u5907\u7ba1\u7406\u540e\u53f0\u8fdb\u884c\u5185\u5bb9\u5ba1\u6838\u548c\u7528\u6237\u7ba1\u7406\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1.2 \u6838\u5fc3\u7279\u6027<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u53cc\u6a21\u5f0f\u8eab\u4efd<\/strong>\uff1a\u652f\u6301\u6ce8\u518c\u7528\u6237\u548c\u533f\u540d\u8bbf\u5ba2<\/li>\n\n\n\n<li><strong>\u5b8c\u6574\u793e\u4ea4\u529f\u80fd<\/strong>\uff1a\u53d1\u5e16\u3001\u56de\u590d\u3001\u70b9\u8d5e\u3001\u6536\u85cf\u3001\u79c1\u4fe1<\/li>\n\n\n\n<li><strong>\u697c\u4e2d\u697c\u56de\u590d<\/strong>\uff1a\u652f\u6301\u5d4c\u5957\u56de\u590d\u548c@\u63d0\u53ca<\/li>\n\n\n\n<li><strong>\u5b9e\u65f6\u901a\u77e5<\/strong>\uff1a\u65b0\u56de\u590d\u3001\u70b9\u8d5e\u901a\u77e5<\/li>\n\n\n\n<li><strong>\u7ba1\u7406\u540e\u53f0<\/strong>\uff1a\u7528\u6237\/\u5185\u5bb9\u7ba1\u7406\u3001\u64cd\u4f5c\u65e5\u5fd7<\/li>\n\n\n\n<li><strong>\u5b89\u5168\u9632\u62a4<\/strong>\uff1aXSS\u8fc7\u6ee4\u3001\u901f\u7387\u9650\u5236\u3001\u654f\u611f\u8bcd\u8fc7\u6ee4<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e8c\u3001\u6280\u672f\u6808\u8be6\u89e3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 \u524d\u7aef\u6280\u672f\u6808<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6280\u672f<\/th><th>\u7248\u672c<\/th><th>\u7528\u9014<\/th><\/tr><\/thead><tbody><tr><td><strong>React<\/strong><\/td><td>18.3.1<\/td><td>\u6838\u5fc3\u6846\u67b6\uff0c\u51fd\u6570\u7ec4\u4ef6 + Hooks<\/td><\/tr><tr><td><strong>TypeScript<\/strong><\/td><td>&#8211;<\/td><td>\u7c7b\u578b\u5b89\u5168<\/td><\/tr><tr><td><strong>React Router DOM<\/strong><\/td><td>latest<\/td><td>\u5ba2\u6237\u7aef\u8def\u7531\uff0cSPA \u5bfc\u822a<\/td><\/tr><tr><td><strong>Vite<\/strong><\/td><td>6.3.5<\/td><td>\u6784\u5efa\u5de5\u5177\uff0cHMR \u70ed\u66f4\u65b0<\/td><\/tr><tr><td><strong>Tailwind CSS<\/strong><\/td><td>&#8211;<\/td><td>\u539f\u5b50\u5316 CSS \u6846\u67b6<\/td><\/tr><tr><td><strong>Radix UI<\/strong><\/td><td>\u591a\u4e2a\u7ec4\u4ef6<\/td><td>\u65e0\u969c\u788d UI \u539f\u8bed\u7ec4\u4ef6<\/td><\/tr><tr><td><strong>Axios<\/strong><\/td><td>1.13.2<\/td><td>HTTP \u5ba2\u6237\u7aef<\/td><\/tr><tr><td><strong>Sonner<\/strong><\/td><td>2.0.3<\/td><td>Toast \u901a\u77e5<\/td><\/tr><tr><td><strong>Lucide React<\/strong><\/td><td>0.487.0<\/td><td>\u56fe\u6807\u5e93<\/td><\/tr><tr><td><strong>React Markdown<\/strong><\/td><td>latest<\/td><td>Markdown \u6e32\u67d3<\/td><\/tr><tr><td><strong>React Syntax Highlighter<\/strong><\/td><td>latest<\/td><td>\u4ee3\u7801\u9ad8\u4eae<\/td><\/tr><tr><td><strong>date-fns<\/strong><\/td><td>latest<\/td><td>\u65e5\u671f\u5904\u7406<\/td><\/tr><tr><td><strong>next-themes<\/strong><\/td><td>0.4.6<\/td><td>\u4e3b\u9898\u5207\u6362\uff08\u660e\/\u6697\uff09<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2.2 \u540e\u7aef\u6280\u672f\u6808<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6280\u672f<\/th><th>\u7248\u672c<\/th><th>\u7528\u9014<\/th><\/tr><\/thead><tbody><tr><td><strong>Node.js<\/strong><\/td><td>&#8211;<\/td><td>\u8fd0\u884c\u65f6\u73af\u5883<\/td><\/tr><tr><td><strong>Express.js<\/strong><\/td><td>4.18.2<\/td><td>Web \u6846\u67b6<\/td><\/tr><tr><td><strong>TypeScript<\/strong><\/td><td>5.3.3<\/td><td>\u7c7b\u578b\u5b89\u5168<\/td><\/tr><tr><td><strong>MySQL2<\/strong><\/td><td>3.6.5<\/td><td>\u6570\u636e\u5e93\u9a71\u52a8\uff08Promise API\uff09<\/td><\/tr><tr><td><strong>bcryptjs<\/strong><\/td><td>3.0.3<\/td><td>\u5bc6\u7801\u52a0\u5bc6\uff08bcrypt \u7b97\u6cd5\uff09<\/td><\/tr><tr><td><strong>UUID<\/strong><\/td><td>9.0.1<\/td><td>\u552f\u4e00\u6807\u8bc6\u7b26\u751f\u6210<\/td><\/tr><tr><td><strong>Helmet<\/strong><\/td><td>7.1.0<\/td><td>HTTP \u5b89\u5168\u5934<\/td><\/tr><tr><td><strong>CORS<\/strong><\/td><td>2.8.5<\/td><td>\u8de8\u57df\u8d44\u6e90\u5171\u4eab<\/td><\/tr><tr><td><strong>express-rate-limit<\/strong><\/td><td>7.1.5<\/td><td>\u901f\u7387\u9650\u5236<\/td><\/tr><tr><td><strong>express-validator<\/strong><\/td><td>7.0.1<\/td><td>\u8bf7\u6c42\u53c2\u6570\u9a8c\u8bc1<\/td><\/tr><tr><td><strong>XSS<\/strong><\/td><td>1.0.14<\/td><td>XSS \u8fc7\u6ee4<\/td><\/tr><tr><td><strong>Multer<\/strong><\/td><td>1.4.5-lts.1<\/td><td>\u6587\u4ef6\u4e0a\u4f20<\/td><\/tr><tr><td><strong>Sharp<\/strong><\/td><td>0.33.1<\/td><td>\u56fe\u7247\u538b\u7f29\u4f18\u5316<\/td><\/tr><tr><td><strong>Winston<\/strong><\/td><td>3.11.0<\/td><td>\u65e5\u5fd7\u6846\u67b6<\/td><\/tr><tr><td><strong>winston-daily-rotate-file<\/strong><\/td><td>4.7.1<\/td><td>\u65e5\u5fd7\u8f6e\u8f6c<\/td><\/tr><tr><td><strong>Compression<\/strong><\/td><td>1.7.4<\/td><td>Gzip \u538b\u7f29<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2.3 \u6570\u636e\u5e93<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6280\u672f<\/th><th>\u7248\u672c<\/th><th>\u914d\u7f6e<\/th><\/tr><\/thead><tbody><tr><td><strong>MySQL<\/strong><\/td><td>8.x<\/td><td>InnoDB \u5f15\u64ce<\/td><\/tr><tr><td><strong>\u5b57\u7b26\u96c6<\/strong><\/td><td>utf8mb4<\/td><td>\u652f\u6301 emoji<\/td><\/tr><tr><td><strong>\u6392\u5e8f\u89c4\u5219<\/strong><\/td><td>utf8mb4_unicode_ci<\/td><td>Unicode \u6392\u5e8f<\/td><\/tr><tr><td><strong>\u8fde\u63a5\u6c60<\/strong><\/td><td>20 \u8fde\u63a5<\/td><td>mysql2 \u8fde\u63a5\u6c60<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2.4 \u90e8\u7f72\u73af\u5883<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u7ec4\u4ef6<\/th><th>\u914d\u7f6e<\/th><\/tr><\/thead><tbody><tr><td><strong>\u670d\u52a1\u5668<\/strong><\/td><td>\u817e\u8baf\u4e91 CVM (101.32.242.75)<\/td><\/tr><tr><td><strong>\u64cd\u4f5c\u7cfb\u7edf<\/strong><\/td><td>Linux<\/td><\/tr><tr><td><strong>Web \u670d\u52a1\u5668<\/strong><\/td><td>Nginx\uff08\u53cd\u5411\u4ee3\u7406 + \u9759\u6001\u6587\u4ef6\uff09<\/td><\/tr><tr><td><strong>\u8fdb\u7a0b\u7ba1\u7406<\/strong><\/td><td>PM2<\/td><\/tr><tr><td><strong>\u57df\u540d<\/strong><\/td><td>forum.zcc609.online<\/td><\/tr><tr><td><strong>\u524d\u7aef\u8def\u5f84<\/strong><\/td><td>\/home\/anonymous-forum\/frontend<\/td><\/tr><tr><td><strong>\u540e\u7aef\u8def\u5f84<\/strong><\/td><td>\/home\/anonymous-forum\/backend<\/td><\/tr><tr><td><strong>\u540e\u7aef\u7aef\u53e3<\/strong><\/td><td>3001<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e09\u3001\u9879\u76ee\u7ed3\u6784\u8be6\u89e3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 \u5b8c\u6574\u76ee\u5f55\u7ed3\u6784<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\u533f\u540d\u8bba\u575b\/\n\u251c\u2500\u2500 .env.development              # \u5f00\u53d1\u73af\u5883\u53d8\u91cf\n\u251c\u2500\u2500 .env.production               # \u751f\u4ea7\u73af\u5883\u53d8\u91cf\n\u251c\u2500\u2500 .gitignore                    # Git \u5ffd\u7565\u89c4\u5219\n\u251c\u2500\u2500 index.html                    # \u5165\u53e3 HTML\n\u251c\u2500\u2500 package.json                  # \u524d\u7aef\u4f9d\u8d56\n\u251c\u2500\u2500 package-lock.json             # \u4f9d\u8d56\u9501\u5b9a\n\u251c\u2500\u2500 vite.config.ts                # Vite \u914d\u7f6e\n\u251c\u2500\u2500 nginx-forum.conf              # Nginx \u914d\u7f6e\u6a21\u677f\n\u251c\u2500\u2500 deploy-to-server.sh           # \u90e8\u7f72\u811a\u672c\n\u251c\u2500\u2500 server-start.sh               # \u670d\u52a1\u542f\u52a8\u811a\u672c\n\u251c\u2500\u2500 \u4e0a\u4f20\u5230\u670d\u52a1\u5668.sh                # \u4e0a\u4f20\u811a\u672c\n\u251c\u2500\u2500 \u90e8\u7f72\u6587\u6863.md                    # \u90e8\u7f72\u8bf4\u660e\n\u2502\n\u251c\u2500\u2500 src\/                          # \u524d\u7aef\u6e90\u7801\n\u2502   \u251c\u2500\u2500 main.tsx                  # \u5e94\u7528\u5165\u53e3\n\u2502   \u251c\u2500\u2500 App.tsx                   # \u6839\u7ec4\u4ef6 + \u8def\u7531\u914d\u7f6e\n\u2502   \u251c\u2500\u2500 index.css                 # Tailwind \u6837\u5f0f\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 components\/               # \u901a\u7528\u7ec4\u4ef6 (25\u4e2a)\n\u2502   \u2502   \u251c\u2500\u2500 ForumHeader.tsx       # \u9876\u90e8\u5bfc\u822a\u680f\n\u2502   \u2502   \u251c\u2500\u2500 ForumSidebar.tsx      # \u4fa7\u8fb9\u680f\uff08\u5206\u7c7b\u3001\u70ed\u5e16\uff09\n\u2502   \u2502   \u251c\u2500\u2500 MobileBottomNav.tsx   # \u79fb\u52a8\u7aef\u5e95\u90e8\u5bfc\u822a\n\u2502   \u2502   \u251c\u2500\u2500 MobileSidebar.tsx     # \u79fb\u52a8\u7aef\u4fa7\u8fb9\u680f\n\u2502   \u2502   \u251c\u2500\u2500 PostCard.tsx          # \u5e16\u5b50\u5361\u7247\n\u2502   \u2502   \u251c\u2500\u2500 PostCardSkeleton.tsx  # \u5e16\u5b50\u9aa8\u67b6\u5c4f\n\u2502   \u2502   \u251c\u2500\u2500 ReplyItem.tsx         # \u56de\u590d\u9879\uff08\u652f\u6301\u697c\u4e2d\u697c\uff09\n\u2502   \u2502   \u251c\u2500\u2500 CreatePostDialog.tsx  # \u53d1\u5e16\u5bf9\u8bdd\u6846\n\u2502   \u2502   \u251c\u2500\u2500 MarkdownEditor.tsx    # Markdown \u7f16\u8f91\u5668\n\u2502   \u2502   \u251c\u2500\u2500 EmojiPicker.tsx       # \u8868\u60c5\u9009\u62e9\u5668\n\u2502   \u2502   \u251c\u2500\u2500 ImageUploader.tsx     # \u56fe\u7247\u4e0a\u4f20\u7ec4\u4ef6\n\u2502   \u2502   \u251c\u2500\u2500 ImageViewer.tsx       # \u56fe\u7247\u67e5\u770b\u5668\n\u2502   \u2502   \u251c\u2500\u2500 ImageGallery.tsx      # \u56fe\u7247\u753b\u5eca\n\u2502   \u2502   \u251c\u2500\u2500 NotificationDropdown.tsx  # \u901a\u77e5\u4e0b\u62c9\n\u2502   \u2502   \u251c\u2500\u2500 MessageDropdown.tsx   # \u6d88\u606f\u4e0b\u62c9\n\u2502   \u2502   \u251c\u2500\u2500 UserCard.tsx          # \u7528\u6237\u4fe1\u606f\u5361\u7247\n\u2502   \u2502   \u251c\u2500\u2500 ThemeToggle.tsx       # \u4e3b\u9898\u5207\u6362\n\u2502   \u2502   \u251c\u2500\u2500 SortSelector.tsx      # \u6392\u5e8f\u9009\u62e9\u5668\n\u2502   \u2502   \u251c\u2500\u2500 EmptyState.tsx        # \u7a7a\u72b6\u6001\n\u2502   \u2502   \u251c\u2500\u2500 LoadMoreIndicator.tsx # \u52a0\u8f7d\u66f4\u591a\u6307\u793a\u5668\n\u2502   \u2502   \u251c\u2500\u2500 ErrorBoundary.tsx     # \u9519\u8bef\u8fb9\u754c\n\u2502   \u2502   \u251c\u2500\u2500 ProtectedRoute.tsx    # \u8def\u7531\u5b88\u536b\n\u2502   \u2502   \u2514\u2500\u2500 KeyboardShortcutsDialog.tsx  # \u5feb\u6377\u952e\u5e2e\u52a9\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 pages\/                    # \u9875\u9762\u7ec4\u4ef6 (15\u4e2a)\n\u2502   \u2502   \u251c\u2500\u2500 PostListPage.tsx      # \u5e16\u5b50\u5217\u8868\uff08\u9996\u9875\uff09\n\u2502   \u2502   \u251c\u2500\u2500 PostDetailPage.tsx    # \u5e16\u5b50\u8be6\u60c5\n\u2502   \u2502   \u251c\u2500\u2500 SearchResultPage.tsx  # \u641c\u7d22\u7ed3\u679c\n\u2502   \u2502   \u251c\u2500\u2500 LoginPage.tsx         # \u767b\u5f55\u9875\n\u2502   \u2502   \u251c\u2500\u2500 RegisterPage.tsx      # \u6ce8\u518c\u9875\n\u2502   \u2502   \u251c\u2500\u2500 ProfilePage.tsx       # \u4e2a\u4eba\u8d44\u6599\n\u2502   \u2502   \u251c\u2500\u2500 MyPostsPage.tsx       # \u6211\u7684\u5e16\u5b50\n\u2502   \u2502   \u251c\u2500\u2500 MyRepliesPage.tsx     # \u6211\u7684\u56de\u590d\n\u2502   \u2502   \u251c\u2500\u2500 MyFavoritesPage.tsx   # \u6211\u7684\u6536\u85cf\n\u2502   \u2502   \u251c\u2500\u2500 MyLikesPage.tsx       # \u6211\u7684\u70b9\u8d5e\n\u2502   \u2502   \u251c\u2500\u2500 DraftsPage.tsx        # \u8349\u7a3f\u7bb1\n\u2502   \u2502   \u251c\u2500\u2500 Messages.tsx          # \u79c1\u4fe1\u5217\u8868\n\u2502   \u2502   \u251c\u2500\u2500 MessageConversation.tsx  # \u79c1\u4fe1\u5bf9\u8bdd\n\u2502   \u2502   \u251c\u2500\u2500 NotificationPage.tsx  # \u901a\u77e5\u9875\u9762\n\u2502   \u2502   \u2514\u2500\u2500 admin\/                # \u7ba1\u7406\u540e\u53f0\n\u2502   \u2502       \u251c\u2500\u2500 AdminDashboard.tsx    # \u7ba1\u7406\u4eea\u8868\u76d8\n\u2502   \u2502       \u251c\u2500\u2500 AdminUsers.tsx        # \u7528\u6237\u7ba1\u7406\n\u2502   \u2502       \u251c\u2500\u2500 AdminPosts.tsx        # \u5e16\u5b50\u7ba1\u7406\n\u2502   \u2502       \u2514\u2500\u2500 AdminLogs.tsx         # \u64cd\u4f5c\u65e5\u5fd7\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 hooks\/                    # \u81ea\u5b9a\u4e49 Hooks\n\u2502   \u2502   \u251c\u2500\u2500 useInfiniteScroll.ts  # \u65e0\u9650\u6eda\u52a8\n\u2502   \u2502   \u2514\u2500\u2500 useKeyboardShortcuts.ts  # \u5feb\u6377\u952e\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 lib\/                      # \u5de5\u5177\u5e93\n\u2502   \u2502   \u251c\u2500\u2500 api.ts                # API \u5c01\u88c5\uff08\u5e16\u5b50\u3001\u56de\u590d\u3001\u4e92\u52a8\uff09\n\u2502   \u2502   \u251c\u2500\u2500 apiClient.ts          # Axios \u5b9e\u4f8b\u914d\u7f6e\n\u2502   \u2502   \u251c\u2500\u2500 auth.ts               # \u8ba4\u8bc1\u5de5\u5177\uff08\u767b\u5f55\u72b6\u6001\u7ba1\u7406\uff09\n\u2502   \u2502   \u251c\u2500\u2500 messageApi.ts         # \u79c1\u4fe1 API\n\u2502   \u2502   \u251c\u2500\u2500 notificationApi.ts    # \u901a\u77e5 API\n\u2502   \u2502   \u251c\u2500\u2500 storage.ts            # \u672c\u5730\u5b58\u50a8\u5de5\u5177\n\u2502   \u2502   \u251c\u2500\u2500 constants.ts          # \u5e38\u91cf\u5b9a\u4e49\n\u2502   \u2502   \u251c\u2500\u2500 utils.ts              # \u901a\u7528\u5de5\u5177\u51fd\u6570\n\u2502   \u2502   \u251c\u2500\u2500 portal.ts             # Portal \u5bb9\u5668\n\u2502   \u2502   \u2514\u2500\u2500 mockData.ts           # \u6a21\u62df\u6570\u636e\uff08\u5f00\u53d1\u7528\uff09\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 types\/                    # \u7c7b\u578b\u5b9a\u4e49\n\u2502   \u2502   \u251c\u2500\u2500 index.ts              # \u4e1a\u52a1\u7c7b\u578b\uff08Post, Reply \u7b49\uff09\n\u2502   \u2502   \u2514\u2500\u2500 auth.ts               # \u8ba4\u8bc1\u7c7b\u578b\n\u2502   \u2502\n\u2502   \u2514\u2500\u2500 styles\/                   # \u6837\u5f0f\n\u2502       \u2514\u2500\u2500 globals.css           # \u5168\u5c40\u6837\u5f0f\n\u2502\n\u251c\u2500\u2500 backend\/                      # \u540e\u7aef\u6e90\u7801\n\u2502   \u251c\u2500\u2500 .env                      # \u5f00\u53d1\u73af\u5883\u53d8\u91cf\n\u2502   \u251c\u2500\u2500 .env.production           # \u751f\u4ea7\u73af\u5883\u53d8\u91cf\n\u2502   \u251c\u2500\u2500 package.json              # \u540e\u7aef\u4f9d\u8d56\n\u2502   \u251c\u2500\u2500 tsconfig.json             # TypeScript \u914d\u7f6e\n\u2502   \u251c\u2500\u2500 backup-db.sh              # \u6570\u636e\u5e93\u5907\u4efd\u811a\u672c\n\u2502   \u251c\u2500\u2500 repair-db.sh              # \u6570\u636e\u5e93\u4fee\u590d\u811a\u672c\n\u2502   \u251c\u2500\u2500 reset-admin.sh            # \u7ba1\u7406\u5458\u5bc6\u7801\u91cd\u7f6e\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 src\/\n\u2502   \u2502   \u251c\u2500\u2500 index.ts              # \u5e94\u7528\u5165\u53e3\n\u2502   \u2502   \u2502\n\u2502   \u2502   \u251c\u2500\u2500 config\/               # \u914d\u7f6e\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 database.ts       # \u6570\u636e\u5e93\u8fde\u63a5\u6c60\n\u2502   \u2502   \u2502   \u2514\u2500\u2500 logger.ts         # Winston \u65e5\u5fd7\u914d\u7f6e\n\u2502   \u2502   \u2502\n\u2502   \u2502   \u251c\u2500\u2500 middleware\/           # \u4e2d\u95f4\u4ef6\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 auth.ts           # \u8ba4\u8bc1\u4e2d\u95f4\u4ef6\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 authenticate()     # \u5f3a\u5236\u767b\u5f55\u9a8c\u8bc1\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 optionalAuth()     # \u53ef\u9009\u767b\u5f55\u9a8c\u8bc1\n\u2502   \u2502   \u2502   \u2502   \u2514\u2500\u2500 requireAdmin()     # \u7ba1\u7406\u5458\u6743\u9650\u9a8c\u8bc1\n\u2502   \u2502   \u2502   \u2502\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 security.ts       # \u5b89\u5168\u4e2d\u95f4\u4ef6\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 sanitizeInput()    # XSS \u8fc7\u6ee4\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 generalLimiter     # \u901a\u7528\u901f\u7387\u9650\u5236\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 createLimiter      # \u521b\u5efa\u5185\u5bb9\u9650\u5236\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 uploadLimiter      # \u4e0a\u4f20\u9650\u5236\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 filterSensitiveWords()  # \u654f\u611f\u8bcd\u8fc7\u6ee4\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 logOperation()     # \u64cd\u4f5c\u65e5\u5fd7\n\u2502   \u2502   \u2502   \u2502   \u251c\u2500\u2500 validateId()       # ID \u683c\u5f0f\u9a8c\u8bc1\n\u2502   \u2502   \u2502   \u2502   \u2514\u2500\u2500 errorHandler()     # \u5168\u5c40\u9519\u8bef\u5904\u7406\n\u2502   \u2502   \u2502   \u2502\n\u2502   \u2502   \u2502   \u2514\u2500\u2500 validation.ts     # \u53c2\u6570\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u251c\u2500\u2500 validateCreatePost     # \u521b\u5efa\u5e16\u5b50\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u251c\u2500\u2500 validateCreateReply    # \u521b\u5efa\u56de\u590d\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u251c\u2500\u2500 validateGetPosts       # \u83b7\u53d6\u5e16\u5b50\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u251c\u2500\u2500 validateSearch         # \u641c\u7d22\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u251c\u2500\u2500 validateToggleLike     # \u70b9\u8d5e\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u251c\u2500\u2500 validateSendMessage    # \u79c1\u4fe1\u9a8c\u8bc1\n\u2502   \u2502   \u2502       \u2514\u2500\u2500 validateSaveDraft      # \u8349\u7a3f\u9a8c\u8bc1\n\u2502   \u2502   \u2502\n\u2502   \u2502   \u251c\u2500\u2500 controllers\/          # \u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 authController.ts      # \u8ba4\u8bc1\u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 postController.ts      # \u5e16\u5b50\u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 replyController.ts     # \u56de\u590d\u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 userController.ts      # \u7528\u6237\u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 interactionController.ts  # \u4e92\u52a8\u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 uploadController.ts    # \u4e0a\u4f20\u63a7\u5236\u5668\n\u2502   \u2502   \u2502   \u2514\u2500\u2500 adminController.ts     # \u7ba1\u7406\u63a7\u5236\u5668\n\u2502   \u2502   \u2502\n\u2502   \u2502   \u251c\u2500\u2500 routes\/               # \u8def\u7531\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 index.ts          # \u8def\u7531\u6c47\u603b\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 auth.ts           # \u8ba4\u8bc1\u8def\u7531\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 posts.ts          # \u5e16\u5b50\u8def\u7531\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 replies.ts        # \u56de\u590d\u8def\u7531\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 users.ts          # \u7528\u6237\u8def\u7531\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 interactions.ts   # \u4e92\u52a8\u8def\u7531\n\u2502   \u2502   \u2502   \u251c\u2500\u2500 upload.ts         # \u4e0a\u4f20\u8def\u7531\n\u2502   \u2502   \u2502   \u2514\u2500\u2500 admin.ts          # \u7ba1\u7406\u8def\u7531\n\u2502   \u2502   \u2502\n\u2502   \u2502   \u2514\u2500\u2500 types\/                # \u7c7b\u578b\u5b9a\u4e49\n\u2502   \u2502       \u251c\u2500\u2500 auth.ts           # \u8ba4\u8bc1\u7c7b\u578b\n\u2502   \u2502       \u2514\u2500\u2500 express.d.ts      # Express \u7c7b\u578b\u6269\u5c55\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 database\/                 # SQL \u811a\u672c\n\u2502   \u2502   \u251c\u2500\u2500 schema.sql            # \u4e3b\u8868\u7ed3\u6784\n\u2502   \u2502   \u251c\u2500\u2500 auth_migration.sql    # \u8ba4\u8bc1\u76f8\u5173\u8868\n\u2502   \u2502   \u251c\u2500\u2500 add_login_fields.sql  # \u767b\u5f55\u5b57\u6bb5\u8fc1\u79fb\n\u2502   \u2502   \u2514\u2500\u2500 add_profile_fields.sql  # \u4e2a\u4eba\u8d44\u6599\u5b57\u6bb5\n\u2502   \u2502\n\u2502   \u251c\u2500\u2500 dist\/                     # \u7f16\u8bd1\u8f93\u51fa\n\u2502   \u251c\u2500\u2500 uploads\/                  # \u4e0a\u4f20\u6587\u4ef6\u5b58\u50a8\n\u2502   \u2514\u2500\u2500 logs\/                     # \u65e5\u5fd7\u6587\u4ef6\n\u2502\n\u251c\u2500\u2500 build\/                        # \u524d\u7aef\u6784\u5efa\u4ea7\u7269\n\u2502   \u251c\u2500\u2500 index.html\n\u2502   \u2514\u2500\u2500 assets\/\n\u2502       \u251c\u2500\u2500 index-*.css\n\u2502       \u2514\u2500\u2500 index-*.js\n\u2502\n\u2514\u2500\u2500 scripts\/                      # \u90e8\u7f72\u811a\u672c\n    \u251c\u2500\u2500 start-services.sh         # \u542f\u52a8\u670d\u52a1\n    \u2514\u2500\u2500 stop-services.sh          # \u505c\u6b62\u670d\u52a1\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u56db\u3001\u529f\u80fd\u6a21\u5757\u8be6\u89e3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">4.1 \u7528\u6237\u8ba4\u8bc1\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u529f\u80fd\u5217\u8868<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u529f\u80fd<\/th><th>\u63a5\u53e3<\/th><th>\u8bf4\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u6ce8\u518c<\/td><td>POST \/api\/auth\/register<\/td><td>\u7528\u6237\u540d+\u5bc6\u7801+\u90ae\u7bb1(\u53ef\u9009)<\/td><\/tr><tr><td>\u767b\u5f55<\/td><td>POST \/api\/auth\/login<\/td><td>\u8fd4\u56de Session Token<\/td><\/tr><tr><td>\u767b\u51fa<\/td><td>POST \/api\/auth\/logout<\/td><td>\u5220\u9664\u4f1a\u8bdd<\/td><\/tr><tr><td>\u83b7\u53d6\u5f53\u524d\u7528\u6237<\/td><td>GET \/api\/auth\/me<\/td><td>\u8fd4\u56de\u7528\u6237\u4fe1\u606f<\/td><\/tr><tr><td>\u4fee\u6539\u5bc6\u7801<\/td><td>POST \/api\/auth\/change-password<\/td><td>\u9700\u9a8c\u8bc1\u65e7\u5bc6\u7801<\/td><\/tr><tr><td>\u533f\u540d\u6a21\u5f0f<\/td><td>POST \/api\/auth\/anonymous<\/td><td>\u751f\u6210\u4e34\u65f6\u533f\u540dID<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u8ba4\u8bc1\u6d41\u7a0b<\/h4>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n1. \u7528\u6237\u6ce8\u518c\n   \u251c\u2500\u2500 \u9a8c\u8bc1\u7528\u6237\u540d\u957f\u5ea6(3-20)\u3001\u5bc6\u7801\u957f\u5ea6(&gt;=6)\n   \u251c\u2500\u2500 \u68c0\u67e5\u7528\u6237\u540d\/\u90ae\u7bb1\u552f\u4e00\u6027\n   \u251c\u2500\u2500 bcrypt \u52a0\u5bc6\u5bc6\u7801 (cost=10)\n   \u251c\u2500\u2500 \u751f\u6210 UUID \u7528\u6237ID\n   \u251c\u2500\u2500 \u751f\u6210\u533f\u540d\u6635\u79f0 &quot;\u7528\u6237_${username}&quot;\n   \u251c\u2500\u2500 \u63d2\u5165 users \u8868\n   \u251c\u2500\u2500 \u521b\u5efa session (30\u5929\u6709\u6548\u671f)\n   \u2514\u2500\u2500 \u8fd4\u56de token + \u7528\u6237\u4fe1\u606f\n\n2. \u7528\u6237\u767b\u5f55\n   \u251c\u2500\u2500 \u67e5\u8be2\u7528\u6237\n   \u251c\u2500\u2500 \u68c0\u67e5\u8d26\u53f7\u6fc0\u6d3b\u72b6\u6001\n   \u251c\u2500\u2500 bcrypt \u6bd4\u5bf9\u5bc6\u7801\n   \u251c\u2500\u2500 \u66f4\u65b0\u767b\u5f55\u4fe1\u606f (login_count, last_login_at, last_login_ip)\n   \u251c\u2500\u2500 \u521b\u5efa\u65b0 session\n   \u2514\u2500\u2500 \u8fd4\u56de token + \u7528\u6237\u4fe1\u606f\n\n3. \u8bf7\u6c42\u9274\u6743 (authenticate \u4e2d\u95f4\u4ef6)\n   \u251c\u2500\u2500 \u4ece Authorization Header \u63d0\u53d6 Bearer Token\n   \u251c\u2500\u2500 \u67e5\u8be2 sessions \u8868 (JOIN users)\n   \u251c\u2500\u2500 \u9a8c\u8bc1 token \u6709\u6548\u6027\u548c\u8fc7\u671f\u65f6\u95f4\n   \u251c\u2500\u2500 \u68c0\u67e5\u8d26\u53f7\u6fc0\u6d3b\u72b6\u6001\n   \u251c\u2500\u2500 \u6ce8\u5165 req.user, req.userId, req.anonymousName\n   \u2514\u2500\u2500 \u8c03\u7528 next()\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">Session \u8868\u7ed3\u6784<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE TABLE sessions (\n    id VARCHAR(36) PRIMARY KEY,\n    user_id VARCHAR(36) NOT NULL,\n    token VARCHAR(255) NOT NULL UNIQUE,\n    expires_at TIMESTAMP NOT NULL,\n    user_agent VARCHAR(500),\n    ip_address VARCHAR(45),\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.2 \u5e16\u5b50\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u529f\u80fd\u5217\u8868<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u529f\u80fd<\/th><th>\u63a5\u53e3<\/th><th>\u8bf4\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u83b7\u53d6\u5e16\u5b50\u5217\u8868<\/td><td>GET \/api\/posts<\/td><td>\u652f\u6301\u5206\u7c7b\u3001\u6392\u5e8f\u3001\u5206\u9875<\/td><\/tr><tr><td>\u83b7\u53d6\u5e16\u5b50\u8be6\u60c5<\/td><td>GET \/api\/posts\/:id<\/td><td>\u81ea\u52a8\u589e\u52a0\u6d4f\u89c8\u91cf<\/td><\/tr><tr><td>\u521b\u5efa\u5e16\u5b50<\/td><td>POST \/api\/posts<\/td><td>\u9700\u767b\u5f55<\/td><\/tr><tr><td>\u5220\u9664\u5e16\u5b50<\/td><td>DELETE \/api\/posts\/:id<\/td><td>\u4f5c\u8005\u6216\u7ba1\u7406\u5458<\/td><\/tr><tr><td>\u641c\u7d22\u5e16\u5b50<\/td><td>GET \/api\/posts\/search<\/td><td>\u5168\u6587\u641c\u7d22<\/td><\/tr><tr><td>\u70ed\u95e8\u5e16\u5b50<\/td><td>GET \/api\/posts\/hot<\/td><td>\u6309\u70ed\u5ea6\u7b97\u6cd5\u6392\u5e8f<\/td><\/tr><tr><td>\u6211\u7684\u5e16\u5b50<\/td><td>GET \/api\/posts\/user\/my-posts<\/td><td>\u5f53\u524d\u7528\u6237\u7684\u5e16\u5b50<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u5e16\u5b50\u5206\u7c7b<\/h4>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ntype PostCategory = 'tech' | 'life' | 'help' | 'other';\n\n\/\/ \u5206\u7c7b\u6620\u5c04\nconst CATEGORIES = {\n  tech: '\u6280\u672f\u4ea4\u6d41',\n  life: '\u751f\u6d3b\u6742\u8c08',\n  help: '\u6c42\u52a9\u95ee\u7b54',\n  other: '\u5176\u4ed6'\n};\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">\u6392\u5e8f\u65b9\u5f0f<\/h4>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ntype SortType = 'latest' | 'most_replies' | 'recent_reply';\n\n\/\/ \u6392\u5e8f SQL\nswitch (sort) {\n  case 'most_replies':\n    ORDER BY reply_count DESC, created_at DESC\n  case 'recent_reply':\n    ORDER BY COALESCE(last_reply_at, created_at) DESC\n  case 'latest':\n  default:\n    ORDER BY created_at DESC\n}\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">\u70ed\u5ea6\u7b97\u6cd5<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nhot_score = reply_count * 0.7 + like_count * 0.3\n-- \u53d6\u6700\u8fd1 24 \u5c0f\u65f6\u5185\u7684\u5e16\u5b50\uff0c\u6309 hot_score \u964d\u5e8f\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.3 \u56de\u590d\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u529f\u80fd\u5217\u8868<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u529f\u80fd<\/th><th>\u63a5\u53e3<\/th><th>\u8bf4\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u83b7\u53d6\u56de\u590d\u5217\u8868<\/td><td>GET \/api\/replies\/post\/:postId<\/td><td>\u6309\u65f6\u95f4\u5347\u5e8f<\/td><\/tr><tr><td>\u521b\u5efa\u56de\u590d<\/td><td>POST \/api\/replies\/post\/:postId<\/td><td>\u652f\u6301\u697c\u4e2d\u697c<\/td><\/tr><tr><td>\u5220\u9664\u56de\u590d<\/td><td>DELETE \/api\/replies\/:id<\/td><td>\u4f5c\u8005\u6216\u7ba1\u7406\u5458<\/td><\/tr><tr><td>\u6211\u7684\u56de\u590d<\/td><td>GET \/api\/replies\/user\/my-replies<\/td><td>\u5305\u542b\u5e16\u5b50\u4fe1\u606f<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u697c\u4e2d\u697c\u56de\u590d<\/h4>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ninterface Reply {\n  id: string;\n  post_id: string;\n  user_id: string;\n  content: string;\n  parent_reply_id: string | null;    \/\/ \u7236\u56de\u590dID\n  parent_floor_number: number | null; \/\/ \u7236\u56de\u590d\u697c\u5c42\n  parent_username: string | null;     \/\/ \u88ab\u56de\u590d\u7528\u6237\u540d\n  created_at: Date;\n}\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">\u56de\u590d\u901a\u77e5\u903b\u8f91<\/h4>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\u521b\u5efa\u56de\u590d\u65f6:\n\u251c\u2500\u2500 \u5982\u679c\u662f\u697c\u4e2d\u697c (\u6709 parent_reply_id)\n\u2502   \u2514\u2500\u2500 \u7ed9\u88ab\u56de\u590d\u7528\u6237\u53d1\u9001\u901a\u77e5\n\u2514\u2500\u2500 \u5982\u679c\u662f\u76f4\u63a5\u56de\u590d\n    \u2514\u2500\u2500 \u7ed9\u5e16\u5b50\u4f5c\u8005\u53d1\u9001\u901a\u77e5\n\n\u901a\u77e5\u5185\u5bb9: &quot;${\u56de\u590d\u8005\u533f\u540d\u540d} \u56de\u590d\u4e86\u4f60\u7684${\u5e16\u5b50\/\u8bc4\u8bba}&quot;\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.4 \u4e92\u52a8\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u529f\u80fd\u5217\u8868<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u529f\u80fd<\/th><th>\u63a5\u53e3<\/th><th>\u8bf4\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u70b9\u8d5e\/\u53d6\u6d88\u70b9\u8d5e<\/td><td>POST \/api\/interactions\/like\/:id<\/td><td>type: post\/reply<\/td><\/tr><tr><td>\u6536\u85cf\/\u53d6\u6d88\u6536\u85cf<\/td><td>POST \/api\/interactions\/favorite\/:postId<\/td><td>\u5e16\u5b50\u6536\u85cf<\/td><\/tr><tr><td>\u83b7\u53d6\u6536\u85cf\u5217\u8868<\/td><td>GET \/api\/interactions\/favorites<\/td><td>\u5f53\u524d\u7528\u6237<\/td><\/tr><tr><td>\u83b7\u53d6\u70b9\u8d5e\u5e16\u5b50<\/td><td>GET \/api\/interactions\/liked-posts<\/td><td>\u5f53\u524d\u7528\u6237<\/td><\/tr><tr><td>\u83b7\u53d6\u83b7\u8d5e\u6570<\/td><td>GET \/api\/interactions\/user-likes<\/td><td>\u6536\u5230\u7684\u603b\u8d5e\u6570<\/td><\/tr><tr><td>\u53d1\u9001\u79c1\u4fe1<\/td><td>POST \/api\/interactions\/messages<\/td><td>\u7528\u6237\u95f4\u79c1\u4fe1<\/td><\/tr><tr><td>\u83b7\u53d6\u5bf9\u8bdd\u5217\u8868<\/td><td>GET \/api\/interactions\/conversations<\/td><td>\u79c1\u4fe1\u4f1a\u8bdd<\/td><\/tr><tr><td>\u83b7\u53d6\u5bf9\u8bdd\u8be6\u60c5<\/td><td>GET \/api\/interactions\/conversation\/:userId<\/td><td>\u4e0e\u67d0\u7528\u6237\u7684\u5bf9\u8bdd<\/td><\/tr><tr><td>\u672a\u8bfb\u6d88\u606f\u6570<\/td><td>GET \/api\/interactions\/unread-count<\/td><td>\u672a\u8bfb\u79c1\u4fe1\u6570<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u70b9\u8d5e\u903b\u8f91<\/h4>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\u70b9\u8d5e\u5207\u6362:\n\u251c\u2500\u2500 \u67e5\u8be2\u662f\u5426\u5df2\u70b9\u8d5e\n\u251c\u2500\u2500 \u5982\u679c\u5df2\u70b9\u8d5e\n\u2502   \u251c\u2500\u2500 \u5220\u9664 likes \u8bb0\u5f55\n\u2502   \u2514\u2500\u2500 \u66f4\u65b0 posts.like_count - 1\n\u2514\u2500\u2500 \u5982\u679c\u672a\u70b9\u8d5e\n    \u251c\u2500\u2500 \u63d2\u5165 likes \u8bb0\u5f55\n    \u251c\u2500\u2500 \u66f4\u65b0 posts.like_count + 1\n    \u2514\u2500\u2500 \u53d1\u9001\u70b9\u8d5e\u901a\u77e5\u7ed9\u4f5c\u8005\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.5 \u901a\u77e5\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u901a\u77e5\u7c7b\u578b<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ntype ENUM('new_reply', 'like', 'system')\n\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u7c7b\u578b<\/th><th>\u89e6\u53d1\u573a\u666f<\/th><\/tr><\/thead><tbody><tr><td>new_reply<\/td><td>\u5e16\u5b50\/\u56de\u590d\u88ab\u56de\u590d<\/td><\/tr><tr><td>like<\/td><td>\u5e16\u5b50\/\u56de\u590d\u88ab\u70b9\u8d5e<\/td><\/tr><tr><td>system<\/td><td>\u7cfb\u7edf\u901a\u77e5<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u901a\u77e5\u8868\u7ed3\u6784<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE TABLE notifications (\n    id VARCHAR(36) PRIMARY KEY,\n    user_id VARCHAR(36) NOT NULL,\n    type ENUM('new_reply', 'like', 'system') NOT NULL,\n    title VARCHAR(100) NOT NULL,\n    content VARCHAR(500) NOT NULL,\n    post_id VARCHAR(36) NULL,        -- \u5173\u8054\u5e16\u5b50\uff08\u53ef\u70b9\u51fb\u8df3\u8f6c\uff09\n    is_read TINYINT(1) DEFAULT 0,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.6 \u7ba1\u7406\u540e\u53f0\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u529f\u80fd\u5217\u8868<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u529f\u80fd<\/th><th>\u63a5\u53e3<\/th><th>\u8bf4\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u83b7\u53d6\u7528\u6237\u5217\u8868<\/td><td>GET \/api\/admin\/users<\/td><td>\u5206\u9875\u3001\u641c\u7d22<\/td><\/tr><tr><td>\u542f\u7528\/\u7981\u7528\u7528\u6237<\/td><td>PATCH \/api\/admin\/users\/:userId\/status<\/td><td>\u5207\u6362\u6fc0\u6d3b\u72b6\u6001<\/td><\/tr><tr><td>\u5220\u9664\u7528\u6237<\/td><td>DELETE \/api\/admin\/users\/:userId<\/td><td>\u4e0d\u80fd\u5220\u9664\u7ba1\u7406\u5458<\/td><\/tr><tr><td>\u83b7\u53d6\u5e16\u5b50\u5217\u8868<\/td><td>GET \/api\/admin\/posts<\/td><td>\u5206\u9875\u3001\u641c\u7d22\u3001\u5206\u7c7b<\/td><\/tr><tr><td>\u5220\u9664\u5e16\u5b50<\/td><td>DELETE \/api\/admin\/posts\/:postId<\/td><td>\u7ba1\u7406\u5458\u5220\u9664<\/td><\/tr><tr><td>\u5220\u9664\u56de\u590d<\/td><td>DELETE \/api\/admin\/replies\/:replyId<\/td><td>\u7ba1\u7406\u5458\u5220\u9664<\/td><\/tr><tr><td>\u83b7\u53d6\u64cd\u4f5c\u65e5\u5fd7<\/td><td>GET \/api\/admin\/logs<\/td><td>\u7ba1\u7406\u5458\u64cd\u4f5c\u8bb0\u5f55<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u64cd\u4f5c\u65e5\u5fd7\u8bb0\u5f55<\/h4>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u8bb0\u5f55\u7684\u64cd\u4f5c\u7c7b\u578b\ntype AdminAction = \n  | 'enable_user'    \/\/ \u542f\u7528\u7528\u6237\n  | 'disable_user'   \/\/ \u7981\u7528\u7528\u6237\n  | 'delete_user'    \/\/ \u5220\u9664\u7528\u6237\n  | 'delete_post'    \/\/ \u5220\u9664\u5e16\u5b50\n  | 'delete_reply';  \/\/ \u5220\u9664\u56de\u590d\n\n\/\/ \u65e5\u5fd7\u8868\u7ed3\u6784\nCREATE TABLE admin_logs (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    admin_id VARCHAR(36) NOT NULL,\n    action VARCHAR(50) NOT NULL,\n    target_type VARCHAR(50),        -- user\/post\/reply\n    target_id VARCHAR(36),\n    description TEXT,\n    ip_address VARCHAR(45),\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.7 \u6587\u4ef6\u4e0a\u4f20\u6a21\u5757<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\u529f\u80fd\u5217\u8868<\/h4>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u529f\u80fd<\/th><th>\u63a5\u53e3<\/th><th>\u8bf4\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u5355\u56fe\u4e0a\u4f20<\/td><td>POST \/api\/upload\/image<\/td><td>\u8fd4\u56de\u56fe\u7247 URL<\/td><\/tr><tr><td>\u591a\u56fe\u4e0a\u4f20<\/td><td>POST \/api\/upload\/images<\/td><td>\u6279\u91cf\u4e0a\u4f20<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">\u56fe\u7247\u5904\u7406<\/h4>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u4f7f\u7528 Sharp \u8fdb\u884c\u56fe\u7247\u4f18\u5316\nconst processImage = async (file) =&gt; {\n  return sharp(file.buffer)\n    .resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })\n    .jpeg({ quality: 80 })\n    .toBuffer();\n};\n\n\/\/ \u6587\u4ef6\u547d\u540d: optimized_${uuid}.${ext}\n\/\/ \u5b58\u50a8\u8def\u5f84: backend\/uploads\/\n\/\/ \u8bbf\u95ee\u8def\u5f84: \/uploads\/optimized_xxx.jpg\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4e94\u3001\u6570\u636e\u5e93\u8bbe\u8ba1<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">5.1 ER \u56fe\uff08\u6587\u5b57\u63cf\u8ff0\uff09<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n                            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n                            \u2502    users     \u2502\n                            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\n                            \u2502 id (PK)      \u2502\n                            \u2502 username     \u2502\n                            \u2502 password_hash\u2502\n                            \u2502 email        \u2502\n                            \u2502 anonymous_name\u2502\n                            \u2502 role         \u2502\n                            \u2502 is_active    \u2502\n                            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                                   \u2502\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\u253c\u2500\u2500\u2500\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                           \u2502                           \u2502\n       \u25bc                           \u25bc                           \u25bc\n\u250c\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\u2510            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502    posts     \u2502            \u2502   sessions   \u2502            \u2502 admin_logs   \u2502\n\u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\n\u2502 id (PK)      \u2502            \u2502 id (PK)      \u2502            \u2502 id (PK)      \u2502\n\u2502 user_id (FK) \u2502\u25c4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 user_id (FK) \u2502            \u2502 admin_id (FK)\u2502\n\u2502 title        \u2502            \u2502 token        \u2502            \u2502 action       \u2502\n\u2502 content      \u2502            \u2502 expires_at   \u2502            \u2502 target_type  \u2502\n\u2502 category     \u2502            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518            \u2502 description  \u2502\n\u2502 reply_count  \u2502                                        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2502 like_count   \u2502\n\u2502 view_count   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502\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\u252c\u2500\u2500\u2500\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                           \u2502                           \u2502\n       \u25bc                           \u25bc                           \u25bc\n\u250c\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\u2510            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   replies    \u2502            \u2502  favorites   \u2502            \u2502    images    \u2502\n\u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\n\u2502 id (PK)      \u2502            \u2502 id (PK)      \u2502            \u2502 id (PK)      \u2502\n\u2502 post_id (FK) \u2502            \u2502 user_id (FK) \u2502            \u2502 related_type \u2502\n\u2502 user_id (FK) \u2502            \u2502 post_id (FK) \u2502            \u2502 related_id   \u2502\n\u2502 content      \u2502            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518            \u2502 url          \u2502\n\u2502 parent_reply_id\u2502                                      \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502\n       \u25bc\n\u250c\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\u2510            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502    likes     \u2502            \u2502  messages    \u2502            \u2502 notifications\u2502\n\u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502            \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\n\u2502 id (PK)      \u2502            \u2502 id (PK)      \u2502            \u2502 id (PK)      \u2502\n\u2502 user_id (FK) \u2502            \u2502 from_user_id \u2502            \u2502 user_id (FK) \u2502\n\u2502 target_type  \u2502            \u2502 to_user_id   \u2502            \u2502 type         \u2502\n\u2502 target_id    \u2502            \u2502 content      \u2502            \u2502 title        \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518            \u2502 is_read      \u2502            \u2502 post_id      \u2502\n                            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518            \u2502 is_read      \u2502\n                                                        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">5.2 \u6838\u5fc3\u8868\u8be6\u7ec6\u8bbe\u8ba1<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">users \u8868<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE TABLE users (\n    id VARCHAR(36) PRIMARY KEY,              -- UUID\n    username VARCHAR(50) UNIQUE,             -- \u7528\u6237\u540d\uff08\u53ef\u4e3a\u7a7a\uff0c\u533f\u540d\u7528\u6237\uff09\n    password_hash VARCHAR(255),              -- bcrypt \u52a0\u5bc6\u5bc6\u7801\n    email VARCHAR(100) UNIQUE,               -- \u90ae\u7bb1\n    phone VARCHAR(20),                       -- \u624b\u673a\u53f7\n    avatar VARCHAR(500),                     -- \u5934\u50cf URL\n    anonymous_name VARCHAR(50) NOT NULL,     -- \u663e\u793a\u540d\u79f0\n    role ENUM('user', 'admin') DEFAULT 'user',\n    is_active BOOLEAN DEFAULT TRUE,          -- \u8d26\u53f7\u72b6\u6001\n    login_count INT DEFAULT 0,               -- \u767b\u5f55\u6b21\u6570\n    last_login_at TIMESTAMP,                 -- \u6700\u540e\u767b\u5f55\u65f6\u95f4\n    last_login_ip VARCHAR(45),               -- \u6700\u540e\u767b\u5f55 IP\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    \n    INDEX idx_username (username),\n    INDEX idx_email (email),\n    INDEX idx_role (role)\n);\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">posts \u8868<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE TABLE posts (\n    id VARCHAR(36) PRIMARY KEY,\n    title VARCHAR(200) NOT NULL,\n    content TEXT NOT NULL,\n    category ENUM('tech', 'life', 'help', 'other') NOT NULL,\n    user_id VARCHAR(36) NOT NULL,\n    anonymous_name VARCHAR(50) NOT NULL,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    reply_count INT DEFAULT 0,               -- \u5197\u4f59\u5b57\u6bb5\uff0c\u63d0\u5347\u67e5\u8be2\u6027\u80fd\n    like_count INT DEFAULT 0,                -- \u5197\u4f59\u5b57\u6bb5\n    view_count INT DEFAULT 0,\n    last_reply_at TIMESTAMP NULL,            -- \u6700\u540e\u56de\u590d\u65f6\u95f4\n    is_deleted TINYINT(1) DEFAULT 0,         -- \u8f6f\u5220\u9664\n    \n    INDEX idx_user_id (user_id),\n    INDEX idx_category (category),\n    INDEX idx_created_at (created_at),\n    INDEX idx_last_reply_at (last_reply_at),\n    FULLTEXT INDEX idx_search (title, content)  -- \u5168\u6587\u7d22\u5f15\n);\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">replies \u8868<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE TABLE replies (\n    id VARCHAR(36) PRIMARY KEY,\n    post_id VARCHAR(36) NOT NULL,\n    user_id VARCHAR(36) NOT NULL,\n    anonymous_name VARCHAR(50) NOT NULL,\n    content TEXT NOT NULL,\n    parent_reply_id VARCHAR(36) NULL,        -- \u697c\u4e2d\u697c\u7236\u56de\u590d\n    parent_floor_number INT NULL,            -- \u7236\u56de\u590d\u697c\u5c42\u53f7\n    parent_username VARCHAR(50) NULL,        -- \u88ab\u56de\u590d\u7528\u6237\u540d\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    like_count INT DEFAULT 0,\n    is_deleted TINYINT(1) DEFAULT 0,\n    \n    INDEX idx_post_id (post_id),\n    INDEX idx_user_id (user_id),\n    INDEX idx_parent_reply_id (parent_reply_id),\n    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE\n);\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">sessions \u8868<\/h4>\n\n\n\n<p><strong>sql<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE TABLE sessions (\n    id VARCHAR(36) PRIMARY KEY,\n    user_id VARCHAR(36) NOT NULL,\n    token VARCHAR(255) NOT NULL UNIQUE,\n    expires_at TIMESTAMP NOT NULL,\n    user_agent VARCHAR(500),\n    ip_address VARCHAR(45),\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    \n    INDEX idx_token (token),\n    INDEX idx_user_id (user_id),\n    INDEX idx_expires_at (expires_at),\n    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE\n);\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u516d\u3001API \u63a5\u53e3\u8be6\u89e3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">6.1 \u8ba4\u8bc1\u63a5\u53e3<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPOST \/api\/auth\/register\nRequest:\n{\n  &quot;username&quot;: &quot;testuser&quot;,\n  &quot;password&quot;: &quot;123456&quot;,\n  &quot;email&quot;: &quot;test@example.com&quot;  \/\/ \u53ef\u9009\n}\nResponse:\n{\n  &quot;success&quot;: true,\n  &quot;message&quot;: &quot;\u6ce8\u518c\u6210\u529f&quot;,\n  &quot;user&quot;: { id, username, email, anonymous_name, role, is_active, created_at },\n  &quot;token&quot;: &quot;uuid_timestamp_random&quot;\n}\n\nPOST \/api\/auth\/login\nRequest:\n{\n  &quot;username&quot;: &quot;testuser&quot;,\n  &quot;password&quot;: &quot;123456&quot;\n}\nResponse:\n{\n  &quot;success&quot;: true,\n  &quot;message&quot;: &quot;\u767b\u5f55\u6210\u529f&quot;,\n  &quot;user&quot;: { ... },\n  &quot;token&quot;: &quot;...&quot;\n}\n\nPOST \/api\/auth\/logout\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n{\n  &quot;success&quot;: true,\n  &quot;message&quot;: &quot;\u767b\u51fa\u6210\u529f&quot;\n}\n\nGET \/api\/auth\/me\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n{\n  &quot;success&quot;: true,\n  &quot;user&quot;: { ... }\n}\n\nPOST \/api\/auth\/change-password\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nRequest:\n{\n  &quot;currentPassword&quot;: &quot;oldpass&quot;,\n  &quot;newPassword&quot;: &quot;newpass&quot;\n}\nResponse:\n{\n  &quot;success&quot;: true,\n  &quot;message&quot;: &quot;\u5bc6\u7801\u4fee\u6539\u6210\u529f&quot;\n}\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">6.2 \u5e16\u5b50\u63a5\u53e3<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nGET \/api\/posts?category=tech&amp;amp;sort=latest&amp;amp;page=1&amp;amp;limit=20\nResponse:\n{\n  &quot;posts&quot;: &#x5B;\n    {\n      &quot;id&quot;: &quot;uuid&quot;,\n      &quot;title&quot;: &quot;\u5e16\u5b50\u6807\u9898&quot;,\n      &quot;content&quot;: &quot;\u5e16\u5b50\u5185\u5bb9&quot;,\n      &quot;category&quot;: &quot;tech&quot;,\n      &quot;user_id&quot;: &quot;uuid&quot;,\n      &quot;anonymous_name&quot;: &quot;\u7528\u6237_xxx&quot;,\n      &quot;user_avatar&quot;: null,\n      &quot;created_at&quot;: &quot;2025-01-01T00:00:00.000Z&quot;,\n      &quot;reply_count&quot;: 5,\n      &quot;like_count&quot;: 10,\n      &quot;view_count&quot;: 100,\n      &quot;is_liked&quot;: false,\n      &quot;is_favorited&quot;: true,\n      &quot;images&quot;: &#x5B;&quot;url1&quot;, &quot;url2&quot;]\n    }\n  ],\n  &quot;pagination&quot;: {\n    &quot;page&quot;: 1,\n    &quot;limit&quot;: 20,\n    &quot;total&quot;: 100,\n    &quot;totalPages&quot;: 5\n  }\n}\n\nGET \/api\/posts\/:id\nResponse:\n{\n  &quot;id&quot;: &quot;uuid&quot;,\n  &quot;title&quot;: &quot;...&quot;,\n  &quot;content&quot;: &quot;...&quot;,\n  ...\n}\n\nPOST \/api\/posts\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nRequest:\n{\n  &quot;title&quot;: &quot;\u5e16\u5b50\u6807\u9898&quot;,\n  &quot;content&quot;: &quot;\u5e16\u5b50\u5185\u5bb9&quot;,\n  &quot;category&quot;: &quot;tech&quot;,\n  &quot;images&quot;: &#x5B;&quot;url1&quot;, &quot;url2&quot;]\n}\nResponse:\n{\n  &quot;id&quot;: &quot;uuid&quot;,\n  &quot;title&quot;: &quot;...&quot;,\n  ...\n}\n\nDELETE \/api\/posts\/:id\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n{\n  &quot;message&quot;: &quot;\u5220\u9664\u6210\u529f&quot;\n}\n\nGET \/api\/posts\/search?q=\u5173\u952e\u8bcd&amp;amp;page=1&amp;amp;limit=20\nResponse:\n{\n  &quot;posts&quot;: &#x5B;...]\n}\n\nGET \/api\/posts\/hot?hours=24&amp;amp;limit=5\nResponse:\n&#x5B;{ ... }, { ... }]\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">6.3 \u56de\u590d\u63a5\u53e3<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nGET \/api\/replies\/post\/:postId\nResponse:\n&#x5B;\n  {\n    &quot;id&quot;: &quot;uuid&quot;,\n    &quot;post_id&quot;: &quot;uuid&quot;,\n    &quot;user_id&quot;: &quot;uuid&quot;,\n    &quot;anonymous_name&quot;: &quot;\u7528\u6237_xxx&quot;,\n    &quot;user_avatar&quot;: null,\n    &quot;content&quot;: &quot;\u56de\u590d\u5185\u5bb9&quot;,\n    &quot;parent_reply_id&quot;: null,\n    &quot;parent_floor_number&quot;: null,\n    &quot;parent_username&quot;: null,\n    &quot;created_at&quot;: &quot;...&quot;,\n    &quot;like_count&quot;: 5,\n    &quot;is_liked&quot;: false,\n    &quot;images&quot;: &#x5B;]\n  }\n]\n\nPOST \/api\/replies\/post\/:postId\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nRequest:\n{\n  &quot;content&quot;: &quot;\u56de\u590d\u5185\u5bb9&quot;,\n  &quot;parentReplyId&quot;: &quot;uuid&quot;,        \/\/ \u53ef\u9009\uff0c\u697c\u4e2d\u697c\n  &quot;parentFloorNumber&quot;: 3,         \/\/ \u53ef\u9009\n  &quot;parentUsername&quot;: &quot;\u7528\u6237_xxx&quot;,   \/\/ \u53ef\u9009\n  &quot;images&quot;: &#x5B;]\n}\nResponse:\n{\n  &quot;id&quot;: &quot;uuid&quot;,\n  ...\n}\n\nDELETE \/api\/replies\/:id\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n{\n  &quot;message&quot;: &quot;\u5220\u9664\u6210\u529f&quot;\n}\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">6.4 \u4e92\u52a8\u63a5\u53e3<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPOST \/api\/interactions\/like\/:id\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nRequest:\n{\n  &quot;type&quot;: &quot;post&quot;  \/\/ \u6216 &quot;reply&quot;\n}\nResponse:\n{\n  &quot;liked&quot;: true,\n  &quot;message&quot;: &quot;\u70b9\u8d5e\u6210\u529f&quot;\n}\n\nPOST \/api\/interactions\/favorite\/:postId\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n{\n  &quot;favorited&quot;: true,\n  &quot;message&quot;: &quot;\u6536\u85cf\u6210\u529f&quot;\n}\n\nGET \/api\/interactions\/favorites\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n&#x5B;{ post1 }, { post2 }]\n\nPOST \/api\/interactions\/messages\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nRequest:\n{\n  &quot;toUserId&quot;: &quot;uuid&quot;,\n  &quot;content&quot;: &quot;\u79c1\u4fe1\u5185\u5bb9&quot;\n}\nResponse:\n{\n  &quot;id&quot;: &quot;uuid&quot;,\n  &quot;from_user_id&quot;: &quot;...&quot;,\n  &quot;to_user_id&quot;: &quot;...&quot;,\n  &quot;content&quot;: &quot;...&quot;,\n  ...\n}\n\nGET \/api\/interactions\/conversations\nHeaders: Authorization: Bearer &amp;lt;token&gt;\nResponse:\n&#x5B;\n  {\n    &quot;other_user_id&quot;: &quot;uuid&quot;,\n    &quot;other_user_name&quot;: &quot;\u7528\u6237_xxx&quot;,\n    &quot;other_user_avatar&quot;: null,\n    &quot;last_message&quot;: &quot;\u6700\u540e\u4e00\u6761\u6d88\u606f&quot;,\n    &quot;last_message_time&quot;: &quot;...&quot;,\n    &quot;unread_count&quot;: 3\n  }\n]\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4e03\u3001\u5b89\u5168\u8bbe\u8ba1<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">7.1 \u8ba4\u8bc1\u5b89\u5168<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u63aa\u65bd<\/th><th>\u5b9e\u73b0<\/th><\/tr><\/thead><tbody><tr><td>\u5bc6\u7801\u52a0\u5bc6<\/td><td>bcrypt (cost=10)<\/td><\/tr><tr><td>Token \u683c\u5f0f<\/td><td>UUID + \u65f6\u95f4\u6233 + \u968f\u673a\u4e32<\/td><\/tr><tr><td>Token \u5b58\u50a8<\/td><td>\u6570\u636e\u5e93 sessions \u8868<\/td><\/tr><tr><td>Token \u6709\u6548\u671f<\/td><td>30 \u5929<\/td><\/tr><tr><td>\u4f1a\u8bdd\u7ba1\u7406<\/td><td>\u4fee\u6539\u5bc6\u7801\u540e\u5176\u4ed6\u8bbe\u5907\u767b\u51fa<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">7.2 \u8bf7\u6c42\u5b89\u5168<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u63aa\u65bd<\/th><th>\u5b9e\u73b0<\/th><\/tr><\/thead><tbody><tr><td>XSS \u9632\u62a4<\/td><td>xss \u5e93\u8fc7\u6ee4\u6240\u6709\u8f93\u5165<\/td><\/tr><tr><td>SQL \u6ce8\u5165<\/td><td>\u53c2\u6570\u5316\u67e5\u8be2 (mysql2)<\/td><\/tr><tr><td>CORS<\/td><td>\u9650\u5236 origin<\/td><\/tr><tr><td>HTTP \u5b89\u5168\u5934<\/td><td>Helmet \u4e2d\u95f4\u4ef6<\/td><\/tr><tr><td>\u901f\u7387\u9650\u5236<\/td><td>express-rate-limit<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">7.3 \u901f\u7387\u9650\u5236\u914d\u7f6e<\/h3>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u901a\u7528 API: 5000 \u6b21\/\u5206\u949f (\u5f00\u53d1\u6a21\u5f0f)\ngeneralLimiter = rateLimit({\n  windowMs: 60 * 1000,\n  max: 5000\n});\n\n\/\/ \u521b\u5efa\u5185\u5bb9: 1000 \u6b21\/\u5206\u949f (\u5f00\u53d1\u6a21\u5f0f)\ncreateLimiter = rateLimit({\n  windowMs: 60 * 1000,\n  max: 1000\n});\n\n\/\/ \u6587\u4ef6\u4e0a\u4f20: 10 \u6b21\/\u5206\u949f\nuploadLimiter = rateLimit({\n  windowMs: 60 * 1000,\n  max: 10\n});\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">7.4 \u654f\u611f\u8bcd\u8fc7\u6ee4<\/h3>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u654f\u611f\u8bcd\u7b49\u7ea7\ntype SensitiveLevel = 'low' | 'medium' | 'high';\n\n\/\/ \u5904\u7406\u903b\u8f91\nif (level === 'high') {\n  \/\/ \u62d2\u7edd\u53d1\u5e03\n  return res.status(400).json({ error: '\u5185\u5bb9\u5305\u542b\u654f\u611f\u8bcd' });\n} else {\n  \/\/ \u8bb0\u5f55\u65e5\u5fd7\uff0c\u5141\u8bb8\u53d1\u5e03\n  await logOperation(...);\n}\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u516b\u3001\u65e5\u5fd7\u7cfb\u7edf<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">8.1 Winston \u914d\u7f6e<\/h3>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u65e5\u5fd7\u7ea7\u522b: error, warn, info, debug\n\/\/ \u65e5\u5fd7\u683c\u5f0f: &#x5B;YYYY-MM-DD HH:mm:ss] LEVEL: message\n\n\/\/ \u8f93\u51fa\u76ee\u6807:\n\/\/ 1. \u63a7\u5236\u53f0 (\u5e26\u989c\u8272)\n\/\/ 2. error-YYYY-MM-DD.log (\u4ec5\u9519\u8bef)\n\/\/ 3. combined-YYYY-MM-DD.log (\u6240\u6709\u65e5\u5fd7)\n\n\/\/ \u65e5\u5fd7\u8f6e\u8f6c:\n\/\/ - \u6bcf\u65e5\u8f6e\u8f6c\n\/\/ - \u5355\u6587\u4ef6\u6700\u5927 20MB\n\/\/ - \u4fdd\u7559 14 \u5929\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">8.2 \u64cd\u4f5c\u65e5\u5fd7<\/h3>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u8bb0\u5f55\u5230 operation_logs \u8868\nawait logOperation(userId, action, targetType, targetId, req);\n\n\/\/ \u8bb0\u5f55\u5185\u5bb9:\n\/\/ - user_id: \u64cd\u4f5c\u7528\u6237\n\/\/ - action: \u64cd\u4f5c\u7c7b\u578b (create_post, delete_reply, like, etc.)\n\/\/ - target_type: \u76ee\u6807\u7c7b\u578b (post, reply, user)\n\/\/ - target_id: \u76ee\u6807 ID\n\/\/ - ip_address: \u5ba2\u6237\u7aef IP\n\/\/ - user_agent: \u6d4f\u89c8\u5668\u4fe1\u606f\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4e5d\u3001\u90e8\u7f72\u67b6\u6784<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">9.1 Nginx \u914d\u7f6e<\/h3>\n\n\n\n<p><strong>nginx<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nserver {\n    listen 80;\n    server_name forum.zcc609.online;\n    \n    # \u524d\u7aef\u9759\u6001\u6587\u4ef6\n    root \/home\/anonymous-forum\/frontend;\n    \n    # SPA \u8def\u7531\n    location \/ {\n        try_files $uri $uri\/ \/index.html;\n    }\n    \n    # API \u53cd\u5411\u4ee3\u7406\n    location \/api\/ {\n        proxy_pass http:\/\/127.0.0.1:3001\/api\/;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    }\n    \n    # \u56fe\u7247\u4ee3\u7406\n    location \/uploads\/ {\n        proxy_pass http:\/\/127.0.0.1:3001\/uploads\/;\n    }\n    \n    # Gzip \u538b\u7f29\n    gzip on;\n    gzip_types text\/plain text\/css application\/json application\/javascript;\n    \n    # \u9759\u6001\u8d44\u6e90\u7f13\u5b58\n    location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg)$ {\n        expires 1y;\n        add_header Cache-Control &quot;public, immutable&quot;;\n    }\n}\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">9.2 PM2 \u8fdb\u7a0b\u7ba1\u7406<\/h3>\n\n\n\n<p><strong>bash<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# \u542f\u52a8\npm2 start dist\/index.js --name anonymous-forum\n\n# \u67e5\u770b\u65e5\u5fd7\npm2 logs anonymous-forum\n\n# \u91cd\u542f\npm2 restart anonymous-forum\n\n# \u505c\u6b62\npm2 stop anonymous-forum\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">9.3 \u90e8\u7f72\u6d41\u7a0b<\/h3>\n\n\n\n<p><strong>bash<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# 1. \u672c\u5730\u6784\u5efa\u524d\u7aef\nnpm run build\n\n# 2. \u4e0a\u4f20\u5230\u670d\u52a1\u5668\nscp -r build\/* root@server:\/home\/anonymous-forum\/frontend\/\n\n# 3. \u4e0a\u4f20\u540e\u7aef\u4ee3\u7801\nscp -r backend\/* root@server:\/home\/anonymous-forum\/backend\/\n\n# 4. \u670d\u52a1\u5668\u7f16\u8bd1\u540e\u7aef\ncd \/home\/anonymous-forum\/backend\nnpm install\nnpm run build\n\n# 5. \u91cd\u542f\u670d\u52a1\npm2 restart anonymous-forum\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u5341\u3001\u524d\u7aef\u8def\u7531\u7ed3\u6784<\/h2>\n\n\n\n<p><strong>typescript<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ App.tsx \u8def\u7531\u914d\u7f6e\n&amp;lt;Routes&gt;\n  {\/* \u8ba4\u8bc1\u9875\u9762 *\/}\n  &amp;lt;Route path=&quot;\/login&quot; element={&amp;lt;LoginPage \/&gt;} \/&gt;\n  &amp;lt;Route path=&quot;\/register&quot; element={&amp;lt;RegisterPage \/&gt;} \/&gt;\n  \n  {\/* \u7ba1\u7406\u540e\u53f0 (\u9700\u8981\u7ba1\u7406\u5458\u6743\u9650) *\/}\n  &amp;lt;Route path=&quot;\/admin\/*&quot; element={\n    &amp;lt;ProtectedRoute requireAuth adminOnly&gt;\n      &amp;lt;AdminDashboard \/&gt;\n    &amp;lt;\/ProtectedRoute&gt;\n  } \/&gt;\n  \n  {\/* \u79c1\u4fe1\u5bf9\u8bdd\u9875\u9762 *\/}\n  &amp;lt;Route path=&quot;\/messages\/:userId&quot; element={&amp;lt;MessageConversation \/&gt;} \/&gt;\n  \n  {\/* \u4e3b\u5e03\u5c40\u9875\u9762 (\u9700\u8981\u767b\u5f55) *\/}\n  &amp;lt;Route element={&amp;lt;MainLayout \/&gt;}&gt;\n    &amp;lt;Route path=&quot;\/&quot; element={&amp;lt;PostListPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/post\/:id&quot; element={&amp;lt;PostDetailPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/search&quot; element={&amp;lt;SearchResultPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/my-replies&quot; element={&amp;lt;MyRepliesPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/my-posts&quot; element={&amp;lt;MyPostsPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/my-favorites&quot; element={&amp;lt;MyFavoritesPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/my-likes&quot; element={&amp;lt;MyLikesPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/notifications&quot; element={&amp;lt;NotificationPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/drafts&quot; element={&amp;lt;DraftsPage \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/messages&quot; element={&amp;lt;Messages \/&gt;} \/&gt;\n    &amp;lt;Route path=&quot;\/profile&quot; element={&amp;lt;ProfilePage \/&gt;} \/&gt;\n  &amp;lt;\/Route&gt;\n  \n  {\/* 404 \u91cd\u5b9a\u5411 *\/}\n  &amp;lt;Route path=&quot;*&quot; element={&amp;lt;Navigate to=&quot;\/&quot; \/&gt;} \/&gt;\n&amp;lt;\/Routes&gt;\n\n<\/pre><\/div>\n\n\n<p>\u4ee5\u4e0a\u662f\u9879\u76ee\u7684\u5b8c\u6574\u8be6\u7ec6\u4fe1\u606f\uff0c\u6db5\u76d6\u6280\u672f\u6808\u3001\u9879\u76ee\u7ed3\u6784\u3001\u529f\u80fd\u6a21\u5757\u3001\u6570\u636e\u5e93\u8bbe\u8ba1\u3001API \u63a5\u53e3\u3001\u5b89\u5168\u8bbe\u8ba1\u3001\u65e5\u5fd7\u7cfb\u7edf\u548c\u90e8\u7f72\u67b6\u6784\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u4e00\u3001\u9879\u76ee\u6982\u8ff0 1.1 \u9879\u76ee\u7b80\u4ecb \u533f\u540d\u8bba\u575b\u662f\u4e00\u4e2a\u652f\u6301\u7528\u6237\u6ce8\u518c\u767b\u5f55\u548c\u533f\u540d\u53d1\u5e16\u7684\u793e\u533a\u5e73\u53f0\uff0c\u63d0\u4f9b\u5e16\u5b50\u53d1\u5e03\u3001\u56de\u590d\u3001\u70b9\u8d5e\u3001 &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/zcc609.online\/?p=110\" class=\"more-link\">Read more<span class=\"screen-reader-text\"> &#8220;\u533f\u540d\u8bba\u575b\u6280\u672f\u6587\u6863&#8221;<\/span><\/a><\/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-110","post","type-post","status-publish","format-standard","hentry","category-1"],"_links":{"self":[{"href":"https:\/\/zcc609.online\/index.php?rest_route=\/wp\/v2\/posts\/110","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zcc609.online\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zcc609.online\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zcc609.online\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zcc609.online\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=110"}],"version-history":[{"count":1,"href":"https:\/\/zcc609.online\/index.php?rest_route=\/wp\/v2\/posts\/110\/revisions"}],"predecessor-version":[{"id":111,"href":"https:\/\/zcc609.online\/index.php?rest_route=\/wp\/v2\/posts\/110\/revisions\/111"}],"wp:attachment":[{"href":"https:\/\/zcc609.online\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=110"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zcc609.online\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=110"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zcc609.online\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=110"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}