commit
2adde55e08
26 changed files with 748 additions and 0 deletions
@ -0,0 +1,10 @@ |
|||||||
|
# 默认忽略的文件 |
||||||
|
/shelf/ |
||||||
|
/workspace.xml |
||||||
|
# 已忽略包含查询文件的默认文件夹 |
||||||
|
/queries/ |
||||||
|
# Datasource local storage ignored files |
||||||
|
/dataSources/ |
||||||
|
/dataSources.local.xml |
||||||
|
# 基于编辑器的 HTTP 客户端请求 |
||||||
|
/httpRequests/ |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="CompilerConfiguration"> |
||||||
|
<annotationProcessing> |
||||||
|
<profile default="true" name="Default" enabled="true" /> |
||||||
|
<profile name="Maven default annotation processors profile" enabled="true"> |
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" /> |
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> |
||||||
|
<outputRelativeToContentRoot value="true" /> |
||||||
|
<module name="ai-native-backend" /> |
||||||
|
</profile> |
||||||
|
</annotationProcessing> |
||||||
|
</component> |
||||||
|
<component name="JavacSettings"> |
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE"> |
||||||
|
<module name="ai-native-backend" options="-parameters" /> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="Encoding"> |
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="RemoteRepositoriesConfiguration"> |
||||||
|
<remote-repository> |
||||||
|
<option name="id" value="central" /> |
||||||
|
<option name="name" value="Maven Central repository" /> |
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" /> |
||||||
|
</remote-repository> |
||||||
|
<remote-repository> |
||||||
|
<option name="id" value="jboss.community" /> |
||||||
|
<option name="name" value="JBoss Community repository" /> |
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> |
||||||
|
</remote-repository> |
||||||
|
<remote-repository> |
||||||
|
<option name="id" value="central" /> |
||||||
|
<option name="name" value="Central Repository" /> |
||||||
|
<option name="url" value="https://maven.aliyun.com/repository/public" /> |
||||||
|
</remote-repository> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" /> |
||||||
|
<component name="MavenProjectsManager"> |
||||||
|
<option name="originalFiles"> |
||||||
|
<list> |
||||||
|
<option value="$PROJECT_DIR$/pom.xml" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" /> |
||||||
|
</project> |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
<parent> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||||
|
<version>3.2.4</version> <!-- using stable 3.2.x version --> |
||||||
|
<relativePath/> <!-- lookup parent from repository --> |
||||||
|
</parent> |
||||||
|
<groupId>com.example</groupId> |
||||||
|
<artifactId>ai-native-backend</artifactId> |
||||||
|
<version>0.0.1-SNAPSHOT</version> |
||||||
|
<name>ai-native-backend</name> |
||||||
|
<description>AI-Native CRM Backend</description> |
||||||
|
<properties> |
||||||
|
<java.version>21</java.version> |
||||||
|
<langchain4j.version>0.30.0</langchain4j.version> |
||||||
|
</properties> |
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.postgresql</groupId> |
||||||
|
<artifactId>postgresql</artifactId> |
||||||
|
<scope>runtime</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.projectlombok</groupId> |
||||||
|
<artifactId>lombok</artifactId> |
||||||
|
<optional>true</optional> |
||||||
|
</dependency> |
||||||
|
<!-- LangChain4j for AI communication --> |
||||||
|
<dependency> |
||||||
|
<groupId>dev.langchain4j</groupId> |
||||||
|
<artifactId>langchain4j-spring-boot-starter</artifactId> |
||||||
|
<version>${langchain4j.version}</version> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>dev.langchain4j</groupId> |
||||||
|
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> |
||||||
|
<version>${langchain4j.version}</version> |
||||||
|
</dependency> |
||||||
|
<!-- JSON type mapping for Hibernate --> |
||||||
|
<dependency> |
||||||
|
<groupId>io.hypersistence</groupId> |
||||||
|
<artifactId>hypersistence-utils-hibernate-63</artifactId> |
||||||
|
<version>3.7.3</version> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-test</artifactId> |
||||||
|
<scope>test</scope> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
<build> |
||||||
|
<plugins> |
||||||
|
<plugin> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||||
|
<configuration> |
||||||
|
<excludes> |
||||||
|
<exclude> |
||||||
|
<groupId>org.projectlombok</groupId> |
||||||
|
<artifactId>lombok</artifactId> |
||||||
|
</exclude> |
||||||
|
</excludes> |
||||||
|
</configuration> |
||||||
|
</plugin> |
||||||
|
</plugins> |
||||||
|
</build> |
||||||
|
</project> |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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 结构是否合规。"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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<ChatMessage> getMessages(Object memoryId) { |
||||||
|
String sessionId = (String) memoryId; |
||||||
|
List<ChatMemoryEntity> entities = repository.findByChatSessionIdOrderByIdAsc(sessionId); |
||||||
|
return entities.stream() |
||||||
|
.map(entity -> ChatMessageDeserializer.messageFromJson(entity.getContent())) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@Transactional |
||||||
|
public void updateMessages(Object memoryId, List<ChatMessage> messages) { |
||||||
|
String sessionId = (String) memoryId; |
||||||
|
repository.deleteByChatSessionId(sessionId); |
||||||
|
|
||||||
|
List<ChatMemoryEntity> 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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
package com.example.ainative.config; |
||||||
|
|
||||||
|
public class UserContextHolder { |
||||||
|
private static final ThreadLocal<String> 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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -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("/**"); |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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<SysDynamicData> getRecords(@PathVariable UUID entityId) { |
||||||
|
return dataRepository.findByEntityId(entityId); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据模块 code 获取数据列表(前端动态表格主要用此接口方便查询) |
||||||
|
*/ |
||||||
|
@GetMapping("/code/{entityCode}") |
||||||
|
public List<SysDynamicData> 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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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<SysEntity> getAllEntities() { |
||||||
|
return sysEntityRepository.findAll(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据 code 获取单一实体定义,方便前台表格按需渲染列等动态行为。 |
||||||
|
*/ |
||||||
|
@GetMapping("/code/{entityCode}") |
||||||
|
public SysEntity getEntityByCode(@PathVariable String entityCode) { |
||||||
|
return sysEntityRepository.findByEntityCode(entityCode); |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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<ChatMemoryEntity, Long> { |
||||||
|
List<ChatMemoryEntity> findByChatSessionIdOrderByIdAsc(String chatSessionId); |
||||||
|
void deleteByChatSessionId(String chatSessionId); |
||||||
|
} |
||||||
@ -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<SysDynamicData, UUID> { |
||||||
|
List<SysDynamicData> findByEntityId(UUID entityId); |
||||||
|
} |
||||||
@ -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, UUID> { |
||||||
|
SysEntity findByEntityCode(String entityCode); |
||||||
|
} |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
Loading…
Reference in new issue