一、这节课要解决什么问题
昨天你已经跑通了项目主流程,用户能搜索课程、下单、报名,但问题是:
- 用户在“我的订单”里能看到已购买课程
- 但在“我的课程”里看不到这些课程
所以今天的目标就是:
- 完成“我的课表”相关功能
- 学会从原型分析需求
- 学会设计接口
- 学会设计数据库表
- 学会跨微服务开发业务
二、这节课的业务主线
业务很简单,主线就一句话:
用户买了课 / 报了课 → 课程加入 learning 服务的课表 → 用户在“我的课程”中看到课程 → 后续才能继续学习
同时课程会有状态流转:
- 未学习
- 学习中
- 已学完
- 已失效
其中“学习计划”虽然在页面上有,但 day02 先不实现完整计划功能,只先把课表打通。
三、你要掌握的 8 个接口
这一节围绕“我的课表”总共梳理出 8 个接口/动作:
- 支付或报名成功后,通过 MQ 把课程加入课表
- 分页查询我的课表:
GET /lessons/page - 查询我最近正在学习的课程:
GET /lessons/now - 根据课程 id 查询学习状态:
GET /lessons/{courseId} - 删除课表中的课程:
DELETE /lessons/{courseId} - 退款后,通过 MQ 从课表中移除课程
- 校验指定课程是否在有效课表中:
GET /lessons/{courseId}/valid - 统计课程学习人数:
GET /lessons/{courseId}/count
你现在最需要先吃透的是前 4 个,尤其是前 2 个,因为这是本节重点。
四、接口设计方法,你要记住这套套路
企业里做接口,一般先看四件事:
- 请求方式
- 请求路径
- 请求参数
- 返回值
设计思路是:
- 请求方式、路径:遵循 RESTful
- 请求参数、返回值:结合页面原型和调用方需求一起定
这里最重要的一点是:不要自己臆想字段。字段到底返回什么,必须根据页面展示和调用场景来推导。
五、本节最重要的两个接口
1)分页查询我的课表
接口:
GET /lessons/page
请求参数:
pageNopageSizesortByisAsc
默认按最近学习时间排序。返回值里要包含:
- 课表 id
- 课程 id
- 课程名
- 课程封面
- 加入时间
- 过期时间
- 已学习小节数
- 总小节数
- 课程状态
- 学习计划频率
- 学习计划状态
这个接口的难点不在分页,而在于:
- learning_lesson 表里只有课表数据
- 课程名、封面、总节数这些信息在课程服务里
所以你需要:
- 先查课表分页数据
- 收集
courseId - 调用课程服务批量查课程信息
- 最后组装成
LearningLessonVO
2)支付后加入课表
这是一个 MQ 监听,不是普通 HTTP 接口。
来源:
- 交易服务在用户报名/支付成功后,发送 MQ 消息
- learning 服务监听后,执行加入课表逻辑
消息信息:
- Exchange:
ORDER_EXCHANGE - RoutingKey:
ORDER_PAY_KEY
消息体核心字段:
orderIduserIdcourseIdsfinishTime
实现思路:
- 监听 MQ 消息
- 校验消息是否为空
- 调用
lessonService.addUserLessons(userId, courseIds) - 在 service 中批量创建
LearningLesson
真正的关键点在于:expireTime 怎么算
因为课表表里要存课程过期时间,但 MQ 消息里没有。
所以你要:
- 根据
courseIds调课程服务 - 查出课程有效期
validDuration - 用
createTime + validDuration算出expireTime - 再批量保存课表数据
六、数据库表:learning_lesson 是这节课核心
课表本质上是:
用户 和 课程 的关系表
最核心字段:
iduser_idcourse_id
再加上学习状态相关字段:
status:课程状态week_freq:每周学习频率plan_status:计划状态learned_sections:已学习小节数latest_section_id:最近学习小节 idlatest_learn_time:最近学习时间create_timeexpire_timeupdate_time
并且 (user_id, course_id) 要做唯一索引,避免重复加入课表。
你要记住一句话:
课表表不冗余存课程名、封面、总节数,这些都从课程服务查。
七、用户信息是怎么拿到的
这个点很重要,面试也常问。
流程是:
- 用户登录后,JWT 在网关校验
- 网关把用户 id 放到请求头
- 下游微服务通过拦截器读取请求头
- 存入
UserContext - 业务代码里直接
UserContext.getUser()获取当前用户 id
所以在分页查询我的课表时,先拿:
Long userId = UserContext.getUser();
然后再按用户查课表。
八、分页查询我的课表的完整实现思路
这段代码逻辑你必须能自己复述出来:
第一步:获取当前登录用户
从 UserContext 拿 userId
第二步:查 learning_lesson 分页数据
按 user_id 过滤,按 latest_learn_time 倒序分页
第三步:提取课程 id
从分页结果里提取 courseId 集合
第四步:调用课程服务
查课程基本信息,拿到:
namecoverUrlsectionNum
第五步:封装 VO
把课表数据 + 课程数据合并后返回给前端
九、查询“正在学习的课程”你也要顺带会
虽然前面说重点是两个接口,但这个接口你最好一起学:
GET /lessons/now
返回内容比分页列表更丰富,除了课程基本信息,还要返回:
- 课表课程总数
courseAmount - 最近学习的小节名称
latestSectionName - 最近学习的小节序号
latestSectionIndex
实现时要:
- 查当前用户状态为“学习中”的最近一门课
- 查课程详情
- 统计用户课表课程总数
- 再调用目录服务查最近学习章节信息
十、本节你真正该掌握的知识点
学完 day02,你至少要吃透这 6 个点:
1. 从原型图推接口
不是上来就写代码,而是先想:
- 页面要什么
- 接口该返回什么
2. 中间关系表设计
learning_lesson 本质就是用户和课程关系表
3. 跨微服务查询
课表服务不存课程全部信息,要通过 Feign 调课程服务补数据
4. MQ 驱动业务
支付成功不是直接写课表,而是交易服务发 MQ,学习服务监听后处理
5. 登录上下文传递
网关解析 JWT,微服务通过 UserContext 获取用户
6. 枚举代替魔法值
LessonStatus、PlanStatus 用枚举,不要在代码里到处写 0/1/2/3。
十一、你今天的学习顺序建议
你可以按这个顺序学,效率最高:
- 先看业务目标:为什么“我的订单有课,但我的课程没课”
- 再看 8 个接口总表,先建立全局认识
- 重点吃透
GET /lessons/page - 再吃透 MQ 加入课表
- 然后看
learning_lesson表结构设计 - 最后看
queryMyLessons()和addUserLessons()两段核心实现
十二、给你的学习任务
今天你至少完成这 3 件事:
- 自己把
learning_lesson表结构默写一遍 - 自己口述一遍“支付成功加入课表”的完整流程
- 不看文档,自己说出
queryMyLessons()的实现步骤