在以前的文章中咱们实现了用户注册和验证功能,接下来咱们继续实现它的登陆,以及登陆成功以后要在页面上显示的信息。
接下来,咱们来编写代码。html
在com.liferunner.service.IUserService
接口中添加用户登陆方法:前端
public interface IUserService { ... /** * 用户登陆 * @param userRequestDTO 请求dto * @return 登陆用户信息 * @throws Exception */ Users userLogin(UserRequestDTO userRequestDTO) throws Exception; }
而后,在com.liferunner.service.impl.UserServiceImpl
实现类中实现:java
@Service @Slf4j public class UserServiceImpl implements IUserService { ... @Override public Users userLogin(UserRequestDTO userRequestDTO) throws Exception { log.info("======用户登陆请求:{}", userRequestDTO); Example example = new Example(Users.class); val condition = example.createCriteria(); condition.andEqualTo("username", userRequestDTO.getUsername()); condition.andEqualTo("password", MD5GeneratorTools.getMD5Str(userRequestDTO.getPassword())); val user = this.usersMapper.selectOneByExample(example); log.info("======用户登陆处理结果:{}", user); return user; } }
Error Tips:
这里有一个小小的坑点
,你们必定要注意,在使用selectOneByExample()
查询的时候,该方法传入的参数必定注意是tk.mybatis.mapper.entity.Example
实例,而不是tk.mybatis.mapper.entity.Example.Criteria
,不然会报动态SQL生成查询错误,信息以下:git
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'distinct' in 'class tk.mybatis.mapper.entity.Example$Criteria' at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440) at com.sun.proxy.$Proxy106.selectOne(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:93) at com.sun.proxy.$Proxy109.selectOneByExample(Unknown Source) at com.liferunner.service.impl.UserServiceImpl.userLogin(UserServiceImpl.java:80) ...
新人在写代码的时候,特别容易在上一行写了查询变量,下一行就直接开用了,越是简单的错误越是让人无从下手。github
@RestController @RequestMapping(value = "/users") @Slf4j @Api(tags = "用户管理") public class UserController { ... @ApiOperation(value = "用户登陆", notes = "用户登陆接口") @PostMapping("/login") public JsonResponse userLogin(@RequestBody UserRequestDTO userRequestDTO, HttpServletRequest request, HttpServletResponse response) { try { if (StringUtils.isBlank(userRequestDTO.getUsername())) return JsonResponse.errorMsg("用户名不能为空"); if (StringUtils.isBlank(userRequestDTO.getPassword()) || userRequestDTO.getPassword().length() < 8) { return JsonResponse.errorMsg("密码为空或长度小于8位"); } val user = this.userService.userLogin(userRequestDTO); UserResponseDTO userResponseDTO = new UserResponseDTO(); BeanUtils.copyProperties(user, userResponseDTO); log.info("BeanUtils copy object {}", userResponseDTO); if (null != userResponseDTO) { // 设置前端存储的cookie信息 CookieTools.setCookie(request, response, "user", JSON.toJSONString(userResponseDTO), true); return JsonResponse.ok(userResponseDTO); } } catch (Exception e) { e.printStackTrace(); log.error("用户登陆失败,{},exception = {}", userRequestDTO, e.getMessage()); } return JsonResponse.errorMsg("用户登陆失败"); } }
在上面的代码中,基本校验问题就再也不赘述,咱们主要关注几点新的特性信息:spring
com.liferunner.dto.UserResponseDTO
将咱们须要展现给前端的数据封装为一个新的返回对象,咱们从数据库中查询出来的Users
pojo包含用户的全部数据,好比其中的password
、mobile
等等一些用户私密的数据是不该该展现给前端的,即使要展现,那也是须要通过脱敏以及加密。所以,常见的作法就是封装一个新的返回对象,其中只须要包含前端须要的数据字段就能够了。@Data @AllArgsConstructor @NoArgsConstructor @Builder @ApiModel(value = "用户信息返回DTO", description = "用户登陆成功后须要的返回对象") public class UserResponseDTO { /** * 主键id */ private String id; /** * 用户名 */ private String username; /** * 昵称 昵称 */ private String nickname; /** * 头像 */ private String face; /** * 性别 1:男 0:女 2:保密 */ private Integer sex; }
在这里建议你们使用Ctrl+C
咱们的com.liferunner.pojo.Users
对象,而后删除掉咱们不须要的字段就能够了,为何这么建议
呢,是由于下一个好处啦。sql
org.springframework.beans.BeanUtils.copyProperties(user, userResponseDTO);
Spring BeanUtils
工具类进行的值拷贝,就减小了咱们循环遍历每个字段去挨个赋值(SetValue)
的工做。(也是一种偷懒小技巧哦,这样是不对的~)CookieTools.setCookie();
Cookie
中,好比我登陆的baidu.com
:此时,鼠标在图片中左侧的Cookies => www.baidu.com
右键clear
,而后再次刷新咱们当前界面,效果以下:
咱们能够看到,从登陆状态已经变为退出状态了,而且Cookies
中的内容也少了不少,这就说明,百度是把咱们的用户登陆信息加密后存储在了浏览器cookie中。
你们能够查看京东,淘宝等等,也是基于这种方式实现的,开篇之初就说过,咱们的系统是基于生产来实现的demo,那么咱们就是用主流的实现方法来作。固然,有的同窗会说,这个应该咱们把数据传递给前端,让前端来实现的!!!固然,你说的对,但是咱们掌握一种实现方式,对于咱们我的而言应该是没有坏处的吧?
这里就须要一个工具类了,你们能够在github传送门来下载相关代码。目录com.liferunner.utils.CookieTools
.shell
com.alibaba.fastjson.JSON.toJSONString(userResponseDTO)
由于咱们要返回的是一个对象,可是cookie
中咱们须要放入的是String
,这里咱们引入了alibaba的JSON工具,在mscx-shop-common/pom.xml
,加入依赖:数据库
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.56</version> </dependency> </dependencies>
在用户操做结束以后,咱们须要将用户从系统中退出登陆,由于咱们的用户登陆信息会存储在浏览器cookie中,所以,咱们须要根据用户的登出操做来删除相关用户缓存:apache
@ApiOperation(value = "用户登出",notes = "用户登出",httpMethod = "POST") @PostMapping("/logout") public JsonResponse userLogout(@RequestParam String uid, HttpServletRequest request,HttpServletResponse response){ // clear front's user cookies CookieTools.deleteCookie(request,response,"user"); // return operational result return JsonResponse.ok(); }
通常在电商场景中,对于请求的响应时间有着极其严格的要求,好比你在一个网站买商品的时候,若是每点击一次按钮都要等待,或者系统感受卡顿一下,你会坚决果断的选择右上角的小红叉
,把它干掉。所以,在咱们系统的开发过程当中,不少时候须要对咱们的请求响应时间进行监控,甚至会经过压力测试来进行测试。可是,让咱们在每个方法中都作这种请求的实现,显然是不合理甚至说是让开发人员难受的,因此,咱们来实现一种通用的作法,那就是经过AOP
,面向切面来实现。关于切面的基本使用,你们能够参考AOP传送门,接下来,开始咱们的编码。
根据springboot
实现功能三部曲:
setp 1. 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
step 2. 启动配置(没有就忽略掉这一步)
setp 3. 加注解
在咱们的mscx-shop-api
项目中,建立com.liferunner.api.aspect
package,而后建立com.liferunner.api.aspect.CommonLogAspect
,代码以下:
package com.liferunner.api.aspect; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import java.util.Date; /** * CommonLogAspect for : AOP切面实现日志确认 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> * @since 2019/11/11 */ @Component @Aspect @Slf4j public class CommonLogAspect { @Around("execution(* com.liferunner.api.controller..*.*(..))") public void recordLogTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("----------- {}.{} process log time started.---------------", proceedingJoinPoint.getTarget().getClass(), proceedingJoinPoint.getSignature().getName()); val startTime = System.currentTimeMillis(); proceedingJoinPoint.proceed(); val afterTime = System.currentTimeMillis(); if (afterTime - startTime > 1000) { log.warn("cost : {}", afterTime - startTime); } else { log.info("cost : {}", afterTime - startTime); } log.info("----------- {}.{} process log time ended.---------------", proceedingJoinPoint.getSourceLocation().getClass(), proceedingJoinPoint.getSignature().getName()); } }
proceedingJoinPoint.proceed();
表示方法执行log.warn(...)
进行日志告警step 4. 效果演示
从上图,咱们明显能看出来咱们每一次的请求耗时,以后就能够针对性的对每个方法进行优化!!!
在咱们开发的过程当中,每每会遇到针对数据库的CRUD
的操做,可是,由于咱们使用了mybatis
动态生成了简单的SQL查询,而不是手动编写的,好比咱们在UserServiceImpl.java
中实现的用户查询以及用户注册代码中的tk.mybatis.mapper.entity.Example
以及 this.usersMapper.insertSelective(user);
public Users findUserByUserName(String username) { // 构建查询条件 Example example = new Example(Users.class); val condition = example.createCriteria() .andEqualTo("username", username); return this.usersMapper.selectOneByExample(example); } @Transactional(propagation = Propagation.REQUIRED) @Override public Users createUser(UserRequestDTO userRequestDTO) throws Exception { log.info("======begin create user : {}=======", userRequestDTO); val user = Users.builder() .id(sid.next()) //生成分布式id .username(userRequestDTO.getUsername()) .password(MD5GeneratorTools.getMD5Str(userRequestDTO.getPassword())) .birthday(DateUtils.parseDate("1970-01-01", "yyyy-MM-dd")) .nickname(userRequestDTO.getUsername()) .face(this.FACE_IMG) .sex(SexEnum.secret.type) .createdTime(new Date()) .updatedTime(new Date()) .build(); this.usersMapper.insertSelective(user); log.info("======end create user : {}=======", userRequestDTO); return user; }
一旦遇到了问题以后,咱们每每不知道究竟是哪里出现了错误,这个时候咱们的SQL
是否有问题咱们也不知道,所以,接下来咱们来配置一种可让咱们看
到SQL的小实现:
1.设置日志配置(如图)
2.修改mybatis配置(log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
)
3.SELECT
效果演示
4.INSERT
效果演示
从上图能够看出控制台JDBC操做进行了2次,其实第一次是对咱们的用户名进行校验。第二次INSERT
是真实的插入。
经过上面的演示结果,你们能够想到,这个日志针在咱们平常的开发中解决问题是很是有必要的。可是必定记得,在上生产的时候,日志必定要关闭,不然数据量一旦大了以后,会对系统的性能形成严重伤害!!!
下一节咱们将继续开发咱们电商的核心部分-商品以及广告的展现,在过程当中使用到的任何开发组件,我都会经过专门的一节来进行介绍的,兄弟们末慌!
gogogo!