Java 篇-项目实战-天机学堂(从0到1)-day3

张开发
2026/4/20 19:42:44 15 分钟阅读

分享文章

Java 篇-项目实战-天机学堂(从0到1)-day3
java 篇 1.基础地基 2.设计原理 3.项目实战学习建议先看视频跟着学再看文档自己敲不会地方到处问问完总结记笔记。业务流程与接口分析业务流程先熟悉一下分析产品原型得出请求参数提交学习记录请求参数根据 id 查询指定课程的学习记录这里返回的数据涉及到两部分一部分是课程的基本信息另一部分是学习记录。要么发两个请求分别调用两个服务但是这样服务器压力比较大要么就发一次请求请求一个服务然后像之前一样通过 feign 接口调其他的另一个服务但是这样带宽会比较大。那这里采用只发一次请求前端也喜欢这样的那调用哪个呢哪个数据多调哪个服务因为如果调数据少的那个服务那另一个传的数据就多了。so 这里调课程微服务。这里加的课表 id因为课程没有课表的信息这里前端通过这个课表 id把课表相关的信息也一并提交了。意思就是为了满足上个接口的需要。返回值当中最近学习的小结 id 的作用就是你点进去接着上次的地方看。而 records 记录的是每节学习情况抽取业务实体创建新分支右键 dev,新建分支然后就切换到这个新分支了实现功能创建学习计划先看下接口描述然后页面上演示这里修改计划和创建计划本质上就是改数据库当中 week_freq 字段所以代码上用 post 或 put 都无所谓那唯一不同的就是创建会改 plan_status 字段接下来开始代码的部分提前定义好了 DTO当中的注解便于后面用 Valid 校验然后是 Controller 层别忘了加 Valid还有传入的参数可以是 planDTO当然因为 planDTO 里面的参数比较少所以也可以直接传里面的参数。然后是 ServiceImpl 层①这个就等同于前面的注释它会判断 lesson 是否为 Null,如果为 Null那它就会抛出后面的信息当然第二个参数可以没有它有默认值。②这里的判断写不写其实无所谓无论你是创建还是修改这个 PlanStatus 那都是 1 呀前端测试通过实现功能查询学习计划先看下接口描述再看下原型图接下来开始代码的编写先是 controller 层既然是分页查询那它传入的参数就一定是分页请求参数返回的结果一定分页结果参数之前也定义过了然后这里是 get 方法,记得 return 返回。那 LearningPlanPageVO 则对 PageDTO 进行了封装因为它对于一般的返回参数多了 weekPoints,weekFinished,weekTotalPlan 属性。那这里的含参构造方法有两种一种是原先的你传入 PageDTO那总行数总页数等这些数据直接从 PageDTO 当中拿那如果不是而是传进来的就是三个参数那就直接赋值。这里还写了无参构造方法不过看起来没啥用。然后进入重头戏serviceImpl 的实现。再来看下查询的结果是咋样的。咱们分析一波返回的数据分为上下两部分上部分是总的统计数据下部分是查询的分页数据。在对上部分进行分析本周实际学习小结数量这里指已学完都说本周了那就不可能到 learning_lesson 当中去拿因为它记录的是总的已学。那就得到 learning_record 当中去拿那怎么去拿呢首先本周那肯定得有时间吧那用户 id 肯定也需要吧那一条 record 就是一个用户对应一门课对应一个小节那小节的状态是不是也需要呀。so 这里就需要三个东西用户 id、时间、状态。搞定本周计划学习小节量既然是计划那就跟 plan_status 和 week_freq 有关那得从 learning_lesson 当中去拿一个用户有多个课程里面的信息如状态、已学小节数等都是实时的所以这里只需把每门课程的 week_freq 累加即可那当然这里的状态要么是还没开始学要么是正在学 不过我觉得可能也会出现重复学习的情况但视频里没提到先忽略把 搞定本周学习积分:TODO 待定搞定在对下部分进行分析某课程本周计划学习小结数量因为前面说过了learning_lesson 是实时的所以可以直接从 week_freq 当中拿。搞定某课程本周已经学习小节数量因为前面说过了learning_lesson 是实时的所以可以直接从 learned_sections 当中拿。搞定学习时间因为前面说过了learning_lesson 是实时的所以可以直接从 latest_learn_time 当中拿。搞定某课程本周已经学习小节数量那这个就得从 learninig_record 表当中去拿了我们想要的是课数量 这样的形式就想到了分组查询。那用什么字段查呢就是 user_id ,finished ,finish_time 搞定课程名称这个 Learning_lesson 表当中没有得从 course 那张表拿那用什么拿那当然是用 course_id 去拿咯这个之前写过的mapid,Object 想起没搞定某课程总小节数量这个也得从 course 那张表拿。搞定开始写代码咯宝贝~先搭个基础框架定义最终输出结果把 userId 始末时间这些固定嘉宾放进来注释标注好流程分区后续去填充各个分区当中对于时间用到了 DateUtils 封装的工具类小细节传入的是年月日输出想要的是年月日时分秒。然后开始干第一个本周学完小节数因为查的是另一张表所以得注入那这里为啥注入的是 mapper 而不是 service 呢因为另一个 service 已经注入我们的这个 lessonservice 了再注入的话就循环依赖了这里是 RequiredArgsConstructor构造器注入。为啥提这个请看下文。Spring 如何解决循环依赖Spring 通过三级缓存来解决单例 Bean 的循环依赖问题。三级缓存机制缓存名称作用一级缓存singletonObjects存放完全初始化好的 Bean二级缓存earlySingletonObjects存放早期暴露的 Bean还没完成属性注入三级缓存singletonFactories存放 Bean 工厂用于生成早期暴露的对象解决流程以 A → B → A 为例核心思路Spring 允许 Bean 在未完全初始化时先暴露一个早期引用给依赖它的其他 Bean。什么情况下 Spring 无法解决场景能否解决原因单例 Bean 字段注入Autowired✅ 能Spring 默认支持单例 Bean Setter 注入✅ 能Spring 默认支持构造器注入❌ 不能实例化时就需要的依赖无法提前暴露Prototype原型Bean❌ 不能Spring 不缓存原型 Bean代理对象Async、Transactional⚠️ 可能出问题代理创建时机可能导致问题ok,继续因为 recordMapper 继承了基础 Mapper所以这里直接用里面的 selectCount 方法里面的参数是 wrapper 参数不是 lambdaquery()你可以这么理解lambdaquery()等于前面的 selectCount(),那为啥不用这个因为这个是另一张表呀兄弟。当中的.gt 代表greater than.lt 代表less than怎么判断是否是这周呢拿 getFinishTime别拿 createTime 了ok完事给下 result等下这里为啥没有健壮性处理呢我的理解是它是计算操作不是直接查表里哪个数据所以不用。干本周计划学习小节量这个就是在本表当中找getBaseMapper()是 MyBatis-Plus 中ServiceImpl类提供的一个方法用于获取当前 Service 对应的 Mapper 对象。然后定义 queryTotalPlan()方法去实现。别忘了加 Param(“userId”),跟 mapper.xml 当中的#{userId}绑注意下格式关注下大小写ok,干掉本周学习积分待定TODO 写前面o 了进入下部分了先把 po 搞过来先你看这就在同张表当中用到 lambdaQuery()了吧那分页查那当然返回的是 Page 了当中的 status 用的是 in别再用 eq 了然后里面呢取枚举类的值视频当中是没有取值的但根据之前操作数据库表的写法就先加上了后面有问题再说。query.toMpPage(“latest_learn_time”, false)传统的就是.page()里面 new Page(),那这里呢传入的请求直接用.toMpPage听名字就是转为 Mp 的 Page,你想想请求四个东东那排序这里是不是得用上所以里面排序字段为latest_learn_timeasc 为 false,降序ok,然后我们只要 records,不要总行数总页数取下子再进行健壮性处理你看这里就用到的之前提到的有参方法ok干掉了再干需要查 course 表的数据这里之前写过直接封装成函数调用一个课程 id 对应一个课程信息。o 了再干每个课程本周已学习小节数量我们提前定义好了键值类这里静态 toMap()方法将列表转为 map,方便根据 id 取当中每一条数据。先定义个 list 集合统计的是 record 表所以用 recordMapper,再定义个.coutLearnedSections()方法去实现键是 lesson_id别写 user_id 了这里起别名得跟 IdAndDTO 当中的对应。换个别名行不行完全可以 只要别名和 DTO 字段名对得上就行代表 ,gt greater than代表 , lt less than这里为啥不直接用 和 呢你没发现对吧这些符合有其他含义了之后再将它转为 map用到前面定义的静态方法ok原料都准备好了开始拼装这里注意输入输出是什么。健壮性处理countmap.getOrDefault(r.getId(),0)有取它没有就用默认值。至此这个接口已经开发完毕开始测试。这里我在测试的时候遇到了小小的问题网站输入 http://nacos.tianji.com/发现打不开了但是输入 http://192.168.150.101/又可以。哦 原来是启动 letVPN 时把 hosts 改没了重新写下C:\Windows\System32\drivers\etc\hostsok,好了学习计划出来了观看课程 3确实有变化这里看课程 3 试看课可以正常放但是到 3 分钟就显示课程不支持试看了然后 java 泛型这门课没有试看就直接显示课程不支持试看了然后这是在已经实现 day2 的检查课程是否有效功能的前提下 然后看了下代码里面就是看数据库有木有 user_id 和 course_id 的记录但是数据库当中是有的然后我把过期时间改了还是不行。算了这个先放了还有我发现lesson 当中这个字段最近学习小节 id它不是说这门课有 20 节课id 就是从 1-20 这样子的它推测这是用在分布式环境中国可能是自增或者雪花。但是具体没找到先过。实现功能查询指定课程的学习记录****分析一下传进来的是 courseId,用户 userId 在线程域当中直接取。那课表 id 和最近学习的小节 id 都在 LearningLesson 当中那 LearningLesson 又可以通过 courseId 和 userId 取得那这两个就干掉了。那 records 在 Learningrecord 当中那 Learningrecord 又可以通过 lessonId 得到so 解决了。这个在 learningClient 当中有直接 copy 过来那接下就是具体的 learningrecordserviceImpl 当中实现划红线的地方看下其他都大差不多。BeanUtils.copyList()直接把整个列表的 PO 转为 DTO 了不是像之前一样 BeanUtils.copyBean()一个一个来了。实现功能查询最近正在学习的课程学习计划先看下原型图接口分析下实现功能提交学习记录明确一下需求先观察分析下请求参数和两张表的字段。看下需要更新每张表的哪几个字段。初步扫一眼learning_record 当中的 moment、finished、finish_time 这几个可能要变的learning_lesson 当中 status、learned_sections、latest_section_id、latest_learn_time 可能要变接下来就是业务的流程这里的“是否第一次学完” 涉及到幂等性处理因为视频可以重复放。开始代码的编写Controller 层因为请求路径就是/learning-records所以这里 PostMapping 不填RequestBody 请求头么那也没有从路径上去读取参数这么一说。接下来是具体的 serviceImpl 的实现按对表的操作可以分成两部分上部分是对学习记录表进行操作新增或更新下部分是对课表进行操作更新字段如学习小节数、课表状态、最近学习小节、最近学习时间那上部分又可以分为两部分1.对考试处理 2.对视频处理那下部分也可以分为两部分 1.小节已完成 2.小节未完成代码上流程体现是这样的这里的 finished 就是用来判断小节是否已完成最后记得 Transactional 当中涉及多表操作注意只有在数据库层面操作才需要值例如传参规范一般像唯一标识放前面然后为啥处理课表数据不传 userId 呢因为 recordDTO 当中记录了 lessonId它就是主键可以通过它直接拿整个记录了之后就是在这个旧的记录上修修补补。接着我们先去完成处理考试的那一部分因为它最简单只需要新增学习记录就行了观察一下可以发现只需要我们额外传的是 userId 和 finished,因为是考试所以 finished 必为 true ,finishTime 和 commitTime 一样createTime 和 updateTime 又有默认值当中的 DbException 为自定义数据库异常继承了运行时异常。如果出现了异常就中断了流程不会执行下面的 return true。然后去处理视频那一部分对着流程图来看得先判断记录是否存在那这里得查询旧的学习记录看有没有。那根据什么查呢一个用户可以有多条记录一个课表 lesson 又有多个小节但是啊 lesson_id 相当于把 user_id 和 course_id 锁住了那这里再帮个 section_id 就行了。如果没有就新建这个过程 copy 前面的考试处理那因为是新建那数据填充当中 finish 部分就没了返回也一定是 false你不可能新建就设置完成把。如果存在就需要更新学习记录进度条 moment 肯定得有那这小节是不是第一次完成的那还需要进行判断满足两个条件第一个就是之前旧数据显示没有完成第二个进度条超过了总时长的一半这里是*2 嫌效率低可以位运算。如果是第一次完成那还需要更新 finished 字段和 finishedTime.ok 现在记录表部分 o 了开始干课表了你会发现无论是不是第一次学完它最终操作都是去更新课表只不过字段不同罢了不是第一次学完更新最近学习小节 id,最近学习时间是第一次学完更新学习的小节数 1小节全学完更新课表状态为已学完但别忘了如果一开始课表状态为未学习那这要提交学习记录是不是状态得变为学习中呀当中 lambdaUpdate()前面得加 lessonSerivice,因为这是在 learningrecord 的类。然后判断课表状态统一用已学习小节数判断不要直接用原先状态判断怪怪的。LessonStatus.LEARNING.getValue()这里得有 getValue()因为是数据库层面的复习一下。.setSql(finished,“learned_sections learned_sections 1”)像这个在原来值的基础上 1,-1 可以直接写 sql 不用之前已经拿到 lesson 了也可以直接赋值也没毛。当中的 BizIllegalException 为自定义业务不合法异常也是继承运行时异常。好了到这里这个功能接口也是开发完了下面进行测试。IDE 上启动 learning 服务以及在 Jenkins 上启动相关的服务如 exam 服务media 服务进入学习中心点击继续学习这里发送了 learning-records 请求如果没有的话检查下代码接口名称对不对然后到数据库把该课程的过期时间改下然后退出重进下页面就有了。然后这个请求隔个几秒就会发送一次类似心跳里面记录了 moment 的变化查看数据库里面的数据也变了当进度超过一半时标记该小节已经完成当进度全播放完会自动开启下小节最近学习小节数更新然后 record 表也多了一条记录返回后页码上也变了如果对你有帮助的话请点赞关注收藏。热爱可抵一切 ❤️

更多文章