在以前的文章中咱們實現了用戶註冊和驗證功能,接下來咱們繼續實現它的登陸,以及登陸成功以後要在頁面上顯示的信息。
接下來,咱們來編寫代碼。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!