短信登录
概述
本指南旨在详细介绍如何实现基于短信验证码的用户登录和注册功能。通过使用腾讯云短信服务,系统能够在用户成功登录后自动创建新用户。本文涵盖了从短信模板配置、数据库设计、短信发送工具类开发,到接口实现和验证逻辑的全流程,适用于 Java 开发环境。
配置短信模板
在开始实现短信登录功能之前,首先需要在腾讯云完成短信模板的配置。具体的配置过程请参考腾讯云官方文档,这里假设您已经完成了模板的配置。
数据库设计
为了存储和管理短信验证码信息,需要创建一个专门的数据库表 sys_sms_code
。以下是表结构的详细设计:
-- ----------------------------
-- Table structure for sys_sms_code
-- ----------------------------
DROP TABLE IF EXISTS `sys_sms_code`;
CREATE TABLE `sys_sms_code` (
`id` bigint(20) PRIMARY KEY COMMENT '主键',
`area_code` varchar(8) DEFAULT NULL COMMENT '国家代码',
`phone` varchar(16) NOT NULL COMMENT '手机号码',
`code` varchar(8) NOT NULL COMMENT '验证码',
`valid_seconds` int(10) unsigned NOT NULL COMMENT '有效时间(秒)',
`code_type` int(11) NOT NULL COMMENT '验证类型',
`ip` varchar(32) DEFAULT NULL COMMENT '用户IP地址',
`remark` VARCHAR(256),
`creator` VARCHAR(64) DEFAULT '',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` VARCHAR(64) DEFAULT '',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` SMALLINT DEFAULT 0,
`tenant_id` BIGINT NOT NULL DEFAULT 0
) COMMENT='短信验证码表';
字段说明
- id: 唯一标识每条验证码记录。
- area_code: 国家或地区代码,例如中国为 "86"。
- phone: 用户的手机号码。
- code: 发送的验证码。
- valid_seconds: 验证码的有效时间,以秒为单位。
- code_type: 验证码的类型,例如注册、登录等。
- ip: 请求发送验证码的用户 IP 地址。
- remark: 备注信息。
- creator/updater: 创建者和更新者信息。
- create_time/update_time: 记录的创建和更新时间。
- deleted: 逻辑删除标识。
- tenant_id: 租户 ID,用于多租户系统。
短信发送工具类
为了实现短信的发送功能,我们需要开发一个工具类 TencentSmsUtils
,该类封装了与腾讯云短信服务的交互逻辑。
引入依赖
首先,在项目的 pom.xml
中引入腾讯云短信服务的依赖:
<properties>
<qcloudsms.version>3.1.270</qcloudsms.version>
</properties>
<dependencies>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>${qcloudsms.version}</version>
</dependency>
</dependencies>
配置信息
将腾讯云短信相关的配置信息存储在数据库表 tio_boot_admin_system_constants_config
中,key
为 systemTxSmsConfig
,配置示例如下:
{
"appId": "1400646561",
"appKey": "",
"secretId": "",
"secretKey": "",
"templateList": [
{
"id": "loginCode",
"name": "登录短信验证码",
"sign": "牛加技术",
"params": ["code"],
"templateId": "1339563",
"validSeconds": 120
}
]
}
实体类定义
定义配置相关的实体类,用于反序列化和传递配置数据。
TxSmsConfig
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TxSmsConfig {
private String secretId; // 短信应用SDK AppKey
private String secretKey; // 短信应用SDK AppKey
private String appId; // 短信应用SDK AppID
private String templateId; // 短信模板ID,需要在短信应用中申请
private String smsSign; // 短信签名
private Integer validSeconds; // 验证码有效时间(秒)
}
TxSmsConfigTemplate
package com.enoleap.maliang.app.model;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TxSmsConfigTemplate implements Serializable {
private static final long serialVersionUID = 367357430209620153L;
private String id; // 配置ID
private String name; // 模板名称,自定义
private String templateId; // 短信模板ID
private String sign; // 短信签名
private String[] params; // 模板参数
private Integer validSeconds; // 验证码有效时间(秒)
}
SystemTxSmsConfig
package com.enoleap.maliang.app.model;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SystemTxSmsConfig implements Serializable {
private static final long serialVersionUID = -1737909437135595915L;
private String secretId; // 短信应用SDK AppKey
private String secretKey; // 短信应用SDK AppKey
private String appId; // 应用ID
private String appKey; // 应用Key
private List<TxSmsConfigTemplate> templateList; // 模板列表
}
DAO 层:SystemConfigDao
负责从数据库中查询短信配置信息。
import com.enoleap.maliang.app.constants.ENoteTableNames;
import com.enoleap.maliang.app.model.SystemTxSmsConfig;
import com.litongjava.db.activerecord.Db;
import com.litongjava.tio.utils.json.JsonUtils;
public class SystemConfigDao {
public SystemTxSmsConfig getSystemTxSmsConfig() {
String sql = String.format("SELECT key_value FROM %s WHERE key_name=?", ENoteTableNames.tio_boot_admin_system_constants_config);
String value = Db.queryStr(sql, "systemTxSmsConfig");
if (value != null) {
return JsonUtils.parse(value, SystemTxSmsConfig.class);
}
return null;
}
}
Service 层:SystemConfigService
提供获取特定短信模板配置的方法。
import java.util.List;
import com.enoleap.maliang.app.dao.SystemConfigDao;
import com.enoleap.maliang.app.model.SystemTxSmsConfig;
import com.enoleap.maliang.app.model.TxSmsConfig;
import com.enoleap.maliang.app.model.TxSmsConfigTemplate;
import com.litongjava.jfinal.aop.Aop;
public class SystemConfigService {
public TxSmsConfig getLoginSmsConfig() {
SystemTxSmsConfig systemTxSmsConfig = Aop.get(SystemConfigDao.class).getSystemTxSmsConfig();
TxSmsConfig txSmsConfig = new TxSmsConfig();
txSmsConfig.setAppId(systemTxSmsConfig.getAppId());
txSmsConfig.setSecretId(systemTxSmsConfig.getSecretId());
txSmsConfig.setSecretKey(systemTxSmsConfig.getSecretKey());
List<TxSmsConfigTemplate> templateList = systemTxSmsConfig.getTemplateList();
for (TxSmsConfigTemplate txSmsConfigTemplate : templateList) {
if ("loginCode".equals(txSmsConfigTemplate.getId())) {
String templateId = txSmsConfigTemplate.getTemplateId();
String sign = txSmsConfigTemplate.getSign();
txSmsConfig.setTemplateId(templateId);
txSmsConfig.setSmsSign(sign);
txSmsConfig.setValidSeconds(txSmsConfigTemplate.getValidSeconds());
return txSmsConfig;
}
}
return null;
}
}
短信工具类:TencentSmsUtils
负责与腾讯云短信服务的交互,包括发送短信的具体实现。
import com.enoleap.maliang.app.model.TxSmsConfig;
import com.litongjava.tio.utils.json.JsonUtils;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
import lombok.extern.slf4j.Slf4j;
/**
* 腾讯云短信服务工具类
*/
@Slf4j
public class TencentSmsUtils {
/**
* 发送短信
*
* @param sdkAppId 短信应用ID
* @param secretId 腾讯云账户SecretId
* @param secretKey 腾讯云账户SecretKey
* @param signName 短信签名
* @param templateId 短信模板ID
* @param params 模板参数
* @param phoneNumberSet 接收短信的手机号集合
* @return SendSmsResponse 发送短信的响应
*/
public static SendSmsResponse sendSMS(String sdkAppId, String secretId, String secretKey, String signName, String templateId,
String[] params, String[] phoneNumberSet) {
SmsClient client = buildSmsClient(secretId, secretKey);
SendSmsRequest req = buildSmsRequest(sdkAppId, signName, templateId, params, phoneNumberSet);
log.info("发送短信请求:{}", JsonUtils.toJson(req));
try {
SendSmsResponse res = client.SendSms(req);
return res;
} catch (TencentCloudSDKException e) {
log.error("发送短信失败: {}", e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 构建短信请求对象
*/
private static SendSmsRequest buildSmsRequest(String sdkAppId, String signName, String templateId, String[] params, String[] phoneNumberSet) {
SendSmsRequest req = new SendSmsRequest();
req.setSmsSdkAppId(sdkAppId);
req.setSignName(signName);
req.setSenderId("");
req.setSessionContext("enote");
req.setExtendCode("");
req.setTemplateId(templateId);
req.setPhoneNumberSet(phoneNumberSet);
req.setTemplateParamSet(params);
return req;
}
/**
* 构建短信客户端
*/
private static SmsClient buildSmsClient(String secretId, String secretKey) {
Credential cred = new Credential(secretId, secretKey);
HttpProfile httpProfile = new HttpProfile();
httpProfile.setReqMethod("POST");
httpProfile.setConnTimeout(60);
httpProfile.setEndpoint("sms.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setSignMethod("HmacSHA256");
clientProfile.setHttpProfile(httpProfile);
SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile);
return client;
}
/**
* 批量发送短信
*/
public static SendSmsResponse sendSmsBat(TxSmsConfig config, String[] phoneList, String[] params) {
return sendSMS(config.getAppId(), config.getSecretId(), config.getSecretKey(),
config.getSmsSign(), config.getTemplateId(), params, phoneList);
}
/**
* 发送单条短信
*/
public static SendSmsResponse sendSms(TxSmsConfig config, String areaCode, String phone, String code) {
String[] phoneList = new String[]{areaCode + phone};
String[] params = {code};
return sendSMS(config.getAppId(), config.getSecretId(), config.getSecretKey(),
config.getSmsSign(), config.getTemplateId(), params, phoneList);
}
}
单元测试:TencentSmsUtilsTest
通过单元测试验证短信发送功能。
import org.junit.Test;
import com.enoleap.maliang.app.config.EnoteMysqlDbConfig;
import com.enoleap.maliang.app.model.TxSmsConfig;
import com.enoleap.maliang.app.services.system.SystemConfigService;
import com.enoleap.maliang.app.utils.TencentSmsUtils;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.boot.tesing.TioBootTest;
import com.litongjava.tio.utils.json.JsonUtils;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
public class TencentSmsUtilsTest {
@Test
public void testSendSms() {
TioBootTest.runWith(EnoteMysqlDbConfig.class);
SystemConfigService systemConfigService = Aop.get(SystemConfigService.class);
TxSmsConfig txSmsConfig = systemConfigService.getLoginSmsConfig();
SendSmsResponse sendSms = TencentSmsUtils.sendSms(txSmsConfig, "86", "15836475191", "123456");
System.out.println(JsonUtils.toJson(sendSms));
}
}
发送的数据格式
发送短信时,数据格式如下:
{
"templateId": "1339563",
"phoneNumberSet": ["8615836475191"],
"templateParamSet": ["123456"],
"sessionContext": "enote",
"smsSdkAppId": "1400646561",
"extendCode": "",
"signName": "牛加技术",
"senderId": ""
}
发送成功返回信息
成功发送短信后,返回的信息格式如下:
{
"sendStatusSet": [
{
"message": "send success",
"code": "Ok",
"sessionContext": "enote",
"phoneNumber": "+8615836475191",
"isoCode": "CN",
"serialNo": "4412:45808295717294761577817519",
"fee": "1"
}
],
"requestId": "fbac04a7-bc02-49e4-954c-8ce939103696"
}
接口实现
在实现短信发送功能时,需要考虑以下两个关键点:
- 防止重复发送:通过查询数据库中已存在的未过期的验证码记录,避免频繁发送短信。
- 生成验证码并存储数据库:生成随机验证码并将其存储至数据库,以便后续验证。
枚举定义
定义验证码类型和手机类型的枚举,便于代码中的类型管理和可读性。
SmsCodeType
import java.util.HashMap;
import java.util.Map;
public enum SmsCodeType {
REGISTER(1, "注册"),
LOGIN(2, "登录"),
CHANGE_PHONE(3, "更换手机号码"),
RESET_PWD(4, "重置登录密码");
private Integer type;
private String desc;
SmsCodeType(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
public Integer getType() {
return type;
}
public String getDesc() {
return desc;
}
public static String getDesc(int type) {
for (SmsCodeType item : SmsCodeType.values()) {
if (item.getType().equals(type)) {
return item.getDesc();
}
}
return "";
}
public static Map<Integer, String> getMap() {
Map<Integer, String> map = new HashMap<>();
for (SmsCodeType item : SmsCodeType.values()) {
map.put(item.getType(), item.getDesc());
}
return map;
}
}
PhoneType
package com.enoleap.maliang.app.enums;
public enum PhoneType {
MAIN_LAND("86", "大陆手机号码"),
HK("852", "香港手机号码");
private String code;
private String desc;
PhoneType(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public static PhoneType getPhoneType(String areaCode) {
if (areaCode != null && !areaCode.isEmpty()) {
if (areaCode.equals("+852") || areaCode.equals("852")) {
return HK;
}
}
return MAIN_LAND;
}
}
Service 层:TencentSmsService
负责处理短信发送的业务逻辑,包括防止重复发送和验证码生成。
import com.enoleap.maliang.app.db.model.SysSmsCode;
import com.enoleap.maliang.app.model.TxSmsConfig;
import com.enoleap.maliang.app.services.system.SystemConfigService;
import com.enoleap.maliang.app.utils.TencentSmsUtils;
import com.jfinal.kit.Kv;
import com.litongjava.db.activerecord.Db;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.utils.hutool.RandomUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
public class TencentSmsService {
/**
* 发送短信验证码
*
* @param codeType 验证码类型
* @param areaCode 国家代码
* @param phone 手机号码
* @param ip 用户IP地址
* @return 响应结果
*/
public RespBodyVo send(Integer codeType, String areaCode, String phone, String ip) {
// 防止重复发送:检查是否存在未过期的验证码记录
String sql = "SELECT COUNT(1) FROM sys_sms_code WHERE NOW() <= create_time + INTERVAL valid_seconds SECOND " +
"AND area_code=? AND phone=? AND deleted=0";
boolean exists = Db.existsBySql(sql, areaCode, phone);
if (exists) {
return RespBodyVo.fail("请求过于频繁,请稍后再试。");
}
// 生成随机验证码
int code = RandomUtils.nextInt(100000, 999999);
// 获取短信配置信息
SystemConfigService systemConfigService = Aop.get(SystemConfigService.class);
TxSmsConfig loginSmsConfig = systemConfigService.getLoginSmsConfig();
// 发送短信
TencentSmsUtils.sendSms(loginSmsConfig, areaCode, phone, String.valueOf(code));
// 生成唯一ID并保存验证码记录
long id = SnowflakeIdUtils.id();
SysSmsCode sysSmsCode = new SysSmsCode();
sysSmsCode.setId(id)
.setAreaCode(areaCode)
.setPhone(phone)
.setCode(String.valueOf(code))
.setIp(ip)
.setValidSeconds(Long.valueOf(loginSmsConfig.getValidSeconds()))
.setCodeType(codeType);
boolean save = sysSmsCode.save();
if (!save) {
return RespBodyVo.fail("验证码保存失败,请稍后再试。");
}
return RespBodyVo.ok(Kv.by("id", id));
}
}
测试类:TencentSmsServiceTest
通过测试类验证短信发送服务的正确性和防止重复发送的逻辑。
import org.junit.Test;
import com.enoleap.maliang.app.config.EnoteMysqlDbConfig;
import com.enoleap.maliang.app.enums.SmsCodeType;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.tesing.TioBootTest;
public class TencentSmsServiceTest {
@Test
public void testSendSms() {
TioBootTest.runWith(EnoteMysqlDbConfig.class);
TencentSmsService smsService = Aop.get(TencentSmsService.class);
RespBodyVo response1 = smsService.send(SmsCodeType.LOGIN.getType(), "86", "15836475191", "192.168.1.2");
System.out.println(response1);
// 尝试重复发送,预期失败
RespBodyVo response2 = smsService.send(SmsCodeType.LOGIN.getType(), "86", "15836475191", "192.168.1.2");
System.out.println(response2);
}
}
请求参数模型:SmsCodeSendVo
定义发送验证码接口的请求参数模型。
package com.enoleap.maliang.app.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class SmsCodeSendVo {
private Integer codeType; // 验证码类型: 1-注册, 2-登录, 3-更换手机号码, 4-重置登录密码
private String areaCode; // 国家或地区代码,如中国为 "86"
private String phone; // 手机号码
}
Controller 层:SmsController
处理前端发送验证码的请求。
import com.enoleap.maliang.app.enums.PhoneType;
import com.enoleap.maliang.app.model.SmsCodeSendVo;
import com.enoleap.maliang.app.services.sms.TencentSmsService;
import com.enoleap.maliang.app.utils.RegexUtil;
import com.litongjava.annotation.EnableCORS;
import com.litongjava.annotation.RequestPath;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.utils.HttpIpUtils;
@RequestPath("/sms")
@EnableCORS
public class SmsController {
private TencentSmsService smsService = Aop.get(TencentSmsService.class);
/**
* 发送验证码接口
*
* @param vo SmsCodeSendVo 请求参数
* @return 响应结果
*/
public RespBodyVo send(SmsCodeSendVo vo) {
Integer codeType = vo.getCodeType();
String areaCode = vo.getAreaCode();
String phone = vo.getPhone();
// 参数校验
if (codeType == null) {
return RespBodyVo.fail("验证码类型不能为空");
}
if (areaCode == null || areaCode.isEmpty()) {
return RespBodyVo.fail("国家代码不能为空");
}
if (phone == null || phone.isEmpty()) {
return RespBodyVo.fail("手机号码不能为空");
}
// 手机号格式校验(针对大陆号码)
PhoneType phoneType = PhoneType.getPhoneType(areaCode);
if (phoneType == PhoneType.MAIN_LAND && !RegexUtil.isMobileSimple(phone)) {
return RespBodyVo.fail("手机号码格式不正确");
}
// 获取真实IP
String realIp = HttpIpUtils.getRealIp(TioRequestContext.getRequest());
// 发送验证码
return smsService.send(codeType, areaCode, phone, realIp);
}
}
登录注册逻辑
实现用户通过短信验证码进行登录或注册的业务逻辑。
数据层:SysUserInfoDao
负责用户信息的数据库操作,包括判断用户是否存在和保存新用户。
package com.enoleap.maliang.app.dao;
import com.enoleap.maliang.app.utils.UserIdUtils;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
public class SysUserInfoDao {
public static final String tableName = "sys_user_info";
/**
* 判断用户名是否存在
*
* @param username 用户名
* @return 存在返回true,否则返回false
*/
public boolean existsUsername(String username) {
return Db.exists(tableName, "username", username);
}
/**
* 通过手机号保存新用户信息
*
* @param areaCode 国家代码
* @param phone 手机号码
* @param username 用户名
* @param nickName 昵称
* @return 新用户的ID,保存失败返回null
*/
public Long saveByPhone(String areaCode, String phone, String username, String nickName) {
long userId = UserIdUtils.random();
Row row = new Row();
row.set("id", userId)
.set("username", username)
.set("area_code", areaCode)
.set("phone", phone)
.set("nick_name", nickName);
boolean save = Db.save(tableName, row);
return save ? userId : null;
}
}
参数模型:LoginVo
定义提交验证码接口的请求参数模型。
package com.enoleap.maliang.app.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class LoginVo {
private Long id; // 验证码ID
private String areaCode; // 国家代码
private String phone; // 手机号码
private Integer codeType;// 验证码类型
private Integer code; // 验证码
}
Service 层:AuthService
负责处理通过验证码进行登录或注册的业务逻辑。
import com.enoleap.maliang.app.dao.SysUserInfoDao;
import com.enoleap.maliang.app.model.LoginVo;
import com.jfinal.kit.Kv;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.utils.hutool.RandomUtils;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AuthService {
/**
* 通过用户名和密码登录
*
* @param username 用户名
* @param password 密码
* @return 登录结果
*/
public TableResult<Kv> doLogin(String username, String password) {
String sql = "SELECT id FROM sys_user_info WHERE username=? AND password=?";
Long userId = Db.queryLong(sql, username, password);
if (userId != null && userId > 0) {
Kv kv = doLogin(userId);
return TableResult.ok(kv);
} else {
return TableResult.fail("用户名或密码不正确");
}
}
/**
* 执行登录操作
*
* @param userId 用户ID
* @return 登录结果
*/
public Kv doLogin(Long userId) {
StpUtil.login(userId);
String tokenValue = StpUtil.getTokenValue();
long tokenTimeout = StpUtil.getTokenTimeout();
Kv kv = new Kv();
kv.set("token", tokenValue);
kv.set("tokenTimeout", tokenTimeout);
Row row = Db.findById("sys_user_info", userId);
kv.set(row.toMap());
return kv;
}
/**
* 通过验证码进行登录或注册
*
* @param vo LoginVo 请求参数
* @return 响应结果
*/
public RespBodyVo loginByPhone(LoginVo vo) {
Long id = vo.getId();
Integer code = vo.getCode();
String areaCode = vo.getAreaCode();
String phone = vo.getPhone();
Integer codeType = vo.getCodeType();
// 验证验证码有效性
if (code != null && !code.equals(123456)) { // 123456 为测试用永久有效验证码
String sql = "SELECT COUNT(1) FROM sys_sms_code WHERE id=? AND area_code=? AND phone=? AND code=? AND code_type=? " +
"AND TIMESTAMPDIFF(SECOND, create_time, NOW()) <= valid_seconds";
if (!Db.existsBySql(sql, id, areaCode, phone, code, codeType)) {
return RespBodyVo.fail("验证码无效或已过期。");
}
}
// 判断用户是否存在
String sql = "SELECT id FROM sys_user_info WHERE area_code=? AND phone=? AND deleted=0";
Long userId = Db.queryLong(sql, areaCode, phone);
if (userId != null) {
// 用户存在,执行登录
Kv loginResult = this.doLogin(userId);
return RespBodyVo.ok(loginResult);
} else {
// 用户不存在,自动注册新用户
log.info("自动注册新用户:{}", phone);
SysUserInfoDao sysUserInfoDao = Aop.get(SysUserInfoDao.class);
userId = sysUserInfoDao.saveByPhone(areaCode, phone, phone, "User_" + RandomUtils.nextInt(1000, 9999));
if (userId == null) {
return RespBodyVo.fail("用户注册失败,请稍后再试。");
}
Kv loginResult = this.doLogin(userId);
return RespBodyVo.ok(loginResult);
}
}
}
Controller 层:AuthController
处理用户通过验证码进行登录或注册的请求。
import com.enoleap.maliang.app.model.LoginVo;
import com.enoleap.maliang.app.services.auth.AuthService;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.annotation.EnableCORS;
import com.litongjava.annotation.RequestPath;
@RequestPath("/auth")
@EnableCORS
public class AuthController {
private AuthService authService = Aop.get(AuthService.class);
/**
* 通过验证码登录或注册
*
* @param vo LoginVo 请求参数
* @return 响应结果
*/
public RespBodyVo loginByPhone(LoginVo vo) {
Long id = vo.getId();
Integer code = vo.getCode();
// 参数校验
if (id == null) {
return RespBodyVo.fail("验证码ID不能为空");
}
if (code == null) {
return RespBodyVo.fail("验证码不能为空");
}
return authService.loginByPhone(vo);
}
}
接口文档
手机验证码登录注册
本文档详细描述了发送验证码和提交验证码进行登录或注册的接口规范,帮助前端开发人员正确调用相关接口。
发送验证码接口
Endpoint:POST /sms/send
请求参数:
{
"codeType": 1, // 验证码类型: 1-注册, 2-登录, 3-更换手机号码, 4-重置登录密码
"areaCode": "86", // 国家代码,如中国为 "86"
"phone": "15836475191" // 手机号
}
参数说明:
- codeType: 指定验证码的用途类型,具体如下:
- 注册
- 登录
- 更换手机号码
- 重置登录密码
- areaCode: 国家或地区的代码,例如中国为 "86"。
- phone: 用户的手机号码。
响应示例:
{
"data": {
"id": "437986347671658496" // 验证码ID,用于提交验证码时关联
},
"msg": null,
"code": 1, // 1 表示成功
"ok": true // 请求成功标识
}
响应参数说明:
- data.id: 唯一的验证码 ID,后续验证时需要使用。
- code: 状态码,1 表示成功,其他值表示失败。
- ok: 请求是否成功的布尔标识。
提交验证码接口
Endpoint:POST /auth/loginByPhone
请求参数:
{
"id": "437986347671658496", // 验证码ID
"code": 440263, // 发送到手机的验证码
"phone": "15836475191", // 手机号
"areaCode": "86", // 国家代码
"codeType": 1 // 验证码类型: 1-注册, 2-登录, 3-更换手机号码, 4-重置登录密码
}
参数说明:
- id: 发送验证码时返回的唯一验证码 ID。
- code: 用户收到的验证码。
- phone: 用户的手机号码。
- areaCode: 国家或地区的代码,例如中国为 "86"。
- codeType: 验证码的用途类型,与发送验证码时的类型保持一致。
逻辑说明:loginByPhone
接口会根据手机号码判断用户是否存在,若不存在,则自动创建新用户。为了测试方便,验证码 440263
为永久有效的测试验证码。
响应示例:
{
"data": {
"tenant_id": "0",
"wx_user_info": null,
"remark": null,
"locale": null,
"config_info": null,
"updater": "",
"header_pic_id": null,
"password": null,
"update_time": 1729589458000,
"id": "52317188646659035", // 用户ID
"app_id": null,
"email": null,
"creator": "",
"create_time": 1729589458000,
"area_code": "86",
"sex": null,
"user_from": null,
"tokenTimeout": "2592000", // Token 有效期(秒)
"facebook_user_info": null,
"wx_open_id": null,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjUyMzE3MTg4NjQ2NjU5MDM1LCJyblN0ciI6ImNRSWRlWG4yUGQwT2h5NkRZUzRnb2kxbWlkQ2ZQb01sIn0.Rc2ieAAIt6JchEVRp_tQ22MtAye3Gh20sg_3ooAFkPg", // 登录 Token
"facebook_id": null,
"deleted": 0,
"user_channel": null,
"phone": null,
"v": 0,
"nick_name": "User_3990", // 用户昵称
"name": null,
"user_level": null,
"username": "15836475191", // 用户名(手机号)
"status": 0
},
"code": 1, // 1 表示成功
"msg": null,
"ok": true // 请求成功标识
}
响应参数说明:
- data: 包含用户信息和登录 Token。
- id: 用户唯一 ID。
- token: 登录后的 Token,用于后续的认证。
- tokenTimeout: Token 的有效期,单位为秒。
- username: 用户名,即手机号码。
- nick_name: 用户昵称。
- 其他字段为用户的相关信息,可根据需要进行扩展。
- code: 状态码,1 表示成功,其他值表示失败。
- ok: 请求是否成功的布尔标识。
总结
通过本指南,您已经了解了如何使用腾讯云短信服务实现基于短信验证码的用户登录和注册功能。整个流程包括短信模板配置、数据库设计、短信发送工具类开发、业务逻辑实现以及接口文档编写。确保各个环节正确配置和实现,可以有效提升用户体验和系统的安全性。
在实际应用中,建议根据具体需求进一步优化和扩展功能,例如增加验证码的复杂度、实现多语言支持、加强安全性措施等。