• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
北莽
帮助1

普通用户前台使用系统

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='讲师';

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+8
spring.datasource.username=root
spring.datasource.password=qwer`123

# mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
学新通

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

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
</dependency>
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+8");
        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)编写接口
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));
    }
}

service

public interface EduTeacherService extends IService<EduTeacher> {
}

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

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

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

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

讲师删除(逻辑删除)

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

/**
 * 逻辑删除插件
 */
@Bean
public ISqlInjector sqlInjector() {
    return new LogicSqlInjector();
}

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

@TableLogic
private Boolean isDeleted;

(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();
    }
}

整合swagger

在父工程下创建一个子模块common,作为公共模块,在common模块下创建一个子模块
学新通
加上相关依赖

<!--swagger-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <scope>provided </scope>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <scope>provided </scope>
</dependency>
@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();
    }
}
学新通

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

<dependency>
   <groupId>com.hxp</groupId>
    <artifactId>service_base</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

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

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

统一结果数据返回

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

public interface ResultCode {
    public static Integer SUCCESS = 20000; //成功
    public static Integer ERROR = 20001; //失败
}

(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;
    }
}
学新通

(3)在service中引入依赖

<dependency>
    <groupId>com.hxp</groupId>
    <artifactId>common_utils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

分页查询讲师

//分页查询讲师的方法
@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)把条件封装到对象(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;
}

(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)在实体类的属性上加上注解

@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

(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);
    }
}

(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)全局异常处理
在公共模块service-base中创建统一异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {

    //指定出现什么异常执行这个方法
    @ExceptionHandler(Exception.class)
    @ResponseBody  //为了能返回数据
    public R error(Exception e) {
        e.printStackTrace();
        return R.error().message("执行了全局异常处理...");
    }
}

(2)特殊异常处理

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

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

@Data
@AllArgsConstructor //有参构造
@NoArgsConstructor  //无参构造
public class GuliException extends RuntimeException{
    private Integer code;
    private String msg;
}

在统一异常类添加规则

@ControllerAdvice
public class GlobalExceptionHandler {
    //自定义异常
    @ExceptionHandler(GuliException.class)
    @ResponseBody
    public R error(GuliException e) {
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }
}

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

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

日志

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="D:/guli_1010/edu" />

    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(te{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">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件-->

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
        <logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              如果未设置此属性,那么当前logger将会继承上级的级别。
    -->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
     -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
        <logger name="com.guli" level="INFO" />

        <!--
            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
            可以包含零个或多个appender元素。
        -->
        <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>
学新通

将错误日志输出到文件
在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");
    }
}

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

上传讲师头像

(1)新建一个子模块
学新通
(2)引入依赖

<!--阿里云oss依赖-->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!--日期工具栏依赖-->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
</dependency>

(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

(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;
    }
}
学新通

(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);
    }
}
@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. 创建数据库表

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='课程科目';

2. 引入easyExcel的依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
</dependency>
<!--xls-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.1.7</version>
</dependency>
<!--xlsx-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.1.7</version>
</dependency>
学新通

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

添加课程分类

学新通

1. 创建excel实体类

@Data
public class SubjectData {
    @ExcelProperty(index = 0)
    private String oneSubjectName;
    @ExcelProperty(index = 1)
    private String twoSubjectName;
}

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) {

    }
}
学新通

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();
    }
}
@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. 针对返回数据创建对应的实体类(一级分类和二级分类),给两个实体类建立关系

@Data
public class OneSubject {
    private String id;
    private String title;
    private List<TwoSubject> children = new ArrayList<>();
}
@Data
public class TwoSubject {
    private String id;
    private String title;
}

2. 编写接口

//课程分类列表展示(树形)
@GetMapping("getAllSubject")
public R getAllSubject() {
    List<OneSubject> list = subjectService.getAllOneTwoSubject();
    return R.ok().data("list",list);
}
//课程分类列表(树形)
@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. 创建数据库表

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='课程章节';
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='课程基本信息';
学新通
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='课程视频';
学新通
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='课程简介';

还要用到 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;
}
学新通

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);
}
//课程描述注入
@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. 创建两个实体类,章节和小节,在章节实体类使用list表示小节

@Data
public class ChapterVo {
    private String id;
    private String title;
    //表示小节
    private List<VideoVo> children = new ArrayList<>();
}
@Data
public class VideoVo {
    private String id;
    private String title;
    private String videoSourceId; //视频id
}

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);
    }
}
@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. 根据课程id查询课程基本信息接口

//根据课程id查询课程基本信息
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@PathVariable String courseId) {
    CourseInfoVo courseInfoVo = courseService.getCourseInfo(courseId);
    return R.ok().data("courseInfoVo", courseInfoVo);
}
//根据课程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;
}

2. 修改课程信息接口

//修改课程信息
@PostMapping("updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
    courseService.updateCourseInfo(courseInfoVo);
    return R.ok();
}
@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. 添加和修改

//根据章节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();
}

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

//删除的方法
@DeleteMapping("{chapterId}")
public R deleteChapter(@PathVariable String chapterId) {
    boolean flag = chapterService.deleteChapter(chapterId);
    if (flag) {
        return R.ok();
    } else {
        return R.error();
    }
}
//删除章节的方法
@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;
    }
}

添加和删除小节

其中删除小节需要删除小节中对应的视频,这里暂时还没写视频相关的接口,所以删除小节直接调用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();
    }
}
学新通

课程信息确认和发布课程

学新通
通过页面可以看出,需要查询的数据有:课程名称、课程价格、课程简介、课程分类、课程讲师、课程封面。
而这些数据分布在多张表中,所以建议使用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;
}

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

@Mapper
public interface EduCourseMapper extends BaseMapper<EduCourse> {
    public CoursePublishVo getPublishCourseInfo(String courseId);
}
<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>

3. 编写接口

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

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

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

(2)application.properties

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

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. 课程列表

@Autowired
private EduCourseService courseService;

@GetMapping
public R getCourseList() {
    List<EduCourse> list = courseService.list(null);
    return R.ok().data("list", list);
}

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

//删除课程
@DeleteMapping("{courseId}")
public R deleteCourse(@PathVariable String courseId) {
    courseService.removeCourse(courseId);
    return R.ok();
}
//删除课程
@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, "删除失败");
    }
}
学新通

在小节的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);
    }
}
学新通

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

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

添加小节上传视频

学新通
学新通
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
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = {"com.hxp"})
public class VodApplication {
    public static void main(String[] args) {
        SpringApplication.run(VodApplication.class, args);
    }
}
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.3.3</version>
</dependency>
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-vod</artifactId>
    <version>2.15.5</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-sdk-vod-upload</artifactId>
    <version>1.4.11</version>
</dependency>
<dependency>
    <groupId>com.谷歌.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>
学新通

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;
    }
}
学新通

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);
    }
}
@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;
    }
}
学新通

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

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

视频删除

点击×,可以删除视频
学新通
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;
    }
}

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, "删除视频失败");
    }
}
学新通

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

前提条件,把相互调用服务在nacos进行注册。
学新通

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

<!--服务注册-->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>
<!--服务调用-->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>

学新通

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);
}

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();
}
学新通

启动其他模块可能出现的问题:
学新通
原因是引入了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();
}
//删除多个阿里云视频
@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, "删除视频失败");
    }
}
学新通

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

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

学新通

@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<EduVideo> 变成 List<String>
    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. 添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

<!--hystrix依赖,主要是用  @HystrixCommand -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2. 在application.properties进行配置

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

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("删除多个视频出错了");
    }
}

4. 在接口上的@FeignClient注解,指定实现类的class
学新通

统计分析模块

需求分析

统计在线教育项目中,每天有多少注册人数,把统计出来的注册人数,使用图表显示出来。
学新通

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

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

学新通
学新通

生成统计数据

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='网站统计日数据';

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+8
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
学新通

3. 代码生成器生成代码

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

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

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

@Component
@FeignClient("service-ucenter")
public interface UcenterClient {
    //查询某一天注册人数
    @GetMapping("/educenter/member/countRegister/{day}")
    public R countRegister(@PathVariable("day") String day);
}
@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();
    }
}
@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. 创建定时任务类
在这个类里面使用表达式设置什么时候执行。
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)));
    }
}

生成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);
}
//图表显示,返回两部分数据,日期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;
}
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhkgjg
系列文章
更多 icon
同类精品
更多 icon
继续加载