![image-20230225060710
前面我们完成了用户登录、用户认证与就诊人管理,现在我们需要把这些信息在我们的平台管理系统中进行统一管理
简单的设置redis和cookie的过期时间,会导致用户在操作的过程中掉线,为了解决这个问题,我们可以使用token续期的方案,具体的做法是生成一个刷新token。
设置token与refreshToken
,都包含用户信息(userId、name、headimgurl),可以一样可以不一样,refreshToken比token时间更长,如token30分钟,refreshToken60分钟
在ApiWxController的callback方法的return "redirect:"
前面,将之前的生成token和存储token的相关代码封装到saveToken方法中。
将如下代码进行封装:
封装成如下代码:
@Resource private AuthContextHolder authContextHolder;
UserVo userVo = new UserVo(); userVo.setName(name); userVo.setUserId(userInfo.getId()); userVo.setHeadimgurl(userInfo.getHeadImgUrl()); authContextHolder.saveToken(userVo, response); return "redirect:" + constantProperties.getSytBaseUrl();
service-util中引入依赖
<!--实体--> <dependency> <groupId>com.atguigu</groupId> <artifactId>model</artifactId> <version>1.0</version> </dependency>
在AuthContextHolder中添加如下方法:
/** * 将token和refreshToken保存在redis和cookie中的通用方法 * @param userVo * @param response */ public void saveToken(UserVo userVo, HttpServletResponse response) { int redisMaxTime = 30; //生成token/refreshToken String token = getToken(); String refreshToken = getToken(); //将生成token/refreshToken存入redis:token做键,userVo做值 redisTemplate.opsForValue()//30分钟 .set("user:token:" + token, userVo, redisMaxTime, TimeUnit.MINUTES); redisTemplate.opsForValue()//60分钟 .set("user:refreshToken:" + refreshToken, userVo, redisMaxTime * 2, TimeUnit.MINUTES); //将token、refreshToken和name存入cookie int cookieMaxTime = 60 * 30;//30分钟 CookieUtils.setCookie(response, "token", token, cookieMaxTime); CookieUtils.setCookie(response, "refreshToken", refreshToken, cookieMaxTime * 2); CookieUtils.setCookie(response, "name", URLEncoder.encode(userVo.getName()), cookieMaxTime * 2); CookieUtils.setCookie(response, "headimgurl", URLEncoder.encode(userVo.getHeadimgurl()), cookieMaxTime * 2); }
private String getToken(){ return UUID.randomUUID().toString().replaceAll("-", ""); }
修改checkAuth方法,当token不存在时,续期token
/** * 检查授权状态并续期 * @param request * @return */ public Long checkAuth(HttpServletRequest request, HttpServletResponse response){ //从http请求头中获取token String token = request.getHeader("token"); if(StringUtils.isEmpty(token)) { //throw new GuiguException(ResultCodeEnum.LOGIN_AUTH); return refreshToken(request, response);//刷新token } Object userVoObj = redisTemplate.opsForValue().get("user:token:" + token); if(userVoObj == null){ //throw new GuiguException(ResultCodeEnum.LOGIN_AUTH); return refreshToken(request, response);//刷新token } UserVo userVo = (UserVo)userVoObj; return userVo.getUserId(); }
/** * 刷新token * @param request * @param response * @return */ public Long refreshToken(HttpServletRequest request, HttpServletResponse response) { //从cookie中获取刷新token String refreshToken = CookieUtils.getCookie(request, "refreshToken"); //从redis中根据刷新token获取用户信息 Object userVoObj = redisTemplate.opsForValue().get("user:refreshToken:" + refreshToken); if(userVoObj == null) { //LOGIN_AURH(214, "登录过期"), throw new GuiguException(ResultCodeEnum.LOGIN_TIMEOUT);//登录过期 } UserVo userVo = (UserVo) userVoObj; saveToken(userVo, response); return userVo.getUserId(); }
FrontUserInfoController和FrontFileController中的方法,添加response参数
跨域ajax访问时,session_id不会被存储,cookie不会被传递到后端,因此需要解决cookie跨域存储的问题
解决方案:
(1)网关配置文件CorsConfig中添加如下设置:
config.setAllowCredentials(true); //避免跨域访问时session_id每次不一致
(2)前端axios初始化文件request.js添加如下设置:
withCredentials: true //避免跨域访问时session_id每次不一致
如果接口返回214状态(登录超时),说明用户超时未操作,直接退出。
// http response 拦截器 service.interceptors.response.use( response => { //如果响应码是214,则需要登录超时 if (response.data.code === 214) { // to re-login MessageBox.confirm('登录超时, 请重新登录', '确认退出', { confirmButtonText: '重新登录', cancelButtonText: '回到首页', type: 'warning' }).then(() => { window.location.href = process.env.BASE_API + '/front/user/wx/login' }).catch(()=>{ window.location.href = '/' }) }else if (response.data.code !== 200) { Message({ message: response.data.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(response.data) } else { return response.data } }, error => { return Promise.reject(error.response) })
myheader.vue中showInfo方法的修改,保证token过期后,用户名信息依然显示
showInfo() { let refreshToken = cookie.get('refreshToken') if (refreshToken) { this.name = cookie.get('name') this.headimgurl = cookie.get('headimgurl') } },
myheader.vue中退出登录方法的修改,需要清空refreshToken
cookie.set('name', '', {domain: 'localhost'}) cookie.set('token', '', {domain: 'localhost'}) cookie.set('refreshToken', '', {domain: 'localhost'}) //清空refreshToken cookie.set('headimgurl', '', {domain: 'localhost'})
补充:由于我们解决了cookie的ajax传递跨域问题,因此之前通过header传输的token其实可以不必要了,我们可以直接在后端从cookie中获取token的值,如下:
String token = request.getHeader("token"); //以上可以替换为如下: String token = CookieUtils.getCookie(request, "token");
FrontPatientController:
package com.atguigu.syt.user.controller.front; @Api(tags = "就诊人管理") @RestController @RequestMapping("/front/user/patient") public class FrontPatientController { @Resource private PatientService patientService; @Resource private AuthContextHolder authContextHolder; @ApiOperation("添加就诊人") @ApiImplicitParam(name = "patient",value = "就诊人对象", required = true) @PostMapping("/auth/save") public Result savePatient(@RequestBody Patient patient, HttpServletRequest request, HttpServletResponse response) { Long userId = authContextHolder.checkAuth(request, response); patient.setUserId(userId); patientService.save(patient); return Result.ok().message("保存成功"); } }
FrontPatientController:
@ApiOperation("修改就诊人") @ApiImplicitParam(name = "patient",value = "就诊人对象", required = true) @PutMapping("/auth/update") public Result updatePatient(@RequestBody Patient patient, HttpServletRequest request, HttpServletResponse response) { authContextHolder.checkAuth(request, response); patientService.updateById(patient); return Result.ok().message("修改成功"); }
FrontPatientController:
@ApiOperation("根据id获取就诊人信息") @ApiImplicitParam(name = "id",value = "就诊人id", required = true) @GetMapping("/auth/get/{id}") public Result<Patient> getPatient(@PathVariable Long id, HttpServletRequest request, HttpServletResponse response) { Long userId = authContextHolder.checkAuth(request, response); //加上userId参数,只可以获取自己名下的就诊人信息 Patient patient = patientService.getPatientById(id, userId); return Result.ok(patient); }
接口:PatientService
/** * 根据id获取本人名下的就诊人信息 * @param id * @param userId * @return */ Patient getPatientById(Long id, Long userId);
实现:PatientServiceImpl
@Override public Patient getPatientById(Long id, Long userId) { LambdaQueryWrapper<Patient> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Patient::getUserId, userId).eq(Patient::getId, id); Patient patient = baseMapper.selectOne(queryWrapper); //封装数据 return this.packPatient(patient); }
辅助方法
@Resource private DictFeignClient dictFeignClient; @Resource private RegionFeignClient regionFeignClient;
/** * 封装Patient对象里面其他参数 * @param patient * @return */ private Patient packPatient(Patient patient) { String certificatesTypeString = dictFeignClient.getName(DictTypeEnum.CERTIFICATES_TYPE.getDictTypeId(),patient.getCertificatesType()); String contactsCertificatesTypeString = dictFeignClient.getName( DictTypeEnum.CERTIFICATES_TYPE.getDictTypeId(),patient.getContactsCertificatesType()); String provinceString = regionFeignClient.getName(patient.getProvinceCode()); String cityString = regionFeignClient.getName(patient.getCityCode()); String districtString = regionFeignClient.getName(patient.getDistrictCode()); patient.getParam().put("certificatesTypeString", certificatesTypeString); patient.getParam().put("contactsCertificatesTypeString", contactsCertificatesTypeString); patient.getParam().put("provinceString", provinceString); patient.getParam().put("cityString", cityString); patient.getParam().put("districtString", districtString); patient.getParam().put("fullAddress", provinceString + cityString + districtString + patient.getAddress()); return patient; }
FrontPatientController
@ApiOperation("获取就诊人列表") @GetMapping("/auth/findAll") public Result<List<Patient>> findAll(HttpServletRequest request, HttpServletResponse response) { Long userId = authContextHolder.checkAuth(request, response); List<Patient> list = patientService.findByUserId(userId); return Result.ok(list); }
接口:PatientService
/** * 根据userId获取就诊人列表 * @param userId * @return */ List<Patient> findByUserId(Long userId);
实现:PatientServiceImpl
@Override public List<Patient> findByUserId(Long userId) { LambdaQueryWrapper<Patient> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Patient::getUserId, userId); List<Patient> patientList = baseMapper.selectList(queryWrapper); patientList.forEach(patient -> { patient.getParam().put("expenseMethod", patient.getIsInsure()==0?"自费":"医保"); }); return patientList; }
FrontPatientController:
@ApiOperation("删除就诊人") @DeleteMapping("/auth/remove/{id}") public Result removePatient(@PathVariable Long id, HttpServletRequest request, HttpServletResponse response) { Long userId = authContextHolder.checkAuth(request, response); //加上userId参数,只可以删除自己名下的就诊人信息 patientService.removeById(id, userId); return Result.ok(); }
接口:PatientService
/** * 根据id删除自己名下的就诊人信息 * @param id * @param userId */ void removeById(Long id, Long userId);
实现:PatientServiceImpl
@Override public void removeById(Long id, Long userId) { LambdaQueryWrapper<Patient> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Patient::getUserId, userId).eq(Patient::getId, id); baseMapper.delete(queryWrapper); }
创建api/patient.js
import request from '~/utils/request' export default { save(patient) { return request({ url: `/front/user/patient/auth/save`, method: 'post', data: patient }) }, updateById(patient) { return request({ url: `/front/user/patient/auth/update`, method: 'put', data: patient }) }, getById(id) { return request({ url: `/front/user/patient/auth/get/${id}`, method: 'get' }) }, findList() { return request({ url: `/front/user/patient/auth/findAll`, method: `get` }) }, removeById(id) { return request({ url: `/front/user/patient/auth/remove/${id}`, method: 'delete' }) } }
资料:资料>就诊人管理>
就诊人列表:pages/patient/index.vue
就诊人添加与修改 :pages/patient/add.vue
就诊人详情与删除:pages/patient/show.vue
AdminUserInfoController:
package com.atguigu.syt.user.controller.admin; @Api(tags = "用户管理接口") @RestController @RequestMapping("/admin/user/userInfo") public class AdminUserInfoController { @Resource private UserInfoService userInfoService; @ApiOperation("分页条件查询") @ApiImplicitParams({ @ApiImplicitParam(name = "page",value = "页码",required = true), @ApiImplicitParam(name = "limit",value = "每页记录数",required = true), @ApiImplicitParam(name = "userInfoQueryVo",value = "查询对象",required = false) }) @GetMapping("/{page}/{limit}") public Result<IPage<UserInfo>> list( @PathVariable Long page, @PathVariable Long limit, UserInfoQueryVo userInfoQueryVo ) { Page<UserInfo> pageParam = new Page<>(page,limit); IPage<UserInfo> pageModel = userInfoService.selectPage(pageParam, userInfoQueryVo); return Result.ok(pageModel); } }
Service接口:UserInfoService
/** * 查询用户分页列表 * @param pageParam * @param userInfoQueryVo * @return */ IPage<UserInfo> selectPage(Page<UserInfo> pageParam, UserInfoQueryVo userInfoQueryVo);
Service实现:UserInfoServiceImpl
@Override public IPage<UserInfo> selectPage(Page<UserInfo> pageParam, UserInfoQueryVo userInfoQueryVo) { //UserInfoQueryVo获取条件值 String keyword = userInfoQueryVo.getKeyword(); //用户名称 String createTimeBegin = userInfoQueryVo.getCreateTimeBegin(); //开始时间 String createTimeEnd = userInfoQueryVo.getCreateTimeEnd(); //结束时间 //对条件值进行非空判断 LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.and(!StringUtils.isEmpty(keyword), i -> i.like(UserInfo::getName, keyword).or().like(UserInfo::getPhone, keyword)) .ge(!StringUtils.isEmpty(createTimeBegin), UserInfo::getCreateTime, createTimeBegin) .le(!StringUtils.isEmpty(createTimeEnd), UserInfo::getCreateTime, createTimeEnd); //调用mapper的方法 IPage<UserInfo> pages = baseMapper.selectPage(pageParam, wrapper); //编号变成对应值封装 pages.getRecords().forEach(this::packUserInfoForList); return pages; }
辅助方法:
/** * 封装用户状态、认证状态、证件类型信息 * @param userInfo * @return */ private UserInfo packUserInfoForList(UserInfo userInfo) { //判断用户是否已经提交实名认证 if(userInfo.getAuthStatus().intValue() != AuthStatusEnum.NO_AUTH.getStatus().intValue()){ String certificatesTypeString = dictFeignClient.getName( DictTypeEnum.CERTIFICATES_TYPE.getDictTypeId(), userInfo.getCertificatesType() ); userInfo.getParam().put("certificatesTypeString", certificatesTypeString); } userInfo.getParam().put("authStatusString", AuthStatusEnum.getStatusNameByStatus(userInfo.getAuthStatus())); userInfo.getParam().put("statusString", UserStatusEnum.getStatusNameByStatus(userInfo.getStatus())); return userInfo; }
Controller:在AdminUserInfoController中添加方法
@ApiOperation("锁定和解锁") @ApiImplicitParams({ @ApiImplicitParam(name = "userId",value = "用户id",required = true), @ApiImplicitParam(name = "status",value = "用户状态",required = true) }) @PutMapping("lock/{userId}/{status}") public Result lock( @PathVariable("userId") Long userId, @PathVariable("status") Integer status){ boolean result = userInfoService.lock(userId, status); if(result){ return Result.ok().message("设置成功"); }else{ return Result.ok().message("设置失败"); } }
Service接口:UserInfoService
/** * 锁定和解锁用户 * @param userId * @param status * @return */ boolean lock(Long userId, Integer status);
Service实现:UserInfoServiceImpl
@Override public boolean lock(Long userId, Integer status) { if(status == 0 || status == 1){ UserInfo userInfo = new UserInfo(); userInfo.setId(userId); userInfo.setStatus(status); return this.updateById(userInfo); } return false; }
Controller:在AdminUserInfoController中添加方法
@ApiOperation("用户详情") @ApiImplicitParam(name = "userId",value = "用户id",required = true) @GetMapping("show/{userId}") public Result<Map<String, Object>> show(@PathVariable Long userId) { return Result.ok(userInfoService.show(userId)); }
Service接口:UserInfoService
/** * 根据用户id获取用户详情 * @param userId * @return */ Map<String, Object> show(Long userId);
Service实现:UserInfoServiceImpl
@Resource private PatientService patientService;
@Override public Map<String, Object> show(Long userId) { Map<String,Object> map = new HashMap<>(); //根据userid查询用户信息 UserInfo userInfo = this.packUserInfo(baseMapper.selectById(userId)); map.put("userInfo",userInfo); //根据userid查询就诊人信息 List<Patient> patientList = patientService.findByUserId(userId); map.put("patientList",patientList); return map; }
Controller:在AdminUserInfoController中添加方法
@ApiOperation("认证审批") @ApiImplicitParams({ @ApiImplicitParam(name = "userId",value = "用户id",required = true), @ApiImplicitParam(name = "authStatus",value = "用户认证审批状态",required = true) }) @PutMapping("approval/{userId}/{authStatus}") public Result approval(@PathVariable Long userId, @PathVariable Integer authStatus) { boolean result = userInfoService.approval(userId,authStatus); if(result){ return Result.ok().message("审批成功"); }else{ return Result.ok().message("审批失败"); } }
Service接口:UserInfoService
/** * 审核用户 * @param userId * @param authStatus * @return */ boolean approval(Long userId, Integer authStatus);
Service实现:UserInfoServiceImpl
/** * 认证审批 2通过 -1不通过 * @param userId * @param authStatus * @return */ @Override public boolean approval(Long userId, Integer authStatus) { if(authStatus == AuthStatusEnum.AUTH_SUCCESS.getStatus() || authStatus == AuthStatusEnum.AUTH_FAIL.getStatus()){ UserInfo userInfo = new UserInfo(); userInfo.setId(userId); userInfo.setAuthStatus(authStatus); return this.updateById(userInfo); } return false; }
在后台管理系统前端项目中添加如下页面,资料:资料>用户管理
用户列表:src/views/syt/userInfo/list.vue
用户详情,详情页面展示用户信息、用户就诊人信息:src/views/syt/userInfo/show.vue
静态路由:
{ path: '/userInfo', component: Layout, redirect: '/userInfo/list', name: 'userInfo', meta: { title: '用户管理', icon: 'user' }, alwaysShow: true, children: [ { path: 'list', name: 'userInfoList', component: () => import('@/views/syt/userInfo/list'), meta: { title: '用户列表', icon: 'el-icon-s-unfold' } }, { path: 'show/:id', name: 'userInfoShow', component: () => import('@/views/syt/userInfo/show'), meta: { title: '查看用户' }, hidden: true } ] },
动态路由:
用户管理:
用户列表:
查看用户:
创建src/api/syt/userInfo.js
import request from '@/utils/request' const apiName = '/admin/user/userInfo' export default { //用户列表 getPageList(page, limit, searchObj) { return request({ url: `${apiName}/${page}/${limit}`, method: 'get', params: searchObj }) }, //锁定与解锁 lock(id, status) { return request({ url: `${apiName}/lock/${id}/${status}`, method: 'put' }) }, //用户详情 show(id) { return request({ url: `${apiName}/show/${id}`, method: 'get' }) }, //认证审批 approval(id, authStatus) { return request({ url: `${apiName}/approval/${id}/${authStatus}`, method: 'put' }) } }
源码:https://gitee.com/dengyaojava/guigu-syt-parent