[springboot 開(kāi)發(fā)單體web shop] 3. 用戶注冊(cè)實(shí)現(xiàn)

[TOC]

用戶注冊(cè)

作為一個(gè)現(xiàn)代化電商平臺(tái),什么最重要呢型豁?of course 是用戶僵蛛,廣大用戶群體是支持我們可持續(xù)發(fā)展的基石,顧客是上帝迎变, 雖然在當(dāng)今上帝已經(jīng)不被重視了充尉,特別是很多的平臺(tái)對(duì)于老用戶就是恨不得趕緊Out...但是用戶量是一切的基礎(chǔ),那我們就開(kāi)始創(chuàng)建我們的上帝吧衣形!

創(chuàng)建數(shù)據(jù)庫(kù)


數(shù)據(jù)庫(kù)的部分喉酌,我在這里就不多講了,大家需要的話可以直接去傳送門 抓取腳本expensive-shop.sql.

生成UserMapper


參考上節(jié)內(nèi)容:傳送門

編寫業(yè)務(wù)邏輯


首先泵喘,我們先來(lái)分析一下要注冊(cè)一個(gè)用戶泪电,我們系統(tǒng)都需要做哪些動(dòng)作?


user register
  • validate
    • input string(校驗(yàn)輸入我們需要通過(guò)兩個(gè)角度處理)
      • FrontEnd valid

      前端校驗(yàn)是為了降低我們服務(wù)器端壓力而做的一部分校驗(yàn)纪铺,這部分校驗(yàn)可以攔截大多數(shù)的錯(cuò)誤請(qǐng)求相速。
      - Backend valid
      > 后端校驗(yàn)是為了防止某些不法小伙伴繞開(kāi)前端從而直接訪問(wèn)我們的api造成數(shù)據(jù)請(qǐng)求服務(wù)器錯(cuò)誤,或者前端小伙伴程序有bug...無(wú)論是哪一種可能性鲜锚,都有可能造成嚴(yán)重的后果突诬。
      - email & mobile invalid

    因?yàn)楸救藳](méi)有追求email / 短信發(fā)送服務(wù)器苫拍,所以這一步就pass,小伙伴們可以自行研究哈旺隙。

  • control
    • create user

      校驗(yàn)通過(guò)后绒极,就可以進(jìn)行創(chuàng)建用戶的動(dòng)作了。
      接下來(lái)蔬捷,我們就可以來(lái)實(shí)際編碼實(shí)現(xiàn)業(yè)務(wù)了垄提,我們使用最基本的分層架構(gòu),在之前我們已經(jīng)通過(guò)Mybatis Generator工具生成了基本的pojo,mapper周拐,對(duì)于簡(jiǎn)單的操作我們只需要再編寫servicecontroller層就可以完成我們的開(kāi)發(fā)工作了铡俐。

編寫user service


mscx-shop-service中創(chuàng)建com.liferunner.service.IUserService接口,包含2個(gè)方法findUserByUserNamecreateUser妥粟,如下:

public interface IUserService {

    /**
     * 根據(jù)用戶名查詢用戶是否存在
     *
     * @param username
     * @return
     */
    Users findUserByUserName(String username);

