6 簡(jiǎn)閱項(xiàng)目 注冊(cè)短信驗(yàn)證功能

  1. 注冊(cè)流程分析
  • 客戶端先發(fā)送一個(gè)手機(jī)號(hào)碼到后端獲取驗(yàn)證碼遏佣,此時(shí)“獲取驗(yàn)證碼”按鈕是激活狀態(tài)贼急,“下一步”按鈕是禁用狀態(tài)

  • 點(diǎn)擊“獲取驗(yàn)證碼”按鈕,該按鈕60秒倒計(jì)時(shí)變?yōu)榻脿顟B(tài)令杈,等待用戶接收驗(yàn)證碼并輸入

  • 后端根據(jù)這個(gè)手機(jī)號(hào)先到數(shù)據(jù)庫查詢?cè)撌謾C(jī)號(hào)是否已被注冊(cè)逗噩,如果已經(jīng)被注冊(cè)就返回“該手機(jī)號(hào)已被注冊(cè)”异雁,不繼續(xù)后續(xù)步驟

  • 如果沒被注冊(cè)纲刀,后端為該手機(jī)號(hào)生成一個(gè)隨機(jī)6位驗(yàn)證碼存在Redis中(指定時(shí)限)示绊,然后將該驗(yàn)證碼通過阿里云的短信服務(wù)接口發(fā)送給客戶端

  • 客戶端收到短信面褐,輸入驗(yàn)證碼展哭,點(diǎn)擊下一步

  • 后端收到客戶端發(fā)送的驗(yàn)證碼匪傍,就將其和Redis中暫存的驗(yàn)證碼比對(duì)析恢,如果一致就放行泽篮,否則提示“驗(yàn)證碼錯(cuò)誤”

  • 驗(yàn)證碼正確帽撑,進(jìn)入填寫密碼的頁面亏拉,填寫完畢提交到后端,加密存入數(shù)據(jù)庫锐极,注冊(cè)成功灵再,讓用戶選擇去登錄翎迁,還是回到首頁

  1. Redis準(zhǔn)備
  • 下載Redis汪榔,Redis是最流行的鍵值對(duì)存儲(chǔ)數(shù)據(jù)庫痴腌,官網(wǎng)只有Linux版本的Redis衷掷,自行下載后可以到阿里云ECS去安裝戚嗅,本地只能從GitHub下載懦胞,地址點(diǎn)這里
    基本操作如下
    解壓后放到本地目錄

    image

cd D:\tools\redis
redis-server.exe redis.windows.conf

image

再開一個(gè)命令行窗口,進(jìn)入redis目錄胀糜,運(yùn)行redis-cli.exe命令教藻,進(jìn)行一下基本操作括堤,set是設(shè)置鍵值對(duì)讥电,keys是列出所有鍵轧抗,get是獲取指定鍵的值鸦致,del是刪除指定的鍵值

image
  1. 阿里云短信服務(wù)
package com.soft1721.jianyue.api.util;

import com.aliyuncs.CommonRequest;

import com.aliyuncs.CommonResponse;

import com.aliyuncs.DefaultAcsClient;

import com.aliyuncs.IAcsClient;

import com.aliyuncs.exceptions.ClientException;

import com.aliyuncs.exceptions.ServerException;

import com.aliyuncs.http.MethodType;

import com.aliyuncs.profile.DefaultProfile;

/**

* 短消息測(cè)試程序

*/

public class SMSTest {

public static void main(String[] args) {

DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "*******", "*****");

IAcsClient client = new DefaultAcsClient(profile);

CommonRequest request = new CommonRequest();

request.setMethod(MethodType.POST);

request.setDomain("dysmsapi.aliyuncs.com");

request.setVersion("2017-05-25");

request.setAction("SendSms");

request.putQueryParameter("RegionId", "cn-hangzhou");

request.putQueryParameter("PhoneNumbers", "****");

request.putQueryParameter("SignName", "****");

request.putQueryParameter("TemplateCode", "SMS_135805735");

request.putQueryParameter("TemplateParam", "{\"code\":\"888888\"}");

try {

CommonResponse response = client.getCommonResponse(request);

System.out.println(response.getData());

} catch (ServerException e) {

e.printStackTrace();

} catch (ClientException e) {

e.printStackTrace();

}

}

}

