阶段更新

This commit is contained in:
2026-07-14 10:09:24 +08:00
parent 3da6718729
commit cc9f6c4d71
13 changed files with 1262 additions and 446 deletions
+11 -11
View File
@@ -1,11 +1,11 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
// biome-ignore lint: disable // biome-ignore lint: disable
export {} export {}
declare global { declare global {
const ElMessage: typeof import('element-plus/es').ElMessage const ElMessage: typeof import('element-plus/es').ElMessage
const ElMessageBox: typeof import('element-plus/es').ElMessageBox const ElMessageBox: typeof import('element-plus/es').ElMessageBox
} }
+77 -77
View File
@@ -1,77 +1,77 @@
/* eslint-disable */ /* eslint-disable */
// @ts-nocheck // @ts-nocheck
// biome-ignore lint: disable // biome-ignore lint: disable
// oxlint-disable // oxlint-disable
// ------ // ------
// Generated by unplugin-vue-components // Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
export {} export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
CommonAside: typeof import('./src/components/CommonAside.vue')['default'] CommonAside: typeof import('./src/components/CommonAside.vue')['default']
CommonHeader: typeof import('./src/components/CommonHeader.vue')['default'] CommonHeader: typeof import('./src/components/CommonHeader.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert'] ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside'] ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBacktop: typeof import('element-plus/es')['ElBacktop'] ElBacktop: typeof import('element-plus/es')['ElBacktop']
ElBadge: typeof import('element-plus/es')['ElBadge'] ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader'] ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider'] ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage'] ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
MessageDetailDialog: typeof import('./src/components/MessageDetailDialog.vue')['default'] MessageDetailDialog: typeof import('./src/components/MessageDetailDialog.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }
export interface GlobalDirectives { export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective'] vLoading: typeof import('element-plus/es')['ElLoadingDirective']
} }
} }
@@ -1,313 +1,313 @@
# 记事本模块部署检查清单 # 记事本模块部署检查清单
## 📋 部署前检查 ## 📋 部署前检查
### 1. 数据库准备 ### 1. 数据库准备
- [ ] 执行 SQL 文件创建数据表 - [ ] 执行 SQL 文件创建数据表
```bash ```bash
mysql -u用户名 -p数据库名 < sql/yz_platform_notebook.sql mysql -u用户名 -p数据库名 < sql/yz_platform_notebook.sql
``` ```
- [ ] 验证表创建成功 - [ ] 验证表创建成功
```sql ```sql
SHOW TABLES LIKE 'yz_platform_notebook'; SHOW TABLES LIKE 'yz_platform_notebook';
DESC yz_platform_notebook; DESC yz_platform_notebook;
``` ```
### 2. 后端代码检查 ### 2. 后端代码检查
- [✅] 模型文件已创建: `go/models/platform_notebook.go` - [✅] 模型文件已创建: `go/models/platform_notebook.go`
- [✅] 模型已注册: `go/models/init.go` - [✅] 模型已注册: `go/models/init.go`
- [✅] 控制器已创建: `go/controllers/platform_notebook.go` - [✅] 控制器已创建: `go/controllers/platform_notebook.go`
- [✅] 路由已注册: `go/routers/platform/platform.go` - [✅] 路由已注册: `go/routers/platform/platform.go`
- [✅] 代码编译通过 - [✅] 代码编译通过
### 3. 前端代码检查 ### 3. 前端代码检查
- [✅] API文件已创建: `platform/src/api/notebook.js` - [✅] API文件已创建: `platform/src/api/notebook.js`
- [✅] 主页面已创建: `platform/src/views/apps/notebook/index.vue` - [✅] 主页面已创建: `platform/src/views/apps/notebook/index.vue`
- [✅] 编辑器组件已创建: `platform/src/views/apps/notebook/components/edit.vue` - [✅] 编辑器组件已创建: `platform/src/views/apps/notebook/components/edit.vue`
- [✅] WangEditor组件已创建: `platform/src/views/apps/notebook/components/WangEditor.vue` - [✅] WangEditor组件已创建: `platform/src/views/apps/notebook/components/WangEditor.vue`
### 4. 依赖检查 ### 4. 依赖检查
- [ ] 前端安装 WangEditor - [ ] 前端安装 WangEditor
```bash ```bash
cd platform cd platform
npm install @wangeditor/editor npm install @wangeditor/editor
# 或 # 或
yarn add @wangeditor/editor yarn add @wangeditor/editor
``` ```
### 5. 菜单配置 ### 5. 菜单配置
- [ ] 在平台管理后台添加记事本菜单项 - [ ] 在平台管理后台添加记事本菜单项
- 路径: `/apps/notebook` - 路径: `/apps/notebook`
- 组件: `apps/notebook/index.vue` - 组件: `apps/notebook/index.vue`
- 图标: `fa-solid fa-book` 或其他合适的图标 - 图标: `fa-solid fa-book` 或其他合适的图标
- 标题: `记事本``我的笔记` - 标题: `记事本``我的笔记`
## 🚀 部署步骤 ## 🚀 部署步骤
### 后端部署 ### 后端部署
1. **停止服务** 1. **停止服务**
```bash ```bash
# 如果服务正在运行,先停止 # 如果服务正在运行,先停止
pkill -f "go run main.go" pkill -f "go run main.go"
``` ```
2. **编译代码** 2. **编译代码**
```bash ```bash
cd go cd go
go build -o server go build -o server
``` ```
3. **启动服务** 3. **启动服务**
```bash ```bash
./server ./server
# 或使用后台运行 # 或使用后台运行
nohup ./server > server.log 2>&1 & nohup ./server > server.log 2>&1 &
``` ```
### 前端部署 ### 前端部署
1. **安装依赖** 1. **安装依赖**
```bash ```bash
cd platform cd platform
npm install npm install
# 或 # 或
yarn install yarn install
``` ```
2. **开发模式测试** 2. **开发模式测试**
```bash ```bash
npm run dev npm run dev
# 或 # 或
yarn dev yarn dev
``` ```
3. **生产构建** 3. **生产构建**
```bash ```bash
npm run build npm run build
# 或 # 或
yarn build yarn build
``` ```
## ✅ 功能测试 ## ✅ 功能测试
### 基础功能测试 ### 基础功能测试
1. **访问页面** 1. **访问页面**
- [ ] 能够正常访问记事本页面 - [ ] 能够正常访问记事本页面
- [ ] 页面布局正常显示 - [ ] 页面布局正常显示
- [ ] 左侧列表和右侧编辑器都能正常显示 - [ ] 左侧列表和右侧编辑器都能正常显示
2. **创建笔记** 2. **创建笔记**
- [ ] 点击"新建笔记"按钮 - [ ] 点击"新建笔记"按钮
- [ ] 输入标题和内容 - [ ] 输入标题和内容
- [ ] 点击"创建"按钮 - [ ] 点击"创建"按钮
- [ ] 创建成功,笔记出现在列表中 - [ ] 创建成功,笔记出现在列表中
3. **编辑笔记** 3. **编辑笔记**
- [ ] 点击列表中的笔记 - [ ] 点击列表中的笔记
- [ ] 笔记内容正确加载到编辑器 - [ ] 笔记内容正确加载到编辑器
- [ ] 修改标题和内容 - [ ] 修改标题和内容
- [ ] 点击"保存"按钮 - [ ] 点击"保存"按钮
- [ ] 保存成功,列表中的笔记信息更新 - [ ] 保存成功,列表中的笔记信息更新
4. **删除笔记** 4. **删除笔记**
- [ ] 点击笔记右侧的"更多"按钮 - [ ] 点击笔记右侧的"更多"按钮
- [ ] 点击"删除" - [ ] 点击"删除"
- [ ] 确认删除 - [ ] 确认删除
- [ ] 笔记从列表中移除 - [ ] 笔记从列表中移除
5. **搜索笔记** 5. **搜索笔记**
- [ ] 在搜索框输入关键词 - [ ] 在搜索框输入关键词
- [ ] 列表自动过滤显示匹配的笔记 - [ ] 列表自动过滤显示匹配的笔记
- [ ] 清空搜索框,显示所有笔记 - [ ] 清空搜索框,显示所有笔记
### 富文本编辑器测试 ### 富文本编辑器测试
1. **文本格式** 1. **文本格式**
- [ ] 加粗、斜体、下划线 - [ ] 加粗、斜体、下划线
- [ ] 标题(H1-H6 - [ ] 标题(H1-H6
- [ ] 字体颜色和背景色 - [ ] 字体颜色和背景色
2. **列表和引用** 2. **列表和引用**
- [ ] 有序列表 - [ ] 有序列表
- [ ] 无序列表 - [ ] 无序列表
- [ ] 引用块 - [ ] 引用块
3. **插入内容** 3. **插入内容**
- [ ] 插入链接 - [ ] 插入链接
- [ ] 插入代码块 - [ ] 插入代码块
- [ ] 插入表格 - [ ] 插入表格
4. **图片上传** (需要配置上传接口) 4. **图片上传** (需要配置上传接口)
- [ ] 点击图片按钮 - [ ] 点击图片按钮
- [ ] 选择图片文件 - [ ] 选择图片文件
- [ ] 图片正确上传并显示 - [ ] 图片正确上传并显示
### API测试 (使用浏览器控制台) ### API测试 (使用浏览器控制台)
```javascript ```javascript
// 1. 在浏览器控制台加载测试脚本 // 1. 在浏览器控制台加载测试脚本
// 将 test-api.js 的内容粘贴到控制台 // 将 test-api.js 的内容粘贴到控制台
// 2. 运行完整测试 // 2. 运行完整测试
await NotebookTest.runFullTest(); await NotebookTest.runFullTest();
// 3. 或单独测试各个功能 // 3. 或单独测试各个功能
await NotebookTest.create(); // 创建笔记 await NotebookTest.create(); // 创建笔记
await NotebookTest.list(); // 获取列表 await NotebookTest.list(); // 获取列表
await NotebookTest.detail(1); // 获取详情 await NotebookTest.detail(1); // 获取详情
await NotebookTest.update(1, {...}); // 更新笔记 await NotebookTest.update(1, {...}); // 更新笔记
await NotebookTest.delete(1); // 删除笔记 await NotebookTest.delete(1); // 删除笔记
``` ```
## 🐛 常见问题排查 ## 🐛 常见问题排查
### 1. 编译错误 ### 1. 编译错误
**问题**: `cannot use &claims.UserID (value of type *int) as *uint64` **问题**: `cannot use &claims.UserID (value of type *int) as *uint64`
**解决**: 已修复,使用类型转换 `userID := uint64(claims.UserID)` **解决**: 已修复,使用类型转换 `userID := uint64(claims.UserID)`
--- ---
**问题**: `undefined: PlatformNotebook` **问题**: `undefined: PlatformNotebook`
**解决**: 检查 `go/models/init.go` 中是否已注册模型 **解决**: 检查 `go/models/init.go` 中是否已注册模型
--- ---
### 2. 数据库错误 ### 2. 数据库错误
**问题**: 表不存在 **问题**: 表不存在
**解决**: **解决**:
```sql ```sql
-- 检查表是否存在 -- 检查表是否存在
SHOW TABLES LIKE 'yz_platform_notebook'; SHOW TABLES LIKE 'yz_platform_notebook';
-- 如果不存在,执行 SQL 文件 -- 如果不存在,执行 SQL 文件
SOURCE sql/yz_platform_notebook.sql; SOURCE sql/yz_platform_notebook.sql;
``` ```
--- ---
**问题**: 字段不存在 **问题**: 字段不存在
**解决**: 确认字段名使用 `create_time`, `update_time`, `delete_time` **解决**: 确认字段名使用 `create_time`, `update_time`, `delete_time`
--- ---
### 3. 前端错误 ### 3. 前端错误
**问题**: WangEditor 未定义 **问题**: WangEditor 未定义
**解决**: **解决**:
```bash ```bash
npm install @wangeditor/editor npm install @wangeditor/editor
``` ```
--- ---
**问题**: API 请求 401 未授权 **问题**: API 请求 401 未授权
**解决**: **解决**:
- 检查用户是否已登录 - 检查用户是否已登录
- 检查 JWT Token 是否有效 - 检查 JWT Token 是否有效
- 检查 Token 是否正确设置在请求头中 - 检查 Token 是否正确设置在请求头中
--- ---
**问题**: 笔记列表为空 **问题**: 笔记列表为空
**解决**: **解决**:
- 检查数据库中是否有数据 - 检查数据库中是否有数据
- 检查用户ID是否匹配 - 检查用户ID是否匹配
- 查看浏览器控制台和网络请求 - 查看浏览器控制台和网络请求
--- ---
### 4. 权限错误 ### 4. 权限错误
**问题**: 无法访问其他用户的笔记 **问题**: 无法访问其他用户的笔记
**说明**: 这是正常的,每个用户只能访问自己的笔记 **说明**: 这是正常的,每个用户只能访问自己的笔记
--- ---
## 📊 性能优化建议 ## 📊 性能优化建议
### 数据库优化 ### 数据库优化
1. 为常用查询字段添加索引(已添加) 1. 为常用查询字段添加索引(已添加)
- `user_id` - `user_id`
- `create_time` - `create_time`
- `is_deleted` - `is_deleted`
2. 定期清理软删除的数据 2. 定期清理软删除的数据
```sql ```sql
-- 删除30天前的软删除数据 -- 删除30天前的软删除数据
DELETE FROM yz_platform_notebook DELETE FROM yz_platform_notebook
WHERE is_deleted = 1 WHERE is_deleted = 1
AND delete_time < DATE_SUB(NOW(), INTERVAL 30 DAY); AND delete_time < DATE_SUB(NOW(), INTERVAL 30 DAY);
``` ```
### 前端优化 ### 前端优化
1. 列表分页加载(已实现) 1. 列表分页加载(已实现)
2. 内容预览截取(已实现) 2. 内容预览截取(已实现)
3. 懒加载编辑器组件 3. 懒加载编辑器组件
4. 防抖搜索功能(可选) 4. 防抖搜索功能(可选)
### 后端优化 ### 后端优化
1. 添加缓存层(Redis 1. 添加缓存层(Redis
2. 内容压缩存储 2. 内容压缩存储
3. 异步处理大文件 3. 异步处理大文件
4. API 限流保护 4. API 限流保护
## 🔐 安全建议 ## 🔐 安全建议
1. **内容安全** 1. **内容安全**
- 前端显示时做 XSS 过滤 - 前端显示时做 XSS 过滤
- 后端存储前做内容校验 - 后端存储前做内容校验
- 限制单篇笔记大小 - 限制单篇笔记大小
2. **访问控制** 2. **访问控制**
- JWT Token 验证(已实现) - JWT Token 验证(已实现)
- 用户权限校验(已实现) - 用户权限校验(已实现)
- API 频率限制 - API 频率限制
3. **数据备份** 3. **数据备份**
- 定期备份数据库 - 定期备份数据库
- 软删除机制(已实现) - 软删除机制(已实现)
- 版本控制(可选) - 版本控制(可选)
## 📝 后续功能扩展 ## 📝 后续功能扩展
- [ ] 笔记分类/文件夹 - [ ] 笔记分类/文件夹
- [ ] 笔记标签 - [ ] 笔记标签
- [ ] 笔记分享 - [ ] 笔记分享
- [ ] 导出功能(PDF/Markdown - [ ] 导出功能(PDF/Markdown
- [ ] 版本历史 - [ ] 版本历史
- [ ] 协作编辑 - [ ] 协作编辑
- [ ] 全文搜索 - [ ] 全文搜索
- [ ] 附件上传 - [ ] 附件上传
- [ ] 模板功能 - [ ] 模板功能
- [ ] 快捷键支持 - [ ] 快捷键支持
## ✨ 完成标志 ## ✨ 完成标志
- [✅] SQL 表创建成功 - [✅] SQL 表创建成功
- [✅] 后端代码编译通过 - [✅] 后端代码编译通过
- [✅] 前端页面正常访问 - [✅] 前端页面正常访问
- [ ] 所有功能测试通过 - [ ] 所有功能测试通过
- [ ] 无控制台错误 - [ ] 无控制台错误
- [ ] 性能表现良好 - [ ] 性能表现良好
## 📞 技术支持 ## 📞 技术支持
如遇到问题,请检查: 如遇到问题,请检查:
1. 浏览器控制台错误信息 1. 浏览器控制台错误信息
2. 后端服务日志 2. 后端服务日志
3. 数据库连接状态 3. 数据库连接状态
4. API 请求响应 4. API 请求响应
祝部署顺利!🎉 祝部署顺利!🎉
View File
+248
View File
@@ -0,0 +1,248 @@
# Go (Beego) 多租户二级域名系统部署与配置指南
本指南详细介绍在 **不改变你目前宝塔部署路径** 的前提下,如何配置 `yunzer.com.cn``dh2.fun` 及其所有二级域名的 Nginx,让它们能够直接访问并使用部署在 `www.yunzer.cn` 的官网前端静态模板。
---
## 🛠️ 第一步:宝塔物理部署路径规划(保持现状)
请确认或保持你目前在宝塔服务器上的物理目录部署,不需要进行任何文件移动:
1. **企业官网门户前台 (`frontend`)**
- 物理路径: `/www/wwwroot/www.yunzer.cn`
- 说明: 你的 `frontend` 编译打包文件平铺部署在此目录下(有 `index.html`)。
2. **平台运营管理后台 (`platform`)**
- 物理路径: `/www/wwwroot/platform.yunzer.cn`
3. **租户/商家管理后台 (`back`)**
- 物理路径: `/www/wwwroot/back.yunzer.cn`
4. **Go 后端 API 服务**
- 运行端口: 本地 `http://10.31.100.2:8081`
---
## 📄 第二步:修改 `yunzer.com.cn` 和 `dh2.fun` 的 Nginx 配置
为了绕过宝塔面板对 SSL 注释规则的保存验证限制,以下配置中**完全保留了宝塔原生生成的 `#SSL-START``#error_page 404/404.html;` 以及 `#SSL-END` 结构**。
请分别在宝塔面板对应站点的 **“配置文件”** 选项中,替换为以下内容:
### 1. `yunzer.com.cn` 的 Nginx 配置文件
```nginx
server {
listen 80;
listen 443 ssl;
listen 443 quic;
http2 on;
# 匹配主域名和所有租户的子域名
server_name yunzer.com.cn ~^(?<subdomain>.+)\.yunzer\.com\.cn$;
index index.html;
# 核心修改:直接指向部署了 frontend 的静态资源路径
root /www/wwwroot/www.yunzer.cn;
#CERT-APPLY-CHECK--START
include /www/server/panel/vhost/nginx/well-known/yunzer.com.cn.conf;
#CERT-APPLY-CHECK--END
include /www/server/panel/vhost/nginx/extension/yunzer.com.cn/*.conf;
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#HTTP_TO_HTTPS_START
set $isRedcert 1;
if ($server_port != 443) {
set $isRedcert 2;
}
if ( $uri ~ /\.well-known/ ) {
set $isRedcert 1;
}
if ($isRedcert != 1) {
rewrite ^(/.*)$ https://$host$1 permanent;
}
#HTTP_TO_HTTPS_END
ssl_certificate /www/server/panel/vhost/cert/yunzer.com.cn/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/yunzer.com.cn/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
#ERROR-PAGE-END
# --- 安全与防盗链拦截 START ---
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$ {
return 404;
}
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
return 404;
}
location ~ \.well-known {
allow all;
}
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
# --- 安全与防盗链拦截 END ---
# 静态资源缓存
location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~* \.(js|css)$ {
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
# --- Go API 反向代理及 Vue 路由支持 ---
# 1. 代理 /api/ 请求到本地运行在 8081 端口的 Go 服务
location /api/ {
proxy_pass http://10.31.100.2:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 2. 代理文件上传目录到 Go 静态服务
location /uploads/ {
proxy_pass http://10.31.100.2:8081/uploads/;
}
# 3. 前端单页面应用 (Vue Router) History 模式重定向
location / {
try_files $uri $uri/ /index.html;
}
access_log /www/wwwlogs/yunzer.com.cn.log;
error_log /www/wwwlogs/yunzer.com.cn.error.log;
}
```
### 2. `dh2.fun` 的 Nginx 配置文件
```nginx
server {
listen 80;
listen 443 ssl;
listen 443 quic;
http2 on;
# 匹配主域名和所有租户的子域名
server_name dh2.fun ~^(?<subdomain>.+)\.dh2\.fun$;
index index.html;
# 核心修改:直接指向部署了 frontend 的静态资源路径
root /www/wwwroot/www.yunzer.cn;
#CERT-APPLY-CHECK--START
include /www/server/panel/vhost/nginx/well-known/dh2.fun.conf;
#CERT-APPLY-CHECK--END
include /www/server/panel/vhost/nginx/extension/dh2.fun/*.conf;
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#HTTP_TO_HTTPS_START
set $isRedcert 1;
if ($server_port != 443) {
set $isRedcert 2;
}
if ( $uri ~ /\.well-known/ ) {
set $isRedcert 1;
}
if ($isRedcert != 1) {
rewrite ^(/.*)$ https://$host$1 permanent;
}
#HTTP_TO_HTTPS_END
ssl_certificate /www/server/panel/vhost/cert/dh2.fun/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/dh2.fun/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
#ERROR-PAGE-END
# 禁止访问的敏感文件
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$
{
return 404;
}
# 禁止访问的敏感目录
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
# --- Go API 反向代理及 Vue 路由支持 ---
location /api/ {
proxy_pass http://10.31.100.2:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /uploads/ {
proxy_pass http://10.31.100.2:8081/uploads/;
}
location / {
try_files $uri $uri/ /index.html;
}
}
```
+1 -1
View File
@@ -4,7 +4,7 @@ import { createComponentLoader } from '@/utils/pathResolver'
export const constantRoute = [ export const constantRoute = [
{ {
path: '/', path: '/',
component: () => import('@/views/theme/index.vue'), component: () => import('@/views/home/index.vue'),
name: '首页', name: '首页',
meta: { meta: {
title: '首页', title: '首页',
+396
View File
@@ -0,0 +1,396 @@
package controllers
import (
"strconv"
"strings"
"server/models"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// IndexPortalController 前台门户及租户官网公共接口(无需登录)
type IndexPortalController struct {
beego.Controller
}
// getTidByHost 根据访问域名识别租户 ID,默认返回 1
func (c *IndexPortalController) getTidByHost() uint64 {
host := c.Ctx.Request.Host
parts := strings.Split(host, ":")
domain := parts[0]
if domain == "localhost" || domain == "127.0.0.1" {
return 1 // 开发环境默认租户
}
var td models.SystemTenantDomain
err := models.Orm.QueryTable(new(models.SystemTenantDomain)).
Filter("full_domain", domain).
Filter("status", 1).
Filter("delete_time__isnull", true).
One(&td)
if err == nil && td.Tid != nil {
return *td.Tid
}
return 1 // 默认租户
}
// GetHeadMenu GET /index/headmenu
func (c *IndexPortalController) GetHeadMenu() {
// 前台的静态导航菜单结构
menuData := []map[string]interface{}{
{
"id": 1,
"title": "首页",
"path": "/",
"type": 2,
},
{
"id": 2,
"title": "解决方案",
"path": "/solutions",
"type": 2,
"children": []map[string]interface{}{
{
"id": 21,
"title": "行业解决方案",
"path": "/solutions/solution",
"type": 2,
},
{
"id": 22,
"title": "成功案例",
"path": "/solutions/successCases",
"type": 2,
},
},
},
{
"id": 3,
"title": "新闻中心",
"path": "/newsCenter",
"type": 2,
"children": []map[string]interface{}{
{
"id": 31,
"title": "站点新闻",
"path": "/newsCenter/news",
"type": 2,
},
{
"id": 32,
"title": "站点公告",
"path": "/newsCenter/announcement",
"type": 2,
},
{
"id": 33,
"title": "技术中心",
"path": "/newsCenter/technologyCenter",
"type": 2,
},
},
},
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": menuData,
}
_ = c.ServeJSON()
}
// GetFooterData GET /index/footerdata
func (c *IndexPortalController) GetFooterData() {
tid := c.getTidByHost()
// 准备默认的 Footer 配置数据
footerMap := map[string]string{
"sitename": "云泽官方网站",
"companyname": "云泽智能科技有限公司",
"copyright": "Copyright © 2026 Yunzer. All Rights Reserved.",
"icp": "苏ICP备2026000000号-1",
"description": "专业为企业建设数字化管理与官网门户的一站式 SaaS 服务平台",
"logo": "",
"logow": "",
"ico": "",
}
// 尝试从租户站点设置表中查询最新的配置
var rows []models.TenantSiteSetting
_, err := models.Orm.QueryTable(new(models.TenantSiteSetting)).
Filter("tid", tid).
Filter("delete_time__isnull", true).
Limit(1).
All(&rows)
if err == nil && len(rows) > 0 {
r := rows[0]
if r.Sitename != "" {
footerMap["sitename"] = r.Sitename
}
if r.Companyname != "" {
footerMap["companyname"] = r.Companyname
}
if r.Copyright != "" {
footerMap["copyright"] = r.Copyright
}
if r.Icp != "" {
footerMap["icp"] = r.Icp
}
if r.Description != "" {
footerMap["description"] = r.Description
}
footerMap["logo"] = r.Logo
footerMap["logow"] = r.Logow
footerMap["ico"] = r.Ico
}
// 将 Map 转换为前端需要的 [{label: "...", value: "..."}] 数组格式
var dataList []map[string]string
for k, v := range footerMap {
dataList = append(dataList, map[string]string{
"label": k,
"value": v,
})
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": dataList,
}
_ = c.ServeJSON()
}
// GetNewsCenterTop4 GET /index/newscentertop4
func (c *IndexPortalController) GetNewsCenterTop4() {
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
var articles []models.CmsArticle
_, err := models.Orm.QueryTable(new(models.CmsArticle)).
Filter("tid", tid).
Filter("status", 1).
Filter("delete_time__isnull", true).
OrderBy("-top", "-recommend", "-id").
Limit(4).
All(&articles)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 200, "data": []interface{}{}}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": articles,
}
_ = c.ServeJSON()
}
// queryCmsArticles 统一的分页查询文章辅助方法
func (c *IndexPortalController) queryCmsArticles(categoryName string, categoryID int64) {
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
page, _ := c.GetInt("page", 1)
limit, _ := c.GetInt("limit", 10)
if page < 1 {
page = 1
}
if limit < 1 {
limit = 10
}
qs := models.Orm.QueryTable(new(models.CmsArticle)).
Filter("tid", tid).
Filter("status", 1).
Filter("delete_time__isnull", true)
// 如果有传具体的分类 ID
if categoryID > 0 {
qs = qs.Filter("cate_id", categoryID)
}
total, _ := qs.Count()
var articles []models.CmsArticle
_, err := qs.OrderBy("-top", "-id").
Limit(limit, (page-1)*limit).
All(&articles)
if err != nil {
c.Data["json"] = map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"list": []interface{}{},
"total": 0,
},
}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"list": articles,
"total": total,
},
}
_ = c.ServeJSON()
}
// GetCompanyNews GET /index/companynews
func (c *IndexPortalController) GetCompanyNews() {
// 可根据实际需要分配固定的分类 ID
c.queryCmsArticles("companynews", 0)
}
// GetKingdeeNews GET /index/kingdeenews
func (c *IndexPortalController) GetKingdeeNews() {
c.queryCmsArticles("kingdeenews", 0)
}
// GetTechnologyCenter GET /index/technologyCenter
func (c *IndexPortalController) GetTechnologyCenter() {
categoryID, _ := c.GetInt64("category_id", 0)
c.queryCmsArticles("technologyCenter", categoryID)
}
// GetTechnologyCategories GET /index/technologyCategories
func (c *IndexPortalController) GetTechnologyCategories() {
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
var categories []models.CmsArticleCategory
_, err := models.Orm.QueryTable(new(models.CmsArticleCategory)).
Filter("tid", tid).
Filter("status", 1).
Filter("delete_time__isnull", true).
OrderBy("sort", "id").
All(&categories)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 200, "data": []interface{}{}}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": categories,
}
_ = c.ServeJSON()
}
// GetCompanyNewsDetail GET /index/companynews/detail/:id
func (c *IndexPortalController) GetCompanyNewsDetail() {
c.getArticleDetail()
}
// GetKingdeeNewsDetail GET /index/kingdeenews/detail/:id
func (c *IndexPortalController) GetKingdeeNewsDetail() {
c.getArticleDetail()
}
// getArticleDetail 查询文章详情的内部辅助方法
func (c *IndexPortalController) getArticleDetail() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
var article models.CmsArticle
err := models.Orm.QueryTable(new(models.CmsArticle)).
Filter("id", id).
Filter("tid", tid).
Filter("delete_time__isnull", true).
One(&article)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "文章不存在"}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": article,
}
_ = c.ServeJSON()
}
// AddLike POST /index/articleLikes/:id
func (c *IndexPortalController) AddLike() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
_, _ = models.Orm.QueryTable(new(models.CmsArticle)).
Filter("id", id).
Filter("tid", tid).
Update(orm.Params{
"likes": orm.ColValue(orm.ColAdd, 1),
})
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "点赞成功"}
_ = c.ServeJSON()
}
// ReduceLike POST /index/articleUnlikes/:id
func (c *IndexPortalController) ReduceLike() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
_, _ = models.Orm.QueryTable(new(models.CmsArticle)).
Filter("id", id).
Filter("tid", tid).
Filter("likes__gt", 0).
Update(orm.Params{
"likes": orm.ColValue(orm.ColMinus, 1),
})
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "已取消点赞"}
_ = c.ServeJSON()
}
// AddViews POST /index/views/:id
func (c *IndexPortalController) AddViews() {
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
tid := c.getTidByHost()
_ = models.EnsureCmsArticleTables()
_, _ = models.Orm.QueryTable(new(models.CmsArticle)).
Filter("id", id).
Filter("tid", tid).
Update(orm.Params{
"views": orm.ColValue(orm.ColAdd, 1),
})
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "阅读量+1"}
_ = c.ServeJSON()
}
// GetOnePageByPath GET /index/onepage/:path
func (c *IndexPortalController) GetOnePageByPath() {
// 简单返回空数据,防止前端单页接口报错
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": nil,
}
_ = c.ServeJSON()
}
+144 -9
View File
@@ -401,41 +401,168 @@ func batchAddPoolRows(c *beego.Controller, module string) {
return return
} }
} }
var accounts []string
var tokens []string
seenAccs := make(map[string]bool)
seenTks := make(map[string]bool)
for _, row := range payload.Rows { for _, row := range payload.Rows {
acc := strings.TrimSpace(row.Account)
tk := strings.TrimSpace(row.Token)
if acc != "" && !seenAccs[acc] {
seenAccs[acc] = true
accounts = append(accounts, acc)
}
if tk != "" && !seenTks[tk] {
seenTks[tk] = true
tokens = append(tokens, tk)
}
}
existingAccs := make(map[string]bool)
existingTks := make(map[string]bool)
if len(accounts) > 0 || len(tokens) > 0 {
condObj := orm.NewCondition()
subCond := orm.NewCondition()
if len(accounts) > 0 {
subCond = subCond.Or("account__in", accounts)
}
if len(tokens) > 0 {
subCond = subCond.Or("token__in", tokens)
}
switch module {
case "cursor":
var dbRows []models.PlatformAccountPoolCursor
cond := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("delete_time__isnull", true)
cond = cond.SetCond(condObj.AndCond(subCond))
if _, err := cond.All(&dbRows, "account", "token"); err == nil {
for _, r := range dbRows {
if r.Account != "" {
existingAccs[r.Account] = true
}
if r.Token != "" {
existingTks[r.Token] = true
}
}
}
case "windsurf":
var dbRows []models.PlatformAccountPoolWindsurf
cond := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("delete_time__isnull", true)
cond = cond.SetCond(condObj.AndCond(subCond))
if _, err := cond.All(&dbRows, "account", "token"); err == nil {
for _, r := range dbRows {
if r.Account != "" {
existingAccs[r.Account] = true
}
if r.Token != "" {
existingTks[r.Token] = true
}
}
}
case "krio":
var dbRows []models.PlatformAccountPoolKiro
cond := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("delete_time__isnull", true)
cond = cond.SetCond(condObj.AndCond(subCond))
if _, err := cond.All(&dbRows, "account", "token"); err == nil {
for _, r := range dbRows {
if r.Account != "" {
existingAccs[r.Account] = true
}
if r.Token != "" {
existingTks[r.Token] = true
}
}
}
case "codex":
var dbRows []models.PlatformAccountPoolCodex
cond := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("delete_time__isnull", true)
cond = cond.SetCond(condObj.AndCond(subCond))
if _, err := cond.All(&dbRows, "account", "token"); err == nil {
for _, r := range dbRows {
if r.Account != "" {
existingAccs[r.Account] = true
}
if r.Token != "" {
existingTks[r.Token] = true
}
}
}
}
}
insertedAccs := make(map[string]bool)
insertedTks := make(map[string]bool)
var successCount int
var duplicateCount int
for _, row := range payload.Rows {
acc := strings.TrimSpace(row.Account)
tk := strings.TrimSpace(row.Token)
isDuplicate := false
if row.DataType == "account" {
if existingAccs[acc] || insertedAccs[acc] {
isDuplicate = true
}
} else if row.DataType == "tk" {
if existingTks[tk] || insertedTks[tk] {
isDuplicate = true
}
} else if row.DataType == "account_tk" {
if (acc != "" && (existingAccs[acc] || insertedAccs[acc])) || (tk != "" && (existingTks[tk] || insertedTks[tk])) {
isDuplicate = true
}
}
if isDuplicate {
duplicateCount++
continue
}
if acc != "" {
insertedAccs[acc] = true
}
if tk != "" {
insertedTks[tk] = true
}
switch module { switch module {
case "cursor": case "cursor":
_, err = models.Orm.Insert(&models.PlatformAccountPoolCursor{ _, err = models.Orm.Insert(&models.PlatformAccountPoolCursor{
DataType: strings.TrimSpace(row.DataType), DataType: strings.TrimSpace(row.DataType),
Account: strings.TrimSpace(row.Account), Account: acc,
Password: strings.TrimSpace(row.Password), Password: strings.TrimSpace(row.Password),
Token: strings.TrimSpace(row.Token), Token: tk,
Remark: strings.TrimSpace(row.Remark), Remark: strings.TrimSpace(row.Remark),
IsExtracted: 0, IsExtracted: 0,
}) })
case "windsurf": case "windsurf":
_, err = models.Orm.Insert(&models.PlatformAccountPoolWindsurf{ _, err = models.Orm.Insert(&models.PlatformAccountPoolWindsurf{
DataType: strings.TrimSpace(row.DataType), DataType: strings.TrimSpace(row.DataType),
Account: strings.TrimSpace(row.Account), Account: acc,
Password: strings.TrimSpace(row.Password), Password: strings.TrimSpace(row.Password),
Token: strings.TrimSpace(row.Token), Token: tk,
Remark: strings.TrimSpace(row.Remark), Remark: strings.TrimSpace(row.Remark),
IsExtracted: 0, IsExtracted: 0,
}) })
case "krio": case "krio":
_, err = models.Orm.Insert(&models.PlatformAccountPoolKiro{ _, err = models.Orm.Insert(&models.PlatformAccountPoolKiro{
DataType: strings.TrimSpace(row.DataType), DataType: strings.TrimSpace(row.DataType),
Account: strings.TrimSpace(row.Account), Account: acc,
Password: strings.TrimSpace(row.Password), Password: strings.TrimSpace(row.Password),
Token: strings.TrimSpace(row.Token), Token: tk,
Remark: strings.TrimSpace(row.Remark), Remark: strings.TrimSpace(row.Remark),
IsExtracted: 0, IsExtracted: 0,
}) })
case "codex": case "codex":
_, err = models.Orm.Insert(&models.PlatformAccountPoolCodex{ _, err = models.Orm.Insert(&models.PlatformAccountPoolCodex{
DataType: strings.TrimSpace(row.DataType), DataType: strings.TrimSpace(row.DataType),
Account: strings.TrimSpace(row.Account), Account: acc,
Password: strings.TrimSpace(row.Password), Password: strings.TrimSpace(row.Password),
Token: strings.TrimSpace(row.Token), Token: tk,
Remark: strings.TrimSpace(row.Remark), Remark: strings.TrimSpace(row.Remark),
IsExtracted: 0, IsExtracted: 0,
}) })
@@ -447,8 +574,16 @@ func batchAddPoolRows(c *beego.Controller, module string) {
poolJSONErr(c, 500, 500, "批量添加失败: "+err.Error()) poolJSONErr(c, 500, 500, "批量添加失败: "+err.Error())
return return
} }
successCount++
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "批量添加成功",
"data": map[string]interface{}{
"success_count": successCount,
"duplicate_count": duplicateCount,
},
} }
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量添加成功"}
_ = c.ServeJSON() _ = c.ServeJSON()
} }
+24 -7
View File
@@ -1,7 +1,24 @@
package index package index
// Register 注册前端站点(index)路由。 import (
// 建议统一使用 /index/* 或根路径,根据后续设计补充。 "server/controllers"
func Register() {
} beego "github.com/beego/beego/v2/server/web"
)
// Register 注册前端站点(index)路由。
func Register() {
beego.Router("/index/headmenu", &controllers.IndexPortalController{}, "get:GetHeadMenu")
beego.Router("/index/footerdata", &controllers.IndexPortalController{}, "get:GetFooterData")
beego.Router("/index/newscentertop4", &controllers.IndexPortalController{}, "get:GetNewsCenterTop4")
beego.Router("/index/companynews", &controllers.IndexPortalController{}, "get:GetCompanyNews")
beego.Router("/index/kingdeenews", &controllers.IndexPortalController{}, "get:GetKingdeeNews")
beego.Router("/index/technologyCenter", &controllers.IndexPortalController{}, "get:GetTechnologyCenter")
beego.Router("/index/technologyCategories", &controllers.IndexPortalController{}, "get:GetTechnologyCategories")
beego.Router("/index/companynews/detail/:id", &controllers.IndexPortalController{}, "get:GetCompanyNewsDetail")
beego.Router("/index/kingdeenews/detail/:id", &controllers.IndexPortalController{}, "get:GetKingdeeNewsDetail")
beego.Router("/index/articleLikes/:id", &controllers.IndexPortalController{}, "post:AddLike")
beego.Router("/index/articleUnlikes/:id", &controllers.IndexPortalController{}, "post:ReduceLike")
beego.Router("/index/views/:id", &controllers.IndexPortalController{}, "post:AddViews")
beego.Router("/index/onepage/:path", &controllers.IndexPortalController{}, "get:GetOnePageByPath")
}
+12 -7
View File
@@ -103,19 +103,24 @@ function openAddDialog(mode = "single") {
async function saveRows(rows) { async function saveRows(rows) {
if (!rows.length) return; if (!rows.length) return;
if (rows.length === 1) { if (rows.length === 1) {
await addAccountPool(moduleKey, rows[0]); return await addAccountPool(moduleKey, rows[0]);
return;
} }
await batchAddAccountPool(moduleKey, rows); return await batchAddAccountPool(moduleKey, rows);
} }
async function handleEditSubmit(payload) { async function handleEditSubmit(payload) {
loading.value = true; loading.value = true;
try { try {
await saveRows(payload.rows || []); const res = await saveRows(payload.rows || []);
ElMessage.success( if (payload.mode === "batch") {
payload.mode === "batch" ? "批量添加成功" : "账号添加成功", const successCount = res?.data?.success_count ?? 0;
); const duplicateCount = res?.data?.duplicate_count ?? 0;
ElMessage.success(
`批量添加完成:成功 ${successCount} 条,重复跳过 ${duplicateCount}`,
);
} else {
ElMessage.success("账号添加成功");
}
await fetchList(); await fetchList();
} finally { } finally {
loading.value = false; loading.value = false;
@@ -165,19 +165,24 @@ function nowText() {
async function saveRows(rows) { async function saveRows(rows) {
if (!rows.length) return; if (!rows.length) return;
if (rows.length === 1) { if (rows.length === 1) {
await addAccountPool(moduleKey, rows[0]); return await addAccountPool(moduleKey, rows[0]);
return;
} }
await batchAddAccountPool(moduleKey, rows); return await batchAddAccountPool(moduleKey, rows);
} }
async function handleEditSubmit(payload) { async function handleEditSubmit(payload) {
loading.value = true; loading.value = true;
try { try {
await saveRows(payload.rows || []); const res = await saveRows(payload.rows || []);
ElMessage.success( if (payload.mode === "batch") {
payload.mode === "batch" ? "批量添加成功" : "账号添加成功", const successCount = res?.data?.success_count ?? 0;
); const duplicateCount = res?.data?.duplicate_count ?? 0;
ElMessage.success(
`批量添加完成:成功 ${successCount} 条,重复跳过 ${duplicateCount}`,
);
} else {
ElMessage.success("账号添加成功");
}
await fetchList(); await fetchList();
} finally { } finally {
loading.value = false; loading.value = false;
+12 -7
View File
@@ -103,19 +103,24 @@ function openAddDialog(mode = "single") {
async function saveRows(rows) { async function saveRows(rows) {
if (!rows.length) return; if (!rows.length) return;
if (rows.length === 1) { if (rows.length === 1) {
await addAccountPool(moduleKey, rows[0]); return await addAccountPool(moduleKey, rows[0]);
return;
} }
await batchAddAccountPool(moduleKey, rows); return await batchAddAccountPool(moduleKey, rows);
} }
async function handleEditSubmit(payload) { async function handleEditSubmit(payload) {
loading.value = true; loading.value = true;
try { try {
await saveRows(payload.rows || []); const res = await saveRows(payload.rows || []);
ElMessage.success( if (payload.mode === "batch") {
payload.mode === "batch" ? "批量添加成功" : "账号添加成功", const successCount = res?.data?.success_count ?? 0;
); const duplicateCount = res?.data?.duplicate_count ?? 0;
ElMessage.success(
`批量添加完成:成功 ${successCount} 条,重复跳过 ${duplicateCount}`,
);
} else {
ElMessage.success("账号添加成功");
}
await fetchList(); await fetchList();
} finally { } finally {
loading.value = false; loading.value = false;
@@ -89,19 +89,24 @@ function openAddDialog(mode = "single") {
async function saveRows(rows) { async function saveRows(rows) {
if (!rows.length) return; if (!rows.length) return;
if (rows.length === 1) { if (rows.length === 1) {
await addAccountPool(moduleKey, rows[0]); return await addAccountPool(moduleKey, rows[0]);
return;
} }
await batchAddAccountPool(moduleKey, rows); return await batchAddAccountPool(moduleKey, rows);
} }
async function handleEditSubmit(payload) { async function handleEditSubmit(payload) {
loading.value = true; loading.value = true;
try { try {
await saveRows(payload.rows || []); const res = await saveRows(payload.rows || []);
ElMessage.success( if (payload.mode === "batch") {
payload.mode === "batch" ? "批量添加成功" : "账号添加成功", const successCount = res?.data?.success_count ?? 0;
); const duplicateCount = res?.data?.duplicate_count ?? 0;
ElMessage.success(
`批量添加完成:成功 ${successCount} 条,重复跳过 ${duplicateCount}`,
);
} else {
ElMessage.success("账号添加成功");
}
await fetchList(); await fetchList();
} finally { } finally {
loading.value = false; loading.value = false;