本人根据自己对前端微不足道的理解和 AI 老师的指导下,艰难地完成了基础crud代码的全栈开发,算是自己的第一个 Java 项目,对此做个简单总结。

后端部分


在前后端分离开发中,前端负责页面交互与数据展示,后端提供接口支持。本文基于 Vue3+Element Plus 构建用户管理前端页面,实现新增、编辑、删除、搜索核心功能,配合后端 RESTful 接口完成联调,并解决开发中常见的报错问题,适合新手快速上手。

技术栈

JavaSE,MySQL,Spring,SpringMVC,MyBatis,SpringBoot,Vue3,Element Plus。

原理

后端原理

依赖注入:通过 @Autowired 实现 Service 与 Dao、Controller 与 Service 层的解耦;
MyBatis 数据映射:开启 map-underscore-to-camel-case 自动转换数据库下划线字段(如 user_id)与 Java 驼峰属性(如 userId),通过注解式 SQL 实现 CRUD;
RESTful 接口规范:用 @GetMapping/{id}/@PostMapping/@PutMapping/{userId}/@DeleteMapping/{id} 对应查、增、改、删操作,路径传主键 + 请求体传数据,确保语义清晰。
整合 Lombok 简化 Pojo 类代码。

前端原理
Vue3 响应式:通过ref/reactive定义响应式数据(如userList/formData),数据变化时自动更新页面(如表格、表单)。
Element Plus 组件化:复用表格(el-table)、表单(el-form)、对话框(el-dialog)等组件,快速搭建交互界面,减少原生 DOM 操作。
Fetch 异步请求:通过原生Fetch API调用后端 RESTful 接口,实现 “数据请求 - 响应 - 页面更新” 闭环,配合async/await简化异步代码。

功能

后端功能

提供用户数据的完整 CRUD 接口:
查:按 ID 查单个用户(/users/{id})、查所有用户(/users);
增:新增用户(/users,请求体传待增字段);
改:按 ID 更新用户信息(/users,请求体传待更字段);
删:按 ID 删除用户(/users/{id})。

前端功能(交互 + 数据展示)
数据展示:用el-table展示用户列表,加载时显示动画,搜索空结果时提示 “未找到用户”。
新增用户:点击 “新增” 打开对话框,表单验证(ID 必为数字、用户名 / 密码必填),提交后刷新列表。
编辑用户:点击 “编辑” 填充当前用户数据,ID 禁用不可改,提交后更新列表。
搜索用户:输入 ID 回车 / 点击搜索,查询单个用户;清空输入框显示所有用户。
删除用户:点击 “删除” 弹出确认框,确认后删除并刷新列表。

前端代码

