Browse Source

feat: Implement initial AI-native CRM backend with dynamic entity and data management, AI agent tools, and core configurations.

main
Boom 1 week ago
parent
commit
170b7945d7
  1. 19
      .gitignore
  2. 2
      .idea/misc.xml
  3. 6
      .idea/vcs.xml
  4. 56
      doc/AGENT_HANDOFF.md
  5. 49
      doc/Instruction.md
  6. 6
      pom.xml
  7. 29
      src/main/java/com/example/ainative/ai/CrmAgent.java
  8. 101
      src/main/java/com/example/ainative/ai/CrmTools.java
  9. 6
      src/main/java/com/example/ainative/controller/AgentController.java
  10. 82
      src/main/java/com/example/ainative/controller/DynamicDataController.java
  11. 11
      src/main/java/com/example/ainative/controller/SysEntityController.java
  12. 3
      src/main/java/com/example/ainative/entity/SysEntity.java
  13. 1
      src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java
  14. 3
      src/main/java/com/example/ainative/repository/SysEntityRepository.java
  15. 7
      src/main/resources/application.yml
  16. 7
      target/classes/application.yml

19
.gitignore vendored

@ -0,0 +1,19 @@
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.restore
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
.idea/
*.iml
*.iws
.idea_modules/
# OS
.DS_Store
Thumbs.db

2
.idea/misc.xml

@ -8,5 +8,5 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK" />
</project> </project>

6
.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

56
doc/AGENT_HANDOFF.md

@ -0,0 +1,56 @@
# AI-Native CRM: Agent Handoff Guide
> [!IMPORTANT]
> This document is for the AI Agent to quickly understand the project context, architecture, and current status during session resume.
## 🎯 Project Vision
An **AI-Native CRM** where business modules and data are managed through AI-driven messages and tools, rather than traditional REST parameters.
---
## 🏗 Architecture Overview
### Backend (`/backend`)
* **Env**: JDK 19 + Spring Boot 3.2.4 + PostgreSQL.
* **Key Logic**:
* `CrmTools.java`: **The Message Hub**. Contains `@Tool` methods for **Define, Save, and Delete**. Directly interacts with Repositories.
* `AgentController.java`: **Primary Entry Point**. Handles natural language commands or "Business Fact Reports".
* **AI-Only Policy**: Create/Update/Delete endpoints have been removed from traditional controllers to force AI-driven flows.
### Frontend (`/frontend`)
* **Env**: Next.js 15 (App Router).
* **Components**:
* `DynamicForm.tsx`: Shared component supporting **Entity Associations (`x-link-entity`)**, Enums, and Format constraints.
* `EntityDataPage` (`/data/[entityCode]`): Full CRUD UI. Actions (Edit/Delete) route through AI messages.
---
## 🛠 Key File Responsibilities
| File | Role |
| :--- | :--- |
| `backend/.../CrmTools.java` | **Source of Truth** for all data-changing tools. |
| `frontend/src/components/DynamicForm.tsx` | Reusable Form Renderer (Schema + AI messaging). |
| `frontend/src/app/data/[entityCode]/page.tsx` | Dynamic List View with Agent-driven Actions. |
---
## 🔄 Core Conventions
1. **AI-First Mutation**: DO NOT create manual REST services for data mutation. Use the AI Tool interface.
2. **Schema Extensions**:
* Associations: `x-link-entity: 'module_code'`.
* Constraints: `enum`, `pattern`, `format: 'date'`.
3. **Fact-Based Messaging**: Frontend should report business facts (e.g., "User filled form X") and let the AI decide the tool to call.
---
## 🚀 Current Progress & Next Steps
- [x] AI-Native CRUD implemented (Agent-driven Insert/Update/Delete).
- [x] Entity Associations & Selectors supported.
- [x] Unified DynamicForm for Generation & Management.
- [ ] Next: Implement Advanced Search & Filtering via AI Query generation.
- [ ] Next: Dashboard Analytics module for dynamic data summary.
---
*Updated at: 2026-03-25. Follow this guide for AI-Native CRM development.*

