1.实现Token校验
## Token校验URL```json
GET /checkToken
```参数```json
HttpServletRequest request
```返回```json
{"msg": "操作成功","code": 200,"status": "ok"
}{"msg": "操作成功","code": 200,"status": "error"
}
在LoginController中实现下面得方法
/*** Token校验* @param request HTTP请求* @return 校验结果*/@RequestMapping("/checkToken")public Result checkToken(HttpServletRequest request) {//1.从请求头中获取tokenString token = request.getHeader("token");System.out.println("从请求头获取到的token:" + token);//2.判断token是否为空或空字符串,如果是则返回错误信息if (token == null || token.isEmpty()) {System.out.println("token为空或空字符串,验证失败");return Result.ok().put("status", "error").put("msg", "token不能为空");}//3.声明变量用于标记JWT格式是否有效boolean isJwtValid;//4.声明变量用于存储从token中解析出的userIdString userId = null;try {//5.使用JWT工具类解析token,获取负载信息Claims claims = Jwts.parser().setSigningKey(jwtUtil.getSecret()).parseClaimsJws(token).getBody();//6.从负载中获取userIduserId = claims.get("userId", String.class);System.out.println("从token中解析出的userId:" + userId);//7.如果解析成功,标记JWT格式有效isJwtValid = true;System.out.println("JWT格式验证通过");} catch (Exception e) {//8.如果解析失败,标记JWT格式无效isJwtValid = false;System.out.println("JWT格式验证失败,异常信息:" + e.getMessage());}//9.如果JWT格式无效,返回错误信息if (!isJwtValid) {System.out.println("JWT格式无效,返回错误响应");return Result.ok().put("status", "error").put("msg", "token格式无效");}//10.根据userId拼接Redis中存储token的keyString redisKey = "communityuser-" + userId;System.out.println("Redis中存储token的key:" + redisKey);//11.从Redis中获取存储的tokenString redisToken = (String) redisTemplate.opsForValue().get(redisKey);System.out.println("从Redis中获取到的token:" + redisToken);//12.判断Redis中是否存在token且与请求中的token一致boolean isTokenValid = redisToken != null && redisToken.equals(token);System.out.println("Redis中token与请求token是否一致:" + isTokenValid);//13.根据双重验证结果返回对应的响应信息if (isTokenValid) {System.out.println("token双重验证通过,返回成功响应");return Result.ok().put("status", "ok").put("msg", "token验证通过");} else {System.out.println("token双重验证失败,返回错误响应");return Result.ok().put("status", "error").put("msg", "token已失效或不匹配");}}
利用上面得Token校验实现动态路由
加载动态路由
实现思路
1.通过session获取用户信息
2.根据userId获取角色名称,需要在user_role表和role表中联表查询
3.根据userId获取用户的权限菜单:
第一步:根据用户的id查询该用户所对应的角色以及该角色所对应的菜单,需要user_role、user_menu、menu三个表联表查询;
第二步:按照查询出来的菜单进行封装,一个一级菜单的信息封装进一个列表,此菜单下的二级菜单的信息封装进此列表的子列表中,若有三级菜单以此类推进行封装
④返回用户信息、角色名称和用户的权限菜单信息,格式如
```json
{"msg": "操作成功","code": 200,"data": {"userId": 1,"username": "admin", "password": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92","realName": "管理员", "contact": "", "mobile": "15679711120", "status": 1},"roles": "超级管理员","routers": [{"name": "系统管理","path": "/sys","hidden": "false","redirect": "noRedirect","component": "Layout","alwaysShow": true,"meta": {"title": "系统管理","icon": "system"},"children": [{"name": "管理员管理","path": "/user","hidden": "false","component": "sys/user/index","meta": {"title": "管理员管理","icon": "user"}}]}]
}
封装返回的routers信息MenuRouterVO、ChildMenuRouterVO、MenuRouterVO
package com.qcby.vo;import lombok.Data;import java.util.List;@Data
public class MenuRouterVO {private String name;private String path;private String component;private String hidden;private String redirect = "noRedirect";private Boolean alwaysShow = true;private MetaVO meta;private List<ChildMenuRouterVO> children;}
package com.qcby.vo;import lombok.Data;@Data
public class ChildMenuRouterVO {private String name;private String path;private String component;private String hidden;private MetaVO meta;}
package com.qcby.vo;import lombok.Data;@Data
public class MetaVO {private String title;private String icon;
}
加载动态路由controller请求
/*** 通过登录的用于加载动态路由* 显示该用户能访问的菜单* @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);}
获取用户的角色名称的mapper
@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);
获取用户的菜单信息service
/*** 重写接口方法,根据用户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;}
获取用户的菜单信息mapper
@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);
结果图
登录进去之后就能够看到一级目录
每个目录下面有对应得子目录
2.修改密码
实现思路
①发送更新密码请求,弹出更新密码弹出层
②前端密码格式验证,新旧密码是否一致验证
③修改密码:第一步获取session中的用户信息;第二步将根据用户查询的密码和前端传来的旧密码进行比较,如果相等,将新密码加密后在数据库中更新密码字段信息,密码更新成功,返回。
修改密码请求处理
/*** 修改用户密码的控制器方法* @return 返回操作结果*/@PutMapping("/updatePassword") // 使用PUT映射处理/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");}
密码修改表单实体类UpdatePasswordFrom
package com.qcby.DTO;import lombok.Data;/*** 密码修改表单实体类* 用于接收前端传递的旧密码修改相关参数*/
@Data
public class UpdatePasswordFrom {/*** 当前密码(旧密码)*/private String password;/*** 新密码*/private String newPassword;/*** 确认新密码(可选,用于前端二次验证)*/private String confirmPassword;
}
结果
3.退出登录模块
实现思路
①登出请求:将当前session设置为无效
②将token设置为空
③将router设置为空
④将cookie里面的token信息清空
⑤返回登录页面
登出请求处理
/*** 用户退出* @param session* @return*/@RequestMapping("/logout")public Result logOut(HttpSession session){session.invalidate();return Result.ok();}
结果图
4.小区的增删改查
对应的实体类
package com.qcby.entity;import lombok.Data;import java.util.Date;@Data
public class Community {private Integer communityId;private String communityName;private Integer termCount;private Integer seq;private String creater;private Date createTime;private Float lng;private Float lat;
}
服务层
package com.qcby.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.DTO.CommunityListFrom;
import com.qcby.entity.Community;
import com.qcby.vo.PageVO;public interface ICommunityService extends IService<Community> {PageVO communityList(CommunityListFrom communityListForm);
}
增删改查的实现思路
对于传入参数,如果有,但是参数不多,只有一个,直接接收;如果参数较多,应将其传入字段封装在一个实体类中。对于get请求,用@PathVariable接收,对于非get请求,用@RequestBody接收。
对于返回参数,如果与数据单表中的字段可以进行一一映射,不需要考虑封装,直接返回;如果无法在单表中进行一一映射,则需要根据返回参数进行实体类封装。
对于单表的增删改查,mybatiaPlus提供了相应的操作,对于多表则需要手写sql语句
4.1小区搜索和查询
传入参数
{"page": 1,"limit": 10,"communityId": "","communityName": ""
}
返回参数
{"msg": "操作成功","code": 200,"data": {"totalCount": 8,"pageSize": 10,"totalPage": 1,"currPage": 1,"list": [{"communityId": 19,"communityName": "北清云际","termCount": 33,"seq": 190,"creater": "admin","createTime": "2023-07-18 00:41:20","lng": 116.298904,"lat": 40.091644,"personCnt": 1},{"communityId": 17,"communityName": "天龙苑","termCount": 10,"seq": 170,"creater": "admin","createTime": "2023-07-18 00:38:06","lng": 116.36206,"lat": 40.088108,"personCnt": 0}]}
}
实现思路:由于返回list中在community表中没有personCnt字段,因此需要封装一个CommunityVO类用于数据的返回。同时由于mybtisPlus中并没有现成的sql用于根据community_id查询相应的居民人数,因此需要编写sql用来查询每个community对于的人数personCnt
CommunityVO类
package com.qcby.vo;import lombok.Data;import java.util.Date;@Data
public class CommunityVO {private Integer communityId;private String communityName;private Integer termCount;private Integer seq;private String creater;private Date createTime;private Float lng;private Float lat;private Integer personCnt;
}
controller
/*** 获取小区的所有信息,分页条件查询* @return*/@GetMapping("/list")public Result getList(CommunityListFrom communityListFrom){PageVO pageVO = this.communityService.communityList(communityListFrom);return Result.ok().put("data",pageVO);}
service
// CommunityServiceImpl.java
package com.qcby.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.CommunityListFrom;
import com.qcby.entity.Community;
import com.qcby.mapper.CommunityMapper;
import com.qcby.service.ICommunityService;
import com.qcby.vo.CommunityVO;
import com.qcby.vo.PageVO;
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;@Service
public class CommunityServiceImpl extends ServiceImpl<CommunityMapper, Community> implements ICommunityService {@Autowiredprivate CommunityMapper communityMapper;@Overridepublic PageVO communityList(CommunityListFrom communityListForm) {Page<Community> page = new Page<>(communityListForm.getPage(), communityListForm.getLimit());QueryWrapper<Community> queryWrapper = new QueryWrapper<>();queryWrapper.like(StringUtils.isNotBlank(communityListForm.getCommunityName()), "community_name", communityListForm.getCommunityName());Page<Community> resultPage = this.communityMapper.selectPage(page, queryWrapper);PageVO pageVO = new PageVO();List<CommunityVO> list = new ArrayList<>();for (Community record : resultPage.getRecords()) {CommunityVO communityVO = new CommunityVO();BeanUtils.copyProperties(record, communityVO);Integer personCount = communityMapper.getCountByCommunityId(record.getCommunityId());communityVO.setPersonCnt(personCount);list.add(communityVO);}pageVO.setList(list);pageVO.setTotalCount(resultPage.getTotal());pageVO.setPageSize(resultPage.getSize());pageVO.setCurrPage(resultPage.getCurrent());pageVO.setTotalPage(resultPage.getPages());return pageVO;}
}
根据communityId查询居民人数的mapper
@Select({"select count(*) from person where community_id = #{communityId} "})Integer getCountByCommunityId(Integer communityId);
分页
用于分页封装的实体类
package com.qcby.vo;import lombok.Data;import java.util.List;@Data
public class PageVO {private Long totalCount;private Long totalPage;private Long pageSize;private Long currPage;private List list;
}
分页的相关配置
package com.qcby.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyBatisPlusConfig {/*** 配置分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页拦截器(支持MySQL、PostgreSQL等主流数据库)interceptor.addInnerInterceptor(new PaginationInnerInterceptor());//添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}
结果图
4.2添加小区
传入参数
{"communityName": "test""lat": "1""lng": "1""seq": 1"termCount": "1"
}
返回参数
{"msg": "操作成功","code": 200
}
controller
/*** 添加小区* @param community* @param session* @return*/@RequestMapping("/add")public Result add(@RequestBody Community community, HttpSession session){User user = (User) session.getAttribute("user");community.setCreater(user.getUsername());boolean save = this.communityService.save(community);if(!save) return Result.error("添加失败");return Result.ok();}
界面
4.3通过id查询小区
传入参数
1
返回参数
{"msg": "操作成功","code": 200,"data": {"communityId": 19,"communityName": "北清云际","termCount": 33,"seq": 190,"lng": 116.298904,"lat": 40.091644}
}
controller
/*** 修改的回显操作* @param id* @return*/@RequestMapping("/info/{id}")public Result info(@PathVariable("id") Integer id){Community community = this.communityService.getById(id);if(community == null){return Result.error("没有此小区");}return Result.ok().put("data",community);}
界面
4.4修改小区
传入参数
{"communityId": 21,"communityName": "1","termCount": "12","lng": "12","lat": "12","seq": 210
}
返回参数
{"msg": "操作成功","code": 200
}
controller
/*** 修改小区信息* @param community* @return*/@RequestMapping("/edit")public Result edit(@RequestBody Community community){boolean updateById = this.communityService.updateById(community);if(!updateById) return Result.error("修改失败");return Result.ok();}
界面
4.5删除小区
传入参数
[21,20
]
返回参数
{"msg": "操作成功","code": 200
}
controller
/*** 仅删除小区信息,不删除关联表数据(不使用try-catch)* @param ids 小区ID数组* @return 操作结果*/@RequestMapping("/del")@Transactionalpublic Result del(@RequestBody Integer[] ids) {// 删除小区数据boolean remove = this.communityService.removeByIds(Arrays.asList(ids));if (!remove) {return Result.error("小区删除失败");}return Result.ok();}
界面
注意:在删除小区的同时也会删除掉这个小区对应的摄像头、访客记录、小区出入记录、小区居民,如果数据库中没有则会个小区对应的内容则会删除失败
5.摄像头的增删改查
和上面的小区的实现基本一致
对应的Controller
package com.qcby.controller;import com.qcby.entity.Camera;
import com.qcby.entity.User;
import com.qcby.service.ICameraService;
import com.qcby.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.List;@RestController
@RequestMapping("/sys/camera")
public class CameraController {@Autowiredprivate ICameraService cameraService;/*** 查询摄像头列表*/@GetMapping("/list/{communityId}")public Result getCameraList(@PathVariable Integer communityId) {try {// 调用关联查询方法,获取包含小区名称的摄像头列表List<Camera> cameraList = cameraService.getCameraListWithCommunityName(communityId);return Result.ok().put("data", cameraList);} catch (Exception e) {return Result.error("查询失败:" + e.getMessage());}}/*** 添加摄像头*/@RequestMapping("/add")public Result add(@RequestBody Camera camera, HttpSession session){User user = (User) session.getAttribute("user");camera.setCreater(user.getUsername());boolean save = cameraService.save(camera);if(!save) return Result.error("添加失败");return Result.ok();}/*** 修改的回显操作* @param id* @return*/@RequestMapping("/info/{id}")public Result info(@PathVariable("id") Integer id){Camera camera = cameraService.getById(id);if(camera == null){return Result.error("没有此摄像头");}return Result.ok().put("data",camera);}/*** 修改摄像头信息* @param camera* @return*/@RequestMapping("/edit")public Result edit(@RequestBody Camera camera){boolean updateById = cameraService.updateById(camera);if(!updateById) return Result.error("修改失败");return Result.ok();}/*** 删除摄像头信息* @param ids 摄像头ID数组* @return 操作结果*/@RequestMapping("/del")public Result del(@RequestBody Integer[] ids){boolean remove = cameraService.removeByIds(Arrays.asList(ids));if(!remove) return Result.error("删除失败");return Result.ok();}
}
对应的实体类
package com.qcby.entity;import lombok.Data;@Data
public class Person {private Integer personId;private Integer communityId;private String termName;private String houseNo;private String userName;private String sex;private String mobile;private String faceUrl;private String personType;private Integer state;private String creater;private String createTime;private String remark;private String faceBase;
}
对应的Mapper.java
package com.qcby.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qcby.entity.Camera;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;
import java.util.List;public interface CameraMapper extends BaseMapper<Camera> {// 联表查询,通过 camera 的 community_id 关联 community 表,获取小区名称@Select("SELECT c.*, com.community_name as communityName " +"FROM camera c " +"LEFT JOIN community com ON c.community_id = com.community_id " +"WHERE c.community_id = #{communityId}")List<Camera> selectCameraWithCommunityName(@Param("communityId") Integer communityId);
}
应的Mapper.xml
<?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.CameraMapper"><!-- 与数据库表字段严格映射 --><resultMap id="BaseResultMap" type="com.qcby.entity.Camera"><id column="camera_id" property="cameraId" /><result column="community_id" property="communityId" /><result column="camera_name" property="cameraName" /><result column="mac_id" property="macId" /><result column="direction" property="direction" /><result column="state" property="state" /><result column="seq" property="seq" /><result column="creater" property="creater" /><result column="create_time" property="createTime" /><result column="remark" property="remark" /></resultMap><!-- 完整查询字段列表 --><sql id="Base_Column_List">camera_id,community_id,camera_name,mac_id,direction,state,seq,creater,create_time,remark</sql><!-- selectById 实现(MyBatis-Plus 基础方法) --><select id="selectById" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List" />FROM cameraWHERE camera_id = #{id}</select><!--修改摄像头信息--><update id="updateById" parameterType="com.qcby.entity.Camera">UPDATE camera<set><if test="et.cameraName != null">camera_name = #{et.cameraName},</if><if test="et.macId != null"> mac_id = #{et.macId},</if><if test="et.direction != null"> direction = #{et.direction},</if><if test="et.state != null"> state = #{et.state},</if><if test="et.seq != null"> seq = #{et.seq},</if><if test="et.remark != null"> remark = #{et.remark},</if></set>WHERE camera_id = #{et.cameraId}</update><!--删除摄像头--><delete id="deleteBatchIds" parameterType="java.util.Collection">DELETE FROM cameraWHERE camera_id IN<foreach item="id" collection="coll" separator="," open="(" close=")">#{id}</foreach></delete>
</mapper>
对应的ICameraService
package com.qcby.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.entity.Camera;import java.util.List;public interface ICameraService extends IService<Camera> {List<Camera> getCameraListWithCommunityName(Integer communityId);
}
对应的CameraServiceImpl
package com.qcby.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcby.entity.Camera;
import com.qcby.mapper.CameraMapper;
import com.qcby.service.ICameraService;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> implements ICameraService {@Overridepublic List<Camera> getCameraListWithCommunityName(Integer communityId) {return baseMapper.selectCameraWithCommunityName(communityId);}
}