<template><div class="user-page"><h1>用户管理</h1><!-- 操作区 --><div class="operation-bar"><el-input v-model="searchId"placeholder="输入ID搜索"style="width: 200px"clearable@keyup.enter="handleSearch" /><el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button><el-button type="success" icon="Plus" @click="openAddDialog">新增用户</el-button></div><!-- 用户表格 --><el-table :data="userList"borderstyle="width: 100%":loading="loading"><el-table-column prop="userId" label="ID" width="80" align="center" /><el-table-column prop="userName" label="用户名" width="150" align="center" /><el-table-column prop="password" label="密码" align="center" /><el-table-column label="操作" width="240" align="center"><template #default="scope"><el-button type="primary"size="small"icon="Edit"@click="openEditDialog(scope.row)"style="margin-right: 5px">编辑</el-button><el-button type="danger"size="small"icon="Delete"@click="handleDelete(scope.row.userId)">删除</el-button></template></el-table-column></el-table><!-- 新增用户对话框 --><el-dialog title="新增用户"v-model="addDialogVisible"width="300px"><el-form :model="addForm" :rules="addRules" ref="addFormRef" label-width="80px"><el-form-item label="用户ID" prop="userId"><el-input v-model.number="addForm.userId" placeholder="请输入ID" /></el-form-item><el-form-item label="用户名" prop="userName"><el-input v-model="addForm.userName" /></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="addForm.password" type="password" /></el-form-item></el-form><template #footer><el-button @click="addDialogVisible = false">取消</el-button><el-button type="primary" @click="handleAddSubmit">确认新增</el-button></template></el-dialog><!-- 编辑用户对话框 --><el-dialog title="编辑用户"v-model="editDialogVisible"width="300px"><el-form :model="editForm" :rules="editRules" ref="editFormRef" label-width="80px"><!-- 编辑时ID不可修改,仅展示 --><el-form-item label="用户ID"><el-input v-model="editForm.userId" disabled /></el-form-item><el-form-item label="用户名" prop="userName"><el-input v-model="editForm.userName" /></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="editForm.password" type="password" /></el-form-item></el-form><template #footer><el-button @click="editDialogVisible = false">取消</el-button><el-button type="primary" @click="handleEditSubmit">确认编辑</el-button></template></el-dialog><!-- 删除确认对话框 --><el-dialog title="确认删除"v-model="deleteDialogVisible"width="300px"><p>确定要删除ID为 {{ deleteId }} 的用户吗?</p><template #footer><el-button @click="deleteDialogVisible = false">取消</el-button><el-button type="danger" @click="confirmDelete">确认删除</el-button></template></el-dialog></div>
</template><script setup>import { ref, reactive, onMounted } from 'vue'import { ElMessage } from 'element-plus'import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue'// 基础数据const userList = ref([])const loading = ref(false)const searchId = ref('')// 新增相关const addDialogVisible = ref(false)          // 新增对话框显示状态const addFormRef = ref(null)                 // 新增表单引用const addForm = reactive({                   // 新增表单数据userId: 0,userName: '',password: ''})const addRules = reactive({                  // 新增表单验证规则userId: [{ required: true, message: '请输入用户ID', trigger: 'blur' },{ type: 'number', message: 'ID必须是数字', trigger: 'blur' }],userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' }]})// 编辑相关const editDialogVisible = ref(false)         // 编辑对话框显示状态const editFormRef = ref(null)                // 编辑表单引用const editForm = reactive({                  // 编辑表单数据userId: '',userName: '',password: ''})const editRules = reactive({                 // 编辑表单验证规则(无需验证ID)userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' }]})// 删除相关const deleteDialogVisible = ref(false)const deleteId = ref(0)// 后端接口地址const API_URL = 'http://localhost:8081/users'// 1. 获取所有用户const getAllUsers = async () => {loading.value = truetry {const res = await fetch(API_URL)if (!res.ok) throw new Error('获取失败')userList.value = await res.json()} catch (err) {console.error('获取用户失败:', err)ElMessage.error('获取用户失败')} finally {loading.value = false}}// 2. 搜索用户const handleSearch = async () => {if (!searchId.value) {getAllUsers()return}loading.value = truetry {const res = await fetch(`${API_URL}/${searchId.value}`)if (res.status === 404) {userList.value = []ElMessage.warning('用户不存在')return}if (!res.ok) throw new Error('搜索失败')userList.value = [await res.json()]} catch (err) {console.error('搜索失败:', err)ElMessage.error('搜索失败')} finally {loading.value = false}}// 3. 打开新增对话框const openAddDialog = () => {// 重置新增表单addForm.userId = ''addForm.userName = ''addForm.password = ''addDialogVisible.value = true}// 4. 提交新增表单const handleAddSubmit = async () => {if (!addFormRef.value) returntry {// 验证新增表单await addFormRef.value.validate()// 发送新增请求const res = await fetch(API_URL, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(addForm)  // 提交新增表单数据})if (!res.ok) throw new Error('新增失败')ElMessage.success('新增成功')addDialogVisible.value = false  // 关闭新增对话框getAllUsers()                   // 刷新列表} catch (err) {console.error('新增失败:', err)ElMessage.error(err.message || '新增失败')}}// 5. 打开编辑对话框const openEditDialog = (user) => {// 填充编辑表单editForm.userId = user.userIdeditForm.userName = user.userNameeditForm.password = user.passwordeditDialogVisible.value = true}// 6. 提交编辑表单const handleEditSubmit = async () => {if (!editFormRef.value) returntry {// 验证编辑表单await editFormRef.value.validate()// 发送编辑请求(URL包含原ID)const res = await fetch(API_URL, {method: 'PUT',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(editForm)  // 提交编辑表单数据})if (!res.ok) throw new Error('编辑失败')ElMessage.success('编辑成功')editDialogVisible.value = false  // 关闭编辑对话框getAllUsers()                    // 刷新列表} catch (err) {console.error('编辑失败:', err)ElMessage.error(err.message || '编辑失败')}}// 7. 删除用户const handleDelete = (id) => {deleteId.value = iddeleteDialogVisible.value = true}// 确认删除const confirmDelete = async () => {try {const res = await fetch(`${API_URL}/${deleteId.value}`, {method: 'DELETE'})if (!res.ok) throw new Error('删除失败')ElMessage.success('删除成功')deleteDialogVisible.value = falsegetAllUsers()} catch (err) {console.error('删除失败:', err)ElMessage.error('删除失败')}}// 初始化onMounted(() => {getAllUsers()})
</script><style scoped>.user-page {padding: 20px;max-width: 1000px;margin: 0 auto;}.operation-bar {margin-bottom: 15px;display: flex;gap: 10px;align-items: center;}h1 {color: #333;font-size: 20px;margin-bottom: 20px;}
</style>

后端代码已展示在文前博客链接中。

问题和挑战

1.输入数字 ID 仍提示 “ID 必须是数字”
普通 v-model 会将输入值转为字符串(如输入 101,实际是 "101"),而验证规则 type: 'number' 校验的是数据类型,不是格式。
解决方案:用 v-model.number 自动将输入字符串转为数字,如果是字符串则转为NaN,符合要求。

2.编辑功能报 “Failed to fetch”
编辑请求 URL 错误(如 http://localhost:8081/users/undefined),可能是 editForm.userId 未正确赋值;也可能是后端 PUT 接口路径与前端不一致。
解决方案:
打印 URL 确认正确性:在 handleEditSubmit 中添加 console.log;修改前端接口路径。

3.前端页面中文乱码
这个确实花了很长时间,反复部署各种配置,跟着 AI 和各路大佬的博客调了好久,最终发现还是经典的 encoding 问题。
附上博客

4.后端 mybatis-plus 与 springboot 版本冲突
试了好多版本都不能正常运行,最后选择放弃阿里的 mybatis-plus,使用 mybatis。毕竟差距确实不大,进行复杂 Dao 层开发时肯定还是要自己手搓,就当练习基础语法了。

5.Linux相关
因为 MySQL 老师讲企业开发中都是在 Linux 环境下进行部署等操作的,于是跟着 AI 一步一步在 Linux 虚拟机中安装 MySQL 和 Redis(本项目没用到Redis),期间有不少问题是自己根本不熟悉 Linux 造成的,后来特地学习了 Linux 相关知识,计划日后继续学习 JVM。

6.“白学”问题
这一点见仁见智,但是部分课程确实白学。我最开始学2021年黑马的 Javaweb,确实好多东西已经用不上了,连老师也是提前准备或对着 ppt 敲的。不过很多人认为学习了 springboot 就没必要学习 SSM 全家桶,这一点我持否认态度。毕竟我一开始也是这样想的时候,直接去看 springboot 根本看不懂,连URL都不知道是什么。学习确实需要脚踏实地,不能妄想一步登天。

总结

整个暑假都在 Java 课程中度过,期间学习了不少前所未闻的知识。虽然第一次做的 Java 项目还是只有基础的crud,但是“守得云开见月明”,相信在自己的努力下,一切终将美好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921264.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921264.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921264.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB矩阵及其运算(二)函数

函数分为MATLAB内置函数及用户自定义函数&#xff0c;用户可以直接调用内置函数进行数据处理。内置函数的使用函数由三部分组成&#xff1a;名称、输入和输出。内置函数示例&#xff1a;单输入单输出函数&#xff1a;sqrt(x)&#xff1b;单输入多输出函数&#xff1a;size(x)&a…

自动化运维-ansible中对于大项目的管理

自动化运维-ansible中对于大项目的管理 一、引用主机清单 在Playbook中引用主机时&#xff0c;hosts 字段指定的目标必须与Ansible主机清单中定义的标识符完全匹配。如果清单中配置的是主机名&#xff0c;则在Playbook中使用IP地址或其他别名将无法匹配&#xff0c;导致任务被跳…

59_基于深度学习的麦穗计数统计系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)

目录 项目介绍&#x1f3af; 功能展示&#x1f31f; 一、环境安装&#x1f386; 环境配置说明&#x1f4d8; 安装指南说明&#x1f3a5; 环境安装教学视频 &#x1f31f; 二、数据集介绍&#x1f31f; 三、系统环境&#xff08;框架/依赖库&#xff09;说明&#x1f9f1; 系统环…

面试问题详解十六:Qt 内存管理机制

在 Qt 开发过程中&#xff0c;很多初学者&#xff08;包括不少有经验的 C 程序员&#xff09;经常会产生这样的疑问&#xff1a;“我在 Qt 中 new 出来的控件好像都没有 delete&#xff0c;那内存不会泄漏吗&#xff1f;”比如下面这段代码&#xff1a; void Widget::createLef…

Pycharm 试用

Ubuntu 重置Pycharm试用期限&#xff08;30 天&#xff09; 先关闭Pycharm删除系统缓存 rm -rf ~/.config/JetBrains/ && rm -rf ~/.local/share/JetBrains/ && rm -rf ~/.cache/JetBrains/删除已经安装的 Pycharm 软件运行目录去官网下载新的 就行了

C++ Qt 开发核心知识

Qt 框架概述Qt 是一个跨平台的 C 应用程序开发框架&#xff0c;广泛用于开发图形用户界面程序。其核心特性包括跨平台能力、丰富的功能模块和强大的工具集。核心概念与机制元对象系统Qt 扩展了标准 C&#xff0c;通过元对象系统提供信号与槽机制、运行时类型信息和动态属性系统…

net9 aspose.cell 自定义公式AbstractCalculationEngine,带超链接excel转html后背景色丢失

AbstractCalculationEngine 是 Aspose.Cells 中一个强大的抽象类&#xff0c;允许您自定义公式计算逻辑。当您需要覆盖默认计算行为或实现自定义函数时非常有用。直接上代码1. 创建自定义计算引擎using Aspose.Cells; using System;// 创建自定义计算引擎 public class CustomC…

如何监控员工的电脑?7款实用的员工电脑管理软件,探索高效管理捷径!

当销售团队在淘宝刷单、设计师用公司电脑挖矿、程序员频繁访问代码托管网站时&#xff0c;企业损失的不仅是带宽——低效、泄密、合规风险正成为隐形利润杀手。 传统管理依赖“人盯人”或抽查日志&#xff0c;但面对分布式办公与远程协作趋势&#xff0c;这些方法早已力不从心…

机器视觉软件--VisionPro、Visual Master,Halcon 和 OpenCV 的学习路线

Halcon 和 OpenCV区别 Halcon 和 OpenCV 都是计算机视觉领域的重要工具&#xff0c;但它们的设计理念、功能侧重和适用场景有显著不同。下面这个表格汇总了它们的核心区别&#xff0c;方便你快速了解&#xff1a; 开发模式与体验​​&#xff1a;Halcon 配备了强大的​​图形化…

算法-根据前序+中序遍历打印树的右视图

题目请根据二叉树的前序遍历&#xff0c;中序遍历恢复二叉树&#xff0c;并打印出二叉树的右视图数据范围&#xff1a; 0≤n≤100000≤n≤10000 要求&#xff1a; 空间复杂度 O(n)O(n)&#xff0c;时间复杂度 O(n)O(n)如输入[1,2,4,5,3],[4,2,5,1,3]时&#xff0c;通过前序遍历…

Kafka面试精讲 Day 7:消息序列化与压缩策略

【Kafka面试精讲 Day 7】消息序列化与压缩策略 在Kafka的高性能消息系统中&#xff0c;消息序列化与压缩是影响吞吐量、延迟和网络开销的核心环节。作为“Kafka面试精讲”系列的第7天&#xff0c;本文聚焦于这一关键主题&#xff0c;深入剖析其原理、实现方式、配置策略及常见…

Xterminal软件下载_Xterminal ssh远程链接工具下载__Xterminal安装包 网盘下载_Xterminal ssh远程链接工具安装包

Xterminal 作为一款国产 SSH 工具&#xff0c;专为开发人员量身打造。它支持 SSH 和 Telnet 协议连接远程服务器与虚拟机&#xff0c;无论是进行代码部署&#xff0c;还是服务器运维&#xff0c;都能轻松胜任。软件界面采用极简设计&#xff0c;黑色背景搭配白色文字&#xff0…

Lua > 洛谷

Lua > 洛谷P1000 超级玛丽游戏P1001 AB ProblemP1008 [NOIP 1998 普及组] 三连击P1035 [NOIP 2002 普及组] 级数求和P1046 [NOIP 2005 普及组] 陶陶摘苹果P1047 [NOIP 2005 普及组] 校门外的树P1085 [NOIP 2004 普及组] 不高兴的津津P1089 [NOIP 2004 提高组] 津津的储蓄计划…

小企业环境-火山方舟和扣子

背景说明 并不是说应该怎么办&#xff0c;而是基本配置有这些可以进行使用&#xff0c;具体不同企业使用的时候肯定要个性化配置。 使用了火山方舟和扣子 火山方舟 应用实验室列表 简单使用了提示词的功能&#xff0c;后端服务ARK_API_KEY 应用ID 来对应请求发送http请求…

QT-事件

Qt事件 除了信号和槽通信机制外&#xff0c;Qt中还提供了事件处理机制实现与用户的交互和对象间的通信。Qt捕获底层操作系统消息&#xff0c;进行封装之后转换为Qt事件&#xff0c;事件处理后才发出信号。 一、事件概述Qt中事件是程序内部或外部发生的动作。比如程序外部&#…

HI3519DRFCV500/HI3519DV500海思核心板IPC算力2.5T图像ISP超高清智能视觉应用提供SDK软件开发包

Hi3519DV500是一颗面向视觉行业推出的超高清智能 SoC。最高支持四路sensor输入&#xff0c;支持最高4K30fps的ISP图像处理能力&#xff0c;支持 2F WDR、多级降噪、六轴防抖、全景拼接、多光 谱融合等多种传统图像增强和处理算法&#xff0c;支持通过AI算法对输入图像进行实时降…

go 初始化组件最佳实践

Go 语言初始化最佳实践 在 Go 语言中, 有一个 init() 函数可以对程序进行包级别的初始化, 但 init() 函数有诸多不便, 例如: 无法返回错误, 进行耗时初始化时, 会增加程序启动时间。因此 init() 函数并不适用于所有初始化。 1.初始化方式 在程序进行初始化时&#xff0c;我们应…

域名暂停解析是怎么回事

域名注册和使用是需要付费的&#xff0c;如果没有及时续费&#xff0c;域名注册商就会暂停该域名的解析服务。相关数据显示&#xff0c;大约有 30% 的域名暂停解析情况是由于欠费引起的。比如&#xff0c;有个小公司的网站域名到期了&#xff0c;负责续费的员工忘记操作&#x…

前端开发的“三剑客”—— ​​HTML、CSS、JavaScript​​

前端开发的“三剑客”—— ​​HTML、CSS、JavaScript​​&#xff0c;是构建所有网页和Web应用的基石。它们分工明确又紧密协作&#xff0c;共同实现了网页的“内容结构”“视觉表现”和“交互行为”。以下是三者的详细解析及协作逻辑&#xff1a;​​1. HTML&#xff1a;网页…

TDengine TIMEDIFF() 函数用户使用手册

TDengine TIMEDIFF() 函数详细使用手册 目录 功能概述函数语法参数说明返回值说明版本变更说明技术特性使用场景及示例时间单位处理数据类型兼容性注意事项常见问题最佳实践 功能概述 TIMEDIFF() 函数用于计算两个时间戳的差值&#xff0c;返回 expr1 - expr2 的结果。结果…