
在现代Web应用开发中,前后端分离架构已经成为主流模式。本文将通过一个完整的用户管理系统案例,详细介绍如何使用Vue.js + Element UI构建前端界面,结合Spring Boot实现后端服务,实现前后端分离开发。该系统包含用户信息的增删改查、批量操作、导入导出等常见功能,是一个典型的企业级管理系统模块。
一、技术栈介绍
1.1 前端技术栈
-
Vue.js:渐进式JavaScript框架,用于构建用户界面
-
Element UI:基于Vue.js的桌面端组件库,提供丰富的UI组件
-
Axios:基于Promise的HTTP客户端,用于前后端通信
-
Vue Router:Vue.js官方的路由管理器
1.2 后端技术栈
-
Spring Boot:简化Spring应用初始搭建和开发过程的框架
-
MyBatis-Plus:MyBatis的增强工具,简化CRUD操作
-
Hutool:Java工具类库,提供Excel导入导出等功能
-
JWT:用于身份验证的JSON Web Token
二、前端实现详解
2.1 项目结构与配置
前端项目采用Vue CLI搭建,主要目录结构如下:
src/
├── views/
│ └── User.vue # 用户管理主页面
├── router/ # 路由配置
├── api/ # API请求封装
└── public/config.js # 全局配置
在public/config.js
中配置后端服务地址:
window.WHITE_IP = "your-server-ip:port";
2.2 用户管理页面布局
用户管理页面主要分为以下几个部分:
-
搜索区域:提供用户名搜索和重置功能
-
操作按钮区域:新增、批量删除、导入导出等功能按钮
-
表格展示区域:用户数据列表展示
-
分页组件:数据分页控制
-
弹窗表单:新增/编辑用户信息
<div class="User-container">
<!-- 搜索区域 -->
<div style="margin: 10px 0">
<el-input v-model="send.name" placeholder="请输入用户名"></el-input>
<el-button @click="selectListPage">搜索</el-button>
<el-button @click="reset">重置</el-button>
<!-- 操作按钮 -->
<el-button @click="insertWindow">新增</el-button>
<el-popconfirm @confirm="confirmRemoveIdList">
<el-button slot="reference">批量删除</el-button>
</el-popconfirm>
<el-button @click="list_export">导出</el-button>
</div>
<!-- 表格区域 -->
<el-table :data="list" @selection-change="onSelectChange">
<!-- 表格列定义 -->
</el-table>
<!-- 分页组件 -->
<el-pagination
@size-change="onSizeChange"
@current-change="onCurrentChange"
:current-page="send.currentPage"
:page-size="send.pageSize"
:total="send.total">
</el-pagination>
<!-- 弹窗表单 -->
<el-dialog :visible.sync="sendFormFlag">
<el-form>
<!-- 表单字段 -->
</el-form>
</el-dialog>
</div>
2.3 核心功能实现
2.3.1 分页查询
分页查询是管理系统的核心功能,前端需要维护当前页码、每页条数等参数:
data() {
return {
send: {
currentPage: 1,
pageSize: 10,
total: 0,
name: ''
},
list: []
}
},
methods: {
// 分页查询
selectListPage() {
this.$http.post("/user/list_page", this.send).then(res => {
if (res.data.code === "200") {
this.send.total = res.data.object.total
this.list = res.data.object.data
}
})
},
// 分页大小变化
onSizeChange(pageSize) {
this.send.pageSize = pageSize
this.selectListPage()
},
// 当前页变化
onCurrentChange(currentPage) {
this.send.currentPage = currentPage
this.selectListPage()
}
}
2.3.2 新增与编辑用户
通过同一个弹窗表单实现新增和编辑功能:
methods: {
// 打开新增窗口
insertWindow() {
this.sendFormFlag = true
this.sendForm = {}
},
// 打开编辑窗口
updateWindow(row) {
this.sendFormFlag = true
this.sendForm = JSON.parse(JSON.stringify(row))
},
// 确认保存
confirmInsertOrUpdate() {
this.$http.post("/user/insertOrUpdate", this.sendForm).then(res => {
if (res.data.code === "200") {
this.$message.success('保存成功')
this.selectListPage()
}
})
}
}
2.3.3 批量操作
批量操作需要处理表格的多选功能:
methods: {
// 多选变化
onSelectChange(val) {
this.sendForm.removeIdList = val
},
// 确认批量删除
confirmRemoveIdList() {
this.sendForm.removeIdList = this.sendForm.removeIdList.map(v => v.id)
this.$http.post("user/list_delete", this.sendForm).then(res => {
this.selectListPage()
})
}
}
2.3.4 导入导出功能
导入导出是管理系统常见功能,使用Element UI的Upload组件实现:
methods: {
// 导出
list_export() {
window.open(`https://${WHITE_IP}/user/list_export`)
},
// 导入成功回调
onImportSuccess() {
this.$message.success("文件导入成功")
this.selectListPage()
}
}
三、后端实现详解
3.1 项目结构
后端采用标准的Spring Boot项目结构:
src/main/java/
└── com.black
├── controller # 控制器层
├── mapper # 数据访问层
├── pojo # 实体类
└── util # 工具类
3.2 核心控制器实现
用户控制器处理所有用户相关的请求:
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
UserMapper userMapper;
// 新增或更新用户
@PostMapping("/insertOrUpdate")
public Res insertOrUpdate(@RequestBody User user) {
if (user.getId() != null) {
userMapper.updateById(user);
} else {
user.setPassword(MyUtils.getSHA256StrJava("123456"));
userMapper.insert(user);
}
return Res.success(null);
}
// 分页查询
@PostMapping("/list_page")
public Res list_page(@RequestBody User user) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(user.getName())) {
queryWrapper.like("name", user.getName());
}
int total = userMapper.selectCount(queryWrapper);
queryWrapper.last("limit " + user.getStart() + "," + user.getEnd());
List<User> dataList = userMapper.selectList(queryWrapper);
Map<String, Object> result = new HashMap<>();
result.put("total", total);
result.put("data", dataList);
return Res.success(result);
}
// 导出Excel
@GetMapping("/list_export")
public void list_export(HttpServletResponse response) throws Exception {
List<User> list = userMapper.selectList(null);
ExcelWriter writer = ExcelUtil.getWriter(true);
// 设置标题别名
writer.addHeaderAlias("id", "ID");
writer.addHeaderAlias("name", "用户名");
// ...其他字段
writer.write(list, true);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("用户信息", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
ServletOutputStream out = response.getOutputStream();
writer.flush(out, true);
out.close();
writer.close();
}
}
3.3 分页查询实现
后端分页查询使用MyBatis-Plus实现:
@PostMapping("/list_page")
public Res list_page(@RequestBody User user) {
// 1. 构建查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(user.getName())) {
queryWrapper.like("name", user.getName());
}
// 2. 获取总数
int total = userMapper.selectCount(queryWrapper);
// 3. 计算分页参数
int start = (user.getCurrentPage() - 1) * user.getPageSize();
int end = user.getPageSize();
// 4. 执行分页查询
queryWrapper.last("limit " + start + "," + end);
List<User> dataList = userMapper.selectList(queryWrapper);
// 5. 返回结果
Map<String, Object> result = new HashMap<>();
result.put("total", total);
result.put("data", dataList);
return Res.success(result);
}
3.4 Excel导出实现
使用Hutool工具库实现Excel导出:
@GetMapping("/list_export")
public void list_export(HttpServletResponse response) throws Exception {
// 1. 查询数据
List<User> list = userMapper.selectList(null);
// 2. 创建Excel写入器
ExcelWriter writer = ExcelUtil.getWriter(true);
// 3. 设置标题别名
writer.addHeaderAlias("id", "ID");
writer.addHeaderAlias("name", "用户名");
writer.addHeaderAlias("nick", "昵称");
// ...其他字段
// 4. 写入数据
writer.write(list, true);
// 5. 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("用户信息", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
// 6. 输出流
ServletOutputStream out = response.getOutputStream();
writer.flush(out, true);
out.close();
writer.close();
}
四、前后端交互设计
4.1 API接口规范
系统采用RESTful风格设计API,主要接口如下:
请求方法 | 路径 | 描述 |
---|---|---|
POST | /user/list_page | 分页查询用户列表 |
POST | /user/insertOrUpdate | 新增或更新用户 |
POST | /user/delete | 删除用户 |
POST | /user/list_delete | 批量删除用户 |
GET | /user/list_export | 导出用户数据到Excel |
4.2 数据格式约定
前后端统一使用JSON格式交互,响应数据格式如下:
成功响应:
{
"code": "200",
"message": "成功",
"object": {
"total": 100,
"data": [...]
}
}
错误响应:
{
"code": "500",
"message": "服务器错误"
}
4.3 Axios封装
对Axios进行统一封装,处理请求和响应:
import axios from 'axios'
import router from '../router'
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 在请求头中添加token
if (localStorage.token) {
config.headers['Authorization'] = localStorage.token
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== '200') {
// token过期处理
if (res.code === '401') {
router.push('/login')
}
return Promise.reject(res)
} else {
return res.object
}
},
error => {
return Promise.reject(error)
}
)
export default service
五、系统安全考虑
5.1 密码安全
用户密码使用SHA256加密存储:
public class MyUtils {
public static String getSHA256StrJava(String str) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(str.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
5.2 接口权限控制
使用JWT进行接口权限验证:
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 验证token
Claims claims = JwtUtil.parseToken(token);
if (claims == null) {
throw new RuntimeException("token不合法");
}
// 将用户信息存入request
request.setAttribute("userInfo", claims.get("userInfo"));
return true;
}
}
5.3 XSS防护
对用户输入进行过滤,防止XSS攻击:
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(httpRequest);
chain.doFilter(xssRequest, response);
}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return cleanXSS(value);
}
private String cleanXSS(String value) {
if (value == null) return null;
return HtmlUtils.htmlEscape(value);
}
}
六、性能优化实践
6.1 前端性能优化
-
组件懒加载:路由组件按需加载
-
表格虚拟滚动:大数据量表格使用虚拟滚动
-
请求防抖:搜索框输入使用防抖处理
// 防抖处理
debounceSelectListPage: _.debounce(function() {
this.selectListPage()
}, 500)
6.2 后端性能优化
-
MyBatis二级缓存:启用MyBatis二级缓存减少数据库查询
-
分页优化:使用更高效的分页查询方式
-
批量操作:使用批量插入代替循环单条插入
// 批量插入示例
public void batchInsert(List<User> userList) {
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insert(user);
}
session.commit();
session.clearCache();
session.close();
}
七、总结与扩展
本文详细介绍了基于Vue.js和Spring Boot的前后端分离用户管理系统的实现。系统实现了用户管理的常见功能,包括:
-
用户信息的增删改查
-
批量操作
-
数据导入导出
-
分页查询与条件过滤
-
系统安全防护
扩展方向
-
角色权限管理:实现更细粒度的权限控制
-
操作日志:记录用户操作日志
-
数据可视化:增加用户数据统计图表
-
消息通知:系统消息通知功能
-
移动端适配:开发响应式布局或单独移动端应用
通过这个项目,开发者可以掌握前后端分离开发的核心技术栈,了解企业级应用开发的常见模式和最佳实践。希望本文能为您的开发工作提供有价值的参考。