運(yùn)行結(jié)果:

image

后端
pom.xml,增加阿里云短信服務(wù)SDK依賴和SpringBoot集成redis依賴

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

util包的StringUtil類增加一個(gè)方法删顶,用來獲取六位隨機(jī)數(shù)驗(yàn)證碼

public static String getVerifyCode() {
    Random random = new Random();
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < 6; i++) {
        stringBuilder.append(String.valueOf(random.nextInt(10)));
    }
    return stringBuilder.toString();
}

編寫SMSUtil短信發(fā)送工具類逗余,主要將手機(jī)號(hào)和短信字符串分離成變量录粱,main方法測(cè)試通過后即可刪除

import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信發(fā)送工具類啥繁,返回生成的隨機(jī)驗(yàn)證碼
 */
public class SMSUtil {
    public static String send(String mobile) {
        DefaultProfile profile = DefaultProfile.getProfile(
                "cn-hangzhou",
                "*******",
                "*******");
        IAcsClient client = new DefaultAcsClient(profile);
        CommonRequest request = new CommonRequest();
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");
        request.putQueryParameter("RegionId", "cn-hangzhou");
        request.putQueryParameter("PhoneNumbers", mobile);
        request.putQueryParameter("SignName", "*****");
        request.putQueryParameter("TemplateCode", "SMS_135805735");
        String verifyCode = StringUtil.getVerifyCode();
        request.putQueryParameter("TemplateParam", "{\"code\":" + verifyCode + "}");
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return verifyCode;
    }

    public static void main(String[] args) {
        System.out.println(send("139****1489"));
    }
}

mapper層增加insertUser方法,自行實(shí)現(xiàn)(id和token不用給值)
service接口增加注冊(cè)方法

void signUp(UserDTO userDTO);

signUp方法實(shí)現(xiàn)代碼

@Override
public void signUp(UserDTO userDTO) {
    User user1 = new User();
    user1.setMobile(userDTO.getMobile());
 user1.setPassword(StringUtil.getBase64Encoder(userDTO.getPassword()));
    user1.setNickname("新用戶");
 user1.setAvatar("http://ppeto2k90.bkt.clouddn.com/avatar/default.png");
    user1.setRegtime(new Date());
    user1.setStatus((short) 1);
    userMapper.insert(user1);
}

單元測(cè)試

@Test
public void signUp() {
    UserDTO userDTO = new UserDTO();
    userDTO.setMobile("139****1489");
    userDTO.setPassword("111");
    userService.signUp(userDTO);
 }

controller層編寫前愁茁,先到StatusConst和MsgConst添加需要用到的常量

public static final int MOBILE_EXIST = 5;
public static final int VERIFYCODE_ERROR = 6;

public static final String MOBILE_EXIST = "手機(jī)號(hào)已被注冊(cè)";
public static final String VERIFYCODE_ERROR = "驗(yàn)證碼錯(cuò)誤";

獲取短信驗(yàn)證碼接口

 @PostMapping(value = "/verify")
    public ResponseResult getVerifyCode(@RequestParam("mobile") String mobile) {
        User user = userService.getUserByMobile(mobile);
        //手機(jī)號(hào)已經(jīng)被注冊(cè)
        if (user != null) {
            return ResponseResult.error(StatusConst.MOBILE_EXIST, MsgConst.MOBILE_EXIST);
        } else {
            //發(fā)送驗(yàn)證碼
            String verifyCode = SMSUtil.send(mobile);
//            String verifyCode = StringUtil.getVerifyCode();
            System.out.println(verifyCode);
            //手機(jī)號(hào)和驗(yàn)證碼作為鍵值對(duì)存入redis中
            redisService.set(mobile, verifyCode);
            return ResponseResult.success();
        }
    }

