天机学堂day02

5 阅读6分钟

一、这节课要解决什么问题

昨天你已经跑通了项目主流程,用户能搜索课程、下单、报名,但问题是:

  • 用户在“我的订单”里能看到已购买课程
  • 但在“我的课程”里看不到这些课程

所以今天的目标就是:

  • 完成“我的课表”相关功能
  • 学会从原型分析需求
  • 学会设计接口
  • 学会设计数据库表
  • 学会跨微服务开发业务

二、这节课的业务主线

业务很简单,主线就一句话:

用户买了课 / 报了课 → 课程加入 learning 服务的课表 → 用户在“我的课程”中看到课程 → 后续才能继续学习

同时课程会有状态流转:

  • 未学习
  • 学习中
  • 已学完
  • 已失效

其中“学习计划”虽然在页面上有,但 day02 先不实现完整计划功能,只先把课表打通。


三、你要掌握的 8 个接口

这一节围绕“我的课表”总共梳理出 8 个接口/动作:

  1. 支付或报名成功后,通过 MQ 把课程加入课表
  2. 分页查询我的课表:GET /lessons/page
  3. 查询我最近正在学习的课程:GET /lessons/now
  4. 根据课程 id 查询学习状态:GET /lessons/{courseId}
  5. 删除课表中的课程:DELETE /lessons/{courseId}
  6. 退款后,通过 MQ 从课表中移除课程
  7. 校验指定课程是否在有效课表中:GET /lessons/{courseId}/valid
  8. 统计课程学习人数:GET /lessons/{courseId}/count

你现在最需要先吃透的是前 4 个,尤其是前 2 个,因为这是本节重点。


四、接口设计方法,你要记住这套套路

企业里做接口,一般先看四件事:

  • 请求方式
  • 请求路径
  • 请求参数
  • 返回值

设计思路是:

  • 请求方式、路径:遵循 RESTful
  • 请求参数、返回值:结合页面原型和调用方需求一起定

这里最重要的一点是:不要自己臆想字段。字段到底返回什么,必须根据页面展示和调用场景来推导。


五、本节最重要的两个接口

1)分页查询我的课表

接口:

  • GET /lessons/page

请求参数:

  • pageNo
  • pageSize
  • sortBy
  • isAsc

默认按最近学习时间排序。返回值里要包含:

  • 课表 id
  • 课程 id
  • 课程名
  • 课程封面
  • 加入时间
  • 过期时间
  • 已学习小节数
  • 总小节数
  • 课程状态
  • 学习计划频率
  • 学习计划状态

这个接口的难点不在分页,而在于:

  • learning_lesson 表里只有课表数据
  • 课程名、封面、总节数这些信息在课程服务里

所以你需要:

  1. 先查课表分页数据
  2. 收集 courseId
  3. 调用课程服务批量查课程信息
  4. 最后组装成 LearningLessonVO

2)支付后加入课表

这是一个 MQ 监听,不是普通 HTTP 接口。

来源:

  • 交易服务在用户报名/支付成功后,发送 MQ 消息
  • learning 服务监听后,执行加入课表逻辑

消息信息:

  • Exchange:ORDER_EXCHANGE
  • RoutingKey:ORDER_PAY_KEY

消息体核心字段:

  • orderId
  • userId
  • courseIds
  • finishTime

实现思路:

  1. 监听 MQ 消息
  2. 校验消息是否为空
  3. 调用 lessonService.addUserLessons(userId, courseIds)
  4. 在 service 中批量创建 LearningLesson

真正的关键点在于:expireTime 怎么算

因为课表表里要存课程过期时间,但 MQ 消息里没有。
所以你要:

  • 根据 courseIds 调课程服务
  • 查出课程有效期 validDuration
  • createTime + validDuration 算出 expireTime
  • 再批量保存课表数据

六、数据库表:learning_lesson 是这节课核心

课表本质上是:

用户 和 课程 的关系表

最核心字段:

  • id
  • user_id
  • course_id

再加上学习状态相关字段:

  • status:课程状态
  • week_freq:每周学习频率
  • plan_status:计划状态
  • learned_sections:已学习小节数
  • latest_section_id:最近学习小节 id
  • latest_learn_time:最近学习时间
  • create_time
  • expire_time
  • update_time

并且 (user_id, course_id) 要做唯一索引,避免重复加入课表。

你要记住一句话:

课表表不冗余存课程名、封面、总节数,这些都从课程服务查。


七、用户信息是怎么拿到的

这个点很重要,面试也常问。

流程是:

  1. 用户登录后,JWT 在网关校验
  2. 网关把用户 id 放到请求头
  3. 下游微服务通过拦截器读取请求头
  4. 存入 UserContext
  5. 业务代码里直接 UserContext.getUser() 获取当前用户 id

所以在分页查询我的课表时,先拿:

Long userId = UserContext.getUser();

然后再按用户查课表。


八、分页查询我的课表的完整实现思路

这段代码逻辑你必须能自己复述出来:

第一步:获取当前登录用户

UserContextuserId

第二步:查 learning_lesson 分页数据

user_id 过滤,按 latest_learn_time 倒序分页

第三步:提取课程 id

从分页结果里提取 courseId 集合

第四步:调用课程服务

查课程基本信息,拿到:

  • name
  • coverUrl
  • sectionNum

第五步:封装 VO

把课表数据 + 课程数据合并后返回给前端


九、查询“正在学习的课程”你也要顺带会

虽然前面说重点是两个接口,但这个接口你最好一起学:

  • GET /lessons/now

返回内容比分页列表更丰富,除了课程基本信息,还要返回:

  • 课表课程总数 courseAmount
  • 最近学习的小节名称 latestSectionName
  • 最近学习的小节序号 latestSectionIndex

实现时要:

  1. 查当前用户状态为“学习中”的最近一门课
  2. 查课程详情
  3. 统计用户课表课程总数
  4. 再调用目录服务查最近学习章节信息

十、本节你真正该掌握的知识点

学完 day02,你至少要吃透这 6 个点:

1. 从原型图推接口

不是上来就写代码,而是先想:

  • 页面要什么
  • 接口该返回什么

2. 中间关系表设计

learning_lesson 本质就是用户和课程关系表

3. 跨微服务查询

课表服务不存课程全部信息,要通过 Feign 调课程服务补数据

4. MQ 驱动业务

支付成功不是直接写课表,而是交易服务发 MQ,学习服务监听后处理

5. 登录上下文传递

网关解析 JWT,微服务通过 UserContext 获取用户

6. 枚举代替魔法值

LessonStatusPlanStatus 用枚举,不要在代码里到处写 0/1/2/3。


十一、你今天的学习顺序建议

你可以按这个顺序学,效率最高:

  1. 先看业务目标:为什么“我的订单有课,但我的课程没课”
  2. 再看 8 个接口总表,先建立全局认识
  3. 重点吃透 GET /lessons/page
  4. 再吃透 MQ 加入课表
  5. 然后看 learning_lesson 表结构设计
  6. 最后看 queryMyLessons()addUserLessons() 两段核心实现

十二、给你的学习任务

今天你至少完成这 3 件事:

  1. 自己把 learning_lesson 表结构默写一遍
  2. 自己口述一遍“支付成功加入课表”的完整流程
  3. 不看文档,自己说出 queryMyLessons() 的实现步骤