阅读前请先下载项目源码,边读边看源码以加深理解和实操,
源码地址已放于文章末尾!
小程序效果预览:




第六章:家政员的工作台——服务者端功能实现
各位同学,欢迎回到我们的项目实战。在彻底搞定了用户端的预约全流程后,我们现在要换个视角,穿上“工作服”,体验一下家政员(服务提供者)的工作日常。
在README.md的规划中,我们知道项目包含三个端:用户端、家政员端和后台管理端。这一章,我们就来揭秘“家政员端”的实现。它不像用户端那样功能繁多,但却至关重要,因为它直接关系到服务的供给侧。家政员需要在这里管理个人资料、设置可预约的时间(也就是“上架库存”),以及核销用户的预约(完成“交易闭环”)。
我们将快速过一下这部分的核心功能,你会发现,很多后端的基础设施(如Service、Model)都是可以复用的,这也是分层架构的巨大优势。
6.1 角色权限的“守门人”:家政员登录与身份验证
与后台管理员类似,家政员端也需要一套独立的登录和身份验证机制,确保只有合法的服务人员才能访问相关功能。
- 前端登录入口:
/miniprogram/projects/workhome/pages/work/login/work_login.js - 后端登录接口:
cloudfunctions/.../controller/work/work_home_controller.js->workLogin() - 后端服务逻辑:
cloudfunctions/.../service/work/work_home_service.js->login() - 身份校验基类:
cloudfunctions/.../controller/work/base_project_work_controller.js
后端登录逻辑 work_home_service.js
// 位置: /cloudfunctions/mcloud/project/workhome/service/work/work_home_service.js
const MeetModel = require('../../../model/meet_model.js');
// ...
class WorkHomeService extends BaseProjectWorkService {
/**
* 登录
*/
async login(phone, password) {
// 查询家政员(MEET)是否存在
let where = {
MEET_PHONE: phone,
MEET_PASSWORD: md5Lib.md5(password)
};
let fields = 'MEET_ID, MEET_TOKEN, MEET_TOKEN_TIME, MEET_STATUS';
let meet = await MeetModel.getOne(where, fields);
if (!meet) this.AppError('账号或密码不正确');
if (meet.MEET_STATUS != 1) this.AppError('该账号已被禁用');
// 生成新的token并更新
let token = dataUtil.genRandomString(32);
let tokenTime = timeUtil.time();
let dataUpdate = {
MEET_TOKEN: token,
MEET_TOKEN_TIME: tokenTime
};
await MeetModel.edit(meet._id, dataUpdate);
return { token };
}
}
代码讲解:
- 复用
MeetModel:这里有一个非常巧妙的设计。家政员(服务者)的数据,并没有单独创建一张worker表,而是直接复用了meet表。meet表既代表一个“服务”,也代表提供这个服务的“家政员”。通过在meet表中增加MEET_PHONE,MEET_PASSWORD,MEET_TOKEN等字段,实现了账户管理功能。这种“一表多用”的设计在业务实体高度绑定的情况下可以简化数据结构。 - 登录逻辑:与后台管理员登录如出一辙,通过手机号和MD5加密后的密码查询用户,成功后生成一个新的
token并存入数据库,然后将token返回给前端。
身份校验 base_project_work_controller.js
所有家政员端接口的Controller都继承自BaseProjectWorkController,这个基类会在执行具体业务方法前,自动进行身份校验。
// 位置: /cloudfunctions/mcloud/project/workhome/controller/work/base_project_work_controller.js
class BaseProjectWorkController extends BaseController {
async initSetup() {
// 调用service进行token校验
let workService = new BaseProjectWorkService();
let meet = await workService.isWork(this._token); // this._token是前端传来的
if (!meet) this.AppError('登录已过期,请重新登录', 401);
this._meetId = meet.MEET_ID; // 将家政员ID挂载到this上,方便后续方法使用
this._meet = meet;
}
}
代码讲解:
initSetup():我们在第二章讲过,这个方法会在application.js中被自动调用。这里它重写了父类的initSetup,在里面执行了登录校验workService.isWork(this._token)。isWork():这个服务方法会拿着前端传来的token去meet表里查找,并检查token是否在有效期内。- 通过这种方式,所有继承自它的Controller(如
work_meet_controller.js)的每个方法,在执行前都自动获得了“身份保护”,无需重复编写校验代码。
6.2 “我的时间我做主”——排班管理
家政员最重要的工作就是设定自己什么时间可以接单。这个功能对应到我们系统里,就是修改day集合的数据。
- 前端页面:
/miniprogram/projects/workhome/pages/work/meet/time/work_meet_time.js - 后端控制器:
.../controller/work/work_meet_controller.js->editMeet() - 后端服务:
.../service/work/work_meet_service.js->editMeet()
work_meet_service.js - 更新排班的核心逻辑
// 位置: /cloudfunctions/mcloud/project/workhome/service/work/work_meet_service.js
const DayModel = require('../../../model/day_model.js');
const MeetModel = require('../../../model/meet_model.js');
// ...
class WorkMeetService extends BaseProjectWorkService {
/**
* 修改自己的可预约时段
* @param {string} meetId 家政员ID
* @param {object} daysSet 包含日期和时段的完整排班数据
*/
async editMeet(meetId, { daysSet }) {
// 1. 先删除旧的排班数据
await DayModel.del({ DAY_MEET_ID: meetId });
// 2. 遍历新的排班数据,逐条插入
for (let i = 0; i < daysSet.length; i++) {
let dayNode = daysSet[i];
if (!dayNode.day || !dayNode.times) continue;
let data = {
DAY_MEET_ID: meetId,
day: dayNode.day,
dayDesc: timeUtil.getWeek(dayNode.day),
times: dayNode.times // [{mark, start, end, limit...}]
};
await DayModel.insert(data);
}
// 3. 更新meet表中的日期缓存
let days = dataUtil.getArrByKey(daysSet, 'day');
let dataMeet = {
MEET_DAYS: days.sort()
};
await MeetModel.edit(meetId, dataMeet);
return;
}
}
代码讲解:
- 全量更新:这里的排班更新采用了一种简单粗暴但有效的方式——“先删后插”。每次家政员保存排班时,后端会先把该家政员(
DAY_MEET_ID)所有旧的day记录全部删除,然后再把前端提交的新的排班数据(daysSet)一条条地重新插入。- 优点:逻辑简单,不需要去比对哪些时段是新增的、哪些是修改的、哪些是删除的。
- 缺点:如果排班数据量巨大,性能可能会有影响。但在当前项目的中小业务规模下,这是一种非常实用的策略。
- 更新
MEET_DAYS缓存:在更新完day表后,会把所有有排期的日期提取出来,更新到meet表本身的MEET_DAYS字段中。这个字段在用户端列表页会用到,用于快速判断某个家政员“近期可约”,而无需去day表里做复杂的查询,是典型的用空间换时间的性能优化。
7.3 核销预约——服务完成的见证
当用户上门时,家政员需要核销用户的预约码,标志着本次服务的正式开始和完成。
- 后端控制器:
.../controller/work/work_meet_controller.js->scanJoin() - 后端服务:
.../service/work/work_meet_service.js->scanJoin()
// 位置: /cloudfunctions/mcloud/project/workhome/service/work/work_meet_service.js
class WorkMeetService extends BaseProjectWorkService {
/**
* 核销
* @param {string} meetId 家政员ID
* @param {string} code 核销码
*/
async scanJoin(meetId, code) {
let where = {
JOIN_CODE: code,
JOIN_MEET_ID: meetId // 必须是预约我自己的单
};
let join = await JoinModel.getOne(where);
if (!join) this.AppError('未找到该预约记录');
if (join.JOIN_IS_CHECKIN) this.AppError('该预约码已被使用');
if (join.JOIN_STATUS != JoinModel.STATUS.SUCC) this.AppError('该预约状态不可核销');
// 更新核销状态和时间
let data = {
JOIN_IS_CHECKIN: 1,
JOIN_CHECKIN_TIME: this._timestamp
};
await JoinModel.edit(join._id, data);
return;
}
}
代码讲解:
- 多重校验:核销是一个关键的业务闭环节点,因此包含了多重安全校验:
JOIN_CODE是否存在。JOIN_MEET_ID是否匹配当前家政员,防止误核销别人的订单。JOIN_IS_CHECKIN是否已经被核销过,防止重复使用。JOIN_STATUS是否是“预约成功”状态,已取消的订单不能被核销。
- 状态变更:校验通过后,将
JOIN_IS_CHECKIN字段置为1,并记录下核销时间。
通过对家政员端这三个核心功能的剖析,我们可以看到,后端代码得到了最大程度的复用。一套设计良好的数据模型和分层架构,可以轻松地支撑起多个不同角色的业务需求。
下一章,我们将戴上“管理员”的帽子,进入拥有最高权限的后台管理系统,看看它是如何“运筹帷幄”,掌控整个平台的。
游戏源码下载地址:
https://thmail.lanzouu.com/iNlWs345lk9i

被折叠的 条评论
为什么被折叠?



