• 谷粒学院——后台管理系统功能模块


    普通用户前台使用系统

    https://blog.csdn.net/weixin_45581692/article/details/127317141

    管理员后台管理系统

    在这里插入图片描述

    讲师管理模块

    环境搭建

    1. 创建数据库表:讲师表

    CREATE TABLE `edu_teacher` (
      `id` char(19) NOT NULL COMMENT '讲师ID',
      `name` varchar(20) NOT NULL COMMENT '讲师姓名',
      `intro` varchar(500) NOT NULL DEFAULT '' COMMENT '讲师简介',
      `career` varchar(500) DEFAULT NULL COMMENT '讲师资历,一句话说明讲师',
      `level` int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师',
      `avatar` varchar(255) DEFAULT NULL COMMENT '讲师头像',
      `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_name` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='讲师';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. 创建项目,引入依赖
    在这里插入图片描述
    3. 编写配置文件(application.properties)

    # 服务端口号
    server.port=8001
    
    # 服务名
    spring.application.name=service-edu
    
    # 环境设置:dev、test、prod
    spring.profiles.active=dev
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guliedu?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=qwer`123
    
    # mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4. 代码生成器生成代码
    相关依赖

    <dependency>
        <groupId>org.apache.velocitygroupId>
        <artifactId>velocity-engine-coreartifactId>
        <version>2.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class CodeGenerator {
        @Test
        public void run() {
            // 1、创建代码生成器
            AutoGenerator mpg = new AutoGenerator();
            // 2、全局配置
            GlobalConfig gc = new GlobalConfig();
            String projectPath = System.getProperty("user.dir");
            //(改)绝对路径
            gc.setOutputDir("D:\\my_items2\\guli_parent\\service\\service_edu" + "/src/main/java");
            gc.setAuthor("hxp");
            gc.setOpen(false); //生成后是否打开资源管理器
            gc.setFileOverride(false); //重新生成时文件是否覆盖
            gc.setServiceName("%sService");	//去掉Service接口的首字母I
            gc.setIdType(IdType.ID_WORKER_STR); //主键策略(id是数字类型改成ID_WORKER,字符串类型改成ID_WORKER_STR)
            gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
            gc.setSwagger2(true);//开启Swagger2模式
    
            mpg.setGlobalConfig(gc);
    
            // 3、数据源配置(改)
            DataSourceConfig dsc = new DataSourceConfig();
            dsc.setUrl("jdbc:mysql://localhost:3306/guliedu?serverTimezone=GMT%2B8");
            dsc.setDriverName("com.mysql.cj.jdbc.Driver");
            dsc.setUsername("root");
            dsc.setPassword("qwer`123");
            dsc.setDbType(DbType.MYSQL); //数据库类型
            mpg.setDataSource(dsc);
    
            // 4、包配置
            PackageConfig pc = new PackageConfig();
            //com.hxp.eduservice
            pc.setParent("com.hxp");
            pc.setModuleName("eduservice"); //模块名(改)
            pc.setController("controller");
            pc.setEntity("entity");
            pc.setService("service");
            pc.setMapper("mapper");
            mpg.setPackageInfo(pc);
    
            // 5、策略配置
            StrategyConfig strategy = new StrategyConfig();
            strategy.setInclude("edu_course", "edu_chapter","edu_course_description","edu_video"); //(改)数据库表名,可以一次生成多个
            strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
            strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
    
            strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
            strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
    
            strategy.setRestControllerStyle(true); //restful api风格控制器
            strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
    
            mpg.setStrategy(strategy);
    
            // 6、执行
            mpg.execute();
        }
    }
    
    • 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

    讲师列表查询

    (1)编写接口
    controller

    @RestController
    @RequestMapping("/eduservice/teacher")
    public class EduTeacherController {
    
        @Autowired
        private EduTeacherService teacherService;
    
        //查询讲师表所有数据
        @ApiOperation(value = "所有讲师列表") //作用在方法上
        @GetMapping("findAll")
        public R findAllTeacher() {
            return R.ok().data("items", teacherService.list(null));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    service

    public interface EduTeacherService extends IService<EduTeacher> {
    }
    
    • 1
    • 2

    (2)创建config包,包下创建配置类
    @MapperScan可以放在主启动类上,但建议统一放在配置类中。

    @Configuration
    @MapperScan("com.hxp.eduservice.mapper") //扫描到mapper接口
    public class EduConfig {
    }
    
    • 1
    • 2
    • 3
    • 4

    细节处理
    根据上面的代码查询出来的时间显示如下:
    在这里插入图片描述
    默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间相差8小时,解决:在application.properties配置文件中加上配置。

    # 返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    
    • 1
    • 2
    • 3

    讲师删除(逻辑删除

    (1)在配置类加上一段配置

    /**
     * 逻辑删除插件
     */
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)在实体类的逻辑删除属性上面添加注解

    @TableLogic
    private Boolean isDeleted;
    
    • 1
    • 2

    (3)编写controller层代码

    //逻辑删除讲师
    @DeleteMapping("{id}")
    public R removeTeacher(@PathVariable String id) { //获取路径中输入的id
        boolean flag = teacherService.removeById(id);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    整合swagger

    在父工程下创建一个子模块common,作为公共模块,在common模块下创建一个子模块
    在这里插入图片描述
    加上相关依赖

    
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger2artifactId>
        <scope>provided scope>
    dependency>
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger-uiartifactId>
        <scope>provided scope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    @Configuration //配置类
    @EnableSwagger2	//swagger注解
    public class SwaggerConfig {
        @Bean
        public Docket webApiConfig(){
            return new Docket(DocumentationType.SWAGGER_2)
                    .groupName("webApi")
                    .apiInfo(webApiInfo())
                    .select()
    //                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                    .paths(Predicates.not(PathSelectors.regex("/error.*")))
                    .build();
    
        }
    
        private ApiInfo webApiInfo(){
            return new ApiInfoBuilder()
                    .title("网站-课程中心API文档")
                    .description("本文档描述了课程中心微服务接口定义")
                    .version("1.0")
                    .contact(new Contact("java", "http://atguigu.com", "1123@qq.com"))
                    .build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在service模块中引入公共模块的依赖

    <dependency>
       <groupId>com.hxpgroupId>
        <artifactId>service_baseartifactId>
        <version>0.0.1-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为了让service模块能扫描到Swagger,需要在主启动类上加@ComponentScan注解

    @SpringBootApplication
    @ComponentScan(basePackages = {"com.hxp"}) //为了能扫描到其他模块
    public class EduApplication {
        public static void main(String[] args) {
            SpringApplication.run(EduApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    统一结果数据返回

    项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一,使前端对数据的操作更一致、轻松。
    在这里插入图片描述
    在common模块下创建子模块common-utils
    在这里插入图片描述
    (1)创建interface,定义数据返回状态码

    public interface ResultCode {
        public static Integer SUCCESS = 20000; //成功
        public static Integer ERROR = 20001; //失败
    }
    
    • 1
    • 2
    • 3
    • 4

    (2)定义返回格式

    @Data
    public class R {
        @ApiModelProperty(value = "是否成功")
        private Boolean success;
    
        @ApiModelProperty(value = "返回码")
        private Integer code;
    
        @ApiModelProperty(value = "返回消息")
        private String message;
    
        @ApiModelProperty(value = "返回数据")
        private Map<String, Object> data = new HashMap<String, Object>();
    
        //将构造方法私有,外部不能随便new
        private R() {}
    
        //成功的静态方法
        public static R ok() {
            R r = new R();
            r.setSuccess(true);
            r.setCode(ResultCode.SUCCESS);
            r.setMessage("成功");
            return r;
        }
    
        //失败的静态方法
        public static R error() {
            R r = new R();
            r.setSuccess(false);
            r.setCode(ResultCode.ERROR);
            r.setMessage("失败");
            return r;
        }
    
        public R success(Boolean success) {
            this.setSuccess(success);
            return this;
        }
    
        public R message(String message) {
            this.setMessage(message);
            return this;
        }
    
        public R code(Integer code) {
            this.setCode(code);
            return this;
        }
    
        public R data(String key, Object value) {
            this.data.put(key, value);
            return this;
        }
    
        public R data(Map<String,Object> map) {
            this.setData(map);
            return this;
        }
    }
    
    • 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

    (3)在service中引入依赖

    <dependency>
        <groupId>com.hxpgroupId>
        <artifactId>common_utilsartifactId>
        <version>0.0.1-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    分页查询讲师

    //分页查询讲师的方法
    @GetMapping("pageTeacher/{current}/{limit}")
    public R pageListTeacher(@PathVariable long current, @PathVariable long limit) {
        Page<EduTeacher> pageTeacher = new Page<>(current, limit);
        //把分页所有数据封装到pageTeacher对象里面
        teacherService.page(pageTeacher, null);
    
        long total = pageTeacher.getTotal(); //总记录数
        List<EduTeacher> records = pageTeacher.getRecords(); //数据list集合
        return R.ok().data("total",total).data("records", records);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    多条件查询带分页
    在这里插入图片描述
    (1)把条件封装到对象(VO对象)里面,把对象传递到接口中
    在这里插入图片描述

    @Data
    public class TeacherQuery {
        @ApiModelProperty(value = "教师名称,模糊查询")
        private String name;
    
        @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
        private Integer level;
    
        @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
        private String begin; //注意,这里使用的是String类型,前端传过来的数据不需要进行类型转换
    
        @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
        private String end;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (2)编写接口,根据条件值进行判断,拼接条件
    注意:参数加了@RequestBody,不能用GET方式提交数据

    //条件查询带分页
    @PostMapping("pageTeacherCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit,
                                  @RequestBody(required = false) TeacherQuery teacherQuery) {
        Page<EduTeacher> pageTeacher = new Page<>(current,limit);
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        //多条件组合查询
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();
        //判断条件值是否为空,如果不为空拼接条件
        if (!StringUtils.isEmpty(name)) {
            wrapper.like("name", name);
        }
        if (!StringUtils.isEmpty(level)) {
            wrapper.eq("level", level);
        }
        if (!StringUtils.isEmpty(begin)) {
            wrapper.ge("gmt_create", begin); //大于等于
        }
        if (!StringUtils.isEmpty(end)) {
            wrapper.le("gmt_create", end); //小于等于
        }
    
        //排序
        wrapper.orderByDesc("gmt_create");
    
        //调用方法实现条件查询分页
        teacherService.page(pageTeacher, wrapper);
        long total = pageTeacher.getTotal();
        List<EduTeacher> records = pageTeacher.getRecords();
        return R.ok().data("total", total).data("records", records);
    }
    
    • 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

    新增讲师

    (1)在实体类的属性上加上注解

    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)创建自动填充类

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
            this.setFieldValByName("gmtCreate", new Date(),metaObject);
            this.setFieldValByName("gmtModified", new Date(), metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            this.setFieldValByName("gmtModified", new Date(), metaObject);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (3)编写controller

    //添加讲师
    @PostMapping("addTeacher")
    public R addTeacher(@RequestBody EduTeacher eduTeacher) {
        boolean save = teacherService.save(eduTeacher);
        if (save) {
            return R.ok();
        } else {
            return R.error();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    统一异常处理

    (1)全局异常处理
    在公共模块service-base中创建统一异常处理器

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        //指定出现什么异常执行这个方法
        @ExceptionHandler(Exception.class)
        @ResponseBody  //为了能返回数据
        public R error(Exception e) {
            e.printStackTrace();
            return R.error().message("执行了全局异常处理...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)特殊异常处理

    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody  //为了能返回数据
    public R error(ArithmeticException e) {
        e.printStackTrace();
        return R.error().message("执行了ArithmeticException异常处理...");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (3)自定义异常处理
    创建自定义异常类继承RuntimeException

    @Data
    @AllArgsConstructor //有参构造
    @NoArgsConstructor  //无参构造
    public class GuliException extends RuntimeException{
        private Integer code;
        private String msg;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在统一异常类添加规则

    @ControllerAdvice
    public class GlobalExceptionHandler {
        //自定义异常
        @ExceptionHandler(GuliException.class)
        @ResponseBody
        public R error(GuliException e) {
            e.printStackTrace();
            return R.error().code(e.getCode()).message(e.getMsg());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用:在可能发送异常的地方加上 try-catch

    try{
    	int a = 1/0;
    } catch (Exception e) {
    	throw new GuliException(20001, "执行了自定义异常处理...");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    日志

    日志记录器(Logger)的行为是分等级的。
    分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。
    默认情况下,springboot从控制台打印出来的日志级别只有INFO及以上级别,可以设置日志级别。

    # 设置日志级别
    logging.level.root=WARN
    
    • 1
    • 2

    把日志不仅输出到控制台,也可以输出到文件中,使用日志工具:Logback日志工具
    第一步,删除application.properties日志配置,不然会有冲突
    第二步,在resource下创建logback-spring.xml

    
    <configuration  scan="true" scanPeriod="10 seconds">
        
        
        
        
    
        <contextName>logbackcontextName>
        
        <property name="log.path" value="D:/guli_1010/edu" />
    
        
        
        
        
        
        
        
        <property name="CONSOLE_LOG_PATTERN"
                  value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
    
        
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            
            
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFOlevel>
            filter>
            <encoder>
                <Pattern>${CONSOLE_LOG_PATTERN}Pattern>
                
                <charset>UTF-8charset>
            encoder>
        appender>
    
        
    
        
        <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            
            <file>${log.path}/log_info.logfile>
            
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
                <charset>UTF-8charset>
            encoder>
            
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                
                <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.logfileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MBmaxFileSize>
                timeBasedFileNamingAndTriggeringPolicy>
                
                <maxHistory>15maxHistory>
            rollingPolicy>
            
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>INFOlevel>
                <onMatch>ACCEPTonMatch>
                <onMismatch>DENYonMismatch>
            filter>
        appender>
    
        
        <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            
            <file>${log.path}/log_warn.logfile>
            
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
                <charset>UTF-8charset> 
            encoder>
            
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.logfileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MBmaxFileSize>
                timeBasedFileNamingAndTriggeringPolicy>
                
                <maxHistory>15maxHistory>
            rollingPolicy>
            
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>warnlevel>
                <onMatch>ACCEPTonMatch>
                <onMismatch>DENYonMismatch>
            filter>
        appender>
    
        
        <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            
            <file>${log.path}/log_error.logfile>
            
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
                <charset>UTF-8charset> 
            encoder>
            
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MBmaxFileSize>
                timeBasedFileNamingAndTriggeringPolicy>
                
                <maxHistory>15maxHistory>
            rollingPolicy>
            
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERRORlevel>
                <onMatch>ACCEPTonMatch>
                <onMismatch>DENYonMismatch>
            filter>
        appender>
    
        
        
        
        <springProfile name="dev">
            
            <logger name="com.guli" level="INFO" />
    
            
            <root level="INFO">
                <appender-ref ref="CONSOLE" />
                <appender-ref ref="INFO_FILE" />
                <appender-ref ref="WARN_FILE" />
                <appender-ref ref="ERROR_FILE" />
            root>
        springProfile>
    
        
        <springProfile name="pro">
    
            <root level="INFO">
                <appender-ref ref="CONSOLE" />
                <appender-ref ref="DEBUG_FILE" />
                <appender-ref ref="INFO_FILE" />
                <appender-ref ref="ERROR_FILE" />
                <appender-ref ref="WARN_FILE" />
            root>
        springProfile>
    
    configuration>
    
    • 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
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160

    将错误日志输出到文件
    在GlobalExceptionHandler.java中,添加注解@Slf4j。
    在这里插入图片描述

    异常输出语句:log.error(e.getMessage());
    在这里插入图片描述

    登录功能

    进行登录调用两个方法,login登录操作方法和info登录之后获取用户信息的方法。

    @RestController
    @RequestMapping("/eduservice/user")
    public class EduLoginController {
        //登录
        @PostMapping("login")
        public R login() {
            return R.ok().data("token","admin");
        }
    
        //用户信息
        @GetMapping("info")
        public R info() {
            return R.ok().data("roles","[admin]").data("name","admin").data("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可能产生跨域问题:通过一个地址去访问另外一个地址,这个过程中如果 “协议 ip 端口” 这三个有任何一个不一样,就会产生跨域问题。
    在这里插入图片描述
    解决:
    (1)在后端接口controller添加注解:@CrossOrigin
    (2)使用网关解决

    上传讲师头像

    (1)新建一个子模块
    在这里插入图片描述
    (2)引入依赖

    
    <dependency>
        <groupId>com.aliyun.ossgroupId>
        <artifactId>aliyun-sdk-ossartifactId>
    dependency>
    
    <dependency>
        <groupId>joda-timegroupId>
        <artifactId>joda-timeartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (3)创建配置文件

    server.port=8002
    
    spring.application.name=service-oss
    
    spring.profiles.active=dev
    
    #阿里云OSS
    aliyun.oss.file.endpoint=oss-cn-hangzhou.aliyuncs.com
    aliyun.oss.file.keyid=LTAI5tEz9ABsH5qFZsxXg5K2
    aliyun.oss.file.keysecret=MHJngccYekVjxzp0YtKQU4HTJsr5Ty
    #bucket可以在控制台创建,也可以使用java创建
    aliyun.oss.file.bucketname=hxp-bucket
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (4)遇到的问题:创建了启动类,启动报错了。
    原因:依赖中引入了数据库相关的依赖,启动时会去找数据库配置,但这个模块不需要数据库也没有配置数据库,只是做上传到oss功能。
    解决:1、添加上数据库配置;2、在启动类添加属性,默认不去加载数据库配置。
    在这里插入图片描述
    (5)常量类编写,让其他类能获取到OSS相关的常量。

    //当项目已启动,spring加载之后,执行接口一个方法
    @Component
    public class ConstantPropertiesUtils implements InitializingBean {
    
        @Value("${aliyun.oss.file.endpoint}")
        private String endpoint;
    
        @Value("${aliyun.oss.file.keyid}")
        private String keyId;
    
        @Value("${aliyun.oss.file.keysecret}")
        private String keySecret;
    
        @Value("${aliyun.oss.file.bucketname}")
        private String bucketName;
    
        //定义公开静态常量
        public static String END_POINT;
        public static String ACCESS_KEY_ID;
        public static String ACCESS_KEY_SECRET;
        public static String BUCKET_NAME;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            END_POINT = endpoint;
            ACCESS_KEY_ID = keyId;
            ACCESS_KEY_SECRET = keySecret;
            BUCKET_NAME = bucketName;
        }
    }
    
    • 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

    (6)编写接口

    @RestController
    @RequestMapping("/eduoss/fileoss")
    public class OssController {
    
        @Autowired
        private OssService ossService;
    
        @PostMapping
        public R uploadOssFile(MultipartFile file) {
            //MultipartFile 获取上传文件
            //返回上传到oss的路径
            String url = ossService.uploadFileAvatar(file);
            return R.ok().data("url",url);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    @Service
    public class OssServiceImpl implements OssService {
    
        //上传头像到oss
        @Override
        public String uploadFileAvatar(MultipartFile file) {
            //工具类获取值
            String endpoint = ConstantPropertiesUtils.END_POINT;
            String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
            String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
            String bucketName = ConstantPropertiesUtils.BUCKET_NAME;
    
            try {
                //创建OSS实例
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
                //获取上传文件输入流
                InputStream inputStream = file.getInputStream();
                //获取文件名称
                String fileName = file.getOriginalFilename();
                //1. 在文件名称中添加随机唯一的值
                String uuid = UUID.randomUUID().toString().replaceAll("-", "");
                fileName = uuid + fileName;
                //2. 把文件按照日期进行分类,获取当前日期
                String datePath = new DateTime().toString("yyyy/MM/dd");
                fileName = datePath + "/" + fileName;
    
                //调用oss方法实现上传
                //第一个参数 Bucket名称
                //第二个参数 上传到oss文件的文件名
                //第三个参数 上传文件输入流
                ossClient.putObject(bucketName, fileName, inputStream);
    
                //关闭OSSClient
                ossClient.shutdown();
    
                //把上传之后文件路径返回
                //需要把上传到阿里云oss路径手动拼接出来
                String url = "https://"+bucketName+"."+endpoint+"/"+fileName;
                return url;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    • 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

    课程分类管理模块

    环境搭建

    1. 创建数据库表

    CREATE TABLE `edu_subject` (
      `id` char(19) NOT NULL COMMENT '课程类别ID',
      `title` varchar(10) NOT NULL COMMENT '类别名称',
      `parent_id` char(19) NOT NULL DEFAULT '0' COMMENT '父ID',
      `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_parent_id` (`parent_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程科目';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 引入easyExcel的依赖

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>2.1.1version>
    dependency>
    
    <dependency>
        <groupId>org.apache.poigroupId>
        <artifactId>poiartifactId>
        <version>3.1.7version>
    dependency>
    
    <dependency>
        <groupId>org.apache.poigroupId>
        <artifactId>poi-ooxmlartifactId>
        <version>3.1.7version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3. 用代码生成器生成代码

    添加课程分类

    在这里插入图片描述

    1. 创建excel实体类

    @Data
    public class SubjectData {
        @ExcelProperty(index = 0)
        private String oneSubjectName;
        @ExcelProperty(index = 1)
        private String twoSubjectName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2. 编写excel的监听器
    注意:监听器无法交给spring管理,所以不能用@Autowired注入对象,要用构造方法传递对象进行使用。

    public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
    
        //因为SubjectExcelListener不能交给Spring进行管理,需要自己new,不能注入其他对象,不能实现数据库操作
        //使用构造器注入
        public EduSubjectService subjectService;
    
        public SubjectExcelListener() {
        }
        public SubjectExcelListener(EduSubjectService subjectService) {
            this.subjectService = subjectService;
        }
    
        @Override
        public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
            if (subjectData == null) {
                throw new GuliException(20001, "文件数据为空");
            }
            //一行一行读取,每次读取有两个值,第一个值一级分类,第二个值二级分类
            EduSubject existOneSubject = this.existOneSubject(subjectService,subjectData.getOneSubjectName());
            if (existOneSubject == null) {  //没有相同一级分类,进行添加
                existOneSubject = new EduSubject();
                existOneSubject.setParentId("0");
                existOneSubject.setTitle(subjectData.getOneSubjectName()); //一级分类名称
                subjectService.save(existOneSubject);
            }
    
            //获取一级分类id值
            String pid = existOneSubject.getId();
            //添加二级分类
            //判断二级分类是否重复
            EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(),pid);
            if (existTwoSubject == null) {
                existTwoSubject = new EduSubject();
                existTwoSubject.setParentId(pid);
                existTwoSubject.setTitle(subjectData.getTwoSubjectName()); //二级分类名称
                subjectService.save(existTwoSubject);
            }
        }
    
        //判断一级分类不能重复添加
        private EduSubject existOneSubject(EduSubjectService subjectService, String name) {
            QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
            wrapper.eq("title", name);
            wrapper.eq("parent_id","0");    //一级分类的parent_id为0
            EduSubject oneSubject = subjectService.getOne(wrapper);
            return oneSubject;
        }
        //判断二级分类不能重复添加
        private EduSubject existTwoSubject(EduSubjectService subjectService, String name, String pid) {
            QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
            wrapper.eq("title", name);
            wrapper.eq("parent_id",pid);    //一级分类的parent_id为0
            EduSubject twoSubject = subjectService.getOne(wrapper);
            return twoSubject;
        }
    
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    
        }
    }
    
    • 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

    3. 编写接口

    @RestController
    @RequestMapping("/eduservice/subject")
    public class EduSubjectController {
    
        @Autowired
        private EduSubjectService subjectService;
    
        //添加课程分类
        @PostMapping("addSubject")
        public R addSubject(MultipartFile file) {
            subjectService.saveSubject(file,subjectService);
            return R.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    @Service
    public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
        //添加课程分类
        @Override
        public void saveSubject(MultipartFile file,EduSubjectService subjectService) {
            try {
                //文件输入流
                InputStream in = file.getInputStream();
                //调用方法进行读取
                EasyExcel.read(in, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
            } catch (Exception e) {
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    课程分类显示

    在这里插入图片描述
    1. 针对返回数据创建对应的实体类(一级分类和二级分类),给两个实体类建立关系

    @Data
    public class OneSubject {
        private String id;
        private String title;
        private List<TwoSubject> children = new ArrayList<>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Data
    public class TwoSubject {
        private String id;
        private String title;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. 编写接口

    //课程分类列表展示(树形)
    @GetMapping("getAllSubject")
    public R getAllSubject() {
        List<OneSubject> list = subjectService.getAllOneTwoSubject();
        return R.ok().data("list",list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //课程分类列表(树形)
    @Override
    public List<OneSubject> getAllOneTwoSubject() {
        //1 查询所有一级分类 parentId=0
        QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
        wrapperOne.eq("parent_id", "0");
        List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
    
        //2 查询所有二级分类 parentId != 0
        QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
        wrapperTwo.ne("parent_id", "0"); //不等于
        List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
    
        //创建list集合,用于存储最终封装的数据
        List<OneSubject> finalSubjectList = new ArrayList<>();
    
        //3 封装一级分类
        for (int i = 0; i < oneSubjectList.size(); i++) {
            //得到oneSubjectList的每个eduSubject对象
            EduSubject eduSubject = oneSubjectList.get(i);
            //把eduSubject里面值获取出来,放到OneSubject对象里面
            OneSubject oneSubject = new OneSubject();
    //            oneSubject.setId(eduSubject.getId());
    //            oneSubject.setTitle(eduSubject.getTitle());
            BeanUtils.copyProperties(eduSubject,oneSubject); //用BeanUtils替换上面两句
            //多个OneSubject放到finalSubjectList里面
            finalSubjectList.add(oneSubject);
    
            //4 封装二级分类,在一级分类循环遍历查询所有的二级分类
            List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
            for (int j = 0; j < twoSubjectList.size(); j++) {
                //获取每个二级分类
                EduSubject tSubject = twoSubjectList.get(j);
                //判断二级分类parentId和一级分类id是否一样
                if (tSubject.getParentId().equals(eduSubject.getId())) {
                    TwoSubject twoSubject = new TwoSubject();
                    BeanUtils.copyProperties(tSubject, twoSubject);
                    twoFinalSubjectList.add(twoSubject);
                }
            }
            //把一级下面所有二级分类放到一级分类中
            oneSubject.setChildren(twoFinalSubjectList);
        }
        return finalSubjectList;
    }
    
    • 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

    课程管理模块

    环境搭建

    1. 创建数据库表

    CREATE TABLE `edu_chapter` (
      `id` char(19) NOT NULL COMMENT '章节ID',
      `course_id` char(19) NOT NULL COMMENT '课程ID',
      `title` varchar(50) NOT NULL COMMENT '章节名称',
      `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '显示排序',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_course_id` (`course_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程章节';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    CREATE TABLE `edu_course` (
      `id` char(19) NOT NULL COMMENT '课程ID',
      `teacher_id` char(19) NOT NULL COMMENT '课程讲师ID',
      `subject_id` char(19) NOT NULL COMMENT '课程专业ID',
      `subject_parent_id` char(19) NOT NULL DEFAULT '' COMMENT '课程专业父级ID',
      `title` varchar(50) NOT NULL COMMENT '课程标题',
      `price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '课程销售价格,设置为0则可免费观看',
      `lesson_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '总课时',
      `cover` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '课程封面图片路径',
      `buy_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '销售数量',
      `view_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '浏览数量',
      `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
      `status` varchar(10) NOT NULL DEFAULT 'Draft' COMMENT '课程状态 Draft未发布  Normal已发布',
      `is_deleted` tinyint(3) DEFAULT NULL COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_title` (`title`),
      KEY `idx_subject_id` (`subject_id`),
      KEY `idx_teacher_id` (`teacher_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程基本信息';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    CREATE TABLE `edu_video` (
      `id` char(19) NOT NULL COMMENT '视频ID',
      `course_id` char(19) NOT NULL COMMENT '课程ID',
      `chapter_id` char(19) NOT NULL COMMENT '章节ID',
      `title` varchar(50) NOT NULL COMMENT '节点名称',
      `video_source_id` varchar(100) DEFAULT NULL COMMENT '云端视频资源',
      `video_original_name` varchar(100) DEFAULT NULL COMMENT '原始文件名称',
      `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
      `play_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '播放次数',
      `is_free` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否可以试听:0收费 1免费',
      `duration` float NOT NULL DEFAULT '0' COMMENT '视频时长(秒)',
      `status` varchar(20) NOT NULL DEFAULT 'Empty' COMMENT 'Empty未上传 Transcoding转码中  Normal正常',
      `size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '视频源文件大小(字节)',
      `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_course_id` (`course_id`),
      KEY `idx_chapter_id` (`chapter_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程视频';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    CREATE TABLE `edu_course_description` (
      `id` char(19) NOT NULL COMMENT '课程ID',
      `description` text COMMENT '课程简介',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程简介';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    还要用到 edu_teacher 和 edu_subject 表。
    表与表之间的关系:
    在这里插入图片描述

    2. 代码生成器生成代码

    添加课程

    在这里插入图片描述
    在这里插入图片描述
    1. 创建VO类封装表单提交的数据

    @Data
    public class CourseInfoVo {
        @ApiModelProperty(value = "课程ID")
        private String id;
    
        @ApiModelProperty(value = "课程讲师ID")
        private String teacherId;
    
        @ApiModelProperty(value = "课程专业ID")
        private String subjectId;
    
        @ApiModelProperty(value = "课程专业父级ID")
        private String subjectParentId;
    
        @ApiModelProperty(value = "课程标题")
        private String title;
    
        @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
        private BigDecimal price;
    
        @ApiModelProperty(value = "总课时")
        private Integer lessonNum;
    
        @ApiModelProperty(value = "课程封面图片路径")
        private String cover;
    
        @ApiModelProperty(value = "课程简介")
        private String description;
    }
    
    • 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

    2. 编写controller和service部分
    注意:课程和描述是一对一的关系,添加之后,id值一样。
    修改 EduCourseDescription 课程描述类的实体类id的类型为手动设置。
    在这里插入图片描述

    //添加课程基本信息的方法
    @PostMapping("addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
        String id= courseService.saveCourseInfo(courseInfoVo);
        return R.ok().data("courseId", id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //课程描述注入
    @Autowired
    private EduCourseDescriptionService courseDescriptionService;
    
    //添加课程基本信息
    @Override
    public String saveCourseInfo(CourseInfoVo courseInfoVo) {
        //1 向课程表添加课程基本信息
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int insert = baseMapper.insert(eduCourse);
        if (insert <= 0) {
            throw new GuliException(20001, "添加课程信息失败");
        }
    
        //2 向课程简介表添加课程简介
        EduCourseDescription courseDescription = new EduCourseDescription();
        courseDescription.setDescription(courseInfoVo.getDescription());
        //获取添加之后课程id,然后设置进描述id,让它们产生一对一的关系
        String cid = eduCourse.getId();
        courseDescription.setId(cid);
        courseDescriptionService.save(courseDescription);
    
        return cid;
    }
    
    • 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

    课程大纲显示(章节和小节)

    在这里插入图片描述
    1. 创建两个实体类,章节和小节,在章节实体类使用list表示小节

    @Data
    public class ChapterVo {
        private String id;
        private String title;
        //表示小节
        private List<VideoVo> children = new ArrayList<>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    @Data
    public class VideoVo {
        private String id;
        private String title;
        private String videoSourceId; //视频id
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 编写接口

    @RestController
    @RequestMapping("/eduservice/chapter")
    public class EduChapterController {
    
        @Autowired
        private EduChapterService chapterService;
    
        //课程大纲列表,根据课程id查询
        @GetMapping("getChapterVideo/{courseId}")
        public R getChapterVideo(@PathVariable String courseId) {
            List<ChapterVo> list = chapterService.getChapterVideoByCourseId(courseId);
            return R.ok().data("allChapterVideo", list);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    @Service
    public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
    
        @Autowired
        private EduVideoService videoService;   //注入小节service
    
        //课程大纲列表
        @Override
        public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
            //1 根据课程id查询课程里面所有的章节
            QueryWrapper<EduChapter> wrapperChapter = new QueryWrapper<>();
            wrapperChapter.eq("course_id", courseId);
            List<EduChapter> eduChapterList = baseMapper.selectList(wrapperChapter);
    
            //2 根据课程id查询课程里面所有的小节
            QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
            wrapperVideo.eq("course_id", courseId);
            List<EduVideo> eduVideoList = videoService.list(wrapperVideo);
    
            //创建list集合,用于最终封装数据
            List<ChapterVo> finalList = new ArrayList<>();
    
            //3 遍历查询章节list集合进行封装
            for (int i = 0; i < eduChapterList.size(); i++) {
                //每个章节
                EduChapter eduChapter = eduChapterList.get(i);
                //将eduChapter对象复制到ChapterVo中
                ChapterVo chapterVo = new ChapterVo();
                BeanUtils.copyProperties(eduChapter, chapterVo);
                //把chapterVo放到最终的集合中
                finalList.add(chapterVo);
    
                //创建集合,用于封装章节的小节
                List<VideoVo> videoVoList = new ArrayList<>();
    
                //4 遍历查询小节list集合,进行封装
                for (int j = 0; j < eduVideoList.size(); j++) {
                    EduVideo eduVideo = eduVideoList.get(j);
                    //判断:小节里面chapterId和章节里面id是否一样
                    if (eduVideo.getChapterId().equals(eduChapter.getId())) {
                        //封装
                        VideoVo videoVo = new VideoVo();
                        BeanUtils.copyProperties(eduVideo, videoVo);
                        videoVoList.add(videoVo);
                    }
                }
                //将封装之后小节的list,设置到章节对象中
                chapterVo.setChildren(videoVoList);
            }
            return finalList;
        }
    }
    
    • 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

    修改课程基本信息

    在课程大纲页面,点击“上一步”,回到第一步,将数据回显在页面中,修改信息内容,然后保存。
    1. 根据课程id查询课程基本信息接口

    //根据课程id查询课程基本信息
    @GetMapping("getCourseInfo/{courseId}")
    public R getCourseInfo(@PathVariable String courseId) {
        CourseInfoVo courseInfoVo = courseService.getCourseInfo(courseId);
        return R.ok().data("courseInfoVo", courseInfoVo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //根据课程id查询课程基本信息
    @Override
    public CourseInfoVo getCourseInfo(String courseId) {
        //1 查询课程表
        EduCourse eduCourse = baseMapper.selectById(courseId);
        CourseInfoVo courseInfoVo = new CourseInfoVo();
        BeanUtils.copyProperties(eduCourse, courseInfoVo);
        //2 查询描述表
        EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
        courseInfoVo.setDescription(courseDescription.getDescription());
        return courseInfoVo;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 修改课程信息接口

    //修改课程信息
    @PostMapping("updateCourseInfo")
    public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
        courseService.updateCourseInfo(courseInfoVo);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Override
    public void updateCourseInfo(CourseInfoVo courseInfoVo) {
        //1 修改课程表
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int update = baseMapper.updateById(eduCourse);
        if (update == 0) {
            throw new GuliException(20001, "修改课程信息失败");
        }
    
        //2 修改描述表
        EduCourseDescription description = new EduCourseDescription();
        description.setId(courseInfoVo.getId());
        description.setDescription(courseInfoVo.getDescription());
        courseDescriptionService.updateById(description);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    章节的添加修改删除

    1. 添加和修改

    //根据章节id查询
    @GetMapping("getChapterInfo/{chapterId}")
    public R getCharpterInfo(@PathVariable String chapterId) {
        EduChapter eduChapter = chapterService.getById(chapterId);
        return R.ok().data("chapter", eduChapter);
    }
    
    //修改章节
    @PostMapping("updateChapter")
    public R updateChapter(@RequestBody EduChapter eduChapter) {
        chapterService.updateById(eduChapter);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2. 删除
    如果删除的章节下面有小节,不让删除

    //删除的方法
    @DeleteMapping("{chapterId}")
    public R deleteChapter(@PathVariable String chapterId) {
        boolean flag = chapterService.deleteChapter(chapterId);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    //删除章节的方法
    @Override
    public boolean deleteChapter(String chapterId) {
        //根据chapterId章节id,查询小节表,如果查询出小节数据,不进行删除
        QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
        wrapper.eq("chapter_id", chapterId);
        int count = videoService.count(wrapper);
        if (count > 0) {
            throw new GuliException(20001, "不能删除");
        } else {
            //删除章节
            int result = baseMapper.deleteById(chapterId);
            return result>0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    添加和删除小节

    其中删除小节需要删除小节中对应的视频,这里暂时还没写视频相关的接口,所以删除小节直接调用removeById方法删除就行。

    @RestController
    @RequestMapping("/eduservice/video")
    public class EduVideoController {
    
        @Autowired
        private EduVideoService videoService;
    
        //添加小节
        @PostMapping("addVideo")
        public R addVideo(@RequestBody EduVideo eduVideo) {
            videoService.save(eduVideo);
            return R.ok();
        }
    
        //删除小节
        // 删除小节的时候,同时把里面视频删掉
        @DeleteMapping("{id}")
        public R deleteVideo(@PathVariable String id) {
            //删除小节
            videoService.removeById(id);
            return R.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    课程信息确认和发布课程

    在这里插入图片描述
    通过页面可以看出,需要查询的数据有:课程名称、课程价格、课程简介、课程分类、课程讲师、课程封面。
    而这些数据分布在多张表中,所以建议使用sql连表查询,查询出这些数据进行封装。

    1. 定义课程发布信息的VO对象

    @Data
    public class CoursePublishVo {
        private String id;
        private String title;
        private String cover;
        private String lessonNum;
        private String subjectLevelOne;
        private String subjectLevelTwo;
        private String teacherName;
        private String price;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 在mapper中定义查询的方法,并且在xml中实现

    @Mapper
    public interface EduCourseMapper extends BaseMapper<EduCourse> {
        public CoursePublishVo getPublishCourseInfo(String courseId);
    }
    
    • 1
    • 2
    • 3
    • 4
    <select id="getPublishCourseInfo" resultType="com.hxp.eduservice.entity.vo.CoursePublishVo">
        SELECT ec.id, ec.title, ec.price, ec.lesson_num AS lessonNum, ec.cover,
               et.name AS teacherName,
               es1.title AS subjectLevelOne,
               es2.title AS subjectLevelTwo
        FROM edu_course ec LEFT JOIN edu_course_description ecd ON ec.id=ecd.id
                           LEFT JOIN edu_teacher et ON ec.teacher_id=et.id
                           LEFT JOIN edu_subject es1 ON ec.subject_parent_id=es1.id
                           LEFT JOIN edu_subject es2 ON ec.subject_id=es2.id
        WHERE ec.id=#{courseId};
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3. 编写接口

    //根据课程id查询课程确认信息
    @GetMapping("getPublishCourseInfo/{id}")
    public R getPublishCourseInfo(@PathVariable String id) {
        CoursePublishVo coursePublishVo = courseService.publishCourseInfo(id);
        return R.ok().data("publishCourse", coursePublishVo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Override
    public CoursePublishVo publishCourseInfo(String id) {
        CoursePublishVo publishCourseInfo = baseMapper.getPublishCourseInfo(id);
        return publishCourseInfo;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4. 可能出现的问题
    可能出现如下错误,原因是没有找到xml文件。maven加载的时候,把java文件夹里面的.java类型文件进行编译,如果有其他类型文件,不会加载。
    在这里插入图片描述
    解决:通过配置实现。
    (1)pom.xml

    <build>
        <resources>
            <resource>
                <directory>src/main/javadirectory>
                <includes>
                    <include>**/*.xmlinclude>
                includes>
                <filtering>falsefiltering>
            resource>
        resources>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)application.properties

    # 配置mapper.xml文件的路径
    mybatis-plus.mapper-locations=classpath:com/hxp/eduservice/mapper/xml/*.xml
    
    • 1
    • 2

    5. 最终发布
    做完前面的操作,已经把课程的信息存到数据库了,但此时用户在前台还看不到课程,所以需要实现最终的发布。

    实现:点击“发布课程”,将这个字段的值改成“Normal”。
    在这里插入图片描述

    //课程最终发布,修改课程状态
    @PostMapping("publishCourse/{id}")
    public R publishCourse(@PathVariable String id) {
        EduCourse eduCourse = new EduCourse();
        eduCourse.setId(id);
        eduCourse.setStatus("Normal");  //设置课程发布状态
        courseService.updateById(eduCourse);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    课程列表显示和删除课程

    在这里插入图片描述

    1. 课程列表

    @Autowired
    private EduCourseService courseService;
    
    @GetMapping
    public R getCourseList() {
        List<EduCourse> list = courseService.list(null);
        return R.ok().data("list", list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2. 删除课程
    课程里面有:课程描述、章节、小节、视频。
    删除课程把里面的内容都删除,按照顺序进行删除,视频 --> 小节 --> 章节 --> 描述 --> 课程本身。

    //删除课程
    @DeleteMapping("{courseId}")
    public R deleteCourse(@PathVariable String courseId) {
        courseService.removeCourse(courseId);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //删除课程
    @Override
    public void removeCourse(String courseId) {
        //1 根据课程id删除小节
        eduVideoService.removeVideoByCourseId(courseId);
    
        //2 根据课程id删除章节
        chapterService.removeChapterByCourseId(courseId);
    
        //3 根据课程id删除描述
        courseDescriptionService.removeById(courseId);
    
        //4 根据课程id删除课程本身
        int result = baseMapper.deleteById(courseId);
        if (result == 0) {
            throw new GuliException(20001, "删除失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在小节的Service中编写删除小节的方法。

    @Service
    public class EduVideoServiceImpl extends ServiceImpl<EduVideoMapper, EduVideo> implements EduVideoService {
    
        @Autowired
        private VodClient vodClient;
    
        //1 根据课程id删除小节
        // 删除小节,同时删除对应视频文件
        @Override
        public void removeVideoByCourseId(String courseId) {
            //TODO 这里删除视频还未写
            //删除小节
            QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
            wrapper.eq("course_id", courseId);
            baseMapper.delete(wrapper);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在章节的service中编写删除章节的方法

    //根据课程id删除章节
    @Override
    public void removeChapterByCourseId(String courseId) {
        QueryWrapper<EduChapter> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id", courseId);
        baseMapper.delete(wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    添加小节上传视频

    在这里插入图片描述
    在这里插入图片描述
    1. 搭建环境,引入依赖
    在这里插入图片描述
    application.properties

    server.port=8003
    spring.application.name=service-vod
    spring.profiles.active=dev
    
    #阿里云视频点播
    aliyun.vod.file.keyid=LTAI5tEz9ABsH5qFZsxXg5K2
    aliyun.vod.file.keysecret=MHJngccYekVjxzp0YtKQU4HTJsr5Ty
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @ComponentScan(basePackages = {"com.hxp"})
    public class VodApplication {
        public static void main(String[] args) {
            SpringApplication.run(VodApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    <dependency>
        <groupId>com.aliyungroupId>
        <artifactId>aliyun-java-sdk-coreartifactId>
        <version>4.3.3version>
    dependency>
    <dependency>
        <groupId>com.aliyun.ossgroupId>
        <artifactId>aliyun-sdk-ossartifactId>
        <version>3.1.0version>
    dependency>
    <dependency>
        <groupId>com.aliyungroupId>
        <artifactId>aliyun-java-sdk-vodartifactId>
        <version>2.15.5version>
    dependency>
    <dependency>
        <groupId>com.aliyungroupId>
        <artifactId>aliyun-sdk-vod-uploadartifactId>
        <version>1.4.11version>
    dependency>
    <dependency>
        <groupId>com.google.code.gsongroupId>
        <artifactId>gsonartifactId>
        <version>2.8.2version>
    dependency>
    
    • 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

    2. 编写常量类

    @Component
    public class ConstantVodUtils implements InitializingBean {
    
        @Value("${aliyun.vod.file.keyid}")
        private String keyid;
    
        @Value("${aliyun.vod.file.keysecret}")
        private String keysecret;
    
        public static String ACCESS_KEY_SECRET;
        public static String ACCESS_KEY_ID;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            ACCESS_KEY_SECRET = keysecret;
            ACCESS_KEY_ID = keyid;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3. 编写接口

    @RestController
    @RequestMapping("/eduvod/video")
    public class VodController {
    
        @Autowired
        private VodService vodService;
    
        @PostMapping("uploadAlyiVideo")
        public R uploadAlyiVideo(MultipartFile file) {
            String videoId = vodService.uploadVideoAly(file);
            return R.ok().data("videoId", videoId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    @Override
    public String uploadVideoAly(MultipartFile file) {
        try {
            //accessKeyId, accessKeySecret
            //fileName:上传文件原始名称
            // 01.03.09.mp4
            String fileName = file.getOriginalFilename();
            //title:上传之后显示名称,01.01.mp4 => 01.01
            String title = fileName.substring(0, fileName.lastIndexOf("."));
            //inputStream:上传文件输入流
            InputStream inputStream = file.getInputStream();
            UploadStreamRequest request = new UploadStreamRequest(ConstantVodUtils.ACCESS_KEY_ID,ConstantVodUtils.ACCESS_KEY_SECRET, title, fileName, inputStream);
    
            UploadVideoImpl uploader = new UploadVideoImpl();
            UploadStreamResponse response = uploader.uploadStream(request);
    
            String videoId = null;
            if (response.isSuccess()) {
                videoId = response.getVideoId();
            } else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
                videoId = response.getVideoId();
            }
            return videoId;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    • 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

    注意:用tomcat传输文件有大小限制,超出会报错。
    解决:在application.properties中加上配置。

    # 最大上传单个文件大小:默认1M
    spring.servlet.multipart.max-file-size=1024MB
    # 最大置总上传的数据大小:默认10M
    spring.servlet.multipart.max-request-size=1024MB
    
    • 1
    • 2
    • 3
    • 4

    视频删除

    点击×,可以删除视频
    在这里插入图片描述
    1. 初始化视频对象

    public class InitVodClient {
        public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
            String regionId = "cn-shanghai";  // 点播服务接入区域
            DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
            DefaultAcsClient client = new DefaultAcsClient(profile);
            return client;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2. 删除阿里云视频接口

    //根据视频id删除阿里云视频
    @DeleteMapping("removeAlyVideo/{id}")
    public R removeAlyVideo(@PathVariable String id) {
        try {
            //初始化对象
            DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
            //创建删除视频request对象
            DeleteVideoRequest request = new DeleteVideoRequest();
            //向request设置视频id
            request.setVideoIds(id);
            //调用初始化对象的方法实现删除
            client.getAcsResponse(request);
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            throw new GuliException(20001, "删除视频失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    删除小节并删除掉小节下的视频

    前提条件,把相互调用服务在nacos进行注册。
    在这里插入图片描述

    1. 创建包client,引入依赖,配置nacos

    
    <dependency>
         <groupId>org.springframework.cloudgroupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
     dependency>
    
    <dependency>
         <groupId>org.springframework.cloudgroupId>
         <artifactId>spring-cloud-starter-openfeignartifactId>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    2. 在调用端service-edu服务启动类添加注解
    在这里插入图片描述
    3. 在调用端,创建interface,使用注解指定调用服务名称,定义调用的方法路径。
    @FeignClient注解用于指定从哪个服务中调用功能,名称与被调用的服务名保持一致。
    @DeleteMapping注解用于被调用的微服务进行地址映射,要写全路径。
    @PathVariable注解要指定参数名称,否则报错。
    @Component注解,防止在其他位置注入VodClient时idea报错。

    写接口方法时,就把之前写的删除视频的方法复制过来,@DeleteMapping写全路径,@PathVariable加上名称即可。

    @FeignClient("service-vod") //调用的服务名称
    @Component
    public interface VodClient {
        //根据视频id删除阿里云视频
        @DeleteMapping("/eduvod/video/removeAlyVideo/{id}")
        public R removeAlyVideo(@PathVariable("id") String id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. 在删除小节的方法中调用方法删除视频

    @Autowired
    private VodClient vodClient; //注入vodClient
    //删除小节
    // 删除小节的时候,同时把阿里云的视频删掉
    @DeleteMapping("{id}")
    public R deleteVideo(@PathVariable String id) {
        //根据小节id获取视频id,调用方法实现视频删除
        EduVideo eduVideo = videoService.getById(id);
        String videoSourceId = eduVideo.getVideoSourceId();
        //根据视频id,远程调用实现视频删除
        if (!StringUtils.isEmpty(videoSourceId)) {
            vodClient.removeAlyVideo(videoSourceId);
        }
        //删除小节
        videoService.removeById(id);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    启动其他模块可能出现的问题:
    在这里插入图片描述
    原因是引入了nacos依赖,而在这个模块没有做相关的配置。解决:
    在这里插入图片描述
    在这里插入图片描述

    删除课程删除视频

    一个课程有多个章节,一个章节有多个小节,每个小节有视频。
    1. 在service-vod创建接口,实现删除多个视频
    这里用org.apache.commons.lang下的StringUtils的join()方法,将list集合转成字符串

    //删除多个阿里云视频
    @DeleteMapping("delete-batch")
    public R deleteBatch(@RequestParam("videoIdList") List<String> videoIdList) {
        vodService.removeMoreAlyVideo(videoIdList);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //删除多个阿里云视频
    @Override
    public void removeMoreAlyVideo(List videoIdList) {
        try {
            //初始化对象
            DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
            //创建删除视频request对象
            DeleteVideoRequest request = new DeleteVideoRequest();
    
            //videoList值 [1,2,3] 转换成 1,2,3
            String videoIds = StringUtils.join(videoIdList.toArray(), ",");
    
            //向request设置视频id,setVideoIds参数是以逗号分开传递的,不能直接传递集合
            request.setVideoIds(videoIds);
            //调用初始化对象的方法实现删除
            client.getAcsResponse(request);
    
        } catch (Exception e) {
            e.printStackTrace();
            throw new GuliException(20001, "删除视频失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2. 在service-edu调用service-vod接口实现删除多个视频的功能。

    //删除多个阿里云视频
    @DeleteMapping("/eduvod/video/delete-batch")
    public R deleteBatch(@RequestParam("videoIdList") List<String> videoIdList);
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    @Autowired
    private VodClient vodClient;
    
    //1 根据课程id删除小节
    // 删除小节,同时删除对应视频文件
    @Override
    public void removeVideoByCourseId(String courseId) {
        // 根据课程id查询课程所有的视频id
        QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
        wrapperVideo.eq("course_id", courseId);
        wrapperVideo.select("video_source_id");
        List<EduVideo> eduVideoList = baseMapper.selectList(wrapperVideo);
        // List 变成 List
        List<String> videoIds = new ArrayList<>();
        for (int i = 0; i < eduVideoList.size(); i++) {
            EduVideo eduVideo = eduVideoList.get(i);
            String videoSourceId = eduVideo.getVideoSourceId();
            if (!StringUtils.isEmpty(videoSourceId)) {
                videoIds.add(videoSourceId);
            }
        }
        //根据多个视频id删除多个视频
        if (videoIds.size() > 0) {
            vodClient.deleteBatch(videoIds);
        }
    
        //删除小节
        QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id", courseId);
        baseMapper.delete(wrapper);
    }
    
    • 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

    整合熔断器

    给远程调用的服务,设置熔断机制。

    1. 添加依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-ribbonartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 在application.properties进行配置

    # 开启熔断机制
    feign.hystrix.enabled=true
    
    • 1
    • 2

    3. 在创建interface之后,创建interface的实现类,调用出错了则会执行实现类的代码

    @Component
    public class VodFileDegradeFeignClient implements VodClient{
        //出错之后执行
        @Override
        public R removeAlyVideo(String id) {
            return R.error().message("删除视频出错了");
        }
    
        @Override
        public R deleteBatch(List<String> videoIdList) {
            return R.error().message("删除多个视频出错了");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. 在接口上的@FeignClient注解,指定实现类的class
    在这里插入图片描述

    统计分析模块

    需求分析

    统计在线教育项目中,每天有多少注册人数,把统计出来的注册人数,使用图表显示出来。
    在这里插入图片描述

    统计某一天的注册人数:查询用户表得到需要的数据。
    注:date函数获取日期时间格式里面的日期部分,去掉时分秒。

    select count(*) from ucenter_member uc where date(uc.gmt_create)='2020-03-09';
    
    • 1

    在这里插入图片描述
    在这里插入图片描述

    生成统计数据

    1. 数据库表

    CREATE TABLE `statistics_daily` (
      `id` char(19) NOT NULL COMMENT '主键',
      `date_calculated` varchar(20) NOT NULL COMMENT '统计日期',
      `register_num` int(11) NOT NULL DEFAULT '0' COMMENT '注册人数',
      `login_num` int(11) NOT NULL DEFAULT '0' COMMENT '登录人数',
      `video_view_num` int(11) NOT NULL DEFAULT '0' COMMENT '每日播放视频数',
      `course_num` int(11) NOT NULL DEFAULT '0' COMMENT '每日新增课程数',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `statistics_day` (`date_calculated`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站统计日数据';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 配置文件

    # 服务端口
    server.port=8008
    # 服务名
    spring.application.name=service-statistics
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guliedu?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=qwer`123
    
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.database= 0
    spring.redis.timeout=1800000
    
    spring.redis.lettuce.pool.max-active=20
    spring.redis.lettuce.pool.max-wait=-1
    #最大阻塞等待时间(负数表示没限制)
    spring.redis.lettuce.pool.max-idle=5
    spring.redis.lettuce.pool.min-idle=0
    #最小空闲
    
    #请求处理的超时时间
    ribbon.ReadTimeout=120000
    #请求连接的超时时间
    ribbon.ConnectTimeout=30000
    
    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    
    # 配置mapper.xml文件的路径
    mybatis-plus.mapper-locations=classpath:com/hxp/staservice/mapper/xml/*.xml
    
    # nacos服务地址
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    
    #mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 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

    3. 代码生成器生成代码

    4. 在service-ucenter查询某一天的注册人数

    //查询某一天注册人数
    @GetMapping("countRegister/{day}")
    public R countRegister(@PathVariable String day) {
        Integer count = memberService.countRegisterDay(day);
        return R.ok().data("countRegister", count);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //查询某一天注册人数
    @Override
    public Integer countRegisterDay(String day) {
        return baseMapper.countRegisterDay(day);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    
    <select id="countRegisterDay" resultType="java.lang.Integer">
        SELECT COUNT(*) FROM ucenter_member uc WHERE DATE(uc.gmt_create)=#{day}
    select>
    
    • 1
    • 2
    • 3
    • 4

    5. 在service_statistics模块,远程调用方法查询注册人数

    @Component
    @FeignClient("service-ucenter")
    public interface UcenterClient {
        //查询某一天注册人数
        @GetMapping("/educenter/member/countRegister/{day}")
        public R countRegister(@PathVariable("day") String day);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    @RestController
    @RequestMapping("/staservice/sta")
    public class StatisticsDailyController {
    
        @Autowired
        private StatisticsDailyService statisticsService;
    
        //统计某一天注册人数,生成统计数据
        @PostMapping("registerCount/{day}")
        public R registerCount(@PathVariable String day) {
            statisticsService.registerCount(day);
            return R.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    @Service
    public class StatisticsDailyServiceImpl extends ServiceImpl<StatisticsDailyMapper, StatisticsDaily> implements StatisticsDailyService {
    
        @Autowired
        private UcenterClient ucenterClient;
    
        //统计某一天注册人数,生成统计数据
        @Override
        public void registerCount(String day) {
    
            //添加记录之前删除表相同日期的数据
            QueryWrapper<StatisticsDaily> wrapper = new QueryWrapper<>();
            wrapper.eq("date_calculated",day);
            baseMapper.delete(wrapper);
    
            //远程调用得到某一天注册人数
            R registerR = ucenterClient.countRegister(day);
            Integer countRegister = (Integer) registerR.getData().get("countRegister");
            System.out.println(countRegister);
            //把获取到的数据添加到统计分析表里面
            StatisticsDaily sta = new StatisticsDaily();
            sta.setRegisterNum(countRegister);
            sta.setDateCalculated(day);
            sta.setVideoViewNum(11);
            sta.setLoginNum(22);
            sta.setCourseNum(33);
            baseMapper.insert(sta);
        }
    }
    
    • 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

    问题:如果是查询同一天,每次都会新增一条记录,并且每条记录的注册人数都不同。
    所以在添加之前,需要先删除原来的记录,再做添加。
    在这里插入图片描述

    添加定时任务

    1. 在启动类添加注解
    在这里插入图片描述

    2. 创建定时任务类
    在这个类里面使用表达式设置什么时候执行。
    cron表达式:设置执行规则

    @Component
    public class ScheduledTask {
    
        @Autowired
        private StatisticsDailyService staService;
    
        //在每天凌晨1点,执行方法,把前一天的数据查询进行添加
        @Scheduled(cron = "0 0 1 * * ?")
        public void task() {
            staService.registerCount(DateUtil.formatDate(DateUtil.addDays(new Date(), -1)));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    生成cron表达式工具:https://www.pppet.net/

    图表显示

    在这里插入图片描述

    //图表显示,返回两部分数据,日期json数组,数量json数组
    @GetMapping("showData/{type}/{begin}/{end}")
    public R showData(@PathVariable String type, @PathVariable String begin,
                      @PathVariable String end) {
        Map<String, Object> map = statisticsService.getShowData(type,begin,end);
        return R.ok().data(map);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    //图表显示,返回两部分数据,日期json数组,数量json数组
    @Override
    public Map<String, Object> getShowData(String type, String begin, String end) {
        //根据条件查询对应数据
        QueryWrapper<StatisticsDaily> wrapper = new QueryWrapper<>();
        wrapper.between("date_calculated",begin,end);
        wrapper.select("date_calculated",type);
        List<StatisticsDaily> staList = baseMapper.selectList(wrapper);
    
        //因为返回有两部分数据:日期 和 日期对应数量
        //前端要求数组json结构,对应后端java代码是list集合
        //创建两个list集合,一个日期list,一个数量list
        List<String> date_calculatedList = new ArrayList<>();
        List<Integer> numDataList = new ArrayList<>();
    
        //遍历查询所有数据list集合,进行封装
        for (int i = 0; i < staList.size(); i++) {
            StatisticsDaily daily = staList.get(i);
            //封装日期list集合
            date_calculatedList.add(daily.getDateCalculated());
            //封装对应数量
            switch (type) {
                case "login_num":
                    numDataList.add(daily.getLoginNum());
                    break;
                case "register_num":
                    numDataList.add(daily.getRegisterNum());
                    break;
                case "video_view_num":
                    numDataList.add(daily.getVideoViewNum());
                    break;
                case "course_num":
                    numDataList.add(daily.getCourseNum());
                    break;
                default:
                    break;
            }
        }
        //把封装之后两个list集合放到map集合,进行返回
        Map<String, Object> map = new HashMap<>();
        map.put("date_calculatedList",date_calculatedList);
        map.put("numDataList",numDataList);
        return map;
    }
    
    • 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
  • 相关阅读:
    【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷C
    Kali生成windows木马程序
    锐捷——浮动静态路由配置
    2024前端面试每日一更——简述MVVM?
    如何制作很火的抖音配音?原来爆款短视频配音方法这么简单
    五、RTMP协议 RTMP播放基本流程
    端侧需要向量数据库吗
    IDEA中启动类是灰色,重启idea启动类自动消失解决方法
    [含毕业设计论文+PPT+源码等]ssm校友录网站+Java后台管理系统|前后分离VUE
    零知识证明笔记——私密交易,pederson,区间证明,所有权证明
  • 原文地址:https://blog.csdn.net/weixin_45581692/article/details/127264865