From 68ac99b9f0b9e13b24708cdb9d8aafe42c397150 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 20 Oct 2025 18:17:25 +0800 Subject: [PATCH 01/50] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=A4=9A=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...241\271\347\233\256\350\257\264\346\230\216.md" | 4 ++++ .../system/service/auth/AdminAuthServiceImpl.java | 4 ++++ .../system/service/user/AdminUserServiceImpl.java | 7 +++++-- .../src/main/resources/application-local.yaml | 14 +++++++------- yudao-server/src/main/resources/application.yaml | 3 +++ 5 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 "docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" diff --git "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" new file mode 100644 index 0000000000..7b82daef1b --- /dev/null +++ "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" @@ -0,0 +1,4 @@ +## 新增模块 +1、在全局pom.xml中放开模块注释 +2、在server/pom.xml中引入依赖 +3、导入对于木块的sql \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 12047f1095..4ec0e30f41 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; @@ -89,6 +90,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS); } + if (user.getTenantId() != null) { + TenantContextHolder.setTenantId(user.getTenantId()); + } // 校验是否禁用 if (CommonStatusEnum.isDisable(user.getStatus())) { createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index f75733505c..d94523862a 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; @@ -263,12 +264,14 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public AdminUserDO getUserByUsername(String username) { - return userMapper.selectByUsername(username); + // 登录场景下需要忽略租户限制,因为此时租户上下文还未设置 + return TenantUtils.executeIgnore(() -> userMapper.selectByUsername(username)); } @Override public AdminUserDO getUserByMobile(String mobile) { - return userMapper.selectByMobile(mobile); + // 登录场景下需要忽略租户限制,因为此时租户上下文还未设置 + return TenantUtils.executeIgnore(() -> userMapper.selectByMobile(mobile)); } @Override diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 4297a17855..48519c35d8 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -48,7 +48,7 @@ spring: primary: master datasource: master: - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://10.200.11.151:53306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 @@ -56,8 +56,8 @@ spring: # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 - username: root - password: 123456 + username: dev + password: qlkj@123 # username: sa # SQL Server 连接的示例 # password: Yudao@2024 # SQL Server 连接的示例 # username: SYSDBA # DM 连接的示例 @@ -66,9 +66,9 @@ spring: # password: Yudao@2024 # OpenGauss 连接的示例 slave: # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true - username: root - password: 123456 + url: jdbc:mysql://10.200.11.151:53306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true + username: dev + password: qlkj@123 # tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) # url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro # driver-class-name: com.taosdata.jdbc.rs.RestfulDriver @@ -82,7 +82,7 @@ spring: host: 127.0.0.1 # 地址 port: 6379 # 端口 database: 0 # 数据库索引 -# password: dev # 密码,建议生产环境开启 + password: qlkj@123 # 密码,建议生产环境开启 --- #################### 定时任务相关配置 #################### diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 5ad93a1d83..2f2951f73b 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -308,6 +308,9 @@ yudao: tenant: # 多租户相关配置项 enable: true ignore-urls: + - /admin-api/system/auth/** # 登录、登出、刷新令牌等认证相关接口 + - /admin-api/system/captcha/** # 获取验证码接口 + - /admin-api/system/sms/code/** # 获取短信验证码接口 - /jmreport/* # 积木报表,无法携带租户编号 ignore-visit-urls: - /admin-api/system/user/profile/** -- Gitee From 2a12985452d2ceaf0d483d3922b86856db75b9a2 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 22 Oct 2025 17:29:15 +0800 Subject: [PATCH 02/50] =?UTF-8?q?=E6=96=B0=E5=A2=9Ecustom=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=89=A9=E5=B1=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 27 ++++ .../security/TenantSecurityWebFilter.java | 6 +- yudao-module-custom/pom.xml | 86 ++++++++++ .../controller/admin/ent/EntController.java | 115 ++++++++++++++ .../controller/admin/ent/vo/EntPageReqVO.java | 28 ++++ .../controller/admin/ent/vo/EntRespVO.java | 29 ++++ .../controller/admin/ent/vo/EntSaveReqVO.java | 29 ++++ .../admin/entinfo/EntInfoController.java | 104 ++++++++++++ .../admin/entinfo/vo/EntInfoPageReqVO.java | 35 +++++ .../admin/entinfo/vo/EntInfoRespVO.java | 35 +++++ .../admin/entinfo/vo/EntInfoSaveReqVO.java | 18 +++ .../custom/dal/dataobject/ent/EntDO.java | 56 +++++++ .../custom/dal/dataobject/ent/EntInfoDO.java | 55 +++++++ .../dal/dataobject/entinfo/EntInfoDO.java | 56 +++++++ .../custom/dal/mysql/ent/EntInfoMapper.java | 32 ++++ .../custom/dal/mysql/ent/EntMapper.java | 29 ++++ .../dal/mysql/entinfo/EntInfoMapper.java | 31 ++++ .../custom/enums/ErrorCodeConstants.java | 9 ++ .../module/custom/service/ent/EntService.java | 73 +++++++++ .../custom/service/ent/EntServiceImpl.java | 148 ++++++++++++++++++ .../service/entinfo/EntInfoService.java | 62 ++++++++ .../service/entinfo/EntInfoServiceImpl.java | 85 ++++++++++ .../main/resources/mapper/ent/EntMapper.xml | 12 ++ .../mapper/entinfo/EntInfoMapper.xml | 12 ++ yudao-server/pom.xml | 5 + 25 files changed, 1174 insertions(+), 3 deletions(-) create mode 100644 yudao-module-custom/pom.xml create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java create mode 100644 yudao-module-custom/src/main/resources/mapper/ent/EntMapper.xml create mode 100644 yudao-module-custom/src/main/resources/mapper/entinfo/EntInfoMapper.xml diff --git a/pom.xml b/pom.xml index 3d64ab53a9..1581eeb7d6 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,8 @@ yudao-module-system yudao-module-infra + + yudao-module-custom @@ -57,6 +59,31 @@ pom import + + + + + ch.qos.logback + logback-classic + 1.4.11 + + + ch.qos.logback + logback-core + 1.4.11 + + + + + org.apache.tomcat.embed + tomcat-embed-core + 9.0.98 + + + org.apache.tomcat.embed + tomcat-embed-websocket + 9.0.98 + diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java index b68aea77ca..f38e5dd98a 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java @@ -68,10 +68,10 @@ public class TenantSecurityWebFilter extends ApiRequestFilter { // 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。 LoginUser user = SecurityFrameworkUtils.getLoginUser(); if (user != null) { + log.info("访问租户({}) ", user.getTenantId()); // 如果获取不到租户编号,则尝试使用登陆用户的租户编号 - if (tenantId == null) { - tenantId = user.getTenantId(); - TenantContextHolder.setTenantId(tenantId); + if (tenantId == null && user.getTenantId() != null) { + TenantContextHolder.setTenantId(user.getTenantId()); // 如果传递了租户编号,则进行比对租户编号,避免越权问题 } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) { log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]", diff --git a/yudao-module-custom/pom.xml b/yudao-module-custom/pom.xml new file mode 100644 index 0000000000..4759dec555 --- /dev/null +++ b/yudao-module-custom/pom.xml @@ -0,0 +1,86 @@ + + + + cn.iocoder.boot + yudao + ${revision} + + 4.0.0 + yudao-module-custom + jar + + ${project.artifactId} + + 自定义扩展模块,比如企业管理、fastgpt对接等 + + + + + + cn.iocoder.boot + yudao-module-system + ${revision} + + + cn.iocoder.boot + yudao-module-infra + ${revision} + + + + cn.iocoder.boot + yudao-common + + + + + cn.iocoder.boot + yudao-spring-boot-starter-biz-data-permission + + + cn.iocoder.boot + yudao-spring-boot-starter-biz-tenant + + + cn.iocoder.boot + yudao-spring-boot-starter-biz-ip + + + + + cn.iocoder.boot + yudao-spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-validation + + + + + cn.iocoder.boot + yudao-spring-boot-starter-mybatis + + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + + cn.iocoder.boot + yudao-spring-boot-starter-excel + + + + org.springframework.boot + spring-boot-starter-mail + + + + + \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java new file mode 100644 index 0000000000..f7d8e29238 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent; + +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import javax.validation.constraints.*; +import javax.validation.*; +import javax.servlet.http.*; +import java.util.*; +import java.io.IOException; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; + +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; + +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.service.ent.EntService; + +@Tag(name = "管理后台 - 企业") +@RestController +@RequestMapping("/custom/ent") +@Validated +public class EntController { + + @Resource + private EntService entService; + + @PostMapping("/create") + @Operation(summary = "创建企业") + @PreAuthorize("@ss.hasPermission('custom:ent:create')") + public CommonResult createEnt(@Valid @RequestBody EntSaveReqVO createReqVO) { + return success(entService.createEnt(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新企业") + @PreAuthorize("@ss.hasPermission('custom:ent:update')") + public CommonResult updateEnt(@Valid @RequestBody EntSaveReqVO updateReqVO) { + entService.updateEnt(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除企业") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('custom:ent:delete')") + public CommonResult deleteEnt(@RequestParam("id") Long id) { + entService.deleteEnt(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Parameter(name = "ids", description = "编号", required = true) + @Operation(summary = "批量删除企业") + @PreAuthorize("@ss.hasPermission('custom:ent:delete')") + public CommonResult deleteEntList(@RequestParam("ids") List ids) { + entService.deleteEntListByIds(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得企业") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('custom:ent:query')") + public CommonResult getEnt(@RequestParam("id") Long id) { + EntDO ent = entService.getEnt(id); + return success(BeanUtils.toBean(ent, EntRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得企业分页") + @PreAuthorize("@ss.hasPermission('custom:ent:query')") + public CommonResult> getEntPage(@Valid EntPageReqVO pageReqVO) { + PageResult pageResult = entService.getEntPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, EntRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出企业 Excel") + @PreAuthorize("@ss.hasPermission('custom:ent:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportEntExcel(@Valid EntPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = entService.getEntPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "企业.xls", "数据", EntRespVO.class, + BeanUtils.toBean(list, EntRespVO.class)); + } + + // ==================== 子表(企业信息) ==================== + + @GetMapping("/ent-info/list-by-ent-id") + @Operation(summary = "获得企业信息列表") + @Parameter(name = "entId", description = "所属企业Id") + @PreAuthorize("@ss.hasPermission('custom:ent:query')") + public CommonResult> getEntInfoListByEntId(@RequestParam("entId") Long entId) { + return success(entService.getEntInfoListByEntId(entId)); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java new file mode 100644 index 0000000000..e977ad1093 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 企业分页 Request VO") +@Data +public class EntPageReqVO extends PageParam { + + @Schema(description = "企业名称", example = "张三") + private String name; + + @Schema(description = "所属行业") + private String industry; + + @Schema(description = "说明", example = "你猜") + private String remark; + + @Schema(description = "状态(0正常 1停用)", example = "2") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java new file mode 100644 index 0000000000..7086f2e51f --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import cn.idev.excel.annotation.*; + +@Schema(description = "管理后台 - 企业 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EntRespVO { + + @Schema(description = "企业名称", example = "张三") + @ExcelProperty("企业名称") + private String name; + + @Schema(description = "所属行业") + @ExcelProperty("所属行业") + private String industry; + + @Schema(description = "说明", example = "你猜") + @ExcelProperty("说明") + private String remark; + + @Schema(description = "状态(0正常 1停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("状态(0正常 1停用)") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java new file mode 100644 index 0000000000..09b9e74ce1 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import javax.validation.constraints.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; + +@Schema(description = "管理后台 - 企业新增/修改 Request VO") +@Data +public class EntSaveReqVO { + + @Schema(description = "企业名称", example = "张三") + private String name; + + @Schema(description = "所属行业") + private String industry; + + @Schema(description = "说明", example = "你猜") + private String remark; + + @Schema(description = "状态(0正常 1停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "状态(0正常 1停用)不能为空") + private Integer status; + + @Schema(description = "企业信息列表") + private List entInfos; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java new file mode 100644 index 0000000000..4898716c7d --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.custom.controller.admin.entinfo; + +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import javax.validation.constraints.*; +import javax.validation.*; +import javax.servlet.http.*; +import java.util.*; +import java.io.IOException; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; + +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; + +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.service.entinfo.EntInfoService; + +@Tag(name = "管理后台 - 企业信息") +@RestController +@RequestMapping("/custom/ent-info") +@Validated +public class EntInfoController { + + @Resource + private EntInfoService entInfoService; + + @PostMapping("/create") + @Operation(summary = "创建企业信息") + @PreAuthorize("@ss.hasPermission('custom:ent-info:create')") + public CommonResult createEntInfo(@Valid @RequestBody EntInfoSaveReqVO createReqVO) { + return success(entInfoService.createEntInfo(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新企业信息") + @PreAuthorize("@ss.hasPermission('custom:ent-info:update')") + public CommonResult updateEntInfo(@Valid @RequestBody EntInfoSaveReqVO updateReqVO) { + entInfoService.updateEntInfo(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除企业信息") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('custom:ent-info:delete')") + public CommonResult deleteEntInfo(@RequestParam("id") Long id) { + entInfoService.deleteEntInfo(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Parameter(name = "ids", description = "编号", required = true) + @Operation(summary = "批量删除企业信息") + @PreAuthorize("@ss.hasPermission('custom:ent-info:delete')") + public CommonResult deleteEntInfoList(@RequestParam("ids") List ids) { + entInfoService.deleteEntInfoListByIds(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得企业信息") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('custom:ent-info:query')") + public CommonResult getEntInfo(@RequestParam("id") Long id) { + EntInfoDO entInfo = entInfoService.getEntInfo(id); + return success(BeanUtils.toBean(entInfo, EntInfoRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得企业信息分页") + @PreAuthorize("@ss.hasPermission('custom:ent-info:query')") + public CommonResult> getEntInfoPage(@Valid EntInfoPageReqVO pageReqVO) { + PageResult pageResult = entInfoService.getEntInfoPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, EntInfoRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出企业信息 Excel") + @PreAuthorize("@ss.hasPermission('custom:ent-info:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportEntInfoExcel(@Valid EntInfoPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = entInfoService.getEntInfoPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "企业信息.xls", "数据", EntInfoRespVO.class, + BeanUtils.toBean(list, EntInfoRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java new file mode 100644 index 0000000000..b5d025e7e2 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 企业信息分页 Request VO") +@Data +public class EntInfoPageReqVO extends PageParam { + + @Schema(description = "资料名称", example = "芋艿") + private String name; + + @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + private Integer source; + + @Schema(description = "资料内容说明/待办内容", example = "随便") + private String remark; + + @Schema(description = "上传fastgpt后的Id", example = "28459") + private String fastgptId; + + @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", example = "2") + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java new file mode 100644 index 0000000000..6a49eaae8e --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import cn.idev.excel.annotation.*; + +@Schema(description = "管理后台 - 企业信息 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EntInfoRespVO { + + @Schema(description = "资料名称", example = "芋艿") + @ExcelProperty("资料名称") + private String name; + + @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + @ExcelProperty("资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + private Integer source; + + @Schema(description = "资料内容说明/待办内容", example = "随便") + @ExcelProperty("资料内容说明/待办内容") + private String remark; + + @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("状态(-1 待上传/待办 0正常 1停用 2 完结)") + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java new file mode 100644 index 0000000000..abbbf78b27 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import javax.validation.constraints.*; + +@Schema(description = "管理后台 - 企业信息新增/修改 Request VO") +@Data +public class EntInfoSaveReqVO { + + @Schema(description = "资料名称", example = "芋艿") + private String name; + + @Schema(description = "资料内容说明/待办内容", example = "随便") + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java new file mode 100644 index 0000000000..c928df69ef --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.custom.dal.dataobject.ent; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * 企业 DO + * + * @author Asy + */ +@TableName("custom_ent") +@KeySequence("custom_ent_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EntDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 企业名称 + */ + private String name; + /** + * 所属行业 + */ + private String industry; + /** + * 说明 + */ + private String remark; + /** + * 所属用户Id + */ + private Long userId; + /** + * 部门Id + */ + private Long deptId; + /** + * 状态(0正常 1停用) + */ + private Integer status; + + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java new file mode 100644 index 0000000000..2630a8be6d --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.custom.dal.dataobject.entinfo; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * 企业信息 DO + * + * @author Asy + */ +@TableName("custom_ent_info") +@KeySequence("custom_ent_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EntInfoDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 资料名称 + */ + private String name; + /** + * 资料来源(0 文档,1 爬虫,2 录音, 3 待办事项) + */ + private Integer source; + /** + * 资料内容说明/待办内容 + */ + private String remark; + /** + * 所属企业Id + */ + private Long entId; + /** + * 上传fastgpt后的Id + */ + private String fastgptId; + /** + * 状态(-1 待上传/待办 0正常 1停用 2 完结) + */ + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java new file mode 100644 index 0000000000..aab8701e91 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.custom.dal.dataobject.entinfo; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * 企业信息 DO + * + * @author Asy + */ +@TableName("custom_ent_info") +@KeySequence("custom_ent_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EntInfoDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 资料名称 + */ + private String name; + /** + * 资料来源(0 文档,1 爬虫,2 录音, 3 待办事项) + */ + private Integer source; + /** + * 资料内容说明/待办内容 + */ + private String remark; + /** + * 所属企业Id + */ + private Long entId; + /** + * 上传fastgpt后的Id + */ + private String fastgptId; + /** + * 状态(-1 待上传/待办 0正常 1停用 2 完结) + */ + private Integer status; + + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java new file mode 100644 index 0000000000..7dd8f23d20 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.custom.dal.mysql.entinfo; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 企业信息 Mapper + * + * @author Asy + */ +@Mapper +public interface EntInfoMapper extends BaseMapperX { + + default List selectListByEntId(Long entId) { + return selectList(EntInfoDO::getEntId, entId); + } + + default int deleteByEntId(Long entId) { + return delete(EntInfoDO::getEntId, entId); + } + + default int deleteByEntIds(List entIds) { + return deleteBatch(EntInfoDO::getEntId, entIds); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java new file mode 100644 index 0000000000..df63e7ef3c --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.custom.dal.mysql.ent; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import org.apache.ibatis.annotations.Mapper; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; + +/** + * 企业 Mapper + * + * @author Asy + */ +@Mapper +public interface EntMapper extends BaseMapperX { + + default PageResult selectPage(EntPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(EntDO::getName, reqVO.getName()) + .eqIfPresent(EntDO::getIndustry, reqVO.getIndustry()) + .eqIfPresent(EntDO::getRemark, reqVO.getRemark()) + .eqIfPresent(EntDO::getStatus, reqVO.getStatus()) + .orderByDesc(EntDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java new file mode 100644 index 0000000000..68ba46f2d9 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.custom.dal.mysql.entinfo; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import org.apache.ibatis.annotations.Mapper; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; + +/** + * 企业信息 Mapper + * + * @author Asy + */ +@Mapper +public interface EntInfoMapper extends BaseMapperX { + + default PageResult selectPage(EntInfoPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(EntInfoDO::getName, reqVO.getName()) + .eqIfPresent(EntInfoDO::getSource, reqVO.getSource()) + .eqIfPresent(EntInfoDO::getRemark, reqVO.getRemark()) + .eqIfPresent(EntInfoDO::getFastgptId, reqVO.getFastgptId()) + .eqIfPresent(EntInfoDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(EntInfoDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(EntInfoDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java new file mode 100644 index 0000000000..f594e27671 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java @@ -0,0 +1,9 @@ +package cn.iocoder.yudao.module.custom.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +// TODO 待办:请将下面的错误码复制到 yudao-module-custom 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!! +// ========== 企业 TODO 补充编号 ========== +public interface ErrorCodeConstants { + ErrorCode ENT_NOT_EXISTS = new ErrorCode(1_003_201_000, "企业不存在"); +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java new file mode 100644 index 0000000000..80a2d57bdb --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.custom.service.ent; + +import java.util.*; +import javax.validation.*; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; + +/** + * 企业 Service 接口 + * + * @author Asy + */ +public interface EntService { + + /** + * 创建企业 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createEnt(@Valid EntSaveReqVO createReqVO); + + /** + * 更新企业 + * + * @param updateReqVO 更新信息 + */ + void updateEnt(@Valid EntSaveReqVO updateReqVO); + + /** + * 删除企业 + * + * @param id 编号 + */ + void deleteEnt(Long id); + + /** + * 批量删除企业 + * + * @param ids 编号 + */ + void deleteEntListByIds(List ids); + + /** + * 获得企业 + * + * @param id 编号 + * @return 企业 + */ + EntDO getEnt(Long id); + + /** + * 获得企业分页 + * + * @param pageReqVO 分页查询 + * @return 企业分页 + */ + PageResult getEntPage(EntPageReqVO pageReqVO); + + // ==================== 子表(企业信息) ==================== + + /** + * 获得企业信息列表 + * + * @param entId 所属企业Id + * @return 企业信息列表 + */ + List getEntInfoListByEntId(Long entId); + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java new file mode 100644 index 0000000000..f0c40831d9 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -0,0 +1,148 @@ +package cn.iocoder.yudao.module.custom.service.ent; + +import cn.hutool.core.collection.CollUtil; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; + +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; +import cn.iocoder.yudao.module.custom.dal.mysql.entinfo.EntInfoMapper; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; +import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.*; + +/** + * 企业 Service 实现类 + * + * @author Asy + */ +@Service +@Validated +public class EntServiceImpl implements EntService { + + @Resource + private EntMapper entMapper; + @Resource + private EntInfoMapper entInfoMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createEnt(EntSaveReqVO createReqVO) { + // 插入 + EntDO ent = BeanUtils.toBean(createReqVO, EntDO.class); + entMapper.insert(ent); + + + // 插入子表 + createEntInfoList(ent.getId(), createReqVO.getEntInfos()); + // 返回 + return ent.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateEnt(EntSaveReqVO updateReqVO) { + // 校验存在 + validateEntExists(updateReqVO.getId()); + // 更新 + EntDO updateObj = BeanUtils.toBean(updateReqVO, EntDO.class); + entMapper.updateById(updateObj); + + // 更新子表 + updateEntInfoList(updateReqVO.getId(), updateReqVO.getEntInfos()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteEnt(Long id) { + // 校验存在 + validateEntExists(id); + // 删除 + entMapper.deleteById(id); + + // 删除子表 + deleteEntInfoByEntId(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteEntListByIds(List ids) { + // 删除 + entMapper.deleteByIds(ids); + + // 删除子表 + deleteEntInfoByEntIds(ids); + } + + + private void validateEntExists(Long id) { + if (entMapper.selectById(id) == null) { + throw exception(ENT_NOT_EXISTS); + } + } + + @Override + public EntDO getEnt(Long id) { + return entMapper.selectById(id); + } + + @Override + public PageResult getEntPage(EntPageReqVO pageReqVO) { + return entMapper.selectPage(pageReqVO); + } + + // ==================== 子表(企业信息) ==================== + + @Override + public List getEntInfoListByEntId(Long entId) { + return entInfoMapper.selectListByEntId(entId); + } + + private void createEntInfoList(Long entId, List list) { + list.forEach(o -> o.setEntId(entId).clean()); + entInfoMapper.insertBatch(list); + } + + private void updateEntInfoList(Long entId, List list) { + list.forEach(o -> o.setEntId(entId).clean()); + List oldList = entInfoMapper.selectListByEntId(entId); + List> diffList = diffList(oldList, list, (oldVal, newVal) -> { + boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId()); + if (same) { + newVal.setId(oldVal.getId()).clean(); // 解决更新情况下:updateTime 不更新 + } + return same; + }); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + entInfoMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + entInfoMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + entInfoMapper.deleteByIds(convertList(diffList.get(2), EntInfoDO::getId)); + } + } + + private void deleteEntInfoByEntId(Long entId) { + entInfoMapper.deleteByEntId(entId); + } + + private void deleteEntInfoByEntIds(List entIds) { + entInfoMapper.deleteByEntIds(entIds); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java new file mode 100644 index 0000000000..47bfeb866c --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.custom.service.entinfo; + +import java.util.*; +import javax.validation.*; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; + +/** + * 企业信息 Service 接口 + * + * @author Asy + */ +public interface EntInfoService { + + /** + * 创建企业信息 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createEntInfo(@Valid EntInfoSaveReqVO createReqVO); + + /** + * 更新企业信息 + * + * @param updateReqVO 更新信息 + */ + void updateEntInfo(@Valid EntInfoSaveReqVO updateReqVO); + + /** + * 删除企业信息 + * + * @param id 编号 + */ + void deleteEntInfo(Long id); + + /** + * 批量删除企业信息 + * + * @param ids 编号 + */ + void deleteEntInfoListByIds(List ids); + + /** + * 获得企业信息 + * + * @param id 编号 + * @return 企业信息 + */ + EntInfoDO getEntInfo(Long id); + + /** + * 获得企业信息分页 + * + * @param pageReqVO 分页查询 + * @return 企业信息分页 + */ + PageResult getEntInfoPage(EntInfoPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java new file mode 100644 index 0000000000..0e95e2bd23 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.custom.service.entinfo; + +import cn.hutool.core.collection.CollUtil; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; + +import cn.iocoder.yudao.module.custom.dal.mysql.entinfo.EntInfoMapper; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; +import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.*; + +/** + * 企业信息 Service 实现类 + * + * @author Asy + */ +@Service +@Validated +public class EntInfoServiceImpl implements EntInfoService { + + @Resource + private EntInfoMapper entInfoMapper; + + @Override + public Long createEntInfo(EntInfoSaveReqVO createReqVO) { + // 插入 + EntInfoDO entInfo = BeanUtils.toBean(createReqVO, EntInfoDO.class); + entInfoMapper.insert(entInfo); + + // 返回 + return entInfo.getId(); + } + + @Override + public void updateEntInfo(EntInfoSaveReqVO updateReqVO) { + // 校验存在 + validateEntInfoExists(updateReqVO.getId()); + // 更新 + EntInfoDO updateObj = BeanUtils.toBean(updateReqVO, EntInfoDO.class); + entInfoMapper.updateById(updateObj); + } + + @Override + public void deleteEntInfo(Long id) { + // 校验存在 + validateEntInfoExists(id); + // 删除 + entInfoMapper.deleteById(id); + } + + @Override + public void deleteEntInfoListByIds(List ids) { + // 删除 + entInfoMapper.deleteByIds(ids); + } + + + private void validateEntInfoExists(Long id) { + if (entInfoMapper.selectById(id) == null) { + throw exception(ENT_INFO_NOT_EXISTS); + } + } + + @Override + public EntInfoDO getEntInfo(Long id) { + return entInfoMapper.selectById(id); + } + + @Override + public PageResult getEntInfoPage(EntInfoPageReqVO pageReqVO) { + return entInfoMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/resources/mapper/ent/EntMapper.xml b/yudao-module-custom/src/main/resources/mapper/ent/EntMapper.xml new file mode 100644 index 0000000000..f16ecbe269 --- /dev/null +++ b/yudao-module-custom/src/main/resources/mapper/ent/EntMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-custom/src/main/resources/mapper/entinfo/EntInfoMapper.xml b/yudao-module-custom/src/main/resources/mapper/entinfo/EntInfoMapper.xml new file mode 100644 index 0000000000..0aa5f9a61a --- /dev/null +++ b/yudao-module-custom/src/main/resources/mapper/entinfo/EntInfoMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 97ee0daf38..c7e1727975 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -31,6 +31,11 @@ yudao-module-infra ${revision} + + cn.iocoder.boot + yudao-module-custom + ${revision} + -- Gitee From 675a1a0398d3fff94c6ca1dba1e08ea639c956be Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 23 Oct 2025 10:15:33 +0800 Subject: [PATCH 03/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=B7=B2=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 +- yudao-module-custom/pom.xml | 11 -- .../controller/admin/ent/EntController.java | 42 ++++--- .../controller/admin/ent/vo/EntPageReqVO.java | 9 +- .../controller/admin/ent/vo/EntRespVO.java | 58 ++++----- .../controller/admin/ent/vo/EntSaveReqVO.java | 11 +- .../admin/entinfo/EntInfoController.java | 42 ++++--- .../admin/entinfo/vo/EntInfoPageReqVO.java | 6 +- .../admin/entinfo/vo/EntInfoRespVO.java | 70 +++++------ .../admin/entinfo/vo/EntInfoSaveReqVO.java | 7 +- .../custom/dal/dataobject/ent/EntDO.java | 111 +++++++++--------- .../custom/dal/dataobject/ent/EntInfoDO.java | 9 +- .../dal/dataobject/entinfo/EntInfoDO.java | 56 --------- .../custom/dal/mysql/ent/EntInfoMapper.java | 73 +++++++----- .../custom/dal/mysql/ent/EntMapper.java | 6 +- .../dal/mysql/entinfo/EntInfoMapper.java | 31 ----- .../module/custom/service/ent/EntService.java | 11 +- .../custom/service/ent/EntServiceImpl.java | 26 ++-- .../service/entinfo/EntInfoService.java | 11 +- .../service/entinfo/EntInfoServiceImpl.java | 25 ++-- 20 files changed, 260 insertions(+), 361 deletions(-) delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java diff --git a/pom.xml b/pom.xml index 1581eeb7d6..89f07ea6d4 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ - + + <!– 修复 Tomcat 安全漏洞 –> org.apache.tomcat.embed tomcat-embed-core @@ -83,7 +83,7 @@ org.apache.tomcat.embed tomcat-embed-websocket 9.0.98 - + --> diff --git a/yudao-module-custom/pom.xml b/yudao-module-custom/pom.xml index 4759dec555..4bc7f12f96 100644 --- a/yudao-module-custom/pom.xml +++ b/yudao-module-custom/pom.xml @@ -17,23 +17,12 @@ - - - cn.iocoder.boot - yudao-module-system - ${revision} - cn.iocoder.boot yudao-module-infra ${revision} - - cn.iocoder.boot - yudao-common - - cn.iocoder.boot diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index f7d8e29238..6b616e7f60 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -1,34 +1,32 @@ package cn.iocoder.yudao.module.custom.controller.admin.ent; -import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.security.access.prepost.PreAuthorize; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Operation; - -import javax.validation.constraints.*; -import javax.validation.*; -import javax.servlet.http.*; -import java.util.*; -import java.io.IOException; - +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; - -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; - -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntRespVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; import cn.iocoder.yudao.module.custom.service.ent.EntService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 企业") @RestController diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java index e977ad1093..db29560e91 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntPageReqVO.java @@ -1,13 +1,8 @@ package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; -import lombok.*; -import java.util.*; -import io.swagger.v3.oas.annotations.media.Schema; import cn.iocoder.yudao.framework.common.pojo.PageParam; -import org.springframework.format.annotation.DateTimeFormat; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; @Schema(description = "管理后台 - 企业分页 Request VO") @Data diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java index 7086f2e51f..e94f171fd7 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java @@ -1,29 +1,29 @@ -package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.util.*; -import cn.idev.excel.annotation.*; - -@Schema(description = "管理后台 - 企业 Response VO") -@Data -@ExcelIgnoreUnannotated -public class EntRespVO { - - @Schema(description = "企业名称", example = "张三") - @ExcelProperty("企业名称") - private String name; - - @Schema(description = "所属行业") - @ExcelProperty("所属行业") - private String industry; - - @Schema(description = "说明", example = "你猜") - @ExcelProperty("说明") - private String remark; - - @Schema(description = "状态(0正常 1停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("状态(0正常 1停用)") - private Integer status; - -} \ No newline at end of file +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 企业 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EntRespVO { + + @Schema(description = "企业名称", example = "张三") + @ExcelProperty("企业名称") + private String name; + + @Schema(description = "所属行业") + @ExcelProperty("所属行业") + private String industry; + + @Schema(description = "说明", example = "你猜") + @ExcelProperty("说明") + private String remark; + + @Schema(description = "状态(0正常 1停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("状态(0正常 1停用)") + private Integer status; + +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java index 09b9e74ce1..512a2a4b37 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java @@ -1,14 +1,17 @@ package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.util.*; -import javax.validation.constraints.*; import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; @Schema(description = "管理后台 - 企业新增/修改 Request VO") @Data public class EntSaveReqVO { + @Schema(description = "编号") + private Long id; @Schema(description = "企业名称", example = "张三") private String name; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java index 4898716c7d..e22d827eb9 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java @@ -1,33 +1,31 @@ package cn.iocoder.yudao.module.custom.controller.admin.entinfo; -import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.security.access.prepost.PreAuthorize; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Operation; - -import javax.validation.constraints.*; -import javax.validation.*; -import javax.servlet.http.*; -import java.util.*; -import java.io.IOException; - +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; - -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; - -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoRespVO; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; import cn.iocoder.yudao.module.custom.service.entinfo.EntInfoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 企业信息") @RestController diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java index b5d025e7e2..120b56573c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; -import lombok.*; -import java.util.*; -import io.swagger.v3.oas.annotations.media.Schema; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; + import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java index 6a49eaae8e..39ccee97b3 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java @@ -1,35 +1,35 @@ -package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.util.*; -import org.springframework.format.annotation.DateTimeFormat; -import java.time.LocalDateTime; -import cn.idev.excel.annotation.*; - -@Schema(description = "管理后台 - 企业信息 Response VO") -@Data -@ExcelIgnoreUnannotated -public class EntInfoRespVO { - - @Schema(description = "资料名称", example = "芋艿") - @ExcelProperty("资料名称") - private String name; - - @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") - @ExcelProperty("资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") - private Integer source; - - @Schema(description = "资料内容说明/待办内容", example = "随便") - @ExcelProperty("资料内容说明/待办内容") - private String remark; - - @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("状态(-1 待上传/待办 0正常 1停用 2 完结)") - private Integer status; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; - -} \ No newline at end of file +package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 企业信息 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EntInfoRespVO { + + @Schema(description = "资料名称", example = "芋艿") + @ExcelProperty("资料名称") + private String name; + + @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + @ExcelProperty("资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + private Integer source; + + @Schema(description = "资料内容说明/待办内容", example = "随便") + @ExcelProperty("资料内容说明/待办内容") + private String remark; + + @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("状态(-1 待上传/待办 0正常 1停用 2 完结)") + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java index abbbf78b27..1e66a081a0 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java @@ -1,14 +1,15 @@ package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.util.*; -import javax.validation.constraints.*; +import lombok.Data; @Schema(description = "管理后台 - 企业信息新增/修改 Request VO") @Data public class EntInfoSaveReqVO { + @Schema(description = "资料id") + private Long id; + @Schema(description = "资料名称", example = "芋艿") private String name; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java index c928df69ef..a534aa7a3a 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java @@ -1,56 +1,55 @@ -package cn.iocoder.yudao.module.custom.dal.dataobject.ent; - -import lombok.*; -import java.util.*; -import java.time.LocalDateTime; -import java.time.LocalDateTime; -import com.baomidou.mybatisplus.annotation.*; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; - -/** - * 企业 DO - * - * @author Asy - */ -@TableName("custom_ent") -@KeySequence("custom_ent_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class EntDO extends BaseDO { - - /** - * 主键 - */ - @TableId - private Long id; - /** - * 企业名称 - */ - private String name; - /** - * 所属行业 - */ - private String industry; - /** - * 说明 - */ - private String remark; - /** - * 所属用户Id - */ - private Long userId; - /** - * 部门Id - */ - private Long deptId; - /** - * 状态(0正常 1停用) - */ - private Integer status; - - -} \ No newline at end of file +package cn.iocoder.yudao.module.custom.dal.dataobject.ent; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 企业 DO + * + * @author Asy + */ +@TableName("custom_ent") +@KeySequence("custom_ent_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EntDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 企业名称 + */ + private String name; + /** + * 所属行业 + */ + private String industry; + /** + * 说明 + */ + private String remark; + /** + * 所属用户Id + */ + private Long userId; + /** + * 部门Id + */ + private Long deptId; + /** + * 状态(0正常 1停用) + */ + private Integer status; + + +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java index 2630a8be6d..eacb1dc36c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -1,11 +1,10 @@ package cn.iocoder.yudao.module.custom.dal.dataobject.entinfo; -import lombok.*; -import java.util.*; -import java.time.LocalDateTime; -import java.time.LocalDateTime; -import com.baomidou.mybatisplus.annotation.*; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; /** * 企业信息 DO diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java deleted file mode 100644 index aab8701e91..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/entinfo/EntInfoDO.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.custom.dal.dataobject.entinfo; - -import lombok.*; -import java.util.*; -import java.time.LocalDateTime; -import java.time.LocalDateTime; -import com.baomidou.mybatisplus.annotation.*; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; - -/** - * 企业信息 DO - * - * @author Asy - */ -@TableName("custom_ent_info") -@KeySequence("custom_ent_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class EntInfoDO extends BaseDO { - - /** - * 主键 - */ - @TableId - private Long id; - /** - * 资料名称 - */ - private String name; - /** - * 资料来源(0 文档,1 爬虫,2 录音, 3 待办事项) - */ - private Integer source; - /** - * 资料内容说明/待办内容 - */ - private String remark; - /** - * 所属企业Id - */ - private Long entId; - /** - * 上传fastgpt后的Id - */ - private String fastgptId; - /** - * 状态(-1 待上传/待办 0正常 1停用 2 完结) - */ - private Integer status; - - -} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java index 7dd8f23d20..d4a4e17f38 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java @@ -1,32 +1,41 @@ -package cn.iocoder.yudao.module.custom.dal.mysql.entinfo; - -import java.util.*; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * 企业信息 Mapper - * - * @author Asy - */ -@Mapper -public interface EntInfoMapper extends BaseMapperX { - - default List selectListByEntId(Long entId) { - return selectList(EntInfoDO::getEntId, entId); - } - - default int deleteByEntId(Long entId) { - return delete(EntInfoDO::getEntId, entId); - } - - default int deleteByEntIds(List entIds) { - return deleteBatch(EntInfoDO::getEntId, entIds); - } - -} \ No newline at end of file +package cn.iocoder.yudao.module.custom.dal.mysql.ent; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 企业信息 Mapper + * + * @author Asy + */ +@Mapper +public interface EntInfoMapper extends BaseMapperX { + + default List selectListByEntId(Long entId) { + return selectList(EntInfoDO::getEntId, entId); + } + + default int deleteByEntId(Long entId) { + return delete(EntInfoDO::getEntId, entId); + } + + default int deleteByEntIds(List entIds) { + return deleteBatch(EntInfoDO::getEntId, entIds); + } + + default PageResult selectPage(EntInfoPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(EntInfoDO::getName, reqVO.getName()) + .likeIfPresent(EntInfoDO::getRemark, reqVO.getRemark()) + .eqIfPresent(EntInfoDO::getSource, reqVO.getSource()) + .eqIfPresent(EntInfoDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(EntInfoDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(EntInfoDO::getId)); + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java index df63e7ef3c..3aa67a3d56 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java @@ -1,13 +1,11 @@ package cn.iocoder.yudao.module.custom.dal.mysql.ent; -import java.util.*; - import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import org.apache.ibatis.annotations.Mapper; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; /** * 企业 Mapper diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java deleted file mode 100644 index 68ba46f2d9..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/entinfo/EntInfoMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.custom.dal.mysql.entinfo; - -import java.util.*; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; -import org.apache.ibatis.annotations.Mapper; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; - -/** - * 企业信息 Mapper - * - * @author Asy - */ -@Mapper -public interface EntInfoMapper extends BaseMapperX { - - default PageResult selectPage(EntInfoPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(EntInfoDO::getName, reqVO.getName()) - .eqIfPresent(EntInfoDO::getSource, reqVO.getSource()) - .eqIfPresent(EntInfoDO::getRemark, reqVO.getRemark()) - .eqIfPresent(EntInfoDO::getFastgptId, reqVO.getFastgptId()) - .eqIfPresent(EntInfoDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(EntInfoDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(EntInfoDO::getId)); - } - -} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 80a2d57bdb..6165d21900 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -1,12 +1,13 @@ package cn.iocoder.yudao.module.custom.service.ent; -import java.util.*; -import javax.validation.*; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; + +import javax.validation.Valid; +import java.util.List; /** * 企业 Service 接口 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index f0c40831d9..56c10e87f8 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -1,26 +1,26 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.hutool.core.collection.CollUtil; -import org.springframework.stereotype.Service; -import javax.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; -import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; - +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; -import cn.iocoder.yudao.module.custom.dal.mysql.entinfo.EntInfoMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; -import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.ENT_NOT_EXISTS; /** * 企业 Service 实现类 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java index 47bfeb866c..e5453272df 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java @@ -1,11 +1,12 @@ package cn.iocoder.yudao.module.custom.service.entinfo; -import java.util.*; -import javax.validation.*; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoSaveReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; + +import javax.validation.Valid; +import java.util.List; /** * 企业信息 Service 接口 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java index 0e95e2bd23..6de68a73b8 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java @@ -1,24 +1,19 @@ package cn.iocoder.yudao.module.custom.service.entinfo; -import cn.hutool.core.collection.CollUtil; -import org.springframework.stereotype.Service; -import javax.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.*; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoSaveReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; -import cn.iocoder.yudao.module.custom.dal.mysql.entinfo.EntInfoMapper; +import javax.annotation.Resource; +import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; -import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.ENT_NOT_EXISTS; /** * 企业信息 Service 实现类 @@ -68,7 +63,7 @@ public class EntInfoServiceImpl implements EntInfoService { private void validateEntInfoExists(Long id) { if (entInfoMapper.selectById(id) == null) { - throw exception(ENT_INFO_NOT_EXISTS); + throw exception(ENT_NOT_EXISTS); } } -- Gitee From 16a1226b94fd626a714a9ee62fb6554b51df0931 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 24 Oct 2025 11:46:41 +0800 Subject: [PATCH 04/50] =?UTF-8?q?=E6=89=B9=E9=87=8F=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/ent/EntController.java | 13 ++++++++ .../admin/ent/vo/EntImportReqVO.java | 31 +++++++++++++++++++ .../module/custom/service/ent/EntService.java | 12 +++++++ .../custom/service/ent/EntServiceImpl.java | 8 +++++ 4 files changed, 64 insertions(+) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index 6b616e7f60..cd0053bdfc 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntRespVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; @@ -18,6 +19,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; @@ -26,6 +28,7 @@ import java.io.IOException; import java.util.List; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.IMPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 企业") @@ -87,6 +90,16 @@ public class EntController { return success(BeanUtils.toBean(pageResult, EntRespVO.class)); } + @PostMapping("/import-excel") + @Operation(summary = "从 Excel 导入企业数据") + @PreAuthorize("@ss.hasPermission('custom:ent:import')") + @ApiAccessLog(operateType = IMPORT) + public CommonResult importEntExcel(@RequestParam("file") MultipartFile file) throws IOException { + List importList = ExcelUtils.read(file, EntImportReqVO.class); + entService.importEntList(importList); + return success("导入成功"); + } + @GetMapping("/export-excel") @Operation(summary = "导出企业 Excel") @PreAuthorize("@ss.hasPermission('custom:ent:export')") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java new file mode 100644 index 0000000000..31411f2ffe --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 企业导入 Request VO") +@Data +public class EntImportReqVO { + + @ExcelProperty("企业名称") + @NotEmpty(message = "企业名称不能为空") + private String name; + + @ExcelProperty("企业所属行业") + private String industry; + + @ExcelProperty("说明") + private String remark; + + @ExcelProperty("所属客户经理") + private String manager; + + @ExcelProperty(value = "状态(0正常 1停用)") + @NotNull(message = "状态(0正常 1停用)不能为空") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 6165d21900..3ff06e4016 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -1,12 +1,14 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; import javax.validation.Valid; +import java.io.IOException; import java.util.List; /** @@ -61,6 +63,16 @@ public interface EntService { */ PageResult getEntPage(EntPageReqVO pageReqVO); + + + /** + * 导入企业 + * + * @param importReqVOList 导入信息列表 + */ + + void importEntList(List importReqVOList) throws IOException; + // ==================== 子表(企业信息) ==================== /** diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 56c10e87f8..7be3090a2f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; @@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.io.IOException; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -145,4 +147,10 @@ public class EntServiceImpl implements EntService { entInfoMapper.deleteByEntIds(entIds); } + @Override + public void importEntList(List importReqVOList) throws IOException { + List entList = BeanUtils.toBean(importReqVOList, EntDO.class); + entMapper.insertBatch(entList); + } + } \ No newline at end of file -- Gitee From 180a388366732c32f1c7e34796fa176833a6aefa Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 24 Oct 2025 11:46:58 +0800 Subject: [PATCH 05/50] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/admin/user/UserController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java index 93d0ca16df..71320f543a 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java @@ -156,10 +156,10 @@ public class UserController { public void importTemplate(HttpServletResponse response) throws IOException { // 手动创建导出 demo List list = Arrays.asList( - UserImportExcelVO.builder().username("yunai").deptId(1L).email("yunai@iocoder.cn").mobile("15601691300") - .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(), - UserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300") - .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build() + UserImportExcelVO.builder().username("zhang").deptId(1L).email("zhang@good.cn").mobile("15688888888") + .nickname("张三").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(), + UserImportExcelVO.builder().username("huang").deptId(2L).email("huang@good.cn").mobile("15666666666") + .nickname("李四").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build() ); // 输出 ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list); -- Gitee From a42d8ea8e065746dd53b981e0bec429a0dbf90be Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 24 Oct 2025 17:19:42 +0800 Subject: [PATCH 06/50] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=AF=BC=E5=85=A5=E5=92=8C=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E9=99=84=E4=BB=B6=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/ent/EntController.java | 22 +++- .../controller/admin/ent/vo/EntSaveReqVO.java | 13 ++- .../admin/entinfo/EntInfoController.java | 102 ------------------ .../admin/entinfo/vo/EntInfoPageReqVO.java | 35 ------ .../admin/entinfo/vo/EntInfoRespVO.java | 35 ------ .../admin/entinfo/vo/EntInfoSaveReqVO.java | 19 ---- .../custom/dal/dataobject/ent/EntInfoDO.java | 6 +- .../custom/dal/mysql/ent/EntInfoMapper.java | 2 +- .../module/custom/service/ent/EntService.java | 10 +- .../custom/service/ent/EntServiceImpl.java | 68 +++++------- .../service/entinfo/EntInfoService.java | 63 ----------- .../service/entinfo/EntInfoServiceImpl.java | 80 -------------- 12 files changed, 71 insertions(+), 384 deletions(-) delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java delete mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index cd0053bdfc..6130011517 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntRespVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.ent.EntService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -67,7 +67,7 @@ public class EntController { @DeleteMapping("/delete-list") @Parameter(name = "ids", description = "编号", required = true) @Operation(summary = "批量删除企业") - @PreAuthorize("@ss.hasPermission('custom:ent:delete')") + @PreAuthorize("@ss.hasPermission('custom:ent:delete')") public CommonResult deleteEntList(@RequestParam("ids") List ids) { entService.deleteEntListByIds(ids); return success(true); @@ -105,12 +105,12 @@ public class EntController { @PreAuthorize("@ss.hasPermission('custom:ent:export')") @ApiAccessLog(operateType = EXPORT) public void exportEntExcel(@Valid EntPageReqVO pageReqVO, - HttpServletResponse response) throws IOException { + HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = entService.getEntPage(pageReqVO).getList(); // 导出 Excel ExcelUtils.write(response, "企业.xls", "数据", EntRespVO.class, - BeanUtils.toBean(list, EntRespVO.class)); + BeanUtils.toBean(list, EntRespVO.class)); } // ==================== 子表(企业信息) ==================== @@ -123,4 +123,18 @@ public class EntController { return success(entService.getEntInfoListByEntId(entId)); } + @PostMapping("/import-attachment") + @Operation(summary = "导入企业附件") + @PreAuthorize("@ss.hasPermission('custom:ent:importFile')") + @ApiAccessLog(operateType = IMPORT) + public CommonResult importEntAttachment( + @RequestParam("entId") Long entId, @RequestParam("entName") String entName, + @RequestParam("attachmentPath") String attachmentPath) { + + // 保存文件并获取访问URL + entService.createEntInfo(entId, entName, attachmentPath); + + return success("导入成功"); + } + } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java index 512a2a4b37..d682c5e40e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java @@ -1,11 +1,9 @@ package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import javax.validation.constraints.NotNull; -import java.util.List; @Schema(description = "管理后台 - 企业新增/修改 Request VO") @Data @@ -26,7 +24,14 @@ public class EntSaveReqVO { @NotNull(message = "状态(0正常 1停用)不能为空") private Integer status; - @Schema(description = "企业信息列表") - private List entInfos; + @Schema(description = "所属客户经理") + @NotNull(message = "所属客户经理不能为空") + private Long userId; + + @Schema(description = "附件路径") + private String attachmentPath; + + @Schema(description = "附件内容") + private String attachmentContent; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java deleted file mode 100644 index e22d827eb9..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/EntInfoController.java +++ /dev/null @@ -1,102 +0,0 @@ -package cn.iocoder.yudao.module.custom.controller.admin.entinfo; - -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoRespVO; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoSaveReqVO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; -import cn.iocoder.yudao.module.custom.service.entinfo.EntInfoService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; -import java.io.IOException; -import java.util.List; - -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 企业信息") -@RestController -@RequestMapping("/custom/ent-info") -@Validated -public class EntInfoController { - - @Resource - private EntInfoService entInfoService; - - @PostMapping("/create") - @Operation(summary = "创建企业信息") - @PreAuthorize("@ss.hasPermission('custom:ent-info:create')") - public CommonResult createEntInfo(@Valid @RequestBody EntInfoSaveReqVO createReqVO) { - return success(entInfoService.createEntInfo(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新企业信息") - @PreAuthorize("@ss.hasPermission('custom:ent-info:update')") - public CommonResult updateEntInfo(@Valid @RequestBody EntInfoSaveReqVO updateReqVO) { - entInfoService.updateEntInfo(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除企业信息") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('custom:ent-info:delete')") - public CommonResult deleteEntInfo(@RequestParam("id") Long id) { - entInfoService.deleteEntInfo(id); - return success(true); - } - - @DeleteMapping("/delete-list") - @Parameter(name = "ids", description = "编号", required = true) - @Operation(summary = "批量删除企业信息") - @PreAuthorize("@ss.hasPermission('custom:ent-info:delete')") - public CommonResult deleteEntInfoList(@RequestParam("ids") List ids) { - entInfoService.deleteEntInfoListByIds(ids); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得企业信息") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('custom:ent-info:query')") - public CommonResult getEntInfo(@RequestParam("id") Long id) { - EntInfoDO entInfo = entInfoService.getEntInfo(id); - return success(BeanUtils.toBean(entInfo, EntInfoRespVO.class)); - } - - @GetMapping("/page") - @Operation(summary = "获得企业信息分页") - @PreAuthorize("@ss.hasPermission('custom:ent-info:query')") - public CommonResult> getEntInfoPage(@Valid EntInfoPageReqVO pageReqVO) { - PageResult pageResult = entInfoService.getEntInfoPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, EntInfoRespVO.class)); - } - - @GetMapping("/export-excel") - @Operation(summary = "导出企业信息 Excel") - @PreAuthorize("@ss.hasPermission('custom:ent-info:export')") - @ApiAccessLog(operateType = EXPORT) - public void exportEntInfoExcel(@Valid EntInfoPageReqVO pageReqVO, - HttpServletResponse response) throws IOException { - pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = entInfoService.getEntInfoPage(pageReqVO).getList(); - // 导出 Excel - ExcelUtils.write(response, "企业信息.xls", "数据", EntInfoRespVO.class, - BeanUtils.toBean(list, EntInfoRespVO.class)); - } - -} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java deleted file mode 100644 index 120b56573c..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoPageReqVO.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 企业信息分页 Request VO") -@Data -public class EntInfoPageReqVO extends PageParam { - - @Schema(description = "资料名称", example = "芋艿") - private String name; - - @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") - private Integer source; - - @Schema(description = "资料内容说明/待办内容", example = "随便") - private String remark; - - @Schema(description = "上传fastgpt后的Id", example = "28459") - private String fastgptId; - - @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", example = "2") - private Integer status; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java deleted file mode 100644 index 39ccee97b3..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoRespVO.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; - -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 企业信息 Response VO") -@Data -@ExcelIgnoreUnannotated -public class EntInfoRespVO { - - @Schema(description = "资料名称", example = "芋艿") - @ExcelProperty("资料名称") - private String name; - - @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") - @ExcelProperty("资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") - private Integer source; - - @Schema(description = "资料内容说明/待办内容", example = "随便") - @ExcelProperty("资料内容说明/待办内容") - private String remark; - - @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("状态(-1 待上传/待办 0正常 1停用 2 完结)") - private Integer status; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; - -} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java deleted file mode 100644 index 1e66a081a0..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/entinfo/vo/EntInfoSaveReqVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 企业信息新增/修改 Request VO") -@Data -public class EntInfoSaveReqVO { - - @Schema(description = "资料id") - private Long id; - - @Schema(description = "资料名称", example = "芋艿") - private String name; - - @Schema(description = "资料内容说明/待办内容", example = "随便") - private String remark; - -} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java index eacb1dc36c..f86eff7334 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.custom.dal.dataobject.entinfo; +package cn.iocoder.yudao.module.custom.dal.dataobject.ent; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; @@ -38,6 +38,10 @@ public class EntInfoDO extends BaseDO { * 资料内容说明/待办内容 */ private String remark; + /** + * 文件地址 + */ + private String path; /** * 所属企业Id */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java index d4a4e17f38..5dc40812f9 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import org.apache.ibatis.annotations.Mapper; import java.util.List; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 3ff06e4016..690d323f9f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import javax.validation.Valid; import java.io.IOException; @@ -83,4 +83,12 @@ public interface EntService { */ List getEntInfoListByEntId(Long entId); + /** + * 新增企业附件 + * + * @param entId 所属企业Id + * @return 企业信息id + */ + Long createEntInfo(Long entId, String entName, String attachmentPath); + } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 7be3090a2f..56787084a7 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -1,14 +1,12 @@ package cn.iocoder.yudao.module.custom.service.ent; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; import org.springframework.stereotype.Service; @@ -17,11 +15,10 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.ENT_NOT_EXISTS; /** @@ -45,9 +42,9 @@ public class EntServiceImpl implements EntService { EntDO ent = BeanUtils.toBean(createReqVO, EntDO.class); entMapper.insert(ent); - // 插入子表 - createEntInfoList(ent.getId(), createReqVO.getEntInfos()); + createEntInfo(ent.getId(), ent.getName(), createReqVO.getAttachmentPath()); + // 返回 return ent.getId(); } @@ -62,7 +59,7 @@ public class EntServiceImpl implements EntService { entMapper.updateById(updateObj); // 更新子表 - updateEntInfoList(updateReqVO.getId(), updateReqVO.getEntInfos()); + createEntInfo(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getAttachmentPath()); } @Override @@ -78,13 +75,13 @@ public class EntServiceImpl implements EntService { } @Override - @Transactional(rollbackFor = Exception.class) + @Transactional(rollbackFor = Exception.class) public void deleteEntListByIds(List ids) { // 删除 entMapper.deleteByIds(ids); - - // 删除子表 - deleteEntInfoByEntIds(ids); + + // 删除子表 + deleteEntInfoByEntIds(ids); } @@ -111,41 +108,34 @@ public class EntServiceImpl implements EntService { return entInfoMapper.selectListByEntId(entId); } - private void createEntInfoList(Long entId, List list) { - list.forEach(o -> o.setEntId(entId).clean()); - entInfoMapper.insertBatch(list); - } + public Long createEntInfo(Long entId, String entName, String attachmentPath) { + // 如果有附件路径,则提取附件内容 + if (attachmentPath != null && !attachmentPath.isEmpty()) { - private void updateEntInfoList(Long entId, List list) { - list.forEach(o -> o.setEntId(entId).clean()); - List oldList = entInfoMapper.selectListByEntId(entId); - List> diffList = diffList(oldList, list, (oldVal, newVal) -> { - boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId()); - if (same) { - newVal.setId(oldVal.getId()).clean(); // 解决更新情况下:updateTime 不更新 - } - return same; - }); - - // 第二步,批量添加、修改、删除 - if (CollUtil.isNotEmpty(diffList.get(0))) { - entInfoMapper.insertBatch(diffList.get(0)); - } - if (CollUtil.isNotEmpty(diffList.get(1))) { - entInfoMapper.updateBatch(diffList.get(1)); - } - if (CollUtil.isNotEmpty(diffList.get(2))) { - entInfoMapper.deleteByIds(convertList(diffList.get(2), EntInfoDO::getId)); - } + EntInfoDO entInfoDO = new EntInfoDO(); + entInfoDO.setEntId(entId); + entInfoDO.setName(entName + "-" + attachmentPath.substring(attachmentPath.lastIndexOf("/") + 1)); + entInfoDO.setSource(0); + entInfoDO.setStatus(-1); + if (attachmentPath.endsWith(".wav") || attachmentPath.endsWith(".mp3")) + entInfoDO.setSource(2); + entInfoDO.setPath(attachmentPath); + + entInfoMapper.insert(entInfoDO); + + return entInfoDO.getId(); + } + + return -1L; } private void deleteEntInfoByEntId(Long entId) { entInfoMapper.deleteByEntId(entId); } - private void deleteEntInfoByEntIds(List entIds) { + private void deleteEntInfoByEntIds(List entIds) { entInfoMapper.deleteByEntIds(entIds); - } + } @Override public void importEntList(List importReqVOList) throws IOException { diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java deleted file mode 100644 index e5453272df..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoService.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.module.custom.service.entinfo; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoSaveReqVO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; - -import javax.validation.Valid; -import java.util.List; - -/** - * 企业信息 Service 接口 - * - * @author Asy - */ -public interface EntInfoService { - - /** - * 创建企业信息 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createEntInfo(@Valid EntInfoSaveReqVO createReqVO); - - /** - * 更新企业信息 - * - * @param updateReqVO 更新信息 - */ - void updateEntInfo(@Valid EntInfoSaveReqVO updateReqVO); - - /** - * 删除企业信息 - * - * @param id 编号 - */ - void deleteEntInfo(Long id); - - /** - * 批量删除企业信息 - * - * @param ids 编号 - */ - void deleteEntInfoListByIds(List ids); - - /** - * 获得企业信息 - * - * @param id 编号 - * @return 企业信息 - */ - EntInfoDO getEntInfo(Long id); - - /** - * 获得企业信息分页 - * - * @param pageReqVO 分页查询 - * @return 企业信息分页 - */ - PageResult getEntInfoPage(EntInfoPageReqVO pageReqVO); - -} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java deleted file mode 100644 index 6de68a73b8..0000000000 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/entinfo/EntInfoServiceImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.iocoder.yudao.module.custom.service.entinfo; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoSaveReqVO; -import cn.iocoder.yudao.module.custom.dal.dataobject.entinfo.EntInfoDO; -import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.ENT_NOT_EXISTS; - -/** - * 企业信息 Service 实现类 - * - * @author Asy - */ -@Service -@Validated -public class EntInfoServiceImpl implements EntInfoService { - - @Resource - private EntInfoMapper entInfoMapper; - - @Override - public Long createEntInfo(EntInfoSaveReqVO createReqVO) { - // 插入 - EntInfoDO entInfo = BeanUtils.toBean(createReqVO, EntInfoDO.class); - entInfoMapper.insert(entInfo); - - // 返回 - return entInfo.getId(); - } - - @Override - public void updateEntInfo(EntInfoSaveReqVO updateReqVO) { - // 校验存在 - validateEntInfoExists(updateReqVO.getId()); - // 更新 - EntInfoDO updateObj = BeanUtils.toBean(updateReqVO, EntInfoDO.class); - entInfoMapper.updateById(updateObj); - } - - @Override - public void deleteEntInfo(Long id) { - // 校验存在 - validateEntInfoExists(id); - // 删除 - entInfoMapper.deleteById(id); - } - - @Override - public void deleteEntInfoListByIds(List ids) { - // 删除 - entInfoMapper.deleteByIds(ids); - } - - - private void validateEntInfoExists(Long id) { - if (entInfoMapper.selectById(id) == null) { - throw exception(ENT_NOT_EXISTS); - } - } - - @Override - public EntInfoDO getEntInfo(Long id) { - return entInfoMapper.selectById(id); - } - - @Override - public PageResult getEntInfoPage(EntInfoPageReqVO pageReqVO) { - return entInfoMapper.selectPage(pageReqVO); - } - -} \ No newline at end of file -- Gitee From d46eb9616c45115753bf85486f64fd0f74dc70ba Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 24 Oct 2025 17:25:25 +0800 Subject: [PATCH 07/50] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/custom/dal/dataobject/ent/EntInfoDO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java index f86eff7334..e5c434bf83 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -51,7 +51,7 @@ public class EntInfoDO extends BaseDO { */ private String fastgptId; /** - * 状态(-1 待上传/待办 0正常 1停用 2 完结) + * 状态(-1 待上传 0正常 1停用 2 待办 3 完结) */ private Integer status; -- Gitee From 3ea1f636346155f6c70471313d9593ca3929f4d6 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 27 Oct 2025 14:53:30 +0800 Subject: [PATCH 08/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/TenantSecurityWebFilter.java | 1 + .../admin/ent/vo/EntInfoPageReqVO.java | 23 +++ .../admin/ent/vo/EntInfoRespVO.java | 35 +++++ .../custom/dal/dataobject/ent/EntDO.java | 4 +- .../custom/dal/mysql/ent/EntInfoMapper.java | 3 +- .../CustomDataPermissionConfiguration.java | 27 ++++ .../module/custom/job/EntInfoProcessJob.java | 135 ++++++++++++++++++ .../controller/admin/user/UserController.http | 7 +- .../src/main/resources/application-local.yaml | 1 + 9 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java index f38e5dd98a..134de019b9 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java @@ -71,6 +71,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter { log.info("访问租户({}) ", user.getTenantId()); // 如果获取不到租户编号,则尝试使用登陆用户的租户编号 if (tenantId == null && user.getTenantId() != null) { + tenantId = user.getTenantId(); TenantContextHolder.setTenantId(user.getTenantId()); // 如果传递了租户编号,则进行比对租户编号,避免越权问题 } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) { diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java new file mode 100644 index 0000000000..b5388ca23f --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 企业分页 Request VO") +@Data +public class EntInfoPageReqVO extends PageParam { + + @Schema(description = "资料名称", example = "芋艿") + private String name; + + @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + private Integer source; + + @Schema(description = "资料内容说明/待办内容", example = "随便") + private String remark; + + @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java new file mode 100644 index 0000000000..5f15021646 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 企业信息 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EntInfoRespVO { + + @Schema(description = "资料名称", example = "芋艿") + @ExcelProperty("资料名称") + private String name; + + @Schema(description = "资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + @ExcelProperty("资料来源(0 文档,1 爬虫,2 录音, 3 待办事项)") + private Integer source; + + @Schema(description = "资料内容说明/待办内容", example = "随便") + @ExcelProperty("资料内容说明/待办内容") + private String remark; + + @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("状态(-1 待上传/待办 0正常 1停用 2 完结)") + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java index a534aa7a3a..383d80e7a1 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.custom.dal.dataobject.ent; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -19,7 +19,7 @@ import lombok.*; @Builder @NoArgsConstructor @AllArgsConstructor -public class EntDO extends BaseDO { +public class EntDO extends TenantBaseDO { /** * 主键 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java index 5dc40812f9..72afe35ae4 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.custom.dal.mysql.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.custom.controller.admin.entinfo.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import org.apache.ibatis.annotations.Mapper; @@ -35,7 +35,6 @@ public interface EntInfoMapper extends BaseMapperX { .likeIfPresent(EntInfoDO::getRemark, reqVO.getRemark()) .eqIfPresent(EntInfoDO::getSource, reqVO.getSource()) .eqIfPresent(EntInfoDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(EntInfoDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(EntInfoDO::getId)); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java new file mode 100644 index 0000000000..e8859e2239 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.custom.framework.datapermission.config; + +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * custom 模块的数据权限 Configuration + * + * @author Asy + */ +@Configuration(proxyBeanMethods = false) +public class CustomDataPermissionConfiguration { + + @Bean + public DeptDataPermissionRuleCustomizer customDeptDataPermissionRuleCustomizer() { + return rule -> { + // 企业数据权限配置 + // dept 字段:企业所属部门 + rule.addDeptColumn(EntDO.class, "dept_id"); + // user 字段:企业所属用户 + rule.addUserColumn(EntDO.class, "user_id"); + }; + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java new file mode 100644 index 0000000000..18545ffc30 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -0,0 +1,135 @@ +package cn.iocoder.yudao.module.custom.job; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class EntInfoProcessJob implements JobHandler { + + @Resource + private EntInfoMapper entInfoMapper; + + @Override + public String execute(String param) { + // 获取entInfo表中status=-1的记录 + EntInfoPageReqVO reqVO = new EntInfoPageReqVO(); + reqVO.setPageNo(1); + reqVO.setPageSize(1000); + reqVO.setStatus(-1); + PageResult pages = entInfoMapper.selectPage(reqVO); + + List pendingEntInfos = pages.getList(); + + for (EntInfoDO entInfo : pendingEntInfos) { + // 处理逻辑将在后续步骤中实现 + processEntInfo(entInfo); + } + + return "处理完成,共处理 " + pendingEntInfos.size() + " 条记录"; + } + + private void processEntInfo(EntInfoDO entInfo) { + // source=0时,读取文件内容 + if (entInfo.getSource() == 0 && StrUtil.isNotEmpty(entInfo.getPath())) { + try { + String content = extractFileContent(entInfo.getPath()); + // 更新entInfo的附件内容字段 + entInfo.setRemark(content); + entInfoMapper.updateById(entInfo); + + // 提交内容给fastgpt分析 + String fastgptId = submitToFastGPT(content); + if (StrUtil.isNotEmpty(fastgptId)) { + entInfo.setFastgptId(fastgptId); + // 更新状态为0(正常) + entInfo.setStatus(0); + entInfoMapper.updateById(entInfo); + + // 按段落总结并导入fastgpt知识库 + importToFastGPTKnowledgeBase(content, fastgptId); + } + } catch (IOException e) { + // 处理文件读取异常 + e.printStackTrace(); + } + } + } + + /** + * 提交内容给FastGPT分析 + * + * @param content 内容 + * @return fastgptId + */ + private String submitToFastGPT(String content) { + // 这里需要替换为实际的FastGPT API地址和参数 + String fastgptApiUrl = "http://localhost:3000/api/v1/chat"; + // 实际使用时需要构建正确的请求参数 + // String response = restTemplate.postForObject(fastgptApiUrl, request, String.class); + // 解析response获取fastgptId + // 暂时返回模拟的ID + return "fastgpt_" + System.currentTimeMillis(); + } + + /** + * 按段落总结并导入fastgpt知识库 + * + * @param content 内容 + * @param fastgptId fastgptId + */ + private void importToFastGPTKnowledgeBase(String content, String fastgptId) { + // 按段落分割内容(这里简单按换行符分割) + String[] paragraphs = content.split("\n"); + + // 这里需要替换为实际的FastGPT知识库API地址和参数 + String knowledgeBaseApiUrl = "http://localhost:3000/api/v1/knowledge-base"; + + for (int i = 0; i < paragraphs.length; i++) { + if (StrUtil.isNotEmpty(paragraphs[i])) { + // 实际使用时需要构建正确的请求参数 + // restTemplate.postForObject(knowledgeBaseApiUrl, request, String.class); + } + } + } + + /** + * 提取文件内容 + * + * @param filePath 文件路径 + * @return 文件内容 + * @throws IOException IO异常 + */ + private String extractFileContent(String filePath) throws IOException { + File file = new File(filePath); + String fileName = file.getName().toLowerCase(); + + if (fileName.endsWith(".doc")) { + // 处理Word 97-2003文档 (需要额外的库支持) + return "暂不支持DOC格式文件"; + } else if (fileName.endsWith(".docx")) { + // 处理Word 2007+文档 (需要额外的库支持) + return "暂不支持DOCX格式文件"; + } else if (fileName.endsWith(".txt")) { + // 处理文本文件 + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } else { + // 其他格式文件不处理内容 + return ""; + } + } +} \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.http b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.http index 0ca69150aa..4ceae71d3e 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.http +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.http @@ -8,4 +8,9 @@ tenant-id: {{adminTenantId}} GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} -visit-tenant-id: 122 \ No newline at end of file +visit-tenant-id: 122 + +### 请求 /simple-list 接口 +GET {{baseUrl}}/system/user/simple-list +Authorization: Bearer {{token}} +tenant-id: 1 \ No newline at end of file diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 48519c35d8..29dc198021 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -167,6 +167,7 @@ logging: name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 level: # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.custom.dal.mysql: debug cn.iocoder.yudao.module.bpm.dal.mysql: debug cn.iocoder.yudao.module.infra.dal.mysql: debug cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 配置 ApiErrorLogMapper 的日志级别为 info,避免和 GlobalExceptionHandler 重复打印 -- Gitee From 3acb2f4aad03c4348476b26d804e11efa59c8c22 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 27 Oct 2025 18:19:00 +0800 Subject: [PATCH 09/50] =?UTF-8?q?1=E3=80=81=E5=BC=80=E5=90=AF=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1=EF=BC=9B=202=E3=80=81=E5=AE=8C?= =?UTF-8?q?=E5=96=84fastgpt=E7=9A=84=E8=B0=83=E7=94=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-custom/pom.xml | 16 + .../api/fastgpt/FastGptAuthInterceptor.java | 33 ++ .../custom/api/fastgpt/FastGptDataApi.java | 122 +++++ .../api/fastgpt/vo/ChatCompletionRequest.java | 73 +++ .../fastgpt/vo/ChatCompletionResponse.java | 53 +++ .../fastgpt/vo/CollectionCreateRequest.java | 24 + .../fastgpt/vo/CollectionCreateResponse.java | 29 ++ .../api/fastgpt/vo/FastGptBaseResponse.java | 19 + .../api/fastgpt/vo/PushDataRequest.java | 72 +++ .../api/fastgpt/vo/PushDataResponse.java | 20 + .../custom/api/fastgpt/vo/SearchRequest.java | 28 ++ .../custom/api/fastgpt/vo/SearchResponse.java | 114 +++++ .../CustomDataPermissionConfiguration.java | 2 +- .../admin/ent/vo/EntInfoPageReqVO.java | 3 + .../custom/dal/dataobject/ent/EntDO.java | 4 + .../custom/dal/dataobject/ent/EntInfoDO.java | 7 +- .../custom/dal/mysql/ent/EntInfoMapper.java | 3 +- .../module/custom/job/EntInfoProcessJob.java | 83 +++- .../custom/service/ent/EntServiceImpl.java | 1 - .../service/fastgpt/FastGptService.java | 431 ++++++++++++++++++ .../src/main/resources/application-local.yaml | 2 +- 21 files changed, 1119 insertions(+), 20 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionResponse.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateRequest.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateResponse.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FastGptBaseResponse.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataResponse.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchRequest.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchResponse.java rename yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/{framework/datapermission => }/config/CustomDataPermissionConfiguration.java (92%) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java diff --git a/yudao-module-custom/pom.xml b/yudao-module-custom/pom.xml index 4bc7f12f96..f50b91b0cf 100644 --- a/yudao-module-custom/pom.xml +++ b/yudao-module-custom/pom.xml @@ -16,6 +16,10 @@ 自定义扩展模块,比如企业管理、fastgpt对接等 + + 2.9.0 + + cn.iocoder.boot @@ -70,6 +74,18 @@ spring-boot-starter-mail + + com.squareup.retrofit2 + retrofit + ${retrofit2.version} + + + + com.squareup.retrofit2 + converter-jackson + ${retrofit2.version} + + \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java new file mode 100644 index 0000000000..5f35d5469a --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + +/** + * FastGPT API认证拦截器 + * 自动在请求头中添加Authorization认证信息 + */ +public class FastGptAuthInterceptor implements Interceptor { + + private final String authToken; + + public FastGptAuthInterceptor(String authToken) { + this.authToken = authToken; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + + // 添加Authorization头 + Request authenticatedRequest = originalRequest.newBuilder() + .header("Authorization", authToken) + .header("Content-Type", "application/json") + .build(); + + return chain.proceed(authenticatedRequest); + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java new file mode 100644 index 0000000000..87e10e6670 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt; + +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.*; + + +/** + * FastGPT知识库API接口 + * 提供知识库的完整CRUD操作、数据管理、训练等功能 + * 有三类接口: + * 对知识库的操作:api/core/dataset + * 对知识库下面的集合的操作:api/core/dataset/collection + * 对知识库下面的集合下的数据的操作:api/core/dataset/data + */ +public interface FastGptDataApi { + + /** + * 为集合批量添加数据 + * 注意,每次最多推送200组数据。 + * + * @param request 推送数据请求参数 + * @return 推送结果 + */ + @POST("api/core/dataset/data/pushData") + Call> pushData( + @Body PushDataRequest request + ); + + /** + * 删除知识库集合下的数据 + * + * @param dataId 数据ID + * @return 删除结果 + */ + @DELETE("api/core/dataset/data/delete") + Call> deleteDatasetData( + @Query("id") String dataId + ); + + /*以上为对知识库下面的集合中的数据的操作,以下为对知识库下面的集合的操作*/ + + /** + * 创建空集合 + * + * @param request 创建集合请求参数 + * @return 创建的集合ID + */ + @POST("api/core/dataset/collection/create") + Call> createEmptyCollection( + @Body CollectionCreateRequest request + ); + + /** + * 创建文件集合 + * + * @param file 上传的文件 + * @param data 知识库相关信息(JSON格式) + * @return 创建结果 + */ + @Multipart + @POST("api/core/dataset/collection/create/localFile") + Call> createFileCollection( + @Part MultipartBody.Part file, + @Part("data") RequestBody data + ); + + /** + * 删除集合 + * + * @param collectionId 集合ID + * @return 删除结果 + */ + @DELETE("api/core/dataset/collection/delete") + Call> deleteCollection( + @Query("id") String collectionId + ); + + /** + * 搜索测试(无法根据文件夹搜索) + * + * @param request 搜索测试请求参数 + * @return 搜索结果 + */ + @POST("api/core/dataset/searchTest") + Call> search( + @Body SearchRequest request + ); + + /** + * 发送对话请求(非流式) + * + * @param request 聊天完成请求对象 + * @return 对话响应 + */ + @POST("api/v1/chat/completions") + @Headers({ + "Content-Type: application/json", + "Accept: application/json" + }) + Call sendChatRequest( + @Body ChatCompletionRequest request + ); + + /** + * 发送流式对话请求 + * + * @param request 聊天完成请求对象 + * @return 流式响应 + */ + @POST("api/v1/chat/completions") + @Headers({ + "Content-Type: application/json", + "Accept: text/event-stream" + }) + Call sendStreamChatRequest( + @Body ChatCompletionRequest request + ); +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java new file mode 100644 index 0000000000..c00196be15 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 聊天完成请求对象 + */ +@Data +public class ChatCompletionRequest { + + /** + * 对话ID + */ + private String chatId; + + /** + * 响应聊天项ID + */ + private String responseChatItemId; + + /** + * 是否流式响应 + */ + private Boolean stream; + + /** + * 是否返回详细信息 + */ + private Boolean detail; + + /** + * 聊天消息列表 + */ + private List messages; + + /** + * 变量参数 + */ + private Map variables; + + public ChatCompletionRequest() { + } + + /** + * 聊天消息对象 + */ + @Data + public static class ChatMessage { + + /** + * 消息角色,如:user, assistant, system + */ + private String role; + + /** + * 消息内容 + */ + private String content; + + public ChatMessage(String role, String content) { + this.role = role; + this.content = content; + } + + public ChatMessage(String content) { + this.role = "user"; + this.content = content; + } + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionResponse.java new file mode 100644 index 0000000000..3a2b0d35af --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionResponse.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ChatCompletionResponse { + private String id; + private String model; + private Usage usage; + private List choices; + + + // Usage 内部类 + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Usage { + private int prompt_tokens; + private int completion_tokens; + private int total_tokens; + + } + + // Choice 内部类 + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Choice { + private Message message; + private String finish_reason; + private int index; + + } + + // Message 内部类 + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Message { + private String role; + private String content; + + } + + public String getContent() { + if (choices != null && !choices.isEmpty() && + choices.get(0).getMessage() != null) { + return choices.get(0).getMessage().getContent(); + } + return null; + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateRequest.java new file mode 100644 index 0000000000..d54ea99a32 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateRequest.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +/** + * 创建集合请求 + */ +@Data +public class CollectionCreateRequest { + /** 知识库ID */ + private String datasetId; + /** 父级ID,不填则默认为根目录 */ + private String parentId; + /** 集合名称(必填) */ + private String name; + /** 类型:folder(文件夹)或virtual(虚拟集合/手动集合) */ + private String type = "virtual"; + /** 元数据(暂时没啥用) */ + private java.util.Map metadata; + + public String toFileJson() { + return "{\"datasetId\":\""+datasetId+"\",\"name\":\""+name+"\"}"; + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateResponse.java new file mode 100644 index 0000000000..686d01ee5d --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/CollectionCreateResponse.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +/** + * 集合创建响应 + */ +@Data +public class CollectionCreateResponse { + /** 新建的集合ID */ + private String collectionId; + /** 创建结果 */ + private CreateResults results; + + /** + * 创建结果 + */ + @Data + public static class CreateResults { + /** 插入的块数量 */ + private Integer insertLen; + /** 超出token的块 */ + private java.util.List overToken; + /** 重复的块 */ + private java.util.List repeat; + /** 错误的块 */ + private java.util.List error; + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FastGptBaseResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FastGptBaseResponse.java new file mode 100644 index 0000000000..a696f23369 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FastGptBaseResponse.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +/** + * FastGPT API统一响应格式 + * @param 数据类型 + */ +@Data +public class FastGptBaseResponse { + /** 状态码,0表示成功 */ + private int code; + /** 状态文本 */ + private String statusText; + /** 响应消息 */ + private String message; + /** 响应数据 */ + private T data; +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java new file mode 100644 index 0000000000..1e73fe6954 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 批量推送数据请求 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PushDataRequest { + /** 集合ID(必填) */ + private String collectionId; + /** 训练模式(必填)chunk */ + private String trainingType = "chunk"; + /** 自定义QA拆分提示词,需严格按照模板,建议不要传入(选填) */ + private String prompt; + /** 可以参考创建训练订单获取该值(选填) */ + private String billId; + /** 数据列表(必填) */ + private List data; + + /** + * 数据项 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class DataItem { + /** 主要数据(必填) */ + private String q; + /** 辅助数据(可选) */ + private String a; + /** 自定义索引(可选) */ + private List indexes; + + /** + * 索引项 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class IndexItem { + /** 索引类型:default-默认索引; custom-自定义索引; summary-总结索引; question-问题索引; image-图片索引 */ + private String type; + /** 关联的向量ID */ + private String dataId; + /** 文本内容(必填) */ + private String text; + } + } + + // 新增一条data + public void addData(String q, String a) { + if (this.data == null) { + this.data = new ArrayList<>(); + } + this.data.add(DataItem.builder() + .q(q) + .a(a) + .build()); + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataResponse.java new file mode 100644 index 0000000000..16c5c26401 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataResponse.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 批量推送数据响应 + */ +@Data +public class PushDataResponse { + /** 最终插入成功的数量 */ + private Integer insertLen; + /** 超出token的数据项 */ + private List overToken; + /** 重复的数据项 */ + private List repeat; + /** 其他错误的数据项 */ + private List error; +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchRequest.java new file mode 100644 index 0000000000..2b91111b7c --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchRequest.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +/** + * 搜索测试请求 + */ +@Data +public class SearchRequest { + /** 知识库ID */ + private String datasetId; + /** 需要测试的文本 */ + private String text; + /** 最大tokens数量 */ + private Integer limit = 5000; + /** 最低相关度(0~1,可选) */ + private Double similarity = 0.6; + /** 搜索模式:embedding | fullTextRecall | mixedRecall */ + private String searchMode = "embedding"; + /** 使用重排 */ + private Boolean usingReRank = false; + /** 使用问题优化 */ + private Boolean datasetSearchUsingExtensionQuery = false; + /** 问题优化模型 */ + private String datasetSearchExtensionModel = ""; + /** 问题优化背景描述 */ + private String datasetSearchExtensionBg = ""; +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchResponse.java new file mode 100644 index 0000000000..2e90b0b82c --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/SearchResponse.java @@ -0,0 +1,114 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.Data; + +import java.util.List; + +/** + * 搜索测试响应 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SearchResponse { + + /** 搜索结果列表 */ + private List list; + /** 搜索模式 */ + private String searchMode; + /** 时长 */ + private String duration; + /** 相似的 */ + private Double similarity; + /** 限制 */ + private Integer limit; + /** 是否重排 */ + private Boolean usingReRank; + /** 是否重排过滤 */ + private Boolean usingSimilarityFilter; + + + /** + * 向量模型 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SearchItem { + /** 数据ID */ + private String id; + /** 问题文本 */ + private String q; + /** 答案文本 */ + private String a; + /** 知识库ID */ + private String datasetId; + /** 集合ID */ + private String collectionId; + /** 源名称 */ + private String sourceName; + /** 最终得分 */ + private Double finalScore=0.0; + /** 分数 */ + private List score; + + /** + * 计算最终分数 + */ + private void calculateFinalScore(String searchMode) { + if (score != null && !score.isEmpty() && searchMode != null) { + for (SearchItemScore item : score) { + if (searchMode.equals(item.getType())) { + this.finalScore = item.getValue(); + break; + } + } + score = null; + } + } + } + + + /** + * 向量模型 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SearchItemScore { + /** 类型 */ + private String type; + /** 分数 */ + private Double value; + /** 排序 */ + private Integer index; + } + + /** + * 当设置score时自动计算最终分数 + */ + public void setList(List list) { + this.list = list; + setItemScore(); + } + + /** + * 当设置searchMode时自动计算最终分数 + */ + @JsonSetter + public void setSearchMode(String searchMode) { + this.searchMode = searchMode; + setItemScore(); + } + + public void setItemScore(){ + if (this.list != null && this.searchMode != null) { + for (SearchItem item : this.list) { + // 或者直接计算分数 + item.calculateFinalScore(this.searchMode); + } + + // 根据最终分数排序 + this.list.sort((o1, o2) -> o2.getFinalScore().compareTo(o1.getFinalScore())); + } + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java similarity index 92% rename from yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java rename to yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java index e8859e2239..c41ab52fb7 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/framework/datapermission/config/CustomDataPermissionConfiguration.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.custom.framework.datapermission.config; +package cn.iocoder.yudao.module.custom.config; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java index b5388ca23f..53915ba90d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java @@ -20,4 +20,7 @@ public class EntInfoPageReqVO extends PageParam { @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") private Integer status; + @Schema(description = "租户") + private Long tenantId; + } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java index 383d80e7a1..987a63bfa0 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java @@ -50,6 +50,10 @@ public class EntDO extends TenantBaseDO { * 状态(0正常 1停用) */ private Integer status; + /** + * 上传fastgpt后的Id + */ + private String fastgptId; } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java index e5c434bf83..408f576baa 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.custom.dal.dataobject.ent; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -19,7 +20,7 @@ import lombok.*; @Builder @NoArgsConstructor @AllArgsConstructor -public class EntInfoDO extends BaseDO { +public class EntInfoDO extends TenantBaseDO { /** * 主键 @@ -46,10 +47,6 @@ public class EntInfoDO extends BaseDO { * 所属企业Id */ private Long entId; - /** - * 上传fastgpt后的Id - */ - private String fastgptId; /** * 状态(-1 待上传 0正常 1停用 2 待办 3 完结) */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java index 72afe35ae4..058c39f76d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java @@ -35,6 +35,7 @@ public interface EntInfoMapper extends BaseMapperX { .likeIfPresent(EntInfoDO::getRemark, reqVO.getRemark()) .eqIfPresent(EntInfoDO::getSource, reqVO.getSource()) .eqIfPresent(EntInfoDO::getStatus, reqVO.getStatus()) - .orderByDesc(EntInfoDO::getId)); + .eqIfPresent(EntInfoDO::getTenantId, reqVO.getTenantId()) + .orderByAsc(EntInfoDO::getUpdateTime)); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 18545ffc30..01d9349665 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -1,11 +1,18 @@ package cn.iocoder.yudao.module.custom.job; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; +import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -17,35 +24,88 @@ import java.util.List; import java.util.stream.Collectors; @Component +@Slf4j public class EntInfoProcessJob implements JobHandler { + @Resource + private EntMapper entMapper; + @Resource private EntInfoMapper entInfoMapper; + @Resource + private FastGptService fastGptService; + @Override + @TenantIgnore public String execute(String param) { - // 获取entInfo表中status=-1的记录 - EntInfoPageReqVO reqVO = new EntInfoPageReqVO(); - reqVO.setPageNo(1); - reqVO.setPageSize(1000); - reqVO.setStatus(-1); - PageResult pages = entInfoMapper.selectPage(reqVO); + // 解析参数,默认分页大小为1000 + int pageSize = 1000; + String[] tenants = null; + if (StrUtil.isNotEmpty(param)) { + try { + if (param.startsWith("{")) { + JSONObject json = JSONUtil.parseObj(param); + pageSize = json.getInt("pageSize", 1000); + String tns = json.getStr("tenants", ""); + if (StrUtil.isNotEmpty(tns)) { + tenants = tns.split(","); + } + } + } catch (NumberFormatException e) { + // 如果参数不是数字,使用默认值 + log.error("参数解析错误:{}", param); + } + } - List pendingEntInfos = pages.getList(); + if (tenants == null || tenants.length == 0) { + log.info("[processEntInfo][tenants参数为空]"); + return "处理异常:请前往基础设施 → 定时任务配置参数"; + } - for (EntInfoDO entInfo : pendingEntInfos) { - // 处理逻辑将在后续步骤中实现 - processEntInfo(entInfo); + // 获取entInfo表中status=-1的记录 + for (String t : tenants) { + EntInfoPageReqVO reqVO = new EntInfoPageReqVO(); + reqVO.setPageNo(1); + reqVO.setPageSize(pageSize); + reqVO.setStatus(-1); + reqVO.setTenantId(Long.valueOf(t)); + PageResult pages = entInfoMapper.selectPage(reqVO); + + List pendingEntInfos = pages.getList(); + + for (EntInfoDO entInfo : pendingEntInfos) { + // 处理逻辑将在后续步骤中实现 + processEntInfo(entInfo); + } + + return "处理完成,共处理 " + pendingEntInfos.size() + " 条记录(分页大小:" + pageSize + ")"; } - return "处理完成,共处理 " + pendingEntInfos.size() + " 条记录"; + return "处理异常"; } private void processEntInfo(EntInfoDO entInfo) { // source=0时,读取文件内容 if (entInfo.getSource() == 0 && StrUtil.isNotEmpty(entInfo.getPath())) { try { + String fastgptId; + // 找到对应的企业,看是否已经创了集合 + EntDO entDO = entMapper.selectOne(EntDO::getId, entInfo.getEntId()); + if (entDO.getFastgptId() == null || entDO.getFastgptId().isEmpty()) { + fastgptId = fastGptService.syncFaqCat(entDO.getTenantId().toString(), entDO.getName()); + if (!fastgptId.isEmpty()) { + EntDO upEnt = new EntDO(); + upEnt.setId(entDO.getId()); + upEnt.setFastgptId(fastgptId); + entMapper.updateById(upEnt); + } + } else fastgptId = entDO.getFastgptId(); + String content = extractFileContent(entInfo.getPath()); + fastGptService.getJsonArrRequest(fastgptId, content, "knowledge_base", entInfo.getId().toString(), entDO.getTenantId().toString()).subscribe(response -> { + + }) // 更新entInfo的附件内容字段 entInfo.setRemark(content); entInfoMapper.updateById(entInfo); @@ -75,6 +135,7 @@ public class EntInfoProcessJob implements JobHandler { * @return fastgptId */ private String submitToFastGPT(String content) { + fastGptService.fa // 这里需要替换为实际的FastGPT API地址和参数 String fastgptApiUrl = "http://localhost:3000/api/v1/chat"; // 实际使用时需要构建正确的请求参数 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 56787084a7..0e26adc24e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -15,7 +15,6 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java new file mode 100644 index 0000000000..005492cab2 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -0,0 +1,431 @@ +package cn.iocoder.yudao.module.custom.service.fastgpt; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; +import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; +import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptAuthInterceptor; +import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptDataApi; +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import javax.annotation.Resource; +import java.io.BufferedReader; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class FastGptService { + + @Resource + private DictDataCommonApi dictDataCommonApi; + + private static final Map apiCache = new ConcurrentHashMap<>(); + + /** + * 同步FAQ数据详情,创建场景(只有)的时候批量添加,文件夹id为数据集,faq为数据集下的数据; + * 后面对faq的单个新增、修改、删除,再使用单独处理的API + * + * @param tenantId 租户ID + * @param datas 数据列表 + */ + public void syncFaq(String tenantId, String collectionId, List datas) { + + try { + log.info(" 添加FAQ数据..."); + PushDataRequest pushRequest = new PushDataRequest(); + pushRequest.setCollectionId(collectionId); + pushRequest.setData(datas); + + Response> response = getApiClient(tenantId).pushData(pushRequest).execute(); + if (response.code() == 200 && response.body() != null) { + PushDataResponse gptId = response.body().getData(); + log.info("✓✓✓ 添加FAQ数据调用成功,len:{}", gptId.getInsertLen()); + } else { + String errorMessage = response.message(); + try (ResponseBody errorBody = response.errorBody()) { + if (errorBody != null) + errorMessage = errorBody.string(); + } catch (Exception e) { + log.warn("读取错误信息失败", e); + } + log.error("XXX 接口调用失败:{}>>>{}", response.message(), errorMessage); + } + } catch (Exception e) { + log.error("FAQ数据同步到fastgpt出错:", e); + } + } + + /** + * 同步知识点作为数据集 + * + * @param tenantId 租户ID + * @param name 数据集名称 + */ + public String syncFaqCat(String tenantId, String name) { + String collectionId = ""; + + try { + String sceneGptId = getDictToken(tenantId, "ent_knowledge"); + // 将faq的目录作为数据集 + log.info(" 创建空数据集..."); + CollectionCreateRequest createRequest = new CollectionCreateRequest(); + createRequest.setName(name); + createRequest.setDatasetId(sceneGptId); + + Response> response = getApiClient(tenantId).createEmptyCollection(createRequest).execute(); + if (response.code() == 200 && response.body() != null) { + String gptId = response.body().getData(); + log.info("✓✓✓ 创建数据集成功,len:{}", gptId); + return gptId; + } else { + String errorMessage = response.message(); + try (ResponseBody errorBody = response.errorBody()) { + if (errorBody != null) + errorMessage = errorBody.string(); + } catch (Exception e) { + log.warn("读取错误信息失败", e); + } + log.error("XXX 创建空数据集失败:{}>>>{}", response.message(), errorMessage); + } + } catch (Exception e) { + log.error("FAQ数据同步到fastgpt出错:", e); + } + + return collectionId; + } + + /** + * 知识检索内容(异步版本) + * + * @param searchText 搜索文本 + * @param tenantId 租户ID + * @param sceneId 场景id + * @return 响应式结果 + */ + public Flux> knowledgeSearchAsync(String searchText, String tenantId, String sceneId) { + return Flux.create(sink -> { + try { + SearchRequest request = new SearchRequest(); + request.setDatasetId(sceneId); + request.setText(searchText); + Response> response = getApiClient(tenantId).search(request).execute(); + + if (response.isSuccessful() && response.body() != null) { + SearchResponse rsp = response.body().getData(); + List results = (rsp != null) ? rsp.getList() : Collections.emptyList(); + sink.next(results); + sink.complete(); + } else { + sink.error(new RuntimeException("知识库查询失败: " + response.message())); + } + } catch (Exception e) { + log.error("知识库查询异常:", e); + sink.error(new RuntimeException("知识库查询失败", e)); + } + }); + } + + + /** + * 获取API客户端实例 + * + * @return FastGptDataApi实例 + */ + private FastGptDataApi getApiClient(String tenantId) { + return apiCache.computeIfAbsent(tenantId, key -> createRetrofitClient(tenantId)); + } + + /** + * 创建Retrofit客户端 + * + * @return FastGptDataApi实例 + */ + private FastGptDataApi createRetrofitClient(String tenantId) { + return createTokenClient("apiToken", tenantId); + } + + /** + * 创建临时API客户端 + * + * @param category 认证token的类型 + * @return FastGptDataApi实例 + */ + private FastGptDataApi createTokenClient(String category, String tenantId) { + + String url = getDictUrl(tenantId); + String token = getDictToken(tenantId, category); + if (StrUtil.isEmpty(url) || StrUtil.isEmpty(token)) { + throw new IllegalArgumentException("fastgpt的URL和token不能为空"); + } + + int readTimeout = 30; + int connectTimeout = 30; + OkHttpClient httpClient = new OkHttpClient.Builder() + .addInterceptor(new FastGptAuthInterceptor(token)) + .connectTimeout(connectTimeout, TimeUnit.SECONDS) + .readTimeout(readTimeout, TimeUnit.SECONDS) + .build(); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(url) + .client(httpClient) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + return retrofit.create(FastGptDataApi.class); + } + + + private FastGptDataApi getChatClient(String category, String tenantId) { + return apiCache.computeIfAbsent(tenantId + "@" + category, key -> createTokenClient(category, tenantId)); + } + + private Map getDictMap() { + List dictGpt = dictDataCommonApi.getDictDataList("fastgpt"); + return dictGpt.stream().collect(Collectors.toMap(DictDataRespDTO::getLabel, DictDataRespDTO::getValue)); + } + + private String getDictUrl(String tenantId) { + Map dictMap = getDictMap(); + return dictMap.get(tenantId + "_url") == null ? dictMap.get("url") == null ? "" : dictMap.get("url") : dictMap.get(tenantId + "_url"); + } + + private String getDictToken(String tenantId, String category) { + Map dictMap = getDictMap(); + return dictMap.get(tenantId + "_" + category) == null ? dictMap.get(category) == null ? "" : dictMap.get(category) : dictMap.get(tenantId + "_" + category); + } + + /** + * 发送对话请求 + * + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 认证类别 + * @param dataId 数据ID + * @param tenantId 租户ID + * @return 流式响应 + */ + public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId) { + + if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); + + // 创建聊天请求 + ChatCompletionRequest request = new ChatCompletionRequest(); + request.setChatId(chatId); + request.setResponseChatItemId(dataId); + request.setStream(true); + request.setDetail(false); + request.setVariables(new HashMap<>()); + + // 创建消息 + ChatCompletionRequest.ChatMessage message = new ChatCompletionRequest.ChatMessage(content); + request.setMessages(Collections.singletonList(message)); + + // 使用 Retrofit 异步调用 + return Flux.create(sink -> + getChatClient(category, tenantId).sendStreamChatRequest(request).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + // 处理成功的响应 + // 处理流式响应 + ResponseBody responseBody = response.body(); + if (responseBody != null) { + log.info("开始处理流式响应:"); + + // 创建ObjectMapper用于解析JSON + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + try { + + // 逐行读取响应内容 + BufferedReader reader = new BufferedReader(responseBody.charStream()); + String line; + + while ((line = reader.readLine()) != null && !sink.isCancelled()) { + // 处理SSE格式的数据 + if (!line.trim().isEmpty() && line.startsWith("data: ")) { + String data = line.substring(6); // 移除"data: "前缀 + + // 检查是否为结束标记 + if ("[DONE]".equals(data.trim())) { + log.info("流式传输结束"); + break; + } + + try { + // 直接通过JSON路径提取content内容 + JsonNode jsonNode = objectMapper.readTree(data); + JsonNode choicesNode = jsonNode.get("choices"); + + if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { + JsonNode firstChoice = choicesNode.get(0); + JsonNode deltaNode = firstChoice.get("delta"); + + if (deltaNode != null) { + JsonNode contentNode = deltaNode.get("content"); + if (contentNode != null && contentNode.isTextual()) { + String text = contentNode.asText(); + if (!text.isEmpty()) { + log.info("接收到的内容片段: {}", text); + sink.next(text); // 通过flux发送内容片段 + } + } + } + } + } catch (Exception e) { + log.warn("解析JSON数据失败: {}", data, e); + } + } + } + } catch (Exception e) { + log.warn("处理流式响应失败: {}", e.getMessage(), e); + } + + if (!sink.isCancelled()) { + sink.complete(); // 完成flux流 + } + } + } else { + // 处理错误响应 + sink.error(new RuntimeException("流式聊天调用失败: " + response.message())); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 处理网络故障等异常 + sink.error(t); + } + }) + ); + } + + + /** + * 发送对话请求 + * + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 聊天类型 + * @param dataId 数据Id + * @param tenantId 租户Id + * @return 流式响应 + */ + public Mono getJsonRequest(String chatId, String content, String category, String dataId, String tenantId) { + return sendChatRequest(chatId, content, category, dataId, tenantId, "json"); + } + + + /** + * 发送对话请求 + * + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 聊天类型 + * @param dataId 数据Id + * @param tenantId 租户Id + * @return 流式响应 + */ + public Mono getJsonArrRequest(String chatId, String content, String category, String dataId, String tenantId) { + return sendChatRequest(chatId, content, category, dataId, tenantId, "jsonArr"); + } + + + /** + * 发送对话请求 + * + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 聊天类型 + * @param dataId 数据Id + * @param tenantId 租户Id + * @return 流式响应 + */ + public Mono getRequest(String chatId, String content, String category, String dataId, String tenantId) { + return sendChatRequest(chatId, content, category, dataId, tenantId, "common"); + } + + + /** + * 分析对话请求 + * + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 聊天类型 + * @param dataId 数据Id + * @param tenantId 租户Id + * @param resultType 返回结果类型:json、jsonArr、common + * @return 流式响应 + */ + private Mono sendChatRequest(String chatId, String content, String category, String dataId, String tenantId, String resultType) { + + if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); + + // 创建聊天请求 + ChatCompletionRequest request = new ChatCompletionRequest(); + request.setChatId(chatId); + request.setResponseChatItemId(dataId); + request.setStream(false); + request.setDetail(false); + request.setVariables(new HashMap<>()); + + // 创建消息 + ChatCompletionRequest.ChatMessage message = new ChatCompletionRequest.ChatMessage(content); + request.setMessages(Collections.singletonList(message)); + + return Mono.create(sink -> + getChatClient(category, tenantId).sendChatRequest(request) + .enqueue(new retrofit2.Callback() { + @Override + public void onResponse(Call call, + Response response) { + if (response.isSuccessful() && response.body() != null) { + // 处理成功响应 + log.info("\n ✓ 非流式聊天调用成功"); + String result = response.body().getContent(); + if ("json".equals(resultType) || "jsonArr".equals(resultType)) { + String regex = "```json\\s*([\\s\\S]*?)\\s*```"; + Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); + Matcher matcher = pattern.matcher(result); + + if (matcher.find()) + // group(1) 获取第一个捕获组的内容,即括号内的部分 + result = matcher.group(1).trim(); + else + result = "json".equals(resultType) ? "{}" : "[]"; + } + sink.success(result); + } else { + sink.error(new Exception("请求失败")); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + log.error("发送非流式聊天请求失败: {}", t.getMessage(), t); + sink.error(t); + } + }) + ); + } + +} \ No newline at end of file diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 29dc198021..80d0f74a44 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -7,7 +7,7 @@ spring: # noinspection SpringBootApplicationYaml exclude: - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 - - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置 +# - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 已注释,开启定时任务 - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建 - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建 # 数据源配置项 -- Gitee From 6191c51dfd56ed64336d79ce5748c3048f4e13f3 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 28 Oct 2025 15:11:31 +0800 Subject: [PATCH 10/50] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=9A=84=E5=A4=A7=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/custom/job/EntInfoProcessJob.java | 91 ++++++++----------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 01d9349665..6256478f08 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -6,6 +6,7 @@ import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.PushDataRequest; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; @@ -102,66 +103,46 @@ public class EntInfoProcessJob implements JobHandler { } } else fastgptId = entDO.getFastgptId(); - String content = extractFileContent(entInfo.getPath()); - fastGptService.getJsonArrRequest(fastgptId, content, "knowledge_base", entInfo.getId().toString(), entDO.getTenantId().toString()).subscribe(response -> { + String content = extractFileContent(entInfo.getPath()), time = String.valueOf(System.currentTimeMillis()); + // 将内容拆分成FAQ + fastGptService.getJsonRequest(time, content, "document_analysis", entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { + /*{ + "summary": "", + "list": [{ + "q": "", + "a": "" + }] + }*/ + PushDataRequest request = new PushDataRequest(); + JSONObject json = JSONUtil.parseObj(response); + List list = json.getJSONArray("list").stream() + .map(item -> JSONUtil.parseObj(item.toString())) + .collect(Collectors.toList()); + list.forEach(item -> { + request.addData(item.getStr("q"), item.getStr("a")); + }); + + // 提交到知识库 + boolean isSuccess = false; + if (!request.getData().isEmpty()) { + isSuccess = true; + fastGptService.syncFaq(entDO.getTenantId().toString(), fastgptId, request.getData()); + } - }) - // 更新entInfo的附件内容字段 - entInfo.setRemark(content); - entInfoMapper.updateById(entInfo); + // 更新entInfo的附件内容字段 + if (json.getStr("summary") != null && !json.getStr("summary").isEmpty() && isSuccess) { + EntInfoDO infoDO = new EntInfoDO(); + infoDO.setId(entInfo.getId()); + infoDO.setRemark(json.getStr("summary")); + infoDO.setStatus(0); + entInfoMapper.updateById(infoDO); + } - // 提交内容给fastgpt分析 - String fastgptId = submitToFastGPT(content); - if (StrUtil.isNotEmpty(fastgptId)) { - entInfo.setFastgptId(fastgptId); - // 更新状态为0(正常) - entInfo.setStatus(0); - entInfoMapper.updateById(entInfo); + }); - // 按段落总结并导入fastgpt知识库 - importToFastGPTKnowledgeBase(content, fastgptId); - } } catch (IOException e) { // 处理文件读取异常 - e.printStackTrace(); - } - } - } - - /** - * 提交内容给FastGPT分析 - * - * @param content 内容 - * @return fastgptId - */ - private String submitToFastGPT(String content) { - fastGptService.fa - // 这里需要替换为实际的FastGPT API地址和参数 - String fastgptApiUrl = "http://localhost:3000/api/v1/chat"; - // 实际使用时需要构建正确的请求参数 - // String response = restTemplate.postForObject(fastgptApiUrl, request, String.class); - // 解析response获取fastgptId - // 暂时返回模拟的ID - return "fastgpt_" + System.currentTimeMillis(); - } - - /** - * 按段落总结并导入fastgpt知识库 - * - * @param content 内容 - * @param fastgptId fastgptId - */ - private void importToFastGPTKnowledgeBase(String content, String fastgptId) { - // 按段落分割内容(这里简单按换行符分割) - String[] paragraphs = content.split("\n"); - - // 这里需要替换为实际的FastGPT知识库API地址和参数 - String knowledgeBaseApiUrl = "http://localhost:3000/api/v1/knowledge-base"; - - for (int i = 0; i < paragraphs.length; i++) { - if (StrUtil.isNotEmpty(paragraphs[i])) { - // 实际使用时需要构建正确的请求参数 - // restTemplate.postForObject(knowledgeBaseApiUrl, request, String.class); + log.error("文件读取异常:{}", e.getMessage(), e); } } } -- Gitee From dd03bd14bb0f91d792d8daf3cdd218822d9914ef Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 28 Oct 2025 17:56:35 +0800 Subject: [PATCH 11/50] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E6=96=87=E6=A1=A3=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-custom/pom.xml | 6 + .../module/custom/job/EntInfoProcessJob.java | 124 +++++++++++++++--- 2 files changed, 113 insertions(+), 17 deletions(-) diff --git a/yudao-module-custom/pom.xml b/yudao-module-custom/pom.xml index f50b91b0cf..613b587fed 100644 --- a/yudao-module-custom/pom.xml +++ b/yudao-module-custom/pom.xml @@ -86,6 +86,12 @@ ${retrofit2.version} + + org.apache.poi + poi-scratchpad + 5.2.3 + + \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 6256478f08..0ad6826d76 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.custom.job; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; @@ -14,13 +15,18 @@ import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; +import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; import java.util.List; import java.util.stream.Collectors; @@ -28,6 +34,16 @@ import java.util.stream.Collectors; @Slf4j public class EntInfoProcessJob implements JobHandler { + /** + * 最大文件大小限制(10MB) + */ + private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; + + /** + * 最大内容长度限制 + */ + private static final int MAX_CONTENT_LENGTH = 100000; + @Resource private EntMapper entMapper; @@ -103,7 +119,8 @@ public class EntInfoProcessJob implements JobHandler { } } else fastgptId = entDO.getFastgptId(); - String content = extractFileContent(entInfo.getPath()), time = String.valueOf(System.currentTimeMillis()); + String content = extractFileContent(entInfo.getPath()); + String time = String.valueOf(System.currentTimeMillis()); // 将内容拆分成FAQ fastGptService.getJsonRequest(time, content, "document_analysis", entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { /*{ @@ -148,7 +165,7 @@ public class EntInfoProcessJob implements JobHandler { } /** - * 提取文件内容 + * 提取文件内容,支持DOC和DOCX格式 * * @param filePath 文件路径 * @return 文件内容 @@ -156,22 +173,95 @@ public class EntInfoProcessJob implements JobHandler { */ private String extractFileContent(String filePath) throws IOException { File file = new File(filePath); + + // 检查文件是否存在 + if (!file.exists()) { + log.warn("文件不存在:{}", filePath); + return "文件不存在:" + filePath; + } + + // 检查文件是否可读 + if (!file.canRead()) { + log.warn("文件无法读取:{}", filePath); + return "文件无法读取:" + filePath; + } + + // 检查文件大小 + if (file.length() > MAX_FILE_SIZE) { + log.warn("文件过大,跳过处理:{},大小:{}MB", filePath, file.length() / (1024 * 1024)); + return "文件过大,超过10MB限制"; + } + String fileName = file.getName().toLowerCase(); - if (fileName.endsWith(".doc")) { - // 处理Word 97-2003文档 (需要额外的库支持) - return "暂不支持DOC格式文件"; - } else if (fileName.endsWith(".docx")) { - // 处理Word 2007+文档 (需要额外的库支持) - return "暂不支持DOCX格式文件"; - } else if (fileName.endsWith(".txt")) { - // 处理文本文件 - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - return reader.lines().collect(Collectors.joining("\n")); + try { + if (fileName.endsWith(".docx")) { + // 处理Word 2007+文档 + return extractDocxContent(file); + } else if (fileName.endsWith(".doc")) { + // 处理Word 97-2003文档 + return extractDocContent(file); + } else if (fileName.endsWith(".txt")) { + // 处理文本文件,使用项目工具类优化 + return FileUtil.readUtf8String(file); + } else if (fileName.endsWith(".wav") || fileName.endsWith(".mp3")) { + // 处理录音文件,使用funasr + return "请配置funasr"; + } else { + // 其他格式文件返回提示 + return "不支持的文件格式:" + fileName; } - } else { - // 其他格式文件不处理内容 - return ""; + } catch (Exception e) { + log.error("提取文件内容失败:{}", filePath, e); + return "文件解析失败:" + e.getMessage(); + } + } + + /** + * 提取DOCX格式文件内容 + * + * @param file DOCX文件 + * @return 文件内容 + * @throws IOException IO异常 + */ + private String extractDocxContent(File file) throws IOException { + try (InputStream inputStream = Files.newInputStream(file.toPath())) { + XWPFDocument document = new XWPFDocument(inputStream); + XWPFWordExtractor extractor = new XWPFWordExtractor(document); + String content = extractor.getText().trim(); + extractor.close(); + + /*// 限制内容长度 + if (content.length() > MAX_CONTENT_LENGTH) { + log.info("DOCX文件内容过长,截取前{}字符", MAX_CONTENT_LENGTH); + content = content.substring(0, MAX_CONTENT_LENGTH); + }*/ + + return content; + } + } + + /** + * 提取DOC格式文件内容 + * + * @param file DOC文件 + * @return 文件内容 + * @throws IOException IO异常 + */ + private String extractDocContent(File file) throws IOException { + try (InputStream inputStream = new FileInputStream(file)) { + HWPFDocument document = new HWPFDocument(inputStream); + WordExtractor extractor = new WordExtractor(document); + String content = extractor.getText().trim(); + extractor.close(); + + // 限制内容长度 + /*if (content.length() > MAX_CONTENT_LENGTH) { + log.info("DOC文件内容过长,截取前{}字符", MAX_CONTENT_LENGTH); + content = content.substring(0, MAX_CONTENT_LENGTH); + }*/ + + return content; } } } \ No newline at end of file -- Gitee From 0e3b5e431a621dbb77039dabf35475cc74b05ef3 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 28 Oct 2025 18:10:32 +0800 Subject: [PATCH 12/50] =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E4=BA=8B=E9=A1=B9=E7=9B=B4=E6=8E=A5=E5=85=A5=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/custom/job/EntInfoProcessJob.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 0ad6826d76..1100b7a08b 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import cn.idev.excel.util.DateUtils; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; @@ -27,6 +28,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; @@ -125,6 +127,7 @@ public class EntInfoProcessJob implements JobHandler { fastGptService.getJsonRequest(time, content, "document_analysis", entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { /*{ "summary": "", + "todolist": "", "list": [{ "q": "", "a": "" @@ -155,6 +158,17 @@ public class EntInfoProcessJob implements JobHandler { entInfoMapper.updateById(infoDO); } + // 提取了待办事项直接入库 + if (json.getStr("todolist") != null && !json.getStr("todolist").isEmpty()) { + EntInfoDO infoDO = new EntInfoDO(); + infoDO.setSource(3); + infoDO.setStatus(2); + infoDO.setEntId(entDO.getId()); + infoDO.setName(entDO.getName() + "-待办" + DateUtils.format(new Date(), "yyyyMMdd")); + infoDO.setRemark(json.getStr("todolist")); + entInfoMapper.insert(infoDO); + } + }); } catch (IOException e) { -- Gitee From 31e909b67a04de8f9940897b02d929b83af3ca90 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 29 Oct 2025 11:02:01 +0800 Subject: [PATCH 13/50] =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=97=AE=E7=AD=94=E5=8F=8A=E9=97=AE=E7=AD=94=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/controller/admin/api/ChatController.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java new file mode 100644 index 0000000000..f50dffb1d5 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "前端接口 - 对话") +@RestController +@RequestMapping("/custom/chat") +@Validated +public class ChatController { +} -- Gitee From a65f192ee8d1726e9615a1e4a455b1060dc883a8 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 29 Oct 2025 18:11:16 +0800 Subject: [PATCH 14/50] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E8=AE=B0=E5=BD=95-=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-custom/README_API.md | 198 ++++++++++++++++++ .../controller/admin/api/ChatController.http | 63 ++++++ .../controller/admin/api/ChatController.java | 120 ++++++++++- .../admin/api/vo/ChatRecordPageReqVO.java | 29 +++ .../controller/admin/api/vo/ChatRecordVO.java | 74 +++++++ .../dal/dataobject/chat/ChatRecordDO.java | 77 +++++++ .../dal/mysql/chat/ChatRecordMapper.java | 53 +++++ .../custom/service/chat/ChatService.java | 88 ++++++++ .../custom/service/chat/ChatServiceImpl.java | 155 ++++++++++++++ .../main/resources/sql/custom_chat_record.sql | 26 +++ 10 files changed, 881 insertions(+), 2 deletions(-) create mode 100644 yudao-module-custom/README_API.md create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java create mode 100644 yudao-module-custom/src/main/resources/sql/custom_chat_record.sql diff --git a/yudao-module-custom/README_API.md b/yudao-module-custom/README_API.md new file mode 100644 index 0000000000..48822a08cf --- /dev/null +++ b/yudao-module-custom/README_API.md @@ -0,0 +1,198 @@ +# 对话模块 API 文档 + +## 1. 流式对话接口 + +### 请求信息 +- **URL**: `/custom/chat/stream` +- **方法**: `POST` +- **Content-Type**: `application/json` + +### 请求头 +- `tenant-id`: 租户ID +- `login-user-id`: 用户ID + +### 请求参数 +```json +{ + "chatId": "可选,如果为空会自动生成", + "stream": true, + "detail": false, + "variables": {}, + "messages": [ + { + "role": "user", + "content": "你好,请介绍一下你自己" + } + ] +} +``` + +### 请求参数说明 +- `chatId`: 对话会话ID,可选,如果为空会自动生成 +- `messages`: 消息列表,目前只支持一条消息 +- `messages[0].content`: 用户发送的消息内容 + +### URL参数 +- `category`: 快速回复, +- `dataId`: 数据ID + +### 响应 +**响应类型**: `Stream` +**响应格式**: 每行一个数据块 +``` +data: {"choices":[{"delta":{"content":"你好"},"id":"chat123","model":"gpt-3.5-turbo"}} + +data: {"choices":[{"delta":{"content":",我是"},"id":"chat123","model":"gpt-3.5-turbo"}} + +data: {"choices":[{"delta":{"content":"AI助手。"},"id":"chat123","model":"gpt-3.5-turbo"}} + +data: [DONE] +``` + +### 响应说明 +- 每行以 `data: ` 开头 +- 流式响应结束时会发送 `data: [DONE]` +- 处理时需要去掉 `data: ` 前缀并解析JSON + +## 2. 非流式对话接口 + +### 请求信息 +- **URL**: `/custom/chat/chat` +- **方法**: `POST` +- **Content-Type**: `application/json` + +### 请求头 +- `tenant-id`: 租户ID +- `login-user-id`: 用户ID + +### 请求参数 +同流式对话接口 + +### 响应 +```json +{ + "code": 200, + "data": "完整回复内容", + "msg": "成功" +} +``` + +## 3. 获取对话记录列表 + +### 请求信息 +- **URL**: `/custom/chat/list` +- **方法**: `POST` +- **Content-Type**: `application/json` + +### 请求头 +- `tenant-id`: 租户ID +- `login-user-id`: 用户ID + +### 请求参数 +```json +{ + "pageNum": 1, + "pageSize": 10 +} +``` + +### 响应 +```json +{ + "code": 200, + "data": [ + { + "id": 1, + "chatId": "chat_123", + "messageId": "msg_456", + "content": "你好", + "role": "user", + "responseId": "", + "model": "", + "promptTokens": 0, + "completionTokens": 0, + "totalTokens": 0, + "createTime": "2024-01-01T10:00:00" + } + ], + "msg": "成功" +} +``` + +## 4. 获取对话详情 + +### 请求信息 +- **URL**: `/custom/chat/detail/{chatId}` +- **方法**: `GET` + +### 请求头 +- `tenant-id`: 租户ID +- `login-user-id`: 用户ID + +### 路径参数 +- `chatId`: 对话ID + +### 响应 +```json +{ + "code": 200, + "data": [ + { + "id": 1, + "chatId": "chat_123", + "messageId": "msg_456", + "content": "你好", + "role": "user", + "responseId": "", + "model": "", + "promptTokens": 10, + "completionTokens": 20, + "totalTokens": 30, + "createTime": "2024-01-01T10:00:00" + }, + { + "id": 2, + "chatId": "chat_123", + "messageId": "msg_789", + "content": "你好,我是AI助手", + "role": "assistant", + "responseId": "resp_123", + "model": "gpt-3.5-turbo", + "promptTokens": 10, + "completionTokens": 20, + "totalTokens": 30, + "createTime": "2024-01-01T10:00:05" + } + ], + "msg": "成功" +} +``` + +## 5. 删除对话记录 + +### 请求信息 +- **URL**: `/custom/chat/{chatId}` +- **方法**: `DELETE` + +### 请求头 +- `tenant-id`: 租户ID +- `login-user-id`: 用户ID + +### 路径参数 +- `chatId`: 对话ID + +### 响应 +```json +{ + "code": 200, + "data": true, + "msg": "成功" +} +``` + +## 注意事项 + +1. 所有接口都需要传递租户ID和用户ID +2. 流式接口会返回响应式流,需要特殊处理 +3. 对话记录会自动保存到数据库 +4. 删除操作实际上是软删除,只是将状态标记为已删除 \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http new file mode 100644 index 0000000000..6a875b4df4 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -0,0 +1,63 @@ +### 流式对话接口测试 +POST {{baseUrl}}/custom/chat/stream +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "chatId": "test_chat_123", + "stream": true, + "detail": false, + "variables": {}, + "messages": [ + { + "role": "user", + "content": "你好,请简单介绍一下你自己" + } + ] +} + +### 非流式对话接口测试 +POST {{baseUrl}}/custom/chat/chat +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "chatId": "test_chat_456", + "stream": false, + "detail": false, + "variables": {}, + "messages": [ + { + "role": "user", + "content": "你好,请简单介绍一下你自己" + } + ] +} + +### 获取对话记录列表测试 +POST {{baseUrl}}/custom/chat/list +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "pageNum": 1, + "pageSize": 10 +} + +### 获取某轮对话详情测试 +GET {{baseUrl}}/custom/chat/detail/test_chat_123 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 获取另一轮对话详情测试 +GET {{baseUrl}}/custom/chat/detail/test_chat_456 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 删除对话记录测试 +DELETE {{baseUrl}}/custom/chat/test_chat_123 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index f50dffb1d5..5aa26ae0a0 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -1,13 +1,129 @@ package cn.iocoder.yudao.module.custom.controller.admin.api; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.ChatCompletionRequest; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.module.custom.service.chat.ChatService; +import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "前端接口 - 对话") @RestController @RequestMapping("/custom/chat") @Validated +@Slf4j +@RequiredArgsConstructor public class ChatController { + + private final FastGptService fastGptService; + + private final ChatService chatService; + + @Operation(summary = "与fastgpt进行流式对话") + @PostMapping("/stream") + public Flux streamChat( + @RequestBody @Validated ChatCompletionRequest request, + @RequestParam String category, + @RequestParam String dataId) { + + Long tenantId = TenantContextHolder.getTenantId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + String chatId = request.getChatId() != null ? request.getChatId() : java.util.UUID.randomUUID().toString(); + String finalDataId = dataId != null ? dataId : java.util.UUID.randomUUID().toString(); + + // 发送流式请求 + Flux streamResponse = fastGptService.sendStreamChatRequest( + chatId, request.getMessages().get(0).getContent(), category, finalDataId, String.valueOf(tenantId)); + + // 异步保存对话记录 + Mono.fromRunnable(() -> { + try { + String userMessageId = chatService.saveChatRecord(tenantId, userId, chatId, request.getMessages().get(0).getContent(), null); + log.info("已保存用户消息,消息ID: {}", userMessageId); + } catch (Exception e) { + log.error("保存用户消息失败", e); + } + }).subscribe(); + + return streamResponse; + } + + @Operation(summary = "与fastgpt进行非流式对话") + @PostMapping("/chat") + public Mono> chat( + @RequestBody @Validated ChatCompletionRequest request, + @RequestParam String category, + @RequestParam(required = false) String dataId) { + + Long tenantId = TenantContextHolder.getTenantId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + String chatId = request.getChatId() != null ? request.getChatId() : java.util.UUID.randomUUID().toString(); + String finalDataId = dataId != null ? dataId : java.util.UUID.randomUUID().toString(); + + // 获取响应并保存记录 + return fastGptService.getJsonRequest(chatId, request.getMessages().get(0).getContent(), category, finalDataId, String.valueOf(tenantId)) + .map(response -> { + // 保存完整对话记录(这里简化处理,实际可能需要解析response) + chatService.saveChatRecord(tenantId, userId, chatId, request.getMessages().get(0).getContent(), null); + return success(response); + }); + } + + @Operation(summary = "获取用户对话记录列表") + @PostMapping("/list") + public CommonResult> getChatList( + @RequestBody @Valid ChatRecordPageReqVO pageReqVO) { + + Long tenantId = TenantContextHolder.getTenantId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + List chatList = chatService.getChatPage(tenantId, userId, pageReqVO); + return success(chatList); + } + + @Operation(summary = "获取某轮对话详情") + @GetMapping("/detail/{chatId}") + @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") + public CommonResult> getChatDetail( + @PathVariable String chatId) { + + Long tenantId = TenantContextHolder.getTenantId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + List chatDetail = chatService.getChatDetail(tenantId, userId, chatId); + return success(chatDetail); + } + + @Operation(summary = "删除对话记录") + @DeleteMapping("/{chatId}") + @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") + public CommonResult deleteChat( + @PathVariable String chatId) { + + Long tenantId = TenantContextHolder.getTenantId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + chatService.deleteChat(tenantId, userId, chatId); + return success(true); + } + } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java new file mode 100644 index 0000000000..854f6b3b39 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 对话记录分页请求 VO + * + * @author admin + */ +@Schema(description = "对话记录分页请求") +@Data +public class ChatRecordPageReqVO { + + /** + * 页码 + */ + @Schema(description = "页码", example = "1") + private Integer pageNum = 1; + + /** + * 每页大小 + */ + @Schema(description = "每页大小", example = "10") + private Integer pageSize = 10; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java new file mode 100644 index 0000000000..cc8c175c9e --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 对话记录 VO + * + * @author admin + */ +@Schema(description = "对话记录") +@Data +public class ChatRecordVO { + + /** + * 编号 + */ + @Schema(description = "编号", example = "1") + private Long id; + /** + * 对话会话ID + */ + @Schema(description = "对话会话ID", example = "chat_123") + private String chatId; + /** + * 消息ID + */ + @Schema(description = "消息ID", example = "msg_123") + private String messageId; + /** + * 消息内容 + */ + @Schema(description = "消息内容") + private String content; + /** + * 消息角色 + */ + @Schema(description = "消息角色", example = "user") + private String role; + /** + * 回复ID + */ + @Schema(description = "回复ID", example = "resp_123") + private String responseId; + /** + * 模型 + */ + @Schema(description = "模型", example = "gpt-3.5-turbo") + private String model; + /** + * 提示token数 + */ + @Schema(description = "提示token数", example = "10") + private Integer promptTokens; + /** + * 完成token数 + */ + @Schema(description = "完成token数", example = "20") + private Integer completionTokens; + /** + * 总token数 + */ + @Schema(description = "总token数", example = "30") + private Integer totalTokens; + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java new file mode 100644 index 0000000000..6040aebd45 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.custom.dal.dataobject.chat; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 对话记录 DO + * + * @author admin + */ +@TableName("custom_chat_record") +@KeySequence("custom_chat_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class ChatRecordDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 租户ID + */ + private Long tenantId; + /** + * 用户ID + */ + private Long userId; + /** + * 对话会话ID + */ + private String chatId; + /** + * 消息ID + */ + private String messageId; + /** + * 消息内容 + */ + private String content; + /** + * 消息角色 (user, assistant, system) + */ + private String role; + /** + * FastGPT响应ID + */ + private String responseId; + /** + * 模型名称 + */ + private String model; + /** + * 提示token数 + */ + private Integer promptTokens; + /** + * 完成token数 + */ + private Integer completionTokens; + /** + * 总token数 + */ + private Integer totalTokens; + /** + * 状态 (normal-正常, deleted-已删除) + */ + private String status; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java new file mode 100644 index 0000000000..e3ebb9a0af --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.custom.dal.mysql.chat; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 对话记录 Mapper + * + * @author admin + */ +@Mapper +public interface ChatRecordMapper extends BaseMapperX { + + default List selectChatRecordListByChatId(Long tenantId, Long userId, String chatId) { + return selectList(new LambdaQueryWrapper() + .eq(ChatRecordDO::getTenantId, tenantId) + .eq(ChatRecordDO::getUserId, userId) + .eq(ChatRecordDO::getChatId, chatId) + .eq(ChatRecordDO::getStatus, "normal") + .orderByAsc(ChatRecordDO::getCreateTime)); + } + + default List selectChatRecordSummaryList(Long tenantId, Long userId) { + return selectList(new LambdaQueryWrapper() + .eq(ChatRecordDO::getTenantId, tenantId) + .eq(ChatRecordDO::getUserId, userId) + .eq(ChatRecordDO::getRole, "user") + .eq(ChatRecordDO::getStatus, "normal") + .orderByDesc(ChatRecordDO::getCreateTime) + .groupBy(ChatRecordDO::getChatId)); + } + + default ChatRecordDO selectByMessageId(String messageId) { + return selectOne(new LambdaQueryWrapper() + .eq(ChatRecordDO::getMessageId, messageId)); + } + + default void updateStatusByChatId(Long tenantId, Long userId, String chatId, String status) { + update(null, new LambdaQueryWrapper() + .eq(ChatRecordDO::getTenantId, tenantId) + .eq(ChatRecordDO::getUserId, userId) + .eq(ChatRecordDO::getChatId, chatId) + .eq(ChatRecordDO::getStatus, "normal") + .set(ChatRecordDO::getStatus, status)); + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java new file mode 100644 index 0000000000..7e8ff6d393 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.custom.service.chat; + +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.ChatCompletionResponse; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; +import reactor.core.publisher.Flux; + +import java.util.List; + +/** + * 对话记录 Service + * + * @author admin + */ +public interface ChatService { + + /** + * 保存对话记录 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param chatId 对话ID + * @param userContent 用户消息内容 + * @param response FastGPT响应 + * @return 保存的消息ID + */ + String saveChatRecord(Long tenantId, Long userId, String chatId, String userContent, ChatCompletionResponse response); + + /** + * 保存对话记录 + * + * @param request 对话请求 + * @return 保存的消息ID + */ + String saveChatRecord(ChatRecordDO request); + + /** + * 通过流式响应保存对话记录 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param chatId 对话ID + * @param userContent 用户消息内容 + * @param streamContent 流式响应内容flux + * @return 保存的消息ID + */ + String saveChatRecordByStream(Long tenantId, Long userId, String chatId, String userContent, Flux streamContent); + + /** + * 获取对话列表 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @return 对话记录列表 + */ + List getChatList(Long tenantId, Long userId); + + /** + * 分页获取对话列表 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param pageReqVO 分页请求 + * @return 对话记录分页结果 + */ + List getChatPage(Long tenantId, Long userId, ChatRecordPageReqVO pageReqVO); + + /** + * 获取某轮对话详情 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param chatId 对话ID + * @return 对话详情 + */ + List getChatDetail(Long tenantId, Long userId, String chatId); + + /** + * 删除对话记录 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param chatId 对话ID + */ + void deleteChat(Long tenantId, Long userId, String chatId); + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java new file mode 100644 index 0000000000..5f4701d57e --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java @@ -0,0 +1,155 @@ +package cn.iocoder.yudao.module.custom.service.chat; + +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.ChatCompletionResponse; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; +import cn.iocoder.yudao.module.custom.dal.mysql.chat.ChatRecordMapper; +import cn.hutool.core.beans.BeanUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 对话记录 Service 实现 + * + * @author admin + */ +@Slf4j +@Service +public class ChatServiceImpl implements ChatService { + + @Resource + private ChatRecordMapper chatRecordMapper; + + @Override + public String saveChatRecord(Long tenantId, Long userId, String chatId, String userContent, ChatCompletionResponse response) { + // 保存用户消息 + String userMessageId = IdUtil.fastSimpleUUID(); + ChatRecordDO userRecord = new ChatRecordDO() + .setTenantId(tenantId) + .setUserId(userId) + .setChatId(chatId) + .setMessageId(userMessageId) + .setContent(userContent) + .setRole("user") + .setStatus("normal"); + + chatRecordMapper.insert(userRecord); + + // 保存助手回复 + if (response != null && response.getChoices() != null && !response.getChoices().isEmpty()) { + String assistantMessageId = IdUtil.fastSimpleUUID(); + ChatCompletionResponse.Choice choice = response.getChoices().get(0); + + ChatRecordDO assistantRecord = new ChatRecordDO() + .setTenantId(tenantId) + .setUserId(userId) + .setChatId(chatId) + .setMessageId(assistantMessageId) + .setContent(choice.getMessage().getContent()) + .setRole("assistant") + .setResponseId(response.getId()) + .setModel(response.getModel()) + .setPromptTokens(response.getUsage() != null ? response.getUsage().getPromptTokens() : 0) + .setCompletionTokens(response.getUsage() != null ? response.getUsage().getCompletionTokens() : 0) + .setTotalTokens(response.getUsage() != null ? response.getUsage().getTotalTokens() : 0) + .setStatus("normal"); + + chatRecordMapper.insert(assistantRecord); + return assistantMessageId; + } + return userMessageId; + } + + @Override + public String saveChatRecord(ChatRecordDO request) { + if (request.getMessageId() == null) { + request.setMessageId(IdUtil.fastSimpleUUID()); + } + if (request.getChatId() == null) { + request.setChatId(IdUtil.fastSimpleUUID()); + } + chatRecordMapper.insert(request); + return request.getMessageId(); + } + + @Override + public String saveChatRecordByStream(Long tenantId, Long userId, String chatId, String userContent, Flux streamContent) { + // 保存用户消息 + String userMessageId = IdUtil.fastSimpleUUID(); + ChatRecordDO userRecord = new ChatRecordDO() + .setTenantId(tenantId) + .setUserId(userId) + .setChatId(chatId) + .setMessageId(userMessageId) + .setContent(userContent) + .setRole("user") + .setStatus("normal"); + + chatRecordMapper.insert(userRecord); + + // 保存助手消息(流式) + String assistantMessageId = IdUtil.fastSimpleUUID(); + StringBuilder contentBuilder = new StringBuilder(); + + // 收集流式响应内容 + List contentList = streamContent.collectList().block(); + if (contentList != null) { + contentList.forEach(contentBuilder::append); + } + + ChatRecordDO assistantRecord = new ChatRecordDO() + .setTenantId(tenantId) + .setUserId(userId) + .setChatId(chatId) + .setMessageId(assistantMessageId) + .setContent(contentBuilder.toString()) + .setRole("assistant") + .setStatus("normal"); + + chatRecordMapper.insert(assistantRecord); + return assistantMessageId; + } + + @Override + public List getChatList(Long tenantId, Long userId) { + return getChatList(tenantId, userId, new ChatRecordPageReqVO()); + } + + @Override + public List getChatPage(Long tenantId, Long userId, ChatRecordPageReqVO pageReqVO) { + // 这里简化处理,实际应该使用分页查询 + List records = chatRecordMapper.selectChatRecordSummaryList(tenantId, userId); + return convertToVO(records); + } + + @Override + public List getChatDetail(Long tenantId, Long userId, String chatId) { + List records = chatRecordMapper.selectChatRecordListByChatId(tenantId, userId, chatId); + return convertToVO(records); + } + + @Override + public void deleteChat(Long tenantId, Long userId, String chatId) { + chatRecordMapper.updateStatusByChatId(tenantId, userId, chatId, "deleted"); + } + + private List convertToVO(List records) { + return records.stream().map(record -> { + ChatRecordVO vo = new ChatRecordVO(); + BeanUtil.copyProperties(record, vo); + return vo; + }).collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql b/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql new file mode 100644 index 0000000000..d4c920465b --- /dev/null +++ b/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql @@ -0,0 +1,26 @@ +-- 对话记录表 +CREATE TABLE `custom_chat_record` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `tenant_id` bigint NOT NULL COMMENT '租户ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `chat_id` varchar(100) NOT NULL COMMENT '对话会话ID', + `message_id` varchar(100) NOT NULL COMMENT '消息ID', + `content` text NOT NULL COMMENT '消息内容', + `role` varchar(20) NOT NULL COMMENT '消息角色 (user, assistant, system)', + `response_id` varchar(100) DEFAULT NULL COMMENT 'FastGPT响应ID', + `model` varchar(50) DEFAULT NULL COMMENT '模型名称', + `prompt_tokens` int DEFAULT '0' COMMENT '提示token数', + `completion_tokens` int DEFAULT '0' COMMENT '完成token数', + `total_tokens` int DEFAULT '0' COMMENT '总token数', + `status` varchar(20) NOT NULL DEFAULT 'normal' COMMENT '状态 (normal-正常, deleted-已删除)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_message_id` (`message_id`), + KEY `idx_tenant_user` (`tenant_id`, `user_id`), + KEY `idx_chat_id` (`chat_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对话记录表'; + +-- 创建序列(如果使用Oracle、PostgreSQL等需要序列的数据库) +-- CREATE SEQUENCE `custom_chat_record_seq` START WITH 1 INCREMENT BY 1; \ No newline at end of file -- Gitee From d76e5e3777f6309db915d64022dfc99aa7d911c7 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 30 Oct 2025 17:04:00 +0800 Subject: [PATCH 15/50] =?UTF-8?q?=E5=BC=80=E5=90=AF=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=EF=BC=8C=E5=B9=B6=E4=BF=9D=E5=AD=98=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=AF=B9=E8=AF=9D=E8=AE=B0=E5=BD=95-=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomDataPermissionConfiguration.java | 2 +- .../controller/admin/api/ChatController.http | 48 ++--- .../controller/admin/api/ChatController.java | 96 ++++------ .../admin/api/vo/ChatRecordPageReqVO.java | 2 - .../controller/admin/api/vo/ChatRecordVO.java | 27 +-- .../dal/dataobject/chat/ChatRecordDO.java | 20 +- .../custom/dal/dataobject/ent/EntInfoDO.java | 1 - .../dal/mysql/chat/ChatRecordMapper.java | 21 +-- .../custom/service/chat/ChatService.java | 55 ++---- .../custom/service/chat/ChatServiceImpl.java | 174 +++++++----------- .../service/fastgpt/FastGptService.java | 21 ++- .../main/resources/sql/custom_chat_record.sql | 6 +- 12 files changed, 166 insertions(+), 307 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java index c41ab52fb7..58224d412d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/config/CustomDataPermissionConfiguration.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.custom.config; -import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 6a875b4df4..743b272964 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -1,43 +1,22 @@ ### 流式对话接口测试 -POST {{baseUrl}}/custom/chat/stream +POST {{baseUrl}}/custom/chat/stream?category=sales&chatId=test_chat_123&dataId=123&userContent=你好,请简单介绍一下你自己 Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} -{ - "chatId": "test_chat_123", - "stream": true, - "detail": false, - "variables": {}, - "messages": [ - { - "role": "user", - "content": "你好,请简单介绍一下你自己" - } - ] -} - -### 非流式对话接口测试 -POST {{baseUrl}}/custom/chat/chat +### 获取对话记录列表测试 +POST {{baseUrl}}/custom/chat/list Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} { - "chatId": "test_chat_456", - "stream": false, - "detail": false, - "variables": {}, - "messages": [ - { - "role": "user", - "content": "你好,请简单介绍一下你自己" - } - ] + "pageNum": 1, + "pageSize": 10 } -### 获取对话记录列表测试 -POST {{baseUrl}}/custom/chat/list +### 获取某轮对话详情测试 +POST {{baseUrl}}/custom/chat/detail/test_chat_123 Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} @@ -47,16 +26,17 @@ tenant-id: {{adminTenantId}} "pageSize": 10 } -### 获取某轮对话详情测试 -GET {{baseUrl}}/custom/chat/detail/test_chat_123 -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - ### 获取另一轮对话详情测试 -GET {{baseUrl}}/custom/chat/detail/test_chat_456 +POST {{baseUrl}}/custom/chat/detail/test_chat_456 +Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} +{ + "pageNum": 1, + "pageSize": 10 +} + ### 删除对话记录测试 DELETE {{baseUrl}}/custom/chat/test_chat_123 Authorization: Bearer {{token}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index 5aa26ae0a0..c67c14e1c3 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -1,26 +1,24 @@ package cn.iocoder.yudao.module.custom.controller.admin.api; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.custom.api.fastgpt.vo.ChatCompletionRequest; -import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import cn.iocoder.yudao.module.custom.service.chat.ChatService; import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import javax.annotation.Resource; import javax.validation.Valid; -import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -39,78 +37,62 @@ public class ChatController { @Operation(summary = "与fastgpt进行流式对话") @PostMapping("/stream") public Flux streamChat( - @RequestBody @Validated ChatCompletionRequest request, - @RequestParam String category, - @RequestParam String dataId) { - - Long tenantId = TenantContextHolder.getTenantId(); - Long userId = SecurityFrameworkUtils.getLoginUserId(); - - String chatId = request.getChatId() != null ? request.getChatId() : java.util.UUID.randomUUID().toString(); - String finalDataId = dataId != null ? dataId : java.util.UUID.randomUUID().toString(); - - // 发送流式请求 - Flux streamResponse = fastGptService.sendStreamChatRequest( - chatId, request.getMessages().get(0).getContent(), category, finalDataId, String.valueOf(tenantId)); - - // 异步保存对话记录 - Mono.fromRunnable(() -> { - try { - String userMessageId = chatService.saveChatRecord(tenantId, userId, chatId, request.getMessages().get(0).getContent(), null); - log.info("已保存用户消息,消息ID: {}", userMessageId); - } catch (Exception e) { - log.error("保存用户消息失败", e); - } - }).subscribe(); - - return streamResponse; - } - - @Operation(summary = "与fastgpt进行非流式对话") - @PostMapping("/chat") - public Mono> chat( - @RequestBody @Validated ChatCompletionRequest request, @RequestParam String category, - @RequestParam(required = false) String dataId) { + @RequestParam String chatId, + @RequestParam String dataId, + @RequestParam String userContent) { Long tenantId = TenantContextHolder.getTenantId(); Long userId = SecurityFrameworkUtils.getLoginUserId(); - String chatId = request.getChatId() != null ? request.getChatId() : java.util.UUID.randomUUID().toString(); - String finalDataId = dataId != null ? dataId : java.util.UUID.randomUUID().toString(); - - // 获取响应并保存记录 - return fastGptService.getJsonRequest(chatId, request.getMessages().get(0).getContent(), category, finalDataId, String.valueOf(tenantId)) - .map(response -> { - // 保存完整对话记录(这里简化处理,实际可能需要解析response) - chatService.saveChatRecord(tenantId, userId, chatId, request.getMessages().get(0).getContent(), null); - return success(response); + // 1. 在请求前保存用户消息 + try { + Long userMessageId = chatService.saveChatRecord(tenantId, userId, chatId, dataId, userContent, null); + log.info("已保存用户消息,消息ID: {}", userMessageId); + } catch (Exception e) { + log.error("保存用户消息失败", e); + } + + // 2. 发送流式请求,使用回调函数保存助手回复 + return fastGptService.sendStreamChatRequest( + chatId, userContent, category, dataId, String.valueOf(tenantId), + (fullResponse) -> { + // 流式输出结束后,保存助手回复 + try { + if (fullResponse != null && !fullResponse.isEmpty()) { + Long assistantMessageId = chatService.saveChatRecord( + tenantId, userId, chatId, dataId, userContent, fullResponse); + log.info("已保存助手回复,消息ID: {}", assistantMessageId); + } + } catch (Exception e) { + log.error("保存助手回复失败", e); + } }); } @Operation(summary = "获取用户对话记录列表") @PostMapping("/list") - public CommonResult> getChatList( - @RequestBody @Valid ChatRecordPageReqVO pageReqVO) { + public CommonResult> getChatList( + @RequestBody @Valid PageParam pageReqVO) { Long tenantId = TenantContextHolder.getTenantId(); Long userId = SecurityFrameworkUtils.getLoginUserId(); - List chatList = chatService.getChatPage(tenantId, userId, pageReqVO); - return success(chatList); + PageResult chatList = chatService.getChatPage(tenantId, userId, pageReqVO); + return success(BeanUtils.toBean(chatList, ChatRecordDO.class)); } @Operation(summary = "获取某轮对话详情") @GetMapping("/detail/{chatId}") @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") - public CommonResult> getChatDetail( - @PathVariable String chatId) { + public CommonResult> getChatDetail(@RequestBody @Valid PageParam pageReqVO, + @PathVariable String chatId) { Long tenantId = TenantContextHolder.getTenantId(); Long userId = SecurityFrameworkUtils.getLoginUserId(); - List chatDetail = chatService.getChatDetail(tenantId, userId, chatId); - return success(chatDetail); + PageResult chatDetail = chatService.getChatDetail(tenantId, userId, chatId, pageReqVO); + return success(BeanUtils.toBean(chatDetail, ChatRecordDO.class)); } @Operation(summary = "删除对话记录") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java index 854f6b3b39..7da864e8ca 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.custom.controller.admin.api.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import java.util.List; - /** * 对话记录分页请求 VO * diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java index cc8c175c9e..1fb352ce69 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; -import java.util.List; /** * 对话记录 VO @@ -41,30 +40,10 @@ public class ChatRecordVO { @Schema(description = "消息角色", example = "user") private String role; /** - * 回复ID + * 是否为标题 */ - @Schema(description = "回复ID", example = "resp_123") - private String responseId; - /** - * 模型 - */ - @Schema(description = "模型", example = "gpt-3.5-turbo") - private String model; - /** - * 提示token数 - */ - @Schema(description = "提示token数", example = "10") - private Integer promptTokens; - /** - * 完成token数 - */ - @Schema(description = "完成token数", example = "20") - private Integer completionTokens; - /** - * 总token数 - */ - @Schema(description = "总token数", example = "30") - private Integer totalTokens; + @Schema(description = "是否为标题", example = "true") + private Boolean isTitle; /** * 创建时间 */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java index 6040aebd45..a070726d51 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java @@ -50,25 +50,9 @@ public class ChatRecordDO extends BaseDO { */ private String role; /** - * FastGPT响应ID + * 是否为标题 (0-否, 1-是,默认第一条为标题) */ - private String responseId; - /** - * 模型名称 - */ - private String model; - /** - * 提示token数 - */ - private Integer promptTokens; - /** - * 完成token数 - */ - private Integer completionTokens; - /** - * 总token数 - */ - private Integer totalTokens; + private Boolean isTitle; /** * 状态 (normal-正常, deleted-已删除) */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java index 408f576baa..f5fb2731ed 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.custom.dal.dataobject.ent; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java index e3ebb9a0af..aff1d1bb6b 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java @@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.custom.dal.mysql.chat; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; -import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; import java.util.List; @@ -27,23 +25,16 @@ public interface ChatRecordMapper extends BaseMapperX { .orderByAsc(ChatRecordDO::getCreateTime)); } - default List selectChatRecordSummaryList(Long tenantId, Long userId) { - return selectList(new LambdaQueryWrapper() + default Long selectCountByChatId(Long tenantId, Long userId, String chatId) { + return selectCount(new LambdaQueryWrapper() .eq(ChatRecordDO::getTenantId, tenantId) .eq(ChatRecordDO::getUserId, userId) - .eq(ChatRecordDO::getRole, "user") - .eq(ChatRecordDO::getStatus, "normal") - .orderByDesc(ChatRecordDO::getCreateTime) - .groupBy(ChatRecordDO::getChatId)); - } - - default ChatRecordDO selectByMessageId(String messageId) { - return selectOne(new LambdaQueryWrapper() - .eq(ChatRecordDO::getMessageId, messageId)); + .eq(ChatRecordDO::getChatId, chatId) + .eq(ChatRecordDO::getStatus, "normal")); } default void updateStatusByChatId(Long tenantId, Long userId, String chatId, String status) { - update(null, new LambdaQueryWrapper() + update(new LambdaUpdateWrapper() .eq(ChatRecordDO::getTenantId, tenantId) .eq(ChatRecordDO::getUserId, userId) .eq(ChatRecordDO::getChatId, chatId) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java index 7e8ff6d393..21e0cbe302 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java @@ -1,12 +1,10 @@ package cn.iocoder.yudao.module.custom.service.chat; -import cn.iocoder.yudao.module.custom.api.fastgpt.vo.ChatCompletionResponse; -import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; -import reactor.core.publisher.Flux; -import java.util.List; +import javax.validation.Valid; /** * 对话记录 Service @@ -18,53 +16,24 @@ public interface ChatService { /** * 保存对话记录 * - * @param tenantId 租户ID - * @param userId 用户ID - * @param chatId 对话ID + * @param tenantId 租户ID + * @param userId 用户ID + * @param chatId 对话ID * @param userContent 用户消息内容 - * @param response FastGPT响应 + * @param response FastGPT响应 * @return 保存的消息ID */ - String saveChatRecord(Long tenantId, Long userId, String chatId, String userContent, ChatCompletionResponse response); - - /** - * 保存对话记录 - * - * @param request 对话请求 - * @return 保存的消息ID - */ - String saveChatRecord(ChatRecordDO request); - - /** - * 通过流式响应保存对话记录 - * - * @param tenantId 租户ID - * @param userId 用户ID - * @param chatId 对话ID - * @param userContent 用户消息内容 - * @param streamContent 流式响应内容flux - * @return 保存的消息ID - */ - String saveChatRecordByStream(Long tenantId, Long userId, String chatId, String userContent, Flux streamContent); - - /** - * 获取对话列表 - * - * @param tenantId 租户ID - * @param userId 用户ID - * @return 对话记录列表 - */ - List getChatList(Long tenantId, Long userId); + Long saveChatRecord(Long tenantId, Long userId, String chatId, String msgId, String userContent, String response); /** * 分页获取对话列表 * - * @param tenantId 租户ID - * @param userId 用户ID + * @param tenantId 租户ID + * @param userId 用户ID * @param pageReqVO 分页请求 * @return 对话记录分页结果 */ - List getChatPage(Long tenantId, Long userId, ChatRecordPageReqVO pageReqVO); + PageResult getChatPage(Long tenantId, Long userId, @Valid PageParam pageReqVO); /** * 获取某轮对话详情 @@ -74,7 +43,7 @@ public interface ChatService { * @param chatId 对话ID * @return 对话详情 */ - List getChatDetail(Long tenantId, Long userId, String chatId); + PageResult getChatDetail(Long tenantId, Long userId, String chatId, @Valid PageParam pageReqVO); /** * 删除对话记录 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java index 5f4701d57e..bdd53e8035 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java @@ -1,23 +1,16 @@ package cn.iocoder.yudao.module.custom.service.chat; -import cn.iocoder.yudao.module.custom.api.fastgpt.vo.ChatCompletionResponse; -import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import cn.iocoder.yudao.module.custom.dal.mysql.chat.ChatRecordMapper; -import cn.hutool.core.beans.BeanUtil; -import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; +import javax.validation.Valid; /** * 对话记录 Service 实现 @@ -32,111 +25,94 @@ public class ChatServiceImpl implements ChatService { private ChatRecordMapper chatRecordMapper; @Override - public String saveChatRecord(Long tenantId, Long userId, String chatId, String userContent, ChatCompletionResponse response) { - // 保存用户消息 - String userMessageId = IdUtil.fastSimpleUUID(); - ChatRecordDO userRecord = new ChatRecordDO() - .setTenantId(tenantId) - .setUserId(userId) - .setChatId(chatId) - .setMessageId(userMessageId) - .setContent(userContent) - .setRole("user") - .setStatus("normal"); - - chatRecordMapper.insert(userRecord); + public Long saveChatRecord(Long tenantId, Long userId, String chatId, String msgId, String userContent, String response) { // 保存助手回复 - if (response != null && response.getChoices() != null && !response.getChoices().isEmpty()) { - String assistantMessageId = IdUtil.fastSimpleUUID(); - ChatCompletionResponse.Choice choice = response.getChoices().get(0); + if (response != null && !response.isEmpty()) { ChatRecordDO assistantRecord = new ChatRecordDO() .setTenantId(tenantId) .setUserId(userId) .setChatId(chatId) - .setMessageId(assistantMessageId) - .setContent(choice.getMessage().getContent()) + .setMessageId(msgId) + .setContent(response) .setRole("assistant") - .setResponseId(response.getId()) - .setModel(response.getModel()) - .setPromptTokens(response.getUsage() != null ? response.getUsage().getPromptTokens() : 0) - .setCompletionTokens(response.getUsage() != null ? response.getUsage().getCompletionTokens() : 0) - .setTotalTokens(response.getUsage() != null ? response.getUsage().getTotalTokens() : 0) + .setIsTitle(false) .setStatus("normal"); chatRecordMapper.insert(assistantRecord); - return assistantMessageId; - } - return userMessageId; - } - @Override - public String saveChatRecord(ChatRecordDO request) { - if (request.getMessageId() == null) { - request.setMessageId(IdUtil.fastSimpleUUID()); - } - if (request.getChatId() == null) { - request.setChatId(IdUtil.fastSimpleUUID()); - } - chatRecordMapper.insert(request); - return request.getMessageId(); - } + return assistantRecord.getId(); + + } else { + // 检查是否为该chatId的第一条记录 + Long recordCount = chatRecordMapper.selectCountByChatId(tenantId, userId, chatId); + boolean isFirstRecord = (recordCount == null || recordCount == 0); + + // 保存用户消息 + ChatRecordDO userRecord = new ChatRecordDO() + .setTenantId(tenantId) + .setUserId(userId) + .setChatId(chatId) + .setMessageId(msgId) + .setContent(userContent) + .setRole("user") + .setIsTitle(isFirstRecord) // 第一条记录设置为标题 + .setStatus("normal"); + + return (long) chatRecordMapper.insert(userRecord); - @Override - public String saveChatRecordByStream(Long tenantId, Long userId, String chatId, String userContent, Flux streamContent) { - // 保存用户消息 - String userMessageId = IdUtil.fastSimpleUUID(); - ChatRecordDO userRecord = new ChatRecordDO() - .setTenantId(tenantId) - .setUserId(userId) - .setChatId(chatId) - .setMessageId(userMessageId) - .setContent(userContent) - .setRole("user") - .setStatus("normal"); - - chatRecordMapper.insert(userRecord); - - // 保存助手消息(流式) - String assistantMessageId = IdUtil.fastSimpleUUID(); - StringBuilder contentBuilder = new StringBuilder(); - - // 收集流式响应内容 - List contentList = streamContent.collectList().block(); - if (contentList != null) { - contentList.forEach(contentBuilder::append); } - ChatRecordDO assistantRecord = new ChatRecordDO() - .setTenantId(tenantId) - .setUserId(userId) - .setChatId(chatId) - .setMessageId(assistantMessageId) - .setContent(contentBuilder.toString()) - .setRole("assistant") - .setStatus("normal"); - - chatRecordMapper.insert(assistantRecord); - return assistantMessageId; } @Override - public List getChatList(Long tenantId, Long userId) { - return getChatList(tenantId, userId, new ChatRecordPageReqVO()); + public PageResult getChatPage(Long tenantId, Long userId, @Valid PageParam pageReqVO) { + // 这里简化处理,实际应该使用分页查询 + return chatRecordMapper.selectPage(pageReqVO, new LambdaQueryWrapperX() + .eqIfPresent(ChatRecordDO::getTenantId, tenantId) + .eqIfPresent(ChatRecordDO::getUserId, userId) + .orderByDesc(ChatRecordDO::getCreateTime)); } - @Override - public List getChatPage(Long tenantId, Long userId, ChatRecordPageReqVO pageReqVO) { - // 这里简化处理,实际应该使用分页查询 - List records = chatRecordMapper.selectChatRecordSummaryList(tenantId, userId); - return convertToVO(records); + public PageResult getChatDetail(Long tenantId, Long userId, String chatId, @Valid PageParam pageReqVO) { + // 首先获取总记录数 + Long totalCount = chatRecordMapper.selectCount(new LambdaQueryWrapperX() + .eqIfPresent(ChatRecordDO::getTenantId, tenantId) + .eqIfPresent(ChatRecordDO::getUserId, userId) + .eqIfPresent(ChatRecordDO::getChatId, chatId) + .eq(ChatRecordDO::getStatus, "normal")); + + // 计算总页数 + PageParam convertedPageParam = getPageParam(pageReqVO, (double) totalCount); + + return chatRecordMapper.selectPage(convertedPageParam, new LambdaQueryWrapperX() + .eqIfPresent(ChatRecordDO::getTenantId, tenantId) + .eqIfPresent(ChatRecordDO::getUserId, userId) + .eqIfPresent(ChatRecordDO::getChatId, chatId) + .eq(ChatRecordDO::getStatus, "normal") + .orderByAsc(ChatRecordDO::getCreateTime)); // 按时间正序排列 } - @Override - public List getChatDetail(Long tenantId, Long userId, String chatId) { - List records = chatRecordMapper.selectChatRecordListByChatId(tenantId, userId, chatId); - return convertToVO(records); + @NotNull + private static PageParam getPageParam(PageParam pageReqVO, double totalCount) { + int pageSize = pageReqVO.getPageSize() > 0 ? pageReqVO.getPageSize() : 10; + int totalPage = (int) Math.ceil(totalCount / pageSize); + + // 转换页码:将从后往前的页码转换为从前往后的页码 + int currentPage = pageReqVO.getPageNo() > 0 ? pageReqVO.getPageNo() : 1; + int convertedPage = totalPage - currentPage + 1; + + // 确保转换后的页码有效 + if (convertedPage <= 0) { + convertedPage = 1; + } + + // 创建新的分页参数 + PageParam convertedPageParam = new PageParam(); + convertedPageParam.setPageNo(convertedPage); + convertedPageParam.setPageSize(pageSize); + return convertedPageParam; } @Override @@ -144,12 +120,4 @@ public class ChatServiceImpl implements ChatService { chatRecordMapper.updateStatusByChatId(tenantId, userId, chatId, "deleted"); } - private List convertToVO(List records) { - return records.stream().map(record -> { - ChatRecordVO vo = new ChatRecordVO(); - BeanUtil.copyProperties(record, vo); - return vo; - }).collect(Collectors.toList()); - } - } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 005492cab2..8e4edaed12 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -12,7 +12,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; -import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -221,9 +220,11 @@ public class FastGptService { * @param category 认证类别 * @param dataId 数据ID * @param tenantId 租户ID + * @param onComplete 流式输出完成后的回调函数,参数为完整的响应内容 * @return 流式响应 */ - public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId) { + public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId, + java.util.function.Consumer onComplete) { if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); @@ -232,7 +233,7 @@ public class FastGptService { request.setChatId(chatId); request.setResponseChatItemId(dataId); request.setStream(true); - request.setDetail(false); + request.setDetail(true); request.setVariables(new HashMap<>()); // 创建消息 @@ -254,8 +255,11 @@ public class FastGptService { // 创建ObjectMapper用于解析JSON ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - try { + // 用于收集完整的响应内容 + StringBuilder fullResponse = new StringBuilder(); + + try { // 逐行读取响应内容 BufferedReader reader = new BufferedReader(responseBody.charStream()); String line; @@ -287,6 +291,7 @@ public class FastGptService { if (!text.isEmpty()) { log.info("接收到的内容片段: {}", text); sink.next(text); // 通过flux发送内容片段 + fullResponse.append(text); // 收集完整响应内容 } } } @@ -301,6 +306,14 @@ public class FastGptService { } if (!sink.isCancelled()) { + // 在流完成时调用回调函数 + if (onComplete != null) { + try { + onComplete.accept(fullResponse.toString()); + } catch (Exception e) { + log.error("执行回调函数时发生错误", e); + } + } sink.complete(); // 完成flux流 } } diff --git a/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql b/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql index d4c920465b..ba78e2ff69 100644 --- a/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql +++ b/yudao-module-custom/src/main/resources/sql/custom_chat_record.sql @@ -7,11 +7,7 @@ CREATE TABLE `custom_chat_record` ( `message_id` varchar(100) NOT NULL COMMENT '消息ID', `content` text NOT NULL COMMENT '消息内容', `role` varchar(20) NOT NULL COMMENT '消息角色 (user, assistant, system)', - `response_id` varchar(100) DEFAULT NULL COMMENT 'FastGPT响应ID', - `model` varchar(50) DEFAULT NULL COMMENT '模型名称', - `prompt_tokens` int DEFAULT '0' COMMENT '提示token数', - `completion_tokens` int DEFAULT '0' COMMENT '完成token数', - `total_tokens` int DEFAULT '0' COMMENT '总token数', + `is_title` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为标题 (0-否, 1-是,默认第一条为标题)', `status` varchar(20) NOT NULL DEFAULT 'normal' COMMENT '状态 (normal-正常, deleted-已删除)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- Gitee From 55143716fd5879b30ee61b9df7ab74df8f685b72 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 30 Oct 2025 17:34:25 +0800 Subject: [PATCH 16/50] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/api/ChatController.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index c67c14e1c3..faf40e9837 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -14,6 +14,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; @@ -22,7 +23,7 @@ import javax.validation.Valid; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -@Tag(name = "前端接口 - 对话") +@Tag(name = "管理后台 - 对话") @RestController @RequestMapping("/custom/chat") @Validated @@ -36,6 +37,11 @@ public class ChatController { @Operation(summary = "与fastgpt进行流式对话") @PostMapping("/stream") + @Parameter(name = "category", description = "对话流程类型", required = true) + @Parameter(name = "chatId", description = "每一个新对话的唯一id", required = true) + @Parameter(name = "dataId", description = "每轮对话的id", required = true) + @Parameter(name = "userContent", description = "用户发送的内容", required = true) + @PreAuthorize("@ss.hasPermission('custom:chat:stream')") public Flux streamChat( @RequestParam String category, @RequestParam String chatId, @@ -72,6 +78,7 @@ public class ChatController { @Operation(summary = "获取用户对话记录列表") @PostMapping("/list") + @PreAuthorize("@ss.hasPermission('custom:chat:list')") public CommonResult> getChatList( @RequestBody @Valid PageParam pageReqVO) { @@ -79,12 +86,13 @@ public class ChatController { Long userId = SecurityFrameworkUtils.getLoginUserId(); PageResult chatList = chatService.getChatPage(tenantId, userId, pageReqVO); - return success(BeanUtils.toBean(chatList, ChatRecordDO.class)); + return success(chatList); } @Operation(summary = "获取某轮对话详情") @GetMapping("/detail/{chatId}") @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") + @PreAuthorize("@ss.hasPermission('custom:chat:detail')") public CommonResult> getChatDetail(@RequestBody @Valid PageParam pageReqVO, @PathVariable String chatId) { @@ -92,12 +100,13 @@ public class ChatController { Long userId = SecurityFrameworkUtils.getLoginUserId(); PageResult chatDetail = chatService.getChatDetail(tenantId, userId, chatId, pageReqVO); - return success(BeanUtils.toBean(chatDetail, ChatRecordDO.class)); + return success(chatDetail); } @Operation(summary = "删除对话记录") @DeleteMapping("/{chatId}") @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") + @PreAuthorize("@ss.hasPermission('custom:chat:delete')") public CommonResult deleteChat( @PathVariable String chatId) { @@ -108,4 +117,4 @@ public class ChatController { return success(true); } -} +} \ No newline at end of file -- Gitee From 77f890f624bebe0ec036c210d4bb8cd78334f31c Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 30 Oct 2025 18:19:46 +0800 Subject: [PATCH 17/50] =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-custom/pom.xml | 5 + .../usertransfer/UserTransferController.java | 36 +++++ .../custom/dal/mysql/ent/EntMapper.java | 12 ++ .../usertransfer/UserTransferService.java | 19 +++ .../usertransfer/UserTransferServiceImpl.java | 64 ++++++++ .../module/system/api/user/AdminUserApi.java | 11 ++ .../system/api/user/AdminUserApiImpl.java | 8 + .../usertransfer/UserTransferService.java | 19 +++ .../usertransfer/UserTransferServiceImpl.java | 143 ++++++++++++++++++ 9 files changed, 317 insertions(+) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferService.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferServiceImpl.java create mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java create mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java diff --git a/yudao-module-custom/pom.xml b/yudao-module-custom/pom.xml index 613b587fed..29f953a4ac 100644 --- a/yudao-module-custom/pom.xml +++ b/yudao-module-custom/pom.xml @@ -26,6 +26,11 @@ yudao-module-infra ${revision} + + cn.iocoder.boot + yudao-module-system + ${revision} + diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java new file mode 100644 index 0000000000..62326bb9e0 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.custom.controller.admin.usertransfer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.custom.service.usertransfer.UserTransferService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 用户") +@RestController +@RequestMapping("/custom/user") +@Validated +public class UserTransferController { + + @Resource + private UserTransferService userTransferService; + + @PostMapping("/transfer") + @Operation(summary = "用户数据转移") + @PreAuthorize("@ss.hasPermission('custom:user:transfer')") + public CommonResult transferUserDataWithEnt(@RequestParam Long fromUserId, @RequestParam Long toUserId) { + boolean success = userTransferService.transferUserDataWithEnt(fromUserId, toUserId); + + return success(success); + } + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java index 3aa67a3d56..9ef92d1f23 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + /** * 企业 Mapper * @@ -24,4 +26,14 @@ public interface EntMapper extends BaseMapperX { .orderByDesc(EntDO::getId)); } + /** + * 根据用户ID查询企业列表 + * + * @param userId 用户ID + * @return 企业列表 + */ + default List selectListByUserId(Long userId) { + return selectList(new LambdaQueryWrapperX().eq(EntDO::getUserId, userId)); + } + } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferService.java new file mode 100644 index 0000000000..f088cfa770 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferService.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.custom.service.usertransfer; + +/** + * 用户数据转移 Service 接口 + * + * @author 芋道源码 + */ +public interface UserTransferService { + + /** + * 转移员工数据(包括企业信息) + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + * @return 转移结果 + */ + boolean transferUserDataWithEnt(Long fromUserId, Long toUserId); + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferServiceImpl.java new file mode 100644 index 0000000000..1d61789e6f --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/usertransfer/UserTransferServiceImpl.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.custom.service.usertransfer; + +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用户数据转移 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class UserTransferServiceImpl implements UserTransferService { + + @Resource + private AdminUserApi adminUserApi; + + @Resource + private EntMapper entMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean transferUserDataWithEnt(Long fromUserId, Long toUserId) { + // 1. 调用system模块的员工数据转移功能 + boolean userTransferSuccess = adminUserApi.transferUserData(fromUserId, toUserId); + if (!userTransferSuccess) { + throw new RuntimeException("系统员工数据转移失败"); + } + + // 2. 转移企业信息 + transferEntData(fromUserId, toUserId); + + return true; + } + + /** + * 转移企业信息 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + */ + private void transferEntData(Long fromUserId, Long toUserId) { + // 获取离职员工的企业信息 + List fromUserEnts = entMapper.selectListByUserId(fromUserId); + + // 将企业信息的所属用户ID更新为接收员工ID + for (EntDO ent : fromUserEnts) { + EntDO updateEnt = new EntDO(); + updateEnt.setId(ent.getId()); + updateEnt.setUserId(toUserId); + entMapper.updateById(updateEnt); + } + + log.info("转移了 {} 个企业信息从用户 {} 到用户 {}", fromUserEnts.size(), fromUserId, toUserId); + } + +} \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index 507fb4b3e7..4262e4c0c9 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -86,4 +86,15 @@ public interface AdminUserApi { */ void validateUserList(Collection ids); + /** + * 转移员工数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + * @return 是否转移成功 + */ + + boolean transferUserData(Long fromUserId, Long toUserId); + + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java index 169bf11c0e..b63621a4ab 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import cn.iocoder.yudao.module.system.service.usertransfer.UserTransferService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -31,6 +32,8 @@ public class AdminUserApiImpl implements AdminUserApi { private AdminUserService userService; @Resource private DeptService deptService; + @Resource + private UserTransferService userTransferService; @Override public AdminUserRespDTO getUser(Long id) { @@ -83,4 +86,9 @@ public class AdminUserApiImpl implements AdminUserApi { userService.validateUserList(ids); } + @Override + public boolean transferUserData(Long fromUserId, Long toUserId) { + return userTransferService.transferUserData(fromUserId, toUserId); + } + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java new file mode 100644 index 0000000000..f477ceda3d --- /dev/null +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.system.service.usertransfer; + +/** + * 用户数据转移 Service 接口 + * + * @author 芋道源码 + */ +public interface UserTransferService { + + /** + * 转移员工数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + * @return 转移结果 + */ + boolean transferUserData(Long fromUserId, Long toUserId); + +} \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java new file mode 100644 index 0000000000..96d4abf565 --- /dev/null +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java @@ -0,0 +1,143 @@ +package cn.iocoder.yudao.module.system.service.usertransfer; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; +import cn.iocoder.yudao.module.system.service.permission.PermissionService; +import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Set; + +/** + * 用户数据转移 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class UserTransferServiceImpl implements UserTransferService { + + @Resource + private AdminUserService adminUserService; + + @Resource + private PermissionService permissionService; + + @Resource + private UserPostMapper userPostMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean transferUserData(Long fromUserId, Long toUserId) { + // 1. 验证用户是否存在 + AdminUserDO fromUser = adminUserService.getUser(fromUserId); + if (fromUser == null) { + throw new RuntimeException("离职员工不存在"); + } + + AdminUserDO toUser = adminUserService.getUser(toUserId); + if (toUser == null) { + throw new RuntimeException("接收员工不存在"); + } + + // 2. 转移角色数据 + transferRoleData(fromUserId, toUserId); + + // 3. 转移部门和岗位数据 + transferDeptAndPostData(fromUserId, toUserId); + + // 4. 禁用离职员工 + disableUser(fromUserId); + + return true; + } + + /** + * 转移角色数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + */ + private void transferRoleData(Long fromUserId, Long toUserId) { + // 获取离职员工的角色 + Set fromUserRoles = permissionService.getUserRoleIdListByUserId(fromUserId); + + // 获取接收员工的角色 + Set toUserRoles = permissionService.getUserRoleIdListByUserId(toUserId); + + // 合并角色(去重) + toUserRoles.addAll(fromUserRoles); + + // 更新接收员工的角色 + permissionService.assignUserRole(toUserId, toUserRoles); + + // 清空离职员工的角色 + permissionService.assignUserRole(fromUserId, CollUtil.newHashSet()); + } + + /** + * 转移部门和岗位数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + */ + private void transferDeptAndPostData(Long fromUserId, Long toUserId) { + // 获取离职员工的岗位 + Set fromUserPosts = convertSet(userPostMapper.selectListByUserId(fromUserId), UserPostDO::getPostId); + + // 获取接收员工的岗位 + Set toUserPosts = convertSet(userPostMapper.selectListByUserId(toUserId), UserPostDO::getPostId); + + // 合并岗位(去重) + toUserPosts.addAll(fromUserPosts); + + // 获取离职员工的部门信息 + AdminUserDO fromUser = adminUserService.getUser(fromUserId); + AdminUserDO toUser = adminUserService.getUser(toUserId); + + // 创建UserSaveReqVO对象,更新接收员工的部门和岗位 + UserSaveReqVO updateReqVO = BeanUtils.toBean(toUser, UserSaveReqVO.class); + updateReqVO.setId(toUserId); + updateReqVO.setPostIds(toUserPosts); + + // 如果需要将离职员工的部门也转移给接收员工,则取消下面的注释 + // updateReqVO.setDeptId(fromUser.getDeptId()); + + // 更新接收员工信息(不更新密码) + updateReqVO.setPassword(null); + + // 更新用户 + adminUserService.updateUser(updateReqVO); + } + + /** + * 禁用用户 + * + * @param userId 用户ID + */ + private void disableUser(Long userId) { + adminUserService.updateUserStatus(userId, CommonStatusEnum.DISABLE.getStatus()); + } + + /** + * 转换Set为HashSet + * + * @param set 原始Set + * @param mapper 映射函数 + * @param 元素类型 + * @param 结果类型 + * @return HashSet + */ + private Set convertSet(Set set, java.util.function.Function mapper) { + return CollUtil.newHashSet(CollUtil.map(set, mapper, true)); + } + +} \ No newline at end of file -- Gitee From 15ca2728bb128cf1e99bed2a7a392b59d0dd4be9 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 31 Oct 2025 17:01:30 +0800 Subject: [PATCH 18/50] =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/usertransfer/UserTransferServiceImpl.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java index 96d4abf565..18085aee16 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java @@ -90,11 +90,9 @@ public class UserTransferServiceImpl implements UserTransferService { * @param toUserId 接收员工ID */ private void transferDeptAndPostData(Long fromUserId, Long toUserId) { - // 获取离职员工的岗位 - Set fromUserPosts = convertSet(userPostMapper.selectListByUserId(fromUserId), UserPostDO::getPostId); - - // 获取接收员工的岗位 - Set toUserPosts = convertSet(userPostMapper.selectListByUserId(toUserId), UserPostDO::getPostId); + // 修改这两行为使用 CollUtil 工具类直接转换 + Set fromUserPosts = CollUtil.newHashSet(CollUtil.map(userPostMapper.selectListByUserId(fromUserId), UserPostDO::getPostId, true)); + Set toUserPosts = CollUtil.newHashSet(CollUtil.map(userPostMapper.selectListByUserId(toUserId), UserPostDO::getPostId, true)); // 合并岗位(去重) toUserPosts.addAll(fromUserPosts); -- Gitee From 35b4d89fc7cf2da22939fac2a87711224b6cc545 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 3 Nov 2025 11:01:14 +0800 Subject: [PATCH 19/50] =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/custom/controller/admin/ent/vo/EntRespVO.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java index e94f171fd7..a9ce8fad35 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java @@ -10,6 +10,9 @@ import lombok.Data; @ExcelIgnoreUnannotated public class EntRespVO { + @Schema(description = "企业id", example = "123") + private String id; + @Schema(description = "企业名称", example = "张三") @ExcelProperty("企业名称") private String name; -- Gitee From 420c263ab66e5cc0f96bb04e9d0b98d588b3b006 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 4 Nov 2025 10:21:27 +0800 Subject: [PATCH 20/50] =?UTF-8?q?=E7=A6=BB=E8=81=8C=E5=91=98=E5=B7=A5?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=BD=AC=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/api/user/AdminUserApiImpl.java | 5 +- .../system/service/user/AdminUserService.java | 9 ++ .../service/user/AdminUserServiceImpl.java | 91 +++++++++++ .../usertransfer/UserTransferService.java | 19 --- .../usertransfer/UserTransferServiceImpl.java | 141 ------------------ 5 files changed, 101 insertions(+), 164 deletions(-) delete mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java delete mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java index b63621a4ab..5796cc2fe1 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; -import cn.iocoder.yudao.module.system.service.usertransfer.UserTransferService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -32,8 +31,6 @@ public class AdminUserApiImpl implements AdminUserApi { private AdminUserService userService; @Resource private DeptService deptService; - @Resource - private UserTransferService userTransferService; @Override public AdminUserRespDTO getUser(Long id) { @@ -88,7 +85,7 @@ public class AdminUserApiImpl implements AdminUserApi { @Override public boolean transferUserData(Long fromUserId, Long toUserId) { - return userTransferService.transferUserData(fromUserId, toUserId); + return userService.transferUserData(fromUserId, toUserId); } } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java index 2a32dc148e..fb4079aec0 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java @@ -214,4 +214,13 @@ public interface AdminUserService { */ boolean isPasswordMatch(String rawPassword, String encodedPassword); + /** + * 转移员工数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + * @return 转移结果 + */ + boolean transferUserData(Long fromUserId, Long toUserId); + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index d94523862a..2fced54d91 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -533,4 +533,95 @@ public class AdminUserServiceImpl implements AdminUserService { return passwordEncoder.encode(password); } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean transferUserData(Long fromUserId, Long toUserId) { + // 1. 验证用户是否存在 + AdminUserDO fromUser = getUser(fromUserId); + if (fromUser == null) { + throw new RuntimeException("离职员工不存在"); + } + + AdminUserDO toUser = getUser(toUserId); + if (toUser == null) { + throw new RuntimeException("接收员工不存在"); + } + + // 2. 转移角色数据 + transferRoleData(fromUserId, toUserId); + + // 3. 转移部门和岗位数据 + transferDeptAndPostData(fromUserId, toUserId); + + // 4. 禁用离职员工 + disableUser(fromUserId); + + return true; + } + + /** + * 转移角色数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + */ + private void transferRoleData(Long fromUserId, Long toUserId) { + // 获取离职员工的角色 + Set fromUserRoles = permissionService.getUserRoleIdListByUserId(fromUserId); + + // 获取接收员工的角色 + Set toUserRoles = permissionService.getUserRoleIdListByUserId(toUserId); + + // 合并角色(去重) + toUserRoles.addAll(fromUserRoles); + + // 更新接收员工的角色 + permissionService.assignUserRole(toUserId, toUserRoles); + + // 清空离职员工的角色 + permissionService.assignUserRole(fromUserId, CollUtil.newHashSet()); + } + + /** + * 转移部门和岗位数据 + * + * @param fromUserId 离职员工ID + * @param toUserId 接收员工ID + */ + private void transferDeptAndPostData(Long fromUserId, Long toUserId) { + // 修改这两行为使用 CollUtil 工具类直接转换 + Set fromUserPosts = CollUtil.newHashSet(CollUtil.map(userPostMapper.selectListByUserId(fromUserId), UserPostDO::getPostId, true)); + Set toUserPosts = CollUtil.newHashSet(CollUtil.map(userPostMapper.selectListByUserId(toUserId), UserPostDO::getPostId, true)); + + // 合并岗位(去重) + toUserPosts.addAll(fromUserPosts); + + // 获取离职员工的部门信息 + AdminUserDO fromUser = getUser(fromUserId); + AdminUserDO toUser = getUser(toUserId); + + // 创建UserSaveReqVO对象,更新接收员工的部门和岗位 + UserSaveReqVO updateReqVO = BeanUtils.toBean(toUser, UserSaveReqVO.class); + updateReqVO.setId(toUserId); + updateReqVO.setPostIds(toUserPosts); + + // 如果需要将离职员工的部门也转移给接收员工,则取消下面的注释 + // updateReqVO.setDeptId(fromUser.getDeptId()); + + // 更新接收员工信息(不更新密码) + updateReqVO.setPassword(null); + + // 更新用户 + updateUser(updateReqVO); + } + + /** + * 禁用用户 + * + * @param userId 用户ID + */ + private void disableUser(Long userId) { + updateUserStatus(userId, CommonStatusEnum.DISABLE.getStatus()); + } } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java deleted file mode 100644 index f477ceda3d..0000000000 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferService.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.system.service.usertransfer; - -/** - * 用户数据转移 Service 接口 - * - * @author 芋道源码 - */ -public interface UserTransferService { - - /** - * 转移员工数据 - * - * @param fromUserId 离职员工ID - * @param toUserId 接收员工ID - * @return 转移结果 - */ - boolean transferUserData(Long fromUserId, Long toUserId); - -} \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java deleted file mode 100644 index 18085aee16..0000000000 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usertransfer/UserTransferServiceImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -package cn.iocoder.yudao.module.system.service.usertransfer; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; -import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; -import cn.iocoder.yudao.module.system.service.permission.PermissionService; -import cn.iocoder.yudao.module.system.service.user.AdminUserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.annotation.Resource; -import java.util.Set; - -/** - * 用户数据转移 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Slf4j -public class UserTransferServiceImpl implements UserTransferService { - - @Resource - private AdminUserService adminUserService; - - @Resource - private PermissionService permissionService; - - @Resource - private UserPostMapper userPostMapper; - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean transferUserData(Long fromUserId, Long toUserId) { - // 1. 验证用户是否存在 - AdminUserDO fromUser = adminUserService.getUser(fromUserId); - if (fromUser == null) { - throw new RuntimeException("离职员工不存在"); - } - - AdminUserDO toUser = adminUserService.getUser(toUserId); - if (toUser == null) { - throw new RuntimeException("接收员工不存在"); - } - - // 2. 转移角色数据 - transferRoleData(fromUserId, toUserId); - - // 3. 转移部门和岗位数据 - transferDeptAndPostData(fromUserId, toUserId); - - // 4. 禁用离职员工 - disableUser(fromUserId); - - return true; - } - - /** - * 转移角色数据 - * - * @param fromUserId 离职员工ID - * @param toUserId 接收员工ID - */ - private void transferRoleData(Long fromUserId, Long toUserId) { - // 获取离职员工的角色 - Set fromUserRoles = permissionService.getUserRoleIdListByUserId(fromUserId); - - // 获取接收员工的角色 - Set toUserRoles = permissionService.getUserRoleIdListByUserId(toUserId); - - // 合并角色(去重) - toUserRoles.addAll(fromUserRoles); - - // 更新接收员工的角色 - permissionService.assignUserRole(toUserId, toUserRoles); - - // 清空离职员工的角色 - permissionService.assignUserRole(fromUserId, CollUtil.newHashSet()); - } - - /** - * 转移部门和岗位数据 - * - * @param fromUserId 离职员工ID - * @param toUserId 接收员工ID - */ - private void transferDeptAndPostData(Long fromUserId, Long toUserId) { - // 修改这两行为使用 CollUtil 工具类直接转换 - Set fromUserPosts = CollUtil.newHashSet(CollUtil.map(userPostMapper.selectListByUserId(fromUserId), UserPostDO::getPostId, true)); - Set toUserPosts = CollUtil.newHashSet(CollUtil.map(userPostMapper.selectListByUserId(toUserId), UserPostDO::getPostId, true)); - - // 合并岗位(去重) - toUserPosts.addAll(fromUserPosts); - - // 获取离职员工的部门信息 - AdminUserDO fromUser = adminUserService.getUser(fromUserId); - AdminUserDO toUser = adminUserService.getUser(toUserId); - - // 创建UserSaveReqVO对象,更新接收员工的部门和岗位 - UserSaveReqVO updateReqVO = BeanUtils.toBean(toUser, UserSaveReqVO.class); - updateReqVO.setId(toUserId); - updateReqVO.setPostIds(toUserPosts); - - // 如果需要将离职员工的部门也转移给接收员工,则取消下面的注释 - // updateReqVO.setDeptId(fromUser.getDeptId()); - - // 更新接收员工信息(不更新密码) - updateReqVO.setPassword(null); - - // 更新用户 - adminUserService.updateUser(updateReqVO); - } - - /** - * 禁用用户 - * - * @param userId 用户ID - */ - private void disableUser(Long userId) { - adminUserService.updateUserStatus(userId, CommonStatusEnum.DISABLE.getStatus()); - } - - /** - * 转换Set为HashSet - * - * @param set 原始Set - * @param mapper 映射函数 - * @param 元素类型 - * @param 结果类型 - * @return HashSet - */ - private Set convertSet(Set set, java.util.function.Function mapper) { - return CollUtil.newHashSet(CollUtil.map(set, mapper, true)); - } - -} \ No newline at end of file -- Gitee From 3b37f47e39a4cad4c682c2a9c2490316c6c0c0ef Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 4 Nov 2025 10:43:24 +0800 Subject: [PATCH 21/50] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=97=B6=E8=BF=94=E5=9B=9E=E7=A7=9F=E6=88=B7?= =?UTF-8?q?Id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/user/vo/profile/UserProfileRespVO.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java index dc9d72e5d3..b9c264bc03 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java @@ -43,6 +43,9 @@ public class UserProfileRespVO { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式") private LocalDateTime createTime; + @Schema(description = "租户Id", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") + private Long tenantId; + /** * 所属角色 */ -- Gitee From 9c7ef5ff2bb977bed7a0cca1e3b638de749852fb Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 5 Nov 2025 12:05:13 +0800 Subject: [PATCH 22/50] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E9=99=84=E4=BB=B6=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...71\347\233\256\350\257\264\346\230\216.md" | 7 ++++- .../controller/admin/ent/EntController.java | 26 ++++++++++++++++--- .../module/custom/service/ent/EntService.java | 15 +++++++++++ .../custom/service/ent/EntServiceImpl.java | 4 +-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" index 7b82daef1b..bc414a0a45 100644 --- "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" +++ "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" @@ -1,4 +1,9 @@ ## 新增模块 1、在全局pom.xml中放开模块注释 2、在server/pom.xml中引入依赖 -3、导入对于木块的sql \ No newline at end of file +3、导入对于木块的sql + +## 数据权限配置 +1、创建需要数据权限的表时,必须有userId和deptId两个字段 +2、注册数据权限,在 CustomDataPermissionConfiguration 中注册对应userId和deptId的2个字段 +3、重启项目 \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index 6130011517..6d706a3e14 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -116,16 +116,16 @@ public class EntController { // ==================== 子表(企业信息) ==================== @GetMapping("/ent-info/list-by-ent-id") - @Operation(summary = "获得企业信息列表") + @Operation(summary = "获取企业附件列表") @Parameter(name = "entId", description = "所属企业Id") - @PreAuthorize("@ss.hasPermission('custom:ent:query')") + @PreAuthorize("@ss.hasPermission('custom:entInfo:query')") public CommonResult> getEntInfoListByEntId(@RequestParam("entId") Long entId) { return success(entService.getEntInfoListByEntId(entId)); } - @PostMapping("/import-attachment") + @PostMapping("/info/import") @Operation(summary = "导入企业附件") - @PreAuthorize("@ss.hasPermission('custom:ent:importFile')") + @PreAuthorize("@ss.hasPermission('custom:entInfo:import')") @ApiAccessLog(operateType = IMPORT) public CommonResult importEntAttachment( @RequestParam("entId") Long entId, @RequestParam("entName") String entName, @@ -137,4 +137,22 @@ public class EntController { return success("导入成功"); } + + @DeleteMapping("/info/delete") + @Operation(summary = "删除企业附件") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('custom:entInfo:delete')") + public CommonResult deleteEntInfo(@RequestParam("id") Long id) { + entService.deleteEntInfoByEntId(id); + return success(true); + } + + @DeleteMapping("/info/delete-list") + @Parameter(name = "ids", description = "编号", required = true) + @Operation(summary = "批量删除企业附件") + @PreAuthorize("@ss.hasPermission('custom:entInfo:delete')") + public CommonResult deleteEntInfoList(@RequestParam("ids") List ids) { + entService.deleteEntInfoByEntIds(ids); + return success(true); + } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 690d323f9f..86fd729f59 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -91,4 +91,19 @@ public interface EntService { */ Long createEntInfo(Long entId, String entName, String attachmentPath); + + + /** + * 删除企业附件 + * + * @param id 编号 + */ + void deleteEntInfoByEntId(Long id); + + /** + * 批量删除企业附件 + * + * @param ids 编号 + */ + void deleteEntInfoByEntIds(List ids); } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 0e26adc24e..6c6a64c3ff 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -128,11 +128,11 @@ public class EntServiceImpl implements EntService { return -1L; } - private void deleteEntInfoByEntId(Long entId) { + public void deleteEntInfoByEntId(Long entId) { entInfoMapper.deleteByEntId(entId); } - private void deleteEntInfoByEntIds(List entIds) { + public void deleteEntInfoByEntIds(List entIds) { entInfoMapper.deleteByEntIds(entIds); } -- Gitee From 2afbb1e2990866ac8f190571211e77c852358475 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 5 Nov 2025 15:43:22 +0800 Subject: [PATCH 23/50] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=80=E5=B1=9E=E5=AE=A2=E6=88=B7=E7=BB=8F?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/custom/controller/admin/ent/vo/EntRespVO.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java index a9ce8fad35..bbb1477a40 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java @@ -5,6 +5,8 @@ import cn.idev.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import javax.validation.constraints.NotNull; + @Schema(description = "管理后台 - 企业 Response VO") @Data @ExcelIgnoreUnannotated @@ -25,6 +27,10 @@ public class EntRespVO { @ExcelProperty("说明") private String remark; + @Schema(description = "所属客户经理") + @NotNull(message = "所属客户经理不能为空") + private Long userId; + @Schema(description = "状态(0正常 1停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @ExcelProperty("状态(0正常 1停用)") private Integer status; -- Gitee From f3e3a5258296fc963786f79d7d073bc250f46484 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 5 Nov 2025 18:14:39 +0800 Subject: [PATCH 24/50] =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=8F=AA=E9=9C=80=E8=A6=81=E4=BF=9D=E5=AD=98=E4=BB=8E=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=BC=80=E5=A7=8B=E7=9A=84=E5=90=8E=E5=8D=8A=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/custom/service/ent/EntServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 6c6a64c3ff..28a3bafe2f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -118,7 +118,10 @@ public class EntServiceImpl implements EntService { entInfoDO.setStatus(-1); if (attachmentPath.endsWith(".wav") || attachmentPath.endsWith(".mp3")) entInfoDO.setSource(2); - entInfoDO.setPath(attachmentPath); + if (attachmentPath.contains("/infra/file/")) + entInfoDO.setPath(attachmentPath.substring(attachmentPath.indexOf("/infra/file/") + 13)); + else + entInfoDO.setPath(attachmentPath); entInfoMapper.insert(entInfoDO); -- Gitee From e5298a9c97ec55853b5a32c7786040d871cfbd48 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 6 Nov 2025 09:53:23 +0800 Subject: [PATCH 25/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=98=BE=E7=A4=BA=EF=BC=9B=20=E5=81=87?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=BC=81=E4=B8=9A=E9=99=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...71\347\233\256\350\257\264\346\230\216.md" | 5 +- .../controller/admin/ent/EntController.http | 94 +++++++++++++++++++ .../controller/admin/ent/EntController.java | 14 ++- .../admin/ent/vo/EntInfoPageReqVO.java | 3 + .../custom/dal/mysql/ent/EntInfoMapper.java | 1 + .../module/custom/service/ent/EntService.java | 16 ++-- .../custom/service/ent/EntServiceImpl.java | 20 +++- 7 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http diff --git "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" index bc414a0a45..4b444539d4 100644 --- "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" +++ "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" @@ -6,4 +6,7 @@ ## 数据权限配置 1、创建需要数据权限的表时,必须有userId和deptId两个字段 2、注册数据权限,在 CustomDataPermissionConfiguration 中注册对应userId和deptId的2个字段 -3、重启项目 \ No newline at end of file +3、重启项目 + +## 文件目录设置 +如果是本地存储,在 基础设施/文件管理/文件配置 下配置对应的目录 \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http new file mode 100644 index 0000000000..c6f6ba17fe --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http @@ -0,0 +1,94 @@ +### 创建企业测试 +POST {{baseUrl}}/custom/ent/create +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "name": "测试企业", + "creditCode": "91110000123456789X", + "legalPerson": "张三", + "registeredCapital": "1000万", + "establishDate": "2020-01-01", + "address": "北京市朝阳区" +} + +### 更新企业测试 +PUT {{baseUrl}}/custom/ent/update +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "id": 1, + "name": "更新后的企业名称", + "creditCode": "91110000123456789X", + "legalPerson": "李四", + "registeredCapital": "2000万", + "establishDate": "2020-01-01", + "address": "北京市海淀区" +} + +### 删除企业测试 +DELETE {{baseUrl}}/custom/ent/delete?id=7 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 批量删除企业测试 +DELETE {{baseUrl}}/custom/ent/delete-list?ids=1,2,3 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 获得企业详情测试 +GET {{baseUrl}}/custom/ent/get?id=7 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 获得企业分页列表测试 +GET {{baseUrl}}/custom/ent/page?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 获得企业分页列表测试(带搜索条件) +GET {{baseUrl}}/custom/ent/page?pageNo=1&pageSize=10&name=腾讯 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 导入企业Excel测试 +POST {{baseUrl}}/custom/ent/import-excel +Content-Type: multipart/form-data; boundary=WebAppBoundary +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +--WebAppBoundary +Content-Disposition: form-data; name="file"; filename="企业数据.xlsx" +Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + +< D:\企业数据.xlsx +--WebAppBoundary-- + +### 导出企业Excel测试 +GET {{baseUrl}}/custom/ent/export-excel?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 获取企业附件列表测试 +GET {{baseUrl}}/custom/ent/ent-info/list-by-ent-id?pageNo=2&pageSize=10&entId=7 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 导入企业附件测试 +POST {{baseUrl}}/custom/ent/info/import?entId=1&entName=腾讯公司&attachmentPath=/upload/2024/01/file.pdf +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 删除企业附件测试 +DELETE {{baseUrl}}/custom/ent/info/delete?id=1 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 批量删除企业附件测试 +DELETE {{baseUrl}}/custom/ent/info/delete-list?ids=1,6,7 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index 6d706a3e14..7947aa4fb1 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -6,10 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntRespVO; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.ent.EntService; @@ -119,8 +116,9 @@ public class EntController { @Operation(summary = "获取企业附件列表") @Parameter(name = "entId", description = "所属企业Id") @PreAuthorize("@ss.hasPermission('custom:entInfo:query')") - public CommonResult> getEntInfoListByEntId(@RequestParam("entId") Long entId) { - return success(entService.getEntInfoListByEntId(entId)); + public CommonResult> getEntPage(@Valid EntInfoPageReqVO infoPageReqVO) { + PageResult pageResult = entService.getEntInfoPage(infoPageReqVO); + return success(BeanUtils.toBean(pageResult, EntInfoRespVO.class)); } @PostMapping("/info/import") @@ -143,7 +141,7 @@ public class EntController { @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('custom:entInfo:delete')") public CommonResult deleteEntInfo(@RequestParam("id") Long id) { - entService.deleteEntInfoByEntId(id); + entService.deleteEntInfo(id); return success(true); } @@ -152,7 +150,7 @@ public class EntController { @Operation(summary = "批量删除企业附件") @PreAuthorize("@ss.hasPermission('custom:entInfo:delete')") public CommonResult deleteEntInfoList(@RequestParam("ids") List ids) { - entService.deleteEntInfoByEntIds(ids); + entService.deleteEntInfoByIds(ids); return success(true); } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java index 53915ba90d..a76a1868d2 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoPageReqVO.java @@ -20,6 +20,9 @@ public class EntInfoPageReqVO extends PageParam { @Schema(description = "状态(-1 待上传/待办 0正常 1停用 2 完结)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") private Integer status; + @Schema(description = "企业Id") + private Long entId; + @Schema(description = "租户") private Long tenantId; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java index 058c39f76d..5aab66ee3a 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntInfoMapper.java @@ -36,6 +36,7 @@ public interface EntInfoMapper extends BaseMapperX { .eqIfPresent(EntInfoDO::getSource, reqVO.getSource()) .eqIfPresent(EntInfoDO::getStatus, reqVO.getStatus()) .eqIfPresent(EntInfoDO::getTenantId, reqVO.getTenantId()) + .eqIfPresent(EntInfoDO::getEntId, reqVO.getEntId()) .orderByAsc(EntInfoDO::getUpdateTime)); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 86fd729f59..1b8fbe40b3 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; @@ -92,18 +93,19 @@ public interface EntService { Long createEntInfo(Long entId, String entName, String attachmentPath); - /** - * 删除企业附件 + * 批量删除企业附件 * - * @param id 编号 + * @param ids 编号 */ - void deleteEntInfoByEntId(Long id); + void deleteEntInfoByIds(List ids); /** - * 批量删除企业附件 + * 删除企业附件 * - * @param ids 编号 + * @param id 编号 */ - void deleteEntInfoByEntIds(List ids); + void deleteEntInfo(Long id); + + PageResult getEntInfoPage(@Valid EntInfoPageReqVO infoPageReqVO); } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 28a3bafe2f..326d967512 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; @@ -131,14 +132,29 @@ public class EntServiceImpl implements EntService { return -1L; } - public void deleteEntInfoByEntId(Long entId) { + private void deleteEntInfoByEntId(Long entId) { entInfoMapper.deleteByEntId(entId); } - public void deleteEntInfoByEntIds(List entIds) { + private void deleteEntInfoByEntIds(List entIds) { entInfoMapper.deleteByEntIds(entIds); } + @Override + public void deleteEntInfoByIds(List ids) { + entInfoMapper.deleteByIds(ids); + } + + @Override + public void deleteEntInfo(Long id) { + entInfoMapper.deleteById(id); + } + + @Override + public PageResult getEntInfoPage(EntInfoPageReqVO infoPageReqVO) { + return entInfoMapper.selectPage(infoPageReqVO); + } + @Override public void importEntList(List importReqVOList) throws IOException { List entList = BeanUtils.toBean(importReqVOList, EntDO.class); -- Gitee From 16f3f30d36f7b4216d7f3acc3c54f8f06c8aa51b Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 6 Nov 2025 11:53:31 +0800 Subject: [PATCH 26/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=EF=BC=9B=20=E5=AF=B9=E8=AF=9D=E5=92=8C?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\345\205\245\346\250\241\346\235\277.xlsx" | Bin 0 -> 8996 bytes yudao-module-custom/pom.xml | 11 +++++ .../api/fastgpt/FastGptAuthInterceptor.java | 2 +- .../controller/admin/api/ChatController.http | 20 ++++---- .../controller/admin/api/ChatController.java | 44 +++++++++++------- .../admin/api/vo/ChatRecordPageReqVO.java | 17 ++----- .../controller/admin/ent/EntController.http | 4 +- .../controller/admin/ent/EntController.java | 1 + .../admin/ent/vo/EntInfoRespVO.java | 3 ++ .../dal/dataobject/chat/ChatRecordDO.java | 4 -- .../dal/mysql/chat/ChatRecordMapper.java | 25 ++++------ .../custom/service/chat/ChatService.java | 18 +++++-- .../custom/service/chat/ChatServiceImpl.java | 29 ++++++------ .../service/fastgpt/FastGptService.java | 15 +++++- 14 files changed, 108 insertions(+), 85 deletions(-) create mode 100644 "docs/\344\274\201\344\270\232\344\277\241\346\201\257\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" diff --git "a/docs/\344\274\201\344\270\232\344\277\241\346\201\257\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" "b/docs/\344\274\201\344\270\232\344\277\241\346\201\257\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" new file mode 100644 index 0000000000000000000000000000000000000000..87f680ef1c80ac77f64fe7f50f0bd5cbb87967a6 GIT binary patch literal 8996 zcmeHtgp7Va5 z<2m17@V+zGHP7rlbFXV=o^`K#&03?Tgn&o@Kn9=!000_*`Chh-2^;_rj|c$Z15n`& zrJS8SKu#W}TD~qIcViA8M+fRWM0loL06gsc|1JN;PXLlUsM5&=lsT8bky>L{oUar| zeY6Yi#bZ$yZR<+vDY7ukv9-O=iM|F(z9qC1sK6VU_vbkrvZ-`(tN};$G-_bRhV->* zn~)1|cK7Z)Y$Ya5bTiQ3&m|xi=O#5ZePNXeNOP_2)Zmj|mQaL{*$@(m2Crt%_3Gd* z_S9n*8GLxs2W?+c(fNw|c;Mp~;OZFugqb?t^Yicmq~#djh-g6xkkO) zRjN{pJ5wF+F0uPk=$SOa^oD>=K#M`sB1{ekSwKBboJyE-A z;3u>(WjD+b51|2%&j8fyW2=Ztc$`L_cZm_^KOH>ipkBCsxQtKHwMcH+kn<|uQiX)p zk!`7i;r@}FmHAI<Ja{So-kBqN4eKcn)e2`c?0wKB@?~;hrD_2hx76!KzSy#w%7p~Xj>Ev0uoT4w2SMw{D z;<^uSmHHRyWWO9rR{%#ibji^03n(IpMAAbHdX)9QnqQQ{O-gF+mPC}-3gm7jjbsK* zrxb2tiA6nmx-*_m*ynC;JzE;k=Rk9MNv5r7^Tf8?JjYFt*4Nb1q3u{Yvlag~fL$f4 zPlJ{Z?}BGUuJ>W?X^=q`-{D}kZ{gWiKr9>{M2mrtUj6yiD zC*$M5>Fw-hZ|>}D|3kMR+Cxs6;=mT&Euq|Id_Ari+4h7WZ6#aKLtD3Kf)X>w8srp? z?HP4~kM$?>QmUKGobm3gr>?XGzOlpR7L11vm1CZz&W1R*reBiOc&{r!vNaNTbFNUG z)8+GR+oV!nK0i6keLo$aqLdgxzs`I*MmjX6vbvkOOG=zXvw@EV7@wC*NKUsV@db{B ztr-gEGyBLI(MyAwdt`Ia6g!0Tv=}PP(hKeB!wPIdmZCSF9gIaGRdt5iN@~^vaQPVU zzHi_T9^tX0gem4PkbI|bOTS4Ka!Q|p`C|A<_S|8kvrFm$GGH+ zM`KX>{b@2jtysP7Ig9E(aiKAOVL6ME_(8E-K30LFktT`nM4j-dX#O@*l;YD*Q_0;+ z)mteq62|oUeW}}m=L9SiWEANgkF~%PDHEow#qf=kuE2^KIWg7r+=JcVFlq`Cg{1^D zQEHy?LfSL+X`u(GchSg47bD-!uQ_$kr(5>|=hIzV(GR))@kPMY2jgHH>^P_OsXEy2&iwvn=)f~p|p;4^ikf%;`X)%60D%&*De z_GBCDN28NBmQ2ryS)NS}Vv}Znvu9`0%%rN|TjQnLOwrUP6J;gl@@mf_+@m8JKwVp# zfK1K_4fNo;^mr|3%@L=ptmn&R31X;-Jf@~={z|9PgP@? zCI3JzS=gw3({|Pm zF?fi6_zwF*NX|pYLkOtmyCo06a!B}iJ6qeuyBOFR?)dehiG6H21drD*m>!Zw<`>dR zpVW^>@YxM>dGgq~k5Uf60_|YcTJTkv=1vK?so6iq=G z%NH>AeS(JdqFv?;^{DB_O4)bVzCHg6^BYC6(u1N$A^{N|)RBF1R6)#%?%(p#8@2bB zJtVIOm|LMoY=bBJR%`velZ&vK{%4$x3yJYzgvD6~6aat(cE+D^*4+jK@^I(;IdK07 zxLHZcuQIuS5&N&NsMr=mL&6-uk~-S8>gPJ1(7jo1sd94*J0a7et3{!-0IdPDPZCSq zHNIcHcg)V}rehr11#L6H4C+tO_&Sm+^$AZlUUff{j6QB+uIIrbI03vA9Tp2r+l@1B zBx}Q`=PS*vK6e~W!+&J6NBc!Gl=pQ-hJQdc>$(9(6DE~y_xR%nyh9)38OX zp08XUArYYBd`M6mf4woD=2?3{8fi}7P@YTYw)~AqG*kmghQ+ltRF<=C82xy*AV|Y7 z418y$fJV5;bic6E0ydg_0Fq0y(q;7Z^?_$B7gv8w2Fi9cBcLsRXy6q@MntlBt^?ZK z2CL-vcUqFY`VyD;@NsKAWg=dM>(}qof$DD3r$CPf)Fj@$m+E|<6L&;_!kK_}pD@hT zbHnxt1fDy{hms zA<$rYVPxHllDF+EpJqc{0EbH$+hU1vnraqAx#qllmpTgB$HXggRy}=>-}n2~bN;$$ zzM*#X4lW?#s43btoDS}0!De?7%G*{T)DdqYyIh~({wvEGzcV(klmf6uSanM$%9uzM z#qErFN5LhcNzXn)O#u6$bPMte{UZMZyPUJeoUHXU1ODV7e_HQQ(j4XJ&W@`u2{nv* zoi)(rx91sYjyJG~`%iKyiv_cV!4jBYvL7$tKgs1`19Aj${@niL)sFs%GnAj81!v}- z`umcM*E~1n|W-Jael{&x6)GYlE`F2jJ-*@a>>0&%c0nv!SGPq)LQ}e zn!Q&ubPo+Jp9Y{}{RRSh?Z>4`Gs#c*n`Gof z=t3eT3W%nMQC7pGd6dLg>6OR0fow*Rv>Zzky{8jz3>R8WWcz)3qfSoZ_8-aMz{74-ip@YaFzvU)riAN4aN?zO+A7`}f(fqn9X#n)`v=SO}5F(!l2DNV7 zJj*QHji}ESOif6Nq>)x-dsxlLH>qN##vh|{MdJAE`!UiEN*MZkmy_byq2iJ2R#PJ z%iZTArgwKY-8hRkr-Qt)=S~qlf&tQapR=JC^Yy)WBpKmi53HT7xnpp-+0-yw*h^7> zk12ip!#`k6cUqxRS>|%`FSQ>fw4c%sr-1eRS_m1D57M~jEYcqVYAsC-!sG@$oc4tc zx9a>+vuCefs@c)|lp6(?<7Eyq^&k&55){A>xyFRky{XWQW6$@OkHVd{;OQr^=3N%1 zSHZ!xmzTA4AkeV&kjLvA9FPTSICiaHgnY4>;NKDzzF`Yg)=};o-ul< zCV3i6MS}~K&w*4%za)2oOV6%Np~l&Y;Xa zEum1#MvEM+I&GWhoctf6amdl>>6Mdy&GSH5^hYMwz)x#?c1mccKgnB$r%15{ALf-7(nvsVyjiA@46GU17J4HkZ4m;tBR!Fl)18z`g(4Z{Qd8&1E$67;zRpyTWbGtTc2&>5GV9k~ zZ!j*L$YqqO2qae|8=8vjt0qXmm=o7DX13YN@}4ipZS$Q$WEb4SJiBs9XUp}fGuomR zj70bp-FHNMXPgX5a2=Bq9!4mYX4DN0L(O`UOi!tc9i)lHr%jeuH@QWPO2uB!-#5@J zi=F7iKssXOLwizX&(1S`$<7o|N;vg?;AWwHJdvGdf${KUp7aaI3r8XvX($78DcR-m z(Z_FNS$K~-=TY_bgNPXlCB06iq_w4dh4Nxa2zg^T)}I(fe~)Sej?im`QId81yn8(6Q5ReQPk73I}A zDt{p2%2;I5;zL5C0=yP}OH`ugm$R-MU!DBizYTLA*I8O@&0I92(Z6)b$mW({neE~?5e*WHX%|0K2}Cs8aCuq9}XzT-`v zK!t7ax|~RcQ0#>MR;O|NWc##qai<13@Z7c{E+k>&NWBZbqu6Kn9^>CcE2QJhR|%F8 zD!_`!SbvC?yN9m>$o)qe_)cfgX_cQ~f%v-^aGupqPlH`*{27H@8$dHxTqD=eAkV@4 z0w^!|+R$TKx%FM~9?HUreaxoyi3W~gdsX(+3>gn!SSGWVG;Deb&X+5~(yVM-Ru0B} zU2T^pOmG2>95A%7WJ|zWRvd?a^Rc~bfU~vlg^0Uf(7OrR;P}EeIs3*J`j< zGDEd8cAlv1cBV~yz|o|9Yci6GIrnN07cw>QVd^WOv}`VA^2DMc{mtdO7FxKY!T63Y zoIJ*s^Q&C63hq+-r69b+vaXcKYoc8EcX5C=w~7 zsuoqxwv(n%l!widh3|vavmSBGMnXY$ym4&qleqKrq&Fo~`edMOuBd5*i$EUh%C{np z=vml@=fqBRh&aiSI6f`94IyYe)7h*Q>e}mBHAHy%v&-LGbUY7&${^t!tj7@vp^0IQ z8R@D|;oGK?kNpKeE%tAoowFx0s-s;ae{XMco8*sev&WNba$P=1wV^wM=Z7D7gA;po z2EKQGPq!qH0Pm`>E z8-6nHlMTD!Q4I_Q@VB7H%OmLrWvy@nt^fM!-h8u*X$m`n1!Eq;pRBVna|2mwdbrs- zS^q)4#398_abWO1cprG?B;aU^t1vDlaj-5yMNJQ77~SdF(b*}Hh}S7P14_#v5o#ei z#u_MPtPHO0PwBeEqUT`kP(+xuTw3XUth9af6+efq;b}+R z1wm$uGAA`CiVn$5V6fs@lFFEwx6RvfE|s6qqAv^!Ay?tZyHh{nN~YGNhb}$%Ji$-- zR+bAoTK@>BH@NPWriFH=k1Q?&#^^CsnRk@S-##hqP1m>CTw)g$8kW4^k2Y~y9hTz} zQMf|Ux9MMg?9_OiOV=~vJ!VO}Mt9D0_+UdqAZe`)<2Lr>m>GY~BW-WQyp`&e)#i@q zmsw*@-|UDMsyDALGf|qm{8dqOFU{ZTF})^MzoMc#+J^-E>x7gvpe!lEjy!{9_PDTG zilwuKnwztWJEw)S8|cs0+5bu?Fi#Cglu!-jB8k{%xI-BistR`;L)M!BF{Y+PjrrKm zIme-fS84aO*0x2Yk-pl!KHL1l`C_`a=%htFGfxLC4^H9%llq|KH#EQgnmeqcxNnL_ zl{)dc*q8?Ir*$iA(mp&A-fVz##Nf)IWA2nr$_=f?>YRx}(3(dqeQQ)#=Gk3hxJ4mg z6mUS{BHxFu`>cK~W%a>#0esx-c#Wrh>zb(u;^>$UwQ?cte7P6z?Im>AM-ZfV>~+F- z$<7Ot_OjT!NmO?8nG4OKH4@}lWW#S(Y8O_I542j z|Jxfyr=8&!ex|`q`&StPXB@nd@6C102&t9SH))p_W0;B4bK@1(C_GW*Y$M?vEg+zgll0$~vW z_*Vxsb8-2f|G^yZ&m}9d-3j`mSAukf9kKT!%ZVvMTu1#KtKUpHK*3KJBuK=f1nwg1 z&jF=UBJW^%%|Sid9^_ZOC{%%JI@1TOOv`Dk1VCpX;c0Q+NSP{bDIs54_?$G$Pq((# z@CWf&bXm432^>(Ovj(agCIxmQ1S{}Frh-o^Nk9Wkn2Oe_#GhHk5PIr;s2QW@8E z)8sQGs#x&%l-&LN_Uz9{RPW6{z7BsvI;c2{2o@vmdi24`76W9SnAfe3LH7J};-!kc z5NNhUadepgqCLhG88%ssr9cIZ=NbqK3KY4yJ-pv=ZdahPd;ZvovUfc{>kNy4g5L`n z0R=5Gj5U;Kyw>h5Z3~<*CBiu-!d~zv2%L+L`{!xZr<~0PIoAwKwRC1w&$;(FN;(Hj zS;F2sJ|OC@)WKC0Yzu^5gX;=qWTEMw?Z5G>iJmqQH9!|Jsf<)c7%n5Th6Rhz?rvUx zql3-NAN~vn&kj?wfBzogKlAsW@n619P*eK5fxmZ7{{#Fnrou?_mp8vE~delNBDvP6&h|1RPmh1cJ${9aS~WhDgGCxhL} z?-i!s4g8+p|1!W&^wYqvZ2x!Y?@7upD1`J6=${retrofit2.version} + + + org.apache.poi + poi + 5.2.3 + + + org.apache.poi + poi-ooxml + 5.2.3 + org.apache.poi poi-scratchpad diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java index 5f35d5469a..68a51b4d4c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptAuthInterceptor.java @@ -24,7 +24,7 @@ public class FastGptAuthInterceptor implements Interceptor { // 添加Authorization头 Request authenticatedRequest = originalRequest.newBuilder() - .header("Authorization", authToken) + .header("Authorization", "Bearer " + authToken) .header("Content-Type", "application/json") .build(); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 743b272964..71bf5a970c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -1,11 +1,11 @@ ### 流式对话接口测试 -POST {{baseUrl}}/custom/chat/stream?category=sales&chatId=test_chat_123&dataId=123&userContent=你好,请简单介绍一下你自己 +POST {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_12345&dataId=12345&userContent=腾讯公司最近发生了什么 Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} ### 获取对话记录列表测试 -POST {{baseUrl}}/custom/chat/list +GET {{baseUrl}}/custom/chat/list Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} @@ -16,7 +16,7 @@ tenant-id: {{adminTenantId}} } ### 获取某轮对话详情测试 -POST {{baseUrl}}/custom/chat/detail/test_chat_123 +GET {{baseUrl}}/custom/chat/detail?chatId=test_chat_123 Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} @@ -26,18 +26,14 @@ tenant-id: {{adminTenantId}} "pageSize": 10 } -### 获取另一轮对话详情测试 -POST {{baseUrl}}/custom/chat/detail/test_chat_456 -Content-Type: application/json + +### 删除对话记录测试 +DELETE {{baseUrl}}/custom/chat/delete?chatId=test_chat_123 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} -{ - "pageNum": 1, - "pageSize": 10 -} -### 删除对话记录测试 -DELETE {{baseUrl}}/custom/chat/test_chat_123 +### 批量删除对话记录测试 +DELETE {{baseUrl}}/custom/chat/delete-list?chatIds=test_chat_123,test_chat_456 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index faf40e9837..66b5f8ab11 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -1,11 +1,12 @@ package cn.iocoder.yudao.module.custom.controller.admin.api; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import cn.iocoder.yudao.module.custom.service.chat.ChatService; import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; @@ -14,12 +15,14 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import javax.validation.Valid; +import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -36,7 +39,7 @@ public class ChatController { private final ChatService chatService; @Operation(summary = "与fastgpt进行流式对话") - @PostMapping("/stream") + @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @Parameter(name = "category", description = "对话流程类型", required = true) @Parameter(name = "chatId", description = "每一个新对话的唯一id", required = true) @Parameter(name = "dataId", description = "每轮对话的id", required = true) @@ -77,38 +80,34 @@ public class ChatController { } @Operation(summary = "获取用户对话记录列表") - @PostMapping("/list") + @GetMapping("/list") @PreAuthorize("@ss.hasPermission('custom:chat:list')") - public CommonResult> getChatList( - @RequestBody @Valid PageParam pageReqVO) { + public CommonResult> getChatList(@Valid ChatRecordPageReqVO pageReqVO) { - Long tenantId = TenantContextHolder.getTenantId(); Long userId = SecurityFrameworkUtils.getLoginUserId(); - PageResult chatList = chatService.getChatPage(tenantId, userId, pageReqVO); - return success(chatList); + PageResult chatList = chatService.getChatPage(userId, pageReqVO); + return success(BeanUtils.toBean(chatList, ChatRecordVO.class)); } @Operation(summary = "获取某轮对话详情") - @GetMapping("/detail/{chatId}") + @GetMapping("/detail") @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") @PreAuthorize("@ss.hasPermission('custom:chat:detail')") - public CommonResult> getChatDetail(@RequestBody @Valid PageParam pageReqVO, - @PathVariable String chatId) { + public CommonResult> getChatDetail(@Valid ChatRecordPageReqVO pageReqVO) { - Long tenantId = TenantContextHolder.getTenantId(); Long userId = SecurityFrameworkUtils.getLoginUserId(); - PageResult chatDetail = chatService.getChatDetail(tenantId, userId, chatId, pageReqVO); - return success(chatDetail); + PageResult chatDetail = chatService.getChatDetail(userId, pageReqVO); + return success(BeanUtils.toBean(chatDetail, ChatRecordVO.class)); } @Operation(summary = "删除对话记录") - @DeleteMapping("/{chatId}") + @DeleteMapping("/delete") @Parameter(name = "chatId", description = "对话ID", required = true, example = "chat_123") @PreAuthorize("@ss.hasPermission('custom:chat:delete')") public CommonResult deleteChat( - @PathVariable String chatId) { + @RequestParam String chatId) { Long tenantId = TenantContextHolder.getTenantId(); Long userId = SecurityFrameworkUtils.getLoginUserId(); @@ -117,4 +116,17 @@ public class ChatController { return success(true); } + @Operation(summary = "批量删除对话记录") + @DeleteMapping("/delete-list") + @Parameter(name = "chatIds", description = "对话Id", required = true) + @PreAuthorize("@ss.hasPermission('custom:chat:delete')") + public CommonResult deleteChat(@RequestParam("chatIds") List chatIds) { + + Long tenantId = TenantContextHolder.getTenantId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + chatService.deleteChatByIds(tenantId, userId, chatIds); + return success(true); + } + } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java index 7da864e8ca..03d2ce2c52 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordPageReqVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.custom.controller.admin.api.vo; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -10,18 +11,8 @@ import lombok.Data; */ @Schema(description = "对话记录分页请求") @Data -public class ChatRecordPageReqVO { - - /** - * 页码 - */ - @Schema(description = "页码", example = "1") - private Integer pageNum = 1; - - /** - * 每页大小 - */ - @Schema(description = "每页大小", example = "10") - private Integer pageSize = 10; +public class ChatRecordPageReqVO extends PageParam { + @Schema(description = "对话Id", example = "1") + private String chatId; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http index c6f6ba17fe..209e12e621 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.http @@ -64,7 +64,7 @@ tenant-id: {{adminTenantId}} Content-Disposition: form-data; name="file"; filename="企业数据.xlsx" Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet -< D:\企业数据.xlsx +< D:\yudao-boot\docs\企业信息导入模板.xlsx --WebAppBoundary-- ### 导出企业Excel测试 @@ -73,7 +73,7 @@ Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} ### 获取企业附件列表测试 -GET {{baseUrl}}/custom/ent/ent-info/list-by-ent-id?pageNo=2&pageSize=10&entId=7 +GET {{baseUrl}}/custom/ent/ent-info/list-by-ent-id?pageNo=1&pageSize=10&entId=7 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index 7947aa4fb1..ef6ea9768f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -89,6 +89,7 @@ public class EntController { @PostMapping("/import-excel") @Operation(summary = "从 Excel 导入企业数据") + @Parameter(name = "file", description = "需要导入的文件") @PreAuthorize("@ss.hasPermission('custom:ent:import')") @ApiAccessLog(operateType = IMPORT) public CommonResult importEntExcel(@RequestParam("file") MultipartFile file) throws IOException { diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java index 5f15021646..21bd06f0df 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntInfoRespVO.java @@ -12,6 +12,9 @@ import java.time.LocalDateTime; @ExcelIgnoreUnannotated public class EntInfoRespVO { + @Schema(description = "企业附件id", example = "123") + private Long id; + @Schema(description = "资料名称", example = "芋艿") @ExcelProperty("资料名称") private String name; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java index a070726d51..2782b1466a 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java @@ -53,9 +53,5 @@ public class ChatRecordDO extends BaseDO { * 是否为标题 (0-否, 1-是,默认第一条为标题) */ private Boolean isTitle; - /** - * 状态 (normal-正常, deleted-已删除) - */ - private String status; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java index aff1d1bb6b..4d21553266 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/chat/ChatRecordMapper.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.custom.dal.mysql.chat; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import java.util.List; @@ -16,29 +15,23 @@ import java.util.List; @Mapper public interface ChatRecordMapper extends BaseMapperX { - default List selectChatRecordListByChatId(Long tenantId, Long userId, String chatId) { - return selectList(new LambdaQueryWrapper() - .eq(ChatRecordDO::getTenantId, tenantId) + default Long selectCountByChatId(Long tenantId, Long userId, String chatId) { + return selectCount(new LambdaQueryWrapper() .eq(ChatRecordDO::getUserId, userId) - .eq(ChatRecordDO::getChatId, chatId) - .eq(ChatRecordDO::getStatus, "normal") - .orderByAsc(ChatRecordDO::getCreateTime)); + .eq(ChatRecordDO::getChatId, chatId)); } - default Long selectCountByChatId(Long tenantId, Long userId, String chatId) { - return selectCount(new LambdaQueryWrapper() + default int deleteByChatId(String chatId, Long tenantId, Long userId) { + return delete(new LambdaQueryWrapper() .eq(ChatRecordDO::getTenantId, tenantId) .eq(ChatRecordDO::getUserId, userId) - .eq(ChatRecordDO::getChatId, chatId) - .eq(ChatRecordDO::getStatus, "normal")); + .eq(ChatRecordDO::getChatId, chatId)); } - default void updateStatusByChatId(Long tenantId, Long userId, String chatId, String status) { - update(new LambdaUpdateWrapper() + default int deleteByChatIds(List chatIds, Long tenantId, Long userId) { + return delete(new LambdaQueryWrapper() .eq(ChatRecordDO::getTenantId, tenantId) .eq(ChatRecordDO::getUserId, userId) - .eq(ChatRecordDO::getChatId, chatId) - .eq(ChatRecordDO::getStatus, "normal") - .set(ChatRecordDO::getStatus, status)); + .in(ChatRecordDO::getChatId, chatIds)); } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java index 21e0cbe302..5dae9f2163 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatService.java @@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.custom.service.chat; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import javax.validation.Valid; +import java.util.List; /** * 对话记录 Service @@ -28,22 +30,20 @@ public interface ChatService { /** * 分页获取对话列表 * - * @param tenantId 租户ID * @param userId 用户ID * @param pageReqVO 分页请求 * @return 对话记录分页结果 */ - PageResult getChatPage(Long tenantId, Long userId, @Valid PageParam pageReqVO); + PageResult getChatPage(Long userId, ChatRecordPageReqVO pageReqVO); /** * 获取某轮对话详情 * - * @param tenantId 租户ID * @param userId 用户ID - * @param chatId 对话ID + * @param pageReqVO 分页参数 * @return 对话详情 */ - PageResult getChatDetail(Long tenantId, Long userId, String chatId, @Valid PageParam pageReqVO); + PageResult getChatDetail(Long userId, ChatRecordPageReqVO pageReqVO); /** * 删除对话记录 @@ -54,4 +54,12 @@ public interface ChatService { */ void deleteChat(Long tenantId, Long userId, String chatId); + /** + * 批量删除对话记录 + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param chatIds 对话ID列表 + */ + void deleteChatByIds(Long tenantId, Long userId, List chatIds); } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java index bdd53e8035..3a7caeb6a1 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.custom.service.chat; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import cn.iocoder.yudao.module.custom.dal.mysql.chat.ChatRecordMapper; import lombok.extern.slf4j.Slf4j; @@ -11,6 +12,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.validation.Valid; +import java.util.List; /** * 对话记录 Service 实现 @@ -37,8 +39,7 @@ public class ChatServiceImpl implements ChatService { .setMessageId(msgId) .setContent(response) .setRole("assistant") - .setIsTitle(false) - .setStatus("normal"); + .setIsTitle(false); chatRecordMapper.insert(assistantRecord); @@ -57,8 +58,7 @@ public class ChatServiceImpl implements ChatService { .setMessageId(msgId) .setContent(userContent) .setRole("user") - .setIsTitle(isFirstRecord) // 第一条记录设置为标题 - .setStatus("normal"); + .setIsTitle(isFirstRecord); return (long) chatRecordMapper.insert(userRecord); @@ -67,30 +67,26 @@ public class ChatServiceImpl implements ChatService { } @Override - public PageResult getChatPage(Long tenantId, Long userId, @Valid PageParam pageReqVO) { + public PageResult getChatPage(Long userId, ChatRecordPageReqVO pageReqVO) { // 这里简化处理,实际应该使用分页查询 return chatRecordMapper.selectPage(pageReqVO, new LambdaQueryWrapperX() - .eqIfPresent(ChatRecordDO::getTenantId, tenantId) .eqIfPresent(ChatRecordDO::getUserId, userId) + .eq(ChatRecordDO::getIsTitle, true) .orderByDesc(ChatRecordDO::getCreateTime)); } - public PageResult getChatDetail(Long tenantId, Long userId, String chatId, @Valid PageParam pageReqVO) { + public PageResult getChatDetail(Long userId, ChatRecordPageReqVO pageReqVO) { // 首先获取总记录数 Long totalCount = chatRecordMapper.selectCount(new LambdaQueryWrapperX() - .eqIfPresent(ChatRecordDO::getTenantId, tenantId) .eqIfPresent(ChatRecordDO::getUserId, userId) - .eqIfPresent(ChatRecordDO::getChatId, chatId) - .eq(ChatRecordDO::getStatus, "normal")); + .eqIfPresent(ChatRecordDO::getChatId, pageReqVO.getChatId())); // 计算总页数 PageParam convertedPageParam = getPageParam(pageReqVO, (double) totalCount); return chatRecordMapper.selectPage(convertedPageParam, new LambdaQueryWrapperX() - .eqIfPresent(ChatRecordDO::getTenantId, tenantId) .eqIfPresent(ChatRecordDO::getUserId, userId) - .eqIfPresent(ChatRecordDO::getChatId, chatId) - .eq(ChatRecordDO::getStatus, "normal") + .eqIfPresent(ChatRecordDO::getChatId, pageReqVO.getChatId()) .orderByAsc(ChatRecordDO::getCreateTime)); // 按时间正序排列 } @@ -117,7 +113,12 @@ public class ChatServiceImpl implements ChatService { @Override public void deleteChat(Long tenantId, Long userId, String chatId) { - chatRecordMapper.updateStatusByChatId(tenantId, userId, chatId, "deleted"); + chatRecordMapper.deleteByChatId(chatId, tenantId, userId); + } + + @Override + public void deleteChatByIds(Long tenantId, Long userId, List chatIds) { + chatRecordMapper.deleteByChatIds(chatIds, tenantId, userId); } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 8e4edaed12..05d849a92e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -263,8 +263,18 @@ public class FastGptService { // 逐行读取响应内容 BufferedReader reader = new BufferedReader(responseBody.charStream()); String line; + int count = 0, done = 0; while ((line = reader.readLine()) != null && !sink.isCancelled()) { + log.info("\n接收到响应行: {}", line); + + if(done == 1 && count++ < 4) { + log.info("\n处理最后的汇总数据: {}", line); + continue; + } + + if(count > 4) break; + // 处理SSE格式的数据 if (!line.trim().isEmpty() && line.startsWith("data: ")) { String data = line.substring(6); // 移除"data: "前缀 @@ -272,7 +282,8 @@ public class FastGptService { // 检查是否为结束标记 if ("[DONE]".equals(data.trim())) { log.info("流式传输结束"); - break; + done = 1; + continue; } try { @@ -289,7 +300,7 @@ public class FastGptService { if (contentNode != null && contentNode.isTextual()) { String text = contentNode.asText(); if (!text.isEmpty()) { - log.info("接收到的内容片段: {}", text); + log.info("\n接收到的内容片段: {}", text); sink.next(text); // 通过flux发送内容片段 fullResponse.append(text); // 收集完整响应内容 } -- Gitee From 3b9da8cb28e0b69d5a84ec72e2e3e23f4aba0b32 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 6 Nov 2025 14:58:56 +0800 Subject: [PATCH 27/50] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E6=97=B6=E7=9A=84=E7=94=A8=E6=88=B7=E5=92=8C?= =?UTF-8?q?=E6=9C=BA=E6=9E=84=E8=AF=86=E5=88=AB=EF=BC=9B=20=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=88=97=E8=A1=A8=E8=BF=94=E5=9B=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\345\205\245\346\250\241\346\235\277.xlsx" | Bin 8996 -> 8936 bytes .../controller/admin/ent/EntController.java | 34 +++++++++++-- .../admin/ent/vo/EntImportReqVO.java | 4 +- .../controller/admin/ent/vo/EntRespVO.java | 3 ++ .../module/custom/convert/ent/EntConvert.java | 46 ++++++++++++++++++ .../custom/enums/ErrorCodeConstants.java | 4 +- .../module/custom/service/ent/EntService.java | 3 +- .../custom/service/ent/EntServiceImpl.java | 5 +- 8 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java diff --git "a/docs/\344\274\201\344\270\232\344\277\241\346\201\257\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" "b/docs/\344\274\201\344\270\232\344\277\241\346\201\257\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" index 87f680ef1c80ac77f64fe7f50f0bd5cbb87967a6..72652ef44188ca23c7aefde22f492b85600d3dfd 100644 GIT binary patch delta 2227 zcmZ9OX*3iJ7l3E6&DbU;$&wjNGafTS}=WtnF_m7x>ezBc=?3uFSm|q!8}z$R=srMnBi< zfclOfvn{FO(w;kHJ#G$To2BqaPyZYm`aLSGE1%lHh{XPx{s;uQRxNiqn5|uD%?rU#tJq!XVz;AA>Q11|0aFb!w@*_ckoLnjZjzPJBf)mF zLf=z%lC$~+j11DZ;*5HCUwrjZbjDoJsMvV6(n{Zm(h-=_6aZ^<48ziv%v^4I25Q{a z8PS3k5iRNp{n?iuNRJtu9Eqa(sIw;`O}<1Q1LhTk54<&(4JKENZ)Du5oalbB(dk?| zhHs3$C|Z%%OMC8|A4lwLZ}i#j_<;t$oRDiWpJg3=M_5a8W>IrH&*gm-R{FBPvcd?| z+)MX7S2`audQ?k?a5hgTAxWVj`EGf7O#WA09992Lw$@O>NlGkD{5X5Tx?Eatat14E z;%GN@%C}YAh?%hatf4rd;G~EcMXbOst4~>HzxK|_EjfH$X4Gq%p3p#=B~1Z>@A~rj zx|)S)T-ju7tcG&^+2t^bFn5o5E*A`NVB~;>8@Q08Ajqg?V}Lps0FY#0gphQ01{(oO zT2Wb-g>7mXcAxOL=r%?!{fZx()$N#-?8$+@uyXy-27c*RZb^zz%j@84r(cmAJ|t3w z!f3iJ)RkGmok95{x0l>dJG&&U90!8z3i|zm2<|eEwcO{G4?`Q1H6-w~fSA4)tGC&< zBWJ&`GFhQ5PQ)UZ1dsd?Om`b33$3D6oz$#nqq^aDWS1d{&B?`!p3o>3p}HvaWH)v| z_A^Dg`C(wL*yc3+_~LQ^f;ovG9PygZ0OC@_dN@_1Ng7^jG|MFKn+YA59wp4c)s^Eo z9JI8y4u92u{7Xy-y}u$YRd9Zsi_h_YA+%Ix^h^a!^TJtO@hhjhdu*6b%z-%UbJuHY za9bEdJUA_dG7}PdXxrpV18xQq?cR;WvqY`FX_XTV1=l$yo$uS{#Gl{sDdznM&=teQ zTvC5@W;5_0v5Aa=8EieJ{skz7mN?z>|CuzX3`Vn@{*x zhb`(#K3tL~8M{$FI7&l_QxCZ@nAvU7z|8lYw)i6{=M>%V@ranEj5YPeKfa=8QAOVi;y+z?^D)uG@6kV|I7X^ zA%084R8|~Q7(|edutDRwq0|EeDdvwG9=km}XKk7Mu=P>Gda$x9hp_j3Sa9vm!|7z%r5X zE1FGVpT?dcAx#7KMN)Z%LB?tU^j2n7*G}8=BU^zc%}Ua^BubwM6S3}HfPVG{tY=eY z^^Jdc30xdN7W4@YD!GKCRab3_`-`qO$-zcHiy%9zi^FZ%*n)W{0adqq#>v5!`7Qk6 zPP^5@gLtsu*+a>kdBl7{U$RBo)i|2PKd`z-(y`D|!gIGuBuv;Rv9u<5aBd|Kp{=>4 zot@7dwJ9Lif2`Ez1ik%qqD_BT;g_OS37fft{-Ex^sdwYB{TWt3(>1oIe!F#rvx7}% z$XhwJnU}{~nb&I8GtC&@)pzj$em?D+-w{@nTg+Z+gwYND)9{@e#tQ=J_5emE7|zqv zMy_qJm(~G-r>+h%l4MTMp_h88s7t3+EFJjyYDX8VJ#=hdOBIAo#Xjbh9L~JuS7J5N zF`48?>;|IApf?Wc@7}b`jH`ubNUIx0iA0^UysGlRn>uh&bS*FCnw{m5!~UGg0U&}m zNet~U?knZ+YxGf(3g=54@-N-$&WQBrQLD(8*#x0`8?WHb4@vAI5MG3VmBs3*%N=_w zQ7&j~QfO)jIod*4%T}Yj->#*HOFzh5Ucx*r>)=8u@1%-nz2+$O=Hi{XzW{myG#tFY*CZ3T%s&Li7fvqTQX!4 zg{;Lb-DG5^h-oA{lkHx))rWiU`{8_e&w0*=^Stl-oG%uombE=l>S&GBg?)Sg&;tbk zQ2+p_Qho?$Z`%oI7 zxd-P*ND`He+zB(gIW0`)CGBq)Rmx)0dVT#gR@F}5@V+w|AJ&$?Ez=Oiv1Sz6XBial z>?Q&&k9f zScz%GECfjoJUKsI$~1jG#0 z_kWocd$YD*7go%72cefAc%>bLqmp$3*BF3>`~GO^?!_JmvKz4>Z#?GNKG&IaT80s> zae)$YyB5Dvn8B99%L(<%t@^!h-y#We3XVoA+IJziIhC?CuCUD{#)M>UGxP2W){o!bquQg`q&XaYYPIJvhGM5$Q|&~uHtQ0xQJjjSe>-Xr>`ttPejSfSWjj7 z;86PgHlcHKeDcoIlaH1{YnEHDp16jxdR)0n zMWA7hIQXj$+U)dqGVXfK3V|hD+BelYIZ91>xePjJ@<|wbUMsu$%AmonA0NHNejd;` z(n)k${uaV;VFY@F9Se1cVz}3Rs2Y!~7;e4poj4CEv<-N|sHJU?AU%zruGn$P1PVUp zBn8XPa@P6=|MJ(oDt*9v@Qnzv=yR~HrbB@e(~xbTG?(k(B#YCQ#zuA&N-n8N^-%Y* z*#nHhG2@PbuJT z;5PGaY#g;-o8vC@9Iiy}>Nip}cpG3AYZ@BnllL&2w>W(yC@4;pNq=UwSCd<9%zE++ zQqg!eY%CtzWbxAez?>)#AysO7{h?kiMv~KFXX%;Q(q-IrE;<`F<3ey33@)sUOY|R# zZ6#8B%G1*f=8o+ZU|*-Wjr1%5m(jdf&T8C7~@g*dVWpKo}>k#m#c+QmJG%78Vg zn78C5Owl_+s>Vcsg^nX+H;3(I-@~apbFvM+FhRXKy}bsJ*eCiR>rDUc+5WtUrz^!W2O|E%)PzH z?xcy5>Io;TNY36Ko;KQq+!>ydQ1nrIvc0oueLeJ@uISW6dPg)bkVrufN(9 z+S8;(pErEKVaFn4raH@YS$JmgOhn9j#p^4L7@%%Mq*+j~<*!A!7Y_gk^MOT3IcoM> z!V5g&`Z8@9u@QQNLKd^`H#1q`m?)vpu6z77$5CeoC6V&E4rY z?ByYs3u%JI!VB`rKCRnfr)Q}L>Q9;79ho5C{~A_vYQPa)o)|AjJb3>*X$_l$azKwyDgH5VSmB-}cD%3g z5W@8hCp^y)_T3GNx1b4idl4rnRweV?^565_g65`lamKx-TZdEs7dq4HZl!<1dPl%5 z==CR~(B^fzYWKS+mcI&HbyI&;an|Gr$Zi+fP2;X>iL)=~Exq$?Q_2XOTfn^^J&TDc zN^Vv9f5vu!K+DTmI{}w%-+Qi_x1~utN=j?XjJRKs!C&1ZFjXfmXUM~sL}7sh`J+_f zwe-(~wa3nxbP>3F#jp!ADDT0c<8w_s6d`P}s@4m0ke*O4{9+`9&vAmk=AP%XM-g4s z9$)2;c*d>BUpVzj(8arvojarW<%qagQKr4^D~`h*KD;1Y&5_RNFr;r)1e>^U-tn2~ z2RkP%%5Ii}ONF{!GQ`Cat#aS-1`}R6VUS+|Z)7Q25R%rP*;i)gjB^X*CbK-GbU*%C zMJ-z1?Y{XHy@Jv6+{xMU$su*VqLK7fi+RZDnD6F0!aswsE>ppl5BAUJ0DeXh`Bz~8 z0DgkW>YTCR;nf8s04T8Uhewowf3i{l0AzkJ{{jsMuPU4K#Dks6c=%zdU%$tt%P}DV z;29N3`G0i^{MhziFp%Iqq!j3+f{^=pUcwJCd-DQ-*nek$6X10f98W9QrlJbHh5Fh0 E8ziqH)&Kwi diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index ef6ea9768f..ef2aa6312d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.custom.controller.admin.ent; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; @@ -7,9 +8,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.*; +import cn.iocoder.yudao.module.custom.convert.ent.EntConvert; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; +import cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.custom.service.ent.EntService; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.service.user.AdminUserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -23,10 +28,13 @@ import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.IMPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @Tag(name = "管理后台 - 企业") @RestController @@ -36,6 +44,8 @@ public class EntController { @Resource private EntService entService; + @Resource + private AdminUserService userService; @PostMapping("/create") @Operation(summary = "创建企业") @@ -83,8 +93,16 @@ public class EntController { @Operation(summary = "获得企业分页") @PreAuthorize("@ss.hasPermission('custom:ent:query')") public CommonResult> getEntPage(@Valid EntPageReqVO pageReqVO) { + // 获得企业分页列表 PageResult pageResult = entService.getEntPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, EntRespVO.class)); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + // 拼接用户名称 + Map userMap = userService.getUserMap( + convertList(pageResult.getList(), EntDO::getUserId)); + return success(new PageResult<>(EntConvert.INSTANCE.convertList(pageResult.getList(), userMap), + pageResult.getTotal())); } @PostMapping("/import-excel") @@ -94,7 +112,14 @@ public class EntController { @ApiAccessLog(operateType = IMPORT) public CommonResult importEntExcel(@RequestParam("file") MultipartFile file) throws IOException { List importList = ExcelUtils.read(file, EntImportReqVO.class); - entService.importEntList(importList); + if (CollUtil.isEmpty(importList)) + return error(ErrorCodeConstants.EMPTY_DATA); + + // 拼接机构 + Map userMap = userService.getUserMap( + convertList(importList, EntImportReqVO::getUserId)); + + entService.importEntList(EntConvert.INSTANCE.convertDeptList(importList, userMap)); return success("导入成功"); } @@ -106,9 +131,12 @@ public class EntController { HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = entService.getEntPage(pageReqVO).getList(); + // 拼接用户数据 + Map userMap = userService.getUserMap( + convertList(list, EntDO::getUserId)); // 导出 Excel ExcelUtils.write(response, "企业.xls", "数据", EntRespVO.class, - BeanUtils.toBean(list, EntRespVO.class)); + EntConvert.INSTANCE.convertList(list, userMap)); } // ==================== 子表(企业信息) ==================== diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java index 31411f2ffe..8f4da417dc 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java @@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; @Schema(description = "管理后台 - 企业导入 Request VO") @Data @@ -22,10 +21,9 @@ public class EntImportReqVO { private String remark; @ExcelProperty("所属客户经理") - private String manager; + private Long userId; @ExcelProperty(value = "状态(0正常 1停用)") - @NotNull(message = "状态(0正常 1停用)不能为空") private Integer status; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java index bbb1477a40..9971584b40 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java @@ -31,6 +31,9 @@ public class EntRespVO { @NotNull(message = "所属客户经理不能为空") private Long userId; + @Schema(description = "所属客户经理的名称") + private String userName; + @Schema(description = "状态(0正常 1停用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @ExcelProperty("状态(0正常 1停用)") private Integer status; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java new file mode 100644 index 0000000000..7dccf011dc --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.custom.convert.ent; + +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntRespVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface EntConvert { + + EntConvert INSTANCE = Mappers.getMapper(EntConvert.class); + + default List convertList(List list, Map userMap) { + return CollectionUtils.convertList(list, ent -> convert(ent, userMap.get(ent.getUserId()))); + } + + default EntRespVO convert(EntDO ent, AdminUserDO user) { + EntRespVO entVO = BeanUtils.toBean(ent, EntRespVO.class); + if (user != null) { + entVO.setUserName(user.getNickname()); + } + return entVO; + } + + default List convertDeptList(List importList, Map userMap) { + return CollectionUtils.convertList(importList, ent -> convertDept(ent, userMap.get(ent.getUserId()))); + } + + + + default EntDO convertDept(EntImportReqVO ent, AdminUserDO user) { + EntDO entVO = BeanUtils.toBean(ent, EntDO.class); + if (user != null) { + entVO.setDeptId(user.getDeptId()); + } + return entVO; + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java index f594e27671..af3658b64d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/ErrorCodeConstants.java @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.custom.enums; import cn.iocoder.yudao.framework.common.exception.ErrorCode; -// TODO 待办:请将下面的错误码复制到 yudao-module-custom 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!! -// ========== 企业 TODO 补充编号 ========== public interface ErrorCodeConstants { ErrorCode ENT_NOT_EXISTS = new ErrorCode(1_003_201_000, "企业不存在"); + + ErrorCode EMPTY_DATA = new ErrorCode(1_003_201_001, "Excel有效数据为空"); } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 1b8fbe40b3..49d0d05d7c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; @@ -72,7 +71,7 @@ public interface EntService { * @param importReqVOList 导入信息列表 */ - void importEntList(List importReqVOList) throws IOException; + void importEntList(List importReqVOList) throws IOException; // ==================== 子表(企业信息) ==================== diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 326d967512..f4601b760b 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntImportReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; @@ -15,7 +14,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; -import java.io.IOException; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -156,8 +154,7 @@ public class EntServiceImpl implements EntService { } @Override - public void importEntList(List importReqVOList) throws IOException { - List entList = BeanUtils.toBean(importReqVOList, EntDO.class); + public void importEntList(List entList) { entMapper.insertBatch(entList); } -- Gitee From 17c98d13f8c2fb7162006499736ad5fa3833c387 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 15:47:41 +0800 Subject: [PATCH 28/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=A8=A1=E7=B3=8A=E6=A3=80=E7=B4=A2=EF=BC=9B=20=E5=B0=B1?= =?UTF-8?q?=E6=98=AF=E5=AF=B9=E8=AF=9D=E5=BC=82=E6=AD=A5=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...71\347\233\256\350\257\264\346\230\216.md" | 61 ++++- .../custom/api/fastgpt/vo/FlowResponse.java | 97 +++++++ .../controller/admin/api/ChatController.http | 2 +- .../controller/admin/api/ChatController.java | 6 +- .../custom/dal/mysql/ent/EntMapper.java | 4 +- .../service/fastgpt/FastGptService.java | 238 +++++++++++++----- 6 files changed, 336 insertions(+), 72 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java diff --git "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" index 4b444539d4..af9f71a5be 100644 --- "a/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" +++ "b/docs/\351\241\271\347\233\256\350\257\264\346\230\216.md" @@ -9,4 +9,63 @@ 3、重启项目 ## 文件目录设置 -如果是本地存储,在 基础设施/文件管理/文件配置 下配置对应的目录 \ No newline at end of file +如果是本地存储,在 基础设施/文件管理/文件配置 下配置对应的目录 + +## 流式对话接口说明 +``` +event取值: +answer: 返回给客户端的文本(最终会算作回答) +fastAnswer: 指定回复返回给客户端的文本(最终会算作回答) +toolCall: 执行工具 +toolParams: 工具参数 +toolResponse: 工具返回 +flowNodeStatus: 运行到的节点状态 +flowResponses: 节点完整响应 +updateVariables: 更新变量 +error: 报错 + +event: flowNodeStatus +data: {"status":"running","name":"知识库搜索"} + +event: fastAnswer +data: {"role":"assistant","content":"\n好的,我这就为您查询待办事项,请稍候。\n用户是:\n机构是:"} + +event: workflowDuration +data: {"durationSeconds":1.83} + +event: flowNodeStatus +data: {"status":"running","name":"AI 对话"} + +event: answer +data: {"role":"ai","content":"电影"} + +event: answer +data: {"role":"ai","content":"《铃"} + +event: answer +data: {"role":"ai","content":"芽之旅》"} + +event: answer +data: {"role":"ai","content":"的导演是新"} + +event: answer +data: {"role":"ai","content":"海诚。"} + +event: flowResponses +data: { +"qouteContent": "腾讯最近进军了机器人行业,这是一个值得关注的动态[690b16b7c483714cb4ab6d1b](CITE)。此外,腾讯在秋招期间招聘了1000人,使得企业规模扩大到了将近2万人[690b1a9cc483714cb4ab7170](CITE)。腾讯的总部位于深圳,目前公司规模已经达到了上万人[690b168dc483714cb4ab6ca0](CITE)。", +"sourceList": [{ +"sourceName": "data (81).csv", +"sourceId": "64fd3b6423aa1307b65896f6", +"quoteList": [{ +"id": "654f2e49b64caef1d9431e8b", +"q": "电影《铃芽之旅》的导演是谁?", +"a": "电影《铃芽之旅》的导演是新海诚!" +}] +}] +} + +event: answer +data: [DONE] + +``` diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java new file mode 100644 index 0000000000..938911d48f --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 最终节点结果解析 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FlowResponse { + + /** 搜索结果列表 */ + private List sourceList; + /** 答复结果 */ + private String content; + + + /** + * 引用的数据集合来源 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SourceItem { + /** 知识库ID */ + private String datasetId; + /** 集合ID */ + private String collectionId; + /** 源名称 */ + private String sourceName; + /** 具体引用的数据块 */ + private List quoteList; + + public SourceItem(String datasetId, String collectionId, String sourceName) { + this.datasetId = datasetId; + this.collectionId = collectionId; + this.sourceName = sourceName; + } + + public void addQuoteItem(String id, String q, String a) { + if (quoteList == null) { + quoteList = new java.util.ArrayList<>(); + } + QuoteItem newQuoteItem = new QuoteItem(id, q, a); + if(!quoteList.contains(newQuoteItem)) + quoteList.add(new QuoteItem(id, q, a)); + } + } + + + /** + * 引用的数据块 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class QuoteItem { + /** 数据ID */ + private String id; + /** 问题文本 */ + private String q; + /** 答案文本 */ + private String a; + + public QuoteItem(String id, String q, String a) { + this.id = id; + this.q = q; + this.a = a; + } + } + + // 新增sourceItem + public void addSourceItem(String datasetId, String collectionId, String sourceName) { + SourceItem item = new SourceItem(datasetId, collectionId, sourceName); + addSourceItem(item); + } + + public void addSourceItem(SourceItem sourceItem) { + if (sourceList == null) { + sourceList = new java.util.ArrayList<>(); + } + if(!sourceList.contains(sourceItem)) + sourceList.add(sourceItem); + } + + public SourceItem getByCollectionId(String collectionId) { + if (sourceList != null && !sourceList.isEmpty()) + for (SourceItem item : sourceList) { + if (item.getCollectionId().equals(collectionId)) { + return item; + } + } + + return null; + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 71bf5a970c..b9f58f74ba 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -1,5 +1,5 @@ ### 流式对话接口测试 -POST {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_12345&dataId=12345&userContent=腾讯公司最近发生了什么 +GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_12345&dataId=12345&userContent=腾讯公司最近发生了什么 Content-Type: application/json Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index 66b5f8ab11..49c6de1ac0 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -39,7 +39,7 @@ public class ChatController { private final ChatService chatService; @Operation(summary = "与fastgpt进行流式对话") - @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @Parameter(name = "category", description = "对话流程类型", required = true) @Parameter(name = "chatId", description = "每一个新对话的唯一id", required = true) @Parameter(name = "dataId", description = "每轮对话的id", required = true) @@ -76,7 +76,9 @@ public class ChatController { } catch (Exception e) { log.error("保存助手回复失败", e); } - }); + }) + .onBackpressureBuffer() // 添加背压缓冲 + .doOnNext(text -> log.info("发送 SSE 数据: {}", text)); // 添加日志 } @Operation(summary = "获取用户对话记录列表") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java index 9ef92d1f23..23460fd841 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/mysql/ent/EntMapper.java @@ -20,8 +20,8 @@ public interface EntMapper extends BaseMapperX { default PageResult selectPage(EntPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(EntDO::getName, reqVO.getName()) - .eqIfPresent(EntDO::getIndustry, reqVO.getIndustry()) - .eqIfPresent(EntDO::getRemark, reqVO.getRemark()) + .likeIfPresent(EntDO::getIndustry, reqVO.getIndustry()) + .likeIfPresent(EntDO::getRemark, reqVO.getRemark()) .eqIfPresent(EntDO::getStatus, reqVO.getStatus()) .orderByDesc(EntDO::getId)); } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 05d849a92e..39d2dbc623 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptAuthInterceptor; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptDataApi; import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; +import com.alibaba.druid.support.json.JSONUtils; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -168,6 +169,18 @@ public class FastGptService { * @return FastGptDataApi实例 */ private FastGptDataApi createTokenClient(String category, String tenantId) { + return createTokenClient(category, tenantId, false); + } + + /** + * 创建临时API客户端 + * + * @param category 认证token的类型 + * @param tenantId 租户ID + * @param isStreaming 是否为流式传输 + * @return FastGptDataApi实例 + */ + private FastGptDataApi createTokenClient(String category, String tenantId, boolean isStreaming) { String url = getDictUrl(tenantId); String token = getDictToken(tenantId, category); @@ -175,13 +188,22 @@ public class FastGptService { throw new IllegalArgumentException("fastgpt的URL和token不能为空"); } - int readTimeout = 30; int connectTimeout = 30; - OkHttpClient httpClient = new OkHttpClient.Builder() + int readTimeout = isStreaming ? 300 : 30; // 流式传输使用5分钟,非流式使用30秒 + int writeTimeout = isStreaming ? 300 : 30; + + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder() .addInterceptor(new FastGptAuthInterceptor(token)) .connectTimeout(connectTimeout, TimeUnit.SECONDS) .readTimeout(readTimeout, TimeUnit.SECONDS) - .build(); + .writeTimeout(writeTimeout, TimeUnit.SECONDS); + + // 只在流式传输时禁用缓存 + if (isStreaming) { + httpClientBuilder.cache(null); + } + + OkHttpClient httpClient = httpClientBuilder.build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(url) @@ -197,6 +219,18 @@ public class FastGptService { return apiCache.computeIfAbsent(tenantId + "@" + category, key -> createTokenClient(category, tenantId)); } + /** + * 获取流式聊天客户端(不使用缓存,每次创建新实例以确保流式传输配置生效) + * + * @param category 认证类别 + * @param tenantId 租户ID + * @return FastGptDataApi实例 + */ + private FastGptDataApi getStreamChatClient(String category, String tenantId) { + // 流式传输客户端不使用缓存,确保配置生效 + return createTokenClient(category, tenantId, true); + } + private Map getDictMap() { List dictGpt = dictDataCommonApi.getDictDataList("fastgpt"); return dictGpt.stream().collect(Collectors.toMap(DictDataRespDTO::getLabel, DictDataRespDTO::getValue)); @@ -215,16 +249,16 @@ public class FastGptService { /** * 发送对话请求 * - * @param chatId 对话标志 - * @param content 对话内容 - * @param category 认证类别 - * @param dataId 数据ID - * @param tenantId 租户ID + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 认证类别 + * @param dataId 数据ID + * @param tenantId 租户ID * @param onComplete 流式输出完成后的回调函数,参数为完整的响应内容 * @return 流式响应 */ public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId, - java.util.function.Consumer onComplete) { + java.util.function.Consumer onComplete) { if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); @@ -240,43 +274,43 @@ public class FastGptService { ChatCompletionRequest.ChatMessage message = new ChatCompletionRequest.ChatMessage(content); request.setMessages(Collections.singletonList(message)); - // 使用 Retrofit 异步调用 - return Flux.create(sink -> - getChatClient(category, tenantId).sendStreamChatRequest(request).enqueue(new retrofit2.Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - // 处理成功的响应 - // 处理流式响应 - ResponseBody responseBody = response.body(); - if (responseBody != null) { - log.info("开始处理流式响应:"); - - // 创建ObjectMapper用于解析JSON - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - // 用于收集完整的响应内容 - StringBuilder fullResponse = new StringBuilder(); - - try { - // 逐行读取响应内容 - BufferedReader reader = new BufferedReader(responseBody.charStream()); - String line; - int count = 0, done = 0; - - while ((line = reader.readLine()) != null && !sink.isCancelled()) { - log.info("\n接收到响应行: {}", line); - - if(done == 1 && count++ < 4) { - log.info("\n处理最后的汇总数据: {}", line); - continue; - } + // 使用 Flux.create 并在单独的线程中处理流式响应 + return Flux.create(sink -> { + // 异步执行 Retrofit 调用,使用专门的流式传输客户端 + getStreamChatClient(category, tenantId).sendStreamChatRequest(request).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + log.info("开始处理流式响应"); + + // 创建ObjectMapper用于解析JSON + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + // 用于收集完整的响应内容 + StringBuilder fullResponse = new StringBuilder(); + + try { + // 逐行读取响应内容 + BufferedReader reader = new BufferedReader(responseBody.charStream()); + String line; + int count = 0, done = 0; + + while ((line = reader.readLine()) != null && !sink.isCancelled()) { + log.info("接收到响应行: {}", line); + + if (done == 1 && count++ < 4) { + log.info("处理最后的汇总数据: {}", line); + continue; + } - if(count > 4) break; + if (count > 4) break; - // 处理SSE格式的数据 - if (!line.trim().isEmpty() && line.startsWith("data: ")) { + // 处理SSE格式的数据 + if (!line.trim().isEmpty()) { + if (line.startsWith("data: ")) { String data = line.substring(6); // 移除"data: "前缀 // 检查是否为结束标记 @@ -300,47 +334,119 @@ public class FastGptService { if (contentNode != null && contentNode.isTextual()) { String text = contentNode.asText(); if (!text.isEmpty()) { - log.info("\n接收到的内容片段: {}", text); - sink.next(text); // 通过flux发送内容片段 - fullResponse.append(text); // 收集完整响应内容 + log.info("接收到的内容片段: {}", text); + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next("{\"role\":\"assistant\",\"content\":\"" + text + "\"}"); + } + fullResponse.append(text); } } } + } else if (jsonNode.isArray() && jsonNode.has("moduleType")) { + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next(handleFlowResponses(jsonNode)); + } + } else { + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next(data); + } } } catch (Exception e) { log.warn("解析JSON数据失败: {}", data, e); } + } else if (line.startsWith("event: ")) { + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next(line); + } } } - } catch (Exception e) { - log.warn("处理流式响应失败: {}", e.getMessage(), e); } + reader.close(); + responseBody.close(); + } catch (Exception e) { + log.error("处理流式响应失败", e); if (!sink.isCancelled()) { - // 在流完成时调用回调函数 - if (onComplete != null) { - try { - onComplete.accept(fullResponse.toString()); - } catch (Exception e) { - log.error("执行回调函数时发生错误", e); - } + sink.error(e); + } + return; + } + + if (!sink.isCancelled()) { + // 在流完成时调用回调函数 + if (onComplete != null) { + try { + onComplete.accept(fullResponse.toString()); + } catch (Exception e) { + log.error("执行回调函数时发生错误", e); } - sink.complete(); // 完成flux流 } + sink.complete(); } - } else { - // 处理错误响应 - sink.error(new RuntimeException("流式聊天调用失败: " + response.message())); } + } else { + sink.error(new RuntimeException("流式聊天调用失败: " + response.message())); } + } - @Override - public void onFailure(Call call, Throwable t) { - // 处理网络故障等异常 - sink.error(t); + @Override + public void onFailure(Call call, Throwable t) { + log.error("流式聊天请求失败", t); + sink.error(t); + } + }); + }, reactor.core.publisher.FluxSink.OverflowStrategy.BUFFER); + } + + private String handleFlowResponses(JsonNode jsonNode) { + + // 解析数组类型的节点数据 + FlowResponse flowResponse = new FlowResponse(); + + for (JsonNode item : jsonNode) { + String moduleType = item.has("moduleType") ? item.get("moduleType").asText() : ""; + + // 提取 chatNode 类型的数据 + if ("chatNode".equals(moduleType) && item.has("historyPreview")) { + JsonNode historyPreview = item.get("historyPreview"); + if (historyPreview.isArray() && !historyPreview.isEmpty()) { + JsonNode lastItem = historyPreview.get(historyPreview.size() - 1); + if (lastItem.has("obj") && "AI".equals(lastItem.get("obj").asText()) && lastItem.has("value")) { + flowResponse.setContent(lastItem.get("value").asText()); } - }) - ); + } + } + + // 提取 datasetSearchNode 类型的数据 + if ("datasetSearchNode".equals(moduleType) && item.has("quoteList")) { + JsonNode quoteListNode = item.get("quoteList"); + if (quoteListNode.isArray()) { + + for (JsonNode quote : quoteListNode) { + String collectionId = quote.has("collectionId") ? quote.get("collectionId").asText() : ""; + + FlowResponse.SourceItem sourceItem = null; + if (quote.has("datasetId") && !StrUtil.isEmpty(collectionId) && quote.has("sourceName")) { + sourceItem = new FlowResponse.SourceItem(quote.get("datasetId").asText(), quote.get("collectionId").asText(), quote.get("sourceName").asText()); + flowResponse.addSourceItem(sourceItem); + } + + if (quote.has("id") && quote.has("q") && quote.has("a")) { + sourceItem = flowResponse.getByCollectionId(collectionId); + if (sourceItem != null) + sourceItem.addQuoteItem(quote.get("id").asText(), quote.get("q").asText(), quote.get("a").asText()); + } + } + + } + } + } + + return JSONUtils.toJSONString(flowResponse); } -- Gitee From 3c22612355522a95eb99722c625eaeb4bc9ccea3 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 16:16:50 +0800 Subject: [PATCH 29/50] =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/fastgpt/FastGptService.java | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 39d2dbc623..f4114b8037 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -301,28 +301,36 @@ public class FastGptService { while ((line = reader.readLine()) != null && !sink.isCancelled()) { log.info("接收到响应行: {}", line); - if (done == 1 && count++ < 4) { - log.info("处理最后的汇总数据: {}", line); - continue; - } - - if (count > 4) break; + if (done == 1) count++; + if (done == 1 && count > 3) break; // 处理SSE格式的数据 if (!line.trim().isEmpty()) { if (line.startsWith("data: ")) { String data = line.substring(6); // 移除"data: "前缀 - - // 检查是否为结束标记 - if ("[DONE]".equals(data.trim())) { - log.info("流式传输结束"); - done = 1; - continue; - } - try { // 直接通过JSON路径提取content内容 JsonNode jsonNode = objectMapper.readTree(data); + + // 已经发送过DONE,处理最后的flowResponse + if (done == 1) { + log.info("处理最后的汇总数据: {}", line); + if (jsonNode.isArray()) { + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next(handleFlowResponses(jsonNode)); + } + } + break; + } + + // 检查是否为结束标记 + if ("[DONE]".equals(data.trim())) { + log.info("流式传输结束"); + done = 1; + continue; + } + JsonNode choicesNode = jsonNode.get("choices"); if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { @@ -337,21 +345,16 @@ public class FastGptService { log.info("接收到的内容片段: {}", text); // 立即发送内容片段 if (!sink.isCancelled()) { - sink.next("{\"role\":\"assistant\",\"content\":\"" + text + "\"}"); + sink.next("data: {\"role\":\"assistant\",\"content\":\"" + text + "\"}"); } fullResponse.append(text); } } } - } else if (jsonNode.isArray() && jsonNode.has("moduleType")) { - // 立即发送内容片段 - if (!sink.isCancelled()) { - sink.next(handleFlowResponses(jsonNode)); - } } else { // 立即发送内容片段 if (!sink.isCancelled()) { - sink.next(data); + sink.next(line); } } } catch (Exception e) { -- Gitee From 05ff9348732b39032ee096a72a5685afcd711b05 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 16:51:21 +0800 Subject: [PATCH 30/50] =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/api/ChatController.http | 4 +-- .../controller/admin/api/ChatController.java | 9 ++++-- .../service/fastgpt/FastGptService.java | 31 ++++++++++--------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index b9f58f74ba..c9e9fa9d7e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -1,6 +1,6 @@ ### 流式对话接口测试 -GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_12345&dataId=12345&userContent=腾讯公司最近发生了什么 -Content-Type: application/json +GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_11&dataId=110705&userContent=腾讯公司最近发生了什么 +Content-Type: text/event-stream Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index 49c6de1ac0..5c0be441aa 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -19,6 +19,7 @@ import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.http.codec.ServerSentEvent; import reactor.core.publisher.Flux; import javax.validation.Valid; @@ -45,7 +46,7 @@ public class ChatController { @Parameter(name = "dataId", description = "每轮对话的id", required = true) @Parameter(name = "userContent", description = "用户发送的内容", required = true) @PreAuthorize("@ss.hasPermission('custom:chat:stream')") - public Flux streamChat( + public Flux> streamChat( @RequestParam String category, @RequestParam String chatId, @RequestParam String dataId, @@ -77,8 +78,12 @@ public class ChatController { log.error("保存助手回复失败", e); } }) + .map(wrapper -> ServerSentEvent.builder() + .event(wrapper.getEvent()) + .data(wrapper.getData()) + .build()) .onBackpressureBuffer() // 添加背压缓冲 - .doOnNext(text -> log.info("发送 SSE 数据: {}", text)); // 添加日志 + .doOnNext(event -> log.info("发送 SSE 事件: event={}, data={}", event.event(), event.data())); } @Operation(summary = "获取用户对话记录列表") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index f4114b8037..93a8dc2501 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -257,7 +257,7 @@ public class FastGptService { * @param onComplete 流式输出完成后的回调函数,参数为完整的响应内容 * @return 流式响应 */ - public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId, + public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId, java.util.function.Consumer onComplete) { if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); @@ -297,12 +297,18 @@ public class FastGptService { BufferedReader reader = new BufferedReader(responseBody.charStream()); String line; int count = 0, done = 0; + String currentEvent = null; while ((line = reader.readLine()) != null && !sink.isCancelled()) { log.info("接收到响应行: {}", line); if (done == 1) count++; - if (done == 1 && count > 3) break; + if (done == 1 && count > 3) { + if (!sink.isCancelled()) { + sink.next(new SseMessageWrapper("finished", "[DONE]")); + } + break; + } // 处理SSE格式的数据 if (!line.trim().isEmpty()) { @@ -318,7 +324,8 @@ public class FastGptService { if (jsonNode.isArray()) { // 立即发送内容片段 if (!sink.isCancelled()) { - sink.next(handleFlowResponses(jsonNode)); + sink.next(new SseMessageWrapper("flowResponses", handleFlowResponses(jsonNode))); + sink.next(new SseMessageWrapper("finished", "[DONE]")); } } break; @@ -343,28 +350,22 @@ public class FastGptService { String text = contentNode.asText(); if (!text.isEmpty()) { log.info("接收到的内容片段: {}", text); - // 立即发送内容片段 - if (!sink.isCancelled()) { - sink.next("data: {\"role\":\"assistant\",\"content\":\"" + text + "\"}"); + // 立即发送内容片段,使用从FastGPT收到的event + if (!sink.isCancelled() && currentEvent != null) { + String responseData = "{\"role\":\"assistant\",\"content\":\"" + text + "\"}"; + sink.next(new SseMessageWrapper(currentEvent, responseData)); + currentEvent = null; } fullResponse.append(text); } } } - } else { - // 立即发送内容片段 - if (!sink.isCancelled()) { - sink.next(line); - } } } catch (Exception e) { log.warn("解析JSON数据失败: {}", data, e); } } else if (line.startsWith("event: ")) { - // 立即发送内容片段 - if (!sink.isCancelled()) { - sink.next(line); - } + currentEvent = line.substring(7); } } } -- Gitee From 98c3a3b969aedf155cc933882857f5d52e798174 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 17:11:46 +0800 Subject: [PATCH 31/50] =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/fastgpt/FastGptService.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 93a8dc2501..ab5089dc64 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -312,9 +312,18 @@ public class FastGptService { // 处理SSE格式的数据 if (!line.trim().isEmpty()) { - if (line.startsWith("data: ")) { + if (line.startsWith("event: ")) { + currentEvent = line.substring(7); + } else if (line.startsWith("data: ")) { String data = line.substring(6); // 移除"data: "前缀 try { + // 检查是否为结束标记 + if ("[DONE]".equals(data.trim())) { + log.info("流式传输结束"); + done = 1; + continue; + } + // 直接通过JSON路径提取content内容 JsonNode jsonNode = objectMapper.readTree(data); @@ -331,13 +340,6 @@ public class FastGptService { break; } - // 检查是否为结束标记 - if ("[DONE]".equals(data.trim())) { - log.info("流式传输结束"); - done = 1; - continue; - } - JsonNode choicesNode = jsonNode.get("choices"); if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { @@ -354,18 +356,19 @@ public class FastGptService { if (!sink.isCancelled() && currentEvent != null) { String responseData = "{\"role\":\"assistant\",\"content\":\"" + text + "\"}"; sink.next(new SseMessageWrapper(currentEvent, responseData)); - currentEvent = null; } fullResponse.append(text); } } } + } else { + if (!sink.isCancelled() && currentEvent != null) { + sink.next(new SseMessageWrapper(currentEvent, data)); + } } } catch (Exception e) { log.warn("解析JSON数据失败: {}", data, e); } - } else if (line.startsWith("event: ")) { - currentEvent = line.substring(7); } } } -- Gitee From 0a71cefe749be9479146c1d02f8517e72280465e Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 17:26:31 +0800 Subject: [PATCH 32/50] =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/fastgpt/vo/ChatCompletionRequest.java | 5 +++++ .../custom/service/fastgpt/FastGptService.java | 1 + .../service/fastgpt/SseMessageWrapper.java | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/SseMessageWrapper.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java index c00196be15..a3d991d54d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/ChatCompletionRequest.java @@ -30,6 +30,11 @@ public class ChatCompletionRequest { * 是否返回详细信息 */ private Boolean detail; + + /** + * 是否返回引用 + */ + private Boolean retainDatasetCite; /** * 聊天消息列表 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index ab5089dc64..26f22d72e2 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -268,6 +268,7 @@ public class FastGptService { request.setResponseChatItemId(dataId); request.setStream(true); request.setDetail(true); + request.setRetainDatasetCite(true); request.setVariables(new HashMap<>()); // 创建消息 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/SseMessageWrapper.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/SseMessageWrapper.java new file mode 100644 index 0000000000..fa3b81e230 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/SseMessageWrapper.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.custom.service.fastgpt; + +import lombok.Data; + +/** + * SSE消息包装类 + * 用于传递event和data信息 + */ +@Data +public class SseMessageWrapper { + private String event; + private String data; + + public SseMessageWrapper(String event, String data) { + this.event = event; + this.data = data; + } +} -- Gitee From 255e1502976beccbcd690b286339492e5e4c48ad Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 17:58:20 +0800 Subject: [PATCH 33/50] =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/api/fastgpt/vo/FlowResponse.java | 2 +- .../controller/admin/api/ChatController.http | 5 +- .../service/fastgpt/FastGptService.java | 189 ++++++++++-------- 3 files changed, 109 insertions(+), 87 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java index 938911d48f..9813cdbdff 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java @@ -80,7 +80,7 @@ public class FlowResponse { if (sourceList == null) { sourceList = new java.util.ArrayList<>(); } - if(!sourceList.contains(sourceItem)) + if(getByCollectionId(sourceItem.getCollectionId()) == null) sourceList.add(sourceItem); } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index c9e9fa9d7e..1fc05f5463 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -1,6 +1,7 @@ ### 流式对话接口测试 -GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_11&dataId=110705&userContent=腾讯公司最近发生了什么 -Content-Type: text/event-stream +GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_12&dataId=110702&userContent=腾讯公司最近发生了什么 +Content-Type: application/json +Accept: text/event-stream Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 26f22d72e2..a2deedd563 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -21,6 +21,9 @@ import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import javax.annotation.Resource; import java.io.BufferedReader; import java.util.*; @@ -38,6 +41,7 @@ public class FastGptService { private DictDataCommonApi dictDataCommonApi; private static final Map apiCache = new ConcurrentHashMap<>(); + private static final ExecutorService asyncExecutor = Executors.newFixedThreadPool(10); /** * 同步FAQ数据详情,创建场景(只有)的时候批量添加,文件夹id为数据集,faq为数据集下的数据; @@ -294,106 +298,116 @@ public class FastGptService { StringBuilder fullResponse = new StringBuilder(); try { - // 逐行读取响应内容 - BufferedReader reader = new BufferedReader(responseBody.charStream()); - String line; - int count = 0, done = 0; - String currentEvent = null; - - while ((line = reader.readLine()) != null && !sink.isCancelled()) { - log.info("接收到响应行: {}", line); - - if (done == 1) count++; - if (done == 1 && count > 3) { - if (!sink.isCancelled()) { - sink.next(new SseMessageWrapper("finished", "[DONE]")); - } - break; - } - - // 处理SSE格式的数据 - if (!line.trim().isEmpty()) { - if (line.startsWith("event: ")) { - currentEvent = line.substring(7); - } else if (line.startsWith("data: ")) { - String data = line.substring(6); // 移除"data: "前缀 - try { - // 检查是否为结束标记 - if ("[DONE]".equals(data.trim())) { - log.info("流式传输结束"); - done = 1; - continue; + // 使用异步线程处理流式响应 + asyncExecutor.submit(() -> { + try { + // 逐行读取响应内容 + BufferedReader reader = new BufferedReader(responseBody.charStream()); + String line; + int count = 0, done = 0; + String currentEvent = null; + + while ((line = reader.readLine()) != null && !sink.isCancelled()) { + log.info("接收到响应行: {}", line); + + if (done == 1) count++; + if (done == 1 && count > 3) { + if (!sink.isCancelled()) { + sink.next(new SseMessageWrapper("finished", "[DONE]")); } + break; + } - // 直接通过JSON路径提取content内容 - JsonNode jsonNode = objectMapper.readTree(data); - - // 已经发送过DONE,处理最后的flowResponse - if (done == 1) { - log.info("处理最后的汇总数据: {}", line); - if (jsonNode.isArray()) { - // 立即发送内容片段 - if (!sink.isCancelled()) { - sink.next(new SseMessageWrapper("flowResponses", handleFlowResponses(jsonNode))); - sink.next(new SseMessageWrapper("finished", "[DONE]")); + // 处理SSE格式的数据 + if (!line.trim().isEmpty()) { + if (line.startsWith("event: ")) { + currentEvent = line.substring(7); + } else if (line.startsWith("data: ")) { + String data = line.substring(6); // 移除"data: "前缀 + try { + // 检查是否为结束标记 + if ("[DONE]".equals(data.trim())) { + log.info("流式传输结束"); + done = 1; + continue; } - } - break; - } - JsonNode choicesNode = jsonNode.get("choices"); - - if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { - JsonNode firstChoice = choicesNode.get(0); - JsonNode deltaNode = firstChoice.get("delta"); - - if (deltaNode != null) { - JsonNode contentNode = deltaNode.get("content"); - if (contentNode != null && contentNode.isTextual()) { - String text = contentNode.asText(); - if (!text.isEmpty()) { - log.info("接收到的内容片段: {}", text); - // 立即发送内容片段,使用从FastGPT收到的event - if (!sink.isCancelled() && currentEvent != null) { - String responseData = "{\"role\":\"assistant\",\"content\":\"" + text + "\"}"; - sink.next(new SseMessageWrapper(currentEvent, responseData)); + // 直接通过JSON路径提取content内容 + JsonNode jsonNode = objectMapper.readTree(data); + + // 已经发送过DONE,处理最后的flowResponse + if (done == 1) { + log.info("处理最后的汇总数据: {}", line); + if (jsonNode.isArray()) { + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next(new SseMessageWrapper("flowResponses", handleFlowResponses(jsonNode))); + sink.next(new SseMessageWrapper("finished", "[DONE]")); } - fullResponse.append(text); } + break; } - } - } else { - if (!sink.isCancelled() && currentEvent != null) { - sink.next(new SseMessageWrapper(currentEvent, data)); + + JsonNode choicesNode = jsonNode.get("choices"); + + if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { + JsonNode firstChoice = choicesNode.get(0); + JsonNode deltaNode = firstChoice.get("delta"); + + if (deltaNode != null) { + JsonNode contentNode = deltaNode.get("content"); + if (contentNode != null && contentNode.isTextual()) { + String text = contentNode.asText(); + if (!text.isEmpty()) { + log.info("接收到的内容片段: {}", text); + // 立即发送内容片段,使用从FastGPT收到的event + if (!sink.isCancelled() && currentEvent != null) { + String responseData = "{\"role\":\"assistant\",\"content\":\"" + text + "\"}"; + sink.next(new SseMessageWrapper(currentEvent, responseData)); + } + fullResponse.append(text); + } + } + } + } else { + if (!sink.isCancelled() && currentEvent != null) { + sink.next(new SseMessageWrapper(currentEvent, data)); + } + } + } catch (Exception e) { + log.warn("解析JSON数据失败: {}", data, e); } } - } catch (Exception e) { - log.warn("解析JSON数据失败: {}", data, e); } } + + reader.close(); + responseBody.close(); + } catch (Exception e) { + log.error("异步处理流式响应失败", e); + if (!sink.isCancelled()) { + sink.error(e); + } + return; } - } - reader.close(); - responseBody.close(); + if (!sink.isCancelled()) { + // 在流完成时调用回调函数 + if (onComplete != null) { + try { + onComplete.accept(fullResponse.toString()); + } catch (Exception e) { + log.error("执行回调函数时发生错误", e); + } + } + sink.complete(); + } + }); } catch (Exception e) { - log.error("处理流式响应失败", e); + log.error("提交异步任务失败", e); if (!sink.isCancelled()) { sink.error(e); } - return; - } - - if (!sink.isCancelled()) { - // 在流完成时调用回调函数 - if (onComplete != null) { - try { - onComplete.accept(fullResponse.toString()); - } catch (Exception e) { - log.error("执行回调函数时发生错误", e); - } - } - sink.complete(); } } } else { @@ -454,7 +468,14 @@ public class FastGptService { } } - return JSONUtils.toJSONString(flowResponse); + // 使用Jackson ObjectMapper替代Druid的JSONUtils + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(flowResponse); + } catch (Exception e) { + log.error("FlowResponse序列化失败", e); + return "{}"; // 返回默认值 + } } -- Gitee From ed9cceb02e7cbc9cc664f36f79ef576f33ca1347 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 18:00:14 +0800 Subject: [PATCH 34/50] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/StreamChatDialog.vue | 500 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 500 insertions(+) create mode 100644 docs/StreamChatDialog.vue diff --git a/docs/StreamChatDialog.vue b/docs/StreamChatDialog.vue new file mode 100644 index 0000000000..ed99dc10e8 --- /dev/null +++ b/docs/StreamChatDialog.vue @@ -0,0 +1,500 @@ + + + + + -- Gitee From f1cacd00fc031092cf1ee5337a34585d4fc26c72 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 7 Nov 2025 18:44:31 +0800 Subject: [PATCH 35/50] =?UTF-8?q?=E7=BB=88=E4=BA=8E=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E8=BF=9B=E8=A1=8C=E6=B5=81=E5=BC=8F=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/StreamChatDialog.vue | 209 ++++++++++++++---- yudao-module-custom/README_API.md | 198 ----------------- .../custom/api/fastgpt/FastGptDataApi.java | 1 + .../controller/admin/api/ChatController.http | 2 +- .../service/fastgpt/FastGptService.java | 202 ++++++++--------- 5 files changed, 263 insertions(+), 349 deletions(-) delete mode 100644 yudao-module-custom/README_API.md diff --git a/docs/StreamChatDialog.vue b/docs/StreamChatDialog.vue index ed99dc10e8..284edfc60d 100644 --- a/docs/StreamChatDialog.vue +++ b/docs/StreamChatDialog.vue @@ -40,6 +40,16 @@ >
+ +
+
+ + | +
+
@@ -112,6 +122,7 @@ interface FormData { interface Message { role: 'user' | 'assistant' content: string + sources?: any[] } const dialogVisible = ref(false) @@ -209,6 +220,7 @@ const handleStop = () => { const startStreamChat = async () => { try { streaming.value = true + // 添加空的助手消息 messages.value.push({ role: 'assistant', content: '' @@ -228,6 +240,9 @@ const startStreamChat = async () => { userContent: formData.userContent }) + // 清空输入框 + formData.userContent = '' + await fetchEventSource(`${baseUrl}/custom/chat/stream?${params.toString()}`, { method: 'GET', headers: { @@ -236,47 +251,116 @@ const startStreamChat = async () => { 'tenant-id': localStorage.getItem('adminTenantId') || '' }, signal: abortController.value.signal, + onopen: () => { + console.log('SSE连接已建立') + }, onmessage: (event) => { try { - console.log(event) + console.log('收到SSE事件:', event.event, event.data) + // 处理不同的事件类型 - if (event.event === 'source') { - // 处理source事件 - const sourceData = JSON.parse(event.data) - console.log('Received source data:', sourceData) - // 这里可以处理引用信息 - } else if (event.event === 'answer') { - // 处理answer事件 - if (event.data === '[DONE]') { - // 流式传输结束 - streaming.value = false - ElMessage.success('对话完成') - return + if (event.event === 'flowNodeStatus') { + // 处理节点状态事件 + try { + const statusData = JSON.parse(event.data) + console.log('节点状态:', statusData.name, '-', statusData.status) + // 可以在这里添加状态更新的UI反馈 + } catch (e) { + console.warn('解析节点状态数据失败:', e) } - - const answerData = JSON.parse(event.data) - if (answerData.code === 0 && answerData.data) { - // 更新最后一条消息的内容 - const lastMessage = messages.value[messages.value.length - 1] - lastMessage.content += answerData.data.content || '' - } else { - ElMessage.error(`错误: ${answerData.msg || '未知错误'}`) - streaming.value = false + } else if (event.event === 'flowResponses') { + // 处理引用信息和对话内容 + try { + const responseData = JSON.parse(event.data) + console.log('收到响应数据:', responseData) + + // 检查是否包含对话内容 + if (typeof responseData === 'string') { + try { + const parsedContent = JSON.parse(responseData) + if (parsedContent.content) { + const lastMessage = messages.value[messages.value.length - 1] + lastMessage.content += parsedContent.content + scrollToBottom() + } + } catch (e) { + // 如果不是JSON格式,直接作为内容处理 + const lastMessage = messages.value[messages.value.length - 1] + lastMessage.content += responseData + scrollToBottom() + } + } else if (responseData && typeof responseData === 'object') { + // 处理引用信息 + const lastMessage = messages.value[messages.value.length - 1] + if (responseData.sources) { + lastMessage.sources = responseData.sources + } + } + } catch (e) { + console.warn('解析响应数据失败:', e) } + } else if (event.event === 'chatMessage' || event.event === 'assistant') { + // 处理聊天消息事件 + try { + const data = JSON.parse(event.data) + if (data && typeof data === 'object') { + const lastMessage = messages.value[messages.value.length - 1] + if (data.content) { + lastMessage.content += data.content + scrollToBottom() + } else if (data.text) { + lastMessage.content += data.text + scrollToBottom() + } + } + } catch (e) { + console.warn('解析聊天消息失败:', e) + } + } else if (event.event === 'finished') { + // 流式传输结束 + streaming.value = false + ElMessage.success('对话完成') + return + } else if (event.data === '[DONE]') { + // 流式传输结束标记 + streaming.value = false + ElMessage.success('对话完成') + return } else { - // 处理默认格式(兼容原有逻辑) - const data = JSON.parse(event.data) - if (data.code === 0 && data.data) { - // 更新最后一条消息的内容 + // 处理其他未知事件类型 + console.log('未知事件类型:', event.event, '数据:', event.data) + try { + const data = JSON.parse(event.data) + if (data && typeof data === 'object') { + // 更新最后一条消息的内容 + const lastMessage = messages.value[messages.value.length - 1] + if (data.content) { + lastMessage.content += data.content + scrollToBottom() + } else if (data.text) { + lastMessage.content += data.text + scrollToBottom() + } else { + // 直接追加原始数据 + lastMessage.content += event.data + scrollToBottom() + } + } else { + // 直接追加原始数据 + const lastMessage = messages.value[messages.value.length - 1] + lastMessage.content += event.data + scrollToBottom() + } + } catch (error) { + console.warn('解析流式数据失败,尝试直接追加:', error) + // 尝试直接追加原始数据 const lastMessage = messages.value[messages.value.length - 1] - lastMessage.content += data.data.content || '' - } else { - ElMessage.error(`错误: ${data.msg || '未知错误'}`) - streaming.value = false + lastMessage.content += event.data + scrollToBottom() } } } catch (error) { - console.error('解析消息失败:', error) + console.error('处理SSE事件失败:', error) console.error('原始数据:', event.data) } }, @@ -287,6 +371,7 @@ const startStreamChat = async () => { throw error }, onclose: () => { + console.log('SSE连接已关闭') streaming.value = false abortController.value = null } @@ -389,6 +474,16 @@ const scrollToBottom = async () => { } } +// 格式化消息内容(支持Markdown等) +const formatMessageContent = (content: string) => { + // 基本的文本格式化 + return content + .replace(/\n/g, '
') + .replace(/\*\*(.*?)\*\*/g, '$1') // 粗体 + .replace(/\*(.*?)\*/g, '$1') // 斜体 + .replace(/`(.*?)`/g, '$1') // 行内代码 +} + // 监听对话框关闭事件,自动重置状态 watch(dialogVisible, (newValue) => { if (!newValue) { @@ -397,12 +492,6 @@ watch(dialogVisible, (newValue) => { } }) -// 格式化消息内容(支持Markdown等) -const formatMessageContent = (content: string) => { - // 这里可以添加Markdown解析等格式化逻辑 - return content.replace(/\n/g, '
') -} - // 处理输入中回车的定时器 let inputTimeout = ref() @@ -464,6 +553,18 @@ defineExpose({ border-radius: 8px; max-width: 80%; line-height: 1.5; + animation: fadeIn 0.3s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } } .message.user { @@ -477,6 +578,25 @@ defineExpose({ align-self: flex-start; } +.message.assistant.streaming { + position: relative; +} + +.cursor-blink { + display: inline-block; + width: 3px; + height: 1em; + background-color: var(--el-text-color-primary); + animation: cursorBlink 0.8s infinite; + vertical-align: bottom; + margin-left: 2px; +} + +@keyframes cursorBlink { + 0%, 100% { opacity: 0; } + 50% { opacity: 1; } +} + .message-content { white-space: pre-wrap; word-wrap: break-word; @@ -484,6 +604,21 @@ defineExpose({ line-height: 1.6; } +.message-content :deep(code) { + background-color: var(--el-color-primary-light-8); + padding: 2px 4px; + border-radius: 3px; + font-family: 'Courier New', monospace; +} + +.message-content :deep(strong) { + font-weight: bold; +} + +.message-content :deep(em) { + font-style: italic; +} + .dialog-footer { padding: 0; margin: 0; diff --git a/yudao-module-custom/README_API.md b/yudao-module-custom/README_API.md deleted file mode 100644 index 48822a08cf..0000000000 --- a/yudao-module-custom/README_API.md +++ /dev/null @@ -1,198 +0,0 @@ -# 对话模块 API 文档 - -## 1. 流式对话接口 - -### 请求信息 -- **URL**: `/custom/chat/stream` -- **方法**: `POST` -- **Content-Type**: `application/json` - -### 请求头 -- `tenant-id`: 租户ID -- `login-user-id`: 用户ID - -### 请求参数 -```json -{ - "chatId": "可选,如果为空会自动生成", - "stream": true, - "detail": false, - "variables": {}, - "messages": [ - { - "role": "user", - "content": "你好,请介绍一下你自己" - } - ] -} -``` - -### 请求参数说明 -- `chatId`: 对话会话ID,可选,如果为空会自动生成 -- `messages`: 消息列表,目前只支持一条消息 -- `messages[0].content`: 用户发送的消息内容 - -### URL参数 -- `category`: 快速回复, -- `dataId`: 数据ID - -### 响应 -**响应类型**: `Stream` -**响应格式**: 每行一个数据块 -``` -data: {"choices":[{"delta":{"content":"你好"},"id":"chat123","model":"gpt-3.5-turbo"}} - -data: {"choices":[{"delta":{"content":",我是"},"id":"chat123","model":"gpt-3.5-turbo"}} - -data: {"choices":[{"delta":{"content":"AI助手。"},"id":"chat123","model":"gpt-3.5-turbo"}} - -data: [DONE] -``` - -### 响应说明 -- 每行以 `data: ` 开头 -- 流式响应结束时会发送 `data: [DONE]` -- 处理时需要去掉 `data: ` 前缀并解析JSON - -## 2. 非流式对话接口 - -### 请求信息 -- **URL**: `/custom/chat/chat` -- **方法**: `POST` -- **Content-Type**: `application/json` - -### 请求头 -- `tenant-id`: 租户ID -- `login-user-id`: 用户ID - -### 请求参数 -同流式对话接口 - -### 响应 -```json -{ - "code": 200, - "data": "完整回复内容", - "msg": "成功" -} -``` - -## 3. 获取对话记录列表 - -### 请求信息 -- **URL**: `/custom/chat/list` -- **方法**: `POST` -- **Content-Type**: `application/json` - -### 请求头 -- `tenant-id`: 租户ID -- `login-user-id`: 用户ID - -### 请求参数 -```json -{ - "pageNum": 1, - "pageSize": 10 -} -``` - -### 响应 -```json -{ - "code": 200, - "data": [ - { - "id": 1, - "chatId": "chat_123", - "messageId": "msg_456", - "content": "你好", - "role": "user", - "responseId": "", - "model": "", - "promptTokens": 0, - "completionTokens": 0, - "totalTokens": 0, - "createTime": "2024-01-01T10:00:00" - } - ], - "msg": "成功" -} -``` - -## 4. 获取对话详情 - -### 请求信息 -- **URL**: `/custom/chat/detail/{chatId}` -- **方法**: `GET` - -### 请求头 -- `tenant-id`: 租户ID -- `login-user-id`: 用户ID - -### 路径参数 -- `chatId`: 对话ID - -### 响应 -```json -{ - "code": 200, - "data": [ - { - "id": 1, - "chatId": "chat_123", - "messageId": "msg_456", - "content": "你好", - "role": "user", - "responseId": "", - "model": "", - "promptTokens": 10, - "completionTokens": 20, - "totalTokens": 30, - "createTime": "2024-01-01T10:00:00" - }, - { - "id": 2, - "chatId": "chat_123", - "messageId": "msg_789", - "content": "你好,我是AI助手", - "role": "assistant", - "responseId": "resp_123", - "model": "gpt-3.5-turbo", - "promptTokens": 10, - "completionTokens": 20, - "totalTokens": 30, - "createTime": "2024-01-01T10:00:05" - } - ], - "msg": "成功" -} -``` - -## 5. 删除对话记录 - -### 请求信息 -- **URL**: `/custom/chat/{chatId}` -- **方法**: `DELETE` - -### 请求头 -- `tenant-id`: 租户ID -- `login-user-id`: 用户ID - -### 路径参数 -- `chatId`: 对话ID - -### 响应 -```json -{ - "code": 200, - "data": true, - "msg": "成功" -} -``` - -## 注意事项 - -1. 所有接口都需要传递租户ID和用户ID -2. 流式接口会返回响应式流,需要特殊处理 -3. 对话记录会自动保存到数据库 -4. 删除操作实际上是软删除,只是将状态标记为已删除 \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java index 87e10e6670..4a898000cd 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java @@ -111,6 +111,7 @@ public interface FastGptDataApi { * @param request 聊天完成请求对象 * @return 流式响应 */ + @Streaming @POST("api/v1/chat/completions") @Headers({ "Content-Type: application/json", diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 1fc05f5463..c7e781d7f7 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -1,5 +1,5 @@ ### 流式对话接口测试 -GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_12&dataId=110702&userContent=腾讯公司最近发生了什么 +GET {{baseUrl}}/custom/chat/stream?category=ent_chat&chatId=chat_132&dataId=6702&userContent=腾讯公司最近发生了什么 Content-Type: application/json Accept: text/event-stream Authorization: Bearer {{token}} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index a2deedd563..1fc649968f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptAuthInterceptor; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptDataApi; import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; -import com.alibaba.druid.support.json.JSONUtils; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -21,13 +20,13 @@ import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - import javax.annotation.Resource; import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -262,7 +261,7 @@ public class FastGptService { * @return 流式响应 */ public Flux sendStreamChatRequest(String chatId, String content, String category, String dataId, String tenantId, - java.util.function.Consumer onComplete) { + java.util.function.Consumer onComplete) { if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); @@ -285,9 +284,8 @@ public class FastGptService { getStreamChatClient(category, tenantId).sendStreamChatRequest(request).enqueue(new retrofit2.Callback() { @Override public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - ResponseBody responseBody = response.body(); - if (responseBody != null) { + if (response.isSuccessful() && response.body() != null) { + try (ResponseBody responseBody = response.body()) { log.info("开始处理流式响应"); // 创建ObjectMapper用于解析JSON @@ -297,117 +295,106 @@ public class FastGptService { // 用于收集完整的响应内容 StringBuilder fullResponse = new StringBuilder(); - try { - // 使用异步线程处理流式响应 - asyncExecutor.submit(() -> { - try { - // 逐行读取响应内容 - BufferedReader reader = new BufferedReader(responseBody.charStream()); - String line; - int count = 0, done = 0; - String currentEvent = null; - - while ((line = reader.readLine()) != null && !sink.isCancelled()) { - log.info("接收到响应行: {}", line); - - if (done == 1) count++; - if (done == 1 && count > 3) { - if (!sink.isCancelled()) { - sink.next(new SseMessageWrapper("finished", "[DONE]")); - } - break; - } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(responseBody.byteStream()))) { + // 逐行读取响应内容 + String line; + int count = 0, done = 0; + String currentEvent = null; - // 处理SSE格式的数据 - if (!line.trim().isEmpty()) { - if (line.startsWith("event: ")) { - currentEvent = line.substring(7); - } else if (line.startsWith("data: ")) { - String data = line.substring(6); // 移除"data: "前缀 - try { - // 检查是否为结束标记 - if ("[DONE]".equals(data.trim())) { - log.info("流式传输结束"); - done = 1; - continue; - } + while ((line = reader.readLine()) != null && !sink.isCancelled()) { + log.info("接收到响应行: {}", line); - // 直接通过JSON路径提取content内容 - JsonNode jsonNode = objectMapper.readTree(data); - - // 已经发送过DONE,处理最后的flowResponse - if (done == 1) { - log.info("处理最后的汇总数据: {}", line); - if (jsonNode.isArray()) { - // 立即发送内容片段 - if (!sink.isCancelled()) { - sink.next(new SseMessageWrapper("flowResponses", handleFlowResponses(jsonNode))); - sink.next(new SseMessageWrapper("finished", "[DONE]")); - } - } - break; + if (done == 1) count++; + if (done == 1 && count > 3) { + if (!sink.isCancelled()) { + sink.next(new SseMessageWrapper("finished", "[DONE]")); + } + break; + } + + // 处理SSE格式的数据 + if (!line.trim().isEmpty()) { + if (line.startsWith("event: ")) { + currentEvent = line.substring(7); + } else if (line.startsWith("data: ")) { + String data = line.substring(6); // 移除"data: "前缀 + try { + // 检查是否为结束标记 + if ("[DONE]".equals(data.trim())) { + log.info("流式传输结束"); + done = 1; + continue; + } + + // 直接通过JSON路径提取content内容 + JsonNode jsonNode = objectMapper.readTree(data); + + // 已经发送过DONE,处理最后的flowResponse + if (done == 1) { + log.info("处理最后的汇总数据: {}", line); + if (jsonNode.isArray()) { + // 立即发送内容片段 + if (!sink.isCancelled()) { + sink.next(new SseMessageWrapper("flowResponses", handleFlowResponses(jsonNode))); + sink.next(new SseMessageWrapper("finished", "[DONE]")); } + } + break; + } - JsonNode choicesNode = jsonNode.get("choices"); - - if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { - JsonNode firstChoice = choicesNode.get(0); - JsonNode deltaNode = firstChoice.get("delta"); - - if (deltaNode != null) { - JsonNode contentNode = deltaNode.get("content"); - if (contentNode != null && contentNode.isTextual()) { - String text = contentNode.asText(); - if (!text.isEmpty()) { - log.info("接收到的内容片段: {}", text); - // 立即发送内容片段,使用从FastGPT收到的event - if (!sink.isCancelled() && currentEvent != null) { - String responseData = "{\"role\":\"assistant\",\"content\":\"" + text + "\"}"; - sink.next(new SseMessageWrapper(currentEvent, responseData)); - } - fullResponse.append(text); - } + JsonNode choicesNode = jsonNode.get("choices"); + + if (choicesNode != null && choicesNode.isArray() && !choicesNode.isEmpty()) { + JsonNode firstChoice = choicesNode.get(0); + JsonNode deltaNode = firstChoice.get("delta"); + + if (deltaNode != null) { + JsonNode contentNode = deltaNode.get("content"); + if (contentNode != null && contentNode.isTextual()) { + String text = contentNode.asText(); + if (!text.isEmpty()) { + log.info("接收到的内容片段: {}", text); + // 立即发送内容片段,使用从FastGPT收到的event + if (!sink.isCancelled() && currentEvent != null) { + String responseData = "{\"role\":\"assistant\",\"content\":\"" + text + "\"}"; + sink.next(new SseMessageWrapper(currentEvent, responseData)); } - } - } else { - if (!sink.isCancelled() && currentEvent != null) { - sink.next(new SseMessageWrapper(currentEvent, data)); + fullResponse.append(text); } } - } catch (Exception e) { - log.warn("解析JSON数据失败: {}", data, e); + } + } else { + if (!sink.isCancelled() && currentEvent != null) { + sink.next(new SseMessageWrapper(currentEvent, data)); } } - } - } - - reader.close(); - responseBody.close(); - } catch (Exception e) { - log.error("异步处理流式响应失败", e); - if (!sink.isCancelled()) { - sink.error(e); - } - return; - } - - if (!sink.isCancelled()) { - // 在流完成时调用回调函数 - if (onComplete != null) { - try { - onComplete.accept(fullResponse.toString()); } catch (Exception e) { - log.error("执行回调函数时发生错误", e); + log.warn("解析JSON数据失败: {}", data, e); } } - sink.complete(); } - }); + } + + reader.close(); + responseBody.close(); } catch (Exception e) { - log.error("提交异步任务失败", e); + log.error("异步处理流式响应失败", e); if (!sink.isCancelled()) { sink.error(e); } + return; + } + + if (!sink.isCancelled()) { + // 在流完成时调用回调函数 + if (onComplete != null) { + try { + onComplete.accept(fullResponse.toString()); + } catch (Exception e) { + log.error("执行回调函数时发生错误", e); + } + } + sink.complete(); } } } else { @@ -432,17 +419,6 @@ public class FastGptService { for (JsonNode item : jsonNode) { String moduleType = item.has("moduleType") ? item.get("moduleType").asText() : ""; - // 提取 chatNode 类型的数据 - if ("chatNode".equals(moduleType) && item.has("historyPreview")) { - JsonNode historyPreview = item.get("historyPreview"); - if (historyPreview.isArray() && !historyPreview.isEmpty()) { - JsonNode lastItem = historyPreview.get(historyPreview.size() - 1); - if (lastItem.has("obj") && "AI".equals(lastItem.get("obj").asText()) && lastItem.has("value")) { - flowResponse.setContent(lastItem.get("value").asText()); - } - } - } - // 提取 datasetSearchNode 类型的数据 if ("datasetSearchNode".equals(moduleType) && item.has("quoteList")) { JsonNode quoteListNode = item.get("quoteList"); -- Gitee From 71424a8a7074ea1bdd5215201df1d00f80e2a56b Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 10 Nov 2025 14:35:32 +0800 Subject: [PATCH 36/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/custom/controller/admin/ent/vo/EntRespVO.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java index 9971584b40..f5b3c530bc 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntRespVO.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; @Schema(description = "管理后台 - 企业 Response VO") @Data @@ -38,4 +39,7 @@ public class EntRespVO { @ExcelProperty("状态(0正常 1停用)") private Integer status; + @Schema(description = "创建时间") + private LocalDateTime createTime; + } -- Gitee From 6be2aee554050d3fb0e03bf0e1bec52332bbd18f Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 10 Nov 2025 17:08:27 +0800 Subject: [PATCH 37/50] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=92=8C=E4=BC=81=E4=B8=9A=E9=99=84=E4=BB=B6=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=88=B0fastgpt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/api/fastgpt/FastGptDataApi.java | 22 ++ .../api/fastgpt/vo/DatasetCreateRequest.java | 30 +++ .../custom/dal/dataobject/ent/EntDO.java | 4 +- .../custom/dal/dataobject/ent/EntInfoDO.java | 6 +- .../module/custom/job/EntInfoProcessJob.java | 190 ++++++++++-------- .../service/fastgpt/FastGptService.java | 50 ++++- 6 files changed, 211 insertions(+), 91 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetCreateRequest.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java index 4a898000cd..aff4aca4e2 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java @@ -30,6 +30,28 @@ public interface FastGptDataApi { @Body PushDataRequest request ); + /** + * 创建知识库 + * + * @param request 创建知识库请求参数 + * @return 创建的知识库信息 + */ + @POST("api/core/dataset/create") + Call> createDataset( + @Body DatasetCreateRequest request + ); + + /** + * 删除知识库 + * + * @param datasetId 知识库ID + * @return 删除结果 + */ + @DELETE("api/core/dataset/delete") + Call> deleteDataset( + @Query("id") String datasetId + ); + /** * 删除知识库集合下的数据 * diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetCreateRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetCreateRequest.java new file mode 100644 index 0000000000..632d9252ca --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetCreateRequest.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import lombok.Data; + +/** + * 创建数据集请求 + */ +@Data +public class DatasetCreateRequest { + /** 父级ID,用于构建目录结构。通常可以为 null 或者直接不传 */ + private String parentId; + /** 类型:dataset或folder,代表普通知识库和文件夹。不传则代表创建普通知识库 */ + private String type = "dataset"; + /** 知识库名(必填) */ + private String name; + /** 介绍(可选) */ + private String intro; + /** 头像地址(可选) */ + private String avatar; + /** 向量模型(不能为空,用系统默认的) */ + private String vectorModel = "default"; + /** 文本处理模型(不能为空,用系统默认的) */ + private String agentModel = "default"; + /** 图片理解模型(建议传空,用系统默认的) */ + private String vlmModel; + + public String getAvatar() { + return avatar == null ? "folder".equals(type) ? "/imgs/files/folder.svg" : "" : avatar; + } +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java index 987a63bfa0..a898806f2c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java @@ -51,9 +51,9 @@ public class EntDO extends TenantBaseDO { */ private Integer status; /** - * 上传fastgpt后的Id + * 对应fastgpt的知识库id */ - private String fastgptId; + private String datasetId; } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java index f5fb2731ed..577de142a8 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntInfoDO.java @@ -31,7 +31,7 @@ public class EntInfoDO extends TenantBaseDO { */ private String name; /** - * 资料来源(0 文档,1 爬虫,2 录音, 3 待办事项) + * 资料来源(0 文档,1 爬虫,2 录音, 3 待办事项, 4 文本) */ private Integer source; /** @@ -51,4 +51,8 @@ public class EntInfoDO extends TenantBaseDO { */ private Integer status; + /** + * 上传fastgpt后的数据集id + */ + private String collectionId; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 1100b7a08b..007d50755f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -30,6 +30,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Component @@ -82,6 +83,9 @@ public class EntInfoProcessJob implements JobHandler { return "处理异常:请前往基础设施 → 定时任务配置参数"; } + /* 这里将企业对应到fastgpt的知识库(datesetId)用于区分权限; + * 将企业信息对应到fastgpt的数据集(collectionId)自动拆块后就是一条条的数据*/ + // 获取entInfo表中status=-1的记录 for (String t : tenants) { EntInfoPageReqVO reqVO = new EntInfoPageReqVO(); @@ -93,9 +97,41 @@ public class EntInfoProcessJob implements JobHandler { List pendingEntInfos = pages.getList(); + // 获取去重的企业Id列表 + List entIds = pendingEntInfos.stream().map(EntInfoDO::getEntId).distinct().collect(Collectors.toList()); + List ents = entMapper.selectByIds(entIds); + String datasetId; + EntDO entDo; + for (EntDO ent : ents) { + if (ent.getDatasetId() == null || ent.getDatasetId().isEmpty()) { + datasetId = fastGptService.syncKnowledge(ent); + if (!StrUtil.isEmpty(datasetId)) { + ent.setDatasetId(datasetId); + entDo = new EntDO(); + entDo.setId(ent.getId()); + entDo.setDatasetId(datasetId); + entMapper.updateById(entDo); + } + } + } + + Map entMap = ents.stream().collect(Collectors.toMap(EntDO::getId, ent -> ent)); + String collectionId; + EntInfoDO entInfoDO; for (EntInfoDO entInfo : pendingEntInfos) { - // 处理逻辑将在后续步骤中实现 - processEntInfo(entInfo); + entDo = entMap.get(entInfo.getEntId()); + // 保存数据集 + collectionId = fastGptService.syncFaqCat(entDo.getTenantId().toString(), entInfo.getName(), entDo.getDatasetId()); + if (collectionId != null && !collectionId.isEmpty()) { + entInfo.setCollectionId(collectionId); + entInfoDO = new EntInfoDO(); + entInfoDO.setId(entInfoDO.getId()); + entInfoDO.setCollectionId(collectionId); + entInfoMapper.updateById(entInfoDO); + } + + // 保存数据集下的数据 + processEntInfo(entInfo, entDo); } return "处理完成,共处理 " + pendingEntInfos.size() + " 条记录(分页大小:" + pageSize + ")"; @@ -104,27 +140,28 @@ public class EntInfoProcessJob implements JobHandler { return "处理异常"; } - private void processEntInfo(EntInfoDO entInfo) { + /** + * 处理企业信息,企业信息为数据集,内容为一条条的数据 + * + * @param entInfo 企业信息 + * @param entDO 企业 + */ + private void processEntInfo(EntInfoDO entInfo, EntDO entDO) { + String content = null; // source=0时,读取文件内容 if (entInfo.getSource() == 0 && StrUtil.isNotEmpty(entInfo.getPath())) { - try { - String fastgptId; - // 找到对应的企业,看是否已经创了集合 - EntDO entDO = entMapper.selectOne(EntDO::getId, entInfo.getEntId()); - if (entDO.getFastgptId() == null || entDO.getFastgptId().isEmpty()) { - fastgptId = fastGptService.syncFaqCat(entDO.getTenantId().toString(), entDO.getName()); - if (!fastgptId.isEmpty()) { - EntDO upEnt = new EntDO(); - upEnt.setId(entDO.getId()); - upEnt.setFastgptId(fastgptId); - entMapper.updateById(upEnt); - } - } else fastgptId = entDO.getFastgptId(); + content = extractFileContent(entInfo.getPath()); + } else if(entInfo.getSource() == 4 && StrUtil.isNotEmpty(entInfo.getRemark())) { + content = entInfo.getRemark(); + } - String content = extractFileContent(entInfo.getPath()); - String time = String.valueOf(System.currentTimeMillis()); - // 将内容拆分成FAQ - fastGptService.getJsonRequest(time, content, "document_analysis", entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { + if (StrUtil.isEmpty(content)) return; + + try { + + String time = String.valueOf(System.currentTimeMillis()); + // 将内容拆分成FAQ + fastGptService.getJsonRequest(time, content, "document_analysis", entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { /*{ "summary": "", "todolist": "", @@ -133,49 +170,49 @@ public class EntInfoProcessJob implements JobHandler { "a": "" }] }*/ - PushDataRequest request = new PushDataRequest(); - JSONObject json = JSONUtil.parseObj(response); - List list = json.getJSONArray("list").stream() - .map(item -> JSONUtil.parseObj(item.toString())) - .collect(Collectors.toList()); - list.forEach(item -> { - request.addData(item.getStr("q"), item.getStr("a")); - }); - - // 提交到知识库 - boolean isSuccess = false; - if (!request.getData().isEmpty()) { - isSuccess = true; - fastGptService.syncFaq(entDO.getTenantId().toString(), fastgptId, request.getData()); - } + PushDataRequest request = new PushDataRequest(); + JSONObject json = JSONUtil.parseObj(response); + List list = json.getJSONArray("list").stream() + .map(item -> JSONUtil.parseObj(item.toString())) + .collect(Collectors.toList()); + list.forEach(item -> { + request.addData(item.getStr("q"), item.getStr("a")); + }); - // 更新entInfo的附件内容字段 - if (json.getStr("summary") != null && !json.getStr("summary").isEmpty() && isSuccess) { - EntInfoDO infoDO = new EntInfoDO(); - infoDO.setId(entInfo.getId()); - infoDO.setRemark(json.getStr("summary")); - infoDO.setStatus(0); - entInfoMapper.updateById(infoDO); - } + // 提交到知识库 + boolean isSuccess = false; + if (!request.getData().isEmpty()) { + isSuccess = true; + fastGptService.syncFaq(entDO.getTenantId().toString(), entInfo.getCollectionId(), request.getData()); + } - // 提取了待办事项直接入库 - if (json.getStr("todolist") != null && !json.getStr("todolist").isEmpty()) { - EntInfoDO infoDO = new EntInfoDO(); - infoDO.setSource(3); - infoDO.setStatus(2); - infoDO.setEntId(entDO.getId()); - infoDO.setName(entDO.getName() + "-待办" + DateUtils.format(new Date(), "yyyyMMdd")); - infoDO.setRemark(json.getStr("todolist")); - entInfoMapper.insert(infoDO); - } + // 更新entInfo的附件内容字段 + if (json.getStr("summary") != null && !json.getStr("summary").isEmpty() && isSuccess) { + EntInfoDO infoDO = new EntInfoDO(); + infoDO.setId(entInfo.getId()); + infoDO.setRemark(json.getStr("summary")); + infoDO.setStatus(0); + entInfoMapper.updateById(infoDO); + } - }); + // 提取了待办事项直接入库 + if (json.getStr("todolist") != null && !json.getStr("todolist").isEmpty()) { + EntInfoDO infoDO = new EntInfoDO(); + infoDO.setSource(3); + infoDO.setStatus(2); + infoDO.setEntId(entDO.getId()); + infoDO.setName(entDO.getName() + "-待办" + DateUtils.format(new Date(), "yyyyMMdd")); + infoDO.setRemark(json.getStr("todolist")); + entInfoMapper.insert(infoDO); + } - } catch (IOException e) { - // 处理文件读取异常 - log.error("文件读取异常:{}", e.getMessage(), e); - } + }); + + } catch (Exception e) { + // 处理文件读取异常 + log.error("处理文件异常:{}", e.getMessage(), e); } + } /** @@ -183,32 +220,19 @@ public class EntInfoProcessJob implements JobHandler { * * @param filePath 文件路径 * @return 文件内容 - * @throws IOException IO异常 */ - private String extractFileContent(String filePath) throws IOException { - File file = new File(filePath); - - // 检查文件是否存在 - if (!file.exists()) { - log.warn("文件不存在:{}", filePath); - return "文件不存在:" + filePath; - } - - // 检查文件是否可读 - if (!file.canRead()) { - log.warn("文件无法读取:{}", filePath); - return "文件无法读取:" + filePath; - } + private String extractFileContent(String filePath) { + try { + File file = new File(filePath); - // 检查文件大小 - if (file.length() > MAX_FILE_SIZE) { - log.warn("文件过大,跳过处理:{},大小:{}MB", filePath, file.length() / (1024 * 1024)); - return "文件过大,超过10MB限制"; - } + // 检查文件是否存在 + if (!file.exists()) { + log.warn("文件不存在:{}", filePath); + return "文件不存在:" + filePath; + } - String fileName = file.getName().toLowerCase(); + String fileName = file.getName().toLowerCase(); - try { if (fileName.endsWith(".docx")) { // 处理Word 2007+文档 return extractDocxContent(file); @@ -220,10 +244,12 @@ public class EntInfoProcessJob implements JobHandler { return FileUtil.readUtf8String(file); } else if (fileName.endsWith(".wav") || fileName.endsWith(".mp3")) { // 处理录音文件,使用funasr - return "请配置funasr"; + log.error("请配置funasr"); + return ""; } else { // 其他格式文件返回提示 - return "不支持的文件格式:" + fileName; + log.error("不支持的文件格式:{}", fileName); + return ""; } } catch (Exception e) { log.error("提取文件内容失败:{}", filePath, e); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 1fc649968f..bcadedeefe 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptAuthInterceptor; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptDataApi; import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -25,8 +26,6 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,7 +39,6 @@ public class FastGptService { private DictDataCommonApi dictDataCommonApi; private static final Map apiCache = new ConcurrentHashMap<>(); - private static final ExecutorService asyncExecutor = Executors.newFixedThreadPool(10); /** * 同步FAQ数据详情,创建场景(只有)的时候批量添加,文件夹id为数据集,faq为数据集下的数据; @@ -82,16 +80,15 @@ public class FastGptService { * @param tenantId 租户ID * @param name 数据集名称 */ - public String syncFaqCat(String tenantId, String name) { + public String syncFaqCat(String tenantId, String name, String datasetId) { String collectionId = ""; try { - String sceneGptId = getDictToken(tenantId, "ent_knowledge"); // 将faq的目录作为数据集 log.info(" 创建空数据集..."); CollectionCreateRequest createRequest = new CollectionCreateRequest(); createRequest.setName(name); - createRequest.setDatasetId(sceneGptId); + createRequest.setDatasetId(datasetId); Response> response = getApiClient(tenantId).createEmptyCollection(createRequest).execute(); if (response.code() == 200 && response.body() != null) { @@ -115,6 +112,47 @@ public class FastGptService { return collectionId; } + /** + * 同步知识库数据 + * + * @param ent 企业信息 + * @return String 知识库id + */ + public String syncKnowledge(EntDO ent) { + String gptId = "", tenantId = ent.getTenantId().toString(); + + // 知识库所属租户文件夹的Id + String knowledgeFolder = getDictToken(tenantId, "ent_knowledge_folder"); + if (knowledgeFolder == null || knowledgeFolder.isEmpty()) { + log.info("[syncKnowledge][ent_knowledge_folder参数为空]"); + return "处理异常:请前往数据字典 → fastgpt配置参数 ent_knowledge_folder"; + } + + // 判断根据不同的操作类型进行操作 + try { + log.info(" 创建企业知识库..."); + DatasetCreateRequest createRequest = new DatasetCreateRequest(); + createRequest.setName(ent.getName()); + createRequest.setIntro("企业【" + createRequest.getName() + "】属于【" + ent.getIndustry() + "】行业。" + ent.getRemark()); + + Response> response = getApiClient(tenantId).createDataset(createRequest).execute(); + if (response.code() == 200) { + if (response.body() != null) { + gptId = response.body().getData(); + log.info("✓✓✓ 创建企业知识库调用成功,name:{}", ent.getName()); + return gptId; + } + + } else { + log.error("XXX HTTP状态码={},创建租户知识库调用失败:{}", response.code(), response.errorBody()); + } + } catch (Exception e) { + log.error("知识库同步到fastgpt出错:", e); + } + + return gptId; + } + /** * 知识检索内容(异步版本) * -- Gitee From 312ee5797a53125a07e72ddb91769293ca289626 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 11 Nov 2025 16:29:26 +0800 Subject: [PATCH 38/50] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E4=BA=8B=E9=A1=B9=E6=8E=A5=E5=8F=A3=E5=92=8C=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/api/ChatController.java | 45 ++++++++++++ .../admin/api/ThirdPartyController.java | 69 +++++++++++++++++++ .../admin/api/vo/ChatTodoRespVO.java | 68 ++++++++++++++++++ .../admin/api/vo/ThirdEntRespVO.java | 18 +++++ .../admin/ent/vo/EntImportReqVO.java | 2 +- .../module/custom/service/ent/EntService.java | 13 ++++ .../custom/service/ent/EntServiceImpl.java | 23 +++++++ .../service/fastgpt/FastGptService.java | 9 ++- .../custom/service/third/ThirdService.java | 15 ++++ .../service/third/ThirdServiceImpl.java | 48 +++++++++++++ 10 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ThirdEntRespVO.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdService.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index 5c0be441aa..810e44f9a9 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -7,8 +7,11 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatTodoRespVO; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.chat.ChatService; +import cn.iocoder.yudao.module.custom.service.ent.EntService; import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -24,6 +27,8 @@ import reactor.core.publisher.Flux; import javax.validation.Valid; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -39,6 +44,8 @@ public class ChatController { private final ChatService chatService; + private final EntService entService; + @Operation(summary = "与fastgpt进行流式对话") @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @Parameter(name = "category", description = "对话流程类型", required = true) @@ -136,4 +143,42 @@ public class ChatController { return success(true); } + + /** + * 查询用户的待办事项 + */ + @Operation(summary = "查询用户的待办事项") + @GetMapping("/todo") + @PreAuthorize("@ss.hasPermission('custom:chat:stream')") + public CommonResult> getTodoList() { + Map> entInfoList = entService.getEntInfoList(); + // 将entInfoList转换成List + List todoList = entInfoList.entrySet().stream() + .map(entry -> new ChatTodoRespVO(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + + return success(todoList); + } + + + /** + * 用户点击完成待办事项 + */ + @Operation(summary = "用户点击完成待办事项") + @PostMapping("/todo/done") + @PreAuthorize("@ss.hasPermission('custom:chat:stream')") + public CommonResult doneTodoList(@RequestParam Long id) { + int i = entService.changeStatus(id); + return success(i > 0); + } + + /** + * 查询用户权限范围内的企业列表 + */ + @Operation(summary = "查询用户权限范围内的企业列表") + @GetMapping("/ent") + @PreAuthorize("@ss.hasPermission('custom:chat:stream')") + public CommonResult> getEntList() { + return success(entService.getEntByUser()); + } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java new file mode 100644 index 0000000000..64329a884a --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api; + +import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; +import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; +import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; +import cn.iocoder.yudao.module.custom.service.ent.EntService; +import cn.iocoder.yudao.module.custom.service.third.ThirdService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "第三方接口 - 企业信息") +@RestController +@RequestMapping("/custom/third") +@Validated +@Slf4j +@RequiredArgsConstructor +public class ThirdPartyController { + + private final ThirdService thirdService; + private final DictDataCommonApi dictDataCommonApi; + + @Operation(summary = "第三方获取企业列表") + @GetMapping("/ent/list") + public CommonResult> getEntList( + @RequestHeader("X-Tenant-Id") Long tenantId, + @RequestHeader("X-API-Key") String apiKey) { + + // 验证API密钥和租户权限 + if (!validateThirdPartyAccess(apiKey, tenantId)) { + return CommonResult.error(401, "未授权的访问"); + } + + try { + List result = thirdService.getEntList(tenantId); + return success(result); + } catch (Exception e) { + log.error("第三方获取企业列表失败,tenantId: {}", tenantId, e); + return CommonResult.error(500, "系统内部错误"); + } + } + + private boolean validateThirdPartyAccess(String apiKey, Long tenantId) { + // 检查API密钥和租户权限 + List apiKeys = dictDataCommonApi.getDictDataList("third_api"); + for (DictDataRespDTO apiKeyItem : apiKeys) { + if (apiKeyItem.getLabel().equals("key_" + tenantId) && apiKeyItem.getValue().equals(apiKey)) { + // 验证API密钥和租户权限 + return true; + } + } + + return false; // 简化示例 + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java new file mode 100644 index 0000000000..87eb6b3159 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "对话记录 - 待办事项") +@Data +@ExcelIgnoreUnannotated +public class ChatTodoRespVO { + + @Schema(description = "企业名称", example = "腾讯") + private String name; + + public ChatTodoRespVO(String key, List value) { + this.name = key; + + for (EntInfoDO entInfoDO : value) { + this.addTodo(entInfoDO.getId(), entInfoDO.getRemark(), + entInfoDO.getStatus() == 3 ? "已办结" : entInfoDO.getStatus() == 2 ? "待办" : "其他", entInfoDO.getCreateTime()); + } + } + + @Data + public class Todo { + + @Schema(description = "企业附件id", example = "123") + private Long id; + + @Schema(description = "待办内容", example = "随便") + private String content; + + @Schema(description = "待办状态", example = "已办结") + private String status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + public Todo(Long id, String content, String status, LocalDateTime createTime) { + this.id = id; + this.content = content; + this.status = status; + this.createTime = createTime; + } + } + + @Schema(description = "待办列表") + private List todoList; + + public void addTodo(Long id, String content, String status, LocalDateTime createTime) { + if (todoList == null) { + todoList = new java.util.ArrayList<>(); + } + todoList.add(new Todo(id, content, status, createTime)); + } + + public ChatTodoRespVO() { + } + + public ChatTodoRespVO(String name) { + this.name = name; + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ThirdEntRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ThirdEntRespVO.java new file mode 100644 index 0000000000..4bd536534d --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ThirdEntRespVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class ThirdEntRespVO { + + @Schema(description = "爬虫对应的collectionId", example = "123") + private String collectionId; + + @Schema(description = "企业名称", example = "张三") + private String name; + + @Schema(description = "企业官网地址") + private String domain; + +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java index 8f4da417dc..ebe2db4732 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java @@ -14,7 +14,7 @@ public class EntImportReqVO { @NotEmpty(message = "企业名称不能为空") private String name; - @ExcelProperty("企业所属行业") + @ExcelProperty("企业官网地址") private String industry; @ExcelProperty("说明") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index 49d0d05d7c..c38f491116 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; @@ -10,6 +11,7 @@ import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import javax.validation.Valid; import java.io.IOException; import java.util.List; +import java.util.Map; /** * 企业 Service 接口 @@ -107,4 +109,15 @@ public interface EntService { void deleteEntInfo(Long id); PageResult getEntInfoPage(@Valid EntInfoPageReqVO infoPageReqVO); + + List getEntByUser(); + + Map> getEntInfoList(); + + /** + * 根据企业附件Id,办结待办事项 + * @param id 企业附件Id + * @return + */ + int changeStatus(Long id); } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index f4601b760b..86321701c4 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; @@ -14,7 +15,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.custom.enums.ErrorCodeConstants.ENT_NOT_EXISTS; @@ -153,9 +158,27 @@ public class EntServiceImpl implements EntService { return entInfoMapper.selectPage(infoPageReqVO); } + @Override + public List getEntByUser() { + List ents = entMapper.selectList(); + return ents.stream().map(EntDO::getDatasetId).filter(Objects::nonNull).collect(Collectors.toList()); + } + @Override public void importEntList(List entList) { entMapper.insertBatch(entList); } + @Override + public Map> getEntInfoList() { + return entInfoMapper.selectList("source", 3).stream().collect(Collectors.groupingBy(EntInfoDO::getName)); + } + + @Override + public int changeStatus(Long id) { + EntInfoDO entInfo = new EntInfoDO(); + entInfo.setId( id); + entInfo.setStatus(3); + return entInfoMapper.updateById(entInfo); + } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index bcadedeefe..c768deb13e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.custom.service.fastgpt; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptAuthInterceptor; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptDataApi; import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; @@ -310,7 +311,13 @@ public class FastGptService { request.setStream(true); request.setDetail(true); request.setRetainDatasetCite(true); - request.setVariables(new HashMap<>()); + + Map variables = new HashMap<>(); + Long userId = SecurityFrameworkUtils.getLoginUserId(), + deptId = SecurityFrameworkUtils.getLoginUserDeptId(); + variables.put("sysDeptId", deptId); + variables.put("sysUserId", userId); + request.setVariables(variables); // 创建消息 ChatCompletionRequest.ChatMessage message = new ChatCompletionRequest.ChatMessage(content); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdService.java new file mode 100644 index 0000000000..2606127d5c --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdService.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.custom.service.third; + +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; + +import java.util.List; + +/** + * 企业 Service 接口 + * + * @author Asy + */ +public interface ThirdService { + + List getEntList(Long tenantId); +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java new file mode 100644 index 0000000000..edb7d41609 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.custom.service.third; + +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; +import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +@Validated +public class ThirdServiceImpl implements ThirdService { + + @Resource + private EntMapper entMapper; + @Resource + private EntInfoMapper entInfoMapper; + + @Override + public List getEntList(Long tenantId) { + TenantContextHolder.setTenantId(tenantId); + List ents = entMapper.selectList(); + Map entMap = ents.stream().collect(Collectors.toMap(EntDO::getId, entDO -> entDO)); + + List entInfos = entInfoMapper.selectList("source", 1); + + return entInfos.stream().map(entInfoDO -> { + if (entMap.get(entInfoDO.getEntId()) != null) { + ThirdEntRespVO thirdEntRespVO = new ThirdEntRespVO(); + thirdEntRespVO.setCollectionId(entInfoDO.getCollectionId()); + if (entMap.get(entInfoDO.getEntId()) != null) { + thirdEntRespVO.setName(entMap.get(entInfoDO.getEntId()).getName()); + thirdEntRespVO.setDomain(entMap.get(entInfoDO.getEntId()).getIndustry()); + } + return thirdEntRespVO; + } + return null; + }).filter(Objects::nonNull).collect(Collectors.toList()); + } +} -- Gitee From 758d7a226dbbd3c9ffa4c5a0ebf2a9c8efe26a51 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 12 Nov 2025 14:44:46 +0800 Subject: [PATCH 39/50] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../permission/PermissionCommonApi.java | 9 + .../system/permission/dto/UserRespDTO.java | 34 +++ .../YudaoSecurityAutoConfiguration.java | 14 +- .../YudaoWebSecurityConfigurerAdapter.java | 8 + .../filter/ApiKeyAuthenticationFilter.java | 220 ++++++++++++++++++ .../controller/admin/api/ChatController.http | 23 +- .../controller/admin/api/ChatController.java | 28 --- .../admin/api/ThirdPartyController.java | 73 +++--- .../admin/ent/vo/EntImportReqVO.java | 3 + .../controller/admin/ent/vo/EntSaveReqVO.java | 4 + .../custom/dal/dataobject/ent/EntDO.java | 6 + .../custom/service/ent/EntServiceImpl.java | 49 +++- .../service/third/ThirdServiceImpl.java | 23 +- .../api/permission/PermissionApiImpl.java | 6 + .../service/permission/PermissionService.java | 9 + .../permission/PermissionServiceImpl.java | 15 ++ 16 files changed, 441 insertions(+), 83 deletions(-) create mode 100644 yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/dto/UserRespDTO.java create mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/ApiKeyAuthenticationFilter.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/PermissionCommonApi.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/PermissionCommonApi.java index f842ed0eea..feca15fa55 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/PermissionCommonApi.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/PermissionCommonApi.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.framework.common.biz.system.permission; import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.UserRespDTO; /** * 权限 API 接口 @@ -35,4 +36,12 @@ public interface PermissionCommonApi { */ DeptDataPermissionRespDTO getDeptDataPermission(Long userId); + /** + * 获得登陆用户信息 + * + * @param userId 用户编号 + * @param tenantId 租户编号 + * @return 用户信息 + */ + UserRespDTO getUser(Long userId, Long tenantId); } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/dto/UserRespDTO.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/dto/UserRespDTO.java new file mode 100644 index 0000000000..367def512f --- /dev/null +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/permission/dto/UserRespDTO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.framework.common.biz.system.permission.dto; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import lombok.Data; + +import java.util.Set; + +/** + * Admin 用户 Response DTO + * + * @author 芋道源码 + */ +@Data +public class UserRespDTO { + + /** + * 用户ID + */ + private Long id; + /** + * 用户账号 + */ + private String username; + + /** + * 部门ID + */ + private Long deptId; + /** + * 所属租户 + */ + private Long tenantId; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index b3793bc0e5..d54832758d 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -1,15 +1,16 @@ package cn.iocoder.yudao.framework.security.config; +import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi; import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy; +import cn.iocoder.yudao.framework.security.core.filter.ApiKeyAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService; import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkServiceImpl; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; -import javax.annotation.Resource; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -21,6 +22,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; +import javax.annotation.Resource; + /** * Spring Security 自动配置类,主要用于相关组件的配置 * @@ -73,6 +76,15 @@ public class YudaoSecurityAutoConfiguration { return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi); } + /** + * API Key 认证过滤器 Bean + */ + @Bean + public ApiKeyAuthenticationFilter apiKeyAuthenticationFilter(DictDataCommonApi dictDataCommonApi, + PermissionCommonApi permissionCommonApi) { + return new ApiKeyAuthenticationFilter(dictDataCommonApi, permissionCommonApi); + } + @Bean("ss") // 使用 Spring Security 的缩写,方便使用 public SecurityFrameworkService securityFrameworkService(PermissionCommonApi permissionApi) { return new SecurityFrameworkServiceImpl(permissionApi); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java index f5bb279683..70db5cdade 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.framework.security.config; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.security.core.filter.ApiKeyAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.web.config.WebProperties; import com.google.common.collect.HashMultimap; @@ -68,6 +69,11 @@ public class YudaoWebSecurityConfigurerAdapter { */ @Resource private TokenAuthenticationFilter authenticationTokenFilter; + /** + * API Key 认证过滤器 Bean + */ + @Resource + private ApiKeyAuthenticationFilter apiKeyAuthenticationFilter; /** * 自定义的权限映射 Bean 们 @@ -147,6 +153,8 @@ public class YudaoWebSecurityConfigurerAdapter { .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景 .anyRequest().authenticated()); + // 添加 API Key Filter (在 Token Filter 之前) + httpSecurity.addFilterBefore(apiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 添加 Token Filter httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); return httpSecurity.build(); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/ApiKeyAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/ApiKeyAuthenticationFilter.java new file mode 100644 index 0000000000..d369dfe9d0 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/ApiKeyAuthenticationFilter.java @@ -0,0 +1,220 @@ +package cn.iocoder.yudao.framework.security.core.filter; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; +import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; +import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.UserRespDTO; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * API Key 认证过滤器 + * 用于第三方接口的 API Key 认证 + * + * @author 芋道源码 + */ +@Slf4j +@RequiredArgsConstructor +public class ApiKeyAuthenticationFilter extends OncePerRequestFilter { + + private final DictDataCommonApi dictDataCommonApi; + private final PermissionCommonApi permissionCommonApi; + + /** + * 需要进行 API Key 认证的路径前缀 + */ + private static final String API_KEY_PATH_PREFIX = "/custom/third/"; + + /** + * API Key 请求头名称 + */ + private static final String API_KEY_HEADER = "X-API-Key"; + + /** + * 租户ID 请求头名称 + */ + private static final String TENANT_ID_HEADER = "X-Tenant-Id"; + + /** + * 用户ID 请求头名称(可选,用于指定第三方接口访问时使用的用户ID) + */ + private static final String USER_ID_PARAM = "sysUserId"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + String requestUri = request.getRequestURI(); + + // 只对第三方接口路径进行 API Key 认证 + if (!requestUri.contains(API_KEY_PATH_PREFIX)) { + chain.doFilter(request, response); + return; + } + + try { + // 获取 API Key 和租户ID + String apiKey = request.getHeader(API_KEY_HEADER); + String tenantIdStr = request.getHeader(TENANT_ID_HEADER); + + // 验证必填参数 + if (StrUtil.isBlank(apiKey)) { + writeErrorResponse(response, GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), + "缺少 API Key"); + return; + } + + if (StrUtil.isBlank(tenantIdStr)) { + writeErrorResponse(response, GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), + "缺少租户ID"); + return; + } + + Long tenantId; + try { + tenantId = Long.parseLong(tenantIdStr); + } catch (NumberFormatException e) { + writeErrorResponse(response, GlobalErrorCodeConstants.BAD_REQUEST.getCode(), + "租户ID格式错误"); + return; + } + + // 验证 API Key + if (!validateApiKey(apiKey, tenantId)) { + log.warn("API Key 验证失败,tenantId: {}, apiKey: {}", tenantId, apiKey); + writeErrorResponse(response, GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), + "API Key 验证失败,请检查"); + return; + } + + // 创建 LoginUser 并设置到 Security 上下文,以支持数据权限 + Long userId = null; + String userIdStr = request.getParameter(USER_ID_PARAM); + if (StrUtil.isNotBlank(userIdStr)) { + try { + userId = Long.parseLong(userIdStr); + } catch (NumberFormatException e) { + log.warn("用户ID格式错误: {}", userIdStr); + } + + // 创建 LoginUser 以支持数据权限 + LoginUser loginUser = buildLoginUser(userId, tenantId); + if (loginUser != null) { + SecurityFrameworkUtils.setLoginUser(loginUser, request); + log.debug("API Key 验证成功,tenantId: {}, userId: {}", tenantId, loginUser.getId()); + } else { + log.debug("API Key 验证成功,tenantId: {}(未创建 LoginUser)", tenantId); + writeErrorResponse(response, GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), + "用户验证失败,请检查"); + } + } + } catch (Exception e) { + log.error("API Key 认证过程发生异常", e); + writeErrorResponse(response, GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(), + "系统内部错误"); + return; + } + + // 继续过滤链 + chain.doFilter(request, response); + } + + /** + * 构建 LoginUser 对象,用于支持数据权限 + * + * @param userId 用户ID(可选) + * @param tenantId 租户ID + * @return LoginUser 对象 + */ + private LoginUser buildLoginUser(Long userId, Long tenantId) { + try { + // 如果没有指定用户ID,尝试获取租户管理员用户 + if (userId == null) { + log.debug("未指定用户ID,跳过创建 LoginUser"); + return null; + } + + // 获取用户信息 + UserRespDTO user = permissionCommonApi.getUser(userId, tenantId); + if (user == null) { + log.warn("用户不存在: userId={}", userId); + return null; + } + + // 创建 LoginUser + LoginUser loginUser = new LoginUser() + .setId(user.getId()) + .setUserType(UserTypeEnum.ADMIN.getValue()) + .setTenantId(tenantId); + + // 获取并设置数据权限信息到上下文 + DeptDataPermissionRespDTO deptDataPermission = permissionCommonApi.getDeptDataPermission(userId); + if (deptDataPermission != null) { + loginUser.setContext("DeptDataPermissionRule", deptDataPermission); + } + + return loginUser; + } catch (Exception e) { + log.error("构建 LoginUser 失败: userId={}, tenantId={}", userId, tenantId, e); + return null; + } + } + + /** + * 验证 API Key 和租户ID + * + * @param apiKey API Key + * @param tenantId 租户ID + * @return 是否验证通过 + */ + private boolean validateApiKey(String apiKey, Long tenantId) { + try { + // 从字典中获取第三方 API Key 配置 + List apiKeys = dictDataCommonApi.getDictDataList("custom_third_api"); + + if (apiKeys == null || apiKeys.isEmpty()) { + log.warn("未配置第三方 API Key,请在字典管理中添加 third_api 字典"); + return false; + } + + // 验证 API Key 和租户ID 的匹配关系 + for (DictDataRespDTO apiKeyItem : apiKeys) { + // 字典标签格式:key_{tenantId} + // 字典值:实际的 API Key + String expectedLabel = "key_" + tenantId; + if (expectedLabel.equals(apiKeyItem.getLabel()) && apiKey.equals(apiKeyItem.getValue())) { + return true; + } + } + + return false; + } catch (Exception e) { + log.error("验证 API Key 时发生异常", e); + return false; + } + } + + /** + * 写入错误响应 + */ + private void writeErrorResponse(HttpServletResponse response, Integer code, String message) { + CommonResult result = CommonResult.error(code, message); + ServletUtils.writeJSON(response, result); + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index c7e781d7f7..32e05522a8 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -37,4 +37,25 @@ tenant-id: {{adminTenantId}} ### 批量删除对话记录测试 DELETE {{baseUrl}}/custom/chat/delete-list?chatIds=test_chat_123,test_chat_456 Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} \ No newline at end of file +tenant-id: {{adminTenantId}} + + +### 查询用户的待办事项 +GET {{baseUrl}}/custom/third/todo?sysUserId=146 +X-Tenant-Id: 162 +X-API-Key: rspCTk8PrVAtS4SFNNj7ppZ7L2dnVJIw + +### 用户点击完成待办事项 +POST {{baseUrl}}/custom/chat/todo/done?id=1 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 查询用户权限范围内的企业列表 +GET {{baseUrl}}/custom/third/ent?sysUserId=144 +X-Tenant-Id: 162 +X-API-Key: rspCTk8PrVAtS4SFNNj7ppZ7L2dnVJIw + +### 获取所有企业列表 +GET {{baseUrl}}/custom/third/ent/all?sysUserId=143 +X-Tenant-Id: 162 +X-API-Key: rspCTk8PrVAtS4SFNNj7ppZ7L2dnVJIw \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index 810e44f9a9..cb78fc7b35 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -143,24 +143,6 @@ public class ChatController { return success(true); } - - /** - * 查询用户的待办事项 - */ - @Operation(summary = "查询用户的待办事项") - @GetMapping("/todo") - @PreAuthorize("@ss.hasPermission('custom:chat:stream')") - public CommonResult> getTodoList() { - Map> entInfoList = entService.getEntInfoList(); - // 将entInfoList转换成List - List todoList = entInfoList.entrySet().stream() - .map(entry -> new ChatTodoRespVO(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()); - - return success(todoList); - } - - /** * 用户点击完成待办事项 */ @@ -171,14 +153,4 @@ public class ChatController { int i = entService.changeStatus(id); return success(i > 0); } - - /** - * 查询用户权限范围内的企业列表 - */ - @Operation(summary = "查询用户权限范围内的企业列表") - @GetMapping("/ent") - @PreAuthorize("@ss.hasPermission('custom:chat:stream')") - public CommonResult> getEntList() { - return success(entService.getEntByUser()); - } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java index 64329a884a..9e209bb295 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java @@ -1,10 +1,9 @@ package cn.iocoder.yudao.module.custom.controller.admin.api; -import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; -import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatTodoRespVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; -import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.ent.EntService; import cn.iocoder.yudao.module.custom.service.third.ThirdService; @@ -12,14 +11,14 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; +import javax.annotation.security.PermitAll; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -29,41 +28,49 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Validated @Slf4j @RequiredArgsConstructor +@PermitAll // 允许所有第三方接口免登录,通过 API Key 过滤器进行认证 public class ThirdPartyController { - + private final ThirdService thirdService; - private final DictDataCommonApi dictDataCommonApi; - + + private final EntService entService; + @Operation(summary = "第三方获取企业列表") - @GetMapping("/ent/list") - public CommonResult> getEntList( - @RequestHeader("X-Tenant-Id") Long tenantId, - @RequestHeader("X-API-Key") String apiKey) { - - // 验证API密钥和租户权限 - if (!validateThirdPartyAccess(apiKey, tenantId)) { - return CommonResult.error(401, "未授权的访问"); - } - + @GetMapping("/ent/all") + public CommonResult> getAllEnt() { try { + // 从上下文获取租户ID(已由 ApiKeyAuthenticationFilter 设置) + Long tenantId = TenantContextHolder.getRequiredTenantId(); + List result = thirdService.getEntList(tenantId); return success(result); } catch (Exception e) { - log.error("第三方获取企业列表失败,tenantId: {}", tenantId, e); + log.error("第三方获取企业列表失败", e); return CommonResult.error(500, "系统内部错误"); } } - - private boolean validateThirdPartyAccess(String apiKey, Long tenantId) { - // 检查API密钥和租户权限 - List apiKeys = dictDataCommonApi.getDictDataList("third_api"); - for (DictDataRespDTO apiKeyItem : apiKeys) { - if (apiKeyItem.getLabel().equals("key_" + tenantId) && apiKeyItem.getValue().equals(apiKey)) { - // 验证API密钥和租户权限 - return true; - } - } - return false; // 简化示例 + /** + * 查询用户的待办事项 + */ + @Operation(summary = "查询用户的待办事项") + @GetMapping("/todo") + public CommonResult> getTodoList() { + Map> entInfoList = entService.getEntInfoList(); + // 将entInfoList转换成List + List todoList = entInfoList.entrySet().stream() + .map(entry -> new ChatTodoRespVO(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + + return success(todoList); + } + + /** + * 查询用户权限范围内的企业列表 + */ + @Operation(summary = "查询用户权限范围内的企业列表") + @GetMapping("/ent") + public CommonResult> getEntList() { + return success(entService.getEntByUser()); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java index ebe2db4732..dbcda8bc3f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java @@ -15,6 +15,9 @@ public class EntImportReqVO { private String name; @ExcelProperty("企业官网地址") + private String domain; + + @ExcelProperty("企业所属行业") private String industry; @ExcelProperty("说明") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java index d682c5e40e..dd670403a7 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntSaveReqVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.custom.controller.admin.ent.vo; +import cn.idev.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -14,6 +15,9 @@ public class EntSaveReqVO { @Schema(description = "企业名称", example = "张三") private String name; + @Schema(description = "企业官网地址") + private String domain; + @Schema(description = "所属行业") private String industry; diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java index a898806f2c..bf937c1641 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/ent/EntDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.custom.dal.dataobject.ent; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; @@ -34,6 +35,11 @@ public class EntDO extends TenantBaseDO { * 所属行业 */ private String industry; + /** + * 官网地址(入info库) + */ + @TableField(exist = false) + private String domain; /** * 说明 */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 86321701c4..b22af5f95d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -2,23 +2,24 @@ package cn.iocoder.yudao.module.custom.service.ent; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntSaveReqVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -48,6 +49,17 @@ public class EntServiceImpl implements EntService { // 插入子表 createEntInfo(ent.getId(), ent.getName(), createReqVO.getAttachmentPath()); + // 插入爬虫数据 + if (createReqVO.getRemark() != null) { + EntInfoDO entInfoDO = new EntInfoDO(); + entInfoDO.setEntId(ent.getId()); + entInfoDO.setName(ent.getName() + "-爬虫"); + entInfoDO.setPath(createReqVO.getDomain()); + entInfoDO.setSource(1); + entInfoDO.setStatus(0); + entInfoMapper.insert(entInfoDO); + } + // 返回 return ent.getId(); } @@ -159,6 +171,7 @@ public class EntServiceImpl implements EntService { } @Override + @DataPermission public List getEntByUser() { List ents = entMapper.selectList(); return ents.stream().map(EntDO::getDatasetId).filter(Objects::nonNull).collect(Collectors.toList()); @@ -167,11 +180,37 @@ public class EntServiceImpl implements EntService { @Override public void importEntList(List entList) { entMapper.insertBatch(entList); + + // 批量插入爬虫 + List entInfoList = new ArrayList<>(); + entList.forEach(ent -> { + EntInfoDO entInfoDO = new EntInfoDO(); + entInfoDO.setEntId(ent.getId()); + entInfoDO.setName(ent.getName() + "-爬虫"); + entInfoDO.setPath(ent.getDomain()); + entInfoDO.setSource(1); + entInfoDO.setStatus(0); + entInfoList.add(entInfoDO); + }); + + if(!entInfoList.isEmpty()) + entInfoMapper.insert(entInfoList); } @Override + @DataPermission public Map> getEntInfoList() { - return entInfoMapper.selectList("source", 3).stream().collect(Collectors.groupingBy(EntInfoDO::getName)); + List ents = entMapper.selectList(); + Map entMap = ents.stream() + .collect(Collectors.toMap(EntDO::getId, ent -> ent)); + Map> infos = entInfoMapper.selectList(new LambdaQueryWrapperX() + .in(EntInfoDO::getEntId, entMap.keySet()) + .eq(EntInfoDO::getSource, 3) + ).stream().collect(Collectors.groupingBy(EntInfoDO::getEntId)); + return infos.entrySet().stream().collect(Collectors.toMap( + entry -> entMap.get(entry.getKey()).getName(), + Map.Entry::getValue + )); } @Override diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java index edb7d41609..753821d547 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/third/ThirdServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.custom.service.third; +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; @@ -25,24 +26,16 @@ public class ThirdServiceImpl implements ThirdService { private EntInfoMapper entInfoMapper; @Override + @DataPermission // 启用数据权限控制 public List getEntList(Long tenantId) { - TenantContextHolder.setTenantId(tenantId); - List ents = entMapper.selectList(); - Map entMap = ents.stream().collect(Collectors.toMap(EntDO::getId, entDO -> entDO)); - List entInfos = entInfoMapper.selectList("source", 1); return entInfos.stream().map(entInfoDO -> { - if (entMap.get(entInfoDO.getEntId()) != null) { - ThirdEntRespVO thirdEntRespVO = new ThirdEntRespVO(); - thirdEntRespVO.setCollectionId(entInfoDO.getCollectionId()); - if (entMap.get(entInfoDO.getEntId()) != null) { - thirdEntRespVO.setName(entMap.get(entInfoDO.getEntId()).getName()); - thirdEntRespVO.setDomain(entMap.get(entInfoDO.getEntId()).getIndustry()); - } - return thirdEntRespVO; - } - return null; - }).filter(Objects::nonNull).collect(Collectors.toList()); + ThirdEntRespVO thirdEntRespVO = new ThirdEntRespVO(); + thirdEntRespVO.setCollectionId(entInfoDO.getCollectionId()); + thirdEntRespVO.setName(entInfoDO.getName()); + thirdEntRespVO.setDomain(entInfoDO.getPath()); + return thirdEntRespVO; + }).filter(e -> e.getCollectionId() != null).collect(Collectors.toList()); } } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java index 08548ebdfc..9f2dfbd7df 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.api.permission; import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.UserRespDTO; import cn.iocoder.yudao.module.system.service.permission.PermissionService; import org.springframework.stereotype.Service; @@ -39,4 +40,9 @@ public class PermissionApiImpl implements PermissionApi { return permissionService.getDeptDataPermission(userId); } + @Override + public UserRespDTO getUser(Long userId, Long tenantId) { + return permissionService.getUser(userId, tenantId); + } + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java index f7f8122b81..d1e5520dd2 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.UserRespDTO; import java.util.Collection; import java.util.Set; @@ -143,4 +144,12 @@ public interface PermissionService { */ DeptDataPermissionRespDTO getDeptDataPermission(Long userId); + /** + * 获得用户信息 + * + * @param userId 用户编号 + * @param tenantId 租户编号 + * @return 用户信息 + */ + UserRespDTO getUser(Long userId, Long tenantId); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java index 4b00edf6e5..6a1632afeb 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java @@ -4,14 +4,17 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.UserRespDTO; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; @@ -329,6 +332,18 @@ public class PermissionServiceImpl implements PermissionService { return result; } + @Override + public UserRespDTO getUser(Long userId, Long tenantId) { + TenantContextHolder.setTenantId(tenantId); + AdminUserDO userDO = userService.getUser(userId); + UserRespDTO user = new UserRespDTO(); + user.setId(userDO.getId()); + user.setUsername(userDO.getUsername()); + user.setDeptId(userDO.getDeptId()); + user.setTenantId(userDO.getTenantId()); + return user; + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * -- Gitee From 859c417f4b9cbf75893ccc32c7f452f72e911a58 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 12 Nov 2025 16:24:13 +0800 Subject: [PATCH 40/50] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=8Efastgpt=E5=AF=B9=E6=8E=A5=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/ent/EntController.java | 5 +-- .../admin/ent/vo/EntImportReqVO.java | 7 ++-- .../module/custom/convert/ent/EntConvert.java | 5 +-- .../module/custom/enums/FastGptConstants.java | 32 +++++++++++++++++++ .../module/custom/job/EntInfoProcessJob.java | 4 ++- .../custom/service/ent/EntServiceImpl.java | 6 ++-- .../service/fastgpt/FastGptService.java | 15 +++++---- 7 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java index ef2aa6312d..3099ead0b3 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/EntController.java @@ -29,6 +29,7 @@ import javax.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.IMPORT; @@ -116,8 +117,8 @@ public class EntController { return error(ErrorCodeConstants.EMPTY_DATA); // 拼接机构 - Map userMap = userService.getUserMap( - convertList(importList, EntImportReqVO::getUserId)); + List userList = userService.getUserListByStatus(0); + Map userMap = userList.stream().collect(Collectors.toMap(AdminUserDO::getNickname, v -> v)); entService.importEntList(EntConvert.INSTANCE.convertDeptList(importList, userMap)); return success("导入成功"); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java index dbcda8bc3f..036744a32d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/ent/vo/EntImportReqVO.java @@ -10,7 +10,7 @@ import javax.validation.constraints.NotEmpty; @Data public class EntImportReqVO { - @ExcelProperty("企业名称") + @ExcelProperty("企业全称") @NotEmpty(message = "企业名称不能为空") private String name; @@ -24,9 +24,6 @@ public class EntImportReqVO { private String remark; @ExcelProperty("所属客户经理") - private Long userId; - - @ExcelProperty(value = "状态(0正常 1停用)") - private Integer status; + private String userName; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java index 7dccf011dc..10afa5358b 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/convert/ent/EntConvert.java @@ -30,8 +30,8 @@ public interface EntConvert { return entVO; } - default List convertDeptList(List importList, Map userMap) { - return CollectionUtils.convertList(importList, ent -> convertDept(ent, userMap.get(ent.getUserId()))); + default List convertDeptList(List importList, Map userMap) { + return CollectionUtils.convertList(importList, ent -> convertDept(ent, userMap.get(ent.getUserName()))); } @@ -40,6 +40,7 @@ public interface EntConvert { EntDO entVO = BeanUtils.toBean(ent, EntDO.class); if (user != null) { entVO.setDeptId(user.getDeptId()); + entVO.setUserId(user.getId()); } return entVO; } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java new file mode 100644 index 0000000000..ea95f3bcc7 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.custom.enums; + +import lombok.Data; + +public interface FastGptConstants { + // fastgpt 类型的名称 + CommonCode FASTGPT_DICT_TYPE = new CommonCode("fastgpt", "数据字典中fastgpt类型配置的名称"); + + // fastgpt类型下配置的数据 + CommonCode FASTGPT_API_URL = new CommonCode("url", "数据字典中fastgpt类型配置的名称"); + CommonCode FASTGPT_API_TOKEN = new CommonCode("apiToken", "同步企业知识库到fastgpt"); + CommonCode FASTGPT_ENT_FOLDER = new CommonCode("ent_knowledge_folder", "企业知识库存放的文件夹"); + CommonCode FASTGPT_DOC_ANALYSIS = new CommonCode("document_analysis", "根据提供的企业附件分析内容"); + + @Data + class CommonCode { + /** + * 错误码 + */ + private final String name; + /** + * 错误提示 + */ + private final String remark; + + public CommonCode(String name, String remark) { + this.name = name; + this.remark = remark; + } + + } +} diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 007d50755f..b0f3652d0e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; +import cn.iocoder.yudao.module.custom.enums.FastGptConstants; import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; import lombok.extern.slf4j.Slf4j; import org.apache.poi.hwpf.HWPFDocument; @@ -96,6 +97,7 @@ public class EntInfoProcessJob implements JobHandler { PageResult pages = entInfoMapper.selectPage(reqVO); List pendingEntInfos = pages.getList(); + if(pendingEntInfos.isEmpty()) continue; // 获取去重的企业Id列表 List entIds = pendingEntInfos.stream().map(EntInfoDO::getEntId).distinct().collect(Collectors.toList()); @@ -161,7 +163,7 @@ public class EntInfoProcessJob implements JobHandler { String time = String.valueOf(System.currentTimeMillis()); // 将内容拆分成FAQ - fastGptService.getJsonRequest(time, content, "document_analysis", entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { + fastGptService.getJsonRequest(time, content, FastGptConstants.FASTGPT_DOC_ANALYSIS.getName(), entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { /*{ "summary": "", "todolist": "", diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index b22af5f95d..5d9b15b48d 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -50,7 +50,7 @@ public class EntServiceImpl implements EntService { createEntInfo(ent.getId(), ent.getName(), createReqVO.getAttachmentPath()); // 插入爬虫数据 - if (createReqVO.getRemark() != null) { + if (createReqVO.getDomain() != null && !createReqVO.getDomain().isEmpty()) { EntInfoDO entInfoDO = new EntInfoDO(); entInfoDO.setEntId(ent.getId()); entInfoDO.setName(ent.getName() + "-爬虫"); @@ -129,13 +129,13 @@ public class EntServiceImpl implements EntService { EntInfoDO entInfoDO = new EntInfoDO(); entInfoDO.setEntId(entId); - entInfoDO.setName(entName + "-" + attachmentPath.substring(attachmentPath.lastIndexOf("/") + 1)); + entInfoDO.setName(attachmentPath.substring(attachmentPath.lastIndexOf("/") + 1)); entInfoDO.setSource(0); entInfoDO.setStatus(-1); if (attachmentPath.endsWith(".wav") || attachmentPath.endsWith(".mp3")) entInfoDO.setSource(2); if (attachmentPath.contains("/infra/file/")) - entInfoDO.setPath(attachmentPath.substring(attachmentPath.indexOf("/infra/file/") + 13)); + entInfoDO.setPath(attachmentPath.substring(attachmentPath.indexOf("/infra/file/") + 12)); else entInfoDO.setPath(attachmentPath); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index c768deb13e..abc57d2a47 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptAuthInterceptor; import cn.iocoder.yudao.module.custom.api.fastgpt.FastGptDataApi; import cn.iocoder.yudao.module.custom.api.fastgpt.vo.*; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; +import cn.iocoder.yudao.module.custom.enums.FastGptConstants; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -123,7 +124,7 @@ public class FastGptService { String gptId = "", tenantId = ent.getTenantId().toString(); // 知识库所属租户文件夹的Id - String knowledgeFolder = getDictToken(tenantId, "ent_knowledge_folder"); + String knowledgeFolder = getDictToken(tenantId, FastGptConstants.FASTGPT_ENT_FOLDER.getName()); if (knowledgeFolder == null || knowledgeFolder.isEmpty()) { log.info("[syncKnowledge][ent_knowledge_folder参数为空]"); return "处理异常:请前往数据字典 → fastgpt配置参数 ent_knowledge_folder"; @@ -134,6 +135,7 @@ public class FastGptService { log.info(" 创建企业知识库..."); DatasetCreateRequest createRequest = new DatasetCreateRequest(); createRequest.setName(ent.getName()); + createRequest.setParentId(knowledgeFolder); createRequest.setIntro("企业【" + createRequest.getName() + "】属于【" + ent.getIndustry() + "】行业。" + ent.getRemark()); Response> response = getApiClient(tenantId).createDataset(createRequest).execute(); @@ -201,7 +203,7 @@ public class FastGptService { * @return FastGptDataApi实例 */ private FastGptDataApi createRetrofitClient(String tenantId) { - return createTokenClient("apiToken", tenantId); + return createTokenClient(FastGptConstants.FASTGPT_API_TOKEN.getName(), tenantId); } /** @@ -227,7 +229,7 @@ public class FastGptService { String url = getDictUrl(tenantId); String token = getDictToken(tenantId, category); if (StrUtil.isEmpty(url) || StrUtil.isEmpty(token)) { - throw new IllegalArgumentException("fastgpt的URL和token不能为空"); + throw new IllegalArgumentException("fastgpt的URL和token不能为空:"+category); } int connectTimeout = 30; @@ -274,13 +276,14 @@ public class FastGptService { } private Map getDictMap() { - List dictGpt = dictDataCommonApi.getDictDataList("fastgpt"); + List dictGpt = dictDataCommonApi.getDictDataList(FastGptConstants.FASTGPT_DICT_TYPE.getName()); return dictGpt.stream().collect(Collectors.toMap(DictDataRespDTO::getLabel, DictDataRespDTO::getValue)); } private String getDictUrl(String tenantId) { Map dictMap = getDictMap(); - return dictMap.get(tenantId + "_url") == null ? dictMap.get("url") == null ? "" : dictMap.get("url") : dictMap.get(tenantId + "_url"); + String url = FastGptConstants.FASTGPT_API_URL.getName(); + return dictMap.get(tenantId + "_" + url) == null ? dictMap.get(url) == null ? "" : dictMap.get(url) : dictMap.get(tenantId + "_" + url); } private String getDictToken(String tenantId, String category) { @@ -314,7 +317,7 @@ public class FastGptService { Map variables = new HashMap<>(); Long userId = SecurityFrameworkUtils.getLoginUserId(), - deptId = SecurityFrameworkUtils.getLoginUserDeptId(); + deptId = SecurityFrameworkUtils.getLoginUserDeptId(); variables.put("sysDeptId", deptId); variables.put("sysUserId", userId); request.setVariables(variables); -- Gitee From 379cdb6de082327bb17b0b58eea6e40848e7ad2f Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 12 Nov 2025 18:58:22 +0800 Subject: [PATCH 41/50] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/fastgpt/vo/PushDataRequest.java | 6 +- .../module/custom/job/EntInfoProcessJob.java | 128 ++++++++++-------- .../service/fastgpt/FastGptService.java | 99 ++++++++++++-- 3 files changed, 162 insertions(+), 71 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java index 1e73fe6954..feaaf40ba9 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/PushDataRequest.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.custom.api.fastgpt.vo; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -21,9 +22,9 @@ public class PushDataRequest { /** 训练模式(必填)chunk */ private String trainingType = "chunk"; /** 自定义QA拆分提示词,需严格按照模板,建议不要传入(选填) */ - private String prompt; + private String prompt = ""; /** 可以参考创建训练订单获取该值(选填) */ - private String billId; + private String billId = ""; /** 数据列表(必填) */ private List data; @@ -40,6 +41,7 @@ public class PushDataRequest { /** 辅助数据(可选) */ private String a; /** 自定义索引(可选) */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) private List indexes; /** diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index b0f3652d0e..170307a4f1 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.custom.job; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.idev.excel.util.DateUtils; @@ -9,6 +10,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.custom.api.fastgpt.vo.PushDataRequest; +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.PushDataResponse; import cn.iocoder.yudao.module.custom.controller.admin.ent.vo.EntInfoPageReqVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; @@ -16,6 +18,7 @@ import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntInfoMapper; import cn.iocoder.yudao.module.custom.dal.mysql.ent.EntMapper; import cn.iocoder.yudao.module.custom.enums.FastGptConstants; import cn.iocoder.yudao.module.custom.service.fastgpt.FastGptService; +import cn.iocoder.yudao.module.infra.service.file.FileService; import lombok.extern.slf4j.Slf4j; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.extractor.WordExtractor; @@ -24,10 +27,11 @@ import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.io.File; +import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Date; import java.util.List; @@ -38,15 +42,8 @@ import java.util.stream.Collectors; @Slf4j public class EntInfoProcessJob implements JobHandler { - /** - * 最大文件大小限制(10MB) - */ - private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; - - /** - * 最大内容长度限制 - */ - private static final int MAX_CONTENT_LENGTH = 100000; + @Resource + private FileService fileService; @Resource private EntMapper entMapper; @@ -97,7 +94,7 @@ public class EntInfoProcessJob implements JobHandler { PageResult pages = entInfoMapper.selectPage(reqVO); List pendingEntInfos = pages.getList(); - if(pendingEntInfos.isEmpty()) continue; + if (pendingEntInfos.isEmpty()) continue; // 获取去重的企业Id列表 List entIds = pendingEntInfos.stream().map(EntInfoDO::getEntId).distinct().collect(Collectors.toList()); @@ -122,12 +119,15 @@ public class EntInfoProcessJob implements JobHandler { EntInfoDO entInfoDO; for (EntInfoDO entInfo : pendingEntInfos) { entDo = entMap.get(entInfo.getEntId()); + collectionId = entInfo.getCollectionId(); // 保存数据集 - collectionId = fastGptService.syncFaqCat(entDo.getTenantId().toString(), entInfo.getName(), entDo.getDatasetId()); + if(collectionId == null || collectionId.isEmpty()) + collectionId = fastGptService.syncFaqCat(entDo.getTenantId().toString(), entInfo.getName(), entDo.getDatasetId()); + if (collectionId != null && !collectionId.isEmpty()) { entInfo.setCollectionId(collectionId); entInfoDO = new EntInfoDO(); - entInfoDO.setId(entInfoDO.getId()); + entInfoDO.setId(entInfo.getId()); entInfoDO.setCollectionId(collectionId); entInfoMapper.updateById(entInfoDO); } @@ -153,7 +153,7 @@ public class EntInfoProcessJob implements JobHandler { // source=0时,读取文件内容 if (entInfo.getSource() == 0 && StrUtil.isNotEmpty(entInfo.getPath())) { content = extractFileContent(entInfo.getPath()); - } else if(entInfo.getSource() == 4 && StrUtil.isNotEmpty(entInfo.getRemark())) { + } else if (entInfo.getSource() == 4 && StrUtil.isNotEmpty(entInfo.getRemark())) { content = entInfo.getRemark(); } @@ -163,52 +163,65 @@ public class EntInfoProcessJob implements JobHandler { String time = String.valueOf(System.currentTimeMillis()); // 将内容拆分成FAQ - fastGptService.getJsonRequest(time, content, FastGptConstants.FASTGPT_DOC_ANALYSIS.getName(), entInfo.getId() + time, entDO.getTenantId().toString()).subscribe(response -> { - /*{ - "summary": "", - "todolist": "", - "list": [{ - "q": "", - "a": "" - }] - }*/ - PushDataRequest request = new PushDataRequest(); - JSONObject json = JSONUtil.parseObj(response); - List list = json.getJSONArray("list").stream() - .map(item -> JSONUtil.parseObj(item.toString())) - .collect(Collectors.toList()); - list.forEach(item -> { - request.addData(item.getStr("q"), item.getStr("a")); - }); - - // 提交到知识库 - boolean isSuccess = false; - if (!request.getData().isEmpty()) { - isSuccess = true; - fastGptService.syncFaq(entDO.getTenantId().toString(), entInfo.getCollectionId(), request.getData()); - } + String response = fastGptService.getJsonRequest(time, content, FastGptConstants.FASTGPT_DOC_ANALYSIS.getName(), + entInfo.getId() + time, entDO.getTenantId().toString()); + + /*{ + "summary": "", + "todolist": [], + "list": [{ + "q": "", + "a": "" + }] + }*/ + PushDataRequest request = new PushDataRequest(); + JSONObject json = JSONUtil.parseObj(response); + List list = json.getJSONArray("list").stream() + .map(item -> JSONUtil.parseObj(item.toString())) + .collect(Collectors.toList()); + list.forEach(item -> { + request.addData("[" + entDO.getName() + "]" + item.getStr("q"), item.getStr("a")); + }); + + // 提交到知识库 + if (request.getData() != null && !request.getData().isEmpty()) { + PushDataResponse rsp = fastGptService.syncFaq(entDO.getTenantId().toString(), entInfo.getCollectionId(), request.getData()); // 更新entInfo的附件内容字段 - if (json.getStr("summary") != null && !json.getStr("summary").isEmpty() && isSuccess) { + if(rsp != null && rsp.getInsertLen() >= request.getData().size()) { EntInfoDO infoDO = new EntInfoDO(); infoDO.setId(entInfo.getId()); - infoDO.setRemark(json.getStr("summary")); + if (json.getStr("summary") != null && !json.getStr("summary").isEmpty()) { + infoDO.setRemark(json.getStr("summary")); + } infoDO.setStatus(0); entInfoMapper.updateById(infoDO); } + } + + // 提取了待办事项直接入库 + Object jsonTodo = json.get("todolist"); + if (jsonTodo != null) { + String todoStr = ""; + if (jsonTodo instanceof List) { + JSONArray todos = json.getJSONArray("todolist"); + for (int i = 0; i < todos.size(); i++) { + todoStr += todos.getStr(i) + "\n"; + } + } else { + todoStr = jsonTodo.toString(); + } - // 提取了待办事项直接入库 - if (json.getStr("todolist") != null && !json.getStr("todolist").isEmpty()) { + if (!todoStr.isEmpty()) { EntInfoDO infoDO = new EntInfoDO(); infoDO.setSource(3); infoDO.setStatus(2); infoDO.setEntId(entDO.getId()); - infoDO.setName(entDO.getName() + "-待办" + DateUtils.format(new Date(), "yyyyMMdd")); - infoDO.setRemark(json.getStr("todolist")); + infoDO.setName(entDO.getName() + "-待办-" + DateUtils.format(new Date(), "yyyyMMdd")); + infoDO.setRemark(todoStr); entInfoMapper.insert(infoDO); } - - }); + } } catch (Exception e) { // 处理文件读取异常 @@ -225,15 +238,18 @@ public class EntInfoProcessJob implements JobHandler { */ private String extractFileContent(String filePath) { try { - File file = new File(filePath); + String[] paths = filePath.split("/get/"); + if(paths.length != 2) return ""; + + byte[] file = fileService.getFileContent(Long.valueOf(paths[0]), paths[1]); // 检查文件是否存在 - if (!file.exists()) { + if (file == null) { log.warn("文件不存在:{}", filePath); - return "文件不存在:" + filePath; + return ""; } - String fileName = file.getName().toLowerCase(); + String fileName = paths[1]; if (fileName.endsWith(".docx")) { // 处理Word 2007+文档 @@ -243,7 +259,7 @@ public class EntInfoProcessJob implements JobHandler { return extractDocContent(file); } else if (fileName.endsWith(".txt")) { // 处理文本文件,使用项目工具类优化 - return FileUtil.readUtf8String(file); + return new String(file, StandardCharsets.UTF_8); } else if (fileName.endsWith(".wav") || fileName.endsWith(".mp3")) { // 处理录音文件,使用funasr log.error("请配置funasr"); @@ -255,7 +271,7 @@ public class EntInfoProcessJob implements JobHandler { } } catch (Exception e) { log.error("提取文件内容失败:{}", filePath, e); - return "文件解析失败:" + e.getMessage(); + return ""; } } @@ -266,8 +282,8 @@ public class EntInfoProcessJob implements JobHandler { * @return 文件内容 * @throws IOException IO异常 */ - private String extractDocxContent(File file) throws IOException { - try (InputStream inputStream = Files.newInputStream(file.toPath())) { + private String extractDocxContent(byte[] file) throws IOException { + try (InputStream inputStream = new ByteArrayInputStream(file)) { XWPFDocument document = new XWPFDocument(inputStream); XWPFWordExtractor extractor = new XWPFWordExtractor(document); String content = extractor.getText().trim(); @@ -290,8 +306,8 @@ public class EntInfoProcessJob implements JobHandler { * @return 文件内容 * @throws IOException IO异常 */ - private String extractDocContent(File file) throws IOException { - try (InputStream inputStream = new FileInputStream(file)) { + private String extractDocContent(byte[] file) throws IOException { + try (InputStream inputStream = new ByteArrayInputStream(file)) { HWPFDocument document = new HWPFDocument(inputStream); WordExtractor extractor = new WordExtractor(document); String content = extractor.getText().trim(); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index abc57d2a47..9be0ef2ef0 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.custom.service.fastgpt; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi; import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; @@ -25,6 +26,7 @@ import retrofit2.converter.jackson.JacksonConverterFactory; import javax.annotation.Resource; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -49,7 +51,7 @@ public class FastGptService { * @param tenantId 租户ID * @param datas 数据列表 */ - public void syncFaq(String tenantId, String collectionId, List datas) { + public PushDataResponse syncFaq(String tenantId, String collectionId, List datas) { try { log.info(" 添加FAQ数据..."); @@ -61,6 +63,7 @@ public class FastGptService { if (response.code() == 200 && response.body() != null) { PushDataResponse gptId = response.body().getData(); log.info("✓✓✓ 添加FAQ数据调用成功,len:{}", gptId.getInsertLen()); + return gptId; } else { String errorMessage = response.message(); try (ResponseBody errorBody = response.errorBody()) { @@ -74,6 +77,8 @@ public class FastGptService { } catch (Exception e) { log.error("FAQ数据同步到fastgpt出错:", e); } + + return null; } /** @@ -136,7 +141,7 @@ public class FastGptService { DatasetCreateRequest createRequest = new DatasetCreateRequest(); createRequest.setName(ent.getName()); createRequest.setParentId(knowledgeFolder); - createRequest.setIntro("企业【" + createRequest.getName() + "】属于【" + ent.getIndustry() + "】行业。" + ent.getRemark()); + createRequest.setIntro("企业【" + createRequest.getName() + "】属于【" + ent.getIndustry() + "】行业。" + (ent.getRemark() == null ? "" : ent.getRemark())); Response> response = getApiClient(tenantId).createDataset(createRequest).execute(); if (response.code() == 200) { @@ -232,9 +237,9 @@ public class FastGptService { throw new IllegalArgumentException("fastgpt的URL和token不能为空:"+category); } - int connectTimeout = 30; - int readTimeout = isStreaming ? 300 : 30; // 流式传输使用5分钟,非流式使用30秒 - int writeTimeout = isStreaming ? 300 : 30; + int connectTimeout = 180; + int readTimeout = isStreaming ? 300 : 180; // 流式传输使用5分钟,非流式使用30秒 + int writeTimeout = isStreaming ? 300 : 180; OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder() .addInterceptor(new FastGptAuthInterceptor(token)) @@ -513,8 +518,8 @@ public class FastGptService { * @param tenantId 租户Id * @return 流式响应 */ - public Mono getJsonRequest(String chatId, String content, String category, String dataId, String tenantId) { - return sendChatRequest(chatId, content, category, dataId, tenantId, "json"); + public String getJsonRequest(String chatId, String content, String category, String dataId, String tenantId) { + return sendSyncChatRequest(chatId, content, category, dataId, tenantId, "json"); } @@ -528,8 +533,8 @@ public class FastGptService { * @param tenantId 租户Id * @return 流式响应 */ - public Mono getJsonArrRequest(String chatId, String content, String category, String dataId, String tenantId) { - return sendChatRequest(chatId, content, category, dataId, tenantId, "jsonArr"); + public String getJsonArrRequest(String chatId, String content, String category, String dataId, String tenantId) { + return sendSyncChatRequest(chatId, content, category, dataId, tenantId, "jsonArr"); } @@ -543,13 +548,13 @@ public class FastGptService { * @param tenantId 租户Id * @return 流式响应 */ - public Mono getRequest(String chatId, String content, String category, String dataId, String tenantId) { - return sendChatRequest(chatId, content, category, dataId, tenantId, "common"); + public String getRequest(String chatId, String content, String category, String dataId, String tenantId) { + return sendSyncChatRequest(chatId, content, category, dataId, tenantId, "common"); } /** - * 分析对话请求 + * 分析对话请求,响应式编程,异步返回结果 * * @param chatId 对话标志 * @param content 对话内容 @@ -583,8 +588,8 @@ public class FastGptService { Response response) { if (response.isSuccessful() && response.body() != null) { // 处理成功响应 - log.info("\n ✓ 非流式聊天调用成功"); String result = response.body().getContent(); + log.info("\n ✓ 非流式聊天调用成功:{}", result); if ("json".equals(resultType) || "jsonArr".equals(resultType)) { String regex = "```json\\s*([\\s\\S]*?)\\s*```"; Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); @@ -611,4 +616,72 @@ public class FastGptService { ); } + /** + * 分析对话请求,同步返回结果 + * + * @param chatId 对话标志 + * @param content 对话内容 + * @param category 聊天类型 + * @param dataId 数据Id + * @param tenantId 租户Id + * @param resultType 返回结果类型:json、jsonArr、common + * @return 响应 + */ + private String sendSyncChatRequest(String chatId, String content, String category, String dataId, String tenantId, String resultType) { + String rst = "json".equals(resultType) ? "{}" : "[]"; + + if (StrUtil.isEmpty(dataId)) dataId = UUID.randomUUID().toString(); + + // 创建聊天请求 + ChatCompletionRequest request = new ChatCompletionRequest(); + request.setChatId(chatId); + request.setResponseChatItemId(dataId); + request.setDetail(false); + request.setStream(false); + request.setVariables(new HashMap<>()); + + // 创建消息 + ChatCompletionRequest.ChatMessage message = new ChatCompletionRequest.ChatMessage(content); + request.setMessages(Collections.singletonList(message)); + + // 同步调用 + try { + Response response = getChatClient(category, tenantId) + .sendChatRequest(request) + .execute(); + + if (response.isSuccessful() && response.body() != null) { + // 处理成功响应 + String result = response.body().getContent(), tmp = ""; + log.info("\n ✓ 同步聊天调用成功:{}", result); + if ("json".equals(resultType) || "jsonArr".equals(resultType)) { + if(result.startsWith("```json")) { + String regex = "```json\\s*([\\s\\S]*?)\\s*```"; + Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); + Matcher matcher = pattern.matcher(result); + + if (matcher.find()) + // group(1) 获取第一个捕获组的内容,即括号内的部分 + tmp = matcher.group(1).trim(); + + } else if(result.startsWith("{") && result.endsWith("}") || (result.startsWith("[") && result.endsWith("]"))) + tmp = result; + + if ("json".equals(resultType)) { + JSONUtil.parseObj(tmp); + } else { + JSONUtil.parseArray(tmp); + } + return tmp; + } + } + + } catch (Exception e) { + log.error("发送非流式聊天请求失败: {}", e.getMessage(), e); + return rst; + } + + return rst; + } + } \ No newline at end of file -- Gitee From 3202c06d4a5b83adc73b6b8f37c1aafd6df650f8 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 12 Nov 2025 20:06:49 +0800 Subject: [PATCH 42/50] =?UTF-8?q?=E5=BE=85=E5=8A=9E=E4=BA=8B=E9=A1=B9?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/custom/controller/admin/api/ChatController.http | 2 +- .../custom/controller/admin/api/ThirdPartyController.java | 3 +++ .../custom/controller/admin/api/vo/ChatTodoRespVO.java | 7 +++++-- .../iocoder/yudao/module/custom/job/EntInfoProcessJob.java | 1 + .../yudao/module/custom/service/ent/EntServiceImpl.java | 4 ++++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 32e05522a8..6564e1b901 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -41,7 +41,7 @@ tenant-id: {{adminTenantId}} ### 查询用户的待办事项 -GET {{baseUrl}}/custom/third/todo?sysUserId=146 +GET {{baseUrl}}/custom/third/todo?sysUserId=145 X-Tenant-Id: 162 X-API-Key: rspCTk8PrVAtS4SFNNj7ppZ7L2dnVJIw diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java index 9e209bb295..83f84806b0 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java @@ -16,6 +16,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.security.PermitAll; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -57,6 +58,8 @@ public class ThirdPartyController { @GetMapping("/todo") public CommonResult> getTodoList() { Map> entInfoList = entService.getEntInfoList(); + if(entInfoList == null || entInfoList.isEmpty()) return success(new ArrayList<>()); + // 将entInfoList转换成List List todoList = entInfoList.entrySet().stream() .map(entry -> new ChatTodoRespVO(entry.getKey(), entry.getValue())) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java index 87eb6b3159..2dbb63ebe8 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java @@ -3,10 +3,13 @@ package cn.iocoder.yudao.module.custom.controller.admin.api.vo; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; @Schema(description = "对话记录 - 待办事项") @@ -39,13 +42,13 @@ public class ChatTodoRespVO { private String status; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; + private String createTime; public Todo(Long id, String content, String status, LocalDateTime createTime) { this.id = id; this.content = content; this.status = status; - this.createTime = createTime; + this.createTime = createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 170307a4f1..fb4432a7e6 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -219,6 +219,7 @@ public class EntInfoProcessJob implements JobHandler { infoDO.setEntId(entDO.getId()); infoDO.setName(entDO.getName() + "-待办-" + DateUtils.format(new Date(), "yyyyMMdd")); infoDO.setRemark(todoStr); + infoDO.setTenantId(entDO.getTenantId()); entInfoMapper.insert(infoDO); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 5d9b15b48d..31eaaae79b 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -201,11 +201,15 @@ public class EntServiceImpl implements EntService { @DataPermission public Map> getEntInfoList() { List ents = entMapper.selectList(); + if(ents.isEmpty()) return Collections.emptyMap(); + Map entMap = ents.stream() .collect(Collectors.toMap(EntDO::getId, ent -> ent)); Map> infos = entInfoMapper.selectList(new LambdaQueryWrapperX() .in(EntInfoDO::getEntId, entMap.keySet()) .eq(EntInfoDO::getSource, 3) + .orderByDesc(EntInfoDO::getCreateTime) + .last("limit 100") ).stream().collect(Collectors.groupingBy(EntInfoDO::getEntId)); return infos.entrySet().stream().collect(Collectors.toMap( entry -> entMap.get(entry.getKey()).getName(), -- Gitee From 95eb5eccb3633bcd9314ac8af71d11a08a629ca9 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Wed, 12 Nov 2025 20:34:13 +0800 Subject: [PATCH 43/50] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/controller/admin/api/ChatController.http | 2 +- .../controller/admin/api/ThirdPartyController.java | 11 +++++++++-- .../controller/admin/api/vo/ChatTodoRespVO.java | 5 ----- .../controller/admin/api/vo/EntKwgRespVO.java | 13 +++++++++++++ 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 6564e1b901..057d8512c9 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -51,7 +51,7 @@ Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} ### 查询用户权限范围内的企业列表 -GET {{baseUrl}}/custom/third/ent?sysUserId=144 +GET {{baseUrl}}/custom/third/ent?sysUserId=146 X-Tenant-Id: 162 X-API-Key: rspCTk8PrVAtS4SFNNj7ppZ7L2dnVJIw diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java index 83f84806b0..9287fea656 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.custom.controller.admin.api; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatTodoRespVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.EntKwgRespVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.ent.EntService; @@ -73,7 +74,13 @@ public class ThirdPartyController { */ @Operation(summary = "查询用户权限范围内的企业列表") @GetMapping("/ent") - public CommonResult> getEntList() { - return success(entService.getEntByUser()); + public CommonResult> getEntList() { + List entList = entService.getEntByUser(); + if(entList == null || entList.isEmpty()) return success(new ArrayList<>()); + // 转为List + List result = entList.stream() + .map(EntKwgRespVO::new) + .collect(Collectors.toList()); + return success(result); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java index 2dbb63ebe8..c4cc17320c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatTodoRespVO.java @@ -1,10 +1,6 @@ package cn.iocoder.yudao.module.custom.controller.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -14,7 +10,6 @@ import java.util.List; @Schema(description = "对话记录 - 待办事项") @Data -@ExcelIgnoreUnannotated public class ChatTodoRespVO { @Schema(description = "企业名称", example = "腾讯") diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java new file mode 100644 index 0000000000..c5c244d423 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api.vo; + +import lombok.Data; + +@Data +public class EntKwgRespVO { + + private String datasetId; + + public EntKwgRespVO(String datasetId) { + this.datasetId = datasetId; + } +} -- Gitee From 7a8217d44e73fce61672079ac75786de370bc502 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Thu, 13 Nov 2025 18:05:21 +0800 Subject: [PATCH 44/50] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=A3=80=E7=B4=A2=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/api/ThirdPartyController.java | 9 ++++++-- .../controller/admin/api/vo/EntKwgRespVO.java | 4 +++- .../module/custom/enums/FastGptConstants.java | 1 + .../module/custom/job/EntInfoProcessJob.java | 23 ++++++++++++++----- .../module/custom/service/ent/EntService.java | 2 +- .../custom/service/ent/EntServiceImpl.java | 5 ++-- 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java index 9287fea656..11d84b80ec 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ThirdPartyController.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatTodoRespVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.EntKwgRespVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ThirdEntRespVO; +import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.ent.EntService; import cn.iocoder.yudao.module.custom.service.third.ThirdService; @@ -20,6 +21,7 @@ import javax.annotation.security.PermitAll; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -75,12 +77,15 @@ public class ThirdPartyController { @Operation(summary = "查询用户权限范围内的企业列表") @GetMapping("/ent") public CommonResult> getEntList() { - List entList = entService.getEntByUser(); + List entList = entService.getEntByUser(); if(entList == null || entList.isEmpty()) return success(new ArrayList<>()); + // 转为List List result = entList.stream() - .map(EntKwgRespVO::new) + .filter(item -> item.getDatasetId() != null) + .map(item -> new EntKwgRespVO(item.getDatasetId(), item.getName())) .collect(Collectors.toList()); + return success(result); } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java index c5c244d423..e4e38171ca 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/EntKwgRespVO.java @@ -6,8 +6,10 @@ import lombok.Data; public class EntKwgRespVO { private String datasetId; + private String name; - public EntKwgRespVO(String datasetId) { + public EntKwgRespVO(String datasetId, String name) { this.datasetId = datasetId; + this.name = name; } } diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java index ea95f3bcc7..b0671c9b8e 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java @@ -11,6 +11,7 @@ public interface FastGptConstants { CommonCode FASTGPT_API_TOKEN = new CommonCode("apiToken", "同步企业知识库到fastgpt"); CommonCode FASTGPT_ENT_FOLDER = new CommonCode("ent_knowledge_folder", "企业知识库存放的文件夹"); CommonCode FASTGPT_DOC_ANALYSIS = new CommonCode("document_analysis", "根据提供的企业附件分析内容"); + CommonCode FASTGPT_ALL_ENT_LIST = new CommonCode("all_ent_list", "保存所有企业名称的数据集"); @Data class CommonCode { diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index fb4432a7e6..30390cb289 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.custom.job; -import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; @@ -28,11 +27,9 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.ByteArrayInputStream; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.Date; import java.util.List; import java.util.Map; @@ -114,6 +111,19 @@ public class EntInfoProcessJob implements JobHandler { } } + // 将企业名称单独保存到数据集,用于检索 + PushDataRequest request = new PushDataRequest(); + for (EntDO ent : ents) { + if (ent.getDatasetId() != null && !ent.getDatasetId().isEmpty()) { + request.addData(ent.getName(), "datasetId:" + ent.getDatasetId() + "\nuserId:" + ent.getUserId()); + } + } + if (request.getData() != null && !request.getData().isEmpty()) { + PushDataResponse rsp = fastGptService.syncFaq(t, FastGptConstants.FASTGPT_ALL_ENT_LIST.getName(), request.getData()); + log.info("[同步企业名称知识库]成功插入:{},失败项:{}", rsp.getInsertLen(), rsp.getError()); + } + + // 保存企业附件 Map entMap = ents.stream().collect(Collectors.toMap(EntDO::getId, ent -> ent)); String collectionId; EntInfoDO entInfoDO; @@ -121,7 +131,7 @@ public class EntInfoProcessJob implements JobHandler { entDo = entMap.get(entInfo.getEntId()); collectionId = entInfo.getCollectionId(); // 保存数据集 - if(collectionId == null || collectionId.isEmpty()) + if (collectionId == null || collectionId.isEmpty()) collectionId = fastGptService.syncFaqCat(entDo.getTenantId().toString(), entInfo.getName(), entDo.getDatasetId()); if (collectionId != null && !collectionId.isEmpty()) { @@ -188,7 +198,8 @@ public class EntInfoProcessJob implements JobHandler { PushDataResponse rsp = fastGptService.syncFaq(entDO.getTenantId().toString(), entInfo.getCollectionId(), request.getData()); // 更新entInfo的附件内容字段 - if(rsp != null && rsp.getInsertLen() >= request.getData().size()) { + if (rsp != null && rsp.getInsertLen() > 0) { + log.info("[同步{}FAQ知识库]成功插入:{},失败项:{}", entDO.getName(), rsp.getInsertLen(), rsp.getError()); EntInfoDO infoDO = new EntInfoDO(); infoDO.setId(entInfo.getId()); if (json.getStr("summary") != null && !json.getStr("summary").isEmpty()) { @@ -240,7 +251,7 @@ public class EntInfoProcessJob implements JobHandler { private String extractFileContent(String filePath) { try { String[] paths = filePath.split("/get/"); - if(paths.length != 2) return ""; + if (paths.length != 2) return ""; byte[] file = fileService.getFileContent(Long.valueOf(paths[0]), paths[1]); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java index c38f491116..fe88644b4c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntService.java @@ -110,7 +110,7 @@ public interface EntService { PageResult getEntInfoPage(@Valid EntInfoPageReqVO infoPageReqVO); - List getEntByUser(); + List getEntByUser(); Map> getEntInfoList(); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java index 31eaaae79b..2d92b97290 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/ent/EntServiceImpl.java @@ -172,9 +172,8 @@ public class EntServiceImpl implements EntService { @Override @DataPermission - public List getEntByUser() { - List ents = entMapper.selectList(); - return ents.stream().map(EntDO::getDatasetId).filter(Objects::nonNull).collect(Collectors.toList()); + public List getEntByUser() { + return entMapper.selectList(); } @Override -- Gitee From aa92071a5f93d92881b1c0989c95f8b11be90424 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 14 Nov 2025 18:04:03 +0800 Subject: [PATCH 45/50] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/api/fastgpt/vo/FlowResponse.java | 2 +- .../controller/admin/api/ChatController.java | 2 +- .../custom/dal/dataobject/chat/ChatRecordDO.java | 4 ++++ .../module/custom/enums/FastGptConstants.java | 1 + .../custom/service/fastgpt/FastGptService.java | 16 ++++++++++++---- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java index 9813cdbdff..c2a493be83 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/FlowResponse.java @@ -15,7 +15,7 @@ public class FlowResponse { /** 搜索结果列表 */ private List sourceList; /** 答复结果 */ - private String content; + private String content = ""; /** diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index cb78fc7b35..f6416c286f 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -87,7 +87,7 @@ public class ChatController { }) .map(wrapper -> ServerSentEvent.builder() .event(wrapper.getEvent()) - .data(wrapper.getData()) + .data(wrapper.getData().replace("\n", "\\n")) .build()) .onBackpressureBuffer() // 添加背压缓冲 .doOnNext(event -> log.info("发送 SSE 事件: event={}, data={}", event.event(), event.data())); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java index 2782b1466a..4de7ca4b32 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/dal/dataobject/chat/ChatRecordDO.java @@ -45,6 +45,10 @@ public class ChatRecordDO extends BaseDO { * 消息内容 */ private String content; + /** + * 消息源 + */ + private String sources; /** * 消息角色 (user, assistant, system) */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java index b0671c9b8e..42af6c9453 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/enums/FastGptConstants.java @@ -12,6 +12,7 @@ public interface FastGptConstants { CommonCode FASTGPT_ENT_FOLDER = new CommonCode("ent_knowledge_folder", "企业知识库存放的文件夹"); CommonCode FASTGPT_DOC_ANALYSIS = new CommonCode("document_analysis", "根据提供的企业附件分析内容"); CommonCode FASTGPT_ALL_ENT_LIST = new CommonCode("all_ent_list", "保存所有企业名称的数据集"); + CommonCode FASTGPT_EXCLUSIVE_SOURCES = new CommonCode("exclusive_sources", "需要剔除掉不在前端展示的搜索源"); @Data class CommonCode { diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index 9be0ef2ef0..b840d26cc5 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -388,8 +388,11 @@ public class FastGptService { if (jsonNode.isArray()) { // 立即发送内容片段 if (!sink.isCancelled()) { - sink.next(new SseMessageWrapper("flowResponses", handleFlowResponses(jsonNode))); + String sources = handleFlowResponses(jsonNode); + sink.next(new SseMessageWrapper("flowResponses", sources)); sink.next(new SseMessageWrapper("finished", "[DONE]")); + // 源也需要保存 + fullResponse.append("@@_@@").append(sources); } } break; @@ -465,6 +468,7 @@ public class FastGptService { } private String handleFlowResponses(JsonNode jsonNode) { + String exclusiveSrc = getDictMap().get(FastGptConstants.FASTGPT_EXCLUSIVE_SOURCES.getName()); // 解析数组类型的节点数据 FlowResponse flowResponse = new FlowResponse(); @@ -478,11 +482,15 @@ public class FastGptService { if (quoteListNode.isArray()) { for (JsonNode quote : quoteListNode) { - String collectionId = quote.has("collectionId") ? quote.get("collectionId").asText() : ""; + String collectionId = quote.has("collectionId") ? quote.get("collectionId").asText() : "", + datasetId = quote.has("datasetId") ? quote.get("datasetId").asText() : ""; + + if (!StrUtil.isEmpty(exclusiveSrc) && exclusiveSrc.contains(datasetId)) + continue; FlowResponse.SourceItem sourceItem = null; - if (quote.has("datasetId") && !StrUtil.isEmpty(collectionId) && quote.has("sourceName")) { - sourceItem = new FlowResponse.SourceItem(quote.get("datasetId").asText(), quote.get("collectionId").asText(), quote.get("sourceName").asText()); + if (!StrUtil.isEmpty(datasetId) && !StrUtil.isEmpty(collectionId) && quote.has("sourceName")) { + sourceItem = new FlowResponse.SourceItem(datasetId, quote.get("collectionId").asText(), quote.get("sourceName").asText()); flowResponse.addSourceItem(sourceItem); } -- Gitee From 2b3445041e09f39edf5874baa99e51135912d30b Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 14 Nov 2025 18:29:40 +0800 Subject: [PATCH 46/50] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E6=95=B0=E6=8D=AEId=E8=8E=B7=E5=8F=96FAQ=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/api/fastgpt/FastGptDataApi.java | 11 +++++++ .../fastgpt/vo/DatasetDataDetailResponse.java | 32 ++++++++++++++++++ .../controller/admin/api/ChatController.java | 20 +++++++++++ .../admin/api/vo/DatasetDataDetailVO.java | 27 +++++++++++++++ .../custom/service/chat/ChatServiceImpl.java | 11 ++++++- .../service/fastgpt/FastGptService.java | 33 +++++++++++++++++++ 6 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java create mode 100644 yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java index aff4aca4e2..6b14cb33f1 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/FastGptDataApi.java @@ -142,4 +142,15 @@ public interface FastGptDataApi { Call sendStreamChatRequest( @Body ChatCompletionRequest request ); + + /** + * 获取知识库集合下的数据详情 + * + * @param dataId 数据ID + * @return 知识库数据详细信息 + */ + @GET("api/core/dataset/data/detail") + Call> getDatasetDataDetail( + @Query("id") String dataId + ); } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java new file mode 100644 index 0000000000..5a80eb4a40 --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.custom.api.fastgpt.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.Date; + +/** + * 数据集数据详情响应 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class DatasetDataDetailResponse { + /** 数据ID */ + private String id; + /** 数据集ID */ + private String datasetId; + /** 问题文本 */ + private String q; + /** 答案文本 */ + private String a; + /** 分块索引 */ + private String chunkIndex; + /** 创建时间戳 */ + private Integer createTime; + /** 更新时间戳 */ + private Date updateTime; + /** 数据状态 */ + private String status; + /** 训练状态 */ + private String trainingStatus; +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java index f6416c286f..48c448b8cc 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.java @@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.custom.api.fastgpt.vo.DatasetDataDetailResponse; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordPageReqVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatRecordVO; import cn.iocoder.yudao.module.custom.controller.admin.api.vo.ChatTodoRespVO; +import cn.iocoder.yudao.module.custom.controller.admin.api.vo.DatasetDataDetailVO; import cn.iocoder.yudao.module.custom.dal.dataobject.chat.ChatRecordDO; import cn.iocoder.yudao.module.custom.dal.dataobject.ent.EntInfoDO; import cn.iocoder.yudao.module.custom.service.chat.ChatService; @@ -153,4 +155,22 @@ public class ChatController { int i = entService.changeStatus(id); return success(i > 0); } + + @Operation(summary = "获取知识库数据详情") + @GetMapping("/data/detail") + @Parameter(name = "dataId", description = "数据ID", required = true, example = "data_123") + @PreAuthorize("@ss.hasPermission('custom:chat:detail')") + public CommonResult getDatasetDataDetail(@RequestParam String dataId) { + Long tenantId = TenantContextHolder.getTenantId(); + + DatasetDataDetailResponse detail = + fastGptService.getDatasetDataDetail(dataId, String.valueOf(tenantId)); + + if (detail != null) { + DatasetDataDetailVO result = BeanUtils.toBean(detail, DatasetDataDetailVO.class); + return success(result); + } else { + return success(null); + } + } } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java new file mode 100644 index 0000000000..69075d7c0c --- /dev/null +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.custom.controller.admin.api.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 数据集数据详情VO + */ +@Schema(description = "管理后台 - 数据集数据详情 Response VO") +@Data +public class DatasetDataDetailVO { + + @Schema(description = "数据ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") + private String id; + + @Schema(description = "数据集ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "dataset_123") + private String datasetId; + + @Schema(description = "问题文本", requiredMode = Schema.RequiredMode.REQUIRED, example = "什么是人工智能?") + private String q; + + @Schema(description = "答案文本", requiredMode = Schema.RequiredMode.REQUIRED, example = "人工智能是...") + private String a; + +} \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java index 3a7caeb6a1..a920683b59 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/chat/ChatServiceImpl.java @@ -32,12 +32,21 @@ public class ChatServiceImpl implements ChatService { // 保存助手回复 if (response != null && !response.isEmpty()) { + // 拆分flowResponses + String content = response, rsp = ""; + String[] rs ; + if((rs=response.split("@@_@@")).length > 1) { + content = rs[0]; + rsp = rs[1]; + } + ChatRecordDO assistantRecord = new ChatRecordDO() .setTenantId(tenantId) .setUserId(userId) .setChatId(chatId) .setMessageId(msgId) - .setContent(response) + .setContent(content) + .setSources(rsp) .setRole("assistant") .setIsTitle(false); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index b840d26cc5..a8e3f91dcb 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -296,6 +296,39 @@ public class FastGptService { return dictMap.get(tenantId + "_" + category) == null ? dictMap.get(category) == null ? "" : dictMap.get(category) : dictMap.get(tenantId + "_" + category); } + /** + * 获取知识库数据详情 + * + * @param dataId 数据ID + * @param tenantId 租户ID + * @return 知识库数据详情 + */ + public DatasetDataDetailResponse getDatasetDataDetail(String dataId, String tenantId) { + try { + Response> response = getApiClient(tenantId) + .getDatasetDataDetail(dataId) + .execute(); + + if (response.isSuccessful() && response.body() != null && response.body().getData() != null) { + log.info("✓✓✓ 获取知识库数据详情成功,数据ID: {}", dataId); + return response.body().getData(); + } else { + String errorMessage = response.message(); + try (ResponseBody errorBody = response.errorBody()) { + if (errorBody != null) + errorMessage = errorBody.string(); + } catch (Exception e) { + log.warn("读取错误信息失败", e); + } + log.error("XXX 获取知识库数据详情失败:{}>>>{}", response.message(), errorMessage); + } + } catch (Exception e) { + log.error("获取知识库数据详情出错:", e); + } + + return null; + } + /** * 发送对话请求 * -- Gitee From 8209c55db5654e9e51b6d8f3d3fd89dad71c9e6c Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Fri, 14 Nov 2025 19:25:33 +0800 Subject: [PATCH 47/50] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E6=95=B0=E6=8D=AEId=E8=8E=B7=E5=8F=96FAQ=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/fastgpt/vo/DatasetDataDetailResponse.java | 14 ++++++-------- .../controller/admin/api/ChatController.http | 5 +++++ .../admin/api/vo/DatasetDataDetailVO.java | 4 ++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java index 5a80eb4a40..e572122588 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/api/fastgpt/vo/DatasetDataDetailResponse.java @@ -21,12 +21,10 @@ public class DatasetDataDetailResponse { private String a; /** 分块索引 */ private String chunkIndex; - /** 创建时间戳 */ - private Integer createTime; - /** 更新时间戳 */ - private Date updateTime; - /** 数据状态 */ - private String status; - /** 训练状态 */ - private String trainingStatus; + /** 所属集合Id */ + private String collectionId; + /** 源id */ + private String sourceId; + /** 源名称 */ + private String sourceName; } \ No newline at end of file diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http index 057d8512c9..0ede052d11 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/ChatController.http @@ -50,6 +50,11 @@ POST {{baseUrl}}/custom/chat/todo/done?id=1 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} +### 根据citeId获取分块详情 +GET {{baseUrl}}/custom/chat/data/detail?dataId=691464ebc483714cb4aee379 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + ### 查询用户权限范围内的企业列表 GET {{baseUrl}}/custom/third/ent?sysUserId=146 X-Tenant-Id: 162 diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java index 69075d7c0c..4e32b1e2bd 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/DatasetDataDetailVO.java @@ -24,4 +24,8 @@ public class DatasetDataDetailVO { @Schema(description = "答案文本", requiredMode = Schema.RequiredMode.REQUIRED, example = "人工智能是...") private String a; + /** 源名称 */ + @Schema(description = "源名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "人工智能.pdf") + private String sourceName; + } \ No newline at end of file -- Gitee From aed3645ee978c9ca12ff231353961fd4fed9dfd8 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 17 Nov 2025 15:09:36 +0800 Subject: [PATCH 48/50] =?UTF-8?q?=E5=BC=95=E7=94=A8=E6=BA=90=E5=85=A5?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/controller/admin/api/vo/ChatRecordVO.java | 5 +++++ .../module/custom/service/fastgpt/FastGptService.java | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java index 1fb352ce69..650f424eda 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/api/vo/ChatRecordVO.java @@ -34,6 +34,11 @@ public class ChatRecordVO { */ @Schema(description = "消息内容") private String content; + /** + * 消息源 + */ + @Schema(description = "消息源") + private String sources; /** * 消息角色 */ diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index a8e3f91dcb..fbb1edbf5c 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -425,7 +425,8 @@ public class FastGptService { sink.next(new SseMessageWrapper("flowResponses", sources)); sink.next(new SseMessageWrapper("finished", "[DONE]")); // 源也需要保存 - fullResponse.append("@@_@@").append(sources); + if(sources != null && !"{}".equals(sources)) + fullResponse.append("@@_@@").append(sources); } } break; @@ -540,12 +541,16 @@ public class FastGptService { // 使用Jackson ObjectMapper替代Druid的JSONUtils try { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.writeValueAsString(flowResponse); + if(!flowResponse.getSourceList().isEmpty()) { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(flowResponse); + } } catch (Exception e) { log.error("FlowResponse序列化失败", e); return "{}"; // 返回默认值 } + + return "{}"; } -- Gitee From d74897af2bcfed61f04dddd516ce1128c3e69267 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Mon, 17 Nov 2025 16:42:09 +0800 Subject: [PATCH 49/50] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/custom/job/EntInfoProcessJob.java | 9 +++------ .../module/custom/service/fastgpt/FastGptService.java | 10 ++++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java index 30390cb289..e9cdbd49fd 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/job/EntInfoProcessJob.java @@ -98,6 +98,7 @@ public class EntInfoProcessJob implements JobHandler { List ents = entMapper.selectByIds(entIds); String datasetId; EntDO entDo; + PushDataRequest request = new PushDataRequest(); for (EntDO ent : ents) { if (ent.getDatasetId() == null || ent.getDatasetId().isEmpty()) { datasetId = fastGptService.syncKnowledge(ent); @@ -107,17 +108,13 @@ public class EntInfoProcessJob implements JobHandler { entDo.setId(ent.getId()); entDo.setDatasetId(datasetId); entMapper.updateById(entDo); + + request.addData(ent.getName(), "datasetId:" + ent.getDatasetId() + "\nuserId:" + ent.getUserId()); } } } // 将企业名称单独保存到数据集,用于检索 - PushDataRequest request = new PushDataRequest(); - for (EntDO ent : ents) { - if (ent.getDatasetId() != null && !ent.getDatasetId().isEmpty()) { - request.addData(ent.getName(), "datasetId:" + ent.getDatasetId() + "\nuserId:" + ent.getUserId()); - } - } if (request.getData() != null && !request.getData().isEmpty()) { PushDataResponse rsp = fastGptService.syncFaq(t, FastGptConstants.FASTGPT_ALL_ENT_LIST.getName(), request.getData()); log.info("[同步企业名称知识库]成功插入:{},失败项:{}", rsp.getInsertLen(), rsp.getError()); diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java index fbb1edbf5c..26c07cd0b9 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/service/fastgpt/FastGptService.java @@ -56,6 +56,8 @@ public class FastGptService { try { log.info(" 添加FAQ数据..."); PushDataRequest pushRequest = new PushDataRequest(); + if(FastGptConstants.FASTGPT_ALL_ENT_LIST.getName().equals(collectionId)) + collectionId = getDictMap().get(FastGptConstants.FASTGPT_ALL_ENT_LIST.getName()); pushRequest.setCollectionId(collectionId); pushRequest.setData(datas); @@ -237,9 +239,9 @@ public class FastGptService { throw new IllegalArgumentException("fastgpt的URL和token不能为空:"+category); } - int connectTimeout = 180; - int readTimeout = isStreaming ? 300 : 180; // 流式传输使用5分钟,非流式使用30秒 - int writeTimeout = isStreaming ? 300 : 180; + int connectTimeout = 420; + int readTimeout = isStreaming ? 600 : 420; // 流式传输使用5分钟,非流式使用30秒 + int writeTimeout = isStreaming ? 600 : 420; OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder() .addInterceptor(new FastGptAuthInterceptor(token)) @@ -541,7 +543,7 @@ public class FastGptService { // 使用Jackson ObjectMapper替代Druid的JSONUtils try { - if(!flowResponse.getSourceList().isEmpty()) { + if(flowResponse.getSourceList() != null && !flowResponse.getSourceList().isEmpty()) { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.writeValueAsString(flowResponse); } -- Gitee From e99f0dc2824b0c4b51ca2148f98e13c48c312b56 Mon Sep 17 00:00:00 2001 From: AsyDong <814318774@qq.com> Date: Tue, 18 Nov 2025 10:29:47 +0800 Subject: [PATCH 50/50] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=BD=AC=E7=A7=BB=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/usertransfer/UserTransferController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java index 62326bb9e0..ac035cb341 100644 --- a/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java +++ b/yudao-module-custom/src/main/java/cn/iocoder/yudao/module/custom/controller/admin/usertransfer/UserTransferController.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.custom.service.usertransfer.UserTransferService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; @@ -15,6 +16,7 @@ import javax.annotation.Resource; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +@Slf4j @Tag(name = "管理后台 - 用户") @RestController @RequestMapping("/custom/user") @@ -28,6 +30,7 @@ public class UserTransferController { @Operation(summary = "用户数据转移") @PreAuthorize("@ss.hasPermission('custom:user:transfer')") public CommonResult transferUserDataWithEnt(@RequestParam Long fromUserId, @RequestParam Long toUserId) { + log.info("[transferUserData]用户权限交接:[fromUserId({}) toUserId({})]", fromUserId, toUserId); boolean success = userTransferService.transferUserDataWithEnt(fromUserId, toUserId); return success(success); -- Gitee