驗(yàn)證短信碼接口

@PostMapping(value = "/check")
public ResponseResult checkVerifyCode(@RequestParam("mobile") String mobile, @RequestParam("verifyCode") String verifyCode) {
    //從Redis中取出這個(gè)手機(jī)號(hào)的驗(yàn)證碼
    String code = redisService.get(mobile).toString();
    //System.out.println(code + "---");
   // System.out.println(verifyCode);
    //和客戶端傳過來的驗(yàn)證碼比對(duì)
    if (code.equals(verifyCode)) {
        return ResponseResult.success();
    } else {
        return ResponseResult.error(StatusConst.VERIFYCODE_ERROR, MsgConst.VERIFYCODE_ERROR);
    }
}

注冊(cè)接口

@PostMapping(value = "/sign_up")
public ResponseResult signUp(@RequestBody UserDTO userDTO) {
    userService.signUp(userDTO);
    return ResponseResult.success();
}

后端swagger測(cè)試結(jié)果

前端

在main.js中配置全局服務(wù)器地址

Vue.prototype.apiServer = 'http://*****:8080/api'

注冊(cè)頁面

<template>
    <view class="container">
        <view class="sign-box">
            <input class="uni-input left" type="number" placeholder="輸入手機(jī)號(hào)" v-model="mobile" required="required" />
            <button class="green-btn small-btn right" @tap="getVerifyCode">獲取驗(yàn)證碼</button>
        </view>
        <input class="uni-input" type="number" placeholder="輸入驗(yàn)證碼" v-model="verifyCode" required="required" />
        <button @tap="checkCode" class="green-btn">下一步</button>
    </view>
</template>

<script>
export default {
    data() {
        return {
            mobile: '',
            verifyCode: ''
        };
    },
    onLoad() {},
    methods: {
        getVerifyCode: function() {
            var _this = this;
            uni.request({
                url: this.apiServer + '/user/verify',
                method: 'POST',
                header: { 'content-type': 'application/x-www-form-urlencoded' },
                data: {
                    mobile: _this.mobile
                },
                success: res => {
                    if (res.data.code === 0) {
                        uni.showToast({
                            title: '驗(yàn)證碼已發(fā)送'
                        });
                        _this.disabled = true;
                        console.log(_this.disabled);
                    } else {
                        uni.showModal({
                            title: '提示',
                            content: res.data.msg
                        });
                    }
                }
            });
        },
        checkCode: function() {
            var _this = this;
            console.log(_this.verifyCode);
            uni.request({
                url: this.apiServer + '/user/check',
                method: 'POST',
                header: { 'content-type': 'application/x-www-form-urlencoded' },
                data: {
                    mobile: _this.mobile,
                    verifyCode: _this.verifyCode
                },
                success: res => {
                    console.log(res.data);
                    if (res.data.code === 0) {
                        uni.navigateTo({
                            url: '../password/password?mobile=' + _this.mobile
                        });
                    } else {
                        uni.showModal({
                            title: '提示',
                            content: res.data.msg
                        });
                    }
                }
            });
        }
    }
};
</script>

<style>
.sign-box {
    display: flex;
    align-items: center;
}
.left {
    flex: 1 1 70%;
}
.small-btn {
    width: 100px;
    height: 40px;
    font-size: 14px;
}
</style>

密碼頁面

<template>
    <view class="container">
        <input class="uni-input" password type="text" placeholder="輸入密碼" v-model="password" required="required" />
        <button class="green-btn" @tap="signUp(userDTO)">注冊(cè)</button>
    </view>
</template>

