diff --git a/InfoGenie-frontend/前端文档.md b/InfoGenie-frontend/前端文档.md
index d5a37150..26abd717 100644
--- a/InfoGenie-frontend/前端文档.md
+++ b/InfoGenie-frontend/前端文档.md
@@ -10,7 +10,7 @@
| 命令 | 说明 |
|------|------|
-| `npm run dev` / `npm start` | 开发服务器(默认端口 3000) |
+| `npm run dev` / `npm start` | 开发服务器(Vite,默认端口 3000) |
| `npm run build` | 生产构建,输出 `dist/` |
| `npm run preview` | 本地预览 `dist/` 构建结果 |
@@ -23,7 +23,7 @@ infogenie-frontend/
├── index.html ← Vite HTML 入口(根目录,非 public/)
├── vite.config.js ← Vite 配置(React 插件、Tailwind 插件、路径别名)
├── package.json
-├── .env.development ← 开发环境变量
+├── .env.development ← 开发环境变量(VITE_*)
├── .env.production ← 生产环境变量
├── public/ ← 静态资源(不参与构建,直接复制到 dist/)
│ ├── assets/logo.png
@@ -34,12 +34,12 @@ infogenie-frontend/
└── src/
├── index.js ← React 根挂载
├── App.js ← 路由、布局、全局 Provider
- ├── components/ ← 公共组件
- ├── pages/ ← 页面组件
+ ├── components/ ← 公共组件(含 FullscreenEmbed、SmallGameIcons 等)
+ ├── pages/ ← 页面组件(含 AdminPage、SmallGamePage)
├── contexts/ ← 全局 Context
- ├── hooks/ ← 自定义 Hooks
+ ├── hooks/ ← 自定义 Hooks(如 useFeatureCardClickStats)
├── utils/ ← Axios 封装、可见性过滤等
- ├── config/ ← 环境变量解析、路由/内容配置
+ ├── config/ ← 环境变量解析、Api60sConfig、StaticPageConfig
└── styles/
├── index.css ← Tailwind 入口 + 全局 base 样式 + 自定义动画
└── shared.js ← 设计系统组件(Tailwind 函数组件)
@@ -52,7 +52,7 @@ infogenie-frontend/
| 特性 | 说明 |
|------|------|
| `@vitejs/plugin-react` | React Fast Refresh + JSX 转换(含 `.js` 扩展名支持) |
-| `@tailwindcss/vite` | Tailwind CSS v4 Vite 插件,零配置文件 |
+| `@tailwindcss/vite` | Tailwind CSS v4 Vite 插件 |
| `treat-js-as-jsx` | 内联插件,使 `.js` 文件中的 JSX 正常编译 |
| `resolve.alias['@']` | `@/` 映射到 `src/` |
| `base: '/'` | 部署根路径 |
@@ -62,18 +62,19 @@ infogenie-frontend/
## 环境变量(`src/config/env.js`)
-变量名使用 Vite 规范的 `VITE_` 前缀(通过 `import.meta.env` 访问):
+变量名使用 Vite 规范的 **`VITE_`** 前缀(通过 `import.meta.env` 访问):
| 变量 | 作用 | 开发默认 / 生产默认 |
|------|------|---------------------|
-| `VITE_API_URL` | 万象口袋 **Go 后端** 根地址 | dev:`http://127.0.0.1:5002`;prod:`https://infogenie.api.shumengya.top` |
+| `VITE_API_URL` | 万象口袋 **Go 后端** 根地址 | dev:`http://127.0.0.1:5002`;prod 见 `.env.production` |
| `VITE_AUTH_URL` | 认证中心 **页面** 域名 | `https://auth.shumengya.top` |
| `VITE_AUTH_API_URL` | 认证中心 **API** 根地址 | `https://auth.api.shumengya.top` |
| `VITE_DEBUG` | 调试开关 | `'true'` 开启 |
+| `VITE_SITE_ADMIN_GATE` | 管理员后台口令,须与后端 `INFOGENIE_SITE_ADMIN_TOKEN` 一致 | 本地写在 `.env.development`,勿提交 |
-运行时可通过 `window.ENV_CONFIG` 查看解析结果。
+运行时可通过 `window.ENV_CONFIG` 查看解析结果(若已注入)。
-> **迁移注意**:从 CRA 迁移时原 `REACT_APP_*` 变量已全部重命名为 `VITE_*`。
+> **迁移注意**:由 CRA 迁出后,原 `REACT_APP_*` 已统一为 `VITE_*`。
---
@@ -91,13 +92,27 @@ infogenie-frontend/
| `/toolbox` | 工具箱 |
| `/aimodel` | AI 应用(需登录) |
| `/profile` | 个人中心 |
-| `/admin` | 管理 |
+| `/admin` | **管理员后台**(本地 `admin_token` 校验) |
| `*` | 重定向首页 |
-### 关键组件
+### 管理员后台(`src/pages/AdminPage.js`)
-- **`RandomSiteBackground`**:全站随机背景(`https://randbg.api.smyhub.com/api/random?format=json`),毛玻璃模糊,会话级缓存。
-- **`Header` / `Footer`**:半透明绿 + `backdrop-filter`;**移动端 `Navigation`** 底栏为不透明渐变 + 圆角顶。
+- 浏览器 **`localStorage.admin_token`** 与内置口令一致方可进入(与后端 **`INFOGENIE_SITE_ADMIN_TOKEN`** 用于接口时是同一套站点管理员概念)。
+- **功能分区**(左侧):概览、**状态监控**、AI 上游、60s 数据源、60s 前台展示、AI 前台展示。
+- **状态监控**:
+ - 并行请求 **`GET /api/health`**(MySQL / 60s 上游 / Redis Ping)与 **`GET /api/admin/site/diagnostics`**(进程监听、MySQL/Redis 连接目标、库名/用户、SMTP、认证中心 URL 等;**不含密码**)。
+ - 请求诊断接口时携带头 **`X-Site-Admin-Token: `**。
+ - 「刷新探测与配置」会同时刷新上述两个数据源。
+
+### 休闲游戏页(`src/pages/SmallGamePage.js`)
+
+- 游戏列表来自 **`src/config/StaticPageConfig.js`** 的 `SMALL_GAMES`。
+- 卡片图标由 **`src/components/SmallGameIcons.js`** 按 `game.id` 渲染 **Emoji**(如 2048→🔢、俄罗斯方块→🧱 等),嵌入在 **`FeatureCardIcon`** 内。
+
+### 其他关键组件
+
+- **`RandomSiteBackground`**:全站随机背景,毛玻璃模糊,会话级缓存。
+- **`Header` / `Footer` / `Navigation`**:导航与布局。
- **`FullscreenEmbed`**:全屏 iframe(游戏、工具箱、AI 静态页),支持注入 token、加载超时提示。
- **`ParticleEffect`**:全局点击粒子动画。
@@ -119,17 +134,12 @@ infogenie-frontend/
### 设计系统(`src/styles/shared.js`)
-所有组件均为 **React 函数组件**,接受 `className` prop(可与额外 Tailwind 类合并):
+所有组件均为 **React 函数组件**,接受 `className` prop(可与 Tailwind 类合并):
-- **`PageWrapper`**:页面容器,带 `animate-page-enter` 入场动画
-- **`Container`**:最大宽度容器,`$narrow`(800px)/ 默认(1200px)
-- **`FeatureGrid`**:响应式 5→4→3→2 列网格,带 `animate-fade-up`
-- **`CatalogCard`** / **`FeatureCard`**:统一卡片样式,顶条渐变色动态注入(`$c` prop)
+- **`PageWrapper`**、**`Container`**、**`FeatureGrid`**、**`CatalogCard`**、**`FeatureCard`**
- **`FeatureCardUseCount`**:卡片右上角点击次数角标
-- **`ModuleCard`**:首页大模块横向卡片(包装 `react-router-dom` `Link`)
-- **`accentFromGradient`**:工具函数,从 gradient 字符串提取首色
-
-> 动态样式(渐变色、动态 grid 列数等)通过 inline `style` prop 注入,Tailwind 负责静态部分。
+- **`ModuleCard`**:首页大模块卡片(`Link`)
+- **`accentFromGradient`**:从 gradient 字符串提取首色
---
@@ -146,9 +156,9 @@ infogenie-frontend/
与 Vite 主站并列的大量**免构建**页面:
- **`public/aimodelapp/<应用名>/`**:AI 小应用(HTML + `script.js` + 可选 `env.js`)。
- - 统一聊天入口:**`public/aimodelapp/shared/ai-chat.js`** 的 `AiChat.complete()`。
+ - 统一聊天入口:**`public/aimodelapp/shared/ai-chat.js`**。
- **优先**请求 **`POST /api/aimodelapp/chat/stream`**(SSE),失败或无内容时回退 **`POST /api/aimodelapp/chat`**。
-- **`public/smallgame/`**、**`public/toolbox/`**:独立小游戏与工具页,由 `FullscreenEmbed` 打开。
+- **`public/smallgame/`**、**`public/toolbox/`**:独立页,由 **`FullscreenEmbed`** 打开。
各 `aimodelapp` 子目录的 **`env.js`** / **`API_CONFIG`** 需指向与主站一致的 Go 后端地址(通常与 `VITE_API_URL` 同源或同网段)。
@@ -156,14 +166,15 @@ infogenie-frontend/
## 与后端协作要点
-1. **登录**:token 存 `localStorage`,AI 与受保护接口依赖 JWT。
-2. **AI**:静态页通过 **同源或配置的 API** 调 Go 的 `/api/aimodelapp/*`;流式响应类型为 **`text/event-stream`**。
-3. **站点开关**:60s / AI 应用显隐由 `GET /api/site/*-disabled` 等驱动(见后端文档)。
-4. **卡片统计**:四大板块列表页的 `CatalogCard` 已接 `feature-card-clicks` 接口。
+1. **登录**:token 存 `localStorage`,AI 与受保护接口依赖 JWT(萌芽认证中心签发)。
+2. **AI**:静态页通过配置的 API 调 Go 的 `/api/aimodelapp/*`;流式为 **`text/event-stream`**。
+3. **站点开关**:60s / AI 应用显隐由 `GET /api/site/*-disabled` 等驱动。
+4. **卡片统计**:列表页 `CatalogCard` 与 **`feature-card-clicks`** 接口对接;可选 Redis 缓存由后端控制。
+5. **管理员**:写配置类接口需后端配置 **`INFOGENIE_SITE_ADMIN_TOKEN`**,与前端管理员口令一致。
---
## 其他文档
-- **Go 后端 API 与表结构**:[`infogenie-backend-go/后端文档.md`](../infogenie-backend-go/后端文档.md)
-- **仓库与工具说明**:[`../.claude/README.md`](../.claude/README.md)
+- **Go 后端 API、Redis、健康与诊断**:[`infogenie-backend-go/后端文档.md`](../infogenie-backend-go/后端文档.md)
+- **仓库总览与多模块说明**:[`../README.md`](../README.md)
diff --git a/README.md b/README.md
index d08ee83a..acefc408 100755
--- a/README.md
+++ b/README.md
@@ -1,472 +1,77 @@
-# InfoGenie 前端架构文档
+# InfoGenie(万象口袋)
-## 项目概述
+多模块仓库:**React + Vite 前端**为主界面,**Go(Gin + GORM)** 为当前主力 API,另有 **Java Spring Boot** 占位模块。大量 **60s 类接口**通过上游开放 API 提供;本站负责聚合导航、认证跳转、AI 应用壳与小游戏嵌入等。
-InfoGenie 是一个基于前后端分离架构的全栈 Web 应用,前端采用 React 单页应用(SPA)架构,结合静态 HTML 页面实现丰富的功能模块。后端提供 RESTful API 接口,支持用户认证、数据获取等核心功能。项目实现了移动优先的响应式设计,通过统一的组件系统和数据流管理,提供了包括API数据展示、小游戏、AI工具等多样化功能模块。
+---
-## 技术栈
+## 仓库结构
-### 核心框架
-- **React 18.2.0**: 前端 UI 框架,使用函数式组件和 Hooks
-- **React Router DOM 6.15.0**: 客户端路由管理
-- **Axios 1.5.0**: HTTP 客户端,用于后端 API 调用
+| 目录 | 说明 |
+|------|------|
+| [`infogenie-frontend/`](infogenie-frontend/) | 主站 SPA(Vite 6、React 18、Tailwind v4) |
+| [`infogenie-backend-go/`](infogenie-backend-go/) | **万象后端 API**(Go):站点配置、AI 代理、健康检查、可选 Redis 缓存等 |
+| [`infogenie-backend-java/`](infogenie-backend-java/) | Spring Boot 3.5 骨架(当前无业务 API 层) |
+| [`InfoGenie-go-backend/`](InfoGenie-go-backend/) | 历史/备用目录,通常仅 README |
-### 样式和 UI
-- **styled-components 6.0.7**: CSS-in-JS 样式解决方案
-- **react-icons 4.11.0**: 图标库
-- **react-hot-toast 2.4.1**: 通知提示组件
+详细 API、表结构、Redis 与诊断接口见 **[infogenie-backend-go/后端文档.md](infogenie-backend-go/后端文档.md)**。
-### 开发工具
-- **Create React App**: 项目脚手架
-- **ESLint**: 代码规范检查
-- **Service Worker**: PWA 支持
+前端路由、Vite、管理员「状态监控」、静态页协作见 **[infogenie-frontend/前端文档.md](infogenie-frontend/前端文档.md)**。
-## 架构设计
+开发与 AI 助手指南见根目录 **[CLAUDE.md](CLAUDE.md)**(若存在)。
-### 整体架构
-```
-前端应用
-├── React SPA (主要页面)
-│ ├── 用户认证系统
-│ ├── 导航和布局
-│ ├── 页面路由
-│ └── 用户管理
-└── 静态 HTML 页面
- ├── API 数据展示页面
- ├── 小游戏页面
- └── AI 模型工具页面
-```
+---
-### 文件结构
-```
-src/
-├── components/ # 公共组件
-│ ├── Header.js # 顶部导航栏
-│ ├── Navigation.js # 底部导航栏(移动端)
-│ └── Footer.js # 页脚
-├── pages/ # 页面组件
-│ ├── HomePage.js # 首页
-│ ├── LoginPage.js # 登录页面
-│ ├── Api60sPage.js # API 60s 页面
-│ ├── SmallGamePage.js # 小游戏页面
-│ ├── AiModelPage.js # AI 模型页面
-│ └── UserProfilePage.js # 用户资料页面
-├── contexts/ # React Context
-│ └── UserContext.js # 用户状态管理
-├── config/ # 配置文件
-│ └── StaticPageConfig.js # 静态页面配置
-├── utils/ # 工具函数
-│ └── api.js # API 调用封装
-└── styles/ # 全局样式
-```
+## 快速启动(本地)
-## API 接口设计
+### 前端(`infogenie-frontend/`)
-### 基础配置
-- **Base URL**: `https://infogenie.api.shumengya.top` (这是生产环境)(可通过环境变量 `REACT_APP_API_URL` 配置测试环境)
-- **认证方式**: JWT Bearer Token
-- **请求格式**: JSON
-- **响应格式**: JSON
-- **超时时间**: 10秒
-
-### 认证相关接口
-
-#### 发送验证码
-```http
-POST /api/auth/send-verification
-Content-Type: application/json
-
-{
- "email": "user@example.com"
-}
-```
-
-#### 验证验证码
-```http
-POST /api/auth/verify-code
-Content-Type: application/json
-
-{
- "email": "user@example.com",
- "code": "123456"
-}
-```
-
-#### 用户登录
-```http
-POST /api/auth/login
-Content-Type: application/json
-
-{
- "email": "user@example.com",
- "password": "password"
-}
-```
-
-#### 用户注册
-```http
-POST /api/auth/register
-Content-Type: application/json
-
-{
- "email": "user@example.com",
- "password": "password",
- "verification_code": "123456"
-}
-```
-
-#### 用户登出
-```http
-POST /api/auth/logout
-Authorization: Bearer
-```
-
-#### 检查登录状态
-```http
-GET /api/auth/check
-Authorization: Bearer
-```
-
-### 用户管理接口
-
-#### 获取用户资料
-```http
-GET /api/user/profile
-Authorization: Bearer
-```
-
-#### 修改密码
-```http
-POST /api/user/change-password
-Authorization: Bearer
-Content-Type: application/json
-
-{
- "old_password": "old_password",
- "new_password": "new_password"
-}
-```
-
-#### 获取用户统计
-```http
-GET /api/user/stats
-Authorization: Bearer
-```
-
-#### 获取游戏数据
-```http
-GET /api/user/game-data
-Authorization: Bearer
-```
-
-#### 用户签到
-```http
-POST /api/user/checkin
-Authorization: Bearer
-```
-
-#### 删除账户
-```http
-POST /api/user/delete
-Authorization: Bearer
-Content-Type: application/json
-
-{
- "password": "password"
-}
-```
-
-### 数据展示接口
-
-前端包含大量静态页面用于展示各种 API 数据,这些页面直接调用后端提供的公开接口:
-
-#### 热搜榜单系列
-- 百度实时热搜: `GET /v2/baidu/realtime`
-- 百度贴吧话题榜: `GET /v2/baidu/tieba`
-- 哔哩哔哩热搜榜: `GET /v2/bilibili/hot`
-- 抖音热搜榜: `GET /v2/douyin/hot`
-- 头条热搜榜: `GET /v2/toutiao/hot`
-- 微博热搜榜: `GET /v2/weibo/hot`
-- 小红书热点: `GET /v2/xiaohongshu/hot`
-- 知乎热门话题: `GET /v2/zhihu/hot`
-- Hacker News 榜单: `GET /v2/hackernews`
-
-#### 日更资讯系列
-- 必应每日壁纸: `GET /v2/bing/wallpaper`
-- 历史上的今天: `GET /v2/history/today`
-- 每日国际汇率: `GET /v2/exchange/rates`
-- 每天60s读懂世界: `GET /v2/60s/world`
-
-#### 实用功能系列
-- 百度百科词条: `GET /v2/baike/search?keyword={keyword}`
-- 公网IP地址: `GET /v2/ip/public`
-- 哈希解压压缩: `POST /v2/hash/{algorithm}`
-- 链接OG信息: `GET /v2/og?url={url}`
-- 密码强度检测: `POST /v2/password/strength`
-- 农历信息: `GET /v2/calendar/lunar?date={date}`
-- 配色方案: `GET /v2/color/schemes`
-- 身体健康分析: `POST /v2/health/analysis`
-- 生成二维码: `POST /v2/qrcode/generate`
-- 实时天气: `GET /v2/weather?location={location}`
-- 随机密码生成器: `GET /v2/password/random`
-- 随机颜色: `GET /v2/color/random`
-- 天气预报: `GET /v2/weather/forecast?location={location}`
-- 在线翻译: `POST /v2/translate`
-- EpicGames免费游戏: `GET /v2/epic/free-games`
-
-#### 娱乐消遣系列
-- 随机唱歌音频: `GET /v2/entertainment/random-song`
-- 随机发病文学: `GET /v2/entertainment/random-meme`
-- 随机搞笑段子: `GET /v2/entertainment/random-joke`
-- 随机冷笑话: `GET /v2/entertainment/random-pun`
-- 随机一言: `GET /v2/entertainment/random-quote`
-- 随机运势: `GET /v2/entertainment/random-fortune`
-- 随机JavaScript趣味题: `GET /v2/entertainment/random-js-quiz`
-- 随机KFC文案: `GET /v2/entertainment/random-kfc`
-
-## 状态管理
-
-### 用户状态管理
-使用 React Context 进行全局状态管理:
-
-```javascript
-const UserContext = createContext();
-
-export const UserProvider = ({ children }) => {
- const [user, setUser] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
- const [isLoggedIn, setIsLoggedIn] = useState(false);
-
- // 用户登录、登出、状态检查等方法
-};
-```
-
-### 本地存储
-- 用户信息和 Token 存储在 localStorage 中
-- 页面刷新后自动恢复用户状态
-
-## 路由设计
-
-```javascript
-const App = () => {
- return (
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
- );
-};
-```
-
-## 响应式设计
-
-- 移动优先设计理念
-- 使用 CSS Grid 和 Flexbox 实现响应式布局
-- 媒体查询适配不同屏幕尺寸
-- 移动端使用底部导航栏,桌面端使用顶部导航
-
-## 安全考虑
-
-### 前端安全措施
-- JWT Token 自动过期和刷新
-- XSS 防护:使用 React 自动转义
-- CSRF 防护:使用 SameSite Cookie
-- 输入验证:前端表单验证
-
-### API 安全要求
-- 所有敏感接口需要 JWT 认证
-- Token 存储在 localStorage,需要后端验证
-- 密码等敏感信息前端不存储明文
-- API 请求包含 CORS 配置
-
-## 部署和构建
-
-### 构建命令
```bash
-npm run build # 生产环境构建
-npm start # 开发环境启动
+cd infogenie-frontend
+npm install
+npm run dev
```
-### 环境变量
-- `REACT_APP_API_URL`: 后端 API 基础地址
-- 支持 `.env` 文件配置不同环境的变量
+默认 **http://localhost:3000**。环境变量见 `infogenie-frontend/.env.development`(`VITE_API_URL` 默认指向本机 Go **5002**)。
-### PWA 支持
-- 注册 Service Worker 实现离线缓存
-- Web App Manifest 支持安装到桌面
+### Go 后端(`infogenie-backend-go/`)
-## 与后端协作要点
-
-1. **API 接口约定**: 遵循 RESTful 设计原则,统一响应格式
-2. **错误处理**: 后端返回统一的错误格式,前端统一处理
-3. **认证流程**: JWT Token 的生成、验证和刷新机制
-4. **数据格式**: 前后端约定清晰的数据结构
-5. **跨域配置**: 后端需要配置 CORS 允许前端域名
-6. **API 版本管理**: 使用 `/v2/` 前缀进行版本控制
-7. **性能优化**: 考虑 API 响应时间和前端缓存策略
-
-## 萌芽币消费系统
-
-### 系统概述
-萌芽币是平台内部的虚拟货币,用于限制和管理用户对AI功能的使用频率。每次调用AI功能需消耗100萌芽币,当用户萌芽币不足时,无法使用AI功能。
-
-### 技术实现
-1. **萌芽币管理器**: `/public/aimodelapp/coin-manager.js`
- - 管理用户萌芽币余额和使用记录
- - 提供UI组件显示萌芽币信息
- - 实现API调用前的余额检查
-
-2. **API集成**:
- - 在 `/src/utils/api.js` 中添加萌芽币查询接口
- - 所有AI功能API调用必须添加JWT Token认证
- - API调用后自动刷新萌芽币余额显示
-
-3. **用户体验**:
- - 在页面右上角显示萌芽币余额和使用记录
- - 当萌芽币不足时,提供友好的提示
- - 引导用户通过签到等方式获取更多萌芽币
-
-### 接口设计
-```http
-GET /api/aimodelapp/coins
-Authorization: Bearer
-
-响应:
-{
- "success": true,
- "data": {
- "coins": 200,
- "ai_cost": 100,
- "can_use_ai": true,
- "username": "用户名",
- "usage_count": 5,
- "recent_usage": [
- {
- "api_type": "chat",
- "cost": 100,
- "timestamp": "2025-09-16T11:15:47.285720"
- },
- ...
- ]
- },
- "message": "当前萌芽币余额: 200"
-}
+```bash
+cd infogenie-backend-go
+# 按需配置 .env.development(APP_ENV、DB_*、可选 REDIS_*)
+go run ./cmd/server
```
-### 页面集成流程
-1. 引入萌芽币管理器 JavaScript 文件
-2. 在API调用前检查萌芽币余额
-3. 处理API响应中的萌芽币相关错误
-4. API调用后刷新萌芽币信息
+默认监听 **http://127.0.0.1:5002**。健康检查:**GET /api/health**。
-详细集成步骤请参考 [前端萌芽币消费系统集成文档](/前端萌芽币消费系统集成文档.md)
+### Java 后端(可选)
-## 技术架构亮点
+```bash
+cd infogenie-backend-java
+mvn spring-boot:run
+```
-### 1. 混合SPA与静态页面的创新架构
+---
-InfoGenie 采用了创新的混合架构设计,将 React SPA 与大量静态 HTML 页面无缝集成,充分发挥两者的优势:
-- **React SPA 核心层**:处理用户认证、全局状态管理和主要导航逻辑,确保一致的用户体验
-- **静态 HTML 模块**:大量功能模块(API数据展示、小游戏、AI工具)使用原生HTML/CSS/JS实现,降低加载时间和资源消耗
-- **通信机制**:通过 postMessage API 和共享环境配置实现 SPA 与静态页面的数据交换和状态同步
+## 环境变量要点
-### 2. 前端性能优化策略
+| 模块 | 说明 |
+|------|------|
+| 前端 | `VITE_API_URL`、`VITE_AUTH_URL`、`VITE_AUTH_API_URL`、`VITE_DEBUG`(见前端文档) |
+| Go | `APP_ENV`、`APP_PORT`、`DB_*`、`INFOGENIE_SITE_ADMIN_TOKEN`、可选 `REDIS_*`、`AUTH_CENTER_API_URL` 等(见后端文档) |
-项目实现了全面的性能优化,确保在各种设备上的流畅体验:
-- **代码分割**:基于路由的动态导入,减少首屏加载时间
-- **资源预加载**:关键资源预加载与懒加载策略结合
-- **缓存策略**:通过 Service Worker 实现静态资源和API响应的智能缓存
-- **性能监控**:页面加载性能关键指标(FCP、LCP、CLS)的实时监控与分析
+**不要**将含真实密码的 `.env` 提交到版本库。
-### 3. 模块化与组件设计
+---
-采用高度模块化的组件设计模式,提升代码可维护性和扩展性:
-- **原子设计系统**:从原子级别组件到页面级别的多层组件设计体系
-- **状态与UI分离**:清晰的关注点分离,提高组件复用性
-- **统一样式系统**:基于 styled-components 的主题化设计系统
-- **组件文档化**:关键组件的详细使用说明和示例代码
+## 对外能力摘要(Go)
-### 4. 安全与用户体验融合
+- **用户**:JWT 校验;登录由萌芽认证中心完成;`/api/user/profile` 等。
+- **站点**:60s / AI 显隐、60s 上游、功能卡片点击统计;管理员 `X-Site-Admin-Token` 写入配置。
+- **AI**:`/api/aimodelapp/chat`、`/chat/stream`(SSE)及多个垂直能力;模型白名单在后端维护。
+- **可观测性**:`/api/health`;管理员 **`/api/admin/site/diagnostics`**(连接摘要,无密码明文)。
-通过创新的用户体验设计,在保证安全性的同时提供流畅体验:
-- **无感刷新认证**:JWT Token 自动刷新机制,避免频繁登录
-- **智能错误处理**:集中式的错误处理系统,提供用户友好的错误提示
-- **渐进式数据加载**:重要数据优先加载,提升感知性能
-- **离线访问支持**:关键功能支持离线访问,增强用户体验
+---
-## 后续扩展建议
+## 许可证与备注
-1. **状态管理升级**: 可考虑引入 Redux 或 Zustand 进行更复杂的状态管理
-2. **组件库**: 可引入 Ant Design 或 Material-UI 统一 UI 组件
-3. **测试覆盖**: 添加单元测试和集成测试,提高代码质量和可靠性
- - Jest 与 React Testing Library 进行组件测试
- - Cypress 进行端到端测试
- - MSW 进行 API 模拟测试
-4. **性能监控**: 集成 Lighthouse CI 和 Web Vitals 进行性能监控
-5. **国际化**: 使用 i18next 支持多语言切换功能
-6. **萌芽币系统扩展**:
- - 实现萌芽币充值功能
- - 针对不同AI功能设置差异化定价
- - 添加萌芽币消费统计和分析功能
-7. **微前端架构**: 考虑将大型功能模块转换为微前端架构,提高扩展性和团队协作效率
-8. **构建优化**: 实施更先进的构建优化策略,如 Tree Shaking、代码分割、资源压缩等
-9. **渐进式Web应用升级**: 强化PWA能力,增强离线使用体验和桌面安装功能
-
-## 技术债务与优化建议
-
-### 1. 代码质量与一致性
-
-当前项目在开发过程中出现了一些不一致的编码风格和实践,需要通过以下措施进行标准化:
-
-- **代码规范统一**: 引入 ESLint + Prettier 强制执行统一的代码风格
-- **类型安全增强**: 考虑引入 TypeScript 或 PropTypes 进行类型检查,降低运行时错误
-- **代码审查流程**: 建立明确的代码审查规范和流程,确保代码质量
-- **文档完善**: 补充关键功能模块和核心组件的详细文档
-
-### 2. 架构优化与重构
-
-以下是需要关注的架构层面优化点:
-
-- **API 层抽象**: 当前的 API 调用过于分散,建议引入统一的 API 请求层和数据模型层
-- **组件结构优化**: 部分组件承担过多职责,应按照单一职责原则进行拆分
-- **状态管理重构**: 当前 Context API 的使用在大型应用中可能导致过度渲染,考虑引入更高效的状态管理方案
-- **静态资源优化**: 优化图像、字体等静态资源的加载策略,减少页面加载时间
-
-### 3. 前端基础设施升级
-
-为提高开发效率和项目可维护性,建议升级以下基础设施:
-
-- **现代化构建工具**: 考虑从 Create React App 迁移至 Vite,提升开发和构建速度
-- **自动化测试框架**: 建立单元测试、集成测试和 E2E 测试的完整体系
-- **CI/CD 流程**: 优化持续集成和部署流程,实现更敏捷的开发和发布
-- **前端监控系统**: 引入错误跟踪和性能监控系统,主动发现并解决问题
-
-## 前端技术栈演进路线
-
-### 近期优化(0-3个月)
-
-1. **架构文档完善**: 补充架构决策记录(ADR)和技术选型依据
-2. **代码质量工具集成**: ESLint、Prettier、Husky 配置统一
-3. **性能优化第一阶段**: 首屏加载优化、资源懒加载实现
-4. **核心组件库重构**: 提取可复用组件,建立组件文档
-
-### 中期规划(3-6个月)
-
-1. **TypeScript 迁移**: 核心模块向 TypeScript 迁移
-2. **状态管理升级**: 评估并引入更适合的状态管理方案
-3. **自动化测试覆盖**: 关键功能的单元测试和集成测试实现
-4. **微前端架构设计**: 设计微前端架构方案,逐步迁移大型功能模块
-
-### 长期展望(6-12个月)
-
-1. **新技术栈评估与升级**: 评估 React 18+ 新特性应用
-2. **前端国际化支持**: 实现多语言和本地化支持
-3. **无障碍访问标准实现**: 符合 WCAG 2.1 AA 级标准
-4. **性能指标达成**: 优化应用性能,使所有关键指标达到业界标准
\ No newline at end of file
+具体许可证以前端 `package.json` 与各子项目声明为准。若文档与代码不一致,以当前代码与 **`后端文档.md` / `前端文档.md`** 为准。
diff --git a/infogenie-backend-go/.env.development b/infogenie-backend-go/.env.development
deleted file mode 100644
index aaa7a512..00000000
--- a/infogenie-backend-go/.env.development
+++ /dev/null
@@ -1,30 +0,0 @@
-# InfoGenie Go Backend - 开发环境配置
-APP_ENV=development
-APP_PORT=5002
-
-# MySQL 测试数据库
-DB_HOST=10.1.1.100
-DB_PORT=3306
-DB_NAME=infogenie-test
-DB_USER=infogenie-test
-DB_PASSWORD=infogenie-test
-
-# JWT
-JWT_SECRET=d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8
-JWT_EXPIRE_DAYS=7
-
-# 邮件服务
-MAIL_HOST=smtp.qiye.aliyun.com
-MAIL_PORT=465
-MAIL_USERNAME=notice@smyhub.com
-MAIL_PASSWORD=tyh@19900420
-
-# AI 配置文件路径
-AI_CONFIG_PATH=ai_config.json
-
-# 萌芽账户认证中心
-AUTH_CENTER_API_URL=https://auth.api.shumengya.top
-AUTH_CENTER_ADMIN_TOKEN=
-
-# 站点前台配置(与前端管理员口令一致,用于保存 60s 功能展示开关)
-INFOGENIE_SITE_ADMIN_TOKEN=shumengya520
diff --git a/infogenie-backend-go/.env.example b/infogenie-backend-go/.env.example
new file mode 100644
index 00000000..d24f1c2e
--- /dev/null
+++ b/infogenie-backend-go/.env.example
@@ -0,0 +1,22 @@
+APP_ENV=development
+APP_PORT=5002
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_NAME=infogenie-test
+DB_USER=infogenie-test
+DB_PASSWORD=
+JWT_SECRET=
+JWT_EXPIRE_DAYS=7
+MAIL_HOST=
+MAIL_PORT=465
+MAIL_USERNAME=
+MAIL_PASSWORD=
+AUTH_CENTER_API_URL=https://auth.api.shumengya.top
+AUTH_CENTER_ADMIN_TOKEN=
+INFOGENIE_SITE_ADMIN_TOKEN=
+# REDIS_ENABLED=false
+# REDIS_ADDR=127.0.0.1:6379
+# REDIS_PASSWORD=
+# REDIS_DB=10
+# REDIS_KEY_PREFIX=infogenie:go:v1:
+# REDIS_SITE_TTL=60
diff --git a/infogenie-backend-go/.gitignore b/infogenie-backend-go/.gitignore
index ce98d634..6cf90272 100644
--- a/infogenie-backend-go/.gitignore
+++ b/infogenie-backend-go/.gitignore
@@ -1,3 +1,4 @@
-# 含数据库密码、SMTP 等,勿提交
-.env.production
-.env.local
+# 含数据库密码、SMTP、JWT、Redis 等,勿提交
+.env
+.env.*
+!.env.example
diff --git a/infogenie-backend-go/ai_config.json b/infogenie-backend-go/ai_config.json
deleted file mode 100644
index 05b14b91..00000000
--- a/infogenie-backend-go/ai_config.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "deepseek": {
- "api_key": "sk-832f8e5250464de08a31523c7fd712",
- "api_base": "https://api.deepseek.com",
- "model": ["deepseek-chat","deepseek-reasoner"]
- },
-
- "kimi": {
- "api_key": "sk-zdg9NBpTlhOcDDpoWfaBKu0KNDdGv18SipORnL2utawja",
- "api_base": "https://api.moonshot.cn",
- "model": ["kimi-k2-0905-preview","kimi-k2-0711-preview"]
- }
-}
\ No newline at end of file
diff --git a/infogenie-backend-go/cmd/server/main.go b/infogenie-backend-go/cmd/server/main.go
index b5ce6c0e..d31a4c98 100644
--- a/infogenie-backend-go/cmd/server/main.go
+++ b/infogenie-backend-go/cmd/server/main.go
@@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin"
"infogenie-backend/config"
+ "infogenie-backend/internal/cache"
"infogenie-backend/internal/database"
"infogenie-backend/internal/router"
)
@@ -23,6 +24,13 @@ func main() {
log.Fatalf("数据库初始化失败: %v", err)
}
+ if err := cache.Init(cfg.Redis); err != nil {
+ log.Fatalf("Redis 初始化失败: %v", err)
+ }
+ if cfg.Redis.Enabled {
+ log.Printf("Redis 已启用: %s DB=%d prefix=%s TTL=%s", cfg.Redis.Addr, cfg.Redis.DB, cfg.Redis.KeyPrefix, cfg.Redis.SiteTTL)
+ }
+
if err := database.AutoMigrate(); err != nil {
log.Fatalf("数据库迁移失败: %v", err)
}
diff --git a/infogenie-backend-go/config/config.go b/infogenie-backend-go/config/config.go
index 684d11ae..cc1d68a0 100644
--- a/infogenie-backend-go/config/config.go
+++ b/infogenie-backend-go/config/config.go
@@ -5,6 +5,7 @@ import (
"os"
"strconv"
"strings"
+ "time"
"github.com/joho/godotenv"
)
@@ -17,10 +18,21 @@ type AppConfig struct {
Mail MailConfig
AI AIConfig
AuthCenter AuthCenterConfig
+ Redis RedisConfig
// SiteAdminToken 与前端管理员口令一致,用于更新站点展示配置(如 60s 功能开关);为空则禁止写入
SiteAdminToken string
}
+// RedisConfig 可选缓存;Enabled 为 false 时不连接 Redis,行为与未接入缓存时一致
+type RedisConfig struct {
+ Enabled bool
+ Addr string
+ Password string
+ DB int
+ KeyPrefix string
+ SiteTTL time.Duration
+}
+
type DBConfig struct {
Host string
Port string
@@ -67,6 +79,10 @@ const (
defaultDevDBName = "infogenie-test"
defaultDevDBUser = "infogenie-test"
defaultDevDBPassword = "infogenie-test"
+
+ defaultDevRedisAddr = "10.1.1.100:6379"
+ defaultRedisDB = 10
+ defaultRedisKeyPrefix = "infogenie:go:v1:"
)
func Load() (*AppConfig, error) {
@@ -129,6 +145,12 @@ func Load() (*AppConfig, error) {
return nil, err
}
+ redisCfg, err := loadRedisConfig(env)
+ if err != nil {
+ return nil, err
+ }
+ cfg.Redis = redisCfg
+
// AI配置现在完全从数据库读取,不再加载ai_config.json文件
cfg.AI = AIConfig{Providers: make(map[string]AIProviderConfig)}
@@ -136,6 +158,53 @@ func Load() (*AppConfig, error) {
return cfg, nil
}
+func loadRedisConfig(env string) (RedisConfig, error) {
+ if !parseBoolEnv(os.Getenv("REDIS_ENABLED")) {
+ return RedisConfig{}, nil
+ }
+ var addr string
+ var err error
+ if env == envProduction {
+ addr, err = getEnvByEnvironment(env, "REDIS_ADDR", "")
+ if err != nil {
+ return RedisConfig{}, err
+ }
+ } else {
+ addr = getEnv("REDIS_ADDR", defaultDevRedisAddr)
+ }
+ if strings.TrimSpace(addr) == "" {
+ return RedisConfig{}, fmt.Errorf("已启用 REDIS_ENABLED 但 REDIS_ADDR 为空")
+ }
+ dbIdx, err := strconv.Atoi(strings.TrimSpace(getEnv("REDIS_DB", strconv.Itoa(defaultRedisDB))))
+ if err != nil || dbIdx < 0 {
+ return RedisConfig{}, fmt.Errorf("无效的 REDIS_DB: %s", getEnv("REDIS_DB", ""))
+ }
+ ttlSec, err := strconv.Atoi(strings.TrimSpace(getEnv("REDIS_SITE_TTL", "60")))
+ if err != nil || ttlSec < 1 {
+ return RedisConfig{}, fmt.Errorf("无效的 REDIS_SITE_TTL: %s", getEnv("REDIS_SITE_TTL", ""))
+ }
+ prefix := strings.TrimSpace(getEnv("REDIS_KEY_PREFIX", defaultRedisKeyPrefix))
+ if prefix == "" {
+ prefix = defaultRedisKeyPrefix
+ }
+ if !strings.HasSuffix(prefix, ":") {
+ prefix += ":"
+ }
+ return RedisConfig{
+ Enabled: true,
+ Addr: addr,
+ Password: getEnv("REDIS_PASSWORD", ""),
+ DB: dbIdx,
+ KeyPrefix: prefix,
+ SiteTTL: time.Duration(ttlSec) * time.Second,
+ }, nil
+}
+
+func parseBoolEnv(raw string) bool {
+ s := strings.ToLower(strings.TrimSpace(raw))
+ return s == "1" || s == "true" || s == "yes" || s == "on"
+}
+
func loadEnvFile(env string) error {
envFile := fmt.Sprintf(".env.%s", env)
if _, err := os.Stat(envFile); err == nil {
diff --git a/infogenie-backend-go/go.mod b/infogenie-backend-go/go.mod
index 07ff732c..30a214cf 100644
--- a/infogenie-backend-go/go.mod
+++ b/infogenie-backend-go/go.mod
@@ -2,26 +2,33 @@ module infogenie-backend
go 1.25.0
+require (
+ github.com/gin-contrib/cors v1.7.6
+ github.com/gin-gonic/gin v1.12.0
+ github.com/joho/godotenv v1.5.1
+ github.com/redis/go-redis/v9 v9.18.0
+ gorm.io/driver/mysql v1.6.0
+ gorm.io/gorm v1.31.1
+)
+
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
- github.com/gin-contrib/cors v1.7.6 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
- github.com/gin-gonic/gin v1.12.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
- github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
- github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@@ -34,12 +41,11 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
+ go.uber.org/atomic v1.11.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
- gorm.io/driver/mysql v1.6.0 // indirect
- gorm.io/gorm v1.31.1 // indirect
)
diff --git a/infogenie-backend-go/go.sum b/infogenie-backend-go/go.sum
index 931d2a78..9e6f29f0 100644
--- a/infogenie-backend-go/go.sum
+++ b/infogenie-backend-go/go.sum
@@ -1,15 +1,24 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
@@ -18,6 +27,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@@ -30,8 +41,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
-github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
-github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@@ -54,11 +65,14 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
+github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
+github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -68,12 +82,20 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
+github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
+github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
+go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
@@ -89,6 +111,7 @@ google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aO
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
diff --git a/infogenie-backend-go/internal/cache/redis.go b/infogenie-backend-go/internal/cache/redis.go
new file mode 100644
index 00000000..63653e7c
--- /dev/null
+++ b/infogenie-backend-go/internal/cache/redis.go
@@ -0,0 +1,119 @@
+package cache
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/redis/go-redis/v9"
+
+ "infogenie-backend/config"
+)
+
+// Key suffixes(完整 key = KeyPrefix + suffix)
+const (
+ KeySite60sDisabled = "site:60s-disabled"
+ KeySite60sSource = "site:60s-source"
+ KeySiteAIModelDisabled = "site:ai-model-disabled"
+)
+
+func KeySiteFeatureClicks(section string) string {
+ return "site:feature-clicks:" + section
+}
+
+var (
+ rdb *redis.Client
+ keyPrefix string
+ siteCacheTTL time.Duration
+ redisOn bool
+)
+
+// Init 在 REDIS_ENABLED 时连接并 Ping;未启用时为空操作
+func Init(rc config.RedisConfig) error {
+ if !rc.Enabled {
+ redisOn = false
+ rdb = nil
+ return nil
+ }
+ redisOn = true
+ keyPrefix = rc.KeyPrefix
+ siteCacheTTL = rc.SiteTTL
+ if siteCacheTTL <= 0 {
+ siteCacheTTL = 60 * time.Second
+ }
+ rdb = redis.NewClient(&redis.Options{
+ Addr: rc.Addr,
+ Password: rc.Password,
+ DB: rc.DB,
+ })
+ if err := rdb.Ping(context.Background()).Err(); err != nil {
+ return fmt.Errorf("redis: %w", err)
+ }
+ return nil
+}
+
+// Enabled 表示已初始化且客户端可用
+func Enabled() bool {
+ return redisOn && rdb != nil
+}
+
+func fullKey(suffix string) string {
+ return keyPrefix + suffix
+}
+
+// GetJSON 命中返回 true;未启用或未命中返回 false(err 仅表示 Redis/JSON 异常)
+func GetJSON(ctx context.Context, suffix string, dest interface{}) (bool, error) {
+ if !Enabled() {
+ return false, nil
+ }
+ val, err := rdb.Get(ctx, fullKey(suffix)).Bytes()
+ if err == redis.Nil {
+ return false, nil
+ }
+ if err != nil {
+ return false, err
+ }
+ if err := json.Unmarshal(val, dest); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// SetJSON ttl<=0 时使用配置的 SiteTTL
+func SetJSON(ctx context.Context, suffix string, v interface{}, ttl time.Duration) error {
+ if !Enabled() {
+ return nil
+ }
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+ if ttl <= 0 {
+ ttl = siteCacheTTL
+ }
+ return rdb.Set(ctx, fullKey(suffix), b, ttl).Err()
+}
+
+// Delete 删除若干后缀对应的 key;失败仅打日志
+func Delete(ctx context.Context, suffixes ...string) {
+ if !Enabled() || len(suffixes) == 0 {
+ return
+ }
+ keys := make([]string, 0, len(suffixes))
+ for _, s := range suffixes {
+ keys = append(keys, fullKey(s))
+ }
+ if err := rdb.Del(ctx, keys...).Err(); err != nil {
+ log.Printf("redis DEL 失败: %v keys=%v", err, keys)
+ }
+}
+
+// Ping 未启用时返回 nil
+func Ping(ctx context.Context) error {
+ if !Enabled() {
+ return nil
+ }
+ return rdb.Ping(ctx).Err()
+}
diff --git a/infogenie-backend-go/internal/handler/feature_card_clicks.go b/infogenie-backend-go/internal/handler/feature_card_clicks.go
index c36ea0e8..a62a46e5 100644
--- a/infogenie-backend-go/internal/handler/feature_card_clicks.go
+++ b/infogenie-backend-go/internal/handler/feature_card_clicks.go
@@ -1,11 +1,13 @@
package handler
import (
+ "log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
+ "infogenie-backend/internal/cache"
"infogenie-backend/internal/database"
"infogenie-backend/internal/model"
)
@@ -43,6 +45,19 @@ func (h *SiteConfigHandler) GetFeatureCardClicks(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_section"})
return
}
+ ctx := c.Request.Context()
+ key := cache.KeySiteFeatureClicks(section)
+ var cached struct {
+ Section string `json:"section"`
+ Counts map[string]uint64 `json:"counts"`
+ }
+ if hit, err := cache.GetJSON(ctx, key, &cached); err != nil {
+ log.Printf("redis GET %s: %v", key, err)
+ } else if hit {
+ c.JSON(http.StatusOK, gin.H{"section": cached.Section, "counts": cached.Counts})
+ return
+ }
+
var rows []model.SiteFeatureCardClick
if err := database.DB.Where("section = ?", section).Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
@@ -52,7 +67,11 @@ func (h *SiteConfigHandler) GetFeatureCardClicks(c *gin.Context) {
for _, r := range rows {
counts[r.ItemID] = r.ClickCount
}
- c.JSON(http.StatusOK, gin.H{"section": section, "counts": counts})
+ payload := gin.H{"section": section, "counts": counts}
+ if err := cache.SetJSON(ctx, key, payload, 0); err != nil {
+ log.Printf("redis SET %s: %v", key, err)
+ }
+ c.JSON(http.StatusOK, payload)
}
type postFeatureCardClickBody struct {
@@ -91,5 +110,6 @@ ON DUPLICATE KEY UPDATE click_count = click_count + 1, updated_at = NOW()`
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
+ cache.Delete(c.Request.Context(), cache.KeySiteFeatureClicks(section))
c.JSON(http.StatusOK, gin.H{"section": section, "item_id": itemID, "count": row.ClickCount})
}
diff --git a/infogenie-backend-go/internal/handler/siteconfig.go b/infogenie-backend-go/internal/handler/siteconfig.go
index 40a64334..ce5ef56e 100644
--- a/infogenie-backend-go/internal/handler/siteconfig.go
+++ b/infogenie-backend-go/internal/handler/siteconfig.go
@@ -3,6 +3,7 @@ package handler
import (
"crypto/subtle"
"errors"
+ "log"
"net/http"
"strings"
@@ -10,6 +11,7 @@ import (
"gorm.io/gorm"
"infogenie-backend/config"
+ "infogenie-backend/internal/cache"
"infogenie-backend/internal/database"
"infogenie-backend/internal/model"
)
@@ -31,6 +33,17 @@ func siteAdminTokenOK(headerToken string) bool {
// Get60sDisabled 公开:返回当前隐藏的 60s 功能 id 列表(与前端 item.id 对应)
func (h *SiteConfigHandler) Get60sDisabled(c *gin.Context) {
+ ctx := c.Request.Context()
+ var cached struct {
+ Disabled []string `json:"disabled"`
+ }
+ if hit, err := cache.GetJSON(ctx, cache.KeySite60sDisabled, &cached); err != nil {
+ log.Printf("redis GET %s: %v", cache.KeySite60sDisabled, err)
+ } else if hit {
+ c.JSON(http.StatusOK, gin.H{"disabled": cached.Disabled})
+ return
+ }
+
var rows []model.Site60sDisabled
if err := database.DB.Order("feature_id").Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
@@ -40,7 +53,11 @@ func (h *SiteConfigHandler) Get60sDisabled(c *gin.Context) {
for _, r := range rows {
ids = append(ids, r.FeatureID)
}
- c.JSON(http.StatusOK, gin.H{"disabled": ids})
+ payload := gin.H{"disabled": ids}
+ if err := cache.SetJSON(ctx, cache.KeySite60sDisabled, payload, 0); err != nil {
+ log.Printf("redis SET %s: %v", cache.KeySite60sDisabled, err)
+ }
+ c.JSON(http.StatusOK, payload)
}
type put60sDisabledBody struct {
@@ -102,6 +119,7 @@ func (h *SiteConfigHandler) Put60sDisabled(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
+ cache.Delete(c.Request.Context(), cache.KeySite60sDisabled)
c.JSON(http.StatusOK, gin.H{"ok": true, "count": len(clean)})
}
@@ -146,14 +164,35 @@ func EffectiveSixtyUpstream(db *gorm.DB) (sourceID string, base string, label st
// Get60sSource 公开:当前站点使用的 60s 上游 base_url(供静态页 iframe 传参)
func (h *SiteConfigHandler) Get60sSource(c *gin.Context) {
+ ctx := c.Request.Context()
+ var cached struct {
+ SourceID string `json:"source_id"`
+ BaseURL string `json:"base_url"`
+ Label string `json:"label"`
+ }
+ if hit, err := cache.GetJSON(ctx, cache.KeySite60sSource, &cached); err != nil {
+ log.Printf("redis GET %s: %v", cache.KeySite60sSource, err)
+ } else if hit {
+ c.JSON(http.StatusOK, gin.H{
+ "source_id": cached.SourceID,
+ "base_url": cached.BaseURL,
+ "label": cached.Label,
+ })
+ return
+ }
+
var row model.Site60sUpstream
_ = database.DB.First(&row, 1).Error
sid, info := resolve60sUpstream(row.SourceID)
- c.JSON(http.StatusOK, gin.H{
+ payload := gin.H{
"source_id": sid,
"base_url": info.Base,
"label": info.Label,
- })
+ }
+ if err := cache.SetJSON(ctx, cache.KeySite60sSource, payload, 0); err != nil {
+ log.Printf("redis SET %s: %v", cache.KeySite60sSource, err)
+ }
+ c.JSON(http.StatusOK, payload)
}
type put60sSourceBody struct {
@@ -195,6 +234,7 @@ func (h *SiteConfigHandler) Put60sSource(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
+ cache.Delete(c.Request.Context(), cache.KeySite60sSource)
_, info := resolve60sUpstream(sid)
c.JSON(http.StatusOK, gin.H{"ok": true, "source_id": sid, "base_url": info.Base, "label": info.Label})
}
@@ -203,6 +243,17 @@ func (h *SiteConfigHandler) Put60sSource(c *gin.Context) {
// GetAIModelDisabled 公开:返回当前隐藏的 AI 应用 id 列表(与前端 StaticPageConfig 中 AI_MODEL_APPS 的索引对应)
func (h *SiteConfigHandler) GetAIModelDisabled(c *gin.Context) {
+ ctx := c.Request.Context()
+ var cached struct {
+ Disabled []string `json:"disabled"`
+ }
+ if hit, err := cache.GetJSON(ctx, cache.KeySiteAIModelDisabled, &cached); err != nil {
+ log.Printf("redis GET %s: %v", cache.KeySiteAIModelDisabled, err)
+ } else if hit {
+ c.JSON(http.StatusOK, gin.H{"disabled": cached.Disabled})
+ return
+ }
+
var rows []model.SiteAIModelDisabled
if err := database.DB.Order("app_id").Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
@@ -212,7 +263,11 @@ func (h *SiteConfigHandler) GetAIModelDisabled(c *gin.Context) {
for _, r := range rows {
ids = append(ids, r.AppID)
}
- c.JSON(http.StatusOK, gin.H{"disabled": ids})
+ payload := gin.H{"disabled": ids}
+ if err := cache.SetJSON(ctx, cache.KeySiteAIModelDisabled, payload, 0); err != nil {
+ log.Printf("redis SET %s: %v", cache.KeySiteAIModelDisabled, err)
+ }
+ c.JSON(http.StatusOK, payload)
}
type putAIModelDisabledBody struct {
@@ -274,5 +329,63 @@ func (h *SiteConfigHandler) PutAIModelDisabled(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
+ cache.Delete(c.Request.Context(), cache.KeySiteAIModelDisabled)
c.JSON(http.StatusOK, gin.H{"ok": true, "count": len(clean)})
}
+
+// GetDiagnostics 返回当前进程解析到的连接配置摘要(不含密码/密钥明文),需 X-Site-Admin-Token
+func (h *SiteConfigHandler) GetDiagnostics(c *gin.Context) {
+ if config.Cfg == nil || strings.TrimSpace(config.Cfg.SiteAdminToken) == "" {
+ c.JSON(http.StatusServiceUnavailable, gin.H{
+ "error": "admin_not_configured",
+ "message": "服务端未配置 INFOGENIE_SITE_ADMIN_TOKEN",
+ })
+ return
+ }
+ if !siteAdminTokenOK(c.GetHeader("X-Site-Admin-Token")) {
+ c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
+ return
+ }
+ cfg := config.Cfg
+
+ sixtyID, sixtyBase, sixtyLabel := EffectiveSixtyUpstream(database.DB)
+
+ redisOut := gin.H{"enabled": cfg.Redis.Enabled}
+ if cfg.Redis.Enabled {
+ redisOut["addr"] = cfg.Redis.Addr
+ redisOut["logical_db"] = cfg.Redis.DB
+ redisOut["key_prefix"] = cfg.Redis.KeyPrefix
+ redisOut["site_cache_ttl_sec"] = int(cfg.Redis.SiteTTL.Seconds())
+ redisOut["password_configured"] = strings.TrimSpace(cfg.Redis.Password) != ""
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "app": gin.H{
+ "env": cfg.Env,
+ "listen_addr": "0.0.0.0:" + cfg.Port,
+ "listen_port": cfg.Port,
+ },
+ "mysql": gin.H{
+ "host": cfg.DB.Host,
+ "port": cfg.DB.Port,
+ "database": cfg.DB.Name,
+ "user": cfg.DB.User,
+ "password_configured": strings.TrimSpace(cfg.DB.Password) != "",
+ },
+ "redis": redisOut,
+ "sixty_upstream": gin.H{
+ "source_id": sixtyID,
+ "base_url": sixtyBase,
+ "label": sixtyLabel,
+ },
+ "auth_center": gin.H{
+ "api_url": cfg.AuthCenter.APIURL,
+ },
+ "mail": gin.H{
+ "host": cfg.Mail.Host,
+ "port": cfg.Mail.Port,
+ "username": cfg.Mail.Username,
+ "password_configured": strings.TrimSpace(cfg.Mail.Password) != "",
+ },
+ })
+}
diff --git a/infogenie-backend-go/internal/router/router.go b/infogenie-backend-go/internal/router/router.go
index 7ebf813e..45472af3 100644
--- a/infogenie-backend-go/internal/router/router.go
+++ b/infogenie-backend-go/internal/router/router.go
@@ -9,6 +9,8 @@ import (
"github.com/gin-gonic/gin"
+ "infogenie-backend/config"
+ "infogenie-backend/internal/cache"
"infogenie-backend/internal/database"
"infogenie-backend/internal/handler"
"infogenie-backend/internal/middleware"
@@ -91,6 +93,15 @@ func Setup(r *gin.Engine) {
overall = "degraded"
}
+ redisHealth := gin.H{"enabled": false}
+ if config.Cfg != nil && config.Cfg.Redis.Enabled {
+ if err := cache.Ping(ctx); err != nil {
+ redisHealth = gin.H{"enabled": true, "ok": false, "error": err.Error()}
+ } else {
+ redisHealth = gin.H{"enabled": true, "ok": true}
+ }
+ }
+
c.JSON(http.StatusOK, gin.H{
"status": overall,
"timestamp": time.Now().Format(time.RFC3339),
@@ -99,6 +110,7 @@ func Setup(r *gin.Engine) {
"ok": mysqlOK,
"status": dbStatus,
},
+ "redis": redisHealth,
"backend_api": gin.H{
"ok": true,
},
@@ -136,6 +148,7 @@ func Setup(r *gin.Engine) {
r.PUT("/api/admin/site/ai-model-disabled", siteH.PutAIModelDisabled)
r.GET("/api/admin/site/ai-runtime", aiRtH.GetAIRuntime)
r.PUT("/api/admin/site/ai-runtime", aiRtH.PutAIRuntime)
+ r.GET("/api/admin/site/diagnostics", siteH.GetDiagnostics)
ai := r.Group("/api/aimodelapp")
{
diff --git a/infogenie-backend-go/scripts/redis_keyspace/main.go b/infogenie-backend-go/scripts/redis_keyspace/main.go
new file mode 100644
index 00000000..4039f243
--- /dev/null
+++ b/infogenie-backend-go/scripts/redis_keyspace/main.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/redis/go-redis/v9"
+)
+
+// 用法: go run . [password]
+// 例: go run . 10.1.1.100:6379
+func main() {
+ addr := "10.1.1.100:6379"
+ pass := os.Getenv("REDIS_PASSWORD")
+ if len(os.Args) >= 2 {
+ addr = os.Args[1]
+ }
+ if len(os.Args) >= 3 {
+ pass = os.Args[2]
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
+ defer cancel()
+
+ r := redis.NewClient(&redis.Options{Addr: addr, Password: pass})
+ defer r.Close()
+ if err := r.Ping(ctx).Err(); err != nil {
+ fmt.Fprintf(os.Stderr, "连接失败 %s: %v\n", addr, err)
+ os.Exit(1)
+ }
+
+ s, err := r.Info(ctx, "keyspace").Result()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "INFO keyspace: %v\n", err)
+ os.Exit(1)
+ }
+
+ used := make(map[int]int64)
+ for _, line := range strings.Split(s, "\r\n") {
+ line = strings.TrimSpace(line)
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+ if !strings.HasPrefix(line, "db") {
+ continue
+ }
+ colon := strings.IndexByte(line, ':')
+ if colon <= 2 {
+ continue
+ }
+ dbStr := line[2:colon]
+ dbNum, err := strconv.Atoi(dbStr)
+ if err != nil {
+ continue
+ }
+ var keys int64
+ for _, part := range strings.Split(line[colon+1:], ",") {
+ part = strings.TrimSpace(part)
+ if strings.HasPrefix(part, "keys=") {
+ keys, _ = strconv.ParseInt(strings.TrimPrefix(part, "keys="), 10, 64)
+ break
+ }
+ }
+ used[dbNum] = keys
+ }
+
+ fmt.Printf("Redis %s — 逻辑库 key 统计(INFO keyspace)\n\n", addr)
+ for i := 0; i < 16; i++ {
+ k, ok := used[i]
+ if !ok {
+ fmt.Printf(" db%-2d : 无记录(通常为 0 个 key / 未写入过)\n", i)
+ } else if k == 0 {
+ fmt.Printf(" db%-2d : 0 keys\n", i)
+ } else {
+ fmt.Printf(" db%-2d : %d keys (已占用)\n", i, k)
+ }
+ }
+ fmt.Println()
+ fmt.Println("说明: Redis 默认 16 个逻辑库(0-15);「无记录」一般可当空闲选用。")
+ fmt.Println("若 redis.conf 里 databases=N,实际库数量可能更大,可用 redis-cli CONFIG GET databases 查看。")
+}
diff --git a/infogenie-backend-go/后端文档.md b/infogenie-backend-go/后端文档.md
index 5606857e..bca45331 100644
--- a/infogenie-backend-go/后端文档.md
+++ b/infogenie-backend-go/后端文档.md
@@ -1,117 +1,173 @@
-# 万象口袋 — Go 后端文档
-
-**技术栈**:Go 1.25+ · Gin · GORM · MySQL
-
-**模块路径**:`infogenie-backend`(见 `go.mod`)
-
-**入口**:`cmd/server/main.go` — 加载配置、连接数据库、`AutoMigrate`、启动 HTTP 服务。
-
----
-
-## 运行与配置
-
-- 环境由 **`APP_ENV`** 决定:`development` 或 `production`(见 `config.Load()`)。
-- 若存在 **`.env.development`** / **`.env.production`**,会通过 `godotenv` 加载对应文件。
-- **`APP_PORT`** 默认 **5002**(与前端 `REACT_APP_API_URL` 开发默认一致)。
-- 数据库、邮件、认证中心、`INFOGENIE_SITE_ADMIN_TOKEN` 等从环境变量读取,详见 `config/config.go`。
-
-**健康检查**:`GET /api/health` — 返回服务状态与数据库 `Ping` 结果。
-
-**根路径**:`GET /` — 返回服务说明与主要 endpoint 分组(`version` 当前为 **3.3.0-go**)。
-
----
-
-## 数据库(GORM AutoMigrate)
-
-启动时会迁移以下模型(见 `internal/database/mysql.go`):
-
-| 模型 | 用途 |
-|------|------|
-| `AIConfig` | 多厂商 AI Key / Base / 模型列表(如 deepseek、kimi) |
-| `Site60sDisabled` | 60s 功能在前端隐藏的 `feature_id` |
-| `SiteAIRuntime` | DeepSeek 兼容网关(Base + Key + 默认模型),优先级高于部分 AIConfig |
-| `Site60sUpstream` | 60s 上游节点(单例 id=1) |
-| `SiteAIModelDisabled` | AI 应用在前端隐藏的 `app_id` |
-| `SiteFeatureCardClick` | 四大板块功能卡片点击统计(`section` + `item_id` 联合主键) |
-
----
-
-## 路由概览(`internal/router/router.go`)
-
-### CORS
-
-全局 `middleware.CORS()`,放行常用 Method/Header(含 `Authorization`、`X-Site-Admin-Token`)。
-
-### 认证与用户
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| GET | `/api/auth/check` | 可选 JWT:校验登录态 |
-| GET | `/api/user/profile` | **需 JWT**:用户资料 |
-
-实际登录、发 token 由 **萌芽账户认证中心** 完成;后端校验 JWT。
-
-### 站点公开配置(无需登录)
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| GET | `/api/site/60s-disabled` | 被隐藏的 60s `feature_id` 列表 |
-| GET | `/api/site/60s-source` | 60s 上游 `source_id` / `base_url` |
-| GET | `/api/site/ai-model-disabled` | 被隐藏的 AI 应用 id 列表 |
-| GET | `/api/site/feature-card-clicks?section=` | 功能卡片点击次数(见下) |
-| POST | `/api/site/feature-card-clicks/increment` | 上报一次点击,返回最新 count |
-
-**`section` 合法值**:`60sapi` · `smallgame` · `toolbox` · `aimodel`
-
-**increment 请求体**:`{ "section": "...", "item_id": "..." }`
-
-### 站点管理(需 `X-Site-Admin-Token`,与环境变量 `INFOGENIE_SITE_ADMIN_TOKEN` 一致)
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| PUT | `/api/admin/site/60s-disabled` | 更新 60s 隐藏列表 |
-| PUT | `/api/admin/site/60s-source` | 切换 60s 上游 |
-| PUT | `/api/admin/site/ai-model-disabled` | 更新 AI 应用隐藏列表 |
-| GET/PUT | `/api/admin/site/ai-runtime` | 读取/更新 DeepSeek 兼容运行时配置 |
-
-### AI 应用(`/api/aimodelapp`,默认 **需 JWT**)
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| POST | `/chat` | 非流式对话,JSON 返回全文 |
-| POST | `/chat/stream` | **SSE 流式**:透传上游 OpenAI 兼容流(`text/event-stream`) |
-| POST | `/name-analysis` 等 | 各垂直能力(姓名、变量命名、写诗、翻译等) |
-| GET | `/models` | 模型列表 |
-
-**流式说明**(`internal/handler/aimodel.go` + `internal/service/ai.go`):
-
-- 上游请求带 `stream: true`,成功后将上游 body **分块写入并 Flush** 到客户端。
-- 支持 **deepseek**(运行时或 `AIConfig`)与 **kimi**(`AIConfig`)。
-- 与 `/chat` 共用同一套 `bindAIModelChat` 校验(消息条数、长度、模型白名单等)。
-
-**模型白名单**:见 `internal/handler/aimodel.go` 中 `allowedModels`(如 deepseek-chat、deepseek-reasoner、部分 kimi 模型)。
-
----
-
-## 核心源码目录
-
-```
-cmd/server/ # main
-config/ # 配置加载
-internal/
- database/ # MySQL 初始化、AutoMigrate
- handler/ # HTTP 处理器(auth、user、aimodel、siteconfig、ai_runtime、feature_card_clicks)
- middleware/ # CORS、JWT
- model/ # GORM 模型
- router/ # 路由注册
- service/ # AI 调用(含 OpenAI 兼容非流式与流式)
-```
-
----
-
-## 与其他工程的关系
-
-- **前端 SPA** 通过 `REACT_APP_API_URL` 指向本服务(开发默认 `http://127.0.0.1:5002`)。
-- **`public/aimodelapp/*/shared/ai-chat.js`** 优先调用 `/api/aimodelapp/chat/stream`,失败时回退 `/chat`。
-
-更完整的前端集成说明见 **`infogenie-frontend/前端文档.md`**。
+# 万象口袋 — Go 后端文档
+
+**技术栈**:Go 1.25+ · Gin · GORM · MySQL ·(可选)Redis(go-redis v9)
+
+**模块路径**:`infogenie-backend`(见 `go.mod`)
+
+**入口**:`cmd/server/main.go` — 加载配置、连接 MySQL、`cache.Init`(若启用 Redis)、`AutoMigrate`、启动 HTTP 服务。
+
+---
+
+## 运行与配置
+
+- 环境由 `**APP_ENV`** 决定:`development` 或 `production`(见 `config.Load()`)。
+- 若存在 `**.env.development**` / `**.env.production**`,会通过 `godotenv` 加载对应文件。
+- `**APP_PORT**` 默认 **5002**(与前端 `VITE_API_URL` 开发默认一致)。
+- 数据库、邮件、认证中心、`INFOGENIE_SITE_ADMIN_TOKEN`、可选 Redis 等从环境变量读取,详见 `config/config.go`。
+
+### Redis(可选)
+
+
+| 变量 | 说明 |
+| ------------------ | --------------------------------------------------------- |
+| `REDIS_ENABLED` | `true` / `1` / `yes` / `on` 时启用;未启用则不连 Redis,站点接口直连 MySQL |
+| `REDIS_ADDR` | `host:port`;生产环境必填;开发未设时默认 `10.1.1.100:6379` |
+| `REDIS_PASSWORD` | 可选 |
+| `REDIS_DB` | 逻辑库编号,默认 **10**(与 `db0` 等业务隔离) |
+| `REDIS_KEY_PREFIX` | Key 前缀,默认 `infogenie:go:v1:`(可自动补 `:`) |
+| `REDIS_SITE_TTL` | 站点类 JSON 缓存 TTL(秒),默认 **60** |
+
+
+启用时启动阶段会 **Ping**;失败则进程退出。站点只读接口对 `60s-disabled`、`60s-source`、`ai-model-disabled`、`feature-card-clicks` 等做 cache-aside,管理端写入成功后删对应 Key(见 `internal/cache/redis.go` 与 `internal/handler/siteconfig.go`、`feature_card_clicks.go`)。
+
+---
+
+## 健康与诊断
+
+### `GET /api/health`(公开)
+
+- `**status`**:`running` 或 `degraded`(MySQL 未连通 **或** 60s 上游探测失败时为 degraded;**不**因 Redis 失败而 degraded)。
+- `**mysql`**:`ok`、`status`(`connected` / `disconnected` / `not_initialized`)。
+- `**sixty_api**`:当前生效的上游 `source_id`、`base_url`、`label`,以及对 `…/v2/ip` 的探测结果(`probe_url`、`http_status`、`latency_ms`、`error`)。
+- `**redis**`:未启用时 `{ "enabled": false }`;启用时 `{ "enabled": true, "ok": bool, "error"?: string }`。
+
+### `GET /api/admin/site/diagnostics`(需管理员)
+
+请求头 `**X-Site-Admin-Token**` 须与 `**INFOGENIE_SITE_ADMIN_TOKEN**` 一致。
+
+返回**不含密码明文**,仅连接与进程摘要,供运维/后台展示:
+
+
+| 字段 | 内容 |
+| ---------------- | ------------------------------------------------------------------------------------------- |
+| `app` | `env`、`listen_addr`(`0.0.0.0:端口`)、`listen_port` |
+| `mysql` | `host`、`port`、`database`、`user`、`password_configured` |
+| `redis` | `enabled`;若启用则含 `addr`、`logical_db`、`key_prefix`、`site_cache_ttl_sec`、`password_configured` |
+| `sixty_upstream` | 库内当前 60s 节点 `source_id` / `base_url` / `label` |
+| `auth_center` | `api_url` |
+| `mail` | SMTP `host`、`port`、`username`、`password_configured` |
+
+
+---
+
+**根路径**:`GET /` — 返回服务说明与主要 endpoint 分组(`version` 当前为 **3.3.0-go**)。
+
+---
+
+## 数据库(GORM AutoMigrate)
+
+启动时会迁移以下模型(见 `internal/database/mysql.go`):
+
+
+| 模型 | 用途 |
+| ---------------------- | ------------------------------------------------- |
+| `AIConfig` | 多厂商 AI Key / Base / 模型列表(如 deepseek、kimi) |
+| `Site60sDisabled` | 60s 功能在前端隐藏的 `feature_id` |
+| `SiteAIRuntime` | DeepSeek 兼容网关(Base + Key + 默认模型),优先级高于部分 AIConfig |
+| `Site60sUpstream` | 60s 上游节点(单例 id=1) |
+| `SiteAIModelDisabled` | AI 应用在前端隐藏的 `app_id` |
+| `SiteFeatureCardClick` | 四大板块功能卡片点击统计(`section` + `item_id` 联合主键) |
+
+
+---
+
+## 路由概览(`internal/router/router.go`)
+
+### CORS
+
+全局 `middleware.CORS()`,放行常用 Method/Header(含 `Authorization`、`X-Site-Admin-Token`)。
+
+### 认证与用户
+
+
+| 方法 | 路径 | 说明 |
+| --- | ------------------- | -------------- |
+| GET | `/api/auth/check` | 可选 JWT:校验登录态 |
+| GET | `/api/user/profile` | **需 JWT**:用户资料 |
+
+
+实际登录、发 token 由 **萌芽账户认证中心** 完成;后端校验 JWT。
+
+### 站点公开配置(无需登录)
+
+
+| 方法 | 路径 | 说明 |
+| ---- | ----------------------------------------- | ------------------------------- |
+| GET | `/api/site/60s-disabled` | 被隐藏的 60s `feature_id` 列表 |
+| GET | `/api/site/60s-source` | 60s 上游 `source_id` / `base_url` |
+| GET | `/api/site/ai-model-disabled` | 被隐藏的 AI 应用 id 列表 |
+| GET | `/api/site/feature-card-clicks?section=` | 功能卡片点击次数(见下) |
+| POST | `/api/site/feature-card-clicks/increment` | 上报一次点击,返回最新 count |
+
+
+`**section` 合法值**:`60sapi` · `smallgame` · `toolbox` · `aimodel`
+
+**increment 请求体**:`{ "section": "...", "item_id": "..." }`
+
+### 站点管理(需 `X-Site-Admin-Token`,与环境变量 `INFOGENIE_SITE_ADMIN_TOKEN` 一致)
+
+
+| 方法 | 路径 | 说明 |
+| ------- | ----------------------------------- | ---------------------- |
+| PUT | `/api/admin/site/60s-disabled` | 更新 60s 隐藏列表 |
+| PUT | `/api/admin/site/60s-source` | 切换 60s 上游 |
+| PUT | `/api/admin/site/ai-model-disabled` | 更新 AI 应用隐藏列表 |
+| GET/PUT | `/api/admin/site/ai-runtime` | 读取/更新 DeepSeek 兼容运行时配置 |
+| GET | `/api/admin/site/diagnostics` | 连接与进程配置快照(不含密钥明文) |
+
+
+### AI 应用(`/api/aimodelapp`,默认 **需 JWT**)
+
+
+| 方法 | 路径 | 说明 |
+| ---- | ------------------ | ----------------------------------------------- |
+| POST | `/chat` | 非流式对话,JSON 返回全文 |
+| POST | `/chat/stream` | **SSE 流式**:透传上游 OpenAI 兼容流(`text/event-stream`) |
+| POST | `/name-analysis` 等 | 各垂直能力(姓名、变量命名、写诗、翻译等) |
+| GET | `/models` | 模型列表 |
+
+
+**流式说明**(`internal/handler/aimodel.go` + `internal/service/ai.go`):
+
+- 上游请求带 `stream: true`,成功后将上游 body **分块写入并 Flush** 到客户端。
+- 支持 **deepseek**(运行时或 `AIConfig`)与 **kimi**(`AIConfig`)。
+- 与 `/chat` 共用同一套 `bindAIModelChat` 校验(消息条数、长度、模型白名单等)。
+
+**模型白名单**:见 `internal/handler/aimodel.go` 中 `allowedModels`(如 deepseek-chat、deepseek-reasoner、部分 kimi 模型)。
+
+---
+
+## 核心源码目录
+
+```
+cmd/server/ # main
+config/ # 配置加载(含 Redis)
+internal/
+ cache/ # Redis 可选封装(Get/Set JSON、Delete、Ping)
+ database/ # MySQL 初始化、AutoMigrate
+ handler/ # HTTP 处理器(auth、user、aimodel、siteconfig、ai_runtime、feature_card_clicks)
+ middleware/ # CORS、JWT
+ model/ # GORM 模型
+ router/ # 路由注册(含 /api/health、diagnostics)
+ service/ # AI 调用(含 OpenAI 兼容非流式与流式)
+scripts/redis_keyspace/ # 可选:本地查看各逻辑库 keyspace(go run,需 REDIS_PASSWORD)
+```
+
+---
+
+## 与其他工程的关系
+
+- **前端 SPA** 通过 `VITE_API_URL` 指向本服务(开发默认 `http://127.0.0.1:5002`)。
+- `**public/aimodelapp/*/shared/ai-chat.js`** 优先调用 `/api/aimodelapp/chat/stream`,失败时回退 `/chat`。
+
+更完整的前端集成说明见 `**infogenie-frontend/前端文档.md**`。
\ No newline at end of file
diff --git a/万象口袋项目经历.md b/万象口袋项目经历.md
index 7fbed010..019b0ca2 100644
--- a/万象口袋项目经历.md
+++ b/万象口袋项目经历.md
@@ -1,61 +1,6 @@
-# 万象口袋项目-跨平台一站式信息聚合式软件 面试资料
-
-**项目名称**:万象口袋项目-跨平台一站式信息聚合式软件
-**时间**:2025.08 - 2026.01
-**技术栈**:Golang,Gin,MySQL,React、
-**技术栈**:Java,SpringBoot,MySQL,React、
-**描述**:跨平台一站式信息聚合式软件
-**上线地址**:https://infogenie.shumengya.top
-**开源地址**:https://github.com/shumengya/infogenie
-
-**本地实现技术栈(当前仓库版本)**
-- 前端:React 18.2.0、React Router DOM 6.15.0、Styled Components 6.0.7、Axios 1.5.0、React Hot Toast、React Icons、Create React App
-- 前端架构:React Context API(用户态管理)、混合架构(SPA + public 静态HTML模块)、PWA(Service Worker 离线缓存与 SWR 策略)
-- 后端:Go 1.25、Gin 1.12、GORM 1.31 + MySQL、gin-contrib/cors、go-playground/validator、joho/godotenv
-- 后端能力:外部认证中心 JWT 校验与用户态透传、AI供应商(DeepSeek/Kimi) OpenAI兼容接口调用、并发扣费互斥与重试回退、站点展示配置与AI上游运行时管理
-- 数据库:MySQL(AIUsageRecord、Site60sDisabled、Site60sUpstream、SiteAIRuntime 等表,GORM 自动迁移)
-- 工程化:后端 Dockerfile + docker-compose,.env 多环境配置;前端 CRA 构建与 Service Worker 预缓存
-
-## 技术特点总结(可用于简历)
-- 前后端分离与RESTful:Gin 路由分组与中间件清晰,统一 JSON 响应;前端 Axios 拦截器统一鉴权与错误提示
-- 混合前端架构:React SPA 与 public 静态HTML模块并存,静态页通过 iframe + window.parent 环境注入与本地 token 传递,兼顾体验与加载速度
-- 认证与用户态:对接外部认证中心,后端中间件验证 JWT 并透传用户态;前端 Context 管理登录态与本地持久化
-- AI接口与计费:DeepSeek/Kimi OpenAI兼容调用、超时与指数回退;并发扣费互斥锁、扣费失败与余额不足的严格返回码
-- 站点运行时配置:管理员口令保护的 60s 展示开关与数据源切换、AI上游 base/key/model 运行时配置写入数据库并脱敏展示
-- 安全与合规:CORS 策略受控、敏感配置环境变量化、后端仅在开发打印基础设施地址;管理员端采用常量口令 + 服务端令牌比对
-- 性能优化:PWA 预缓存入口与 runtime 缓存、SW 导航离线回退、静态模块直出;前端渐变背景与轻量组件保持流畅交互
-- 工程化与部署:后端 Dockerfile + docker-compose 支持;前端 CRA 标准构建流程与静态资源缓存策略;多环境 .env
-
-## 架构设计亮点
-- SPA核心层:React Router v6 管理路由;UserContext 统一登录态;Navigation/Header/Footer 组成稳定布局
-- 静态模块:60sAPI/小游戏/工具箱大量原生HTML/CSS/JS模块,FullscreenEmbed 全屏嵌入并注入 token
-- 站点后台:AdminPage 管理 60s 展示开关、数据源与 AI 上游;与后端 X-Site-Admin-Token 双向校验
-- 后端分层:router/middleware/handler/service/database/model 分层清晰;GORM 自动迁移与表结构约束
-- 可靠性:AI 调用指数退避重试、健康检查检测数据库连接状态、并发扣费互斥锁规避竞态
-
-## 关键功能模块
-- 60s聚合信息展示:周期资讯/热门榜单/实用功能/消遣娱乐分类配置;支持节点切换(self/official),静态页按 itemId 映射
-- 小游戏集合:2048、俄罗斯方块、别踩白方块、贪吃蛇、扫雷、跑酷、打飞机、FloppyBird、网页魔方等,移动端优化即点即玩
-- AI工具集:变量命名、写诗、姓名评测、语言翻译、文章转文言文、表情生成、Linux命令、Markdown排版、亲戚称呼计算器
-- 认证与用户:认证中心接入,后端 JWT 验证并透传用户资料;前端登录回调与本地存储持久化
-- 计费与统计:每次 AI 调用扣除 100 萌芽币、扣费通过管理员接口更新认证中心;记录 AIUsageRecord 便于统计
-
-## 安全与性能实践
-- 认证与保护:后端 JWT 验证中间件、管理员令牌常量 + 服务端校验;并发扣费互斥锁避免竞态
-- 配置安全:环境变量/.env 管理敏感信息,管理员口令与服务端令牌一致性校验;AI Key 脱敏回显与保留策略
-- 前端性能:PWA 预缓存 + runtime SWR、离线导航回退、静态模块直出;Axios 错误统一提示与 401 清理会话
-- 后端健壮:AI 调用超时与指数退避、健康检查检测数据库连接状态、GORM 连接池与生命周期管理
-
-## 部署与运维
-- 前端:npm run build 标准构建,Service Worker 自动预缓存入口;支持静态资源离线加载
-- 后端:Dockerfile + docker-compose 一键编排,环境变量控制端口与数据库;GORM 自动迁移表结构
-- 可观测性:AIUsageRecord 与站点配置表为运营与审计提供基础数据;脚本与工具支撑静态页一致性校验
-
-## 个人职责与成果(示例措辞)
-- 推动 React SPA + 静态HTML 混合架构落地,封装 FullscreenEmbed 组件实现 token 注入与全屏嵌入
-- 设计后端 Gin 分层与中间件体系,接入外部认证中心,完成 JWT 验证与用户态透传
-- 实现 AI 调用链路(DeepSeek/Kimi),加入指数回退与超时控制;并发扣费互斥锁与扣费回滚提示
-- 搭建后端 Docker 与 compose,GORM 自动迁移与连接池优化;完善 .env 多环境配置
-- 开发管理员后台:60s 展示开关、数据源切换、AI 上游运行时配置(密钥脱敏与保留策略)
-- 完成 PWA 缓存策略与离线回退、Axios 拦截器统一错误提示,保证移动端与弱网下的可用性
-
+- **项目名称**:万象口袋- 跨平台信息聚合网站
+- **时间**:2025.08 - 至今
+- **技术栈**:Golang,Gin,GORM,MySQL,Redis,React,Vite,Tailwind,Docker
+- **描述**:产品定位为一站式资讯与工具导航:首页与栏目以配置驱动,整合 60s 类 API 浏览、休闲小游戏、实用工具箱及多款 AI 应用(后端 OpenAI 格式兼容与 SSE 流式输出)。后端采用 Go + Gin + GORM,MySQL 自动迁移存站点开关;可选接入 Redis 对高频只读配置做 cache-aside,独立逻辑库与 Key 前缀避免污染其他业务。提供聚合健康检查与仅管理员可调用的诊断快照接口。前端基于 React 与 Vite,Tailwind 统一视觉,axios 封装与路由守卫;对接萌芽认证中心 JWT,管理员后台支持 AI 上游、前台显隐配置及运行状态监控。支持多环境变量与开发/生产数据库隔离约束,兼顾可运维与交付效率。
+- **项目地址**:[https://infogenie.smyhub.com](https://infogenie.smyhub.com) **|** **开源地址**:[https://github.com/shumengya/infogenie](https://github.com/shumengya/infogenie)
+