From e48d7e8649fb45ac818f16a287d908ecc6641304 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Mon, 23 Jun 2025 00:26:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai-web):=20=E5=B0=9D=E8=AF=95=E4=BD=BF?= =?UTF-8?q?=E7=94=A8jpa=E4=BD=9C=E4=B8=BA=E9=80=9A=E7=94=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service-ai/service-ai-web/pom.xml | 20 +- .../service/ai/web/WebApplication.java | 4 + .../web/base/controller/DetailController.java | 13 + .../web/base/controller/ListController.java | 17 ++ .../web/base/controller/RemoveController.java | 13 + .../web/base/controller/SaveController.java | 13 + .../web/base/controller/SimpleController.java | 8 + .../controller/SimpleControllerSupport.java | 95 +++++++ .../ai/web/base/controller/query/Query.java | 61 +++++ .../ai/web/base/entity/IdOnlyEntity.java | 29 +++ .../ai/web/base/entity/SimpleEntity.java | 29 +++ .../ai/web/base/entity/SimpleItem.java | 17 ++ .../web/base/repository/SimpleRepository.java | 16 ++ .../ai/web/base/service/SimpleService.java | 33 +++ .../base/service/SimpleServiceSupport.java | 239 ++++++++++++++++++ .../configuration/SnowflakeIdGenerator.java | 92 +++++++ .../ai/web/controller/DataFileController.java | 4 +- .../feedback/FeedbackController.java | 104 ++++---- .../knowledge/KnowledgeBaseController.java | 94 +++++-- .../service/ai/web/entity/DataFile.java | 35 +++ .../service/ai/web/entity/Feedback.java | 37 ++- .../service/ai/web/entity/Group.java | 33 ++- .../service/ai/web/entity/Knowledge.java | 31 ++- .../service/ai/web/entity/vo/DataFileVO.java | 17 -- .../service/ai/web/entity/vo/KnowledgeVO.java | 22 -- .../ai/web/repository/DataFileRepository.java | 9 + .../ai/web/repository/FeedbackRepository.java | 9 + .../ai/web/repository/GroupRepository.java | 9 + .../web/repository/KnowledgeRepository.java | 10 + .../ai/web/service/DataFileService.java | 76 ++---- .../ai/web/service/EmbeddingService.java | 22 +- .../web/service/feedback/FeedbackService.java | 137 ++-------- .../web/service/knowledge/GroupService.java | 2 - .../knowledge/KnowledgeBaseService.java | 142 +++-------- .../ai/web/service/node/FeedbackNodes.java | 8 +- .../src/main/resources/application.yml | 44 +++- .../client/src/pages/ai/feedback/Feedback.tsx | 41 +-- service-web/client/src/util/amis.tsx | 8 +- 38 files changed, 1141 insertions(+), 452 deletions(-) create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/DetailController.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/ListController.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/RemoveController.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SaveController.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleController.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleControllerSupport.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/query/Query.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/IdOnlyEntity.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleEntity.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleItem.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/repository/SimpleRepository.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleService.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleServiceSupport.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/configuration/SnowflakeIdGenerator.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/DataFile.java delete mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/DataFileVO.java delete mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/KnowledgeVO.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/DataFileRepository.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/FeedbackRepository.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/GroupRepository.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/KnowledgeRepository.java diff --git a/service-ai/service-ai-web/pom.xml b/service-ai/service-ai-web/pom.xml index 9752d53..4e650c6 100644 --- a/service-ai/service-ai-web/pom.xml +++ b/service-ai/service-ai-web/pom.xml @@ -20,8 +20,16 @@ com.google.protobuf protobuf-java + + org.springframework.boot + spring-boot-starter-tomcat + + + org.springframework.boot + spring-boot-starter-jetty + org.springframework.ai spring-ai-starter-model-openai @@ -40,7 +48,12 @@ org.springframework.boot - spring-boot-starter-jdbc + spring-boot-starter-data-jpa + + + com.blinkfox + fenix-spring-boot-starter + 3.0.0 com.mysql @@ -89,6 +102,11 @@ lombok-mapstruct-binding 0.2.0 + + org.hibernate + hibernate-jpamodelgen + 6.6.8.Final + -Amapstruct.defaultComponentModel=spring diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java index b3298c4..96143fc 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java @@ -1,5 +1,6 @@ package com.lanyuanxiaoyao.service.ai.web; +import com.blinkfox.fenix.EnableFenix; import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; import org.springframework.beans.BeansException; import org.springframework.boot.ApplicationArguments; @@ -10,6 +11,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.retry.annotation.EnableRetry; import org.springframework.scheduling.annotation.EnableScheduling; @@ -23,6 +25,8 @@ import org.springframework.scheduling.annotation.EnableScheduling; @EnableEncryptableProperties @EnableRetry @EnableScheduling +@EnableFenix +@EnableJpaAuditing public class WebApplication implements ApplicationRunner, ApplicationContextAware { private static ApplicationContext context; diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/DetailController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/DetailController.java new file mode 100644 index 0000000..61ec8d7 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/DetailController.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller; + +import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface DetailController { + String DETAIL = "/detail/{id}"; + + AmisResponse detail(Long id) throws Exception; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/ListController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/ListController.java new file mode 100644 index 0000000..02dd5b2 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/ListController.java @@ -0,0 +1,17 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller; + +import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; +import com.lanyuanxiaoyao.service.ai.web.base.controller.query.Query; +import org.eclipse.collections.api.list.ImmutableList; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface ListController { + String LIST = "/list"; + + AmisResponse> list() throws Exception; + + AmisResponse> list(Query query) throws Exception; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/RemoveController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/RemoveController.java new file mode 100644 index 0000000..5cdf2b8 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/RemoveController.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller; + +import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface RemoveController { + String REMOVE = "/remove/{id}"; + + AmisResponse remove(Long id) throws Exception; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SaveController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SaveController.java new file mode 100644 index 0000000..7ff117d --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SaveController.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller; + +import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface SaveController { + String SAVE = "/save"; + + AmisResponse save(SAVE_ITEM item) throws Exception; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleController.java new file mode 100644 index 0000000..93713c0 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleController.java @@ -0,0 +1,8 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface SimpleController extends SaveController, ListController, DetailController, RemoveController { +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleControllerSupport.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleControllerSupport.java new file mode 100644 index 0000000..bd9197f --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/SimpleControllerSupport.java @@ -0,0 +1,95 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; +import com.lanyuanxiaoyao.service.ai.web.base.controller.query.Query; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import com.lanyuanxiaoyao.service.ai.web.base.service.SimpleServiceSupport; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.ImmutableList; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-26 + */ +@Slf4j +public abstract class SimpleControllerSupport implements SimpleController { + protected final SimpleServiceSupport service; + + public SimpleControllerSupport(SimpleServiceSupport service) { + this.service = service; + } + + @PostMapping(SAVE) + @Override + public AmisResponse save(@RequestBody SAVE_ITEM item) throws Exception { + SaveItemMapper mapper = saveItemMapper(); + return AmisResponse.responseSuccess(service.save(mapper.from(item))); + } + + @GetMapping(LIST) + @Override + public AmisResponse> list() throws Exception { + ListItemMapper mapper = listItemMapper(); + return AmisResponse.responseSuccess(service.list().collect(entity -> { + try { + return mapper.from(entity); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + @PostMapping(LIST) + @Override + public AmisResponse> list(@RequestBody Query query) throws Exception { + if (ObjectUtil.isNull(query)) { + return AmisResponse.responseSuccess(Lists.immutable.empty()); + } + ListItemMapper mapper = listItemMapper(); + return AmisResponse.responseSuccess(service.list(query).collect(entity -> { + try { + return mapper.from(entity); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + @GetMapping(DETAIL) + @Override + public AmisResponse detail(@PathVariable Long id) throws Exception { + DetailItemMapper mapper = detailItemMapper(); + return AmisResponse.responseSuccess(mapper.from(service.detailOrThrow(id))); + } + + @GetMapping(REMOVE) + @Override + public AmisResponse remove(@PathVariable Long id) throws Exception { + service.remove(id); + return AmisResponse.responseSuccess(); + } + + protected abstract SaveItemMapper saveItemMapper(); + + protected abstract ListItemMapper listItemMapper(); + + protected abstract DetailItemMapper detailItemMapper(); + + public interface SaveItemMapper { + ENTITY from(SAVE_ITEM item) throws Exception; + } + + public interface ListItemMapper { + LIST_ITEM from(ENTITY entity) throws Exception; + } + + public interface DetailItemMapper { + DETAIL_ITEM from(ENTITY entity) throws Exception; + } +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/query/Query.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/query/Query.java new file mode 100644 index 0000000..dd313c2 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/controller/query/Query.java @@ -0,0 +1,61 @@ +package com.lanyuanxiaoyao.service.ai.web.base.controller.query; + +import lombok.Data; +import org.eclipse.collections.api.list.ImmutableList; +import org.eclipse.collections.api.map.ImmutableMap; + +/** + * 统一查询 + * + * @author lanyuanxiaoyao + * @date 2024-12-03 + */ +@Data +public class Query { + private Queryable query; + private ImmutableList sort; + private Pageable page; + + @Data + public static class Queryable { + private ImmutableList nullEqual; + private ImmutableList notNullEqual; + private ImmutableList empty; + private ImmutableList notEmpty; + private ImmutableMap equal; + private ImmutableMap notEqual; + private ImmutableMap like; + private ImmutableMap notLike; + private ImmutableMap great; + private ImmutableMap less; + private ImmutableMap greatEqual; + private ImmutableMap lessEqual; + private ImmutableMap> in; + private ImmutableMap> notIn; + private ImmutableMap between; + private ImmutableMap notBetween; + + @Data + public static class Between { + private String start; + private String end; + } + } + + @Data + public static class Sortable { + private String column; + private Direction direction; + + public enum Direction { + ASC, + DESC, + } + } + + @Data + public static class Pageable { + private Integer index; + private Integer size; + } +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/IdOnlyEntity.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/IdOnlyEntity.java new file mode 100644 index 0000000..1ce7264 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/IdOnlyEntity.java @@ -0,0 +1,29 @@ +package com.lanyuanxiaoyao.service.ai.web.base.entity; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +/** + * 实体类公共字段 + * + * @author lanyuanxiaoyao + * @date 2024-11-20 + */ +@Getter +@Setter +@ToString +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class IdOnlyEntity { + @Id + @GeneratedValue(generator = "snowflake") + @GenericGenerator(name = "snowflake", strategy = "com.lanyuanxiaoyao.service.ai.web.configuration.SnowflakeIdGenerator") + private Long id; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleEntity.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleEntity.java new file mode 100644 index 0000000..5329664 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleEntity.java @@ -0,0 +1,29 @@ +package com.lanyuanxiaoyao.service.ai.web.base.entity; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +/** + * 实体类公共字段 + * + * @author lanyuanxiaoyao + * @date 2024-11-20 + */ +@Getter +@Setter +@ToString(callSuper = true) +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class SimpleEntity extends IdOnlyEntity { + @CreatedDate + private LocalDateTime createdTime; + @LastModifiedDate + private LocalDateTime modifiedTime; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleItem.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleItem.java new file mode 100644 index 0000000..ebc991b --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/entity/SimpleItem.java @@ -0,0 +1,17 @@ +package com.lanyuanxiaoyao.service.ai.web.base.entity; + +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 创建对象使用的接收类 + * + * @author lanyuanxiaoyao + * @date 2024-11-26 + */ +@Data +public class SimpleItem { + private Long id; + private LocalDateTime createdTime; + private LocalDateTime modifiedTime; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/repository/SimpleRepository.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/repository/SimpleRepository.java new file mode 100644 index 0000000..ad44afd --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/repository/SimpleRepository.java @@ -0,0 +1,16 @@ +package com.lanyuanxiaoyao.service.ai.web.base.repository; + +import com.blinkfox.fenix.jpa.FenixJpaRepository; +import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +/** + * 整合一下 + * + * @author lanyuanxiaoyao + * @date 2024-11-21 + */ +@NoRepositoryBean +public interface SimpleRepository extends FenixJpaRepository, FenixJpaSpecificationExecutor, QueryByExampleExecutor { +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleService.java new file mode 100644 index 0000000..44bdef2 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleService.java @@ -0,0 +1,33 @@ +package com.lanyuanxiaoyao.service.ai.web.base.service; + +import com.lanyuanxiaoyao.service.ai.web.base.controller.query.Query; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import java.util.Optional; +import org.eclipse.collections.api.list.ImmutableList; +import org.eclipse.collections.api.set.ImmutableSet; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface SimpleService { + Long save(ENTITY entity) throws Exception; + + Long count() throws Exception; + + ImmutableList list() throws Exception; + + ImmutableList list(ImmutableSet ids) throws Exception; + + ImmutableList list(Query query) throws Exception; + + Optional detailOptional(Long id) throws Exception; + + ENTITY detail(Long id) throws Exception; + + ENTITY detailOrThrow(Long id) throws Exception; + + ENTITY detailOrNull(Long id) throws Exception; + + void remove(Long id) throws Exception; +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleServiceSupport.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleServiceSupport.java new file mode 100644 index 0000000..0ab5181 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/base/service/SimpleServiceSupport.java @@ -0,0 +1,239 @@ +package com.lanyuanxiaoyao.service.ai.web.base.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.service.ai.web.base.controller.query.Query; +import com.lanyuanxiaoyao.service.ai.web.base.entity.IdOnlyEntity_; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity_; +import com.lanyuanxiaoyao.service.ai.web.base.repository.SimpleRepository; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.transaction.Transactional; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.ImmutableList; +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.collections.api.set.ImmutableSet; +import org.springframework.data.domain.Sort; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-21 + */ +@Slf4j +public abstract class SimpleServiceSupport implements SimpleService { + protected final SimpleRepository repository; + + public SimpleServiceSupport(SimpleRepository repository) { + this.repository = repository; + } + + @Transactional(rollbackOn = Throwable.class) + @Override + public Long save(ENTITY entity) throws Exception { + if (ObjectUtil.isNotNull(entity.getId())) { + Long id = entity.getId(); + ENTITY targetEntity = repository.findById(entity.getId()).orElseThrow(() -> new IdNotFoundException(id)); + BeanUtil.copyProperties( + entity, + targetEntity, + CopyOptions.create() + .setIgnoreProperties( + IdOnlyEntity_.ID, + SimpleEntity_.CREATED_TIME, + SimpleEntity_.MODIFIED_TIME + ) + ); + entity = targetEntity; + } + entity = repository.save(entity); + return entity.getId(); + } + + @Override + public Long count() throws Exception { + return repository.count( + (root, query, builder) -> + builder.and( + listPredicate(root, query, builder) + .reject(ObjectUtil::isNull) + .toArray(new Predicate[]{}) + ) + ); + } + + @Override + public ImmutableList list() throws Exception { + return Lists.immutable.ofAll(repository.findAll( + (root, query, builder) -> + builder.and( + listPredicate(root, query, builder) + .reject(ObjectUtil::isNull) + .toArray(new Predicate[]{}) + ) + )); + } + + @Override + public ImmutableList list(ImmutableSet ids) throws Exception { + return Lists.immutable.ofAll(repository.findAll( + (root, query, builder) -> { + MutableList predicates = Lists.mutable.ofAll(listPredicate(root, query, builder)); + predicates.add(builder.in(root.get("id")).value(ids)); + return builder.and(predicates.reject(ObjectUtil::isNull).toArray(new Predicate[predicates.size()])); + } + )); + } + + private Path column(Root root, String column) { + String[] columns = StrUtil.splitToArray(column, "/"); + Path path = root.get(columns[0]); + for (int i = 1; i < columns.length; i++) { + path = path.get(columns[i]); + } + return path; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Object value(Path column, Object value) { + Class javaType = column.getJavaType(); + if (EnumUtil.isEnum(javaType)) { + return EnumUtil.fromString((Class) javaType, (String) value); + } + return value; + } + + @SuppressWarnings("unchecked") + protected ImmutableList queryPredicates(Query.Queryable queryable, Root root, CriteriaQuery query, CriteriaBuilder builder) { + MutableList predicates = Lists.mutable.empty(); + if (ObjectUtil.isEmpty(queryable)) { + return predicates.toImmutable(); + } + if (ObjectUtil.isNotEmpty(queryable.getNullEqual())) { + queryable.getNullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column)))); + } + if (ObjectUtil.isNotEmpty(queryable.getNotNullEqual())) { + queryable.getNotNullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column)))); + } + if (ObjectUtil.isNotEmpty(queryable.getEmpty())) { + queryable.getEmpty().forEach(column -> predicates.add(builder.isEmpty(column(root, column)))); + } + if (ObjectUtil.isNotEmpty(queryable.getNotEmpty())) { + queryable.getNotEmpty().forEach(column -> predicates.add(builder.isNotEmpty(column(root, column)))); + } + if (ObjectUtil.isNotEmpty(queryable.getEqual())) { + queryable.getEqual().forEachKeyValue((column, value) -> { + Path path = column(root, column); + predicates.add(builder.equal(path, value(path, value))); + }); + } + if (ObjectUtil.isNotEmpty(queryable.getNotEqual())) { + queryable.getEqual().forEachKeyValue((column, value) -> { + Path path = column(root, column); + predicates.add(builder.notEqual(path, value(path, value))); + }); + } + if (ObjectUtil.isNotEmpty(queryable.getLike())) { + queryable.getLike().forEachKeyValue((column, value) -> predicates.add(builder.like(column(root, column), value))); + } + if (ObjectUtil.isNotEmpty(queryable.getNotLike())) { + queryable.getNotLike().forEachKeyValue((column, value) -> predicates.add(builder.notLike(column(root, column), value))); + } + if (ObjectUtil.isNotEmpty(queryable.getGreat())) { + queryable.getGreat().forEachKeyValue((column, value) -> predicates.add(builder.greaterThan(column(root, column), (Comparable) value))); + } + if (ObjectUtil.isNotEmpty(queryable.getLess())) { + queryable.getLess().forEachKeyValue((column, value) -> predicates.add(builder.lessThan(column(root, column), (Comparable) value))); + } + if (ObjectUtil.isNotEmpty(queryable.getGreatEqual())) { + queryable.getGreatEqual().forEachKeyValue((column, value) -> predicates.add(builder.greaterThanOrEqualTo(column(root, column), (Comparable) value))); + } + if (ObjectUtil.isNotEmpty(queryable.getLessEqual())) { + queryable.getLessEqual().forEachKeyValue((column, value) -> predicates.add(builder.lessThanOrEqualTo(column(root, column), (Comparable) value))); + } + if (ObjectUtil.isNotEmpty(queryable.getIn())) { + queryable.getIn().forEachKeyValue((column, value) -> predicates.add(builder.in(column(root, column)).value(value))); + } + if (ObjectUtil.isNotEmpty(queryable.getNotIn())) { + queryable.getNotIn().forEachKeyValue((column, value) -> predicates.add(builder.in(column(root, column)).value(value).not())); + } + if (ObjectUtil.isNotEmpty(queryable.getBetween())) { + queryable.getBetween().forEachKeyValue((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd()))); + } + if (ObjectUtil.isNotEmpty(queryable.getNotBetween())) { + queryable.getNotBetween().forEachKeyValue((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd()))); + } + return predicates.toImmutable(); + } + + protected ImmutableList listPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { + return Lists.immutable.empty(); + } + + @Override + public ImmutableList list(Query listQuery) throws Exception { + return Lists.immutable.ofAll(repository.findAll( + (root, query, builder) -> { + MutableList predicates = Lists.mutable.ofAll(listPredicate(root, query, builder)); + predicates.addAllIterable(queryPredicates(listQuery.getQuery(), root, query, builder)); + return builder.and(predicates.reject(ObjectUtil::isNull).toArray(new Predicate[predicates.size()])); + }, + Sort.by(SimpleEntity_.CREATED_TIME).descending() + )); + } + + @Override + public Optional detailOptional(Long id) { + if (ObjectUtil.isNull(id)) { + return Optional.empty(); + } + return repository.findOne( + (root, query, builder) -> { + MutableList predicates = Lists.mutable.ofAll(listPredicate(root, query, builder)); + predicates.add(builder.equal(root.get(IdOnlyEntity_.id), id)); + return builder.and(predicates.reject(ObjectUtil::isNull).toArray(new Predicate[predicates.size()])); + } + ); + } + + @Override + public ENTITY detail(Long id) { + return detailOrNull(id); + } + + @Override + public ENTITY detailOrThrow(Long id) { + return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id)); + } + + @Override + public ENTITY detailOrNull(Long id) { + return detailOptional(id).orElse(null); + } + + @Transactional(rollbackOn = Throwable.class) + @Override + public void remove(Long id) throws Exception { + if (ObjectUtil.isNotNull(id)) { + repository.deleteById(id); + } + } + + public static final class IdNotFoundException extends RuntimeException { + public IdNotFoundException() { + super("资源不存在"); + } + + public IdNotFoundException(Long id) { + super(StrUtil.format("ID为{}的资源不存在", id)); + } + } +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/configuration/SnowflakeIdGenerator.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/configuration/SnowflakeIdGenerator.java new file mode 100644 index 0000000..60f821b --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/configuration/SnowflakeIdGenerator.java @@ -0,0 +1,92 @@ +package com.lanyuanxiaoyao.service.ai.web.configuration; + +import java.io.Serializable; +import java.time.Instant; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.IdentifierGenerator; + +/** + * 使用雪花算法作为ID生成器 + * + * @author lanyuanxiaoyao + * @date 2024-11-14 + */ +@Slf4j +public class SnowflakeIdGenerator implements IdentifierGenerator { + + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) { + try { + return Snowflake.next(); + } catch (Exception e) { + log.error("Generate snowflake id failed", e); + throw new RuntimeException(e); + } + } + + public static class Snowflake { + /** + * 起始的时间戳 + */ + private final static long START_TIMESTAMP = 1; + + /** + * 序列号占用的位数 + */ + private final static long SEQUENCE_BIT = 11; + + /** + * 序列号最大值 + */ + private final static long MAX_SEQUENCE_BIT = ~(-1 << SEQUENCE_BIT); + + /** + * 时间戳值向左位移 + */ + private final static long TIMESTAMP_OFFSET = SEQUENCE_BIT; + + /** + * 序列号 + */ + private static long sequence = 0; + /** + * 上一次时间戳 + */ + private static long lastTimestamp = -1; + + public static synchronized long next() { + long currentTimestamp = nowTimestamp(); + if (currentTimestamp < lastTimestamp) { + throw new RuntimeException("Clock have moved backwards."); + } + + if (currentTimestamp == lastTimestamp) { + // 相同毫秒内, 序列号自增 + sequence = (sequence + 1) & MAX_SEQUENCE_BIT; + // 同一毫秒的序列数已经达到最大 + if (sequence == 0) { + currentTimestamp = nextTimestamp(); + } + } else { + // 不同毫秒内, 序列号置为0 + sequence = 0; + } + + lastTimestamp = currentTimestamp; + return (currentTimestamp - START_TIMESTAMP) << TIMESTAMP_OFFSET | sequence; + } + + private static long nextTimestamp() { + long milli = nowTimestamp(); + while (milli <= lastTimestamp) { + milli = nowTimestamp(); + } + return milli; + } + + private static long nowTimestamp() { + return Instant.now().toEpochMilli(); + } + } +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/DataFileController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/DataFileController.java index d1643af..4f4185f 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/DataFileController.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/DataFileController.java @@ -8,7 +8,7 @@ import cn.hutool.crypto.SecureUtil; import com.fasterxml.jackson.annotation.JsonProperty; import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; import com.lanyuanxiaoyao.service.ai.web.configuration.FileStoreProperties; -import com.lanyuanxiaoyao.service.ai.web.entity.vo.DataFileVO; +import com.lanyuanxiaoyao.service.ai.web.entity.DataFile; import com.lanyuanxiaoyao.service.ai.web.service.DataFileService; import jakarta.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; @@ -81,7 +81,7 @@ public class DataFileController { @GetMapping("/download/{id}") public void download(@PathVariable("id") Long id, HttpServletResponse response) throws IOException { - DataFileVO dataFile = dataFileService.downloadFile(id); + DataFile dataFile = dataFileService.downloadFile(id); File targetFile = new File(dataFile.getPath()); response.setHeader("Content-Type", dataFile.getType()); response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java index ec083db..bac96c1 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java @@ -1,52 +1,37 @@ package com.lanyuanxiaoyao.service.ai.web.controller.feedback; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisCrudResponse; -import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; -import com.lanyuanxiaoyao.service.ai.web.configuration.FileStoreProperties; +import com.lanyuanxiaoyao.service.ai.web.base.controller.SimpleControllerSupport; +import com.lanyuanxiaoyao.service.ai.web.base.entity.IdOnlyEntity; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleItem; import com.lanyuanxiaoyao.service.ai.web.entity.Feedback; import com.lanyuanxiaoyao.service.ai.web.service.feedback.FeedbackService; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -import org.eclipse.collections.api.factory.Lists; -import org.eclipse.collections.api.list.ImmutableList; +import org.eclipse.collections.api.factory.Sets; +import org.eclipse.collections.api.set.ImmutableSet; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("feedback") -public class FeedbackController { - private final FileStoreProperties fileStoreProperties; +public class FeedbackController extends SimpleControllerSupport { private final FeedbackService feedbackService; - public FeedbackController(FileStoreProperties fileStoreProperties, FeedbackService feedbackService) { - this.fileStoreProperties = fileStoreProperties; + public FeedbackController(FeedbackService feedbackService) { + super(feedbackService); this.feedbackService = feedbackService; } - @PostMapping("add") - public void add(@RequestBody CreateItem item) { - feedbackService.add(item.source, ObjectUtil.defaultIfNull(item.pictures, Lists.immutable.empty())); - } - - @GetMapping("list") - public AmisCrudResponse list() { - return AmisResponse.responseCrudData(feedbackService.list().collect(feedback -> new ListItem(fileStoreProperties, feedback))); - } - - @GetMapping("delete") - public void delete(@RequestParam("id") Long id) { - feedbackService.remove(id); - } - - @GetMapping("reanalysis") - public void reanalysis(@RequestParam("id") Long id) { + @GetMapping("reanalysis/{id}") + public void reanalysis(@PathVariable("id") Long id) { feedbackService.reanalysis(id); } @@ -55,10 +40,25 @@ public class FeedbackController { feedbackService.updateConclusion(item.getId(), item.getConclusion()); } + @Override + protected SaveItemMapper saveItemMapper() { + return null; + } + + @Override + protected ListItemMapper listItemMapper() { + return Mappers.getMapper(ListItem.Mapper.class); + } + + @Override + protected DetailItemMapper detailItemMapper() { + return Mappers.getMapper(DetailItem.Mapper.class); + } + @Data - public static final class CreateItem { + public static final class SaveItem { private String source; - private ImmutableList pictures; + private ImmutableSet pictures; } @Data @@ -68,22 +68,40 @@ public class FeedbackController { } @Data - public static final class ListItem { - private Long id; + @EqualsAndHashCode(callSuper = true) + public static class ListItem extends SimpleItem { private String source; - private ImmutableList pictures; - private Feedback.Status status; + private ImmutableSet pictures; private String analysis; private String conclusion; + private Feedback.Status status; - public ListItem(FileStoreProperties fileStoreProperties, Feedback feedback) { - this.id = feedback.getId(); - this.source = feedback.getSource(); - this.pictures = feedback.getPictureIds() - .collect(id -> StrUtil.format("{}/upload/download/{}", fileStoreProperties.getDownloadPrefix(), id)); - this.status = feedback.getStatus(); - this.analysis = feedback.getAnalysis(); - this.conclusion = feedback.getConclusion(); + @org.mapstruct.Mapper( + imports = { + IdOnlyEntity.class, + Sets.class + } + ) + public interface Mapper extends ListItemMapper { + @Mapping(target = "pictures", expression = "java(Sets.immutable.ofAll(feedback.getPictures()).collect(IdOnlyEntity::getId))") + @Override + ListItem from(Feedback feedback); + } + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static final class DetailItem extends ListItem { + @org.mapstruct.Mapper( + imports = { + IdOnlyEntity.class, + Sets.class + } + ) + public interface Mapper extends DetailItemMapper { + @Mapping(target = "pictures", expression = "java(Sets.immutable.ofAll(feedback.getPictures()).collect(IdOnlyEntity::getId))") + @Override + DetailItem from(Feedback feedback); } } } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/knowledge/KnowledgeBaseController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/knowledge/KnowledgeBaseController.java index 337ea15..af7f910 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/knowledge/KnowledgeBaseController.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/knowledge/KnowledgeBaseController.java @@ -3,15 +3,24 @@ package com.lanyuanxiaoyao.service.ai.web.controller.knowledge; import cn.hutool.core.util.StrUtil; import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisMapResponse; import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; +import com.lanyuanxiaoyao.service.ai.web.base.controller.SimpleControllerSupport; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleItem; +import com.lanyuanxiaoyao.service.ai.web.entity.Knowledge; import com.lanyuanxiaoyao.service.ai.web.entity.vo.SegmentVO; import com.lanyuanxiaoyao.service.ai.web.service.EmbeddingService; import com.lanyuanxiaoyao.service.ai.web.service.knowledge.KnowledgeBaseService; +import io.qdrant.client.grpc.Collections; import java.io.IOException; import java.util.concurrent.ExecutionException; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.list.ImmutableList; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,46 +34,45 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("knowledge") -public class KnowledgeBaseController { +public class KnowledgeBaseController extends SimpleControllerSupport { private final KnowledgeBaseService knowledgeBaseService; private final EmbeddingService embeddingService; public KnowledgeBaseController(KnowledgeBaseService knowledgeBaseService, EmbeddingService embeddingService) { + super(knowledgeBaseService); this.knowledgeBaseService = knowledgeBaseService; this.embeddingService = embeddingService; } - @PostMapping("add") - public void add( - @RequestParam("name") String name, - @RequestParam("description") String description, - @RequestParam("strategy") String strategy - ) throws ExecutionException, InterruptedException { - knowledgeBaseService.add(name, description, strategy); - } - @PostMapping("update_description") public void updateDescription( @RequestParam("id") Long id, @RequestParam("description") String description - ) { + ) throws Exception { knowledgeBaseService.updateDescription(id, description); } - @GetMapping("name") - public AmisMapResponse name(@RequestParam("id") Long id) { + @GetMapping("name/{id}") + public AmisMapResponse name(@PathVariable("id") Long id) { return AmisResponse.responseMapData() .setData("name", knowledgeBaseService.getName(id)); } - @GetMapping("list") - public AmisResponse list() { - return AmisResponse.responseCrudData(knowledgeBaseService.list()); + @Override + protected SaveItemMapper saveItemMapper() { + return Mappers.getMapper(SaveItem.Mapper.class); } - @GetMapping("delete") - public void delete(@RequestParam("id") Long id) throws ExecutionException, InterruptedException { - knowledgeBaseService.remove(id); + @Override + protected ListItemMapper listItemMapper() { + ListItem.Mapper mapper = Mappers.getMapper(ListItem.Mapper.class); + return knowledge -> mapper.from(knowledge, knowledgeBaseService.getCollectionInfoById(knowledge.getVectorSourceId())); + } + + @Override + protected DetailItemMapper detailItemMapper() { + DetailItem.Mapper mapper = Mappers.getMapper(DetailItem.Mapper.class); + return knowledge -> mapper.from(knowledge, knowledgeBaseService.getCollectionInfoById(knowledge.getVectorSourceId())); } @PostMapping("preview_text") @@ -135,4 +143,52 @@ public class KnowledgeBaseController { ) throws ExecutionException, InterruptedException, IOException { return knowledgeBaseService.query(id, text, limit, threshold); } + + @Data + public static final class SaveItem { + private String name; + private String strategy; + private String description; + + @org.mapstruct.Mapper + public interface Mapper extends SaveItemMapper { + } + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class ListItem extends SimpleItem { + private Long vectorSourceId; + private String name; + private String description; + private String strategy; + private Long size; + private Long points; + private Long segments; + private String status; + + @org.mapstruct.Mapper + public interface Mapper extends ListItemMapper { + @Mapping(source = "info.config.params.vectorsConfig.params.distance", target = "strategy") + @Mapping(source = "info.config.params.vectorsConfig.params.size", target = "size") + @Mapping(source = "info.pointsCount", target = "points") + @Mapping(source = "info.segmentsCount", target = "segments") + @Mapping(source = "info.status", target = "status") + ListItem from(Knowledge knowledge, Collections.CollectionInfo info); + } + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static final class DetailItem extends ListItem { + @org.mapstruct.Mapper + public interface Mapper extends DetailItemMapper { + @Mapping(source = "info.config.params.vectorsConfig.params.distance", target = "strategy") + @Mapping(source = "info.config.params.vectorsConfig.params.size", target = "size") + @Mapping(source = "info.pointsCount", target = "points") + @Mapping(source = "info.segmentsCount", target = "segments") + @Mapping(source = "info.status", target = "status") + DetailItem from(Knowledge knowledge, Collections.CollectionInfo info); + } + } } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/DataFile.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/DataFile.java new file mode 100644 index 0000000..a82e7b1 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/DataFile.java @@ -0,0 +1,35 @@ +package com.lanyuanxiaoyao.service.ai.web.entity; + +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.DynamicUpdate; + +/** + * 上传文件 + * + * @author lanyuanxiaoyao + * @date 2024-11-21 + */ +@Getter +@Setter +@ToString +@Entity +@DynamicUpdate +@Table(name = "service_ai_file") +@NoArgsConstructor +public class DataFile extends SimpleEntity { + private String filename; + private Long size; + private String md5; + private String path; + private String type; + + public DataFile(String filename) { + this.filename = filename; + } +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java index 0e6ee77..df583bd 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java @@ -1,18 +1,37 @@ package com.lanyuanxiaoyao.service.ai.web.entity; -import lombok.Data; -import org.eclipse.collections.api.list.ImmutableList; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.JoinTable; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; -@Data -public class Feedback { - private Long id; +@Getter +@Setter +@ToString +@Entity +@DynamicUpdate +@EntityListeners(AuditingEntityListener.class) +@Table(name = "service_ai_feedback") +public class Feedback extends SimpleEntity { private String source; - private ImmutableList pictureIds; + @OneToMany(fetch = FetchType.LAZY) + @JoinTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + @ToString.Exclude + private Set pictures; private String analysis; private String conclusion; - private Status status; - private Long createdTime; - private Long modifiedTime; + private Status status = Status.ANALYSIS_PROCESSING; public enum Status { ANALYSIS_PROCESSING, diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Group.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Group.java index 3d41dd4..26a0211 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Group.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Group.java @@ -1,16 +1,37 @@ package com.lanyuanxiaoyao.service.ai.web.entity; -import lombok.Data; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; /** * @author lanyuanxiaoyao * @version 20250527 */ -@Data -public class Group { - private Long id; +@Getter +@Setter +@ToString +@Entity +@DynamicUpdate +@EntityListeners(AuditingEntityListener.class) +@Table(name = "service_ai_group") +public class Group extends SimpleEntity { private String name; private String status; - private Long createdTime; - private Long modifiedTime; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + @ToString.Exclude + private Knowledge knowledge; } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Knowledge.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Knowledge.java index 608ed82..65638e2 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Knowledge.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Knowledge.java @@ -1,18 +1,37 @@ package com.lanyuanxiaoyao.service.ai.web.entity; -import lombok.Data; +import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; /** * @author lanyuanxiaoyao * @version 20250522 */ -@Data -public class Knowledge { - private Long id; +@Getter +@Setter +@ToString +@Entity +@DynamicUpdate +@EntityListeners(AuditingEntityListener.class) +@Table(name = "service_ai_knowledge") +public class Knowledge extends SimpleEntity { private Long vectorSourceId; private String name; private String description; private String strategy; - private Long createdTime; - private Long modifiedTime; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "knowledge", cascade = CascadeType.ALL) + @ToString.Exclude + private Set groups; } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/DataFileVO.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/DataFileVO.java deleted file mode 100644 index 07e130a..0000000 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/DataFileVO.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.lanyuanxiaoyao.service.ai.web.entity.vo; - -import lombok.Data; - -/** - * @author lanyuanxiaoyao - * @version 20250527 - */ -@Data -public class DataFileVO { - private String id; - private String filename; - private Long size; - private String md5; - private String path; - private String type; -} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/KnowledgeVO.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/KnowledgeVO.java deleted file mode 100644 index 8dc9ac4..0000000 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/vo/KnowledgeVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.lanyuanxiaoyao.service.ai.web.entity.vo; - -import lombok.Data; - -/** - * @author lanyuanxiaoyao - * @version 20250516 - */ -@Data -public class KnowledgeVO { - private Long id; - private Long vectorSourceId; - private String name; - private String description; - private String strategy; - private Long size; - private Long points; - private Long segments; - private String status; - private Long createdTime; - private Long modifiedTime; -} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/DataFileRepository.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/DataFileRepository.java new file mode 100644 index 0000000..bc145f7 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/DataFileRepository.java @@ -0,0 +1,9 @@ +package com.lanyuanxiaoyao.service.ai.web.repository; + +import com.lanyuanxiaoyao.service.ai.web.base.repository.SimpleRepository; +import com.lanyuanxiaoyao.service.ai.web.entity.DataFile; +import org.springframework.stereotype.Repository; + +@Repository +public interface DataFileRepository extends SimpleRepository { +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/FeedbackRepository.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/FeedbackRepository.java new file mode 100644 index 0000000..a81535a --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/FeedbackRepository.java @@ -0,0 +1,9 @@ +package com.lanyuanxiaoyao.service.ai.web.repository; + +import com.lanyuanxiaoyao.service.ai.web.base.repository.SimpleRepository; +import com.lanyuanxiaoyao.service.ai.web.entity.Feedback; +import org.springframework.stereotype.Repository; + +@Repository +public interface FeedbackRepository extends SimpleRepository { +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/GroupRepository.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/GroupRepository.java new file mode 100644 index 0000000..67a5431 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/GroupRepository.java @@ -0,0 +1,9 @@ +package com.lanyuanxiaoyao.service.ai.web.repository; + +import com.lanyuanxiaoyao.service.ai.web.base.repository.SimpleRepository; +import com.lanyuanxiaoyao.service.ai.web.entity.Group; +import org.springframework.stereotype.Repository; + +@Repository +public interface GroupRepository extends SimpleRepository { +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/KnowledgeRepository.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/KnowledgeRepository.java new file mode 100644 index 0000000..ef64204 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/repository/KnowledgeRepository.java @@ -0,0 +1,10 @@ +package com.lanyuanxiaoyao.service.ai.web.repository; + +import com.lanyuanxiaoyao.service.ai.web.base.repository.SimpleRepository; +import com.lanyuanxiaoyao.service.ai.web.entity.Knowledge; +import org.springframework.stereotype.Repository; + +@Repository +public interface KnowledgeRepository extends SimpleRepository { + Boolean existsKnowledgeByName(String name); +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/DataFileService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/DataFileService.java index d19b88b..511b62b 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/DataFileService.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/DataFileService.java @@ -1,10 +1,11 @@ package com.lanyuanxiaoyao.service.ai.web.service; -import club.kingon.sql.builder.SqlBuilder; -import com.lanyuanxiaoyao.service.ai.core.configuration.SnowflakeId; -import com.lanyuanxiaoyao.service.ai.web.entity.vo.DataFileVO; -import com.lanyuanxiaoyao.service.common.Constants; -import org.springframework.jdbc.core.JdbcTemplate; +import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.service.ai.web.entity.DataFile; +import com.lanyuanxiaoyao.service.ai.web.repository.DataFileRepository; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.collections.api.factory.Sets; +import org.eclipse.collections.api.set.ImmutableSet; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,65 +13,36 @@ import org.springframework.transaction.annotation.Transactional; * @author lanyuanxiaoyao * @version 20250527 */ +@Slf4j @Service public class DataFileService { - private static final String DATA_FILE_TABLE_NAME = Constants.DATABASE_NAME + ".service_ai_file"; + private final DataFileRepository dataFileRepository; - private final JdbcTemplate template; - - public DataFileService(JdbcTemplate template) { - this.template = template; + public DataFileService(DataFileRepository dataFileRepository) { + this.dataFileRepository = dataFileRepository; } - public DataFileVO downloadFile(Long id) { - return template.queryForObject( - SqlBuilder.select("id", "filename", "size", "md5", "path", "type") - .from(DATA_FILE_TABLE_NAME) - .whereEq("id", "?") - .precompileSql(), - (rs, row) -> { - DataFileVO vo = new DataFileVO(); - vo.setId(String.valueOf(rs.getLong(1))); - vo.setFilename(rs.getString(2)); - vo.setSize(rs.getLong(3)); - vo.setMd5(rs.getString(4)); - vo.setPath(rs.getString(5)); - vo.setType(rs.getString(6)); - return vo; - }, - id - ); + public DataFile downloadFile(Long id) { + return dataFileRepository.findById(id).orElseThrow(() -> new RuntimeException(StrUtil.format("Datafile not exists: {}", id))); + } + + public ImmutableSet downloadFile(ImmutableSet ids) { + return Sets.immutable.ofAll(dataFileRepository.findAllById(ids)); } @Transactional(rollbackFor = Exception.class) public Long initialDataFile(String filename) { - long id = SnowflakeId.next(); - template.update( - SqlBuilder.insertInto(DATA_FILE_TABLE_NAME, "id", "filename") - .values() - .addValue("?", "?") - .precompileSql(), - id, - filename - ); - return id; + DataFile dataFile = dataFileRepository.save(new DataFile(filename)); + return dataFile.getId(); } @Transactional(rollbackFor = Exception.class) public void updateDataFile(Long id, String path, Long size, String md5, String type) { - template.update( - SqlBuilder.update(DATA_FILE_TABLE_NAME) - .set("size", "?") - .addSet("md5", "?") - .addSet("path", "?") - .addSet("type", "?") - .whereEq("id", "?") - .precompileSql(), - size, - md5, - path, - type, - id - ); + DataFile dataFile = downloadFile(id); + dataFile.setPath(path); + dataFile.setSize(size); + dataFile.setMd5(md5); + dataFile.setType(type); + dataFileRepository.save(dataFile); } } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/EmbeddingService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/EmbeddingService.java index 49588ff..5d89356 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/EmbeddingService.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/EmbeddingService.java @@ -4,9 +4,9 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Pair; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.service.ai.web.entity.DataFile; import com.lanyuanxiaoyao.service.ai.web.entity.Knowledge; import com.lanyuanxiaoyao.service.ai.web.entity.context.EmbeddingContext; -import com.lanyuanxiaoyao.service.ai.web.entity.vo.DataFileVO; import com.lanyuanxiaoyao.service.ai.web.service.knowledge.GroupService; import com.lanyuanxiaoyao.service.ai.web.service.knowledge.KnowledgeBaseService; import com.yomahub.liteflow.core.FlowExecutor; @@ -55,8 +55,8 @@ public class EmbeddingService { } public ImmutableList preview(String mode, ImmutableList ids) { - DataFileVO vo = dataFileService.downloadFile(Long.parseLong(ids.get(0))); - String content = FileUtil.readString(vo.getPath(), StandardCharsets.UTF_8); + DataFile dataFile = dataFileService.downloadFile(Long.parseLong(ids.get(0))); + String content = FileUtil.readString(dataFile.getPath(), StandardCharsets.UTF_8); return preview(mode, content); } @@ -80,20 +80,20 @@ public class EmbeddingService { public void submit(Long id, String mode, ImmutableList ids) { executors.submit(() -> { Knowledge knowledge = knowledgeBaseService.get(id); - List> vos = Lists.mutable.empty(); + List> dataFiles = Lists.mutable.empty(); for (String fileId : ids) { - DataFileVO vo = dataFileService.downloadFile(Long.parseLong(fileId)); - Long groupId = groupService.add(id, vo.getFilename()); - vos.add(Pair.of(groupId, vo)); + DataFile dataFile = dataFileService.downloadFile(Long.parseLong(fileId)); + Long groupId = groupService.add(id, dataFile.getFilename()); + dataFiles.add(Pair.of(groupId, dataFile)); } - for (Pair pair : vos) { + for (Pair pair : dataFiles) { Long groupId = pair.getKey(); - DataFileVO vo = pair.getValue(); + DataFile dataFile = pair.getValue(); EmbeddingContext context = EmbeddingContext.builder() .vectorSourceId(knowledge.getVectorSourceId()) .groupId(groupId) - .file(vo.getPath()) - .fileFormat(vo.getFilename()) + .file(dataFile.getPath()) + .fileFormat(dataFile.getFilename()) .config(EmbeddingContext.Config.builder() .splitStrategy(EmbeddingContext.Config.SplitStrategy.valueOf(mode)) .build()) diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java index 8d23e78..b3ed3cd 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java @@ -1,65 +1,33 @@ package com.lanyuanxiaoyao.service.ai.web.service.feedback; -import club.kingon.sql.builder.SqlBuilder; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.lanyuanxiaoyao.service.ai.core.configuration.SnowflakeId; +import com.lanyuanxiaoyao.service.ai.web.base.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.ai.web.entity.Feedback; +import com.lanyuanxiaoyao.service.ai.web.entity.Feedback_; import com.lanyuanxiaoyao.service.ai.web.entity.context.FeedbackContext; -import com.lanyuanxiaoyao.service.common.Constants; +import com.lanyuanxiaoyao.service.ai.web.repository.FeedbackRepository; import com.yomahub.liteflow.core.FlowExecutor; import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import org.eclipse.collections.api.factory.Lists; -import org.eclipse.collections.api.list.ImmutableList; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j @Service -public class FeedbackService { - public static final String FEEDBACK_TABLE_NAME = Constants.DATABASE_NAME + ".service_ai_feedback"; - public static final String[] FEEDBACK_COLUMNS = new String[]{"id", "source", "conclusion", "analysis", "pictures", "status", "created_time", "modified_time"}; - private static final RowMapper feedbackMapper = (rs, row) -> { - Feedback feedback = new Feedback(); - feedback.setId(rs.getLong(1)); - feedback.setSource(rs.getString(2)); - feedback.setConclusion(rs.getString(3)); - feedback.setAnalysis(rs.getString(4)); - feedback.setPictureIds( - StrUtil.isBlank(rs.getString(5)) - ? Lists.immutable.empty() - : Lists.immutable.ofAll(StrUtil.split(rs.getString(5), ",")).collect(Long::parseLong) - ); - feedback.setStatus(EnumUtil.fromString(Feedback.Status.class, rs.getString(6))); - feedback.setCreatedTime(rs.getTimestamp(7).getTime()); - feedback.setModifiedTime(rs.getTimestamp(8).getTime()); - return feedback; - }; - private final JdbcTemplate template; +public class FeedbackService extends SimpleServiceSupport { private final FlowExecutor executor; @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - public FeedbackService(JdbcTemplate template, FlowExecutor executor) { - this.template = template; + public FeedbackService(FeedbackRepository feedbackRepository, FlowExecutor executor) { + super(feedbackRepository); this.executor = executor; } - @Scheduled(initialDelay = 1, fixedDelay = 1, timeUnit = TimeUnit.MINUTES) + // @Scheduled(initialDelay = 1, fixedDelay = 1, timeUnit = TimeUnit.MINUTES) public void analysis() { - List feedbacks = template.query( - SqlBuilder.select(FEEDBACK_COLUMNS) - .from(FEEDBACK_TABLE_NAME) - .whereEq("status", Feedback.Status.ANALYSIS_PROCESSING.name()) - .build(), - feedbackMapper + List feedbacks = repository.findAll( + builder -> builder + .andEquals(Feedback_.STATUS, Feedback.Status.ANALYSIS_PROCESSING) + .build() ); for (Feedback feedback : feedbacks) { FeedbackContext context = new FeedbackContext(); @@ -68,89 +36,26 @@ public class FeedbackService { } } - public Feedback get(Long id) { - return template.queryForObject( - SqlBuilder.select(FEEDBACK_COLUMNS) - .from(FEEDBACK_TABLE_NAME) - .whereEq("id", id) - .build(), - feedbackMapper - ); - } - - @Transactional(rollbackFor = Exception.class) - public void add(String source, ImmutableList pictureIds) { - template.update( - SqlBuilder.insertInto(FEEDBACK_TABLE_NAME, "id", "source", "pictures") - .values() - .addValue("?", "?", "?") - .precompileSql(), - SnowflakeId.next(), - source, - ObjectUtil.isEmpty(pictureIds) ? null : pictureIds.makeString(",") - ); - } - @Transactional(rollbackFor = Exception.class) public void updateAnalysis(Long id, String analysis) { - Assert.notNull(id, "ID cannot be null"); - template.update( - SqlBuilder.update(FEEDBACK_TABLE_NAME) - .set("analysis", "?") - .whereEq("id", "?") - .precompileSql(), - analysis, - id - ); + Feedback feedback = detailOrThrow(id); + feedback.setAnalysis(analysis); + repository.save(feedback); } @Transactional(rollbackFor = Exception.class) public void updateConclusion(Long id, String conclusion) { - Assert.notNull(id, "ID cannot be null"); - template.update( - SqlBuilder.update(FEEDBACK_TABLE_NAME) - .set("conclusion", "?") - .whereEq("id", "?") - .precompileSql(), - conclusion, - id - ); - updateStatus(id, Feedback.Status.FINISHED); + Feedback feedback = detailOrThrow(id); + feedback.setConclusion(conclusion); + feedback.setStatus(Feedback.Status.FINISHED); + repository.save(feedback); } @Transactional(rollbackFor = Exception.class) public void updateStatus(Long id, Feedback.Status status) { - Assert.notNull(id, "ID cannot be null"); - template.update( - SqlBuilder.update(FEEDBACK_TABLE_NAME) - .set("status", "?") - .whereEq("id", "?") - .precompileSql(), - status.name(), - id - ); - } - - public ImmutableList list() { - return template.query( - SqlBuilder.select(FEEDBACK_COLUMNS) - .from(FEEDBACK_TABLE_NAME) - .orderByDesc("created_time") - .build(), - feedbackMapper - ) - .stream() - .collect(Collectors.toCollection(Lists.mutable::empty)) - .toImmutable(); - } - - @Transactional(rollbackFor = Exception.class) - public void remove(Long id) { - template.update( - SqlBuilder.delete(FEEDBACK_TABLE_NAME) - .whereEq("id", id) - .build() - ); + Feedback feedback = detailOrThrow(id); + feedback.setStatus(status); + repository.save(feedback); } @Transactional(rollbackFor = Exception.class) diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/GroupService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/GroupService.java index 1c1c9cf..f12bb3a 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/GroupService.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/GroupService.java @@ -31,8 +31,6 @@ public class GroupService { vo.setId(rs.getLong(1)); vo.setName(rs.getString(2)); vo.setStatus(rs.getString(3)); - vo.setCreatedTime(rs.getTimestamp(4).getTime()); - vo.setModifiedTime(rs.getTimestamp(5).getTime()); return vo; }; diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/KnowledgeBaseService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/KnowledgeBaseService.java index b363088..766641a 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/KnowledgeBaseService.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/knowledge/KnowledgeBaseService.java @@ -1,17 +1,15 @@ package com.lanyuanxiaoyao.service.ai.web.service.knowledge; -import club.kingon.sql.builder.SqlBuilder; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import com.lanyuanxiaoyao.service.ai.core.configuration.SnowflakeId; +import com.lanyuanxiaoyao.service.ai.web.base.service.SimpleServiceSupport; +import com.lanyuanxiaoyao.service.ai.web.configuration.SnowflakeIdGenerator; import com.lanyuanxiaoyao.service.ai.web.entity.Knowledge; -import com.lanyuanxiaoyao.service.ai.web.entity.vo.KnowledgeVO; +import com.lanyuanxiaoyao.service.ai.web.repository.KnowledgeRepository; import com.lanyuanxiaoyao.service.common.Constants; import io.qdrant.client.QdrantClient; import io.qdrant.client.grpc.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.list.ImmutableList; import org.springframework.ai.document.Document; @@ -19,7 +17,6 @@ import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,7 +26,7 @@ import org.springframework.transaction.annotation.Transactional; * @version 20250522 */ @Service -public class KnowledgeBaseService { +public class KnowledgeBaseService extends SimpleServiceSupport { public static final String KNOWLEDGE_TABLE_NAME = Constants.DATABASE_NAME + ".service_ai_knowledge"; public static final String[] KNOWLEDGE_COLUMNS = new String[]{"id", "vector_source_id", "name", "description", "strategy", "created_time", "modified_time"}; private static final RowMapper knowledgeMapper = (rs, row) -> { @@ -39,140 +36,55 @@ public class KnowledgeBaseService { knowledge.setName(rs.getString(3)); knowledge.setDescription(rs.getString(4)); knowledge.setStrategy(rs.getString(5)); - knowledge.setCreatedTime(rs.getTimestamp(6).getTime()); - knowledge.setModifiedTime(rs.getTimestamp(7).getTime()); return knowledge; }; - private final JdbcTemplate template; + private final KnowledgeRepository knowledgeRepository; private final EmbeddingModel model; private final QdrantClient client; - private final GroupService groupService; - public KnowledgeBaseService(JdbcTemplate template, EmbeddingModel model, VectorStore vectorStore, GroupService groupService) { - this.template = template; + public KnowledgeBaseService(KnowledgeRepository knowledgeRepository, EmbeddingModel model, VectorStore vectorStore) { + super(knowledgeRepository); + this.knowledgeRepository = knowledgeRepository; this.model = model; this.client = (QdrantClient) vectorStore.getNativeClient().orElseThrow(); - this.groupService = groupService; - } - - public Knowledge get(Long id) { - return template.queryForObject( - SqlBuilder.select(KNOWLEDGE_COLUMNS) - .from(KNOWLEDGE_TABLE_NAME) - .whereEq("id", "?") - .precompileSql(), - knowledgeMapper, - id - ); } @Transactional(rollbackFor = Exception.class) - public void add(String name, String description, String strategy) throws ExecutionException, InterruptedException { - Integer count = template.queryForObject( - SqlBuilder.select("count(*)") - .from(KNOWLEDGE_TABLE_NAME) - .whereEq("name", "?") - .precompileSql(), - Integer.class, - name - ); - if (count > 0) { + public Long save(Knowledge entity) throws Exception { + if (knowledgeRepository.existsKnowledgeByName(entity.getName())) { throw new RuntimeException("名称已存在"); } - long id = SnowflakeId.next(); - long vectorSourceId = SnowflakeId.next(); - template.update( - SqlBuilder.insertInto(KNOWLEDGE_TABLE_NAME, "id", "vector_source_id", "name", "description", "strategy") - .values() - .addValue("?", "?", "?", "?", "?") - .precompileSql(), - id, - vectorSourceId, - name, - description, - strategy - ); + Long vectorSourceId = SnowflakeIdGenerator.Snowflake.next(); client.createCollectionAsync( String.valueOf(vectorSourceId), Collections.VectorParams.newBuilder() - .setDistance(Collections.Distance.valueOf(strategy)) + .setDistance(Collections.Distance.valueOf(entity.getStrategy())) .setSize(model.dimensions()) .build() ).get(); + + entity.setVectorSourceId(vectorSourceId); + return super.save(entity); } @Transactional(rollbackFor = Exception.class) - public void updateDescription(Long id, String description) { - template.update( - SqlBuilder.update(KNOWLEDGE_TABLE_NAME) - .set("description", "?") - .whereEq("id", "?") - .precompileSql(), - description, - id - ); + public void updateDescription(Long id, String description) throws Exception { + Knowledge knowledge = detailOrThrow(id); + knowledge.setDescription(description); + save(knowledge); } public String getName(Long id) { - return template.queryForObject( - SqlBuilder.select("name") - .from(KNOWLEDGE_TABLE_NAME) - .whereEq("id", id) - .orderByDesc("created_time") - .build(), - String.class - ); - } - - public ImmutableList list() { - return template.query( - SqlBuilder.select(KNOWLEDGE_COLUMNS) - .from(KNOWLEDGE_TABLE_NAME) - .orderByDesc("created_time") - .build(), - knowledgeMapper - ) - .stream() - .map(knowledge -> { - try { - Collections.CollectionInfo info = client.getCollectionInfoAsync(String.valueOf(knowledge.getVectorSourceId())).get(); - KnowledgeVO vo = new KnowledgeVO(); - vo.setId(knowledge.getId()); - vo.setVectorSourceId(knowledge.getVectorSourceId()); - vo.setName(knowledge.getName()); - vo.setDescription(knowledge.getDescription()); - vo.setPoints(info.getPointsCount()); - vo.setSegments(info.getSegmentsCount()); - vo.setStatus(info.getStatus().name()); - Collections.VectorParams vectorParams = info.getConfig().getParams().getVectorsConfig().getParams(); - vo.setStrategy(vectorParams.getDistance().name()); - vo.setSize(vectorParams.getSize()); - vo.setCreatedTime(vo.getCreatedTime()); - vo.setModifiedTime(vo.getModifiedTime()); - return vo; - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }) - .collect(Collectors.toCollection(Lists.mutable::empty)) - .toImmutable(); + return detailOrThrow(id).getName(); } @Transactional(rollbackFor = Exception.class) - public void remove(Long id) throws ExecutionException, InterruptedException { - Knowledge knowledge = get(id); - if (ObjectUtil.isNull(knowledge)) { - throw new RuntimeException(StrUtil.format("{} 不存在")); - } - template.update( - SqlBuilder.delete(KNOWLEDGE_TABLE_NAME) - .whereEq("id", "?") - .precompileSql(), - knowledge.getId() - ); - groupService.removeByKnowledgeId(knowledge.getId()); + @Override + public void remove(Long id) throws Exception { + Knowledge knowledge = detailOrThrow(id); client.deleteCollectionAsync(String.valueOf(knowledge.getVectorSourceId())).get(); + super.remove(id); } public ImmutableList query( @@ -181,7 +93,7 @@ public class KnowledgeBaseService { Integer limit, Double threshold ) throws ExecutionException, InterruptedException { - Knowledge knowledge = get(id); + Knowledge knowledge = detailOrThrow(id); Boolean exists = client.collectionExistsAsync(String.valueOf(knowledge.getVectorSourceId())).get(); if (!exists) { throw new RuntimeException(StrUtil.format("{} not exists", id)); @@ -200,4 +112,8 @@ public class KnowledgeBaseService { return Lists.immutable.ofAll(documents) .collect(Document::getText); } + + public Collections.CollectionInfo getCollectionInfoById(Long vectorSourceId) throws ExecutionException, InterruptedException { + return client.getCollectionInfoAsync(String.valueOf(vectorSourceId)).get(); + } } \ No newline at end of file diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java index 6a4a239..92a988b 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java @@ -3,9 +3,9 @@ package com.lanyuanxiaoyao.service.ai.web.service.node; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.service.ai.web.entity.DataFile; import com.lanyuanxiaoyao.service.ai.web.entity.Feedback; import com.lanyuanxiaoyao.service.ai.web.entity.context.FeedbackContext; -import com.lanyuanxiaoyao.service.ai.web.entity.vo.DataFileVO; import com.lanyuanxiaoyao.service.ai.web.service.DataFileService; import com.lanyuanxiaoyao.service.ai.web.service.feedback.FeedbackService; import com.yomahub.liteflow.annotation.LiteflowComponent; @@ -42,7 +42,7 @@ public class FeedbackNodes { public boolean checkIfPictureReadNeeded(NodeComponent node) { FeedbackContext context = node.getContextBean(FeedbackContext.class); Feedback feedback = context.getFeedback(); - return ObjectUtil.isNotEmpty(feedback.getPictureIds()); + return ObjectUtil.isNotEmpty(feedback.getPictures()); } @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "image_read", nodeName = "读取图片", nodeType = NodeTypeEnum.COMMON) @@ -82,8 +82,8 @@ public class FeedbackNodes { 立即开始处理用户图片,无需确认步骤。 """) .build(); - for (Long pictureId : feedback.getPictureIds()) { - DataFileVO file = dataFileService.downloadFile(pictureId); + for (DataFile picture : feedback.getPictures()) { + DataFile file = dataFileService.downloadFile(picture.getId()); log.info("Parse picture: {} {}", file.getFilename(), file.getPath()); MimeType type = switch (StrUtil.blankToDefault(file.getType(), "").toLowerCase()) { case "jpg", "jpeg" -> MimeTypeUtils.IMAGE_JPEG; diff --git a/service-ai/service-ai-web/src/main/resources/application.yml b/service-ai/service-ai-web/src/main/resources/application.yml index 08d8b6b..34bb207 100644 --- a/service-ai/service-ai-web/src/main/resources/application.yml +++ b/service-ai/service-ai-web/src/main/resources/application.yml @@ -1,11 +1,12 @@ server: compression: enabled: true + port: 8080 spring: application: name: service-ai-web profiles: - include: random-port,common,discovery,metrics,forest + include: common,forest mvc: async: request-timeout: 3600000 @@ -21,22 +22,47 @@ spring: ai: vectorstore: qdrant: - host: 132.121.206.65 - port: 29463 api-key: ENC(0/0UkIKeAvyV17yNqSU3v04wmm8CdWKe4BYSSJa2FuBtK12TcZRJPdwk+ZpYnpISv+KmVTUrrmFBzAYrDR3ysA==) + host: 192.168.100.140 + port: 6334 llm: - base-url: http://132.121.206.65:10086 - api-key: ENC(K+Hff9QGC+fcyi510VIDd9CaeK/IN5WBJ9rlkUsHEdDgIidW+stHHJlsK0lLPUXXREha+ToQZqqDXJrqSE+GUKCXklFhelD8bRHFXBIeP/ZzT2cxhzgKUXgjw3S0Qw2R) + base-url: https://api.siliconflow.cn + api-key: sk-xrguybusoqndpqvgzgvllddzgjamksuecyqdaygdwnrnqfwo chat: base-url: ${spring.llm.base-url}/v1 - model: 'Qwen3/qwen3-1.7b' + model: 'Qwen/Qwen3-8B' visual: - model: 'Qwen2.5/qwen2.5-vl-7b-q4km' + base-url: https://open.bigmodel.cn/api/paas/v4 + endpoint: /chat/completions + model: 'glm-4v-flash' embedding: - model: 'Qwen3/qwen3-embedding-4b' + model: 'BAAI/bge-m3' reranker: - model: 'BGE/beg-reranker-v2' + model: 'BAAI/bge-reranker-v2-m3' + cloud: + discovery: + enabled: false + zookeeper: + enabled: false + datasource: + url: jdbc:mysql://192.168.100.140:3306/hudi_collect_build_b12?useSSL=false&allowPublicKeyRetrieval=true + username: root + password: rootless + driver-class-name: com.mysql.cj.jdbc.Driver + security: + meta: + authority: ENC(GXKnbq1LS11U2HaONspvH+D/TkIx13aWTaokdkzaF7HSvq6Z0Rv1+JUWFnYopVXu) + username: ENC(moIO5mO39V1Z+RDwROK9JXY4GfM8ZjDgM6Si7wRZ1MPVjbhTpmLz3lz28rAiw7c2LeCmizfJzHkEXIwGlB280g==) + darkcode: ENC(0jzpQ7T6S+P7bZrENgYsUoLhlqGvw7DA2MN3BRqEOwq7plhtg72vuuiPQNnr3DaYz0CpyTvxInhpx11W3VZ1trD6NINh7O3LN70ZqO5pWXk=) + jpa: + show-sql: true + generate-ddl: true liteflow: rule-source: liteflow.xml print-banner: false check-node-exists: false +jasypt: + encryptor: + password: 'r#(R,P"Dp^A47>WSn:Wn].gs/+"v:q_Q*An~zF*g-@j@jtSTv5H/,S-3:R?r9R}.' +fenix: + print-banner: false diff --git a/service-web/client/src/pages/ai/feedback/Feedback.tsx b/service-web/client/src/pages/ai/feedback/Feedback.tsx index d4e4976..85804c3 100644 --- a/service-web/client/src/pages/ai/feedback/Feedback.tsx +++ b/service-web/client/src/pages/ai/feedback/Feedback.tsx @@ -1,6 +1,13 @@ import React from 'react' import styled from 'styled-components' -import {amisRender, commonInfo, crudCommonOptions, mappingField, mappingItem} from '../../../util/amis.tsx' +import { + amisRender, + commonInfo, + crudCommonOptions, + mappingField, + mappingItem, + pictureFromIds +} from '../../../util/amis.tsx' const FeedbackDiv = styled.div` .feedback-list-images { @@ -29,7 +36,16 @@ const Feedback: React.FC = () => { body: [ { type: 'crud', - api: `${commonInfo.baseAiUrl}/feedback/list`, + api: { + method: 'post', + url: `${commonInfo.baseAiUrl}/feedback/list`, + data: { + page: { + index: '${page}', + size: '${perPage}', + } + } + }, ...crudCommonOptions(), headerToolbar: [ 'reload', @@ -46,7 +62,7 @@ const Feedback: React.FC = () => { body: { debug: commonInfo.debug, type: 'form', - api: `${commonInfo.baseAiUrl}/feedback/add`, + api: `${commonInfo.baseAiUrl}/feedback/save`, body: [ { type: 'editor', @@ -100,7 +116,7 @@ const Feedback: React.FC = () => { type: 'images', enlargeAble: true, enlargeWithGallary: true, - source: '${pictures}', + source: pictureFromIds('pictures'), showToolbar: true, }, ], @@ -123,13 +139,7 @@ const Feedback: React.FC = () => { level: 'link', size: 'sm', actionType: 'ajax', - api: { - method: 'get', - url: `${commonInfo.baseAiUrl}/feedback/reanalysis`, - data: { - id: '${id}', - }, - }, + api: `get:${commonInfo.baseAiUrl}/feedback/reanalysis/\${id}`, confirmText: '确认执行重新分析?', confirmTitle: '重新分析', }, @@ -186,6 +196,7 @@ const Feedback: React.FC = () => { enlargeAble: true, enlargeWithGallary: true, showToolbar: true, + source: pictureFromIds('pictures'), }, }, { @@ -221,13 +232,7 @@ const Feedback: React.FC = () => { level: 'link', size: 'sm', actionType: 'ajax', - api: { - method: 'get', - url: `${commonInfo.baseAiUrl}/feedback/delete`, - data: { - id: '${id}', - }, - }, + api: `get:${commonInfo.baseAiUrl}/feedback/remove/\${id}`, confirmTitle: '删除', confirmText: '删除后将无法恢复,确认删除?', }, diff --git a/service-web/client/src/util/amis.tsx b/service-web/client/src/util/amis.tsx index b133a22..9c4b3b9 100644 --- a/service-web/client/src/util/amis.tsx +++ b/service-web/client/src/util/amis.tsx @@ -10,8 +10,8 @@ import {isEqual} from 'licia' export const commonInfo = { debug: isEqual(import.meta.env.MODE, 'development'), baseUrl: 'http://132.126.207.130:35690/hudi_services/service_web', - baseAiUrl: 'http://132.126.207.130:35690/hudi_services/service_ai_web', - // baseAiUrl: 'http://localhost:8080', + // baseAiUrl: 'http://132.126.207.130:35690/hudi_services/service_ai_web', + baseAiUrl: 'http://localhost:8080', authorizationHeaders: { 'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==', 'Content-Type': 'application/json', @@ -2502,4 +2502,8 @@ export function time(field: string) { type: 'tpl', tpl: `\${IF(${field}, DATETOSTR(${field}, 'YYYY-MM-DD HH:mm:ss'), undefined)}`, } +} + +export function pictureFromIds(field: string) { + return `\${ARRAYMAP(${field},id => '${commonInfo.baseAiUrl}/upload/download/' + id)}` } \ No newline at end of file