    /**
     * 創(chuàng)建用戶
     *
     * @param userRequestDTO 用戶請(qǐng)求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;
    }
}

這里有幾處地方有必要說(shuō)明一下:

UserServiceImpl#findUserByUserName 說(shuō)明

  • tk.mybatis.mapper.entity.Example 通過(guò)使用Example來(lái)構(gòu)建mybatis的查詢參數(shù)勾给,如果有多個(gè)查詢條件滩报,可以通過(guò)example.createCriteria().addxxx逐一添加。

UserServiceImpl#createUser 說(shuō)明

  • @Transactional(propagation = Propagation.REQUIRED),開(kāi)啟事務(wù)播急,選擇事務(wù)傳播級(jí)別為REQUIRED,表示必須要有一個(gè)事務(wù)存在露泊,如果調(diào)用者不存在事務(wù),那本方法就自己開(kāi)啟一個(gè)新的事物旅择,如果調(diào)用方本身存在一個(gè)活躍的事務(wù),那本方法就加入到它里面(同生共死)侣姆。
  • org.n3r.idworker.Sid, 這個(gè)是一個(gè)開(kāi)源的 分布式ID生成器組件生真,傳送門, 后期有機(jī)會(huì)的話,會(huì)專門寫一個(gè)id生成器文章捺宗。
  • MD5GeneratorTools 是用來(lái)對(duì)數(shù)據(jù)進(jìn)行MD5加密的工具類柱蟀,大家可以在源碼中下載。也可以直接使用java.security.MessageDigest 直接加密實(shí)現(xiàn)蚜厉,總之密碼不能明文存儲(chǔ)就行了长已。
  • SexEnum 這個(gè)是一個(gè)表述性別類型的枚舉,在我們編碼的規(guī)范中昼牛,盡量要求不要出現(xiàn)Magic number,就是開(kāi)發(fā)界常說(shuō)的魔術(shù)數(shù)字(即1,2,300...)
  • 這里的日志打印术瓮,可能有人會(huì)問(wèn)為什么你沒(méi)有聲明類似:private final static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); ,這是因?yàn)槲覀冊(cè)陂_(kāi)始的時(shí)候,我們引入了lombok依賴贰健,不記得的同學(xué)可以參考傳送門胞四。在這里依賴中,它繼承了很多的日志組件伶椿,我們只需要使用一個(gè)注解lombok.extern.slf4j.Slf4j來(lái)開(kāi)啟日志辜伟,使用log.info..就可以了氓侧。
  • UserRequestDTO 又是個(gè)什么鬼?在我們開(kāi)發(fā)的過(guò)程中导狡,很可能會(huì)有大批量的參數(shù)需要傳遞约巷,這時(shí)我們?nèi)绻褂?code>xxx#(String aa,Integer bb,Boolean cc...)會(huì)讓我們煩不勝數(shù),而且看著也不美觀旱捧,這時(shí)候我們就可以選擇創(chuàng)建一個(gè)新對(duì)象來(lái)幫助我們傳遞數(shù)據(jù)独郎,那么也就是我們的UserRequestDTO對(duì)象,所謂的DTO就是Data Transfer Object的首字母縮寫廊佩,顧名思義囚聚,它是用來(lái)傳遞數(shù)據(jù)對(duì)象用的。

編寫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("密碼為空或長(zhǎng)度小于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) 說(shuō)明

  • JsonResponse對(duì)象是為了方便返回給客戶端一個(gè)統(tǒng)一的格式而封裝的數(shù)據(jù)對(duì)象践剂。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonResponse {

    // 定義jackson對(duì)象
    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) 說(shuō)明

  • 如上文所講鬼譬,需要先做各種校驗(yàn)
  • 成功則返回JsonResponse
  • 細(xì)心的同學(xué)可能看到了上文中有幾個(gè)注解@Api(tags="用戶管理"),@ApiOperation("創(chuàng)建用戶"),這個(gè)是Swagger 的注解,我們會(huì)在下一節(jié)和大家詳細(xì)探討逊脯,以及如何生成off-line docs优质。

測(cè)試API


在我們每次修改完成之后,都盡可能的mvn clean install一次军洼,因?yàn)槲覀冸`屬不同的project巩螃,如果不重新安裝一次,偶爾遇到的問(wèn)題會(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)用就行測(cè)試?yán)病?/p>

UserController#validateUsername(username) 測(cè)試

測(cè)試API的方式有很多種,比如curl localhost:8080/validateUsername甘桑,在比如使用超級(jí)流行的Postman也是完全ok的拍皮,我這里用的是之前在第一篇中和大家所說(shuō)的一個(gè)插件Restful Toolkit(可以實(shí)現(xiàn)和postman一樣的簡(jiǎn)單效果,同時(shí)還能幫助我們生成一部分測(cè)試信息),當(dāng)我們應(yīng)用啟動(dòng)之后跑杭,效果如下圖铆帽,

rest plugin

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

result

可以看到請(qǐng)求成功了屑迂,并且返回我們自定義的JSON格式數(shù)據(jù)。

UserController#createUser(UserRequestDTO) 測(cè)試

接著我們繼續(xù)測(cè)試用戶注冊(cè)接口冯键,請(qǐng)求如下:

send

可以看到惹盼,當(dāng)我們選擇create方法時(shí),插件自動(dòng)幫我們?cè)O(shè)置請(qǐng)求類型為POST惫确,并且RequestBody的默認(rèn)值也幫助我們生成了手报,我只修改了默認(rèn)的usernamepassword值,confimPassword的默認(rèn)值我沒(méi)有變動(dòng)改化,那按照我們的校驗(yàn)邏輯掩蛤,它應(yīng)該返回的是return JsonResponse.errorMsg("兩次密碼不一致!");這一行陈肛,點(diǎn)擊Send:
result

修改confimPassword12345678,點(diǎn)擊Send:
result2

可以看到揍鸟,創(chuàng)建用戶成功,并且將當(dāng)前創(chuàng)建的用戶返回到了我們請(qǐng)求客戶端句旱。那么我們繼續(xù)重復(fù)點(diǎn)擊創(chuàng)建阳藻,會(huì)怎么樣呢?繼續(xù)Send:
result3

可以看到谈撒,我們的驗(yàn)證重復(fù)用戶也已經(jīng)生效啦腥泥。

下節(jié)預(yù)告


下一節(jié)我們將學(xué)習(xí)如何使用Swagger自動(dòng)生成API接口文檔給前端,以及如果沒(méi)有外部網(wǎng)絡(luò)的情況下啃匿,或者需要和第三方平臺(tái)對(duì)接的時(shí)候蛔外,我們?nèi)绾紊?code>離線文檔給到第三方。
gogogo溯乒!


奔跑的人生 | 博客園 | segmentfault | spring4all | csdn | 掘金 | OSChina | 簡(jiǎn)書(shū) | 頭條 | 知乎 | 51CTO

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夹厌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子橙数,更是在濱河造成了極大的恐慌,老刑警劉巖帅戒,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灯帮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逻住,警方通過(guò)查閱死者的電腦和手機(jī)钟哥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瞎访,“玉大人腻贰,你說(shuō)我怎么就攤上這事“墙眨” “怎么了播演?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵冀瓦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我写烤,道長(zhǎng)翼闽,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任洲炊,我火速辦了婚禮感局,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暂衡。我一直安慰自己询微,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布狂巢。 她就那樣靜靜地躺著撑毛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隧膘。 梳的紋絲不亂的頭發(fā)上代态,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音疹吃,去河邊找鬼徘跪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛车份,可吹牛的內(nèi)容都是我干的进肯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腔呜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叁温!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起核畴,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤膝但,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后谤草,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跟束,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年丑孩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冀宴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡温学,死狀恐怖略贮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤逃延,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布览妖,位于F島的核電站,受9級(jí)特大地震影響真友,放射性物質(zhì)發(fā)生泄漏黄痪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一盔然、第九天 我趴在偏房一處隱蔽的房頂上張望桅打。 院中可真熱鬧,春花似錦愈案、人聲如沸挺尾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遭铺。三九已至,卻和暖如春恢准,著一層夾襖步出監(jiān)牢的瞬間魂挂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工馁筐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涂召,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓敏沉,卻偏偏與公主長(zhǎng)得像果正,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盟迟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容