[springboot 開發單體web shop] 3. 用戶註冊實現

  • 2019 年 11 月 7 日
  • 筆記

用戶註冊

作為一個現代化電商平台,什麼最重要呢?of course 是用戶,廣大用戶群體是支援我們可持續發展的基石,顧客是上帝, 雖然在當今上帝已經不被重視了,特別是很多的平台對於老用戶就是恨不得趕緊Out…但是用戶量是一切的基礎,那我們就開始創建我們的上帝吧!

## 創建資料庫

資料庫的部分,我在這裡就不多講了,大家需要的話可以直接去傳送門 抓取腳本expensive-shop.sql.

## 生成UserMapper

參考上節內容:傳送門

## 編寫業務邏輯

首先,我們先來分析一下要註冊一個用戶,我們系統都需要做哪些動作?
user register

  • validate
    • input string(校驗輸入我們需要通過兩個角度處理)
      • FrontEnd valid

        前端校驗是為了降低我們伺服器端壓力而做的一部分校驗,這部分校驗可以攔截大多數的錯誤請求。

      • Backend valid

        後端校驗是為了防止某些不法小夥伴繞開前端從而直接訪問我們的api造成數據請求伺服器錯誤,或者前端小夥伴程式有bug…無論是哪一種可能性,都有可能造成嚴重的後果。

    • email & mobile invalid

      因為本人沒有追求email / 簡訊發送伺服器,所以這一步就pass,小夥伴們可以自行研究哈。

  • control
    • create user

      校驗通過後,就可以進行創建用戶的動作了。
      接下來,我們就可以來實際編碼實現業務了,我們使用最基本的分層架構,在之前我們已經通過Mybatis Generator工具生成了基本的pojo,mapper,對於簡單的操作我們只需要再編寫servicecontroller層就可以完成我們的開發工作了。

## 編寫user service

mscx-shop-service中創建com.liferunner.service.IUserService介面,包含2個方法findUserByUserNamecreateUser,如下:

public interface IUserService {        /**       * 根據用戶名查詢用戶是否存在       *       * @param username       * @return       */      Users findUserByUserName(String username);        /**       * 創建用戶       *       * @param userRequestDTO 用戶請求dto       * @return 當前用戶       */      Users createUser(UserRequestDTO userRequestDTO) throws Exception;  }

接著,我們需要具體實現這個介面類,如下:

@Service  @Slf4j  public class UserServiceImpl implements IUserService {      private final String FACE_IMG = "https://avatars1.githubusercontent.com/u/4083152?s=88&v=4";        // 構造器注入      private final UsersMapper usersMapper;      private final Sid sid;        @Autowired      public UserServiceImpl(UsersMapper usersMapper, Sid sid) {          this.usersMapper = usersMapper;          this.sid = sid;      }        @Override      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;      }  }

這裡有幾處地方有必要說明一下:

UserServiceImpl#findUserByUserName 說明

  • tk.mybatis.mapper.entity.Example 通過使用Example來構建mybatis的查詢參數,如果有多個查詢條件,可以通過example.createCriteria().addxxx逐一添加。

UserServiceImpl#createUser 說明

  • @Transactional(propagation = Propagation.REQUIRED),開啟事務,選擇事務傳播級別為REQUIRED,表示必須要有一個事務存在,如果調用者不存在事務,那本方法就自己開啟一個新的事物,如果調用方本身存在一個活躍的事務,那本方法就加入到它裡面(同生共死)。
  • org.n3r.idworker.Sid, 這個是一個開源的 分散式ID生成器組件,傳送門, 後期有機會的話,會專門寫一個id生成器文章。
  • MD5GeneratorTools 是用來對數據進行MD5加密的工具類,大家可以在源碼中下載。也可以直接使用java.security.MessageDigest 直接加密實現,總之密碼不能明文存儲就行了。
  • SexEnum 這個是一個表述性別類型的枚舉,在我們編碼的規範中,盡量要求不要出現Magic number,就是開發界常說的魔術數字(即1,2,300…)
  • 這裡的日誌列印,可能有人會問為什麼你沒有聲明類似:private final static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); ,這是因為我們在開始的時候,我們引入了lombok依賴,不記得的同學可以參考傳送門。在這裡依賴中,它繼承了很多的日誌組件,我們只需要使用一個註解lombok.extern.slf4j.Slf4j來開啟日誌,使用log.info..就可以了。
  • UserRequestDTO 又是個什麼鬼?在我們開發的過程中,很可能會有大批量的參數需要傳遞,這時我們如果使用xxx#(String aa,Integer bb,Boolean cc...)會讓我們煩不勝數,而且看著也不美觀,這時候我們就可以選擇創建一個新對象來幫助我們傳遞數據,那麼也就是我們的UserRequestDTO對象,所謂的DTO就是Data Transfer Object的首字母縮寫,顧名思義,它是用來傳遞數據對象用的。

## 編寫user controller

同樣在mscx-shop-api中,創建com.liferunner.api.controller.UserController,實現用戶創建。

