From 2adde55e082ce505bcc7ac802957faade164c90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B0=E7=81=B0?= <654612@qq.com> Date: Tue, 24 Mar 2026 19:41:49 +0800 Subject: [PATCH] feat: Implement the initial AI-native CRM backend application with Spring Boot, LangChain4j, and JPA persistence. --- .idea/.gitignore | 10 ++ .idea/compiler.xml | 19 +++ .idea/encodings.xml | 6 + .idea/jarRepositories.xml | 20 +++ .idea/misc.xml | 12 ++ pom.xml | 78 ++++++++++++ .../example/ainative/AiNativeApplication.java | 13 ++ .../com/example/ainative/ai/CrmAgent.java | 21 ++++ .../com/example/ainative/ai/CrmTools.java | 54 ++++++++ .../ai/PersistentChatMemoryStore.java | 52 ++++++++ .../com/example/ainative/config/AiConfig.java | 20 +++ .../ainative/config/UserContextHolder.java | 17 +++ .../ainative/config/UserInterceptor.java | 25 ++++ .../example/ainative/config/WebMvcConfig.java | 18 +++ .../ainative/controller/AgentController.java | 32 +++++ .../controller/DynamicDataController.java | 116 ++++++++++++++++++ .../controller/SysEntityController.java | 33 +++++ .../ainative/entity/ChatMemoryEntity.java | 26 ++++ .../ainative/entity/SysDynamicData.java | 33 +++++ .../example/ainative/entity/SysEntity.java | 38 ++++++ .../repository/ChatMemoryRepository.java | 11 ++ .../repository/SysDynamicDataRepository.java | 11 ++ .../repository/SysEntityRepository.java | 10 ++ src/main/resources/application.yml | 28 +++++ target/classes/application.yml | 28 +++++ .../compile/default-compile/inputFiles.lst | 17 +++ 26 files changed, 748 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/example/ainative/AiNativeApplication.java create mode 100644 src/main/java/com/example/ainative/ai/CrmAgent.java create mode 100644 src/main/java/com/example/ainative/ai/CrmTools.java create mode 100644 src/main/java/com/example/ainative/ai/PersistentChatMemoryStore.java create mode 100644 src/main/java/com/example/ainative/config/AiConfig.java create mode 100644 src/main/java/com/example/ainative/config/UserContextHolder.java create mode 100644 src/main/java/com/example/ainative/config/UserInterceptor.java create mode 100644 src/main/java/com/example/ainative/config/WebMvcConfig.java create mode 100644 src/main/java/com/example/ainative/controller/AgentController.java create mode 100644 src/main/java/com/example/ainative/controller/DynamicDataController.java create mode 100644 src/main/java/com/example/ainative/controller/SysEntityController.java create mode 100644 src/main/java/com/example/ainative/entity/ChatMemoryEntity.java create mode 100644 src/main/java/com/example/ainative/entity/SysDynamicData.java create mode 100644 src/main/java/com/example/ainative/entity/SysEntity.java create mode 100644 src/main/java/com/example/ainative/repository/ChatMemoryRepository.java create mode 100644 src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java create mode 100644 src/main/java/com/example/ainative/repository/SysEntityRepository.java create mode 100644 src/main/resources/application.yml create mode 100644 target/classes/application.yml create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b6b1ecf --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..ec8fe1b --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..63e9001 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..abb532a --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6546f14 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b59ddcb --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.4 + + + com.example + ai-native-backend + 0.0.1-SNAPSHOT + ai-native-backend + AI-Native CRM Backend + + 21 + 0.30.0 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + + dev.langchain4j + langchain4j-spring-boot-starter + ${langchain4j.version} + + + dev.langchain4j + langchain4j-open-ai-spring-boot-starter + ${langchain4j.version} + + + + io.hypersistence + hypersistence-utils-hibernate-63 + 3.7.3 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/src/main/java/com/example/ainative/AiNativeApplication.java b/src/main/java/com/example/ainative/AiNativeApplication.java new file mode 100644 index 0000000..66b27a2 --- /dev/null +++ b/src/main/java/com/example/ainative/AiNativeApplication.java @@ -0,0 +1,13 @@ +package com.example.ainative; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AiNativeApplication { + + public static void main(String[] args) { + SpringApplication.run(AiNativeApplication.class, args); + } + +} diff --git a/src/main/java/com/example/ainative/ai/CrmAgent.java b/src/main/java/com/example/ainative/ai/CrmAgent.java new file mode 100644 index 0000000..4d6089d --- /dev/null +++ b/src/main/java/com/example/ainative/ai/CrmAgent.java @@ -0,0 +1,21 @@ +package com.example.ainative.ai; + +import dev.langchain4j.service.MemoryId; +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.V; +import dev.langchain4j.service.spring.AiService; + +@AiService +public interface CrmAgent { + + @SystemMessage(""" + You are a smart AI assistant embedded within a modern enterprise CRM system. + Your goal is to help the user configure business modules, dynamic forms, and insert data. + You have tools to define database schema structures (JSON Schema) and manipulate dynamic data. + Always respond concisely. If you need to manipulate the UI, explain what you are doing. + + Current User/Tenant context ID: {{userContext}} + """) + String chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("userContext") String userContext); +} diff --git a/src/main/java/com/example/ainative/ai/CrmTools.java b/src/main/java/com/example/ainative/ai/CrmTools.java new file mode 100644 index 0000000..523a815 --- /dev/null +++ b/src/main/java/com/example/ainative/ai/CrmTools.java @@ -0,0 +1,54 @@ +package com.example.ainative.ai; + +import com.example.ainative.entity.SysEntity; +import com.example.ainative.repository.SysEntityRepository; +import dev.langchain4j.agent.tool.Tool; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CrmTools { + + private final SysEntityRepository sysEntityRepository; + + /** + * AI 工具:用于定义新的动态业务对象(Entity)。 + * 当用户试图创建新表单、新模块(比如“线索管理”、“合同管理”)时,大模型会自动决定调用本工具, + * 并将它根据系统指令生成的参数传入进来。 + * + * @param entityCode 模块唯一标识符,英文,比如 crm_lead (便于后期通过接口精确操作某模块) + * @param entityName 模块中文名称跨,比如 销售线索 + * @param jsonSchema 这里是大模型输出的最核心数据:一个合规的 JSON Schema (如属性定义、校验规则),由前端随后解析渲染 + * @return 返回一段让 AI 知道是否成功的指令结果,这个结果大模型能看到并进一步回答用户。 + */ + @Tool("创建一个新的业务表单模块,当你确定了用户创建新功能的意图后调用。参数包括:entityCode (英文唯一标识), entityName (中文名称), jsonSchema。") + public String defineDynamicModule(String entityCode, String entityName, String jsonSchema) { + log.info("Agent 正在调用工具执行创建新模块: Code={}, Name={}", entityCode, entityName); + + // 1. 业务校验:防重和容错 + SysEntity existing = sysEntityRepository.findByEntityCode(entityCode); + if (existing != null) { + return "模块代码 [" + entityCode + "] 已存在,创建失败,请让用户考虑使用其他代码名称。"; + } + + // 2. 实体赋值与入库 + try { + SysEntity entity = new SysEntity(); + entity.setEntityCode(entityCode); + entity.setEntityName(entityName); + // 此处直接存储大模型生成的 JSON 字符串(PostgreSQL 的 Hibernate 驱动会将其封装为 JSONB) + entity.setSchemaDefinition(jsonSchema); + + SysEntity saved = sysEntityRepository.save(entity); + return "核心模块 [" + entityName + "] 已成功持久化并部署到系统中。后台对应的 Entity ID (UUID) 为: " + saved.getId(); + } catch (Exception e) { + log.error("保存 Schema 时出现异常", e); + return "数据库保存异常,请检查 JSON 结构是否合规。"; + } + } +} diff --git a/src/main/java/com/example/ainative/ai/PersistentChatMemoryStore.java b/src/main/java/com/example/ainative/ai/PersistentChatMemoryStore.java new file mode 100644 index 0000000..a44cfe6 --- /dev/null +++ b/src/main/java/com/example/ainative/ai/PersistentChatMemoryStore.java @@ -0,0 +1,52 @@ +package com.example.ainative.ai; + +import com.example.ainative.entity.ChatMemoryEntity; +import com.example.ainative.repository.ChatMemoryRepository; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.ChatMessageDeserializer; +import dev.langchain4j.data.message.ChatMessageSerializer; +import dev.langchain4j.store.memory.chat.ChatMemoryStore; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class PersistentChatMemoryStore implements ChatMemoryStore { + + private final ChatMemoryRepository repository; + + @Override + public List getMessages(Object memoryId) { + String sessionId = (String) memoryId; + List entities = repository.findByChatSessionIdOrderByIdAsc(sessionId); + return entities.stream() + .map(entity -> ChatMessageDeserializer.messageFromJson(entity.getContent())) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public void updateMessages(Object memoryId, List messages) { + String sessionId = (String) memoryId; + repository.deleteByChatSessionId(sessionId); + + List entities = messages.stream().map(message -> { + ChatMemoryEntity entity = new ChatMemoryEntity(); + entity.setChatSessionId(sessionId); + entity.setContent(ChatMessageSerializer.messageToJson(message)); + return entity; + }).collect(Collectors.toList()); + + repository.saveAll(entities); + } + + @Override + @Transactional + public void deleteMessages(Object memoryId) { + repository.deleteByChatSessionId((String) memoryId); + } +} diff --git a/src/main/java/com/example/ainative/config/AiConfig.java b/src/main/java/com/example/ainative/config/AiConfig.java new file mode 100644 index 0000000..06ac26a --- /dev/null +++ b/src/main/java/com/example/ainative/config/AiConfig.java @@ -0,0 +1,20 @@ +package com.example.ainative.config; + +import dev.langchain4j.memory.chat.ChatMemoryProvider; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.store.memory.chat.ChatMemoryStore; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AiConfig { + + @Bean + public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore chatMemoryStore) { + return memoryId -> MessageWindowChatMemory.builder() + .id(memoryId) + .maxMessages(50) // Keep last 50 messages in context + .chatMemoryStore(chatMemoryStore) + .build(); + } +} diff --git a/src/main/java/com/example/ainative/config/UserContextHolder.java b/src/main/java/com/example/ainative/config/UserContextHolder.java new file mode 100644 index 0000000..5e592bd --- /dev/null +++ b/src/main/java/com/example/ainative/config/UserContextHolder.java @@ -0,0 +1,17 @@ +package com.example.ainative.config; + +public class UserContextHolder { + private static final ThreadLocal currentUser = new ThreadLocal<>(); + + public static void setCurrentUser(String userId) { + currentUser.set(userId); + } + + public static String getCurrentUser() { + return currentUser.get() != null ? currentUser.get() : "anonymous"; + } + + public static void clear() { + currentUser.remove(); + } +} diff --git a/src/main/java/com/example/ainative/config/UserInterceptor.java b/src/main/java/com/example/ainative/config/UserInterceptor.java new file mode 100644 index 0000000..52dfa70 --- /dev/null +++ b/src/main/java/com/example/ainative/config/UserInterceptor.java @@ -0,0 +1,25 @@ +package com.example.ainative.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class UserInterceptor implements HandlerInterceptor { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String userId = request.getHeader("X-Creator-Id"); + if (userId != null && !userId.isBlank()) { + UserContextHolder.setCurrentUser(userId); + } else { + UserContextHolder.setCurrentUser("anonymous"); + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + UserContextHolder.clear(); + } +} diff --git a/src/main/java/com/example/ainative/config/WebMvcConfig.java b/src/main/java/com/example/ainative/config/WebMvcConfig.java new file mode 100644 index 0000000..598d136 --- /dev/null +++ b/src/main/java/com/example/ainative/config/WebMvcConfig.java @@ -0,0 +1,18 @@ +package com.example.ainative.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebMvcConfig implements WebMvcConfigurer { + + private final UserInterceptor userInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(userInterceptor).addPathPatterns("/**"); + } +} diff --git a/src/main/java/com/example/ainative/controller/AgentController.java b/src/main/java/com/example/ainative/controller/AgentController.java new file mode 100644 index 0000000..81e1bc4 --- /dev/null +++ b/src/main/java/com/example/ainative/controller/AgentController.java @@ -0,0 +1,32 @@ +package com.example.ainative.controller; + +import com.example.ainative.ai.CrmAgent; +import com.example.ainative.config.UserContextHolder; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/agent") +@RequiredArgsConstructor +public class AgentController { + + private final CrmAgent crmAgent; + + /** + * 对话交互核心入口 + * 这里提供了一个传统的 REST 包装版本。当前端(如 CopilotKit 或测试客户端)发送自然语言时,会调用此处。 + * + * @param sessionId 唯一会话识别(与底层的 ChatMemory 持久化紧密对应,确保 AI 能“记住”对话) + * @param message 用户的原始需求 (比如:"帮我建一个客户关系系统的线索录入表,有姓名和手机号") + * @return AI 所思所想或执行完 @Tool 后的结果字符串反馈 + */ + @PostMapping("/chat") + public String chat(@RequestParam String sessionId, @RequestBody String message) { + // 先从拦截器上下文中拿到刚才设置的调用者身份 + String currentUser = UserContextHolder.getCurrentUser(); + + // 传递给底层的 Agent:这背后的复杂工作流完全交由 Langchain4J 托管! + // 如果用户的 message 意图命中“建表”,框架会在返回前自己拦截调去跑 CrmTools 里的 defineDynamicModule。 + return crmAgent.chat(sessionId, message, currentUser); + } +} diff --git a/src/main/java/com/example/ainative/controller/DynamicDataController.java b/src/main/java/com/example/ainative/controller/DynamicDataController.java new file mode 100644 index 0000000..1404997 --- /dev/null +++ b/src/main/java/com/example/ainative/controller/DynamicDataController.java @@ -0,0 +1,116 @@ +package com.example.ainative.controller; + +import com.example.ainative.config.UserContextHolder; +import com.example.ainative.entity.SysDynamicData; +import com.example.ainative.entity.SysEntity; +import com.example.ainative.repository.SysDynamicDataRepository; +import com.example.ainative.repository.SysEntityRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +/** + * 动态数据交互控制器 + * 负责接收 AI 生成的 UI 提交过来的所有结构不固定的 JSON 数据。 + */ +@RestController +@RequestMapping("/api/dynamic") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") // 允许前端 Next.js (通常是 3000 端口) 进行跨域调用 +public class DynamicDataController { + + private final SysDynamicDataRepository dataRepository; + private final SysEntityRepository entityRepository; + + /** + * 根据模块 UUID 获取数据列表 + */ + @GetMapping("/{entityId}") + public List getRecords(@PathVariable UUID entityId) { + return dataRepository.findByEntityId(entityId); + } + + /** + * 根据模块 code 获取数据列表(前端动态表格主要用此接口方便查询) + */ + @GetMapping("/code/{entityCode}") + public List getRecordsByCode(@PathVariable String entityCode) { + SysEntity entity = entityRepository.findByEntityCode(entityCode); + if (entity != null) { + return dataRepository.findByEntityId(entity.getId()); + } + return List.of(); + } + + /** + * 自动注册/获取动态模块接口:解决前端渲染表单和后端实体记录未创建的时序问题 + * 允许前端在提交实际数据前,主动将推断出的模块结构注册到数据库。 + */ + @PostMapping("/schema") + public SysEntity defineSchema(@RequestBody SysEntity requestedEntity) { + SysEntity entity = entityRepository.findByEntityCode(requestedEntity.getEntityCode()); + if (entity == null) { + entity = new SysEntity(); + entity.setEntityCode(requestedEntity.getEntityCode()); + entity.setEntityName(requestedEntity.getEntityName()); + entity.setSchemaDefinition(requestedEntity.getSchemaDefinition()); + return entityRepository.save(entity); + } + return entity; + } + + /** + * 核心接口:按 entityCode 直接入库 + * 前端不需要知道复杂的 UUID,只需根据 AI 生成的 crm_lead 等 Code 即可提交。 + */ + @PostMapping("/code/{entityCode}") + public SysDynamicData createRecordByCode(@PathVariable String entityCode, @RequestBody String jsonData) { + // 1. 先通过 Code 找到模块定义的 ID + SysEntity entity = entityRepository.findByEntityCode(entityCode); + if (entity == null) { + throw new RuntimeException("模块 " + entityCode + " 尚未定义,无法入库。请先让 AI 创建该模块。"); + } + + // 2. 构造动态数据记录 + SysDynamicData record = new SysDynamicData(); + record.setEntityId(entity.getId()); + record.setData(jsonData); // 这里的 String 在 JSONB 映射下会自动存为二进制 JSON 格式 + record.setCreatorId(UserContextHolder.getCurrentUser()); + + return dataRepository.save(record); + } + + /** + * 原始的 UUID 入库接口 (保留备用) + */ + @PostMapping("/{entityId}") + public SysDynamicData createRecord(@PathVariable UUID entityId, @RequestBody String jsonData) { + SysDynamicData record = new SysDynamicData(); + record.setEntityId(entityId); + record.setData(jsonData); + record.setCreatorId(UserContextHolder.getCurrentUser()); + + return dataRepository.save(record); + } + + /** + * 修改现有记录 + */ + @PutMapping("/{id}") + public SysDynamicData updateRecord(@PathVariable UUID id, @RequestBody String jsonData) { + return dataRepository.findById(id).map(record -> { + record.setData(jsonData); + return dataRepository.save(record); + }).orElseThrow(() -> new RuntimeException("Record not found")); + } + + /** + * 删除现有记录 + */ + @DeleteMapping("/{id}") + public void deleteRecord(@PathVariable UUID id) { + dataRepository.deleteById(id); + } +} diff --git a/src/main/java/com/example/ainative/controller/SysEntityController.java b/src/main/java/com/example/ainative/controller/SysEntityController.java new file mode 100644 index 0000000..4ddbd11 --- /dev/null +++ b/src/main/java/com/example/ainative/controller/SysEntityController.java @@ -0,0 +1,33 @@ +package com.example.ainative.controller; + +import com.example.ainative.entity.SysEntity; +import com.example.ainative.repository.SysEntityRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/entities") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") // Allow frontend to fetch menus +public class SysEntityController { + + private final SysEntityRepository sysEntityRepository; + + /** + * 获取所有已定义的模块列表,供左侧菜单动态渲染使用。 + */ + @GetMapping + public List getAllEntities() { + return sysEntityRepository.findAll(); + } + + /** + * 根据 code 获取单一实体定义,方便前台表格按需渲染列等动态行为。 + */ + @GetMapping("/code/{entityCode}") + public SysEntity getEntityByCode(@PathVariable String entityCode) { + return sysEntityRepository.findByEntityCode(entityCode); + } +} diff --git a/src/main/java/com/example/ainative/entity/ChatMemoryEntity.java b/src/main/java/com/example/ainative/entity/ChatMemoryEntity.java new file mode 100644 index 0000000..8e65a3d --- /dev/null +++ b/src/main/java/com/example/ainative/entity/ChatMemoryEntity.java @@ -0,0 +1,26 @@ +package com.example.ainative.entity; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.ZonedDateTime; + +@Data +@Entity +@Table(name = "chat_memory") +public class ChatMemoryEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "chat_session_id", nullable = false) + private String chatSessionId; + + @Column(name = "content", nullable = false, columnDefinition = "TEXT") + private String content; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private ZonedDateTime createdAt; +} diff --git a/src/main/java/com/example/ainative/entity/SysDynamicData.java b/src/main/java/com/example/ainative/entity/SysDynamicData.java new file mode 100644 index 0000000..314502e --- /dev/null +++ b/src/main/java/com/example/ainative/entity/SysDynamicData.java @@ -0,0 +1,33 @@ +package com.example.ainative.entity; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.time.ZonedDateTime; +import java.util.UUID; + +@Data +@Entity +@Table(name = "sys_dynamic_data") +public class SysDynamicData { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "entity_id") + private UUID entityId; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "data", columnDefinition = "jsonb", nullable = false) + private String data; + + @Column(name = "creator_id", length = 50) + private String creatorId; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private ZonedDateTime createdAt; +} diff --git a/src/main/java/com/example/ainative/entity/SysEntity.java b/src/main/java/com/example/ainative/entity/SysEntity.java new file mode 100644 index 0000000..25264e9 --- /dev/null +++ b/src/main/java/com/example/ainative/entity/SysEntity.java @@ -0,0 +1,38 @@ +package com.example.ainative.entity; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.UpdateTimestamp; +import org.hibernate.type.SqlTypes; + +import java.time.ZonedDateTime; +import java.util.UUID; + +@Data +@Entity +@Table(name = "sys_entities") +public class SysEntity { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "entity_code", unique = true, nullable = false, length = 50) + private String entityCode; + + @Column(name = "entity_name", nullable = false, length = 100) + private String entityName; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "schema_definition", columnDefinition = "jsonb", nullable = false) + private String schemaDefinition; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private ZonedDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private ZonedDateTime updatedAt; +} diff --git a/src/main/java/com/example/ainative/repository/ChatMemoryRepository.java b/src/main/java/com/example/ainative/repository/ChatMemoryRepository.java new file mode 100644 index 0000000..546aa04 --- /dev/null +++ b/src/main/java/com/example/ainative/repository/ChatMemoryRepository.java @@ -0,0 +1,11 @@ +package com.example.ainative.repository; + +import com.example.ainative.entity.ChatMemoryEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ChatMemoryRepository extends JpaRepository { + List findByChatSessionIdOrderByIdAsc(String chatSessionId); + void deleteByChatSessionId(String chatSessionId); +} diff --git a/src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java b/src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java new file mode 100644 index 0000000..8973ea7 --- /dev/null +++ b/src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java @@ -0,0 +1,11 @@ +package com.example.ainative.repository; + +import com.example.ainative.entity.SysDynamicData; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface SysDynamicDataRepository extends JpaRepository { + List findByEntityId(UUID entityId); +} diff --git a/src/main/java/com/example/ainative/repository/SysEntityRepository.java b/src/main/java/com/example/ainative/repository/SysEntityRepository.java new file mode 100644 index 0000000..0c63d42 --- /dev/null +++ b/src/main/java/com/example/ainative/repository/SysEntityRepository.java @@ -0,0 +1,10 @@ +package com.example.ainative.repository; + +import com.example.ainative.entity.SysEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface SysEntityRepository extends JpaRepository { + SysEntity findByEntityCode(String entityCode); +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..aea9df9 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,28 @@ +spring: + application: + name: ai-native-backend + datasource: + url: jdbc:postgresql://localhost:5432/ai_native_db + username: ai_agent_user + password: Agent@2026 + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.PostgreSQLDialect + +langchain4j: + open-ai: + chat-model: + api-key: ${ZHIPU_API_KEY:61833436cbd642ed844d0128a99b2e89.PTbpERpysO3qSf8w} + base-url: https://open.bigmodel.cn/api/paas/v4/ + model-name: glm-4-plus + log-requests: true + log-responses: true + +server: + port: 8080 diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..aea9df9 --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,28 @@ +spring: + application: + name: ai-native-backend + datasource: + url: jdbc:postgresql://localhost:5432/ai_native_db + username: ai_agent_user + password: Agent@2026 + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.PostgreSQLDialect + +langchain4j: + open-ai: + chat-model: + api-key: ${ZHIPU_API_KEY:61833436cbd642ed844d0128a99b2e89.PTbpERpysO3qSf8w} + base-url: https://open.bigmodel.cn/api/paas/v4/ + model-name: glm-4-plus + log-requests: true + log-responses: true + +server: + port: 8080 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..2bbb204 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,17 @@ +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/entity/SysDynamicData.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/controller/AgentController.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/config/UserContextHolder.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/controller/DynamicDataController.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/ai/PersistentChatMemoryStore.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/entity/SysEntity.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/ai/CrmTools.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/entity/ChatMemoryEntity.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/controller/SysEntityController.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/AiNativeApplication.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/config/WebMvcConfig.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/config/UserInterceptor.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/repository/ChatMemoryRepository.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/repository/SysEntityRepository.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/ai/CrmAgent.java +/Users/hui/project/ai/backend/src/main/java/com/example/ainative/config/AiConfig.java