[TOC]
用戶注冊
作為一個(gè)現(xiàn)代化電商平臺(tái),什么最重要呢?of course 是用戶,廣大用戶群體是支持我們可持續(xù)發(fā)展的基石,顧客是上帝, 雖然在當(dāng)今上帝已經(jīng)不被重視了,特別是很多的平臺(tái)對于老用戶就是恨不得趕緊Out...但是用戶量是一切的基礎(chǔ),那我們就開始創(chuàng)建我們的上帝吧!
創(chuàng)建數(shù)據(jù)庫
數(shù)據(jù)庫的部分,我在這里就不多講了,大家需要的話可以直接去傳送門 抓取腳本expensive-shop.sql.
生成UserMapper
參考上節(jié)內(nèi)容:傳送門
編寫業(yè)務(wù)邏輯
首先,我們先來分析一下要注冊一個(gè)用戶,我們系統(tǒng)都需要做哪些動(dòng)作?

- validate
- input string(校驗(yàn)輸入我們需要通過兩個(gè)角度處理)
- FrontEnd valid
前端校驗(yàn)是為了降低我們服務(wù)器端壓力而做的一部分校驗(yàn),這部分校驗(yàn)可以攔截大多數(shù)的錯(cuò)誤請求。
- Backend valid
> 后端校驗(yàn)是為了防止某些不法小伙伴繞開前端從而直接訪問我們的api造成數(shù)據(jù)請求服務(wù)器錯(cuò)誤,或者前端小伙伴程序有bug...無論是哪一種可能性,都有可能造成嚴(yán)重的后果。
- email & mobile invalid
因?yàn)楸救藳]有追求email / 短信發(fā)送服務(wù)器,所以這一步就pass,小伙伴們可以自行研究哈。
- input string(校驗(yàn)輸入我們需要通過兩個(gè)角度處理)
- control
- create user
校驗(yàn)通過后,就可以進(jìn)行創(chuàng)建用戶的動(dòng)作了。
接下來,我們就可以來實(shí)際編碼實(shí)現(xiàn)業(yè)務(wù)了,我們使用最基本的分層架構(gòu),在之前我們已經(jīng)通過Mybatis Generator工具生成了基本的pojo,mapper,對于簡單的操作我們只需要再編寫service和controller層就可以完成我們的開發(fā)工作了。
- create user
編寫user service
在mscx-shop-service中創(chuàng)建com.liferunner.service.IUserService接口,包含2個(gè)方法findUserByUserName和createUser,如下:
public interface IUserService {
/**
* 根據(jù)用戶名查詢用戶是否存在
*
* @param username
* @return
*/
Users findUserByUserName(String username);
/**
* 創(chuàng)建用戶
*
* @param userRequestDTO 用戶請求dto
* @return 當(dāng)前用戶
*/
Users createUser(UserRequestDTO userRequestDTO) throws Exception;
}
接著,我們需要具體實(shí)現(xiàn)這個(gè)接口類,如下:
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
private final String FACE_IMG = "https://avatars1.githubusercontent.com/u/4083152?s=88&v=4";
// 構(gòu)造器注入
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) {
// 構(gòu)建查詢條件
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來構(gòu)建mybatis的查詢參數(shù),如果有多個(gè)查詢條件,可以通過example.createCriteria().addxxx逐一添加。
UserServiceImpl#createUser 說明
-
@Transactional(propagation = Propagation.REQUIRED),開啟事務(wù),選擇事務(wù)傳播級(jí)別為REQUIRED,表示必須要有一個(gè)事務(wù)存在,如果調(diào)用者不存在事務(wù),那本方法就自己開啟一個(gè)新的事物,如果調(diào)用方本身存在一個(gè)活躍的事務(wù),那本方法就加入到它里面(同生共死)。 -
org.n3r.idworker.Sid, 這個(gè)是一個(gè)開源的 分布式ID生成器組件,傳送門, 后期有機(jī)會(huì)的話,會(huì)專門寫一個(gè)id生成器文章。 -
MD5GeneratorTools是用來對數(shù)據(jù)進(jìn)行MD5加密的工具類,大家可以在源碼中下載。也可以直接使用java.security.MessageDigest直接加密實(shí)現(xiàn),總之密碼不能明文存儲(chǔ)就行了。 -
SexEnum這個(gè)是一個(gè)表述性別類型的枚舉,在我們編碼的規(guī)范中,盡量要求不要出現(xiàn)Magic number,就是開發(fā)界常說的魔術(shù)數(shù)字(即1,2,300...) - 這里的日志打印,可能有人會(huì)問為什么你沒有聲明類似:
private final static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);,這是因?yàn)槲覀冊陂_始的時(shí)候,我們引入了lombok依賴,不記得的同學(xué)可以參考傳送門。在這里依賴中,它繼承了很多的日志組件,我們只需要使用一個(gè)注解lombok.extern.slf4j.Slf4j來開啟日志,使用log.info..就可以了。 -
UserRequestDTO又是個(gè)什么鬼?在我們開發(fā)的過程中,很可能會(huì)有大批量的參數(shù)需要傳遞,這時(shí)我們?nèi)绻褂?code>xxx#(String aa,Integer bb,Boolean cc...)會(huì)讓我們煩不勝數(shù),而且看著也不美觀,這時(shí)候我們就可以選擇創(chuàng)建一個(gè)新對象來幫助我們傳遞數(shù)據(jù),那么也就是我們的UserRequestDTO對象,所謂的DTO就是Data Transfer Object的首字母縮寫,顧名思義,它是用來傳遞數(shù)據(jù)對象用的。
編寫user controller
同樣在mscx-shop-api中,創(chuàng)建com.liferunner.api.controller.UserController,實(shí)現(xiàn)用戶創(chuàng)建。
@RestController
@RequestMapping(name = "/users")
@Slf4j
@Api(tags="用戶管理")
public class UserController {
@Autowired
private IUserService userService;
@ApiOperation("校驗(yàn)是否重名")
@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("創(chuàng)建用戶")
@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("創(chuàng)建用戶失敗,{}", userRequestDTO);
}
return JsonResponse.errorMsg("創(chuàng)建用戶失敗");
}
}
UserController#validateUsername(username) 說明
-
JsonResponse對象是為了方便返回給客戶端一個(gè)統(tǒng)一的格式而封裝的數(shù)據(jù)對象。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonResponse {
// 定義jackson對象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 響應(yīng)業(yè)務(wù)狀態(tài)
private Integer status;
// 響應(yīng)消息
private String message;
// 響應(yīng)中的數(shù)據(jù)
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) 說明
- 如上文所講,需要先做各種校驗(yàn)
- 成功則返回
JsonResponse - 細(xì)心的同學(xué)可能看到了上文中有幾個(gè)注解
@Api(tags="用戶管理"),@ApiOperation("創(chuàng)建用戶"),這個(gè)是Swagger 的注解,我們會(huì)在下一節(jié)和大家詳細(xì)探討,以及如何生成off-line docs。
測試API
在我們每次修改完成之后,都盡可能的mvn clean install一次,因?yàn)槲覀冸`屬不同的project,如果不重新安裝一次,偶爾遇到的問題會(huì)讓人懷疑人生的。
...
[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] ------------------------------------------------------------------------
當(dāng)看到上述運(yùn)行結(jié)果之后,就可以啟動(dòng)我們的應(yīng)用就行測試?yán)病?/p>
UserController#validateUsername(username) 測試
測試API的方式有很多種,比如curl localhost:8080/validateUsername,在比如使用超級(jí)流行的Postman也是完全ok的,我這里用的是之前在第一篇中和大家所說的一個(gè)插件Restful Toolkit(可以實(shí)現(xiàn)和postman一樣的簡單效果,同時(shí)還能幫助我們生成一部分測試信息),當(dāng)我們應(yīng)用啟動(dòng)之后,效果如下圖,

我們可以看到,插件幫我們生成了幾個(gè)測試方法,比如我們點(diǎn)擊validateUsername,下方就會(huì)生成當(dāng)前方法是一個(gè)包含username參數(shù)的GET方法,demoData是插件默認(rèn)給我們生成的測試數(shù)據(jù)。可以隨意修改。
點(diǎn)擊Send:

可以看到請求成功了,并且返回我們自定義的JSON格式數(shù)據(jù)。
UserController#createUser(UserRequestDTO) 測試
接著我們繼續(xù)測試用戶注冊接口,請求如下:

可以看到,當(dāng)我們選擇
create方法時(shí),插件自動(dòng)幫我們設(shè)置請求類型為POST,并且RequestBody的默認(rèn)值也幫助我們生成了,我只修改了默認(rèn)的username和password值,confimPassword的默認(rèn)值我沒有變動(dòng),那按照我們的校驗(yàn)邏輯,它應(yīng)該返回的是return JsonResponse.errorMsg("兩次密碼不一致!");這一行,點(diǎn)擊Send:
修改
confimPassword為12345678,點(diǎn)擊Send:
可以看到,創(chuàng)建用戶成功,并且將當(dāng)前創(chuàng)建的用戶返回到了我們請求客戶端。那么我們繼續(xù)重復(fù)點(diǎn)擊創(chuàng)建,會(huì)怎么樣呢?繼續(xù)Send:

可以看到,我們的驗(yàn)證重復(fù)用戶也已經(jīng)生效啦。
下節(jié)預(yù)告
下一節(jié)我們將學(xué)習(xí)如何使用Swagger自動(dòng)生成API接口文檔給前端,以及如果沒有外部網(wǎng)絡(luò)的情況下,或者需要和第三方平臺(tái)對接的時(shí)候,我們?nèi)绾紊?code>離線文檔給到第三方。
gogogo!
奔跑的人生 | 博客園 | segmentfault | spring4all | csdn | 掘金 | OSChina | 簡書 | 頭條 | 知乎 | 51CTO