智慧社区--4
1、Excel表的导入导出
excel导出
传入参数
{"page": 1,"limit": 10,"userName": "","communityId": "","mobile": ""
}
返回参数
{"msg": "操作成功","code": 200,"data": "20230719185659.xls"
}
实现思路
①根据传入参数,查询居民信息
②将居民信息写入excel文件,并导出。
具体而言:
1.准备Excel模板:
创建一个Excel模板文件,该文件包含所需的表头和其他固定内容。
在代码中指定模板文件的路径。
2.复制模板文件:
为了避免直接修改原始模板文件,先复制一份模板文件。
在复制的文件名中添加时间戳或其他唯一标识,确保每次导出的文件都是新的。
3.读取数据:
获取需要导出的数据,并将其存储在一个列表中。
4.操作Excel文件:
使用Apache POI库来操作Excel文件。
读取复制后的Excel文件,获取工作簿和工作表。
5.写入数据到Excel:
遍历数据列表,为每条数据创建一个新的行。
在每行中创建单元格,并将数据写入到对应的单元格中。
可以根据需要设置单元格的样式、格式等。
6.保存Excel文件:
将修改后的Excel文件保存到指定的路径。
7.将文件名返回
引入依赖
<dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.2.0</version></dependency>
在配置文件中配置Excel的地址
#文件上传位置
upload:face: D:/community/upload/face/excel: D:/community/upload/excel/urlPrefix: http://localhost:8089/
对居民进行Excel表的导入导出
在PersonController配置对应的地址和方法
package com.qcby.controller;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qcby.DTO.PersonFaceForm;
import com.qcby.DTO.PersonListFrom;
import com.qcby.config.ApiConfiguration;
import com.qcby.entity.Community;
import com.qcby.entity.Person;
import com.qcby.entity.User;
import com.qcby.service.ICommunityService;
import com.qcby.service.IPersonService;
import com.qcby.utils.*;
import com.qcby.vo.CommunityVO;
import com.qcby.vo.PageVO;
import com.qcby.vo.PersonVO;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;@RestController
@RequestMapping("/sys/person")
public class PersonController {@Autowiredprivate IPersonService personService;@Autowiredprivate ICommunityService communityService;@Autowiredprivate ApiConfiguration apiConfiguration;@Value("${upload.face}")private String face;@Value("${upload.excel}")private String excel;@Value("${upload.urlPrefix}")private String urlPrefix;/*** 数据的导出功能实现* @param personListForm* @return*/@GetMapping("/exportExcel")public Result exportExcel(PersonListFrom personListForm) {//1.获取满足条件的数据(就是service中的分页方法,与list一样)PageVO pageVO = personService.personList(personListForm);//2.只需要满足条件的数据List list = pageVO.getList();//3.处理数据放入Excel表格中String path = excel;path= ExcelUtil.ExpPersonInfo(list, path);return Result.ok().put("data", path);}
}
ExcelUtil文件导出工具类
package com.qcby.utils;import com.qcby.vo.PersonVO;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.BorderStyle;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;public class ExcelUtil {public static String ExpPersonInfo(List<PersonVO> info, String path){POIFSFileSystem fs = null;//判断起始行数(根据模板决定),跳过前两行int headRow = 2;//生成的文件名String descfile = null;try{//复制模板文件(源模板文件路径)String srcfile = path + "personInfo.xls";Date date = new Date();//基于时间戳生成新的文件名SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");String datestr = format.format(date);descfile = datestr + ".xls";try {//复制模板文件FileInputStream fis = new FileInputStream(srcfile);FileOutputStream fos = new FileOutputStream(path + descfile);//4kb缓冲区byte [] buffer = new byte[1024*4];while(fis.read(buffer) != -1){fos.write(buffer);}fis.close();fos.close();}catch (Exception e){e.printStackTrace();}//写数据//打开创建的文件fs = new POIFSFileSystem(new FileInputStream(path + descfile));FileOutputStream fos = new FileOutputStream(path + descfile);//创建工作簿对象HSSFWorkbook wb1 = new HSSFWorkbook(fs);//获取第一个工作表HSSFSheet sheet = wb1.getSheetAt(0);// 人员数量int size = info.size();// 列计数器int col = 0;//创建单元格样式HSSFCellStyle style = wb1.createCellStyle();// 左边框style.setBorderLeft(BorderStyle.THIN);// 右边框style.setBorderRight(BorderStyle.THIN);// 上边框style.setBorderTop(BorderStyle.THIN);// 下边框style.setBorderBottom(BorderStyle.THIN);//依次写入人员数据for(int i = 0;i < size;i++){//每行开始时重置列计数器col = 0;//获取当前人员对象PersonVO p = info.get(i);//创建行(从第三行开始,跳过表头)HSSFRow row = sheet.createRow(i + headRow);// 创建单元格并设置值HSSFCell cell = null;cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getPersonId());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getCommunityName());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getTermName());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getHouseNo());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getUserName());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getSex());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getMobile());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getPersonType());cell = row.createCell(col++);cell.setCellStyle( style);cell.setCellValue(p.getRemark());}// 写入数据并关闭流wb1.write(fos);fos.close();}catch (Exception e){e.printStackTrace();}// 返回生成的文件名return descfile;}
}
结果
就会生成一个当前时间的Excel文件
excel导入
分为文件上传和数据导入两个功能
实现思路(文件上传)
①指定文件上传位置
②获取上传的文件
③通过UUID生成新的文件名
④文件上传
⑤返回上传的文件名
对应的Controller的代码
package com.qcby.controller;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qcby.DTO.PersonFaceForm;
import com.qcby.DTO.PersonListFrom;
import com.qcby.config.ApiConfiguration;
import com.qcby.entity.Community;
import com.qcby.entity.Person;
import com.qcby.entity.User;
import com.qcby.service.ICommunityService;
import com.qcby.service.IPersonService;
import com.qcby.utils.*;
import com.qcby.vo.CommunityVO;
import com.qcby.vo.PageVO;
import com.qcby.vo.PersonVO;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;@RestController
@RequestMapping("/sys/person")
public class PersonController {@Autowiredprivate IPersonService personService;@Autowiredprivate ICommunityService communityService;@Autowiredprivate ApiConfiguration apiConfiguration;@Value("${upload.face}")private String face;@Value("${upload.excel}")private String excel;@Value("${upload.urlPrefix}")private String urlPrefix;/*** 文件上传* @param file* @return* @throws Exception*/@PostMapping("/excelUpload")public Result excelUpload(@RequestParam("uploadExcel") MultipartFile file) throws IOException {if(file.getOriginalFilename().equals("")){return Result.error("没有选中要上传的文件");}else {String picName = UUID.randomUUID().toString();String oriName = file.getOriginalFilename();String extName = oriName.substring(oriName.lastIndexOf("."));String newFileName = picName + extName;File targetFile = new File(excel + newFileName);//保存文件file.transferTo(targetFile);return Result.ok().put("data", newFileName);}}
}
根据自己创建的excel文件,将文件进行上传
实现思路(数据导入)
文件导入的实现思路主要涉及以下几个步骤:
1.获取用户信息:
通过HttpSession对象从会话中获取当前登录的用户信息,用于存储到数据表person中creat的字段
2.读取文件
根据传入的文件名和预设的文件路径,构建完整的文件路径
使用Apache POI库的POIFSFileSystem
和HSSFWorkbook
类来读取Excel文件。POIFSFileSystem
用于处理Office文档的底层文件系统,而HSSFWorkbook
则代表一个Excel工作簿。
3.异常处理
在读取文件的过程中,可能会遇到文件不存在、文件损坏或读取错误等异常。使用try-catch
块来捕获这些异常,并打印堆栈跟踪以便于调试和错误处理。
4.获取工作表内容
从工作簿中获取第一个工作表(或根据需求获取特定的工作表)。
获取工作表的总行数和总列数,以确定数据范围。
5.数据提取
根据需要,跳过表头行。
遍历工作表的每一行和每一列,使用HSSFRow
和HSSFCell
类获取单元格数据。
使用DataFormatter
类将单元格数据格式化为字符串,以便于后续处理。
6.存储数据
创建一个二维对象数组,用于存储从Excel文件中提取的数据。
将格式化后的单元格数据按行和列的顺序存入二维数组中。
7.写入数据
将二维对象数组中的数据逐条保存到数据库中。
8.返回响应
返回响应给前端,前端可以根据响应中的信息进行相应的处理(如显示导入成功提示、刷新页面等)。
controller
package com.qcby.controller;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qcby.DTO.PersonFaceForm;
import com.qcby.DTO.PersonListFrom;
import com.qcby.config.ApiConfiguration;
import com.qcby.entity.Community;
import com.qcby.entity.Person;
import com.qcby.entity.User;
import com.qcby.service.ICommunityService;
import com.qcby.service.IPersonService;
import com.qcby.utils.*;
import com.qcby.vo.CommunityVO;
import com.qcby.vo.PageVO;
import com.qcby.vo.PersonVO;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;@RestController
@RequestMapping("/sys/person")
public class PersonController {@Autowiredprivate IPersonService personService;@Autowiredprivate ICommunityService communityService;@Autowiredprivate ApiConfiguration apiConfiguration;@Value("${upload.face}")private String face;@Value("${upload.excel}")private String excel;@Value("${upload.urlPrefix}")private String urlPrefix;/*** 实现数据库新增表中数据* 数据导入操作* @param fileName* @param session* @return*/@PostMapping("/parsefile/{fileName}")public Result parsefile(@PathVariable("fileName") String fileName, HttpSession session){User user = (User) session.getAttribute("user");//POIFSFileSystem 是 Apache POI 库中的核心类//专门用于处理 OLE 2 Compound Document Format(微软复合文档格式)//是 Java 开发中处理传统 Microsoft Office 文档(如 .xls, .doc, .ppt)的基础组件POIFSFileSystem fs = null;//HSSFWorkbook 是 Apache POI 库中处理 Microsoft Excel 格式(.xls 文件)的核心类// 作为 Horrible SpreadSheet Format 的缩写,它提供了完整的 API 来创建、读取和修改传统 Excel// 二进制文件。HSSFWorkbook wb = null;try{String basePath = excel + fileName;fs = new POIFSFileSystem(new FileInputStream(basePath));wb = new HSSFWorkbook(fs);}catch (Exception e){e.printStackTrace();}//这段代码的核心功能是从Excel中提取数据到二维数组,特别处理了第一列的数值转换。//获取工作簿中的第一个工作表HSSFSheet sheet = wb.getSheetAt(0);//声明二维数组用于存储数据Object[][] data = null;//获取总行数(索引从0开始)int r = sheet.getLastRowNum()+1;//获取第一行的列数int c = sheet.getRow(0).getLastCellNum();//表头行数(跳过前2行)int headRow = 2;// 创建二维数组(行数=总行数-表头行)data = new Object[r-headRow][c];// 从第3行开始(跳过表头)for(int i = headRow;i<r; i++){//获取当前行HSSFRow row = sheet.getRow(i);//遍历每列for(int j = 0; j < c; j++){HSSFCell cell = null;try {//获取单元格cell = row.getCell(j);try {cell = row.getCell(j);//使用DataFormatter将单元格值统一转为字符串DataFormatter dataFormatter = new DataFormatter();// 格式化单元格值为字符串String a = dataFormatter.formatCellValue(cell);// 存储到数组data[i-headRow][j] = a;}catch (Exception e){// 异常处理:当单元格读取失败时// 先设置为空字符串data[i-headRow][j] = "";// 特殊处理第一列(索引0)if(j == 0){try{// 尝试获取数值double d = cell.getNumericCellValue();// 转换为整数后存储为字符串data[i - headRow][j] = (int)d + "";}catch (Exception ex){// 如果转换失败,保持为空data[i - headRow][j] = "";}}}}catch (Exception e){System.out.println("i="+i+";j="+j+":"+e.getMessage());}}}int row = data.length;int col = 0;String errinfo = "";headRow = 3;//String[] stitle={"ID","小区名称","所属楼栋","房号","姓名","性别","手机号码","居住性质","状态","备注"};errinfo = "";for(int i = 0;i<row;i++){Person single = new Person();single.setPersonId(0);single.setState(1);single.setFaceUrl("");try{col = 1;String communityName = data[i][col++].toString();QueryWrapper<Community> queryWrapper = new QueryWrapper<>();queryWrapper.eq("community_name",communityName);Community community = this.communityService.getOne(queryWrapper);if(community == null){errinfo += "Excel文件第" + (i+headRow) + "行小区名称不存在!";return Result.ok().put("status","fail").put("data",errinfo);}single.setCommunityId(community.getCommunityId());single.setTermName(data[i][col++].toString());single.setHouseNo(data[i][col++].toString());single.setUserName(data[i][col++].toString());single.setSex(data[i][col++].toString());single.setMobile(data[i][col++].toString());single.setPersonType(data[i][col++].toString());single.setRemark(data[i][col++].toString());single.setCreater(user.getUsername());this.personService.save(single);}catch (Exception e){e.printStackTrace();}}return Result.ok().put("status","success").put("data","数据导入完成!");}
}
结果
2.系统管理
2.1、用户管理
提供查询和搜索用户、根据id查询用户信息、添加用户、修改用户、删除用户的功能
对应的代码
package com.qcby.controller;import cn.hutool.crypto.SecureUtil;
import com.qcby.DTO.UpdatePasswordFrom;
import com.qcby.DTO.UserListForm;
import com.qcby.entity.User;
import com.qcby.mapper.RoleMapper;
import com.qcby.service.IMenuService;
import com.qcby.service.IUserService;
import com.qcby.utils.Result;
import com.qcby.vo.MenuRouterVO;
import com.qcby.vo.PageVO;
import com.qcby.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpSession;
import java.util.List;@RestController
@RequestMapping("/sys/user")
public class UserController {@Autowiredprivate RoleMapper roleMapper;@Autowiredprivate IUserService userService;@Autowiredprivate IMenuService menuService;/*** 通过登录的用于加载动态路由* 显示该用户能访问的菜单* @param session* @return*/@RequestMapping("/getRouters")public Result getRouters(HttpSession session){//获取用户名称User user = (User)session.getAttribute("user");//获取用户的角色名称String roles = roleMapper.getRoleNameByUserId(user.getUserId());//获取用户的权限菜单List<MenuRouterVO> routers = this.menuService.getMenuRouterByUserId(user.getUserId());return Result.ok().put("data", user).put("roles", roles).put("routers",routers);}/*** 修改用户密码的控制器方法* @return 返回操作结果*/@RequestMapping("/updatePassword")public Result updatePassword(@RequestBody UpdatePasswordFrom updatePasswordFrom, HttpSession session){// 从session中获取当前登录的用户信息User user = (User)session.getAttribute("user");// 获取用户当前存储的密码String pwd = user.getPassword();// 使用SHA-256算法对用户输入的原密码进行加密String password = SecureUtil.sha256(updatePasswordFrom.getPassword());// 比较用户当前存储的密码和用户输入的原密码是否一致if(pwd.equals(password)){String newpassword = SecureUtil.sha256(updatePasswordFrom.getNewPassword());user.setPassword(newpassword);if(userService.updateById(user)){return Result.ok().put("status","success");}return Result.error("修改失败");}// 原密码验证失败return Result.ok().put("status","passwordError");}/*** 查询角色列表*/@RequestMapping("/list")public Result getUserList(UserListForm form) { // 接收分页和查询条件PageVO pageVO = userService.getUserListWithRole(form);return Result.ok().put("data", pageVO);}/*** 新增用户*/@PostMapping("/add")public Result add(@RequestBody UserVO userVO) {try {// 从UserVO中获取roleIdboolean success = userService.addUser(userVO, userVO.getRoleId());return success ? Result.ok() : Result.error("新增失败");} catch (RuntimeException e) {return Result.error(e.getMessage()); // 捕获用户名重复等异常}}/*** 根据ID查询用户详情(用于回显)*/@GetMapping("/info/{id}")public Result getUserInfo(@PathVariable Integer id) {UserVO userVO = userService.getUserInfoById(id);if (userVO == null) {return Result.error("用户不存在");}return Result.ok().put("data", userVO);}/*** 修改用户*/@PutMapping("/edit")public Result edit(@RequestBody UserVO userVO) {try {// 从UserVO中获取roleIdboolean success = userService.updateUser(userVO, userVO.getRoleId());return success ? Result.ok() : Result.error("修改失败");} catch (RuntimeException e) {return Result.error(e.getMessage());}}/*** 删除用户* 参数: 用户ID列表(JSON数组)*/@DeleteMapping("/del")public Result del(@RequestBody List<Integer> userIds) {boolean success = userService.deleteUsers(userIds);return success ? Result.ok() : Result.error("删除失败");}
}
package com.qcby.DTO;import lombok.Data;@Data
public class UserListForm {private Integer page = 1;private Integer limit = 10;private String username;private String startDate;private String endDate;private String mobile;private String realName;
}
package com.qcby.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("user")
public class User {@TableId(value = "user_id",type = IdType.AUTO)private Integer userId;private String username;private String password;private String realName;private String contact;private String mobile;private Integer status;
}
package com.qcby.entity;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("user_role") // 对应数据库表名
public class UserRole {@TableField("user_id")private Integer userId; // 用户ID(外键关联user表)@TableField("role_id")private Integer roleId; // 角色ID(外键关联role表)
}
package com.qcby.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;@Data
public class Role {@TableId(type = IdType.AUTO)private Integer roleId;private String roleName;private Integer type;private String remark;private String createTime;
}
package com.qcby.mapper;import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qcby.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qcby.vo.UserVO;
import org.apache.ibatis.annotations.Param;public interface UserMapper extends BaseMapper<User> {IPage<UserVO> selectUserVO(Page<UserVO> page, @Param(Constants.WRAPPER) Wrapper<UserVO> queryWrapper);Long selectUserVOCount(@Param(Constants.WRAPPER) Wrapper<UserVO> queryWrapper);// 新增:根据ID查询用户详情(含角色信息)UserVO selectUserVOById(@Param(Constants.WRAPPER) Wrapper<UserVO> queryWrapper);
}
package com.qcby.service.impl;import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcby.DTO.UserListForm;
import com.qcby.entity.User;
import com.qcby.entity.UserRole;
import com.qcby.mapper.UserMapper;
import com.qcby.mapper.UserRoleMapper;
import com.qcby.service.IUserService;
import com.qcby.vo.PageVO;
import com.qcby.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserRoleMapper userRoleMapper;@Overridepublic User getOne(QueryWrapper<User> queryWrapper) {return super.getOne(queryWrapper);}@Overridepublic PageVO getUserListWithRole(UserListForm form) {// 现有分页查询逻辑不变Page<UserVO> page = new Page<>(form.getPage(), form.getLimit());QueryWrapper<UserVO> queryWrapper = new QueryWrapper<>();if (StringUtils.isNotBlank(form.getUsername())) {queryWrapper.like("u.username", form.getUsername());}if (StringUtils.isNotBlank(form.getRealName())) {queryWrapper.like("u.real_name", form.getRealName());}// 确保查询条件正确应用IPage<UserVO> userPage = userMapper.selectUserVO(page, queryWrapper);Long total = userPage.getTotal();PageVO pageVO = new PageVO();pageVO.setList(userPage.getRecords());pageVO.setTotalPage((total + page.getSize() - 1) / page.getSize());pageVO.setCurrPage(userPage.getCurrent());pageVO.setPageSize(userPage.getSize());pageVO.setTotalCount(total);return pageVO;}/*** 新增用户(含角色关联)* 事务注解确保用户表和角色关联表操作原子性*/@Override@Transactional(rollbackFor = Exception.class)public boolean addUser(User user, Integer roleId) {// 1. 校验用户名唯一性QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", user.getUsername());User existUser = userMapper.selectOne(queryWrapper);if (existUser != null) {throw new RuntimeException("用户名已存在");}// 2. 密码加密(使用SHA-256,与修改密码逻辑一致)user.setPassword(SecureUtil.sha256(user.getPassword()));// 3. 保存用户信息到user表int insert = userMapper.insert(user);if (insert <= 0) {return false;}// 4. 保存用户-角色关联到user_role表UserRole userRole = new UserRole();userRole.setUserId(user.getUserId()); // 插入后自增ID会自动回填userRole.setRoleId(roleId);int roleInsert = userRoleMapper.insert(userRole);return roleInsert > 0;}/*** 修改用户(含角色关联更新)*/@Override@Transactional(rollbackFor = Exception.class)public boolean updateUser(User user, Integer roleId) {// 1. 校验用户是否存在User existUser = userMapper.selectById(user.getUserId());if (existUser == null) {throw new RuntimeException("用户不存在");}// 2. 处理密码:若传入密码不为空则加密更新,否则不更新密码if (StringUtils.isNotBlank(user.getPassword())) {user.setPassword(SecureUtil.sha256(user.getPassword()));} else {user.setPassword(null); // 避免覆盖原有密码}// 3. 更新用户表信息int update = userMapper.updateById(user);if (update <= 0) {return false;}// 4. 更新角色关联:先删除旧关联,再插入新关联userRoleMapper.deleteByUserId(user.getUserId());UserRole userRole = new UserRole();userRole.setUserId(user.getUserId());userRole.setRoleId(roleId);int roleInsert = userRoleMapper.insert(userRole);return roleInsert > 0;}/*** 批量删除用户(含角色关联删除)*/@Override@Transactional(rollbackFor = Exception.class)public boolean deleteUsers(List<Integer> userIds) {// 1. 先删除用户-角色关联userRoleMapper.deleteByUserIds(userIds);// 2. 再删除用户表记录int delete = userMapper.deleteBatchIds(userIds);return delete > 0;}/*** 根据ID查询用户详情(含角色ID)*/@Overridepublic UserVO getUserInfoById(Integer id) {// 调用mapper查询用户详情(包含角色信息)QueryWrapper<UserVO> queryWrapper = new QueryWrapper<>();queryWrapper.eq("u.user_id", id);return userMapper.selectUserVOById(queryWrapper);}
}
package com.qcby.service;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.DTO.UserListForm;
import com.qcby.entity.User;
import com.qcby.vo.PageVO;
import com.qcby.vo.UserVO;
import java.util.List;public interface IUserService extends IService<User> {User getOne(QueryWrapper<User> queryWrapper);PageVO getUserListWithRole(UserListForm form);// 新增用户(含角色关联)boolean addUser(User user, Integer roleId);// 修改用户(含角色关联更新)boolean updateUser(User user, Integer roleId);// 批量删除用户(含角色关联删除)boolean deleteUsers(List<Integer> userIds);// 根据ID查询用户详情(含角色ID)UserVO getUserInfoById(Integer id);
}
package com.qcby.vo;import com.qcby.entity.User;
import lombok.Data;@Data
public class UserVO extends User {private Integer roleId;private String roleName;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mapper.UserMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.qcby.entity.User"><id column="user_id" property="userId" /><result column="username" property="username" /><result column="password" property="password" /><result column="real_name" property="realName" /><result column="contact" property="contact" /><result column="mobile" property="mobile" /><result column="status" property="status" /></resultMap><!-- 用户VO结果映射(包含角色信息) --><resultMap id="UserVOResultMap" type="com.qcby.vo.UserVO" extends="BaseResultMap"><result column="role_id" property="roleId" /><result column="role_name" property="roleName" /></resultMap><!-- 查询用户列表带角色信息 --><select id="selectUserVO" resultMap="UserVOResultMap">select u.user_id, u.username, u.password, u.real_name,u.contact, u.mobile, u.status, r.role_id, r.role_name as role_namefrom `user` uleft join user_role ur on u.user_id = ur.user_idleft join role r on r.role_id = ur.role_id<where>${ew.customSqlSegment}</where></select><!-- 自定义计数查询 --><select id="selectUserVOCount" resultType="java.lang.Long">select count(distinct u.user_id)from `user` uleft join user_role ur on u.user_id = ur.user_idleft join role r on r.role_id = ur.role_id<where>${ew.customSqlSegment}</where></select><select id="selectUserVOById" resultMap="UserVOResultMap">select u.user_id, u.username, u.password, u.real_name,u.contact, u.mobile, u.status, r.role_id, r.role_name as role_namefrom `user` uleft join user_role ur on u.user_id = ur.user_idleft join role r on r.role_id = ur.role_id${ew.customSqlSegment}</select></mapper>
结果
新增
修改
删除
2.2、角色管理
提供查询和搜索角色、根据id查询角色信息、添加角色、修改角色、删除角色的功能
对应的代码
package com.qcby.controller;import com.qcby.DTO.RoleListForm;
import com.qcby.entity.Role;
import com.qcby.service.IRoleService;
import com.qcby.utils.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/sys/role")
public class RoleController {private static final Logger log = LoggerFactory.getLogger(RoleController.class);@Autowiredprivate IRoleService roleService;// 添加角色@PostMapping("/add")public Result add(@RequestBody Map<String, Object> params) {// log.info("=== 开始处理添加角色请求 ===");// log.info("接收的原始参数: {}", params);// 解析参数Role role = new Role();role.setRoleName((String) params.get("roleName"));role.setType((Integer) params.get("type"));role.setRemark((String) params.get("remark"));List<Integer> menuIdList = (List<Integer>) params.get("menuIdList");// log.info("解析后的角色信息: roleName={}, type={}, remark={}",// role.getRoleName(), role.getType(), role.getRemark());// log.info("解析后的菜单ID列表: {}", menuIdList);boolean success = roleService.saveRole(role, menuIdList);// log.info("添加角色结果: {}", success ? "成功" : "失败");return success ? Result.ok() : Result.error("添加失败");}// 修改角色@PutMapping("/edit")public Result edit(@RequestBody Map<String, Object> params) {// log.info("=== 开始处理修改角色请求 ===");// log.info("接收的原始参数: {}", params);// 解析参数Role role = new Role();role.setRoleId((Integer) params.get("roleId"));role.setRoleName((String) params.get("roleName"));role.setType((Integer) params.get("type"));role.setRemark((String) params.get("remark"));List<Integer> menuIdList = (List<Integer>) params.get("menuIdList");// log.info("解析后的角色ID: {}", role.getRoleId());// log.info("解析后的菜单ID列表: {}", menuIdList);boolean success = roleService.updateRole(role, menuIdList);// log.info("修改角色结果: {}", success ? "成功" : "失败");return success ? Result.ok() : Result.error("修改失败");}// 查询角色详情@GetMapping("/info/{roleId}")public Result info(@PathVariable Integer roleId) {// log.info("=== 开始查询角色详情 ===");// log.info("查询的角色ID: {}", roleId);Map<String, Object> roleInfo = roleService.getRoleInfo(roleId);// log.info("查询到的角色详情: {}", roleInfo);// log.info("其中菜单ID列表: {}", roleInfo != null ? roleInfo.get("menuIdList") : "null");return roleInfo != null ? Result.ok().put("data", roleInfo) : Result.error("角色不存在");}@GetMapping("/getRoleList")public Result getRoleListForSelect() {List<Role> roles = roleService.list();return Result.ok().put("data", roles);}@GetMapping("/list")public Result list(@Validated RoleListForm roleListForm) {Map<String, Object> pageResult = roleService.getPageList(roleListForm);return Result.ok().put("data", pageResult);}@DeleteMapping("/del")public Result del(@RequestBody List<Integer> roleIds) {boolean success = roleService.removeRoles(roleIds);return success ? Result.ok() : Result.error("删除失败");}
}
package com.qcby.DTO;import lombok.Data;@Data
public class RoleListForm {private Integer page = 1;private Integer limit = 10;private String roleName;
}
package com.qcby.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;@Data
public class Role {@TableId(type = IdType.AUTO)private Integer roleId;private String roleName;private Integer type;private String remark;private String createTime;
}
package com.qcby.mapper;import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.qcby.entity.Role;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;@Repository
public interface RoleMapper extends BaseMapper<Role> {@Select("SELECT role_name FROM role, user_role where user_role.role_id=role.role_id and user_role.user_id=#{userId}")public String getRoleNameByUserId(Integer userId);IPage<Role> selectRolePage(IPage<Role> page, @Param(Constants.WRAPPER) Wrapper<Role> queryWrapper);
}
package com.qcby.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcby.DTO.RoleListForm;
import com.qcby.entity.Role;
import com.qcby.entity.RoleMenu;
import com.qcby.mapper.RoleMapper;
import com.qcby.mapper.RoleMenuMapper;
import com.qcby.mapper.UserRoleMapper;
import com.qcby.service.IRoleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {private static final Logger log = LoggerFactory.getLogger(RoleServiceImpl.class);@Autowiredprivate RoleMapper roleMapper;@Autowiredprivate RoleMenuMapper roleMenuMapper;@Autowiredprivate UserRoleMapper userRoleMapper;// 添加角色(含菜单关联)@Override@Transactional(rollbackFor = Exception.class)public boolean saveRole(Role role, List<Integer> menuIdList) {// log.info("=== 开始执行添加角色业务逻辑 ===");role.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 保存角色基本信息boolean save = save(role);// log.info("角色基本信息保存结果: {}", save);// log.info("保存后生成的角色ID: {}", role.getRoleId());if (!save) {// log.error("角色基本信息保存失败");return false;}// 处理菜单关联// log.info("原始菜单ID列表: {}", menuIdList);if (menuIdList != null && !menuIdList.isEmpty()) {// 过滤无效IDList<Integer> validMenuIds = menuIdList.stream().filter(menuId -> menuId != null).collect(Collectors.toList());// log.info("过滤后的有效菜单ID列表: {}", validMenuIds);if (!validMenuIds.isEmpty()) {List<RoleMenu> roleMenuList = new ArrayList<>();for (Integer menuId : validMenuIds) {RoleMenu roleMenu = new RoleMenu();roleMenu.setRoleId(role.getRoleId());roleMenu.setMenuId(menuId);roleMenuList.add(roleMenu);}// log.info("准备插入的角色-菜单关联数据: {}", roleMenuList);// 执行批量插入int insertCount = roleMenuMapper.batchInsert(roleMenuList);// log.info("角色-菜单关联数据插入条数: {}", insertCount);if (insertCount != validMenuIds.size()) {// log.warn("插入的关联数据条数与预期不符,预期: {}, 实际: {}", validMenuIds.size(), insertCount);}} else {// log.warn("没有有效的菜单ID,不执行关联插入");}} else {// log.warn("菜单ID列表为空,不执行关联插入");}return true;}// 查询角色详情(含菜单ID列表)@Overridepublic Map<String, Object> getRoleInfo(Integer roleId) {// log.info("=== 开始查询角色详情业务逻辑 ===");// log.info("查询的角色ID: {}", roleId);Map<String, Object> result = new HashMap<>();Role role = getById(roleId);// log.info("查询到的角色基本信息: {}", role);if (role == null) {// log.error("未查询到角色信息,角色ID: {}", roleId);return null;}// 查询关联的菜单ID列表List<Integer> menuIdList = roleMenuMapper.selectMenuIdsByRoleId(roleId);// log.info("从数据库查询到的菜单ID列表: {}", menuIdList);result.put("roleId", role.getRoleId());result.put("roleName", role.getRoleName());result.put("type", role.getType());result.put("remark", role.getRemark());result.put("createTime", role.getCreateTime());result.put("menuIdList", menuIdList);return result;}// 更新角色(含菜单关联)@Override@Transactional(rollbackFor = Exception.class)public boolean updateRole(Role role, List<Integer> menuIdList) {// log.info("=== 开始执行更新角色业务逻辑 ===");// log.info("更新的角色ID: {}", role.getRoleId());// 更新角色基本信息boolean update = updateById(role);// log.info("角色基本信息更新结果: {}", update);if (!update) {// log.error("角色基本信息更新失败");return false;}// 先删除旧的关联关系int deleteCount = roleMenuMapper.deleteByRoleId(role.getRoleId());// log.info("删除旧的角色-菜单关联数据条数: {}", deleteCount);// 处理新的菜单关联// log.info("新的菜单ID列表: {}", menuIdList);if (menuIdList != null && !menuIdList.isEmpty()) {List<Integer> validMenuIds = menuIdList.stream().filter(menuId -> menuId != null).collect(Collectors.toList());// log.info("过滤后的有效菜单ID列表: {}", validMenuIds);if (!validMenuIds.isEmpty()) {List<RoleMenu> roleMenuList = new ArrayList<>();for (Integer menuId : validMenuIds) {RoleMenu roleMenu = new RoleMenu();roleMenu.setRoleId(role.getRoleId());roleMenu.setMenuId(menuId);roleMenuList.add(roleMenu);}// log.info("准备插入的新角色-菜单关联数据: {}", roleMenuList);int insertCount = roleMenuMapper.batchInsert(roleMenuList);// log.info("新角色-菜单关联数据插入条数: {}", insertCount);} else {// log.warn("没有有效的新菜单ID,不执行关联插入");}} else {// log.warn("新的菜单ID列表为空,不执行关联插入");}return true;}// 其他方法保持不变...@Overridepublic Map<String, Object> getPageList(RoleListForm roleListForm) {Integer page = roleListForm.getPage();Integer limit = roleListForm.getLimit();String roleName = roleListForm.getRoleName();IPage<Role> pageParam = new Page<>(page, limit);QueryWrapper<Role> queryWrapper = new QueryWrapper<>();if (roleName != null && !roleName.isEmpty()) {queryWrapper.like("role_name", roleName);}IPage<Role> rolePage = roleMapper.selectRolePage(pageParam, queryWrapper);Map<String, Object> pageResult = new HashMap<>();pageResult.put("totalCount", rolePage.getTotal());pageResult.put("pageSize", rolePage.getSize());pageResult.put("totalPage", rolePage.getPages());pageResult.put("currPage", rolePage.getCurrent());pageResult.put("list", rolePage.getRecords());return pageResult;}@Override@Transactional(rollbackFor = Exception.class)public boolean removeRoles(List<Integer> roleIds) {userRoleMapper.deleteByRoleIds(roleIds);for (Integer roleId : roleIds) {roleMenuMapper.deleteByRoleId(roleId);}return removeByIds(roleIds);}
}
package com.qcby.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.DTO.RoleListForm;
import com.qcby.entity.Role;import java.util.List;
import java.util.Map;public interface IRoleService extends IService<Role> {// 分页查询角色列表(使用ListForm接收参数)Map<String, Object> getPageList(RoleListForm roleListForm);// 添加角色(含菜单关联)boolean saveRole(Role role, List<Integer> menuIdList);// 根据角色ID查询角色详情(含菜单ID列表)Map<String, Object> getRoleInfo(Integer roleId);// 更新角色(含菜单关联)boolean updateRole(Role role, List<Integer> menuIdList);// 批量删除角色(含关联关系清理)boolean removeRoles(List<Integer> roleIds);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mapper.RoleMapper"><!-- 1. 通用查询映射结果(供BaseMapper默认方法使用) --><resultMap id="BaseResultMap" type="com.qcby.entity.Role"><id column="role_id" property="roleId" jdbcType="INTEGER"/><result column="role_name" property="roleName" jdbcType="VARCHAR"/><result column="type" property="type" jdbcType="INTEGER"/><result column="remark" property="remark" jdbcType="VARCHAR"/><result column="create_time" property="createTime" jdbcType="VARCHAR"/></resultMap><!-- 2. 通用查询列(供BaseMapper方法使用) --><sql id="Base_Column_List">role_id, role_name, type, remark, create_time</sql><!-- 3. 分页查询角色列表(自定义方法) --><select id="selectRolePage" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List"/>FROMrole${ew.customSqlSegment}</select><!-- 4. 支持BaseMapper的selectById方法 --><select id="selectById" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List"/>FROM roleWHERE role_id = #{id}</select><update id="updateById" parameterType="com.qcby.entity.Role">UPDATE role<set><if test="et.roleName != null and et.roleName != ''">role_name = #{et.roleName},</if><if test="et.type != null">type = #{et.type},</if><if test="et.remark != null and et.remark != ''">remark = #{et.remark},</if></set>WHERE role_id = #{et.roleId}</update><delete id="deleteBatchIds" parameterType="java.util.List">DELETE FROM role WHERE role_id IN<foreach collection="coll" item="id" open="(" separator="," close=")">#{id}</foreach></delete></mapper>
结果
新增
修改
删除
2.3、菜单管理
提供查询和搜索菜单、根据id查询菜单信息、添加菜单、删除菜单的功能
对应的代码
package com.qcby.controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qcby.entity.Menu;
import com.qcby.entity.RoleMenu;
import com.qcby.service.IMenuService;
import com.qcby.service.RoleMenuService;
import com.qcby.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/sys/menu")
public class MenuController {@Autowiredprivate IMenuService menuService;@Autowiredprivate RoleMenuService roleMenuService;// 菜单列表查询(树形结构)@GetMapping("/list")public Result list() {List<Menu> menuTreeList = menuService.getMenuTreeList();return Result.ok().put("data", menuTreeList);}// 添加菜单@PostMapping("/add")public Result add(@RequestBody Menu menu) {boolean save = menuService.save(menu);return save ? Result.ok() : Result.error("添加失败");}// 修改菜单@PutMapping("/edit")public Result edit(@RequestBody Menu menu) {boolean update = menuService.updateById(menu);return update ? Result.ok() : Result.error("修改失败");}// 删除菜单@DeleteMapping("/del/{id}")public Result del(@PathVariable("id") Integer id){//构造条件QueryWrapper<Menu> queryWrapper1 = new QueryWrapper<>();queryWrapper1.eq("parent_id", id);//1.通过父id查询数据库返回所有的子id进行删除 使用in关键字//删除子级菜单和role之间的关系QueryWrapper<RoleMenu> queryWrapper = new QueryWrapper<>();List<Menu> menuList = this.menuService.list(queryWrapper1);for (Menu menu : menuList) {QueryWrapper<RoleMenu> queryWrapper2 = new QueryWrapper<>();queryWrapper.in("menu_id", menu.getMenuId());this.roleMenuService.remove(queryWrapper2);}//删除子级菜单this.menuService.remove(queryWrapper1);//删除父级菜单维护关系queryWrapper.eq("menu_id", id);this.roleMenuService.remove(queryWrapper);//删除父级菜单boolean remove = this.menuService.removeById(id);if(remove) return Result.ok();return Result.error("删除菜单失败");}// 根据ID查询菜单详情@GetMapping("/info/{id}")public Result getInfo(@PathVariable Integer id) {Menu menu = menuService.getById(id);return Result.ok().put("data", menu);}
}
package com.qcby.entity;import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;import java.util.List;@Data
public class Menu {@JsonProperty("id") // 指定JSON序列化时的字段名为idprivate Integer menuId;private Integer parentId;private String name;private String path;private String component;private Integer menuType;private Integer status;private String icon;private Integer sort;private String hidden;@TableField(exist = false)private List<Menu> children;
}
package com.qcby.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qcby.entity.Menu;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface MenuMapper extends BaseMapper<Menu> {@Select({"select m.menu_id,m.parent_id,m.name,m.path,m.component," +"m.menu_type,m.status,m.icon,m.sort,m.hidden from " +"user_role ur,role_menu rm,menu m where ur.role_id = rm.role_id" +" and rm.menu_id = m.menu_id " +"and ur.user_id = #{userId} order by m.sort"})public List<Menu> getMenusByUserId(Integer userId);List<Menu> selectAllMenus();}
package com.qcby.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcby.entity.Menu;
import com.qcby.mapper.MenuMapper;
import com.qcby.service.IMenuService;
import com.qcby.vo.ChildMenuRouterVO;
import com.qcby.vo.MenuRouterVO;
import com.qcby.vo.MetaVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {@Autowiredprivate MenuMapper menuMapper;/*** 重写接口方法,根据用户ID获取菜单路由信息* @param userId 用户ID* @return 菜单路由信息列表*/@Overridepublic List<MenuRouterVO> getMenuRouterByUserId(Integer userId) {// 1. 根据用户ID查询该用户拥有的角色及对应的所有菜单列表List<Menu> menuList = this.menuMapper.getMenusByUserId(userId);// 2. 创建最终要返回的菜单路由VO集合(VO用于前端展示的数据模型)List<MenuRouterVO> list = new ArrayList<>();// 3. 遍历所有菜单,筛选出一级菜单(父菜单ID为0)并封装成MenuRouterVOfor (Menu menu : menuList) {// 筛选一级菜单(parentId为0表示是顶级菜单)if (menu.getParentId() == 0) {// 创建一级菜单路由VO对象MenuRouterVO menuRouterVO = new MenuRouterVO();// 复制Menu对象的属性到MenuRouterVO(使用框架提供的属性拷贝工具类)BeanUtils.copyProperties(menu, menuRouterVO);// 封装Meta信息(前端显示需要的标题和图标)MetaVO metaVO = new MetaVO();metaVO.setTitle(menu.getName()); // 设置菜单标题metaVO.setIcon(menu.getIcon()); // 设置菜单图标menuRouterVO.setMeta(metaVO); // 将meta信息设置到路由对象// 获取当前一级菜单的ID,用于匹配其子菜单Integer menuId = menu.getMenuId();// 4. 创建子菜单集合,用于存储当前一级菜单下的所有子菜单List<ChildMenuRouterVO> children = new ArrayList<>();// 遍历所有菜单,筛选出属于当前一级菜单的子菜单for (Menu child : menuList) {// 判断当前菜单是否为当前一级菜单的子菜单(子菜单的parentId等于父菜单的menuId)if(child.getParentId() == menuId){// 5. 创建子菜单路由VO对象并封装数据ChildMenuRouterVO childVO = new ChildMenuRouterVO();BeanUtils.copyProperties(child, childVO); // 复制基本属性// 封装子菜单的Meta信息MetaVO childMetaVO = new MetaVO();childMetaVO.setTitle(child.getName()); // 子菜单标题childMetaVO.setIcon(child.getIcon()); // 子菜单图标childVO.setMeta(childMetaVO); // 设置子菜单的meta信息// 将子菜单添加到子菜单集合children.add(childVO);}}// 6. 将子菜单集合设置到当前一级菜单路由对象中menuRouterVO.setChildren(children);// 7. 将封装好的一级菜单路由对象添加到最终返回的集合中list.add(menuRouterVO);}}// 返回整理好的菜单路由列表(包含一级菜单和对应的子菜单)return list;}// /**
// * 按照要求返回指定格式的
// * 用户可以访问的所有菜单
// * 数据库查询所有菜单在按要求封装
// * @param userId
// * @return
// */
// @Override
// public List<MenuRouterVO> getMenuRouterByUserId(Integer userId) {
// //1.根据用户的id查询该用所对应的角色以及该角色所对应的菜单
// List<Menu> menuList=menuMapper.getMenusByUserId(userId);
// //2.创建一个集合List<MenuRouterVO> 最终的集合
// List<MenuRouterVO> list=new ArrayList<MenuRouterVO>();
// //3.遍历该用户所能查看的所有菜单找到一级菜单封装进MenuRouterVO
// for (Menu menu : menuList) {
// //前提prentId=0才可以
// if(menu.getParentId()==0){
// //创建一个新的父级菜单对象
// MenuRouterVO menuRouterVO = new MenuRouterVO();
// //给父级菜单对象赋值 bean实体类的封装工具类 框架提供的
// //将指定对象中的相同属性赋值给新对象(目标对象)
// BeanUtils.copyProperties(menu,menuRouterVO);
// //再将没有的属性进行赋值
// MetaVO metaVO = new MetaVO();
// metaVO.setTitle(menu.getName());
// metaVO.setIcon(menu.getIcon());
// menuRouterVO.setMeta(metaVO);
// //父级菜单的最后一个属性children 赋值
// List<ChildMenuRouterVO> children = new ArrayList<>();
// //生成children
// Integer menuId = menu.getMenuId();
// //二次遍历 找子菜单
// //4.不是一级菜单的继续遍历找到属于哪个一级菜单下挂在该菜单下
// for (Menu child : menuList) {
// if(child.getParentId() == menuId){
// //5.封装子菜单ChildMenuRouterVO 在放进集合List<ChildMenuRouterVO>
// ChildMenuRouterVO childVO = new ChildMenuRouterVO();
// BeanUtils.copyProperties(child, childVO);
// MetaVO childMetaVO = new MetaVO();
// childMetaVO.setTitle(child.getName());
// childMetaVO.setIcon(child.getIcon());
// childVO.setMeta(childMetaVO);
// children.add(childVO);
// }
// }
// //6.将子菜单集合挂在MenuRouterVO的children的集合属性下
// menuRouterVO.setChildren(children);
// //7.将每一个MenuRouterVO放进大集合
// list.add(menuRouterVO);
// }
// }
// //8.返回外层大集合List<MenuRouterVO>
// return list;
// }// 获取菜单树形结构@Overridepublic List<Menu> getMenuTreeList() {// 强制使用自定义查询方法,避免查询无效字段List<Menu> allMenus = baseMapper.selectAllMenus();return buildMenuTree(allMenus, 0);}private List<Menu> buildMenuTree(List<Menu> allMenus, Integer parentId) {return allMenus.stream().filter(menu -> parentId.equals(menu.getParentId())).map(menu -> {menu.setChildren(buildMenuTree(allMenus, menu.getMenuId()));return menu;}).collect(Collectors.toList());}
}
package com.qcby.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.entity.Menu;
import com.qcby.vo.MenuRouterVO;import java.util.List;public interface IMenuService extends IService<Menu> {List<MenuRouterVO> getMenuRouterByUserId(Integer userId);List<Menu> getMenuTreeList();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mapper.MenuMapper"><resultMap id="BaseResultMap" type="com.qcby.entity.Menu"><id column="menu_id" property="menuId" /><result column="parent_id" property="parentId" /><result column="name" property="name" /><result column="path" property="path" /><result column="component" property="component" /><result column="menu_type" property="menuType" /><result column="status" property="status" /><result column="icon" property="icon" /><result column="sort" property="sort" /><result column="hidden" property="hidden" /></resultMap><!-- 自定义selectById方法 --><select id="selectById" parameterType="java.lang.Integer" resultMap="BaseResultMap">SELECTmenu_id, parent_id, name, path, component,menu_type, status, icon, sort, hiddenFROM menuWHERE menu_id = #{et.id}</select><select id="selectAllMenus" resultMap="BaseResultMap">SELECTmenu_id, parent_id, name, path, component,menu_type, status, icon, sort, hiddenFROM menuORDER BY sort ASC</select><update id="updateById" parameterType="com.qcby.entity.Menu">UPDATE menu<set><if test="et.parentId != null">parent_id = #{et.parentId},</if><if test="et.name != null">name = #{et.name},</if><if test="et.path != null">path = #{et.path},</if><if test="et.component != null">component = #{et.component},</if><if test="et.menuType != null">menu_type = #{et.menuType},</if><if test="et.status != null">status = #{et.status},</if><if test="et.icon != null">icon = #{et.icon},</if><if test="et.sort != null">sort = #{et.sort},</if><if test="et.hidden != null">hidden = #{et.hidden},</if></set>WHERE menu_id = #{et.menuId}</update><delete id="deleteById" parameterType="java.lang.Integer">DELETE FROM menuWHERE menu_id = #{id}</delete>
</mapper>
结果
新增
修改
删除
2.4、日志管理
日志(log)数据表设计
存储操作的日志信息
我们要自己去手动的定义那个方法加日志,那个方法不加日志使用SpringAOP技术,对方法进行加强
添加日志的注解类
旨在提供一种声明式的方式来表示某个方法可能需要特定的日志记录行为
package com.qcby.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD) //这个元注解表示LogAnnotation只能被用于方法上。
@Retention(RetentionPolicy.RUNTIME) //这个元注解表示LogAnnotation的生命周期是运行时。也就是说,这个注解不仅会被保留在类文件中,还会在JVM加载类时保留,因此可以在运行时通过反射读取它。
@Documented //这个元注解表示如果某个元素(类、方法、变量等)使用了LogAnnotation,那么在使用javadoc生成API文档时,这个注解也会被包含在生成的文档中。
public @interface LogAnnotation {String value() default "";
}
方法上添加日志注解
当方法上添加日志注解后,该方法在调用时就会生成相应的日志信息。当在添加小区的方法上添加日志注解后,如果调用添加小区这个方法,就会生成相应的添加小区的日志信息,如:
Log切面类
自动记录带有@LogAnnotation注解的方法的日志信息。
package com.qcby.aspect;import com.google.gson.Gson;
import com.qcby.annotation.LogAnnotation;
import com.qcby.entity.Log;
import com.qcby.entity.User;
import com.qcby.service.LogService;
import com.qcby.utils.HttpContextUtil;
import com.qcby.utils.IPUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;@Aspect
@Component
public class LogAspect {@Autowiredprivate LogService logService;public static User user; // 保留静态变量user@Pointcut("@annotation(com.qcby.annotation.LogAnnotation)")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {// 从当前请求的session中获取用户信息并赋值给静态变量userHttpServletRequest request = HttpContextUtil.getHttpServletRequest();HttpSession session = request.getSession();user = (User) session.getAttribute("user");long beginTime = System.currentTimeMillis();//执行方法Object result = point.proceed();//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;//保存日志saveLog(point, (int) time);// 清除静态变量,避免线程安全问题user = null;return result;}private void saveLog(ProceedingJoinPoint joinPoint, int time) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Log log = new Log();LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);if (logAnnotation != null) {//注解上的描述log.setOperation(logAnnotation.value());}//请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();log.setMethod(className + "." + methodName + "()");//请求的参数Object[] args = joinPoint.getArgs();try {String params = new Gson().toJson(args[0]);log.setParams(params);} catch (Exception e) {}HttpServletRequest request = HttpContextUtil.getHttpServletRequest();log.setIp(IPUtil.getIpAddr(request));log.setTime(time);//登录用户信息if (user != null) {log.setUsername(user.getUsername());this.logService.save(log);}}}
提供日志搜索查看和删除功能
package com.qcby.controller;import com.qcby.DTO.LogListForm;
import com.qcby.service.LogService;
import com.qcby.utils.Result;
import com.qcby.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.Arrays;@RestController
@RequestMapping("/sys/log")
public class LogController {@Autowiredprivate LogService logService;/*** 日志分页查询(直接获取封装好的PageVO)*/@GetMapping("/list")public Result list(LogListForm form) {// 调用Service层的方法,直接获取PageVOPageVO pageVO = logService.getLogPage(form);// 将分页数据放入返回结果return Result.ok().put("data", pageVO);}/*** 批量删除日志*/@DeleteMapping("/del")public Result delete(@RequestBody Integer[] ids) {boolean success = logService.removeByIds(Arrays.asList(ids));return success ? Result.ok() : Result.error("删除失败");}
}
package com.qcby.DTO;import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;@Data
public class LogListForm {private Integer page = 1;private Integer limit = 10;private String username;private String operation;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date startDate;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date endDate;
}
package com.qcby.entity;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;import java.util.Date;@Data
public class Log {// 日志ID,主键private Integer logId;// 用户名private String username;// 用户操作private String operation;// 请求方法private String method;// 请求参数private String params;// 执行时长(毫秒)private Integer time;// IP地址private String ip;// 创建时间@TableField(fill = FieldFill.INSERT)private Date createTime;
}
package com.qcby.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcby.DTO.LogListForm;
import com.qcby.entity.Log;
import com.qcby.mapper.LogMapper;
import com.qcby.service.LogService;
import com.qcby.vo.LogVO;
import com.qcby.vo.PageVO;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;@Service
public class LogServiceImpl extends ServiceImpl<LogMapper, Log> implements LogService {/*** 日志分页查询实现(包含实体转换和分页封装)*/@Overridepublic PageVO getLogPage(LogListForm form) {// 1. 创建分页对象Page<Log> page = new Page<>(form.getPage(), form.getLimit());// 2. 构建查询条件QueryWrapper<Log> queryWrapper = new QueryWrapper<>();// 条件过滤if (form.getUsername() != null && !form.getUsername().isEmpty()) {queryWrapper.like("username", form.getUsername());}if (form.getOperation() != null && !form.getOperation().isEmpty()) {queryWrapper.like("operation", form.getOperation());}if (form.getStartDate() != null) {queryWrapper.ge("create_time", form.getStartDate());}if (form.getEndDate() != null) {queryWrapper.le("create_time", form.getEndDate());}// 排序:按创建时间倒序queryWrapper.orderByDesc("create_time");// 3. 执行分页查询IPage<Log> logPage = baseMapper.selectPage(page, queryWrapper);// 4. 将Log实体转换为LogVOList<LogVO> logVOList = logPage.getRecords().stream().map(log -> {LogVO logVO = new LogVO();BeanUtils.copyProperties(log, logVO);return logVO;}).collect(Collectors.toList());// 5. 封装分页结果VOPageVO pageVO = new PageVO();pageVO.setTotalCount(logPage.getTotal());pageVO.setTotalPage(logPage.getPages());pageVO.setPageSize(logPage.getSize());pageVO.setCurrPage(logPage.getCurrent());pageVO.setList(logVOList); // 设置转换后的VO列表return pageVO;}
}
package com.qcby.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.DTO.LogListForm;
import com.qcby.entity.Log;
import com.qcby.vo.PageVO;public interface LogService extends IService<Log> {PageVO getLogPage(LogListForm form);
}
package com.qcby.vo;import lombok.Data;import java.util.Date;@Data
public class LogVO {private Integer logId;private String username;private String operation;private String params;private Integer time;private String ip;private Date createTime;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mapper.LogMapper"><!-- 根据ID批量删除日志记录 --><delete id="deleteBatchIds" parameterType="java.util.List">DELETE FROM logWHERE log_id IN<foreach collection="coll" item="id" open="(" separator="," close=")">#{id}</foreach></delete></mapper>
界面
删除