少儿舞蹈小程序(20):手机号登录与多角色注册
通过“手机号验证 + 角色选择 + 信息补充”这一套组合拳,我们不仅为少儿舞蹈小程序构建了一个安全、高效且用户体验友好的登录注册系统,更重要的是,我们从一开始就建立了清晰的数据模型和角色划分,为未来实现复杂的业务功能(如排课、点名、家校沟通、权限控制等)铺平了道路。这种思路充分利用了低代码平台的优势,将复杂的后端逻辑封装在简单的API调用中,让开发者能更专注于前端页面的搭建和业务功能的实现。希望这
目录
对于许多初涉小程序开发的伙伴来说,用户系统(登录与注册)往往是第一个需要攻克的难关。传统的用户名密码模式看似简单,但在低代码平台和现代移动应用的生态中,却显得有些“格格不入”。
本文将打破惯性思维,为你详细解析一套更符合低代码平台逻辑、以手机号为核心的用户认证与多角色管理方案。这套方案不仅能优化用户首次登录的体验,还能清晰地管理应用内的不同角色(如“家长”和“老师”),为后续的功能权限管理打下坚实的基础。
一、核心逻辑:告别传统,拥抱手机号验证码登录
在低代码平台中,用户体系通常被划分为 “内部用户” 和 “外部用户”。
- 内部用户:通常指应用的管理者、运营人员等。他们拥有后台操作权限,可以手动在后台添加账号、设置密码。这类账号数量有限(例如,平台可能只提供100个授权名额),成本较高,不适合作为面向大众的用户体系。
- 外部用户:即我们应用的主要使用者,如学生家长、老师等。他们的数量庞大,增长迅速。对于外部用户,最佳实践是采用 手机号验证码登录。
为什么选择手机号验证码登录?
- 低门槛,高转化:用户无需记忆复杂的账号密码,只需输入手机号和验证码即可完成登录/注册,流程简单快捷,能有效降低新用户的注册门槛。
- 自动注册:用户首次通过手机号验证后,系统会自动在“外部用户”体系中为其创建一个账号,省去了繁琐的注册步骤。
- 身份唯一性:手机号是天然的、唯一的身份标识(UID),便于我们进行用户身份的识别与关联。
- 符合平台规范:无论是微信小程序还是其他主流平台,都对手机号授权登录提供了成熟的官方接口,安全可靠。
我们的核心登录/注册流程如下:
- 用户打开我的页面,系统强制做产品级别的认证,此时只是登录到了云开发
- 用户通过手机号验证码验证后,进入“我的”页面,检测到用户在用户表里是否存在记录。
- 记录不存在则是新用户,引导到角色选择页面
- 跳转到对应的注册页面完成注册
关键点:应用拿着这个手机号去系统的 用户表 (User) 中查询。
* 如果查询到数据:说明该用户已注册过,登录成功,直接进入“我的”页面。
* 如果未查询到数据:说明是新用户,此时 不应 立即创建用户信息,而是应该先引导用户选择自己的角色。
二、角色分离:家长与老师的精准区分
在少儿舞蹈小程序中,“家长”和“老师”是两个核心但权限、属性截然不同的角色。家长需要查看孩子的课表、缴费、接收通知;而老师则需要进行点名、发布课堂反馈、管理学员等。
因此,在用户首次登录时,我们必须让其明确自己的角色。
角色选择与注册流程:
- 当系统检测到新用户(在用户表中无记录)时,自动跳转到一个 “角色选择页面”。
- 页面上提供两个清晰的选项:“我是家长”、“我是老师”。
- 用户根据自己的身份点击相应的按钮。
- 点击后,页面分别跳转到 “家长信息注册页” 或 “老师信息注册页”。
- 用户在各自的注册页中填写必要的补充信息(例如,家长的宝宝姓名、老师的教龄等)。
- 提交信息后,系统会完成以下操作:
- 在 用户表 中创建一条新记录,核心字段就是用户的手机号。
- 同时,在对应的 家长表 或 老师表 中创建一条记录,并与用户表中的记录进行关联。
通过这个流程,我们就完美地将一个通过手机号登录的用户,与具体的业务角色身份(家长或老师)绑定了起来。
三、数据模型:三张表奠定用户系统基石
清晰的数据结构是系统稳定运行的保障。我们需要设计三张核心数据表:用户表、家长表、老师表。
1 用户表 (Users)
这张表是整个用户系统的基础,用于存储所有通过手机验证的用户,作为身份认证的唯一凭证。
字段名 (Field Name) | 数据类型 (Data Type) | 描述 (Description) | 主键/外键 (Key) | 备注 (Notes) |
---|---|---|---|---|
id |
String / Integer |
唯一标识符 | Primary Key | 建议使用平台自动生成的唯一ID |
phone_number |
String |
用户手机号 | 核心字段,唯一且不能为空 | |
role |
String |
用户角色 | ‘parent’, ‘teacher’ | |
created_at |
Datetime |
创建时间 | 记录用户首次注册时间 | |
last_login_at |
Datetime |
最后登录时间 | 可用于分析用户活跃度 |
2 家长表 (Parents)
用于存储家长的详细业务信息。
字段名 (Field Name) | 数据类型 (Data Type) | 描述 (Description) | 主键/外键 (Key) | 备注 (Notes) |
---|---|---|---|---|
id |
String / Integer |
唯一标识符 | Primary Key | |
user_id |
String / Integer |
关联的用户ID | Foreign Key (Users.id) | 与用户表一一对应 |
child_name |
String |
宝宝姓名 | 核心业务字段 | |
child_gender |
String |
宝宝性别 | ‘男’, ‘女’ | |
child_birthday |
Date |
宝宝生日 | ||
contact_name |
String |
家长称谓 | 例如:王女士,李爸爸 | |
created_at |
Datetime |
记录创建时间 |
3 老师表 (Teachers)
用于存储老师的详细业务信息。
字段名 (Field Name) | 数据类型 (Data Type) | 描述 (Description) | 主键/外键 (Key) | 备注 (Notes) |
---|---|---|---|---|
id |
String / Integer |
唯一标识符 | Primary Key | |
user_id |
String / Integer |
关联的用户ID | Foreign Key (Users.id) | 与用户表一一对应 |
teacher_name |
String |
老师姓名 | 核心业务字段 | |
gender |
String |
性别 | ‘男’, ‘女’ | |
teaching_since |
Integer |
教龄(年) | 例如:5 | |
introduction |
Text |
老师简介 | ||
profile_picture |
Image URL |
老师头像 | ||
created_at |
Datetime |
记录创建时间 |
四、API 设计:连接前后端的桥梁
前后端分离的开发模式下,我们需要定义清晰的API接口来处理数据交互。
1 登录/检查用户状态 API
API参照过往教程
2 家长注册 API
/**
* 家长角色注册
* @param {object} params - 传入参数
* @param {string} params.phoneNumber - 用户的手机号
* @param {string} params.childName - 宝宝姓名
* @param {string} params.childGender - 宝宝性别
* @param {string} params.childBirthday - 宝宝生日
* @param {string} params.contactName - 家长称谓
* @param {object} context - 调用上下文
* @returns {Promise<object>} - 返回操作结果
*/
module.exports = async function (params, context) {
// --- 1. 参数校验 ---
const requiredFields = ['phoneNumber', 'childName', 'contactName'];
for (const field of requiredFields) {
if (!params[field]) {
return {
success: false,
code: 'INVALID_PARAMS',
message: `缺少必要的注册信息: ${field}`,
};
}
}
const { phoneNumber, childName, childGender, childBirthday, contactName } = params;
// --- 2. 准备数据 ---
const userData = {
phone: phoneNumber,
role: "1", // 明确角色为家长
};
// --- 3. 写入数据库(伪事务处理) ---
// 理想情况下应使用事务,确保两张表要么都成功,要么都失败。
// 在无事务支持的低代码平台,需要做好失败时的补偿(回滚)操作。
let createdUser = null;
try {
// 3.1 首先创建用户主表记录
const createUserResult = await context.callModel({
dataSourceName: 'dance_users',
methodName: 'wedaCreateV2',
params: {
data: userData,
},
});
if (!createUserResult || !createUserResult.id) {
throw new Error('创建用户主表记录失败');
}
createdUser = createUserResult; // 保存创建的用户信息
// 3.2 准备家长表数据,并关联刚创建的用户ID
const parentData = {
user_id: { _id: createdUser.id }, // 关键步骤:建立关联
child_name: childName,
child_gender: childGender,
child_birthday: childBirthday,
contact_name: contactName,
};
// 3.3 创建家长详情表记录
const createParentResult = await context.callModel({
dataSourceName: 'dance_parent',
methodName: 'wedaCreateV2',
params: {
data: parentData,
},
});
if (!createParentResult || !createParentResult.id) {
throw new Error('创建家长详情记录失败');
}
// --- 4. 返回成功结果 ---
return {
success: true,
message: '家长注册成功',
data: {
userId: createdUser.id,
parentId: createParentResult.id,
role: '1',
},
};
} catch (error) {
console.error('家长注册时发生错误:', error);
// 回滚逻辑:如果用户主表创建成功,但家长表失败了,则删除已创建的用户主表记录
if (createdUser && createdUser.id) {
await context.callModel({
dataSourceName: 'dance_users',
methodName: 'wedaDeleteV2', // 假设有删除方法
params: {
filter:{
where:{
_id:{$eq:createdUser.id}
}
}
},
});
console.log(`已回滚删除用户: ${createdUser.id}`);
}
return {
success: false,
code: 'INTERNAL_ERROR',
message: '注册失败,请稍后重试',
};
}
};
3 老师注册 API
/**
* 老师角色注册
* @param {object} params - 传入参数
* @param {string} params.phoneNumber - 用户的手机号
* @param {string} params.teacherName - 老师姓名
* @param {string} params.gender - 性别
* @param {number} params.teachingSince - 教龄
* @param {string} params.introduction - 老师简介
* @param {object} context - 调用上下文
* @returns {Promise<object>} - 返回操作结果
*/
module.exports = async function (params, context) {
// --- 1. 参数校验 ---
if (!params.phoneNumber || !params.teacherName) {
return {
success: false,
code: 'INVALID_PARAMS',
message: '缺少手机号或老师姓名',
};
}
const { phoneNumber, teacherName, gender, teachingSince, introduction } = params;
// --- 2. 准备数据 ---
const userData = {
phone: phoneNumber,
role: "teacher", // 明确角色为老师
};
// --- 3. 写入数据库(伪事务处理) ---
let createdUser = null;
try {
// 3.1 创建用户主表记录
const createUserResult = await context.callModel({
dataSourceName: 'dance_users',
methodName: 'wedaCreateV2',
params: {
data: userData,
},
});
if (!createUserResult || !createUserResult.id) {
throw new Error('创建用户主表记录失败');
}
createdUser = createUserResult;
// 3.2 准备老师表数据,并关联用户ID
const teacherData = {
user_id: { _id: createdUser.id }, // 建立关联
teacher_name: teacherName,
gender: gender,
teaching_since: teachingSince,
introduction: introduction,
};
// 3.3 创建老师详情表记录
const createTeacherResult = await context.callModel({
dataSourceName: 'dance_teacher',
methodName: 'wedaCreateV2',
params: {
data: teacherData,
},
});
if (!createTeacherResult || !createTeacherResult.id) {
throw new Error('创建老师详情记录失败');
}
// --- 4. 返回成功结果 ---
return {
success: true,
message: '老师注册成功',
data: {
userId: createdUser.id,
teacherId: createTeacherResult.id,
role: '2',
},
};
} catch (error) {
console.error('老师注册时发生错误:', error);
// 回滚逻辑
if (createdUser && createdUser.id) {
await context.callModel({
dataSourceName: 'dance_users',
methodName: 'wedaDeleteV2', // 假设有删除方法
params: {
filter:{
where:{
_id:{$eq:createdUser.id}
}
}
},
});
console.log(`已回滚删除用户: ${createdUser.id}`);
}
return {
success: false,
code: 'INTERNAL_ERROR',
message: '注册失败,请稍后重试',
};
}
};
五、页面搭建:从空白到功能完善
现在,我们将以上逻辑和API串联起来,搭建出实际的前端页面。
1 “我的”页面
- 核心逻辑:页面加载时,首先检查本地存储中是否存在用户信息(如
token
和role
)。- 存在:说明用户已登录。直接调用一个
login
接口获取最新的用户信息(例如家长可以看到宝宝信息,老师可以看到自己的排课),并渲染页面。 - 不存在:显示一个“点击登录/注册”的按钮。
- 存在:说明用户已登录。直接调用一个
- UI组件:用户信息展示区(头像、昵称)、功能列表(我的课表、我的订单等)、登录按钮。
具体的逻辑我们其实在购物车下单的时候已经实现,可以翻看过往的教程
2 角色选择页面
点击创建页面的图标,创建角色选择页面
在页面中添加普通容器,里边添加两个按钮,设置纵向排列,水平垂直居中
给按钮设置点击事件,打开对应的页面
3 家长/老师信息填写页面
- UI:标准的表单页面,包含输入框(宝宝姓名、老师姓名)、选择器(性别、生日)等。
- 核心逻辑:
- 页面加载时,绑定系统登录对象的用户手机号。
- 用户填写完所有必填项后,点击“提交”按钮。
- 前端对表单数据进行校验(如姓名不能为空)。
- 校验通过后,调用对应的注册API。
- 注册成功后,自动跳转回“我的”页面,此时“我的”页面会显示已登录状态。
界面我们使用表单容器搭建,选择对应的数据模型
我们无需显示的在页面上放一个手机号的组件,只需要在表单提交的时候传入入参即可
总结
通过“手机号验证 + 角色选择 + 信息补充”这一套组合拳,我们不仅为少儿舞蹈小程序构建了一个安全、高效且用户体验友好的登录注册系统,更重要的是,我们从一开始就建立了清晰的数据模型和角色划分,为未来实现复杂的业务功能(如排课、点名、家校沟通、权限控制等)铺平了道路。
这种思路充分利用了低代码平台的优势,将复杂的后端逻辑封装在简单的API调用中,让开发者能更专注于前端页面的搭建和业务功能的实现。希望这篇博客能为你提供有价值的参考!
更多推荐
所有评论(0)