Compare commits

...

1256 Commits

Author SHA1 Message Date
RockYang b18b8ccb02 release: v4.2.8
整合开源版 v4.2.8 功能:Sora2 视频、路由重构、手机站开关、DALL-E 参考图,以及启动时自动同步数据表字段等修复与优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:12:37 +08:00
RockYang f8a01cb9a2 feat: 启动时自动同步 model 与数据表字段
用 AutoMigrate 补齐缺列,并删除 model 中已不存在的多余列(保护 id/时间戳),修复 ChatModel.desc 等字段缺失导致的 1054 错误。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:37:38 +08:00
RockYang 64832a2a04 update readme 2026-05-24 10:14:28 +08:00
RockYang b1dd781a3d update readme 2026-05-24 10:12:51 +08:00
RockYang 71dc082e40 Merge tag 'v4.2.7' 2026-05-09 20:56:07 +08:00
RockYang 10836bbb4a merge v4.2.6
整合 v4.2.6 的后端中间件与服务层重构、前端样式体系迁移和管理端/移动端功能更新,统一清理历史冲突并完成版本升级。

Made-with: Cursor
2026-04-08 15:08:34 +08:00
RockYang d8237ae301 merge v4.2.5 2026-04-05 21:35:30 +08:00
RockYang 3974f1be87 merge v4.2.4 2026-02-14 17:20:53 +08:00
RockYang 126c9921a8 merge v4.2.3 2026-01-29 20:11:50 +08:00
RockYang c84754fc0c merge v4.2.2 2025-12-02 11:18:30 +08:00
RockYang 172807d326 add EasyDevelop deploy docs 2025-12-02 11:15:54 +08:00
RockYang 72608af77e merge v4.2.1 2025-11-26 20:09:48 +08:00
RockYang 7879459c3f update docker images version to docker-compose.yaml 2025-11-11 11:07:22 +08:00
RockYang ad482dad7f merge v4.2.0 2025-11-11 10:17:03 +08:00
RockYang 58e99976b3 更新数据库文件 2025-10-27 16:25:58 +08:00
RockYang 567803ebeb 添加备案信息配置项,给登录页面 Logo 增加圆角 2025-10-27 16:25:58 +08:00
RockYang 8e3d67cc7f 优化编译指令,减少程序体积 2025-10-27 16:25:58 +08:00
RockYang b463608ab7 支持微模型绑定 Dalle 绘图的 API KEY 2025-10-27 16:25:58 +08:00
RockYang eefd36562d 管理后台用户算力日志页面增加过滤查询功能 2025-10-27 16:25:58 +08:00
RockYang ebf4497b34 允许配置登录注册页面的自定义 Logo 2025-10-27 16:25:58 +08:00
RockYang 194b0343bf 修复微信登录功能 2025-10-27 16:25:58 +08:00
RockYang adb1dfbded 首页增加码云连接地址 2025-10-27 16:25:58 +08:00
RockYang 9e689f6022 支持在 Chat 页面显示,隐藏对话列表 2025-10-27 16:25:55 +08:00
RockYang 499f5c915f merge and resolve conflicts 2025-10-27 16:21:46 +08:00
RockYang a9f11d79a9 optimize login page styles 2025-10-27 16:19:34 +08:00
RockYang db5e9ea2d0 更改文档地址 2025-10-27 16:19:34 +08:00
GeekMaster 64f8693871 Merge branch 'main' of gitee.com:blackfox/geekai-plus 2025-09-17 16:31:41 +08:00
GeekMaster 4270847b5d optimize regenerate for mobile chat page 2025-09-17 16:18:16 +08:00
GeekMaster 3eb0177188 add prompt edit function 2025-09-17 16:04:04 +08:00
GeekMaster 48203e0d31 optimize jimeng power config 2025-09-16 20:35:53 +08:00
GeekMaster 6e6a496f1b remove reset password page 2025-09-16 19:31:06 +08:00
GeekMaster ddfa588163 update version 2025-09-16 19:25:46 +08:00
GeekMaster 809d8d71bd Jimeng AI 4.0 for mobile is ready 2025-09-16 19:13:41 +08:00
GeekMaster 49254b2a32 optimize ParamBuilderMobile components 2025-09-15 22:18:46 +08:00
GeekMaster 5979451ea6 ParamBuilder component support hidden input 2025-09-15 21:05:34 +08:00
GeekMaster 46c3d65bea update change log file 2025-09-15 20:36:34 +08:00
GeekMaster c4b44d84e3 Jimeng VirtualHuman and actionTransfer is ready 2025-09-15 20:29:46 +08:00
RockYang 822d1831cd rm unuse certs 2025-09-13 10:48:00 +08:00
RockYang 0d28402aa3 fixed bug for sub user power to negtive nuber 2025-09-13 10:35:55 +08:00
RockYang 359999c045 check user power for create ai3d job 2025-09-13 10:29:40 +08:00
GeekMaster 2c6eee7fc1 acommpelish jimeng AI refactor for PC 2025-09-12 18:58:52 +08:00
GeekMaster c5badb3e13 add user lock for chat api, Prevent insufficient deduction of user power caused by submitting multiple requests at one time 2025-09-12 15:05:14 +08:00
GeekMaster 65fb58585c refactor parameters for JimengCreate page 2025-09-11 15:48:07 +08:00
RockYang 02aedd9edc Merge branch 'bug-fix' 2025-09-10 19:19:21 +08:00
GeekMaster 896b5de0a4 param builder component is ready 2025-09-10 17:04:37 +08:00
GeekMaster 1ca58606da optimize jimeng AI task params build 2025-09-09 18:05:51 +08:00
GeekMaster 3d8c37cb38 allow user to set default index page 2025-09-09 11:11:34 +08:00
GeekMaster c8eec6c97e Merge branch 'bug-fix' 2025-09-09 10:40:47 +08:00
RockYang 330ac397d4 fixed bug for change admin user password 2025-09-08 22:10:29 +08:00
RockYang 8aa75a49d2 fixed bug for can not change admin user password 2025-09-08 18:59:31 +08:00
RockYang 5d1cbb9050 Merge branch 'bug-fix' 2025-09-08 16:39:02 +08:00
RockYang 8cf40ec6fd update database sql file 2025-09-08 16:38:42 +08:00
RockYang 37b757d910 Merge branch 'bug-fix' 2025-09-08 15:19:01 +08:00
RockYang ef6b642011 Dalle supported image to image 2025-09-08 10:57:11 +08:00
RockYang 8b2cf90aab allow user to upload attachment for mobile chat page 2025-09-08 08:43:32 +08:00
RockYang 18961d3c26 acomplish re-generating for mobile chat page 2025-09-07 18:10:45 +08:00
RockYang 976b71e592 merge Login page Register page 2025-09-07 16:36:56 +08:00
RockYang da63446e64 Merge branch 'bug-fix' 2025-09-07 15:19:02 +08:00
RockYang 2522be1738 fixed FootBar error 2025-09-06 21:24:24 +08:00
RockYang 8f37ab859b Merge branch 'bug-fix' 2025-09-06 13:12:31 +08:00
RockYang 1cb3c42bb9 optimize code for function call handler, fixed bug for dalle plugin 2025-09-06 13:12:08 +08:00
RockYang a60ffca135 remove AI3D module files 2025-09-06 12:38:49 +08:00
RockYang 1ff0636745 allow the el-dialog body show scrollbar 2025-09-06 12:19:59 +08:00
GeekMaster 26f3db09a7 增加订单超时系统配置,WebFooter 增加公安备案号 2025-09-05 10:42:02 +08:00
GeekMaster 52d297624d AI3D 功能完成 2025-09-04 18:36:49 +08:00
GeekMaster 53866d1461 AI3D 页面支持 dark 模式 2025-09-03 18:10:29 +08:00
GeekMaster 54c8856adf AI3D 页面功能完成 2025-09-03 16:00:28 +08:00
RockYang ead30c8779 调整 3D 生成 PC 页面 2025-09-03 07:28:16 +08:00
GeekMaster f8e4d2880f 3D生成服务已经完成 2025-09-02 18:55:45 +08:00
GeekMaster 85b4cc0a3c 增加3D生成功能需求文档 2025-09-01 19:39:19 +08:00
GeekMaster 3271b924fa 文本审核记录功能完成 2025-09-01 17:41:45 +08:00
GeekMaster 2e2b4ee370 修复重新生成的 Bug 2025-09-01 14:28:20 +08:00
GeekMaster 14524f0559 修复重新生成的 Bug 2025-09-01 10:58:15 +08:00
GeekMaster 957954f5ee 数据表重命名 2025-09-01 09:26:12 +08:00
RockYang 3531c7f356 重命名数据表 2025-09-01 07:35:37 +08:00
GeekMaster 9a4239290b 完成文本审查服务开发 2025-08-31 18:21:09 +08:00
GeekMaster 0585edd895 迁移 License,privacy, 用户协议数据 2025-08-31 11:07:42 +08:00
GeekMaster 52313fc7f6 修正部分API授权 2025-08-31 10:52:25 +08:00
GeekMaster 9254b8fafe 微信登录验证完成 2025-08-30 20:30:52 +08:00
GeekMaster c83c88ef27 移动端支付已完成 2025-08-30 18:05:38 +08:00
RockYang 3c065b99fb 支付模块重构完成 2025-08-30 16:27:39 +08:00
RockYang 3a6f8ccc16 验证码配置重构完成 2025-08-29 17:45:31 +08:00
GeekMaster 539e91c12e 插件配置已完成 2025-08-29 14:49:51 +08:00
RockYang 696ef20a80 存储配置功能完成 2025-08-29 09:08:17 +08:00
GeekMaster ebaaefaf7a 算力配置功能已完成 2025-08-28 14:59:42 +08:00
RockYang 77a8969f73 算力配置完成 2025-08-28 11:07:19 +08:00
GeekMaster b3b1981264 更新配置代码 2025-08-26 15:33:32 +08:00
GeekMaster b6d81890cf 控制器中间件授权改造完成 2025-08-26 14:22:14 +08:00
RockYang 728de61bd6 采用中间件授权的方式 2025-08-26 10:45:57 +08:00
RockYang fb76e24c51 micro fix 2025-08-26 00:00:56 +08:00
GeekMaster d9a177864d 支持本地 geekai service api 2025-08-25 17:38:49 +08:00
GeekMaster 536b4b8056 支付,OSS 服务重构完成 2025-08-24 19:32:45 +08:00
RockYang 7fb0aad3c7 重构系统配置 2025-08-24 12:45:51 +08:00
GeekMaster b05334bb77 调整支付配置页面UI 2025-08-21 16:28:30 +08:00
GeekMaster b155597e28 完成配置分组 2025-08-20 16:16:20 +08:00
RockYang 0956bef9db 系统配置重构,支持后台管理页面配置 2025-08-20 10:47:17 +08:00
GeekMaster 6242f648f1 登录注册页面支持 dark 主题模式 2025-08-15 18:51:33 +08:00
GeekMaster c0832a24b9 登录注册页面支持 dark 主题模式 2025-08-15 18:51:12 +08:00
GeekMaster c9a7449d2d 退出登录之后不是刷新页面,而是直接跳转到登录页面 2025-08-14 16:59:16 +08:00
RockYang f82270d097 给所有的 AI 任务提交按钮添加loading状态 2025-08-12 09:17:01 +08:00
RockYang 5a4778074e 登录的时候提示正在登录中,并锁定按钮 2025-08-12 07:56:30 +08:00
RockYang 9070531531 修复聊天页面报错重连的 bug 2025-08-11 21:32:21 +08:00
RockYang ad52613738 修复聊天页面报错重连的 bug 2025-08-11 20:29:19 +08:00
RockYang e7db720986 增加批量删除模型功能 2025-08-11 13:53:03 +08:00
RockYang c651103eff 移动端 VideoCreate 页面支持 dark 主题 2025-08-11 13:34:56 +08:00
RockYang 80ee41aee7 JiMeng, Suno 支持 dark 主题 2025-08-11 11:43:24 +08:00
RockYang 000969d902 优化生成按钮 UI 2025-08-09 23:49:29 +08:00
RockYang 9a94d98725 手机端即梦页面功能完成 2025-08-09 23:25:47 +08:00
RockYang c0a89d6f32 优化手机端 profile 页面 2025-08-09 09:49:41 +08:00
GeekMaster 604ce985bd 调整即梦AI移动端功能 2025-08-08 18:01:42 +08:00
GeekMaster 8c03ecad2b 调整即梦AI移动端参数 2025-08-08 16:30:45 +08:00
GeekMaster af31f91973 调整即梦AI移动端参数 2025-08-08 13:29:36 +08:00
GeekMaster 0ce5df6811 手机端视频生成页面调整完成 2025-08-08 12:47:12 +08:00
RockYang 4e237c9560 优化移动端即梦页面 2025-08-07 22:27:09 +08:00
RockYang e456210944 视频生成移动端页面重构完成 2025-08-07 20:04:20 +08:00
GeekMaster cb7235bb83 Suno页面重构完成 2025-08-07 16:26:12 +08:00
RockYang eab8265b9c Suno PC 端页面重构完成 2025-08-07 14:00:09 +08:00
GeekMaster 5f24df6cee 重构 suno 页面 2025-08-07 11:57:32 +08:00
RockYang e8e3783af0 视频创作和即梦 AI 手机页面 2025-08-07 08:02:20 +08:00
RockYang ec00f156f0 完成移动端Suno页面功能 2025-08-06 17:58:39 +08:00
RockYang bb6e90d50a 完成移动端邀请页面功能 2025-08-06 09:57:14 +08:00
GeekMaster 8d2519d5a1 整合登录注册页面 2025-08-04 17:39:23 +08:00
GeekMaster 6c35c69ed7 移动端首页整合完毕 2025-08-04 15:04:06 +08:00
GeekMaster e994060e93 调整移动端页面UI布局 2025-08-04 12:08:42 +08:00
GeekMaster f7cf992598 移动端重构第一版 2025-08-02 11:17:18 +08:00
GeekMaster 92915f7678 stylus 语法换成 saas 语法全部完成 2025-08-02 10:24:10 +08:00
GeekMaster 54f8494b5c stylus 语法换成 saas 语法 2025-08-01 17:32:06 +08:00
GeekMaster 068b5ddeef Dashboard page is ready 2025-08-01 10:45:49 +08:00
RockYang 8168377e47 优化 dashboard 页面 2025-08-01 07:22:59 +08:00
GeekMaster 4d048dbfa7 增加 dashboard 页面 2025-07-31 16:59:59 +08:00
GeekMaster 0ee230cf41 重构路由注册功能 2025-07-31 12:05:31 +08:00
GeekMaster 9fba68fb14 即梦绘画添加图片特效预览 2025-07-29 18:34:29 +08:00
GeekMaster ff96fada02 1. 首页应用图标同时支持 iconfont 和自定义上传图标
2. 所有模型外键字段都改成int(11) 而不是 bigint
2025-07-29 11:35:11 +08:00
GeekMaster c19b7db8c0 优化支付宝和微信支付服务参数:
1. 删减支付宝参数,只需要传入应用私钥和支付宝公钥。
2. 所有的秘钥直接写入配置文档传入,而不是传入秘钥文件路径,简化参数配置
2025-07-25 19:15:36 +08:00
GeekMaser 31fbf7e48a Merge pull request #247 from zayn-code/themechange
主题切换按钮:月亮图标重复了
2025-06-03 09:20:57 +08:00
GeekMaser e2e24078c5 Merge pull request #248 from zayn-code/chatlist
移动端会话列表:切换tab的时候有时会出现列表一直在加载中
2025-06-03 09:20:19 +08:00
zhangyao 39ef92e1ce 移动端会话列表:切换tab的时候有时会出现列表一直在加载中 2025-05-30 15:55:18 +08:00
zhangyao f63ba187dd 移动端会话列表:切换tab的时候有时会出现列表一直在加载中 2025-05-30 15:48:50 +08:00
zhangyao 23d1ac1021 主题切换按钮:月亮图标重复了 2025-05-30 15:46:33 +08:00
GeekMaser f08329eef4 Merge pull request #241 from coderwei99/chore/migrate-to-pnpm
Chore/migrate to pnpm
2025-05-18 12:24:19 +08:00
coderwei e993a582cd chore: 修正环境变量配置文件 2025-05-16 14:42:58 +08:00
coderwei 1524e189dc chore: migrate from npm to pnpm 2025-05-16 14:32:55 +08:00
GeekMaster 2b5165324c update docker image version 2025-04-17 10:22:41 +08:00
GeekMaster 8f67826072 update docker image version 2025-04-17 10:08:38 +08:00
GeekMaster ac9a31f049 merge code for v4.1.8 2025-04-17 10:07:04 +08:00
RockYang ed2cdbbc31 更新 README 2025-04-07 17:52:06 +08:00
RockYang 8bd2feaf7e Merge branch 'main' of gitee.com:blackfox/geekai 2025-03-25 10:48:46 +08:00
RockYang 4eccfe6d2c remove sensitive message for openai API KEY 2025-03-25 10:48:18 +08:00
RockYang 5ba2e000a1 update database file 2025-03-24 20:29:03 +08:00
RockYang fe9adadb3a update database file 2025-03-24 20:29:03 +08:00
RockYang 805c2a045e merge release v4.1.7 2025-03-24 17:13:16 +08:00
RockYang fd7b196134 merge release v4.1.7 2025-03-24 17:13:16 +08:00
RockYang 1b1c327c35 Merge tag 'v4.1.7' of gitee.com:blackfox/geekai-plus 2025-03-24 11:27:11 +08:00
RockYang a228f603d2 Merge tag 'v4.1.7' of gitee.com:blackfox/geekai-plus 2025-03-24 11:27:11 +08:00
RockYang 7d0a05ee11 update v4.1.5 sql file 2025-03-06 14:03:44 +08:00
RockYang 5f0a62b63e update v4.1.5 sql file 2025-03-06 14:03:44 +08:00
RockYang 825a1b1027 文档对话不显示文档列表的 bug 2025-03-06 14:00:13 +08:00
RockYang 38dde1a373 文档对话不显示文档列表的 bug 2025-03-06 14:00:13 +08:00
RockYang 950e7d1b00 Merge branch 'dev' 2025-03-05 18:42:44 +08:00
RockYang 89ded74345 Merge branch 'dev' 2025-03-05 18:42:44 +08:00
RockYang d8f9f48278 merge v4.1.6 2025-03-05 18:42:30 +08:00
RockYang 818bb38617 merge v4.1.6 2025-03-05 18:42:30 +08:00
GeekMaser bdd76addf3 Merge pull request #238 from gotoworld/main
v4.1.5中,存在Merge冲突脚本 #237
2025-02-25 09:27:15 +08:00
GeekMaser 0202d75ff4 Merge pull request #238 from gotoworld/main
v4.1.5中,存在Merge冲突脚本 #237
2025-02-25 09:27:15 +08:00
Welcome Aboard 2936f21f12 Merge pull request #1 from gotoworld/gotoworld-patch-1
Bugs: fixed merge conflict in geekai_plus-v4.1.5.sql
2025-02-24 19:59:00 +08:00
Welcome Aboard a0831ec6cd Merge pull request #1 from gotoworld/gotoworld-patch-1
Bugs: fixed merge conflict in geekai_plus-v4.1.5.sql
2025-02-24 19:59:00 +08:00
Welcome Aboard 705ca4d20a Bugs: fixed merge conflict in geekai_plus-v4.1.5.sql 2025-02-24 19:57:40 +08:00
Welcome Aboard 2926aab7a4 Bugs: fixed merge conflict in geekai_plus-v4.1.5.sql 2025-02-24 19:57:40 +08:00
RockYang 6a066f1b8e 默认允许跨域请求API 2025-02-11 14:34:31 +08:00
RockYang 775fb1f853 默认允许跨域请求API 2025-02-11 14:34:31 +08:00
RockYang b7d137247a 优化 docker 容器依赖关系 2025-02-11 10:10:43 +08:00
RockYang 22278c40bf 优化 docker 容器依赖关系 2025-02-11 10:10:43 +08:00
RockYang 4373642ebd 优化打包脚本,新增 arm64 架构打包脚本 2025-02-11 10:01:05 +08:00
RockYang e29b32bd2d 优化打包脚本,新增 arm64 架构打包脚本 2025-02-11 10:01:05 +08:00
RockYang 5bf90920f5 resolve conflicts 2025-02-11 09:53:41 +08:00
RockYang fa6bb5e46a resolve conflicts 2025-02-11 09:53:41 +08:00
RockYang edcbb3e226 merge v4.1.5 2025-02-11 09:45:26 +08:00
RockYang cebf8497a4 merge v4.1.5 2025-02-11 09:45:26 +08:00
RockYang 5213bdf08b fixed bug for calculate chat message tokens 2025-01-19 13:08:38 +08:00
RockYang cded4bdcc7 fixed bug for calculate chat message tokens 2025-01-19 13:08:38 +08:00
RockYang cf817fd8ea merge code for v4.1.4 2025-01-18 23:20:41 +08:00
RockYang 596b700d63 merge code for v4.1.4 2025-01-18 23:20:41 +08:00
RockYang a2481ff1cf update readme 2025-01-07 11:58:16 +08:00
RockYang 59dfa95bf8 update readme 2025-01-07 11:58:16 +08:00
RockYang bc7d06d3e5 修复登录失效的 Bug 2024-12-21 21:44:52 +08:00
RockYang f6b5a94b29 修复登录失效的 Bug 2024-12-21 21:44:52 +08:00
RockYang 8e81dfa12a update database name 2024-12-16 11:26:54 +08:00
RockYang 188fb23f08 update database name 2024-12-16 11:26:54 +08:00
RockYang 0ff76f0f21 update database file 2024-12-16 11:11:42 +08:00
RockYang 5347a12035 update database file 2024-12-16 11:11:42 +08:00
RockYang 787caa84c8 merge config.toml file 2024-12-16 10:09:23 +08:00
RockYang 691d453f41 merge config.toml file 2024-12-16 10:09:23 +08:00
RockYang c2503e663a merge v4.1.3 2024-12-16 10:07:52 +08:00
RockYang 5b7f2603ae merge v4.1.3 2024-12-16 10:07:52 +08:00
RockYang 405a88862b Merge branch 'dev' 2024-11-27 15:04:26 +08:00
RockYang 3fd7f810f0 Merge branch 'dev' 2024-11-27 15:04:26 +08:00
RockYang 296eabe09a merge v4.1.2 2024-11-27 15:00:02 +08:00
RockYang e04a48623d merge v4.1.2 2024-11-27 15:00:02 +08:00
RockYang 2e1bad387c redeem export function is ready 2024-11-27 11:52:18 +08:00
RockYang 54b45ec2ff update docker-compose.yaml 2024-11-14 16:37:43 +08:00
RockYang 5c18a50330 update docker-compose.yaml 2024-11-14 16:37:43 +08:00
RockYang c434f85045 update database 2024-11-14 16:01:27 +08:00
RockYang d78d3ffe02 update database 2024-11-14 16:01:27 +08:00
RockYang 4d10279870 merge v4.1.1 and fixed conflicts 2024-11-13 18:40:04 +08:00
RockYang 44240f65d5 merge v4.1.1 and fixed conflicts 2024-11-13 18:40:04 +08:00
RockYang 97e81a7dcc fixed bug for chat context not work for chating with image 2024-11-12 18:23:27 +08:00
RockYang 9e8f1ed6bf meta prompt function is ready 2024-11-12 17:13:38 +08:00
RockYang 4dbfdab50d auto restore user's power for failure tasks 2024-11-11 18:12:35 +08:00
RockYang c39814ce2b save task origin info for AI generating jobs 2024-11-11 17:22:08 +08:00
RockYang 95a071014c enable to set the translate model 2024-11-08 18:06:39 +08:00
RockYang 6e03f4b363 fixed bug for audio and video downloading 2024-11-05 11:38:32 +08:00
RockYang 9de9489673 remove sensitive words 2024-11-04 10:05:57 +08:00
RockYang bb8644dea0 remove sensitive words 2024-11-04 10:05:57 +08:00
RockYang ff9142ddd4 micro fix 2024-10-30 18:11:36 +08:00
RockYang 9814fec930 update the default api url to https://api.geekai.pro 2024-10-29 14:09:55 +08:00
RockYang 35f469fb82 update the default api url to https://api.geekai.pro 2024-10-29 14:09:55 +08:00
RockYang 53ba731159 update database sql file 2024-10-29 14:07:49 +08:00
RockYang 705ad58a8f update database sql file 2024-10-29 14:07:49 +08:00
RockYang c96f86fbbf 优化实时语音对话组件,处理异常 2024-10-23 18:04:09 +08:00
RockYang 4b3b64e9e2 modify text link color for register page 2024-10-21 18:26:19 +08:00
RockYang 2f2e146951 Merge branch 'main' of gitee.com:blackfox/geekai-plus 2024-10-21 18:21:21 +08:00
RockYang a09c529414 更换登录页面背景图片 2024-10-21 18:21:04 +08:00
RockYang a8d1d58e95 给 realtime 语音对话增加音效 2024-10-18 06:26:05 +08:00
RockYang 6695be815e 优化充值产品定价逻辑,确保手机端和PC端显示的价格一致 2024-10-17 18:15:25 +08:00
RockYang fbd3478772 the relay server for openai websocket is ready 2024-10-17 16:46:41 +08:00
RockYang 2102e1afbb add websocket relayer for openai realtime api 2024-10-16 18:16:09 +08:00
RockYang 155c56f502 integrated openai realtime console 2024-10-15 19:25:18 +08:00
RockYang 37a9b0e485 add PCM16 audio stream to wave is reday 2024-10-14 18:39:50 +08:00
RockYang 52a3f13f1e add voice chat test case 2024-10-12 19:07:29 +08:00
RockYang a678a11c33 suno and luma task management funtion in admin console is ready 2024-10-10 17:07:40 +08:00
RockYang d34b785238 image task list page for admin console is ready 2024-10-09 18:17:44 +08:00
RockYang 1086e1d631 fixed bug in FileSelect component for deleting files 2024-10-08 18:00:46 +08:00
RockYang b2f57aa483 merge v4.1.0 and fixed conflicts 2024-10-08 17:54:08 +08:00
RockYang 3094b9c8fd merge v4.1.0 and fixed conflicts 2024-10-08 17:54:08 +08:00
RockYang 4c2dba1004 merge v4.1.0 and fixed conflicts 2024-10-08 17:51:14 +08:00
RockYang 0f0a4c0a7e merge v4.1.0 and fixed conflicts 2024-10-08 17:51:14 +08:00
RockYang c374126f69 fixed bug for websocket message handler rebind 2024-10-08 16:41:19 +08:00
RockYang 8498cd71dc fixed webscoket event re-bind bug 2024-10-05 21:18:59 +08:00
RockYang bf30517393 fixed alipay mobile payment 2024-10-05 11:45:44 +08:00
RockYang 495f86e7fc fixed alipay mobile payment 2024-10-05 10:23:00 +08:00
RockYang 471017657f auto jump to mobile page when use mobile device access the page 2024-10-04 11:25:01 +08:00
RockYang 4861dd75be fixed bug for: websocket is not auto connected when user not login 2024-10-02 07:26:34 +08:00
RockYang 342ceea371 update database 2024-09-30 17:12:23 +08:00
RockYang 880c34dfee add message handler ONLY when websocket connect successfully 2024-09-30 16:33:26 +08:00
RockYang a1e487100d support wechat and alipay payment for mobile page 2024-09-30 16:20:40 +08:00
RockYang 77948b1e16 optimize the vue component communication, replace event listening with share data 2024-09-30 14:20:59 +08:00
RockYang e28a12a1ee websocket api refactor is ready 2024-09-29 19:28:47 +08:00
RockYang 00a8bc6784 mj websocket refactor is ready 2024-09-29 07:51:08 +08:00
RockYang 8fffa60569 sd websocket refactor is finished 2024-09-27 18:28:54 +08:00
RockYang 2debe7e927 refactor websocket message protocol, keep the only connection for all clients 2024-09-27 17:50:54 +08:00
RockYang 478bc32ddd add ws handler 2024-09-25 18:43:12 +08:00
RockYang dfd2be1265 update database sql file for v4.1.4 2024-09-23 15:54:22 +08:00
RockYang 33ff2e29e5 logout the user when it has been disabled 2024-09-20 16:49:03 +08:00
RockYang 4df1c7a136 add release v4.1.4 2024-09-20 15:50:04 +08:00
RockYang c522248d39 add email white list check in register handler 2024-09-20 14:10:40 +08:00
RockYang 246d96ee63 add email white list 2024-09-20 10:40:37 +08:00
RockYang 909ae4aa00 payment for mobile page is ready 2024-09-19 19:03:03 +08:00
RockYang 26e3ababcf wechat payment for mobile page is ready 2024-09-19 17:59:27 +08:00
RockYang 4d9f89f630 wechat payment for pc is ready 2024-09-19 14:42:25 +08:00
RockYang b989199edb fixed bug geek-plus#6, register page first tab not auto active 2024-09-19 09:03:14 +08:00
RockYang 0d81fe6c8e geek payment notify api is ready 2024-09-18 22:24:05 +08:00
RockYang be45f41e34 urgent bug fix: remove suno and luma task will recharge user power 2024-09-18 20:33:29 +08:00
RockYang e9ac58b1ef Geek Pay notify is ready 2024-09-18 18:07:49 +08:00
RockYang 59d9ae96ac add geek payment 2024-09-18 07:03:46 +08:00
RockYang 3cff6f7189 recommend user to use Google Chrome 2024-09-15 10:25:50 +08:00
RockYang 14aee28289 merge app type function branch 2024-09-14 18:17:55 +08:00
RockYang 8c1b4d4516 refactor AI chat message struct, allow users to set whether the AI responds in stream, compatible with the GPT-o1 model 2024-09-14 17:06:13 +08:00
胡双明 068df8fa15 feat: 应用分类功能 2024-09-14 11:05:49 +08:00
RockYang e371310d02 refactor chat message body struct 2024-09-14 07:11:45 +08:00
RockYang 96b8121210 return at least one chat role for getUserRoles API 2024-09-14 05:55:56 +08:00
RockYang 1960a85ead add tid field for chat app role 2024-09-13 18:32:13 +08:00
RockYang 69cb479e01 fixed bug, filelist page support pagination, do not load captcha component for user login first time 2024-09-13 17:03:05 +08:00
胡双明 db90131bd6 Merge branch 'main' into husm_2024-09-02 2024-09-13 10:25:15 +08:00
RockYang c18d272413 fixed bug for reset password 2024-09-12 17:25:19 +08:00
RockYang cd0952e170 fixed bug for register page code verification 2024-09-12 15:42:09 +08:00
胡双明 e6a6d0485d Merge branch 'main' into husm_2024-09-02 2024-09-11 09:57:01 +08:00
RockYang a642ae0332 optimize download function for suno 2024-09-11 08:39:28 +08:00
胡双明 67f508c7da feat: 210 AI对话页面文件列表增加分页功能 2024-09-10 18:14:34 +08:00
RockYang 2225f09797 file list api support pagination 2024-09-10 15:24:36 +08:00
胡双明 fe7fa46a0c Merge branch 'main' into husm_2024-09-02 2024-09-10 14:29:08 +08:00
RockYang 6d8901fa21 update v4.1.3 database sql file 2024-09-10 11:15:26 +08:00
RockYang 7750cb0021 update v4.1.3 database sql file 2024-09-10 11:11:17 +08:00
RockYang 327d68a347 support multiple delete users, update database sql file 2024-09-10 10:56:04 +08:00
RockYang 7c272b0efd update config file 2024-09-09 18:58:03 +08:00
RockYang 5eb4ed157e merge luma page code for v4.1.3 2024-09-09 18:07:10 +08:00
RockYang d4e00b960c Merge remote-tracking branch 'inet/husm_2024-09-02' into dev-4.1.3 2024-09-09 10:54:43 +08:00
RockYang 79adc871ef export the newest database sql file 2024-09-05 15:29:00 +08:00
RockYang af0eff88b8 export the newest database sql file 2024-09-05 15:29:00 +08:00
RockYang c755befa28 优化聊天页面代码,刷新页面之后自动加载当前对话的 role_id 和 model_id 2024-09-05 14:50:37 +08:00
胡双明 3f6890da27 feat: 生成视频页面2 2024-09-05 11:56:02 +08:00
RockYang 8144fada25 merged v4.0.9 and fixed conflicts 2024-09-05 11:02:32 +08:00
RockYang 08319391ea merged v4.0.9 and fixed conflicts 2024-09-05 11:02:32 +08:00
RockYang ae7f1c03e2 add auto execute task to downloading video files 2024-09-05 10:54:58 +08:00
胡双明 151017ed47 feat: 生成视频页面 2024-09-05 08:50:49 +08:00
RockYang 941d15b635 remove unused css file 2024-09-04 22:42:56 +08:00
RockYang d8058400dd adjust chat page styles 2024-09-04 18:07:39 +08:00
RockYang 5a1ed953de user can select function tools by themself 2024-09-04 14:53:21 +08:00
RockYang 58d127b602 add page and total field for pagination vo 2024-09-03 18:32:15 +08:00
RockYang 13bf73e6cf add sync lock for sub or add user's power 2024-09-03 12:09:36 +08:00
RockYang aa46fdb113 luma create and list api is ready 2024-09-02 18:08:50 +08:00
胡双明 4a09da3a25 feat(Luma): 加上传前后帧调换功能 2024-09-02 17:33:03 +08:00
RockYang d70fcbb9f3 optimize ChatPlus page, fixed bug for websocket reconnection 2024-09-02 16:35:15 +08:00
RockYang a16ef6476d add luma api service 2024-08-30 18:12:14 +08:00
RockYang dae75343d0 update change log file 2024-08-30 16:47:52 +08:00
RockYang d7564127d2 suno add new function for merging full songs and upload custom music 2024-08-30 16:46:48 +08:00
RockYang c6bf54fd9d add logo for favirate icon 2024-08-29 13:36:35 +08:00
RockYang 8d47072e1c luma page, upload image and remove image function is ready 2024-08-26 17:59:05 +08:00
RockYang d601226187 add download for video 2024-08-26 07:24:04 +08:00
RockYang 4801832d9d luma page video list component is ready 2024-08-23 18:25:58 +08:00
RockYang 6b0f42d0b8 luma page is ready 2024-08-20 14:31:40 +08:00
RockYang 08379c21ac refactor reset password functions 2024-08-19 12:05:00 +08:00
RockYang 2499bd9ad3 fixed styles 2024-08-19 06:42:53 +08:00
RockYang ad6da0d320 add bind mobile, bind email, bind wechat function is ready 2024-08-14 15:56:50 +08:00
RockYang a2583c0591 add mobile and email filed for user 2024-08-13 18:40:50 +08:00
RockYang 0ea4a29152 add verification code for login and register page 2024-08-13 14:55:47 +08:00
RockYang 7fe50788a5 add Captcha components 2024-08-13 10:01:07 +08:00
RockYang c4a68076d6 fixed conflicts 2024-08-13 06:48:22 +08:00
RockYang 9e365895af add wechat login for login dialog 2024-08-12 18:00:34 +08:00
RockYang 9ece25e17d add drag icon for dragable rows 2024-08-12 14:00:50 +08:00
RockYang 2140eff51b release v4.1.2 2024-08-09 18:50:02 +08:00
RockYang dfba3c30a5 redeem code function is ready 2024-08-09 18:19:51 +08:00
RockYang 4b717109d2 add redeem code function 2024-08-08 18:28:50 +08:00
RockYang e54e908fbc add cache for getting user info and system configs 2024-08-08 14:37:33 +08:00
RockYang 7a11a9ef15 add clear unpaid order functions 2024-08-07 18:00:28 +08:00
RockYang c9d0700fd9 refactor stable diffusion service, use api key instead of configs 2024-08-07 17:30:59 +08:00
RockYang f9b809801d refactor midjourney service, use api key in database 2024-08-06 18:30:57 +08:00
RockYang cc551ba266 show sql error message 2024-08-05 16:14:44 +08:00
RockYang 754ba02263 fixed build script 2024-08-01 18:45:53 +08:00
RockYang 23787ff462 fixed build script 2024-08-01 18:45:53 +08:00
RockYang 7ddf57ae06 merge v4.0.8 2024-08-01 18:09:00 +08:00
RockYang 9f603c5e77 merge v4.0.8 2024-08-01 18:09:00 +08:00
RockYang 3d720bdd81 update change log 2024-08-01 08:54:04 +08:00
RockYang cc5180a6f7 update readme 2024-08-01 08:52:46 +08:00
RockYang a8eccbe43b update readme 2024-08-01 08:52:46 +08:00
RockYang 2ac77ac39f restore use power when removed not finish jobs 2024-07-31 16:08:46 +08:00
RockYang 4eaa518cf3 remove platform field for api key and chat model 2024-07-30 17:24:21 +08:00
RockYang 5622f94fc8 update datebasesl 2024-07-30 14:55:49 +08:00
RockYang 95457b7dcd add back-to-top component for all list page 2024-07-29 11:00:53 +08:00
RockYang 6a9de72c78 remove chat debug log 2024-07-28 18:55:17 +08:00
RockYang 088a614160 add function to generate lyrics 2024-07-28 10:04:53 +08:00
RockYang 013ee98f53 song detail page is ready 2024-07-26 19:12:44 +08:00
RockYang dc46ed0e05 adjust chat records layout styles 2024-07-25 11:01:27 +08:00
RockYang 2a0c657ca3 add put url file for oss interface 2024-07-23 18:36:26 +08:00
RockYang a0aee80c63 remove other platform supports, ONLY use chatGPT API 2024-07-22 18:36:58 +08:00
RockYang 4910be3403 optimize foot copyright snaps 2024-07-22 17:54:09 +08:00
RockYang 1541d74c84 add close button for music player 2024-07-22 07:12:21 +08:00
RockYang 34c9151dc1 enable use random pure color background for index page 2024-07-19 18:43:01 +08:00
RockYang 6c69770ed6 the music player is ready 2024-07-18 18:34:11 +08:00
RockYang 5b7c38c67f add suno page 2024-07-17 18:58:09 +08:00
RockYang b7bec8ecb7 support upload file from clipboard 2024-07-17 10:23:02 +08:00
RockYang 82fe9c3596 allow user to use chat role directly, no need to add to workspace 2024-07-16 18:28:08 +08:00
RockYang 9f44c34d34 update docs url 2024-07-16 18:15:34 +08:00
RockYang afd84516b0 update docs url 2024-07-16 18:15:34 +08:00
RockYang 024f00b73c show error message for Midjourney task list page 2024-07-16 17:16:58 +08:00
RockYang 24a21bf2ee fixed bug for function call for openai 2024-07-16 06:25:40 +08:00
RockYang f22c6bf658 优化版权显示逻辑,允许激活用户更改自定义版权 2024-07-15 18:44:14 +08:00
RockYang 0df700ec18 update docker-compose file 2024-07-12 18:13:43 +08:00
RockYang 46141f87b8 update database file, add tika host config 2024-07-12 18:10:32 +08:00
RockYang eecce10018 tidy apis 2024-07-12 14:39:14 +08:00
RockYang b793b81768 update geekai image version 2024-07-05 11:09:13 +08:00
RockYang c8506e3d3b update geekai image version 2024-07-05 11:09:13 +08:00
RockYang bddd611cc1 wechat login is ready 2024-07-04 15:34:32 +08:00
RockYang b399fc557a feat: support wechat login function 2024-07-02 18:27:06 +08:00
RockYang 233f6e00f0 fixed conflicts 2024-06-30 06:09:12 +08:00
RockYang b20ea734fd fixed conflicts 2024-06-30 06:09:12 +08:00
RockYang b7dba68549 optimize ngin configuration for chat-plus.conf 2024-06-30 05:44:39 +08:00
RockYang 7ab0acd6a4 optimize ngin configuration for chat-plus.conf 2024-06-30 05:44:39 +08:00
RockYang 1d60ccc516 docs: update change log file 2024-06-28 16:21:43 +08:00
RockYang 2a8d2213b1 feat: optimize chat page data list style, support list style and chat style 2024-06-28 15:53:49 +08:00
RockYang a27ce36a32 feat: chat with file function is ready 2024-06-27 18:01:49 +08:00
RockYang 3fdcc895ed enable set custom index background image 2024-06-27 10:49:31 +08:00
RockYang 88e510d07a add test code for reading pdf files 2024-06-26 18:50:48 +08:00
RockYang 2526feb0d9 feat: new UI for chat file manager is ready 2024-06-25 18:59:27 +08:00
RockYang 4171b2bdfb fixed bug: mobile chat list could not update chat title 2024-06-25 09:53:08 +08:00
RockYang 0611f8de6c update version 2024-06-23 17:54:42 +08:00
RockYang df9e495dc5 add wechat payment configs sample 2024-06-22 16:04:04 +08:00
RockYang 8a48476105 update docker-compose file 2024-06-22 12:37:41 +08:00
RockYang 9d4a1705a5 add database files 2024-06-22 12:17:35 +08:00
RockYang 871e5733c7 finish mobile wechat payment 2024-06-22 12:10:43 +08:00
RockYang bdea12c51a update docker image mirror url 2024-06-19 08:35:05 +08:00
RockYang e8a663e3c7 update docker image mirror url 2024-06-19 08:35:05 +08:00
RockYang a27d9ea259 Merge branch 'main' of gitee.com:blackfox/geekai 2024-06-19 08:22:00 +08:00
RockYang 422d439627 Merge branch 'main' of gitee.com:blackfox/geekai 2024-06-19 08:22:00 +08:00
RockYang 7cd824c284 update docker image version to 4.0.6 2024-06-15 15:35:32 +08:00
RockYang edeff3c169 update docker image version to 4.0.6 2024-06-15 15:35:32 +08:00
RockYang e27d95e2b5 update docker image version to 4.0.6 2024-06-15 15:33:38 +08:00
RockYang 13076421a1 update docker image version to 4.0.6 2024-06-15 15:33:38 +08:00
RockYang 3943e7f05f update change log files 2024-06-14 18:23:54 +08:00
GeekMaster 6839827db0 Merge branch 'main' of https://github.com/yangjian102621/geekai into main 2024-06-12 15:44:26 +08:00
GeekMaster cd0d9dad98 Merge branch 'main' of https://github.com/yangjian102621/geekai into main 2024-06-12 15:44:26 +08:00
RockYang 857da34b9c wechat payment is ready for PC 2024-06-12 14:20:37 +08:00
RockYang ccad7e7bb5 change payment component, upgrade golang to 1.22.4 2024-06-11 11:48:41 +08:00
RockYang b8de15d66e fix bug: free model not record the chat history 2024-06-06 15:01:32 +08:00
RockYang c02661ea29 fixe page styles 2024-06-05 18:08:23 +08:00
RockYang 3c70c8ae59 fixed bug markmap generation 2024-06-04 16:21:08 +08:00
RockYang d6a04f96fe Merge pull request #208 from mari1995/main
图片墙,选择框单选问题
2024-06-04 08:33:51 +08:00
RockYang b985b62068 Merge pull request #208 from mari1995/main
图片墙,选择框单选问题
2024-06-04 08:33:51 +08:00
RockYang 297b760293 dalle3 and gptt-4o api compatible with azure 2024-06-03 18:34:37 +08:00
RockYang 5f820b9dc1 merge v4.0.6 2024-06-03 14:22:08 +08:00
RockYang 088abfe7ab merge v4.0.6 2024-06-03 14:22:08 +08:00
RockYang 17340ae0ac fixed bug for function call error None is not of type 'array' 2024-05-30 09:59:44 +08:00
RockYang fe687af8ca fixed bug for mobile chat share 2024-05-30 08:37:14 +08:00
RockYang 7e26029268 add v4.0.8 database sql file 2024-05-29 17:41:37 +08:00
RockYang 03f7f2a53e feat: add dalle3 page for h5 2024-05-29 17:25:01 +08:00
RockYang 296260bf6a feat: add system config for enable rand background image for index page 2024-05-29 16:24:56 +08:00
RockYang dd07acd21a feat: add system config for enable rand background image for index page 2024-05-29 16:23:42 +08:00
RockYang 89f6402fbf fix markdown formula parse plugin 2024-05-29 13:49:45 +08:00
SSMario 6788edbe9d Update Image.vue 2024-05-27 21:23:11 +08:00
SSMario 853059c814 Update Image.vue 2024-05-27 21:23:11 +08:00
SSMario 3895305882 Update Image.vue 2024-05-27 21:00:59 +08:00
SSMario 5a0876f8dc Update Image.vue 2024-05-27 21:00:59 +08:00
RockYang 2e32238652 micro fixs 2024-05-27 17:39:17 +08:00
SSMario c2acbaaa94 Update ImagesWall.vue 2024-05-27 17:04:15 +08:00
SSMario ad1a99aa44 Update ImagesWall.vue 2024-05-27 17:04:15 +08:00
RockYang 24e4be019a fixed bug for dalle prompt translate 2024-05-27 11:42:14 +08:00
RockYang 13c917ad7e put model and app selector on the top of chat page 2024-05-24 12:33:22 +08:00
RockYang 0a8cf6870f fixed bug for payment api authorization 2024-05-24 11:31:38 +08:00
RockYang 061291cebb chore: use config value for order pay timeout 2024-05-22 18:15:06 +08:00
RockYang b8e0d7760b feat: add sign check for PC QR code payment 2024-05-22 17:47:53 +08:00
RockYang 962de0183c extract code for saving chat history 2024-05-22 15:32:44 +08:00
RockYang 627396dbf7 check if the api url in whitelist for mj plus client 2024-05-22 11:47:04 +08:00
RockYang 6c300bb018 fixed bug for mobile chat page change chat model not work 2024-05-21 17:54:03 +08:00
RockYang 9273df4af2 auto resize the input element rows, when use inputed more than one line 2024-05-21 17:36:47 +08:00
RockYang 4a99be2f15 remove license code 2024-05-21 16:20:29 +08:00
RockYang 669d784f54 remove license code 2024-05-21 16:20:29 +08:00
RockYang 5253d657b6 add logs for updating database failed 2024-05-21 11:55:38 +08:00
RockYang 27c816cf3b merge conflicts for v4.0.5 2024-05-21 11:30:40 +08:00
RockYang c2548c5007 merge conflicts for v4.0.5 2024-05-21 11:30:40 +08:00
RockYang 0d81776212 update docker image name 2024-05-21 11:21:27 +08:00
RockYang bdec823148 update docker image name 2024-05-21 11:21:27 +08:00
RockYang a6de958daa update docker image url 2024-05-21 11:03:11 +08:00
RockYang cccab31c0f rename project name to geekai 2024-05-20 15:14:02 +08:00
RockYang 50c25d4574 rename project name to geekai 2024-05-20 15:14:02 +08:00
RockYang 68100f7f24 rename project name to geekai 2024-05-20 15:11:14 +08:00
RockYang 38777ea285 update database file 2024-05-19 19:37:16 +08:00
RockYang 00c8f08179 remove code for set left component fixed height 2024-05-18 08:07:09 +08:00
RockYang 45d6579fb6 refactor login dialog for front page 2024-05-18 00:27:32 +08:00
RockYang dad9254128 finished refactor chat page UI 2024-05-17 19:25:38 +08:00
RockYang 4ddf3bf2bf fixed bugs for send message captcha component 2024-05-17 08:41:09 +08:00
RockYang 9934143f00 fixed bugs for send message captcha component 2024-05-17 08:41:09 +08:00
RockYang f617bde81b fixed bugs for send message captcha component 2024-05-16 22:33:00 +08:00
RockYang b2771b7b3f feat: add top navbar for front page 2024-05-16 20:10:00 +08:00
RockYang 44b3237dd6 feat: support add external link menu 2024-05-16 10:53:00 +08:00
RockYang b987a2d365 handler chat error in the chat entry func 2024-05-15 15:30:34 +08:00
RockYang 1cf299f090 add charge link for insufficient of power 2024-05-15 07:10:31 +08:00
RockYang c87d78c666 fix bug for white-list api key check 2024-05-14 22:30:42 +08:00
RockYang 3d37a3d367 update readme 2024-05-14 18:23:12 +08:00
RockYang 79c8d90049 update readme 2024-05-14 18:23:12 +08:00
RockYang 73d8236697 update change log 2024-05-14 18:20:52 +08:00
RockYang 1afc3e5e1c update change log 2024-05-14 18:20:52 +08:00
RockYang 87e8bbfa41 update version 2024-05-14 18:03:58 +08:00
RockYang 3e3d3e162e refactor: use waterflow component in mj, sd and dall image drawing page 2024-05-13 19:04:00 +08:00
RockYang cc80b87fee fixed sd page waterfall component 2024-05-11 18:27:35 +08:00
RockYang b614ecccc2 enable to update AI Drawing configuarations in admin console page 2024-05-11 17:27:14 +08:00
RockYang 8251c6589b fix: markmap do not cost power in front page 2024-05-11 07:08:14 +08:00
RockYang 4edf2ea02a optimize chat handler error handle code 2024-05-10 18:26:19 +08:00
RockYang 12dd4659cd fix bug: remove chat role failed 2024-05-10 17:38:55 +08:00
RockYang 5d4f40c004 fixed bug for dalle3 task not decrease power 2024-05-10 11:18:37 +08:00
RockYang 3c34e8e0e7 fixed bug for dalle3 task not decrease power 2024-05-10 11:17:52 +08:00
RockYang 7441a3a717 fixed bug for dalle3 task not decrease power 2024-05-10 11:17:52 +08:00
RockYang 3eef9a836b add toolbar for markmap component 2024-05-10 06:38:34 +08:00
RockYang 922202734a fixed bug for markmap 2024-05-09 21:55:40 +08:00
RockYang fffd7e375f fixed bug for markmap 2024-05-09 21:55:40 +08:00
RockYang d87f09c957 use proxy for downloading discord images 2024-05-09 18:48:53 +08:00
RockYang b29884f8df add stable diffusion default negtive prompt system config 2024-05-07 16:49:54 +08:00
RockYang b270960a04 remove license code 2024-05-07 16:41:35 +08:00
RockYang c0ee2c2d8e remove license code 2024-05-07 16:41:35 +08:00
RockYang 5c4899df6e upgrade to v4.0.4 2024-05-07 16:32:05 +08:00
RockYang be1580e949 upgrade to v4.0.4 2024-05-07 16:32:05 +08:00
RockYang 6424eb871c add stable diffusion default negtive prompt system config 2024-05-07 16:21:31 +08:00
RockYang 8a8c43c7a5 handle the exception for web front page 2024-05-06 17:39:58 +08:00
RockYang 9a503ddc72 fixed conflicts 2024-05-06 14:44:09 +08:00
RockYang b130466c8f chore: change module name to geekai, add copyright in source code 2024-05-06 14:41:27 +08:00
RockYang 2f0215ac87 Update README.md 2024-05-06 10:45:50 +08:00
RockYang 3ab5459778 Update README.md 2024-05-06 10:45:50 +08:00
RockYang dd5cc206e5 update LICENSE.
Signed-off-by: RockYang <yangjian102621@gmail.com>
2024-05-05 03:05:23 +00:00
RockYang 4eb1bf2de2 update LICENSE.
Signed-off-by: RockYang <yangjian102621@gmail.com>
2024-05-05 03:05:23 +00:00
RockYang 6c47551b03 fix bug for waterflow component 2024-05-05 10:52:29 +08:00
RockYang 657ecccee3 update changelog 2024-05-04 21:32:43 +08:00
RockYang b4ba70c0f3 update changelog 2024-05-04 21:32:43 +08:00
RockYang c2bf5e845a fix bug for license synchronize 2024-05-04 21:30:29 +08:00
RockYang 8900e72e45 opt: optimize mobile images page styles 2024-05-03 08:14:33 +08:00
RockYang 0cb192135d fix: fix bug for dalle power refund 2024-05-01 08:48:32 +08:00
RockYang c1ca687630 chore: update docker image name 2024-05-01 07:59:20 +08:00
RockYang 29cc24508b opt: styles and view micro optimization 2024-05-01 07:40:56 +08:00
RockYang 3db55cbd48 opt: close unused websocket connections 2024-04-30 22:54:39 +08:00
RockYang c0a7f41747 chore: replace docker image url with AliYun 2024-04-30 19:08:33 +08:00
RockYang 1ba08bbfa6 feat: change theme for mobile site is ready 2024-04-30 18:57:15 +08:00
RockYang 51a8f42d89 feat: mobile page refactor is finished 2024-04-29 19:22:00 +08:00
RockYang 3b081ff0f4 opt: add chat config for mobile chat session 2024-04-29 09:39:23 +08:00
RockYang 7f31a301e3 feat: add index page for mobile 2024-04-28 19:09:26 +08:00
RockYang 039d949523 theme change is ready 2024-04-27 13:13:28 +08:00
RockYang 5b16dc6ee7 feat: admin console dark theme 2024-04-26 18:10:17 +08:00
RockYang 34648c704f opt: optimize index and login page UI 2024-04-26 16:07:02 +08:00
RockYang 1966e69460 synchronize license every 10 secs 2024-04-26 14:35:01 +08:00
RockYang 74b1b07b31 change index page background 2024-04-25 18:54:33 +08:00
RockYang 2e023cb8dc update readme 2024-04-25 06:27:30 +08:00
RockYang 118c0e1ff1 update readme 2024-04-25 06:27:30 +08:00
RockYang e933f32d9c remove unused files 2024-04-24 21:22:56 +08:00
RockYang 81d075c028 remove unused files 2024-04-24 21:22:56 +08:00
RockYang 514dd6c76a show license info in admin active page, optimize markdown generate prompt 2024-04-24 19:00:28 +08:00
RockYang 0b2501c1d8 output the error to chat page directly, replace the common error message 'AI开小差' 2024-04-24 10:10:03 +08:00
RockYang ddc323a8c3 output the error to chat page directly, replace the common error message 'AI开小差' 2024-04-24 10:10:03 +08:00
RockYang 2933c057a2 fixed bug for markmap 2024-04-23 20:47:06 +08:00
RockYang c1d892069e release v4.0.4 2024-04-23 18:47:23 +08:00
RockYang 20ed6d1d3d release v4.0.4 2024-04-23 18:47:23 +08:00
RockYang 90f5275201 optimize styles and release v4.0.4 2024-04-23 18:46:32 +08:00
RockYang fe64b203da feat: hide more navigator items 2024-04-22 16:27:53 +08:00
RockYang 9eb1353455 feat: support markmap svg export and download as png image 2024-04-22 14:20:51 +08:00
RockYang 8a1c55c731 allow users to select a chatApp to chat in chat app list page 2024-04-22 11:18:55 +08:00
RockYang 9059bebd07 optimize index page UI 2024-04-22 10:43:33 +08:00
RockYang d18f79fe74 optimize code for remove timeout and failed image drawing job 2024-04-21 21:44:28 +08:00
RockYang 3a8f0be28a image wall page add dalle 2024-04-21 20:42:42 +08:00
RockYang 60cf380f96 dalle image page is ready 2024-04-21 20:23:47 +08:00
RockYang ab8240613e DO NOT refresh finished jobs when job is running 2024-04-20 21:30:55 +08:00
RockYang d6e9dc6839 fixed markdown generating styles 2024-04-19 18:22:45 +08:00
RockYang 6891bdde53 support send email use TLS 2024-04-19 12:04:59 +08:00
RockYang bda335212d Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2024-04-19 10:56:05 +08:00
RockYang ecadfeab19 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2024-04-19 10:56:05 +08:00
RockYang 06f4cdc649 fixed bug for QWen response blank quotes 2024-04-19 10:55:29 +08:00
RockYang a38d547200 fixed bug for QWen response blank quotes 2024-04-19 10:55:29 +08:00
RockYang a15f431a7f fixed bug for QWen response blank quotes 2024-04-19 10:30:02 +08:00
RockYang 3bb5b66e8b fixed bug for chat handler doRequest method 2024-04-16 23:49:56 +08:00
RockYang 9bf7fa4081 compatible freeGPT35 API 2024-04-15 21:03:19 +08:00
RockYang d9ff3242c6 compatible freeGPT35 API 2024-04-15 21:03:19 +08:00
RockYang cd562ab8ed fixed bug for websocket close tip message 2024-04-15 18:15:15 +08:00
RockYang 3a8a69ac2e feat: markmap function is ready 2024-04-15 17:23:59 +08:00
RockYang 4ca9dfd9c0 fixed upscale and variation action url 2024-04-15 15:14:49 +08:00
RockYang 0d5b4936bd fixed upscale and variation action url 2024-04-15 15:14:49 +08:00
RockYang b5f6eaf159 opt: close the old connection for mj and sd clients 2024-04-15 09:34:20 +08:00
RockYang adfee8bf58 update version 2024-04-15 09:05:54 +08:00
RockYang 25c80921e0 update version 2024-04-15 09:05:54 +08:00
RockYang fbfa2a71a9 release v4.0.3 2024-04-15 08:26:07 +08:00
RockYang 5c51ed48c3 release v4.0.3 2024-04-15 08:26:07 +08:00
RockYang 22febfc42a markmap enable to select ai model 2024-04-15 06:16:53 +08:00
RockYang de482dd9f4 fixed chat page styles 2024-04-12 21:57:41 +08:00
RockYang 74af67a41f feat: markmap page view is ready 2024-04-12 18:49:24 +08:00
RockYang 170b41441b feat: allow chat model bind a fixed api key 2024-04-12 17:09:22 +08:00
RockYang 4991e50e16 chore: update build.sh 2024-04-11 21:26:33 +08:00
RockYang 1f236ef929 feat: use custom mode for mj upscale and variation operarions 2024-04-11 17:32:34 +08:00
RockYang 7f86c5984d feat: add index page 2024-04-10 18:23:55 +08:00
RockYang 6fcee49b19 feat: support for freeGPT35 API 2024-04-10 14:49:07 +08:00
RockYang ac527851a0 feat: support gpt-4-turbo-2014-04-09 vision function 2024-04-10 11:47:10 +08:00
RockYang 64bbeaedcb fixed bug for gpt-4-turbo-2024-0409 model function calls 2024-04-10 10:23:45 +08:00
RockYang 3e984a1bcb fix bug: remove timeout task ONLY for unfinished(progress < 100) 2024-04-10 06:25:54 +08:00
RockYang a7237fe62f Merge branch 'main' of gitee.com:blackfox/chatgpt-plus 2024-04-07 18:33:55 +08:00
RockYang 79165214d7 Merge branch 'main' of gitee.com:blackfox/chatgpt-plus 2024-04-07 18:33:55 +08:00
RockYang c3c454b7d7 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2024-04-07 18:32:20 +08:00
RockYang 3f7855dd6e Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2024-04-07 18:32:20 +08:00
RockYang d4d708d44b update database files 2024-04-07 18:26:45 +08:00
RockYang f0610281e5 update database files 2024-04-07 18:26:45 +08:00
RockYang 7f0b6a3a46 set the enable status for adding new api key with default value true 2024-04-07 10:17:10 +08:00
RockYang e87e7e2164 set the enable status for adding new api key with default value true 2024-04-07 10:17:10 +08:00
RockYang c2a7c089d2 update change log 2024-04-07 08:40:43 +08:00
RockYang 1a3ad390fd update change log 2024-04-07 08:40:43 +08:00
RockYang df5bd4df60 Merge branch 'dev' 2024-04-07 08:03:14 +08:00
RockYang c383367e9f Merge branch 'dev' 2024-04-07 08:03:14 +08:00
RockYang 79b6010104 optimize ngin configuration for chat-plus.conf 2024-04-07 08:02:47 +08:00
RockYang 7a9f45c96b optimize ngin configuration for chat-plus.conf 2024-04-07 08:02:47 +08:00
RockYang 97b0a98793 fix 404 error with remove api keys 2024-04-07 08:01:25 +08:00
RockYang 7277cb289f fix 404 error with remove api keys 2024-04-07 08:01:25 +08:00
RockYang 86b2d5eff2 fix bug for remove api 404 errorc 2024-04-06 20:36:52 +08:00
RockYang 7873847616 feat: show bind model on chat role list page 2024-04-05 21:21:28 +08:00
RockYang 5e46f149d9 feat: support uploading role icon 2024-04-05 17:41:23 +08:00
RockYang 574fc52332 feat: allow bind a chat model for chat role 2024-04-05 12:51:18 +08:00
RockYang 8c924ca98f update change log 2024-04-05 06:58:13 +08:00
RockYang a9adc0c1a2 feat: stable diffusion image drawing on mobile is ready 2024-04-03 18:13:48 +08:00
RockYang ed286c8894 feat: midjourney role and style consistency is ready 2024-04-02 19:01:28 +08:00
RockYang d900a3d08e update README.md.
Signed-off-by: RockYang <yangjian102621@gmail.com>
2024-04-02 10:00:45 +00:00
RockYang a55da15202 update README.md.
Signed-off-by: RockYang <yangjian102621@gmail.com>
2024-04-02 10:00:45 +00:00
RockYang cdf5b66729 Update README.md 2024-04-02 17:59:53 +08:00
RockYang 9728118f94 Update README.md 2024-04-02 17:59:53 +08:00
RockYang 9cc2ea412b feat: image preview for stable-diffusion task is ready 2024-04-02 17:24:38 +08:00
RockYang 3f1ad4b7dc feat: support midjourney --cref and --sref for role consistency 2024-04-02 14:59:53 +08:00
RockYang 56c225bf20 feat: update menu icons, add version in site titles 2024-04-01 18:20:00 +08:00
RockYang 8b68bfb28c show power for chat and imaging page 2024-03-31 20:49:12 +08:00
RockYang a5299b52d4 opt: change the relative path with absolute path for midjourney image uploading 2024-03-31 17:45:22 +08:00
RockYang 9e9bc52737 Merge branch 'main' into dev 2024-03-30 11:57:31 +08:00
RockYang 76106e4504 remove dead code 2024-03-30 11:57:23 +08:00
RockYang a6c00c42fa fixed conflicts 2024-03-29 18:10:59 +08:00
RockYang 4e23005b5b fixed conflicts 2024-03-29 18:10:59 +08:00
RockYang f27e2de220 update readme file 2024-03-29 18:06:55 +08:00
RockYang eefcabcff3 update databases 2024-03-29 17:43:38 +08:00
RockYang 63e4669e3f feat: support custom menu 2024-03-29 15:41:58 +08:00
RockYang 7d5428f775 add tip for mj image buttons 2024-03-28 21:41:49 +08:00
RockYang ab3c4562fa fix: fix overflow hidden for admin page 2024-03-28 18:51:09 +08:00
RockYang 13f89bf335 fix: fix overflow hidden for mobile page 2024-03-28 18:13:33 +08:00
RockYang e6a18445c3 fix: use slide captcha for iphone 2024-03-28 15:00:53 +08:00
RockYang c8ab209426 feat: add mj_action_power system config item 2024-03-28 09:53:41 +08:00
RockYang 360fea4085 feat: change midjourney origin implements, replace midjourney bot with midjourney-proxy 2024-03-27 18:57:15 +08:00
RockYang 9794d67eaa feat: auto translate and rewrite prompt for midjourney and stable-diffusion 2024-03-27 13:45:52 +08:00
RockYang b60a639312 feat: stable-diffusion refactored, replace websocket api with sdapi 2024-03-26 18:23:08 +08:00
RockYang 870706c4ff fix: can not change user's power in admin console 2024-03-25 11:40:03 +08:00
RockYang 491471fa10 fix: fix bug for update user's power in admin page did not work 2024-03-23 16:21:37 +08:00
RockYang 6dbf61d4e4 No need to login with Stable-Diffusion page and Invite page 2024-03-23 15:45:37 +08:00
RockYang 81545d192b fix: fix code highlight error when add formule detecting 2024-03-22 19:09:04 +08:00
RockYang 37cdc26160 chore: correct prompt messages 2024-03-22 18:27:57 +08:00
RockYang fb6a35ebe4 feat: add chart for admin dashbord 2024-03-22 16:57:30 +08:00
RockYang f7a565bb80 feat: integrate xxl-job-admin to implements automatic task scheduling 2024-03-22 13:47:16 +08:00
RockYang bf5e72b7e0 feat: save prompt in power log for dalle-3 2024-03-21 15:55:39 +08:00
RockYang ec6186596d feat: add manager list page in console page 2024-03-21 15:24:28 +08:00
RockYang dece19cec6 feat: add powerlog page for admin console 2024-03-21 13:46:39 +08:00
RockYang 6aa1d27711 opt: optimize the formula show styles 2024-03-21 11:04:12 +08:00
RockYang 264e77f383 always parse authorization token for all request 2024-03-20 21:11:52 +08:00
RockYang 0cbc284e42 fixed conflicts 2024-03-20 20:40:22 +08:00
RockYang 9e42a334fa update README.md.
Signed-off-by: RockYang <yangjian102621@gmail.com>
2024-03-20 20:39:44 +08:00
RockYang 49438d2ee1 feat: Async loading midjourney job for mobile MidJourney page 2024-03-20 18:39:14 +08:00
RockYang 735aa24020 feat: h5 payment for payjs is ready 2024-03-20 17:46:39 +08:00
RockYang 14f63a203d feat: payment for mobile pages is ready 2024-03-20 16:14:02 +08:00
RockYang e11c0a3633 feat: the power log page is ready 2024-03-20 14:14:30 +08:00
RockYang ca83f139f7 feat: no need login refactor with member and chatApps page 2024-03-19 18:59:02 +08:00
RockYang 58e44b7d12 feat: load preview page do not require user to login 2024-03-19 18:25:01 +08:00
RockYang 8f47474edd feat: optimize login dialog 2024-03-19 10:47:13 +08:00
RockYang c5200aada8 feat: refactoring adjustments for reward page is ready 2024-03-18 18:28:34 +08:00
RockYang 4e39b93673 feat: refactoring adjustments for member pages 2024-03-18 16:59:07 +08:00
RockYang f215643f2e feat: The 'chat_models' field of user table, holds the model IDS in place of the model values 2024-03-18 15:37:46 +08:00
RockYang aa72be6ff3 remove new-ui files 2024-03-18 12:01:34 +08:00
RockYang b567e56b60 chore: adjust page styles 2024-03-18 06:46:08 +08:00
RockYang 17431c1707 重构主体工作完成 2024-03-15 18:35:10 +08:00
RockYang 7c9ebef5e9 restore new ui files 2024-03-15 11:13:02 +08:00
廖彦棋 72b0e11543 fix(ui): 交互修复调整 2024-03-15 11:07:41 +08:00
RockYang 9e2af9dbca feat: refactor user list page for new UI 2024-03-15 09:29:19 +08:00
廖彦棋 b7cc987132 fix(ui): 用户管理有效期传参调整,权限标识补充 2024-03-15 09:25:06 +08:00
廖彦棋 8476c73b0c Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-14 17:49:18 +08:00
廖彦棋 e76b49afd2 refactor(ui): 无权限页面调整 2024-03-14 17:49:15 +08:00
吴汉强 c712b95e0d feat(ui): 后台首页去掉权限判断 2024-03-14 17:24:40 +08:00
廖彦棋 1ee1a8d6d9 Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-14 17:19:41 +08:00
廖彦棋 f01c764589 feat(ui): 无权限判断 2024-03-14 17:19:39 +08:00
吴汉强 6df66d150b Merge remote-tracking branch 'origin/ui' into ui 2024-03-14 17:12:53 +08:00
吴汉强 6132f94eff feat(ui): 新增 sql 2024-03-14 17:12:48 +08:00
廖彦棋 ac5e67ea73 Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-14 17:06:15 +08:00
吴汉强 143d2b44d0 feat(ui): 403,没权限 2024-03-14 16:41:38 +08:00
吴汉强 ef130fe377 feat(ui): 网站配置需授权,去掉 2024-03-14 16:28:49 +08:00
廖彦棋 d7d1e2100c fix(ui): 调整 2024-03-14 16:14:49 +08:00
吴汉强 af5afd7700 feat(ui): 后端加权限验证 2024-03-14 15:39:12 +08:00
廖彦棋 a64df5cf0c feat(ui): 角色管理 2024-03-14 15:25:17 +08:00
廖彦棋 3866319373 feat(ui): 新增系统分类菜单 2024-03-14 15:17:53 +08:00
廖彦棋 ec5ad809f2 Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-14 10:56:56 +08:00
廖彦棋 268acb174d fix(ui): 修复 2024-03-14 10:56:54 +08:00
吴汉强 3733877be1 Merge remote-tracking branch 'origin/ui' into ui 2024-03-14 10:28:39 +08:00
吴汉强 6d5579a8d6 feat(ui): 登录接口返回权限 2024-03-14 10:28:32 +08:00
廖彦棋 306cd2f945 feat(ui): 管理后台新增权限及部分组合式函数优化 2024-03-14 10:27:09 +08:00
廖彦棋 5d4dd1e66f Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-13 17:30:26 +08:00
廖彦棋 5f371bdc4a feat(ui): web移动端初始化 2024-03-13 17:30:24 +08:00
吴汉强 aa58ba392e Merge remote-tracking branch 'origin/ui' into ui 2024-03-13 17:24:38 +08:00
吴汉强 0485610818 feat(ui): 增加角色管理,管理员方法新增角色关联 2024-03-13 17:24:30 +08:00
chenzifan 7cb8becc09 Merge remote-tracking branch 'origin/ui' into ui 2024-03-13 14:40:45 +08:00
chenzifan 8fa535a01b refactor: remove api change to post request 2024-03-13 14:40:38 +08:00
吴汉强 5f202e03a2 Merge remote-tracking branch 'origin/ui' into ui 2024-03-13 11:41:07 +08:00
吴汉强 ef2dfe330b feat(ui): 增加系统权限管理 2024-03-13 11:41:01 +08:00
廖彦棋 fcd86fbebd fix(ui): ui调整 2024-03-13 09:54:20 +08:00
RockYang 960d294aa2 docs: update sql file 2024-03-13 08:49:40 +08:00
RockYang 27f8e63f3d Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-13 08:48:10 +08:00
chenzifan e8d7ad58be fix: 删除系统管理员失效的问题 2024-03-13 08:47:17 +08:00
chenzifan f4d537e0f5 Merge remote-tracking branch 'origin/ui' into ui
# Conflicts:
#	api/handler/admin/admin_user_handler.go
2024-03-13 08:46:16 +08:00
chenzifan d6c93dd537 fix: 删除系统管理员失效的问题 2024-03-13 08:45:09 +08:00
廖彦棋 aa5fff47de Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-13 08:44:50 +08:00
廖彦棋 c920d8423d feat(ui): 调整 2024-03-13 08:44:48 +08:00
chenzf@pvc123.com b8fcfe2e7c feat: 超级管理员不支持修改和删除 2024-03-12 21:16:05 +08:00
RockYang 072c96e1ec Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-12 18:07:24 +08:00
RockYang 38c8ece642 fix conflicts 2024-03-12 18:07:19 +08:00
chenzifan 9bf1576fb4 feat: 增加系统管理员 2024-03-12 18:06:49 +08:00
RockYang d648e177c4 fix conflicts 2024-03-12 18:03:24 +08:00
RockYang e8f62af7f6 refactor: use power replace calls for front pages 2024-03-12 17:47:06 +08:00
RockYang e746aafa2f refactor: 重构项目,为所有的 AI 工具都引入算力,采用算力统一结算各个工具的调用次数和权限 2024-03-12 15:40:44 +08:00
廖彦棋 71f7a1b166 Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-12 08:37:29 +08:00
廖彦棋 678f570a90 refactor(ui): 调整 2024-03-12 08:37:27 +08:00
huangqj 4bc1f195db fix(ui):环境变量 2024-03-11 16:10:49 +08:00
廖彦棋 b2ff49ee94 feat(ui): 新增系统管理员 2024-03-11 15:59:15 +08:00
廖彦棋 d9558f13ad fix(ui): 删除冗余 2024-03-11 14:23:11 +08:00
廖彦棋 8dc8f2567e fix(ui): 删除冗余 2024-03-11 14:22:39 +08:00
RockYang 316636f83c feat: replace Tools param with Function param for OpenAI chat API 2024-03-11 14:09:19 +08:00
廖彦棋 e3c2fbf82a Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-11 13:52:24 +08:00
廖彦棋 1b6175e598 fix(ui): type 2024-03-11 13:52:22 +08:00
chenzifan 26dc479596 feat: 优化后台UI 2024-03-11 13:51:26 +08:00
chenzifan dca25dbb78 Merge remote-tracking branch 'origin/ui' into ui 2024-03-11 13:46:46 +08:00
廖彦棋 c55275872e refactor(ui): 调整优化 2024-03-11 13:46:08 +08:00
廖彦棋 d4d1fb26f6 feat(ui): 细节优化 2024-03-11 12:02:20 +08:00
廖彦棋 a48cb7bc34 feat(ui): 上传功能补充 2024-03-11 11:41:50 +08:00
廖彦棋 8af6e9290f feat(ui): 登录新增验证码及记住密码功能 2024-03-11 10:49:13 +08:00
廖彦棋 14b277d813 feat(ul): 顶部信息 2024-03-11 09:00:00 +08:00
huangqj 20915038a3 feat(ui):看板图表 样式调整 2024-03-11 08:35:21 +08:00
chenzifan 15723457cb Merge remote-tracking branch 'origin/ui' into ui 2024-03-11 08:01:54 +08:00
RockYang be8a0ec184 opt: remove global keyup event bind 2024-03-10 09:45:59 +08:00
RockYang ec79fc933c opt: remove global keyup event bind 2024-03-10 09:45:59 +08:00
RockYang b02e3aad95 docs: update config.toml 2024-03-10 09:45:59 +08:00
RockYang 3ed5a420a7 docs: update config.toml 2024-03-10 09:45:59 +08:00
RockYang 08eca511ad docs: add database sql file for v3.2.7 2024-03-10 09:45:59 +08:00
RockYang 711e701933 docs: add database sql file for v3.2.7 2024-03-10 09:45:59 +08:00
RockYang c34e911596 feat: allow to view chat message in manager console 2024-03-10 09:45:59 +08:00
RockYang e6bbd83c6b feat: allow to view chat message in manager console 2024-03-10 09:45:59 +08:00
RockYang 8a452c3072 feat: download image which ai generated in dialog and replace the image url 2024-03-10 09:45:59 +08:00
RockYang b99b310306 feat: download image which ai generated in dialog and replace the image url 2024-03-10 09:45:59 +08:00
RockYang 13bfb14107 feat: image-wall page for mobile is ready 2024-03-10 09:45:59 +08:00
RockYang e3c46016e0 feat: image-wall page for mobile is ready 2024-03-10 09:45:59 +08:00
RockYang 4188b0969e feat: allow user config third-party platform openai and mj api key 2024-03-10 09:45:59 +08:00
RockYang eb739fcccb feat: allow user config third-party platform openai and mj api key 2024-03-10 09:45:59 +08:00
RockYang 0c27795a10 feat: added delete file function 2024-03-10 09:45:59 +08:00
RockYang 9a94505f43 feat: added delete file function 2024-03-10 09:45:59 +08:00
RockYang d05693c5c1 fix: verifycation component touch event coordinates misplace in iphone browser 2024-03-10 09:45:59 +08:00
RockYang 7c5c3d8a3c fix: verifycation component touch event coordinates misplace in iphone browser 2024-03-10 09:45:59 +08:00
RockYang c0b2063b38 fix: fix bug for regenerate button did not work 2024-03-10 09:45:59 +08:00
RockYang 7027f9a55b fix: fix bug for regenerate button did not work 2024-03-10 09:45:59 +08:00
RockYang 4d183747b1 fix: Upscale and Variation task overrite each other 2024-03-10 09:45:59 +08:00
RockYang cf3d8af260 fix: Upscale and Variation task overrite each other 2024-03-10 09:45:59 +08:00
RockYang 08fe1b2f75 feat: midjourney mobile page all function is ready 2024-03-10 09:45:59 +08:00
RockYang 4ea7352674 feat: midjourney mobile page all function is ready 2024-03-10 09:45:59 +08:00
RockYang db3e8a267e feat: mobile mj list page is ready 2024-03-10 09:45:59 +08:00
RockYang 6e6885849b feat: mobile mj list page is ready 2024-03-10 09:45:59 +08:00
RockYang 8fc62682c4 feat: add mj image list component for mobile page. fixed bug for html tag escape 2024-03-10 09:45:59 +08:00
RockYang 827f2b9739 feat: add mj image list component for mobile page. fixed bug for html tag escape 2024-03-10 09:45:59 +08:00
RockYang 75031914a3 feat: add functions mj page for mobile 2024-03-10 09:45:59 +08:00
RockYang 54d70623ab feat: add functions mj page for mobile 2024-03-10 09:45:59 +08:00
RockYang a4c9fdd95a opt: add default extension for mj image 2024-03-10 09:45:59 +08:00
RockYang ed819e8c79 opt: add default extension for mj image 2024-03-10 09:45:59 +08:00
RockYang 6a9bfeb5aa feat: mj for mobile page payout is ready 2024-03-10 09:45:59 +08:00
RockYang 94680c4411 feat: mj for mobile page payout is ready 2024-03-10 09:45:59 +08:00
RockYang e654766f60 add docs and github link 2024-03-10 09:45:59 +08:00
RockYang 8bcbb68438 add docs and github link 2024-03-10 09:45:59 +08:00
RockYang 0ef6955f96 opt: enable use cdn url for mj-plus 2024-03-10 09:45:59 +08:00
RockYang b9f31d42f4 opt: enable use cdn url for mj-plus 2024-03-10 09:45:59 +08:00
RockYang b4501557c9 feat: LaTeX parse is ready 2024-03-10 09:45:59 +08:00
RockYang dc2b768c6b feat: LaTeX parse is ready 2024-03-10 09:45:59 +08:00
RockYang a2ed99e6cb feat: add model field for chat_item and and chat_history data table 2024-03-10 09:45:59 +08:00
RockYang 60df732db7 feat: add model field for chat_item and and chat_history data table 2024-03-10 09:45:59 +08:00
RockYang 6bd6bb3885 feat: add err_msg field for mj and sd jobs 2024-03-10 09:45:59 +08:00
RockYang 6d85d128cc feat: add err_msg field for mj and sd jobs 2024-03-10 09:45:59 +08:00
RockYang 399cf65fc9 feat: blend and swap face function for midjourney-plus is ready 2024-03-10 09:45:59 +08:00
RockYang 96785ca037 feat: blend and swap face function for midjourney-plus is ready 2024-03-10 09:45:59 +08:00
RockYang 24906a6df1 feat: add blend and swapface task implements for midjourney 2024-03-10 09:45:59 +08:00
RockYang 7cfc747653 feat: add blend and swapface task implements for midjourney 2024-03-10 09:45:59 +08:00
RockYang d772bbebe6 opt: refactor chat session page for mobile device 2024-03-10 09:45:59 +08:00
RockYang 05f911628a opt: refactor chat session page for mobile device 2024-03-10 09:45:59 +08:00
RockYang 14988853a3 opt: optimize chat list page for mobile 2024-03-10 09:45:59 +08:00
RockYang 18b718a328 opt: optimize chat list page for mobile 2024-03-10 09:45:59 +08:00
RockYang 7b3f16ac9f feat: use vant replace element-plus as mobile UI framework 2024-03-10 09:45:59 +08:00
RockYang 6d9b9bd3f7 feat: use vant replace element-plus as mobile UI framework 2024-03-10 09:45:59 +08:00
RockYang 82b2755c18 feat: add websocket heartbeat message for mj page 2024-03-10 09:45:59 +08:00
RockYang 04e990aeb3 feat: add websocket heartbeat message for mj page 2024-03-10 09:45:59 +08:00
廖彦棋 cbbcd4c571 fix(ui): 细节调整 2024-03-08 17:46:48 +08:00
huangqj 5ad9adeb4a feat(ui):细节调整 2024-03-08 10:24:38 +08:00
廖彦棋 13c9594be0 feat(ui): prettier 2024-03-08 09:59:40 +08:00
廖彦棋 e4f5f78bfa refactor(ui): 登录页重构 2024-03-08 09:45:09 +08:00
廖彦棋 bfa0208692 refactor(ui): 优化 2024-03-08 09:12:39 +08:00
huangqj 64994cdeda feat(ui):路由 2024-03-08 08:35:41 +08:00
廖彦棋 9796a841d9 Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-07 18:03:08 +08:00
廖彦棋 404b493d23 feat(ui): 过期跳转登录 2024-03-07 18:03:06 +08:00
huangqj c04f9eb993 feat(ui):apiKey 语言模型 角色管理 产品 2024-03-07 17:58:25 +08:00
廖彦棋 825da42208 feat(ui): 新增系统设置 2024-03-07 17:24:50 +08:00
廖彦棋 721a8f1ecb feat(ui): 对话管理 2024-03-07 15:32:32 +08:00
huangqj 586a174d6c feat(ui):用户 2024-03-07 15:05:01 +08:00
huangqj bec6966542 feat(ui):simpleTable 2024-03-07 14:59:45 +08:00
huangqj b1f4e1d4d8 feat(ui):searchtable 2024-03-07 14:59:16 +08:00
huangqj d7d64e8259 feat(ui):用户 2024-03-07 14:03:55 +08:00
廖彦棋 08b498fccd feat(ui): 函数管理 2024-03-07 11:40:57 +08:00
廖彦棋 ecc5a52232 feat(ui): 新增弹窗及时间格式化 2024-03-07 09:23:45 +08:00
chenzifan 7327b90255 Merge branch 'main' of 172.28.1.6:yangjian/chatgpt-plus into ui 2024-03-07 08:37:54 +08:00
chenzifan 6ce7a9c16f feat: 增加系统用户管理 2024-03-07 08:37:48 +08:00
廖彦棋 eb2658a91c fix(ui): ts类型 2024-03-06 18:20:07 +08:00
RockYang 9f59a18756 feat: update changelogs 2024-03-06 17:58:17 +08:00
廖彦棋 ef06f0da98 feat(ui): 新增登录 2024-03-06 17:54:38 +08:00
RockYang 4dec0e2c69 feat: Mj and sd jobs data loading in pages 2024-03-06 17:31:54 +08:00
RockYang 5f36f1af11 add prompt translating function for mobile midjourney page 2024-03-06 16:22:03 +08:00
huangqj 5a1a596098 feat(ui):simpleTable 2024-03-06 15:33:37 +08:00
廖彦棋 7a25c6e062 feat(ui): 新增请求方法及表格 2024-03-06 13:55:38 +08:00
廖彦棋 a73ed71b9d feat(ui): 管理后台基础配置 2024-03-06 10:23:55 +08:00
廖彦棋 2abbb95ea2 chore(ui): 目录结构调整 2024-03-06 09:32:47 +08:00
廖彦棋 1f80ba9463 feat(ui): 初始化 2024-03-06 09:27:11 +08:00
chenzifan 4327fdac12 refactor: 初始化UI重构 2024-03-06 08:57:46 +08:00
RockYang a6f05a5874 Merge branch 'dev' of gitee.com:blackfox/chatgpt-plus-pro into dev 2024-03-04 08:34:12 +08:00
RockYang 3fb74a1598 feat: add removing order button in admin order list page 2024-03-03 19:27:22 +08:00
RockYang 2ac44cdeb6 fix: fix major bugs for unauthorized access to data 2024-03-03 10:40:32 +08:00
RockYang 4e4dc4cb73 Update README.md 2024-03-01 23:08:05 +08:00
RockYang 855a953010 Update README.md 2024-03-01 23:08:05 +08:00
RockYang 3264fda06e fix: fixed bug image preview im mobile chat session page 2024-02-29 15:41:45 +08:00
RockYang 2c7d472069 feat: add draw same image for midjourney page 2024-02-29 11:44:09 +08:00
RockYang 8182e6797f opt: add logs for mj-plus api error 2024-02-28 15:50:42 +08:00
RockYang 99cb48ce88 opt: replace proxy url for discord image url 2024-02-27 17:45:57 +08:00
RockYang e391bfca80 feat: add change password in with mobile page 2024-02-27 15:36:20 +08:00
RockYang 0181ad3715 feat: add image preview for mobile chat page 2024-02-26 18:11:37 +08:00
RockYang b95dff0751 feat: replace http polling with webscoket notify in sd image page 2024-02-26 15:45:54 +08:00
RockYang 668d4c9c64 chore: replace 'token' with power 2024-02-23 18:11:57 +08:00
RockYang 66c0d1b2f7 docs: update mj-plus api domain 2024-02-23 15:41:02 +08:00
RockYang bef0996375 opt: remove global keyup event bind 2024-02-23 11:43:27 +08:00
RockYang 391a1e4cf0 docs: update config.toml 2024-02-22 17:53:39 +08:00
RockYang 38facf517a docs: add database sql file for v3.2.7 2024-02-22 17:28:22 +08:00
RockYang 5c9b1e8764 feat: allow to view chat message in manager console 2024-02-22 17:16:44 +08:00
RockYang dbcf14b566 feat: download image which ai generated in dialog and replace the image url 2024-02-20 18:38:03 +08:00
RockYang cc59f0c761 feat: image-wall page for mobile is ready 2024-02-20 17:38:18 +08:00
RockYang f263fc067a feat: allow user config third-party platform openai and mj api key 2024-02-20 11:23:55 +08:00
RockYang 6635b88baa feat: added delete file function 2024-02-19 16:43:03 +08:00
RockYang 87ea2a611f fix: verifycation component touch event coordinates misplace in iphone browser 2024-02-19 14:04:50 +08:00
RockYang ea8a2135cd fix: fix bug for regenerate button did not work 2024-02-19 11:22:42 +08:00
RockYang 76d86395ef fix: Upscale and Variation task overrite each other 2024-02-16 18:08:29 +08:00
RockYang e268870636 feat: midjourney mobile page all function is ready 2024-02-16 15:55:04 +08:00
RockYang 49e9f41ef2 feat: mobile mj list page is ready 2024-02-15 18:11:22 +08:00
RockYang 68cda968a1 feat: add mj image list component for mobile page. fixed bug for html tag escape 2024-02-15 11:39:04 +08:00
RockYang f6826fcefc feat: add functions mj page for mobile 2024-01-31 07:24:35 +08:00
RockYang 56a91db7e7 opt: add default extension for mj image 2024-01-30 21:46:17 +08:00
RockYang 8f4d94f046 feat: mj for mobile page payout is ready 2024-01-30 18:34:01 +08:00
RockYang 9446bf5f98 add docs and github link 2024-01-30 16:18:27 +08:00
RockYang 23e353b18b opt: enable use cdn url for mj-plus 2024-01-28 21:56:25 +08:00
RockYang 46daaa4ba1 feat: LaTeX parse is ready 2024-01-26 18:04:53 +08:00
RockYang f66d59531d feat: add model field for chat_item and and chat_history data table 2024-01-26 16:54:00 +08:00
RockYang b76cd1d5ae feat: add err_msg field for mj and sd jobs 2024-01-26 14:50:36 +08:00
RockYang 55ba0d08a5 feat: blend and swap face function for midjourney-plus is ready 2024-01-26 11:57:08 +08:00
RockYang cc78780a8e feat: add blend and swapface task implements for midjourney 2024-01-25 18:50:24 +08:00
RockYang 4a00809061 opt: refactor chat session page for mobile device 2024-01-25 14:07:10 +08:00
RockYang cd760bbd84 opt: optimize chat list page for mobile 2024-01-24 18:23:24 +08:00
RockYang 84bdc6be7f feat: use vant replace element-plus as mobile UI framework 2024-01-24 17:34:30 +08:00
RockYang 8dd6bf8933 feat: add websocket heartbeat message for mj page 2024-01-24 09:33:04 +08:00
RockYang 08f170d217 add v3.2.6 database sql file 2024-01-23 18:00:49 +08:00
RockYang 20da6a1c20 doc: update config sample file 2024-01-23 17:56:22 +08:00
RockYang 8586931630 fix: auto fill apiURL when platform changed for ApiKey add page 2024-01-23 17:30:54 +08:00
RockYang 667fc79a6f feat: merge sms branch,add DuanXinBao sms service implemetation 2024-01-23 16:16:47 +08:00
RockYang ac7430dded opt: add heartbeat message for websocket connects 2024-01-22 18:42:51 +08:00
whale_fall 9c73e16560 feat: 添加支持多个短信服务商支持 添加短信宝服务商支持,同时添加配置示例 2024-01-22 16:38:44 +08:00
RockYang c605e222a7 fix: fix bug for wechat transfer message parse failed 2024-01-22 16:10:08 +08:00
RockYang 578f26a738 feat: HuPiPay order check function is ready 2024-01-22 15:17:26 +08:00
RockYang 24218e7570 opt: verify the order in notify callback 2024-01-22 13:58:25 +08:00
RockYang 16f946550e chore: print error detail when call http api failed with mj 2024-01-21 22:30:24 +08:00
RockYang 9960a31485 opt: add image upload support for md-editor-3 2024-01-19 18:43:13 +08:00
RockYang 437632f859 fixed conflicts 2024-01-19 18:21:49 +08:00
RockYang 98f0f442eb feat: system notice function is ready 2024-01-19 18:19:51 +08:00
RockYang 3b999dc5e2 feat: system notice function is ready 2024-01-19 18:18:10 +08:00
RockYang 20943ad3d3 fix: fixed bug for img_call increased when upscale task run failed 2024-01-19 17:10:52 +08:00
RockYang 3b1544b5e4 feat: add system config item for wechat qrcode 2024-01-19 16:58:13 +08:00
RockYang b317d597ac chore: optimize variable name 2024-01-19 11:26:22 +08:00
RockYang 3bb19811e7 Merge branch 'main' into dev 2024-01-19 10:09:18 +08:00
RockYang 065a01de48 Merge branch 'main' of gitee.com:blackfox/chatgpt-plus 2024-01-19 10:09:01 +08:00
RockYang 09fb2f6557 !4 添加支持阿里旗下的大模型 通义千问对话
Merge pull request !4 from 鲸落/qwen
2024-01-19 02:06:17 +00:00
whale_fall 9ca97bf4bc feat: 添加支持阿里的通义千问对话 2024-01-19 09:52:16 +08:00
RockYang 0c59503edb feat: add image publish function, ONLY published image show in image wall page 2024-01-19 06:52:23 +08:00
RockYang 6fa08861f8 update change log 2024-01-18 18:11:25 +08:00
RockYang 72b95151a1 feat: add system config disable user registeration 2024-01-18 17:24:02 +08:00
RockYang 378e6ec9af opt: compatible wechat old message format for parsing wechat transfer message 2024-01-18 16:58:20 +08:00
RockYang 27b68c1174 opt: optimize order query alg, reduce polling times 2024-01-18 09:39:36 +08:00
RockYang 1ae8eabe99 update config 2024-01-16 15:24:06 +08:00
RockYang 72726c1cf9 docs: update change log 2024-01-16 15:05:54 +08:00
RockYang 6203d9a150 docs: update config files 2024-01-16 14:38:18 +08:00
RockYang 861f11af66 opt: optimize markdown image parser, identify image and blockquote tags 2024-01-16 10:13:00 +08:00
RockYang 39fdfae541 feat: attachments manage function is ready 2024-01-15 18:48:01 +08:00
RockYang 9f57fb1421 feat: gpt-4-gizmo-g-* model is supported 2024-01-15 15:03:05 +08:00
RockYang 059f57db2d feat: gpt-4-all model is ready 2024-01-15 14:07:24 +08:00
RockYang 13c8be8d06 opt: optimize vip recharge logic 2024-01-15 11:01:57 +08:00
RockYang e0dbfb0488 Merge branch 'dev' 2024-01-15 10:29:01 +08:00
RockYang 722ed128a1 opt: optimize vip recharge logic 2024-01-15 10:28:46 +08:00
RockYang cc628821e6 feat: add asynchronously pull midjourney task progress in case the synchronization callback is fails 2024-01-12 18:24:28 +08:00
RockYang 857af61af1 feat: midjourney plus service is ready 2024-01-11 18:16:48 +08:00
RockYang f6fc484cb3 feat: update video tutorial 2024-01-10 08:50:13 +08:00
RockYang ba07946f1e feat: update video tutorial 2024-01-10 08:48:05 +08:00
RockYang 600f668c63 remove api key for hupipay 2024-01-09 17:16:27 +08:00
RockYang 8a8990b12b fix: fixed bug for gorm insert record failed and Error is not nil 2024-01-08 18:10:32 +08:00
RockYang fe85c1bbb9 feat: change mobile field to username 2024-01-08 17:34:09 +08:00
RockYang a89ff72308 add changelog 2024-01-08 12:01:58 +08:00
RockYang a99e58184b chore: do not close pop window when click model 2024-01-08 11:01:19 +08:00
RockYang d981bc8fd0 fix: function call 兼容中转 API 2024-01-07 22:32:59 +08:00
RockYang ff22480d89 opt: add support to disable code verify 2024-01-07 17:31:26 +08:00
RockYang 8ec85e2829 feat: payjs payment channel is ready 2024-01-07 14:36:02 +08:00
RockYang 706aacd8e6 fix: add user failed in admin user list page 2024-01-07 10:49:36 +08:00
RockYang 803bc2d9fc release v3.2.4 2024-01-06 21:09:19 +08:00
RockYang 7cc67cf8f0 chore: remove useless system config items 2024-01-06 17:38:55 +08:00
RockYang 9909c3a0ed chore: error recover is enable ONLY in debug mode 2024-01-06 17:16:02 +08:00
RockYang 60a3751839 feat: payjs service is ready 2024-01-06 15:53:30 +08:00
RockYang ce87e0c40b chore: rename bind username api 2024-01-05 18:21:47 +08:00
RockYang 71cd548c00 feat: email registration function is ready 2024-01-05 18:17:11 +08:00
RockYang 1ddd05302b feat: update api key last_use_time after dalle3 call 2024-01-04 18:15:00 +08:00
RockYang ea3301c75c feat: support dall-e3 api mirrors, add name field for ApiKey 2024-01-04 16:29:57 +08:00
RockYang 4b1c4f7ccc feat: refactor LLM api request code, get API URL from ApiKey object 2024-01-04 14:51:33 +08:00
RockYang 21f2622a4b feat: api key manage page funciton is ready 2024-01-04 10:48:04 +08:00
RockYang 79f6a43019 fix: fixed bug for concurrency risk for getting token for chat histroy with issue #92 2024-01-04 09:03:19 +08:00
RockYang 3d75093b2c fix: add unique key for MidJourney task_id 2024-01-03 18:06:10 +08:00
RockYang 023dc89c3e feat: show notice in chat page 2024-01-03 15:19:24 +08:00
RockYang b5641be30d feat: fixed bug for wechat bot to parse transactions. enable user to exchange reward with img_calls 2024-01-03 11:15:54 +08:00
RockYang ff4515bbf1 fix: fixed chat export page styles 2024-01-02 11:32:36 +08:00
RockYang 5dc0fe05af fix: fixed for img_call repeated reductions 2024-01-01 18:54:48 +08:00
RockYang 25bba912f6 feat: add switch for enable|disable chat role 2023-12-29 17:51:56 +08:00
RockYang cb6e323596 feat: add nickname field for user 2023-12-29 17:39:37 +08:00
RockYang cf4b04e047 feat: add authorization for local function call 2023-12-29 17:21:29 +08:00
RockYang 243b5be31c update changelog 2023-12-29 11:53:37 +08:00
RockYang c3d753cf38 feat: add router for function manager 2023-12-29 11:22:26 +08:00
RockYang 70c46d098b fix: restore user img_calls quota when image task run failed 2023-12-29 10:41:29 +08:00
RockYang 816e4a096e merge pull request #72 2023-12-29 10:09:37 +08:00
RockYang 1d1e819817 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-12-29 09:39:52 +08:00
RockYang 342daf1be8 Merge pull request #72 from Unclesimonlau/main
重新设计了移动端web页面,新增了移动端CSS,增加移动端SD绘图页面
2023-12-29 09:39:33 +08:00
RockYang f6bb9b582a merge pull request #71 2023-12-29 09:31:25 +08:00
RockYang aad62bc976 Merge pull request #71 from JingHong0202/main
fix: Azure Api request failure after changing the API-version parameter
2023-12-29 09:27:23 +08:00
RockYang 38a49a7f37 chore: remove dead code 2023-12-29 09:02:55 +08:00
RockYang f603bf6be7 feat: function manager refactor is ready 2023-12-28 18:14:38 +08:00
JingHong 6cc05e9e27 Merge branch 'yangjian102621:main' into main 2023-12-27 23:33:46 +08:00
UncleSimonlau c00e67542f Merge branch 'main' of https://github.com/Unclesimonlau/chatgpt-plus 2023-12-26 14:19:25 +08:00
RockYang 22660362a3 fix: fixed bug #70, XunFei 1.5 url version map error 2023-12-26 14:19:00 +08:00
RockYang b1ab9975b7 fix: fixed bug #70, XunFei 1.5 url version map error 2023-12-25 08:54:17 +08:00
RockYang 7ca89d8b54 feat: function CRUD operation is ready 2023-12-24 22:12:12 +08:00
jinghong0202 998b07ab8c fix: Azure Api 更换api-version参数后请求失败的问题 2023-12-24 08:36:34 +01:00
RockYang 4936896ff7 feat: function add for admin page is ready 2023-12-23 22:30:27 +08:00
RockYang 18b7484c5b feat: support CDN reverse proxy for MidJourney and OpenAI API 2023-12-22 17:25:31 +08:00
RockYang 6754c8e85e feat: add function list page in admin console 2023-12-21 18:06:09 +08:00
RockYang 57243ff010 opt: optimize image compress alg, add cache control for image 2023-12-21 15:00:46 +08:00
RockYang 7c4dfe96ee feat: add funcitons manger page 2023-12-21 08:58:24 +08:00
RockYang bf19120c27 feat: auto translate image creating prompt 2023-12-19 18:54:19 +08:00
RockYang 3d37087916 fix: fixed bug for HuPiPay qrcode generation. set field 'openid' of result struct to Any data type 2023-12-19 11:31:57 +08:00
RockYang 6949f1328c chore: update copyright information 2023-12-18 18:19:41 +08:00
RockYang b63090c20f feat: add remove action to remove task and images for MJ and SD task list page 2023-12-18 17:44:52 +08:00
RockYang b15ebda948 opt: merge RAG branch 2023-12-18 16:41:40 +08:00
RockYang 252eef2e5e opt: make sure the Upscale and Variation task is assign to the same mj service with Image task 2023-12-18 16:34:33 +08:00
RockYang 3bea8f9706 fix: fixed bug for mj service pool config pointer 2023-12-15 22:52:57 +08:00
RockYang dfe808bee7 feat: add img_calls field for recharge products 2023-12-15 16:56:56 +08:00
RockYang 58f3dd5336 chore: update default config.toml 2023-12-15 11:23:13 +08:00
RockYang b00f16648f opt: limit the image display size in reply component 2023-12-15 10:48:13 +08:00
RockYang fc66c31e89 docs: update changelog 2023-12-15 09:04:02 +08:00
RockYang a26c849532 docs: add sql file 2023-12-14 17:05:51 +08:00
RockYang d2991e60b6 refactor: refactor stable diffusion service, add service pool support 2023-12-14 16:48:54 +08:00
RockYang 10ba1430f9 chore: add sub dir support for OSS 2023-12-13 17:02:49 +08:00
RockYang 6d71f24f75 refactor: add midjourney pool implementation, add translate prompt for mj drawing 2023-12-13 16:38:27 +08:00
RockYang 8f4d20e411 fix: fixed bug for aliyun OSS img url 2023-12-13 09:49:55 +08:00
RockYang cf758d773e refactor mj service, add mj service pool support 2023-12-12 18:33:24 +08:00
RockYang c012f0c4c5 opt: remove default value for stable-diffusion page 2023-12-12 09:59:20 +08:00
RockYang 9e85900057 docs: update readme file 2023-12-12 09:52:28 +08:00
RockYang 68e3b787f9 docs: update build config.toml 2023-12-12 07:25:36 +08:00
RockYang 6706704ad6 docs: update changelog file 2023-12-11 17:17:17 +08:00
RockYang 1067fa015e add translate api for midjourney 2023-12-11 17:01:02 +08:00
RockYang 411335ebcb feat: add prompt translate handler 2023-12-11 06:56:00 +08:00
RockYang f19c15491e feat: add system config item for dall e3 generate image num 2023-12-10 17:13:25 +08:00
RockYang 0da81a4d10 chore: change default params for stable diffusion 2023-12-10 14:45:22 +08:00
RockYang 049839a5c7 feat: add HuPiPay payment support 2023-12-08 19:43:13 +08:00
RockYang a901927844 feat: add image generation API URL in chat configurations 2023-12-07 16:31:32 +08:00
RockYang 07ab0bc5a1 docs: add arm64 build script 2023-12-07 15:44:20 +08:00
RockYang a6025e6fab fix: fixed bug for prompt code format, prevent xss attacks 2023-12-07 14:02:13 +08:00
RockYang e841a61bf0 opt: save chat ID when the chat websocket disconnect 2023-12-07 11:07:08 +08:00
RockYang 2f1c2110c6 feat: adjust task list component styles 2023-12-06 19:05:51 +08:00
RockYang 8e573eea4c Merge branch 'pr_3' into dev 2023-12-06 18:54:45 +08:00
RockYang 149765764e feat: refactor midjourney image creating page 2023-12-06 18:54:30 +08:00
RockYang 45d6444c7e docs: update readme file 2023-12-06 14:44:06 +08:00
liyuwanglan aa65f49190 修改 2023-12-05 11:08:03 +08:00
RockYang 1bda41ff67 docs: update comments 2023-11-30 17:35:56 +08:00
RockYang 5a799139cd fix: fixed bug for upload image failed 2023-11-29 17:46:46 +08:00
RockYang eb36d0742a opt: create new chat session when change role or model, fix bug for mobile no validate 2023-11-29 17:36:27 +08:00
RockYang d8a9123852 docs: update change logs 2023-11-28 15:33:30 +08:00
RockYang 19031c2197 docs: 添加一键部署脚本 2023-11-28 15:25:51 +08:00
RockYang 7a2ffdf39c opt: 缩略图生成算法 2023-11-28 14:50:19 +08:00
RockYang ce8fa79206 feat: 为大图片生成缩略图,加快前端图片加载速度 2023-11-28 12:04:02 +08:00
RockYang b05c63549d fix: fix bug for oss image domain 2023-11-28 07:27:18 +08:00
RockYang 9f98660423 docs: update build script 2023-11-27 18:24:52 +08:00
RockYang 7b0fa862d2 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-11-27 12:03:23 +08:00
RockYang bfe2d4d573 feat: merge mysql and redis docker service to docker-compose.yaml file 2023-11-27 10:56:18 +08:00
RockYang 26950c5673 Merge pull request #55 from openjst/main
修改code显示颜色样式
2023-11-27 10:27:13 +08:00
RockYang 86788362a5 docs: make a full docker-compose.yaml 2023-11-27 07:21:37 +08:00
openjst deedc5fb2e 修改code显示颜色样式 2023-11-26 23:26:08 +08:00
RockYang f4fbe67db9 feat: implements image function replace Mj with DALL-E-3 2023-11-26 20:37:48 +08:00
RockYang 599ce0eade feat: add type field for api key 2023-11-24 18:05:59 +08:00
RockYang 11f3ab8dc7 feat: add support for registing use force use invite code 2023-11-24 12:02:28 +08:00
RockYang 75b4a6dd46 opt: optimize image preview for MidJourney image list page, only preview current image not for all images 2023-11-23 17:55:12 +08:00
RockYang 86bbea337d opt: 增加中间件自动对HTTP请求的参数去掉首尾空格 2023-11-23 17:50:55 +08:00
RockYang e63a30064b opt: optimize styles for invitation page 2023-11-23 17:40:15 +08:00
RockYang 222b1ddbd9 feat: add invitation and promotion functions 2023-11-23 16:30:15 +08:00
RockYang a88dd88a07 docs: add database sql file for v3.1.9 2023-11-23 09:58:01 +08:00
RockYang a47c01fd41 docs: update change log 2023-11-23 09:46:49 +08:00
RockYang 35e385f7a7 docs: update change log and readme file 2023-11-23 09:14:42 +08:00
RockYang 191e3b7d2c feat: 支持讯飞大模型 v3.0 2023-11-23 07:11:13 +08:00
RockYang 13fbbc190a feat: reset password function is ready 2023-11-22 18:00:45 +08:00
RockYang 61c6a6e7f3 feat: add copy code btn in chat page, fixed bug for code wrap in model of ChatGLM and XunFei 2023-11-22 17:00:43 +08:00
RockYang 911f218f0b feat: add feekback orcode in chat page 2023-11-22 12:05:58 +08:00
RockYang af41350eb9 fix: fixed bug for enable user vip in admin console not work 2023-11-22 11:30:58 +08:00
RockYang 8f63e348b3 option: replace leveldb with redis in storing message code 2023-11-22 10:57:24 +08:00
RockYang 8901cf8e81 Merge pull request #52 from KunMingStar/fix_migrate
fix: remove dead code
2023-11-22 08:23:01 +08:00
tongkunming e11736e1d2 fix: remove dead code 2023-11-21 22:18:53 +08:00
RockYang c062fba69c chore: remove dead code 2023-11-21 19:04:53 +08:00
RockYang 4628095fe7 feat: add wechat id card for closing register 2023-11-20 16:38:04 +08:00
RockYang 787ed9bc0f docs: update docs installation docs url 2023-11-17 18:42:13 +08:00
RockYang daaff8974e docs: update readme and config sample files 2023-11-16 17:20:38 +08:00
RockYang 4fcbd511c0 fix: fix bug with missing chat context 2023-11-15 18:20:34 +08:00
RockYang 64d6bff6d9 fix: unbind event for login page when the component is unmount 2023-11-14 18:19:40 +08:00
RockYang 998cbace92 feat: add link on logo 2023-11-14 13:50:44 +08:00
RockYang 2204038951 fix: fix member page flex styles 2023-11-14 11:21:16 +08:00
RockYang 998f67ac5d fix: deducating the user's img call quota after stable diffusion callback 2023-11-14 08:59:39 +08:00
RockYang be981a2c9b feat: 更改 MJ 和 SD 菜单图标 2023-11-13 11:27:42 +08:00
RockYang da3cf0d4a3 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-11-13 10:11:45 +08:00
RockYang 7124b9a36c feat: 更改 MJ 和 SD 菜单图标 2023-11-13 10:11:21 +08:00
RockYang 712a6b6c39 chore: change xxl-job name 2023-11-12 15:39:12 +08:00
RockYang ed0a1c57d6 opt: 优化 ItemList 组件样式,调整支付页面布局 2023-11-11 22:11:04 +08:00
RockYang 7858228505 docs: update change log file 2023-11-11 12:58:57 +08:00
RockYang 2525a22d78 fix: fix bug for issue 49, stable diffusion service not decrease img_calls 2023-11-11 11:00:26 +08:00
RockYang 7b4ef8fc31 opt: 优化 ItemList 组件样式 2023-11-11 10:53:33 +08:00
RockYang ce14cd02e4 feat: add order list compponent 2023-11-10 18:06:32 +08:00
RockYang 05f501af52 Merge branch 'alipay' 2023-11-10 16:51:00 +08:00
RockYang e94fa4844f docs: export database, update readme doc, remove useless configs for alipay 2023-11-10 16:49:07 +08:00
RockYang e0965aae5e feat: add switch to disable xxl-job service, update readme 2023-11-10 15:22:35 +08:00
RockYang 414c1de963 feat: 增加订单倒计时组件,自动清理过期未支付订单 2023-11-10 14:39:27 +08:00
RockYang b589102be8 feat: add system configration switch option for order pay service, support sandbox env for alipay 2023-11-09 18:28:56 +08:00
RockYang df367e0d47 feat: check if the user's chat quota is gt than current chat model required before starting a conversation 2023-11-09 16:56:44 +08:00
RockYang 680219ebcb Merge pull request #47 from KunMingStar/fix/sendMessage
fix: 添加messages为空校验
2023-11-09 08:21:24 +08:00
RockYang ef87487f60 fix: fix bug for token expired with QiNiu oss upload file 2023-11-08 21:14:09 +08:00
RockYang 8bafe72434 feat: adjust layout for user profile page 2023-11-08 18:33:26 +08:00
RockYang 76dcc69e44 feat: order payment function is ready 2023-11-08 17:48:07 +08:00
tongkunming 682ef22194 fix: 添加messages为空校验 2023-11-08 16:51:36 +08:00
RockYang ee8dd41605 feat: update stable diffusion api version 2023-11-08 14:54:10 +08:00
RockYang d0b8d666e4 feat: finish payment page layout 2023-11-07 18:10:28 +08:00
RockYang 4a81826d19 feat: adjust table styles for markdown 2023-11-07 12:02:16 +08:00
RockYang 848a10c3a8 feat: integrated Alipay payment module 2023-11-06 17:55:46 +08:00
RockYang a4baa29678 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-11-06 15:04:11 +08:00
RockYang a9b5280691 opt:update mj api version, wrapper chat api error message as a constant 2023-11-06 15:03:56 +08:00
RockYang 264706210b chore: remove invalid filepath for windows 2023-11-02 19:16:02 +08:00
RockYang 7677ae254f feat: different AI model consuming different amounts of use_calls 2023-10-26 14:38:06 +08:00
RockYang 7ccb4c5f06 docs: add database full backup 2023-10-26 14:00:06 +08:00
RockYang 01205af018 feat: 支持文心4.0模型,不同的用户可以订阅不同的AI模型 2023-10-26 13:41:49 +08:00
RockYang 5b5150e6d4 feat: add system configration item to close rewarding function 2023-10-19 11:19:29 +08:00
RockYang 9d8e3f5049 docs: add alipay reward qrcode 2023-10-17 17:58:03 +08:00
RockYang 99ede83bdc chore: set app list page as the index page 2023-10-16 14:20:44 +08:00
RockYang 61488e840d update changelog file 2023-10-16 13:40:44 +08:00
RockYang a80d01209c opt: adjust styles for ItemList component, cut string for chat role's hello message 2023-10-16 10:46:10 +08:00
RockYang d2a8d655c8 feat: finish adding chat role to user function 2023-10-16 06:56:42 +08:00
RockYang 80e2b34abc opt: adjust ItemList component styles 2023-10-15 15:47:42 +08:00
RockYang fb3d43d2d5 opt: optimize imgae loading for image wall page, loading thumb image replace source image 2023-10-13 23:07:41 +08:00
RockYang ebdec4fa44 feat: create chat app list page, build the layout of page 2023-10-13 18:05:40 +08:00
RockYang ead8dbbaa5 feat: auto login when register successfully 2023-10-13 16:27:40 +08:00
RockYang 95615a5501 docs: update images for readme 2023-10-13 15:55:30 +08:00
RockYang 75487b1f58 Merge branch 'main' into image-wall 2023-10-13 15:39:34 +08:00
RockYang c7daaa00cd feat: image wall stable diffusion image list component is ready 2023-10-13 15:16:40 +08:00
RockYang 606f24e981 feat: optimize the midjourney image list styles 2023-10-13 11:14:39 +08:00
RockYang c83d9287a7 docs: update readme, database files 2023-10-13 09:59:05 +08:00
RockYang fa8092b2cd docs: update readme file 2023-10-13 06:49:21 +08:00
RockYang b2b07a2be6 docs: update change log 2023-10-13 06:39:44 +08:00
RockYang a21628e350 style: fix style for update chat title input element 2023-10-13 06:29:25 +08:00
RockYang 9fcd686fda feat: image wall page is ready 2023-10-12 18:09:50 +08:00
RockYang 1759fd4cf9 opt: close websocket connection when finish a chat call for XunFei API 2023-10-12 10:02:12 +08:00
RockYang 12e7837602 feat: XunFei ai mode api implements is ready 2023-10-11 18:17:03 +08:00
RockYang ff349f2edf feat: adjust package struct, put chat code the seperate 'chatimpl' package, fix bug: baidu api chat context number must be even number 2023-10-11 15:46:40 +08:00
RockYang 088d8ca207 feat: add system configration for enable/disable funciton in chat session 2023-10-11 14:35:47 +08:00
RockYang c37902d3a4 finish baidu ai model api implementation 2023-10-11 14:21:16 +08:00
RockYang da38684cdd add baidu ai model api configrations 2023-10-10 18:19:56 +08:00
RockYang cb2cb72333 docs: update readme 2023-10-09 12:13:21 +08:00
RockYang d53c8f9830 docs: add 3.1.4 release version change logs 2023-10-09 12:05:51 +08:00
RockYang c8136d4231 doc: update database sql file 2023-10-08 20:44:55 +08:00
RockYang e4b8380530 docs: udpate readme and config.toml template 2023-10-08 18:09:46 +08:00
RockYang fbd599194c feat: add system config item for reward image, add app config item to use custom text2img param json file 2023-10-08 17:48:50 +08:00
RockYang 0270ec26fb feat: add configuration handler for AliYun SMS signature and template ID 2023-10-08 12:01:09 +08:00
RockYang 79e342be57 optimze the style for stable diffusion image dialog 2023-10-08 11:39:55 +08:00
RockYang c89f23b018 feat: stable diffusion page function is ready 2023-10-06 22:25:37 +08:00
RockYang f971ec5d34 fix: fixed bug for generating the upload file path 2023-10-05 18:09:42 +08:00
RockYang c5776ce41f feat: stable diffusion page is ready 2023-09-28 18:09:45 +08:00
RockYang 75c5ebbffa feat: migrate the chatgpt-plus-ext project code to this project 2023-09-27 18:14:07 +08:00
RockYang d51a724ade feat: add implements for stable diffusion service 2023-09-26 18:16:51 +08:00
RockYang c1143d7a6d fix: fixed bug for register error with parse args 2023-09-26 09:30:18 +08:00
RockYang f3697431a4 fix: fixed bug for add user in admin console page that calls and img_calls parameter not work 2023-09-22 18:15:32 +08:00
RockYang e3d6e5f420 feat: the upload handler for AliYun OSS is ready 2023-09-22 09:56:45 +08:00
RockYang af6f7a7146 docs: adjust ui styles for mj image page 2023-09-20 14:33:15 +08:00
RockYang c18d95e89a docs: update docker-compose.yaml, change image version to v3.1.3 2023-09-20 11:42:23 +08:00
RockYang fa42d03a65 some page ui optimization, add release v3.1.3 2023-09-20 11:39:11 +08:00
RockYang 22f7de2c09 fix socket connect for mj task notify 2023-09-20 06:59:30 +08:00
RockYang b4b9df81cb opt: add sessionId for mj task 2023-09-19 18:15:08 +08:00
RockYang 2a71c2b0e7 feat: mj advance drawing page function is ready, use better task scheduling argorithm 2023-09-17 18:03:45 +08:00
RockYang c37b3d3df5 feat: add task type params for add new mj task 2023-09-16 14:01:53 +08:00
RockYang ae135b89d6 feat: adjust UI for task list page 2023-09-15 18:16:59 +08:00
RockYang 4112426848 feat: midjourney page task and image list component is ready 2023-09-15 17:40:39 +08:00
RockYang 638b399b31 feat: midjourney page parameter ui is ready 2023-09-14 18:28:24 +08:00
RockYang 8a5713ef7c feat: finish mj model layout 2023-09-14 06:55:03 +08:00
RockYang 382d7bfaa1 feat: add ImageMj page view 2023-09-13 18:04:31 +08:00
RockYang 3daf2f7738 feat: optimize mj notidy api, use job queue to send ai drawing request 2023-09-13 15:50:00 +08:00
RockYang 944d35971d feat: chat chrawing function is refactored 2023-09-13 06:57:25 +08:00
RockYang 9ce8af0b82 feat: refactor MidJourney service for conpatible drawing in chat and draw in app 2023-09-12 18:01:24 +08:00
RockYang d825b9ec26 fix: fixed bug for jwt token expire caculation 2023-09-12 10:49:55 +08:00
RockYang 9afe4aea4b feat: new WebUI for the main page, add MJ and SD drawing function pages 2023-09-11 16:22:11 +08:00
RockYang 3cc8c3284a opt: optimize the styles of chat page; caculate all tokens of context as chat history's token 2023-09-11 13:34:20 +08:00
RockYang e2c18c4e1e chore: update gpt avatar 2023-09-10 12:02:37 +08:00
RockYang 81fe768f6a opt: refactor the web page's router and layout 2023-09-08 22:14:58 +08:00
RockYang 8923779ab0 opt: return error when download mj image failed 2023-09-08 21:02:21 +08:00
RockYang 583218a045 opt: add lock for mj task callback 2023-09-08 18:12:18 +08:00
RockYang 153b2bfa53 opt: add lock for mj task callback 2023-09-08 17:23:32 +08:00
RockYang 0a9b325360 opt: Only create actived upload service 2023-09-08 06:46:24 +08:00
RockYang af9e6f2c46 update desktop package deps 2023-09-07 18:05:13 +08:00
RockYang 0e31726fd3 fix: remove username and nickname field for updating user info 2023-09-07 15:41:01 +08:00
RockYang 590d9c2e61 Update README.md 2023-09-07 15:23:38 +08:00
RockYang c12fe9f25e adjust home page styles 2023-09-07 15:12:43 +08:00
RockYang 9bd83f3acf use absolute path for all routes 2023-09-07 10:27:18 +08:00
RockYang aada15fc41 feat: add linux package configs for desktop 2023-09-07 09:45:11 +08:00
RockYang 6a2d383be6 feat: extract css to a single file, add chat_id path variable support 2023-09-07 09:29:16 +08:00
RockYang 545c8fa482 feat: use eletron to build desktop version for chatgpt-plus 2023-09-06 18:19:27 +08:00
RockYang e3d18643b8 opt: clear space in the reward transfer number 2023-09-06 17:09:55 +08:00
RockYang 2f318bbe9f feat: add QiNiu OSS storage implements 2023-09-06 14:37:13 +08:00
RockYang fac7eb61b3 remove username search for console user list page 2023-09-05 17:52:46 +08:00
RockYang 4450d03a54 update sql file, remove truncate table sql 2023-09-05 17:05:33 +08:00
RockYang e4e7868b30 fix: 采用弹窗的方式显示验证码,解决验证码在低分辨率下被掩盖的Bug 2023-09-05 16:47:40 +08:00
RockYang 3d7fa680cb docs: update change log and create a new release version 2023-09-05 14:28:30 +08:00
RockYang c38035e25e fix: replace session handler with jwt authorization 2023-09-05 11:47:03 +08:00
RockYang 8d4fdaf902 refactor: refactor mobile pages for the chat model updating 2023-09-04 18:15:56 +08:00
RockYang 2820adad53 feat: allow user to set custom api keys for different platforms 2023-09-04 17:34:29 +08:00
RockYang f7a427d2c0 refactor: refactor chat model, replace mode value with mode id. refactored system config module, add seperate configration for every chat model 2023-09-04 16:32:20 +08:00
RockYang 59ed8c9660 refactor: refactor chat_handler, add support for Azure and ChatGLM 2023-09-04 06:43:15 +08:00
RockYang 71562ab0e5 docs: add log dir to docker 2023-08-24 08:55:11 +08:00
RockYang 278ddf037f docs: update docker-compose file 2023-08-23 06:56:04 +08:00
RockYang ac3151af92 chore: update bin file name 2023-08-22 22:18:07 +08:00
RockYang 1189ad7862 feat: change order by for login log and reward records 2023-08-22 22:04:46 +08:00
RockYang 88c13e7b9a opt: optimize element ui dialog styles, make it to the center of screen 2023-08-22 08:57:14 +08:00
RockYang 596baac62e opt: upgrade element-plus to 2.3.0, optimize the styles for for element dialog 2023-08-22 07:14:13 +08:00
RockYang 5a8503ec14 chore: remove debug codes 2023-08-21 10:55:49 +08:00
RockYang e44f95724d doc: update readme docs 2023-08-21 06:57:04 +08:00
RockYang dec0d0dea7 feat: delete the old avatar image file when update the user profile 2023-08-21 06:27:30 +08:00
RockYang 46f96e94ec feat: add oss service factory implements, add support for setting custom upload handler, localstorage and minio oss 2023-08-20 22:29:08 +08:00
RockYang 89b30bcf58 opt: optimize code for remove chat item 2023-08-20 19:06:18 +08:00
RockYang 473d8f5f85 feat: add minio service implementation, download midjourney image to local storage 2023-08-20 16:17:42 +08:00
RockYang 08d8d65599 opt:Optimize the algorithm to check whether Midjourney images can continue to variation 2023-08-17 14:20:16 +08:00
RockYang 2964c1d82e docs: update demo url 2023-08-17 07:18:44 +08:00
RockYang 75ab8552d9 chore: show img_calls num for user profile page 2023-08-17 07:09:53 +08:00
RockYang b78d5b3d03 docs: update docs 2023-08-17 06:55:11 +08:00
RockYang 064081c771 feat: add authorization for MidJourney function calls 2023-08-16 23:16:44 +08:00
RockYang 3525d35d15 chore: update database sql file 2023-08-15 22:53:32 +08:00
RockYang 9d9ee7c585 opt: unset timeout for websocket connection 2023-08-15 18:29:53 +08:00
RockYang e65c32c4c7 feat: midjourney image variation function is ready 2023-08-15 17:31:02 +08:00
RockYang 113769791f feat: midjourney image upscale function is ready 2023-08-15 15:28:40 +08:00
RockYang 5d2a1d21d5 feat: midjourney drawing image function is ready 2023-08-14 17:59:21 +08:00
RockYang 6e40f92aaf feat: add midjourney message receive handler 2023-08-14 07:09:52 +08:00
RockYang 302cb8a5be feat: add midjouney api implements, optimize function calls 2023-08-11 18:46:56 +08:00
RockYang 0b27890484 feat: remove wechat bot, replace with api callback for receive wechat payment transactions 2023-08-10 17:24:23 +08:00
RockYang 5db247e632 feat: add samples and function introduce in welcome page 2023-08-08 14:19:09 +08:00
RockYang c1155a4338 docs: add dashboard image 2023-08-07 20:07:05 +08:00
RockYang da806b9492 feat: enabled user to set default GPT model for chat 2023-08-04 14:25:54 +08:00
RockYang 3d6d46b30d feat: chat export function is ready 2023-08-04 12:08:07 +08:00
RockYang 57c69738ba feat: add chat export button 2023-08-03 18:24:30 +08:00
RockYang 0c8157dbc0 feat: add rewards statistic in dashboard page 2023-08-03 10:19:37 +08:00
RockYang d7b278f2f7 opt: add enabled_msg_service config var to system config database 2023-08-03 10:04:45 +08:00
RockYang 18d74f1057 opt: optimize build script, auto push to aliyun docker hub 2023-08-02 18:09:19 +08:00
RockYang e04856f794 opt: optimize the layout for regiseter page. add function to disable registration 2023-08-02 17:44:13 +08:00
RockYang d7bbfb0fc3 feat: the dashboard page is ready for admin console 2023-08-02 16:37:47 +08:00
RockYang a0857817de refactor: refactor admin console page layout 2023-08-02 15:00:18 +08:00
RockYang 8d39234fd0 fix: fixed bug for chat request's token reach the max token limit for OpenAI 2023-08-01 17:58:03 +08:00
RockYang 0fce1c0fd8 feat: new function for add user in manger console user list page 2023-08-01 16:02:49 +08:00
RockYang c23c285f82 doc: add video tutorial for deploying system 2023-08-01 10:15:43 +08:00
RockYang 81b560d791 docs: remove docs for deploy manually 2023-07-31 20:04:22 +08:00
RockYang 55459a4f42 opt: optimize the build script and docker-compose yaml file 2023-07-31 17:31:02 +08:00
RockYang 9eafd3e6ca feat: add env var to set log level 2023-07-31 08:34:11 +08:00
RockYang e7ac26ff5a feat: update system config cache for AppServer when updating system config in admin console 2023-07-31 08:13:20 +08:00
RockYang fad78641d6 add system config cache for AppServer object 2023-07-31 06:56:28 +08:00
RockYang a32cb0142b feat: allow user to login with username and mobile no 2023-07-27 15:37:50 +08:00
RockYang 4a3c133152 fix: deauthorize some apis 2023-07-27 10:53:14 +08:00
RockYang ae79c34508 fix: fix bug for mobile verify code function not available in register page 2023-07-27 10:29:59 +08:00
RockYang 7eb69c1ad7 style: adjust reward image styles 2023-07-26 17:57:19 +08:00
RockYang 8aaff0af89 chore: optimize build script, compress reward img 2023-07-26 17:48:16 +08:00
RockYang 4b77280c65 feat: add reset password function to user list page 2023-07-26 15:22:11 +08:00
RockYang a0df84e4f2 docs: add change log file 2023-07-25 17:37:20 +08:00
RockYang 8876e6a098 docs: 更新文档和数据库文件 2023-07-25 17:25:23 +08:00
RockYang 9bee2a90f9 feat: 完成人机交互验证 API 接入,增加短信防刷验证 2023-07-25 17:00:24 +08:00
RockYang cd6a811bbd feat: 头条,微博热搜等函数 API 实现 2023-07-25 15:02:43 +08:00
RockYang 84f9a83f55 opt: 取消强制手机号验证,更新配置 2023-07-24 18:18:09 +08:00
RockYang 2efc669ab2 feat: 完成众筹后台管理功能 2023-07-24 15:59:29 +08:00
RockYang 5444ed77ad chore: add error log for reading chat response buffer 2023-07-24 12:05:38 +08:00
RockYang dd71fe80a5 opt: record user_id for reward verify 2023-07-22 08:42:30 +08:00
RockYang b73092eb64 feat: reward verify is ready 2023-07-21 22:29:14 +08:00
RockYang aa28f87b0c feat: 增加打赏核销功能 2023-07-21 18:26:51 +08:00
RockYang 1e1bcd4a30 feat: 集成微信收款服务 2023-07-20 17:46:32 +08:00
RockYang d24b3c46bf put logs in log file 2023-07-20 16:36:12 +08:00
RockYang 37222f07d9 fix: 修复阿里云短信发送失败的 bug 2023-07-16 16:05:11 +08:00
RockYang 49646a79e5 fix: 用户使用自己绑定的 API 调用,则不计算 calls 和 token 消耗 2023-07-16 11:55:45 +08:00
RockYang 9940e1210e Merge branch 'main' into prod 2023-07-16 09:57:27 +08:00
RockYang 10892812c3 chore: 更新项目打包脚本 2023-07-16 09:55:00 +08:00
RockYang d8ff5987dd feat: plugin function is ready 2023-07-15 21:52:30 +08:00
RockYang d014d418e9 feat: 支持上下文深度配置,计算每轮对话消耗的总 token 数量 2023-07-15 18:37:25 +08:00
RockYang cc1b56501d refactor: 更改 OpenAI 请求 Body 数据结构,兼容函数调用请求 2023-07-15 18:00:40 +08:00
RockYang a5ad9648bf chore: small fixs 2023-07-14 18:00:01 +08:00
RockYang 3630099234 feat: 完成每日早报函数开发 2023-07-10 18:59:53 +08:00
RockYang 941a24c75b fix: fixed conflicts 2023-07-10 10:11:17 +08:00
RockYang 50bdc01484 docs: update readme file 2023-07-10 09:44:24 +08:00
RockYang d9e340ddc4 refactor: 调整项目目录结构,移除其他语言 API 目录 2023-07-10 09:42:11 +08:00
RockYang beee1e91d6 test: add plugin test code 2023-07-10 07:05:56 +08:00
RockYang 35935d2bac fixed conflicts 2023-07-06 10:49:38 +08:00
RockYang 5d0b0ad33e opt: add tip message when no available key 2023-07-06 10:47:36 +08:00
RockYang d1a6ac531f opt: add tip message when no available key 2023-07-06 10:34:01 +08:00
RockYang ae255a3bd9 fix: 取消 ElMessage 的 appendTo 属性,防止被 Dialog 组件覆盖 2023-07-04 17:59:55 +08:00
RockYang fa733afa51 opt: 优化验证码发送逻辑,加入防刷验证 2023-07-04 17:15:02 +08:00
RockYang b60c723457 Merge branch 'main' into feat-sms 2023-07-04 10:55:20 +08:00
RockYang 2bb7ff3c13 Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-07-04 10:54:35 +08:00
RockYang 68b4cf00e2 docs: update README, 增加免责申明 2023-07-04 10:52:52 +08:00
RockYang 440169ff60 feat: 短信验证码功能已完成,手机端同步实现。 2023-07-03 15:18:15 +08:00
RockYang 1fc361242e opt: 将短信发送按钮封装成组件 2023-07-03 06:55:15 +08:00
RockYang 72cc6f3d75 feat: 注册短信验证码验证功能已经开启 2023-07-02 20:51:13 +08:00
RockYang a2b1924e00 Merge branch 'main' into prod 2023-07-02 00:04:59 +08:00
RockYang 7e926167f0 fix: 修复 nodejs apple M1 跨平台打包,运行报错 exec format error 2023-07-02 00:04:12 +08:00
RockYang 32484bd32e feat: 增加用户 token 消耗统计功能 2023-07-01 23:29:24 +08:00
RockYang 04ade1008b Merge branch 'main' into prod 2023-07-01 10:24:08 +08:00
RockYang 4603de0e70 Merge pull request #18 from ly307787186/main
Update Setting.vue
2023-07-01 10:22:01 +08:00
RockYang 12c1ff65e9 opt: remove chat role info from user login api's response 2023-06-30 18:52:43 +08:00
ly307787186 7b8cbf7f86 Update Setting.vue
关掉aip-key 必需的验证
2023-06-30 13:20:30 +08:00
RockYang c198e83f70 fix: fixed bug for Emoji can not insert to MySQL 2023-06-30 09:15:57 +08:00
RockYang e60863a290 chore: change ubuntu docker image with aliyun 2023-06-29 15:56:11 +08:00
RockYang 946563d3b2 chore: add test code for fix role icon url of db 2023-06-29 08:58:19 +08:00
RockYang 3f2ef1d54e fix: 修正前端 user_init_call 字段错误和用户注册初始化头像路径问题 2023-06-28 20:01:44 +08:00
RockYang 0d2b60b905 fix: 修复 PC 端聊天界面滚动条问题 2023-06-28 18:16:28 +08:00
RockYang 161b11e428 opt: 优化启动参数接收处理 2023-06-28 05:51:55 +08:00
RockYang f7748d51df opt: 通过环境变量来传参,修正 docker compose 配置参数 2023-06-27 18:29:46 +08:00
RockYang 5d13b4b705 docs: 更新文档,新增移动端预览图 2023-06-27 14:28:00 +08:00
RockYang 92e52a2284 fix: 修复 markdown 换行符不解析的 Bug,修复新发布的模型 token 统计失败错误 2023-06-27 14:18:20 +08:00
RockYang 3efd5fb77a feat: vue-mobile => 完成用户信息修改功能,前后端都添加文件上传功能。 2023-06-27 12:11:55 +08:00
RockYang c3d62bb8d8 feat: vue-mobile => 完成移动端聊天配置功能 2023-06-26 18:18:45 +08:00
RockYang c099e843d5 feat: vue-mobile => 完成会话聊天页面功能,增加主题切换功能 2023-06-26 16:39:00 +08:00
RockYang 5f3a5871f1 feat: vue-mobile => 优化聊天记录拍版样式 2023-06-25 18:21:38 +08:00
RockYang f01fdd0070 feat: vue-mobile => 完善移动端聊天列表页功能 2023-06-25 17:01:04 +08:00
RockYang d20cc367b8 fixed: go-api => 增加全局错误处理 handler,修复业务处理异常导致服务退出的 Bug 2023-06-25 11:34:55 +08:00
RockYang eadb9a733f docs: 增加容器部署文档 2023-06-25 11:06:18 +08:00
RockYang 32ac454b5b opt: 优化前端登录判断逻辑 2023-06-25 09:46:23 +08:00
RockYang 2cadd6af44 feat: chat list page for mobile is ready 2023-06-25 06:53:22 +08:00
RockYang 3c8b5cb313 feat: 完成移动端前段框架搭建 2023-06-24 11:45:26 +08:00
RockYang fcf2387794 style: 调整聊天侧边栏样式 2023-06-23 18:31:50 +08:00
RockYang f9783b4806 opt: 优化 docker-compse 构建脚本,修复后端路由 Bug 2023-06-23 18:04:16 +08:00
RockYang cbdc532f83 chore: 添加 docker 镜像构建脚本 2023-06-23 07:08:16 +08:00
RockYang 124554bae5 opt: 抽离 session 验证函数,修正前端路由覆盖 bug 2023-06-23 06:31:25 +08:00
RockYang 2725536d7d chore: 使用阿里云镜像仓库 2023-06-22 22:23:48 +08:00
RockYang beeef7a4f7 docs: 增加 docker-compose 部署支持 2023-06-22 22:14:18 +08:00
RockYang 7f2ebb6aeb docs: 完善新版本文档 2023-06-22 15:47:51 +08:00
RockYang c8e30383ba refactor: embed xdb file for ip2region 2023-06-22 11:08:44 +08:00
RockYang a7d5a6ccb9 style: 调整后台管理框架样式 2023-06-21 18:44:18 +08:00
RockYang be3380aaf3 refactor: 更新 iconfont 图标,增加打赏二维码弹窗 2023-06-21 15:34:38 +08:00
RockYang 40a4ab5410 refactor: refactor the frame layout of admin module 2023-06-21 14:22:28 +08:00
RockYang 54a2181960 refactor: user login log list for admin is ready 2023-06-21 06:53:41 +08:00
RockYang e490e15bc4 feat: API Key manage is ready 2023-06-20 18:05:33 +08:00
RockYang 469844d97b opt: optimize role sorting 2023-06-20 16:07:26 +08:00
RockYang 892ea29ba8 refactor: chat role manage for admin is ready 2023-06-20 11:46:13 +08:00
RockYang acde1c6742 refactor: user remove is ready for console 2023-06-20 07:08:37 +08:00
RockYang 07da11d852 refactor: 管理后台用户编辑功能 is ready 2023-06-19 21:53:07 +08:00
RockYang 502b8c2270 refactor: 管理后台用户列表页面重构 2023-06-19 18:23:09 +08:00
RockYang af3f7ac810 refactor: 完成管理后台的系统设置页面重构 2023-06-19 15:58:52 +08:00
RockYang 935c6caf96 feat: admin login page is ready 2023-06-19 11:09:23 +08:00
RockYang 90bce1d437 refactor: refactor controller handler module and admin module 2023-06-19 07:06:59 +08:00
RockYang 831dd3e2e0 style: optimize code styles 2023-06-18 15:17:59 +08:00
RockYang b8e208eb32 style: optimize styles for change password page 2023-06-17 00:02:49 +08:00
RockYang 542c908e30 fix: fixed bu for 'slice bounds out of range' 2023-06-16 23:30:04 +08:00
RockYang 08529242bf style: add logo at login and register page 2023-06-16 18:14:43 +08:00
RockYang 1ae9449d92 chore: tracke the logo file 2023-06-16 17:44:38 +08:00
RockYang 7fd0b1fa08 feat: import iconfont styles, change password function is ready 2023-06-16 17:28:21 +08:00
RockYang 111572e3f2 fix: add lock map data structure, fixed bug for 'concurrent map writes' 2023-06-16 15:32:11 +08:00
RockYang c9875d24b4 feat: add footbar for login and register page, add send button for chat page 2023-06-16 13:57:05 +08:00
RockYang 94a75603c3 chore: compress bg images 2023-06-16 11:14:45 +08:00
RockYang d55925bccd chore: delete bin file 2023-06-16 10:01:34 +08:00
RockYang 0b5adcbfab fix: fixed bug for ssl websocket url 2023-06-16 10:00:05 +08:00
RockYang 8d84562f32 chore: update config sample file 2023-06-15 16:03:42 +08:00
RockYang 136d159833 opt: add email and mobile validation for register 2023-06-15 15:09:00 +08:00
RockYang e3026062d8 opt: automatic get the host for api and websocket from the 'location' var 2023-06-15 14:39:05 +08:00
RockYang 8a69c69b7f opt: update the main chat compnent after updating the user profile 2023-06-15 11:29:16 +08:00
RockYang 197714a57a refactor: add system config key 'user_init_calls' to init the new register user's api calls 2023-06-15 10:06:21 +08:00
RockYang 567cdc6f8d refactor: V3 版本重构已基本完成 2023-06-15 09:41:30 +08:00
RockYang be6e8c7713 Merge pull request #6 from EyreFree/master
feat: docker supported
2023-06-05 08:32:34 +08:00
EyreFree f667b35403 docker 2023-06-03 20:48:32 +08:00
RockYang 942e482ca4 chore: 打招呼的时候输出本项目地址 2023-05-09 16:11:50 +08:00
RockYang 10b381965e Update 1.bug.yml 2023-05-09 15:43:48 +08:00
RockYang 79c3a99a38 Update 1.bug.yml 2023-05-09 15:39:47 +08:00
RockYang 463d3c8e97 Update 1.bug.yml 2023-05-09 15:35:07 +08:00
RockYang da007729c6 Update 1.bug.yml 2023-05-09 15:32:57 +08:00
RockYang b7b42b5fd4 chore: 重命名 issue 模版 2023-05-09 15:29:57 +08:00
RockYang 735f9bd053 chore: 添加 issue 和 PR 模板 2023-05-09 15:26:38 +08:00
RockYang 6096b65374 chore: 替换文字描述 用户 => 2023-05-09 14:30:09 +08:00
RockYang 727d342eb3 docs: 优化文档 2023-05-06 18:33:27 +08:00
RockYang 58815ba8bc docs: 优化文档 2023-05-06 18:13:44 +08:00
RockYang 49d0502775 docs: 优化文档排版 2023-05-06 16:36:04 +08:00
RockYang 0b6c6e6d2c docs: 完善文档 2023-05-06 16:27:44 +08:00
RockYang 45ede6047a opt: 优化配置文档加载 2023-05-06 13:58:56 +08:00
RockYang 8f469d4ebb Merge branch 'master' of gitee.com:blackfox/wechat-gpt 2023-05-06 12:07:17 +08:00
RockYang f3264d056d add LICENSE.
Signed-off-by: RockYang <yangjian102621@gmail.com>
2023-05-06 04:03:33 +00:00
464 changed files with 45341 additions and 22358 deletions
+37
View File
@@ -0,0 +1,37 @@
---
name: frontend-developer
description: Use this agent when you need assistance with frontend development tasks including Vue.js components, UI implementation, styling, responsive design, state management, or frontend architecture decisions. Examples: <example>Context: User is working on a Vue.js component and needs help with implementing a responsive layout. user: 'I need to create a mobile-friendly chat interface component' assistant: 'I'll use the frontend-developer agent to help design and implement this responsive chat component' <commentary>Since this involves frontend development work with Vue.js and responsive design, use the frontend-developer agent.</commentary></example> <example>Context: User encounters styling issues with Element Plus components. user: 'The Element Plus dialog is not displaying correctly on mobile devices' assistant: 'Let me use the frontend-developer agent to troubleshoot this mobile styling issue' <commentary>This is a frontend styling problem that requires expertise in Element Plus and responsive design.</commentary></example>
color: purple
---
You are a Senior Frontend Development Engineer with deep expertise in modern web development technologies, particularly Vue.js 3, Element Plus, Vant, and responsive design patterns. You specialize in creating high-quality, maintainable frontend applications with excellent user experience.
Your core responsibilities include:
- Developing Vue.js 3 components using Composition API and best practices
- Implementing responsive designs that work seamlessly across desktop and mobile devices
- Working with Element Plus for desktop UI and Vant for mobile components
- Managing application state using Pinia store patterns
- Styling with Stylus preprocessor and Tailwind CSS utilities
- Optimizing build processes with Vite and ensuring proper code organization
- Implementing theme switching (dark/light mode) and accessibility features
- Follow decoupled development, with HTML, CSS, and JS codes placed in separate files for easier maintenance
When working on frontend tasks, you will:
1. Analyze requirements and suggest the most appropriate Vue.js patterns and component structures
2. Ensure responsive design principles are followed, considering both desktop and mobile viewports
3. Choose appropriate UI components from Element Plus (desktop) or Vant (mobile) libraries
4. Write clean, maintainable code following Vue.js 3 Composition API best practices
5. Consider performance implications and suggest optimizations when relevant
6. Ensure proper state management using Pinia when component state needs to be shared
7. Follow the project's established patterns for routing, API integration, and component organization
8. Provide specific code examples and explain the reasoning behind architectural decisions
You have deep knowledge of:
- Vue.js 3 ecosystem (Vue Router, Pinia, Composition API)
- Modern CSS techniques and preprocessors (Stylus, Tailwind)
- Component library integration (Element Plus, Vant)
- Build tools and development workflow (Vite, npm scripts)
- Cross-browser compatibility and mobile-first design principles
- Performance optimization and code splitting strategies
Always consider the user experience, code maintainability, and alignment with modern frontend development standards. When suggesting solutions, provide clear explanations and consider both immediate needs and long-term scalability.
+6
View File
@@ -0,0 +1,6 @@
重构当前页面代码
1. 把当前页面 JS 代码全部抽离,然后是采用 Pinia 重构
2. 把当前页面 CSS 代码全部抽离,如果是 stylus 语法代码,则需要改成 SCSS 语法代码
3. 尽量做到代码的复用性,不要重复造轮子
4. 移动端的 css 和 js 分别放到对应的 mobile 目录下,不要覆盖 PC 端的代码
+5
View File
@@ -0,0 +1,5 @@
web/src/assets/fonts filter=lfs diff=lfs merge=lfs -text
web/src/assets/iconfont filter=lfs diff=lfs merge=lfs -text
web/src/assets/img filter=lfs diff=lfs merge=lfs -text
web/public filter=lfs diff=lfs merge=lfs -text
api/res filter=lfs diff=lfs merge=lfs -text
+1
View File
@@ -14,3 +14,4 @@ logs
*.njsproj
*.sln
*.sw?
miniprogram
+35
View File
@@ -1,5 +1,40 @@
# 更新日志
## v4.2.8
- Bug 修复:修复管理后台邮件配置报“参数错误”问题
- 功能优化:手机端也不显示后端禁用的菜单功能页面
- 功能优化:**聊天页面支持 Sora2 文生视频/图生视频功能** 🔥🔥🔥
- 功能优化:**重构路由代码,优化路由加载逻辑,支持关闭手机站功能** 🔥🔥🔥
- Bug 修复:修复用户算力扣减日志记录逻辑错误问题
- 功能优化:优化 404 页面样式,兼容 PC 端和移动端
- Bug 修复:ipv6 地址登录报错:Data too long for column 'last_login_ip'
- Bug 修复:移动端登录页面输入密码的时候会覆盖在确认按钮上层
- 功能优化:**移动端的 DALL-E 绘图页面支持上传参考图** 🔥🔥🔥
- 功能优化:优化 WebFooter 组件,如果配置了备案号,则显示备案号,否则不显示
- 功能优化:启动时自动同步 model 与数据表字段,缺列自动新建、多余列自动删除
## v4.2.7
- Bug 修复:修复超级管理员无法修改密码的 Bug
- Bug 修复:微信登录配置更新后,没有同步更新到系统配置
- 功能优化: 给 AI 对话 API 加上线程锁,确保同一个用户同时只有一个对话请求
- 功能新增:支持即梦 AI 4.0 图片编辑,即梦 AI 数字人,动作迁移功能。🔥🔥🔥
- 功能新增:新增 AI 对话编辑功能,并优化了重新生成逻辑
## v4.2.6
- 功能重构:优化系统配置管理功能,把 OSS,支付,短信,邮件等配置全部迁移到管理后台,无需通过修改配置文档的方式修改 🎉🎉🎉
- 功能优化:重构 API 授权代码,采用中间件鉴权方式,实现更加精准的 API 鉴权 🎉🎉🎉
- 功能优化:优化 PC 端的 Suno 音乐,视频生成,以及即梦 AI 页面 UI
- 功能优化:重构登录和注册页面,兼容移动端和 PC 端,并且所有的登录组件共用了同一套组件代码,大大降低维护成本 🎉🎉🎉
- 功能优化:管理后台增加模型批量删除功能
- 功能优化:优化 Table 组件 UI,并支持 dark 主题
- 功能优化:移动端对话页面支持上传文件和图片
- 功能新增:新增微信扫码登录支持
- 功能新增:新增安全监控,内容审核功能,支持敏感内容过滤拦截
- 功能新增:DALL-E 绘图支持参 Google Banana 图片编辑功能
## v4.2.5
- 功能优化:在代码右下角增加复制代码功能按钮,增加收起和展开代码功能
-195
View File
@@ -1,195 +0,0 @@
# 即梦 AI 配置功能说明
## 功能概述
即梦 AI 配置功能允许管理员通过 Web 界面配置即梦 AI 的 API 密钥和算力消耗设置,支持动态配置更新,无需重启服务。
## 功能特性
### 1. 秘钥配置
- AccessKey 和 SecretKey 配置
- 支持密码显示/隐藏
- 连接测试功能
### 2. 算力配置
- 文生图算力消耗
- 图生图算力消耗
- 图片编辑算力消耗
- 图片特效算力消耗
- 文生视频算力消耗
- 图生视频算力消耗
### 3. 动态配置
- 配置实时生效
- 无需重启服务
- 支持配置验证
## API 接口
### 获取配置
```
GET /api/admin/jimeng/config
```
### 更新配置
```
POST /api/admin/jimeng/config
Content-Type: application/json
{
"config": {
"access_key": "your_access_key",
"secret_key": "your_secret_key",
"power": {
"text_to_image": 10,
"image_to_image": 15,
"image_edit": 20,
"image_effects": 25,
"text_to_video": 30,
"image_to_video": 35
}
}
}
```
### 测试连接
```
POST /api/admin/jimeng/config/test
Content-Type: application/json
{
"config": {
"access_key": "your_access_key",
"secret_key": "your_secret_key"
}
}
```
## 前端页面
### 访问路径
管理后台 -> 即梦 AI -> 配置设置
### 页面功能
1. **秘钥配置标签页**
- AccessKey 输入框(密码模式)
- SecretKey 输入框(密码模式)
- 测试连接按钮
2. **算力配置标签页**
- 各种任务类型的算力消耗配置
- 数字输入框,支持 1-100 范围
- 提示信息说明
3. **操作按钮**
- 保存配置
- 重置配置
## 配置存储
配置存储在数据库的`config`表中:
- 配置键:`jimeng`
- 配置值:JSON 格式的即梦 AI 配置
## 默认配置
如果配置不存在,系统会使用以下默认值:
```json
{
"access_key": "",
"secret_key": "",
"power": {
"text_to_image": 10,
"image_to_image": 15,
"image_edit": 20,
"image_effects": 25,
"text_to_video": 30,
"image_to_video": 35
}
}
```
## 使用流程
1. **初始配置**
- 访问管理后台即梦 AI 配置页面
- 填写 AccessKey 和 SecretKey
- 点击"测试连接"验证配置
- 调整各功能算力消耗
- 保存配置
2. **配置更新**
- 修改需要更新的配置项
- 保存配置
- 配置立即生效
3. **故障排查**
- 使用"测试连接"功能验证 API 密钥
- 检查配置是否正确保存
- 查看服务日志
## 注意事项
1. **权限要求**
- 只有管理员可以访问配置页面
- 需要有效的管理员登录会话
2. **配置验证**
- AccessKey 和 SecretKey 不能为空
- 算力消耗必须大于 0
- 建议先测试连接再保存配置
3. **服务影响**
- 配置更新不会影响正在进行的任务
- 新任务会使用更新后的配置
- 客户端配置会在下次请求时更新
## 错误处理
1. **配置加载失败**
- 使用默认配置
- 记录错误日志
2. **连接测试失败**
- 显示具体错误信息
- 建议检查 API 密钥
3. **配置保存失败**
- 显示错误信息
- 保留原有配置
## 开发说明
### 后端文件
- `api/handler/admin/jimeng_handler.go` - 配置管理 API
- `api/service/jimeng/service.go` - 配置服务逻辑
- `api/core/types/jimeng.go` - 配置类型定义
### 前端文件
- `web/src/views/admin/jimeng/JimengSetting.vue` - 配置页面
### 数据库
- `config`表存储配置信息
- 配置键:`jimeng`
- 配置值:JSON 格式
+59 -1
View File
@@ -1,13 +1,67 @@
# GeekAI-PLUS
# 🚀 GeekAI-PLUS:一站式 AI 创意生产力平台
**重新定义 AI 创作体验,让每个人都能成为内容创作大师**
基于 GeekAI 项目开发的高级版,增加了很多高级功能,比如思维导图,Dalle 绘画等。**高级版源码不会一次性开放,只提供镜像给大家免费使用**,源码会逐步逐步按照版同步迁移到[社区版(GeekAI](https://github.com/yangjian102621/geekai)。所以如果大家想要二次开发,请移步去社区版。
## ✨ 核心特色
### 🎨 **全能 AI 创作矩阵**
- **智能对话**:集成 ChatGPT、Claude 等多款顶级 AI 模型,支持角色扮演和专业对话
- **图像生成**:整合 MidJourney、DALL-E、Stable Diffusion 三大主流 AI 绘画引擎
- **音频创作**:Suno AI 音乐生成,从旋律到歌词一键创作专属音乐
- **视频制作**Luma 和 KeLing,即梦,Veo3 视频 AI,文本到视频,创意无限
- **思维导图**:AI 辅助思维整理,复杂想法可视化呈现
### 🏗️ **企业级技术架构**
- **高性能后端**Go + Gin + MySQL + Redis,支持高并发访问
- **现代化前端**Vue3 + Element Plus + Vant,桌面移动双端适配
- **智能缓存**:多层缓存策略,响应速度提升 80%
- **弹性部署**:Docker 容器化部署,一键启动,轻松扩展
- **私有化部署**:支持私有化部署,私有化部署不支持升级,需要手动升级
- **文档支持**:丰富且详细的部署和 API 开发文档支持,二次开发轻松上手
### 💼 **商业化就绪**
- **完整用户系统**:注册登录、权限管理、积分充值
- **灵活计费模式**:支持按次付费、包月订阅等多种商业模式
- **数据统计分析**:用户行为、消费记录、系统性能全方位监控
- **管理后台**:功能完备的管理员界面,运营数据一目了然
### 🎯 **用户体验优势**
- **响应式设计**:完美适配桌面、平板、手机等全终端设备
- **暗黑模式**:支持明暗主题切换,护眼舒适
- **实时交互**:WebSocket 实时通信,创作过程流畅无卡顿
- **文件管理**:支持多种云存储,作品安全可靠
## 🎪 **应用场景**
- **内容创作者**:博客写作、社交媒体素材、短视频制作
- **企业营销**:品牌宣传材料、产品介绍、创意广告
- **教育培训**:课件制作、知识图谱、互动内容
- **个人娱乐**:AI 聊天、创意绘画、音乐创作
## 🔥 **为什么选择 GeekAI-PLUS**
1. **技术领先**:集成当前最先进的 AI 技术,始终保持创新前沿
2. **开箱即用**:完整的商业化解决方案,无需从零开发
3. **高度定制**:模块化架构设计,支持个性化功能扩展
4. **稳定可靠**:经过大量用户验证,性能稳定,安全可信
5. **持续更新**:紧跟 AI 技术发展,功能持续迭代升级
## 演示站点
[Geek-AI 创作系统](https://www.geekai.me)
## 文档地址
[Geek-AI 文档](https://www.geekai.me/docs/)
## 部署
1. 安装 docker 和 docker-compose 程序,这个自行解决。
2. 直接在项目根目录运行启动命令:
```shell
@@ -15,5 +69,9 @@
```
## 功能截图
请参考 [GeekAI 项目介绍](https://docs.geekai.me/info/)。
---
_让 AI 成为你最强大的创作伙伴,开启无限创意可能!_
+1
View File
@@ -16,6 +16,7 @@ tmp
bin
data
config.toml
config.toml.*.bak
static/upload
static/audio
storage.json
+1 -1
View File
@@ -1,6 +1,6 @@
Listen = "0.0.0.0:5678"
ProxyURL = "" # 如 http://127.0.0.1:7777
MysqlDns = "root:12345678@tcp(172.22.11.200:3307)/chatgpt_plus?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local"
MysqlDns = "root:12345678@tcp(localhost:3306)/geekai?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local"
StaticDir = "./static" # 静态资源的目录
StaticUrl = "/static" # 静态资源访问 URL
TikaHost = "http://tika:9998"
+12 -412
View File
@@ -8,117 +8,50 @@ package core
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"bytes"
"context"
"fmt"
"geekai/core/middleware"
"geekai/core/types"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"image"
"image/jpeg"
"io"
"net/http"
"os"
"runtime/debug"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"github.com/imroc/req/v3"
"github.com/nfnt/resize"
"github.com/shirou/gopsutil/host"
"golang.org/x/image/webp"
"gorm.io/gorm"
)
// AuthConfig 定义授权配置
type AuthConfig struct {
ExactPaths map[string]bool // 精确匹配的路径
PrefixPaths map[string]bool // 前缀匹配的路径
}
var authConfig = &AuthConfig{
ExactPaths: map[string]bool{
"/api/user/login": false,
"/api/user/logout": false,
"/api/user/resetPass": false,
"/api/user/register": false,
"/api/admin/login": false,
"/api/admin/logout": false,
"/api/admin/login/captcha": false,
"/api/app/list": false,
"/api/app/type/list": false,
"/api/app/list/user": false,
"/api/model/list": false,
"/api/mj/imgWall": false,
"/api/mj/notify": false,
"/api/invite/hits": false,
"/api/sd/imgWall": false,
"/api/dall/imgWall": false,
"/api/product/list": false,
"/api/menu/list": false,
"/api/markMap/client": false,
"/api/payment/doPay": false,
"/api/payment/payWays": false,
"/api/download": false,
"/api/dall/models": false,
},
PrefixPaths: map[string]bool{
"/api/test/": false,
"/api/payment/notify/": false,
"/api/user/clogin": false,
"/api/config/": false,
"/api/function/": false,
"/api/sms/": false,
"/api/captcha/": false,
"/static/": false,
},
}
type AppServer struct {
Config *types.AppConfig
Engine *gin.Engine
SysConfig *types.SystemConfig // system config cache
Redis *redis.Client
}
func NewServer(appConfig *types.AppConfig) *AppServer {
func NewServer(appConfig *types.AppConfig, redis *redis.Client, sysConfig *types.SystemConfig) *AppServer {
gin.SetMode(gin.ReleaseMode)
gin.DefaultWriter = io.Discard
return &AppServer{
Config: appConfig,
Engine: gin.Default(),
Config: appConfig,
Redis: redis,
Engine: gin.Default(),
SysConfig: sysConfig,
}
}
func (s *AppServer) Init(debug bool, client *redis.Client) {
s.Engine.Use(corsMiddleware())
s.Engine.Use(staticResourceMiddleware())
s.Engine.Use(authorizeMiddleware(s, client))
s.Engine.Use(parameterHandlerMiddleware())
func (s *AppServer) Init(client *redis.Client) {
s.Engine.Use(middleware.ThumbMiddleware())
s.Engine.Use(middleware.ParameterHandlerMiddleware())
s.Engine.Use(errorHandler)
// 添加静态资源访问
s.Engine.Static("/static", s.Config.StaticDir)
}
func (s *AppServer) Run(db *gorm.DB) error {
// 重命名 config 表字段
if db.Migrator().HasColumn(&model.Config{}, "config_json") {
db.Migrator().RenameColumn(&model.Config{}, "config_json", "value")
}
if db.Migrator().HasColumn(&model.Config{}, "marker") {
db.Migrator().RenameColumn(&model.Config{}, "marker", "name")
}
if db.Migrator().HasIndex(&model.Config{}, "idx_chatgpt_configs_key") {
db.Migrator().DropIndex(&model.Config{}, "idx_chatgpt_configs_key")
}
if db.Migrator().HasIndex(&model.Config{}, "marker") {
db.Migrator().DropIndex(&model.Config{}, "marker")
}
// load system configs
var sysConfig model.Config
err := db.Where("name", "system").First(&sysConfig).Error
@@ -130,70 +63,17 @@ func (s *AppServer) Run(db *gorm.DB) error {
return fmt.Errorf("failed to decode system config: %v", err)
}
// 迁移数据表
logger.Info("Migrating database tables...")
db.AutoMigrate(
&model.ChatItem{},
&model.ChatMessage{},
&model.ChatRole{},
&model.ChatModel{},
&model.InviteCode{},
&model.InviteLog{},
&model.Menu{},
&model.Order{},
&model.Product{},
&model.User{},
&model.Function{},
&model.File{},
&model.Redeem{},
&model.Config{},
&model.ApiKey{},
&model.AdminUser{},
&model.AppType{},
&model.SdJob{},
&model.SunoJob{},
&model.PowerLog{},
&model.VideoJob{},
&model.MidJourneyJob{},
&model.UserLoginLog{},
&model.DallJob{},
&model.JimengJob{},
)
// 手动删除字段
if db.Migrator().HasColumn(&model.Order{}, "deleted_at") {
db.Migrator().DropColumn(&model.Order{}, "deleted_at")
}
if db.Migrator().HasColumn(&model.ChatItem{}, "deleted_at") {
db.Migrator().DropColumn(&model.ChatItem{}, "deleted_at")
}
if db.Migrator().HasColumn(&model.ChatMessage{}, "deleted_at") {
db.Migrator().DropColumn(&model.ChatMessage{}, "deleted_at")
}
if db.Migrator().HasColumn(&model.User{}, "chat_config") {
db.Migrator().DropColumn(&model.User{}, "chat_config")
}
if db.Migrator().HasColumn(&model.ChatModel{}, "category") {
db.Migrator().DropColumn(&model.ChatModel{}, "category")
}
if db.Migrator().HasColumn(&model.ChatModel{}, "description") {
db.Migrator().DropColumn(&model.ChatModel{}, "description")
}
logger.Info("Database tables migrated successfully")
// 统计安装信息
go func() {
info, err := host.Info()
if err == nil {
apiURL := fmt.Sprintf("%s/%s", s.Config.ApiConfig.ApiURL, "api/installs/push")
apiURL := fmt.Sprintf("%s/api/installs/push", types.GeekAPIURL)
timestamp := time.Now().Unix()
product := "geekai-plus"
signStr := fmt.Sprintf("%s#%s#%d", product, info.HostID, timestamp)
sign := utils.Sha256(signStr)
resp, err := req.C().R().SetBody(map[string]interface{}{"product": product, "device_id": info.HostID, "timestamp": timestamp, "sign": sign}).Post(apiURL)
if err != nil {
logger.Errorf("register install info failed: %v", err)
} else {
if err == nil {
logger.Debugf("register install info success: %v", resp.String())
}
}
@@ -215,283 +95,3 @@ func errorHandler(c *gin.Context) {
//加载完 defer recover,继续后续接口调用
c.Next()
}
// 跨域中间件设置
func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin")
// 设置允许的请求源
if origin != "" {
c.Header("Access-Control-Allow-Origin", origin)
} else {
c.Header("Access-Control-Allow-Origin", "*")
}
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
//允许跨域设置可以返回其他子段,可以自定义字段
c.Header("Access-Control-Allow-Headers", "Authorization, Body-Length, Body-Type, Admin-Authorization,content-type")
// 允许浏览器(客户端)可以解析的头部 (重要)
c.Header("Access-Control-Expose-Headers", "Body-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
//设置缓存时间
c.Header("Access-Control-Max-Age", "172800")
//允许客户端传递校验信息比如 cookie (重要)
c.Header("Access-Control-Allow-Credentials", "true")
if method == http.MethodOptions {
c.JSON(http.StatusOK, "ok!")
}
defer func() {
if err := recover(); err != nil {
logger.Info("Panic info is: %v", err)
}
}()
c.Next()
}
}
// 用户授权验证
func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
if !needLogin(c) {
c.Next()
return
}
clientProtocols := c.GetHeader("Sec-WebSocket-Protocol")
var tokenString string
isAdminApi := strings.Contains(c.Request.URL.Path, "/api/admin/")
if isAdminApi { // 后台管理 API
tokenString = c.GetHeader(types.AdminAuthHeader)
} else if clientProtocols != "" { // Websocket 连接
// 解析子协议内容
protocols := strings.Split(clientProtocols, ",")
if protocols[0] == "realtime" {
tokenString = strings.TrimSpace(protocols[1][25:])
} else if protocols[0] == "token" {
tokenString = strings.TrimSpace(protocols[1])
}
} else {
tokenString = c.GetHeader(types.UserAuthHeader)
}
if tokenString == "" {
resp.NotAuth(c, "You should put Authorization in request headers")
c.Abort()
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
if isAdminApi {
return []byte(s.Config.AdminSession.SecretKey), nil
} else {
return []byte(s.Config.Session.SecretKey), nil
}
})
if err != nil {
resp.NotAuth(c, fmt.Sprintf("Error with parse auth token: %v", err))
c.Abort()
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
resp.NotAuth(c, "Token is invalid")
c.Abort()
return
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
resp.NotAuth(c, "Token is expired")
c.Abort()
return
}
key := fmt.Sprintf("users/%v", claims["user_id"])
if isAdminApi {
key = fmt.Sprintf("admin/%v", claims["user_id"])
}
if _, err := client.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c, "Token is not found in redis")
c.Abort()
return
}
c.Set(types.LoginUserID, claims["user_id"])
c.Next()
}
}
func needLogin(c *gin.Context) bool {
path := c.Request.URL.Path
// 如果不是 API 路径,不需要登录
if !strings.HasPrefix(path, "/api") {
return false
}
// 检查精确匹配的路径
if skip, exists := authConfig.ExactPaths[path]; exists {
return skip
}
// 检查前缀匹配的路径
for prefix, skip := range authConfig.PrefixPaths {
if strings.HasPrefix(path, prefix) {
return skip
}
}
return true
}
// 跳过授权
func (s *AppServer) SkipAuth(url string, prefix bool) {
if prefix {
authConfig.PrefixPaths[url] = false
} else {
authConfig.ExactPaths[url] = false
}
}
// 统一参数处理
func parameterHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// GET 参数处理
params := c.Request.URL.Query()
for key, values := range params {
for i, value := range values {
params[key][i] = strings.TrimSpace(value)
}
}
// update get parameters
c.Request.URL.RawQuery = params.Encode()
// skip file upload requests
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
c.Next()
return
}
if strings.Contains(contentType, "application/json") {
// process POST JSON request body
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Next()
return
}
// 还原请求体
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 将请求体解析为 JSON
var jsonData map[string]interface{}
if err := c.ShouldBindJSON(&jsonData); err != nil {
c.Next()
return
}
// 对 JSON 数据中的字符串值去除两端空格
trimJSONStrings(jsonData)
// 更新请求体
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
}
c.Next()
}
}
// 递归对 JSON 数据中的字符串值去除两端空格
func trimJSONStrings(data interface{}) {
switch v := data.(type) {
case map[string]interface{}:
for key, value := range v {
switch valueType := value.(type) {
case string:
v[key] = strings.TrimSpace(valueType)
case map[string]interface{}, []interface{}:
trimJSONStrings(value)
}
}
case []interface{}:
for i, value := range v {
switch valueType := value.(type) {
case string:
v[i] = strings.TrimSpace(valueType)
case map[string]interface{}, []interface{}:
trimJSONStrings(value)
}
}
}
}
// 静态资源中间件
func staticResourceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
url := c.Request.URL.String()
// 拦截生成缩略图请求
if strings.HasPrefix(url, "/static/") && strings.Contains(url, "?imageView2") {
r := strings.SplitAfter(url, "imageView2")
size := strings.Split(r[1], "/")
if len(size) != 8 {
c.String(http.StatusNotFound, "invalid thumb args")
return
}
with := utils.IntValue(size[3], 0)
height := utils.IntValue(size[5], 0)
quality := utils.IntValue(size[7], 75)
// 打开图片文件
filePath := strings.TrimLeft(c.Request.URL.Path, "/")
file, err := os.Open(filePath)
if err != nil {
c.String(http.StatusNotFound, "Image not found")
return
}
defer file.Close()
// 解码图片
img, _, err := image.Decode(file)
// for .webp image
if err != nil {
img, err = webp.Decode(file)
}
if err != nil {
c.String(http.StatusInternalServerError, "Error decoding image")
return
}
var newImg image.Image
if height == 0 || with == 0 {
// 固定宽度,高度自适应
newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3)
} else {
// 生成缩略图
newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3)
}
var buffer bytes.Buffer
err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality})
if err != nil {
logger.Error(err)
c.String(http.StatusInternalServerError, err.Error())
return
}
// 设置图片缓存有效期为一年 (365天)
c.Header("Cache-Control", "max-age=31536000, public")
// 直接输出图像数据流
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
c.Abort() // 中断请求
}
c.Next()
}
}
+97 -2
View File
@@ -11,10 +11,12 @@ import (
"bytes"
"geekai/core/types"
logger2 "geekai/logger"
"geekai/store/model"
"geekai/utils"
"os"
"github.com/BurntSushi/toml"
"gorm.io/gorm"
)
var logger = logger2.GetLogger()
@@ -30,7 +32,6 @@ func NewDefaultConfig() *types.AppConfig {
SecretKey: utils.RandString(64),
MaxAge: 86400,
},
ApiConfig: types.ApiConfig{},
OSS: types.OSSConfig{
Active: "local",
Local: types.LocalStorageConfig{
@@ -38,7 +39,6 @@ func NewDefaultConfig() *types.AppConfig {
BasePath: "./static/upload",
},
},
AlipayConfig: types.AlipayConfig{Enabled: false, SandBox: false},
}
}
@@ -74,3 +74,98 @@ func SaveConfig(config *types.AppConfig) error {
return os.WriteFile(config.Path, buf.Bytes(), 0644)
}
func LoadSystemConfig(db *gorm.DB) *types.SystemConfig {
// 加载系统配置
var sysConfig model.Config
var baseConfig types.BaseConfig
db.Where("name", "system").First(&sysConfig)
err := utils.JsonDecode(sysConfig.Value, &baseConfig)
if err != nil {
logger.Error("load system config error: ", err)
}
// 加载验证码配置
var captchaConfig types.CaptchaConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeyCaptcha).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &captchaConfig)
if err != nil {
logger.Error("load geek service config error: ", err)
}
// 加载微信登录配置
var wxLoginConfig types.WxLoginConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeyWxLogin).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &wxLoginConfig)
if err != nil {
logger.Error("load wx login config error: ", err)
}
// 加载短信配置
var smsConfig types.SMSConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeySms).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &smsConfig)
if err != nil {
logger.Error("load sms config error: ", err)
}
// 加载 OSS 配置
var ossConfig types.OSSConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeyOss).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &ossConfig)
if err != nil {
logger.Error("load oss config error: ", err)
}
// 加载 SMTP 配置
var smtpConfig types.SmtpConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeySmtp).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &smtpConfig)
if err != nil {
logger.Error("load smtp config error: ", err)
}
// 加载支付配置
var paymentConfig types.PaymentConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeyPayment).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &paymentConfig)
if err != nil {
logger.Error("load payment config error: ", err)
}
// 加载文本审查配置
var moderationConfig types.ModerationConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeyModeration).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &moderationConfig)
if err != nil {
logger.Error("load moderation config error: ", err)
}
// 加载即梦AI配置
var jimengConfig types.JimengConfig
sysConfig.Id = 0
db.Where("name", types.ConfigKeyJimeng).First(&sysConfig)
err = utils.JsonDecode(sysConfig.Value, &jimengConfig)
if err != nil {
logger.Error("load jimeng config error: ", err)
}
return &types.SystemConfig{
Base: baseConfig,
SMS: smsConfig,
OSS: ossConfig,
SMTP: smtpConfig,
Payment: paymentConfig,
Captcha: captchaConfig,
WxLogin: wxLoginConfig,
Moderation: moderationConfig,
Jimeng: jimengConfig,
}
}
+112
View File
@@ -0,0 +1,112 @@
package middleware
import (
"context"
"fmt"
"geekai/core/types"
logger2 "geekai/logger"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt"
)
var logger = logger2.GetLogger()
// 前端用户授权验证
func UserAuthMiddleware(secretKey string, redis *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader(types.UserAuthHeader)
if tokenString == "" {
resp.NotAuth(c, "无效的授权令牌")
c.Abort()
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("不支持的令牌签名方法: %v", token.Header["alg"])
}
return []byte(secretKey), nil
})
if err != nil {
resp.NotAuth(c, fmt.Sprintf("解析授权令牌失败: %v", err))
c.Abort()
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
resp.NotAuth(c, "令牌无效")
c.Abort()
return
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
resp.NotAuth(c, "令牌过期")
c.Abort()
return
}
key := fmt.Sprintf("users/%v", claims["user_id"])
if _, err := redis.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c, "当前用户已退出登录")
c.Abort()
return
}
c.Set(types.LoginUserID, claims["user_id"])
}
}
// 管理后台用户授权验证
func AdminAuthMiddleware(secretKey string, redis *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader(types.AdminAuthHeader)
if tokenString == "" {
resp.NotAuth(c, "无效的授权令牌")
c.Abort()
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("不支持的令牌签名方法: %v", token.Header["alg"])
}
return []byte(secretKey), nil
})
if err != nil {
resp.NotAuth(c, fmt.Sprintf("解析授权令牌失败: %v", err))
c.Abort()
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
resp.NotAuth(c, "令牌无效")
c.Abort()
return
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
resp.NotAuth(c, "令牌过期")
c.Abort()
return
}
key := fmt.Sprintf("admin/%v", claims["user_id"])
if _, err := redis.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c, "当前用户已退出登录")
c.Abort()
return
}
c.Set(types.AdminUserID, claims["user_id"])
}
}
+80
View File
@@ -0,0 +1,80 @@
package middleware
import (
"bytes"
"geekai/utils"
"io"
"strings"
"github.com/gin-gonic/gin"
)
// 统一参数处理
func ParameterHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// GET 参数处理
params := c.Request.URL.Query()
for key, values := range params {
for i, value := range values {
params[key][i] = strings.TrimSpace(value)
}
}
// update get parameters
c.Request.URL.RawQuery = params.Encode()
// skip file upload requests
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
c.Next()
return
}
if strings.Contains(contentType, "application/json") {
// process POST JSON request body
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Next()
return
}
// 还原请求体
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 将请求体解析为 JSON
var jsonData map[string]any
if err := c.ShouldBindJSON(&jsonData); err != nil {
c.Next()
return
}
// 对 JSON 数据中的字符串值去除两端空格
trimJSONStrings(jsonData)
// 更新请求体
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
}
c.Next()
}
}
// 递归对 JSON 数据中的字符串值去除两端空格
func trimJSONStrings(data any) {
switch v := data.(type) {
case map[string]any:
for key, value := range v {
switch valueType := value.(type) {
case string:
v[key] = strings.TrimSpace(valueType)
case map[string]any, []any:
trimJSONStrings(value)
}
}
case []any:
for i, value := range v {
switch valueType := value.(type) {
case string:
v[i] = strings.TrimSpace(valueType)
case map[string]any, []any:
trimJSONStrings(value)
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
package middleware
import (
"context"
"fmt"
"geekai/core/types"
"geekai/utils"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
)
// RateLimitEvery 使用 Redis 做固定间隔限流:在 interval 内仅允许一次请求
// Key 优先使用登录用户ID,若没有则退化为 route + IP
func RateLimitEvery(redisClient *redis.Client, interval time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
keyID := ""
if userID, ok := c.Get(types.LoginUserID); ok {
keyID = fmt.Sprintf("user:%s", utils.InterfaceToString(userID))
} else {
keyID = fmt.Sprintf("ip:%s", c.ClientIP())
}
fullPath := c.FullPath()
if fullPath == "" {
fullPath = c.Request.URL.Path
}
key := fmt.Sprintf("rl:%s:%s", fullPath, keyID)
okSet, err := redisClient.SetNX(context.Background(), key, 1, interval).Result()
if err != nil {
// Redis 异常时放行,避免误伤可用性
return
}
if !okSet {
c.JSON(http.StatusTooManyRequests, types.BizVo{Code: types.Failed, Message: "请求过于频繁,请稍后重试"})
c.Abort()
return
}
}
}
+78
View File
@@ -0,0 +1,78 @@
package middleware
import (
"bytes"
"geekai/utils"
"image"
"image/jpeg"
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
"github.com/nfnt/resize"
"golang.org/x/image/webp"
)
// 缩略图中间件
func ThumbMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
url := c.Request.URL.String()
// 拦截生成缩略图请求
if strings.HasPrefix(url, "/static/") && strings.Contains(url, "?imageView2") {
r := strings.SplitAfter(url, "imageView2")
size := strings.Split(r[1], "/")
if len(size) != 8 {
c.String(http.StatusNotFound, "invalid thumb args")
return
}
with := utils.IntValue(size[3], 0)
height := utils.IntValue(size[5], 0)
quality := utils.IntValue(size[7], 75)
// 打开图片文件
filePath := strings.TrimLeft(c.Request.URL.Path, "/")
file, err := os.Open(filePath)
if err != nil {
c.String(http.StatusNotFound, "Image not found")
return
}
defer file.Close()
// 解码图片
img, _, err := image.Decode(file)
// for .webp image
if err != nil {
img, err = webp.Decode(file)
}
if err != nil {
c.String(http.StatusInternalServerError, "Error decoding image")
return
}
var newImg image.Image
if height == 0 || with == 0 {
// 固定宽度,高度自适应
newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3)
} else {
// 生成缩略图
newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3)
}
var buffer bytes.Buffer
err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality})
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
// 设置图片缓存有效期为一年 (365天)
c.Header("Cache-Control", "max-age=31536000, public")
// 直接输出图像数据流
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
c.Abort() // 中断请求
}
c.Next()
}
}
+57 -118
View File
@@ -17,88 +17,17 @@ type AppConfig struct {
Session Session
AdminSession Session
ProxyURL string
MysqlDns string // mysql 连接地址
StaticDir string // 静态资源目录
StaticUrl string // 静态资源 URL
Redis RedisConfig // redis 连接信息
ApiConfig ApiConfig // ChatPlus API authorization configs
SMS SMSConfig // send mobile message config
OSS OSSConfig // OSS config
SmtpConfig SmtpConfig // 邮件发送配置
XXLConfig XXLConfig
AlipayConfig AlipayConfig // 支付宝支付渠道配置
HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置
GeekPayConfig GeekPayConfig // GEEK 支付配置
WechatPayConfig WechatPayConfig // 微信支付渠道配置
TikaHost string // TiKa 服务器地址
}
type SmtpConfig struct {
UseTls bool // 是否使用 TLS 发送
Host string
Port int
AppName string // 应用名称
From string // 发件人邮箱地址
Password string // 发件人邮箱密码
}
type ApiConfig struct {
ApiURL string
AppId string
Token string
JimengConfig JimengConfig // 即梦AI配置
}
type AlipayConfig struct {
Enabled bool // 是否启用该支付通道
SandBox bool // 是否沙盒环境
AppId string // 应用 ID
UserId string // 支付宝用户 ID
PrivateKey string // 用户私钥文件路径
PublicKey string // 用户公钥文件路径
AlipayPublicKey string // 支付宝公钥文件路径
RootCert string // Root 秘钥路径
NotifyURL string // 异步通知地址
ReturnURL string // 同步通知地址
}
type WechatPayConfig struct {
Enabled bool // 是否启用该支付通道
AppId string // 公众号的APPID,如:wxd678efh567hg6787
MchId string // 直连商户的商户号,由微信支付生成并下发
SerialNo string // 商户证书的证书序列号
PrivateKey string // 用户私钥文件路径
ApiV3Key string // API V3 秘钥
NotifyURL string // 异步通知地址
}
type HuPiPayConfig struct { //虎皮椒第四方支付配置
Enabled bool // 是否启用该支付通道
AppId string // App ID
AppSecret string // app 密钥
ApiURL string // 支付网关
NotifyURL string // 异步通知地址
ReturnURL string // 同步通知地址
}
// GeekPayConfig GEEK支付配置
type GeekPayConfig struct {
Enabled bool
AppId string // 商户 ID
PrivateKey string // 私钥
ApiURL string // API 网关
NotifyURL string // 异步通知地址
ReturnURL string // 同步通知地址
Methods []string // 支付方式
}
type XXLConfig struct { // XXL 任务调度配置
Enabled bool
ServerAddr string
ExecutorIp string
ExecutorPort string
AccessToken string
RegistryKey string
MysqlDns string // mysql 连接地址
StaticDir string // 静态资源目录
StaticUrl string // 静态资源 URL
Redis RedisConfig // redis 连接信息
SMS SMSConfig // send mobile message config
OSS OSSConfig // OSS config
SmtpConfig SmtpConfig // 邮件发送配置
AlipayConfig AlipayConfig // 支付宝支付渠道配置
GeekPayConfig EpayConfig // GEEK 支付配置
WechatPayConfig WxPayConfig // 微信支付渠道配置
TikaHost string // TiKa 服务器地址
}
type RedisConfig struct {
@@ -108,52 +37,32 @@ type RedisConfig struct {
DB int
}
// LicenseKey 存储许可证书的 KEY
const LicenseKey = "Geek-AI-License"
type License struct {
Key string `json:"key"` // 许可证书密钥
MachineId string `json:"machine_id"` // 机器码
ExpiredAt int64 `json:"expired_at"` // 过期时间
IsActive bool `json:"is_active"` // 是否激活
Configs LicenseConfig `json:"configs"`
}
type LicenseConfig struct {
UserNum int `json:"user_num"` // 用户数量
DeCopy bool `json:"de_copy"` // 去版权
}
func (c RedisConfig) Url() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
type SystemConfig struct {
Title string `json:"title,omitempty"` // 网站标题
Slogan string `json:"slogan,omitempty"` // 网站 slogan
AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题
Logo string `json:"logo,omitempty"` // 圆形 Logo
BarLogo string `json:"bar_logo,omitempty"` // 条形 Logo
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
DailyPower int `json:"daily_power,omitempty"` // 每日签到赠送算力
InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值
VipMonthPower int `json:"vip_month_power,omitempty"` // VIP 会员每月赠送的算力值
type BaseConfig struct {
Title string `json:"title,omitempty"` // 网站标题
Slogan string `json:"slogan,omitempty"` // 网站 slogan
AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题
Logo string `json:"logo,omitempty"` // 圆形 Logo
BarLogo string `json:"bar_logo,omitempty"` // 条形 Logo
RegisterWays []string `json:"register_ways,omitempty"` // 注册方式:支持手机(mobile),邮箱注册(email),账号密码注册
EnabledRegister bool `json:"enabled_register,omitempty"` // 是否开放注册
OrderPayTimeout int `json:"order_pay_timeout,omitempty"` //订单支付超时时间
VipInfoText string `json:"vip_info_text,omitempty"` // 会员页面充值说明
OrderPayTimeout int `json:"order_pay_timeout,omitempty"` //订单支付超时时间,单位:分钟
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
DailyPower int `json:"daily_power,omitempty"` // 每日签到赠送算力
InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力
DallPower int `json:"dall_power,omitempty"` // DALL-E-3 绘图消耗算力
SunoPower int `json:"suno_power,omitempty"` // Suno 生成歌曲消耗算力
LumaPower int `json:"luma_power,omitempty"` // Luma 生成视频消耗算力
KeLingPowers map[string]int `json:"keling_powers,omitempty"` // 可灵生成视频消耗算力
AdvanceVoicePower int `json:"advance_voice_power,omitempty"` // 高级语音对话消耗算力
PromptPower int `json:"prompt_power,omitempty"` // 生成提示词消耗算力
WechatCardURL string `json:"wechat_card_url,omitempty"` // 微信客服地址
@@ -163,15 +72,45 @@ type SystemConfig struct {
SdNegPrompt string `json:"sd_neg_prompt"` // SD 默认反向提示词
MjMode string `json:"mj_mode"` // midjourney 默认的API模式,relax, fast, turbo
IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单
Copyright string `json:"copyright"` // 版权信息
DefaultNickname string `json:"default_nickname"` // 默认昵称
ICP string `json:"icp"` // ICP 备案号
MarkMapText string `json:"mark_map_text"` // 思维导入的默认文本
IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单
IndexPage string `json:"index_page"` // 首页显示的页面
Copyright string `json:"copyright"` // 版权信息
ICP string `json:"icp"` // ICP 备案号
GaBeian string `json:"ga_beian"` // 公安备案号
EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码
EmailWhiteList []string `json:"email_white_list"` // 邮箱白名单列表
AssistantModelId int `json:"assistant_model_id"` // 用来做提示词,翻译的AI模型 id
MaxFileSize int `json:"max_file_size"` // 最大文件大小,单位:MB
EnableMobileSite bool `json:"enable_mobile_site"` // 是否开启手机站点
}
type SystemConfig struct {
Base BaseConfig
Payment PaymentConfig
OSS OSSConfig
SMS SMSConfig
SMTP SmtpConfig
Captcha CaptchaConfig
WxLogin WxLoginConfig
Jimeng JimengConfig
Moderation ModerationConfig
}
// 配置键名常量
const (
ConfigKeySystem = "system"
ConfigKeyNotice = "notice"
ConfigKeyAgreement = "agreement"
ConfigKeyPrivacy = "privacy"
ConfigKeyMarkMap = "mark_map"
ConfigKeyCaptcha = "captcha"
ConfigKeyWxLogin = "wx_login"
ConfigKeySms = "sms"
ConfigKeySmtp = "smtp"
ConfigKeyOss = "oss"
ConfigKeyPayment = "payment"
ConfigKeyModeration = "moderation"
ConfigKeyAI3D = "ai3d"
ConfigKeyJimeng = "jimeng"
)
+33
View File
@@ -0,0 +1,33 @@
package types
import "os"
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// GeekAI 增值服务
var GeekAPIURL = "https://sapi.geekai.me"
func init() {
if os.Getenv("GEEK_API_URL") != "" {
GeekAPIURL = os.Getenv("GEEK_API_URL")
}
}
// CaptchaConfig 行为验证码配置
type CaptchaConfig struct {
ApiKey string `json:"api_key,omitempty"`
Type string `json:"type,omitempty"` // 验证码类型, 可选值: "dot" 或 "slide"
Enabled bool `json:"enabled,omitempty"`
}
// WxLoginConfig 微信登录配置
type WxLoginConfig struct {
ApiKey string `json:"api_key,omitempty"`
NotifyURL string `json:"notify_url,omitempty"` // 登录成功回调 URL
Enabled bool `json:"enabled,omitempty"` // 是否启用微信登录
}
+59 -11
View File
@@ -2,17 +2,65 @@ package types
// JimengConfig 即梦AI配置
type JimengConfig struct {
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
Power JimengPower `json:"power"`
// 即梦AI的AccessKey和SecretKey
AccessKey string `json:"access_key,omitempty"`
SecretKey string `json:"secret_key,omitempty"`
// 火山引擎大模型专用的验证方式
ApiKey string `json:"api_key,omitempty"`
// 算力配置
Powers map[string]int `json:"powers,omitempty"`
}
// JimengPower 即梦AI算力配置
type JimengPower struct {
TextToImage int `json:"text_to_image"`
ImageToImage int `json:"image_to_image"`
ImageEdit int `json:"image_edit"`
ImageEffects int `json:"image_effects"`
TextToVideo int `json:"text_to_video"`
ImageToVideo int `json:"image_to_video"`
// JMTaskStatus 任务状态
type JMTaskStatus string
const (
JMTaskStatusSubmited = JMTaskStatus("submited") // 任务已提交
JMTaskStatusInQueue = JMTaskStatus("in_queue") // 任务排队中
JMTaskStatusGenerating = JMTaskStatus("generating") // 任务处理中
JMTaskStatusDone = JMTaskStatus("done") // 处理完成
JMTaskStatusNotFound = JMTaskStatus("not_found") // 任务未找到
JMTaskStatusSuccess = JMTaskStatus("success") // 任务成功
JMTaskStatusFailed = JMTaskStatus("failed") // 任务失败
JMTaskStatusExpired = JMTaskStatus("expired") // 任务过期
)
// JMTaskType 任务类型
type JMTaskType string
const (
JMTaskTypeImage = JMTaskType("image") // 文生图
JMTaskTypeVideo = JMTaskType("video") // 图生图
JMTaskTypeVirtualHuman = JMTaskType("virtual_human") // 图像编辑
JMTaskTypeActionTransfer = JMTaskType("action_transfer") // 图像特效
)
// JimengTaskRequest 即梦AI任务请求
type JimengTaskRequest struct {
TaskType JMTaskType `json:"type"` // 任务类型
ReqKey string `json:"req_key"` // 请求Key
Action string `json:"action"` // 请求Action
Power int `json:"power"` // 消耗算力
// 公共参数
Prompt string `json:"prompt,omitempty"`
ImageUrls []string `json:"image_urls,omitempty"`
// 图片生成参数
Size string `json:"size,omitempty"`
UsePreLLM bool `json:"use_pre_llm,omitempty"`
Scale float64 `json:"scale,omitempty"`
ForceSingle bool `json:"force_single,omitempty"`
// 视频生成参数
Duration int `json:"duration,omitempty"` // 视频时长,单位:秒
TemplateId string `json:"template_id,omitempty"` // 运镜模板ID
AspectRatio string `json:"aspect_ratio,omitempty"`
CameraStrength string `json:"camera_strength,omitempty"` // 运镜强度
// 数字人视频生成参数
AudioURL string `json:"audio_url,omitempty"` // 音频URL
RecognizeKey string `json:"recognize_key,omitempty"` // 识别主体请求Key
// 视频动作迁移参数
VideoURL string `json:"video_url,omitempty"` // 动作视频URL
}
+73
View File
@@ -0,0 +1,73 @@
package types
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 文本审查
type ModerationConfig struct {
Enable bool `json:"enable,omitempty"` // 是否启用文本审查
Active string `json:"active,omitempty"`
EnableGuide bool `json:"enable_guide,omitempty"` // 是否启用模型引导提示词
GuidePrompt string `json:"guide_prompt,omitempty"` // 模型引导提示词
Gitee ModerationGiteeConfig `json:"gitee,omitempty"`
Baidu ModerationBaiduConfig `json:"baidu,omitempty"`
Tencent ModerationTencentConfig `json:"tencent,omitempty"`
}
const (
ModerationGitee = "gitee"
ModerationBaidu = "baidu"
ModerationTencent = "tencent"
)
// GiteeAI 文本审查配置
type ModerationGiteeConfig struct {
ApiKey string `json:"api_key,omitempty"`
Model string `json:"model,omitempty"` // 文本审核模型
}
// 百度文本审查配置
type ModerationBaiduConfig struct {
AccessKey string `json:"access_key,omitempty"`
SecretKey string `json:"secret_key,omitempty"`
}
// 腾讯云文本审查配置
type ModerationTencentConfig struct {
AccessKey string `json:"access_key,omitempty"`
SecretKey string `json:"secret_key,omitempty"`
}
type ModerationResult struct {
Flagged bool `json:"flagged,omitempty"`
Categories map[string]bool `json:"categories,omitempty"`
CategoryScores map[string]float64 `json:"category_scores,omitempty"`
}
var ModerationCategories = map[string]string{
"politic": "内容涉及人物、事件或敏感的政治观点",
"porn": "明确的色情内容",
"insult": "具有侮辱、攻击性语言、人身攻击或冒犯性表达",
"violence": "包含暴力、血腥、攻击行为或煽动暴力的言论",
"illegal": "涉及违法活动的内容,如诈骗、赌博等",
"terror": "宣扬恐怖主义、极端暴力或煽动恐怖行为的内容",
"ad": "垃圾广告或未经许可的推广内容",
"spam": "无意义重复内容或诱导性信息",
"abuse": "人身攻击、恶意辱骂或侮辱性言论",
"polity": "涉及国家政治、领导人或政策的违规讨论内容",
}
// 敏感词来源
const (
ModerationSourceChat = "chat"
ModerationSourceMJ = "mj"
ModerationSourceDalle = "dalle"
ModerationSourceSD = "sd"
ModerationSourceSuno = "suno"
ModerationSourceVideo = "video"
ModerationSourceJiMeng = "jimeng"
)
+12 -16
View File
@@ -11,29 +11,25 @@ type OrderStatus int
const (
OrderNotPaid = OrderStatus(0)
OrderScanned = OrderStatus(1) // 已扫码
OrderPaidSuccess = OrderStatus(2)
OrderPaidSuccess = OrderStatus(2) // 已支付
OrderPaidFailed = OrderStatus(3) // 已关闭
)
type OrderRemark struct {
Days int `json:"days"` // 有效期
Power int `json:"power"` // 增加算力点数
Name string `json:"name"` // 产品名称
Price float64 `json:"price"`
Discount float64 `json:"discount"`
Days int `json:"days"` // 有效期
Power int `json:"power"` // 增加算力点数
Name string `json:"name"` // 产品名称
Price float64 `json:"price"`
}
var PayMethods = map[string]string{
// PayChannel 支付渠道
var PayChannel = map[string]string{
"alipay": "支付宝商号",
"wechat": "微信商号",
"hupi": "虎皮椒",
"geek": "易支付",
"wxpay": "微信商号",
"epay": "易支付",
}
var PayNames = map[string]string{
var PayWays = map[string]string{
"alipay": "支付宝",
"wxpay": "微信支付",
"qqpay": "QQ钱包",
"jdpay": "京东支付",
"douyin": "抖音支付",
"paypal": "PayPal支付",
}
+24 -26
View File
@@ -8,41 +8,39 @@ package types
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
type OSSConfig struct {
Active string
Local LocalStorageConfig
Minio MiniOssConfig
QiNiu QiNiuOssConfig
AliYun AliYunOssConfig
Active string `json:"active,omitempty"`
Local LocalStorageConfig `json:"local,omitempty"`
Minio MiniOssConfig `json:"minio,omitempty"`
QiNiu QiNiuOssConfig `json:"qiniu,omitempty"`
AliYun AliYunOssConfig `json:"aliyun,omitempty"`
}
type MiniOssConfig struct {
Endpoint string
AccessKey string
AccessSecret string
Bucket string
SubDir string
UseSSL bool
Domain string
Endpoint string `json:"endpoint,omitempty"`
AccessKey string `json:"access_key,omitempty"`
AccessSecret string `json:"access_secret,omitempty"`
Bucket string `json:"bucket,omitempty"`
UseSSL bool `json:"use_ssl,omitempty"`
Domain string `json:"domain,omitempty"`
}
type QiNiuOssConfig struct {
Zone string
AccessKey string
AccessSecret string
Bucket string
SubDir string
Domain string
Zone string `json:"zone,omitempty"`
AccessKey string `json:"access_key,omitempty"`
AccessSecret string `json:"access_secret,omitempty"`
Bucket string `json:"bucket,omitempty"`
Domain string `json:"domain,omitempty"`
}
type AliYunOssConfig struct {
Endpoint string
AccessKey string
AccessSecret string
Bucket string
SubDir string
Domain string
Endpoint string `json:"endpoint,omitempty"`
AccessKey string `json:"access_key,omitempty"`
AccessSecret string `json:"access_secret,omitempty"`
Bucket string `json:"bucket,omitempty"`
Domain string `json:"domain,omitempty"`
}
type LocalStorageConfig struct {
BasePath string
BaseURL string
BasePath string `json:"base_path,omitempty"`
BaseURL string `json:"base_url,omitempty"`
}
+60
View File
@@ -0,0 +1,60 @@
package types
type PaymentConfig struct {
Alipay AlipayConfig `json:"alipay,omitempty"` // 支付宝支付渠道配置
Epay EpayConfig `json:"epay,omitempty"` // 易支付配置
WxPay WxPayConfig `json:"wxpay,omitempty"` // 微信支付渠道配置
}
// AlipayConfig 支付宝支付配置
type AlipayConfig struct {
Enabled bool `json:"enabled,omitempty"` // 是否启用该支付通道
SandBox bool `json:"sandbox,omitempty"` // 是否沙盒环境
AppId string `json:"app_id,omitempty"` // 应用 ID
PrivateKey string `json:"private_key,omitempty"` // 应用私钥
AlipayPublicKey string `json:"alipay_public_key,omitempty"` // 支付宝公钥
Domain string `json:"domain,omitempty"` // 支付回调域名
}
func (c *AlipayConfig) Equal(other *AlipayConfig) bool {
return c.AppId == other.AppId &&
c.PrivateKey == other.PrivateKey &&
c.AlipayPublicKey == other.AlipayPublicKey &&
c.Domain == other.Domain
}
// WxPayConfig 微信支付配置
type WxPayConfig struct {
Enabled bool `json:"enabled,omitempty"` // 是否启用该支付通道
AppId string `json:"app_id,omitempty"` // 公众号的APPID,如:wxd678efh567hg6787
MchId string `json:"mch_id,omitempty"` // 直连商户的商户号,由微信支付生成并下发
SerialNo string `json:"serial_no,omitempty"` // 商户证书的证书序列号
PrivateKey string `json:"private_key,omitempty"` // 商户证书私钥
ApiV3Key string `json:"api_v3_key,omitempty"` // API V3 秘钥
Domain string `json:"domain,omitempty"` // 支付回调域名
}
func (c *WxPayConfig) Equal(other *WxPayConfig) bool {
return c.AppId == other.AppId &&
c.MchId == other.MchId &&
c.SerialNo == other.SerialNo &&
c.PrivateKey == other.PrivateKey &&
c.ApiV3Key == other.ApiV3Key &&
c.Domain == other.Domain
}
// EpayConfig 易支付配置
type EpayConfig struct {
Enabled bool `json:"enabled,omitempty"` // 是否启用该支付通道
AppId string `json:"app_id,omitempty"` // 商户 ID
PrivateKey string `json:"private_key,omitempty"` // 私钥
ApiURL string `json:"api_url,omitempty"` // z支付 API 网关
Domain string `json:"domain,omitempty"` // 支付回调域名
}
func (c *EpayConfig) Equal(other *EpayConfig) bool {
return c.AppId == other.AppId &&
c.PrivateKey == other.PrivateKey &&
c.ApiURL == other.ApiURL &&
c.Domain == other.Domain
}
+1
View File
@@ -8,6 +8,7 @@ package types
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
const LoginUserID = "LOGIN_USER_ID"
const AdminUserID = "ADMIN_USER_ID"
const LoginUserCache = "LOGIN_USER_CACHE"
const UserAuthHeader = "Authorization"
+11 -14
View File
@@ -8,26 +8,23 @@ package types
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
type SMSConfig struct {
Active string
Ali SmsConfigAli
Bao SmsConfigBao
Active string `json:"active,omitempty"`
Ali SmsConfigAli `json:"aliyun,omitempty"`
Bao SmsConfigBao `json:"bao,omitempty"`
}
// SmsConfigAli 阿里云短信平台配置
type SmsConfigAli struct {
AccessKey string
AccessSecret string
Product string
Domain string
Sign string // 短信签名
CodeTempId string // 验证码短信模板 ID
AccessKey string `json:"access_key,omitempty"`
AccessSecret string `json:"access_secret,omitempty"`
Sign string `json:"sign,omitempty"` // 短信签名
CodeTempId string `json:"code_temp_id,omitempty"` // 验证码短信模板 ID
}
// SmsConfigBao 短信宝平台配置
type SmsConfigBao struct {
Username string //短信宝平台注册的用户名
Password string //短信宝平台注册的密码
Domain string //域名
Sign string // 短信签名
CodeTemplate string // 验证码短信模板 匹配
Username string `json:"username,omitempty"` //短信宝平台注册的用户名
Password string `json:"password,omitempty"` //短信宝平台注册的密码
Sign string `json:"sign,omitempty"` // 短信签名
CodeTemplate string `json:"code_template,omitempty"` // 验证码短信模板 匹配
}
+26
View File
@@ -0,0 +1,26 @@
package types
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
type SmtpConfig struct {
UseTls bool `json:"use_tls,omitempty"` // 是否使用 TLS 发送
Host string `json:"host,omitempty"` // 邮件服务器地址
Port int `json:"port,omitempty"` // 邮件服务器端口
AppName string `json:"app_name,omitempty"` // 应用名称
From string `json:"from,omitempty"` // 发件人邮箱地址
Password string `json:"password,omitempty"` // 发件人邮箱密码
}
func (s *SmtpConfig) Equal(other *SmtpConfig) bool {
return s.UseTls == other.UseTls &&
s.Host == other.Host &&
s.Port == other.Port &&
s.AppName == other.AppName &&
s.From == other.From &&
s.Password == other.Password
}
+11 -11
View File
@@ -70,17 +70,17 @@ type SdTaskParams struct {
// DallTask DALL-E task
type DallTask struct {
ModelId uint `json:"model_id"`
ModelName string `json:"model_name"`
Id uint `json:"id"`
UserId uint `json:"user_id"`
Prompt string `json:"prompt"`
N int `json:"n"`
Quality string `json:"quality"`
Size string `json:"size"`
Style string `json:"style"`
Power int `json:"power"`
TranslateModelId int `json:"translate_model_id"` // 提示词翻译模型ID
ModelId uint `json:"model_id"`
ModelName string `json:"model_name"`
ModelValue string `json:"model_value"`
Image []string `json:"image,omitempty"`
Id uint `json:"id"`
UserId uint `json:"user_id"`
Prompt string `json:"prompt"`
AspectRatio string `json:"aspect_ratio"`
Size string `json:"size"`
Power int `json:"power"`
TranslateModelId int `json:"translate_model_id"` // 提示词翻译模型ID
}
type SunoTask struct {
+45
View File
@@ -0,0 +1,45 @@
package types
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import "sync"
// UserLockManager 提供基于用户ID的TryLock功能,确保同一用户并发请求串行化
type UserLockManager struct {
mu sync.Mutex
locks map[uint]bool
}
func NewUserLockManager() *UserLockManager {
return &UserLockManager{mu: sync.Mutex{}, locks: make(map[uint]bool)}
}
// TryLock 尝试为指定用户加锁。若已被占用返回 false
func (m *UserLockManager) TryLock(userId uint) bool {
if userId == 0 {
return true
}
m.mu.Lock()
defer m.mu.Unlock()
if m.locks[userId] {
return false
}
m.locks[userId] = true
return true
}
// Unlock 释放指定用户的锁
func (m *UserLockManager) Unlock(userId uint) {
if userId == 0 {
return
}
m.mu.Lock()
delete(m.locks, userId)
m.mu.Unlock()
}
+1 -1
View File
@@ -4,7 +4,7 @@ build_name: runner-build
build_log: runner-build-errors.log
valid_ext: .go, .tpl, .tmpl, .html
no_rebuild_ext: .tpl, .tmpl, .html, .js, .vue
ignored: assets, tmp, web, .git, .idea, test, data
ignored: assets, tmp, web, .git, .idea, test, data, static
build_delay: 600
colors: 1
log_color_main: cyan
+3 -9
View File
@@ -24,17 +24,16 @@ require (
gorm.io/driver/mysql v1.4.7
)
require github.com/xxl-job/xxl-job-executor-go v1.2.0
require (
github.com/go-pay/gopay v1.5.101
github.com/go-rod/rod v0.116.2
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/go-tika v0.3.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/sashabaranov/go-openai v1.38.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shopspring/decimal v1.3.1
github.com/syndtr/goleveldb v1.0.0
github.com/volcengine/volcengine-go-sdk v1.1.34
golang.org/x/image v0.15.0
)
@@ -50,13 +49,9 @@ require (
github.com/gorilla/css v1.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/ysmood/fetchup v0.3.0 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
require (
@@ -69,7 +64,6 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gaukas/godicttls v0.0.3 // indirect
github.com/go-basic/ipv4 v1.0.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/goccy/go-json v0.10.2 // indirect
+8 -20
View File
@@ -46,8 +46,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-basic/ipv4 v1.0.0 h1:gjyFAa1USC1hhXTkPOwBWDPfMcUaIM+tvo1XzV9EZxs=
github.com/go-basic/ipv4 v1.0.0/go.mod h1:etLBnaxbidQfuqE6wgZQfs38nEWNmzALkxDZe4xY8Dg=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@@ -80,8 +78,6 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@@ -89,6 +85,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -102,6 +100,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -112,6 +111,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA=
@@ -261,22 +261,8 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
github.com/xxl-job/xxl-job-executor-go v1.2.0 h1:MTl2DpwrK2+hNjRRks2k7vB3oy+3onqm9OaSarneeLQ=
github.com/xxl-job/xxl-job-executor-go v1.2.0/go.mod h1:bUFhz/5Irp9zkdYk5MxhQcDDT6LlZrI8+rv5mHtQ1mo=
github.com/ysmood/fetchup v0.3.0 h1:UhYz9xnLEVn2ukSuK3KCgcznWpHMdrmbsPpllcylyu8=
github.com/ysmood/fetchup v0.3.0/go.mod h1:hbysoq65PXL0NQeNzUczNYIKpwpkwFL4LXMDEvIQq9A=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=
github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/volcengine/volcengine-go-sdk v1.1.34 h1:ha90JycCCTJNCse0UDziBgBsuX98ITOrkwYlDWcm7NI=
github.com/volcengine/volcengine-go-sdk v1.1.34/go.mod h1:oxoVo+A17kvkwPkIeIHPVLjSw7EQAm+l/Vau1YGHN+A=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@@ -408,6 +394,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+49 -35
View File
@@ -11,6 +11,7 @@ import (
"context"
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
logger2 "geekai/logger"
@@ -19,9 +20,10 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -29,7 +31,7 @@ import (
var logger = logger2.GetLogger()
const SuperManagerID = 1
const SuperUsername = "admin"
type ManagerHandler struct {
handler.BaseHandler
@@ -45,6 +47,26 @@ func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client, cap
}
}
// RegisterRoutes 注册路由
func (h *ManagerHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/")
// 公开接口,不需要授权
group.POST("login", h.Login)
group.GET("logout", h.Logout)
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("session", h.Session)
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("enable", h.Enable)
group.GET("remove", h.Remove)
group.POST("resetPass", h.ResetPass)
}
}
// Login 登录
func (h *ManagerHandler) Login(c *gin.Context) {
var data struct {
@@ -59,19 +81,6 @@ func (h *ManagerHandler) Login(c *gin.Context) {
return
}
if h.App.SysConfig.EnabledVerify {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
} else {
check = h.captcha.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
return
}
}
var manager model.AdminUser
res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager)
if res.Error != nil {
@@ -85,7 +94,7 @@ func (h *ManagerHandler) Login(c *gin.Context) {
}
// 超级管理员默认是ID:1
if manager.Id != SuperManagerID && manager.Status == false {
if manager.Username != SuperUsername && !manager.Status {
resp.ERROR(c, "该用户已被禁止登录,请联系超级管理员")
return
}
@@ -116,7 +125,7 @@ func (h *ManagerHandler) Login(c *gin.Context) {
IsSuperAdmin bool `json:"is_super_admin"`
Token string `json:"token"`
}{
IsSuperAdmin: manager.Id == 1,
IsSuperAdmin: manager.Username == SuperUsername,
Token: tokenString,
}
@@ -135,16 +144,15 @@ func (h *ManagerHandler) Logout(c *gin.Context) {
// Session 会话检测
func (h *ManagerHandler) Session(c *gin.Context) {
id := h.GetLoginUserId(c)
key := fmt.Sprintf("admin/%d", id)
if _, err := h.redis.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c)
id := h.GetAdminId(c)
if id == 0 {
resp.NotAuth(c, "当前用户已退出登录")
return
}
var manager model.AdminUser
res := h.DB.Where("id", id).First(&manager)
if res.Error != nil {
resp.NotAuth(c)
err := h.DB.Where("id", id).First(&manager).Error
if err != nil {
resp.NotAuth(c, "当前用户已退出登录")
return
}
@@ -219,12 +227,19 @@ func (h *ManagerHandler) Remove(c *gin.Context) {
return
}
if id == SuperManagerID {
var user model.AdminUser
res := h.DB.Where("id", id).First(&user)
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
}
if user.Username == SuperUsername {
resp.ERROR(c, "超级管理员不能删除")
return
}
res := h.DB.Where("id", id).Delete(&model.AdminUser{})
res = h.DB.Where("id", id).Delete(&model.AdminUser{})
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
@@ -255,8 +270,14 @@ func (h *ManagerHandler) Enable(c *gin.Context) {
// ResetPass 重置密码
func (h *ManagerHandler) ResetPass(c *gin.Context) {
id := h.GetLoginUserId(c)
if id != SuperManagerID {
id := h.GetAdminId(c)
var user model.AdminUser
res := h.DB.Where("id", id).First(&user)
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
}
if user.Username != SuperUsername {
resp.ERROR(c, "只有超级管理员能够进行该操作")
return
}
@@ -270,13 +291,6 @@ func (h *ManagerHandler) ResetPass(c *gin.Context) {
return
}
var user model.AdminUser
res := h.DB.Where("id", data.Id).First(&user)
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
}
password := utils.GenPassword(data.Password, user.Salt)
user.Password = password
res = h.DB.Updates(&user)
+15
View File
@@ -10,6 +10,7 @@ package admin
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/store/model"
@@ -30,6 +31,20 @@ func NewApiKeyHandler(app *core.AppServer, db *gorm.DB) *ApiKeyHandler {
return &ApiKeyHandler{BaseHandler: handler.BaseHandler{DB: db, App: app}}
}
// RegisterRoutes 注册路由
func (h *ApiKeyHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/apikey/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
}
}
func (h *ApiKeyHandler) Save(c *gin.Context) {
var data struct {
Id uint `json:"id"`
+24 -9
View File
@@ -10,6 +10,7 @@ package admin
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/store/model"
@@ -30,14 +31,29 @@ func NewChatAppHandler(app *core.AppServer, db *gorm.DB) *ChatAppHandler {
return &ChatAppHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatAppHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/role/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("sort", h.Sort)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
}
}
// Save 创建或者更新某个角色
func (h *ChatAppHandler) Save(c *gin.Context) {
var data vo.ChatRole
var data vo.ChatApp
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
var role model.ChatRole
var role model.ChatApp
err := utils.CopyObject(data, &role)
if err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -65,8 +81,8 @@ func (h *ChatAppHandler) Save(c *gin.Context) {
}
func (h *ChatAppHandler) List(c *gin.Context) {
var items []model.ChatRole
var roles = make([]vo.ChatRole, 0)
var items []model.ChatApp
var roles = make([]vo.ChatApp, 0)
res := h.DB.Order("sort_num ASC").Find(&items)
if res.Error != nil {
resp.ERROR(c, "No data found")
@@ -107,7 +123,7 @@ func (h *ChatAppHandler) List(c *gin.Context) {
}
for _, v := range items {
var role vo.ChatRole
var role vo.ChatApp
err := utils.CopyObject(v, &role)
if err == nil {
role.Id = v.Id
@@ -135,7 +151,7 @@ func (h *ChatAppHandler) Sort(c *gin.Context) {
}
for index, id := range data.Ids {
err := h.DB.Model(&model.ChatRole{}).Where("id = ?", id).Update("sort_num", data.Sorts[index]).Error
err := h.DB.Model(&model.ChatApp{}).Where("id = ?", id).Update("sort_num", data.Sorts[index]).Error
if err != nil {
resp.ERROR(c, err.Error())
return
@@ -157,7 +173,7 @@ func (h *ChatAppHandler) Set(c *gin.Context) {
return
}
err := h.DB.Model(&model.ChatRole{}).Where("id = ?", data.Id).Update(data.Filed, data.Value).Error
err := h.DB.Model(&model.ChatApp{}).Where("id = ?", data.Id).Update(data.Filed, data.Value).Error
if err != nil {
resp.ERROR(c, err.Error())
return
@@ -172,9 +188,8 @@ func (h *ChatAppHandler) Remove(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
res := h.DB.Where("id", id).Delete(&model.ChatRole{})
res := h.DB.Where("id", id).Delete(&model.ChatApp{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "删除失败!")
return
}
@@ -2,12 +2,14 @@ package admin
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
@@ -20,6 +22,21 @@ func NewChatAppTypeHandler(app *core.AppServer, db *gorm.DB) *ChatAppTypeHandler
return &ChatAppTypeHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatAppTypeHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/app/type/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.POST("save", h.Save)
group.GET("remove", h.Remove)
group.POST("enable", h.Enable)
group.POST("sort", h.Sort)
}
}
// Save 创建或更新App类型
func (h *ChatAppTypeHandler) Save(c *gin.Context) {
var data struct {
+28 -12
View File
@@ -9,6 +9,7 @@ package admin
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/store/model"
@@ -28,16 +29,31 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB) *ChatHandler {
return &ChatHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/chat/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.POST("list", h.List)
group.POST("message", h.Messages)
group.GET("history", h.History)
group.GET("remove", h.RemoveChat)
group.GET("message/remove", h.RemoveMessage)
}
}
type chatItemVo struct {
Username string `json:"username"`
UserId uint `json:"user_id"`
ChatId string `json:"chat_id"`
Title string `json:"title"`
Role vo.ChatRole `json:"role"`
Model string `json:"model"`
Token int `json:"token"`
CreatedAt int64 `json:"created_at"`
MsgNum int `json:"msg_num"` // 消息数量
Username string `json:"username"`
UserId uint `json:"user_id"`
ChatId string `json:"chat_id"`
Title string `json:"title"`
Role vo.ChatApp `json:"role"`
Model string `json:"model"`
Token int `json:"token"`
CreatedAt int64 `json:"created_at"`
MsgNum int `json:"msg_num"` // 消息数量
}
func (h *ChatHandler) List(c *gin.Context) {
@@ -87,7 +103,7 @@ func (h *ChatHandler) List(c *gin.Context) {
}
var messages []model.ChatMessage
var users []model.User
var roles []model.ChatRole
var roles []model.ChatApp
h.DB.Where("chat_id IN ?", chatIds).Find(&messages)
h.DB.Where("id IN ?", userIds).Find(&users)
h.DB.Where("id IN ?", roleIds).Find(&roles)
@@ -95,7 +111,7 @@ func (h *ChatHandler) List(c *gin.Context) {
tokenMap := make(map[string]int)
userMap := make(map[uint]string)
msgMap := make(map[string]int)
roleMap := make(map[uint]vo.ChatRole)
roleMap := make(map[uint]vo.ChatApp)
for _, msg := range messages {
tokenMap[msg.ChatId] += msg.Tokens
msgMap[msg.ChatId] += 1
@@ -104,7 +120,7 @@ func (h *ChatHandler) List(c *gin.Context) {
userMap[user.Id] = user.Username
}
for _, r := range roles {
var roleVo vo.ChatRole
var roleVo vo.ChatApp
err := utils.CopyObject(r, &roleVo)
if err != nil {
continue
+48
View File
@@ -8,7 +8,9 @@ package admin
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/store/model"
@@ -28,6 +30,22 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
return &ChatModelHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatModelHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/model/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("set", h.Set)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
group.POST("batch-remove", h.BatchRemove)
}
}
func (h *ChatModelHandler) Save(c *gin.Context) {
var data struct {
Id uint `json:"id"`
@@ -201,3 +219,33 @@ func (h *ChatModelHandler) Remove(c *gin.Context) {
}
resp.SUCCESS(c)
}
// BatchRemove 批量删除模型
func (h *ChatModelHandler) BatchRemove(c *gin.Context) {
var data struct {
Ids []uint `json:"ids"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
if len(data.Ids) == 0 {
resp.ERROR(c, "请选择要删除的模型")
return
}
// 执行批量删除
err := h.DB.Where("id IN ?", data.Ids).Delete(&model.ChatModel{}).Error
if err != nil {
logger.Error("批量删除模型失败:", err)
resp.ERROR(c, "批量删除失败:"+err.Error())
return
}
resp.SUCCESS(c, gin.H{
"message": fmt.Sprintf("成功删除 %d 个模型", len(data.Ids)),
"deleted_count": len(data.Ids),
})
}
+334 -159
View File
@@ -9,106 +9,378 @@ package admin
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/service"
"geekai/store"
"geekai/service/oss"
"geekai/service/payment"
"geekai/service/sms"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/host"
"gorm.io/gorm"
)
type ConfigHandler struct {
handler.BaseHandler
levelDB *store.LevelDB
licenseService *service.LicenseService
sysConfig *types.SystemConfig
alipayService *payment.AlipayService
wxpayService *payment.WxPayService
epayService *payment.EPayService
smsManager *sms.SmsManager
uploaderManager *oss.UploaderManager
smtpService *service.SmtpService
captchaService *service.CaptchaService
wxLoginService *service.WxLoginService
}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, licenseService *service.LicenseService) *ConfigHandler {
func NewConfigHandler(
app *core.AppServer,
db *gorm.DB,
sysConfig *types.SystemConfig,
alipayService *payment.AlipayService,
wxpayService *payment.WxPayService,
epayService *payment.EPayService,
smsManager *sms.SmsManager,
uploaderManager *oss.UploaderManager,
smtpService *service.SmtpService,
captchaService *service.CaptchaService,
wxLoginService *service.WxLoginService,
) *ConfigHandler {
return &ConfigHandler{
BaseHandler: handler.BaseHandler{App: app, DB: db},
levelDB: levelDB,
licenseService: licenseService,
BaseHandler: handler.BaseHandler{App: app, DB: db},
sysConfig: sysConfig,
alipayService: alipayService,
wxpayService: wxpayService,
epayService: epayService,
smsManager: smsManager,
uploaderManager: uploaderManager,
smtpService: smtpService,
captchaService: captchaService,
wxLoginService: wxLoginService,
}
}
func (h *ConfigHandler) Update(c *gin.Context) {
var data struct {
Key string `json:"key"`
Config struct {
types.SystemConfig
Content string `json:"content,omitempty"`
Updated bool `json:"updated,omitempty"`
} `json:"config"`
ConfigBak types.SystemConfig `json:"config_bak,omitempty"`
// RegisterRoutes 注册路由
func (h *ConfigHandler) RegisterRoutes() {
rg := h.App.Engine.Group("/api/admin/config")
// 需要管理员登录的接口
rg.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
rg.POST("update/base", h.UpdateBase)
rg.POST("update/power", h.UpdatePower)
rg.POST("update/notice", h.UpdateNotice)
rg.POST("update/agreement", h.UpdateAgreement)
rg.POST("update/privacy", h.UpdatePrivacy)
rg.POST("update/mark_map", h.UpdateMarkMap)
rg.POST("update/captcha", h.UpdateCaptcha)
rg.POST("update/wx_login", h.UpdateWxLogin)
rg.POST("update/payment", h.UpdatePayment)
rg.POST("update/sms", h.UpdateSms)
rg.POST("update/oss", h.UpdateOss)
rg.POST("update/smtp", h.UpdateStmp)
rg.GET("get", h.Get)
}
}
// UpdateBase 更新基础配置
func (h *ConfigHandler) UpdateBase(c *gin.Context) {
var data types.BaseConfig
if err := c.ShouldBindJSON(&data); err != nil {
logger.Errorf("Update config failed: %v", err)
resp.ERROR(c, types.InvalidArgs)
return
}
// ONLY authorized user can change the copyright
if (data.Key == "system" && data.Config.Copyright != data.ConfigBak.Copyright) && !h.licenseService.GetLicense().Configs.DeCopy {
resp.ERROR(c, "您无权修改版权信息,请先联系作者获取授权")
err := h.Update(types.ConfigKeySystem, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 如果要启用图形验证码功能,则检查是否配置了 API 服务
if data.Config.EnabledVerify && h.App.Config.ApiConfig.AppId == "" {
resp.ERROR(c, "启用验证码服务需要先配置 GeekAI 官方 API 服务 AppId 和 Token")
return
}
h.sysConfig.Base = data
value := utils.JsonEncode(&data.Config)
config := model.Config{Name: data.Key, Value: value}
res := h.DB.FirstOrCreate(&config, model.Config{Name: data.Key})
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
}
if config.Id > 0 {
config.Value = value
res := h.DB.Updates(&config)
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
}
// update config cache for AppServer
var cfg model.Config
h.DB.Where("name", data.Key).First(&cfg)
var err error
if data.Key == "system" {
err = utils.JsonDecode(cfg.Value, &h.App.SysConfig)
}
if err != nil {
resp.ERROR(c, "Failed to update config cache: "+err.Error())
return
}
logger.Infof("Update AppServer's config successfully: %v", config.Value)
}
resp.SUCCESS(c, config)
resp.SUCCESS(c, data)
}
// Get 获取指定的系统配置
func (h *ConfigHandler) Get(c *gin.Context) {
key := c.Query("key")
// UpdatePower 更新系统配置
func (h *ConfigHandler) UpdatePower(c *gin.Context) {
var data struct {
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
DailyPower int `json:"daily_power,omitempty"` // 每日签到赠送算力
InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力
SunoPower int `json:"suno_power,omitempty"` // Suno 生成歌曲消耗算力
LumaPower int `json:"luma_power,omitempty"` // Luma 生成视频消耗算力
KeLingPowers map[string]int `json:"keling_powers,omitempty"` // 可灵生成视频消耗算力
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
h.sysConfig.Base.InitPower = data.InitPower
h.sysConfig.Base.DailyPower = data.DailyPower
h.sysConfig.Base.InvitePower = data.InvitePower
h.sysConfig.Base.MjPower = data.MjPower
h.sysConfig.Base.MjActionPower = data.MjActionPower
h.sysConfig.Base.SdPower = data.SdPower
h.sysConfig.Base.SunoPower = data.SunoPower
h.sysConfig.Base.LumaPower = data.LumaPower
h.sysConfig.Base.KeLingPowers = data.KeLingPowers
err := h.Update(types.ConfigKeySystem, h.sysConfig.Base)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, h.sysConfig.Base)
}
// UpdateNotice 更新公告配置
func (h *ConfigHandler) UpdateNotice(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyNotice, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdateAgreement 更新用户协议配置
func (h *ConfigHandler) UpdateAgreement(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyAgreement, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdatePrivacy 更新隐私政策配置
func (h *ConfigHandler) UpdatePrivacy(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyPrivacy, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdateMarkMap 更新思维导图配置
func (h *ConfigHandler) UpdateMarkMap(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyMarkMap, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdateCaptcha 更新行为验证码配置
func (h *ConfigHandler) UpdateCaptcha(c *gin.Context) {
var data types.CaptchaConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyCaptcha, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
h.captchaService.UpdateConfig(data)
resp.SUCCESS(c, data)
}
// UpdatePayment 更新支付配置
func (h *ConfigHandler) UpdatePayment(c *gin.Context) {
var data types.PaymentConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyPayment, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 如果启用状态发生改变,则需要更新支付服务配置
if data.WxPay.Enabled {
err = h.wxpayService.UpdateConfig(&data.WxPay)
if err != nil {
resp.ERROR(c, err.Error())
return
}
}
if data.Epay.Enabled {
h.epayService.UpdateConfig(&data.Epay)
}
if data.Alipay.Enabled {
err = h.alipayService.UpdateConfig(&data.Alipay)
if err != nil {
resp.ERROR(c, err.Error())
return
}
}
h.sysConfig.Payment = data
resp.SUCCESS(c, data)
}
// UpdateSms 更新短信配置
func (h *ConfigHandler) UpdateSms(c *gin.Context) {
var data types.SMSConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeySms, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 更新服务配置
h.smsManager.UpdateConfig(data)
resp.SUCCESS(c, data)
}
// UpdateOss 更新 Oss 配置
func (h *ConfigHandler) UpdateOss(c *gin.Context) {
var data types.OSSConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyOss, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 更新服务配置
h.uploaderManager.UpdateConfig(data)
h.sysConfig.OSS = data
resp.SUCCESS(c, data)
}
// UpdateStmp 更新 Stmp 配置
func (h *ConfigHandler) UpdateStmp(c *gin.Context) {
var data types.SmtpConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeySmtp, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 更新服务配置
h.smtpService.UpdateConfig(&data)
h.sysConfig.SMTP = data
resp.SUCCESS(c, data)
}
// UpdateWxLogin 更新微信登录配置
func (h *ConfigHandler) UpdateWxLogin(c *gin.Context) {
var data types.WxLoginConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyWxLogin, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
h.wxLoginService.UpdateConfig(data)
h.sysConfig.WxLogin = data
resp.SUCCESS(c, data)
}
// Update 更新系统配置
func (h *ConfigHandler) Update(name string, value any) error {
var config model.Config
res := h.DB.Where("name", key).First(&config)
err := h.DB.Where("name", name).First(&config).Error
if err != nil { // 不存在则创建
config.Name = name
config.Value = utils.JsonEncode(value)
return h.DB.Create(&config).Error
} else { // 存在则更新
config.Value = utils.JsonEncode(value)
return h.DB.Updates(&config).Error
}
}
// Get 获取指定名称的系统配置
func (h *ConfigHandler) Get(c *gin.Context) {
name := c.Query("key")
var config model.Config
res := h.DB.Where("name", name).First(&config)
if res.Error != nil {
resp.ERROR(c, res.Error.Error())
return
}
var value map[string]interface{}
var value map[string]any
err := utils.JsonDecode(config.Value, &value)
if err != nil {
resp.ERROR(c, err.Error())
@@ -117,100 +389,3 @@ func (h *ConfigHandler) Get(c *gin.Context) {
resp.SUCCESS(c, value)
}
// Active 激活系统
func (h *ConfigHandler) Active(c *gin.Context) {
var data struct {
License string `json:"license"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
info, err := host.Info()
if err != nil {
resp.ERROR(c, err.Error())
return
}
err = h.licenseService.ActiveLicense(data.License, info.HostID)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c)
}
// GetLicense 获取 License 信息
func (h *ConfigHandler) GetLicense(c *gin.Context) {
license := h.licenseService.GetLicense()
resp.SUCCESS(c, license)
}
// FixData 修复数据
func (h *ConfigHandler) FixData(c *gin.Context) {
resp.ERROR(c, "当前升级版本没有数据需要修正!")
//var fixed bool
//version := "data_fix_4.1.4"
//err := h.levelDB.Get(version, &fixed)
//if err == nil || fixed {
// resp.ERROR(c, "当前版本数据修复已完成,请不要重复执行操作")
// return
//}
//tx := h.DB.Begin()
//var users []model.User
//err = tx.Find(&users).Error
//if err != nil {
// resp.ERROR(c, err.Error())
// return
//}
//for _, user := range users {
// if user.Email != "" || user.Mobile != "" {
// continue
// }
// if utils.IsValidEmail(user.Username) {
// user.Email = user.Username
// } else if utils.IsValidMobile(user.Username) {
// user.Mobile = user.Username
// }
// err = tx.Save(&user).Error
// if err != nil {
// resp.ERROR(c, err.Error())
// tx.Rollback()
// return
// }
//}
//
//var orders []model.Order
//err = h.DB.Find(&orders).Error
//if err != nil {
// resp.ERROR(c, err.Error())
// return
//}
//for _, order := range orders {
// if order.PayWay == "支付宝" {
// order.PayWay = "alipay"
// order.PayType = "alipay"
// } else if order.PayWay == "微信支付" {
// order.PayWay = "wechat"
// order.PayType = "wxpay"
// } else if order.PayWay == "hupi" {
// order.PayType = "wxpay"
// }
// err = tx.Save(&order).Error
// if err != nil {
// resp.ERROR(c, err.Error())
// tx.Rollback()
// return
// }
//}
//tx.Commit()
//err = h.levelDB.Put(version, true)
//if err != nil {
// resp.ERROR(c, err.Error())
// return
//}
//resp.SUCCESS(c)
}
+158 -36
View File
@@ -13,10 +13,11 @@ import (
"geekai/handler"
"geekai/store/model"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"gorm.io/gorm"
"time"
)
type DashboardHandler struct {
@@ -27,46 +28,161 @@ func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
return &DashboardHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *DashboardHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/dashboard/")
group.GET("stats", h.Stats)
}
// statsVo 增加 recentOrders、recentUsers 字段
// 最近订单
type OrderBrief struct {
OrderNo string `json:"order_no"`
Amount float64 `json:"amount"`
CreatedAt time.Time `json:"created_at"`
}
// 最近用户
type UserBrief struct {
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
LastActive time.Time `json:"last_active"`
}
type statsVo struct {
Users int64 `json:"users"`
Chats int64 `json:"chats"`
Tokens int `json:"tokens"`
Income float64 `json:"income"`
Chart map[string]map[string]float64 `json:"chart"`
Users int64 `json:"users"`
Chats int64 `json:"chats"`
Tokens int `json:"tokens"`
Income float64 `json:"income"`
Chart map[string]map[string]float64 `json:"chart"`
TodayUsers int64 `json:"todayUsers"`
TodayChats int64 `json:"todayChats"`
TodayTokens int `json:"todayTokens"`
TodayIncome float64 `json:"todayIncome"`
TodayOrders int64 `json:"todayOrders"`
TodayImageJobs int64 `json:"todayImageJobs"`
TodayVideoJobs int64 `json:"todayVideoJobs"`
TodayMusicJobs int64 `json:"todayMusicJobs"`
Orders int64 `json:"orders"`
ImageJobs int64 `json:"imageJobs"`
VideoJobs int64 `json:"videoJobs"`
MusicJobs int64 `json:"musicJobs"`
RecentOrders []OrderBrief `json:"recentOrders"`
RecentUsers []UserBrief `json:"recentUsers"`
}
func (h *DashboardHandler) Stats(c *gin.Context) {
stats := statsVo{}
// new users statistic
var userCount int64
now := time.Now()
zeroTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
res := h.DB.Model(&model.User{}).Where("created_at > ?", zeroTime).Count(&userCount)
if res.Error == nil {
stats.Users = userCount
// 总用户数
h.DB.Model(&model.User{}).Count(&stats.Users)
// 今日新增用户
h.DB.Model(&model.User{}).Where("created_at > ?", zeroTime).Count(&stats.TodayUsers)
// 总对话数
h.DB.Model(&model.ChatItem{}).Count(&stats.Chats)
// 今日新增对话
h.DB.Model(&model.ChatItem{}).Where("created_at > ?", zeroTime).Count(&stats.TodayChats)
// 总算力消耗
var powerLogs []model.PowerLog
h.DB.Where("mark = ?", types.PowerSub).Find(&powerLogs)
for _, item := range powerLogs {
stats.Tokens += item.Amount
}
// new chats statistic
var chatCount int64
res = h.DB.Model(&model.ChatItem{}).Where("created_at > ?", zeroTime).Count(&chatCount)
if res.Error == nil {
stats.Chats = chatCount
// 今日算力消耗
var todayPowerLogs []model.PowerLog
h.DB.Where("mark = ?", types.PowerSub).Where("created_at > ?", zeroTime).Find(&todayPowerLogs)
for _, item := range todayPowerLogs {
stats.TodayTokens += item.Amount
}
// tokens took stats
var historyMessages []model.ChatMessage
res = h.DB.Where("created_at > ?", zeroTime).Find(&historyMessages)
for _, item := range historyMessages {
stats.Tokens += item.Tokens
}
// 订单收入
var orders []model.Order
res = h.DB.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", zeroTime).Find(&orders)
for _, item := range orders {
// 总收入
var allOrders []model.Order
h.DB.Where("status = ?", types.OrderPaidSuccess).Find(&allOrders)
for _, item := range allOrders {
stats.Income += item.Amount
}
// 今日收入
var todayOrders []model.Order
h.DB.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", zeroTime).Find(&todayOrders)
for _, item := range todayOrders {
stats.TodayIncome += item.Amount
}
// 订单总数
h.DB.Model(&model.Order{}).Where("status = ?", types.OrderPaidSuccess).Count(&stats.Orders)
// 今日订单数
h.DB.Model(&model.Order{}).Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", zeroTime).Count(&stats.TodayOrders)
// 图片生成任务统计
var mjJobs, sdJobs, dallJobs, jimengImageJobs int64
h.DB.Model(&model.MidJourneyJob{}).Count(&mjJobs)
h.DB.Model(&model.SdJob{}).Count(&sdJobs)
h.DB.Model(&model.DallJob{}).Count(&dallJobs)
h.DB.Model(&model.JimengJob{}).Where("type IN ?", []string{"text_to_image", "image_to_image", "image_edit", "image_effects"}).Count(&jimengImageJobs)
stats.ImageJobs = mjJobs + sdJobs + dallJobs + jimengImageJobs
logger.Info("stats.ImageJobs", stats.ImageJobs)
// 今日图片生成任务统计
var todayMjJobs, todaySdJobs, todayDallJobs, todayJimengImageJobs int64
h.DB.Model(&model.MidJourneyJob{}).Where("created_at > ?", zeroTime).Count(&todayMjJobs)
h.DB.Model(&model.SdJob{}).Where("created_at > ?", zeroTime).Count(&todaySdJobs)
h.DB.Model(&model.DallJob{}).Where("created_at > ?", zeroTime).Count(&todayDallJobs)
h.DB.Model(&model.JimengJob{}).Where("type IN ?", []string{"text_to_image", "image_to_image", "image_edit", "image_effects"}).Where("created_at > ?", zeroTime).Count(&todayJimengImageJobs)
stats.TodayImageJobs = todayMjJobs + todaySdJobs + todayDallJobs + todayJimengImageJobs
// 视频生成任务统计
var videoJobs, jimengVideoJobs int64
h.DB.Model(&model.VideoJob{}).Count(&videoJobs)
h.DB.Model(&model.JimengJob{}).Where("type IN ?", []string{"text_to_video", "image_to_video"}).Count(&jimengVideoJobs)
stats.VideoJobs = videoJobs + jimengVideoJobs
// 今日视频生成任务统计
var todayVideoJobs, todayJimengVideoJobs int64
h.DB.Model(&model.VideoJob{}).Where("created_at > ?", zeroTime).Count(&todayVideoJobs)
h.DB.Model(&model.JimengJob{}).Where("type IN ?", []string{"text_to_video", "image_to_video"}).Where("created_at > ?", zeroTime).Count(&todayJimengVideoJobs)
stats.TodayVideoJobs = todayVideoJobs + todayJimengVideoJobs
// 音乐生成任务统计
h.DB.Model(&model.SunoJob{}).Count(&stats.MusicJobs)
// 今日音乐生成任务统计
h.DB.Model(&model.SunoJob{}).Where("created_at > ?", zeroTime).Count(&stats.TodayMusicJobs)
// recentOrders: 最近10条已支付订单
var orderList []model.Order
h.DB.Model(&model.Order{}).Where("status = ?", types.OrderPaidSuccess).Order("created_at desc").Limit(10).Find(&orderList)
for _, o := range orderList {
stats.RecentOrders = append(stats.RecentOrders, OrderBrief{
OrderNo: o.OrderNo,
Amount: o.Amount,
CreatedAt: o.CreatedAt,
})
}
// recentUsers: 最近10个注册用户
var userList []model.User
h.DB.Model(&model.User{}).Order("created_at desc").Limit(10).Find(&userList)
for _, u := range userList {
lastActive := u.UpdatedAt
if lastActive.IsZero() {
lastActive = u.CreatedAt
}
stats.RecentUsers = append(stats.RecentUsers, UserBrief{
Nickname: u.Nickname,
Avatar: u.Avatar,
LastActive: lastActive,
})
}
// 统计7天的订单的图表
startDate := now.Add(-7 * 24 * time.Hour).Format("2006-01-02")
var statsChart = make(map[string]map[string]float64)
@@ -81,23 +197,29 @@ func (h *DashboardHandler) Stats(c *gin.Context) {
// 统计用户7天增加的曲线
var users []model.User
res = h.DB.Model(&model.User{}).Where("created_at > ?", startDate).Find(&users)
if res.Error == nil {
err := h.DB.Model(&model.User{}).Where("created_at > ?", startDate).Find(&users).Error
if err == nil {
for _, item := range users {
userStatistic[item.CreatedAt.Format("2006-01-02")] += 1
}
}
// 统计7天Token 消耗
res = h.DB.Where("created_at > ?", startDate).Find(&historyMessages)
for _, item := range historyMessages {
historyMessagesStatistic[item.CreatedAt.Format("2006-01-02")] += float64(item.Tokens)
// 统计7天算力消耗
var chartPowerLogs []model.PowerLog
err = h.DB.Where("mark = ?", types.PowerSub).Where("created_at > ?", startDate).Find(&chartPowerLogs).Error
if err == nil {
for _, item := range chartPowerLogs {
historyMessagesStatistic[item.CreatedAt.Format("2006-01-02")] += float64(item.Amount)
}
}
// 统计最近7天的订单
res = h.DB.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", startDate).Find(&orders)
for _, item := range orders {
incomeStatistic[item.CreatedAt.Format("2006-01-02")], _ = decimal.NewFromFloat(incomeStatistic[item.CreatedAt.Format("2006-01-02")]).Add(decimal.NewFromFloat(item.Amount)).Float64()
var orders []model.Order
err = h.DB.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", startDate).Find(&orders).Error
if err == nil {
for _, item := range orders {
incomeStatistic[item.CreatedAt.Format("2006-01-02")], _ = decimal.NewFromFloat(incomeStatistic[item.CreatedAt.Format("2006-01-02")]).Add(decimal.NewFromFloat(item.Amount)).Float64()
}
}
statsChart["users"] = userStatistic
+16 -1
View File
@@ -9,6 +9,7 @@ package admin
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/store/model"
@@ -30,6 +31,21 @@ func NewFunctionHandler(app *core.AppServer, db *gorm.DB) *FunctionHandler {
return &FunctionHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *FunctionHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/function/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
group.GET("token", h.GenToken)
}
}
func (h *FunctionHandler) Save(c *gin.Context) {
var data vo.Function
if err := c.ShouldBindJSON(&data); err != nil {
@@ -119,7 +135,6 @@ func (h *FunctionHandler) GenToken(c *gin.Context) {
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
if err != nil {
logger.Error("error with generate token", err)
resp.ERROR(c)
return
}
+15
View File
@@ -10,6 +10,7 @@ package admin
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/service"
@@ -33,6 +34,20 @@ func NewImageHandler(app *core.AppServer, db *gorm.DB, userService *service.User
return &ImageHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, userService: userService, uploader: manager}
}
// RegisterRoutes 注册路由
func (h *ImageHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/image/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.POST("list/mj", h.MjList)
group.POST("list/sd", h.SdList)
group.POST("list/dall", h.DallList)
group.GET("remove", h.Remove)
}
}
type imageQuery struct {
Prompt string `json:"prompt"`
Username string `json:"username"`
+38 -55
View File
@@ -21,18 +21,18 @@ import (
// AdminJimengHandler 管理后台即梦AI处理器
type AdminJimengHandler struct {
handler.BaseHandler
jimengService *jimeng.Service
userService *service.UserService
uploader *oss.UploaderManager
jimengClient *jimeng.Client
userService *service.UserService
uploader *oss.UploaderManager
}
// NewAdminJimengHandler 创建管理后台即梦AI处理器
func NewAdminJimengHandler(app *core.AppServer, db *gorm.DB, jimengService *jimeng.Service, userService *service.UserService, uploader *oss.UploaderManager) *AdminJimengHandler {
func NewAdminJimengHandler(app *core.AppServer, db *gorm.DB, jimengClient *jimeng.Client, userService *service.UserService, uploader *oss.UploaderManager) *AdminJimengHandler {
return &AdminJimengHandler{
BaseHandler: handler.BaseHandler{App: app, DB: db},
jimengService: jimengService,
userService: userService,
uploader: uploader,
BaseHandler: handler.BaseHandler{App: app, DB: db},
jimengClient: jimengClient,
userService: userService,
uploader: uploader,
}
}
@@ -43,7 +43,6 @@ func (h *AdminJimengHandler) RegisterRoutes() {
rg.GET("/jobs/:id", h.JobDetail)
rg.POST("/jobs/remove", h.BatchRemove)
rg.GET("/stats", h.Stats)
rg.GET("/config", h.GetConfig)
rg.POST("/config/update", h.UpdateConfig)
}
@@ -132,7 +131,7 @@ func (h *AdminJimengHandler) BatchRemove(c *gin.Context) {
continue // 跳过不存在的
}
tx := h.DB.Begin()
if job.Status != model.JMTaskStatusSuccess && job.Power > 0 {
if job.Status != types.JMTaskStatusSuccess && job.Power > 0 {
remark := fmt.Sprintf("任务未成功,退回算力。任务ID%dErr: %s", job.Id, job.ErrMsg)
err = h.userService.IncreasePower(job.UserId, job.Power, model.PowerLog{
Type: types.PowerRefund,
@@ -173,7 +172,7 @@ func (h *AdminJimengHandler) BatchRemove(c *gin.Context) {
// Stats 获取统计信息
func (h *AdminJimengHandler) Stats(c *gin.Context) {
type StatResult struct {
Status model.JMTaskStatus `json:"status"`
Status types.JMTaskStatus `json:"status"`
Count int64 `json:"count"`
}
@@ -199,13 +198,13 @@ func (h *AdminJimengHandler) Stats(c *gin.Context) {
for _, stat := range stats {
result["totalTasks"] = result["totalTasks"].(int64) + stat.Count
switch stat.Status {
case model.JMTaskStatusInQueue:
case types.JMTaskStatusInQueue:
result["pendingTasks"] = stat.Count
case model.JMTaskStatusSuccess:
case types.JMTaskStatusSuccess:
result["completedTasks"] = stat.Count
case model.JMTaskStatusGenerating:
case types.JMTaskStatusGenerating:
result["processingTasks"] = stat.Count
case model.JMTaskStatusFailed:
case types.JMTaskStatusFailed:
result["failedTasks"] = stat.Count
}
}
@@ -213,12 +212,6 @@ func (h *AdminJimengHandler) Stats(c *gin.Context) {
resp.SUCCESS(c, result)
}
// GetConfig 获取即梦AI配置
func (h *AdminJimengHandler) GetConfig(c *gin.Context) {
jimengConfig := h.jimengService.GetConfig()
resp.SUCCESS(c, jimengConfig)
}
// UpdateConfig 更新即梦AI配置
func (h *AdminJimengHandler) UpdateConfig(c *gin.Context) {
var req types.JimengConfig
@@ -238,59 +231,49 @@ func (h *AdminJimengHandler) UpdateConfig(c *gin.Context) {
}
// 验证算力配置
if req.Power.TextToImage <= 0 {
resp.ERROR(c, "文生图算力必须大于0")
if len(req.Powers) == 0 {
resp.ERROR(c, "请至少配置一个模型的积分")
return
}
if req.Power.ImageToImage <= 0 {
resp.ERROR(c, "图生图算力必须大于0")
return
}
if req.Power.ImageEdit <= 0 {
resp.ERROR(c, "图片编辑算力必须大于0")
return
}
if req.Power.ImageEffects <= 0 {
resp.ERROR(c, "图片特效算力必须大于0")
return
}
if req.Power.TextToVideo <= 0 {
resp.ERROR(c, "文生视频算力必须大于0")
return
}
if req.Power.ImageToVideo <= 0 {
resp.ERROR(c, "图生视频算力必须大于0")
return
for key, val := range req.Powers {
if val <= 0 {
resp.ERROR(c, fmt.Sprintf("模型 %s 的积分必须大于0", key))
return
}
}
// 保存配置
tx := h.DB.Begin()
value := utils.JsonEncode(&req)
config := model.Config{Name: "jimeng", Value: value}
var exist model.Config
tx.Where("name", types.ConfigKeyJimeng).First(&exist)
err := tx.FirstOrCreate(&config, model.Config{Name: "jimeng"}).Error
if err != nil {
resp.ERROR(c, "保存配置失败: "+err.Error())
return
}
if config.Id > 0 {
config.Value = value
err = tx.Updates(&config).Error
if exist.Id > 0 {
exist.Value = value
err := tx.Updates(&exist).Error
if err != nil {
resp.ERROR(c, "更新配置失败: "+err.Error())
return
}
} else {
exist.Name = types.ConfigKeyJimeng
exist.Value = value
err := tx.Create(&exist).Error
if err != nil {
resp.ERROR(c, "创建配置失败: "+err.Error())
return
}
}
// 更新服务中的客户端配置
updateErr := h.jimengService.UpdateClientConfig(req.AccessKey, req.SecretKey)
if updateErr != nil {
resp.ERROR(c, updateErr.Error())
err := h.jimengClient.UpdateConfig(req)
if err != nil {
resp.ERROR(c, err.Error())
tx.Rollback()
return
}
tx.Commit()
h.App.SysConfig.Jimeng = req
resp.SUCCESS(c, gin.H{"message": "配置更新成功"})
}
+14
View File
@@ -10,6 +10,7 @@ package admin
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/service"
@@ -33,6 +34,19 @@ func NewMediaHandler(app *core.AppServer, db *gorm.DB, userService *service.User
return &MediaHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, userService: userService, uploader: manager}
}
// RegisterRoutes 注册路由
func (h *MediaHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/media/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.POST("suno", h.SunoList)
group.POST("videos", h.Videos)
group.GET("remove", h.Remove)
}
}
type mediaQuery struct {
Type string `json:"type"` // 任务类型 luma, keling
Prompt string `json:"prompt"`
+10
View File
@@ -27,6 +27,16 @@ func NewMenuHandler(app *core.AppServer, db *gorm.DB) *MenuHandler {
return &MenuHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *MenuHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/menu/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("enable", h.Enable)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
}
func (h *MenuHandler) Save(c *gin.Context) {
var data struct {
Id uint `json:"id"`
+333
View File
@@ -0,0 +1,333 @@
package admin
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/service/moderation"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ModerationHandler struct {
handler.BaseHandler
sysConfig *types.SystemConfig
moderationManager *moderation.ServiceManager
}
func NewModerationHandler(app *core.AppServer, db *gorm.DB, sysConfig *types.SystemConfig, moderationManager *moderation.ServiceManager) *ModerationHandler {
return &ModerationHandler{BaseHandler: handler.BaseHandler{DB: db, App: app}, sysConfig: sysConfig, moderationManager: moderationManager}
}
// RegisterRoutes 注册路由
func (h *ModerationHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/moderation/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.POST("list", h.List)
group.GET("remove", h.Remove)
group.POST("batch-remove", h.BatchRemove)
group.GET("source-list", h.GetSourceList)
group.POST("config", h.UpdateModeration)
group.POST("test", h.TestModeration)
}
}
// List 获取文本审核记录列表
func (h *ModerationHandler) List(c *gin.Context) {
var data struct {
Username string `json:"username"`
Source string `json:"source"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
session := h.DB.Session(&gorm.Session{})
// 构建查询条件
if data.Username != "" {
// 通过用户名查找用户ID
var user model.User
if err := h.DB.Where("username LIKE ?", "%"+data.Username+"%").First(&user).Error; err == nil {
session = session.Where("user_id", user.Id)
}
}
if data.Source != "" {
session = session.Where("source", data.Source)
}
if data.StartDate != "" && data.EndDate != "" {
startTime := data.StartDate + " 00:00:00"
endTime := data.EndDate + " 23:59:59"
session = session.Where("created_at >= ? AND created_at <= ?", startTime, endTime)
}
// 统计总数
var total int64
session.Model(&model.Moderation{}).Count(&total)
// 分页
page := data.Page
pageSize := data.PageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
session = session.Offset(offset).Limit(pageSize)
// 查询数据
var items []model.Moderation
err := session.Order("id DESC").Find(&items).Error
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 获取用户信息
userIds := make([]uint, 0)
for _, item := range items {
userIds = append(userIds, item.UserId)
}
var users []model.User
if len(userIds) > 0 {
h.DB.Where("id IN ?", userIds).Find(&users)
}
userMap := make(map[uint]string)
for _, user := range users {
userMap[user.Id] = user.Username
}
// 转换为响应数据
list := make([]map[string]any, 0)
for _, item := range items {
var moderation types.ModerationResult
err := utils.JsonDecode(item.Result, &moderation)
if err != nil {
continue
}
var result []string
for value, label := range types.ModerationCategories {
if moderation.Categories[value] {
result = append(result, label)
}
}
list = append(list, map[string]any{
"id": item.Id,
"user_id": item.UserId,
"username": userMap[item.UserId],
"source": item.Source,
"input": item.Input,
"output": item.Output,
"result": result,
"created_at": item.CreatedAt.Unix(),
})
}
resp.SUCCESS(c, map[string]any{
"items": list,
"total": total,
"page": page,
"page_size": pageSize,
})
}
func (h *ModerationHandler) Remove(c *gin.Context) {
id := h.GetInt(c, "id", 0)
if id <= 0 {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.DB.Where("id", id).Delete(&model.Moderation{}).Error
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c)
}
// BatchRemove 批量删除文本审核记录
func (h *ModerationHandler) BatchRemove(c *gin.Context) {
var data struct {
Ids []uint `json:"ids"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
if len(data.Ids) == 0 {
resp.ERROR(c, "请选择要删除的记录")
return
}
err := h.DB.Where("id IN ?", data.Ids).Delete(&model.Moderation{}).Error
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c)
}
// 获取 source 列表
func (h *ModerationHandler) GetSourceList(c *gin.Context) {
sources := []gin.H{
{
"id": types.ModerationSourceChat,
"name": "AI对话",
},
{
"id": types.ModerationSourceMJ,
"name": "Midjourney 绘图",
},
{
"id": types.ModerationSourceDalle,
"name": "Dalle 绘图",
},
{
"id": types.ModerationSourceSD,
"name": "StableDiffusion 绘图",
},
{
"id": types.ModerationSourceSuno,
"name": "Suno 音乐",
},
{
"id": types.ModerationSourceVideo,
"name": "视频生成",
},
{
"id": types.ModerationSourceJiMeng,
"name": "即梦AI",
},
}
resp.SUCCESS(c, sources)
}
// UpdateModeration 更新文本审查配置
func (h *ModerationHandler) UpdateModeration(c *gin.Context) {
var data types.ModerationConfig
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
var config model.Config
err := h.DB.Where("name", types.ConfigKeyModeration).First(&config).Error
if err != nil {
config.Name = types.ConfigKeyModeration
config.Value = utils.JsonEncode(data)
err = h.DB.Create(&config).Error
} else {
config.Value = utils.JsonEncode(data)
err = h.DB.Updates(&config).Error
}
if err != nil {
resp.ERROR(c, err.Error())
return
}
h.moderationManager.UpdateConfig(data)
h.sysConfig.Moderation = data
resp.SUCCESS(c, data)
}
// 测试结果类型,用于前端显示
type ModerationTestResult struct {
IsAbnormal bool `json:"isAbnormal"`
Details []ModerationTestDetail `json:"details"`
}
type ModerationTestDetail struct {
Category string `json:"category"`
Description string `json:"description"`
Confidence string `json:"confidence"`
IsCategory bool `json:"isCategory"`
}
// TestModeration 测试文本审查服务
func (h *ModerationHandler) TestModeration(c *gin.Context) {
var data struct {
Text string `json:"text"`
Service string `json:"service"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
if data.Text == "" {
resp.ERROR(c, "测试文本不能为空")
return
}
// 检查是否启用了文本审查
if !h.sysConfig.Moderation.Enable {
resp.ERROR(c, "文本审查服务未启用")
return
}
// 获取当前激活的审核服务
service := h.moderationManager.GetService()
// 执行文本审核
result, err := service.Moderate(data.Text)
if err != nil {
resp.ERROR(c, "审核服务调用失败: "+err.Error())
return
}
// 转换为前端需要的格式
testResult := ModerationTestResult{
IsAbnormal: result.Flagged,
Details: make([]ModerationTestDetail, 0),
}
// 构建详细信息
for category, description := range types.ModerationCategories {
score := result.CategoryScores[category]
isCategory := result.Categories[category]
testResult.Details = append(testResult.Details, ModerationTestDetail{
Category: category,
Description: description,
Confidence: fmt.Sprintf("%.2f", score),
IsCategory: isCategory,
})
}
resp.SUCCESS(c, testResult)
}
+16 -8
View File
@@ -29,6 +29,14 @@ func NewOrderHandler(app *core.AppServer, db *gorm.DB) *OrderHandler {
return &OrderHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *OrderHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/order/")
group.POST("list", h.List)
group.GET("remove", h.Remove)
group.GET("clear", h.Clear)
}
func (h *OrderHandler) List(c *gin.Context) {
var data struct {
OrderNo string `json:"order_no"`
@@ -68,16 +76,16 @@ func (h *OrderHandler) List(c *gin.Context) {
order.Id = item.Id
order.CreatedAt = item.CreatedAt.Unix()
order.UpdatedAt = item.UpdatedAt.Unix()
payMethod, ok := types.PayMethods[item.PayWay]
payChannel, ok := types.PayChannel[item.Channel]
if !ok {
payMethod = item.PayWay
payChannel = item.Channel
}
payName, ok := types.PayNames[item.PayType]
payWays, ok := types.PayWays[item.PayWay]
if !ok {
payName = item.PayWay
payWays = item.PayWay
}
order.PayMethod = payMethod
order.PayName = payName
order.ChannelName = payChannel
order.PayName = payWays
list = append(list, order)
} else {
logger.Error(err)
@@ -121,8 +129,8 @@ func (h *OrderHandler) Clear(c *gin.Context) {
}
deleteIds := make([]uint, 0)
for _, order := range orders {
// 只删除 15 分钟内的未支付订单
if time.Now().After(order.CreatedAt.Add(time.Minute * 15)) {
// 只删除超时的未支付订单
if time.Now().After(order.CreatedAt.Add(time.Minute * time.Duration(h.App.SysConfig.Base.OrderPayTimeout))) {
deleteIds = append(deleteIds, order.Id)
}
}
+6
View File
@@ -28,6 +28,12 @@ func NewPowerLogHandler(app *core.AppServer, db *gorm.DB) *PowerLogHandler {
return &PowerLogHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *PowerLogHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/powerLog/")
group.POST("list", h.List)
}
func (h *PowerLogHandler) List(c *gin.Context) {
var data struct {
Username string `json:"username"`
+16 -9
View File
@@ -15,9 +15,10 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"time"
)
type ProductHandler struct {
@@ -28,14 +29,22 @@ func NewProductHandler(app *core.AppServer, db *gorm.DB) *ProductHandler {
return &ProductHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ProductHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/product/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("enable", h.Enable)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
}
func (h *ProductHandler) Save(c *gin.Context) {
var data struct {
Id uint `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Discount float64 `json:"discount"`
Enabled bool `json:"enabled"`
Days int `json:"days"`
Power int `json:"power"`
CreatedAt int64 `json:"created_at"`
}
@@ -45,12 +54,10 @@ func (h *ProductHandler) Save(c *gin.Context) {
}
item := model.Product{
Name: data.Name,
Price: data.Price,
Discount: data.Discount,
Days: data.Days,
Power: data.Power,
Enabled: data.Enabled}
Name: data.Name,
Price: data.Price,
Power: data.Power,
Enabled: data.Enabled}
item.Id = data.Id
if item.Id > 0 {
item.CreatedAt = time.Unix(data.CreatedAt, 0)
+10
View File
@@ -29,6 +29,16 @@ func NewRedeemHandler(app *core.AppServer, db *gorm.DB) *RedeemHandler {
return &RedeemHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *RedeemHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/redeem/")
group.GET("list", h.List)
group.POST("create", h.Create)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
group.POST("export", h.Export)
}
func (h *RedeemHandler) List(c *gin.Context) {
page := h.GetInt(c, "page", 1)
pageSize := h.GetInt(c, "page_size", 20)
+13 -1
View File
@@ -9,6 +9,7 @@ package admin
import (
"geekai/core"
"geekai/core/middleware"
"geekai/handler"
"geekai/service/oss"
"geekai/store/model"
@@ -28,6 +29,17 @@ func NewUploadHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderMan
return &UploadHandler{BaseHandler: handler.BaseHandler{DB: db, App: app}, uploaderManager: manager}
}
// RegisterRoutes 注册路由
func (h *UploadHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/upload")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.POST("", h.Upload)
}
}
func (h *UploadHandler) Upload(c *gin.Context) {
// 判断文件大小
f, err := c.FormFile("file")
@@ -36,7 +48,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
return
}
if h.App.SysConfig.MaxFileSize > 0 && f.Size > int64(h.App.SysConfig.MaxFileSize)*1024*1024 {
if h.App.SysConfig.Base.MaxFileSize > 0 && f.Size > int64(h.App.SysConfig.Base.MaxFileSize)*1024*1024 {
resp.ERROR(c, "文件大小超过限制")
return
}
+22 -19
View File
@@ -10,30 +10,44 @@ package admin
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/handler"
"geekai/service"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type UserHandler struct {
handler.BaseHandler
licenseService *service.LicenseService
redis *redis.Client
redis *redis.Client
}
func NewUserHandler(app *core.AppServer, db *gorm.DB, licenseService *service.LicenseService, redisCli *redis.Client) *UserHandler {
return &UserHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, licenseService: licenseService, redis: redisCli}
func NewUserHandler(app *core.AppServer, db *gorm.DB, redisCli *redis.Client) *UserHandler {
return &UserHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, redis: redisCli}
}
// RegisterRoutes 注册路由
func (h *UserHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/admin/user/")
// 需要管理员授权的接口
group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.POST("save", h.Save)
group.GET("remove", h.Remove)
group.GET("loginLog", h.LoginLog)
group.GET("genLoginLink", h.GenLoginLink)
group.POST("resetPass", h.ResetPass)
}
}
// List 用户列表
@@ -98,13 +112,6 @@ func (h *UserHandler) Save(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
// 检测最大注册人数
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}
var user = model.User{}
var res *gorm.DB
var userVo vo.User
@@ -182,11 +189,7 @@ func (h *UserHandler) Save(c *gin.Context) {
ChatModels: utils.JsonEncode(data.ChatModels),
ExpiredTime: utils.Str2stamp(data.ExpiredTime),
}
if h.licenseService.GetLicense().Configs.DeCopy {
u.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
} else {
u.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
}
u.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
res = h.DB.Create(&u)
_ = utils.CopyObject(u, &userVo)
userVo.Id = u.Id
+10 -1
View File
@@ -15,9 +15,10 @@ import (
logger2 "geekai/logger"
"geekai/store/model"
"geekai/utils"
"gorm.io/gorm"
"strings"
"gorm.io/gorm"
"github.com/gin-gonic/gin"
)
@@ -69,6 +70,14 @@ func (h *BaseHandler) GetLoginUserId(c *gin.Context) uint {
return uint(utils.IntValue(utils.InterfaceToString(userId), 0))
}
func (h *BaseHandler) GetAdminId(c *gin.Context) uint {
userId, ok := c.Get(types.AdminUserID)
if !ok {
return 0
}
return uint(utils.IntValue(utils.InterfaceToString(userId), 0))
}
func (h *BaseHandler) IsLogin(c *gin.Context) bool {
return h.GetLoginUserId(c) > 0
}
+41 -4
View File
@@ -8,23 +8,45 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"geekai/core"
"geekai/core/types"
"geekai/service"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
)
// 今日头条函数实现
type CaptchaHandler struct {
App *core.AppServer
service *service.CaptchaService
}
func NewCaptchaHandler(s *service.CaptchaService) *CaptchaHandler {
return &CaptchaHandler{service: s}
func NewCaptchaHandler(app *core.AppServer, s *service.CaptchaService, sysConfig *types.SystemConfig) *CaptchaHandler {
return &CaptchaHandler{App: app, service: s}
}
// RegisterRoutes 注册路由
func (h *CaptchaHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/captcha/")
// 无需授权的接口
group.GET("get", h.Get)
group.POST("check", h.Check)
group.GET("slide/get", h.SlideGet)
group.POST("slide/check", h.SlideCheck)
group.GET("config", h.GetConfig)
}
func (h *CaptchaHandler) GetConfig(c *gin.Context) {
resp.SUCCESS(c, gin.H{"enabled": h.service.GetConfig().Enabled, "type": h.service.GetConfig().Type})
}
func (h *CaptchaHandler) Get(c *gin.Context) {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
data, err := h.service.Get()
if err != nil {
resp.ERROR(c, err.Error())
@@ -36,6 +58,11 @@ func (h *CaptchaHandler) Get(c *gin.Context) {
// Check verify the captcha data
func (h *CaptchaHandler) Check(c *gin.Context) {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
var data struct {
Key string `json:"key"`
Dots string `json:"dots"`
@@ -55,6 +82,11 @@ func (h *CaptchaHandler) Check(c *gin.Context) {
// SlideGet 获取滑动验证图片
func (h *CaptchaHandler) SlideGet(c *gin.Context) {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
data, err := h.service.SlideGet()
if err != nil {
resp.ERROR(c, err.Error())
@@ -66,6 +98,11 @@ func (h *CaptchaHandler) SlideGet(c *gin.Context) {
// SlideCheck 滑动验证结果校验
func (h *CaptchaHandler) SlideCheck(c *gin.Context) {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
var data struct {
Key string `json:"key"`
X int `json:"x"`
@@ -9,6 +9,7 @@ package handler
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/store/model"
"geekai/store/vo"
@@ -19,18 +20,31 @@ import (
"gorm.io/gorm"
)
type ChatRoleHandler struct {
type ChatAppHandler struct {
BaseHandler
}
func NewChatRoleHandler(app *core.AppServer, db *gorm.DB) *ChatRoleHandler {
return &ChatRoleHandler{BaseHandler: BaseHandler{App: app, DB: db}}
func NewChatAppHandler(app *core.AppServer, db *gorm.DB) *ChatAppHandler {
return &ChatAppHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatAppHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/app/")
group.GET("list", h.List)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.GET("list/user", h.ListByUser)
group.POST("update", h.UpdateApp)
}
}
// List 获取用户聊天应用列表
func (h *ChatRoleHandler) List(c *gin.Context) {
func (h *ChatAppHandler) List(c *gin.Context) {
tid := h.GetInt(c, "tid", 0)
var roles []model.ChatRole
var roles []model.ChatApp
session := h.DB.Where("enable", true)
if tid > 0 {
session = session.Where("tid", tid)
@@ -41,9 +55,9 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
return
}
var roleVos = make([]vo.ChatRole, 0)
var roleVos = make([]vo.ChatApp, 0)
for _, r := range roles {
var v vo.ChatRole
var v vo.ChatApp
err := utils.CopyObject(r, &v)
if err == nil {
v.Id = r.Id
@@ -54,10 +68,10 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
}
// ListByUser 获取用户添加的角色列表
func (h *ChatRoleHandler) ListByUser(c *gin.Context) {
func (h *ChatAppHandler) ListByUser(c *gin.Context) {
id := h.GetInt(c, "id", 0)
userId := h.GetLoginUserId(c)
var roles []model.ChatRole
var roles []model.ChatApp
session := h.DB.Where("enable", true)
// 如果用户没登录,则获取所有角色
if userId > 0 {
@@ -86,9 +100,9 @@ func (h *ChatRoleHandler) ListByUser(c *gin.Context) {
return
}
var roleVos = make([]vo.ChatRole, 0)
var roleVos = make([]vo.ChatApp, 0)
for _, r := range roles {
var v vo.ChatRole
var v vo.ChatApp
err := utils.CopyObject(r, &v)
if err == nil {
v.Id = r.Id
@@ -98,8 +112,8 @@ func (h *ChatRoleHandler) ListByUser(c *gin.Context) {
resp.SUCCESS(c, roleVos)
}
// UpdateRole 更新用户聊天角色
func (h *ChatRoleHandler) UpdateRole(c *gin.Context) {
// UpdateApp 更新用户聊天应用
func (h *ChatAppHandler) UpdateApp(c *gin.Context) {
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
+6
View File
@@ -19,6 +19,12 @@ func NewChatAppTypeHandler(app *core.AppServer, db *gorm.DB) *ChatAppTypeHandler
return &ChatAppTypeHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatAppTypeHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/app/type/")
group.GET("list", h.List)
}
// List 获取App类型列表
func (h *ChatAppTypeHandler) List(c *gin.Context) {
var items []model.AppType
+184 -276
View File
@@ -14,9 +14,12 @@ import (
"errors"
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/service/sora"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
@@ -39,6 +42,7 @@ import (
const (
ChatEventStart = "start"
ChatEventEnd = "end"
ChatEventComplete = "complete"
ChatEventError = "error"
ChatEventMessageDelta = "message_delta"
ChatEventTitle = "title"
@@ -54,44 +58,82 @@ type ChatInput struct {
Stream bool `json:"stream"`
Files []vo.File `json:"files"`
ChatModel model.ChatModel `json:"chat_model,omitempty"`
ChatRole model.ChatRole `json:"chat_role,omitempty"`
ChatRole model.ChatApp `json:"chat_role,omitempty"`
LastMsgId uint `json:"last_msg_id,omitempty"` // 最后的消息ID,用于重新生成答案的时候过滤上下文
}
type ChatHandler struct {
BaseHandler
redis *redis.Client
uploadManager *oss.UploaderManager
licenseService *service.LicenseService
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
userService *service.UserService
redis *redis.Client
uploadManager *oss.UploaderManager
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
userService *service.UserService
moderationManager *moderation.ServiceManager
userLocks *types.UserLockManager
soraService *sora.SoraService
}
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService, userService *service.UserService) *ChatHandler {
func NewChatHandler(app *core.AppServer,
db *gorm.DB,
redis *redis.Client,
manager *oss.UploaderManager,
userService *service.UserService,
soraService *sora.SoraService,
moderationManager *moderation.ServiceManager) *ChatHandler {
return &ChatHandler{
BaseHandler: BaseHandler{App: app, DB: db},
redis: redis,
uploadManager: manager,
licenseService: licenseService,
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
userService: userService,
BaseHandler: BaseHandler{App: app, DB: db},
redis: redis,
uploadManager: manager,
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
userService: userService,
moderationManager: moderationManager,
userLocks: types.NewUserLockManager(),
soraService: soraService,
}
}
// RegisterRoutes 注册路由
func (h *ChatHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/chat/")
// 其他接口需要用户授权
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.Any("message", h.Chat)
group.GET("list", h.List)
group.GET("detail", h.Detail)
group.POST("update", h.Update)
group.GET("remove", h.Remove)
group.GET("history", h.History)
group.GET("clear", h.Clear)
group.POST("tokens", h.Tokens)
group.GET("stop", h.StopGenerate)
group.POST("tts", h.TextToSpeech)
}
}
// Chat 处理聊天请求
func (h *ChatHandler) Chat(c *gin.Context) {
var input ChatInput
if err := c.ShouldBindJSON(&input); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
// 设置SSE响应头
c.Header("Prompt-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no")
var input ChatInput
if err := c.ShouldBindJSON(&input); err != nil {
pushMessage(c, ChatEventError, types.InvalidArgs)
c.Abort()
return
}
// 用户级并发锁,确保同一用户同时只有一个对话请求
if !h.userLocks.TryLock(input.UserId) {
pushMessage(c, ChatEventError, "您有一个对话请求正在进行中,请稍后再试或先停止当前生成!")
c.Abort()
return
}
defer h.userLocks.Unlock(input.UserId)
ctx, cancel := context.WithCancel(c.Request.Context())
defer cancel()
@@ -113,7 +155,7 @@ func (h *ChatHandler) Chat(c *gin.Context) {
}
// 验证聊天角色
var chatRole model.ChatRole
var chatRole model.ChatApp
err := h.DB.First(&chatRole, input.RoleId).Error
if err != nil || !chatRole.Enable {
pushMessage(c, ChatEventError, "当前聊天角色不存在或者未启用,请更换角色之后再发起对话!")
@@ -166,7 +208,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
}
if userVo.Power < input.ChatModel.Power {
return fmt.Errorf("您当前剩余算力 %d 已不足以支付当前模型的单次对话需要消耗的算力 %d,[立即购买](/member)。", userVo.Power, input.ChatModel.Power)
return fmt.Errorf("您的算力不足,请购买算力。")
}
if userVo.ExpiredTime > 0 && userVo.ExpiredTime <= time.Now().Unix() {
@@ -229,17 +271,24 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
// 加载聊天上下文
chatCtx := make([]any, 0)
messages := make([]any, 0)
if h.App.SysConfig.EnableContext {
if h.App.SysConfig.Base.EnableContext {
_ = utils.JsonDecode(input.ChatRole.Context, &messages)
if h.App.SysConfig.ContextDeep > 0 {
if h.App.SysConfig.Base.ContextDeep > 0 {
var historyMessages []model.ChatMessage
dbSession := h.DB.Session(&gorm.Session{}).Where("chat_id", input.ChatId)
if input.LastMsgId > 0 { // 重新生成逻辑
if input.LastMsgId > 0 { // 重新生成和编辑逻辑
var lastMessage model.ChatMessage
err = dbSession.Where("id < ?", input.LastMsgId).Where("type", types.ReplyMsg).Order("id DESC").First(&lastMessage).Error
if err != nil {
input.LastMsgId = 0
} else {
input.LastMsgId = lastMessage.Id
}
dbSession = dbSession.Where("id < ?", input.LastMsgId)
// 删除对应的聊天记录
h.DB.Debug().Where("chat_id", input.ChatId).Where("id >= ?", input.LastMsgId).Delete(&model.ChatMessage{})
h.DB.Debug().Where("chat_id", input.ChatId).Where("id > ?", input.LastMsgId).Delete(&model.ChatMessage{})
}
err = dbSession.Limit(h.App.SysConfig.ContextDeep).Order("id DESC").Find(&historyMessages).Error
err = dbSession.Limit(h.App.SysConfig.Base.ContextDeep).Order("id DESC").Find(&historyMessages).Error
if err == nil {
for i := len(historyMessages) - 1; i >= 0; i-- {
msg := historyMessages[i]
@@ -267,7 +316,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
}
// 上下文的深度超出了模型的最大上下文深度
if len(chatCtx) >= h.App.SysConfig.ContextDeep {
if len(chatCtx) >= h.App.SysConfig.Base.ContextDeep {
break
}
@@ -277,6 +326,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
}
reqMgs := make([]any, 0)
// 添加引导提示词,防止模型生成违规内容
if h.App.SysConfig.Moderation.EnableGuide {
reqMgs = append(reqMgs, map[string]any{
"role": "system",
"content": h.App.SysConfig.Moderation.GuidePrompt,
})
}
for i := len(chatCtx) - 1; i >= 0; i-- {
reqMgs = append(reqMgs, chatCtx[i])
}
@@ -287,24 +344,22 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
for _, file := range input.Files {
logger.Debugf("detected file: %+v", file.URL)
// 处理图片
if isImageURL(file.URL) {
if isImageURL(file.URL) || isVideoURL(file.URL) {
imgList = append(imgList, gin.H{
"type": "image_url",
"image_url": gin.H{
"url": file.URL,
},
})
} else {
// 如果不是逆向模型,则提取文件内容
modelValue := input.ChatModel.Value
if !(strings.Contains(modelValue, "-all") || strings.HasPrefix(modelValue, "gpt-4-gizmo") || strings.HasPrefix(modelValue, "claude")) {
content, err := utils.ReadFileContent(file.URL, h.App.Config.TikaHost)
if err != nil {
logger.Error("error with read file: ", err)
continue
} else {
fileContents = append(fileContents, fmt.Sprintf("%s 文件内容:%s", file.Name, content))
}
} else if isTextURL(file.URL) {
// 处理文件,提取文件内容
content, err := utils.ReadFileContent(file.URL, h.App.Config.TikaHost)
if err != nil {
logger.Error("error with read file: ", err)
continue
} else {
fileContents = append(fileContents, fmt.Sprintf("%s 文件内容:%s", file.Name, content))
logger.Debugf("fileContents: %s", fileContents)
}
}
}
@@ -320,16 +375,16 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
}
if len(imgList) > 0 {
imgList = append(imgList, map[string]interface{}{
imgList = append(imgList, map[string]any{
"type": "text",
"text": input.Prompt,
})
req.Messages = append(reqMgs, map[string]interface{}{
req.Messages = append(reqMgs, map[string]any{
"role": "user",
"content": imgList,
})
} else {
req.Messages = append(reqMgs, map[string]interface{}{
req.Messages = append(reqMgs, map[string]any{
"role": "user",
"content": finalPrompt,
})
@@ -357,23 +412,43 @@ func isImageURL(url string) bool {
".svg": true,
".ico": true,
}
return validImageExts[ext]
}
if !validImageExts[ext] {
// 判断是个链接是否是文本链接
func isTextURL(url string) bool {
// 检查是否是有效的URL
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
return false
}
// 发送HEAD请求检查Content-Type
client := &http.Client{
Timeout: 5 * time.Second,
// 检查文件扩展名
ext := strings.ToLower(path.Ext(url))
validTextExts := map[string]bool{
".txt": true,
".doc": true,
".docx": true,
".pdf": true,
}
resp, err := client.Head(url)
if err != nil {
return validTextExts[ext]
}
// 判断是个链接是否是视频
func isVideoURL(url string) bool {
// 检查是否是有效的URL
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
return false
}
defer resp.Body.Close()
contentType := resp.Header.Get("Content-Type")
return strings.HasPrefix(contentType, "image/")
// 检查文件扩展名
ext := strings.ToLower(path.Ext(url))
validVideoExts := map[string]bool{
".mp4": true,
".avi": true,
".mov": true,
".wmv": true,
}
return validVideoExts[ext]
}
// Tokens 统计 token 数量
@@ -445,7 +520,7 @@ func (h *ChatHandler) StopGenerate(c *gin.Context) {
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, input ChatInput, apiKey *model.ApiKey) (*http.Response, error) {
// if the chat model bind a KEY, use it directly
if input.ChatModel.KeyId > 0 {
h.DB.Where("id", input.ChatModel.KeyId).Find(apiKey)
h.DB.Where("id", input.ChatModel.KeyId).Where("enabled", true).Find(apiKey)
} else { // use the last unused key
h.DB.Where("type", "chat").Where("enabled", true).Order("last_used_at ASC").First(apiKey)
}
@@ -454,11 +529,6 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, input
return nil, errors.New("no available key, please import key")
}
// ONLY allow apiURL in blank list
err := h.licenseService.IsValidApiURL(apiKey.ApiURL)
if err != nil {
return nil, err
}
logger.Debugf("对话请求消息体:%+v", req)
var apiURL string
p, _ := url.Parse(apiKey.ApiURL)
@@ -516,6 +586,7 @@ func (h *ChatHandler) subUserPower(userVo vo.User, input ChatInput, promptTokens
}
func (h *ChatHandler) saveChatHistory(
c *gin.Context,
req types.ApiRequest,
usage Usage,
message types.Message,
@@ -524,6 +595,34 @@ func (h *ChatHandler) saveChatHistory(
promptCreatedAt time.Time,
replyCreatedAt time.Time) {
// 文本审核
if h.App.SysConfig.Moderation.Enable {
moderationResult, err := h.moderationManager.GetService().Moderate(usage.Content)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
logger.Debugf("moderationResult: %+v", moderationResult)
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: userVo.Id,
Source: types.ModerationSourceChat,
Input: usage.Prompt,
Output: usage.Content,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
pushMessage(c, ChatEventError, "很抱歉,内容触发敏感词预警,AI 无法回答!!!")
// 更新用户算力
if input.ChatModel.Power > 0 {
h.subUserPower(userVo, input, 0, 0)
}
return
}
}
// 追加聊天记录
// for prompt
var promptTokens, replyTokens, totalTokens int
@@ -564,6 +663,16 @@ func (h *ChatHandler) saveChatHistory(
replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
totalTokens = replyTokens + getTotalTokens(req)
}
// 如果是 Sora 相关模型,则下载视频
files := make([]vo.File, 0)
if strings.HasPrefix(req.Model, "sora") {
video, err := h.soraService.DownloadVideoURL(message.Content)
if err == nil {
files = append(files, *video)
}
}
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: input.ChatId,
@@ -572,7 +681,7 @@ func (h *ChatHandler) saveChatHistory(
Icon: input.ChatRole.Icon,
Content: utils.JsonEncode(vo.MsgContent{
Text: message.Content,
Files: input.Files,
Files: files,
}),
Tokens: replyTokens,
TotalTokens: totalTokens,
@@ -586,6 +695,23 @@ func (h *ChatHandler) saveChatHistory(
logger.Error("failed to save reply history message: ", err)
}
// 发送完整聊天记录给前端
var messageVo vo.ChatMessage
err = utils.CopyObject(historyReplyMsg, &messageVo)
if err == nil {
// 解析内容
var content vo.MsgContent
err = utils.JsonDecode(historyReplyMsg.Content, &content)
if err != nil {
content.Text = historyReplyMsg.Content
}
content.Files = files
messageVo.Content = content
messageVo.CreatedAt = historyReplyMsg.CreatedAt.Unix()
messageVo.UpdatedAt = historyReplyMsg.UpdatedAt.Unix()
pushMessage(c, ChatEventComplete, messageVo)
}
// 更新用户算力
if input.ChatModel.Power > 0 {
h.subUserPower(userVo, input, promptTokens, replyTokens)
@@ -710,221 +836,3 @@ func (h *ChatHandler) TextToSpeech(c *gin.Context) {
logger.Error("写入音频数据到响应失败:", err)
}
}
// // OPenAI 消息发送实现
// func (h *ChatHandler) sendOpenAiMessage(
// req types.ApiRequest,
// userVo vo.User,
// ctx context.Context,
// session *types.ChatSession,
// role model.ChatRole,
// prompt string,
// c *gin.Context) error {
// promptCreatedAt := time.Now() // 记录提问时间
// start := time.Now()
// var apiKey = model.ApiKey{}
// response, err := h.doRequest(ctx, req, session, &apiKey)
// logger.Info("HTTP请求完成,耗时:", time.Since(start))
// if err != nil {
// if strings.Contains(err.Error(), "context canceled") {
// return fmt.Errorf("用户取消了请求:%s", prompt)
// } else if strings.Contains(err.Error(), "no available key") {
// return errors.New("抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
// }
// return err
// } else {
// defer response.Body.Close()
// }
// if response.StatusCode != 200 {
// body, _ := io.ReadAll(response.Body)
// return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, string(body))
// }
// contentType := response.Header.Get("Prompt-Type")
// if strings.Contains(contentType, "text/event-stream") {
// replyCreatedAt := time.Now() // 记录回复时间
// // 循环读取 Chunk 消息
// var message = types.Message{Role: "assistant"}
// var contents = make([]string, 0)
// var function model.Function
// var toolCall = false
// var arguments = make([]string, 0)
// var reasoning = false
// pushMessage(c, ChatEventStart, "开始响应")
// scanner := bufio.NewScanner(response.Body)
// for scanner.Scan() {
// line := scanner.Text()
// if !strings.Contains(line, "data:") || len(line) < 30 {
// continue
// }
// var responseBody = types.ApiResponse{}
// err = json.Unmarshal([]byte(line[6:]), &responseBody)
// if err != nil { // 数据解析出错
// return errors.New(line)
// }
// if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
// continue
// }
// if responseBody.Choices[0].Delta.Prompt == nil &&
// responseBody.Choices[0].Delta.ToolCalls == nil &&
// responseBody.Choices[0].Delta.ReasoningContent == "" {
// continue
// }
// if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
// pushMessage(c, ChatEventError, "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。")
// break
// }
// var tool types.ToolCall
// if len(responseBody.Choices[0].Delta.ToolCalls) > 0 {
// tool = responseBody.Choices[0].Delta.ToolCalls[0]
// if toolCall && tool.Function.Name == "" {
// arguments = append(arguments, tool.Function.Arguments)
// continue
// }
// }
// // 兼容 Function Call
// fun := responseBody.Choices[0].Delta.FunctionCall
// if fun.Name != "" {
// tool = *new(types.ToolCall)
// tool.Function.Name = fun.Name
// } else if toolCall {
// arguments = append(arguments, fun.Arguments)
// continue
// }
// if !utils.IsEmptyValue(tool) {
// res := h.DB.Where("name = ?", tool.Function.Name).First(&function)
// if res.Error == nil {
// toolCall = true
// callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label)
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": callMsg,
// })
// contents = append(contents, callMsg)
// }
// continue
// }
// if responseBody.Choices[0].FinishReason == "tool_calls" ||
// responseBody.Choices[0].FinishReason == "function_call" { // 函数调用完毕
// break
// }
// // output stopped
// if responseBody.Choices[0].FinishReason != "" {
// break // 输出完成或者输出中断了
// } else { // 正常输出结果
// // 兼容思考过程
// if responseBody.Choices[0].Delta.ReasoningContent != "" {
// reasoningContent := responseBody.Choices[0].Delta.ReasoningContent
// if !reasoning {
// reasoningContent = fmt.Sprintf("<think>%s", reasoningContent)
// reasoning = true
// }
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": reasoningContent,
// })
// contents = append(contents, reasoningContent)
// } else if responseBody.Choices[0].Delta.Prompt != "" {
// finalContent := responseBody.Choices[0].Delta.Prompt
// if reasoning {
// finalContent = fmt.Sprintf("</think>%s", responseBody.Choices[0].Delta.Prompt)
// reasoning = false
// }
// contents = append(contents, utils.InterfaceToString(finalContent))
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": finalContent,
// })
// }
// }
// } // end for
// if err := scanner.Err(); err != nil {
// if strings.Contains(err.Error(), "context canceled") {
// logger.Info("用户取消了请求:", prompt)
// } else {
// logger.Error("信息读取出错:", err)
// }
// }
// if toolCall { // 调用函数完成任务
// params := make(map[string]any)
// _ = utils.JsonDecode(strings.Join(arguments, ""), &params)
// logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
// params["user_id"] = userVo.Id
// var apiRes types.BizVo
// r, err := req2.C().R().SetHeader("Body-Type", "application/json").
// SetHeader("Authorization", function.Token).
// SetBody(params).Post(function.Action)
// errMsg := ""
// if err != nil {
// errMsg = err.Error()
// } else {
// all, _ := io.ReadAll(r.Body)
// err = json.Unmarshal(all, &apiRes)
// if err != nil {
// errMsg = err.Error()
// } else if apiRes.Code != types.Success {
// errMsg = apiRes.Message
// }
// }
// if errMsg != "" {
// errMsg = "调用函数工具出错:" + errMsg
// contents = append(contents, errMsg)
// } else {
// errMsg = utils.InterfaceToString(apiRes.Data)
// contents = append(contents, errMsg)
// }
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": errMsg,
// })
// }
// // 消息发送成功
// if len(contents) > 0 {
// usage := Usage{
// Prompt: prompt,
// Prompt: strings.Join(contents, ""),
// PromptTokens: 0,
// CompletionTokens: 0,
// TotalTokens: 0,
// }
// message.Prompt = usage.Prompt
// h.saveChatHistory(req, usage, message, session, role, userVo, promptCreatedAt, replyCreatedAt)
// }
// } else {
// var respVo OpenAIResVo
// body, err := io.ReadAll(response.Body)
// if err != nil {
// return fmt.Errorf("读取响应失败:%v", body)
// }
// err = json.Unmarshal(body, &respVo)
// if err != nil {
// return fmt.Errorf("解析响应失败:%v", body)
// }
// content := respVo.Choices[0].Message.Prompt
// if strings.HasPrefix(req.Model, "o1-") {
// content = fmt.Sprintf("AI思考结束,耗时:%d 秒。\n%s", time.Now().Unix()-session.Start, respVo.Choices[0].Message.Prompt)
// }
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": content,
// })
// respVo.Usage.Prompt = prompt
// respVo.Usage.Prompt = content
// h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, session, role, userVo, promptCreatedAt, time.Now())
// }
// return nil
// }
+3 -3
View File
@@ -42,9 +42,9 @@ func (h *ChatHandler) List(c *gin.Context) {
modelValues = append(modelValues, chat.Model)
}
var roles []model.ChatRole
var roles []model.ChatApp
var models []model.ChatModel
roleMap := make(map[uint]model.ChatRole)
roleMap := make(map[uint]model.ChatApp)
modelMap := make(map[string]model.ChatModel)
h.DB.Where("id IN ?", roleIds).Find(&roles)
h.DB.Where("value IN ?", modelValues).Find(&models)
@@ -205,7 +205,7 @@ func (h *ChatHandler) Detail(c *gin.Context) {
}
// 填充角色名称
var role model.ChatRole
var role model.ChatApp
res = h.DB.Where("id", chatItem.RoleId).First(&role)
if res.Error != nil {
resp.ERROR(c, "Role not found")
+6
View File
@@ -26,6 +26,12 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
return &ChatModelHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ChatModelHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/model/")
group.GET("list", h.List)
}
// List 模型列表
func (h *ChatModelHandler) List(c *gin.Context) {
var items []model.ChatModel
+10 -2
View File
@@ -212,6 +212,14 @@ func (h *ChatHandler) sendOpenAiMessage(
} else {
errMsg = utils.InterfaceToString(apiRes.Data)
contents = append(contents, errMsg)
// 扣减算力
if function.Power > 0 {
h.userService.DecreasePower(userVo.Id, function.Power, model.PowerLog{
Type: types.PowerConsume,
Model: function.Name,
Remark: fmt.Sprintf("函数调用,%s(%s)", function.Label, function.Name),
})
}
}
pushMessage(c, "text", errMsg)
}
@@ -226,7 +234,7 @@ func (h *ChatHandler) sendOpenAiMessage(
TotalTokens: 0,
}
message.Content = usage.Content
h.saveChatHistory(req, usage, message, input, userVo, promptCreatedAt, replyCreatedAt)
h.saveChatHistory(c, req, usage, message, input, userVo, promptCreatedAt, replyCreatedAt)
}
} else { // 非流式输出
var respVo OpenAIResVo
@@ -242,7 +250,7 @@ func (h *ChatHandler) sendOpenAiMessage(
pushMessage(c, "text", content)
respVo.Usage.Prompt = input.Prompt
respVo.Usage.Content = content
h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, input, userVo, promptCreatedAt, time.Now())
h.saveChatHistory(c, req, respVo.Usage, respVo.Choices[0].Message, input, userVo, promptCreatedAt, time.Now())
}
return nil
+10 -10
View File
@@ -9,7 +9,6 @@ package handler
import (
"geekai/core"
"geekai/service"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
@@ -20,11 +19,18 @@ import (
type ConfigHandler struct {
BaseHandler
licenseService *service.LicenseService
}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, licenseService *service.LicenseService) *ConfigHandler {
return &ConfigHandler{BaseHandler: BaseHandler{App: app, DB: db}, licenseService: licenseService}
func NewConfigHandler(app *core.AppServer, db *gorm.DB) *ConfigHandler {
return &ConfigHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ConfigHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/config/")
// 无需授权的接口
group.GET("get", h.Get)
}
// Get 获取指定的系统配置
@@ -46,9 +52,3 @@ func (h *ConfigHandler) Get(c *gin.Context) {
resp.SUCCESS(c, value)
}
// License 获取 License 配置
func (h *ConfigHandler) License(c *gin.Context) {
license := h.licenseService.GetLicense()
resp.SUCCESS(c, license.Configs)
}
+58 -12
View File
@@ -10,9 +10,11 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/dalle"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/store/model"
"geekai/store/vo"
@@ -25,16 +27,18 @@ import (
type DallJobHandler struct {
BaseHandler
dallService *dalle.Service
uploader *oss.UploaderManager
userService *service.UserService
dallService *dalle.Service
uploader *oss.UploaderManager
userService *service.UserService
moderationManager *moderation.ServiceManager
}
func NewDallJobHandler(app *core.AppServer, db *gorm.DB, service *dalle.Service, manager *oss.UploaderManager, userService *service.UserService) *DallJobHandler {
func NewDallJobHandler(app *core.AppServer, db *gorm.DB, service *dalle.Service, manager *oss.UploaderManager, userService *service.UserService, moderationManager *moderation.ServiceManager) *DallJobHandler {
return &DallJobHandler{
dallService: service,
uploader: manager,
userService: userService,
dallService: service,
uploader: manager,
userService: userService,
moderationManager: moderationManager,
BaseHandler: BaseHandler{
App: app,
DB: db,
@@ -42,6 +46,24 @@ func NewDallJobHandler(app *core.AppServer, db *gorm.DB, service *dalle.Service,
}
}
// RegisterRoutes 注册路由
func (h *DallJobHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/dall/")
// 公开接口,不需要授权
group.GET("imgWall", h.ImgWall)
group.GET("models", h.GetModels)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("image", h.Image)
group.GET("jobs", h.JobList)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
}
}
// Image 创建一个绘画任务
func (h *DallJobHandler) Image(c *gin.Context) {
var data types.DallTask
@@ -50,6 +72,29 @@ func (h *DallJobHandler) Image(c *gin.Context) {
return
}
// 文本审核
if h.App.SysConfig.Moderation.Enable {
moderationResult, err := h.moderationManager.GetService().Moderate(data.Prompt)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: h.GetLoginUserId(c),
Source: types.ModerationSourceDalle,
Input: data.Prompt,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
resp.ERROR(c, "当前创作内容包含敏感词,提示词未通过文本审核,请重新输入!")
return
}
}
var chatModel model.ChatModel
if res := h.DB.Where("id = ?", data.ModelId).First(&chatModel); res.Error != nil {
resp.ERROR(c, "模型不存在")
@@ -72,12 +117,13 @@ func (h *DallJobHandler) Image(c *gin.Context) {
task := types.DallTask{
UserId: uint(userId),
ModelId: chatModel.Id,
ModelName: chatModel.Value,
ModelName: chatModel.Name,
ModelValue: chatModel.Value,
Image: data.Image,
Prompt: data.Prompt,
Quality: data.Quality,
AspectRatio: data.AspectRatio,
Size: data.Size,
Style: data.Style,
TranslateModelId: h.App.SysConfig.AssistantModelId,
TranslateModelId: h.App.SysConfig.Base.AssistantModelId,
Power: chatModel.Power,
}
job := model.DallJob{
@@ -98,7 +144,7 @@ func (h *DallJobHandler) Image(c *gin.Context) {
// 扣减算力
err = h.userService.DecreasePower(user.Id, chatModel.Power, model.PowerLog{
Type: types.PowerConsume,
Model: chatModel.Value,
Model: chatModel.Name,
Remark: fmt.Sprintf("绘画提示词:%s", utils.CutWords(task.Prompt, 10)),
})
if err != nil {
+32 -102
View File
@@ -13,7 +13,6 @@ import (
"geekai/core"
"geekai/core/types"
"geekai/service"
"geekai/service/crawler"
"geekai/service/dalle"
"geekai/service/oss"
"geekai/store/model"
@@ -31,7 +30,6 @@ import (
type FunctionHandler struct {
BaseHandler
config types.ApiConfig
uploadManager *oss.UploaderManager
dallService *dalle.Service
userService *service.UserService
@@ -49,13 +47,23 @@ func NewFunctionHandler(
App: server,
DB: db,
},
config: config.ApiConfig,
uploadManager: manager,
dallService: dallService,
userService: userService,
}
}
// RegisterRoutes 注册路由
func (h *FunctionHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/function/")
group.GET("list", h.List)
// 需要用户授权的接口
group.POST("weibo", h.WeiBo)
group.POST("zaobao", h.ZaoBao)
group.POST("dalle3", h.Dall3)
}
type resVo struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
@@ -107,16 +115,10 @@ func (h *FunctionHandler) WeiBo(c *gin.Context) {
return
}
if h.config.Token == "" {
resp.ERROR(c, "无效的 API Token")
return
}
url := fmt.Sprintf("%s/api/weibo/fetch", h.config.ApiURL)
url := fmt.Sprintf("%s/api/weibo/fetch", types.GeekAPIURL)
var res resVo
r, err := req.C().R().
SetHeader("AppId", h.config.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.config.Token)).
SetHeader("Authorization", "Bearer geekai-plus").
SetSuccessResult(&res).Get(url)
if err != nil {
resp.ERROR(c, fmt.Sprintf("%v", err))
@@ -146,16 +148,10 @@ func (h *FunctionHandler) ZaoBao(c *gin.Context) {
return
}
if h.config.Token == "" {
resp.ERROR(c, "无效的 API Token")
return
}
url := fmt.Sprintf("%s/api/zaobao/fetch", h.config.ApiURL)
url := fmt.Sprintf("%s/api/zaobao/fetch", types.GeekAPIURL)
var res resVo
r, err := req.C().R().
SetHeader("AppId", h.config.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.config.Token)).
SetHeader("Authorization", "Bearer geekai-plus").
SetSuccessResult(&res).Get(url)
if err != nil {
resp.ERROR(c, fmt.Sprintf("%v", err))
@@ -193,16 +189,23 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
return
}
var chatModel model.ChatModel
res := h.DB.Where("type = ?", "img").Where("enabled", true).First(&chatModel)
if res.Error != nil {
resp.ERROR(c, "没有找到可用的AI绘图模型!")
return
}
logger.Debugf("绘画参数:%+v", params)
var user model.User
res := h.DB.Where("id = ?", params["user_id"]).First(&user)
res = h.DB.Where("id = ?", params["user_id"]).First(&user)
if res.Error != nil {
resp.ERROR(c, "当前用户不存在!")
return
}
if user.Power < h.App.SysConfig.DallPower {
resp.ERROR(c, "创建 DALL-E 绘图任务失败,算力不足")
if user.Power < chatModel.Power {
resp.ERROR(c, "创建绘图任务失败,算力不足")
return
}
@@ -211,24 +214,21 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
task := types.DallTask{
UserId: user.Id,
Prompt: prompt,
ModelId: 0,
ModelName: "dall-e-3",
TranslateModelId: h.App.SysConfig.AssistantModelId,
N: 1,
Quality: "standard",
Size: "1024x1024",
Style: "vivid",
Power: h.App.SysConfig.DallPower,
ModelId: chatModel.Id,
ModelValue: chatModel.Value,
ModelName: chatModel.Name,
TranslateModelId: h.App.SysConfig.Base.AssistantModelId,
Power: chatModel.Power,
}
job := model.DallJob{
UserId: user.Id,
Prompt: prompt,
Power: h.App.SysConfig.DallPower,
Power: chatModel.Power,
TaskInfo: utils.JsonEncode(task),
}
err := h.DB.Create(&job).Error
if err != nil {
resp.ERROR(c, "创建 DALL-E 绘图任务失败:"+err.Error())
resp.ERROR(c, "创建绘图任务失败:"+err.Error())
return
}
@@ -253,76 +253,6 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
resp.SUCCESS(c, content)
}
// 实现一个联网搜索的函数工具,采用爬虫实现
func (h *FunctionHandler) WebSearch(c *gin.Context) {
if err := h.checkAuth(c); err != nil {
resp.ERROR(c, err.Error())
return
}
var params map[string]interface{}
if err := c.ShouldBindJSON(&params); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
// 从参数中获取搜索关键词
keyword, ok := params["keyword"].(string)
if !ok || keyword == "" {
resp.ERROR(c, "搜索关键词不能为空")
return
}
// 从参数中获取最大页数,默认为1页
maxPages := 1
if pages, ok := params["max_pages"].(float64); ok {
maxPages = int(pages)
}
// 获取用户ID
userID, ok := params["user_id"].(float64)
if !ok {
resp.ERROR(c, "用户ID不能为空")
return
}
// 查询用户信息
var user model.User
res := h.DB.Where("id = ?", int(userID)).First(&user)
if res.Error != nil {
resp.ERROR(c, "用户不存在")
return
}
// 检查用户算力是否足够
searchPower := 1 // 每次搜索消耗1点算力
if user.Power < searchPower {
resp.ERROR(c, "算力不足,无法执行网络搜索")
return
}
// 执行网络搜索
searchResults, err := crawler.SearchWeb(keyword, maxPages)
if err != nil {
resp.ERROR(c, fmt.Sprintf("搜索失败: %v", err))
return
}
// 扣减用户算力
err = h.userService.DecreasePower(user.Id, searchPower, model.PowerLog{
Type: types.PowerConsume,
Model: "web_search",
Remark: fmt.Sprintf("网络搜索:%s", utils.CutWords(keyword, 10)),
})
if err != nil {
resp.ERROR(c, "扣减算力失败:"+err.Error())
return
}
// 返回搜索结果
resp.SUCCESS(c, searchResults)
}
// List 获取所有的工具函数列表
func (h *FunctionHandler) List(c *gin.Context) {
var items []model.Function
+134 -14
View File
@@ -8,14 +8,18 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"strings"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strings"
)
// InviteHandler 用户邀请
@@ -27,6 +31,23 @@ func NewInviteHandler(app *core.AppServer, db *gorm.DB) *InviteHandler {
return &InviteHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *InviteHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/invite/")
// 公开接口,不需要授权
group.GET("hits", h.Hits)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.GET("code", h.Code)
group.GET("list", h.List)
group.GET("stats", h.Stats)
group.GET("rules", h.Rules)
}
}
// Code 获取当前用户邀请码
func (h *InviteHandler) Code(c *gin.Context) {
userId := h.GetLoginUserId(c)
@@ -65,21 +86,34 @@ func (h *InviteHandler) List(c *gin.Context) {
var total int64
session.Model(&model.InviteLog{}).Count(&total)
var items []model.InviteLog
var list = make([]vo.InviteLog, 0)
offset := (page - 1) * pageSize
res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items)
if res.Error == nil {
for _, item := range items {
var v vo.InviteLog
err := utils.CopyObject(item, &v)
if err == nil {
v.Id = item.Id
v.CreatedAt = item.CreatedAt.Unix()
list = append(list, v)
} else {
logger.Error(err)
}
err := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items).Error
if err != nil {
resp.ERROR(c, err.Error())
return
}
userIds := make([]uint, 0)
for _, item := range items {
userIds = append(userIds, item.UserId)
}
userMap := make(map[uint]model.User)
var users []model.User
h.DB.Model(&model.User{}).Where("id IN (?)", userIds).Find(&users)
for _, user := range users {
userMap[user.Id] = user
}
var list = make([]vo.InviteLog, 0)
for _, item := range items {
var v vo.InviteLog
err := utils.CopyObject(item, &v)
if err != nil {
continue
}
v.CreatedAt = item.CreatedAt.Unix()
v.Avatar = userMap[item.UserId].Avatar
list = append(list, v)
}
resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list))
}
@@ -90,3 +124,89 @@ func (h *InviteHandler) Hits(c *gin.Context) {
h.DB.Model(&model.InviteCode{}).Where("code = ?", code).UpdateColumn("hits", gorm.Expr("hits + ?", 1))
resp.SUCCESS(c)
}
// Stats 获取邀请统计
func (h *InviteHandler) Stats(c *gin.Context) {
userId := h.GetLoginUserId(c)
// 获取邀请码
var inviteCode model.InviteCode
res := h.DB.Where("user_id = ?", userId).First(&inviteCode)
if res.Error != nil {
resp.ERROR(c, "邀请码不存在")
return
}
// 统计累计邀请数
var totalInvite int64
h.DB.Model(&model.InviteLog{}).Where("inviter_id = ?", userId).Count(&totalInvite)
// 统计今日邀请数
today := time.Now().Format("2006-01-02")
var todayInvite int64
h.DB.Model(&model.InviteLog{}).Where("inviter_id = ? AND DATE(created_at) = ?", userId, today).Count(&todayInvite)
// 获取系统配置中的邀请奖励
var config model.Config
var invitePower int = 200 // 默认值
if h.DB.Where("name = ?", "system").First(&config).Error == nil {
var configMap map[string]any
if utils.JsonDecode(config.Value, &configMap) == nil {
if power, ok := configMap["invite_power"].(float64); ok {
invitePower = int(power)
}
}
}
// 计算获得奖励总数
rewardTotal := int(totalInvite) * invitePower
// 构建邀请链接
inviteLink := fmt.Sprintf("%s/register?invite=%s", h.App.Config.StaticUrl, inviteCode.Code)
stats := vo.InviteStats{
InviteCount: int(totalInvite),
RewardTotal: rewardTotal,
TodayInvite: int(todayInvite),
InviteCode: inviteCode.Code,
InviteLink: inviteLink,
}
resp.SUCCESS(c, stats)
}
// Rules 获取奖励规则
func (h *InviteHandler) Rules(c *gin.Context) {
// 获取系统配置中的邀请奖励
var config model.Config
var invitePower int = 200 // 默认值
if h.DB.Where("name = ?", "system").First(&config).Error == nil {
var configMap map[string]interface{}
if utils.JsonDecode(config.Value, &configMap) == nil {
if power, ok := configMap["invite_power"].(float64); ok {
invitePower = int(power)
}
}
}
rules := []vo.RewardRule{
{
Id: 1,
Title: "好友注册",
Desc: "好友通过邀请链接成功注册",
Icon: "icon-user-fill",
Color: "#1989fa",
Reward: invitePower,
},
{
Id: 2,
Title: "好友首次充值",
Desc: "好友首次充值任意金额",
Icon: "icon-money",
Color: "#07c160",
Reward: invitePower * 2, // 假设首次充值奖励是注册奖励的2倍
},
}
resp.SUCCESS(c, rules)
}
+147 -233
View File
@@ -1,12 +1,14 @@
package handler
import (
"errors"
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/jimeng"
"geekai/service/moderation"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
@@ -19,198 +21,83 @@ import (
// JimengHandler 即梦AI处理器
type JimengHandler struct {
BaseHandler
jimengService *jimeng.Service
userService *service.UserService
jimengService *jimeng.Service
userService *service.UserService
moderationManager *moderation.ServiceManager
}
// NewJimengHandler 创建即梦AI处理器
func NewJimengHandler(app *core.AppServer, jimengService *jimeng.Service, db *gorm.DB, userService *service.UserService) *JimengHandler {
func NewJimengHandler(app *core.AppServer, jimengService *jimeng.Service, db *gorm.DB, userService *service.UserService, moderationManager *moderation.ServiceManager) *JimengHandler {
return &JimengHandler{
BaseHandler: BaseHandler{App: app, DB: db},
jimengService: jimengService,
userService: userService,
BaseHandler: BaseHandler{App: app, DB: db},
jimengService: jimengService,
userService: userService,
moderationManager: moderationManager,
}
}
// RegisterRoutes 注册路由,新增统一任务接口
func (h *JimengHandler) RegisterRoutes() {
rg := h.App.Engine.Group("/api/jimeng")
rg.POST("task", h.CreateTask) // 只保留统一任务接口
rg.GET("power-config", h.GetPowerConfig) // 新增算力配置接口
rg.POST("jobs", h.Jobs)
rg.GET("remove", h.Remove)
rg.GET("retry", h.Retry)
}
group := h.App.Engine.Group("/api/jimeng/")
group.GET("power-config", h.GetPowerConfig)
// JimengTaskRequest 统一任务请求结构体
// 支持所有生图和生成视频类型
type JimengTaskRequest struct {
TaskType string `json:"task_type" binding:"required"`
Prompt string `json:"prompt"`
ImageInput string `json:"image_input"`
ImageUrls []string `json:"image_urls"`
BinaryDataBase64 []string `json:"binary_data_base64"`
Scale float64 `json:"scale"`
Width int `json:"width"`
Height int `json:"height"`
Gpen float64 `json:"gpen"`
Skin float64 `json:"skin"`
SkinUnifi float64 `json:"skin_unifi"`
GenMode string `json:"gen_mode"`
Seed int64 `json:"seed"`
UsePreLLM bool `json:"use_pre_llm"`
TemplateId string `json:"template_id"`
AspectRatio string `json:"aspect_ratio"`
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("task", h.CreateTask)
group.POST("jobs", h.Jobs)
group.GET("remove", h.Remove)
group.GET("retry", h.Retry)
}
}
// CreateTask 统一任务创建接口
func (h *JimengHandler) CreateTask(c *gin.Context) {
var req JimengTaskRequest
var req types.JimengTaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
// 新增:除图像特效外,其他任务类型必须有提示词
if req.TaskType != "image_effects" && req.Prompt == "" {
resp.ERROR(c, "提示词不能为空")
// 文本审核
if h.App.SysConfig.Moderation.Enable && req.Prompt != "" {
moderationResult, err := h.moderationManager.GetService().Moderate(req.Prompt)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: h.GetLoginUserId(c),
Source: types.ModerationSourceJiMeng,
Input: req.Prompt,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
resp.ERROR(c, "当前创作内容包含敏感词,请重新输入!")
return
}
}
if req.Prompt == "" && len(req.ImageUrls) == 0 {
resp.ERROR(c, "提示词和图片不能同时为空")
return
}
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
return
}
if req.Width == 0 {
req.Width = 1328
}
if req.Height == 0 {
req.Height = 1328
}
if req.Seed == 0 {
req.Seed = -1
}
var powerCost int
var taskType model.JMTaskType
var params map[string]any
var reqKey string
var modelName string
switch req.TaskType {
case "text_to_image":
powerCost = h.getPowerFromConfig(model.JMTaskTypeTextToImage)
taskType = model.JMTaskTypeTextToImage
reqKey = jimeng.ReqKeyTextToImage
modelName = "即梦文生图"
if req.Scale == 0 {
req.Scale = 2.5
}
params = map[string]any{
"seed": req.Seed,
"scale": req.Scale,
"width": req.Width,
"height": req.Height,
"use_pre_llm": req.UsePreLLM,
}
case "image_to_image":
powerCost = h.getPowerFromConfig(model.JMTaskTypeImageToImage)
taskType = model.JMTaskTypeImageToImage
reqKey = jimeng.ReqKeyImageToImagePortrait
modelName = "即梦图生图"
if req.Gpen == 0 {
req.Gpen = 0.4
}
if req.Skin == 0 {
req.Skin = 0.3
}
if req.GenMode == "" {
if req.Prompt != "" {
req.GenMode = jimeng.GenModeCreative
} else {
req.GenMode = jimeng.GenModeReference
}
}
params = map[string]any{
"image_input": req.ImageInput,
"width": req.Width,
"height": req.Height,
"gpen": req.Gpen,
"skin": req.Skin,
"skin_unifi": req.SkinUnifi,
"gen_mode": req.GenMode,
"seed": req.Seed,
}
case "image_edit":
powerCost = h.getPowerFromConfig(model.JMTaskTypeImageEdit)
taskType = model.JMTaskTypeImageEdit
reqKey = jimeng.ReqKeyImageEdit
modelName = "即梦图像编辑"
if req.Scale == 0 {
req.Scale = 0.5
}
params = map[string]any{
"seed": req.Seed,
"scale": req.Scale,
}
if len(req.ImageUrls) > 0 {
params["image_urls"] = req.ImageUrls
}
if len(req.BinaryDataBase64) > 0 {
params["binary_data_base64"] = req.BinaryDataBase64
}
case "image_effects":
powerCost = h.getPowerFromConfig(model.JMTaskTypeImageEffects)
taskType = model.JMTaskTypeImageEffects
reqKey = jimeng.ReqKeyImageEffects
modelName = "即梦图像特效"
if req.Width == 0 {
req.Width = 1328
}
if req.Height == 0 {
req.Height = 1328
}
params = map[string]any{
"image_input1": req.ImageInput,
"template_id": req.TemplateId,
"width": req.Width,
"height": req.Height,
}
case "text_to_video":
powerCost = h.getPowerFromConfig(model.JMTaskTypeTextToVideo)
taskType = model.JMTaskTypeTextToVideo
reqKey = jimeng.ReqKeyTextToVideo
modelName = "即梦文生视频"
if req.Seed == 0 {
req.Seed = -1
}
if req.AspectRatio == "" {
req.AspectRatio = jimeng.AspectRatio16_9
}
params = map[string]any{
"seed": req.Seed,
"aspect_ratio": req.AspectRatio,
}
case "image_to_video":
powerCost = h.getPowerFromConfig(model.JMTaskTypeImageToVideo)
taskType = model.JMTaskTypeImageToVideo
reqKey = jimeng.ReqKeyImageToVideo
modelName = "即梦图生视频"
if req.Seed == 0 {
req.Seed = -1
}
params = map[string]any{
"seed": req.Seed,
"aspect_ratio": req.AspectRatio,
}
if len(req.ImageUrls) > 0 {
params["image_urls"] = req.ImageUrls
}
if len(req.BinaryDataBase64) > 0 {
params["binary_data_base64"] = req.BinaryDataBase64
}
default:
resp.ERROR(c, "不支持的任务类型")
// 获取算力消耗
powerCost, err := h.getTaskPower(req)
if err != nil {
resp.ERROR(c, "计算任务消耗积分失败: "+err.Error())
return
}
@@ -218,16 +105,9 @@ func (h *JimengHandler) CreateTask(c *gin.Context) {
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
return
}
req.Power = powerCost
taskReq := &jimeng.CreateTaskRequest{
Type: taskType,
Prompt: req.Prompt,
Params: params,
ReqKey: reqKey,
Power: powerCost,
}
job, err := h.jimengService.CreateTask(user.Id, taskReq)
job, err := h.jimengService.CreateTask(user.Id, &req)
if err != nil {
logger.Errorf("create jimeng task failed: %v", err)
resp.ERROR(c, "创建任务失败")
@@ -236,11 +116,42 @@ func (h *JimengHandler) CreateTask(c *gin.Context) {
h.userService.DecreasePower(user.Id, powerCost, model.PowerLog{
Type: types.PowerConsume,
Model: "jimeng",
Remark: fmt.Sprintf("%s,任务ID%d", modelName, job.Id),
Model: job.ReqKey,
Remark: h.getTaskRemark(req, job.Id),
})
resp.SUCCESS(c, job)
resp.SUCCESS(c)
}
func (h *JimengHandler) getTaskRemark(req types.JimengTaskRequest, jobId uint) string {
remark := fmt.Sprintf("即梦任务%s,任务ID%d", req.ReqKey, jobId)
perUnit, ok := h.App.SysConfig.Jimeng.Powers[req.ReqKey]
if !ok || perUnit <= 0 {
return remark // Fallback if power not found or invalid
}
switch req.TaskType {
case types.JMTaskTypeImage:
remark = fmt.Sprintf("即梦图片生成,任务ID%d%d积分/张", jobId, perUnit)
case types.JMTaskTypeVideo:
seconds := 0
if perUnit > 0 {
seconds = req.Power / perUnit
}
remark = fmt.Sprintf("即梦视频生成,任务ID%d%d积分/秒, %d秒", jobId, perUnit, seconds)
case types.JMTaskTypeVirtualHuman:
seconds := 0
if perUnit > 0 {
seconds = req.Power / perUnit
}
remark = fmt.Sprintf("即梦数字人视频生成,任务ID%d,%d积分/秒, %d秒", jobId, perUnit, seconds)
case types.JMTaskTypeActionTransfer:
seconds := 0
if perUnit > 0 {
seconds = req.Power / perUnit
}
remark = fmt.Sprintf("即梦视频动作迁移,任务ID%d%d积分/秒, %d秒", jobId, perUnit, seconds)
}
return remark
}
// Jobs 获取任务列表
@@ -265,17 +176,13 @@ func (h *JimengHandler) Jobs(c *gin.Context) {
switch req.Filter {
case "image":
query = query.Where("type IN (?)", []model.JMTaskType{
model.JMTaskTypeTextToImage,
model.JMTaskTypeImageToImage,
model.JMTaskTypeImageEdit,
model.JMTaskTypeImageEffects,
})
query = query.Where("type = ?", types.JMTaskTypeImage)
case "video":
query = query.Where("type IN (?)", []model.JMTaskType{
model.JMTaskTypeTextToVideo,
model.JMTaskTypeImageToVideo,
})
query = query.Where("type = ?", types.JMTaskTypeVideo)
case "virtual_human":
query = query.Where("type = ?", types.JMTaskTypeVirtualHuman)
case "action_transfer":
query = query.Where("type = ?", types.JMTaskTypeActionTransfer)
}
if len(req.Ids) > 0 {
@@ -333,31 +240,19 @@ func (h *JimengHandler) Remove(c *gin.Context) {
resp.ERROR(c, "无权限操作")
return
}
if job.Status != model.JMTaskStatusFailed {
resp.ERROR(c, "只有失败的任务能删除")
// 正在运行中的任务能删除
if job.Status == types.JMTaskStatusGenerating || job.Status == types.JMTaskStatusInQueue {
resp.ERROR(c, "正在运行中的任务不能删除,否则无法退回算力")
return
}
tx := h.DB.Begin()
if err := tx.Where("id = ? AND user_id = ?", jobId, user.Id).Delete(&model.JimengJob{}).Error; err != nil {
if err := h.DB.Where("id = ? AND user_id = ?", jobId, user.Id).Delete(&model.JimengJob{}).Error; err != nil {
logger.Errorf("delete jimeng job failed: %v", err)
resp.ERROR(c, "删除任务失败")
return
}
// 退回算力
err = h.userService.IncreasePower(user.Id, job.Power, model.PowerLog{
Type: types.PowerRefund,
Model: "jimeng",
Remark: fmt.Sprintf("删除任务,退回%d算力", job.Power),
})
if err != nil {
resp.ERROR(c, "退回算力失败")
tx.Rollback()
return
}
tx.Commit()
resp.SUCCESS(c, gin.H{})
}
@@ -384,13 +279,13 @@ func (h *JimengHandler) Retry(c *gin.Context) {
}
// 只有失败的任务才能重试
if job.Status != model.JMTaskStatusFailed {
if job.Status != types.JMTaskStatusFailed {
resp.ERROR(c, "只有失败的任务才能重试")
return
}
// 重置任务状态
if err := h.jimengService.UpdateJobStatus(uint(jobId), model.JMTaskStatusInQueue, ""); err != nil {
if err := h.jimengService.UpdateJobStatus(uint(jobId), types.JMTaskStatusInQueue, ""); err != nil {
logger.Errorf("reset job status failed: %v", err)
resp.ERROR(c, "重置任务状态失败")
return
@@ -406,37 +301,56 @@ func (h *JimengHandler) Retry(c *gin.Context) {
resp.SUCCESS(c, gin.H{"message": "重试任务已提交"})
}
// getPowerFromConfig 从配置中获取指定类型的算力消耗
func (h *JimengHandler) getPowerFromConfig(taskType model.JMTaskType) int {
config := h.jimengService.GetConfig()
switch taskType {
case model.JMTaskTypeTextToImage:
return config.Power.TextToImage
case model.JMTaskTypeImageToImage:
return config.Power.ImageToImage
case model.JMTaskTypeImageEdit:
return config.Power.ImageEdit
case model.JMTaskTypeImageEffects:
return config.Power.ImageEffects
case model.JMTaskTypeTextToVideo:
return config.Power.TextToVideo
case model.JMTaskTypeImageToVideo:
return config.Power.ImageToVideo
func (h *JimengHandler) getTaskPower(req types.JimengTaskRequest) (int, error) {
logger.Debugf("getTaskPower req: %+v", req)
config := h.App.SysConfig.Jimeng
basePower, ok := config.Powers[req.ReqKey]
if !ok || basePower <= 0 {
return 0, errors.New("未配置模型积分或配置不合法")
}
switch req.TaskType {
case types.JMTaskTypeImage:
return basePower, nil
case types.JMTaskTypeVideo:
if req.Duration == 0 {
return 0, errors.New("视频时长不能为0")
}
return basePower * req.Duration, nil
case types.JMTaskTypeVirtualHuman:
if req.AudioURL == "" {
return 0, errors.New("音频URL不能为空")
}
audioDuration, err := utils.AudioDurationFromURL(req.AudioURL)
if err != nil {
return 0, err
}
seconds := int(audioDuration.Seconds())
if seconds <= 0 {
return 0, errors.New("音频时长无效")
}
return basePower * seconds, nil
case types.JMTaskTypeActionTransfer:
if req.VideoURL == "" {
return 0, errors.New("视频URL不能为空")
}
videoDuration, err := utils.VideoDurationMP4FromURL(req.VideoURL)
if err != nil {
return 0, err
}
seconds := int(videoDuration.Seconds())
if seconds <= 0 {
return 0, errors.New("视频时长无效")
}
return basePower * seconds, nil
default:
return 10
return 0, errors.New("任务类型不支持")
}
}
// GetPowerConfig 获取即梦各任务类型算力消耗配置
func (h *JimengHandler) GetPowerConfig(c *gin.Context) {
config := h.jimengService.GetConfig()
config := h.App.SysConfig.Jimeng
resp.SUCCESS(c, gin.H{
"text_to_image": config.Power.TextToImage,
"image_to_image": config.Power.ImageToImage,
"image_edit": config.Power.ImageEdit,
"image_effects": config.Power.ImageEffects,
"text_to_video": config.Power.TextToVideo,
"image_to_video": config.Power.ImageToVideo,
"powers": config.Powers,
})
}
+12
View File
@@ -10,6 +10,7 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/store/model"
@@ -35,6 +36,17 @@ func NewMarkMapHandler(app *core.AppServer, db *gorm.DB, userService *service.Us
}
}
// RegisterRoutes 注册路由
func (h *MarkMapHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/markMap/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("gen", h.Generate)
}
}
// Generate 生成思维导图
func (h *MarkMapHandler) Generate(c *gin.Context) {
var data struct {
+26 -1
View File
@@ -13,6 +13,7 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
@@ -25,6 +26,13 @@ func NewMenuHandler(app *core.AppServer, db *gorm.DB) *MenuHandler {
return &MenuHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *MenuHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/menu/")
group.GET("list", h.List)
group.GET("list/all", h.ListAll)
}
// List 数据列表
func (h *MenuHandler) List(c *gin.Context) {
index := h.GetBool(c, "index")
@@ -33,7 +41,7 @@ func (h *MenuHandler) List(c *gin.Context) {
session := h.DB.Session(&gorm.Session{})
session = session.Where("enabled", true)
if index {
session = session.Where("id IN ?", h.App.SysConfig.IndexNavs)
session = session.Where("id IN ?", h.App.SysConfig.Base.IndexNavs)
}
res := session.Order("sort_num ASC").Find(&items)
if res.Error == nil {
@@ -47,3 +55,20 @@ func (h *MenuHandler) List(c *gin.Context) {
}
resp.SUCCESS(c, list)
}
// ListAll 获取所有的菜单列表
func (h *MenuHandler) ListAll(c *gin.Context) {
var items []model.Menu
var list = make([]vo.Menu, 0)
res := h.DB.Order("sort_num ASC").Find(&items)
if res.Error == nil {
for _, item := range items {
var product vo.Menu
err := utils.CopyObject(item, &product)
if err == nil {
list = append(list, product)
}
}
}
resp.SUCCESS(c, list)
}
+63 -17
View File
@@ -10,9 +10,11 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/mj"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/store/model"
"geekai/store/vo"
@@ -27,18 +29,20 @@ import (
type MidJourneyHandler struct {
BaseHandler
mjService *mj.Service
snowflake *service.Snowflake
uploader *oss.UploaderManager
userService *service.UserService
mjService *mj.Service
snowflake *service.Snowflake
uploader *oss.UploaderManager
userService *service.UserService
moderationManager *moderation.ServiceManager
}
func NewMidJourneyHandler(app *core.AppServer, db *gorm.DB, snowflake *service.Snowflake, service *mj.Service, manager *oss.UploaderManager, userService *service.UserService) *MidJourneyHandler {
func NewMidJourneyHandler(app *core.AppServer, db *gorm.DB, snowflake *service.Snowflake, service *mj.Service, manager *oss.UploaderManager, userService *service.UserService, moderationManager *moderation.ServiceManager) *MidJourneyHandler {
return &MidJourneyHandler{
snowflake: snowflake,
mjService: service,
uploader: manager,
userService: userService,
snowflake: snowflake,
mjService: service,
uploader: manager,
userService: userService,
moderationManager: moderationManager,
BaseHandler: BaseHandler{
App: app,
DB: db,
@@ -46,6 +50,25 @@ func NewMidJourneyHandler(app *core.AppServer, db *gorm.DB, snowflake *service.S
}
}
// RegisterRoutes 注册路由
func (h *MidJourneyHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/mj/")
// 公开接口,不需要授权
group.GET("imgWall", h.ImgWall)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("image", h.Image)
group.POST("upscale", h.Upscale)
group.POST("variation", h.Variation)
group.GET("jobs", h.JobList)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
}
}
func (h *MidJourneyHandler) preCheck(c *gin.Context) bool {
user, err := h.GetLoginUser(c)
if err != nil {
@@ -53,7 +76,7 @@ func (h *MidJourneyHandler) preCheck(c *gin.Context) bool {
return false
}
if user.Power < h.App.SysConfig.MjPower {
if user.Power < h.App.SysConfig.Base.MjPower {
resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画!")
return false
}
@@ -90,6 +113,29 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
return
}
// 文本审核
if h.App.SysConfig.Moderation.Enable {
moderationResult, err := h.moderationManager.GetService().Moderate(data.Prompt)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: h.GetLoginUserId(c),
Source: types.ModerationSourceMJ,
Input: data.Prompt,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
resp.ERROR(c, "当前创作内容包含敏感词,请重新输入!")
return
}
}
var params = ""
if data.Rate != "" && !strings.Contains(params, "--ar") {
params += " --ar " + data.Rate
@@ -159,8 +205,8 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
Params: params,
UserId: userId,
ImgArr: data.ImgArr,
Mode: h.App.SysConfig.MjMode,
TranslateModelId: h.App.SysConfig.AssistantModelId,
Mode: h.App.SysConfig.Base.MjMode,
TranslateModelId: h.App.SysConfig.Base.AssistantModelId,
}
job := model.MidJourneyJob{
Type: data.TaskType,
@@ -169,7 +215,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
TaskInfo: utils.JsonEncode(task),
Progress: 0,
Prompt: fmt.Sprintf("%s %s", data.Prompt, params),
Power: h.App.SysConfig.MjPower,
Power: h.App.SysConfig.Base.MjPower,
CreatedAt: time.Now(),
}
opt := "绘图"
@@ -232,7 +278,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
Index: data.Index,
MessageId: data.MessageId,
MessageHash: data.MessageHash,
Mode: h.App.SysConfig.MjMode,
Mode: h.App.SysConfig.Base.MjMode,
}
job := model.MidJourneyJob{
Type: types.TaskUpscale.String(),
@@ -240,7 +286,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
TaskId: taskId,
TaskInfo: utils.JsonEncode(task),
Progress: 0,
Power: h.App.SysConfig.MjActionPower,
Power: h.App.SysConfig.Base.MjActionPower,
CreatedAt: time.Now(),
}
if res := h.DB.Create(&job); res.Error != nil || res.RowsAffected == 0 {
@@ -287,7 +333,7 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
ChannelId: data.ChannelId,
MessageId: data.MessageId,
MessageHash: data.MessageHash,
Mode: h.App.SysConfig.MjMode,
Mode: h.App.SysConfig.Base.MjMode,
}
job := model.MidJourneyJob{
Type: types.TaskVariation.String(),
@@ -296,7 +342,7 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
TaskId: taskId,
TaskInfo: utils.JsonEncode(task),
Progress: 0,
Power: h.App.SysConfig.MjActionPower,
Power: h.App.SysConfig.Base.MjActionPower,
CreatedAt: time.Now(),
}
if res := h.DB.Create(&job); res.Error != nil || res.RowsAffected == 0 {
+17
View File
@@ -9,6 +9,7 @@ package handler
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service/oss"
"geekai/store/model"
@@ -32,6 +33,22 @@ func NewNetHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManage
return &NetHandler{BaseHandler: BaseHandler{App: app, DB: db}, uploaderManager: manager}
}
// RegisterRoutes 注册路由
func (h *NetHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/upload")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("", h.Upload)
group.POST("list", h.List)
group.GET("remove", h.Remove)
}
// 公开接口,不需要授权
h.App.Engine.GET("/api/download", h.Download)
}
func (h *NetHandler) Upload(c *gin.Context) {
file, err := h.uploaderManager.GetUploadHandler().PutFile(c, "file")
if err != nil {
+22 -18
View File
@@ -9,12 +9,12 @@ package handler
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -28,6 +28,18 @@ func NewOrderHandler(app *core.AppServer, db *gorm.DB) *OrderHandler {
return &OrderHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *OrderHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/order/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
group.GET("query", h.Query)
}
}
// List 订单列表
func (h *OrderHandler) List(c *gin.Context) {
page := h.GetInt(c, "page", 1)
@@ -48,20 +60,21 @@ func (h *OrderHandler) List(c *gin.Context) {
order.Id = item.Id
order.CreatedAt = item.CreatedAt.Unix()
order.UpdatedAt = item.UpdatedAt.Unix()
payMethod, ok := types.PayMethods[item.PayWay]
payChannel, ok := types.PayChannel[item.Channel]
if !ok {
payMethod = item.PayWay
payChannel = item.PayWay
}
payName, ok := types.PayNames[item.PayType]
payWays, ok := types.PayWays[item.PayWay]
if !ok {
payName = item.PayWay
payWays = item.PayWay
}
order.PayMethod = payMethod
order.PayName = payName
order.ChannelName = payChannel
order.PayName = payWays
list = append(list, order)
} else {
logger.Error(err)
}
}
}
resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list))
@@ -82,17 +95,8 @@ func (h *OrderHandler) Query(c *gin.Context) {
return
}
counter := 0
for {
time.Sleep(time.Second)
var item model.Order
h.DB.Where("order_no = ?", orderNo).First(&item)
if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
order.Status = item.Status
break
}
counter++
}
var item model.Order
h.DB.Where("order_no = ?", orderNo).First(&item)
resp.SUCCESS(c, gin.H{"status": order.Status})
}
+274 -240
View File
@@ -11,6 +11,7 @@ import (
"embed"
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/payment"
@@ -33,52 +34,149 @@ type PayWay struct {
// PaymentHandler 支付服务回调 handler
type PaymentHandler struct {
BaseHandler
alipayService *payment.AlipayService
huPiPayService *payment.HuPiPayService
geekPayService *payment.GeekPayService
wechatPayService *payment.WechatPayService
snowflake *service.Snowflake
userService *service.UserService
fs embed.FS
lock sync.Mutex
signKey string // 用来签名的随机秘钥
alipayService *payment.AlipayService
epayService *payment.EPayService
wxpayService *payment.WxPayService
snowflake *service.Snowflake
userService *service.UserService
fs embed.FS
lock sync.Mutex
config *types.PaymentConfig
}
func NewPaymentHandler(
server *core.AppServer,
alipayService *payment.AlipayService,
huPiPayService *payment.HuPiPayService,
geekPayService *payment.GeekPayService,
wechatPayService *payment.WechatPayService,
geekPayService *payment.EPayService,
wxpayService *payment.WxPayService,
db *gorm.DB,
userService *service.UserService,
snowflake *service.Snowflake,
fs embed.FS) *PaymentHandler {
fs embed.FS,
sysConfig *types.SystemConfig) *PaymentHandler {
return &PaymentHandler{
alipayService: alipayService,
huPiPayService: huPiPayService,
geekPayService: geekPayService,
wechatPayService: wechatPayService,
snowflake: snowflake,
userService: userService,
fs: fs,
lock: sync.Mutex{},
alipayService: alipayService,
epayService: geekPayService,
wxpayService: wxpayService,
snowflake: snowflake,
userService: userService,
fs: fs,
lock: sync.Mutex{},
BaseHandler: BaseHandler{
App: server,
DB: db,
},
signKey: utils.RandString(32),
config: &sysConfig.Payment,
}
}
func (h *PaymentHandler) Pay(c *gin.Context) {
// RegisterRoutes 注册路由
func (h *PaymentHandler) RegisterRoutes() {
rg := h.App.Engine.Group("/api/payment/")
// 支付回调接口(公开)
rg.POST("notify/alipay", h.AlipayNotify)
rg.GET("notify/epay", h.EPayNotify)
rg.POST("notify/wxpay", h.WxpayNotify)
// 需要用户登录的接口
rg.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
rg.POST("create", h.CreateOrder)
}
}
func (h *PaymentHandler) StartSyncOrders() {
go func() {
for {
err := h.SyncOrders()
if err != nil {
logger.Error(err)
}
time.Sleep(time.Second * 5)
}
}()
}
// SyncOrders 同步订单状态
func (h *PaymentHandler) SyncOrders() error {
defer func() {
if err := recover(); err != nil {
logger.Errorf("同步订单状态发生异常: %v", err)
}
}()
var orders []model.Order
err := h.DB.Where("status", types.OrderNotPaid).Where("checked", false).Find(&orders).Error
if err != nil {
return err
}
for _, order := range orders {
time.Sleep(time.Second * 1)
//超时15分钟的订单,直接标记为已关闭
if time.Now().After(order.CreatedAt.Add(time.Minute * 5)) {
h.DB.Model(&model.Order{}).Where("id", order.Id).Update("checked", true)
logger.Errorf("订单超时:%v", order)
continue
}
// 查询订单状态
var res payment.OrderInfo
switch order.Channel {
case payment.PayChannelEpay:
res, err = h.epayService.Query(order.OrderNo)
if err != nil {
logger.Errorf("error with query order info: %v", err)
continue
}
// 微信支付
case payment.PayChannelWX:
res, err = h.wxpayService.Query(order.OrderNo)
logger.Debugf("微信支付订单状态:%+v", res)
if err != nil {
logger.Errorf("error with query order info: %v", err)
continue
}
case payment.PayChannelAL:
res, err = h.alipayService.Query(order.OrderNo)
logger.Debugf("支付宝支付订单状态:%+v", res)
if err != nil {
logger.Errorf("error with query order info: %v", err)
continue
}
}
// 订单已关闭
if res.Closed() {
h.DB.Model(&model.Order{}).Where("id", order.Id).Updates(map[string]any{
"checked": true,
"status": types.OrderPaidFailed,
})
logger.Errorf("订单已关闭:%v", order)
continue
}
// 订单未支付,不处理,继续轮询
if !res.Success() {
continue
}
// 订单支付成功
err = h.paySuccess(res)
if err != nil {
logger.Errorf("error with deal order: %v", err)
continue
}
}
return nil
}
func (h *PaymentHandler) CreateOrder(c *gin.Context) {
var data struct {
PayWay string `json:"pay_way"`
PayType string `json:"pay_type"`
ProductId int `json:"product_id"`
UserId int `json:"user_id"`
Device string `json:"device"`
Host string `json:"host"`
PayWay string `json:"pay_way,omitempty"` // 支付方式:支付宝,微信
Pid int `json:"pid,omitempty"`
Device string `json:"device,omitempty"`
Domain string `json:"domain,omitempty"` // 支付回调域名
Channel string `json:"channel,omitempty"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -86,7 +184,7 @@ func (h *PaymentHandler) Pay(c *gin.Context) {
}
var product model.Product
err := h.DB.Where("id", data.ProductId).First(&product).Error
err := h.DB.Where("id", data.Pid).First(&product).Error
if err != nil {
resp.ERROR(c, "Product not found")
return
@@ -97,136 +195,118 @@ func (h *PaymentHandler) Pay(c *gin.Context) {
resp.ERROR(c, "error with generate trade no: "+err.Error())
return
}
userId := h.GetLoginUserId(c)
var user model.User
err = h.DB.Where("id", data.UserId).First(&user).Error
err = h.DB.Where("id", userId).First(&user).Error
if err != nil {
resp.NotAuth(c)
return
}
amount := product.Discount
var payURL, returnURL, notifyURL string
amount := product.Price
var payURL, notifyURL string
switch data.PayWay {
case "alipay":
if h.App.Config.AlipayConfig.NotifyURL != "" { // 用于本地调试支付
notifyURL = h.App.Config.AlipayConfig.NotifyURL
} else {
notifyURL = fmt.Sprintf("%s/api/payment/notify/alipay", data.Host)
}
if h.App.Config.AlipayConfig.ReturnURL != "" { // 用于本地调试支付
returnURL = h.App.Config.AlipayConfig.ReturnURL
} else {
returnURL = fmt.Sprintf("%s/payReturn", data.Host)
}
money := fmt.Sprintf("%.2f", amount)
if data.Device == "wechat" {
payURL, err = h.alipayService.PayMobile(payment.AlipayParams{
case "wxpay":
logger.Debugf("微信支付,%+v", data)
data.Channel = payment.PayChannelWX
// 优先使用微信官方支付
if h.config.WxPay.Enabled {
data.Channel = "wxpay"
if h.config.WxPay.Domain != "" {
data.Domain = h.config.WxPay.Domain
}
notifyURL = fmt.Sprintf("%s/api/payment/notify/wxpay", data.Domain)
payURL, err = h.wxpayService.Pay(payment.PayRequest{
OutTradeNo: orderNo,
Subject: product.Name,
TotalFee: money,
ReturnURL: returnURL,
NotifyURL: notifyURL,
})
} else {
payURL, err = h.alipayService.PayPC(payment.AlipayParams{
OutTradeNo: orderNo,
Subject: product.Name,
TotalFee: money,
ReturnURL: returnURL,
NotifyURL: notifyURL,
})
}
if err != nil {
resp.ERROR(c, "error with generate pay url: "+err.Error())
return
}
break
case "wechat":
if h.App.Config.WechatPayConfig.NotifyURL != "" {
notifyURL = h.App.Config.WechatPayConfig.NotifyURL
} else {
notifyURL = fmt.Sprintf("%s/api/payment/notify/wechat", data.Host)
}
if data.Device == "wechat" {
payURL, err = h.wechatPayService.PayUrlH5(payment.WechatPayParams{
OutTradeNo: orderNo,
TotalFee: int(amount * 100),
TotalFee: fmt.Sprintf("%d", int(amount*100)),
Subject: product.Name,
NotifyURL: notifyURL,
ClientIP: c.ClientIP(),
Device: data.Device,
PayWay: payment.PayWayWX,
})
} else {
payURL, err = h.wechatPayService.PayUrlNative(payment.WechatPayParams{
if err != nil {
resp.ERROR(c, err.Error())
return
}
} else if h.config.Epay.Enabled { // 聚合支付
logger.Debugf("聚合支付%+v", data)
data.Channel = payment.PayChannelEpay
if h.config.Epay.Domain != "" {
data.Domain = h.config.Epay.Domain
}
notifyURL = fmt.Sprintf("%s/api/payment/notify/epay", data.Domain)
params := payment.PayRequest{
OutTradeNo: orderNo,
TotalFee: int(amount * 100),
Subject: product.Name,
TotalFee: fmt.Sprintf("%f", amount),
ClientIP: c.ClientIP(),
Device: data.Device,
PayWay: payment.PayWayWX,
NotifyURL: notifyURL,
}
r, err := h.epayService.Pay(params)
logger.Debugf("请求支付结果,%+v", r)
if err != nil {
resp.ERROR(c, err.Error())
return
} else {
payURL = r
}
} else {
resp.ERROR(c, "系统没有配置可用的支付渠道!")
return
}
case "alipay":
if h.config.Alipay.Enabled {
logger.Debugf("支付宝,%+v", data)
data.Channel = payment.PayChannelAL
if h.config.Alipay.Domain != "" { // 用于本地调试支付
data.Domain = h.config.Alipay.Domain
}
notifyURL = fmt.Sprintf("%s/api/payment/notify/alipay", data.Domain)
money := fmt.Sprintf("%.2f", amount)
payURL, err = h.alipayService.Pay(payment.PayRequest{
Device: data.Device,
OutTradeNo: orderNo,
Subject: product.Name,
TotalFee: money,
NotifyURL: notifyURL,
})
}
if err != nil {
resp.ERROR(c, err.Error())
return
}
break
case "hupi":
if h.App.Config.HuPiPayConfig.NotifyURL != "" {
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
} else {
notifyURL = fmt.Sprintf("%s/api/payment/notify/hupi", data.Host)
}
if h.App.Config.HuPiPayConfig.ReturnURL != "" {
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
} else {
returnURL = fmt.Sprintf("%s/payReturn", data.Host)
}
r, err := h.huPiPayService.Pay(payment.HuPiPayParams{
Version: "1.1",
TradeOrderId: orderNo,
TotalFee: fmt.Sprintf("%f", amount),
Title: product.Name,
NotifyURL: notifyURL,
ReturnURL: returnURL,
WapName: "GeekAI助手",
})
if err != nil {
resp.ERROR(c, err.Error())
return
}
payURL = r.URL
break
case "geek":
if h.App.Config.GeekPayConfig.NotifyURL != "" {
notifyURL = h.App.Config.GeekPayConfig.NotifyURL
} else {
notifyURL = fmt.Sprintf("%s/api/payment/notify/geek", data.Host)
}
if h.App.Config.GeekPayConfig.ReturnURL != "" {
data.Host = utils.GetBaseURL(h.App.Config.GeekPayConfig.ReturnURL)
}
if data.Device == "wechat" { // 微信客户端打开,调回手机端用户中心页面
returnURL = fmt.Sprintf("%s/mobile/profile", data.Host)
} else {
returnURL = fmt.Sprintf("%s/payReturn", data.Host)
}
params := payment.GeekPayParams{
OutTradeNo: orderNo,
Method: "web",
Name: product.Name,
Money: fmt.Sprintf("%f", amount),
ClientIP: c.ClientIP(),
Device: data.Device,
Type: data.PayType,
ReturnURL: returnURL,
NotifyURL: notifyURL,
}
res, err := h.geekPayService.Pay(params)
if err != nil {
resp.ERROR(c, err.Error())
if err != nil {
resp.ERROR(c, "error with generate pay url: "+err.Error())
return
}
} else if h.config.Epay.Enabled { // 聚合支付
logger.Debugf("聚合支付,%+v", data)
data.Channel = payment.PayChannelEpay
if h.config.Epay.Domain != "" {
data.Domain = h.config.Epay.Domain
}
notifyURL = fmt.Sprintf("%s/api/payment/notify/epay", data.Domain)
params := payment.PayRequest{
OutTradeNo: orderNo,
Subject: product.Name,
TotalFee: fmt.Sprintf("%f", amount),
ClientIP: c.ClientIP(),
Device: data.Device,
PayWay: data.PayWay,
NotifyURL: notifyURL,
}
r, err := h.epayService.Pay(params)
if err != nil {
resp.ERROR(c, err.Error())
return
} else {
payURL = r
}
} else {
resp.ERROR(c, "系统没有配置可用的支付渠道!")
return
}
payURL = res.PayURL
default:
resp.ERROR(c, "不支持的支付渠道")
return
@@ -234,43 +314,40 @@ func (h *PaymentHandler) Pay(c *gin.Context) {
// 创建订单
remark := types.OrderRemark{
Days: product.Days,
Power: product.Power,
Name: product.Name,
Price: product.Price,
Discount: product.Discount,
Power: product.Power,
Name: product.Name,
Price: product.Price,
}
order := model.Order{
UserId: user.Id,
Username: user.Username,
ProductId: product.Id,
OrderNo: orderNo,
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
PayWay: data.PayWay,
PayType: data.PayType,
Remark: utils.JsonEncode(remark),
UserId: user.Id,
Username: user.Username,
OrderNo: orderNo,
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
PayWay: data.PayWay,
Channel: data.Channel,
Remark: utils.JsonEncode(remark),
}
err = h.DB.Create(&order).Error
if err != nil {
resp.ERROR(c, "error with create order: "+err.Error())
return
}
resp.SUCCESS(c, payURL)
resp.SUCCESS(c, gin.H{"pay_url": payURL, "order_no": orderNo})
}
// 异步通知回调公共逻辑
func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
// 支付成功处理
func (h *PaymentHandler) paySuccess(info payment.OrderInfo) error {
h.lock.Lock()
defer h.lock.Unlock()
var order model.Order
err := h.DB.Where("order_no = ?", orderNo).First(&order).Error
err := h.DB.Where("order_no", info.OutTradeNo).First(&order).Error
if err != nil {
return fmt.Errorf("error with fetch order: %v", err)
}
h.lock.Lock()
defer h.lock.Unlock()
// 已支付订单,直接返回
if order.Status == types.OrderPaidSuccess {
return nil
@@ -290,19 +367,21 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
// 增加用户算力
err = h.userService.IncreasePower(order.UserId, remark.Power, model.PowerLog{
Type: types.PowerRecharge,
Model: order.PayWay,
Remark: fmt.Sprintf("充值算力,金额:%f,订单号:%s", order.Amount, order.OrderNo),
Type: types.PowerRecharge,
Model: order.Subject,
Remark: fmt.Sprintf("充值算力,金额:%f,订单号:%s", order.Amount, order.OrderNo),
CreatedAt: time.Now(),
})
if err != nil {
return err
}
// 更新订单状态
order.PayTime = time.Now().Unix()
order.PayTime = utils.Str2stamp(info.PayTime)
order.Status = types.OrderPaidSuccess
order.TradeNo = tradeNo
err = h.DB.Updates(&order).Error
order.TradeNo = info.TradeId
order.Checked = true
err = h.DB.Debug().Updates(&order).Error
if err != nil {
return fmt.Errorf("error with update order info: %v", err)
}
@@ -317,54 +396,6 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
return nil
}
// GetPayWays 获取支付方式
func (h *PaymentHandler) GetPayWays(c *gin.Context) {
payWays := make([]gin.H, 0)
if h.App.Config.AlipayConfig.Enabled {
payWays = append(payWays, gin.H{"pay_way": "alipay", "pay_type": "alipay"})
}
if h.App.Config.HuPiPayConfig.Enabled {
payWays = append(payWays, gin.H{"pay_way": "hupi", "pay_type": "wxpay"})
}
if h.App.Config.GeekPayConfig.Enabled {
for _, v := range h.App.Config.GeekPayConfig.Methods {
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": v})
}
}
if h.App.Config.WechatPayConfig.Enabled {
payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wxpay"})
}
resp.SUCCESS(c, payWays)
}
// HuPiPayNotify 虎皮椒支付异步回调
func (h *PaymentHandler) HuPiPayNotify(c *gin.Context) {
err := c.Request.ParseForm()
if err != nil {
c.String(http.StatusOK, "fail")
return
}
orderNo := c.Request.Form.Get("trade_order_id")
tradeNo := c.Request.Form.Get("open_order_id")
logger.Infof("收到虎皮椒订单支付回调,%+v", c.Request.Form)
if err = h.huPiPayService.Check(orderNo); err != nil {
logger.Error("订单校验失败:", err)
c.String(http.StatusOK, "fail")
return
}
err = h.notify(orderNo, tradeNo)
if err != nil {
logger.Error(err)
c.String(http.StatusOK, "fail")
return
}
c.String(http.StatusOK, "success")
}
// AlipayNotify 支付宝支付回调
func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
err := c.Request.ParseForm()
@@ -373,16 +404,15 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
return
}
result := h.alipayService.TradeVerify(c.Request)
logger.Infof("收到支付宝商号订单支付回调:%+v", result)
if !result.Success() {
logger.Error("订单校验失败:", result.Message)
orderInfo, err := h.alipayService.Query(c.Request.Form.Get("out_trade_no"))
logger.Infof("收到支付宝商号订单支付回调:%+v", orderInfo)
if !orderInfo.Success() {
logger.Errorf("订单校验失败:%v", err)
c.String(http.StatusOK, "fail")
return
}
tradeNo := c.Request.Form.Get("trade_no")
err = h.notify(result.OutTradeNo, tradeNo)
err = h.paySuccess(orderInfo)
if err != nil {
logger.Error(err)
c.String(http.StatusOK, "fail")
@@ -392,28 +422,35 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
c.String(http.StatusOK, "success")
}
// GeekPayNotify 支付异步回调
func (h *PaymentHandler) GeekPayNotify(c *gin.Context) {
// EPayNotify 易支付支付异步回调
func (h *PaymentHandler) EPayNotify(c *gin.Context) {
var params = make(map[string]string)
for k := range c.Request.URL.Query() {
params[k] = c.Query(k)
}
logger.Infof("收到GeekPay订单支付回调:%+v", params)
// 检查支付状态
logger.Infof("收到易支付订单支付回调:%+v", params)
// 检查支付状态, 如果未支付,则返回成功
if params["trade_status"] != "TRADE_SUCCESS" {
c.String(http.StatusOK, "success")
return
}
sign := h.geekPayService.Sign(params)
sign := h.epayService.Sign(params)
if sign != c.Query("sign") {
logger.Errorf("签名验证失败, %s, %s", sign, c.Query("sign"))
c.String(http.StatusOK, "fail")
return
}
// 查询订单状态
order, err := h.epayService.Query(params["out_trade_no"])
if err != nil {
logger.Error(err)
c.String(http.StatusOK, "fail")
return
}
err := h.notify(params["out_trade_no"], params["trade_no"])
err = h.paySuccess(order)
if err != nil {
logger.Error(err)
c.String(http.StatusOK, "fail")
@@ -423,26 +460,23 @@ func (h *PaymentHandler) GeekPayNotify(c *gin.Context) {
c.String(http.StatusOK, "success")
}
// WechatPayNotify 微信商户支付异步回调
func (h *PaymentHandler) WechatPayNotify(c *gin.Context) {
// WxpayNotify 微信商户支付异步回调
func (h *PaymentHandler) WxpayNotify(c *gin.Context) {
err := c.Request.ParseForm()
if err != nil {
c.String(http.StatusOK, "fail")
return
}
result := h.wechatPayService.TradeVerify(c.Request)
logger.Infof("收到微信商号订单支付回调:%+v", result)
if !result.Success() {
logger.Error("订单校验失败:", err)
c.JSON(http.StatusBadRequest, gin.H{
"code": "FAIL",
"message": err.Error(),
})
orderInfo, err := h.wxpayService.TradeVerify(c.Request)
logger.Infof("收到微信商号订单支付回调:%+v", orderInfo)
if err != nil {
logger.Errorf("订单校验失败:%v", err)
c.JSON(http.StatusBadRequest, gin.H{"code": "FAIL"})
return
}
err = h.notify(result.OutTradeNo, result.TradeId)
err = h.paySuccess(orderInfo)
if err != nil {
logger.Error(err)
c.String(http.StatusOK, "fail")
+56
View File
@@ -9,11 +9,13 @@ package handler
import (
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -27,6 +29,18 @@ func NewPowerLogHandler(app *core.AppServer, db *gorm.DB) *PowerLogHandler {
return &PowerLogHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *PowerLogHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/powerLog/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("list", h.List)
group.GET("stats", h.Stats)
}
}
func (h *PowerLogHandler) List(c *gin.Context) {
var data struct {
Model string `json:"model"`
@@ -72,3 +86,45 @@ func (h *PowerLogHandler) List(c *gin.Context) {
}
resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list))
}
// Stats 获取用户算力统计
func (h *PowerLogHandler) Stats(c *gin.Context) {
userId := h.GetLoginUserId(c)
if userId == 0 {
resp.NotAuth(c)
return
}
// 获取用户信息(包含余额)
var user model.User
if err := h.DB.Where("id", userId).First(&user).Error; err != nil {
resp.ERROR(c, "用户不存在")
return
}
// 计算总消费(所有支出记录)
var totalConsume int64
h.DB.Model(&model.PowerLog{}).
Where("user_id", userId).
Where("mark", types.PowerSub).
Select("COALESCE(SUM(amount), 0)").
Scan(&totalConsume)
// 计算今日消费
today := time.Now().Format("2006-01-02")
var todayConsume int64
h.DB.Model(&model.PowerLog{}).
Where("user_id", userId).
Where("mark", types.PowerSub).
Where("DATE(created_at) = ?", today).
Select("COALESCE(SUM(amount), 0)").
Scan(&todayConsume)
stats := map[string]interface{}{
"total": totalConsume,
"today": todayConsume,
"balance": user.Power,
}
resp.SUCCESS(c, stats)
}
+7
View File
@@ -13,6 +13,7 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
@@ -25,6 +26,12 @@ func NewProductHandler(app *core.AppServer, db *gorm.DB) *ProductHandler {
return &ProductHandler{BaseHandler: BaseHandler{App: app, DB: db}}
}
// RegisterRoutes 注册路由
func (h *ProductHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/product/")
group.GET("list", h.List)
}
// List 模型列表
func (h *ProductHandler) List(c *gin.Context) {
var items []model.Product
+22 -43
View File
@@ -10,12 +10,14 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"strings"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -39,6 +41,20 @@ func NewPromptHandler(app *core.AppServer, db *gorm.DB, userService *service.Use
}
}
// RegisterRoutes 注册路由
func (h *PromptHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/prompt/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis)).Use(middleware.RateLimitEvery(h.App.Redis, 30*time.Second))
{
group.POST("lyric", h.Lyric)
group.POST("image", h.Image)
group.POST("video", h.Video)
group.POST("meta", h.MetaPrompt)
}
}
// Lyric 生成歌词
func (h *PromptHandler) Lyric(c *gin.Context) {
var data struct {
@@ -48,25 +64,12 @@ func (h *PromptHandler) Lyric(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.LyricPromptTemplate, data.Prompt), h.App.SysConfig.AssistantModelId)
content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.LyricPromptTemplate, data.Prompt), h.App.SysConfig.Base.AssistantModelId)
if err != nil {
resp.ERROR(c, err.Error())
return
}
if h.App.SysConfig.PromptPower > 0 {
userId := h.GetLoginUserId(c)
err = h.userService.DecreasePower(userId, h.App.SysConfig.PromptPower, model.PowerLog{
Type: types.PowerConsume,
Model: h.getPromptModel(),
Remark: "生成歌词",
})
if err != nil {
resp.ERROR(c, err.Error())
return
}
}
resp.SUCCESS(c, content)
}
@@ -79,23 +82,12 @@ func (h *PromptHandler) Image(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.ImagePromptOptimizeTemplate, data.Prompt), h.App.SysConfig.AssistantModelId)
content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.ImagePromptOptimizeTemplate, data.Prompt), h.App.SysConfig.Base.AssistantModelId)
if err != nil {
resp.ERROR(c, err.Error())
return
}
if h.App.SysConfig.PromptPower > 0 {
userId := h.GetLoginUserId(c)
err = h.userService.DecreasePower(userId, h.App.SysConfig.PromptPower, model.PowerLog{
Type: types.PowerConsume,
Model: h.getPromptModel(),
Remark: "生成绘画提示词",
})
if err != nil {
resp.ERROR(c, err.Error())
return
}
}
resp.SUCCESS(c, strings.Trim(content, `"`))
}
@@ -108,25 +100,12 @@ func (h *PromptHandler) Video(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.VideoPromptTemplate, data.Prompt), h.App.SysConfig.AssistantModelId)
content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.VideoPromptTemplate, data.Prompt), h.App.SysConfig.Base.AssistantModelId)
if err != nil {
resp.ERROR(c, err.Error())
return
}
if h.App.SysConfig.PromptPower > 0 {
userId := h.GetLoginUserId(c)
err = h.userService.DecreasePower(userId, h.App.SysConfig.PromptPower, model.PowerLog{
Type: types.PowerConsume,
Model: h.getPromptModel(),
Remark: "生成视频脚本",
})
if err != nil {
resp.ERROR(c, err.Error())
return
}
}
resp.SUCCESS(c, strings.Trim(content, `"`))
}
@@ -158,9 +137,9 @@ func (h *PromptHandler) MetaPrompt(c *gin.Context) {
}
func (h *PromptHandler) getPromptModel() string {
if h.App.SysConfig.AssistantModelId > 0 {
if h.App.SysConfig.Base.AssistantModelId > 0 {
var chatModel model.ChatModel
h.DB.Where("id", h.App.SysConfig.AssistantModelId).First(&chatModel)
h.DB.Where("id", h.App.SysConfig.Base.AssistantModelId).First(&chatModel)
return chatModel.Value
}
return "gpt-4o"
+15 -2
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/store/model"
@@ -39,6 +40,18 @@ func NewRealtimeHandler(server *core.AppServer, db *gorm.DB, userService *servic
return &RealtimeHandler{BaseHandler: BaseHandler{App: server, DB: db}, userService: userService}
}
// RegisterRoutes 注册路由
func (h *RealtimeHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/realtime/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.Any("", h.Connection)
group.POST("voice", h.VoiceChat)
}
}
func (h *RealtimeHandler) Connection(c *gin.Context) {
// 获取客户端请求中指定的子协议
clientProtocols := c.GetHeader("Sec-WebSocket-Protocol")
@@ -154,7 +167,7 @@ func (h *RealtimeHandler) VoiceChat(c *gin.Context) {
return
}
if user.Power < h.App.SysConfig.AdvanceVoicePower {
if user.Power < h.App.SysConfig.Base.AdvanceVoicePower {
resp.ERROR(c, "当前用户算力不足,无法使用该功能")
return
}
@@ -198,7 +211,7 @@ func (h *RealtimeHandler) VoiceChat(c *gin.Context) {
h.DB.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
// 扣减算力
err = h.userService.DecreasePower(userId, h.App.SysConfig.AdvanceVoicePower, model.PowerLog{
err = h.userService.DecreasePower(userId, h.App.SysConfig.Base.AdvanceVoicePower, model.PowerLog{
Type: types.PowerConsume,
Model: "advanced-voice",
Remark: "实时语音通话",
+15 -2
View File
@@ -10,14 +10,16 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/store/model"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"sync"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type RedeemHandler struct {
@@ -30,6 +32,17 @@ func NewRedeemHandler(app *core.AppServer, db *gorm.DB, userService *service.Use
return &RedeemHandler{BaseHandler: BaseHandler{App: app, DB: db}, userService: userService}
}
// RegisterRoutes 注册路由
func (h *RedeemHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/redeem/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("verify", h.Verify)
}
}
func (h *RedeemHandler) Verify(c *gin.Context) {
var data struct {
Code string `json:"code"`
+60 -15
View File
@@ -10,8 +10,10 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/service/sd"
"geekai/store"
@@ -28,12 +30,13 @@ import (
type SdJobHandler struct {
BaseHandler
redis *redis.Client
sdService *sd.Service
uploader *oss.UploaderManager
snowflake *service.Snowflake
leveldb *store.LevelDB
userService *service.UserService
redis *redis.Client
sdService *sd.Service
uploader *oss.UploaderManager
snowflake *service.Snowflake
leveldb *store.LevelDB
userService *service.UserService
moderationManager *moderation.ServiceManager
}
func NewSdJobHandler(app *core.AppServer,
@@ -42,13 +45,15 @@ func NewSdJobHandler(app *core.AppServer,
manager *oss.UploaderManager,
snowflake *service.Snowflake,
userService *service.UserService,
levelDB *store.LevelDB) *SdJobHandler {
levelDB *store.LevelDB,
moderationManager *moderation.ServiceManager) *SdJobHandler {
return &SdJobHandler{
sdService: service,
uploader: manager,
snowflake: snowflake,
leveldb: levelDB,
userService: userService,
sdService: service,
uploader: manager,
snowflake: snowflake,
leveldb: levelDB,
userService: userService,
moderationManager: moderationManager,
BaseHandler: BaseHandler{
App: app,
DB: db,
@@ -56,6 +61,23 @@ func NewSdJobHandler(app *core.AppServer,
}
}
// RegisterRoutes 注册路由
func (h *SdJobHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/sd/")
// 公开接口,不需要授权
group.GET("imgWall", h.ImgWall)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("image", h.Image)
group.GET("jobs", h.JobList)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
}
}
func (h *SdJobHandler) preCheck(c *gin.Context) bool {
user, err := h.GetLoginUser(c)
if err != nil {
@@ -63,7 +85,7 @@ func (h *SdJobHandler) preCheck(c *gin.Context) bool {
return false
}
if user.Power < h.App.SysConfig.SdPower {
if user.Power < h.App.SysConfig.Base.SdPower {
resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画!")
return false
}
@@ -84,6 +106,29 @@ func (h *SdJobHandler) Image(c *gin.Context) {
return
}
if h.App.SysConfig.Moderation.Enable {
moderationResult, err := h.moderationManager.GetService().Moderate(data.Prompt)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: h.GetLoginUserId(c),
Source: types.ModerationSourceSD,
Input: data.Prompt,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
resp.ERROR(c, "当前创作内容包含敏感词,请重新输入!")
return
}
}
if data.Width <= 0 {
data.Width = 512
}
@@ -131,7 +176,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
HdSteps: data.HdSteps,
},
UserId: userId,
TranslateModelId: h.App.SysConfig.AssistantModelId,
TranslateModelId: h.App.SysConfig.Base.AssistantModelId,
}
job := model.SdJob{
@@ -142,7 +187,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
TaskInfo: utils.JsonEncode(task),
Prompt: data.Prompt,
Progress: 0,
Power: h.App.SysConfig.SdPower,
Power: h.App.SysConfig.Base.SdPower,
CreatedAt: time.Now(),
}
res := h.DB.Create(&job)
+24 -17
View File
@@ -24,24 +24,31 @@ const CodeStorePrefix = "/verify/codes/"
type SmsHandler struct {
BaseHandler
redis *redis.Client
sms *sms.ServiceManager
smtp *service.SmtpService
captcha *service.CaptchaService
redis *redis.Client
sms *sms.SmsManager
smtp *service.SmtpService
captchaService *service.CaptchaService
}
func NewSmsHandler(
app *core.AppServer,
client *redis.Client,
sms *sms.ServiceManager,
sms *sms.SmsManager,
smtp *service.SmtpService,
captcha *service.CaptchaService) *SmsHandler {
return &SmsHandler{
redis: client,
sms: sms,
captcha: captcha,
smtp: smtp,
BaseHandler: BaseHandler{App: app}}
redis: client,
sms: sms,
captchaService: captcha,
smtp: smtp,
BaseHandler: BaseHandler{App: app}}
}
// RegisterRoutes 注册路由
func (h *SmsHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/sms/")
// 无需授权的接口
group.POST("code", h.SendCode)
}
// SendCode 发送验证码
@@ -56,12 +63,12 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
if h.App.SysConfig.EnabledVerify {
if h.captchaService.GetConfig().Enabled {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
check = h.captchaService.SlideCheck(data)
} else {
check = h.captcha.Check(data)
check = h.captchaService.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
@@ -72,14 +79,14 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
code := utils.RandomNumber(6)
var err error
if strings.Contains(data.Receiver, "@") { // email
if !utils.Contains(h.App.SysConfig.RegisterWays, "email") {
if !utils.Contains(h.App.SysConfig.Base.RegisterWays, "email") {
resp.ERROR(c, "系统已禁用邮箱注册!")
return
}
// 检查邮箱后缀是否在白名单
if len(h.App.SysConfig.EmailWhiteList) > 0 {
if len(h.App.SysConfig.Base.EmailWhiteList) > 0 {
inWhiteList := false
for _, suffix := range h.App.SysConfig.EmailWhiteList {
for _, suffix := range h.App.SysConfig.Base.EmailWhiteList {
if strings.HasSuffix(data.Receiver, suffix) {
inWhiteList = true
break
@@ -92,7 +99,7 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
}
err = h.smtp.SendVerifyCode(data.Receiver, code)
} else {
if !utils.Contains(h.App.SysConfig.RegisterWays, "mobile") {
if !utils.Contains(h.App.SysConfig.Base.RegisterWays, "mobile") {
resp.ERROR(c, "系统已禁用手机号注册!")
return
}
+55 -9
View File
@@ -10,8 +10,10 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/service/suno"
"geekai/store/model"
@@ -26,20 +28,41 @@ import (
type SunoHandler struct {
BaseHandler
sunoService *suno.Service
uploader *oss.UploaderManager
userService *service.UserService
sunoService *suno.Service
uploader *oss.UploaderManager
userService *service.UserService
moderationManager *moderation.ServiceManager
}
func NewSunoHandler(app *core.AppServer, db *gorm.DB, service *suno.Service, uploader *oss.UploaderManager, userService *service.UserService) *SunoHandler {
func NewSunoHandler(app *core.AppServer, db *gorm.DB, service *suno.Service, uploader *oss.UploaderManager, userService *service.UserService, moderationManager *moderation.ServiceManager) *SunoHandler {
return &SunoHandler{
BaseHandler: BaseHandler{
App: app,
DB: db,
},
sunoService: service,
uploader: uploader,
userService: userService,
sunoService: service,
uploader: uploader,
userService: userService,
moderationManager: moderationManager,
}
}
// RegisterRoutes 注册路由
func (h *SunoHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/suno/")
// 公开接口,不需要授权
group.GET("play", h.Play)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("create", h.Create)
group.GET("list", h.List)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
group.POST("update", h.Update)
group.GET("detail", h.Detail)
}
}
@@ -64,13 +87,36 @@ func (h *SunoHandler) Create(c *gin.Context) {
return
}
if h.App.SysConfig.Moderation.Enable {
moderationResult, err := h.moderationManager.GetService().Moderate(data.Prompt)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: h.GetLoginUserId(c),
Source: types.ModerationSourceSuno,
Input: data.Prompt,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
resp.ERROR(c, "当前创作内容包含敏感词,请重新输入!")
return
}
}
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
return
}
if user.Power < h.App.SysConfig.SunoPower {
if user.Power < h.App.SysConfig.Base.SunoPower {
resp.ERROR(c, "您的算力不足,请充值后再试!")
return
}
@@ -118,7 +164,7 @@ func (h *SunoHandler) Create(c *gin.Context) {
RefSongId: data.RefSongId,
RefTaskId: data.RefTaskId,
ExtendSecs: data.ExtendSecs,
Power: h.App.SysConfig.SunoPower,
Power: h.App.SysConfig.Base.SunoPower,
SongId: utils.RandString(32),
}
if data.Lyrics != "" {
+19 -4
View File
@@ -1,21 +1,36 @@
package handler
import (
"geekai/core"
"geekai/core/middleware"
"geekai/service"
"geekai/service/payment"
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"net/http"
)
type TestHandler struct {
App *core.AppServer
db *gorm.DB
snowflake *service.Snowflake
js *payment.GeekPayService
js *payment.EPayService
}
func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.GeekPayService) *TestHandler {
return &TestHandler{db: db, snowflake: snowflake, js: js}
func NewTestHandler(app *core.AppServer, db *gorm.DB, snowflake *service.Snowflake, js *payment.EPayService) *TestHandler {
return &TestHandler{App: app, db: db, snowflake: snowflake, js: js}
}
// RegisterRoutes 注册路由
func (h *TestHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/test/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.Any("sse", h.PostTest, h.SseTest)
}
}
func (h *TestHandler) SseTest(c *gin.Context) {
+224 -253
View File
@@ -8,8 +8,10 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"context"
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/store"
@@ -20,8 +22,6 @@ import (
"strings"
"time"
"github.com/imroc/req/v3"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
@@ -35,9 +35,10 @@ type UserHandler struct {
searcher *xdb.Searcher
redis *redis.Client
levelDB *store.LevelDB
licenseService *service.LicenseService
captcha *service.CaptchaService
captchaService *service.CaptchaService
userService *service.UserService
wxLoginService *service.WxLoginService
ipSearcher *xdb.Searcher
}
func NewUserHandler(
@@ -48,15 +49,43 @@ func NewUserHandler(
levelDB *store.LevelDB,
captcha *service.CaptchaService,
userService *service.UserService,
licenseService *service.LicenseService) *UserHandler {
wxLoginService *service.WxLoginService,
ipSearcher *xdb.Searcher) *UserHandler {
return &UserHandler{
BaseHandler: BaseHandler{DB: db, App: app},
searcher: searcher,
redis: client,
levelDB: levelDB,
captcha: captcha,
licenseService: licenseService,
captchaService: captcha,
userService: userService,
wxLoginService: wxLoginService,
ipSearcher: ipSearcher,
}
}
// RegisterRoutes 注册路由
func (h *UserHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/user/")
// 公开接口,不需要授权
group.POST("register", h.Register)
group.POST("login", h.Login)
group.POST("resetPass", h.ResetPass)
group.GET("login/qrcode", h.GetWxLoginQRCode)
group.POST("login/callback", h.WxLoginCallback)
group.GET("login/status", h.GetWxLoginState)
group.GET("logout", h.Logout)
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.GET("session", h.Session)
group.GET("profile", h.Profile)
group.POST("profile/update", h.ProfileUpdate)
group.POST("password", h.UpdatePass)
group.POST("bind/mobile", h.BindMobile)
group.POST("bind/email", h.BindEmail)
group.GET("signin", h.SignIn)
}
}
@@ -80,12 +109,13 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
if h.App.SysConfig.EnabledVerify && data.RegWay == "username" {
// 人机验证
if h.captchaService.GetConfig().Enabled {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
check = h.captchaService.SlideCheck(data)
} else {
check = h.captcha.Check(data)
check = h.captchaService.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
@@ -99,14 +129,6 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
// 检测最大注册人数
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}
// 检查验证码
var key string
if data.RegWay == "email" {
@@ -125,30 +147,8 @@ func (h *UserHandler) Register(c *gin.Context) {
}
}
// 验证邀请码
inviteCode := model.InviteCode{}
if data.InviteCode != "" {
res := h.DB.Where("code = ?", data.InviteCode).First(&inviteCode)
if res.Error != nil {
resp.ERROR(c, "无效的邀请码")
return
}
}
salt := utils.RandString(8)
user := model.User{
Username: data.Username,
Password: utils.GenPassword(data.Password, salt),
Avatar: "/images/avatar/user.png",
Salt: salt,
Status: true,
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
ChatConfig: "{}",
ChatModels: "{}",
Power: h.App.SysConfig.InitPower,
}
// check if the username is existing
user := model.User{Username: data.Username, Password: data.Password}
var item model.User
session := h.DB.Session(&gorm.Session{})
if data.Mobile != "" {
@@ -168,78 +168,19 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
// 被邀请人也获得赠送算力
if data.InviteCode != "" {
user.Power += h.App.SysConfig.InvitePower
}
if h.licenseService.GetLicense().Configs.DeCopy {
user.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
} else {
defaultNickname := h.App.SysConfig.DefaultNickname
if defaultNickname == "" {
defaultNickname = "极客学长"
}
user.Nickname = fmt.Sprintf("%s@%d", defaultNickname, utils.RandomNumber(6))
}
tx := h.DB.Begin()
if err := tx.Create(&user).Error; err != nil {
user, err := h.createNewUser(user, data.InviteCode)
if err != nil {
resp.ERROR(c, err.Error())
return
}
// 记录邀请关系
if data.InviteCode != "" {
// 增加邀请数量
h.DB.Model(&model.InviteCode{}).Where("code = ?", data.InviteCode).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1))
if h.App.SysConfig.InvitePower > 0 {
err := h.userService.IncreasePower(inviteCode.UserId, h.App.SysConfig.InvitePower, model.PowerLog{
Type: types.PowerInvite,
Model: "Invite",
Remark: fmt.Sprintf("邀请用户注册奖励,金额:%d,邀请码:%s,新用户:%s", h.App.SysConfig.InvitePower, inviteCode.Code, user.Username),
})
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
}
// 添加邀请记录
err := tx.Create(&model.InviteLog{
InviterId: inviteCode.UserId,
UserId: user.Id,
Username: user.Username,
InviteCode: inviteCode.Code,
Remark: fmt.Sprintf("奖励 %d 算力", h.App.SysConfig.InvitePower),
}).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
}
tx.Commit()
_ = h.redis.Del(c, key) // 注册成功,删除短信验证码
// 自动登录创建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.Id,
"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
token, err := h.doLogin(&user, c.ClientIP())
if err != nil {
resp.ERROR(c, "Failed to generate token, "+err.Error())
resp.ERROR(c, err.Error())
return
}
// 保存到 redis
key = fmt.Sprintf("users/%d", user.Id)
if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
resp.ERROR(c, "error with save token: "+err.Error())
return
}
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
resp.SUCCESS(c, gin.H{"token": token, "user_id": user.Id, "username": user.Username})
}
// Login 用户登录
@@ -255,15 +196,12 @@ func (h *UserHandler) Login(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
verifyKey := fmt.Sprintf("users/verify/%s", data.Username)
needVerify, err := h.redis.Get(c, verifyKey).Bool()
if h.App.SysConfig.EnabledVerify && needVerify {
if h.captchaService.GetConfig().Enabled {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
check = h.captchaService.SlideCheck(data)
} else {
check = h.captcha.Check(data)
check = h.captchaService.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
@@ -274,54 +212,28 @@ func (h *UserHandler) Login(c *gin.Context) {
var user model.User
res := h.DB.Where("username = ?", data.Username).First(&user)
if res.Error != nil {
h.redis.Set(c, verifyKey, true, 0)
resp.ERROR(c, "用户名不存在")
return
}
password := utils.GenPassword(data.Password, user.Salt)
if password != user.Password {
h.redis.Set(c, verifyKey, true, 0)
resp.ERROR(c, "用户名或密码错误")
return
}
if user.Status == false {
if !user.Status {
resp.ERROR(c, "该用户已被禁止登录,请联系管理员")
return
}
// 更新最后登录时间和IP
user.LastLoginIp = c.ClientIP()
user.LastLoginAt = time.Now().Unix()
h.DB.Model(&user).Updates(user)
h.DB.Create(&model.UserLoginLog{
UserId: user.Id,
Username: user.Username,
LoginIp: c.ClientIP(),
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
})
// 创建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.Id,
"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
token, err := h.doLogin(&user, c.ClientIP())
if err != nil {
resp.ERROR(c, "Failed to generate token, "+err.Error())
resp.ERROR(c, err.Error())
return
}
// 保存到 redis
sessionKey := fmt.Sprintf("users/%d", user.Id)
if _, err = h.redis.Set(c, sessionKey, tokenString, 0).Result(); err != nil {
resp.ERROR(c, "error with save token: "+err.Error())
return
}
// 移除登录行为验证码
h.redis.Del(c, verifyKey)
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
resp.SUCCESS(c, gin.H{"token": token, "user_id": user.Id, "username": user.Username})
}
// Logout 注 销
@@ -333,134 +245,168 @@ func (h *UserHandler) Logout(c *gin.Context) {
resp.SUCCESS(c)
}
// CLogin 第三方登录请求二维码
func (h *UserHandler) CLogin(c *gin.Context) {
returnURL := h.GetTrim(c, "return_url")
var res types.BizVo
apiURL := fmt.Sprintf("%s/api/clogin/request", h.App.Config.ApiConfig.ApiURL)
r, err := req.C().R().SetBody(gin.H{"login_type": "wx", "return_url": returnURL}).
SetHeader("AppId", h.App.Config.ApiConfig.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
SetSuccessResult(&res).
Post(apiURL)
// GetWxLoginQRCode 获取微信登录二维码URL
func (h *UserHandler) GetWxLoginQRCode(c *gin.Context) {
if !h.wxLoginService.GetConfig().Enabled {
resp.ERROR(c, "微信登录功能未启用")
return
}
if h.wxLoginService.GetConfig().ApiKey == "" {
resp.ERROR(c, "微信登录服务令牌未配置")
return
}
state := utils.RandString(32)
qrCodeURL, err := h.wxLoginService.GetLoginQrCodeUrl(state)
if err != nil {
resp.ERROR(c, err.Error())
return
}
if r.IsErrorState() {
resp.ERROR(c, "error with login http status: "+r.Status)
return
}
if res.Code != types.Success {
resp.ERROR(c, "error with http response: "+res.Message)
return
}
resp.SUCCESS(c, res.Data)
resp.SUCCESS(c, gin.H{
"url": qrCodeURL,
"state": state,
})
}
// CLoginCallback 第三方登录回调
func (h *UserHandler) CLoginCallback(c *gin.Context) {
loginType := c.Query("login_type")
code := c.Query("code")
userId := h.GetInt(c, "user_id", 0)
action := c.Query("action")
// 查询微信登录状态
func (h *UserHandler) GetWxLoginState(c *gin.Context) {
state := c.Query("state")
if state == "" {
resp.ERROR(c, "参数错误")
return
}
var res types.BizVo
apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL)
r, err := req.C().R().SetBody(gin.H{"login_type": loginType, "code": code}).
SetHeader("AppId", h.App.Config.ApiConfig.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
SetSuccessResult(&res).
Post(apiURL)
status, err := h.wxLoginService.GetLoginStatus(state)
if err != nil {
resp.ERROR(c, err.Error())
return
}
if r.IsErrorState() {
resp.ERROR(c, "error with login http status: "+r.Status)
if status.Status != service.LoginStatusSuccess {
resp.SUCCESS(c, status)
return
}
if res.Code != types.Success {
resp.ERROR(c, "error with http response: "+res.Message)
return
}
// login successfully
data := res.Data.(map[string]interface{})
// 登录成功
var user model.User
if action == "bind" && userId > 0 {
err = h.DB.Where("openid", data["openid"]).First(&user).Error
if err == nil {
resp.ERROR(c, "该微信已经绑定其他账号,请先解绑")
return
}
err = h.DB.Where("id", userId).First(&user).Error
h.DB.Where("openid = ?", status.OpenID).First(&user)
if user.Id == 0 {
// 创建新用户
user, err = h.createNewUser(model.User{OpenId: status.OpenID}, "")
if err != nil {
resp.ERROR(c, "绑定用户不存在")
resp.ERROR(c, err.Error())
return
}
}
err = h.DB.Model(&user).UpdateColumn("openid", data["openid"]).Error
if err != nil {
resp.ERROR(c, "更新用户信息失败,"+err.Error())
return
}
resp.SUCCESS(c, gin.H{"token": ""})
token, err := h.doLogin(&user, c.ClientIP())
if err != nil {
resp.ERROR(c, err.Error())
return
}
session := gin.H{}
tx := h.DB.Where("openid", data["openid"]).First(&user)
if tx.Error != nil {
// create new user
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}
status.Status = service.LoginStatusExpired
h.wxLoginService.SetLoginStatus(state, *status)
salt := utils.RandString(8)
password := fmt.Sprintf("%d", utils.RandomNumber(8))
user = model.User{
Username: fmt.Sprintf("%s@%d", loginType, utils.RandomNumber(10)),
Password: utils.GenPassword(password, salt),
Avatar: fmt.Sprintf("%s", data["avatar"]),
Salt: salt,
Status: true,
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
Power: h.App.SysConfig.InitPower,
OpenId: fmt.Sprintf("%s", data["openid"]),
Nickname: fmt.Sprintf("%s", data["nickname"]),
}
status.Status = service.LoginStatusSuccess
status.Token = token
resp.SUCCESS(c, status)
}
tx = h.DB.Create(&user)
if tx.Error != nil {
resp.ERROR(c, "保存数据失败")
logger.Error(tx.Error)
return
// createNewUser 创建新用户
func (h *UserHandler) createNewUser(user model.User, code string) (model.User, error) {
if user.OpenId != "" {
user.Platform = "wechat"
user.Nickname = fmt.Sprintf("微信用户@%d", utils.RandomNumber(6))
user.Username = fmt.Sprintf("wx@%d", utils.RandomNumber(8))
user.Password = "geekai123"
} else {
user.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
if user.Username == "" || user.Password == "" {
return user, fmt.Errorf("用户名或密码不能为空")
}
session["username"] = user.Username
session["password"] = password
} else { // login directly
// 更新最后登录时间和IP
user.LastLoginIp = c.ClientIP()
user.LastLoginAt = time.Now().Unix()
h.DB.Model(&user).Updates(user)
h.DB.Create(&model.UserLoginLog{
UserId: user.Id,
Username: user.Username,
LoginIp: c.ClientIP(),
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
})
}
salt := utils.RandString(8)
user.Salt = salt
user.Password = utils.GenPassword(user.Password, salt)
user.Avatar = "/images/avatar/user.png"
user.Status = true
user.ChatRoles = utils.JsonEncode([]string{"gpt"})
user.ChatConfig = "{}"
user.ChatModels = "{}"
user.Power = h.App.SysConfig.Base.InitPower
// 创建用户
tx := h.DB.Begin()
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return user, err
}
// 记录邀请关系
if code != "" {
var inviteCode model.InviteCode
err := h.DB.Where("code = ?", code).First(&inviteCode).Error
if err != nil {
logger.Error("无效的邀请码:", err.Error())
tx.Rollback()
return user, fmt.Errorf("无效的邀请码")
}
// 增加邀请数量
h.DB.Model(&model.InviteCode{}).Where("code = ?", code).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1))
if h.App.SysConfig.Base.InvitePower > 0 {
err := h.userService.IncreasePower(inviteCode.UserId, h.App.SysConfig.Base.InvitePower, model.PowerLog{
Type: types.PowerInvite,
Model: "Invite",
Remark: fmt.Sprintf("邀请用户注册奖励,金额:%d,邀请码:%s,新用户:%s", h.App.SysConfig.Base.InvitePower, inviteCode.Code, user.Username),
})
if err != nil {
tx.Rollback()
return user, err
}
// 添加邀请记录
err = tx.Create(&model.InviteLog{
InviterId: inviteCode.UserId,
UserId: user.Id,
Username: user.Username,
InviteCode: inviteCode.Code,
Remark: fmt.Sprintf("奖励 %d 算力", h.App.SysConfig.Base.InvitePower),
}).Error
if err != nil {
tx.Rollback()
return user, err
}
}
}
tx.Commit()
return user, nil
}
// doLogin 执行登录操作
func (h *UserHandler) doLogin(user *model.User, ip string) (string, error) {
// 更新最后登录时间和IP
user.LastLoginIp = ip
user.LastLoginAt = time.Now().Unix()
err := h.DB.Model(user).Updates(user).Error
if err != nil {
return "", fmt.Errorf("failed to update user: %v", err)
}
// 记录登录日志
h.DB.Create(&model.UserLoginLog{
UserId: user.Id,
Username: user.Username,
LoginIp: ip,
LoginAddress: utils.Ip2Region(h.ipSearcher, ip),
})
// 创建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.Id,
@@ -468,17 +414,42 @@ func (h *UserHandler) CLoginCallback(c *gin.Context) {
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
if err != nil {
resp.ERROR(c, "Failed to generate token, "+err.Error())
return
return "", fmt.Errorf("failed to generate token: %v", err)
}
// 保存到 redis
key := fmt.Sprintf("users/%d", user.Id)
if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
resp.ERROR(c, "error with save token: "+err.Error())
sessionKey := fmt.Sprintf("users/%d", user.Id)
if _, err = h.redis.Set(context.Background(), sessionKey, tokenString, 0).Result(); err != nil {
return "", fmt.Errorf("error with save token: %v", err)
}
return tokenString, nil
}
// WxLoginCallback 微信登录回调处理
func (h *UserHandler) WxLoginCallback(c *gin.Context) {
var data struct {
OpenID string `json:"openid"`
State string `json:"state"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
session["token"] = tokenString
resp.SUCCESS(c, session)
if data.OpenID == "" || data.State == "" {
resp.ERROR(c, "参数错误")
return
}
// 设置登录状态
status := service.LoginStatus{
Status: service.LoginStatusSuccess,
OpenID: data.OpenID,
}
h.wxLoginService.SetLoginStatus(data.State, status)
resp.SUCCESS(c, status)
}
// Session 获取/验证会话
@@ -742,11 +713,11 @@ func (h *UserHandler) SignIn(c *gin.Context) {
// 签到
h.levelDB.Put(key, true)
if h.App.SysConfig.DailyPower > 0 {
h.userService.IncreasePower(userId, h.App.SysConfig.DailyPower, model.PowerLog{
if h.App.SysConfig.Base.DailyPower > 0 {
h.userService.IncreasePower(userId, h.App.SysConfig.Base.DailyPower, model.PowerLog{
Type: types.PowerSignIn,
Model: "SignIn",
Remark: fmt.Sprintf("每日签到奖励,金额:%d", h.App.SysConfig.DailyPower),
Remark: fmt.Sprintf("每日签到奖励,金额:%d", h.App.SysConfig.Base.DailyPower),
})
}
resp.SUCCESS(c)
+54 -12
View File
@@ -10,8 +10,10 @@ package handler
import (
"fmt"
"geekai/core"
"geekai/core/middleware"
"geekai/core/types"
"geekai/service"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/service/video"
"geekai/store/model"
@@ -26,20 +28,37 @@ import (
type VideoHandler struct {
BaseHandler
videoService *video.Service
uploader *oss.UploaderManager
userService *service.UserService
videoService *video.Service
uploader *oss.UploaderManager
userService *service.UserService
moderationManager *moderation.ServiceManager
}
func NewVideoHandler(app *core.AppServer, db *gorm.DB, service *video.Service, uploader *oss.UploaderManager, userService *service.UserService) *VideoHandler {
func NewVideoHandler(app *core.AppServer, db *gorm.DB, service *video.Service, uploader *oss.UploaderManager, userService *service.UserService, moderationManager *moderation.ServiceManager) *VideoHandler {
return &VideoHandler{
BaseHandler: BaseHandler{
App: app,
DB: db,
},
videoService: service,
uploader: uploader,
userService: userService,
videoService: service,
uploader: uploader,
userService: userService,
moderationManager: moderationManager,
}
}
// RegisterRoutes 注册路由
func (h *VideoHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/video/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.POST("luma/create", h.LumaCreate)
group.POST("keling/create", h.KeLingCreate)
group.GET("list", h.List)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
}
}
@@ -62,13 +81,36 @@ func (h *VideoHandler) LumaCreate(c *gin.Context) {
return
}
if h.App.SysConfig.Moderation.Enable {
moderationResult, err := h.moderationManager.GetService().Moderate(data.Prompt)
if err != nil {
logger.Error("failed to moderate content: ", err)
}
if moderationResult.Flagged {
// 记录违规内容
moderation := model.Moderation{
UserId: h.GetLoginUserId(c),
Source: types.ModerationSourceVideo,
Input: data.Prompt,
Result: utils.JsonEncode(moderationResult),
}
err = h.DB.Create(&moderation).Error
if err != nil {
logger.Error("failed to save moderation: ", err)
}
resp.ERROR(c, "当前创作内容包含敏感词,请重新输入!")
return
}
}
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
return
}
if user.Power < h.App.SysConfig.LumaPower {
if user.Power < h.App.SysConfig.Base.LumaPower {
resp.ERROR(c, "您的算力不足,请充值后再试!")
return
}
@@ -85,14 +127,14 @@ func (h *VideoHandler) LumaCreate(c *gin.Context) {
Type: types.VideoLuma,
Prompt: data.Prompt,
Params: params,
TranslateModelId: h.App.SysConfig.AssistantModelId,
TranslateModelId: h.App.SysConfig.Base.AssistantModelId,
}
// 插入数据库
job := model.VideoJob{
UserId: uint(userId),
Type: types.VideoLuma,
Prompt: data.Prompt,
Power: h.App.SysConfig.LumaPower,
Power: h.App.SysConfig.Base.LumaPower,
TaskInfo: utils.JsonEncode(task),
}
tx := h.DB.Create(&job)
@@ -147,7 +189,7 @@ func (h *VideoHandler) KeLingCreate(c *gin.Context) {
// 计算当前任务所需算力
key := fmt.Sprintf("%s_%s_%s", data.Model, data.Mode, data.Duration)
power := h.App.SysConfig.KeLingPowers[key]
power := h.App.SysConfig.Base.KeLingPowers[key]
if power == 0 {
resp.ERROR(c, "当前模型暂不支持")
return
@@ -181,7 +223,7 @@ func (h *VideoHandler) KeLingCreate(c *gin.Context) {
Type: types.VideoKeLing,
Prompt: data.Prompt,
Params: params,
TranslateModelId: h.App.SysConfig.AssistantModelId,
TranslateModelId: h.App.SysConfig.Base.AssistantModelId,
Channel: data.Channel,
}
// 插入数据库
+109 -255
View File
@@ -19,10 +19,12 @@ import (
"geekai/service/dalle"
"geekai/service/jimeng"
"geekai/service/mj"
"geekai/service/moderation"
"geekai/service/oss"
"geekai/service/payment"
"geekai/service/sd"
"geekai/service/sms"
"geekai/service/sora"
"geekai/service/suno"
"geekai/service/video"
"geekai/store"
@@ -30,7 +32,7 @@ import (
"log"
"os"
"os/signal"
"strconv"
"runtime/debug"
"syscall"
"time"
@@ -71,15 +73,16 @@ func main() {
if configFile == "" {
configFile = "config.toml"
}
debug, _ := strconv.ParseBool(os.Getenv("APP_DEBUG"))
logger.Info("Loading config file: ", configFile)
if !debug {
defer func() {
if err := recover(); err != nil {
logger.Error("Panic Error:", err)
defer func() {
if err := recover(); err != nil {
logger.Error("Panic Error:", err)
// 打印堆栈信息
if os.Getenv("GEEKAI_DEBUG") == "true" {
debug.PrintStack()
}
}()
}
}
}()
app := fx.New(
// 初始化配置应用配置
@@ -89,16 +92,16 @@ func main() {
log.Fatal(err)
}
config.Path = configFile
if debug {
_ = core.SaveConfig(config)
}
return config
}),
// 创建应用服务
fx.Provide(core.NewServer),
// 初始化
fx.Invoke(func(s *core.AppServer, client *redis.Client) {
s.Init(debug, client)
s.Init(client)
}),
fx.Provide(func(db *gorm.DB) *types.SystemConfig {
return core.LoadSystemConfig(db)
}),
// 初始化数据库
@@ -126,7 +129,7 @@ func main() {
}),
// 创建控制器
fx.Provide(handler.NewChatRoleHandler),
fx.Provide(handler.NewChatAppHandler),
fx.Provide(handler.NewUserHandler),
fx.Provide(handler.NewChatHandler),
fx.Provide(handler.NewNetHandler),
@@ -143,6 +146,12 @@ func main() {
fx.Provide(handler.NewPowerLogHandler),
fx.Provide(handler.NewJimengHandler),
fx.Provide(service.NewMigrationService),
fx.Invoke(func(migrationService *service.MigrationService) {
migrationService.StartMigrate()
}),
// 管理后台控制器
fx.Provide(admin.NewConfigHandler),
fx.Provide(admin.NewAdminHandler),
fx.Provide(admin.NewApiKeyHandler),
@@ -153,16 +162,12 @@ func main() {
fx.Provide(admin.NewChatModelHandler),
fx.Provide(admin.NewProductHandler),
fx.Provide(admin.NewOrderHandler),
fx.Provide(admin.NewChatHandler),
fx.Provide(admin.NewPowerLogHandler),
fx.Provide(admin.NewAdminJimengHandler),
// 创建服务
fx.Provide(sms.NewSendServiceManager),
fx.Provide(func(config *types.AppConfig) *service.CaptchaService {
return service.NewCaptchaService(config.ApiConfig)
}),
fx.Provide(oss.NewUploaderManager),
// 邮件服务
fx.Provide(service.NewSmtpService),
// Dalle 服务
fx.Provide(dalle.NewService),
fx.Invoke(func(s *dalle.Service) {
s.Run()
@@ -170,14 +175,6 @@ func main() {
s.CheckTaskStatus()
}),
// 邮件服务
fx.Provide(service.NewSmtpService),
// License 服务
fx.Provide(service.NewLicenseService),
fx.Invoke(func(licenseService *service.LicenseService) {
licenseService.SyncLicense()
}),
// MidJourney service pool
fx.Provide(mj.NewService),
fx.Provide(mj.NewClient),
@@ -187,6 +184,9 @@ func main() {
s.DownloadImages()
}),
// Sora service
fx.Provide(sora.NewSoraService),
// Stable Diffusion 机器人
fx.Provide(sd.NewService),
fx.Invoke(func(s *sd.Service, config *types.AppConfig) {
@@ -208,302 +208,175 @@ func main() {
}),
// 即梦AI 服务
fx.Provide(jimeng.NewClient),
fx.Provide(jimeng.NewService),
fx.Invoke(func(service *jimeng.Service) {
service.Start()
}),
fx.Provide(service.NewUserService),
fx.Provide(payment.NewAlipayService),
fx.Provide(payment.NewHuPiPay),
fx.Provide(payment.NewJPayService),
fx.Provide(payment.NewWechatService),
fx.Provide(service.NewSnowflake),
fx.Provide(service.NewXXLJobExecutor),
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
if config.XXLConfig.Enabled {
go func() {
log.Fatal(exec.Run())
}()
}
// 创建短信服务
fx.Provide(sms.NewAliYunSmsService),
fx.Provide(sms.NewBaoSmsService),
fx.Provide(sms.NewSmsManager),
fx.Provide(service.NewCaptchaService),
fx.Provide(service.NewWxLoginService),
// 支付服务
fx.Provide(payment.NewAlipayService),
fx.Provide(payment.NewEPayService),
fx.Provide(payment.NewWxpayService),
// 文件上传服务
fx.Provide(oss.NewLocalStorage),
fx.Provide(oss.NewMiniOss),
fx.Provide(oss.NewQiNiuOss),
fx.Provide(oss.NewAliYunOss),
fx.Provide(oss.NewUploaderManager),
// 用户服务
fx.Provide(service.NewUserService),
// 文本审查服务
fx.Provide(moderation.NewGiteeAIModeration),
fx.Provide(moderation.NewBaiduAIModeration),
fx.Provide(moderation.NewTencentAIModeration),
fx.Provide(moderation.NewServiceManager),
fx.Provide(admin.NewModerationHandler),
fx.Invoke(func(s *core.AppServer, h *admin.ModerationHandler) {
h.RegisterRoutes()
}),
// 注册路由
fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) {
group := s.Engine.Group("/api/app/")
group.GET("list", h.List)
group.GET("list/user", h.ListByUser)
group.POST("update", h.UpdateRole)
fx.Invoke(func(s *core.AppServer, h *handler.ChatAppHandler) {
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.UserHandler) {
group := s.Engine.Group("/api/user/")
group.POST("register", h.Register)
group.POST("login", h.Login)
group.GET("logout", h.Logout)
group.GET("session", h.Session)
group.GET("profile", h.Profile)
group.POST("profile/update", h.ProfileUpdate)
group.POST("password", h.UpdatePass)
group.POST("bind/mobile", h.BindMobile)
group.POST("bind/email", h.BindEmail)
group.POST("resetPass", h.ResetPass)
group.GET("clogin", h.CLogin)
group.GET("clogin/callback", h.CLoginCallback)
group.GET("signin", h.SignIn)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.ChatHandler) {
group := s.Engine.Group("/api/chat/")
group.Any("message", h.Chat)
group.GET("list", h.List)
group.GET("detail", h.Detail)
group.POST("update", h.Update)
group.GET("remove", h.Remove)
group.GET("history", h.History)
group.GET("clear", h.Clear)
group.POST("tokens", h.Tokens)
group.GET("stop", h.StopGenerate)
group.POST("tts", h.TextToSpeech)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.NetHandler) {
s.Engine.POST("/api/upload", h.Upload)
s.Engine.POST("/api/upload/list", h.List)
s.Engine.GET("/api/upload/remove", h.Remove)
s.Engine.GET("/api/download", h.Download)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
group := s.Engine.Group("/api/sms/")
group.POST("code", h.SendCode)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.CaptchaHandler) {
group := s.Engine.Group("/api/captcha/")
group.GET("get", h.Get)
group.POST("check", h.Check)
group.GET("slide/get", h.SlideGet)
group.POST("slide/check", h.SlideCheck)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.RedeemHandler) {
group := s.Engine.Group("/api/redeem/")
group.POST("verify", h.Verify)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.MidJourneyHandler) {
group := s.Engine.Group("/api/mj/")
group.POST("image", h.Image)
group.POST("upscale", h.Upscale)
group.POST("variation", h.Variation)
group.GET("jobs", h.JobList)
group.GET("imgWall", h.ImgWall)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
group := s.Engine.Group("/api/sd")
group.POST("image", h.Image)
group.GET("jobs", h.JobList)
group.GET("imgWall", h.ImgWall)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
group := s.Engine.Group("/api/config/")
group.GET("get", h.Get)
group.GET("license", h.License)
h.RegisterRoutes()
}),
// 管理后台控制器
// 管理后台路由注册
fx.Invoke(func(s *core.AppServer, h *admin.ConfigHandler) {
group := s.Engine.Group("/api/admin/config")
group.POST("update", h.Update)
group.GET("get", h.Get)
group.POST("active", h.Active)
group.GET("fixData", h.FixData)
group.GET("license", h.GetLicense)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.ManagerHandler) {
group := s.Engine.Group("/api/admin/")
group.POST("login", h.Login)
group.GET("logout", h.Logout)
group.GET("session", h.Session)
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("enable", h.Enable)
group.GET("remove", h.Remove)
group.POST("resetPass", h.ResetPass)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.ApiKeyHandler) {
group := s.Engine.Group("/api/admin/apikey/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.UserHandler) {
group := s.Engine.Group("/api/admin/user/")
group.GET("list", h.List)
group.POST("save", h.Save)
group.GET("remove", h.Remove)
group.GET("loginLog", h.LoginLog)
group.GET("genLoginLink", h.GenLoginLink)
group.POST("resetPass", h.ResetPass)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.ChatAppHandler) {
group := s.Engine.Group("/api/admin/role/")
group.GET("list", h.List)
group.POST("save", h.Save)
group.POST("sort", h.Sort)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.RedeemHandler) {
group := s.Engine.Group("/api/admin/redeem/")
group.GET("list", h.List)
group.POST("create", h.Create)
group.POST("set", h.Set)
group.GET("remove", h.Remove)
group.POST("export", h.Export)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.DashboardHandler) {
group := s.Engine.Group("/api/admin/dashboard/")
group.GET("stats", h.Stats)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.ChatModelHandler) {
group := s.Engine.Group("/api/model/")
group.GET("list", h.List)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.ChatModelHandler) {
group := s.Engine.Group("/api/admin/model/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("set", h.Set)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
group := s.Engine.Group("/api/payment/")
group.POST("doPay", h.Pay)
group.GET("payWays", h.GetPayWays)
group.POST("notify/alipay", h.AlipayNotify)
group.GET("notify/geek", h.GeekPayNotify)
group.POST("notify/wechat", h.WechatPayNotify)
group.POST("notify/hupi", h.HuPiPayNotify)
h.RegisterRoutes()
h.StartSyncOrders()
}),
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
group := s.Engine.Group("/api/admin/product/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("enable", h.Enable)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.OrderHandler) {
group := s.Engine.Group("/api/admin/order/")
group.POST("list", h.List)
group.GET("remove", h.Remove)
group.GET("clear", h.Clear)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.OrderHandler) {
group := s.Engine.Group("/api/order/")
group.GET("list", h.List)
group.GET("query", h.Query)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.ProductHandler) {
group := s.Engine.Group("/api/product/")
group.GET("list", h.List)
h.RegisterRoutes()
}),
fx.Provide(handler.NewInviteHandler),
fx.Invoke(func(s *core.AppServer, h *handler.InviteHandler) {
group := s.Engine.Group("/api/invite/")
group.GET("code", h.Code)
group.GET("list", h.List)
group.GET("hits", h.Hits)
h.RegisterRoutes()
}),
fx.Provide(admin.NewFunctionHandler),
fx.Invoke(func(s *core.AppServer, h *admin.FunctionHandler) {
group := s.Engine.Group("/api/admin/function/")
group.POST("save", h.Save)
group.POST("set", h.Set)
group.GET("list", h.List)
group.GET("remove", h.Remove)
group.GET("token", h.GenToken)
h.RegisterRoutes()
}),
fx.Provide(admin.NewUploadHandler),
fx.Invoke(func(s *core.AppServer, h *admin.UploadHandler) {
s.Engine.POST("/api/admin/upload", h.Upload)
h.RegisterRoutes()
}),
fx.Provide(handler.NewFunctionHandler),
fx.Invoke(func(s *core.AppServer, h *handler.FunctionHandler) {
group := s.Engine.Group("/api/function/")
group.POST("weibo", h.WeiBo)
group.POST("zaobao", h.ZaoBao)
group.POST("dalle3", h.Dall3)
group.POST("websearch", h.WebSearch)
group.GET("list", h.List)
h.RegisterRoutes()
}),
fx.Provide(admin.NewChatHandler),
fx.Invoke(func(s *core.AppServer, h *admin.ChatHandler) {
group := s.Engine.Group("/api/admin/chat/")
group.POST("list", h.List)
group.POST("message", h.Messages)
group.GET("history", h.History)
group.GET("remove", h.RemoveChat)
group.GET("message/remove", h.RemoveMessage)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *handler.PowerLogHandler) {
group := s.Engine.Group("/api/powerLog/")
group.POST("list", h.List)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, h *admin.PowerLogHandler) {
group := s.Engine.Group("/api/admin/powerLog/")
group.POST("list", h.List)
h.RegisterRoutes()
}),
fx.Provide(admin.NewMenuHandler),
fx.Invoke(func(s *core.AppServer, h *admin.MenuHandler) {
group := s.Engine.Group("/api/admin/menu/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("enable", h.Enable)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
h.RegisterRoutes()
}),
fx.Provide(handler.NewMenuHandler),
fx.Invoke(func(s *core.AppServer, h *handler.MenuHandler) {
group := s.Engine.Group("/api/menu/")
group.GET("list", h.List)
h.RegisterRoutes()
}),
fx.Provide(handler.NewMarkMapHandler),
fx.Invoke(func(s *core.AppServer, h *handler.MarkMapHandler) {
s.Engine.POST("/api/markMap/gen", h.Generate)
h.RegisterRoutes()
}),
fx.Provide(handler.NewDallJobHandler),
fx.Invoke(func(s *core.AppServer, h *handler.DallJobHandler) {
group := s.Engine.Group("/api/dall")
group.POST("image", h.Image)
group.GET("jobs", h.JobList)
group.GET("imgWall", h.ImgWall)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
group.GET("models", h.GetModels)
h.RegisterRoutes()
}),
fx.Provide(handler.NewSunoHandler),
fx.Invoke(func(s *core.AppServer, h *handler.SunoHandler) {
group := s.Engine.Group("/api/suno")
group.POST("create", h.Create)
group.GET("list", h.List)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
group.POST("update", h.Update)
group.GET("detail", h.Detail)
group.GET("play", h.Play)
h.RegisterRoutes()
}),
fx.Provide(handler.NewVideoHandler),
fx.Invoke(func(s *core.AppServer, h *handler.VideoHandler) {
group := s.Engine.Group("/api/video")
group.POST("luma/create", h.LumaCreate)
group.POST("keling/create", h.KeLingCreate)
group.GET("list", h.List)
group.GET("remove", h.Remove)
group.GET("publish", h.Publish)
h.RegisterRoutes()
}),
// 即梦AI 路由
@@ -515,30 +388,19 @@ func main() {
}),
fx.Provide(admin.NewChatAppTypeHandler),
fx.Invoke(func(s *core.AppServer, h *admin.ChatAppTypeHandler) {
group := s.Engine.Group("/api/admin/app/type")
group.POST("save", h.Save)
group.GET("list", h.List)
group.GET("remove", h.Remove)
group.POST("enable", h.Enable)
group.POST("sort", h.Sort)
h.RegisterRoutes()
}),
fx.Provide(handler.NewChatAppTypeHandler),
fx.Invoke(func(s *core.AppServer, h *handler.ChatAppTypeHandler) {
group := s.Engine.Group("/api/app/type")
group.GET("list", h.List)
h.RegisterRoutes()
}),
fx.Provide(handler.NewTestHandler),
fx.Invoke(func(s *core.AppServer, h *handler.TestHandler) {
group := s.Engine.Group("/api/test")
group.Any("sse", h.PostTest, h.SseTest)
h.RegisterRoutes()
}),
fx.Provide(handler.NewPromptHandler),
fx.Invoke(func(s *core.AppServer, h *handler.PromptHandler) {
group := s.Engine.Group("/api/prompt")
group.POST("/lyric", h.Lyric)
group.POST("/image", h.Image)
group.POST("/video", h.Video)
group.POST("/meta", h.MetaPrompt)
h.RegisterRoutes()
}),
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
go func() {
@@ -563,23 +425,15 @@ func main() {
}),
fx.Provide(admin.NewImageHandler),
fx.Invoke(func(s *core.AppServer, h *admin.ImageHandler) {
group := s.Engine.Group("/api/admin/image")
group.POST("/list/mj", h.MjList)
group.POST("/list/sd", h.SdList)
group.POST("/list/dall", h.DallList)
group.GET("/remove", h.Remove)
h.RegisterRoutes()
}),
fx.Provide(admin.NewMediaHandler),
fx.Invoke(func(s *core.AppServer, h *admin.MediaHandler) {
group := s.Engine.Group("/api/admin/media")
group.POST("/suno", h.SunoList)
group.POST("/videos", h.Videos)
group.GET("/remove", h.Remove)
h.RegisterRoutes()
}),
fx.Provide(handler.NewRealtimeHandler),
fx.Invoke(func(s *core.AppServer, h *handler.RealtimeHandler) {
s.Engine.Any("/api/realtime", h.Connection)
s.Engine.POST("/api/realtime/voice", h.VoiceChat)
h.RegisterRoutes()
}),
)
// 启动应用程序
-38
View File
@@ -1,38 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDszCCApugAwIBAgIQICMRB0rBU2/rZJbfJGMYIzANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjMxMTA3MDYzNTQxWhcNMjQxMTA2MDYzNTQxWjCB
hDELMAkGA1UEBhMCQ04xHzAdBgNVBAoMFm1ib25meTkwMTVAc2FuZGJveC5jb20xDzANBgNVBAsM
BkFsaXBheTFDMEEGA1UEAww65pSv5LuY5a6dKOS4reWbvSnnvZHnu5zmioDmnK/mnInpmZDlhazl
j7gtMjA4ODcyMTAyMDc1MDU4MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKsoKcw5
sxaiyV7mpWzDtnQ1K518eQLP0+dJlZAf06aBep/Aj9DIqrba/k7DHt8dKQvILMLAMpN1+2IRxbaO
yxMa/laj3lZ1eHrB6F077O3D62oHcE3noZtXL0N1zZAxpmkNmYIHeLZS2oLMS4ANu47O/wpDC7BV
HjdpZugtdPJ4mxdCpM9GDdLs7W4s5QI4PUPK4skFNMFoKI+0cYP/9ju87UP//IHC/K510GWNl+Gn
Cvgag3AmiIB0utJNsGhxm6zT1T9tUWjW9iz/BxBKiPatsCX9VpPQzGnW7ZonRQtiZSokIlP2IPvl
H5DcwpWUz3/LUY0SmKxnKOEYeOOqCW8CAwEAAaMSMBAwDgYDVR0PAQH/BAQDAgTwMA0GCSqGSIb3
DQEBCwUAA4IBAQAtgxF2EzjOndEFxBUD9tFwcSt6XKGggOp52oft1pvynPg4ALTLafOtfEPDrFBH
PwpYrSu9s9C8NJtaA2HrlCfBjIuwEFTXiN+HPvS0SwSPKt9AXEiTcOF8vDcGamEen8QI4fo5Jia7
2VRKkerkww5/+FzSaVO7ZUKuL80M1QJStmAZc8kPPwdYOTTW2bGf8BcmSDL6SPElBkt7tCCRd4sn
+jq4cZ0yb2i77rBZCwHcTvfTqIBblPwLv4uGvg3+83BxIB5w6Kqp06bKEAPmobFY5IVHa+ON0/qi
BXxXr+WQ3piKRVQEN64+PTAjSc67Ix1umvpLl3Ko6Ry7NJmpDcUn
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDszCCApugAwIBAgIQIBkIGbgVxq210KxLJ+YA/TANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxJTAjBgNVBAsMHENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IHRlc3QxNjA0BgNVBAMMLUFudCBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgUjEgdGVzdDAeFw0xOTA4MTkxMTE2MDBaFw0yNDA4MDExMTE2MDBaMIGRMQswCQYDVQQGEwJD
TjEbMBkGA1UECgwSQW50IEZpbmFuY2lhbCB0ZXN0MSUwIwYDVQQLDBxDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSB0ZXN0MT4wPAYDVQQDDDVBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IENsYXNzIDIgUjEgdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMh4FKYO
ZyRQHD6eFbPKZeSAnrfjfU7xmS9Yoozuu+iuqZlb6Z0SPLUqqTZAFZejOcmr07ln/pwZxluqplxC
5+B48End4nclDMlT5HPrDr3W0frs6Xsa2ZNcyil/iKNB5MbGll8LRAxntsKvZZj6vUTMb705gYgm
VUMILwi/ZxKTQqBtkT/kQQ5y6nOZsj7XI5rYdz6qqOROrpvS/d7iypdHOMIM9Iz9DlL1mrCykbBi
t25y+gTeXmuisHUwqaRpwtCGK4BayCqxRGbNipe6W73EK9lBrrzNtTr9NaysesT/v+l25JHCL9tG
wpNr1oWFzk4IHVOg0ORiQ6SUgxZUTYcCAwEAAaMSMBAwDgYDVR0PAQH/BAQDAgTwMA0GCSqGSIb3
DQEBCwUAA4IBAQBWThEoIaQoBX2YeRY/I8gu6TYnFXtyuCljANnXnM38ft+ikhE5mMNgKmJYLHvT
yWWWgwHoSAWEuml7EGbE/2AK2h3k0MdfiWLzdmpPCRG/RJHk6UB1pMHPilI+c0MVu16OPpKbg5Vf
LTv7dsAB40AzKsvyYw88/Ezi1osTXo6QQwda7uefvudirtb8FcQM9R66cJxl3kt1FXbpYwheIm/p
j1mq64swCoIYu4NrsUYtn6CV542DTQMI5QdXkn+PzUUly8F6kDp+KpMNd0avfWNL5+O++z+F5Szy
1CPta1D7EQ/eYmMP+mOQ35oifWIoFCpN6qQVBS/Hob1J/UUyg7BW
-----END CERTIFICATE-----
-88
View File
@@ -1,88 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----
-19
View File
@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIQICMRB2LW76yahgdg3IFNPDANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjMxMTA3MDU0NjE5WhcNMjQxMTExMDU0NjE5WjBr
MQswCQYDVQQGEwJDTjEfMB0GA1UECgwWbWJvbmZ5OTAxNUBzYW5kYm94LmNvbTEPMA0GA1UECwwG
QWxpcGF5MSowKAYDVQQDDCEyMDg4NzIxMDIwNzUwNTgxLTkwMjEwMDAxMzE2NTgwMjMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxihQPf1Q+g9ArgM46shVqL5sbRha/df95D1PsWyEq
ANmWmG4zZ+ksYDVQrc4KzhSRoi56sm/7TDFYTmM6bW99e/nKW58WxyZB4ie5qA3F4n17psPyDqb8
IokcQmCphSFDaXQD6AoXoLNtTM0vAI2cWxAgebZ/vsrdj5Ntjt+Rp3NYMCk1i5xovHcfILzLEGbX
QXoT9fo5AhHotTWa6xHVLPUGY9qwLzQxHzBmvy5ZMfnOfJkm/mDisTSqAUB59F3dzU/1ARVkEZ1w
Mgb4XohWBw6iurQfbMnH2mIomAAwwZVFv+sXDbL9yMbSMo/SjVsTQprn0Q0EnwLo7nmmOM6HAgMB
AAGjEjAQMA4GA1UdDwEB/wQEAwIE8DANBgkqhkiG9w0BAQsFAAOCAQEAn3Y4/C1h9R6ONsBqX3/q
XfHX7yX1FM0Y1x48X3/Yxk6HivAkTukhhhVYVKJsbrbzRqHDp9vhAP/FR6o6pAevaYMmLov0VMXU
7oAuetgkaYEYkDuNen5/Hpdhqi2vTtdT+q9w8zHJd6MDQ0aoHgIxpLKw5vof2R1N4fwSgNXMiXE5
kmllKQMem/+on2p+Sj80/2asxryHIGlH87qPzkffv+kIOkZthbTApTFLLjdVri2QHGe8/cc4xy01
/9iR3IUzNahotT41lJ4bMevBY7XMAS3n5ekyABN/9ZRJqhWdXgmFCRN/u56qd6lDgu7R2M2QUoyc
LuW5DfgRItKlmUB7sw==
-----END CERTIFICATE-----
-1
View File
@@ -1 +0,0 @@
MIIEpQIBAAKCAQEAsYoUD39UPoPQK4DOOrIVai+bG0YWv3X/eQ9T7FshKgDZlphuM2fpLGA1UK3OCs4UkaIuerJv+0wxWE5jOm1vfXv5ylufFscmQeInuagNxeJ9e6bD8g6m/CKJHEJgqYUhQ2l0A+gKF6CzbUzNLwCNnFsQIHm2f77K3Y+TbY7fkadzWDApNYucaLx3HyC8yxBm10F6E/X6OQIR6LU1musR1Sz1BmPasC80MR8wZr8uWTH5znyZJv5g4rE0qgFAefRd3c1P9QEVZBGdcDIG+F6IVgcOorq0H2zJx9piKJgAMMGVRb/rFw2y/cjG0jKP0o1bE0Ka59ENBJ8C6O55pjjOhwIDAQABAoIBAFetNfz1R7hbxjlFshMAkVzQR8wvT9qbvl+dtzdZRcaFhu89NecDIP7+QDYor0FcxoGpU0TazDyRQyk2BQD8vHt+9zv9BVLtZLJSqoWgPbUFBi1DjS8EF2ka8RVYnn35NhUhhd7L//ftL88Bh673mfembQ9srDjoEy1Z01feoABAnCMkNFl986DmEwnarvEufXSDIgeN4ioMxha4NvfIPuI0zpVdV1O9sv+SGC+VEWZBtN3GNsaf4zS/f8FVGvTiU/Abz0gSw/iwSPHclDWQDTN3yFHf/tfqlzh0mH0WfhnuOBFWXzK+R7fbnM+asI9ttvzRcfpzgRGXdPcNcOv/6cECgYEA3DVqpi1k8MYfJixju6SG5gfyhM4VFksFmCMaNPgtatDMBKLMTgV/Ej6LXREojcy29uZl83F09pVlpd41eG39ULIPktixA/BqErQ2UaWh6kOxifycpu22Jh0r09hax6UgVrcBrrnCJEjcFsuJlrZvXQSzc3PBxjWy5gjabS5h9iECgYEAzmVAIh2frF01Y95zsLueAhhZwCtPanm6kf7ivR4r1plIX3b2sNRhWGmEHFgaCE6Braa0ogQ73Hd26kw4ZW+D6QMGC/zjCBEzDLLf++SjdVUHiY5AR4WHqXzq1jdAlsVyo9R661oAOp3lhiJVGLNXkHyEfEVPHsaxJh4osYSbX6cCgYEAx32Qx0i6eDFTyLZQB46uMrgiaVN04QRH5iJuvGvUYT8UhGKjaU8rZfDJOh+wOH2rhxMEaz1uc3C2bERY9mfWI4Ob/jFWc7YZsiYWS3Mcsuhubw4tMECLUg39RWZsHw8ls8kIuixIh6yFzhTH6YQOcRswIrhMZG8DScfdcSmiz2ECgYEAkWP1t5KSpkLKl11etcKUXfl1T8+yk9jIOowIgRw92WAFAWq2AH67TCKYM7dEL1HOO9tRJ0hAOt/U3ttuZtYVYBEHM26jJ02mXm2rJrA7DS4mrxmL4lYH6LbcXqZxU0Qnq4zEQgIWYzRTORf6Rfof1uJAGaJhR9bDd4yLMfGt2cUCgYEAo216Y61xOHUTA4AF1eekk+r+uOcQgQDvLXfs9FkDdJLk0mPG48/+eIYpPFnANJ/riF/DWOp8WGEe2IzA9yUFexzDbNQK8ha9kGcxaSAyiCwzjZ/t9/+hScDSV8kNqWSRSisu/YOFleEHbokT6mbLZ+gdqES8mUUanaEBzRQYGxo=
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

+24 -28
View File
@@ -8,35 +8,38 @@ package service
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"errors"
"fmt"
"geekai/core/types"
"github.com/imroc/req/v3"
"time"
"github.com/imroc/req/v3"
)
type CaptchaService struct {
config types.ApiConfig
config types.CaptchaConfig
client *req.Client
}
func NewCaptchaService(config types.ApiConfig) *CaptchaService {
func NewCaptchaService(sysConfig *types.SystemConfig) *CaptchaService {
return &CaptchaService{
config: config,
config: sysConfig.Captcha,
client: req.C().SetTimeout(10 * time.Second),
}
}
func (s *CaptchaService) Get() (interface{}, error) {
if s.config.Token == "" {
return nil, errors.New("无效的 API Token")
}
func (s *CaptchaService) UpdateConfig(config types.CaptchaConfig) {
s.config = config
}
url := fmt.Sprintf("%s/api/captcha/get", s.config.ApiURL)
func (s *CaptchaService) GetConfig() types.CaptchaConfig {
return s.config
}
func (s *CaptchaService) Get() (any, error) {
url := fmt.Sprintf("%s/api/captcha/get", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().
SetHeader("AppId", s.config.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.Token)).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.ApiKey)).
SetSuccessResult(&res).Get(url)
if err != nil || r.IsErrorState() {
return nil, fmt.Errorf("请求 API 失败:%v", err)
@@ -49,12 +52,11 @@ func (s *CaptchaService) Get() (interface{}, error) {
return res.Data, nil
}
func (s *CaptchaService) Check(data interface{}) bool {
url := fmt.Sprintf("%s/api/captcha/check", s.config.ApiURL)
func (s *CaptchaService) Check(data any) bool {
url := fmt.Sprintf("%s/api/captcha/check", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().
SetHeader("AppId", s.config.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.Token)).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.ApiKey)).
SetBodyJsonMarshal(data).
SetSuccessResult(&res).Post(url)
if err != nil || r.IsErrorState() {
@@ -68,16 +70,11 @@ func (s *CaptchaService) Check(data interface{}) bool {
return true
}
func (s *CaptchaService) SlideGet() (interface{}, error) {
if s.config.Token == "" {
return nil, errors.New("无效的 API Token")
}
url := fmt.Sprintf("%s/api/captcha/slide/get", s.config.ApiURL)
func (s *CaptchaService) SlideGet() (any, error) {
url := fmt.Sprintf("%s/api/captcha/slide/get", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().
SetHeader("AppId", s.config.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.Token)).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.ApiKey)).
SetSuccessResult(&res).Get(url)
if err != nil || r.IsErrorState() {
return nil, fmt.Errorf("请求 API 失败:%v", err)
@@ -90,12 +87,11 @@ func (s *CaptchaService) SlideGet() (interface{}, error) {
return res.Data, nil
}
func (s *CaptchaService) SlideCheck(data interface{}) bool {
url := fmt.Sprintf("%s/api/captcha/slide/check", s.config.ApiURL)
func (s *CaptchaService) SlideCheck(data any) bool {
url := fmt.Sprintf("%s/api/captcha/slide/check", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().
SetHeader("AppId", s.config.AppId).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.Token)).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.ApiKey)).
SetBodyJsonMarshal(data).
SetSuccessResult(&res).Post(url)
if err != nil || r.IsErrorState() {
-333
View File
@@ -1,333 +0,0 @@
package crawler
import (
"context"
"errors"
"fmt"
"geekai/logger"
"net/url"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/proto"
)
// Service 网络爬虫服务
type Service struct {
browser *rod.Browser
}
// NewService 创建一个新的爬虫服务
func NewService() (*Service, error) {
// 启动浏览器
path, _ := launcher.LookPath()
u := launcher.New().Bin(path).
Headless(true). // 无头模式
Set("disable-web-security", ""). // 禁用网络安全限制
Set("disable-gpu", ""). // 禁用 GPU 加速
Set("no-sandbox", ""). // 禁用沙箱模式
Set("disable-setuid-sandbox", ""). // 禁用 setuid 沙箱
MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
return &Service{
browser: browser,
}, nil
}
// SearchResult 搜索结果
type SearchResult struct {
Title string `json:"title"` // 标题
URL string `json:"url"` // 链接
Content string `json:"content"` // 内容摘要
}
// WebSearch 网络搜索
func (s *Service) WebSearch(keyword string, maxPages int) ([]SearchResult, error) {
if keyword == "" {
return nil, errors.New("搜索关键词不能为空")
}
if maxPages <= 0 {
maxPages = 1
}
if maxPages > 10 {
maxPages = 10 // 最多搜索 10 页
}
results := make([]SearchResult, 0)
// 使用百度搜索
searchURL := fmt.Sprintf("https://www.baidu.com/s?wd=%s", url.QueryEscape(keyword))
// 设置页面超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 创建页面
page := s.browser.MustPage()
defer page.MustClose()
// 设置视口大小
err := page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{
Width: 1280,
Height: 800,
})
if err != nil {
return nil, fmt.Errorf("设置视口失败: %v", err)
}
// 导航到搜索页面
err = page.Context(ctx).Navigate(searchURL)
if err != nil {
return nil, fmt.Errorf("导航到搜索页面失败: %v", err)
}
// 等待搜索结果加载完成
err = page.WaitLoad()
if err != nil {
return nil, fmt.Errorf("等待页面加载完成失败: %v", err)
}
// 分析当前页面的搜索结果
for i := 0; i < maxPages; i++ {
if i > 0 {
// 点击下一页按钮
nextPage, err := page.Element("a.n")
if err != nil || nextPage == nil {
break // 没有下一页
}
err = nextPage.Click(proto.InputMouseButtonLeft, 1)
if err != nil {
break // 点击下一页失败
}
// 等待新页面加载
err = page.WaitLoad()
if err != nil {
break
}
}
// 提取搜索结果
resultElements, err := page.Elements(".result, .c-container")
if err != nil || resultElements == nil {
continue
}
for _, result := range resultElements {
// 获取标题
titleElement, err := result.Element("h3, .t")
if err != nil || titleElement == nil {
continue
}
title, err := titleElement.Text()
if err != nil {
continue
}
// 获取 URL
linkElement, err := titleElement.Element("a")
if err != nil || linkElement == nil {
continue
}
href, err := linkElement.Attribute("href")
if err != nil || href == nil {
continue
}
// 获取内容摘要 - 尝试多个可能的选择器
var contentElement *rod.Element
var content string
// 尝试多个可能的选择器来适应不同版本的百度搜索结果
selectors := []string{".content-right_8Zs40", ".c-abstract", ".content_LJ0WN", ".content"}
for _, selector := range selectors {
contentElement, err = result.Element(selector)
if err == nil && contentElement != nil {
content, _ = contentElement.Text()
if content != "" {
break
}
}
}
// 如果所有选择器都失败,尝试直接从结果块中提取文本
if content == "" {
// 获取结果元素的所有文本
fullText, err := result.Text()
if err == nil && fullText != "" {
// 简单处理:从全文中移除标题,剩下的可能是摘要
fullText = strings.Replace(fullText, title, "", 1)
// 清理文本
content = strings.TrimSpace(fullText)
// 限制内容长度
if len(content) > 200 {
content = content[:200] + "..."
}
}
}
// 添加到结果集
results = append(results, SearchResult{
Title: title,
URL: *href,
Content: content,
})
// 限制结果数量,每页最多 10 条
if len(results) >= 10*maxPages {
break
}
}
}
// 获取真实 URL(百度搜索结果中的 URL 是短链接,需要跳转获取真实 URL)
for i, result := range results {
realURL, err := s.getRedirectURL(result.URL)
if err == nil && realURL != "" {
results[i].URL = realURL
}
}
return results, nil
}
// 获取真实 URL
func (s *Service) getRedirectURL(shortURL string) (string, error) {
// 创建页面
page, err := s.browser.Page(proto.TargetCreateTarget{URL: ""})
if err != nil {
return shortURL, err // 返回原始URL
}
defer func() {
_ = page.Close()
}()
// 导航到短链接
err = page.Navigate(shortURL)
if err != nil {
return shortURL, err // 返回原始URL
}
// 等待重定向完成
time.Sleep(2 * time.Second)
// 获取当前 URL
info, err := page.Info()
if err != nil {
return shortURL, err // 返回原始URL
}
return info.URL, nil
}
// Close 关闭浏览器
func (s *Service) Close() error {
if s.browser != nil {
err := s.browser.Close()
s.browser = nil
return err
}
return nil
}
// SearchWeb 封装的搜索方法
func SearchWeb(keyword string, maxPages int) (string, error) {
// 添加panic恢复机制
defer func() {
if r := recover(); r != nil {
log := logger.GetLogger()
log.Errorf("爬虫服务崩溃: %v", r)
}
}()
service, err := NewService()
if err != nil {
return "", fmt.Errorf("创建爬虫服务失败: %v", err)
}
defer service.Close()
// 设置超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
// 使用goroutine和通道来处理超时
resultChan := make(chan []SearchResult, 1)
errChan := make(chan error, 1)
go func() {
results, err := service.WebSearch(keyword, maxPages)
if err != nil {
errChan <- err
return
}
resultChan <- results
}()
// 等待结果或超时
select {
case <-ctx.Done():
return "", fmt.Errorf("搜索超时: %v", ctx.Err())
case err := <-errChan:
return "", fmt.Errorf("搜索失败: %v", err)
case results := <-resultChan:
if len(results) == 0 {
return "未找到关于 \"" + keyword + "\" 的相关搜索结果", nil
}
// 格式化结果
var builder strings.Builder
builder.WriteString(fmt.Sprintf("为您找到关于 \"%s\" 的 %d 条搜索结果:\n\n", keyword, len(results)))
for i, result := range results {
// // 尝试打开链接获取实际内容
// page := service.browser.MustPage()
// defer page.MustClose()
// // 设置页面超时
// pageCtx, pageCancel := context.WithTimeout(context.Background(), 10*time.Second)
// defer pageCancel()
// // 导航到目标页面
// err := page.Context(pageCtx).Navigate(result.URL)
// if err == nil {
// // 等待页面加载
// _ = page.WaitLoad()
// // 获取页面标题
// title, err := page.Eval("() => document.title")
// if err == nil && title.Value.String() != "" {
// result.Title = title.Value.String()
// }
// // 获取页面主要内容
// if content, err := page.Element("body"); err == nil {
// if text, err := content.Text(); err == nil {
// // 清理并截取内容
// text = strings.TrimSpace(text)
// if len(text) > 200 {
// text = text[:200] + "..."
// }
// result.Prompt = text
// }
// }
// }
builder.WriteString(fmt.Sprintf("%d. **%s**\n", i+1, result.Title))
builder.WriteString(fmt.Sprintf(" 链接: %s\n", result.URL))
if result.Content != "" {
builder.WriteString(fmt.Sprintf(" 摘要: %s\n", result.Content))
}
builder.WriteString("\n")
}
return builder.String(), nil
}
}
@@ -94,12 +94,12 @@ func (s *Service) Run() {
}
type imgReq struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
N int `json:"n,omitempty"`
Size string `json:"size,omitempty"`
Quality string `json:"quality,omitempty"`
Style string `json:"style,omitempty"`
Model string `json:"model"`
Image []string `json:"image,omitempty"`
Prompt string `json:"prompt"`
AspectRatio string `json:"aspect_ratio,omitempty"`
Size string `json:"size,omitempty"`
ResponseFormat string `json:"response_format,omitempty"`
}
type imgRes struct {
@@ -113,30 +113,21 @@ type imgRes struct {
type ErrRes struct {
Error struct {
Code interface{} `json:"code"`
Message string `json:"message"`
Param interface{} `json:"param"`
Type string `json:"type"`
Code any `json:"code"`
Message string `json:"message"`
Param any `json:"param"`
Type string `json:"type"`
} `json:"error"`
}
func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
logger.Debugf("绘画参数:%+v", task)
prompt := task.Prompt
// translate prompt
if utils.HasChinese(prompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.TranslatePromptTemplate, prompt), task.TranslateModelId)
if err == nil {
prompt = content
logger.Debugf("重写后提示词:%s", prompt)
}
}
var chatModel model.ChatModel
if task.ModelId > 0 {
s.db.Where("id", task.ModelId).First(&chatModel)
} else {
s.db.Where("value", task.ModelName).First(&chatModel)
s.db.Where("value", task.ModelValue).First(&chatModel)
}
// get image generation API KEY
@@ -159,13 +150,18 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
}
apiURL := fmt.Sprintf("%s/v1/images/generations", apiKey.ApiURL)
reqBody := imgReq{
Model: chatModel.Value,
Prompt: prompt,
N: 1,
Size: task.Size,
Style: task.Style,
Quality: task.Quality,
Model: chatModel.Value,
Prompt: task.Prompt,
AspectRatio: task.AspectRatio,
Size: task.Size,
ResponseFormat: "url",
}
// 图片编辑
if len(task.Image) > 0 {
reqBody.Image = task.Image
}
logger.Infof("Channel:%s, API KEY:%s, BODY: %+v", apiURL, apiKey.Value, reqBody)
r, err := s.httpClient.R().SetHeader("Body-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value).
@@ -186,9 +182,9 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
// update the api key last use time
s.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
var imgURL string
var data = map[string]interface{}{
var data = map[string]any{
"progress": 100,
"prompt": prompt,
"prompt": task.Prompt,
}
// 如果返回的是base64,则需要上传到oss
if res.Data[0].B64Json != "" {
@@ -210,11 +206,7 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
var content string
if sync {
imgURL, err := s.downloadImage(task.Id, res.Data[0].Url)
if err != nil {
return "", fmt.Errorf("error with download image: %v", err)
}
content = fmt.Sprintf("```\n%s\n```\n下面是我为你创作的图片:\n\n![](%s)\n", prompt, imgURL)
content = fmt.Sprintf("```\n%s\n```\n下面是我为你创作的图片:\n\n![](%s)\n", task.Prompt, imgURL)
}
return content, nil
+174 -32
View File
@@ -1,26 +1,42 @@
package jimeng
import (
"context"
"encoding/json"
"errors"
"fmt"
"geekai/core/types"
"net/http"
"net/url"
"strings"
"time"
"github.com/volcengine/volc-sdk-golang/base"
"github.com/volcengine/volc-sdk-golang/service/visual"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
"github.com/volcengine/volcengine-go-sdk/volcengine"
)
// Client 即梦API客户端
type Client struct {
visual *visual.Visual
config types.JimengConfig
}
// NewClient 创建即梦API客户端
func NewClient(accessKey, secretKey string) *Client {
func NewClient(sysConfig *types.SystemConfig) *Client {
client := &Client{}
client.UpdateConfig(sysConfig.Jimeng)
return client
}
func (c *Client) UpdateConfig(config types.JimengConfig) error {
// 使用官方SDK的visual实例
visualInstance := visual.NewInstance()
visualInstance.Client.SetAccessKey(accessKey)
visualInstance.Client.SetSecretKey(secretKey)
visualInstance.Client.SetAccessKey(config.AccessKey)
visualInstance.Client.SetSecretKey(config.SecretKey)
// 添加即梦AI专有的API配置
jimengApis := map[string]*base.ApiInfo{
@@ -40,6 +56,22 @@ func NewClient(accessKey, secretKey string) *Client {
"Version": []string{"2022-08-31"},
},
},
"CVSubmitTask": {
Method: http.MethodPost,
Path: "/",
Query: url.Values{
"Action": []string{"CVSubmitTask"},
"Version": []string{"2022-08-31"},
},
},
"CVGetResult": {
Method: http.MethodPost,
Path: "/",
Query: url.Values{
"Action": []string{"CVGetResult"},
"Version": []string{"2022-08-31"},
},
},
"CVProcess": {
Method: http.MethodPost,
Path: "/",
@@ -55,13 +87,52 @@ func NewClient(accessKey, secretKey string) *Client {
visualInstance.Client.ApiInfoList[name] = info
}
return &Client{
visual: visualInstance,
c.config = config
c.visual = visualInstance
return c.testConnection()
}
// GetErrorMessage 根据错误代码获取对应的错误信息
func GetErrorMessage(code int) string {
if message, exists := errorCodeMessages[code]; exists {
return message
}
return fmt.Sprintf("未知错误代码: %d", code)
}
// HandleResponseError 处理响应错误,根据错误代码返回详细的错误信息
func HandleResponseError(code int, message string) error {
if code == ECSuccess {
return nil
}
return errors.New(GetErrorMessage(code))
}
// testConnection 测试即梦AI连接
func (c *Client) testConnection() error {
// 使用一个简单的查询任务来测试连接
testReq := &QueryTaskRequest{
ReqKey: "test_connection",
TaskId: "test_task_id_12345",
}
_, err := c.QueryTask(testReq, ASyncActionGetResult)
// 即使任务不存在,只要不是认证错误就说明连接正常
if err != nil {
// 检查是否是认证错误
if strings.Contains(err.Error(), "InvalidAccessKey") {
return fmt.Errorf("认证失败,请检查AccessKey和SecretKey是否正确")
}
// 其他错误(如任务不存在)说明连接正常
return nil
}
return nil
}
// SubmitTask 提交异步任务
func (c *Client) SubmitTask(req *SubmitTaskRequest) (*SubmitTaskResponse, error) {
func (c *Client) SubmitTask(req map[string]any) (*SubmitTaskResponse, error) {
// 直接将请求转为map[string]interface{}
reqBodyBytes, err := json.Marshal(req)
if err != nil {
@@ -70,9 +141,14 @@ func (c *Client) SubmitTask(req *SubmitTaskRequest) (*SubmitTaskResponse, error)
// 直接使用序列化后的字节
jsonBody := reqBodyBytes
action := ASyncActionSubmit
if v, ok := req["action"]; ok {
action = v.(string)
delete(req, "action")
}
// 调用SDK的JSON方法
respBody, statusCode, err := c.visual.Client.Json("CVSync2AsyncSubmitTask", nil, string(jsonBody))
respBody, statusCode, err := c.visual.Client.Json(action, nil, string(jsonBody))
if err != nil {
return nil, fmt.Errorf("submit task failed (status: %d): %w", statusCode, err)
}
@@ -85,11 +161,70 @@ func (c *Client) SubmitTask(req *SubmitTaskRequest) (*SubmitTaskResponse, error)
return nil, fmt.Errorf("unmarshal response failed: %w", err)
}
// 检查响应错误代码
if err := HandleResponseError(result.Code, result.Message); err != nil {
return nil, err
}
return &result, nil
}
// 识别数字人主体
func (c *Client) AvatarRecognition(imgUrl string, reqKey string) error {
params := map[string]any{
"image_url": imgUrl,
"req_key": reqKey,
}
reqBodyBytes, err := json.Marshal(params)
if err != nil {
return fmt.Errorf("marshal request failed: %w", err)
}
// 调用SDK的JSON方法
respBody, statusCode, err := c.visual.Client.Json(SyncActionSubmit, nil, string(reqBodyBytes))
if err != nil {
return fmt.Errorf("submit task failed (status: %d): %w", statusCode, err)
}
// 解析响应
var result SubmitTaskResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return fmt.Errorf("unmarshal response failed: %w", err)
}
// 检查响应错误代码
if err := HandleResponseError(result.Code, result.Message); err != nil {
return err
}
// 等待任务完成
for {
resp, err := c.QueryTask(&QueryTaskRequest{
ReqKey: reqKey,
TaskId: result.Data.TaskId,
}, SyncActionGetResult)
if err != nil {
return fmt.Errorf("query task failed: %w", err)
}
if resp.Data.Status != types.JMTaskStatusDone {
time.Sleep(time.Second * 3)
continue
}
var respData map[string]int
if err := json.Unmarshal([]byte(resp.Data.RespData), &respData); err != nil {
return fmt.Errorf("unmarshal response failed: %w", err)
}
logger.Debugf("Jimeng AvatarRecognition Response: %+v", resp)
if respData["status"] == 1 {
return nil
} else {
return errors.New("不包含人、类人、拟人等主体")
}
}
}
// QueryTask 查询任务结果
func (c *Client) QueryTask(req *QueryTaskRequest) (*QueryTaskResponse, error) {
func (c *Client) QueryTask(req *QueryTaskRequest, action string) (*QueryTaskResponse, error) {
// 序列化请求
jsonBody, err := json.Marshal(req)
if err != nil {
@@ -97,7 +232,7 @@ func (c *Client) QueryTask(req *QueryTaskRequest) (*QueryTaskResponse, error) {
}
// 调用SDK的JSON方法
respBody, statusCode, err := c.visual.Client.Json("CVSync2AsyncGetResult", nil, string(jsonBody))
respBody, statusCode, err := c.visual.Client.Json(action, nil, string(jsonBody))
if err != nil {
return nil, fmt.Errorf("query task failed (status: %d): %w", statusCode, err)
}
@@ -110,30 +245,37 @@ func (c *Client) QueryTask(req *QueryTaskRequest) (*QueryTaskResponse, error) {
return nil, fmt.Errorf("unmarshal response failed: %w", err)
}
return &result, nil
}
// SubmitSyncTask 提交同步任务(仅用于文生图)
func (c *Client) SubmitSyncTask(req *SubmitTaskRequest) (*QueryTaskResponse, error) {
// 序列化请求
jsonBody, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("marshal request failed: %w", err)
}
// 调用SDK的JSON方法
respBody, statusCode, err := c.visual.Client.Json("CVProcess", nil, string(jsonBody))
if err != nil {
return nil, fmt.Errorf("submit sync task failed (status: %d): %w", statusCode, err)
}
logger.Infof("Jimeng SubmitSyncTask Response: %s", string(respBody))
// 解析响应,同步任务直接返回结果
var result QueryTaskResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("unmarshal response failed: %w", err)
// 检查响应错误代码
if err := HandleResponseError(result.Code, result.Message); err != nil {
return nil, err
}
return &result, nil
}
// SubmitSyncImageTask 提交同步生图任务
func (c *Client) SubmitSyncImageTask(req types.JimengTaskRequest) (*model.ImagesResponse, error) {
// 配置火山引擎访问密钥,目前只支持API Key验证
client := arkruntime.NewClientWithApiKey(c.config.ApiKey)
// 构造生图请求
sequentialImageGeneration := model.SequentialImageGeneration("disabled")
generateReq := model.GenerateImagesRequest{
Model: req.ReqKey, // 模型名称
Prompt: req.Prompt, // 提示词
Size: volcengine.String(req.Size), // 图片尺寸
SequentialImageGeneration: &sequentialImageGeneration, // 禁用序列生成
ResponseFormat: volcengine.String(model.GenerateImagesResponseFormatURL), // 响应格式为 URL
Watermark: volcengine.Bool(false), // 不添加水印
OptimizePrompt: volcengine.Bool(true), // 优化提示词
}
if len(req.ImageUrls) > 0 {
generateReq.Image = req.ImageUrls
}
// 调用生图 API
resp, err := client.GenerateImages(context.Background(), generateReq)
if err != nil {
return nil, err
}
return &resp, nil
}
+157 -303
View File
@@ -4,20 +4,19 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"gorm.io/gorm"
"geekai/core/types"
logger2 "geekai/logger"
"geekai/service"
"geekai/service/oss"
"geekai/store"
"geekai/store/model"
"geekai/utils"
"geekai/core/types"
"github.com/go-redis/redis/v8"
)
@@ -25,38 +24,31 @@ var logger = logger2.GetLogger()
// Service 即梦服务(合并了消费者功能)
type Service struct {
db *gorm.DB
redis *redis.Client
taskQueue *store.RedisQueue
client *Client
ctx context.Context
cancel context.CancelFunc
running bool
uploader *oss.UploaderManager
db *gorm.DB
redis *redis.Client
taskQueue *store.RedisQueue
client *Client
ctx context.Context
cancel context.CancelFunc
running bool
uploader *oss.UploaderManager
userService *service.UserService
}
// NewService 创建即梦服务
func NewService(db *gorm.DB, redisCli *redis.Client, uploader *oss.UploaderManager) *Service {
func NewService(db *gorm.DB, redisCli *redis.Client, uploader *oss.UploaderManager, client *Client, userService *service.UserService) *Service {
taskQueue := store.NewRedisQueue("JimengTaskQueue", redisCli)
// 从数据库加载配置
var config model.Config
db.Where("name = ?", "Jimeng").First(&config)
var jimengConfig types.JimengConfig
if config.Id > 0 {
_ = utils.JsonDecode(config.Value, &jimengConfig)
}
client := NewClient(jimengConfig.AccessKey, jimengConfig.SecretKey)
ctx, cancel := context.WithCancel(context.Background())
return &Service{
db: db,
redis: redisCli,
taskQueue: taskQueue,
client: client,
ctx: ctx,
cancel: cancel,
running: false,
uploader: uploader,
db: db,
redis: redisCli,
taskQueue: taskQueue,
client: client,
ctx: ctx,
cancel: cancel,
running: false,
uploader: uploader,
userService: userService,
}
}
@@ -107,35 +99,29 @@ func (s *Service) processNextTask() {
if err := s.ProcessTask(jobId); err != nil {
logger.Errorf("process jimeng task failed: job_id=%d, error=%v", jobId, err)
s.UpdateJobStatus(jobId, model.JMTaskStatusFailed, err.Error())
s.UpdateJobStatus(jobId, types.JMTaskStatusFailed, err.Error())
} else {
logger.Infof("Jimeng task processed successfully: job_id=%d", jobId)
}
}
// CreateTask 创建任务
func (s *Service) CreateTask(userId uint, req *CreateTaskRequest) (*model.JimengJob, error) {
func (s *Service) CreateTask(userId uint, req *types.JimengTaskRequest) (*model.JimengJob, error) {
// 生成任务ID
taskId := utils.RandString(20)
// 序列化任务参数
paramsJson, err := json.Marshal(req.Params)
if err != nil {
return nil, fmt.Errorf("marshal task params failed: %w", err)
}
// 创建任务记录
job := &model.JimengJob{
UserId: userId,
TaskId: taskId,
Type: req.Type,
ReqKey: req.ReqKey,
Prompt: req.Prompt,
TaskParams: string(paramsJson),
Status: model.JMTaskStatusInQueue,
Power: req.Power,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
UserId: userId,
TaskId: taskId,
Type: req.TaskType,
ReqKey: req.ReqKey,
Prompt: req.Prompt,
Params: utils.JsonEncode(req),
Status: types.JMTaskStatusSubmited,
Power: req.Power,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 保存到数据库
@@ -160,25 +146,71 @@ func (s *Service) ProcessTask(jobId uint) error {
}
// 更新任务状态为处理中
if err := s.UpdateJobStatus(job.Id, model.JMTaskStatusGenerating, ""); err != nil {
if err := s.UpdateJobStatus(job.Id, types.JMTaskStatusGenerating, ""); err != nil {
return fmt.Errorf("update job status failed: %w", err)
}
// 解析任务参数
var req types.JimengTaskRequest
err := utils.JsonDecode(job.Params, &req)
if err != nil {
return fmt.Errorf("parse task params failed: %w", err)
}
// 构建请求并提交任务
req, err := s.buildTaskRequest(&job)
params, err := s.buildTaskRequest(&req)
if err != nil {
return s.handleTaskError(job.Id, fmt.Sprintf("build task request failed: %v", err))
}
logger.Infof("提交即梦任务: %+v", req)
// 数字人任务,先识别主体
if req.TaskType == types.JMTaskTypeVirtualHuman {
if err := s.client.AvatarRecognition(req.ImageUrls[0], req.RecognizeKey); err != nil {
return s.handleTaskError(job.Id, fmt.Sprintf("avatar recognition failed: %v", err))
}
}
// 提交异步任务
resp, err := s.client.SubmitTask(req)
// 同步任务 ,后台执行
if req.ReqKey == DoubaoSeedream40ReqKey {
go func() {
resp, err := s.client.SubmitSyncImageTask(req)
if err != nil {
_ = s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err))
return
}
logger.Infof("同步任务提交成功: %+v", resp)
// 更新原始数据
rawData, _ := json.Marshal(resp)
updates := map[string]any{
"raw_data": string(rawData),
}
if resp.Error != nil {
updates["status"] = types.JMTaskStatusFailed
updates["err_msg"] = resp.Error.Message
s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(updates)
return
}
// 更新任务状态
updates["status"] = types.JMTaskStatusSuccess
// 下载图片
imgUrl, err := s.uploader.GetUploadHandler().PutUrlFile(*resp.Data[0].Url, ".png", false)
if err == nil {
updates["img_url"] = imgUrl
}
s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(updates)
}()
return nil
}
logger.Debugf("提交即梦任务: %+v", params)
// 异步任务 ,前台执行
resp, err := s.client.SubmitTask(params)
if err != nil {
return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err))
}
if resp.Code != 10000 {
if resp.Code != CodeSuccess {
return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message))
}
@@ -187,6 +219,7 @@ func (s *Service) ProcessTask(jobId uint) error {
if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{
"task_id": resp.Data.TaskId,
"raw_data": string(rawData),
"status": types.JMTaskStatusInQueue, // 把任务状态改成排队中,以便开启轮询
"updated_at": time.Now(),
}).Error; err != nil {
logger.Errorf("update jimeng job task_id failed: %v", err)
@@ -196,172 +229,51 @@ func (s *Service) ProcessTask(jobId uint) error {
}
// buildTaskRequest 构建任务请求(统一的参数解析)
func (s *Service) buildTaskRequest(job *model.JimengJob) (*SubmitTaskRequest, error) {
// 解析任务参数
func (s *Service) buildTaskRequest(req *types.JimengTaskRequest) (map[string]any, error) {
var params map[string]any
if err := json.Unmarshal([]byte(job.TaskParams), &params); err != nil {
err := utils.JsonDecode(utils.JsonEncode(req), &params)
if err != nil {
return nil, fmt.Errorf("parse task params failed: %w", err)
}
// 构建基础请求
req := &SubmitTaskRequest{
ReqKey: job.ReqKey,
Prompt: job.Prompt,
}
// 根据任务类型设置特定参数
switch job.Type {
case model.JMTaskTypeTextToImage:
s.setTextToImageParams(req, params)
case model.JMTaskTypeImageToImage:
s.setImageToImageParams(req, params)
case model.JMTaskTypeImageEdit:
s.setImageEditParams(req, params)
case model.JMTaskTypeImageEffects:
s.setImageEffectsParams(req, params)
case model.JMTaskTypeTextToVideo:
s.setTextToVideoParams(req, params)
case model.JMTaskTypeImageToVideo:
s.setImageToVideoParams(req, params)
default:
return nil, fmt.Errorf("unsupported task type: %s", job.Type)
}
return req, nil
}
// setTextToImageParams 设置文生图参数
func (s *Service) setTextToImageParams(req *SubmitTaskRequest, params map[string]any) {
if seed, ok := params["seed"]; ok {
if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil {
req.Seed = seedVal
}
}
if scale, ok := params["scale"]; ok {
if scaleVal, ok := scale.(float64); ok {
req.Scale = scaleVal
}
}
if width, ok := params["width"]; ok {
if widthVal, ok := width.(float64); ok {
req.Width = int(widthVal)
}
}
if height, ok := params["height"]; ok {
if heightVal, ok := height.(float64); ok {
req.Height = int(heightVal)
}
}
if usePreLlm, ok := params["use_pre_llm"]; ok {
if usePreLlmVal, ok := usePreLlm.(bool); ok {
req.UsePreLLM = usePreLlmVal
}
}
}
// setImageToImageParams 设置图生图参数
func (s *Service) setImageToImageParams(req *SubmitTaskRequest, params map[string]any) {
if imageInput, ok := params["image_input"].(string); ok {
req.ImageInput = imageInput
}
if gpen, ok := params["gpen"]; ok {
if gpenVal, ok := gpen.(float64); ok {
req.Gpen = gpenVal
}
}
if skin, ok := params["skin"]; ok {
if skinVal, ok := skin.(float64); ok {
req.Skin = skinVal
}
}
if skinUnifi, ok := params["skin_unifi"]; ok {
if skinUnifiVal, ok := skinUnifi.(float64); ok {
req.SkinUnifi = skinUnifiVal
}
}
if genMode, ok := params["gen_mode"].(string); ok {
req.GenMode = genMode
}
s.setCommonParams(req, params) // 复用通用参数
}
// setImageEditParams 设置图像编辑参数
func (s *Service) setImageEditParams(req *SubmitTaskRequest, params map[string]any) {
if imageUrls, ok := params["image_urls"].([]any); ok {
for _, url := range imageUrls {
if urlStr, ok := url.(string); ok {
req.ImageUrls = append(req.ImageUrls, urlStr)
// 把 size 转成 width 和 height
if size, ok := params["size"]; ok {
if sizeStr, ok := size.(string); ok {
if strings.Contains(sizeStr, "x") {
sizes := strings.Split(sizeStr, "x")
params["width"] = sizes[0]
params["height"] = sizes[1]
}
}
delete(params, "size")
}
if binaryData, ok := params["binary_data_base64"].([]any); ok {
for _, data := range binaryData {
if dataStr, ok := data.(string); ok {
req.BinaryDataBase64 = append(req.BinaryDataBase64, dataStr)
}
}
}
if scale, ok := params["scale"]; ok {
if scaleVal, ok := scale.(float64); ok {
req.Scale = scaleVal
}
}
s.setCommonParams(req, params)
}
// setImageEffectsParams 设置图像特效参数
func (s *Service) setImageEffectsParams(req *SubmitTaskRequest, params map[string]any) {
if imageInput1, ok := params["image_input1"].(string); ok {
req.ImageInput1 = imageInput1
}
if templateId, ok := params["template_id"].(string); ok {
req.TemplateId = templateId
}
if width, ok := params["width"]; ok {
if widthVal, ok := width.(float64); ok {
req.Width = int(widthVal)
// duration 转成 frames
if duration, ok := params["duration"]; ok {
if secs, ok := duration.(int); ok {
params["frames"] = secs*24 + 1
}
delete(params, "duration")
}
if height, ok := params["height"]; ok {
if heightVal, ok := height.(float64); ok {
req.Height = int(heightVal)
}
}
}
// setTextToVideoParams 设置文生视频参数
func (s *Service) setTextToVideoParams(req *SubmitTaskRequest, params map[string]any) {
if aspectRatio, ok := params["aspect_ratio"].(string); ok {
req.AspectRatio = aspectRatio
// 单独处理图片特效任务
if req.ReqKey == ImageEffectReqKey {
params["image_input1"] = req.ImageUrls[0]
delete(params, "image_urls")
}
s.setCommonParams(req, params)
}
// setImageToVideoParams 设置图生视频参数
func (s *Service) setImageToVideoParams(req *SubmitTaskRequest, params map[string]any) {
s.setImageEditParams(req, params) // 复用图像编辑的参数设置
if aspectRatio, ok := params["aspect_ratio"].(string); ok {
req.AspectRatio = aspectRatio
// 动作迁移,数字人任务参数处理
if req.TaskType == types.JMTaskTypeVirtualHuman || req.TaskType == types.JMTaskTypeActionTransfer {
params["image_url"] = req.ImageUrls[0]
delete(params, "image_urls")
}
if req.RecognizeKey != "" {
delete(params, "recognize_key")
}
}
// setCommonParams 设置通用参数(seed, width, height等)
func (s *Service) setCommonParams(req *SubmitTaskRequest, params map[string]any) {
if seed, ok := params["seed"]; ok {
if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil {
req.Seed = seedVal
}
}
if width, ok := params["width"]; ok {
if widthVal, ok := width.(float64); ok {
req.Width = int(widthVal)
}
}
if height, ok := params["height"]; ok {
if heightVal, ok := height.(float64); ok {
req.Height = int(heightVal)
}
}
// 删除多余参数,剩下的就是各个任务自己专有参数了
delete(params, "type")
delete(params, "power")
return params, nil
}
// pollTaskStatus 轮询任务状态
@@ -369,29 +281,32 @@ func (s *Service) pollTaskStatus() {
for {
var jobs []model.JimengJob
s.db.Where("status IN (?)", []model.JMTaskStatus{model.JMTaskStatusGenerating, model.JMTaskStatusInQueue}).Find(&jobs)
if len(jobs) == 0 {
logger.Debugf("no jimeng task to poll, sleep 10s")
time.Sleep(10 * time.Second)
continue
}
// 找出排队中和处理中的任务进行轮询
s.db.Where("status IN (?)", []types.JMTaskStatus{
types.JMTaskStatusGenerating,
types.JMTaskStatusInQueue}).Find(&jobs)
for _, job := range jobs {
// 任务超时处理
if job.UpdatedAt.Before(time.Now().Add(-5 * time.Minute)) {
if job.UpdatedAt.Before(time.Now().Add(-10 * time.Minute)) {
s.handleTaskError(job.Id, "task timeout")
continue
}
// 豆包生图 4.0 是同步任务,不需要轮询
if job.ReqKey == DoubaoSeedream40ReqKey {
continue
}
// 查询任务状态
resp, err := s.client.QueryTask(&QueryTaskRequest{
ReqKey: job.ReqKey,
TaskId: job.TaskId,
ReqJson: `{"return_url":true}`,
})
}, ASyncActionGetResult)
if err != nil {
logger.Errorf("query jimeng task status failed: %v", err)
s.handleTaskError(job.Id, fmt.Sprintf("query task failed: %s", err.Error()))
continue
}
@@ -399,13 +314,13 @@ func (s *Service) pollTaskStatus() {
rawData, _ := json.Marshal(resp)
s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Update("raw_data", string(rawData))
if resp.Code != 10000 {
if resp.Code != CodeSuccess {
s.handleTaskError(job.Id, fmt.Sprintf("query task failed: %s", resp.Message))
continue
}
switch resp.Data.Status {
case model.JMTaskStatusDone:
case types.JMTaskStatusDone:
// 判断任务是否成功
if resp.Message != "Success" {
s.handleTaskError(job.Id, fmt.Sprintf("task failed: %s", resp.Data.AlgorithmBaseResp.StatusMessage))
@@ -414,7 +329,7 @@ func (s *Service) pollTaskStatus() {
// 任务完成,更新结果
updates := map[string]any{
"status": model.JMTaskStatusSuccess,
"status": types.JMTaskStatusSuccess,
"updated_at": time.Now(),
}
@@ -437,24 +352,37 @@ func (s *Service) pollTaskStatus() {
}
s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(updates)
case model.JMTaskStatusInQueue, model.JMTaskStatusGenerating:
case types.JMTaskStatusInQueue, types.JMTaskStatusGenerating:
// 任务处理中
s.UpdateJobStatus(job.Id, model.JMTaskStatusGenerating, "")
s.UpdateJobStatus(job.Id, types.JMTaskStatusGenerating, "")
case model.JMTaskStatusNotFound:
case types.JMTaskStatusNotFound:
// 任务未找到
s.handleTaskError(job.Id, "task not found")
case model.JMTaskStatusExpired:
// 任务过期
s.handleTaskError(job.Id, "task expired")
case types.JMTaskStatusExpired:
continue
default:
logger.Warnf("unknown task status: %s", resp.Data.Status)
}
}
// 找出失败的任务,并恢复其扣减算力
s.db.Where("status = ?", types.JMTaskStatusFailed).Where("power > ?", 0).Find(&jobs)
for _, job := range jobs {
err := s.userService.IncreasePower(job.UserId, job.Power, model.PowerLog{
Type: types.PowerRefund,
Model: job.ReqKey,
Remark: fmt.Sprintf("任务失败,退回算力。任务ID%d", job.Id),
})
if err != nil {
continue
}
// 更新任务状态
s.db.Model(&job).UpdateColumn("power", 0)
}
time.Sleep(5 * time.Second)
}
@@ -462,7 +390,7 @@ func (s *Service) pollTaskStatus() {
}
// UpdateJobStatus 更新任务状态
func (s *Service) UpdateJobStatus(jobId uint, status model.JMTaskStatus, errMsg string) error {
func (s *Service) UpdateJobStatus(jobId uint, status types.JMTaskStatus, errMsg string) error {
updates := map[string]any{
"status": status,
"updated_at": time.Now(),
@@ -476,7 +404,7 @@ func (s *Service) UpdateJobStatus(jobId uint, status model.JMTaskStatus, errMsg
// handleTaskError 处理任务错误
func (s *Service) handleTaskError(jobId uint, errMsg string) error {
logger.Errorf("Jimeng task error (job_id: %d): %s", jobId, errMsg)
return s.UpdateJobStatus(jobId, model.JMTaskStatusFailed, errMsg)
return s.UpdateJobStatus(jobId, types.JMTaskStatusFailed, errMsg)
}
// PushTaskToQueue 推送任务到队列(用于手动重试)
@@ -487,8 +415,8 @@ func (s *Service) PushTaskToQueue(jobId uint) error {
// GetTaskStats 获取任务统计信息
func (s *Service) GetTaskStats() (map[string]any, error) {
type StatResult struct {
Status string `json:"status"`
Count int64 `json:"count"`
Status types.JMTaskStatus `json:"status"`
Count int64 `json:"count"`
}
var stats []StatResult
@@ -510,7 +438,7 @@ func (s *Service) GetTaskStats() (map[string]any, error) {
for _, stat := range stats {
result["total"] = result["total"].(int64) + stat.Count
result[stat.Status] = stat.Count
result[string(stat.Status)] = stat.Count
}
return result, nil
@@ -524,77 +452,3 @@ func (s *Service) GetJob(jobId uint) (*model.JimengJob, error) {
}
return &job, nil
}
// testConnection 测试即梦AI连接
func (s *Service) testConnection(accessKey, secretKey string) error {
testClient := NewClient(accessKey, secretKey)
// 使用一个简单的查询任务来测试连接
testReq := &QueryTaskRequest{
ReqKey: "test_connection",
TaskId: "test_task_id_12345",
}
_, err := testClient.QueryTask(testReq)
// 即使任务不存在,只要不是认证错误就说明连接正常
if err != nil {
// 检查是否是认证错误
if strings.Contains(err.Error(), "InvalidAccessKey") {
return fmt.Errorf("认证失败,请检查AccessKey和SecretKey是否正确")
}
// 其他错误(如任务不存在)说明连接正常
return nil
}
return nil
}
// UpdateClientConfig 更新客户端配置
func (s *Service) UpdateClientConfig(accessKey, secretKey string) error {
// 创建新的客户端
newClient := NewClient(accessKey, secretKey)
// 测试新客户端是否可用
err := s.testConnection(accessKey, secretKey)
if err != nil {
return err
}
// 更新客户端
s.client = newClient
return nil
}
var defaultPower = types.JimengPower{
TextToImage: 20,
ImageToImage: 20,
ImageEdit: 20,
ImageEffects: 20,
TextToVideo: 300,
ImageToVideo: 300,
}
// GetConfig 获取即梦AI配置
func (s *Service) GetConfig() *types.JimengConfig {
var config model.Config
err := s.db.Where("name", "jimeng").First(&config).Error
if err != nil {
// 如果配置不存在,返回默认配置
return &types.JimengConfig{
AccessKey: "",
SecretKey: "",
Power: defaultPower,
}
}
var jimengConfig types.JimengConfig
err = utils.JsonDecode(config.Value, &jimengConfig)
if err != nil {
return &types.JimengConfig{
AccessKey: "",
SecretKey: "",
Power: defaultPower,
}
}
return &jimengConfig
}
+74 -83
View File
@@ -1,43 +1,9 @@
package jimeng
import "geekai/store/model"
// ReqKey 常量定义
const (
ReqKeyTextToImage = "high_aes_general_v30l_zt2i" // 文生图
ReqKeyImageToImagePortrait = "i2i_portrait_photo" // 图生图人像写真
ReqKeyImageEdit = "seededit_v3.0" // 图像编辑
ReqKeyImageEffects = "i2i_multi_style_zx2x" // 图像特效
ReqKeyTextToVideo = "jimeng_vgfm_t2v_l20" // 文生视频
ReqKeyImageToVideo = "jimeng_vgfm_i2v_l20" // 图生视频
import (
"geekai/core/types"
)
// SubmitTaskRequest 提交任务请求
type SubmitTaskRequest struct {
ReqKey string `json:"req_key"`
// 文生图参数
Prompt string `json:"prompt,omitempty"`
Seed int64 `json:"seed,omitempty"`
Scale float64 `json:"scale,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
UsePreLLM bool `json:"use_pre_llm,omitempty"`
// 图生图参数
ImageInput string `json:"image_input,omitempty"`
ImageUrls []string `json:"image_urls,omitempty"`
BinaryDataBase64 []string `json:"binary_data_base64,omitempty"`
Gpen float64 `json:"gpen,omitempty"`
Skin float64 `json:"skin,omitempty"`
SkinUnifi float64 `json:"skin_unifi,omitempty"`
GenMode string `json:"gen_mode,omitempty"`
// 图像编辑参数
// 图像特效参数
ImageInput1 string `json:"image_input1,omitempty"`
TemplateId string `json:"template_id,omitempty"`
// 视频生成参数
AspectRatio string `json:"aspect_ratio,omitempty"`
}
// SubmitTaskResponse 提交任务响应
type SubmitTaskResponse struct {
Code int `json:"code"`
@@ -73,7 +39,7 @@ type QueryTaskResponse struct {
ImageUrls []string `json:"image_urls"`
VideoUrl string `json:"video_url"`
RespData string `json:"resp_data"`
Status model.JMTaskStatus `json:"status"`
Status types.JMTaskStatus `json:"status"`
LlmResult string `json:"llm_result"`
PeResult string `json:"pe_result"`
PredictTagsResult string `json:"predict_tags_result"`
@@ -83,9 +49,73 @@ type QueryTaskResponse struct {
} `json:"data"`
}
const CodeSuccess = 10000
// 即梦AI错误代码常量
const (
// 成功
ECSuccess = 10000
// 请求参数错误 (50200-50215)
ECReqInvalidArgs = 50200 // 参数错误
ECReqMissingArgs = 50201 // 缺少参数
ECParseArgs = 50204 // 参数类型错误/参数缺失
ECImageSizeLimited = 50205 // 图像尺寸超过限制
ECImageEmpty = 50206 // 请求参数中没有获取到图像
ECImageDecodeError = 50207 // 图像解码错误
ECVideoEmpty = 50209 // 请求参数中没有获取到视频
ECVideoDecodeError = 50210 // 视频解码错误
ECVideoSizeLimited = 50211 // 视频尺寸超过限制
ECReqBodySizeLimited = 50213 // 请求Body过大
ECVideoTimeTooLong = 50214 // 输入视频时长过大
ECRPCProcess = 50215 // 请求处理失败
// 算法服务错误 (60102-60208)
ECJPFaceDetect = 60102 // 算法服务需要输入人脸图,但未检测到
ECFSLeaderRiskError = 60208 // 输入图片中包含敏感信息,未通过审核
// 权限和系统错误 (50400-50501)
ECAuth = 50400 // 权限校验失败
ECReqMethod = 50402 // 访问的接口不存在
ECReqLimit = 50429 // 超过调用QPS限制
ECInternal = 50500 // 服务器内部错误
ECRPCInternal = 50501 // 服务器内部RPC错误
)
// 错误代码到错误信息的映射
var errorCodeMessages = map[int]string{
// 成功
ECSuccess: "请求成功",
// 请求参数错误
ECReqInvalidArgs: "参数错误,检查入参及MIME类型",
ECReqMissingArgs: "缺少参数,检查入参及MIME类型",
ECParseArgs: "参数类型错误/参数缺失,检查入参及MIME类型",
ECImageSizeLimited: "图像尺寸超过限制,参考接口文档入参要求部分",
ECImageEmpty: "请求参数中没有获取到图像,检查入参",
ECImageDecodeError: "图像解码错误:没有获取到图像或者通过image_base64参数传递图像是base64解码错误,检查输出图片或检查base64是否错误携带前缀",
ECVideoEmpty: "请求参数中没有获取到视频。输入为视频时可能返回此错误,检查入参",
ECVideoDecodeError: "视频解码错误。输入为视频时可能返回此错误,检查输入视频是否不正确",
ECVideoSizeLimited: "视频尺寸超过限制。输入为视频时可能返回此错误,检查输入视频大小",
ECReqBodySizeLimited: "请求Body过大,超出接口限制,检查请求Body大小",
ECVideoTimeTooLong: "输入视频时长过大,检查输入视频时长",
ECRPCProcess: "由于输入的图片、视频、参数等不满足要求,导致请求处理失败。若接口文档中有具体说明,优先参考其具体含义,按照具体服务说明进行检查",
// 算法服务错误
ECJPFaceDetect: "算法服务需要输入人脸图,但未检测到,检查输入图片是否包含人脸",
ECFSLeaderRiskError: "输入图片中包含敏感信息,未通过审核",
// 权限和系统错误
ECAuth: "权限校验失败,请检查是否已创建应用并开通服务或签名,参考接入指南及快速接入",
ECReqMethod: "访问的接口不存在,检查入参",
ECReqLimit: "超过调用QPS限制,购买QPS增项包",
ECInternal: "服务器内部错误,提工单",
ECRPCInternal: "服务器内部RPC错误,提工单",
}
// CreateTaskRequest 创建任务请求
type CreateTaskRequest struct {
Type model.JMTaskType `json:"type"`
Type types.JMTaskType `json:"type"`
Prompt string `json:"prompt"`
Params map[string]any `json:"params"`
ReqKey string `json:"req_key"`
@@ -93,53 +123,14 @@ type CreateTaskRequest struct {
Power int `json:"power,omitempty"`
}
// LogoInfo 水印信息
type LogoInfo struct {
AddLogo bool `json:"add_logo"`
Position int `json:"position"`
Language int `json:"language"`
Opacity float64 `json:"opacity"`
LogoTextContent string `json:"logo_text_content"`
}
// ReqJsonConfig 查询配置
type ReqJsonConfig struct {
ReturnUrl bool `json:"return_url"`
LogoInfo *LogoInfo `json:"logo_info,omitempty"`
}
// ImageEffectTemplate 图像特效模板
const (
TemplateIdFelt3DPolaroid = "felt_3d_polaroid" // 毛毡3d拍立得风格
TemplateIdMyWorld = "my_world" // 像素世界风
TemplateIdMyWorldUniversal = "my_world_universal" // 像素世界-万物通用版
TemplateIdPlasticBubbleFigure = "plastic_bubble_figure" // 盲盒玩偶风
TemplateIdPlasticBubbleFigureCartoon = "plastic_bubble_figure_cartoon_text" // 塑料泡罩人偶-文字卡头版
TemplateIdFurryDreamDoll = "furry_dream_doll" // 毛绒玩偶风
TemplateIdMicroLandscapeMiniWorld = "micro_landscape_mini_world" // 迷你世界玩偶风
TemplateIdMicroLandscapeProfessional = "micro_landscape_mini_world_professional" // 微型景观小世界-职业版
TemplateIdAcrylicOrnaments = "acrylic_ornaments" // 亚克力挂饰
TemplateIdFeltKeychain = "felt_keychain" // 毛毡钥匙扣
TemplateIdLofiPixelCharacter = "lofi_pixel_character_mini_card" // Lofi像素人物小卡
TemplateIdAngelFigurine = "angel_figurine" // 天使形象手办
TemplateIdLyingInFluffyBelly = "lying_in_fluffy_belly" // 躺在毛茸茸肚皮里
TemplateIdGlassBall = "glass_ball" // 玻璃球
ImageEffectReqKey = "i2i_multi_style_zx2x"
DoubaoSeedream40ReqKey = "doubao-seedream-4-0-250828"
)
// AspectRatio 视频宽高比
const (
AspectRatio16_9 = "16:9" // 1280×720
AspectRatio9_16 = "9:16" // 720×1280
AspectRatio1_1 = "1:1" // 960×960
AspectRatio4_3 = "4:3" // 960×720
AspectRatio3_4 = "3:4" // 720×960
AspectRatio21_9 = "21:9" // 1680×720
AspectRatio9_21 = "9:21" // 720×1680
)
// GenMode 生成模式
const (
GenModeCreative = "creative" // 提示词模式
GenModeReference = "reference" // 全参考模式
GenModeReferenceChar = "reference_char" // 人物参考模式
ASyncActionSubmit = "CVSync2AsyncSubmitTask" // 异步提交任务
SyncActionSubmit = "CVSubmitTask" // 同步提交任务
ASyncActionGetResult = "CVSync2AsyncGetResult" // 异步获取结果
SyncActionGetResult = "CVGetResult" // 同步获取结果
)
-204
View File
@@ -1,204 +0,0 @@
package service
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"errors"
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/store"
"strings"
"time"
"github.com/imroc/req/v3"
"github.com/shirou/gopsutil/host"
)
type LicenseService struct {
config types.ApiConfig
levelDB *store.LevelDB
license *types.License
urlWhiteList []string
machineId string
}
func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) *LicenseService {
var license types.License
var machineId string
err := levelDB.Get(types.LicenseKey, &license)
logger.Infof("License: %+v", server.SysConfig)
info, err := host.Info()
if err == nil {
machineId = info.HostID
}
logger.Infof("License: %+v", license)
return &LicenseService{
config: server.Config.ApiConfig,
levelDB: levelDB,
license: &license,
machineId: machineId,
}
}
type License struct {
Name string `json:"name"`
License string `json:"license"`
MachineId string `json:"mid"`
ActiveAt int64 `json:"active_at"`
ExpiredAt int64 `json:"expired_at"`
UserNum int `json:"user_num"`
Configs types.LicenseConfig `json:"configs"`
}
// ActiveLicense 激活 License
func (s *LicenseService) ActiveLicense(license string, machineId string) error {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data License `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/active")
response, err := req.C().R().
SetBody(map[string]string{"license": license, "machine_id": machineId}).
SetSuccessResult(&res).Post(apiURL)
if err != nil {
return fmt.Errorf("发送激活请求失败: %v", err)
}
if response.IsErrorState() {
return fmt.Errorf("发送激活请求失败:%v", response.Status)
}
if res.Code != types.Success {
return fmt.Errorf("激活失败:%v", res.Message)
}
s.license = &types.License{
Key: license,
MachineId: machineId,
Configs: res.Data.Configs,
ExpiredAt: res.Data.ExpiredAt,
IsActive: true,
}
err = s.levelDB.Put(types.LicenseKey, s.license)
if err != nil {
return fmt.Errorf("保存许可证书失败:%v", err)
}
return nil
}
// SyncLicense 定期同步 License
func (s *LicenseService) SyncLicense() {
go func() {
retryCounter := 0
for {
license, err := s.fetchLicense()
if err != nil {
retryCounter++
if retryCounter < 5 {
logger.Debug(err)
}
s.license.IsActive = false
} else {
s.license = license
}
urls, err := s.fetchUrlWhiteList()
if err == nil {
s.urlWhiteList = urls
}
time.Sleep(time.Second * 10)
}
}()
}
func (s *LicenseService) fetchLicense() (*types.License, error) {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data License `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/check")
response, err := req.C().R().
SetBody(map[string]string{"license": s.license.Key, "machine_id": s.machineId}).
SetSuccessResult(&res).Post(apiURL)
if err != nil {
return nil, fmt.Errorf("发送激活请求失败: %v", err)
}
if response.IsErrorState() {
return nil, fmt.Errorf("激活失败:%v", response.Status)
}
if res.Code != types.Success {
return nil, fmt.Errorf("激活失败:%v", res.Message)
}
return &types.License{
Key: res.Data.License,
MachineId: res.Data.MachineId,
Configs: res.Data.Configs,
ExpiredAt: res.Data.ExpiredAt,
IsActive: true,
}, nil
}
func (s *LicenseService) fetchUrlWhiteList() ([]string, error) {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data []string `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/urls")
response, err := req.C().R().SetSuccessResult(&res).Get(apiURL)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
if response.IsErrorState() {
return nil, fmt.Errorf("发送请求失败:%v", response.Status)
}
if res.Code != types.Success {
return nil, fmt.Errorf("获取白名单失败:%v", res.Message)
}
return res.Data, nil
}
// GetLicense 获取许可信息
func (s *LicenseService) GetLicense() *types.License {
return s.license
}
// IsValidApiURL 判断是否合法的中转 URL
func (s *LicenseService) IsValidApiURL(uri string) error {
// 获得许可授权的直接放行
if s.license.IsActive {
if s.license.MachineId != s.machineId {
return errors.New("系统使用了盗版的许可证书")
}
if time.Now().Unix() > s.license.ExpiredAt {
return errors.New("系统许可证书已经过期")
}
return nil
}
if len(s.urlWhiteList) == 0 {
urls, err := s.fetchUrlWhiteList()
if err == nil {
s.urlWhiteList = urls
}
}
for _, v := range s.urlWhiteList {
if strings.HasPrefix(uri, v) {
return nil
}
}
return fmt.Errorf("当前 API 地址 %s 不在白名单列表当中。", uri)
}
+408
View File
@@ -0,0 +1,408 @@
package service
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Copyright 2023 The Geek-AI Authors. All rights reserved.
// Use of this source code is governed by a Apache-2.0 license
// that can be found in the LICENSE file.
// @Author yangjian102621@163.com
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"encoding/json"
"fmt"
"geekai/core/types"
"geekai/store"
"geekai/store/model"
"strings"
"sync"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
const (
// 迁移状态Redis key
MigrationStatusKey = "config_migration:status"
// 迁移完成标志
MigrationCompleted = "completed"
)
// MigrationService 配置迁移服务
type MigrationService struct {
db *gorm.DB
redisClient *redis.Client
appConfig *types.AppConfig
levelDB *store.LevelDB
}
func NewMigrationService(db *gorm.DB, redisClient *redis.Client, appConfig *types.AppConfig, levelDB *store.LevelDB) *MigrationService {
return &MigrationService{
db: db,
redisClient: redisClient,
appConfig: appConfig,
levelDB: levelDB,
}
}
func (s *MigrationService) StartMigrate() {
// 表结构同步必须在对外服务前完成,避免缺列导致业务报错
s.TableMigration()
go func() {
_ = s.MigrateConfig(s.appConfig)
}()
}
// 迁移配置内容
func (s *MigrationService) MigrateConfigContent() error {
// 用户协议
if err := s.saveConfig(types.ConfigKeyPrivacy, map[string]string{
"content": "用户协议内容",
}); err != nil {
return fmt.Errorf("迁移配置内容失败: %v", err)
}
// 隐私政策
if err := s.saveConfig(types.ConfigKeyAgreement, map[string]string{
"content": "隐私政策内容",
}); err != nil {
return fmt.Errorf("迁移配置内容失败: %v", err)
}
// 思维导图
if err := s.saveConfig(types.ConfigKeyMarkMap, map[string]string{
"content": `# GeekAI 演示站
- 完整的开源系统前端应用和后台管理系统皆可开箱即用
- 基于 Websocket 实现完美的打字机体验
- 内置了各种预训练好的角色应用,轻松满足你的各种聊天和应用需求
- 支持 OPenAIAzure文心一言讯飞星火清华 ChatGLM等多个大语言模型
- 支持 MidJourney / Stable Diffusion AI 绘画集成开箱即用
- 支持使用个人微信二维码作为充值收费的支付渠道无需企业支付通道
- 已集成支付宝支付功能微信支付支持多种会员套餐和点卡购买功能
- 集成插件 API 功能可结合大语言模型的 function 功能开发各种强大的插件`,
}); err != nil {
return fmt.Errorf("迁移配置内容失败: %v", err)
}
// 微信登录配置
if err := s.saveConfig(types.ConfigKeyWxLogin, map[string]string{
"api_key": "",
"notify_url": "",
"enabled": "false",
}); err != nil {
return fmt.Errorf("迁移配置内容失败: %v", err)
}
// 验证码配置
if err := s.saveConfig(types.ConfigKeyCaptcha, map[string]string{
"api_key": "",
"type": "dot",
"enabled": "false",
}); err != nil {
return fmt.Errorf("迁移配置内容失败: %v", err)
}
// 文本审核
if err := s.saveConfig(types.ConfigKeyModeration, map[string]any{
"enable": "false",
"active": "gitee",
"enable_guide": "false",
"guide_prompt": "",
"gitee": map[string]string{
"api_key": "",
"model": "Security-semantic-filtering",
},
"baidu": map[string]string{
"access_key": "",
"secret_key": "",
},
"tencent": map[string]string{
"access_key": "",
"secret_key": "",
},
}); err != nil {
return fmt.Errorf("迁移配置内容失败: %v", err)
}
return nil
}
// 永不删除的保护列(大小写不敏感)
var protectedColumns = map[string]struct{}{
"id": {},
"created_at": {},
"updated_at": {},
}
// allModels 全部需要同步的数据表 model
func allModels() []any {
return []any{
&model.AdminUser{},
&model.ApiKey{},
&model.AppType{},
&model.ChatApp{},
&model.ChatItem{},
&model.ChatMessage{},
&model.ChatModel{},
&model.Config{},
&model.DallJob{},
&model.File{},
&model.Function{},
&model.InviteCode{},
&model.InviteLog{},
&model.JimengJob{},
&model.Menu{},
&model.MidJourneyJob{},
&model.Moderation{},
&model.Order{},
&model.PowerLog{},
&model.Product{},
&model.Redeem{},
&model.SdJob{},
&model.SunoJob{},
&model.User{},
&model.UserLoginLog{},
&model.VideoJob{},
}
}
// 数据表迁移:先处理字段重命名(保数据),再全量同步 schema
func (s *MigrationService) TableMigration() {
logger.Info("开始数据表迁移...")
s.renameColumns()
if err := s.SyncAllModels(); err != nil {
logger.Errorf("同步数据表字段失败: %v", err)
return
}
logger.Info("数据表迁移完成")
}
// renameColumns 只处理「改名」场景:删旧加新会丢数据,必须先 Rename
func (s *MigrationService) renameColumns() {
m := s.db.Migrator()
if m.HasColumn(&model.JimengJob{}, "task_params") {
_ = m.RenameColumn(&model.JimengJob{}, "task_params", "params")
}
if m.HasColumn(&model.Order{}, "pay_type") {
_ = m.RenameColumn(&model.Order{}, "pay_type", "channel")
}
if m.HasColumn(&model.Config{}, "config_json") {
_ = m.RenameColumn(&model.Config{}, "config_json", "value")
}
if m.HasColumn(&model.Config{}, "marker") {
_ = m.RenameColumn(&model.Config{}, "marker", "name")
}
if m.HasIndex(&model.Config{}, "idx_chatgpt_configs_key") {
_ = m.DropIndex(&model.Config{}, "idx_chatgpt_configs_key")
}
if m.HasIndex(&model.Config{}, "marker") {
_ = m.DropIndex(&model.Config{}, "marker")
}
}
// SyncAllModels 按 model 定义同步所有数据表:缺列新建,多余列删除
func (s *MigrationService) SyncAllModels() error {
var firstErr error
for _, m := range allModels() {
if err := s.syncModel(m); err != nil {
logger.Errorf("同步 model %T 失败: %v", m, err)
if firstErr == nil {
firstErr = err
}
}
}
return firstErr
}
func (s *MigrationService) syncModel(dst any) error {
tableName := s.tableName(dst)
if err := s.db.AutoMigrate(dst); err != nil {
return fmt.Errorf("AutoMigrate %s: %w", tableName, err)
}
if err := s.dropUnusedColumns(dst); err != nil {
return fmt.Errorf("drop unused columns %s: %w", tableName, err)
}
logger.Infof("已同步数据表: %s", tableName)
return nil
}
func (s *MigrationService) dropUnusedColumns(dst any) error {
dbCols, err := s.db.Migrator().ColumnTypes(dst)
if err != nil {
return err
}
modelCols, err := s.modelColumnNames(dst)
if err != nil {
return err
}
for _, col := range dbCols {
name := col.Name()
if s.isProtectedColumn(name) {
continue
}
if _, ok := modelCols[strings.ToLower(name)]; ok {
continue
}
logger.Infof("删除多余字段: %s.%s", s.tableName(dst), name)
if err := s.db.Migrator().DropColumn(dst, name); err != nil {
return fmt.Errorf("DropColumn %s: %w", name, err)
}
}
return nil
}
func (s *MigrationService) modelColumnNames(dst any) (map[string]struct{}, error) {
parsed, err := schema.Parse(dst, &schemaCache, s.db.Config.NamingStrategy)
if err != nil {
return nil, err
}
cols := make(map[string]struct{}, len(parsed.Fields))
for _, field := range parsed.Fields {
if field.DBName == "" || field.IgnoreMigration {
continue
}
cols[strings.ToLower(field.DBName)] = struct{}{}
}
return cols, nil
}
func (s *MigrationService) isProtectedColumn(name string) bool {
_, ok := protectedColumns[strings.ToLower(name)]
return ok
}
func (s *MigrationService) tableName(dst any) string {
stmt := &gorm.Statement{DB: s.db}
if err := stmt.Parse(dst); err != nil {
return fmt.Sprintf("%T", dst)
}
return stmt.Schema.Table
}
// schema.Parse 进程内复用的 schema cache
var schemaCache sync.Map
// 迁移配置数据
func (s *MigrationService) MigrateConfig(config *types.AppConfig) error {
logger.Info("开始迁移配置到数据库...")
// 迁移支付配置
if err := s.migratePaymentConfig(config); err != nil {
logger.Errorf("迁移支付配置失败: %v", err)
return err
}
// 迁移存储配置
if err := s.migrateStorageConfig(config); err != nil {
logger.Errorf("迁移存储配置失败: %v", err)
return err
}
// 迁移通信配置
if err := s.migrateCommunicationConfig(config); err != nil {
logger.Errorf("迁移通信配置失败: %v", err)
return err
}
// 迁移配置内容
if err := s.MigrateConfigContent(); err != nil {
logger.Errorf("迁移配置内容失败: %v", err)
return err
}
logger.Info("配置迁移完成")
return nil
}
// 迁移支付配置
func (s *MigrationService) migratePaymentConfig(config *types.AppConfig) error {
paymentConfig := types.PaymentConfig{
Alipay: config.AlipayConfig,
Epay: config.GeekPayConfig,
WxPay: config.WechatPayConfig,
}
if err := s.saveConfig(types.ConfigKeyPayment, paymentConfig); err != nil {
return err
}
return nil
}
// 迁移存储配置
func (s *MigrationService) migrateStorageConfig(config *types.AppConfig) error {
ossConfig := types.OSSConfig{
Active: config.OSS.Active,
Local: config.OSS.Local,
Minio: config.OSS.Minio,
QiNiu: config.OSS.QiNiu,
AliYun: config.OSS.AliYun,
}
return s.saveConfig(types.ConfigKeyOss, ossConfig)
}
// 迁移通信配置
func (s *MigrationService) migrateCommunicationConfig(config *types.AppConfig) error {
// SMTP配置
smtpConfig := map[string]any{
"use_tls": config.SmtpConfig.UseTls,
"host": config.SmtpConfig.Host,
"port": config.SmtpConfig.Port,
"app_name": config.SmtpConfig.AppName,
"from": config.SmtpConfig.From,
"password": config.SmtpConfig.Password,
}
if err := s.saveConfig(types.ConfigKeySmtp, smtpConfig); err != nil {
return err
}
// 短信配置
smsConfig := map[string]any{
"active": strings.ToLower(config.SMS.Active),
"aliyun": map[string]any{
"access_key": config.SMS.Ali.AccessKey,
"access_secret": config.SMS.Ali.AccessSecret,
"sign": config.SMS.Ali.Sign,
"code_temp_id": config.SMS.Ali.CodeTempId,
},
"bao": map[string]any{
"username": config.SMS.Bao.Username,
"password": config.SMS.Bao.Password,
"sign": config.SMS.Bao.Sign,
"code_template": config.SMS.Bao.CodeTemplate,
},
}
return s.saveConfig(types.ConfigKeySms, smsConfig)
}
// 保存配置到数据库
func (s *MigrationService) saveConfig(key string, config any) error {
// 检查是否已存在
var existingConfig model.Config
if err := s.db.Where("name", key).First(&existingConfig).Error; err == nil {
// 配置已存在,跳过
logger.Infof("配置 %s 已存在,跳过迁移", key)
return nil
}
// 序列化配置
configJSON, err := json.Marshal(config)
if err != nil {
return err
}
// 保存到数据库
newConfig := model.Config{
Name: key,
Value: string(configJSON),
}
if err := s.db.Create(&newConfig).Error; err != nil {
return err
}
logger.Infof("成功迁移配置 %s", key)
return nil
}
+5 -12
View File
@@ -13,7 +13,6 @@ import (
"fmt"
"geekai/core/types"
logger2 "geekai/logger"
"geekai/service"
"geekai/store/model"
"geekai/utils"
"github.com/imroc/req/v3"
@@ -26,9 +25,8 @@ import (
// Client MidJourney client
type Client struct {
client *req.Client
licenseService *service.LicenseService
db *gorm.DB
client *req.Client
db *gorm.DB
}
type ImageReq struct {
@@ -77,11 +75,10 @@ type QueryRes struct {
var logger = logger2.GetLogger()
func NewClient(licenseService *service.LicenseService, db *gorm.DB) *Client {
func NewClient(db *gorm.DB) *Client {
return &Client{
client: req.C().SetTimeout(time.Minute).SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"),
licenseService: licenseService,
db: db,
client: req.C().SetTimeout(time.Minute).SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"),
db: db,
}
}
@@ -198,10 +195,6 @@ func (c *Client) doRequest(body interface{}, apiPath string, channel string) (Im
return ImageRes{}, fmt.Errorf("no available MidJourney api key: %v", err)
}
if err = c.licenseService.IsValidApiURL(apiKey.ApiURL); err != nil {
return ImageRes{}, err
}
apiURL := fmt.Sprintf("%s/%s", apiKey.ApiURL, apiPath)
logger.Info("API URL: ", apiURL)
r, err := req.C().R().
-19
View File
@@ -67,25 +67,6 @@ func (s *Service) Run() {
continue
}
// translate prompt
if utils.HasChinese(task.Prompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.TranslatePromptTemplate, task.Prompt), task.TranslateModelId)
if err == nil {
task.Prompt = content
} else {
logger.Warnf("error with translate prompt: %v", err)
}
}
// translate negative prompt
if task.NegPrompt != "" && utils.HasChinese(task.NegPrompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.TranslatePromptTemplate, task.NegPrompt), task.TranslateModelId)
if err == nil {
task.NegPrompt = content
} else {
logger.Warnf("error with translate prompt: %v", err)
}
}
// use fast mode as default
if task.Mode == "" {
task.Mode = "fast"
@@ -0,0 +1,33 @@
package moderation
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"errors"
"geekai/core/types"
)
type BaiduAIModeration struct {
config types.ModerationBaiduConfig
}
func NewBaiduAIModeration(sysConfig *types.SystemConfig) *BaiduAIModeration {
return &BaiduAIModeration{
config: sysConfig.Moderation.Baidu,
}
}
func (s *BaiduAIModeration) UpdateConfig(config types.ModerationBaiduConfig) {
s.config = config
}
func (s *BaiduAIModeration) Moderate(text string) (types.ModerationResult, error) {
return types.ModerationResult{}, errors.New("not implemented")
}
var _ Service = (*BaiduAIModeration)(nil)

Some files were not shown because too many files have changed in this diff Show More