49
doc/Instruction.md

@ -0,0 +1,49 @@
# AI-Native CRM 系统开发手册
## 🎯 系统定位 (Core Vision)
本系统是一款 **AI-Native (原生 AI)** 的声明式 CRM。与传统 CRM 的本质区别在于:
* **非硬编码业务逻辑**:业务模块、字段定义和 UI 呈现均由 AI 动态生成并持久化。
* **零研发排期**:业务专家通过自然语言描述需求,即可立刻生成生产可用的功能模块。
---
## 🚀 核心特性 (Key Features)
### 1. 生成式 UI (Generative UI)
* 集成 **CopilotKit**,在前端实现 AI 对应用的深度操控。
* 支持通过自然语言一键渲染(Render)基于 JSON Schema 的复杂表单。
* **UniversalModuleRenderer**:万能组件渲染引擎,适配多种动态业务场景。
### 2. 动态多租户架构
* 后端使用 `SysEntity` (元数据) 和 `SysDynamicData` (实例数据) 支撑无限扩展。
* 支持不同租户、不同模块的字段在运行时动态注入,无需繁琐的数据库迁移动作。
### 3. AI 交互中枢 (AI Hub)
* **后端驱动**:基于 **LangChain4j** 构建 Java 原生 AI 逻辑,支持 RAG 及复杂工作流。
* **会话持久化**:原生支持 AI 会话的数据库存储,重启不丢失交互上下文。
---
## 📋 技术基准 (Benchmarks & Tech Stack)
### Backend (Java Ecosystem)
* **Runner**: OpenJDK 19
* **Framework**: Spring Boot 3.2.4 (Latest Stable)
* **Persistence**: Spring Data JPA + Hibernate 6.x
* **Database**: PostgreSQL (Support JSONB for Dynamic Data)
* **AI SDK**: LangChain4j 0.30.0
* **Build Tool**: Maven
### Frontend (Modern Web)
* **Framework**: Next.js (App Router)
* **Language**: TypeScript
* **Styling**: Tailwind CSS + Shadcn UI (Visual Esthetics)
* **Copilot**: CopilotKit (React SDK + Popup UI)
* **Font**: Geist (Advanced Typography)
---
## 🛠 开发规范
1. **动态性优先**:任何新功能应考虑是否可通过配置或 AI 生成,而非硬编码 Controller。
2. **Schema 为王**:业务定义必须严格遵循 JSON Schema 标准,以保证前后端渲染的一致性。
3. **Prompt 工程**:系统 Prompt 存储于前端 `page.tsx` 或后端专用 AI 服务类中,需定期维护。

6
pom.xml

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version> <!-- using stable 3.2.x version --> <version>3.5.12</version> <!-- using stable 3.2.x version -->
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.example</groupId> <groupId>com.example</groupId>
@ -14,8 +14,8 @@
<name>ai-native-backend</name> <name>ai-native-backend</name>
<description>AI-Native CRM Backend</description> <description>AI-Native CRM Backend</description>
<properties> <properties>
<java.version>21</java.version> <java.version>19</java.version>
<langchain4j.version>0.30.0</langchain4j.version> <langchain4j.version>1.12.2-beta22</langchain4j.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>

29
src/main/java/com/example/ainative/ai/CrmAgent.java