@RestController  @RequestMapping(name = "/users")  @Slf4j  @Api(tags="用戶管理")  public class UserController {        @Autowired      private IUserService userService;        @ApiOperation("校驗是否重名")      @GetMapping("/validateUsername")      public JsonResponse validateUsername(@RequestParam String username) {          // 判斷用戶名是否非法          if (StringUtils.isBlank(username))              return JsonResponse.errorMsg("用戶名不能為空!");          if (null != userService.findUserByUserName(username))              return JsonResponse.errorMsg("用戶名已存在!");          // 用戶名可用          return JsonResponse.ok();      }        @ApiOperation("創建用戶")      @PostMapping("/create")      public JsonResponse createUser(@RequestBody UserRequestDTO userRequestDTO) {          try {              if (StringUtils.isBlank(userRequestDTO.getUsername()))                  return JsonResponse.errorMsg("用戶名不能為空");              if (null != this.userService.findUserByUserName(userRequestDTO.getUsername())) {                  return JsonResponse.errorMsg("用戶名已存在!");              }              if (StringUtils.isBlank(userRequestDTO.getPassword()) ||                      StringUtils.isBlank(userRequestDTO.getConfimPassword()) ||                      userRequestDTO.getPassword().length() < 8) {                  return JsonResponse.errorMsg("密碼為空或長度小於8位");              }              if (!userRequestDTO.getPassword().equals(userRequestDTO.getConfimPassword()))                  return JsonResponse.errorMsg("兩次密碼不一致!");              val user = this.userService.createUser(userRequestDTO);              if (null != user)                  return JsonResponse.ok(user);          } catch (Exception e) {              log.error("創建用戶失敗,{}", userRequestDTO);          }          return JsonResponse.errorMsg("創建用戶失敗");      }  }

UserController#validateUsername(username) 說明

  • JsonResponse對象是為了方便返回給客戶端一個統一的格式而封裝的數據對象。
@Data  @NoArgsConstructor  @AllArgsConstructor  public class JsonResponse {        // 定義jackson對象      private static final ObjectMapper MAPPER = new ObjectMapper();      // 響應業務狀態      private Integer status;      // 響應消息      private String message;      // 響應中的數據      private Object data;        public static JsonResponse build(Integer status, String msg, Object data) {          return new JsonResponse(status, msg, data);      }        public static JsonResponse ok(Object data) {          return new JsonResponse(data);      }        public static JsonResponse ok() {          return new JsonResponse(null);      }        public static JsonResponse errorMsg(String msg) {          return new JsonResponse(500, msg, null);      }        public static JsonResponse errorMap(Object data) {          return new JsonResponse(501, "error", data);      }        public static JsonResponse errorTokenMsg(String msg) {          return new JsonResponse(502, msg, null);      }        public static JsonResponse errorException(String msg) {          return new JsonResponse(555, msg, null);      }        public static JsonResponse errorUserQQ(String msg) {          return new JsonResponse(556, msg, null);      }        public JsonResponse(Object data) {          this.status = 200;          this.message = "OK";          this.data = data;      }        public Boolean isOK() {          return this.status == 200;      }  }

UserController#createUser(UserRequestDTO) 說明

  • 如上文所講,需要先做各種校驗
  • 成功則返回JsonResponse
  • 細心的同學可能看到了上文中有幾個註解@Api(tags="用戶管理"),@ApiOperation("創建用戶"),這個是Swagger 的註解,我們會在下一節和大家詳細探討,以及如何生成off-line docs

測試API


在我們每次修改完成之後,都儘可能的mvn clean install一次,因為我們隸屬不同的project,如果不重新安裝一次,偶爾遇到的問題會讓人懷疑人生的。

...  [INFO] expensive-shop ..................................... SUCCESS [  1.220 s]  [INFO] mscx-shop-common ................................... SUCCESS [  9.440 s]  [INFO] mscx-shop-pojo ..................................... SUCCESS [  2.020 s]  [INFO] mscx-shop-mapper ................................... SUCCESS [  1.564 s]  [INFO] mscx-shop-service .................................. SUCCESS [  1.366 s]  [INFO] mscx-shop-api ...................................... SUCCESS [  4.614 s]  [INFO] ------------------------------------------------------------------------  [INFO] BUILD SUCCESS  [INFO] ------------------------------------------------------------------------  [INFO] Total time:  20.739 s  [INFO] Finished at: 2019-11-06T14:53:55+08:00  [INFO] ------------------------------------------------------------------------

當看到上述運行結果之後,就可以啟動我們的應用就行測試啦~

UserController#validateUsername(username) 測試

測試API的方式有很多種,比如curl localhost:8080/validateUsername,在比如使用超級流行的Postman也是完全ok的,我這裡用的是之前在第一篇中和大家所說的一個插件Restful Toolkit(可以實現和postman一樣的簡單效果,同時還能幫助我們生成一部分測試資訊),當我們應用啟動之後,效果如下圖,
rest plugin

我們可以看到,插件幫我們生成了幾個測試方法,比如我們點擊validateUsername,下方就會生成當前方法是一個包含username參數的GET方法,demoData是插件默認給我們生成的測試數據。可以隨意修改。
點擊Send:
result
可以看到請求成功了,並且返回我們自定義的JSON格式數據。

UserController#createUser(UserRequestDTO) 測試

接著我們繼續測試用戶註冊介面,請求如下:
send
可以看到,當我們選擇create方法時,插件自動幫我們設置請求類型為POST,並且RequestBody的默認值也幫助我們生成了,我只修改了默認的usernamepassword值,confimPassword的默認值我沒有變動,那按照我們的校驗邏輯,它應該返回的是return JsonResponse.errorMsg("兩次密碼不一致!");這一行,點擊Send:
result
修改confimPassword12345678,點擊Send:
result2
可以看到,創建用戶成功,並且將當前創建的用戶返回到了我們請求客戶端。那麼我們繼續重複點擊創建,會怎麼樣呢?繼續Send:
result3
可以看到,我們的驗證重複用戶也已經生效啦。

下節預告


下一節我們將學習如何使用Swagger自動生成API介面文檔給前端,以及如果沒有外部網路的情況下,或者需要和第三方平台對接的時候,我們如何生成離線文檔給到第三方。
gogogo!