增加后台管理,支持docker部署
79
README.md
@@ -1,29 +1,22 @@
|
|||||||
## 平台简介
|
## 平台简介
|
||||||
[](https://gitee.com/ageerle/ruoyi-ai/blob/master/LICENSE)
|
|
||||||
[](https://www.jetbrains.com/?from=ruoyi-chatgpt)
|
|
||||||
<br>
|
|
||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
|
||||||
[]()
|
|
||||||
[]()
|
|
||||||
|
|
||||||
> 基于ruoyi-plus实现AI聊天和绘画功能-后端
|
> 基于ruoyi-plus实现AI聊天和绘画功能-后端
|
||||||
后台管理界面使用elementUI 服务端使用Java17+SpringBoot3.X
|
|
||||||
|
|
||||||
> 项目文档:https://1m29197bp6.k.topthink.com/@ruoyi-ai/xiangmuchushihua.html
|
> 本项目完全开源免费!
|
||||||
|
后台管理界面使用elementUI服务端使用Java17+SpringBoot3.X
|
||||||
|
|
||||||
|
> Docker部署文档:https://easydoc.net/s/80136029
|
||||||
|
|
||||||
实现功能
|
实现功能
|
||||||
1. 支持ChatGPT4,Dall-E-3,ChatGPT-4-All(文件对话,图文对话)模型
|
1. 支持ChatGPT4,Dall-E-3,ChatGPT-4-All模型
|
||||||
2. 支持GPTS 可以使用Openai的所有的GPTs
|
2. 支持语音克隆(只需5分钟素材,即可克隆任意音色,基于GPT-SoVITS实现)
|
||||||
3. 支持语音克隆
|
3. 支持GPTS 可以使用Openai的所有的GPTs
|
||||||
4. 支持文生图模型 MidJourney(mj换脸,混图,重绘,角色一致性)
|
4. 支持文生图模型 MidJourney
|
||||||
5. 支持微信小程序
|
5. 支持微信小程序
|
||||||
6. 支持个人二维码实时到账(易支付)
|
6. 支持个人二维码实时到账(易支付)
|
||||||
7. 支持suno-v3(文生音乐)
|
7. 支持斗鱼、B站等直播间弹幕监听和AI自动回复
|
||||||
8. 支持个人微信接入ChatGPT
|
8. 支持个人微信接入ChatGPT
|
||||||
9. 支持stable-diffusion(对话式)
|
>测试功能: 私有知识库
|
||||||
10. 支持私有知识库
|
|
||||||
|
|
||||||
>测试功能: 微信机器人
|
|
||||||
>
|
>
|
||||||
>项目地址
|
>项目地址
|
||||||
<ul>
|
<ul>
|
||||||
@@ -33,6 +26,7 @@
|
|||||||
<li>演示地址: https://web.pandarobot.chat</li>
|
<li>演示地址: https://web.pandarobot.chat</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
## 小程序演示
|
## 小程序演示
|
||||||
<div>
|
<div>
|
||||||
<img style="margin:10px" src="./image/03.png" alt="drawing" width="300px" height="400px"/>
|
<img style="margin:10px" src="./image/03.png" alt="drawing" width="300px" height="400px"/>
|
||||||
@@ -49,38 +43,41 @@
|
|||||||
<div>
|
<div>
|
||||||
<img style="margin-top:10px" src="./image/07.png" alt="drawing" width="550px" height="300px"/>
|
<img style="margin-top:10px" src="./image/07.png" alt="drawing" width="550px" height="300px"/>
|
||||||
<img style="margin-top:10px" src="./image/08.png" alt="drawing" width="550px" height="300px"/>
|
<img style="margin-top:10px" src="./image/08.png" alt="drawing" width="550px" height="300px"/>
|
||||||
<img style="margin-top:10px" src="./image/13.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
<img style="margin-top:10px" src="./image/14.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 绘图(mj换脸、mj混图、mj角色一致(英文)、sd绘图)
|
## MJ绘图
|
||||||
<div>
|
<div>
|
||||||
<img style="margin-top:10px" src="./image/10.png" alt="drawing" width="550px" height="300px"/>
|
<img style="margin-top:10px" src="./image/10.png" alt="drawing" width="550px" height="300px"/>
|
||||||
<img style="margin-top:10px" src="./image/11.png" alt="drawing" width="550px" height="300px"/>
|
<img style="margin-top:10px" src="./image/11.png" alt="drawing" width="550px" height="300px"/>
|
||||||
<img style="margin-top:10px" src="./image/15.png" alt="drawing" width="550px" height="300px"/>
|
</div>
|
||||||
<img style="margin-top:10px" src="./image/16.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
|
## 微信智能助手
|
||||||
|
<div>
|
||||||
|
<img style="margin-top:10px" src="./image/09.png" alt="drawing" width="550px" height="300px"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 语音克隆
|
## 语音克隆
|
||||||
<div>
|
|
||||||
<img style="margin-top:10px" src="./image/17.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
<img style="margin-top:10px" src="./image/18.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 知识库
|
|
||||||
<div>
|
|
||||||
<img style="margin:10px" src="./image/12.png" alt="drawing" width="550px" height="400px"/>
|
|
||||||
<img style="margin:10px" src="./image/私有知识库业务架构图.drawio.png" alt="drawing" width="550px" height="400px"/>
|
|
||||||
<img style="margin-top:10px" src="./image/19.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
<img style="margin-top:10px" src="./image/20.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
<img style="margin-top:10px" src="./image/21.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
<img style="margin-top:10px" src="./image/22.png" alt="drawing" width="550px" height="300px"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 后台管理
|
https://github.com/ageerle/ruoyi-ai/assets/32251822/c3dd75eb-c5bd-4ab0-93bf-9221f4888827
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 弹幕助手
|
||||||
|
|
||||||
|
|
||||||
|
https://github.com/ageerle/ruoyi-ai/assets/32251822/2d809d94-6cfb-41b1-9dc0-a72ccfc63ba2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 私有知识库管理
|
||||||
<div>
|
<div>
|
||||||
<img style="margin-top:10px" src="./image/23.png" alt="drawing" width="550px" height="300px"/>
|
<img style="margin-top:10px;width:50%" src="./image/12.png" alt="drawing" width="550px" height="300px"/>
|
||||||
<img style="margin-top:10px" src="./image/24.png" alt="drawing" width="550px" height="300px"/>
|
<img style="margin-top:10px;width:50%" src="./image/私有知识库业务架构图.drawio.png" alt="drawing" width="550px" height="300px"/>
|
||||||
|
<img style="margin-top:10px;width:50%" src="https://github.com/ageerle/ruoyi-ai/assets/32251822/6d44ebcc-1ec4-43e0-b164-cac150e8a044" alt="drawing" width="550px" height="300px"/>
|
||||||
|
<img style="margin-top:10px;width:50%" src="https://github.com/ageerle/ruoyi-ai/assets/32251822/fbd50daf-19d9-4e0f-8e5e-8603af5fb520" alt="drawing" width="550px" height="300px"/>
|
||||||
|
<img style="margin-top:10px;width:50%" src="https://github.com/ageerle/ruoyi-ai/assets/32251822/b14dffe7-2ae1-4fa3-8bb0-bf0b44d7ebd9" alt="drawing" width="550px" height="300px"/>
|
||||||
|
<img style="margin-top:10px;width:50%" src="https://github.com/ageerle/ruoyi-ai/assets/32251822/4ae97a41-f1d2-4bb2-966f-83211193a115" alt="drawing" width="550px" height="300px"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 进群学习
|
## 进群学习
|
||||||
@@ -88,6 +85,12 @@
|
|||||||
<img src="./image/01.png" alt="drawing" width="300px" height="300px"/>
|
<img src="./image/01.png" alt="drawing" width="300px" height="300px"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## 支持一下
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src="./image/25.png" alt="drawing" width="300px" height="300px"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
## 参考项目
|
## 参考项目
|
||||||
<ol>
|
<ol>
|
||||||
<li>https://github.com/Grt1228/chatgpt-java</li>
|
<li>https://github.com/Grt1228/chatgpt-java</li>
|
||||||
|
|||||||
BIN
image/25.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
@@ -1,6 +1,12 @@
|
|||||||
#基础镜像
|
#基础镜像
|
||||||
FROM findepi/graalvm:java17-native
|
FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
ENV LANGUAGE C.UTF-8
|
||||||
|
ENV LC_ALL C.UTF-8
|
||||||
|
ENV SERVER_PORT=6039
|
||||||
|
|
||||||
MAINTAINER ageerle
|
MAINTAINER ageerle
|
||||||
|
|
||||||
RUN mkdir -p /ruoyi/server/logs \
|
RUN mkdir -p /ruoyi/server/logs \
|
||||||
@@ -11,12 +17,13 @@ RUN mkdir -p /ruoyi/server/logs \
|
|||||||
#工作空间
|
#工作空间
|
||||||
WORKDIR /ruoyi/server
|
WORKDIR /ruoyi/server
|
||||||
|
|
||||||
ENV SERVER_PORT=6039
|
|
||||||
|
|
||||||
EXPOSE ${SERVER_PORT}
|
EXPOSE ${SERVER_PORT}
|
||||||
|
|
||||||
ADD ./target/ruoyi-admin.jar ./app.jar
|
ADD ./target/ruoyi-admin.jar ./app.jar
|
||||||
|
|
||||||
|
|
||||||
ENTRYPOINT ["java", \
|
ENTRYPOINT ["java", \
|
||||||
"-Djava.security.egd=file:/dev/./urandom", \
|
"-Djava.security.egd=file:/dev/./urandom", \
|
||||||
"-Dserver.port=${SERVER_PORT}", \
|
"-Dserver.port=${SERVER_PORT}", \
|
||||||
@@ -24,3 +31,5 @@ ENTRYPOINT ["java", \
|
|||||||
# "-Dskywalking.agent.service_name=ruoyi-server", \
|
# "-Dskywalking.agent.service_name=ruoyi-server", \
|
||||||
# "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \
|
# "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \
|
||||||
"-jar", "app.jar"]
|
"-jar", "app.jar"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
@Controller
|
@Controller
|
||||||
public class IndexController {
|
public class IndexController {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 访问首页,提示语
|
* 访问首页,提示语
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.xmzs.controller;
|
package com.xmzs.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
|
import com.xmzs.common.core.domain.R;
|
||||||
import com.xmzs.common.wechat.Wechat;
|
import com.xmzs.common.wechat.Wechat;
|
||||||
|
import com.xmzs.common.wechat.controller.LoginController;
|
||||||
|
import com.xmzs.common.wechat.core.MsgCenter;
|
||||||
import com.xmzs.system.cofing.KeywordConfig;
|
import com.xmzs.system.cofing.KeywordConfig;
|
||||||
import com.xmzs.system.cofing.QqConfig;
|
|
||||||
import com.xmzs.system.cofing.WechatConfig;
|
import com.xmzs.system.cofing.WechatConfig;
|
||||||
import com.xmzs.system.handler.WechatMessageHandler;
|
import com.xmzs.system.handler.WechatMessageHandler;
|
||||||
import com.xmzs.system.service.ISseService;
|
import com.xmzs.system.service.ISseService;
|
||||||
@@ -11,7 +13,7 @@ import lombok.Getter;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,13 +41,21 @@ public class WeChatController {
|
|||||||
* 获取微信登录二维码
|
* 获取微信登录二维码
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@PostMapping("/getQr")
|
@GetMapping("/getQr")
|
||||||
public void getQr() {
|
public R<String> getQr() {
|
||||||
//微信
|
//微信
|
||||||
if (wechatConfig.getEnable()){
|
if (wechatConfig.getEnable()){
|
||||||
log.info("正在登录微信,请按提示操作:");
|
log.info("正在登录微信,请按提示操作:");
|
||||||
wechatBot = new Wechat(new WechatMessageHandler(sseService, keywordConfig), wechatConfig.getQrPath());
|
wechatBot = new Wechat(new WechatMessageHandler(sseService, keywordConfig));
|
||||||
|
// 登陆
|
||||||
|
LoginController login = new LoginController();
|
||||||
|
String qrCode = login.login_1();
|
||||||
|
new Thread(login::login_2).start();
|
||||||
wechatBot.start();
|
wechatBot.start();
|
||||||
|
return R.ok(qrCode);
|
||||||
|
}else {
|
||||||
|
return R.fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ spring:
|
|||||||
driverClassName: com.mysql.cj.jdbc.Driver
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
||||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||||
url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
url: ${DB_URL}
|
||||||
username: ry-vue
|
username: ${DB_USERNAME}
|
||||||
password: ry-vue
|
password: ${DB_PASSWORD}
|
||||||
|
|
||||||
|
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
# slave:
|
# slave:
|
||||||
@@ -103,9 +104,9 @@ spring:
|
|||||||
spring.data:
|
spring.data:
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
host: localhost
|
host: ${REDIS_HOST}
|
||||||
# 端口,默认为6379
|
# 端口,默认为6379
|
||||||
port: 6379
|
port: ${REDIS_PORT}
|
||||||
# 数据库索引
|
# 数据库索引
|
||||||
database: 0
|
database: 0
|
||||||
# 密码(如没有密码请注释掉)
|
# 密码(如没有密码请注释掉)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ server:
|
|||||||
# 日志配置
|
# 日志配置
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.xmzs: @logging.level@
|
com.xmzs: '@logging.level@'
|
||||||
org.springframework: warn
|
org.springframework: warn
|
||||||
config: classpath:logback-plus.xml
|
config: classpath:logback-plus.xml
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ spring:
|
|||||||
# 国际化资源文件路径
|
# 国际化资源文件路径
|
||||||
basename: i18n/messages
|
basename: i18n/messages
|
||||||
profiles:
|
profiles:
|
||||||
active: @profiles.active@
|
active: '@profiles.active@'
|
||||||
# 文件上传
|
# 文件上传
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
@@ -332,26 +332,24 @@ keyword:
|
|||||||
# ai语音指令(TTS模型 https://platform.openai.com/docs/api-reference/audio)
|
# ai语音指令(TTS模型 https://platform.openai.com/docs/api-reference/audio)
|
||||||
audio: "ai语音"
|
audio: "ai语音"
|
||||||
|
|
||||||
|
#绘画价格配置(元)
|
||||||
mj:
|
mj:
|
||||||
api-secret: 'sk-xx'
|
# 放大
|
||||||
task-store:
|
upsample: 0.1
|
||||||
type: in_memory
|
# 变化
|
||||||
timeout: 30d
|
change: 0.3
|
||||||
translate-way: gpt
|
# 图生图
|
||||||
# proxy:
|
blend: 0.3
|
||||||
# host: 127.0.0.1
|
# 图生文
|
||||||
# port: 10809
|
describe: 0.1
|
||||||
ng-discord:
|
# 文生图
|
||||||
server: 'https://xxx.pandarobot.chat/'
|
imagine: 0.3
|
||||||
cdn: 'https://xxx.pandarobot.chat/'
|
# 局部重绘
|
||||||
wss: 'https://xxx.pandarobot.chat/'
|
inpaint: 0.3
|
||||||
openai:
|
# 提示词分析
|
||||||
gpt-api-url: 'https://api.pandarobot.chat/'
|
shorten: 0.1
|
||||||
gpt-api-key: 'sk-xx'
|
# 换脸
|
||||||
accounts:
|
faceSwapping: 0.3
|
||||||
- guild-id: 'xx'
|
|
||||||
channel-id: 'xx'
|
|
||||||
user-token: 'xx'
|
|
||||||
|
|
||||||
--- # mail 邮件发送
|
--- # mail 邮件发送
|
||||||
mail:
|
mail:
|
||||||
@@ -365,7 +363,7 @@ mail:
|
|||||||
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
||||||
user: ageerle@163.com
|
user: ageerle@163.com
|
||||||
# 密码(填写授权码)
|
# 密码(填写授权码)
|
||||||
pass: xx
|
pass: ${MAIL_PASS}
|
||||||
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
||||||
starttlsEnable: true
|
starttlsEnable: true
|
||||||
# 使用SSL安全连接
|
# 使用SSL安全连接
|
||||||
@@ -375,15 +373,14 @@ mail:
|
|||||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||||
connectionTimeout: 0
|
connectionTimeout: 0
|
||||||
|
|
||||||
# chatgpt配置信息
|
# chatgpt和mj共用一个key
|
||||||
chat:
|
chat:
|
||||||
apiKey: 'sk-xxx'
|
apiKey: ${CHAT_API_KEY}
|
||||||
apiHost: 'https://api.pandarobot.chat/'
|
apiHost: ${CHAT_API_HOST}
|
||||||
|
|
||||||
# 支付配置信息
|
# 支付配置信息
|
||||||
pay:
|
pay:
|
||||||
pid: 'xxx'
|
pid: ${PAY_PID}
|
||||||
key: 'xxx'
|
key: ${PAY_KEY}
|
||||||
payUrl: 'https://pay.pandarobot.chat/mapi.php'
|
payUrl: 'https://pay.pandarobot.chat/mapi.php'
|
||||||
notify_url: 'https://www.pandarobot.chat/pay/returnUrl'
|
notify_url: 'https://www.pandarobot.chat/pay/returnUrl'
|
||||||
return_url: 'https://www.pandarobot.chat/pay/notifyUrl'
|
return_url: 'https://www.pandarobot.chat/pay/notifyUrl'
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@font-face {font-family: "Engravers' Old English BT";
|
||||||
|
src: url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot"); /* IE9*/
|
||||||
|
src: url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
|
||||||
|
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff2") format("woff2"), /* chrome firefox */
|
||||||
|
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff") format("woff"), /* chrome firefox */
|
||||||
|
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.ttf") format("truetype"), /* chrome firefox opera Safari, Android, iOS 4.2+*/
|
||||||
|
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.svg#Engravers' Old English BT") format("svg"); /* iOS 4.1- */
|
||||||
|
}
|
||||||
25
ruoyi-admin/src/main/resources/static/assets/css/vno.css
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
ruoyi-admin/src/main/resources/static/assets/img/logo.jpg
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
ruoyi-admin/src/main/resources/static/assets/img/logo1.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
ruoyi-admin/src/main/resources/static/assets/img/logo2.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
ruoyi-admin/src/main/resources/static/assets/img/logo3.jpg
Normal file
|
After Width: | Height: | Size: 115 KiB |
38
ruoyi-admin/src/main/resources/static/assets/js/bing.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const https = require('https')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
hostname: 'www.bing.com',
|
||||||
|
port: 443,
|
||||||
|
path: '/HPImageArchive.aspx?format=js&idx=0&n=8',
|
||||||
|
method: 'GET'
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = https.request(options, bing_res => {
|
||||||
|
let bing_body = [], bing_data = {};
|
||||||
|
bing_res.on('data', (chunk) => {
|
||||||
|
bing_body.push(chunk);
|
||||||
|
});
|
||||||
|
bing_res.on('end', () => {
|
||||||
|
bing_body = Buffer.concat(bing_body);
|
||||||
|
bing_data = JSON.parse(bing_body.toString());
|
||||||
|
let img_array = bing_data.images;
|
||||||
|
let img_url = [];
|
||||||
|
img_array.forEach(img => {
|
||||||
|
img_url.push(img.url);
|
||||||
|
});
|
||||||
|
var jsonpStr = "getBingImages(" + JSON.stringify(img_url) + ")";
|
||||||
|
fs.writeFile('./assets/json/images.json', jsonpStr, (err) => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
console.log("JSON data is saved: " + jsonpStr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
req.on('error', error => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
req.end()
|
||||||
104
ruoyi-admin/src/main/resources/static/assets/js/main.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
var iUp = (function () {
|
||||||
|
var time = 0,
|
||||||
|
duration = 150,
|
||||||
|
clean = function () {
|
||||||
|
time = 0;
|
||||||
|
},
|
||||||
|
up = function (element) {
|
||||||
|
setTimeout(function () {
|
||||||
|
element.classList.add("up");
|
||||||
|
}, time);
|
||||||
|
time += duration;
|
||||||
|
},
|
||||||
|
down = function (element) {
|
||||||
|
element.classList.remove("up");
|
||||||
|
},
|
||||||
|
toggle = function (element) {
|
||||||
|
setTimeout(function () {
|
||||||
|
element.classList.toggle("up");
|
||||||
|
}, time);
|
||||||
|
time += duration;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
clean: clean,
|
||||||
|
up: up,
|
||||||
|
down: down,
|
||||||
|
toggle: toggle
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function getBingImages(imgUrls) {
|
||||||
|
/**
|
||||||
|
* 获取Bing壁纸
|
||||||
|
* 先使用 GitHub Action 每天获取 Bing 壁纸 URL 并更新 images.json 文件
|
||||||
|
* 然后读取 images.json 文件中的数据
|
||||||
|
*/
|
||||||
|
var indexName = "bing-image-index";
|
||||||
|
var index = sessionStorage.getItem(indexName);
|
||||||
|
var panel = document.querySelector('#panel');
|
||||||
|
if (isNaN(index) || index == 7) index = 0;
|
||||||
|
else index++;
|
||||||
|
var imgUrl = imgUrls[index];
|
||||||
|
var url = "https://www.cn.bing.com" + imgUrl;
|
||||||
|
panel.style.background = "url('" + url + "') center center no-repeat #666";
|
||||||
|
panel.style.backgroundSize = "cover";
|
||||||
|
sessionStorage.setItem(indexName, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptEmail(encoded) {
|
||||||
|
var address = atob(encoded);
|
||||||
|
window.location.href = "mailto:" + address;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// 获取一言数据
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var res = JSON.parse(this.responseText);
|
||||||
|
document.getElementById('description').innerHTML = res.hitokoto + "<br/> -「<strong>" + res.from + "</strong>」";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.open("GET", "https://v1.hitokoto.cn", true);
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
var iUpElements = document.querySelectorAll(".iUp");
|
||||||
|
iUpElements.forEach(function (element) {
|
||||||
|
iUp.up(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
var avatarElement = document.querySelector(".js-avatar");
|
||||||
|
avatarElement.addEventListener('load', function () {
|
||||||
|
avatarElement.classList.add("show");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var btnMobileMenu = document.querySelector('.btn-mobile-menu__icon');
|
||||||
|
var navigationWrapper = document.querySelector('.navigation-wrapper');
|
||||||
|
|
||||||
|
btnMobileMenu.addEventListener('click', function () {
|
||||||
|
if (navigationWrapper.style.display == "block") {
|
||||||
|
navigationWrapper.addEventListener('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
|
||||||
|
navigationWrapper.classList.toggle('visible');
|
||||||
|
navigationWrapper.classList.toggle('animated');
|
||||||
|
navigationWrapper.classList.toggle('bounceOutUp');
|
||||||
|
navigationWrapper.removeEventListener('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', arguments.callee);
|
||||||
|
});
|
||||||
|
navigationWrapper.classList.toggle('animated');
|
||||||
|
navigationWrapper.classList.toggle('bounceInDown');
|
||||||
|
navigationWrapper.classList.toggle('animated');
|
||||||
|
navigationWrapper.classList.toggle('bounceOutUp');
|
||||||
|
} else {
|
||||||
|
navigationWrapper.classList.toggle('visible');
|
||||||
|
navigationWrapper.classList.toggle('animated');
|
||||||
|
navigationWrapper.classList.toggle('bounceInDown');
|
||||||
|
}
|
||||||
|
btnMobileMenu.classList.toggle('social');
|
||||||
|
btnMobileMenu.classList.toggle('iconfont');
|
||||||
|
btnMobileMenu.classList.toggle('icon-list');
|
||||||
|
btnMobileMenu.classList.toggle('social');
|
||||||
|
btnMobileMenu.classList.toggle('iconfont');
|
||||||
|
btnMobileMenu.classList.toggle('icon-angleup');
|
||||||
|
btnMobileMenu.classList.toggle('animated');
|
||||||
|
btnMobileMenu.classList.toggle('fadeIn');
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
getBingImages(["/th?id=OHR.TheRoachesPeakDistrict_EN-US9733115206_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.SanMiguelAllende_EN-US9621237021_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.JediMonastery_EN-US9398447907_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.SonoranSpring_EN-US9207877073_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.CratersOfTheMoon_EN-US6516727783_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.HawaiianLei_EN-US6290126556_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.CheetahRain_EN-US6179670004_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.TulouFujian_EN-US6009679228_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp"])
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1714852590465" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1571" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M263.1 844.8h497.7c22.9 0 41.5 18.6 41.5 41.5s-18.6 41.5-41.5 41.5H263.1c-22.9 0-41.5-18.6-41.5-41.5s18.6-41.5 41.5-41.5zM138.7 98.2h746.6c22.9 0 41.5 18.6 41.5 41.5v580.7c0 11-4.4 21.6-12.1 29.3-7.8 7.8-18.3 12.1-29.3 12.1H138.7c-11 0-21.6-4.4-29.3-12.1-7.8-7.8-12.1-18.3-12.1-29.3V139.7c-0.1-22.9 18.5-41.5 41.4-41.5z m331.8 478.9v60.4h83v-60.3c8.5-5.8 17.4-12.8 26.5-20.5l49.4 49.4 58.7-58.7-49.5-49.4c7.7-9 14.6-18 20.5-26.5h60.3v-83h-60.3c-6.4-9.1-13.3-18-20.5-26.5l49.4-49.4-58.7-58.6-49.4 49.4c-8.5-7.3-17.3-14.1-26.5-20.5v-60.3h-83V283c-8.5 5.8-17.4 12.8-26.5 20.5l-49.4-49.4-58.5 58.6 49.4 49.4c-7.2 8.5-14.1 17.3-20.5 26.5h-60.3v83h60.3c5.8 8.5 12.8 17.4 20.5 26.5L336 547.3l58.7 58.7 49.4-49.4c9.1 7.7 18 14.6 26.4 20.5z m0 0" p-id="1572"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -139,7 +139,7 @@ public class TikTokensUtil {
|
|||||||
* @return Encoding
|
* @return Encoding
|
||||||
*/
|
*/
|
||||||
public static Encoding getEncoding(@NotNull String modelName) {
|
public static Encoding getEncoding(@NotNull String modelName) {
|
||||||
return modelMap.get(modelName);
|
return modelMap.getOrDefault(modelName, modelMap.get(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,7 +204,9 @@ public class TikTokensUtil {
|
|||||||
tokensPerMessage = 3;
|
tokensPerMessage = 3;
|
||||||
tokensPerName = 1;
|
tokensPerName = 1;
|
||||||
}else {
|
}else {
|
||||||
log.warn("不支持的model {}. See https://github.com/openai/openai-python/blob/main/chatml.md 更多信息.",modelName);
|
log.warn("不支持的model {} 按gpt4计费",modelName);
|
||||||
|
tokensPerMessage = 3;
|
||||||
|
tokensPerName = 1;
|
||||||
}
|
}
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
for (Message msg : messages) {
|
for (Message msg : messages) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.xmzs.common.config.PayConfig;
|
|||||||
import com.xmzs.common.service.PayService;
|
import com.xmzs.common.service.PayService;
|
||||||
import com.xmzs.common.utils.MD5Util;
|
import com.xmzs.common.utils.MD5Util;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -18,6 +19,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class PayServiceImpl implements PayService {
|
public class PayServiceImpl implements PayService {
|
||||||
|
|
||||||
private final PayConfig payConfig;
|
private final PayConfig payConfig;
|
||||||
@@ -42,6 +44,7 @@ public class PayServiceImpl implements PayService {
|
|||||||
map.put("type", payConfig.getType());
|
map.put("type", payConfig.getType());
|
||||||
map.put("sign", sign);
|
map.put("sign", sign);
|
||||||
String body = HttpUtil.post(payConfig.getPayUrl(), map);
|
String body = HttpUtil.post(payConfig.getPayUrl(), map);
|
||||||
|
log.info("支付返回信息:{},配置信息: {}",body,payConfig);
|
||||||
JSONObject jsonObject = new JSONObject(body);
|
JSONObject jsonObject = new JSONObject(body);
|
||||||
return (String) jsonObject.get("qrcode");
|
return (String) jsonObject.get("qrcode");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,12 +69,12 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||||
if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
|
// if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
|
||||||
StopWatch stopWatch = invokeTimeTL.get();
|
// StopWatch stopWatch = invokeTimeTL.get();
|
||||||
stopWatch.stop();
|
// stopWatch.stop();
|
||||||
log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
|
// log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
|
||||||
invokeTimeTL.remove();
|
// invokeTimeTL.remove();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.xmzs.common.wechat;
|
package com.xmzs.common.wechat;
|
||||||
|
|
||||||
import com.xmzs.common.wechat.controller.LoginController;
|
|
||||||
import com.xmzs.common.wechat.core.MsgCenter;
|
import com.xmzs.common.wechat.core.MsgCenter;
|
||||||
import com.xmzs.common.wechat.face.IMsgHandlerFace;
|
import com.xmzs.common.wechat.face.IMsgHandlerFace;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -12,13 +11,9 @@ public class Wechat {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(Wechat.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Wechat.class);
|
||||||
private IMsgHandlerFace msgHandler;
|
private IMsgHandlerFace msgHandler;
|
||||||
|
|
||||||
public Wechat(IMsgHandlerFace msgHandler, String qrPath) {
|
public Wechat(IMsgHandlerFace msgHandler) {
|
||||||
System.setProperty("jsse.enableSNIExtension", "false"); // 防止SSL错误
|
System.setProperty("jsse.enableSNIExtension", "false"); // 防止SSL错误
|
||||||
this.msgHandler = msgHandler;
|
this.msgHandler = msgHandler;
|
||||||
|
|
||||||
// 登陆
|
|
||||||
LoginController login = new LoginController();
|
|
||||||
login.login(qrPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.xmzs.common.wechat.controller;
|
package com.xmzs.common.wechat.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.xmzs.common.core.exception.base.BaseException;
|
||||||
import com.xmzs.common.wechat.utils.SleepUtils;
|
import com.xmzs.common.wechat.utils.SleepUtils;
|
||||||
|
import com.xmzs.common.wechat.utils.enums.URLEnum;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -17,74 +19,71 @@ import com.xmzs.common.wechat.utils.tools.CommonTools;
|
|||||||
* 登陆控制器
|
* 登陆控制器
|
||||||
*
|
*
|
||||||
* @author https://github.com/yaphone
|
* @author https://github.com/yaphone
|
||||||
* @date 创建时间:2017年5月13日 下午12:56:07
|
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
*
|
* @date 创建时间:2017年5月13日 下午12:56:07
|
||||||
*/
|
*/
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
private static Logger LOG = LoggerFactory.getLogger(LoginController.class);
|
private static Logger LOG = LoggerFactory.getLogger(LoginController.class);
|
||||||
private ILoginService loginService = new LoginServiceImpl();
|
private ILoginService loginService = new LoginServiceImpl();
|
||||||
private static Core core = Core.getInstance();
|
private static Core core = Core.getInstance();
|
||||||
|
|
||||||
public void login(String qrPath) {
|
|
||||||
if (core.isAlive()) { // 已登陆
|
|
||||||
LOG.info("itchat4j已登陆");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
for (int count = 0; count < 10; count++) {
|
|
||||||
LOG.info("获取UUID");
|
|
||||||
while (loginService.getUuid() == null) {
|
|
||||||
LOG.info("1. 获取微信UUID");
|
|
||||||
while (loginService.getUuid() == null) {
|
|
||||||
LOG.warn("1.1. 获取微信UUID失败,两秒后重新获取");
|
|
||||||
SleepUtils.sleep(2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.info("2. 获取登陆二维码图片");
|
|
||||||
if (loginService.getQR(qrPath)) {
|
|
||||||
break;
|
|
||||||
} else if (count == 10) {
|
|
||||||
LOG.error("2.2. 获取登陆二维码图片失败,系统退出");
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.info("3. 请扫描二维码图片,并在手机上确认");
|
|
||||||
if (!core.isAlive()) {
|
|
||||||
loginService.login();
|
|
||||||
core.setAlive(true);
|
|
||||||
LOG.info(("登陆成功"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
LOG.info("4. 登陆超时,请重新扫描二维码图片");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.info("5. 登陆成功,微信初始化");
|
/**
|
||||||
if (!loginService.webWxInit()) {
|
* 获取二维码地址
|
||||||
LOG.info("6. 微信初始化异常");
|
* 风险:已登录账号不可调用该接口,会移除当前core信息
|
||||||
System.exit(0);
|
* @return
|
||||||
}
|
*/
|
||||||
|
public String login_1() {
|
||||||
|
if (core.isAlive()) {
|
||||||
|
LOG.warn("微信已登陆");
|
||||||
|
throw new BaseException("微信已登陆");
|
||||||
|
}
|
||||||
|
LOG.info("1.获取微信UUID");
|
||||||
|
while (loginService.getUuid() == null) {
|
||||||
|
LOG.warn("1.1. 获取微信UUID失败,一秒后重新获取");
|
||||||
|
SleepUtils.sleep(1000);
|
||||||
|
}
|
||||||
|
LOG.info("2. 获取登陆二维码图片");
|
||||||
|
return URLEnum.QRCODE_URL.getUrl() + core.getUuid();
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("6. 开启微信状态通知");
|
|
||||||
loginService.wxStatusNotify();
|
|
||||||
|
|
||||||
LOG.info("7. 清除。。。。");
|
public void login_2() {
|
||||||
CommonTools.clearScreen();
|
LOG.info("3. 请扫描二维码图片,并在手机上确认");
|
||||||
LOG.info(String.format("欢迎回来, %s", core.getNickName()));
|
if (!core.isAlive()) {
|
||||||
|
loginService.login();
|
||||||
|
core.setAlive(true);
|
||||||
|
LOG.info(("登陆成功"));
|
||||||
|
}
|
||||||
|
LOG.info("4. 登陆超时,请重新扫描二维码图片");
|
||||||
|
|
||||||
LOG.info("8. 开始接收消息");
|
|
||||||
loginService.startReceiving();
|
|
||||||
|
|
||||||
LOG.info("9. 获取联系人信息");
|
LOG.info("5. 登陆成功,微信初始化");
|
||||||
loginService.webWxGetContact();
|
if (!loginService.webWxInit()) {
|
||||||
|
LOG.info("6. 微信初始化异常");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("10. 获取群好友及群好友列表");
|
LOG.info("6. 开启微信状态通知");
|
||||||
loginService.WebWxBatchGetContact();
|
loginService.wxStatusNotify();
|
||||||
|
|
||||||
LOG.info("11. 缓存本次登陆好友相关消息");
|
LOG.info("7. 清除。。。。");
|
||||||
WechatTools.setUserInfo(); // 登陆成功后缓存本次登陆好友相关消息(NickName, UserName)
|
CommonTools.clearScreen();
|
||||||
|
LOG.info(String.format("欢迎回来, %s", core.getNickName()));
|
||||||
|
|
||||||
LOG.info("12.开启微信状态检测线程");
|
LOG.info("8. 开始接收消息");
|
||||||
new Thread(new CheckLoginStatusThread()).start();
|
loginService.startReceiving();
|
||||||
}
|
|
||||||
|
LOG.info("9. 获取联系人信息");
|
||||||
|
loginService.webWxGetContact();
|
||||||
|
|
||||||
|
LOG.info("10. 获取群好友及群好友列表");
|
||||||
|
loginService.WebWxBatchGetContact();
|
||||||
|
|
||||||
|
LOG.info("11. 缓存本次登陆好友相关消息");
|
||||||
|
WechatTools.setUserInfo(); // 登陆成功后缓存本次登陆好友相关消息(NickName, UserName)
|
||||||
|
|
||||||
|
LOG.info("12.开启微信状态检测线程");
|
||||||
|
new Thread(new CheckLoginStatusThread()).start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,22 @@
|
|||||||
package com.xmzs.midjourney.controller;
|
package com.xmzs.midjourney.controller;
|
||||||
|
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.xmzs.common.chat.constant.OpenAIConst;
|
|
||||||
import com.xmzs.common.core.domain.model.LoginUser;
|
|
||||||
import com.xmzs.common.core.exception.base.BaseException;
|
|
||||||
import com.xmzs.common.satoken.utils.LoginHelper;
|
|
||||||
import com.xmzs.midjourney.domain.InsightFace;
|
import com.xmzs.midjourney.domain.InsightFace;
|
||||||
import com.xmzs.system.domain.bo.ChatMessageBo;
|
import com.xmzs.midjourney.domain.MjPriceConfig;
|
||||||
import com.xmzs.system.service.IChatMessageService;
|
import com.xmzs.midjourney.util.MjOkHttpUtil;
|
||||||
import com.xmzs.system.service.IChatService;
|
|
||||||
import com.xmzs.system.service.ISseService;
|
import com.xmzs.system.service.IChatCostService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.MediaType;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
import okio.Buffer;
|
|
||||||
import okio.BufferedSink;
|
|
||||||
import okio.GzipSink;
|
|
||||||
import okio.Okio;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@Api(tags = "任务查询")
|
@Api(tags = "任务查询")
|
||||||
@RestController
|
@RestController
|
||||||
@@ -39,62 +25,22 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class FaceController {
|
public class FaceController {
|
||||||
|
|
||||||
@Value("${chat.apiKey}")
|
private final IChatCostService chatCostService;
|
||||||
private String apiKey;
|
|
||||||
@Value("${chat.apiHost}")
|
|
||||||
private String apiHost;
|
|
||||||
|
|
||||||
@Autowired
|
private final MjOkHttpUtil mjOkHttpUtil;
|
||||||
private IChatService chatService;
|
|
||||||
|
|
||||||
@Autowired
|
private final MjPriceConfig priceConfig;
|
||||||
private ISseService sseService;
|
|
||||||
|
|
||||||
@ApiOperation(value = "换脸")
|
@ApiOperation(value = "换脸")
|
||||||
@PostMapping("/insight-face/swap")
|
@PostMapping("/insight-face/swap")
|
||||||
public String insightFace(@RequestBody InsightFace insightFace) {
|
public String insightFace(@RequestBody InsightFace insightFace) {
|
||||||
// 查询是否是付费用户
|
// 扣除接口费用并且保存消息记录
|
||||||
sseService.checkUserGrade();
|
chatCostService.taskDeduct("mj","换脸", NumberUtils.toDouble(priceConfig.getFaceSwapping(), 0.3));
|
||||||
// 扣除接口费用
|
|
||||||
chatService.mjTaskDeduct("换脸", OpenAIConst.MJ_COST_TYPE2);
|
|
||||||
OkHttpClient client = new OkHttpClient.Builder()
|
|
||||||
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时时间
|
|
||||||
.writeTimeout(30, TimeUnit.SECONDS) // 写入超时时间
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS) // 读取超时时间
|
|
||||||
.build();
|
|
||||||
// 创建一个Request对象来配置你的请求
|
|
||||||
// 创建请求体(这里使用JSON作为媒体类型)
|
// 创建请求体(这里使用JSON作为媒体类型)
|
||||||
String jsonStr = JSONUtil.toJsonStr(insightFace);
|
String insightFaceJson = JSONUtil.toJsonStr(insightFace);
|
||||||
|
String url = "mj/insight-face/swap";
|
||||||
MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
Request request = mjOkHttpUtil.createPostRequest(url, insightFaceJson);
|
||||||
okhttp3.RequestBody body = okhttp3.RequestBody.create(jsonStr, JSON);
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
Buffer buffer = new Buffer();
|
|
||||||
GzipSink gzipSink = new GzipSink(buffer);
|
|
||||||
BufferedSink gzipBufferedSink = Okio.buffer(gzipSink);
|
|
||||||
try {
|
|
||||||
body.writeTo(gzipBufferedSink);
|
|
||||||
gzipBufferedSink.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建POST请求
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.header("mj-api-secret", apiKey)
|
|
||||||
.header("Content-Encoding", "gzip")
|
|
||||||
.url(apiHost + "mj/insight-face/swap") // 替换为你的URL
|
|
||||||
.post(body)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response response = client.newCall(request).execute()) {
|
|
||||||
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
|
||||||
if (response.body() != null) {
|
|
||||||
return response.body().string();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("换脸失败! {}", e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,24 @@
|
|||||||
package com.xmzs.midjourney.controller;
|
package com.xmzs.midjourney.controller;
|
||||||
|
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
|
||||||
import cn.hutool.core.util.RandomUtil;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.xmzs.common.chat.constant.OpenAIConst;
|
import com.xmzs.midjourney.domain.MjPriceConfig;
|
||||||
import com.xmzs.common.core.domain.model.LoginUser;
|
|
||||||
import com.xmzs.common.core.exception.base.BaseException;
|
|
||||||
import com.xmzs.common.satoken.utils.LoginHelper;
|
|
||||||
import com.xmzs.midjourney.Constants;
|
|
||||||
import com.xmzs.midjourney.ProxyProperties;
|
|
||||||
import com.xmzs.midjourney.ReturnCode;
|
|
||||||
import com.xmzs.midjourney.dto.*;
|
import com.xmzs.midjourney.dto.*;
|
||||||
import com.xmzs.midjourney.enums.TaskAction;
|
import com.xmzs.midjourney.enums.ActionType;
|
||||||
import com.xmzs.midjourney.enums.TaskStatus;
|
import com.xmzs.midjourney.util.*;
|
||||||
import com.xmzs.midjourney.enums.TranslateWay;
|
import com.xmzs.system.service.IChatCostService;
|
||||||
import com.xmzs.midjourney.exception.BannedPromptException;
|
|
||||||
import com.xmzs.midjourney.result.SubmitResultVO;
|
|
||||||
import com.xmzs.midjourney.service.TaskService;
|
|
||||||
import com.xmzs.midjourney.service.TaskStoreService;
|
|
||||||
import com.xmzs.midjourney.service.TranslateService;
|
|
||||||
import com.xmzs.midjourney.support.Task;
|
|
||||||
import com.xmzs.midjourney.support.TaskCondition;
|
|
||||||
import com.xmzs.midjourney.util.BannedPromptUtils;
|
|
||||||
import com.xmzs.midjourney.util.ConvertUtils;
|
|
||||||
import com.xmzs.midjourney.util.MimeTypeUtils;
|
|
||||||
import com.xmzs.midjourney.util.SnowFlake;
|
|
||||||
import com.xmzs.midjourney.util.TaskChangeParams;
|
|
||||||
import com.xmzs.system.domain.bo.ChatMessageBo;
|
|
||||||
import com.xmzs.system.service.IChatMessageService;
|
|
||||||
import com.xmzs.system.service.IChatService;
|
|
||||||
import com.xmzs.system.service.ISseService;
|
|
||||||
import eu.maxschuster.dataurl.DataUrl;
|
|
||||||
import eu.maxschuster.dataurl.DataUrlSerializer;
|
|
||||||
import eu.maxschuster.dataurl.IDataUrlSerializer;
|
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.Request;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import okhttp3.*;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Api(tags = "任务提交")
|
@Api(tags = "任务提交")
|
||||||
@RestController
|
@RestController
|
||||||
@@ -63,285 +26,114 @@ import okhttp3.*;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SubmitController {
|
public class SubmitController {
|
||||||
private final TranslateService translateService;
|
|
||||||
private final ProxyProperties properties;
|
|
||||||
private final TaskService taskService;
|
|
||||||
private final TaskStoreService taskStoreService;
|
|
||||||
@Value("${chat.apiKey}")
|
|
||||||
private String apiKey;
|
|
||||||
@Value("${chat.apiHost}")
|
|
||||||
private String apiHost;
|
|
||||||
@Autowired
|
|
||||||
private IChatService chatService;
|
|
||||||
@Autowired
|
|
||||||
private IChatMessageService chatMessageService;
|
|
||||||
@Autowired
|
|
||||||
private ISseService sseService;
|
|
||||||
|
|
||||||
@ApiOperation(value = "提交Imagine任务")
|
private final MjPriceConfig priceConfig;
|
||||||
@PostMapping("/imagine")
|
|
||||||
public SubmitResultVO imagine(@RequestBody SubmitImagineDTO imagineDTO) {
|
private final IChatCostService chatCostService;
|
||||||
String prompt = imagineDTO.getPrompt();
|
|
||||||
if (CharSequenceUtil.isBlank(prompt)) {
|
private final MjOkHttpUtil mjOkHttpUtil;
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "prompt不能为空");
|
|
||||||
}
|
@ApiOperation(value = "绘图变化")
|
||||||
prompt = prompt.trim();
|
@PostMapping("/change")
|
||||||
Task task = newTask(imagineDTO);
|
public String change(@RequestBody SubmitChangeDTO changeDTO) {
|
||||||
task.setAction(TaskAction.IMAGINE);
|
String jsonStr = JSONUtil.toJsonStr(changeDTO);
|
||||||
task.setPrompt(prompt);
|
String url = "mj/submit/change";
|
||||||
String promptEn = translatePrompt(prompt);
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
try {
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
BannedPromptUtils.checkBanned(promptEn);
|
}
|
||||||
} catch (BannedPromptException e) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.BANNED_PROMPT, "可能包含敏感词")
|
@ApiOperation(value = "执行动作")
|
||||||
.setProperty("promptEn", promptEn).setProperty("bannedWord", e.getMessage());
|
@PostMapping("/action")
|
||||||
}
|
public String action(@RequestBody SubmitActionDTO changeDTO) {
|
||||||
List<String> base64Array = Optional.ofNullable(imagineDTO.getBase64Array()).orElse(new ArrayList<>());
|
ActionType actionType = ActionType.fromCustomId(getAction(changeDTO.getCustomId()));
|
||||||
if (CharSequenceUtil.isNotBlank(imagineDTO.getBase64())) {
|
Optional.ofNullable(actionType).ifPresentOrElse(
|
||||||
base64Array.add(imagineDTO.getBase64());
|
type -> {
|
||||||
}
|
switch (type) {
|
||||||
List<DataUrl> dataUrls;
|
case UP_SAMPLE:
|
||||||
try {
|
chatCostService.taskDeduct("mj","放大", NumberUtils.toDouble(priceConfig.getUpsample(), 0.1));
|
||||||
dataUrls = ConvertUtils.convertBase64Array(base64Array);
|
break;
|
||||||
} catch (MalformedURLException e) {
|
case IN_PAINT:
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误");
|
// 局部重绘已经扣费,不执行任何操作
|
||||||
}
|
break;
|
||||||
task.setPromptEn(promptEn);
|
default:
|
||||||
task.setDescription("/imagine " + prompt);
|
chatCostService.taskDeduct("mj","变化", NumberUtils.toDouble(priceConfig.getChange(), 0.3));
|
||||||
return this.taskService.submitImagine(task, dataUrls);
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> chatCostService.taskDeduct("mj","变化", NumberUtils.toDouble(priceConfig.getChange(), 0.3))
|
||||||
|
);
|
||||||
|
|
||||||
|
String jsonStr = JSONUtil.toJsonStr(changeDTO);
|
||||||
|
String url = "mj/submit/action";
|
||||||
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "绘图变化-simple")
|
@ApiOperation(value = "绘图变化-simple")
|
||||||
@PostMapping("/simple-change")
|
@PostMapping("/simple-change")
|
||||||
public SubmitResultVO simpleChange(@RequestBody SubmitSimpleChangeDTO simpleChangeDTO) {
|
public String simpleChange(@RequestBody SubmitSimpleChangeDTO simpleChangeDTO) {
|
||||||
TaskChangeParams changeParams = ConvertUtils.convertChangeParams(simpleChangeDTO.getContent());
|
String jsonStr = JSONUtil.toJsonStr(simpleChangeDTO);
|
||||||
if (changeParams == null) {
|
String url = "mj/submit/simple-change";
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "content参数错误");
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
}
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
SubmitChangeDTO changeDTO = new SubmitChangeDTO();
|
|
||||||
changeDTO.setAction(changeParams.getAction());
|
|
||||||
changeDTO.setTaskId(changeParams.getId());
|
|
||||||
changeDTO.setIndex(changeParams.getIndex());
|
|
||||||
changeDTO.setState(simpleChangeDTO.getState());
|
|
||||||
changeDTO.setNotifyHook(simpleChangeDTO.getNotifyHook());
|
|
||||||
return change(changeDTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "绘图变化")
|
@ApiOperation(value = "提交图生图、混图任务")
|
||||||
@PostMapping("/change")
|
@PostMapping("/blend")
|
||||||
public SubmitResultVO change(@RequestBody SubmitChangeDTO changeDTO) {
|
public String blend(@RequestBody SubmitBlendDTO blendDTO) {
|
||||||
if (CharSequenceUtil.isBlank(changeDTO.getTaskId())) {
|
chatCostService.taskDeduct("mj","图生图", NumberUtils.toDouble(priceConfig.getBlend(), 0.3));
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "taskId不能为空");
|
String jsonStr = JSONUtil.toJsonStr(blendDTO);
|
||||||
}
|
String url = "mj/submit/blend";
|
||||||
if (!Set.of(TaskAction.UPSCALE, TaskAction.VARIATION, TaskAction.REROLL).contains(changeDTO.getAction())) {
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "action参数错误");
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
}
|
|
||||||
String description = "/up " + changeDTO.getTaskId();
|
|
||||||
if (TaskAction.REROLL.equals(changeDTO.getAction())) {
|
|
||||||
description += " R";
|
|
||||||
} else {
|
|
||||||
description += " " + changeDTO.getAction().name().charAt(0) + changeDTO.getIndex();
|
|
||||||
}
|
|
||||||
if (TaskAction.UPSCALE.equals(changeDTO.getAction())) {
|
|
||||||
TaskCondition condition = new TaskCondition().setDescription(description);
|
|
||||||
Task existTask = this.taskStoreService.findOne(condition);
|
|
||||||
if (existTask != null) {
|
|
||||||
return SubmitResultVO.of(ReturnCode.EXISTED, "任务已存在", existTask.getId())
|
|
||||||
.setProperty("status", existTask.getStatus())
|
|
||||||
.setProperty("imageUrl", existTask.getImageUrl());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Task targetTask = this.taskStoreService.get(changeDTO.getTaskId());
|
|
||||||
if (targetTask == null) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.NOT_FOUND, "关联任务不存在或已失效");
|
|
||||||
}
|
|
||||||
if (!TaskStatus.SUCCESS.equals(targetTask.getStatus())) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "关联任务状态错误");
|
|
||||||
}
|
|
||||||
if (!Set.of(TaskAction.IMAGINE, TaskAction.VARIATION, TaskAction.REROLL, TaskAction.BLEND).contains(targetTask.getAction())) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "关联任务不允许执行变化");
|
|
||||||
}
|
|
||||||
Task task = newTask(changeDTO);
|
|
||||||
task.setAction(changeDTO.getAction());
|
|
||||||
task.setPrompt(targetTask.getPrompt());
|
|
||||||
task.setPromptEn(targetTask.getPromptEn());
|
|
||||||
task.setProperty(Constants.TASK_PROPERTY_FINAL_PROMPT, targetTask.getProperty(Constants.TASK_PROPERTY_FINAL_PROMPT));
|
|
||||||
task.setProperty(Constants.TASK_PROPERTY_PROGRESS_MESSAGE_ID, targetTask.getProperty(Constants.TASK_PROPERTY_MESSAGE_ID));
|
|
||||||
task.setProperty(Constants.TASK_PROPERTY_DISCORD_INSTANCE_ID, targetTask.getProperty(Constants.TASK_PROPERTY_DISCORD_INSTANCE_ID));
|
|
||||||
task.setDescription(description);
|
|
||||||
int messageFlags = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_FLAGS);
|
|
||||||
String messageId = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_MESSAGE_ID);
|
|
||||||
String messageHash = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_MESSAGE_HASH);
|
|
||||||
if (TaskAction.UPSCALE.equals(changeDTO.getAction())) {
|
|
||||||
return this.taskService.submitUpscale(task, messageId, messageHash, changeDTO.getIndex(), messageFlags);
|
|
||||||
} else if (TaskAction.VARIATION.equals(changeDTO.getAction())) {
|
|
||||||
return this.taskService.submitVariation(task, messageId, messageHash, changeDTO.getIndex(), messageFlags);
|
|
||||||
} else {
|
|
||||||
return this.taskService.submitReroll(task, messageId, messageHash, messageFlags);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "绘图变化")
|
@ApiOperation(value = "提交图生文任务")
|
||||||
@PostMapping("/action")
|
@PostMapping("/describe")
|
||||||
public String action(@RequestBody SubmitActionDTO changeDTO) {
|
public String describe(@RequestBody SubmitDescribeDTO describeDTO) {
|
||||||
// 查询是否是付费用户
|
chatCostService.taskDeduct("mj","图生文", NumberUtils.toDouble(priceConfig.getDescribe(), 0.1));
|
||||||
sseService.checkUserGrade();
|
String jsonStr = JSONUtil.toJsonStr(describeDTO);
|
||||||
// 扣除接口费用
|
String url = "mj/submit/describe";
|
||||||
if ("upsample".equals(getAction(changeDTO.getCustomId()))) {
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
mjTaskDeduct("放大", OpenAIConst.MJ_COST_TYPE2);
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
} else {
|
}
|
||||||
// Inpaint: 局部重绘
|
|
||||||
// reroll 重绘
|
|
||||||
// upsample 放大
|
|
||||||
// zoom 变焦
|
|
||||||
// upscale 高清放大
|
|
||||||
// variation 变化
|
|
||||||
if (!"Inpaint".equals(getAction(changeDTO.getCustomId()))) {
|
|
||||||
mjTaskDeduct("变化", OpenAIConst.MJ_COST_TYPE1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OkHttpClient client = new OkHttpClient.Builder()
|
|
||||||
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时时间
|
|
||||||
.writeTimeout(30, TimeUnit.SECONDS) // 写入超时时间
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS) // 读取超时时间
|
|
||||||
.build();
|
|
||||||
|
|
||||||
String jsonStr = JSONUtil.toJsonStr(changeDTO);
|
@ApiOperation(value = "提交文生图任务")
|
||||||
|
@PostMapping("/imagine")
|
||||||
|
public String imagine(@RequestBody SubmitImagineDTO imagineDTO) {
|
||||||
|
chatCostService.taskDeduct("mj",imagineDTO.getPrompt(), NumberUtils.toDouble(priceConfig.getImagine(), 0.3));
|
||||||
|
String jsonStr = JSONUtil.toJsonStr(imagineDTO);
|
||||||
|
String url = "mj/submit/imagine";
|
||||||
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
MediaType mediaType = MediaType.parse("application/json");
|
@ApiOperation(value = "提交局部重绘任务")
|
||||||
okhttp3.RequestBody body = okhttp3.RequestBody.create(jsonStr, mediaType);
|
@PostMapping("/modal")
|
||||||
Request request = new Request.Builder()
|
public String modal(@RequestBody SubmitModalDTO submitModalDTO) {
|
||||||
.url(apiHost + "mj/submit/action")
|
chatCostService.taskDeduct("mj","局部重绘", NumberUtils.toDouble(priceConfig.getInpaint(), 0.1));
|
||||||
.method("POST", body)
|
String jsonStr = JSONUtil.toJsonStr(submitModalDTO);
|
||||||
.header("mj-api-secret", apiKey) // 设置Authorization header
|
String url = "mj/submit/modal";
|
||||||
.build();
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
try {
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
Response response = client.newCall(request).execute();
|
}
|
||||||
return response.body().string();
|
|
||||||
} catch (IOException e) {
|
@ApiOperation(value = "提交提示词分析任务")
|
||||||
log.error("绘图变化失败:{}", e.getMessage());
|
@PostMapping("/shorten")
|
||||||
}
|
public String shorten(@RequestBody SubmitShortenDTO submitShortenDTO) {
|
||||||
return null;
|
chatCostService.taskDeduct("mj","提示词分析", NumberUtils.toDouble(priceConfig.getShorten(), 0.1));
|
||||||
|
String jsonStr = JSONUtil.toJsonStr(submitShortenDTO);
|
||||||
|
String url = "mj/submit/shorten";
|
||||||
|
Request request = mjOkHttpUtil.createPostRequest(url, jsonStr);
|
||||||
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAction(String customId) {
|
public String getAction(String customId) {
|
||||||
// 检查 customId 是否为空
|
if (customId == null || customId.isEmpty()) {
|
||||||
if(customId == null || customId.isEmpty()) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 使用 "::" 分割字符串
|
|
||||||
String[] parts = customId.split("::");
|
String[] parts = customId.split("::");
|
||||||
// "MJ", "Inpaint", "1", "4fca7c14-181c-4...", "SOLO"
|
return customId.endsWith("SOLO") ? parts[1] : parts[2];
|
||||||
if(customId.endsWith("SOLO")) {
|
|
||||||
return parts[1];
|
|
||||||
}
|
|
||||||
// 返回 "upsample" 值,假设它总是在第三个位置
|
|
||||||
return parts[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mjTaskDeduct(String prompt, double cost) {
|
|
||||||
//扣除费用
|
|
||||||
chatService.deductUserBalance(getUserId(), cost);
|
|
||||||
// 保存消息记录
|
|
||||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
|
||||||
chatMessageBo.setUserId(getUserId());
|
|
||||||
chatMessageBo.setModelName("mj");
|
|
||||||
chatMessageBo.setContent(prompt);
|
|
||||||
chatMessageBo.setDeductCost(cost);
|
|
||||||
chatMessageBo.setTotalTokens(0);
|
|
||||||
chatMessageService.insertByBo(chatMessageBo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户Id
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Long getUserId() {
|
|
||||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
|
||||||
if (loginUser == null) {
|
|
||||||
throw new BaseException("用户未登录!");
|
|
||||||
}
|
|
||||||
return loginUser.getUserId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation(value = "提交Describe任务")
|
|
||||||
@PostMapping("/describe")
|
|
||||||
public SubmitResultVO describe(@RequestBody SubmitDescribeDTO describeDTO) {
|
|
||||||
if (CharSequenceUtil.isBlank(describeDTO.getBase64())) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64不能为空");
|
|
||||||
}
|
|
||||||
IDataUrlSerializer serializer = new DataUrlSerializer();
|
|
||||||
DataUrl dataUrl;
|
|
||||||
try {
|
|
||||||
dataUrl = serializer.unserialize(describeDTO.getBase64());
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误");
|
|
||||||
}
|
|
||||||
Task task = newTask(describeDTO);
|
|
||||||
task.setAction(TaskAction.DESCRIBE);
|
|
||||||
String taskFileName = task.getId() + "." + MimeTypeUtils.guessFileSuffix(dataUrl.getMimeType());
|
|
||||||
task.setDescription("/describe " + taskFileName);
|
|
||||||
return this.taskService.submitDescribe(task, dataUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation(value = "提交Blend任务")
|
|
||||||
@PostMapping("/blend")
|
|
||||||
public SubmitResultVO blend(@RequestBody SubmitBlendDTO blendDTO) {
|
|
||||||
List<String> base64Array = blendDTO.getBase64Array();
|
|
||||||
if (base64Array == null || base64Array.size() < 2 || base64Array.size() > 5) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64List参数错误");
|
|
||||||
}
|
|
||||||
if (blendDTO.getDimensions() == null) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "dimensions参数错误");
|
|
||||||
}
|
|
||||||
IDataUrlSerializer serializer = new DataUrlSerializer();
|
|
||||||
List<DataUrl> dataUrlList = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
for (String base64 : base64Array) {
|
|
||||||
DataUrl dataUrl = serializer.unserialize(base64);
|
|
||||||
dataUrlList.add(dataUrl);
|
|
||||||
}
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误");
|
|
||||||
}
|
|
||||||
Task task = newTask(blendDTO);
|
|
||||||
task.setAction(TaskAction.BLEND);
|
|
||||||
task.setDescription("/blend " + task.getId() + " " + dataUrlList.size());
|
|
||||||
return this.taskService.submitBlend(task, dataUrlList, blendDTO.getDimensions());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task newTask(BaseSubmitDTO base) {
|
|
||||||
Task task = new Task();
|
|
||||||
task.setId(System.currentTimeMillis() + RandomUtil.randomNumbers(3));
|
|
||||||
task.setSubmitTime(System.currentTimeMillis());
|
|
||||||
task.setState(base.getState());
|
|
||||||
String notifyHook = CharSequenceUtil.isBlank(base.getNotifyHook()) ? this.properties.getNotifyHook() : base.getNotifyHook();
|
|
||||||
task.setProperty(Constants.TASK_PROPERTY_NOTIFY_HOOK, notifyHook);
|
|
||||||
task.setProperty(Constants.TASK_PROPERTY_NONCE, SnowFlake.INSTANCE.nextId());
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String translatePrompt(String prompt) {
|
|
||||||
if (TranslateWay.NULL.equals(this.properties.getTranslateWay()) || CharSequenceUtil.isBlank(prompt)) {
|
|
||||||
return prompt;
|
|
||||||
}
|
|
||||||
List<String> imageUrls = new ArrayList<>();
|
|
||||||
Matcher imageMatcher = Pattern.compile("https?://[a-z0-9-_:@&?=+,.!/~*'%$]+\\x20+", Pattern.CASE_INSENSITIVE).matcher(prompt);
|
|
||||||
while (imageMatcher.find()) {
|
|
||||||
imageUrls.add(imageMatcher.group(0));
|
|
||||||
}
|
|
||||||
String paramStr = "";
|
|
||||||
Matcher paramMatcher = Pattern.compile("\\x20+-{1,2}[a-z]+.*$", Pattern.CASE_INSENSITIVE).matcher(prompt);
|
|
||||||
if (paramMatcher.find()) {
|
|
||||||
paramStr = paramMatcher.group(0);
|
|
||||||
}
|
|
||||||
String imageStr = CharSequenceUtil.join("", imageUrls);
|
|
||||||
String text = prompt.substring(imageStr.length(), prompt.length() - paramStr.length());
|
|
||||||
if (CharSequenceUtil.isNotBlank(text)) {
|
|
||||||
text = this.translateService.translateToEnglish(text).trim();
|
|
||||||
}
|
|
||||||
return imageStr + text + paramStr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,15 @@
|
|||||||
package com.xmzs.midjourney.controller;
|
package com.xmzs.midjourney.controller;
|
||||||
|
|
||||||
import cn.hutool.core.comparator.CompareUtil;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.xmzs.midjourney.dto.SubmitImagineDTO;
|
import com.xmzs.midjourney.util.MjOkHttpUtil;
|
||||||
import com.xmzs.midjourney.dto.TaskConditionDTO;
|
import com.xmzs.midjourney.dto.TaskConditionDTO;
|
||||||
import com.xmzs.midjourney.loadbalancer.DiscordLoadBalancer;
|
|
||||||
import com.xmzs.midjourney.result.SubmitResultVO;
|
|
||||||
import com.xmzs.midjourney.service.TaskStoreService;
|
|
||||||
import com.xmzs.midjourney.support.Task;
|
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import io.swagger.annotations.ApiParam;
|
import io.swagger.annotations.ApiParam;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.*;
|
import okhttp3.Request;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@Api(tags = "任务查询")
|
@Api(tags = "任务查询")
|
||||||
@RestController
|
@RestController
|
||||||
@@ -34,58 +17,32 @@ import java.util.Objects;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TaskController {
|
public class TaskController {
|
||||||
private final TaskStoreService taskStoreService;
|
|
||||||
private final DiscordLoadBalancer discordLoadBalancer;
|
|
||||||
|
|
||||||
@Value("${chat.apiKey}")
|
private final MjOkHttpUtil mjOkHttpUtil;
|
||||||
private String apiKey;
|
|
||||||
@Value("${chat.apiHost}")
|
|
||||||
private String apiHost;
|
|
||||||
|
|
||||||
@ApiOperation(value = "指定ID获取任务")
|
@ApiOperation(value = "指定ID获取任务")
|
||||||
@GetMapping("/{id}/fetch")
|
@GetMapping("/{id}/fetch")
|
||||||
public String fetch(@ApiParam(value = "任务ID") @PathVariable String id) {
|
public String fetch(@ApiParam(value = "任务ID") @PathVariable String id) {
|
||||||
OkHttpClient client = new OkHttpClient();
|
String url = "mj/task/" + id + "/fetch";
|
||||||
// 创建一个Request对象来配置你的请求
|
Request request = mjOkHttpUtil.createGetRequest(url);
|
||||||
Request request = new Request.Builder()
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
.header("mj-api-secret", apiKey) // 设置Authorization header
|
|
||||||
.url(apiHost+"mj/task/" + id + "/fetch")
|
|
||||||
.build();
|
|
||||||
try (Response response = client.newCall(request).execute()) {
|
|
||||||
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
|
||||||
if (response.body() != null) {
|
|
||||||
return response.body().string();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("任务:{}查询失败:{}",id,e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "查询任务队列")
|
|
||||||
@GetMapping("/queue")
|
|
||||||
public List<Task> queue() {
|
|
||||||
return this.discordLoadBalancer.getQueueTaskIds().stream()
|
|
||||||
.map(this.taskStoreService::get).filter(Objects::nonNull)
|
|
||||||
.sorted(Comparator.comparing(Task::getSubmitTime))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation(value = "查询所有任务")
|
|
||||||
@GetMapping("/list")
|
|
||||||
public List<Task> list() {
|
|
||||||
return this.taskStoreService.list().stream()
|
|
||||||
.sorted((t1, t2) -> CompareUtil.compare(t2.getSubmitTime(), t1.getSubmitTime()))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation(value = "根据ID列表查询任务")
|
@ApiOperation(value = "根据ID列表查询任务")
|
||||||
@PostMapping("/list-by-condition")
|
@PostMapping("/list-by-condition")
|
||||||
public List<Task> listByIds(@RequestBody TaskConditionDTO conditionDTO) {
|
public String listByIds(@RequestBody TaskConditionDTO conditionDTO) {
|
||||||
if (conditionDTO.getIds() == null) {
|
String url = "mj/task/list-by-condition";
|
||||||
return Collections.emptyList();
|
String conditionJson = JSONUtil.toJsonStr(conditionDTO);
|
||||||
}
|
Request request = mjOkHttpUtil.createPostRequest(url,conditionJson);
|
||||||
return conditionDTO.getIds().stream().map(this.taskStoreService::get).filter(Objects::nonNull).toList();
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "获取任务图片的seed")
|
||||||
|
@GetMapping("/{id}/image-seed")
|
||||||
|
public String getSeed(@ApiParam(value = "任务ID") @PathVariable String id) {
|
||||||
|
String url = "mj/task/" + id + "/image-seed";
|
||||||
|
Request request = mjOkHttpUtil.createGetRequest(url);
|
||||||
|
return mjOkHttpUtil.executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.xmzs.midjourney.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘画费用信息
|
||||||
|
*
|
||||||
|
* @author Admin
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "mj")
|
||||||
|
public class MjPriceConfig {
|
||||||
|
/**
|
||||||
|
* 放大图像
|
||||||
|
*/
|
||||||
|
private String upsample;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变化
|
||||||
|
*/
|
||||||
|
private String change;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图生图
|
||||||
|
*/
|
||||||
|
private String blend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图生文
|
||||||
|
*/
|
||||||
|
private String describe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文生图
|
||||||
|
*/
|
||||||
|
private String imagine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 局部重绘
|
||||||
|
*/
|
||||||
|
private String inpaint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提示词分析
|
||||||
|
*/
|
||||||
|
private String shorten;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 换脸
|
||||||
|
*/
|
||||||
|
private String faceSwapping;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.xmzs.midjourney.dto;
|
package com.xmzs.midjourney.dto;
|
||||||
|
|
||||||
import com.xmzs.midjourney.enums.TaskAction;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.xmzs.midjourney.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel("局部重绘提交参数")
|
||||||
|
public class SubmitModalDTO extends BaseSubmitDTO{
|
||||||
|
|
||||||
|
private String maskBase64;
|
||||||
|
|
||||||
|
private String taskId;
|
||||||
|
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.xmzs.midjourney.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel("prompt分析提交参数")
|
||||||
|
public class SubmitShortenDTO extends BaseSubmitDTO{
|
||||||
|
|
||||||
|
private String botType;
|
||||||
|
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.xmzs.midjourney.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author WangLe
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum ActionType {
|
||||||
|
IN_PAINT("Inpaint"), // 局部重绘操作
|
||||||
|
RE_ROLL("reroll"), // 重绘操作
|
||||||
|
UP_SAMPLE("upsample"), // 放大操作
|
||||||
|
ZOOM("zoom"), // 变焦操作
|
||||||
|
UPSCALE("upscale"), // 高清放大操作
|
||||||
|
VARIATION("variation"); // 变化操作
|
||||||
|
|
||||||
|
private final String action;
|
||||||
|
|
||||||
|
ActionType(String action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ActionType fromCustomId(String customId) {
|
||||||
|
for (ActionType type : values()) {
|
||||||
|
if (type.getAction().equals(customId)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.xmzs.midjourney.enums;
|
package com.xmzs.midjourney.enums;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public enum BlendDimensions {
|
public enum BlendDimensions {
|
||||||
|
|
||||||
PORTRAIT("2:3"),
|
PORTRAIT("2:3"),
|
||||||
@@ -15,7 +18,4 @@ public enum BlendDimensions {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.xmzs.midjourney.util;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author WangLe
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class MjOkHttpUtil {
|
||||||
|
|
||||||
|
@Value("${chat.apiKey}")
|
||||||
|
private List<String> apiKey;
|
||||||
|
@Value("${chat.apiHost}")
|
||||||
|
private String apiHost;
|
||||||
|
|
||||||
|
private static final String API_SECRET_HEADER = "mj-api-secret";
|
||||||
|
|
||||||
|
private final OkHttpClient client = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(300, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(300, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(300, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public String executeRequest(Request request) {
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
}
|
||||||
|
return response.body() != null ? response.body().string() : null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// 这里应根据实际情况使用适当的日志记录方式
|
||||||
|
log.error("请求失败: {}",e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request createPostRequest(String url, String json) {
|
||||||
|
MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
RequestBody body = RequestBody.create(json, JSON);
|
||||||
|
return new Request.Builder()
|
||||||
|
.url(apiHost + url)
|
||||||
|
.post(body)
|
||||||
|
.header(API_SECRET_HEADER, apiKey.get(0))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request createGetRequest(String url) {
|
||||||
|
return new Request.Builder()
|
||||||
|
.url(apiHost + url)
|
||||||
|
.header(API_SECRET_HEADER, apiKey.get(0))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.xmzs.system.controller.system;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import com.xmzs.common.core.domain.R;
|
||||||
|
import com.xmzs.common.core.validate.AddGroup;
|
||||||
|
import com.xmzs.common.core.validate.EditGroup;
|
||||||
|
import com.xmzs.common.excel.utils.ExcelUtil;
|
||||||
|
import com.xmzs.common.idempotent.annotation.RepeatSubmit;
|
||||||
|
import com.xmzs.common.log.annotation.Log;
|
||||||
|
import com.xmzs.common.log.enums.BusinessType;
|
||||||
|
import com.xmzs.common.mybatis.core.page.PageQuery;
|
||||||
|
import com.xmzs.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import com.xmzs.common.web.core.BaseController;
|
||||||
|
import com.xmzs.system.domain.bo.SysModelBo;
|
||||||
|
import com.xmzs.system.domain.vo.SysModelVo;
|
||||||
|
import com.xmzs.system.service.ISysModelService;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模型
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2024-04-04
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/model")
|
||||||
|
public class SysModelController extends BaseController {
|
||||||
|
|
||||||
|
private final ISysModelService sysModelService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统模型列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo<SysModelVo> list(SysModelBo bo, PageQuery pageQuery) {
|
||||||
|
return sysModelService.queryPageList(bo, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统模型列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/modelList")
|
||||||
|
public R<List<SysModelVo>> modelList(SysModelBo bo) {
|
||||||
|
bo.setModelShow("0");
|
||||||
|
return R.ok(sysModelService.queryList(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出系统模型列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:model:export")
|
||||||
|
@Log(title = "系统模型", businessType = BusinessType.EXPORT)
|
||||||
|
@PostMapping("/export")
|
||||||
|
public void export(SysModelBo bo, HttpServletResponse response) {
|
||||||
|
List<SysModelVo> list = sysModelService.queryList(bo);
|
||||||
|
ExcelUtil.exportExcel(list, "系统模型", SysModelVo.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统模型详细信息
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:model:query")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public R<SysModelVo> getInfo(@NotNull(message = "主键不能为空")
|
||||||
|
@PathVariable Long id) {
|
||||||
|
return R.ok(sysModelService.queryById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增系统模型
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:model:add")
|
||||||
|
@Log(title = "系统模型", businessType = BusinessType.INSERT)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PostMapping()
|
||||||
|
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysModelBo bo) {
|
||||||
|
return toAjax(sysModelService.insertByBo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改系统模型
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:model:edit")
|
||||||
|
@Log(title = "系统模型", businessType = BusinessType.UPDATE)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PutMapping()
|
||||||
|
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysModelBo bo) {
|
||||||
|
return toAjax(sysModelService.updateByBo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除系统模型
|
||||||
|
*
|
||||||
|
* @param ids 主键串
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:model:remove")
|
||||||
|
@Log(title = "系统模型", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||||
|
@PathVariable Long[] ids) {
|
||||||
|
return toAjax(sysModelService.deleteWithValidByIds(List.of(ids), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import java.math.BigDecimal;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("payment_orders")
|
@TableName("sys_pay_order")
|
||||||
public class PaymentOrders extends BaseEntity {
|
public class PaymentOrders extends BaseEntity {
|
||||||
|
|
||||||
@Serial
|
@Serial
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.xmzs.system.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.xmzs.common.mybatis.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模型对象 sys_model
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2024-04-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_model")
|
||||||
|
public class SysModel extends BaseEntity {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(value = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型描述
|
||||||
|
*/
|
||||||
|
private String modelDescribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型价格
|
||||||
|
*/
|
||||||
|
private double modelPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计费类型
|
||||||
|
*/
|
||||||
|
private String modelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示
|
||||||
|
*/
|
||||||
|
private String modelShow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.xmzs.system.domain.bo;
|
||||||
|
|
||||||
|
import com.xmzs.common.core.validate.AddGroup;
|
||||||
|
import com.xmzs.common.core.validate.EditGroup;
|
||||||
|
import com.xmzs.common.mybatis.core.domain.BaseEntity;
|
||||||
|
import com.xmzs.system.domain.SysModel;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模型业务对象 sys_model
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2024-04-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@AutoMapper(target = SysModel.class, reverseConvertGenerate = false)
|
||||||
|
public class SysModelBo extends BaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型描述
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "模型描述不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String modelDescribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型价格
|
||||||
|
*/
|
||||||
|
@NotNull(message = "模型价格不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private double modelPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计费类型 (1 token扣费; 2 次数扣费 )
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "计费类型不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String modelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型状态 (0 显示; 1 隐藏 )
|
||||||
|
*/
|
||||||
|
private String modelShow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.xmzs.system.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.xmzs.system.domain.SysModel;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模型视图对象 sys_model
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2024-04-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AutoMapper(target = SysModel.class)
|
||||||
|
public class SysModelVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "模型名称")
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型描述
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "模型描述")
|
||||||
|
private String modelDescribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型价格
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "模型价格")
|
||||||
|
private double modelPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计费类型
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "计费类型")
|
||||||
|
private String modelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示
|
||||||
|
*/
|
||||||
|
private String modelShow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,14 +4,16 @@ package com.xmzs.system.listener;
|
|||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.xmzs.common.chat.config.LocalCache;
|
import com.xmzs.common.chat.config.LocalCache;
|
||||||
import com.xmzs.common.chat.entity.chat.ChatCompletion;
|
|
||||||
import com.xmzs.common.chat.entity.chat.ChatCompletionResponse;
|
import com.xmzs.common.chat.entity.chat.ChatCompletionResponse;
|
||||||
import com.xmzs.common.chat.utils.TikTokensUtil;
|
import com.xmzs.common.chat.utils.TikTokensUtil;
|
||||||
import com.xmzs.common.core.utils.SpringUtils;
|
import com.xmzs.common.core.utils.SpringUtils;
|
||||||
import com.xmzs.common.core.utils.StringUtils;
|
import com.xmzs.common.core.utils.StringUtils;
|
||||||
import com.xmzs.system.domain.bo.ChatMessageBo;
|
import com.xmzs.system.domain.bo.ChatMessageBo;
|
||||||
|
import com.xmzs.system.domain.bo.SysModelBo;
|
||||||
|
import com.xmzs.system.domain.vo.SysModelVo;
|
||||||
import com.xmzs.system.service.IChatMessageService;
|
import com.xmzs.system.service.IChatMessageService;
|
||||||
import com.xmzs.system.service.IChatService;
|
import com.xmzs.system.service.IChatCostService;
|
||||||
|
import com.xmzs.system.service.ISysModelService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -24,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +48,7 @@ public class SSEEventSourceListener extends EventSourceListener {
|
|||||||
public SSEEventSourceListener(ResponseBodyEmitter emitter) {
|
public SSEEventSourceListener(ResponseBodyEmitter emitter) {
|
||||||
this.emitter = emitter;
|
this.emitter = emitter;
|
||||||
}
|
}
|
||||||
|
private static final ISysModelService sysModelService = SpringUtils.getBean(ISysModelService.class);
|
||||||
private String modelName;
|
private String modelName;
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@@ -66,34 +69,34 @@ public class SSEEventSourceListener extends EventSourceListener {
|
|||||||
//成功响应
|
//成功响应
|
||||||
emitter.complete();
|
emitter.complete();
|
||||||
if(StringUtils.isNotEmpty(modelName)){
|
if(StringUtils.isNotEmpty(modelName)){
|
||||||
IChatService IChatService = SpringUtils.context().getBean(IChatService.class);
|
IChatCostService IChatCostService = SpringUtils.context().getBean(IChatCostService.class);
|
||||||
IChatMessageService chatMessageService = SpringUtils.context().getBean(IChatMessageService.class);
|
IChatMessageService chatMessageService = SpringUtils.context().getBean(IChatMessageService.class);
|
||||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||||
chatMessageBo.setModelName(modelName);
|
chatMessageBo.setModelName(modelName);
|
||||||
chatMessageBo.setContent(stringBuffer.toString());
|
chatMessageBo.setContent(stringBuffer.toString());
|
||||||
Long userId = (Long)LocalCache.CACHE.get("userId");
|
Long userId = (Long)LocalCache.CACHE.get("userId");
|
||||||
chatMessageBo.setUserId(userId);
|
chatMessageBo.setUserId(userId);
|
||||||
if(ChatCompletion.Model.GPT_4_ALL.getName().equals(modelName)
|
|
||||||
|| modelName.startsWith(ChatCompletion.Model.GPT_4_GIZMO.getName())
|
//查询按次数扣费的模型
|
||||||
|| modelName.startsWith(ChatCompletion.Model.NET.getName())
|
SysModelBo sysModelBo = new SysModelBo();
|
||||||
|| ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName().equals(modelName)
|
sysModelBo.setModelType("2");
|
||||||
|| ChatCompletion.Model.CLAUDE_3_SONNET.getName().equals(modelName)
|
sysModelBo.setModelName(modelName);
|
||||||
|| ChatCompletion.Model.STABLE_DIFFUSION.getName().equals(modelName)
|
List<SysModelVo> sysModelList = sysModelService.queryList(sysModelBo);
|
||||||
|| ChatCompletion.Model.SUNO_V3.getName().equals(modelName)
|
if (CollectionUtil.isNotEmpty(sysModelList)){
|
||||||
){
|
chatMessageBo.setDeductCost(0d);
|
||||||
chatMessageBo.setDeductCost(0.0);
|
chatMessageBo.setRemark("提问时扣费");
|
||||||
chatMessageBo.setTotalTokens(0);
|
|
||||||
// 保存消息记录
|
// 保存消息记录
|
||||||
chatMessageService.insertByBo(chatMessageBo);
|
chatMessageService.insertByBo(chatMessageBo);
|
||||||
}else {
|
}else{
|
||||||
// 扣除余额
|
|
||||||
int tokens = TikTokensUtil.tokens(modelName,stringBuffer.toString());
|
int tokens = TikTokensUtil.tokens(modelName,stringBuffer.toString());
|
||||||
chatMessageBo.setTotalTokens(tokens);
|
chatMessageBo.setTotalTokens(tokens);
|
||||||
IChatService.deductToken(chatMessageBo);
|
// 按token扣费并且保存消息记录
|
||||||
|
IChatCostService.deductToken(chatMessageBo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 解析返回内容
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class);
|
ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class);
|
||||||
if(completionResponse == null || CollectionUtil.isEmpty(completionResponse.getChoices())){
|
if(completionResponse == null || CollectionUtil.isEmpty(completionResponse.getChoices())){
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.xmzs.system.mapper;
|
||||||
|
|
||||||
|
import com.xmzs.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
|
import com.xmzs.system.domain.SysModel;
|
||||||
|
import com.xmzs.system.domain.vo.SysModelVo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模型Mapper接口
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2024-04-04
|
||||||
|
*/
|
||||||
|
public interface SysModelMapper extends BaseMapperPlus<SysModel, SysModelVo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.xmzs.system.service;
|
||||||
|
|
||||||
|
import com.xmzs.system.domain.bo.ChatMessageBo;
|
||||||
|
|
||||||
|
public interface IChatCostService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据消耗的tokens扣除余额
|
||||||
|
*
|
||||||
|
* @param chatMessageBo
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
|
||||||
|
void deductToken(ChatMessageBo chatMessageBo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣除用户的余额
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void deductUserBalance(Long userId, Double numberCost);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣除任务费用并且保存记录
|
||||||
|
*
|
||||||
|
* @param type 任务类型
|
||||||
|
* @param prompt 任务描述
|
||||||
|
* @param cost 扣除费用
|
||||||
|
*/
|
||||||
|
void taskDeduct(String type,String prompt, double cost);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否付费
|
||||||
|
*/
|
||||||
|
void checkUserGrade();
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.xmzs.system.service;
|
||||||
|
|
||||||
|
import com.xmzs.common.mybatis.core.page.PageQuery;
|
||||||
|
import com.xmzs.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import com.xmzs.system.domain.bo.SysModelBo;
|
||||||
|
import com.xmzs.system.domain.vo.SysModelVo;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模型Service接口
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2024-04-04
|
||||||
|
*/
|
||||||
|
public interface ISysModelService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统模型
|
||||||
|
*/
|
||||||
|
SysModelVo queryById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统模型列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<SysModelVo> queryPageList(SysModelBo bo, PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统模型列表
|
||||||
|
*/
|
||||||
|
List<SysModelVo> queryList(SysModelBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增系统模型
|
||||||
|
*/
|
||||||
|
Boolean insertByBo(SysModelBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改系统模型
|
||||||
|
*/
|
||||||
|
Boolean updateByBo(SysModelBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并批量删除系统模型信息
|
||||||
|
*/
|
||||||
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package com.xmzs.system.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.xmzs.common.core.domain.model.LoginUser;
|
||||||
|
import com.xmzs.common.core.exception.ServiceException;
|
||||||
|
import com.xmzs.common.core.exception.base.BaseException;
|
||||||
|
import com.xmzs.common.satoken.utils.LoginHelper;
|
||||||
|
import com.xmzs.system.domain.ChatToken;
|
||||||
|
import com.xmzs.system.domain.SysUser;
|
||||||
|
import com.xmzs.system.domain.bo.ChatMessageBo;
|
||||||
|
import com.xmzs.system.domain.bo.SysModelBo;
|
||||||
|
import com.xmzs.system.domain.vo.SysModelVo;
|
||||||
|
import com.xmzs.system.mapper.SysUserMapper;
|
||||||
|
import com.xmzs.system.service.IChatCostService;
|
||||||
|
import com.xmzs.system.service.IChatMessageService;
|
||||||
|
import com.xmzs.system.service.IChatTokenService;
|
||||||
|
import com.xmzs.system.service.ISysModelService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hncboy
|
||||||
|
* @date 2023/3/22 19:41
|
||||||
|
* 聊天相关业务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChatCostServiceImpl implements IChatCostService {
|
||||||
|
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
|
||||||
|
private final IChatMessageService chatMessageService;
|
||||||
|
|
||||||
|
private final IChatTokenService chatTokenService;
|
||||||
|
|
||||||
|
private final ISysModelService sysModelService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据消耗的tokens扣除余额
|
||||||
|
*
|
||||||
|
* @param chatMessageBo
|
||||||
|
*/
|
||||||
|
public void deductToken(ChatMessageBo chatMessageBo) {
|
||||||
|
// 计算总token数
|
||||||
|
ChatToken chatToken = chatTokenService.queryByUserId(chatMessageBo.getUserId(), chatMessageBo.getModelName());
|
||||||
|
if (chatToken == null) {
|
||||||
|
chatToken = new ChatToken();
|
||||||
|
chatToken.setToken(0);
|
||||||
|
}
|
||||||
|
int totalTokens = chatToken.getToken() + chatMessageBo.getTotalTokens();
|
||||||
|
// 如果总token数大于等于1000,进行费用扣除
|
||||||
|
if (totalTokens >= 1000) {
|
||||||
|
// 计算费用
|
||||||
|
int token1 = totalTokens / 1000;
|
||||||
|
int token2 = totalTokens % 1000;
|
||||||
|
if (token2 > 0) {
|
||||||
|
// 保存剩余tokens
|
||||||
|
chatToken.setToken(token2);
|
||||||
|
chatTokenService.editToken(chatToken);
|
||||||
|
} else {
|
||||||
|
chatTokenService.resetToken(chatMessageBo.getUserId(), chatMessageBo.getModelName());
|
||||||
|
}
|
||||||
|
// 扣除用户余额
|
||||||
|
|
||||||
|
SysModelBo sysModelBo = new SysModelBo();
|
||||||
|
sysModelBo.setModelName(chatMessageBo.getModelName());
|
||||||
|
List<SysModelVo> sysModelList = sysModelService.queryList(sysModelBo);
|
||||||
|
double modelPrice = sysModelList.get(0).getModelPrice();
|
||||||
|
Double numberCost = token1 * modelPrice;
|
||||||
|
deductUserBalance(chatMessageBo.getUserId(), numberCost);
|
||||||
|
chatMessageBo.setDeductCost(numberCost);
|
||||||
|
} else {
|
||||||
|
// 扣除用户余额
|
||||||
|
deductUserBalance(chatMessageBo.getUserId(), 0.0);
|
||||||
|
chatMessageBo.setDeductCost(0d);
|
||||||
|
chatMessageBo.setRemark("不满1kToken,计入下一次!");
|
||||||
|
chatToken.setToken(totalTokens);
|
||||||
|
chatToken.setModelName(chatMessageBo.getModelName());
|
||||||
|
chatToken.setUserId(chatMessageBo.getUserId());
|
||||||
|
chatTokenService.editToken(chatToken);
|
||||||
|
}
|
||||||
|
// 保存消息记录
|
||||||
|
chatMessageService.insertByBo(chatMessageBo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从用户余额中扣除费用
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param numberCost 要扣除的费用
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void deductUserBalance(Long userId, Double numberCost) {
|
||||||
|
SysUser sysUser = sysUserMapper.selectById(userId);
|
||||||
|
if (sysUser == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Double userBalance = sysUser.getUserBalance();
|
||||||
|
if (userBalance < numberCost || userBalance == 0) {
|
||||||
|
throw new ServiceException("余额不足,请联系管理员充值!");
|
||||||
|
}
|
||||||
|
sysUserMapper.update(null,
|
||||||
|
new LambdaUpdateWrapper<SysUser>()
|
||||||
|
.set(SysUser::getUserBalance, Math.max(userBalance - numberCost, 0))
|
||||||
|
.eq(SysUser::getUserId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣除任务费用
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void taskDeduct(String type,String prompt, double cost) {
|
||||||
|
// 判断用户是否付费
|
||||||
|
checkUserGrade();
|
||||||
|
// 扣除费用
|
||||||
|
deductUserBalance(getUserId(), cost);
|
||||||
|
// 保存消息记录
|
||||||
|
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||||
|
chatMessageBo.setUserId(getUserId());
|
||||||
|
chatMessageBo.setModelName(type);
|
||||||
|
chatMessageBo.setContent(prompt);
|
||||||
|
chatMessageBo.setDeductCost(cost);
|
||||||
|
chatMessageBo.setTotalTokens(0);
|
||||||
|
chatMessageService.insertByBo(chatMessageBo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否付费
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void checkUserGrade() {
|
||||||
|
SysUser sysUser = sysUserMapper.selectById(getUserId());
|
||||||
|
if("0".equals(sysUser.getUserGrade())){
|
||||||
|
throw new BaseException("免费用户暂时不支持此模型,请切换gpt-3.5-turbo模型或者点击《进入市场选购您的商品》充值后使用!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户Id
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Long getUserId() {
|
||||||
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
throw new BaseException("用户未登录!");
|
||||||
|
}
|
||||||
|
return loginUser.getUserId();
|
||||||
|
}
|
||||||
|
}
|
||||||