mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-16 21:33:40 +00:00
Compare commits
17 Commits
v2.0.3
...
256f72c487
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
256f72c487 | ||
|
|
009aa5f1a5 | ||
|
|
d22d2cb708 | ||
|
|
59104028b6 | ||
|
|
c2f6a8321a | ||
|
|
5476a4b0b7 | ||
|
|
cd490aa0e5 | ||
|
|
b1ff44df4b | ||
|
|
dc6d00f0fc | ||
|
|
990da8da6f | ||
|
|
8fc7ad0359 | ||
|
|
8a89d9eb9c | ||
|
|
abfa9ae97b | ||
|
|
bde2e8ff8e | ||
|
|
4d8eb1850f | ||
|
|
5aaa3a5662 | ||
|
|
8fec96f5b2 |
33
README.md
33
README.md
@@ -63,18 +63,19 @@
|
||||
用户名: admin 密码:admin123
|
||||
|
||||
### 源码地址
|
||||
[1]github
|
||||
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
|
||||
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
|
||||
- 前端服务-小程序端: https://github.com/ageerle/ruoyi-uniapp
|
||||
- 后端服务:https://github.com/ageerle/ruoyi-ai
|
||||
|
||||
[2]gitee
|
||||
[1]gitee
|
||||
- 前端服务-用户端: https://gitee.com/ageerle/ruoyi-web
|
||||
- 前端服务-管理端: https://gitee.com/ageerle/ruoyi-admin
|
||||
- 前端服务-小程序端: https://gitee.com/ageerle/ruoyi-uniapp
|
||||
- 后端服务:https://gitee.com/ageerle/ruoyi-ai
|
||||
|
||||
[2]github
|
||||
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
|
||||
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
|
||||
- 前端服务-小程序端: https://github.com/ageerle/ruoyi-uniapp
|
||||
- 后端服务:https://github.com/ageerle/ruoyi-ai
|
||||
|
||||
[3]gitcode
|
||||
- 前端服务-用户端:https://gitcode.com/ageerle/ruoyi-web
|
||||
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
|
||||
@@ -83,7 +84,7 @@
|
||||
|
||||
### 配套文档
|
||||
- 配套文档: https://doc.pandarobot.chat
|
||||
- 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
|
||||
- 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
|
||||
|
||||
### 核心功能
|
||||
1. 全套开源系统:提供完整的前端应用、后台管理以及小程序应用,基于MIT协议,开箱即用。
|
||||
@@ -96,7 +97,16 @@
|
||||
|
||||
### 项目演示
|
||||
|
||||
#### mcp支持(需要切换dev分支 下周发布正式版)
|
||||
#### mcp支持
|
||||
|
||||
### 如何使用
|
||||
1. ruoyi-admin\src\main\resources\application.yml中mcp.client.enabled改为true
|
||||
2. application.yml中配置openai api-key(用于推理使用那个工具,并构建工具所需参数)
|
||||
3. 启动[ruoyi-mcp-server]
|
||||
4. [mcp-server.json]中配置fileSystem.command(npx本地安装路径)
|
||||
5. 指定fileSystem操作目录(本地必须存在指定的目录)
|
||||
6. 配置search1api.env.SEARCH1API_KEY 申请地址:https://www.search1api.com/
|
||||
7. 详情教程:https://blog.csdn.net/weixin_42416319/article/details/147385808
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
||||
<img src="image/mcp-01.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||
<img src="image/mcp-02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||
@@ -215,9 +225,12 @@
|
||||
|
||||
### 开发计划
|
||||
|
||||
- 流程编排
|
||||
| 主题 | 方向 | 时间节点 |
|
||||
| --- |-----------------------------------|--------|
|
||||
| 前端简化版 | 与element-plus-x框架合作,推出基于该框架的前端简化版 | 2025.5 |
|
||||
| agent2agent | Agent2Agent协议支持 | 2025.6 |
|
||||
| 流程编排 | 通过可视化界面和灵活的配置方式,快速构建AI应用 | 2025.7 |
|
||||
|
||||
通过流程编排功能,用户可以将不同的模型按照业务逻辑进行有序连接。这将解决单一模型能力不足的问题,充分发挥多个模型的协同作用,从而更好地满足企业的复杂业务需求。
|
||||
|
||||
- 感谢
|
||||
|
||||
|
||||
BIN
image/mcp-01.png
BIN
image/mcp-01.png
Binary file not shown.
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 227 KiB |
12
pom.xml
12
pom.xml
@@ -20,7 +20,7 @@
|
||||
<java.version>17</java.version>
|
||||
<mysql.version>8.0.33</mysql.version>
|
||||
<mybatis.version>3.5.16</mybatis.version>
|
||||
<springdoc.version>2.1.0</springdoc.version>
|
||||
<springdoc.version>2.8.5</springdoc.version>
|
||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||
<poi.version>5.2.3</poi.version>
|
||||
<easyexcel.version>3.2.1</easyexcel.version>
|
||||
@@ -337,11 +337,11 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.ruoyi</groupId>-->
|
||||
<!-- <artifactId>ruoyi-demo</artifactId>-->
|
||||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-wechat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -42,12 +42,6 @@
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- demo模块 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.ruoyi</groupId>-->
|
||||
<!-- <artifactId>ruoyi-demo</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
@@ -58,6 +52,16 @@
|
||||
<artifactId>ruoyi-chat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-wechat</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.ruoyi.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ spring:
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://43.139.70.230:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=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
|
||||
username: ry-vue
|
||||
password: xx
|
||||
|
||||
|
||||
@@ -215,11 +215,16 @@ mybatis-encryptor:
|
||||
publicKey:
|
||||
privateKey:
|
||||
|
||||
# Swagger配置
|
||||
swagger:
|
||||
springdoc:
|
||||
api-docs:
|
||||
# 是否开启接口文档
|
||||
enabled: true
|
||||
# swagger-ui:
|
||||
# # 持久化认证数据
|
||||
# persistAuthorization: true
|
||||
info:
|
||||
# 标题
|
||||
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
|
||||
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
|
||||
# 描述
|
||||
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
|
||||
# 版本
|
||||
@@ -236,24 +241,10 @@ swagger:
|
||||
type: APIKEY
|
||||
in: HEADER
|
||||
name: ${sa-token.token-name}
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
# 是否开启接口文档
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
# 持久化认证数据
|
||||
persistAuthorization: true
|
||||
#这里定义了两个分组,可定义多个,也可以不定义
|
||||
group-configs:
|
||||
- group: 1.演示模块
|
||||
packages-to-scan: org.ruoyi.demo
|
||||
- group: 2.通用模块
|
||||
packages-to-scan: org.ruoyi.web
|
||||
- group: 3.系统模块
|
||||
- group: 1.系统模块
|
||||
packages-to-scan: org.ruoyi.system
|
||||
- group: 4.代码生成模块
|
||||
packages-to-scan: org.ruoyi.generator
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
@@ -323,11 +314,11 @@ wechat:
|
||||
spring:
|
||||
ai:
|
||||
openai:
|
||||
api-key: sk-xxx
|
||||
api-key: sk-xx
|
||||
base-url: https://api.pandarobot.chat/
|
||||
mcp:
|
||||
client:
|
||||
enabled: true
|
||||
enabled: false
|
||||
name: ruoyi-ai-mcp
|
||||
sse:
|
||||
connections:
|
||||
@@ -335,4 +326,5 @@ spring:
|
||||
url: http://127.0.0.1:8081
|
||||
stdio:
|
||||
servers-configuration: classpath:mcp-server.json
|
||||
request-timeout: 300s
|
||||
|
||||
|
||||
BIN
ruoyi-admin/src/main/resources/static/favicon.ico
Normal file
BIN
ruoyi-admin/src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
@@ -159,12 +159,20 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 微信模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-chat</artifactId>
|
||||
<artifactId>ruoyi-common-wechat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 支付模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-pay</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -56,6 +56,12 @@ public class ChatRequest {
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.doc.config.properties.SwaggerProperties;
|
||||
import org.ruoyi.common.doc.config.properties.SpringDocProperties;
|
||||
import org.ruoyi.common.doc.handler.OpenApiHandler;
|
||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
@@ -36,26 +36,26 @@ import java.util.Set;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@AutoConfiguration(before = SpringDocConfiguration.class)
|
||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
||||
@EnableConfigurationProperties(SpringDocProperties.class)
|
||||
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class SwaggerConfig {
|
||||
public class SpringDocConfig {
|
||||
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OpenAPI.class)
|
||||
public OpenAPI openApi(SwaggerProperties swaggerProperties) {
|
||||
public OpenAPI openApi(SpringDocProperties properties) {
|
||||
OpenAPI openApi = new OpenAPI();
|
||||
// 文档基本信息
|
||||
SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo();
|
||||
SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
|
||||
Info info = convertInfo(infoProperties);
|
||||
openApi.info(info);
|
||||
// 扩展文档信息
|
||||
openApi.externalDocs(swaggerProperties.getExternalDocs());
|
||||
openApi.tags(swaggerProperties.getTags());
|
||||
openApi.paths(swaggerProperties.getPaths());
|
||||
openApi.components(swaggerProperties.getComponents());
|
||||
Set<String> keySet = swaggerProperties.getComponents().getSecuritySchemes().keySet();
|
||||
openApi.externalDocs(properties.getExternalDocs());
|
||||
openApi.tags(properties.getTags());
|
||||
openApi.paths(properties.getPaths());
|
||||
openApi.components(properties.getComponents());
|
||||
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||
List<SecurityRequirement> list = new ArrayList<>();
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
keySet.forEach(securityRequirement::addList);
|
||||
@@ -65,7 +65,7 @@ public class SwaggerConfig {
|
||||
return openApi;
|
||||
}
|
||||
|
||||
private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) {
|
||||
private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
|
||||
Info info = new Info();
|
||||
info.setTitle(infoProperties.getTitle());
|
||||
info.setDescription(infoProperties.getDescription());
|
||||
@@ -18,8 +18,8 @@ import java.util.List;
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "swagger")
|
||||
public class SwaggerProperties {
|
||||
@ConfigurationProperties(prefix = "springdoc")
|
||||
public class SpringDocProperties {
|
||||
|
||||
/**
|
||||
* 文档基本信息
|
||||
@@ -11,6 +11,7 @@ import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.common.core.utils.StreamUtils;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||
@@ -154,11 +155,11 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr))
|
||||
tagsStr = tagsStr.stream()
|
||||
.map(str -> propertyResolverUtils.resolve(str, locale))
|
||||
.collect(Collectors.toSet());
|
||||
.map(str -> propertyResolverUtils.resolve(str, locale))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (springdocTags.containsKey(handlerMethod)) {
|
||||
Tag tag = springdocTags.get(handlerMethod);
|
||||
io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
|
||||
tagsStr.add(tag.getName());
|
||||
if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
|
||||
openAPI.addTagsItem(tag);
|
||||
@@ -182,7 +183,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
if (javadocProvider.isPresent()) {
|
||||
String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
|
||||
if (StringUtils.isNotBlank(description)) {
|
||||
Tag tag = new Tag();
|
||||
io.swagger.v3.oas.models.tags.Tag tag = new io.swagger.v3.oas.models.tags.Tag();
|
||||
|
||||
// 自定义部分 修改使用java注释当tag名
|
||||
List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
|
||||
@@ -203,7 +204,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
|
||||
if (!CollectionUtils.isEmpty(tags)) {
|
||||
// Existing tags
|
||||
List<Tag> openApiTags = openAPI.getTags();
|
||||
List<io.swagger.v3.oas.models.tags.Tag> openApiTags = openAPI.getTags();
|
||||
if (!CollectionUtils.isEmpty(openApiTags))
|
||||
tags.addAll(openApiTags);
|
||||
openAPI.setTags(new ArrayList<>(tags));
|
||||
@@ -211,7 +212,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
|
||||
// Handle SecurityRequirement at operation level
|
||||
io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
|
||||
.getSecurityRequirements(handlerMethod);
|
||||
.getSecurityRequirements(handlerMethod);
|
||||
if (securityRequirements != null) {
|
||||
if (securityRequirements.length == 0)
|
||||
operation.setSecurity(Collections.emptyList());
|
||||
@@ -222,23 +223,23 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
return operation;
|
||||
}
|
||||
|
||||
private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
|
||||
private void buildTagsFromMethod(Method method, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr, Locale locale) {
|
||||
// method tags
|
||||
Set<Tags> tagsSet = AnnotatedElementUtils
|
||||
.findAllMergedAnnotations(method, Tags.class);
|
||||
.findAllMergedAnnotations(method, Tags.class);
|
||||
Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
|
||||
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
|
||||
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
|
||||
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
||||
if (!CollectionUtils.isEmpty(methodTags)) {
|
||||
tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
|
||||
tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
|
||||
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
||||
addTags(allTags, tags, locale);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
|
||||
Optional<Set<Tag>> optionalTagSet = AnnotationsUtils
|
||||
.getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
|
||||
private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<io.swagger.v3.oas.models.tags.Tag> tags, Locale locale) {
|
||||
Optional<Set<io.swagger.v3.oas.models.tags.Tag>> optionalTagSet = AnnotationsUtils
|
||||
.getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
|
||||
optionalTagSet.ifPresent(tagsSet -> {
|
||||
tagsSet.forEach(tag -> {
|
||||
tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
|
||||
|
||||
@@ -1 +1 @@
|
||||
org.ruoyi.common.doc.config.SwaggerConfig
|
||||
org.ruoyi.common.doc.config.SpringDocConfig
|
||||
|
||||
@@ -34,6 +34,11 @@ public class ChatMessage extends BaseEntity {
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.ruoyi.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 会话管理对象 chat_session
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("chat_session")
|
||||
public class ChatSession extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 会话标题
|
||||
*/
|
||||
private String sessionTitle;
|
||||
|
||||
/**
|
||||
* 会话内容
|
||||
*/
|
||||
private String sessionContent;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -40,6 +40,11 @@ public class ChatMessageBo extends BaseEntity {
|
||||
@NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 对话角色
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.ruoyi.domain.bo;
|
||||
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.core.domain.BaseEntity;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.ruoyi.domain.ChatSession;
|
||||
|
||||
/**
|
||||
* 会话管理业务对象 chat_session
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = ChatSession.class, reverseConvertGenerate = false)
|
||||
public class ChatSessionBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 会话标题
|
||||
*/
|
||||
@NotBlank(message = "会话标题不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String sessionTitle;
|
||||
|
||||
/**
|
||||
* 会话内容
|
||||
*/
|
||||
@NotBlank(message = "会话内容不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String sessionContent;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public class ChatMessageVo implements Serializable {
|
||||
@ExcelProperty(value = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.ruoyi.domain.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.domain.ChatSession;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 会话管理视图对象 chat_session
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = ChatSession.class)
|
||||
public class ChatSessionVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@ExcelProperty(value = "主键")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@ExcelProperty(value = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 会话标题
|
||||
*/
|
||||
@ExcelProperty(value = "会话标题")
|
||||
private String sessionTitle;
|
||||
|
||||
/**
|
||||
* 会话内容
|
||||
*/
|
||||
@ExcelProperty(value = "会话内容")
|
||||
private String sessionContent;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.ruoyi.mapper;
|
||||
|
||||
import org.ruoyi.core.mapper.BaseMapperPlus;
|
||||
import org.ruoyi.domain.ChatSession;
|
||||
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||
|
||||
/**
|
||||
* 会话管理Mapper接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
public interface ChatSessionMapper extends BaseMapperPlus<ChatSession, ChatSessionVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.ruoyi.service;
|
||||
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会话管理Service接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
public interface IChatSessionService {
|
||||
|
||||
/**
|
||||
* 查询会话管理
|
||||
*/
|
||||
ChatSessionVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 查询会话管理列表
|
||||
*/
|
||||
TableDataInfo<ChatSessionVo> queryPageList(ChatSessionBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询会话管理列表
|
||||
*/
|
||||
List<ChatSessionVo> queryList(ChatSessionBo bo);
|
||||
|
||||
/**
|
||||
* 新增会话管理
|
||||
*/
|
||||
Boolean insertByBo(ChatSessionBo bo);
|
||||
|
||||
/**
|
||||
* 修改会话管理
|
||||
*/
|
||||
Boolean updateByBo(ChatSessionBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除会话管理信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.ruoyi.service;
|
||||
|
||||
/**
|
||||
* 企业微信聊天管理Service接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-04-08
|
||||
*/
|
||||
public interface IChatVxService {
|
||||
|
||||
|
||||
/**
|
||||
* 企业微信应用回复
|
||||
* @param prompt 提示词
|
||||
* @return 回复内容
|
||||
*/
|
||||
String chat(String prompt);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.ruoyi.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.domain.ChatSession;
|
||||
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||
import org.ruoyi.mapper.ChatSessionMapper;
|
||||
import org.ruoyi.service.IChatSessionService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 会话管理Service业务层处理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class ChatSessionServiceImpl implements IChatSessionService {
|
||||
|
||||
private final ChatSessionMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询会话管理
|
||||
*/
|
||||
@Override
|
||||
public ChatSessionVo queryById(Long id){
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询会话管理列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<ChatSessionVo> queryPageList(ChatSessionBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<ChatSession> lqw = buildQueryWrapper(bo);
|
||||
Page<ChatSessionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询会话管理列表
|
||||
*/
|
||||
@Override
|
||||
public List<ChatSessionVo> queryList(ChatSessionBo bo) {
|
||||
LambdaQueryWrapper<ChatSession> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<ChatSession> buildQueryWrapper(ChatSessionBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<ChatSession> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(bo.getUserId() != null, ChatSession::getUserId, bo.getUserId());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getSessionTitle()), ChatSession::getSessionTitle, bo.getSessionTitle());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getSessionContent()), ChatSession::getSessionContent, bo.getSessionContent());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会话管理
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(ChatSessionBo bo) {
|
||||
ChatSession add = MapstructUtils.convert(bo, ChatSession.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会话管理
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(ChatSessionBo bo) {
|
||||
ChatSession update = MapstructUtils.convert(bo, ChatSession.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(ChatSession entity){
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除会话管理
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.ruoyi.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.service.IChatVxService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ChatVxServiceImpl implements IChatVxService {
|
||||
|
||||
private final OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
@Override
|
||||
public String chat(String prompt) {
|
||||
List<Message> messageList = new ArrayList<>();
|
||||
Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
|
||||
messageList.add(message);
|
||||
ChatCompletion chatCompletion = ChatCompletion
|
||||
.builder()
|
||||
.messages(messageList)
|
||||
.model("gpt-4o-mini")
|
||||
.stream(false)
|
||||
.build();
|
||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import jakarta.validation.constraints.*;
|
||||
public class KnowledgeInfoBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
*
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "不能为空", groups = { EditGroup.class })
|
||||
private Long id;
|
||||
@@ -29,13 +29,13 @@ public class KnowledgeInfoBo extends BaseEntity {
|
||||
/**
|
||||
* 知识库ID
|
||||
*/
|
||||
@NotBlank(message = "知识库ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
@NotBlank(message = "知识库ID不能为空", groups = {EditGroup.class })
|
||||
private String kid;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
@NotNull(message = "用户ID不能为空", groups = {EditGroup.class })
|
||||
private Long uid;
|
||||
|
||||
/**
|
||||
@@ -53,25 +53,21 @@ public class KnowledgeInfoBo extends BaseEntity {
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@NotBlank(message = "描述不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 知识分隔符
|
||||
*/
|
||||
@NotBlank(message = "知识分隔符不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String knowledgeSeparator;
|
||||
|
||||
/**
|
||||
* 提问分隔符
|
||||
*/
|
||||
@NotBlank(message = "提问分隔符不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String questionSeparator;
|
||||
|
||||
/**
|
||||
* 重叠字符数
|
||||
*/
|
||||
@NotNull(message = "重叠字符数不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long overlapChar;
|
||||
|
||||
/**
|
||||
@@ -101,8 +97,6 @@ public class KnowledgeInfoBo extends BaseEntity {
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<module>ruoyi-chat</module>
|
||||
<module>ruoyi-system</module>
|
||||
<module>ruoyi-generator</module>
|
||||
<module>ruoyi-wechat</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -75,12 +75,19 @@ public class ChatConfigController extends BaseController {
|
||||
/**
|
||||
* 新增配置信息
|
||||
*/
|
||||
@SaCheckPermission("system:config:add")
|
||||
@Log(title = "配置信息", businessType = BusinessType.INSERT)
|
||||
@SaCheckPermission("system:config:edit")
|
||||
@Log(title = "新增或者修改配置信息", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatConfigBo bo) {
|
||||
return toAjax(chatConfigService.insertByBo(bo));
|
||||
@PostMapping("/saveOrUpdate")
|
||||
public R<Void> saveOrUpdate(@RequestBody List<ChatConfigBo> boList) {
|
||||
for (ChatConfigBo chatConfigBo : boList) {
|
||||
if(chatConfigBo.getId() == null){
|
||||
chatConfigService.insertByBo(chatConfigBo);
|
||||
}else {
|
||||
chatConfigService.updateByBo(chatConfigBo);
|
||||
}
|
||||
}
|
||||
return toAjax(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,6 @@ public class ChatGptsController extends BaseController {
|
||||
/**
|
||||
* 查询应用管理列表
|
||||
*/
|
||||
@SaCheckPermission("system:gpts:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<ChatGptsVo> list(ChatGptsBo bo, PageQuery pageQuery) {
|
||||
return chatGptsService.queryPageList(bo, pageQuery);
|
||||
|
||||
@@ -45,6 +45,14 @@ public class ChatPackagePlanController extends BaseController {
|
||||
return chatPackagePlanService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询套餐列表-不分页
|
||||
*/
|
||||
@GetMapping("/listPlan")
|
||||
public R<List<ChatPackagePlanVo>> listPlan() {
|
||||
return R.ok(chatPackagePlanService.queryList(new ChatPackagePlanBo()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出套餐管理列表
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.ruoyi.chat.controller.chat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||
import org.ruoyi.service.IChatSessionService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.ruoyi.common.log.annotation.Log;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
|
||||
/**
|
||||
* 会话管理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/system/session")
|
||||
public class ChatSessionController extends BaseController {
|
||||
|
||||
private final IChatSessionService chatSessionService;
|
||||
|
||||
/**
|
||||
* 查询会话管理列表
|
||||
*/
|
||||
@SaCheckPermission("system:session:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<ChatSessionVo> list(ChatSessionBo bo, PageQuery pageQuery) {
|
||||
return chatSessionService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出会话管理列表
|
||||
*/
|
||||
@SaCheckPermission("system:session:export")
|
||||
@Log(title = "会话管理", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(ChatSessionBo bo, HttpServletResponse response) {
|
||||
List<ChatSessionVo> list = chatSessionService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "会话管理", ChatSessionVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话管理详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@SaCheckPermission("system:session:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<ChatSessionVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(chatSessionService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会话管理
|
||||
*/
|
||||
@SaCheckPermission("system:session:add")
|
||||
@Log(title = "会话管理", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatSessionBo bo) {
|
||||
return toAjax(chatSessionService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会话管理
|
||||
*/
|
||||
@SaCheckPermission("system:session:edit")
|
||||
@Log(title = "会话管理", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ChatSessionBo bo) {
|
||||
return toAjax(chatSessionService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话管理
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("system:session:remove")
|
||||
@Log(title = "会话管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(chatSessionService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,10 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
* 知识库管理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -16,6 +16,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* 绘画(换脸)任务查询
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Api(tags = "任务查询")
|
||||
@RestController
|
||||
@RequestMapping("/mj")
|
||||
|
||||
@@ -14,10 +14,10 @@ import org.ruoyi.common.core.utils.OkHttpUtil;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 描述:文生视频
|
||||
* 文生视频
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2024/6/27
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/luma")
|
||||
|
||||
@@ -15,9 +15,14 @@ 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.util.Optional;
|
||||
|
||||
/**
|
||||
* 绘画任务提交
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Api(tags = "任务提交")
|
||||
@RestController
|
||||
@RequestMapping("/mj/submit")
|
||||
|
||||
@@ -14,6 +14,13 @@ import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.common.core.utils.OkHttpUtil;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* 文生歌曲任务提交
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sunoapi")
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -11,6 +11,13 @@ import org.ruoyi.chat.domain.dto.TaskConditionDTO;
|
||||
import org.ruoyi.chat.util.MjOkHttpUtil;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* 绘画任务查询
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-05-03
|
||||
*/
|
||||
@Api(tags = "任务查询")
|
||||
@RestController
|
||||
@RequestMapping("/mj/task")
|
||||
|
||||
@@ -4,8 +4,8 @@ import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum BillingType {
|
||||
TOKEN("1", "token扣费"), // token扣费
|
||||
TIMES("2", "次数扣费"); // 次数扣费
|
||||
TOKEN("1", "token扣费"),
|
||||
TIMES("2", "次数扣费");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
@@ -4,9 +4,9 @@ import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum ChatModeType {
|
||||
OLLAMA("ollama", "本地部署模型"), // token扣费
|
||||
CHAT("chat", "中转模型"), // 次数扣费
|
||||
VECTOR("vector", "知识库向量模型"); // 次数扣费
|
||||
OLLAMA("ollama", "本地部署模型"),
|
||||
CHAT("chat", "中转模型"),
|
||||
VECTOR("vector", "知识库向量模型");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
@@ -16,11 +16,4 @@ public enum ChatModeType {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.ruoyi.chat.enums;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 描述:
|
||||
* 描述:是否显示
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2025/4/10
|
||||
|
||||
@@ -12,6 +12,7 @@ import okhttp3.sse.EventSource;
|
||||
import okhttp3.sse.EventSourceListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.chat.util.SSEUtil;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
@@ -84,10 +85,10 @@ public class SSEEventSourceListener extends EventSourceListener {
|
||||
modelName = completionResponse.getModel();
|
||||
}
|
||||
stringBuffer.append(content);
|
||||
emitter.send(content);
|
||||
emitter.send(data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,11 +48,4 @@ public interface ISseService {
|
||||
UploadFileResponse upload(MultipartFile file);
|
||||
|
||||
|
||||
/**
|
||||
* 企业应用回复
|
||||
* @param prompt 提示词
|
||||
* @return 回复内容
|
||||
*/
|
||||
String wxCpChat(String prompt);
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
/**
|
||||
* 扣除用户余额
|
||||
*/
|
||||
@Override
|
||||
public void deductToken(ChatRequest chatRequest) {
|
||||
|
||||
int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
|
||||
@@ -53,6 +54,7 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
String modelName = chatRequest.getModel();
|
||||
|
||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
chatMessageBo.setSessionId(chatRequest.getSessionId());
|
||||
|
||||
Object userId = LocalCache.CACHE.get("userId");
|
||||
if(userId!=null){
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import java.util.List;
|
||||
@@ -29,6 +30,10 @@ public class OpenAIServiceImpl implements IChatService {
|
||||
|
||||
private OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
|
||||
@Value("${spring.ai.mcp.client.enabled}")
|
||||
private Boolean enabled;
|
||||
|
||||
private final ChatClient chatClient;
|
||||
|
||||
public OpenAIServiceImpl(ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpSyncClients) {
|
||||
@@ -43,10 +48,12 @@ public class OpenAIServiceImpl implements IChatService {
|
||||
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey());
|
||||
String toolString = mcpChat(chatRequest.getPrompt());
|
||||
Message userMessage = Message.builder().content("工具返回信息:"+toolString).role(Message.Role.USER).build();
|
||||
List<Message> messages = chatRequest.getMessages();
|
||||
messages.add(userMessage);
|
||||
if (enabled) {
|
||||
String toolString = mcpChat(chatRequest.getPrompt());
|
||||
Message userMessage = Message.builder().content("工具返回信息:"+toolString).role(Message.Role.USER).build();
|
||||
messages.add(userMessage);
|
||||
}
|
||||
SSEEventSourceListener listener = new SSEEventSourceListener(emitter);
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
|
||||
@@ -11,10 +11,9 @@ import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.chat.service.chat.ISseService;
|
||||
import org.ruoyi.chat.util.IpUtil;
|
||||
import org.ruoyi.chat.util.SSEUtil;
|
||||
import org.ruoyi.common.chat.config.LocalCache;
|
||||
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.entity.files.UploadFileResponse;
|
||||
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
||||
@@ -25,9 +24,12 @@ import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.core.utils.file.FileUtils;
|
||||
import org.ruoyi.common.core.utils.file.MimeTypeUtils;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
import org.ruoyi.domain.ChatSession;
|
||||
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.EmbeddingService;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.ruoyi.service.IChatSessionService;
|
||||
import org.ruoyi.service.VectorStoreService;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -66,6 +68,8 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
private final OllamaServiceImpl ollamaService;
|
||||
|
||||
private final IChatSessionService chatSessionService;
|
||||
|
||||
private ChatModelVo chatModelVo;
|
||||
|
||||
|
||||
@@ -80,20 +84,45 @@ public class SseServiceImpl implements ISseService {
|
||||
checkUnauthenticatedUserChatLimit(request);
|
||||
}else {
|
||||
LocalCache.CACHE.put("userId", chatCostService.getUserId());
|
||||
|
||||
chatRequest.setUserId(chatCostService.getUserId());
|
||||
// 保存会话信息
|
||||
if(chatRequest.getSessionId()==null){
|
||||
ChatSessionBo chatSessionBo = new ChatSessionBo();
|
||||
chatSessionBo.setUserId(chatCostService.getUserId());
|
||||
chatSessionBo.setSessionTitle(getFirst10Characters(chatRequest.getPrompt()));
|
||||
chatSessionBo.setSessionContent(chatRequest.getPrompt());
|
||||
chatSessionService.insertByBo(chatSessionBo);
|
||||
chatRequest.setSessionId(chatSessionBo.getId());
|
||||
}
|
||||
// 保存消息记录 并扣除费用
|
||||
chatCostService.deductToken(chatRequest);
|
||||
}
|
||||
// 根据模型名称前缀调用不同的处理逻辑
|
||||
// 根据模型分类调用不同的处理逻辑
|
||||
switchModelAndHandle(chatRequest,sseEmitter);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
sseEmitter.completeWithError(e);
|
||||
SSEUtil.sendErrorEvent(sseEmitter,e.getMessage());
|
||||
}
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话标题
|
||||
*
|
||||
* @param str 原字符
|
||||
* @return 截取后的字符
|
||||
*/
|
||||
public static String getFirst10Characters(String str) {
|
||||
// 判断字符串长度
|
||||
if (str.length() > 10) {
|
||||
// 如果长度大于10,截取前10个字符
|
||||
return str.substring(0, 10);
|
||||
} else {
|
||||
// 如果长度不足10,返回整个字符串
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查未登录用户是否超过当日对话次数限制
|
||||
*
|
||||
@@ -119,7 +148,6 @@ public class SseServiceImpl implements ISseService {
|
||||
count++;
|
||||
RedisUtils.setCacheObject(redisKey, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,8 +173,7 @@ public class SseServiceImpl implements ISseService {
|
||||
if(StringUtils.isEmpty(sysPrompt)){
|
||||
sysPrompt ="你是一个由RuoYI-AI开发的人工智能助手,名字叫熊猫助手。你擅长中英文对话,能够理解并处理各种问题,提供安全、有帮助、准确的回答。" +
|
||||
"当前时间:"+ DateUtils.getDate()+
|
||||
"#注意:回复之前注意结合上下文和工具返回内容。";
|
||||
|
||||
"#注意:回复之前注意结合上下文和工具返回内容进行回复。";
|
||||
}
|
||||
// 设置系统默认提示词
|
||||
Message sysMessage = Message.builder().content(sysPrompt).role(Message.Role.SYSTEM).build();
|
||||
@@ -273,20 +300,4 @@ public class SseServiceImpl implements ISseService {
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String wxCpChat(String prompt) {
|
||||
List<Message> messageList = new ArrayList<>();
|
||||
Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
|
||||
messageList.add(message);
|
||||
ChatCompletion chatCompletion = ChatCompletion
|
||||
.builder()
|
||||
.messages(messageList)
|
||||
.model("gpt-4o-mini")
|
||||
.stream(false)
|
||||
.build();
|
||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class OpenAiVectorizationImpl implements VectorizationService {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(knowledgeInfoVo.getVectorModel());
|
||||
String apiHost= chatModelVo.getApiHost();
|
||||
String apiKey= chatModelVo.getApiKey();
|
||||
openAiStreamClient = chatConfig.createOpenAiStreamClient(apiHost,apiKey);
|
||||
openAiStreamClient = ChatConfig.createOpenAiStreamClient(apiHost,apiKey);
|
||||
Embedding embedding = buildEmbedding(chunkList, knowledgeInfoVo);
|
||||
EmbeddingResponse embeddings = openAiStreamClient.embeddings(embedding);
|
||||
// 处理 OpenAI 返回的嵌入数据
|
||||
|
||||
@@ -21,11 +21,8 @@ public class SSEUtil {
|
||||
* @param errorMessage 错误信息
|
||||
*/
|
||||
public static void sendErrorEvent(ResponseBodyEmitter sseEmitter, String errorMessage) {
|
||||
SseEmitter.SseEventBuilder event = SseEmitter.event()
|
||||
.name("error")
|
||||
.data(errorMessage);
|
||||
try {
|
||||
sseEmitter.send(event);
|
||||
sseEmitter.send(errorMessage);
|
||||
} catch (IOException e) {
|
||||
log.error("SSE发送失败: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ import org.ruoyi.common.core.constant.TenantConstants;
|
||||
import org.ruoyi.common.core.domain.dto.RoleDTO;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
|
||||
import org.ruoyi.common.core.domain.model.VisitorLoginUser;
|
||||
import org.ruoyi.common.core.enums.*;
|
||||
import org.ruoyi.common.core.exception.user.CaptchaException;
|
||||
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
|
||||
import org.ruoyi.common.core.exception.user.UserException;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.core.utils.*;
|
||||
import org.ruoyi.common.log.event.LogininforEvent;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
@@ -32,8 +30,7 @@ import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.common.tenant.exception.TenantException;
|
||||
import org.ruoyi.common.tenant.helper.TenantHelper;
|
||||
import org.ruoyi.system.domain.SysUser;
|
||||
import org.ruoyi.system.domain.bo.SysUserBo;
|
||||
import org.ruoyi.system.domain.vo.LoginVo;
|
||||
|
||||
import org.ruoyi.system.domain.vo.SysTenantVo;
|
||||
import org.ruoyi.system.domain.vo.SysUserVo;
|
||||
import org.ruoyi.system.mapper.SysUserMapper;
|
||||
@@ -43,7 +40,6 @@ import org.springframework.stereotype.Service;
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -60,8 +56,6 @@ public class SysLoginService {
|
||||
private final WxMaService wxMaService;
|
||||
private final ISysPermissionService permissionService;
|
||||
private final ISysTenantService tenantService;
|
||||
private final ISysUserService userService;
|
||||
private final ConfigService configService;
|
||||
|
||||
@Value("${user.password.maxRetryCount}")
|
||||
private Integer maxRetryCount;
|
||||
@@ -69,7 +63,7 @@ public class SysLoginService {
|
||||
private Integer lockTime;
|
||||
|
||||
/**
|
||||
* 获取微信
|
||||
* 获取微信code
|
||||
* @param xcxCode 获取xcxCode
|
||||
*/
|
||||
public String getOpenidFromCode(String xcxCode) {
|
||||
@@ -137,9 +131,8 @@ public class SysLoginService {
|
||||
return StpUtil.getTokenValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 游客登录
|
||||
* 微信小程序登录
|
||||
*
|
||||
* @param loginBody
|
||||
* @return String
|
||||
@@ -164,57 +157,6 @@ public class SysLoginService {
|
||||
}
|
||||
}
|
||||
|
||||
public LoginVo mpLogin(String openid) {
|
||||
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
|
||||
SysUserVo user = userService.selectUserByOpenId(openid);
|
||||
VisitorLoginUser loginUser = new VisitorLoginUser();
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
// 改为自增
|
||||
String name = "用户" + UUID.randomUUID().toString().replace("-", "");
|
||||
// 设置默认用户名
|
||||
sysUser.setUserName(name);
|
||||
// 设置默认昵称
|
||||
sysUser.setNickName(name);
|
||||
// 设置默认密码
|
||||
sysUser.setPassword(BCrypt.hashpw("123456"));
|
||||
// 设置微信openId
|
||||
sysUser.setOpenId(openid);
|
||||
String configValue = configService.getConfigValue("mail", "amount");
|
||||
// 设置默认余额
|
||||
sysUser.setUserBalance(NumberUtils.toDouble(configValue, 1));
|
||||
// 注册用户,设置默认租户为0
|
||||
SysUser registerUser = userService.registerUser(sysUser, "0");
|
||||
|
||||
// 构建登录用户信息
|
||||
loginUser.setTenantId("0");
|
||||
loginUser.setUserId(registerUser.getUserId());
|
||||
loginUser.setUsername(registerUser.getUserName());
|
||||
loginUser.setUserType(UserType.APP_USER.getUserType());
|
||||
loginUser.setOpenid(openid);
|
||||
loginUser.setNickName(registerUser.getNickName());
|
||||
|
||||
} else {
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(user.getUserId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setNickName(user.getNickName());
|
||||
loginUser.setAvatar(user.getWxAvatar());
|
||||
loginUser.setOpenid(openid);
|
||||
}
|
||||
// 生成token
|
||||
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
|
||||
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
||||
LoginVo loginVo = new LoginVo();
|
||||
// 生成令牌
|
||||
loginVo.setToken(StpUtil.getTokenValue());
|
||||
loginVo.setUserInfo(loginUser);
|
||||
return loginVo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
|
||||
32
ruoyi-modules/ruoyi-wechat/pom.xml
Normal file
32
ruoyi-modules/ruoyi-wechat/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ruoyi-wechat</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-chat-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.ruoyi.builder;
|
||||
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public abstract class AbstractBuilder {
|
||||
public abstract WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, WxCpService service);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user