<script>
export default {
    data() {
        return {
            userDTO: {
                mobile: '',
                password: ''
            }
        };
    },
    onLoad: function(option) {
        //option為object類型促煮,會(huì)序列化上個(gè)頁面?zhèn)鬟f的參數(shù)
        console.log(option.mobile);
        this.mobile = option.mobile;
    },
    methods: {
        signUp: function(userDTO) {
            var _this = this;
            uni.request({
                url: this.apiServer + '/user/sign_up',
                method: 'POST',
                header: { 'content-type': 'application/json' },
                data: {
                    mobile: _this.mobile,
                    password: _this.password
                },
                success: res => {
                    if (res.data.code === 0) {
                    uni.showModal({
                        title: '提示',
                        content: '注冊(cè)成功'
                    })
                    uni.navigateTo({
                        url: '../signin/signin'
                        });
                    } else {
                        uni.showModal({
                            title: '提示',
                            content: res.data.msg
                        });
                    }
                }
            });
        }
    }
};
</script>

<style></style>

登錄頁面

<template>
    <view class="uni-flex uni-column container">
        <input class="uni-input" type="number" placeholder="輸入手機(jī)號(hào)" v-model="userDTO.mobile" required="required" />
        <input class="uni-input" password type="text" placeholder="輸入密碼" v-model="userDTO.password" required="required" />
        <button class="green-btn" @tap="signIn(userDTO)">登錄</button>
        <navigator url="../signup/signup" class="nav">注冊(cè)新賬號(hào)</navigator>
    </view>
</template>

<script>
export default {
    data() {
        return {
            userDTO: {
                mobile: '',
                password: ''
            }
        };
    },
    onLoad() {},
    methods: {
        signIn: function(userDTO) {
            var _this = this;
            uni.request({
                url: this.apiServer + '/user/sign_in',
                method: 'POST',
                data: {
                    mobile: userDTO.mobile,
                    password: userDTO.password
                },
                header: {
                    'content-type': 'application/json'
                },
                success: res => {
                    // console.log(res.data.data);
                    if (res.data.code == 0) {
                        //將用戶數(shù)據(jù)記錄在本地存儲(chǔ)
                        uni.setStorageSync('login_key', {
                            userId: res.data.data.id,
                            nickname: res.data.data.nickname,
                            avatar: res.data.data.avatar,
                            token: res.data.data.token,
                            login: true
                        });
                        uni.showToast({
                            title: '登錄成功'
                        });
                        uni.switchTab({
                            url: '../my/my'
                        });
                    }
                    //登錄失敗绳匀,彈出各種原因
                    else {
                        uni.showModal({
                            title: '提示',
                            content: res.data.msg
                        });
                    }
                }
            });
        }
    }
};
</script>

<style scoped>
.nav {
    color: #00b26a;
    margin-top: 15px;
}
</style>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市痹仙,隨后出現(xiàn)的幾起案子开仰,更是在濱河造成了極大的恐慌抖所,老刑警劉巖田轧,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傻粘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡窒典,警方通過查閱死者的電腦和手機(jī)稽莉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門昧甘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來战得,“玉大人常侦,你說我怎么就攤上這事聋亡。” “怎么了井厌?”我有些...
    開封第一講書人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)垢袱。 經(jīng)常有香客問我请契,道長(zhǎng)爽锥,這世上最難降的妖魔是什么氯夷? 我笑而不...
    開封第一講書人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任腮考,我火速辦了婚禮踩蔚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘飘蚯。我一直安慰自己孝冒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開白布庄涡。 她就那樣靜靜地躺著,像睡著了一般搬设。 火紅的嫁衣襯著肌膚如雪穴店。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評(píng)論 1 314
  • 那天拿穴,我揣著相機(jī)與錄音泣洞,去河邊找鬼。 笑死默色,一個(gè)胖子當(dāng)著我的面吹牛球凰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腿宰,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼呕诉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吃度!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亦渗,我...
    開封第一講書人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤亿虽,失蹤者是張志新(化名)和其女友劉穎如迟,沒想到半個(gè)月后昔搂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡带族,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年择克,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汁政。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺鸦。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辫继,到底是詐尸還是另有隱情遣耍,我是刑警寧澤瘦穆,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站黔姜,受9級(jí)特大地震影響纳寂,放射性物質(zhì)發(fā)生泄漏腋粥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一纵竖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽混稽。三九已至痘系,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佛纫,已是汗流浹背甥啄。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工衣形, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留句狼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓舍扰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捶枢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361