为了支持手机验证码登录和密码重置功能,需要在 Parse Server 中实现以下 Cloud Functions。
sendSMSCode - 发送短信验证码功能说明: 发送6位数字验证码到用户手机
请求参数:
{
phoneNumber: string, // 手机号
purpose: string // 用途:'login' | 'register' | 'resetPassword'
}
实现示例:
// cloud/main.js
Parse.Cloud.define('sendSMSCode', async (request) => {
const { phoneNumber, purpose } = request.params;
// 验证手机号格式
if (!/^1[3-9]\d{9}$/.test(phoneNumber)) {
throw new Parse.Error(400, '手机号格式不正确');
}
// 生成6位随机验证码
const code = Math.floor(100000 + Math.random() * 900000).toString();
// 设置过期时间(5分钟)
const expiresAt = new Date(Date.now() + 5 * 60 * 1000);
// 保存验证码到数据库
const SMSCode = Parse.Object.extend('SMSCode');
const smsCode = new SMSCode();
smsCode.set('phoneNumber', phoneNumber);
smsCode.set('code', code);
smsCode.set('purpose', purpose);
smsCode.set('expiresAt', expiresAt);
smsCode.set('used', false);
await smsCode.save(null, { useMasterKey: true });
// 调用短信服务发送验证码(需要集成短信服务商)
// 示例:阿里云短信、腾讯云短信等
await sendSMS(phoneNumber, code, purpose);
return {
success: true,
message: '验证码已发送'
};
});
// 短信发送函数(需要根据实际短信服务商实现)
async function sendSMS(phoneNumber, code, purpose) {
// TODO: 集成实际的短信服务商API
// 示例:阿里云、腾讯云、云片等
console.log(`发送验证码 ${code} 到 ${phoneNumber},用途:${purpose}`);
}
loginWithPhoneCode - 验证码登录功能说明: 使用手机号和验证码进行登录
请求参数:
{
phoneNumber: string, // 手机号
smsCode: string // 验证码
}
返回数据:
{
sessionToken: string // 用户会话令牌
}
实现示例:
Parse.Cloud.define('loginWithPhoneCode', async (request) => {
const { phoneNumber, smsCode } = request.params;
// 查询验证码
const SMSCode = Parse.Object.extend('SMSCode');
const query = new Parse.Query(SMSCode);
query.equalTo('phoneNumber', phoneNumber);
query.equalTo('code', smsCode);
query.equalTo('purpose', 'login');
query.equalTo('used', false);
query.greaterThan('expiresAt', new Date());
query.descending('createdAt');
const codeRecord = await query.first({ useMasterKey: true });
if (!codeRecord) {
throw new Parse.Error(400, '验证码无效或已过期');
}
// 标记验证码已使用
codeRecord.set('used', true);
await codeRecord.save(null, { useMasterKey: true });
// 查找用户
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('phone', phoneNumber);
const user = await userQuery.first({ useMasterKey: true });
if (!user) {
throw new Parse.Error(404, '该手机号未注册');
}
// 生成新的 session token
const sessionToken = await Parse.Cloud.run('createUserSession', {
userId: user.id
}, { useMasterKey: true });
return {
sessionToken: sessionToken
};
});
// 创建用户会话
Parse.Cloud.define('createUserSession', async (request) => {
const { userId } = request.params;
const user = await new Parse.Query(Parse.User).get(userId, { useMasterKey: true });
if (!user) {
throw new Parse.Error(404, '用户不存在');
}
// 创建新的 session
const Session = Parse.Object.extend('_Session');
const session = new Session();
session.set('user', user);
session.set('createdWith', {
action: 'login',
authProvider: 'sms'
});
session.set('restricted', false);
session.set('expiresAt', new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)); // 1年
await session.save(null, { useMasterKey: true });
return session.get('sessionToken');
});
requestPasswordResetBySMS - 请求短信重置密码功能说明: 发送密码重置验证码
请求参数:
{
phoneNumber: string // 手机号
}
实现示例:
Parse.Cloud.define('requestPasswordResetBySMS', async (request) => {
const { phoneNumber } = request.params;
// 查找用户
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('phone', phoneNumber);
const user = await userQuery.first({ useMasterKey: true });
if (!user) {
throw new Parse.Error(404, '该手机号未注册');
}
// 调用发送验证码函数
await Parse.Cloud.run('sendSMSCode', {
phoneNumber: phoneNumber,
purpose: 'resetPassword'
});
return {
success: true,
message: '验证码已发送'
};
});
resetPasswordWithSMS - 使用验证码重置密码功能说明: 验证验证码并重置密码
请求参数:
{
phoneNumber: string, // 手机号
smsCode: string, // 验证码
newPassword: string // 新密码
}
实现示例:
Parse.Cloud.define('resetPasswordWithSMS', async (request) => {
const { phoneNumber, smsCode, newPassword } = request.params;
// 验证密码强度
if (newPassword.length < 6) {
throw new Parse.Error(400, '密码长度至少6位');
}
// 查询验证码
const SMSCode = Parse.Object.extend('SMSCode');
const query = new Parse.Query(SMSCode);
query.equalTo('phoneNumber', phoneNumber);
query.equalTo('code', smsCode);
query.equalTo('purpose', 'resetPassword');
query.equalTo('used', false);
query.greaterThan('expiresAt', new Date());
query.descending('createdAt');
const codeRecord = await query.first({ useMasterKey: true });
if (!codeRecord) {
throw new Parse.Error(400, '验证码无效或已过期');
}
// 标记验证码已使用
codeRecord.set('used', true);
await codeRecord.save(null, { useMasterKey: true });
// 查找用户
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('phone', phoneNumber);
const user = await userQuery.first({ useMasterKey: true });
if (!user) {
throw new Parse.Error(404, '该手机号未注册');
}
// 重置密码
user.setPassword(newPassword);
await user.save(null, { useMasterKey: true });
return {
success: true,
message: '密码重置成功'
};
});
用于存储短信验证码记录:
| 字段名 | 类型 | 说明 |
|---|---|---|
| phoneNumber | String | 手机号 |
| code | String | 6位验证码 |
| purpose | String | 用途(login/register/resetPassword) |
| expiresAt | Date | 过期时间 |
| used | Boolean | 是否已使用 |
| createdAt | Date | 创建时间(自动) |
| updatedAt | Date | 更新时间(自动) |
权限设置:
阿里云短信服务
@alicloud/dysmsapi20170525腾讯云短信
tencentcloud-sdk-nodejs云片网
yunpian-sms// 安装依赖
// npm install @alicloud/dysmsapi20170525 --save
const Dysmsapi = require('@alicloud/dysmsapi20170525');
const OpenApi = require('@alicloud/openapi-client');
// 初始化客户端
function createSMSClient() {
const config = new OpenApi.Config({
accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID,
accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET,
endpoint: 'dysmsapi.aliyuncs.com'
});
return new Dysmsapi(config);
}
// 发送短信
async function sendSMS(phoneNumber, code, purpose) {
const client = createSMSClient();
// 根据用途选择不同的模板
const templates = {
login: 'SMS_123456789', // 登录验证码模板ID
register: 'SMS_987654321', // 注册验证码模板ID
resetPassword: 'SMS_111222333' // 重置密码模板ID
};
const request = new Dysmsapi.SendSmsRequest({
phoneNumbers: phoneNumber,
signName: '再生视界', // 短信签名
templateCode: templates[purpose],
templateParam: JSON.stringify({ code: code })
});
try {
const response = await client.sendSms(request);
console.log('短信发送成功:', response);
return response;
} catch (error) {
console.error('短信发送失败:', error);
throw new Parse.Error(500, '短信发送失败');
}
}
在 Parse Server 初始化时添加邮件配置:
const api = new ParseServer({
databaseURI: 'mongodb://localhost:27017/recycle',
appId: 'YOUR_APP_ID',
masterKey: 'YOUR_MASTER_KEY',
serverURL: 'http://localhost:1337/parse',
// 邮件配置
emailAdapter: {
module: '@parse/simple-mailgun-adapter',
options: {
fromAddress: 'noreply@recycle-app.com',
domain: 'mg.recycle-app.com',
apiKey: 'YOUR_MAILGUN_API_KEY'
}
},
// 邮件验证
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 2 * 60 * 60, // 2小时
// 密码重置
appName: '再生视界',
publicServerURL: 'https://recycle-app.com'
});
curl -X POST \
http://localhost:1337/parse/functions/sendSMSCode \
-H 'Content-Type: application/json' \
-H 'X-Parse-Application-Id: YOUR_APP_ID' \
-d '{
"phoneNumber": "13800138000",
"purpose": "login"
}'
curl -X POST \
http://localhost:1337/parse/functions/loginWithPhoneCode \
-H 'Content-Type: application/json' \
-H 'X-Parse-Application-Id: YOUR_APP_ID' \
-d '{
"phoneNumber": "13800138000",
"smsCode": "123456"
}'
在部署到生产环境前,请确保:
验证码安全
短信轰炸防护
密钥安全
如果您的应用暂时不需要手机验证码登录功能,可以:
密码登录功能已完全可用,不依赖任何 Cloud Functions。