数据库课程设计实战:构建文本分割结果的管理系统
数据库课程设计实战构建文本分割结果的管理系统每次做数据库课程设计你是不是也头疼选题要么太简单像学生信息管理做出来感觉没深度要么太复杂比如电商系统光表关系就画晕了。最后交上去的往往是个“看起来能用”但“实际上没人用”的系统。今天咱们换个思路。我带你做一个真正有用的数据库课程设计项目一个管理AI文本分割结果的Web系统。想象一下你用BERT模型把一篇长文章自动切分成多个段落这些段落结果怎么存怎么查怎么让其他人也能上传文章、查看分割结果这就是我们这个系统要解决的问题。它不只是一个“玩具”而是一个有实际应用场景的迷你项目。你会完整经历数据库设计E-R图、建表→ 后端业务逻辑调用模型、处理数据→ 前端交互上传、查看、导出的全流程。做完这个你的课程设计报告不仅有理论更有能跑起来的代码和系统含金量直接拉满。1. 项目全景我们要做一个什么系统简单说我们要做一个Web应用它的核心工作流是这样的用户通过网页上传一篇文本比如一篇论文、一份报告。后端系统接收到文本调用一个预设的BERT文本分割模型我们这里会简化模拟这个过程将长文本按语义切分成多个段落。系统将原始文章、分割后的各个段落、以及这次处理任务的记录全部保存到数据库中。用户可以在网页上查看自己的所有处理记录点开任何一次记录都能看到原文和对应的分段结果并且可以导出这些分段结果。看到这里你应该能脑子里冒出几个关键的“东西”用户、文章、段落、任务记录。没错它们就是咱们数据库里要管理的核心实体。这个系统麻雀虽小五脏俱全涵盖了用户管理、文件处理、异步任务、结果展示等多个常见模块的设计要点。2. 核心设计画出E-R图与创建数据库表做数据库设计第一步不是打开MySQL Workbench就建表而是先理清实体和关系。咱们用最经典的E-R图来梳理。2.1 实体关系E-R图分析我们的系统主要涉及四个核心实体用户 (User)谁在使用系统。需要记录最基本的信息。原文 (OriginalText)用户上传的原始长文本。一篇文章只对应一个原文实体。分割段落 (Segment)原文经过模型处理后产生的多个段落。一篇原文对应多个段落这是“一对多”关系。处理任务 (Task)记录每一次文本分割请求的元信息。比如什么时候提交的、处理状态是什么排队中/处理中/成功/失败。一个用户可以有多个任务一个任务只关联一篇原文。它们之间的关系可以直观地表示为下图所示的概念模型erDiagram USER ||--o{ TASK : creates USER { int id PK varchar username varchar password_hash datetime created_at } TASK ||--|| ORIGINAL_TEXT : processes TASK { int id PK int user_id FK int original_text_id FK varchar status datetime submitted_at datetime finished_at } ORIGINAL_TEXT ||--o{ SEGMENT : is split into ORIGINAL_TEXT { int id PK text content varchar title datetime uploaded_at } SEGMENT { int id PK int original_text_id FK int segment_index text content }上图清晰地展示了实体间的联系USER创建TASKTASK处理ORIGINAL_TEXT而一篇ORIGINAL_TEXT被分割成多个SEGMENT。2.2 将E-R图转化为真实的SQL表有了清晰的E-R图建表就是按图索骥。这里给出MySQL的建表SQL语句你可以直接在你的数据库里执行。-- 创建数据库 CREATE DATABASE IF NOT EXISTS text_segmentation_system; USE text_segmentation_system; -- 1. 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名用于登录, password_hash VARCHAR(255) NOT NULL COMMENT 加密后的密码, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 账户创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 2. 原文表 CREATE TABLE original_text ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) DEFAULT 未命名文档 COMMENT 文章标题可用户输入或自动生成, content TEXT NOT NULL COMMENT 原始文本内容, uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户上传的原始文本; -- 3. 分割段落表 CREATE TABLE segment ( id INT PRIMARY KEY AUTO_INCREMENT, original_text_id INT NOT NULL COMMENT 所属原文ID, segment_index INT NOT NULL COMMENT 段落在原文中的顺序从0开始, content TEXT NOT NULL COMMENT 分割后的段落内容, FOREIGN KEY (original_text_id) REFERENCES original_text(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文本分割后的段落; -- 4. 处理任务表 CREATE TABLE task ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 发起任务的用户ID, original_text_id INT NOT NULL COMMENT 被处理的原文ID, status ENUM(pending, processing, success, failed) DEFAULT pending COMMENT 任务状态, submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 任务提交时间, finished_at DATETIME DEFAULT NULL COMMENT 任务完成时间, error_message TEXT COMMENT 如果失败记录错误信息, FOREIGN KEY (user_id) REFERENCES user(id), FOREIGN KEY (original_text_id) REFERENCES original_text(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文本分割任务记录表;建表语句的关键点解析关系与外键这是体现数据库设计精髓的地方。segment.original_text_id关联original_text.id实现了“原文-段落”的一对多关系。ON DELETE CASCADE表示删除原文时其所有段落自动删除保持数据一致性。task表有两个外键分别指向user和original_text记录了“谁”在“何时”处理了“哪篇文章”。字段设计password_hash存储加密后的密码绝对不要存明文密码。status使用ENUM类型约束任务状态比随意用字符串更规范。segment_index记录段落顺序方便前端按原序展示。created_at,uploaded_at,submitted_at这些时间戳对于管理和审计至关重要。注释使用COMMENT为每个表和字段添加注释这在团队协作和后期维护时非常有用。表建好后你的数据库就有了坚实的骨架。接下来我们要让数据流动起来。3. 后端实现用Spring Boot搭建业务逻辑为了更贴近企业开发主流我们选择用Java Spring Boot来实现后端。它生态完善能帮你更好地理解Web后端的分层架构Controller, Service, Repository。3.1 项目结构与核心依赖首先创建一个标准的Spring Boot项目。你的pom.xml需要包含以下关键依赖dependencies !-- Web支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据库访问 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 简化代码的Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies3.2 数据层定义实体与仓库根据数据库表我们创建对应的JPA实体类。这里以OriginalText和Segment为例展示一对多关系的映射。// OriginalText.java Entity Table(name original_text) Data // Lombok注解自动生成getter/setter等方法 NoArgsConstructor public class OriginalText { Id GeneratedValue(strategy GenerationType.IDENTITY) private Integer id; private String title; Lob // 表示大文本字段 Column(columnDefinition TEXT) private String content; private LocalDateTime uploadedAt; // 一对多关系一篇原文有多个段落 OneToMany(mappedBy originalText, cascade CascadeType.ALL, fetch FetchType.LAZY) private ListSegment segments new ArrayList(); PrePersist protected void onCreate() { uploadedAt LocalDateTime.now(); if (title null || title.trim().isEmpty()) { title 未命名文档- uploadedAt.format(DateTimeFormatter.ofPattern(yyyyMMddHHmm)); } } }// Segment.java Entity Table(name segment) Data NoArgsConstructor public class Segment { Id GeneratedValue(strategy GenerationType.IDENTITY) private Integer id; ManyToOne(fetch FetchType.LAZY) JoinColumn(name original_text_id, nullable false) private OriginalText originalText; private Integer segmentIndex; // 段落序号 Lob Column(columnDefinition TEXT) private String content; }然后为每个实体创建Repository接口Spring Data JPA会自动为我们实现基本的增删改查。// OriginalTextRepository.java Repository public interface OriginalTextRepository extends JpaRepositoryOriginalText, Integer { } // SegmentRepository.java Repository public interface SegmentRepository extends JpaRepositorySegment, Integer { ListSegment findByOriginalTextIdOrderBySegmentIndexAsc(Integer originalTextId); }3.3 业务层模拟文本分割与任务调度这是系统的核心。我们创建一个TextSegmentationService来处理业务逻辑。为了简化我们模拟BERT分割过程实际项目中这里会调用Python服务或相关的Java NLP库。// TextSegmentationService.java Service Slf4j // Lombok注解用于日志 public class TextSegmentationService { Autowired private OriginalTextRepository originalTextRepo; Autowired private SegmentRepository segmentRepo; Autowired private TaskRepository taskRepo; Autowired private TaskExecutor taskExecutor; // Spring的异步任务执行器 /** * 提交一个新的文本分割任务 */ public Task submitSegmentationTask(String title, String content, User user) { // 1. 保存原文 OriginalText originalText new OriginalText(); originalText.setTitle(title); originalText.setContent(content); originalText originalTextRepo.save(originalText); // 2. 创建任务记录 Task task new Task(); task.setUser(user); task.setOriginalText(originalText); task.setStatus(TaskStatus.PENDING); task taskRepo.save(task); // 3. 异步执行分割处理 taskExecutor.execute(() - { processSegmentationTask(task.getId(), originalText.getId(), content); }); log.info(用户 {} 提交了文本分割任务 {}, 原文ID: {}, user.getUsername(), task.getId(), originalText.getId()); return task; } /** * 异步处理任务模拟BERT分割 */ Async // 声明为异步方法 public void processSegmentationTask(Integer taskId, Integer originalTextId, String content) { Task task taskRepo.findById(taskId).orElseThrow(); task.setStatus(TaskStatus.PROCESSING); taskRepo.save(task); try { // 模拟处理耗时 Thread.sleep(2000); // **模拟BERT文本分割逻辑** // 这里用一个简单的按句号分割来模拟真实情况复杂得多 String[] sentences content.split([。!?]); ListSegment segments new ArrayList(); for (int i 0; i sentences.length; i) { if (!sentences[i].trim().isEmpty()) { Segment seg new Segment(); seg.setOriginalText(new OriginalText()); // 设置代理对象只需ID seg.getOriginalText().setId(originalTextId); seg.setSegmentIndex(i); seg.setContent(sentences[i].trim() 。); // 把标点加回去 segments.add(seg); } } // 批量保存段落 segmentRepo.saveAll(segments); // 更新任务状态为成功 task.setStatus(TaskStatus.SUCCESS); task.setFinishedAt(LocalDateTime.now()); taskRepo.save(task); log.info(任务 {} 处理成功生成 {} 个段落, taskId, segments.size()); } catch (Exception e) { // 处理失败 task.setStatus(TaskStatus.FAILED); task.setErrorMessage(e.getMessage()); task.setFinishedAt(LocalDateTime.now()); taskRepo.save(task); log.error(处理任务 {} 时发生错误, taskId, e); } } /** * 获取一次任务的所有分割结果 */ public SegmentationResult getSegmentationResult(Integer taskId) { Task task taskRepo.findById(taskId).orElseThrow(); if (!TaskStatus.SUCCESS.equals(task.getStatus())) { throw new RuntimeException(任务未完成或失败无法获取结果); } OriginalText originalText task.getOriginalText(); ListSegment segments segmentRepo.findByOriginalTextIdOrderBySegmentIndexAsc(originalText.getId()); SegmentationResult result new SegmentationResult(); result.setTask(task); result.setOriginalText(originalText); result.setSegments(segments); return result; } }3.4 控制层提供RESTful API最后我们创建控制器Controller来暴露HTTP接口供前端调用。// TextSegmentationController.java RestController RequestMapping(/api/tasks) Slf4j public class TextSegmentationController { Autowired private TextSegmentationService segmentationService; Autowired private UserService userService; // 假设有一个管理用户登录的服务 PostMapping(/submit) public ResponseEntityMapString, Object submitText(RequestBody TextSubmissionRequest request, RequestHeader(Authorization) String token) { // 1. 根据token验证用户 (简化处理) User currentUser userService.getCurrentUser(token); // 2. 调用服务提交任务 Task task segmentationService.submitSegmentationTask(request.getTitle(), request.getContent(), currentUser); // 3. 返回任务ID和状态 MapString, Object response new HashMap(); response.put(taskId, task.getId()); response.put(status, task.getStatus()); response.put(message, 任务已提交正在处理中); return ResponseEntity.ok(response); } GetMapping(/{taskId}/result) public ResponseEntitySegmentationResult getResult(PathVariable Integer taskId, RequestHeader(Authorization) String token) { userService.validateToken(token); // 验证权限 SegmentationResult result segmentationService.getSegmentationResult(taskId); return ResponseEntity.ok(result); } GetMapping(/my-tasks) public ResponseEntityListTask getMyTasks(RequestHeader(Authorization) String token) { User currentUser userService.getCurrentUser(token); ListTask tasks taskRepo.findByUserOrderBySubmittedAtDesc(currentUser); return ResponseEntity.ok(tasks); } } // 用于接收前端提交文本的请求体 Data class TextSubmissionRequest { private String title; private String content; }至此一个具备核心功能的后端就搭建好了。它提供了提交任务、查询结果、查看历史任务的API。4. 前端实现一个简单可用的操作界面前端我们力求简洁明了使用基础的HTML、JavaScript和一点CSS让你能快速理解前后端如何交互。4.1 主页面任务提交与列表创建一个index.html包含一个表单用于提交文本和一个区域用于展示历史任务列表。!DOCTYPE html html head title文本分割管理系统/title style body { font-family: sans-serif; margin: 40px; } .container { max-width: 800px; margin: auto; } textarea { width: 100%; height: 150px; margin: 10px 0; } button { padding: 10px 20px; background: #4CAF50; color: white; border: none; cursor: pointer; } .task-list { margin-top: 30px; border-top: 1px solid #ccc; } .task-item { padding: 10px; border-bottom: 1px solid #eee; } .status { display: inline-block; padding: 3px 8px; border-radius: 3px; font-size: 0.9em; } .status-pending { background: #fff3cd; color: #856404; } .status-success { background: #d4edda; color: #155724; } .status-failed { background: #f8d7da; color: #721c24; } /style /head body div classcontainer h1 文本分割处理系统/h1 h2提交新文本/h2 form idsubmitForm input typetext idtitle placeholder输入文章标题可选 stylewidth:100%; padding:8px; margin-bottom:10px; textarea idcontent placeholder请粘贴或输入需要分割的长文本.../textarea button typesubmit提交分割任务/button /form div idmessage/div div classtask-list h2我的处理任务/h2 div idtasksContainer加载中.../div /div /div script srcapp.js/script !-- 引入我们的JS逻辑 -- /body /html4.2 交互逻辑调用后端API创建app.js处理表单提交、轮询任务状态、展示列表等所有前端逻辑。// app.js const API_BASE http://localhost:8080/api; // 假设后端运行在8080端口 let authToken demo-token-123; // 简化处理实际应从登录接口获取 // 1. 提交新文本 document.getElementById(submitForm).addEventListener(submit, async function(e) { e.preventDefault(); const title document.getElementById(title).value; const content document.getElementById(content).value; if (!content.trim()) { alert(请输入文本内容); return; } const submitBtn this.querySelector(button); submitBtn.disabled true; submitBtn.textContent 提交中...; try { const response await fetch(${API_BASE}/tasks/submit, { method: POST, headers: { Content-Type: application/json, Authorization: authToken }, body: JSON.stringify({ title, content }) }); const result await response.json(); document.getElementById(message).innerHTML p stylecolor:green;✅ 任务提交成功任务ID: ${result.taskId}状态: ${result.status}/p; // 清空表单 document.getElementById(title).value ; document.getElementById(content).value ; // 刷新任务列表 loadMyTasks(); // 开始轮询这个新任务的状态 pollTaskStatus(result.taskId); } catch (error) { document.getElementById(message).innerHTML p stylecolor:red;❌ 提交失败: ${error.message}/p; console.error(提交错误:, error); } finally { submitBtn.disabled false; submitBtn.textContent 提交分割任务; } }); // 2. 轮询任务状态简化版实际可用WebSocket async function pollTaskStatus(taskId) { const checkStatus async () { try { const response await fetch(${API_BASE}/tasks/${taskId}/result, { headers: { Authorization: authToken } }); if (response.status 200) { // 任务成功刷新列表显示最新状态 loadMyTasks(); document.getElementById(message).innerHTML p任务 ${taskId} 处理完成a hrefresult.html?taskId${taskId} target_blank查看结果/a/p; return true; } } catch (e) { // 忽略轮询中的错误任务可能还在处理 } return false; }; // 轮询5次每次间隔2秒 for (let i 0; i 5; i) { const done await checkStatus(); if (done) break; await new Promise(resolve setTimeout(resolve, 2000)); } } // 3. 加载“我的任务”列表 async function loadMyTasks() { try { const response await fetch(${API_BASE}/tasks/my-tasks, { headers: { Authorization: authToken } }); const tasks await response.json(); const container document.getElementById(tasksContainer); if (tasks.length 0) { container.innerHTML p暂无处理任务。/p; return; } const html tasks.map(task div classtask-item strong任务#${task.id}/strong: ${task.originalText.title || 无标题} span classstatus status-${task.status}${task.status}/span br small提交于: ${new Date(task.submittedAt).toLocaleString()}/small ${task.status SUCCESS ? a hrefresult.html?taskId${task.id} stylemargin-left:15px;查看分割结果/a : } /div ).join(); container.innerHTML html; } catch (error) { console.error(加载任务列表失败:, error); document.getElementById(tasksContainer).innerHTML p stylecolor:red;加载失败请检查网络。/p; } } // 页面加载时获取任务列表 window.onload loadMyTasks;4.3 结果展示页查看与导出创建一个result.html用于展示某次任务的具体分割结果并提供导出功能。!-- result.html 简化版 -- !DOCTYPE html html head title分割结果/title style .segment { background: #f9f9f9; margin: 15px 0; padding: 15px; border-left: 4px solid #4CAF50; } .segment-index { font-weight: bold; color: #666; } button { margin: 10px 5px; padding: 8px 15px; } /style /head body h1文本分割结果/h1 div idoriginalText/div h2分割段落 (span idsegmentCount0/span 个)/h2 button onclickexportAsTxt()导出为TXT/button button onclickexportAsJson()导出为JSON/button div idsegmentsContainer/div script const urlParams new URLSearchParams(window.location.search); const taskId urlParams.get(taskId); const authToken demo-token-123; async function loadResult() { if (!taskId) { document.body.innerHTML p未指定任务ID/p; return; } try { const response await fetch(http://localhost:8080/api/tasks/${taskId}/result, { headers: { Authorization: authToken } }); const result await response.json(); // 展示原文 document.getElementById(originalText).innerHTML h3原文: ${result.originalText.title}/h3 p stylewhite-space: pre-wrap;${result.originalText.content}/p; // 展示段落 const segmentsHtml result.segments.map(seg div classsegment div classsegment-index段落 ${seg.segmentIndex 1}:/div p${seg.content}/p /div ).join(); document.getElementById(segmentsContainer).innerHTML segmentsHtml; document.getElementById(segmentCount).textContent result.segments.length; // 保存数据供导出使用 window.currentResult result; } catch (error) { document.body.innerHTML p加载失败: ${error.message}/p; } } function exportAsTxt() { const result window.currentResult; let txtContent 原文标题: ${result.originalText.title}\n\n; txtContent 原文内容:\n${result.originalText.content}\n\n; txtContent 分割结果 \n\n; result.segments.forEach((seg, idx) { txtContent [段落 ${idx 1}]\n${seg.content}\n\n; }); const blob new Blob([txtContent], { type: text/plain }); const link document.createElement(a); link.href URL.createObjectURL(blob); link.download 分割结果_任务${taskId}.txt; link.click(); } function exportAsJson() { const result window.currentResult; const jsonStr JSON.stringify(result, null, 2); const blob new Blob([jsonStr], { type: application/json }); const link document.createElement(a); link.href URL.createObjectURL(blob); link.download 分割结果_任务${taskId}.json; link.click(); } loadResult(); /script /body /html5. 总结与展望走完这个完整的项目你应该对如何将一个想法落地成一个有数据库、有后端逻辑、有前端交互的Web系统有了更具体的感受。这不仅仅是完成一个课程设计更是体验了一次小型的产品开发流程。从E-R图设计到建表你理解了数据是如何被组织和关联的。通过Spring Boot实现后端你看到了业务逻辑如异步任务处理如何与数据库交互。最后一个简单的前端页面将所有功能串联起来让用户能够实际使用。这个过程中模拟的BERT文本分割是一个占位符你可以很容易地将其替换为调用真实的Python NLP服务比如通过HTTP或gRPC或者集成一个Java版的句子分割库让项目立刻“活”起来。这个项目的扩展性也很强。你可以继续为它添加更多功能比如用户注册登录、文本处理队列管理、更复杂的模型参数配置、处理进度实时推送用WebSocket、甚至是一个更漂亮的前端界面。无论你往哪个方向深化这次实战所积累的关于系统设计、数据流转和全栈协作的经验都会是你简历上非常扎实的一笔。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2460706.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!