feat: Implement the initial AI-native CRM backend application with Spring Boot, LangChain4j, and JPA persistence.
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
Generated
+19
@@ -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>
|
||||
Generated
+6
@@ -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>
|
||||
Generated
+20
@@ -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>
|
||||
Generated
+12
@@ -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
|
||||
Reference in New Issue
Block a user