從零用java實現 小紅書 springboot vue uniapp(14) 集成阿里云短信驗證碼
移動端演示 http://8.146.211.120:8081/#/
管理端演示 http://8.146.211.120:8088/#/
項目整體介紹及演示
前言
在現代應用中,手機號不僅是用戶的唯一標識,更是保障賬戶安全的第一道防線。通過短信驗證碼進行登錄或注冊,已成為行業標準。它省去了用戶記憶復雜密碼的麻煩,同時提供了可靠的身份驗證。在本文中,我們將為我們的小紅書項目集成阿里云短信服務,實現一個完整、安全、且對開發者友好的手機驗證碼登錄流程。
核心技術實現
整個流程分為后端和前端兩部分。后端負責生成驗證碼、調用阿里云接口發送短信、以及校驗驗證碼的有效性。前端則負責提供用戶界面,引導用戶輸入手機號并完成驗證。
1. 后端實現:從配置到接口
步驟一:引入阿里云SDK依賴
首先,我們需要在項目的 pom.xml
中加入阿里云短信服務的Java SDK。
<dependency><groupId>com.aliyun</groupId><artifactId>dysmsapi20170525</artifactId><version>2.0.24</version> <!-- 請使用最新版本 -->
</dependency>
步驟二:參數化配置
為了代碼的靈活性和安全性,我們將所有敏感信息和配置項都放入 application.yml
中。這樣做的好處是,我們可以在不同環境(開發、測試、生產)中使用不同的配置,而無需修改代碼。
# 阿里云短信服務
aliyun:sms:# 是否啟用真實短信發送, false則為測試模式enabled: false # 阿里云賬號的accessKeyIdaccess-key-id: YOUR_ACCESS_KEY_ID# 阿里云賬號的accessKeySecretaccess-key-secret: YOUR_ACCESS_KEY_SECRET# 短信簽名名稱 (需在阿里云控制臺申請)sign-name: 你的簽名# 短信模板CODE (需在阿里云控制臺申請)template-code: SMS_XXXXXX# 短信驗證碼有效期(分鐘)expire-minutes: 1
enabled
: 這是一個非常有用的開關。當設為false
時,系統進入“測試模式”,驗證碼會直接在接口中返回,而不會真實發送,方便開發調試。access-key-id
&access-key-secret
: 你的阿里云賬戶憑證。sign-name
&template-code
: 在阿里云短信服務控制臺創建的短信簽名和模板。
步驟三:封裝短信發送服務
接下來,我們創建一個 SmsServiceImpl
,它將負責所有與發送短信相關的邏輯。這種封裝能讓我們的業務代碼(如登錄邏輯)保持干凈,只需調用一個簡單的方法即可。
SmsServiceImpl.java
:
@Slf4j
@Service
public class SmsServiceImpl implements SmsService {@Autowiredprivate SmsProperties smsProperties;@Autowiredprivate AliyunSmsConfig aliyunSmsConfig;@Autowiredprivate SystemSettingService systemSettingService; // 用于獲取系統設置@Overridepublic void sendMessage(String phoneNumber, String code) throws Exception {// 從系統設置或配置中獲取當前的模式// String smsMode = systemSettingService.getSmsMode();// 此處我們直接使用yml中的配置boolean isRealMode = smsProperties.isEnabled();if (!isRealMode) {// 測試模式:只記錄日志,不真實發送log.info("【測試模式】短信發送 - 手機號: {}, 驗證碼: {}", phoneNumber, code);return;}try {log.info("【真實模式】開始發送短信 - 手機號: {}, 驗證碼: {}", phoneNumber, code);Client client = aliyunSmsConfig.createClient();Map<String, String> templateParamMap = new HashMap<>();templateParamMap.put("code", code); // 模板參數,key必須與模板中的變量名一致String templateParam = JSON.toJSONString(templateParamMap);SendSmsRequest sendSmsRequest = new SendSmsRequest().setPhoneNumbers(phoneNumber).setSignName(smsProperties.getSignName()).setTemplateCode(smsProperties.getTemplateCode()).setTemplateParam(templateParam);RuntimeOptions runtime = new RuntimeOptions();SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);log.info("短信發送結果: {}", sendSmsResponse.getBody().getMessage());if (sendSmsResponse.getBody() == null || !"OK".equals(sendSmsResponse.getBody().getCode())) {throw new ApiException("短信發送失敗: " + (sendSmsResponse.getBody() != null ? sendSmsResponse.getBody().getMessage() : "未知錯誤"));}} catch (Exception e) {log.error("短信發送異常", e);throw new ApiException("短信發送異常: " + e.getMessage());}}
}
該服務通過檢查 aliyun.sms.enabled
的值來決定是打印日志(測試模式)還是調用阿里云API(真實模式)。
步驟四:創建登錄接口
最后,我們在 LoginApi
中創建兩個端點:一個用于獲取驗證碼,另一個用于通過驗證碼登錄。
LoginApi.java
:
@RestController
public class LoginApi {@AutowiredLoginService loginService;@AutowiredAuthorService authorService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@ApiOperation(value = "獲取驗證碼")@GetMapping("/api/getCode")public ResultBean<String> getCode(String phoneNumber) {// loginService 內部會生成驗證碼、存入Redis、并調用SmsService發送String code = loginService.sendSmsCode(phoneNumber);if (code == null) {// 真實模式下,service層返回null,提示用戶return ResultBean.success("驗證碼已發送,請注意查收");}// 測試模式下,service層返回驗證碼,直接顯示給前端return ResultBean.success(code);}@ApiOperation(value = "驗證碼登陸")@PostMapping("/api/checkCode")@Transactionalpublic ResultBean<Author> checkCode(@RequestBody PhoneLoginDto phoneLoginDto) {// 1. 校驗驗證碼是否正確 (loginService會去Redis中比對)loginService.checkCode(phoneLoginDto);// 2. 驗證通過,執行登錄或注冊邏輯Author author = authorService.selectAuthorByPhoneNumber(phoneLoginDto.getPhoneNumber());if(author == null){// 用戶不存在,創建新用戶author = authorService.createNewAuthor(phoneLoginDto.getPhoneNumber());} else if(author.getDeleted().equals(1)){throw new ApiException("當前用戶狀態異常~");}// 3. 生成Token并返回用戶信息final String token = jwtTokenUtil.generateTokenByUserId(author.getAuthorId());author.setToken(token);return ResultBean.success(author);}
}
/api/getCode
: 這個接口的設計完美地結合了測試與真實模式。在開發階段,我們能立刻看到驗證碼,極大提高了調試效率。/api/checkCode
: 它首先驗證驗證碼的正確性,然后執行“查找或創建”用戶的邏輯,最后簽發JWT令牌,完成整個登錄閉環。
主要邏輯是將驗證碼保存到redis 設置過期時間 到期重新發送 根據輸入的值和redis數據庫比較
2. 前端交互:Uniapp 實現
前端的實現相對直接,主要是在登錄頁面 login.vue
中完成。
- UI布局: 包含一個手機號輸入框、一個“獲取驗證碼”按鈕和一個“登錄”按鈕。
- 獲取驗證碼:
- 點擊“獲取驗證碼”按鈕時,調用后端的
/api/getCode
接口。 - 接口調用成功后,啟動一個60秒的倒計時,期間按鈕變為不可點擊狀態,防止用戶重復發送。
- 點擊“獲取驗證碼”按鈕時,調用后端的
- 登錄:
- 用戶輸入手機號和收到的驗證碼后,點擊“登錄”按鈕。
- 調用后端的
/api/checkCode
接口,并傳遞手機號和驗證碼。 - 登錄成功后,后端會返回用戶信息和
Token
。前端需要將這些信息(特別是Token
)保存到本地存儲(如uni.setStorageSync
),并跳轉到應用主頁。
通過前后端的緊密配合,我們就構建了一個完整且健壯的短信驗證碼登錄功能。
代碼地址
https://gitee.com/ddeatrr/springboot_vue_xhs