项目日志 学成在线 企业级微服务大项目实战《学成在线》【二】(课程相关接口) 何平安 2024-02-04 2025-03-08 !!!移步我的老博客:企业级微服务大项目实战《学成在线》【二】(课程相关接口) - 何平安 - 博客园 !!!
下面正式开始开发!
对了我的笔记肯定不会把全部代码都打上去,我会挑一些技术点进行阐述。
补充下为啥要叫DTO,PO啥的:
DTO :前端给后端传递的数据
VO :后端给前端传递的数据
DO :数据库表结构
PO :数据库表结构到JAVA的映射类
课程信息 查询 开发习惯从底层开始,所以就从DAO层(mapper层)开始写,再写service。
先在content-service写个测试类,配置和包看黑马的去。介绍下以前学过的分页查询插件courseBaseMapper,实质上就是在sql语句上加上limit等语句,可以看下测试类的代码:
1 2 3 4 5 6 7 8 9 10 11 @SpringBootTest public class CourseBaseMapperTests { @Autowired CourseBaseMapper courseBaseMapper; @Test public void testCourse () { CourseBase courseBase = courseBaseMapper.selectById(18 ); Assertions.assertNotNull(courseBase); } }
ctrl点击selectById就可以看到这个插件的底层实现原理。然后是分页查询的全部代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void testCourse () { CourseBase courseBase = courseBaseMapper.selectById(18 ); Assertions.assertNotNull(courseBase); PageParams pageParams = new PageParams (); pageParams.setPageNo(1L ); pageParams.setPageSize(10L ); QueryCourseParamsDto courseParamsDto = new QueryCourseParamsDto (); courseParamsDto.setCourseName("java" ); LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(StringUtils.isNotEmpty(courseParamsDto.getCourseName()),CourseBase::getName,courseParamsDto.getCourseName()); queryWrapper.eq(StringUtils.isNotEmpty(courseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,courseParamsDto.getAuditStatus()); Page<CourseBase> page = new Page <>(1 ,10 ); Page<CourseBase> pageResult = courseBaseMapper.selectPage(page, queryWrapper); PageResult<CourseBase> tPageResult = new PageResult <>(pageResult.getRecords(),pageResult.getTotal(),pageParams.getPageNo(),pageParams.getPageSize()); System.out.println(tPageResult); }
运行出来拿到断点有结果就成了:
大概流程就是利用LambdaQueryWrapper那个插件然后一直利用参数查询所需要的结果,一步一步装填数据,最终以PageResult的形式返回就行。
然后说一下根据课程状态进行查询,在实际开发中一般会以代码来代替课程状态的中文,也是为了方便修改和规范,所以有个xc2.0_system的dictionary的数据库表就拿来表示对应关系:
直接在刚刚的代码下加一个条件:courseParamsDto.setAuditStatus(“202004”);
接下来就来测试接口,在默认文件里api模块下有个Controller类里面接口是写好的,然后就可以运行ContentApplication的springboot启动项,然后浏览器输入localhost:63040/content/swagger-ui.html,然后测试一下有数据就成功。还有个HttpClient插件接口测试,可以去下载搜一下,postman也可以。像我的就比较推荐httpclient直接就在idea里测试:
它还可以设置环境,在api-test下创建一个env.json文件,里面编写:
{ “dev”: { “access_token”: “”, “gateway_host”: “localhost:63010”, “content_host”: “localhost:63040”, “system_host”: “localhost:63110”, “media_host”: “localhost:63050”, “search_host”: “localhost:63080”, “auth_host”: “localhost:63070”, “checkcode_host”: “localhost:63075”, “learning_host”: “localhost:63020” } }
然后再在http里将localhost:63040换成NaN
前后端联调 解压打开前端文件这里用idea打开,然后去设置里配置npm和node的位置,打开根目录下的package.json ,运行serve的(idea它有),或者右键package.json选择查看npm脚本找到serve运行。对了如果运行失败,大概率是你的node版本与该项目的node文件版本不一致,两种办法,一是卸载项目里的node_moudels,再cnpm install安装自己版本的node文件,二是你自己更换node版本,这里是16.17.0,但是我用了第一种方法发现运行是没问题,但是一直报错,可能版本不同有些语法也不同吧哈哈哈,还是用第二种吧。
这里推荐另一个插件nvm,可以管理node的版本:windows下node更换版本(简单操作)_node版本更改-CSDN博客 ,有了它想切换哪个版本就切换。推荐我这样做,算是第三种方法吧qwq
将资料的system文件导入java项目中,复制api的resourse到service并修改下数据库的连接地址。
解决跨域问题 在浏览器通过http://localhost:8601/地址访问前端工程。
chrome浏览器报错如下:
Access to XMLHttpRequest at ‘http://localhost:63110/system/dictionary/all ‘ from origin ‘http://localhost:8601 ‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
firefox浏览器报错如下:
已拦截跨源请求:同源策略禁止读取位于 http://localhost:63110/system/dictionary/all 的远程资源。(原因:CORS 头缺少 ‘Access-Control-Allow-Origin’)。状态码:200。
提示:从http://localhost:8601 访问http://localhost:63110/system/dictionary/all被CORS policy阻止,因为没有Access-Control-Allow-Origin 头信息。CORS全称是 cross origin resource share 表示跨域资源共享。
出这个提示的原因是基于浏览器的同源策略,去判断是否跨域请求,同源策略是浏览器的一种安全机制,从一个地址请求另一个地址,如果协议、主机、端口三者全部一致则不属于跨域,否则有一个不一致就是跨域请求。
比如:
从http://localhost:8601 到 http://localhost:8602 由于端口不同,是跨域。
从http://192.168.101.10:8601 到 http://192.168.101.11:8601 由于主机不同,是跨域。
从http://192.168.101.10:8601 到 https://192.168.101.10:8601 由于协议不同,是跨域。
注意:服务器之间不存在跨域请求。
浏览器判断是跨域请求会在请求头上添加origin,表示这个请求来源哪里。
比如:
GET / HTTP/1.1 Origin: http://localhost:8601
服务器收到请求判断这个Origin是否允许跨域,如果允许则在响应头中说明允许该来源的跨域请求,如下:
Access-Control-Allow-Origin:http://localhost:8601
如果允许任何域名来源的跨域请求,则响应如下:
Access-Control-Allow-Origin:*
解决跨域的方法:
1、JSONP
通过script标签的src属性进行跨域请求,如果服务端要响应内容则首先读取请求参数callback的值,callback是一个回调函数的名称,服务端读取callback的值后将响应内容通过调用callback函数的方式告诉请求方。如下图:
2、添加响应头
服务端在响应头添加 Access-Control-Allow-Origin:*
3、通过nginx代理跨域
由于服务端之间没有跨域,浏览器通过nginx去访问跨域地址。
1)浏览器先访问http://192.168.101.10:8601 nginx提供的地址,进入页面
2)此页面要跨域访问http://192.168.101.11:8601 ,不能直接跨域访问http://www.baidu.com:8601 ,而是访问nginx的一个同源地址,比如:http://192.168.101.11:8601/api ,通过http://192.168.101.11:8601/api 的代理去访问http://www.baidu.com:8601 。
这样就实现了跨域访问。
浏览器到http://192.168.101.11:8601/api 没有跨域
nginx到http://www.baidu.com:8601 通过服务端通信,没有跨域。
我们准备使用方案2解决跨域问题。在内容管理的api工程config包下编写GlobalCorsConfig.java,
或直接从课程资料/项目工程下拷贝,
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.xuecheng.system.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter; @Configuration public class GlobalCorsConfig { @Bean public CorsFilter corsFilter () { CorsConfiguration config = new CorsConfiguration (); config.addAllowedOrigin("*" ); config.setAllowCredentials(true ); config.addAllowedHeader("*" ); config.addAllowedMethod("*" ); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); source.registerCorsConfiguration("/**" , config); return new CorsFilter (source); } }
此配置类实现了跨域过虑器,在响应头添加Access-Control-Allow-Origin。
重启系统管理服务,前端工程可以正常进入http://localhost:8601 ,观察浏览器记录,成功解决跨域。
课程分类查询 先来看看树状查询的sql语句例子:
1 2 3 4 5 6 7 select one.id one_id, one.label one_lable, two.id two_id, two.label two_lable from course_category one inner join course_category two on two.parentid = one.id where one.parentid = '1'
树状数据结构的样子就是数据结构里的树:parentid就是它的父节点
为什么选择在mysql中递归而不在java中递归:因为Java每递归一次就要连接一次数据库,性能不好。
还是根据接口文档给的json参数来写接口,不然传参都不知道传啥。
这是mapper的sql语句:
1 2 3 4 5 6 7 8 9 < select id= "selectTreeNodes" resultType= "com.xuecheng.content.model.dto.CourseCategoryTreeDto" parameterType= "string"> with recursive t1 as ( select * from course_category p where id= #{id} union all select t.* from course_category t inner join t1 on t1.id = t.parentid ) select * from t1 order by t1.id, t1.orderby < / select >
主要就是写写sql,这里比较难,其它的都是那三层架构。,然后是最难的service将查的数据整理并返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Service @Slf4j public class CourseCategoryServiceImpl implements CourseCategoryService { @Autowired CourseCategoryMapper courseCategoryMapper; @Override public List<CourseCategoryTreeDto> queryTreeNodes (String id) { List<CourseCategoryTreeDto> treeNodes = courseCategoryMapper.selectTreeNodes(id); Map<String, CourseCategoryTreeDto> mapTemp = treeNodes.stream().filter(item->!id.equals(item.getId())).collect(Collectors.toMap(key -> key.getId(), value -> value, (key1, key2) -> key2)); List<CourseCategoryTreeDto> categoryTreeDtos = new ArrayList <>(); treeNodes.stream().filter(item->!id.equals(item.getId())).forEach(item->{ if (item.getParentid().equals(id)){ categoryTreeDtos.add(item); } CourseCategoryTreeDto courseCategoryTreeDto = mapTemp.get(item.getParentid()); if (courseCategoryTreeDto!=null ){ if (courseCategoryTreeDto.getChildrenTreeNodes() ==null ){ courseCategoryTreeDto.setChildrenTreeNodes(new ArrayList <CourseCategoryTreeDto>()); } courseCategoryTreeDto.getChildrenTreeNodes().add(item); } }); return categoryTreeDtos; }
逻辑性比较强,大概流程就是遍历每个节点,如果有父节点则添加到父节点的list去,有一步的if是判断它的子节点是否为空,为空则创建一个该节点的孩子节点的list,因为默认childrenNodes为null,比如1-1走的流程就是先走过滤器,然后添加到1的子节点上,1-1-1则是看到它的父节点1-1的childrennodes为空先创建一个list,然后再添加到该list下。
新增课程 来看看service层的接口吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 @Override @Transactional public CourseBaseInfoDto createCourseBase (Long companyId,AddCourseDto dto) { if (StringUtils.isEmpty(dto.getName())) { throw new RuntimeException ("课程名称为空" ); } if (StringUtils.isEmpty(dto.getMt())) { throw new RuntimeException ("课程分类为空" ); } if (StringUtils.isEmpty(dto.getSt())) { throw new RuntimeException ("课程分类为空" ); } if (StringUtils.isEmpty(dto.getGrade())) { throw new RuntimeException ("课程等级为空" ); } if (StringUtils.isEmpty(dto.getTeachmode())) { throw new RuntimeException ("教育模式为空" ); } if (StringUtils.isEmpty(dto.getUsers())) { throw new RuntimeException ("适应人群为空" ); } if (StringUtils.isEmpty(dto.getCharge())) { throw new RuntimeException ("收费规则为空" ); } CourseBase insertCourseBase = new CourseBase (); BeanUtils.copyProperties(dto,insertCourseBase); insertCourseBase.setAuditStatus("202002" ); insertCourseBase.setStatus("203001" ); insertCourseBase.setCompanyId(companyId); insertCourseBase.setCreateDate(LocalDateTime.now()); int insert = courseBaseMapper.insert(insertCourseBase); if (insert<=0 ){ throw new RuntimeException ("新增课程基本信息失败" ); } CourseMarket courseMarket = new CourseMarket (); BeanUtils.copyProperties(dto,courseMarket); Long courseId = insertCourseBase.getId(); courseMarket.setId(courseId); saveCourseMarket(courseMarket); CourseBaseInfoDto courseBaseInfo = getCourseBaseInfo(courseId); return courseBaseInfo; } public int saveCourseMarket (CourseMarket courseMarket) { String charge = courseMarket.getCharge(); if (StringUtils.isEmpty(charge)){ throw new RuntimeException ("收费规则不能为空" ); } if ("201001" .equals(charge)) if (courseMarket.getPrice() == null || courseMarket.getPrice().floatValue() <= 0 ){ throw new RuntimeException ("收费课程价格为空" ); } Long id = courseMarket.getId(); CourseMarket courseMarket1 = courseMarketMapper.selectById(id); if (courseMarket1 == null ){ courseMarketMapper.insert(courseMarket); }else { BeanUtils.copyProperties(courseMarket,courseMarket1); courseMarket1.setId(courseMarket.getId()); courseMarketMapper.updateById(courseMarket1); } return 1 ; } public CourseBaseInfoDto getCourseBaseInfo (long courseId) { CourseBase courseBase = courseBaseMapper.selectById(courseId); if (courseBase==null ) return null ; CourseMarket courseMarket = courseMarketMapper.selectById(courseId); CourseBaseInfoDto courseBaseInfoDto = new CourseBaseInfoDto (); BeanUtils.copyProperties(courseBase,courseBaseInfoDto); if (courseMarket!=null ) BeanUtils.copyProperties(courseMarket,courseBaseInfoDto); CourseCategory courseCategoryMt = courseCategoryMapper.selectById(courseBaseInfoDto.getMt()); CourseCategory courseCategorySt = courseCategoryMapper.selectById(courseBaseInfoDto.getSt()); courseBaseInfoDto.setMtName(courseCategoryMt.getName()); courseBaseInfoDto.setStName(courseCategorySt.getName()); return courseBaseInfoDto; } }
步骤就是先排除有没有必填信息没填,然后再是添加默认信息,以及营销表的检验和插入,最后将插入的课程的基本信息和营销信息并返回,测试参数(记得设置运行环境~):
### 课程新增 POST /content/course Content-Type: application/json
{ “charge”: “201000”, “price”: 0, “originalPrice”:0, “qq”: “22333”, “wechat”: “223344”, “phone”: “13333333”, “validDays”: 365, “mt”: “1-1”, “st”: “1-1-1”, “name”: “斤斤计较急急急急急急急急急急急急急急急”, “pic”: “”, “teachmode”: “200002”, “users”: “初级人员”, “tags”: “”, “grade”: “204001”, “description”: “怕怕怕怕怕怕怕怕怕怕怕怕怕怕”, “objectives”: “” }
调试成功的截图:
异常处理 全局异常处理器
从 Spring 3.0 - Spring 3.2 版本之间,对 Spring 架构和 SpringMVC 的Controller 的异常捕获提供了相应的异常处理。
@ExceptionHandler
Spring3.0提供的标识在方法上或类上的注解,用来表明方法的处理异常类型。
@ControllerAdvice
Spring3.2提供的新注解,从名字上可以看出大体意思是控制器增强, 在项目中来增强SpringMVC中的Controller。通常和**@ExceptionHandler
** 结合使用,来处理SpringMVC的异常信息。
@ResponseStatus
Spring3.0提供的标识在方法上或类上的注解,用状态代码和应返回的原因标记方法或异常类。 调用处理程序方法时,状态代码将应用于HTTP响应。
通过上面的两个注解便可实现微服务端全局异常处理,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.xuecheng.base.exception; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; /** * @author woldier * @version 1.0 * @description 全局异常处理器 * @date 2023/3/6 15:18 **/ @ControllerAdvice @Slf4j public class GlobalExceptionHandler { /** * @description XueChengPlusException.class 异常处理,提取异常信息并且返回 * @param e * @return com.xuecheng.base.exception.RestErrorResponse * @author: woldier * @date: 2023/3/6 15:22 */ @ExceptionHandler(XueChengPlusException.class) @ResponseBody @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "业务模块抛出异常") public RestErrorResponse doXueChengPlusException(XueChengPlusException e){ return new RestErrorResponse(e.getErrMessage()); } /** * @description Exception 异常处理,此异常为未知异常 * @param e * @return com.xuecheng.base.exception.RestErrorResponse * @author: woldier * @date: 2023/3/6 15:27 */ @ExceptionHandler(Exception.class) @ResponseBody @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "服务器未知异常") public RestErrorResponse doXueChengPlusException(Exception e){ log.error(e.getMessage()); e.printStackTrace(); return new RestErrorResponse(CommonError.UNKOWN_ERROR.getErrMessage()); } }
修改课程 介绍下EditCourseDto这个Dto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Data @ApiModel(value = "EditCourseDto", description = "修改课程基本信息") public class EditCourseDto extends AddCourseDto { @NotNull(groups = {ValidationGroups.Update.class},message = "修改课程时id不能为空") @ApiModelProperty(value = "课程名称", required = true) private Long id; }
继承了AddCourseDto的所有信息并且加了个id属性,大概意思就是修改课程时它的属性跟添加课程时的属性一样不能为空,并且加了个修改课程的id。
service层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Override @Transactional public CourseBaseInfoDto updateCourseBase (Long companyId, EditCourseDto editCourseDto) throws XueChengPlusException { Long id = editCourseDto.getId(); CourseBase courseBase = courseBaseMapper.selectById(id); if (courseBase==null ){ XueChengPlusException.cast("课程不存在" ); } if (!courseBase.getCompanyId().equals(companyId)) XueChengPlusException.cast("companyID不一致" ); BeanUtils.copyProperties(editCourseDto,courseBase); courseBase.setChangeDate(LocalDateTime.now()); int i = courseBaseMapper.updateById(courseBase); if (i<=0 ){ XueChengPlusException.cast("更新失败" ); } return getCourseBaseInfo(editCourseDto.getId()); }
很好理解,就是检验companyId和一些属性后再修改。
课程计划 查询 对于录播课程,可以先看看播放列表,它有层级关系,第几章第几节啥的,还有对应视频或文档的信息。
可以运行下下面的sql查看对应关系就知道大概关系了:
select one.id one_id, one.pname one_pname, two.id two_id, two.pname two_pname, tm.media_fileName, tm.media_id from teachplan one inner join teachplan two on two.parentid=one.id left join teachplan_media tm on two.id = tm.teachplan_id
where one.parentid=0 and one.course_id=117 order by one.id, two.id;
就Dao层比较麻烦要一一写,其它的都不难:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <resultMap id ="treeNodeResultMap" type ="com.xuecheng.content.model.dto.TeachplanDto" > <id column ="one_id" property ="id" /> <result column ="one_pname" property ="pname" /> <result column ="one_parentid" property ="parentid" /> <result column ="one_grade" property ="grade" /> <result column ="one_mediaType" property ="mediaType" /> <result column ="one_stratTime" property ="stratTime" /> <result column ="one_endTime" property ="endTime" /> <result column ="one_orderby" property ="orderby" /> <result column ="one_courseId" property ="courseId" /> <result column ="one_coursePubId" property ="coursePubId" /> <collection property ="teachPlanTreeNodes" ofType ="com.xuecheng.content.model.dto.TeachplanDto" > <id column ="two_id" property ="id" /> <result column ="two_pname" property ="pname" /> <result column ="two_parentid" property ="parentid" /> <result column ="two_grade" property ="grade" /> <result column ="two_mediaType" property ="mediaType" /> <result column ="two_stratTime" property ="stratTime" /> <result column ="two_endTime" property ="endTime" /> <result column ="two_orderby" property ="orderby" /> <result column ="two_courseId" property ="courseId" /> <result column ="two_coursePubId" property ="coursePubId" /> <association property ="teachplanMedia" javaType ="com.xuecheng.content.model.po.TeachplanMedia" > <result column ="teachplanMeidaId" property ="id" /> <result column ="mediaFilename" property ="mediaFilename" /> <result column ="mediaId" property ="mediaId" /> <result column ="two_id" property ="teachplanId" /> <result column ="two_courseId" property ="courseId" /> <result column ="two_coursePubId" property ="coursePubId" /> </association > </collection > </resultMap > <select id ="selectTreeNodes" resultMap ="treeNodeResultMap" parameterType ="long" > SELECT one.id one_id, one.pname one_pname, one.parentid one_parentid, one.grade one_grade, one.media_type one_mediaType, one.start_time one_startTime, one.end_time one_endTime, one.orderby one_orderby, one.course_id one_courseId, one.course_pub_id one_coursePubId, two.id two_id, two.pname two_pname, two.parentid two_parentid, two.grade two_grade, two.media_type two_mediaType, two.start_time two_stratTime, two.end_time two_endTime, two.orderby two_orderby, two.course_id two_courseId, two.course_pub_id two_coursePubId, m1.media_fileName mediaFilename, m1.id teachplanMeidaId, m1.media_id mediaId FROM teachplan one LEFT JOIN teachplan two ON one.id = two.parentid LEFT JOIN teachplan_media m1 on two.id = m1.teachplan_id WHERE one.grade="1" AND one.course_id = #{courseId} ORDER BY one.orderby, two.orderby </select >
新增/修改 补充下关于LambdaQueryWrapper的用法:MyBatis-Plus LambdaQueryWrapper使用说明_mybatisplus lambdaquerywrapper-CSDN博客
数据模型
1、新增第一级课程计划
名称默认为:新章名称 [点击修改]
grade:1
orderby: 所属课程中同级别下排在最后
2、新增第二级课程计划
名称默认为:新小节名称 [点击修改]
grade:2
orderby: 所属课程计划中排在最后
3、修改第一级、第二级课程计划的名称,修改第二级课程计划是否免费
service:自己看吧何平安懒得写了,有注释的看得懂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Override @Transactional public void saveOrUpdateTeachPlan (SaveTeachplanDto dto) { Long teachplanId = dto.getId(); if (teachplanId == null ){ TeachplanDto teachplanDto = new TeachplanDto (); BeanUtils.copyProperties(dto,teachplanDto); Long parentid = dto.getParentid(); Long courseid = dto.getCourseId(); LambdaQueryWrapper<Teachplan> teachplanLambdaQueryWrapper = new LambdaQueryWrapper <>(); LambdaQueryWrapper<Teachplan> eq = teachplanLambdaQueryWrapper.eq(Teachplan::getCourseId, courseid).eq(Teachplan::getParentid, parentid); Integer count = teachplanMapper.selectCount(eq); teachplanDto.setOrderby(count+1 ); teachplanMapper.insert(teachplanDto); }else { Teachplan teachplan = teachplanMapper.selectById(teachplanId); BeanUtils.copyProperties(dto,teachplan); teachplanMapper.updateById(teachplan); } }
删除课程计划 service流程:
1 2 3 4 5 * 1.查询数据库中课程计划 * 查看是否存在不存在抛出异常 * 2.获取课程计划的等级 * 若为章则递归删除其子节点 * 若为节,直接删除,查询是否有媒体信息,有则删除
service总代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 Teachplan teachplan = teachplanMapper.selectById(id); if (teachplan == null ){ XueChengPlusException.cast("课程不存在" ); } Integer grade = teachplan.getGrade(); if (grade == 2 ){ this .deleteGrade2(id); }else { List<TeachplanDto> teachplanDtos =teachplanMapper.selectTreeNodes(id); List<TeachplanDto> teachPlanTreeNodes = teachplanDtos.stream().filter(e -> e.getId().equals(teachplan.getId())).collect(Collectors.toList()).get(0 ).getTeachPlanTreeNodes(); teachplanMapper.deleteById(id); teachPlanTreeNodes.forEach(e->this .deleteGrade2(e.getId())); }
上下移动课程计划 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Override @Transactional public void move (Long id, Boolean moveWay) throws XueChengPlusException { Teachplan teachplan = teachplanMapper.selectById(id); if (teachplan == null ) XueChengPlusException.cast("课程计划不存在" ); Long courseId = teachplan.getCourseId(); Integer grade = teachplan.getGrade(); Integer orderby = teachplan.getOrderby(); LambdaQueryWrapper<Teachplan> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper .eq(Teachplan::getCourseId, courseId) .eq(Teachplan::getGrade, grade); if (moveWay) lambdaQueryWrapper.orderByDesc(Teachplan::getOrderby); else lambdaQueryWrapper.orderByAsc(Teachplan::getOrderby); List<Teachplan> teachplanList = teachplanMapper.selectList(lambdaQueryWrapper); List<Teachplan> teachplanList1 = teachplanList.stream().filter(e -> moveWay ? e.getOrderby() > orderby : e.getOrderby() < orderby).collect(Collectors.toList()); if (teachplanList1.isEmpty()) XueChengPlusException.cast("无法完成该操作" ); Teachplan teachplan1 = teachplanList1.get(teachplanList1.size()-1 ); teachplan.setOrderby(teachplan1.getOrderby()); teachplan1.setOrderby(orderby); teachplanMapper.updateById(teachplan); teachplanMapper.updateById(teachplan1); }
教师管理 进入教师设置页面后可以点击修改教师信息,或者点击新增教师信息
这两个业务共用一个接口,通过查看是否传入id来进行区分
课程教师信息删除
service层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 package com.xuecheng.content.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.xuecheng.base.exception.XueChengPlusException;import com.xuecheng.content.mapper.CourseBaseMapper;import com.xuecheng.content.mapper.CourseTeacherMapper;import com.xuecheng.content.model.dto.TeacherSaveOrUpdateDto;import com.xuecheng.content.model.po.CourseTeacher;import com.xuecheng.content.service.CourseTeacherService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;import java.util.List;@Service @Slf4j public class CourseTeacherServiceImpl extends ServiceImpl <CourseTeacherMapper, CourseTeacher> implements CourseTeacherService { @Autowired CourseBaseMapper courseBaseMapper; @Override public List<CourseTeacher> listTeacherByCourseId (Long courseId) throws XueChengPlusException { if (courseId == null || courseId<0 ){ XueChengPlusException.cast("课程id不存在" ); } LambdaQueryWrapper<CourseTeacher> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(CourseTeacher::getCourseId,courseId); List<CourseTeacher> courseTeacherList = this .list(lambdaQueryWrapper); if (courseTeacherList.isEmpty()) return null ; return courseTeacherList; } @Override @Transactional public void saveOrUpdateTeacher (TeacherSaveOrUpdateDto dto) throws XueChengPlusException { Long id = dto.getId(); Long courseId = dto.getCourseId(); CourseTeacher courseTeacher = new CourseTeacher (); BeanUtils.copyProperties(dto,courseTeacher); if (id != null ){ LambdaQueryWrapper<CourseTeacher> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(CourseTeacher::getCourseId,courseId); CourseTeacher one = this .getOne(lambdaQueryWrapper); if (one == null ){ XueChengPlusException.cast("教师不存在" ); } this .updateById(courseTeacher); }else { if (courseBaseMapper.selectById(courseId)==null ) XueChengPlusException.cast("课程不存在" ); courseTeacher.setCreateDate(LocalDateTime.now()); if (!this .save(courseTeacher)) XueChengPlusException.cast("新增失败" ); } } @Override @Transactional public void deleteTeacher (Long courseId, Long id) throws XueChengPlusException { if (courseId == null || id == null ) XueChengPlusException.cast("id或者课程id为空" ); if (courseBaseMapper.selectById(id) == null ) XueChengPlusException.cast("课程id不合法" ); LambdaQueryWrapper<CourseTeacher> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(CourseTeacher::getCourseId,courseId); lambdaQueryWrapper.eq(CourseTeacher::getId,id); CourseTeacher one = this .getOne(lambdaQueryWrapper); if (one == null ) XueChengPlusException.cast("教师不存在" ); this .removeById(id); } }
删除课程 在课程管理界面,点击删除按钮删除课程
service层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @Override @Transactional public void deleteCourseById (Long id) throws XueChengPlusException { CourseBase courseBase = courseBaseMapper.selectById(id); CourseMarket courseMarket = new CourseMarket (); BeanUtils.copyProperties(courseBase, courseMarket); if (courseBase == null ){ XueChengPlusException.cast("id不合法" ); } List<CourseTeacher> list = courseTeacherService.listTeacherByCourseId(id); if (list != null &&!list.isEmpty()){ list.forEach(e-> { try { courseTeacherService.deleteTeacher(id, e.getId()); } catch (XueChengPlusException ex) { throw new RuntimeException (ex); } }); } List<TeachplanDto> teachplanList = teachPlanService.findTeachplanList(id); if (teachplanList != null && !teachplanList.isEmpty()){ teachplanList.forEach(e->{ List<TeachplanDto> teachPlanTreeNodes = e.getTeachPlanTreeNodes(); if (teachPlanTreeNodes != null && !teachPlanTreeNodes.isEmpty()){ teachPlanTreeNodes.forEach(kid->teachPlanService.deleteGrade2(kid.getId())); } try { teachPlanService.deleteTeachPlan(e.getId()); } catch (XueChengPlusException ex) { throw new RuntimeException (ex); } }); } LambdaQueryWrapper<CourseMarket> courseMarketLambdaQueryWrapper=new LambdaQueryWrapper <>(); courseMarketLambdaQueryWrapper.eq(CourseMarket::getId,id); if ("201001" .equals(courseMarket.getCharge())){ courseMarketMapper.deleteById(id); } courseBaseMapper.deleteById(id); }
Nacos远程服务器搭建 先拉取1.4.1的镜像并启动:
docker pull nacos/nacos-server:1.4.1
docker run –env MODE=standalone –name nacos -d -p 8848:8848 nacos/nacos-server:1.4.1
进入nacos配置文件:
docker exec -it nacos bash
配置MySQL:
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://xxxxxxxx:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456