@ -6,16 +6,33 @@ import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V; import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService; import dev.langchain4j.service.spring.AiService;
@AiService @AiService(tools = "crmTools")
public interface CrmAgent { public interface CrmAgent {
@SystemMessage(""" @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. 你是一个 AI 原生 CRM 系统的核心引擎你的任务是通过自然语言管理业务模块Schema和动态数据替代传统的 REST 接口
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}} ## 核心运行准则严禁违反
1. **执行重于对话**系统已禁用传统的增删改接口所有数据变更必须通过调用工具实现
2. **禁止虚假模拟**严禁在未成功调用工具的情况下回复任何暗示操作已完成的内容已保存已创建
3. **事实驱动**前端会报告业务事实用户提交了表单内容你必须根据事实判断该调用哪个工具insertDynamicData
## 建模协议 (defineDynamicModule)
当定义或更新模块结构时生成的 JSON Schema 需遵循
- **实体关联**若字段需关联其他实体必须在属性中加入 `"x-link-entity": "目标实体Code"`
- **约束增强**优先使用 `enum` (下拉选项)`pattern` (正则校验)`format: "date"` 等标准 JSON Schema 约束
- **命名规范**entityCode 采用下划线命名法student_course
## 数据操作协议
- **入库 (insert)**将用户提供的或事实报告中的信息提取为 JSON 字符串
- **物理删除 (delete)**仅在用户明确指令或业务流程需要时执行
## 输出风格
- 保持简洁
- 必须在工具执行成功后再简要反馈执行结果或后续 UI 操作建议
当前租户上下文 ID: {{userContext}}
""") """)
String chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("userContext") String userContext); String chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("userContext") String userContext);
} }

101
src/main/java/com/example/ainative/ai/CrmTools.java

@ -1,54 +1,99 @@
package com.example.ainative.ai; package com.example.ainative.ai;
import com.example.ainative.config.UserContextHolder;
import com.example.ainative.entity.SysDynamicData;
import com.example.ainative.entity.SysEntity; import com.example.ainative.entity.SysEntity;
import com.example.ainative.repository.SysDynamicDataRepository;
import com.example.ainative.repository.SysEntityRepository; import com.example.ainative.repository.SysEntityRepository;
import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.agent.tool.Tool;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Slf4j @Slf4j
@Component @Component("crmTools")
@RequiredArgsConstructor @RequiredArgsConstructor
public class CrmTools { public class CrmTools {
private final SysEntityRepository sysEntityRepository; private final SysEntityRepository sysEntityRepository;
private final SysDynamicDataRepository sysDynamicDataRepository;
/** @Transactional
* AI 工具用于定义新的动态业务对象Entity @Tool("核心业务处理:根据消息意图定义或执行模型变更 (Schema)。支持以下高级能力:\n" +
* 当用户试图创建新表单新模块比如线索管理合同管理大模型会自动决定调用本工具 "1. [实体关联]:若字段需要关联其他实体,需在 Schema 属性中加入 `x-link-entity: '目标实体Code'`。\n" +
* 并将它根据系统指令生成的参数传入进来 "2. [值约束]:支持标准 JSON Schema 的 `enum` (下拉选择)、`pattern` (正则校验)、`format` (如 date, email)、`minLength` 等约束。\n" +
* "3. [示例]:商品关联分类,分类字段可定义为 { 'type': 'string', 'x-link-entity': 'category' }")
* @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) { public String defineDynamicModule(String entityCode, String entityName, String jsonSchema) {
log.info("Agent 正在调用工具执行创建新模块: Code={}, Name={}", entityCode, entityName); log.info("Agent Tool 正在处理消息:定义模块: Code={}, Name={}", entityCode, entityName);
// 1. 业务校验:防重和容错
SysEntity existing = sysEntityRepository.findByEntityCode(entityCode);
if (existing != null) {
return "模块代码 [" + entityCode + "] 已存在,创建失败,请让用户考虑使用其他代码名称。";
}
// 2. 实体赋值与入库
try { try {
SysEntity existing = sysEntityRepository.findByEntityCode(entityCode);
if (existing != null) {
existing.setSchemaDefinition(jsonSchema);
existing.setEntityName(entityName);
existing.setCreatorId(UserContextHolder.getCurrentUser());
sysEntityRepository.save(existing);
return "AI 已更新现有模块 [" + entityName + "]。";
}
SysEntity entity = new SysEntity(); SysEntity entity = new SysEntity();
entity.setEntityCode(entityCode); entity.setEntityCode(entityCode);
entity.setEntityName(entityName); entity.setEntityName(entityName);
// 此处直接存储大模型生成的 JSON 字符串(PostgreSQL 的 Hibernate 驱动会将其封装为 JSONB)
entity.setSchemaDefinition(jsonSchema); entity.setSchemaDefinition(jsonSchema);
entity.setCreatorId(UserContextHolder.getCurrentUser());
SysEntity saved = sysEntityRepository.save(entity); SysEntity saved = sysEntityRepository.save(entity);
return "核心模块 [" + entityName + "] 已成功持久化并部署到系统中。后台对应的 Entity ID (UUID) 为: " + saved.getId(); return "AI 已成功解析并持久化业务模块 [" + entityName + " (" + entityCode + ")],ID: " + saved.getId();
} catch (Exception e) { } catch (Exception e) {
log.error("保存 Schema 时出现异常", e); log.error("AI 处理定义请求失败: {}", e.getMessage());
return "数据库保存异常,请检查 JSON 结构是否合规。"; return "处理失败: " + e.getMessage();
}
}
/**
* AI 工具封装直接处理业务数据入库意图
*/
@Transactional
@Tool("核心业务处理:将业务消息中的动态数据持久化到指定模块。参数:entityCode (模块代码), jsonData (JSON 数据)。")
public String insertDynamicData(String entityCode, String jsonData) {
log.info("Agent Tool 正在处理消息:将数据保存至模块: Code={}", entityCode);
try {
SysEntity entity = sysEntityRepository.findByEntityCode(entityCode);
if (entity == null) {
return "AI 无法找到模块 [" + entityCode + "],请先下达创建模块的指令。";
}
SysDynamicData record = new SysDynamicData();
record.setEntityId(entity.getId());
record.setData(jsonData);
record.setCreatorId(UserContextHolder.getCurrentUser());
SysDynamicData saved = sysDynamicDataRepository.save(record);
return "AI 已自动完成数据入库,记录主键 ID: " + saved.getId();
} catch (Exception e) {
log.error("AI 自动化入库失败: {}", e.getMessage());
return "入库失败: " + e.getMessage();
}
}
/**
* AI 工具封装删除指定的业务数据
*/
@Transactional
@Tool("核心业务处理:根据 ID 物理删除指定的业务记录。参数:id (记录的唯一 UUID)。")
public String deleteDynamicData(String id) {
log.info("Agent Tool 正在处理消息:删除记录 ID: {}", id);
try {
java.util.UUID uuid = java.util.UUID.fromString(id);
if (!sysDynamicDataRepository.existsById(uuid)) {
return "AI 无法找到 ID 为 [" + id + "] 的记录,可能已被删除。";
}
sysDynamicDataRepository.deleteById(uuid);
return "AI 已成功从系统中移除该条记录 (ID: " + id + ")。";
} catch (Exception e) {
log.error("AI 物理删除失败: {}", e.getMessage());
return "删除操作遇到技术故障: " + e.getMessage();
} }
} }
} }

6
src/main/java/com/example/ainative/controller/AgentController.java

@ -3,11 +3,13 @@ package com.example.ainative.controller;
import com.example.ainative.ai.CrmAgent; import com.example.ainative.ai.CrmAgent;
import com.example.ainative.config.UserContextHolder; import com.example.ainative.config.UserContextHolder;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@RequestMapping("/api/agent") @RequestMapping("/api/agent")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class AgentController { public class AgentController {
private final CrmAgent crmAgent; private final CrmAgent crmAgent;
@ -15,7 +17,7 @@ public class AgentController {
/** /**
* 对话交互核心入口 * 对话交互核心入口
* 这里提供了一个传统的 REST 包装版本当前端 CopilotKit 或测试客户端发送自然语言时会调用此处 * 这里提供了一个传统的 REST 包装版本当前端 CopilotKit 或测试客户端发送自然语言时会调用此处
* *
* @param sessionId 唯一会话识别与底层的 ChatMemory 持久化紧密对应确保 AI 记住对话 * @param sessionId 唯一会话识别与底层的 ChatMemory 持久化紧密对应确保 AI 记住对话
* @param message 用户的原始需求 (比如"帮我建一个客户关系系统的线索录入表,有姓名和手机号") * @param message 用户的原始需求 (比如"帮我建一个客户关系系统的线索录入表,有姓名和手机号")
* @return AI 所思所想或执行完 @Tool 后的结果字符串反馈 * @return AI 所思所想或执行完 @Tool 后的结果字符串反馈
@ -24,7 +26,7 @@ public class AgentController {
public String chat(@RequestParam String sessionId, @RequestBody String message) { public String chat(@RequestParam String sessionId, @RequestBody String message) {
// 先从拦截器上下文中拿到刚才设置的调用者身份 // 先从拦截器上下文中拿到刚才设置的调用者身份
String currentUser = UserContextHolder.getCurrentUser(); String currentUser = UserContextHolder.getCurrentUser();
log.info("message:{}",message);
// 传递给底层的 Agent:这背后的复杂工作流完全交由 Langchain4J 托管! // 传递给底层的 Agent:这背后的复杂工作流完全交由 Langchain4J 托管!
// 如果用户的 message 意图命中“建表”,框架会在返回前自己拦截调去跑 CrmTools 里的 defineDynamicModule。 // 如果用户的 message 意图命中“建表”,框架会在返回前自己拦截调去跑 CrmTools 里的 defineDynamicModule。
return crmAgent.chat(sessionId, message, currentUser); return crmAgent.chat(sessionId, message, currentUser);

82
src/main/java/com/example/ainative/controller/DynamicDataController.java

@ -1,5 +1,6 @@
package com.example.ainative.controller; package com.example.ainative.controller;
import com.example.ainative.ai.CrmTools;
import com.example.ainative.config.UserContextHolder; import com.example.ainative.config.UserContextHolder;
import com.example.ainative.entity.SysDynamicData; import com.example.ainative.entity.SysDynamicData;
import com.example.ainative.entity.SysEntity; import com.example.ainative.entity.SysEntity;
@ -18,7 +19,7 @@ import java.util.UUID;
@RestController @RestController
@RequestMapping("/api/dynamic") @RequestMapping("/api/dynamic")
@RequiredArgsConstructor @RequiredArgsConstructor
@CrossOrigin(origins = "*") // 允许前端 Next.js (通常是 3000 端口) 进行跨域调用 @CrossOrigin(origins = "*")
public class DynamicDataController { public class DynamicDataController {
private final SysDynamicDataRepository dataRepository; private final SysDynamicDataRepository dataRepository;
@ -29,88 +30,19 @@ public class DynamicDataController {
*/ */
@GetMapping("/{entityId}") @GetMapping("/{entityId}")
public List<SysDynamicData> getRecords(@PathVariable UUID entityId) { public List<SysDynamicData> getRecords(@PathVariable UUID entityId) {
return dataRepository.findByEntityId(entityId); return dataRepository.findByEntityIdAndCreatorId(entityId, UserContextHolder.getCurrentUser());
} }
/** /**
* 根据模块 code 获取数据列表前端动态表格主要用此接口方便查询 * 根据模块 code 获取数据列表
*/ */
@GetMapping("/code/{entityCode}") @GetMapping("/code/{entityCode}")
public List<SysDynamicData> getRecordsByCode(@PathVariable String entityCode) { public List<SysDynamicData> getRecordsByCode(@PathVariable String entityCode) {
SysEntity entity = entityRepository.findByEntityCode(entityCode); SysEntity entity = entityRepository.findByEntityCodeAndCreatorId(entityCode, UserContextHolder.getCurrentUser());
if (entity != null) { if (entity != null) {
return dataRepository.findByEntityId(entity.getId()); return dataRepository.findByEntityIdAndCreatorId(entity.getId(), UserContextHolder.getCurrentUser());
} }
return List.of(); 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);
}
} }

11
src/main/java/com/example/ainative/controller/SysEntityController.java

@ -10,24 +10,27 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/api/entities") @RequestMapping("/api/entities")
@RequiredArgsConstructor @RequiredArgsConstructor
@CrossOrigin(origins = "*") // Allow frontend to fetch menus @CrossOrigin(origins = "*")
public class SysEntityController { public class SysEntityController {
private final SysEntityRepository sysEntityRepository; private final SysEntityRepository sysEntityRepository;
/** /**
* 获取所有已定义的模块列表供左侧菜单动态渲染使用 * 获取所有已定义的模块列表供左侧菜单动态渲染使用
* 该接口属于传统查询用于 UI 展现
*/ */
@GetMapping @GetMapping
public List<SysEntity> getAllEntities() { public List<SysEntity> getAllEntities() {
return sysEntityRepository.findAll(); return sysEntityRepository.findByCreatorId(com.example.ainative.config.UserContextHolder.getCurrentUser());
} }
/** /**
* 根据 code 获取单一实体定义方便前台表格按需渲染列等动态行为 * 根据 code 获取单一实体定义
*/ */
@GetMapping("/code/{entityCode}") @GetMapping("/code/{entityCode}")
public SysEntity getEntityByCode(@PathVariable String entityCode) { public SysEntity getEntityByCode(@PathVariable String entityCode) {
return sysEntityRepository.findByEntityCode(entityCode); return sysEntityRepository.findByEntityCodeAndCreatorId(entityCode, com.example.ainative.config.UserContextHolder.getCurrentUser());
} }
} }

3
src/main/java/com/example/ainative/entity/SysEntity.java

@ -28,6 +28,9 @@ public class SysEntity {
@Column(name = "schema_definition", columnDefinition = "jsonb", nullable = false) @Column(name = "schema_definition", columnDefinition = "jsonb", nullable = false)
private String schemaDefinition; private String schemaDefinition;
@Column(name = "creator_id", length = 50)
private String creatorId;
@CreationTimestamp @CreationTimestamp
@Column(name = "created_at", updatable = false) @Column(name = "created_at", updatable = false)
private ZonedDateTime createdAt; private ZonedDateTime createdAt;

1
src/main/java/com/example/ainative/repository/SysDynamicDataRepository.java

@ -8,4 +8,5 @@ import java.util.UUID;
public interface SysDynamicDataRepository extends JpaRepository<SysDynamicData, UUID> { public interface SysDynamicDataRepository extends JpaRepository<SysDynamicData, UUID> {
List<SysDynamicData> findByEntityId(UUID entityId); List<SysDynamicData> findByEntityId(UUID entityId);
List<SysDynamicData> findByEntityIdAndCreatorId(UUID entityId, String creatorId);
} }

3
src/main/java/com/example/ainative/repository/SysEntityRepository.java

@ -3,8 +3,11 @@ package com.example.ainative.repository;
import com.example.ainative.entity.SysEntity; import com.example.ainative.entity.SysEntity;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface SysEntityRepository extends JpaRepository<SysEntity, UUID> { public interface SysEntityRepository extends JpaRepository<SysEntity, UUID> {
SysEntity findByEntityCode(String entityCode); SysEntity findByEntityCode(String entityCode);
List<SysEntity> findByCreatorId(String creatorId);
SysEntity findByEntityCodeAndCreatorId(String entityCode, String creatorId);
} }

7
src/main/resources/application.yml

@ -9,7 +9,7 @@ spring:
jpa: jpa:
hibernate: hibernate:
ddl-auto: none ddl-auto: none
show-sql: true show-sql: false
properties: properties:
hibernate: hibernate:
format_sql: true format_sql: true
@ -26,3 +26,8 @@ langchain4j:
server: server:
port: 8080 port: 8080
logging:
level:
dev.ai4j: debug
org.hibernate.sql: error

7
target/classes/application.yml

@ -9,7 +9,7 @@ spring:
jpa: jpa:
hibernate: hibernate:
ddl-auto: none ddl-auto: none
show-sql: true show-sql: false
properties: properties:
hibernate: hibernate:
format_sql: true format_sql: true
@ -26,3 +26,8 @@ langchain4j:
server: server:
port: 8080 port: 8080
logging:
level:
dev.ai4j: debug
org.hibernate.sql: error

Loading…
Cancel
Save