第一章:简介


  • 这是一个真实的企业项目
  • 技术架构:
    • springCloudAlibaba
    • VUE实现了前后端分离开发(解决跨域的问题)
    • 数据库:mysql
    • ORM:mybatisPlus
  • 把我们开发的模块都分为不同的微服务部分
  • 把需要上传的图片及视频都存储在阿里云

1、技术选型

查看SpringCloud 和SpringBoot版本兼容性
image.png
查看SpringCloud 对其余各个技术版本的兼容性 需要转换成JSON进行查看

2、学习版本定稿

学习时必须和定稿版本一致

技术 版本
java 1.8
Spring Cloud Greenwich.RELEASE
Spring Boot 2.1.3.RELEASE
Spring Cloud Alibaba 2.1.0.RELEASE
mybatisPlus 3.0.5
Maven 3.3.3
MySQL 5.7

第二章:搭建项目环境

2.1 创建一个maven项目,作为父工程

image.png

2.2 pom.xml依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <!-- 模型版本。maven2.0必须是这样写,现在是maven2唯一支持的版本 -->
  6. <modelVersion>4.0.0</modelVersion>
  7. <!-- 公司或者组织的唯一标志,并且配置时生成的路径也是由此生成, 如 com.example,maven 会将该项目打成的 jar 包放本地路径:/com/example -->
  8. <groupId>com.example</groupId>
  9. <!-- 本项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 -->
  10. <artifactId>movies</artifactId>
  11. <!-- 本项目目前所处的版本号 -->
  12. <version>1.0-SNAPSHOT</version>
  13. <!-- 打包的机制,如 pom、jar、maven-plugin、ejb、war、ear、rar和par,默认为jar -->
  14. <packaging>pom</packaging>
  15. <!-- Spring Boot 的父级依赖-->
  16. <parent>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-parent</artifactId>
  19. <version>2.1.3.RELEASE</version>
  20. </parent>
  21. <!-- 帮助定义构件输出的一些附属构件,附属构件与主构件对应,有时候需要加上classifier才能唯一的确定该构件 不能直接定义项目的classifer,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成的 -->
  22. <!--<classifier></classifier>-->
  23. <!-- 统一管理jar包版本 -->
  24. <properties>
  25. <java.version>1.8</java.version>
  26. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  27. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  28. <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
  29. <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
  30. <mybatis-plus.version>3.0.5</mybatis-plus.version>
  31. <velocity.version>2.0</velocity.version>
  32. <swagger.version>2.7.0</swagger.version>
  33. <aliyun.oss.version>2.8.3</aliyun.oss.version>
  34. <jodatime.version>2.10.1</jodatime.version>
  35. <poi.version>3.17</poi.version>
  36. <commons-fileupload.version>1.3.1</commons-fileupload.version>
  37. <commons-io.version>2.6</commons-io.version>
  38. <httpclient.version>4.5.1</httpclient.version>
  39. <jwt.version>0.7.0</jwt.version>
  40. <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version>
  41. <aliyun-sdk-oss.version>3.1.0</aliyun-sdk-oss.version>
  42. <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version>
  43. <aliyun-java-vod-upload.version>1.4.11</aliyun-java-vod-upload.version>
  44. <aliyun-sdk-vod-upload.version>1.4.11</aliyun-sdk-vod-upload.version>
  45. <fastjson.version>1.2.28</fastjson.version>
  46. <gson.version>2.8.2</gson.version>
  47. <json.version>20170516</json.version>
  48. <commons-dbutils.version>1.7</commons-dbutils.version>
  49. <canal.client.version>1.1.0</canal.client.version>
  50. <docker.image.prefix>zx</docker.image.prefix>
  51. <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
  52. </properties>
  53. <!--子模块继承之后,提供作用:锁定版本+子module不用写groupId和version -->
  54. <dependencyManagement>
  55. <!-- 定义本项目的依赖关系 -->
  56. <dependencies>
  57. <!--spring cloud-->
  58. <dependency>
  59. <groupId>org.springframework.cloud</groupId>
  60. <artifactId>spring-cloud-dependencies</artifactId>
  61. <version>${spring-cloud.version}</version>
  62. <type>pom</type>
  63. <scope>import</scope>
  64. </dependency>
  65. <!--spring cloud alibaba-->
  66. <dependency>
  67. <groupId>com.alibaba.cloud</groupId>
  68. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  69. <version>${spring-cloud-alibaba.version}</version>
  70. <type>pom</type>
  71. <scope>import</scope>
  72. </dependency>
  73. <!--mybatis-plus 持久层-->
  74. <dependency>
  75. <groupId>com.baomidou</groupId>
  76. <artifactId>mybatis-plus-boot-starter</artifactId>
  77. <version>${mybatis-plus.version}</version>
  78. </dependency>
  79. <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
  80. <dependency>
  81. <groupId>org.apache.velocity</groupId>
  82. <artifactId>velocity-engine-core</artifactId>
  83. <version>${velocity.version}</version>
  84. </dependency>
  85. <!--Swagger 用于生成、描述、调用和可视化RESTful 风格的Web服务-->
  86. <dependency>
  87. <groupId>io.springfox</groupId>
  88. <artifactId>springfox-swagger2</artifactId>
  89. <version>${swagger.version}</version>
  90. </dependency>
  91. <!--springfox-swagger-ui 是一个webjar, 方便进行maven集成-->
  92. <dependency>
  93. <groupId>io.springfox</groupId>
  94. <artifactId>springfox-swagger-ui</artifactId>
  95. <version>${swagger.version}</version>
  96. </dependency>
  97. <!--aliyunOSS 是一种海量、安全、低成本、高可靠的云存储服务-->
  98. <dependency>
  99. <groupId>com.aliyun.oss</groupId>
  100. <artifactId>aliyun-sdk-oss</artifactId>
  101. <version>${aliyun.oss.version}</version>
  102. </dependency>
  103. <!--日期时间工具-->
  104. <dependency>
  105. <groupId>joda-time</groupId>
  106. <artifactId>joda-time</artifactId>
  107. <version>${jodatime.version}</version>
  108. </dependency>
  109. <!--提供API给Java程序对Microsoft Office格式档案读和写的功能-->
  110. <!--xls-->
  111. <dependency>
  112. <groupId>org.apache.poi</groupId>
  113. <artifactId>poi</artifactId>
  114. <version>${poi.version}</version>
  115. </dependency>
  116. <!--xlsx-->
  117. <dependency>
  118. <groupId>org.apache.poi</groupId>
  119. <artifactId>poi-ooxml</artifactId>
  120. <version>${poi.version}</version>
  121. </dependency>
  122. <!--文件上传-->
  123. <dependency>
  124. <groupId>commons-fileupload</groupId>
  125. <artifactId>commons-fileupload</artifactId>
  126. <version>${commons-fileupload.version}</version>
  127. </dependency>
  128. <!--commons-io 可以挺提高 IO 功能开发的效率-->
  129. <dependency>
  130. <groupId>commons-io</groupId>
  131. <artifactId>commons-io</artifactId>
  132. <version>${commons-io.version}</version>
  133. </dependency>
  134. <!--httpclient 可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包-->
  135. <dependency>
  136. <groupId>org.apache.httpcomponents</groupId>
  137. <artifactId>httpclient</artifactId>
  138. <version>${httpclient.version}</version>
  139. </dependency>
  140. <!--将Java对象序列化为JSON,反之亦然-->
  141. <dependency>
  142. <groupId>com.google.code.gson</groupId>
  143. <artifactId>gson</artifactId>
  144. <version>${gson.version}</version>
  145. </dependency>
  146. <!-- JWT 用于在JVM和Android上创建和验证JSON Web令牌 -->
  147. <dependency>
  148. <groupId>io.jsonwebtoken</groupId>
  149. <artifactId>jjwt</artifactId>
  150. <version>${jwt.version}</version>
  151. </dependency>
  152. <!--aliyun-->
  153. <!--阿里云的核心SDK-->
  154. <dependency>
  155. <groupId>com.aliyun</groupId>
  156. <artifactId>aliyun-java-sdk-core</artifactId>
  157. <version>${aliyun-java-sdk-core.version}</version>
  158. </dependency>
  159. <!--提供基于网络的数据存取服务-->
  160. <dependency>
  161. <groupId>com.aliyun.oss</groupId>
  162. <artifactId>aliyun-sdk-oss</artifactId>
  163. <version>${aliyun-sdk-oss.version}</version>
  164. </dependency>
  165. <!-- 阿里云SDK点播服务包重构,支持composer方式使用-->
  166. <dependency>
  167. <groupId>com.aliyun</groupId>
  168. <artifactId>aliyun-java-sdk-vod</artifactId>
  169. <version>${aliyun-java-sdk-vod.version}</version>
  170. </dependency>
  171. <!--阿里云视频点播上传客户端-->
  172. <dependency>
  173. <groupId>com.aliyun</groupId>
  174. <artifactId>aliyun-java-vod-upload</artifactId>
  175. <version>${aliyun-java-vod-upload.version}</version>
  176. </dependency>
  177. <!--文件上传的方式和基本流程-->
  178. <dependency>
  179. <groupId>com.aliyun</groupId>
  180. <artifactId>aliyun-sdk-vod-upload</artifactId>
  181. <version>${aliyun-sdk-vod-upload.version}</version>
  182. </dependency>
  183. <!--fastjson 提供服务器端、安卓客户端两种解析工具-->
  184. <dependency>
  185. <groupId>com.alibaba</groupId>
  186. <artifactId>fastjson</artifactId>
  187. <version>${fastjson.version}</version>
  188. </dependency>
  189. <!--json 轻量级的数据交换格式-->
  190. <dependency>
  191. <groupId>org.json</groupId>
  192. <artifactId>json</artifactId>
  193. <version>${json.version}</version>
  194. </dependency>
  195. <!--用于操作数据库的工具包-->
  196. <dependency>
  197. <groupId>commons-dbutils</groupId>
  198. <artifactId>commons-dbutils</artifactId>
  199. <version>${commons-dbutils.version}</version>
  200. </dependency>
  201. <!--实现数据同步-->
  202. <dependency>
  203. <groupId>com.alibaba.otter</groupId>
  204. <artifactId>canal.client</artifactId>
  205. <version>${canal.client.version}</version>
  206. </dependency>
  207. </dependencies>
  208. </dependencyManagement>
  209. <build>
  210. <plugins>
  211. <plugin>
  212. <groupId>org.springframework.boot</groupId>
  213. <artifactId>spring-boot-maven-plugin</artifactId>
  214. <configuration>
  215. <fork>true</fork>
  216. <addResources>true</addResources>
  217. </configuration>
  218. </plugin>
  219. </plugins>
  220. </build>
  221. </project>

2.3 创建子工程

2.3.1 subproject_service服务的子工程

  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>movies</artifactId>
  7. <groupId>com.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>subproject_service</artifactId>
  12. <packaging>pom</packaging>
  13. <dependencies>
  14. <!--nacos客户端-->
  15. <dependency>
  16. <groupId>com.alibaba.cloud</groupId>
  17. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  18. </dependency>
  19. <!--web-->
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-web</artifactId>
  23. </dependency>
  24. <!--mybatis-plus-->
  25. <dependency>
  26. <groupId>com.baomidou</groupId>
  27. <artifactId>mybatis-plus-boot-starter</artifactId>
  28. </dependency>
  29. <!--mysql驱动-->
  30. <dependency>
  31. <groupId>mysql</groupId>
  32. <artifactId>mysql-connector-java</artifactId>
  33. </dependency>
  34. <!--mybatisplus-代码生成器的模板-->
  35. <dependency>
  36. <groupId>org.apache.velocity</groupId>
  37. <artifactId>velocity-engine-core</artifactId>
  38. </dependency>
  39. <!--swagger-->
  40. <dependency>
  41. <groupId>io.springfox</groupId>
  42. <artifactId>springfox-swagger2</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>io.springfox</groupId>
  46. <artifactId>springfox-swagger-ui</artifactId>
  47. </dependency>
  48. <!--lombok-->
  49. <dependency>
  50. <groupId>org.projectlombok</groupId>
  51. <artifactId>lombok</artifactId>
  52. </dependency>
  53. <!--xls-->
  54. <dependency>
  55. <groupId>org.apache.poi</groupId>
  56. <artifactId>poi</artifactId>
  57. </dependency>
  58. <dependency>
  59. <groupId>org.apache.poi</groupId>
  60. <artifactId>poi-ooxml</artifactId>
  61. </dependency>
  62. <dependency>
  63. <groupId>commons-fileupload</groupId>
  64. <artifactId>commons-fileupload</artifactId>
  65. </dependency>
  66. <!--httpCilents-->
  67. <dependency>
  68. <groupId>org.apache.httpcomponents</groupId>
  69. <artifactId>httpclient</artifactId>
  70. </dependency>
  71. <dependency>
  72. <groupId>commons-io</groupId>
  73. <artifactId>commons-io</artifactId>
  74. </dependency>
  75. <!--gson-->
  76. <dependency>
  77. <groupId>com.google.code.gson</groupId>
  78. <artifactId>gson</artifactId>
  79. </dependency>
  80. </dependencies>
  81. </project>

2.3.2 public_common公共工程

  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <!--公共工程-->
  6. <parent>
  7. <artifactId>movies</artifactId>
  8. <groupId>com.example</groupId>
  9. <version>1.0-SNAPSHOT</version>
  10. </parent>
  11. <modelVersion>4.0.0</modelVersion>
  12. <artifactId>public_common</artifactId>
  13. <packaging>pom</packaging>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-web</artifactId>
  18. <scope>provided</scope>
  19. </dependency>
  20. <!--mybatis-plus-->
  21. <dependency>
  22. <groupId>com.baomidou</groupId>
  23. <artifactId>mybatis-plus-boot-starter</artifactId>
  24. <scope>provided</scope>
  25. </dependency>
  26. <!--为了简化实体类,配置lombok-->
  27. <dependency>
  28. <groupId>org.projectlombok</groupId>
  29. <artifactId>lombok</artifactId>
  30. </dependency>
  31. <!--为了测试前后端分离项目 swagger-->
  32. <dependency>
  33. <groupId>io.springfox</groupId>
  34. <artifactId>springfox-swagger2</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>io.springfox</groupId>
  38. <artifactId>springfox-swagger-ui</artifactId>
  39. </dependency>
  40. <!--二级缓存配置-->
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-starter-data-redis</artifactId>
  44. </dependency>
  45. </dependencies>
  46. </project>

2.3.3 在public_common下创建子模块common_util和service_base

在创建模块时,注意路径是否在public_common模块下

2.3.3.1 创建service_base模块

  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>public_common</artifactId>
  7. <groupId>com.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>service_base</artifactId>
  12. <dependencies>
  13. <!--添加工具模块-->
  14. <dependency>
  15. <groupId>com.example</groupId>
  16. <artifactId>common_util</artifactId>
  17. <version>1.0-SNAPSHOT</version>
  18. </dependency>
  19. </dependencies>
  20. </project>

2.3.3.2 创建common_util模块

image.png

2.3.4 在service_base中编写配置swagger类

  1. package com.example.movies.config;
  2. import com.google.common.base.Predicates;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import springfox.documentation.builders.ApiInfoBuilder;
  6. import springfox.documentation.builders.PathSelectors;
  7. import springfox.documentation.service.ApiInfo;
  8. import springfox.documentation.service.Contact;
  9. import springfox.documentation.spi.DocumentationType;
  10. import springfox.documentation.spring.web.plugins.Docket;
  11. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  12. /**
  13. * 配置类.
  14. */
  15. @Configuration
  16. @EnableSwagger2
  17. public class SwaggerConfig {
  18. /**
  19. * 构建api文档的详细信息函数
  20. * @return
  21. */
  22. private ApiInfo webApiInfo() {
  23. return new ApiInfoBuilder()
  24. // 页面标题
  25. .title("Api文档")
  26. // 描述
  27. .description("描述微服务接口定义")
  28. // 版本号
  29. .version("1.0")
  30. // 创建人
  31. .contact(new Contact("java", "http://zmxdata.com", "hksupports@163.com"))
  32. .build();
  33. }
  34. /**
  35. * 构建接口文档
  36. * @return
  37. */
  38. @Bean
  39. public Docket webApiConfig() {
  40. return new Docket(DocumentationType.SWAGGER_2)
  41. // 分组名
  42. .groupName("webApi")
  43. // 设置描述文件
  44. .apiInfo(webApiInfo())
  45. // 用来等待文件描述词状态的改变
  46. .select()
  47. // 只显示api路径下的页面
  48. .paths(Predicates.not(PathSelectors.regex("/error.*")))
  49. .build();
  50. }
  51. }

2.4 在subproject_service模块中创建子模块service_actor

  • 演员模块

image.png

  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>xiudun_service</artifactId>
  7. <groupId>com.xiudun</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>service_actor</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.alibaba</groupId>
  15. <artifactId>easyexcel</artifactId>
  16. <version>2.1.1</version>
  17. </dependency>
  18. </dependencies>
  19. </project>

2.4.1 配置公共部分

  • 主配置类
  1. package com.example.movies;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  6. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  7. /**
  8. * 主配置类.
  9. */
  10. @SpringBootApplication
  11. @EnableSwagger2
  12. @EnableDiscoveryClient
  13. @MapperScan("com.example.movies.mapper")
  14. public class ActorApplication {
  15. public static void main(String[] args) {
  16. SpringApplication.run(ActorApplication.class, args);
  17. }
  18. }
  • 配置文件:application.yml,注意:冒号后必须有空格,这是编写格式
  1. server:
  2. # 端口
  3. port: 8081
  4. spring:
  5. application:
  6. # 服务名
  7. name: service-actor
  8. profiles:
  9. # 开发环境
  10. active: dev
  11. # mysql 配置
  12. datasource:
  13. driver-class-name: com.mysql.cj.jdbc.Driver
  14. url: jdbc:mysql://localhost:3306/movies?useUnicode=true&characterEncoding=utf8$serverTimezone=GMT%2B8ame
  15. username: root
  16. password: 123456
  17. # json 的时间格式
  18. jackson:
  19. date-format: yyyy-MM-dd HH:mm:ss
  20. time-zone: GMT+8
  21. # 注册中心
  22. cloud:
  23. nacos:
  24. discovery:
  25. server-addr: localhost:8848

image.png

image.png

第三章:编写演员模块

3.1 生成数据库

  1. #
  2. # 影视图片表
  3. #
  4. CREATE TABLE `crm_banner` (
  5. `id` char(19) NOT NULL DEFAULT '' COMMENT 'ID',
  6. `title` varchar(20) DEFAULT '' COMMENT '标题',
  7. `image_url` varchar(500) NOT NULL DEFAULT '' COMMENT '图片地址',
  8. `link_url` varchar(500) DEFAULT '' COMMENT '链接地址',
  9. `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
  10. `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  11. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  12. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  13. PRIMARY KEY (`id`),
  14. UNIQUE KEY `uk_name` (`title`)
  15. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页banner表';
  16. #
  17. # Data for table "crm_banner"
  18. #
  19. INSERT INTO `crm_banner` VALUES ('1194556896025845762','test1',
  20. 'https://xxx.jpg',
  21. '/course',1,0,'2019-11-13 18:05:32','2019-11-18 10:28:22'),('1194607458461216769','test2',
  22. 'https://xxxx.jpg',
  23. '/teacher',2,0,'2019-11-13 21:26:27','2019-11-14 09:12:15');
  24. #
  25. # 影视片段表
  26. #
  27. CREATE TABLE `edu_chapter` (
  28. `id` char(19) NOT NULL COMMENT '片段ID',
  29. `movies_id` char(19) NOT NULL COMMENT '影视ID',
  30. `title` varchar(50) NOT NULL COMMENT '片段名称',
  31. `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '显示排序',
  32. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  33. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  34. PRIMARY KEY (`id`),
  35. KEY `idx_movies_id` (`movies_id`)
  36. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='影视';
  37. #
  38. # Data for table "edu_chapter"
  39. #
  40. INSERT INTO `edu_chapter` VALUES ('1','14','第一片段:剧情介绍',0,'2019-01-01 12:27:40','2019-01-01 12:55:30'),
  41. ('1181729226915577857','18','故事发生的时间',70,'2019-10-09 08:32:58','2019-10-09 08:33:20'),
  42. ('1192252428399751169','1192252213659774977','第一段',0,'2019-11-07 09:28:25','2019-11-07 09:28:25'),
  43. ('15','18','第一片段:演员入戏',0,'2019-01-01 12:27:40','2019-10-09 09:13:19'),
  44. ('3','14','第二片段:演员公司入职',0,'2019-01-01 12:55:35','2019-01-01 12:27:40'),
  45. ('44','18','第三片段:演员学习',0,'2019-01-01 12:27:40','2019-01-01 12:27:40');
  46. #
  47. # 影片表
  48. #
  49. CREATE TABLE `edu_movies` (
  50. `id` char(19) NOT NULL COMMENT '影视ID',
  51. `actor_id` char(19) NOT NULL COMMENT '影视演员ID',
  52. `subject_id` char(19) NOT NULL COMMENT '影视专业ID',
  53. `subject_parent_id` char(19) NOT NULL COMMENT '影视专业父级ID',
  54. `title` varchar(50) NOT NULL COMMENT '影视标题',
  55. `price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '影视销售价格,设置为0则可免费观看',
  56. `video_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '总段数',
  57. `cover` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '影视封面图片路径',
  58. `buy_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '销售数量',
  59. `view_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '浏览数量',
  60. `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
  61. `status` varchar(10) NOT NULL DEFAULT 'Draft' COMMENT '影视状态 Draft未发布 Normal已发布',
  62. `is_deleted` tinyint(3) DEFAULT NULL COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  63. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  64. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  65. PRIMARY KEY (`id`),
  66. KEY `idx_title` (`title`),
  67. KEY `idx_subject_id` (`subject_id`),
  68. KEY `idx_actor_id` (`actor_id`)
  69. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='影视';
  70. #
  71. # Data for table "edu_movies"
  72. #
  73. INSERT INTO `edu_movies` VALUES ('1192252213659774977','1189389726308478977','1178214681139539969',
  74. '1178214681118568449','基础影视:test',0.01,2,
  75. 'https://xxx.gif',4,387,1,'Normal',0,'2019-11-07 09:27:33','2019-11-18 13:35:03'),
  76. ('14','1189389726308478977','1101348944971091969','1101348944920760321','我的祖国',0.00,3,
  77. 'http://xxxx.jpg',3,44,15,'Normal',0,'2018-04-02 18:33:34','2019-11-16 21:21:45'),
  78. ('15','1189389726308478977','1101348944971091969','1101348944920760321','冠军',0.00,23,
  79. 'http://xxx.jpg',0,51,17,'Normal',0,'2018-04-02 18:34:32','2019-11-12 10:19:20'),
  80. ('18','1189389726308478977','1178214681139539969','1178214681118568449','战狼2',0.01,20,
  81. 'http://xxx.jpg',151,737,6,'Normal',0,'2018-04-02 21:28:46','2019-11-18 11:14:52');
  82. #
  83. # 影视收藏表
  84. #
  85. CREATE TABLE `edu_movies_collect` (
  86. `id` char(19) NOT NULL COMMENT '收藏ID',
  87. `movies_id` char(19) NOT NULL COMMENT '影视演员ID',
  88. `member_id` char(19) NOT NULL DEFAULT '' COMMENT '影视专业ID',
  89. `is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  90. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  91. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  92. PRIMARY KEY (`id`)
  93. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='影视收藏';
  94. #
  95. # Data for table "edu_movies_collect"
  96. #
  97. INSERT INTO `edu_movies_collect` VALUES ('1196269345666019330','1192252213659774977','1',1,'2019-11-18 11:30:12','2019-11-18 11:30:12');
  98. #
  99. # 影视简介表
  100. #
  101. CREATE TABLE `edu_movies_description` (
  102. `id` char(19) NOT NULL COMMENT '影视ID',
  103. `description` text COMMENT '影视简介',
  104. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  105. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  106. PRIMARY KEY (`id`)
  107. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='影视简介';
  108. #
  109. # Data for table "edu_movies_description"
  110. #
  111. INSERT INTO `edu_movies_description` VALUES ('1104870479077879809','<p>11</p>','2019-03-11 06:23:44',
  112. '2019-03-11 06:23:44'),('1192252213659774977','<p>影片</p>','2019-11-07 09:27:33','2019-11-13 16:21:28'),
  113. ('14','','2019-03-13 06:04:43','2019-03-13 06:05:33'),('15','','2019-03-13 06:03:33','2019-03-13 06:04:22'),
  114. ('18','<p>本影片介绍了中国的历史</p>','2019-03-06 18:06:36','2019-10-30 19:58:36');
  115. #
  116. # 影视类别表
  117. #
  118. CREATE TABLE `edu_subject` (
  119. `id` char(19) NOT NULL COMMENT '影视类别ID',
  120. `title` varchar(10) NOT NULL COMMENT '类别名称',
  121. `parent_id` char(19) NOT NULL DEFAULT '0' COMMENT '父ID',
  122. `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
  123. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  124. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  125. PRIMARY KEY (`id`),
  126. KEY `idx_parent_id` (`parent_id`)
  127. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='影视科目';
  128. #
  129. # Data for table "edu_subject"
  130. #
  131. INSERT INTO `edu_subject` VALUES ('1178214681118568449','战争','0',1,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  132. ('1178214681139539969','爱情','1178214681118568449',1,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  133. ('1178214681181483010','城市剧','0',3,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  134. ('1178214681210843137','伦理剧','1178214681181483010',4,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  135. ('1178214681231814658','喜剧','0',5,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  136. ('1178214681252786178','武打片','1178214681231814658',5,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  137. ('1178214681294729217','纪录片','1178214681231814658',6,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  138. ('1178214681324089345','连续剧','0',7,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  139. ('1178214681353449473','战争1','1178214681324089345',7,'2019-09-29 15:47:25','2019-09-29 15:47:25'),
  140. ('1178214681382809602','言情剧','1178214681324089345',8,'2019-09-29 15:47:25','2019-09-29 15:47:25');
  141. #
  142. # 演员(主演)表
  143. #
  144. CREATE TABLE `edu_actor` (
  145. `id` char(19) NOT NULL COMMENT '演员ID',
  146. `name` varchar(20) NOT NULL COMMENT '演员姓名',
  147. `intro` varchar(500) NOT NULL DEFAULT '' COMMENT '演员简介',
  148. `career` varchar(500) DEFAULT NULL COMMENT '演员资历,一句话说明演员',
  149. `level` int(10) unsigned NOT NULL COMMENT '头衔 1高级演员 2首席演员',
  150. `avatar` varchar(255) DEFAULT NULL COMMENT '演员头像',
  151. `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
  152. `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  153. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  154. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  155. PRIMARY KEY (`id`),
  156. UNIQUE KEY `uk_name` (`name`)
  157. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='演员';
  158. #
  159. # Data for table "edu_actor"
  160. #
  161. INSERT INTO `edu_actor` VALUES ('1','张三','一级演员','一线',1,'https://xx/avatar/default.jpg',0,0,
  162. '2019-10-30 14:18:46','2019-11-12 13:36:36'),
  163. ('1189389726308478977','李四','一级演员','一线',2,'https://xxx/actor/11.png',1,0,'2019-10-30 11:53:03',
  164. '2019-10-30 11:53:03'),('1189390295668469762','李刚','高级演员简介','高级演员',2,
  165. 'https://xxx.png',2,0,'2019-10-30 11:55:19','2019-11-12 13:37:52'),
  166. ('1189426437876985857','王二','高级演员简介','高级演员',1,'https://2ec96ef88.png',0,0,'2019-10-30 14:18:56',
  167. '2019-11-12 13:37:35'),('1189426464967995393','王五','高级演员简介','高级演员',1,
  168. 'https://xxxx.png',0,0,'2019-10-30 14:19:02','2019-11-12 13:37:18'),
  169. ('1192249914833055746','李四1','高级演员简介','高级演员',1,'https://d825d.png',0,0,
  170. '2019-11-07 09:18:25','2019-11-12 13:37:01'),
  171. ('1192327476087115778','1222-12-12','1111','11',1,'https://fd8a4.png',0,1,
  172. '2019-11-07 14:26:37','2019-11-11 16:26:26'),
  173. ('1195337453429129218','test','sdfsdf','sdfdf',1,'https://default.jpg',0,1,
  174. '2019-11-15 21:47:12','2019-11-15 21:47:27');
  175. #
  176. # 电影视频表
  177. #
  178. CREATE TABLE `edu_video` (
  179. `id` char(19) NOT NULL COMMENT '视频ID',
  180. `movies_id` char(19) NOT NULL COMMENT '影视ID',
  181. `chapter_id` char(19) NOT NULL COMMENT '章节ID',
  182. `title` varchar(50) NOT NULL COMMENT '节点名称',
  183. `video_source_id` varchar(100) DEFAULT NULL COMMENT '云端视频资源',
  184. `video_original_name` varchar(100) DEFAULT NULL COMMENT '原始文件名称',
  185. `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
  186. `play_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '播放次数',
  187. `is_free` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否可以试听:0收费 1免费',
  188. `duration` float NOT NULL DEFAULT '0' COMMENT '视频时长(秒)',
  189. `status` varchar(20) NOT NULL DEFAULT 'Empty' COMMENT 'Empty未上传 Transcoding转码中 Normal正常',
  190. `size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '视频源文件大小(字节)',
  191. `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
  192. `gmt_create` datetime NOT NULL COMMENT '创建时间',
  193. `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  194. PRIMARY KEY (`id`),
  195. KEY `idx_movies_id` (`movies_id`),
  196. KEY `idx_chapter_id` (`chapter_id`)
  197. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='影视视频';
  198. #
  199. # Data for table "edu_video"
  200. #
  201. INSERT INTO `edu_video` VALUES ('1182499307429339137','18','32','第一节','','',0,0,0,0,'',0,1,
  202. '2019-10-11 11:32:59','2019-10-11 11:57:38'),('1185312444399071234','14','1','12','','',0,0,0,0,
  203. 'Empty',0,1,'2019-10-19 05:51:23','2019-10-19 05:51:33'),('1189434737808990210','18','44','测试','','',
  204. 1,0,0,0,'Empty',0,1,'2019-10-30 14:51:55','2019-10-30 14:51:55'),
  205. ('1189471423678939138','18','1181729226915577857','test','2b887dc9584d4dc68908780ec57cd3b9',
  206. '视频',1,0,0,0,'Empty',0,1,'2019-10-30 17:17:41','2019-10-30 17:17:41'),
  207. ('1189476403626409986','18','1181729226915577857','22','5155c73dc112475cbbddccf4723f7cef',
  208. '视频.mp4',0,0,0,0,'Empty',0,1,'2019-10-30 17:37:29','2019-10-30 17:37:29'),
  209. ('1192252824606289921','1192252213659774977','1192252428399751169','第一段落',
  210. '756cf06db9cb4f30be85a9758b19c645','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,0,0,
  211. 'Empty',0,1,'2019-11-07 09:29:59','2019-11-07 09:29:59'),
  212. ('1192628092797730818','1192252213659774977','1192252428399751169','第二段落',
  213. '2a02d726622f4c7089d44cb993c531e1','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,1,0,
  214. 'Empty',0,1,'2019-11-08 10:21:10','2019-11-08 10:21:22'),
  215. ('1192632495013380097','1192252213659774977','1192252428399751169','第三段落',
  216. '4e560c892fdf4fa2b42e0671aa42fa9d','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,1,0,'Empty',0,1,
  217. '2019-11-08 10:38:40','2019-11-08 10:38:40'),('1194117638832111617','1192252213659774977','1192252428399751169',
  218. '第四段落','4e560c892fdf4fa2b42e0671aa42fa9d','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,0,0,'Empty',0
  219. ,1,'2019-11-12 13:00:05','2019-11-12 13:00:05'),('1196263770832023554','1192252213659774977',
  220. '1192252428399751169','第五段落','27d21158b0834cb5a8d50710937de330','eae2b847ef8503b81f5d5593d769dde2.mp4',
  221. 5,0,0,0,'Empty',0,1,'2019-11-18 11:08:03','2019-11-18 11:08:03'),
  222. ('17','18','15','第一节:喜剧','196116a6fee742e1ba9f6c18f65bd8c1','1',1,1000,1,100,'喜剧1',
  223. 0,1,'2019-01-01 13:08:57','2019-10-11 11:26:39'),('18','18','15','第二节:爱情剧',
  224. '2d99b08ca0214909899910c9ba042d47','爱情剧',2,999,1,100,'爱情剧',0,1,'2019-01-01 13:09:02','2019-03-08 03:30:27'),
  225. ('19','18','15','第三节:爱情剧','51120d59ddfd424cb5ab08b44fc8b23a',
  226. 'eae2b847ef8503b81f5d5593d769dde2.mp4',3,888,0,100,'Draft',0,1,'2019-01-01 13:09:05','2019-11-12 12:50:45'),
  227. ('20','18','15','第四节:爱情剧','2a38988892d84df598752226c50f3fa3','爱情剧总结.avi',4,666,0,100,
  228. 'Draft',0,1,'2019-01-01 13:09:05','2019-10-11 09:20:09');

image.png

3.2 代码生成器

  1. package com.example.movies;
  2. import com.baomidou.mybatisplus.annotation.DbType;
  3. import com.baomidou.mybatisplus.annotation.IdType;
  4. import com.baomidou.mybatisplus.generator.AutoGenerator;
  5. import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
  6. import com.baomidou.mybatisplus.generator.config.GlobalConfig;
  7. import com.baomidou.mybatisplus.generator.config.PackageConfig;
  8. import com.baomidou.mybatisplus.generator.config.StrategyConfig;
  9. import com.baomidou.mybatisplus.generator.config.rules.DateType;
  10. import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
  11. import org.junit.Test;
  12. /**
  13. * 自动生成代码.
  14. * 记得修改配置信息.
  15. */
  16. public class CodeGenerator {
  17. @Test
  18. public void run() {
  19. // 1、创建代码生成器
  20. AutoGenerator mpg = new AutoGenerator();
  21. // 2、全局配置
  22. GlobalConfig gc = new GlobalConfig();
  23. String projectPath = System.getProperty("user.dir");
  24. gc.setOutputDir(projectPath + "/src/main/java");
  25. gc.setAuthor("yourName");
  26. gc.setOpen(false); //生成后是否打开资源管理器
  27. gc.setFileOverride(false); //重新生成时文件是否覆盖
  28. gc.setServiceName("%sService"); //去掉Service接口的首字母I
  29. gc.setIdType(IdType.ID_WORKER_STR); //主键策略
  30. gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
  31. gc.setSwagger2(true);//开启Swagger2模式
  32. mpg.setGlobalConfig(gc);
  33. // 3、数据源配置
  34. DataSourceConfig dsc = new DataSourceConfig();
  35. dsc.setUrl("jdbc:mysql://localhost:3306/movies?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
  36. dsc.setDriverName("com.mysql.cj.jdbc.Driver");
  37. dsc.setUsername("root");
  38. dsc.setPassword("123456");
  39. dsc.setDbType(DbType.MYSQL);
  40. mpg.setDataSource(dsc);
  41. // 4、包配置
  42. PackageConfig pc = new PackageConfig();
  43. pc.setModuleName("movies"); //模块名
  44. pc.setParent("com.example");
  45. pc.setController("controller");
  46. pc.setEntity("entity");
  47. pc.setService("service");
  48. pc.setMapper("mapper");
  49. mpg.setPackageInfo(pc);
  50. // 5、策略配置
  51. StrategyConfig strategy = new StrategyConfig();
  52. strategy.setInclude("edu_actor", "edu_movies"); // 表名称
  53. strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
  54. strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
  55. strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
  56. strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
  57. strategy.setRestControllerStyle(true); //restful api风格控制器
  58. strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
  59. mpg.setStrategy(strategy);
  60. // 6、执行
  61. mpg.execute();
  62. }
  63. }

3.3 编写演员的功能实现

3.3.1 dao数据访问层

  1. package com.example.movies.mapper;
  2. import com.example.movies.entity.EduActor;
  3. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4. /**
  5. * <p>
  6. * 演员 Mapper 接口
  7. * </p>
  8. */
  9. public interface EduActorMapper extends BaseMapper<EduActor> {
  10. }

3.3.2 service业务层

  • 接口
  1. package com.example.movies.service;
  2. import com.example.movies.entity.EduActor;
  3. import com.baomidou.mybatisplus.extension.service.IService;
  4. /**
  5. * <p>
  6. * 演员 服务类
  7. * </p>
  8. */
  9. public interface EduActorService extends IService<EduActor> {
  10. }
  • 实现类
  1. package com.example.movies.service.impl;
  2. import com.example.movies.entity.EduActor;
  3. import com.example.movies.mapper.EduActorMapper;
  4. import com.example.movies.service.EduActorService;
  5. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  6. import org.springframework.stereotype.Service;
  7. /**
  8. * <p>
  9. * 演员 服务实现类
  10. * </p>
  11. */
  12. @Service
  13. public class EduActorServiceImpl extends ServiceImpl<EduActorMapper, EduActor> implements EduActorService {
  14. }

3.3.3 controller控制层

  1. package com.example.movies.controller;
  2. import com.example.movies.service.EduActorService;
  3. import io.swagger.annotations.ApiOperation;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import java.util.List;
  9. /**
  10. * <p>
  11. * 演员 前端控制器
  12. * </p>
  13. */
  14. @RestController
  15. @RequestMapping("/movies/edu-actor")
  16. public class EduActorController {
  17. /**
  18. * 依赖注册service
  19. */
  20. @Autowired
  21. private EduActorService actorService;
  22. @ApiOperation("显示演员信息")
  23. @GetMapping("/list")
  24. public List list(){
  25. return actorService.list(null);
  26. }
  27. }

3.4 编写逻辑删除(假删除)拦截器、编写分页的拦截器

  • service_base模块中编写
  1. package com.example.movies.config;
  2. import com.baomidou.mybatisplus.core.injector.ISqlInjector;
  3. import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
  4. import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.transaction.annotation.EnableTransactionManagement;
  8. /**
  9. * 配置拦截器.
  10. */
  11. @Configuration
  12. @EnableTransactionManagement
  13. public class MoviesConfig {
  14. /**
  15. * 逻辑删除插件
  16. * @return
  17. */
  18. @Bean
  19. public ISqlInjector sqlInjector() {
  20. return new LogicSqlInjector();
  21. }
  22. /**
  23. * 分页插件
  24. * @return
  25. */
  26. @Bean
  27. public PaginationInterceptor paginationInterceptor() {
  28. return new PaginationInterceptor();
  29. }
  30. }

3.5 在service_base模块中完成自动填充

  • 就是自动完成数据添加的时间和修改的时间及逻辑删除的默认值填写

  • 编写实现

  1. package com.example.movies.config;
  2. import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
  3. import org.apache.ibatis.reflection.MetaObject;
  4. import org.springframework.stereotype.Component;
  5. import java.util.Date;
  6. @Component
  7. public class MyMetaObjectHandler implements MetaObjectHandler {
  8. @Override
  9. public void insertFill(MetaObject metaObject) { // 添加数据
  10. // 添加日期到指定的字段中
  11. setFieldValByName("gmtCreate", new Date(), metaObject);
  12. setFieldValByName("gmtModified", new Date(), metaObject);
  13. // 逻辑删除的默认值
  14. setFieldValByName("isDeleted", 0, metaObject);
  15. }
  16. @Override
  17. public void updateFill(MetaObject metaObject) { // 修改数据
  18. setFieldValByName("gmtModified", new Date(), metaObject);
  19. }
  20. }
  • 添加一个日志输出,方便代码的调试
  1. #配置日志
  2. mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  • 在对应的实体Bean中添加

image.png

  1. package com.example.movies.controller;
  2. import com.example.movies.entity.EduActor;
  3. import com.example.movies.service.EduActorService;
  4. import io.swagger.annotations.ApiOperation;
  5. import io.swagger.annotations.ApiParam;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.web.bind.annotation.*;
  8. import java.util.List;
  9. /**
  10. * <p>
  11. * 演员 前端控制器
  12. * </p>
  13. */
  14. @RestController
  15. @RequestMapping("/movies/edu-actor")
  16. public class EduActorController {
  17. /**
  18. * 依赖注册service
  19. */
  20. @Autowired
  21. private EduActorService actorService;
  22. @ApiOperation("显示演员信息")
  23. @GetMapping("/list")
  24. public List list(){
  25. return actorService.list(null);
  26. }
  27. @ApiOperation("保存演员数据")
  28. @PostMapping("/save")
  29. public void saveActor(EduActor actor) {
  30. actorService.save(actor);
  31. }
  32. @ApiOperation("逻辑删除演员")
  33. @DeleteMapping("del/{id}")
  34. public void delect(@ApiParam(name = "id", value = "演员的主键", required = true) @PathVariable String id) {
  35. actorService.removeById(id);
  36. }
  37. }

3.6 在subProject_service模块中添加依赖

  1. <!--添加依赖-->
  2. <dependency>
  3. <groupId>com.example</groupId>
  4. <artifactId>service_base</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>

第四章:配置公共设置

4.1 公共的返回数据格式

  • 在我们的项目中要求都是封装成json返回,一般我们会将所有接口的数据格式统一。
  • 列表格式:
  1. {
  2. "success":true,
  3. "code":20000,
  4. "message":"成功",
  5. "data":{
  6. "items":[
  7. {
  8. "id":"1001",
  9. "name":"刘德华",
  10. "intro":"香港的著名演员"
  11. }
  12. ]
  13. }
  14. }
  • 分页格式:
  1. {
  2. "success":true,
  3. "code":20000,
  4. "message":"成功",
  5. "data":{
  6. "total":20,
  7. "rows":[
  8. {
  9. "id":"1001",
  10. "name":"刘德华",
  11. "intro":"香港的著名演员"
  12. }
  13. ]
  14. }
  15. }
  • 无返回数据格式
  1. {
  2. "success":true,
  3. "code":20000,
  4. "message":"成功",
  5. "data":{}
  6. }
  • 失败数据格式
  1. {
  2. "success":false,
  3. "code":20001,
  4. "message":"失败",
  5. "data":{}
  6. }
  • 统一格式:
  1. {
  2. "success":布尔,//响应是否成功
  3. "code":数字,//响应码
  4. "message":字符串,//返回信息
  5. "data":HashMap//返回数据
  6. }

4.2 在子模块public_common/common_util中创建

  • 创建返回码的接口
  1. package com.example.util;
  2. /**
  3. * 返回码接口.
  4. */
  5. public interface ResultCode {
  6. /**
  7. * 成功
  8. */
  9. int SUCCESS = 20000;
  10. /**
  11. * 失败
  12. */
  13. int ERROR = 20001;
  14. }
  • 创建结果类
  1. package com.example.util;
  2. import io.swagger.annotations.ApiModelProperty;
  3. import lombok.Data;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. /**
  7. * 结果类.
  8. */
  9. @Data
  10. public class R {
  11. @ApiModelProperty("是否成功")
  12. private Boolean success;
  13. @ApiModelProperty("返回码")
  14. private Integer code;
  15. @ApiModelProperty("返回消息")
  16. private String message;
  17. @ApiModelProperty("返回数据")
  18. private Map<String, Object> data = new HashMap<>();
  19. public R() { } // 构造方法
  20. /**
  21. * 静态方法
  22. */
  23. public static R ok() {
  24. R r = new R();
  25. r.setSuccess(true);
  26. r.setCode(ResultCode.SUCCESS);
  27. r.setMessage("成功");
  28. return r;
  29. }
  30. public static R error() {
  31. R r = new R();
  32. r.setSuccess(false);
  33. r.setCode(ResultCode.ERROR);
  34. r.setMessage("失败");
  35. return r;
  36. }
  37. public R success(boolean success) {
  38. this.setSuccess(success);
  39. return this;
  40. }
  41. public R message(String message) {
  42. this.setMessage(message);
  43. return this;
  44. }
  45. public R code(int code) {
  46. this.setCode(code);
  47. return this;
  48. }
  49. public R data(String key, Object value) {
  50. this.data.put(key, value);
  51. return this;
  52. }
  53. public R data(Map<String, Object> map) {
  54. this.data(map);
  55. return this;
  56. }
  57. }

4.2.1 在使用的模块中引入即可

  1. <!--添加工具模块-->
  2. <dependency>
  3. <groupId>com.example</groupId>
  4. <artifactId>common_util</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>
  • 修改演员的controller
  1. package com.example.movies.controller;
  2. import com.example.movies.entity.EduActor;
  3. import com.example.movies.service.EduActorService;
  4. import com.example.util.R;
  5. import io.swagger.annotations.ApiOperation;
  6. import io.swagger.annotations.ApiParam;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.*;
  9. import java.util.List;
  10. /**
  11. * <p>
  12. * 演员 前端控制器
  13. * </p>
  14. */
  15. @RestController
  16. @RequestMapping("/movies/edu-actor")
  17. public class EduActorController {
  18. /**
  19. * 依赖注册service
  20. */
  21. @Autowired
  22. private EduActorService actorService;
  23. @ApiOperation("显示演员信息")
  24. @GetMapping("/list")
  25. public R list(){
  26. return R.ok().data("items",actorService.list(null));
  27. }
  28. @ApiOperation("保存演员数据")
  29. @PostMapping("/save")
  30. public R saveActor(EduActor actor){
  31. actorService.save(actor);
  32. return R.ok();
  33. }
  34. @ApiOperation("逻辑删除演员")
  35. @DeleteMapping("del/{id}")
  36. public R delete(@ApiParam(name = "id",value = "演员的主键",required = true)
  37. @PathVariable String id){
  38. actorService.removeById(id);
  39. return R.ok();
  40. }
  41. }

4.2.2 数据的分页

  • service
  1. package com.example.movies.service;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.example.movies.entity.EduActor;
  4. import com.baomidou.mybatisplus.extension.service.IService;
  5. import java.util.Map;
  6. /**
  7. * <p>
  8. * 演员 服务类
  9. * </p>
  10. */
  11. public interface EduActorService extends IService<EduActor> {
  12. /**
  13. * 分页
  14. * @param page
  15. * @return
  16. */
  17. Map<String,Object> getActorList(Page<EduActor> page);
  18. }
  • serviceImpl
  1. package com.example.movies.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.example.movies.entity.EduActor;
  5. import com.example.movies.mapper.EduActorMapper;
  6. import com.example.movies.service.EduActorService;
  7. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  8. import org.springframework.stereotype.Service;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12. /**
  13. * <p>
  14. * 演员 服务实现类
  15. * </p>
  16. */
  17. @Service
  18. public class EduActorServiceImpl extends ServiceImpl<EduActorMapper, EduActor> implements EduActorService {
  19. @Override
  20. public Map<String, Object> getActorList(Page<EduActor> page) {
  21. // 分页条件
  22. QueryWrapper<EduActor> wrapper = new QueryWrapper<>();
  23. wrapper.orderByDesc("id");
  24. baseMapper.selectPage(page,wrapper);
  25. // 当前页、总的页数、每页记录数、总的记录数
  26. long current = page.getCurrent();
  27. long pages = page.getPages();
  28. long size = page.getSize();
  29. long total = page.getTotal();
  30. // 记录
  31. List<EduActor> records = page.getRecords();
  32. // 上一页、下一页
  33. boolean hasPrevious = page.hasPrevious();
  34. boolean hasNext = page.hasNext();
  35. // 添加分页条件
  36. Map<String, Object> map = new HashMap<>();
  37. map.put("current", current);
  38. map.put("pages", pages);
  39. map.put("size", size);
  40. map.put("total", total);
  41. map.put("records", records);
  42. map.put("hasPrevious", hasPrevious);
  43. map.put("hasNext", hasNext);
  44. return map;
  45. }
  46. }
  • controller
  1. package com.example.movies.controller;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.example.movies.entity.EduActor;
  4. import com.example.movies.service.EduActorService;
  5. import com.example.util.R;
  6. import io.swagger.annotations.Api;
  7. import io.swagger.annotations.ApiOperation;
  8. import io.swagger.annotations.ApiParam;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.web.bind.annotation.*;
  11. import java.util.List;
  12. /**
  13. * <p>
  14. * 演员 前端控制器
  15. * </p>
  16. */
  17. @RestController
  18. @RequestMapping("/movies/edu-actor")
  19. public class EduActorController {
  20. /**
  21. * 依赖注册service
  22. */
  23. @Autowired
  24. private EduActorService actorService;
  25. @ApiOperation("显示演员信息")
  26. @GetMapping("/list")
  27. public R list(){
  28. return R.ok().data("items",actorService.list(null));
  29. }
  30. @ApiOperation("保存演员数据")
  31. @PostMapping("/save")
  32. public R saveActor(EduActor actor){
  33. actorService.save(actor);
  34. return R.ok();
  35. }
  36. @ApiOperation("逻辑删除演员")
  37. @DeleteMapping("del/{id}")
  38. public R delete(@ApiParam(name = "id",value = "演员的主键",required = true)
  39. @PathVariable String id){
  40. actorService.removeById(id);
  41. return R.ok();
  42. }
  43. @ApiOperation("演员分页")
  44. @GetMapping("{page}/{limit}")
  45. public R listPage(@ApiParam(name = "page",value = "当前页码",required = true)
  46. @PathVariable long page,
  47. @ApiParam(name = "limit",value = "每页显示的记录数",required = true)
  48. @PathVariable long limit){
  49. // 添加分页参数
  50. Page<EduActor> actorPage = new Page<>(page, limit);
  51. actorService.getActorList(actorPage);
  52. List<EduActor> records = actorPage.getRecords();
  53. long total = actorPage.getTotal();
  54. //返回
  55. return R.ok().data("total", total).data("rows", records);
  56. }
  57. }

4.2.3 演员完成条件查询

  • 根据演员的名称、头衔level、演员的演戏时间

4.2.3.1 创建一个查询类

  1. package com.example.movies.query;
  2. import io.swagger.annotations.ApiModel;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. /**
  6. * 演员的查询条件类.
  7. */
  8. @Data
  9. @ApiModel(value = "actor查询对象", description = "把演员的查询条件封装")
  10. public class ActorQuery {
  11. @ApiModelProperty(value = "演员的名称,模糊查询")
  12. private String name;
  13. @ApiModelProperty(value = "头衔,1表示一线")
  14. private Integer level;
  15. @ApiModelProperty(value = "开始查询时间", example = "2020-02-25 14:19:02")
  16. private String begin;
  17. @ApiModelProperty(value = "结束查询时间", example = "2020-02-25 14:19:02")
  18. private String end;
  19. }

4.2.3.2 业务编写

  • service
  1. package com.example.movies.service;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.example.movies.entity.EduActor;
  4. import com.baomidou.mybatisplus.extension.service.IService;
  5. import com.example.movies.query.ActorQuery;
  6. import java.util.Map;
  7. /**
  8. * <p>
  9. * 演员 服务类
  10. * </p>
  11. */
  12. public interface EduActorService extends IService<EduActor> {
  13. /**
  14. * 演员分页
  15. * @param page 分页条件
  16. * @return
  17. */
  18. Map<String,Object> getActorList(Page<EduActor> page);
  19. /**
  20. * 演员条件分页
  21. * @param page 分页条件
  22. * @param actorQuery 查询条件
  23. */
  24. void pageQuery(Page<EduActor> page, ActorQuery actorQuery);
  25. }
  • serviceImpl
  1. package com.example.movies.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.example.movies.entity.EduActor;
  5. import com.example.movies.mapper.EduActorMapper;
  6. import com.example.movies.query.ActorQuery;
  7. import com.example.movies.service.EduActorService;
  8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  9. import org.apache.commons.lang3.StringUtils;
  10. import org.springframework.stereotype.Service;
  11. import java.util.HashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. /**
  15. * <p>
  16. * 演员 服务实现类
  17. * </p>
  18. */
  19. @Service
  20. public class EduActorServiceImpl extends ServiceImpl<EduActorMapper, EduActor> implements EduActorService {
  21. @Override
  22. public Map<String, Object> getActorList(Page<EduActor> page) {
  23. // 分页条件
  24. QueryWrapper<EduActor> wrapper = new QueryWrapper<>();
  25. wrapper.orderByDesc("id");
  26. baseMapper.selectPage(page,wrapper);
  27. // 当前页、总的页数、每页记录数、总的记录数
  28. long current = page.getCurrent();
  29. long pages = page.getPages();
  30. long size = page.getSize();
  31. long total = page.getTotal();
  32. // 记录
  33. List<EduActor> records = page.getRecords();
  34. // 上一页、下一页
  35. boolean hasPrevious = page.hasPrevious();
  36. boolean hasNext = page.hasNext();
  37. // 添加分页条件
  38. Map<String, Object> map = new HashMap<>();
  39. map.put("current", current);
  40. map.put("pages", pages);
  41. map.put("size", size);
  42. map.put("total", total);
  43. map.put("records", records);
  44. map.put("hasPrevious", hasPrevious);
  45. map.put("hasNext", hasNext);
  46. return map;
  47. }
  48. @Override
  49. public void pageQuery(Page<EduActor> page, ActorQuery actorQuery) {
  50. // 查询对象
  51. QueryWrapper<EduActor> queryWrapper = new QueryWrapper<>();
  52. queryWrapper.orderByDesc("sort");
  53. // 添加查询的条件
  54. if (actorQuery == null) {
  55. baseMapper.selectPage(page, queryWrapper);
  56. return ;
  57. }
  58. // 演员的名称
  59. String name = actorQuery.getName();
  60. // 演员的等级
  61. Integer level = actorQuery.getLevel();
  62. // 演员的时间段
  63. String begin = actorQuery.getBegin();
  64. String end = actorQuery.getEnd();
  65. // 判断
  66. if (name != null && !name.isEmpty()) {
  67. // 模糊
  68. queryWrapper.like("name", name);
  69. }
  70. if (level != null && !StringUtils.isEmpty(level + "")) {
  71. // 等于
  72. queryWrapper.eq("level", level);
  73. }
  74. if (begin != null && !begin.isEmpty()) {
  75. // 大于
  76. queryWrapper.ge("begin", begin);
  77. }
  78. if (end != null && !end.isEmpty()) {
  79. // 小于
  80. queryWrapper.le("end", end);
  81. }
  82. // 添加条件到mapper
  83. baseMapper.selectPage(page, queryWrapper);
  84. }
  85. }
  • controller
  1. package com.example.movies.controller;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.example.movies.entity.EduActor;
  4. import com.example.movies.query.ActorQuery;
  5. import com.example.movies.service.EduActorService;
  6. import com.example.util.R;
  7. import io.swagger.annotations.Api;
  8. import io.swagger.annotations.ApiOperation;
  9. import io.swagger.annotations.ApiParam;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.web.bind.annotation.*;
  12. import java.util.List;
  13. /**
  14. * <p>
  15. * 演员 前端控制器
  16. * </p>
  17. */
  18. @RestController
  19. @RequestMapping("/movies/edu-actor")
  20. public class EduActorController {
  21. /**
  22. * 依赖注册service
  23. */
  24. @Autowired
  25. private EduActorService actorService;
  26. @ApiOperation("显示演员信息")
  27. @GetMapping("/list")
  28. public R list(){
  29. return R.ok().data("items", actorService.list(null));
  30. }
  31. @ApiOperation("保存演员数据")
  32. @PostMapping("/save")
  33. public R saveActor(EduActor actor){
  34. actorService.save(actor);
  35. return R.ok();
  36. }
  37. @ApiOperation("逻辑删除演员")
  38. @DeleteMapping("del/{id}")
  39. public R delete(@ApiParam(name = "id", value = "演员的主键", required = true)
  40. @PathVariable String id){
  41. actorService.removeById(id);
  42. return R.ok();
  43. }
  44. @ApiOperation("演员条件分页")
  45. @GetMapping("{page}/{limit}")
  46. public R listPage(@ApiParam(name = "page", value = "当前页码", required = true)
  47. @PathVariable long page,
  48. @ApiParam(name = "limit", value = "每页显示的记录数", required = true)
  49. @PathVariable long limit,
  50. @ApiParam(name = "actorQuery",value = "查询对象",required = false) ActorQuery actorQuery){
  51. // 添加分页参数
  52. Page<EduActor> actorPage = new Page<>(page, limit);
  53. // actorService.getActorList(actorPage);
  54. // 添加查询条件
  55. actorService.pageQuery(actorPage, actorQuery);
  56. List<EduActor> records = actorPage.getRecords();
  57. long total = actorPage.getTotal();
  58. //返回
  59. return R.ok().data("total", total).data("rows", records);
  60. }
  61. }

4.3 编写统一异常处理

  • 自定义异常类
  1. package com.example.exceptionhandler;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. /**
  6. * 自定义异常类.
  7. */
  8. @Data
  9. @AllArgsConstructor
  10. @NoArgsConstructor
  11. public class customException extends RuntimeException{
  12. /**
  13. * 异常码
  14. */
  15. private Integer code;
  16. /**
  17. * 异常信息
  18. */
  19. private String msg;
  20. }
  • 全局统一异常信息类
  1. package com.example.exceptionhandler;
  2. import com.example.util.R;
  3. import io.swagger.annotations.ApiModel;
  4. import io.swagger.annotations.ApiOperation;
  5. import org.springframework.web.bind.annotation.ControllerAdvice;
  6. import org.springframework.web.bind.annotation.ExceptionHandler;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. /**
  9. * 全局统一异常信息类.
  10. * @ResponseBody // 返回的数据格式(json)
  11. */
  12. @ControllerAdvice
  13. public class GlobalExceptionHandler {
  14. @ApiOperation(value = "指定出现异常调用的方法")
  15. @ExceptionHandler(Exception.class)
  16. @ResponseBody
  17. public R error(Exception e) {
  18. e.printStackTrace();
  19. return R.error().message("这是一个全局的异常...");
  20. }
  21. @ApiOperation(value = "自定义异常")
  22. @ExceptionHandler(customException.class)
  23. @ResponseBody
  24. public R error(customException e) {
  25. e.printStackTrace();
  26. return R.error().code(e.getCode()).message(e.getMessage());
  27. }
  28. @ApiOperation(value = "特定异常")
  29. @ExceptionHandler(ArithmeticException.class)
  30. @ResponseBody
  31. public R error(ArithmeticException e) {
  32. e.printStackTrace();
  33. return R.error().message("执行了ArithmeticException异常...");
  34. }
  35. }

image.png

第五章:演员前端开发

5.1 解压vue-admin-template-master模板到指定的目录中

  • 需要在线安装依赖的软件

image.png

  1. npm install
  • 下载后的依赖都在node_modules目录中

image.png

  • 启动模块
  1. npm run dev

image.png

  • 去掉代码检查

image.png

5.2 模板的目录结构

image.png

  • src

image.png

  • 国际化配置

image.png

5.3 调试登录的错误

image.png

  • 查看登录代码

image.png

  • 把连接的地址修改为自己项目的连接

image.png

  • 编写登录方法

image.png

  1. package com.example.movies.controller;
  2. import com.example.util.R;
  3. import org.springframework.web.bind.annotation.*;
  4. /**
  5. * 自己编写一个登录.
  6. * @CrossOrigin 跨域
  7. */
  8. @RestController
  9. @RequestMapping("/user")
  10. @CrossOrigin
  11. public class ActorLoginController {
  12. @PostMapping("/login")
  13. public R login() { // 登录
  14. return R.ok().data("token", "admin");
  15. }
  16. @GetMapping("/info")
  17. public R info() { // 登录返回信息
  18. return R.ok()
  19. .data("roles", "admin")
  20. .data("name", "admin")
  21. .data("avatar", "xxx");
  22. }
  23. }

image.png

5.4 编写演员页面

5.4.1 编写路由

  • src/router/index.js添加演员路由
  1. {
  2. path: '/actor',
  3. component: Layout,
  4. redirect: '/actor/list',
  5. name: 'Example',
  6. meta: { title: '演员信息', icon: 'example' },
  7. children: [
  8. {
  9. path: 'list',
  10. name: 'Table',
  11. component: () => import('@/views/actor/list'),
  12. meta: { title: '演员列表', icon: 'table' }
  13. },
  14. {
  15. path: 'save',
  16. name: 'Tree',
  17. component: () => import('@/views/actor/saveActor'),
  18. meta: { title: '添加演员', icon: 'tree' }
  19. },
  20. {
  21. path: 'edit/:id',
  22. name: 'Tree',
  23. component: () => import('@/views/actor/saveActor'),
  24. meta: { title: '修改演员', icon: 'tree' },
  25. hidden: true
  26. }
  27. ]
  28. },
  • 编写可以访问后台代码的js脚本,src/api/movies/actor.js
  1. import request from '@/utils/request'
  2. export default {
  3. //显示分页演员列表
  4. getPageList(page, limit, searchObj){
  5. return request({
  6. url: `/edu-actor/${page}/${limit}`,
  7. method: 'post',
  8. data: searchObj
  9. })
  10. },
  11. //显示所有演员列表
  12. getActorAll(){
  13. return request({
  14. url: `/edu-actor/list`,
  15. method: 'get'
  16. })
  17. },
  18. //根据id删除演员
  19. deleteActor(id){
  20. return request({
  21. url: `/edu-actor/del/${id}`,
  22. method: 'delete'
  23. })
  24. },
  25. //保存演员信息
  26. saveActor(actor){
  27. return request({
  28. url: `/edu-actor/save`,
  29. method: 'post',
  30. data: actor
  31. })
  32. },
  33. //获取更新对象
  34. findById(id){
  35. return request({
  36. url: `/edu-actor/data/${id}`,
  37. method: 'get'
  38. })
  39. },
  40. //更新对象
  41. updateActor(actor){
  42. return request({
  43. url: `/edu-actor/update`,
  44. method: 'put',
  45. data: actor
  46. })
  47. }
  48. }

5.4.2 编写vue页面

  • 编写list.vue页面显示演员信息
  • 编写saveActor.vue页面保存演员信息

5.4.2.1 编写演员的列表页面list.vue

  1. <template>
  2. <div class="app-container">
  3. <!-- 演员查询表单 -->
  4. <el-form :inline="true" class="demo-form-inline">
  5. <el-form-item label="演员名称">
  6. <el-input v-model="actorQuery.name" placeholder="演员名称"></el-input>
  7. </el-form-item>
  8. <el-form-item label="演员头衔">
  9. <el-select v-model="actorQuery.level" placeholder="演员头衔">
  10. <el-option label="一线演员" value="1"></el-option>
  11. <el-option label="二线演员" value="2"></el-option>
  12. </el-select>
  13. </el-form-item>
  14. <el-form-item label="添加时间">
  15. <el-date-picker
  16. v-model="actorQuery.begin"
  17. type="datetime"
  18. placeholder="选择开始日期时间"
  19. value-format="yyyy-MM-dd HH:mm:ss"
  20. default-time="00:00:00"
  21. />
  22. </el-form-item>
  23. <el-form-item label="添加时间">
  24. <el-date-picker
  25. v-model="actorQuery.end"
  26. type="datetime"
  27. placeholder="选择结束日期时间"
  28. value-format="yyyy-MM-dd HH:mm:ss"
  29. default-time="00:00:00"
  30. />
  31. </el-form-item>
  32. <el-form-item>
  33. <el-button type="primary" @click="getList()">查 询</el-button>
  34. <el-button type="default" @click="resetData()">清 空</el-button>
  35. </el-form-item>
  36. </el-form>
  37. <!-- 演员列表的表格 -->
  38. <el-table :data="list" style="width: 100%">
  39. <el-table-column label="序号">
  40. <template slot-scope="scope">
  41. {{(page-1)*limit+scope.$index+1}}
  42. </template>
  43. </el-table-column>
  44. <el-table-column label="演员名称" prop="name"> </el-table-column>
  45. <el-table-column label="演员头衔">
  46. <template slot-scope="scope">
  47. {{scope.row.level===1?'一线演员':'二线演员'}}
  48. </template>
  49. </el-table-column>
  50. <el-table-column label="演员介绍" prop="intro"> </el-table-column>
  51. <el-table-column label="演员入职日期" prop="gmtCreate"> </el-table-column>
  52. <el-table-column>
  53. <template slot-scope="scope">
  54. <el-button size="mini" @click="handleEdit(scope.$index, scope.row)">更 新</el-button >
  55. <el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">刪 除</el-button>
  56. </template>
  57. </el-table-column>
  58. </el-table>
  59. <!-- 分页按钮 -->
  60. <el-pagination
  61. @size-change="handleSizeChange"
  62. @current-change="getList"
  63. :current-page="page"
  64. :page-sizes="[3,5,7]"
  65. :page-size="limit"
  66. layout="total, sizes, prev, pager, next, jumper"
  67. :total="total">
  68. </el-pagination>
  69. </div>
  70. </template>
  71. <script>
  72. //引入调用演员的js
  73. import actor from "@/api/movies/actor.js";
  74. export default {
  75. //编写核心代码
  76. data() {
  77. return {
  78. list: null,
  79. page: 1,
  80. limit: 3,
  81. total: 0,
  82. actorQuery: {},
  83. };
  84. },
  85. created() {},
  86. methods: {
  87. //查询方法
  88. getList() {},
  89. //清空方法
  90. resetData() {
  91. this.actorQuery = {};
  92. },
  93. //删除
  94. handleDelete(){}
  95. },
  96. };
  97. </script>
  98. <style>
  99. </style>

5.4.2.2 实现前端的功能

  • list列表
  1. <template>
  2. <div class="app-container">
  3. <!-- 演员查询表单 -->
  4. <el-form :inline="true" class="demo-form-inline">
  5. <el-form-item label="演员名称">
  6. <el-input v-model="actorQuery.name" placeholder="演员名称"></el-input>
  7. </el-form-item>
  8. <el-form-item label="演员头衔">
  9. <el-select v-model="actorQuery.level" placeholder="演员头衔">
  10. <el-option label="一线演员" :value="1"></el-option>
  11. <el-option label="二线演员" :value="2"></el-option>
  12. </el-select>
  13. </el-form-item>
  14. <el-form-item label="添加时间">
  15. <el-date-picker
  16. v-model="actorQuery.begin"
  17. type="datetime"
  18. placeholder="选择开始日期时间"
  19. value-format="yyyy-MM-dd HH:mm:ss"
  20. default-time="00:00:00"
  21. />
  22. </el-form-item>
  23. <el-form-item label="添加时间">
  24. <el-date-picker
  25. v-model="actorQuery.end"
  26. type="datetime"
  27. placeholder="选择结束日期时间"
  28. value-format="yyyy-MM-dd HH:mm:ss"
  29. default-time="00:00:00"
  30. />
  31. </el-form-item>
  32. <el-form-item>
  33. <el-button type="primary" @click="getList()">查 询</el-button>
  34. <el-button type="default" @click="resetData()">清 空</el-button>
  35. </el-form-item>
  36. </el-form>
  37. <!-- 演员列表的表格 -->
  38. <el-table :data="list" style="width: 100%">
  39. <el-table-column label="序号">
  40. <template slot-scope="scope">
  41. {{(page-1)*limit+scope.$index+1}}
  42. </template>
  43. </el-table-column>
  44. <el-table-column label="演员名称" prop="name"> </el-table-column>
  45. <el-table-column label="演员头衔">
  46. <template slot-scope="scope">
  47. {{scope.row.level===1?'一线演员':'二线演员'}}
  48. </template>
  49. </el-table-column>
  50. <el-table-column label="演员介绍" prop="intro"> </el-table-column>
  51. <el-table-column label="演员入职日期" prop="gmtCreate"> </el-table-column>
  52. <el-table-column>
  53. <template slot-scope="scope">
  54. <router-link :to="'/actor/edit/'+scope.row.id">
  55. <el-button size="mini">更 新</el-button >
  56. </router-link>
  57. <el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">刪 除</el-button>
  58. </template>
  59. </el-table-column>
  60. </el-table>
  61. <!-- 分页按钮 -->
  62. <el-pagination
  63. @size-change="handleSizeChange"
  64. @current-change="getList"
  65. :current-page="page"
  66. :page-sizes="[3,5,7]"
  67. :page-size="limit"
  68. layout="total, sizes, prev, pager, next, jumper"
  69. :total="total">
  70. </el-pagination>
  71. </div>
  72. </template>
  73. <script>
  74. //引入调用演员的js
  75. import actor from "@/api/movies/actor.js";
  76. export default {
  77. //编写核心代码
  78. data() {
  79. return {
  80. list: null,
  81. page: 1,
  82. limit: 3,
  83. total: 0,
  84. actorQuery: {
  85. },
  86. };
  87. },
  88. created() {
  89. this.getList()
  90. },
  91. methods: {
  92. //查询方法
  93. getList(page=1) {
  94. this.page = page
  95. //调用自定义函数
  96. actor.getPageList(this.page,this.limit,this.actorQuery).then((result) => {//请求成功
  97. this.list = result.data.items
  98. this.total = result.data.total
  99. }).catch((err) => {
  100. });
  101. // actor.getActorAll().then((result) => {//请求成功
  102. // this.list = result.data.items
  103. // this.total = result.data.total
  104. // });
  105. },
  106. handleSizeChange(size){
  107. this.limit = size
  108. this.getList()
  109. },
  110. //清空方法
  111. resetData() {
  112. this.actorQuery = {};
  113. },
  114. //删除
  115. handleDelete(id){
  116. this.$confirm('是否删除?','提示',{
  117. confirmButtonText: '确定',
  118. cancelButtonText: '取消',
  119. type: 'warning'
  120. }).then(res=>{
  121. actor.deleteActor(id).then(res=>{
  122. this.$message({
  123. type: 'success',
  124. message: '删除成功'
  125. })
  126. //调用演员的列表方法
  127. this.getList()
  128. })
  129. })
  130. }
  131. },
  132. };
  133. </script>
  134. <style>
  135. </style>
  • saveActor.vue保存
  1. <template>
  2. <div class="app-container">
  3. <!-- 演员表单 -->
  4. <el-form label-width="80px">
  5. <el-form-item label="演员名称">
  6. <el-input v-model="actor.name"></el-input>
  7. </el-form-item>
  8. <el-form-item label="演员排序">
  9. <el-input v-model="actor.sort"></el-input>
  10. </el-form-item>
  11. <el-form-item label="演员头衔">
  12. <el-input v-model="actor.level"></el-input>
  13. </el-form-item>
  14. <el-form-item label="演员简介">
  15. <el-input v-model="actor.intro"></el-input>
  16. </el-form-item>
  17. <el-form-item label="演员资历">
  18. <el-input v-model="actor.career"></el-input>
  19. </el-form-item>
  20. <el-form-item >
  21. <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保 存</el-button>
  22. <el-button :disabled="saveBtnDisabled" type="default" @click="resetActor">清 空</el-button>
  23. </el-form-item>
  24. </el-form>
  25. </div>
  26. </template>
  27. <script>
  28. //需要导入控件
  29. import ImageCropper from '@/components/ImageCropper'
  30. import PanThumb from '@/components/PanThumb'
  31. //引入调用演员的js
  32. import actorFun from "@/api/movies/actor.js";
  33. export default {
  34. //注册控件
  35. components:{ImageCropper,PanThumb},
  36. data(){
  37. return{
  38. actor:{},
  39. saveBtnDisabled: false,
  40. imageCropperShow: false,
  41. imagecropperKey: 0
  42. }
  43. },
  44. created(){
  45. this.init()
  46. },
  47. watch:{//监听路由的变化
  48. $route(to,from){
  49. this.init()
  50. }
  51. },
  52. methods:{
  53. //初始化方法
  54. init(){
  55. if(this.$route.params && this.$route.params.id){
  56. const id = this.$route.params.id
  57. //调用获取演员的方法
  58. this.getInfo(id)
  59. }
  60. },
  61. //保存或更新
  62. saveOrUpdate(){
  63. //根据演员的id判断是否为保存或更新
  64. if(this.actor.id){
  65. this.update()
  66. }else{
  67. this.save()
  68. }
  69. },
  70. //保存
  71. save(){
  72. actorFun.saveActor(this.actor).then((result) => {
  73. this.$message({
  74. type: 'success',
  75. message: '保存成功'
  76. })
  77. //显示列表,通过路由的路径显示演员列表
  78. this.$router.push({path:'/actor/list'})
  79. }).catch((err) => {
  80. });
  81. },
  82. //更新
  83. update(){
  84. actorFun.updateActor(this.actor).then(res=>{
  85. this.$message({
  86. type: 'success',
  87. message: '更新成功'
  88. })
  89. //返回到演员列表
  90. this.$router.push({path:'/actor/list'})
  91. })
  92. },
  93. getInfo(id){
  94. actorFun.findById(id).then(res=>{
  95. this.actor = res.data.actor
  96. })
  97. },
  98. //清空
  99. resetActor(){
  100. this.actor = {}
  101. }
  102. }
  103. }
  104. </script>

5.4.3 编写java代码

  • 处理跨域,在 EduActorController 上添加@CrossOrigin
  • 添加更新方法
  • 实现演员条件查询
  1. package com.example.movies.controller;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.example.movies.entity.EduActor;
  5. import com.example.movies.query.ActorQuery;
  6. import com.example.movies.service.EduActorService;
  7. import com.example.util.R;
  8. import io.swagger.annotations.ApiOperation;
  9. import io.swagger.annotations.ApiParam;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.util.StringUtils;
  12. import org.springframework.web.bind.annotation.*;
  13. import java.util.List;
  14. /**
  15. * <p>
  16. * 演员 前端控制器
  17. * </p>
  18. * @CrossOrigin 解决跨域问题
  19. */
  20. @RestController
  21. @RequestMapping("/edu-actor")
  22. @CrossOrigin
  23. public class EduActorController {
  24. /**
  25. * 依赖注册service
  26. */
  27. @Autowired
  28. private EduActorService actorService;
  29. @ApiOperation("显示演员信息")
  30. @GetMapping("/list")
  31. public R list(){
  32. return R.ok().data("items", actorService.list(null));
  33. }
  34. @ApiOperation("保存演员数据")
  35. @PostMapping("/save")
  36. public R saveActor(@RequestBody EduActor actor){
  37. actorService.save(actor);
  38. return R.ok();
  39. }
  40. @ApiOperation("逻辑删除演员")
  41. @DeleteMapping("del/{id}")
  42. public R delete(@ApiParam(name = "id", value = "演员的主键", required = true)
  43. @PathVariable String id){
  44. actorService.removeById(id);
  45. return R.ok();
  46. }
  47. @ApiOperation("演员条件分页")
  48. @PostMapping("/{page}/{limit}")
  49. public R listPage(@ApiParam(name = "page", value = "当前页码", required = true)
  50. @PathVariable long page,
  51. @ApiParam(name = "limit", value = "每页显示的记录数", required = true)
  52. @PathVariable long limit,
  53. @ApiParam(name = "actorQuery",value = "查询对象",required = false) @RequestBody(required = false) ActorQuery actorQuery){
  54. // 创建分页对象
  55. Page<EduActor> actorPage = new Page<>(page, limit);
  56. // 构造分页条件
  57. QueryWrapper<EduActor> wrapper = new QueryWrapper<>();
  58. if (actorQuery != null) {
  59. String name = actorQuery.getName();
  60. Integer level = actorQuery.getLevel();
  61. String begin = actorQuery.getBegin();
  62. String end = actorQuery.getEnd();
  63. // 拼接条件
  64. if (!StringUtils.isEmpty(name)) {
  65. wrapper.like("name", name);
  66. }
  67. if (!StringUtils.isEmpty(level)) {
  68. wrapper.eq("level", level);
  69. }
  70. if (!StringUtils.isEmpty(begin)) {
  71. wrapper.ge("gmt_create", begin);
  72. }
  73. if (!StringUtils.isEmpty(end)) {
  74. wrapper.le("gmt_create", end);
  75. }
  76. }
  77. // 排序
  78. wrapper.orderByDesc("gmt_create");
  79. // 分页
  80. actorService.page(actorPage, wrapper);
  81. // 总记录数
  82. long total = actorPage.getTotal();
  83. // 没有显示的记录
  84. List<EduActor> records = actorPage.getRecords();
  85. // 返回
  86. return R.ok().data("total", total).data("items", records);
  87. }
  88. @ApiOperation("获取需要更新的对象")
  89. @GetMapping("/data/{id}")
  90. public R getData(@PathVariable String id) {
  91. EduActor actor = actorService.getById(id);
  92. return R.ok().data("actor", actor);
  93. }
  94. @ApiOperation("更新演员信息")
  95. @PutMapping("/update")
  96. public R update(@RequestBody EduActor actor) {
  97. actorService.updateById(actor);
  98. return R.ok();
  99. }
  100. }

image.png

注意:如果前端提示跨域

  1. 后端代码是否已经处理跨域了
  2. 查看提交的方式是否正确

5.5 上传的图片存储在阿里云OSS

5.5.1 开通阿里云oss服务

  • 价格明细

image.png

  • 创建bucket

image.png

  • 选择:添加名称、标准存储、公共读、不开通

image.png

  • 查看生成的id和秘钥

image.png
image.png
image.png

  • 通过手机认证后可以获取到:id和秘钥,这是我们开发上传图片必须的条件。

image.png

5.5.2 查看sdk的使用

  • 添加依赖配置

image.png

5.5.3 创建一个子项目完成oss

  • subproject_service/service_oss

  • pom

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>subproject_service</artifactId>
  7. <groupId>com.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>service_oss</artifactId>
  12. <dependencies>
  13. <!--阿里云-->
  14. <dependency>
  15. <groupId>com.aliyun.oss</groupId>
  16. <artifactId>aliyun-sdk-oss</artifactId>
  17. </dependency>
  18. <!--日期工具栏-->
  19. <dependency>
  20. <groupId>joda-time</groupId>
  21. <artifactId>joda-time</artifactId>
  22. </dependency>
  23. <!--添加工具模块-->
  24. <dependency>
  25. <groupId>com.example</groupId>
  26. <artifactId>common_util</artifactId>
  27. <version>1.0-SNAPSHOT</version>
  28. </dependency>
  29. </dependencies>
  30. </project>
  • 配置application.properties
  1. # 端口号
  2. server.port=8082
  3. # 服务名称
  4. spring.application.name=server-oss
  5. # 开发环境
  6. spring.profiles.active=dev
  7. # oss的网址、id、密钥
  8. aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
  9. aliyun.oss.file.keyid=LTAI5tLdYSRZLG7QcX
  10. aliyun.oss.file.keysecret=ggSt3VGWrniYKyjtZGVf8M
  11. # buket名称
  12. aliyun.oss.file.bucketname=oss-movies
  13. # nacos地址
  14. spring.cloud.nacos.discovery.server-addr=localhost:8848
  • 主配置类
  1. package com.example.movies;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  5. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  6. /**
  7. * oss 启动类.
  8. * 不需要加载项目的数据源.
  9. */
  10. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
  11. @EnableDiscoveryClient
  12. public class OssApplication {
  13. public static void main(String[] args) {
  14. SpringApplication.run(OssApplication.class, args);
  15. }
  16. }
  • 创建一个工具类读取application.properties中的配置信息
  1. package com.example.movies.util;
  2. import org.springframework.beans.factory.InitializingBean;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * 启动时自动读取.
  7. */
  8. @Component
  9. public class ConstantPropertiesUtils implements InitializingBean {
  10. /**
  11. * 读取配置文件application.properties
  12. * oss的网址、id、密钥、Bucket名称
  13. */
  14. @Value("${aliyun.oss.file.endpoint}")
  15. private String endpoint;
  16. @Value("${aliyun.oss.file.keyid}")
  17. private String keyId;
  18. @Value("${aliyun.oss.file.keysecret}")
  19. private String keySecret;
  20. @Value("${aliyun.oss.file.bucketname}")
  21. private String buckerName;
  22. /**
  23. * 创建常量
  24. */
  25. public static String END_POINT;
  26. public static String ACCESS_KEY_ID;
  27. public static String ACCESS_KEY_SECRET;
  28. public static String BUCKET_NAME;
  29. @Override
  30. public void afterPropertiesSet() throws Exception {
  31. END_POINT = endpoint;
  32. ACCESS_KEY_ID = keyId;
  33. ACCESS_KEY_SECRET = keySecret;
  34. BUCKET_NAME = buckerName;
  35. }
  36. }
  • 业务层实现上传图片
  1. package com.example.movies.server;
  2. import org.springframework.web.multipart.MultipartFile;
  3. /**
  4. * 业务层 实现上传图片.
  5. */
  6. public interface OssService {
  7. String uploadFileAvatar(MultipartFile file);
  8. }
  • 实现类:完成图片上传,且返回地址
  1. package com.example.movies.server.impl;
  2. import com.aliyun.oss.OSS;
  3. import com.aliyun.oss.OSSClientBuilder;
  4. import com.example.movies.server.OssService;
  5. import com.example.movies.util.ConstantPropertiesUtils;
  6. import org.joda.time.DateTime;
  7. import org.springframework.web.multipart.MultipartFile;
  8. import java.io.IOException;
  9. import java.io.InputStream;
  10. import java.util.UUID;
  11. /**
  12. * 实现类 完成图片上传,且返回地址.
  13. */
  14. @Component
  15. public class OssServiceImpl implements OssService {
  16. /**
  17. * 演员的头像或影像的海报上传
  18. * @param file
  19. * @return
  20. */
  21. @Override
  22. public String uploadFileAvatar(MultipartFile file) {
  23. // 通过工具类获取配置数据
  24. // 获取oss的网址
  25. String endPoint = ConstantPropertiesUtils.END_POINT;
  26. // 获取访问凭证的ID
  27. String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
  28. // 获取访问凭证的秘钥
  29. String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
  30. // 获取 bucker 名称
  31. String bucketName = ConstantPropertiesUtils.BUCKET_NAME;
  32. // 完成上传
  33. try {
  34. // 获取上传的输入流
  35. InputStream inputStream = file.getInputStream();
  36. // 获取文件名
  37. String filename = file.getOriginalFilename();
  38. // 为文件起一个唯一的名称
  39. filename = UUID.randomUUID().toString().replaceAll("-", "") + filename;
  40. // 按照年月日创建上传的目录存放图片:yyyy/MM/dd
  41. String datePath = new DateTime().toString("yyyy/MM/dd");
  42. // 拼接
  43. filename = datePath + "/" + filename;
  44. // 调用oss实现上传
  45. OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
  46. // 参数: bucker名称、文件名称、输入流
  47. ossClient.putObject(bucketName, filename, inputStream);
  48. // 关闭ossClient
  49. ossClient.shutdown();
  50. // 上传文件后需要获取文件的路径
  51. String url = "http://" + bucketName + "." + endPoint + "/" + filename;
  52. return url;
  53. } catch (IOException e) {
  54. e.printStackTrace();
  55. }
  56. return null;
  57. }
  58. }
  • controller
  1. package com.example.movies.controller;
  2. import com.example.movies.server.OssService;
  3. import com.example.util.R;
  4. import io.swagger.annotations.ApiOperation;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.CrossOrigin;
  7. import org.springframework.web.bind.annotation.PostMapping;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RestController;
  10. import org.springframework.web.multipart.MultipartFile;
  11. /**
  12. * 控制器 对象存储.
  13. */
  14. @RestController
  15. @RequestMapping("/oss")
  16. @CrossOrigin
  17. public class OssController {
  18. @Autowired
  19. private OssService ossService;
  20. @ApiOperation("图片上传")
  21. @PostMapping("/fileoss")
  22. public R uploadOssFile(MultipartFile file) {
  23. String url = ossService.uploadFileAvatar(file);
  24. return R.ok().data("url", url);
  25. }
  26. }

5.6 配置网关

5.6.1 创建一个独立的项目完成网关配置

[

](https://cn.bing.com/dict/search?q=configuration&FORM=BDVSP6&cc=cn)

  • 注意:网关配置中不允许有spring-boot-start-web,因为冲突

image.png

  • pom
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>movies</artifactId>
  7. <groupId>com.example</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <!--网关配置-->
  12. <artifactId>Gateway_configuration</artifactId>
  13. <dependencies>
  14. <!--网关依赖-->
  15. <dependency>
  16. <groupId>org.springframework.cloud</groupId>
  17. <artifactId>spring-cloud-starter-gateway</artifactId>
  18. </dependency>
  19. <!--nacos依赖-->
  20. <dependency>
  21. <groupId>com.alibaba.cloud</groupId>
  22. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  23. </dependency>
  24. </dependencies>
  25. </project>
  • 主配置类
  1. package com.example.movies;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. /**
  6. * 网关 主配置类.
  7. */
  8. @SpringBootApplication
  9. @EnableDiscoveryClient
  10. public class GatewayApplication {
  11. public static void main(String[] args) {
  12. SpringApplication.run(GatewayApplication.class, args);
  13. }
  14. }
  • 配置文件
  1. server:
  2. port: 7000
  3. spring:
  4. application:
  5. name: service-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true
  11. #跨域
  12. routes:
  13. - id: gateway-user
  14. uri: lb://service-actor
  15. predicates:
  16. - Path=/user/**
  17. - id: gateway-actor
  18. uri: lb://service-actor
  19. predicates:
  20. - Path=/edu-actor/**
  21. - id: gateway-movies
  22. uri: lb://service-actor
  23. predicates:
  24. - Path=/edu-movies/**
  25. - id: gateway-oss
  26. uri: lb://service-oss
  27. predicates:
  28. - Path=/oss/**
  29. nacos:
  30. discovery:
  31. server-addr: localhost:8848
  • 编写一个配置类完成跨域设置
  1. package com.xiudun.util;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.cors.CorsConfiguration;
  5. import org.springframework.web.cors.reactive.CorsWebFilter;
  6. import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
  7. //跨域配置类
  8. @Configuration
  9. public class XiudunCorsConfiguration {
  10. @Bean
  11. public CorsWebFilter corsWebFilter(){
  12. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  13. //跨域设置
  14. CorsConfiguration corsConfiguration = new CorsConfiguration();
  15. corsConfiguration.addAllowedHeader("*");
  16. corsConfiguration.addAllowedMethod("*");
  17. corsConfiguration.addAllowedOrigin("*");
  18. corsConfiguration.setAllowCredentials(true);
  19. source.registerCorsConfiguration("/**",corsConfiguration);
  20. return new CorsWebFilter(source);
  21. }
  22. }

5.6.2 配置前端

  • 要把原来在controller中的跨域注解@CrossOrigin都去掉,否则会出现多次跨域错误
  • 在前端使用网关的地址和端口

image.png

  • 可以通过网关正常访问我们的vue

5.6.3 添加一个第三方的图片上传控件

image.png
在线影视 - 图34

  • 把组件复制到前端的项目中;src/components/

在线影视 - 图35
image.png

  • 在vue页面中使用组件

image.png

在线影视 - 图38

  • 上传组件
  1. <!-- 演员头像 -->
  2. <el-form-item label="演员头像">
  3. <!-- 头像的缩略图 -->
  4. <pan-thumb :image="actor.avatar"/>
  5. <!-- 图片的上传按钮 -->
  6. <el-button type="primary" icon="el-icon-upload" @click="imageCropperShow=true">演员头像</el-button>
  7. <!-- 使用导入后的组件 -->
  8. <image-cropper
  9. v-show="imageCropperShow"
  10. :width="300"
  11. :height="300"
  12. :key="imagecropperKey"
  13. :url="'http://localhost:8082/oss/fileoss'"
  14. field="file"
  15. @colse="close"
  16. @crop-upload-success="cropSuccess"/>
  17. </el-form-item>
  • 图片上传方法
  1. methods:{
  2. //演员头像图片的上传
  3. cropSuccess(data){
  4. //上传成功
  5. this.imageCropperShow = false
  6. this.actor.avatar = data.url
  7. this.imagecropperKey = this.imagecropperKey+1
  8. },
  9. close(){//关闭上传的window
  10. this.imageCropperShow = false
  11. this.imagecropperKey = this.imagecropperKey+1
  12. },
  13. }
  • 上传后需要到阿里云中查看是否添加了图片

image.png
在线影视 - 图40

  • 完整的上传vue代码
  1. <template>
  2. <div class="app-container">
  3. <!-- 演员表单 -->
  4. <el-form label-width="80px">
  5. <el-form-item label="演员名称">
  6. <el-input v-model="actor.name"></el-input>
  7. </el-form-item>
  8. <el-form-item label="演员排序">
  9. <el-input v-model="actor.sort"></el-input>
  10. </el-form-item>
  11. <el-form-item label="演员头衔">
  12. <el-input v-model="actor.level"></el-input>
  13. </el-form-item>
  14. <el-form-item label="演员简介">
  15. <el-input v-model="actor.intro"></el-input>
  16. </el-form-item>
  17. <el-form-item label="演员资历">
  18. <el-input v-model="actor.career"></el-input>
  19. </el-form-item>
  20. <!-- 演员头像 -->
  21. <el-form-item label="演员头像">
  22. <!-- 头像的缩略图 -->
  23. <pan-thumb :image="actor.avatar"/>
  24. <!-- 图片的上传按钮 -->
  25. <el-button type="primary" icon="el-icon-upload" @click="imageCropperShow=true">演员头像</el-button>
  26. <!-- 使用导入后的组件 -->
  27. <image-cropper
  28. v-show="imageCropperShow"
  29. :width="300"
  30. :height="300"
  31. :key="imagecropperKey"
  32. :url="'http://localhost:8082/oss/fileoss'"
  33. field="file"
  34. @colse="close"
  35. @crop-upload-success="cropSuccess"/>
  36. </el-form-item>
  37. <el-form-item >
  38. <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保 存</el-button>
  39. <el-button :disabled="saveBtnDisabled" type="default" @click="resetActor">清 空</el-button>
  40. </el-form-item>
  41. </el-form>
  42. </div>
  43. </template>
  44. <script>
  45. //需要导入控件
  46. import ImageCropper from '@/components/ImageCropper'
  47. import PanThumb from '@/components/PanThumb'
  48. //引入调用演员的js
  49. import actorFun from "@/api/movies/actor.js";
  50. export default {
  51. //注册控件
  52. components:{ImageCropper,PanThumb},
  53. data(){
  54. return{
  55. actor:{},
  56. saveBtnDisabled: false,
  57. imageCropperShow: false,
  58. imagecropperKey: 0
  59. }
  60. },
  61. created(){
  62. this.init()
  63. },
  64. watch:{//监听路由的变化
  65. $route(to,from){
  66. this.init()
  67. }
  68. },
  69. methods:{
  70. //演员头像图片的上传
  71. cropSuccess(data){
  72. //上传成功
  73. this.imageCropperShow = false
  74. this.actor.avatar = data.url
  75. this.imagecropperKey = this.imagecropperKey+1
  76. },
  77. close(){//关闭上传的window
  78. this.imageCropperShow = false
  79. this.imagecropperKey = this.imagecropperKey+1
  80. },
  81. //初始化方法
  82. init(){
  83. if(this.$route.params && this.$route.params.id){
  84. const id = this.$route.params.id
  85. //调用获取演员的方法
  86. this.getInfo(id)
  87. }
  88. },
  89. //保存或更新
  90. saveOrUpdate(){
  91. //根据演员的id判断是否为保存或更新
  92. if(this.actor.id){
  93. this.update()
  94. }else{
  95. this.save()
  96. }
  97. },
  98. //保存
  99. save(){
  100. actorFun.saveActor(this.actor).then((result) => {
  101. this.$message({
  102. type: 'success',
  103. message: '保存成功'
  104. })
  105. //显示列表,通过路由的路径显示演员列表
  106. this.$router.push({path:'/actor/list'})
  107. }).catch((err) => {
  108. });
  109. },
  110. //更新
  111. update(){
  112. actorFun.updateActor(this.actor).then(res=>{
  113. this.$message({
  114. type: 'success',
  115. message: '更新成功'
  116. })
  117. //返回到演员列表
  118. this.$router.push({path:'/actor/list'})
  119. })
  120. },
  121. getInfo(id){
  122. actorFun.findById(id).then(res=>{
  123. this.actor = res.data.actor
  124. })
  125. },
  126. //清空
  127. resetActor(){
  128. this.actor = {}
  129. }
  130. }
  131. }
  132. </script>
  133. <style>
  134. </style>

第六章:影片分类

  • 对应的数据表:edu_subject

6.1 Excel导入导出

  1. 数据的导入:减少输入的工作量
  2. 数据的导出:统计信息归档
  3. 数据的传输:异构系统间数据传输

6.2 EasyExcel完成导入导出

特点:Java领域解析,生成Excel比较有名的框架有Apache poi,jxl等,但他们都存在一个严重的问题就是非常的耗内存,如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc.

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。

6.3 使用EasyExcel

  • domain:可能有数据表对应或没有
  • vo:表示数据载体(没有数据表对应)
  • entity: 一定有对应的数据表
  • beans:等同vo

6.3.1 编写execl读写操作测试类

  • 创建VO ```java package com.example.movies.vo;

import com.alibaba.excel.annotation.ExcelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;

/**

  • 学生的测试bean. */ @Data @NoArgsConstructor @AllArgsConstructor public class StudentVo { @ExcelProperty(“学号”) private Integer sno; @ExcelProperty(“姓名”) private String sname; }
  1. - 创建读取监听器
  2. ```java
  3. package com.example.movies.vo;
  4. import com.alibaba.excel.context.AnalysisContext;
  5. import com.alibaba.excel.event.AnalysisEventListener;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import java.util.Map;
  9. /**
  10. * execl监听器.
  11. */
  12. public class ExcelLisener extends AnalysisEventListener<StudentVo> {
  13. // 创建集合
  14. private List<StudentVo> list = new ArrayList<>();
  15. // 读取execl数据
  16. @Override
  17. public void invoke(StudentVo studentVo, AnalysisContext analysisContext) {
  18. System.out.println(studentVo);
  19. list.add(studentVo);
  20. }
  21. // 读取头信息
  22. @Override
  23. public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
  24. System.out.println("表头:" + headMap);
  25. }
  26. // 读取后
  27. @Override
  28. public void doAfterAllAnalysed(AnalysisContext analysisContext) {
  29. }
  30. }
  • 创建execl读写测试类 ```java package com.example.movies;

import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.read.metadata.ReadSheet; import com.alibaba.excel.write.metadata.WriteSheet; import com.example.movies.vo.ExcelLisener; import com.example.movies.vo.StudentVo; import org.junit.Test;

import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.ArrayList;

/**

  • execl的读写. / public class DemoExcelTexts { /*

    • 自动关闭写入流 */ @Test public void testExeclWriteA() { ArrayList vos = new ArrayList<>(); for (int i = 0; i < 10; i++) {

      1. vos.add(new StudentVo(1001+i,"张三"+i));

      } // 创建文件 String file = “F:/JAVA/aa.xlsx”; // 写入execl EasyExcel.write(file, StudentVo.class).sheet(“写入”).doWrite(vos); }

      /**

    • 把数据写入excel方法2
    • 必须手动关闭写入流 */ @Test public void testExeclWriteB() { ArrayList vos = new ArrayList<>(); for (int i = 0; i < 10; i++) {

      1. vos.add(new StudentVo(1001+i, "张三"+i));

      } // 创建文件 String file = “F:/JAVA/bb.xlsx”; // 写入execl ExcelWriter excelWriter = EasyExcel.write(file, StudentVo.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet(“写入2”).build(); excelWriter.write(vos, writeSheet); // 关闭流 excelWriter.finish(); }

      /**

    • 读取方式1 */ @Test public void testExeclReadA() { // 文件的路径 String file = “F:/JAVA/aa.xlsx”; // 读取数据 EasyExcel.read(file, StudentVo.class, new ExcelLisener()).sheet().doRead(); }

      /**

    • 读取方式2 */ @Test public void testExeclReadB() { // 文件的路径 String file = “F:/JAVA/aa.xlsx”; try {
      1. // 缓冲流
      2. BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
      3. ExcelReader excelReader = EasyExcel.read(inputStream, StudentVo.class, new ExcelLisener()).build();
      4. ReadSheet readSheet = EasyExcel.readSheet(0).build();
      5. excelReader.read(readSheet);
      6. // 关闭流
      7. excelReader.finish();
      } catch (Exception e) {
      1. e.printStackTrace();
      } } } ```

6.4 通过EasyExcel完成影片分类导入

  • excel的数据模板

影片分类.xlsx

6.4.1 在演员服务中实现excel

  • 创建实体
  1. package com.xiudun.vo;
  2. import com.alibaba.excel.annotation.ExcelProperty;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. import lombok.ToString;
  7. @Data
  8. @AllArgsConstructor
  9. @NoArgsConstructor
  10. @ToString
  11. public class ReadData {
  12. @ExcelProperty(value = "一级分类",index = 0)
  13. private String oneSubjectName;
  14. @ExcelProperty(value = "二级分类",index = 1)
  15. private String twoSubjectName;
  16. }
  • 编写主配置类
  1. package com.xiudun;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class SubjectApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(SubjectApplication.class,args);
  8. }
  9. }
  • 使用代码生成器生成对应的不同层的代码
  1. package com.xiudun.entity;
  2. import com.baomidou.mybatisplus.annotation.FieldFill;
  3. import com.baomidou.mybatisplus.annotation.IdType;
  4. import java.util.Date;
  5. import com.baomidou.mybatisplus.annotation.TableField;
  6. import com.baomidou.mybatisplus.annotation.TableId;
  7. import java.io.Serializable;
  8. import io.swagger.annotations.ApiModel;
  9. import io.swagger.annotations.ApiModelProperty;
  10. import lombok.Data;
  11. import lombok.EqualsAndHashCode;
  12. import lombok.experimental.Accessors;
  13. /**
  14. * <p>
  15. * 影视科目
  16. * </p>
  17. *
  18. * @author xiudun
  19. * @since 2021-02-28
  20. */
  21. @Data
  22. @EqualsAndHashCode(callSuper = false)
  23. @Accessors(chain = true)
  24. @ApiModel(value="EduSubject对象", description="影视科目")
  25. public class EduSubject implements Serializable {
  26. private static final long serialVersionUID = 1L;
  27. @ApiModelProperty(value = "影视类别ID")
  28. @TableId(value = "id", type = IdType.ID_WORKER_STR)
  29. private String id;
  30. @ApiModelProperty(value = "类别名称")
  31. private String title;
  32. @ApiModelProperty(value = "父ID")
  33. private String parentId;
  34. @ApiModelProperty(value = "排序字段")
  35. private Integer sort;
  36. @ApiModelProperty(value = "创建时间")
  37. @TableField(fill = FieldFill.INSERT)
  38. private Date gmtCreate;
  39. @ApiModelProperty(value = "更新时间")
  40. @TableField(fill = FieldFill.INSERT_UPDATE)
  41. private Date gmtModified;
  42. }
  • 编写监听器类
  1. package com.xiudun.util;
  2. import com.alibaba.excel.context.AnalysisContext;
  3. import com.alibaba.excel.event.AnalysisEventListener;
  4. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5. import com.xiudun.entity.EduSubject;
  6. import com.xiudun.service.EduSubjectService;
  7. import com.xiudun.vo.ReadData;
  8. import java.util.Map;
  9. //excel监听器
  10. public class ExcelLisener extends AnalysisEventListener<ReadData> {
  11. //注入业务
  12. private EduSubjectService subjectService;
  13. //构造方法
  14. public ExcelLisener() {}
  15. public ExcelLisener(EduSubjectService subjectService) {
  16. this.subjectService = subjectService;
  17. }
  18. //判断一级分类是否重复
  19. private EduSubject existOneSubject(EduSubjectService subjectService,String name){
  20. //查询条件
  21. QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
  22. wrapper.eq("title",name);
  23. wrapper.eq("parent_id","0");
  24. //添加判断条件
  25. EduSubject subject = subjectService.getOne(wrapper);
  26. return subject;
  27. }
  28. //判断二级分类是否重复
  29. private EduSubject existTwoSubject(EduSubjectService subjectService,String name,String pid){
  30. //创建条件对象
  31. QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
  32. //添加添加条件
  33. wrapper.eq("title",name);
  34. wrapper.eq("parent_id",pid);
  35. //获取二级的对象
  36. EduSubject subject = subjectService.getOne(wrapper);
  37. return subject;
  38. }
  39. //读取excel数据
  40. public void invoke(ReadData readData, AnalysisContext analysisContext) {
  41. //判断
  42. if (readData==null) {
  43. throw new RuntimeException("添加数据失败");
  44. }
  45. //添加一级分类
  46. EduSubject existOneSubject = existOneSubject(subjectService, readData.getOneSubjectName());
  47. if (existOneSubject==null) {//没有相同的
  48. existOneSubject = new EduSubject();
  49. existOneSubject.setTitle(readData.getOneSubjectName());
  50. existOneSubject.setParentId("0");
  51. subjectService.save(existOneSubject);
  52. }
  53. //获取一级分类的id
  54. String pid = existOneSubject.getId();
  55. //添加二级分类
  56. EduSubject existTwoSubject = existTwoSubject(subjectService, readData.getTwoSubjectName(), pid);
  57. if (existTwoSubject==null) {
  58. existTwoSubject = new EduSubject();
  59. existTwoSubject.setTitle(readData.getTwoSubjectName());
  60. existTwoSubject.setParentId(pid);
  61. subjectService.save(existTwoSubject);
  62. }
  63. }
  64. @Override//读取头信息
  65. public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
  66. System.out.println("表头:"+headMap);
  67. }
  68. //读取后
  69. public void doAfterAllAnalysed(AnalysisContext analysisContext) { }
  70. }
  • 编写一个业务实现:通过文件上传的方式把影片的分类(Excel)添加到数据表中
  1. package com.xiudun.service;
  2. import com.xiudun.entity.EduSubject;
  3. import com.baomidou.mybatisplus.extension.service.IService;
  4. import org.springframework.web.multipart.MultipartFile;
  5. /**
  6. * <p>
  7. * 影视科目 服务类
  8. * </p>
  9. *
  10. * @author xiudun
  11. * @since 2021-02-28
  12. */
  13. public interface EduSubjectService extends IService<EduSubject> {
  14. //上传excel
  15. void batchImport(MultipartFile file,EduSubjectService subjectService);
  16. }
  • 业务实现类
  1. package com.xiudun.service.impl;
  2. import com.alibaba.excel.EasyExcel;
  3. import com.xiudun.entity.EduSubject;
  4. import com.xiudun.mapper.EduSubjectMapper;
  5. import com.xiudun.service.EduSubjectService;
  6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  7. import com.xiudun.util.ExcelLisener;
  8. import com.xiudun.vo.ReadData;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.web.multipart.MultipartFile;
  11. import java.io.InputStream;
  12. /**
  13. * <p>
  14. * 影视科目 服务实现类
  15. * </p>
  16. *
  17. * @author xiudun
  18. * @since 2021-02-28
  19. */
  20. @Service
  21. public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
  22. @Override
  23. public void batchImport(MultipartFile file, EduSubjectService subjectService) {
  24. try {
  25. //文件输入流
  26. InputStream inputStream = file.getInputStream();
  27. //需要指定读取那个Class,读取默认sheet,文件流自动关闭
  28. EasyExcel.read(inputStream, ReadData.class,new ExcelLisener(subjectService)).sheet().doRead();
  29. }catch (Exception e){
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  • 编写controller
  1. package com.xiudun.controller;
  2. import com.xiudun.service.EduSubjectService;
  3. import com.xiudun.util.R;
  4. import io.swagger.annotations.Api;
  5. import io.swagger.annotations.ApiOperation;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.web.bind.annotation.PostMapping;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RestController;
  10. import org.springframework.web.multipart.MultipartFile;
  11. /**
  12. * <p>
  13. * 影视科目 前端控制器
  14. * </p>
  15. *
  16. * @author xiudun
  17. * @since 2021-02-28
  18. */
  19. @Api("影片分类管理")
  20. @RestController
  21. @RequestMapping("/edu-subject")
  22. public class EduSubjectController {
  23. //注入业务
  24. @Autowired
  25. private EduSubjectService subjectService;
  26. @ApiOperation("excel批量导入数据")
  27. @PostMapping("addSubject")
  28. public R addSubject(MultipartFile file){
  29. //上传文件
  30. subjectService.batchImport(file,subjectService);
  31. return R.ok();
  32. }
  33. }
  • 使用swagger测试

在线影视 - 图41

  • 添加网关配置
  1. server:
  2. port: 7000
  3. spring:
  4. application:
  5. name: service-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true
  11. #跨域
  12. routes:
  13. - id: gateway-user
  14. uri: lb://service-actor
  15. predicates:
  16. - Path=/user/**
  17. - id: gateway-actor
  18. uri: lb://service-actor
  19. predicates:
  20. - Path=/edu-actor/**
  21. - id: gateway-movies
  22. uri: lb://service-actor
  23. predicates:
  24. - Path=/edu-movies/**
  25. - id: gateway-oss
  26. uri: lb://service-oss
  27. predicates:
  28. - Path=/oss/**
  29. - id: gateway-oss
  30. uri: lb://service-subject
  31. predicates:
  32. - Path=/edu-subject/**
  33. nacos:
  34. discovery:
  35. server-addr: localhost:8848
  • 前端使用树形表示数据展现,所以,需要在实体中添加一个外来属性
  1. package com.xiudun.entity;
  2. import com.baomidou.mybatisplus.annotation.FieldFill;
  3. import com.baomidou.mybatisplus.annotation.IdType;
  4. import java.util.Date;
  5. import com.baomidou.mybatisplus.annotation.TableField;
  6. import com.baomidou.mybatisplus.annotation.TableId;
  7. import java.io.Serializable;
  8. import java.util.List;
  9. import io.swagger.annotations.ApiModel;
  10. import io.swagger.annotations.ApiModelProperty;
  11. import lombok.Data;
  12. import lombok.EqualsAndHashCode;
  13. import lombok.experimental.Accessors;
  14. /**
  15. * <p>
  16. * 影视科目
  17. * </p>
  18. *
  19. * @author xiudun
  20. * @since 2021-02-28
  21. */
  22. @Data
  23. @EqualsAndHashCode(callSuper = false)
  24. @Accessors(chain = true)
  25. @ApiModel(value="EduSubject对象", description="影视科目")
  26. public class EduSubject implements Serializable {
  27. private static final long serialVersionUID = 1L;
  28. @ApiModelProperty(value = "影视类别ID")
  29. @TableId(value = "id", type = IdType.ID_WORKER_STR)
  30. private String id;
  31. @ApiModelProperty(value = "类别名称")
  32. private String title;
  33. @ApiModelProperty(value = "父ID")
  34. private String parentId;
  35. @ApiModelProperty(value = "排序字段")
  36. private Integer sort;
  37. @ApiModelProperty(value = "创建时间")
  38. @TableField(fill = FieldFill.INSERT)
  39. private Date gmtCreate;
  40. @ApiModelProperty(value = "更新时间")
  41. @TableField(fill = FieldFill.INSERT_UPDATE)
  42. private Date gmtModified;
  43. @ApiModelProperty("添加一个子菜单属性")
  44. @TableField(exist = false)
  45. private List<EduSubject> children;
  46. }
  • 在service中添加树形方法
  1. public interface EduSubjectService extends IService<EduSubject> {
  2. //上传excel
  3. void batchImport(MultipartFile file,EduSubjectService subjectService);
  4. //树形
  5. List<EduSubject> listTree();
  6. }
  • 实现类
  1. package com.xiudun.service.impl;
  2. import com.alibaba.excel.EasyExcel;
  3. import com.xiudun.entity.EduSubject;
  4. import com.xiudun.mapper.EduSubjectMapper;
  5. import com.xiudun.service.EduSubjectService;
  6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  7. import com.xiudun.util.ExcelLisener;
  8. import com.xiudun.vo.ReadData;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.web.multipart.MultipartFile;
  11. import java.io.InputStream;
  12. import java.util.List;
  13. import java.util.stream.Collectors;
  14. /**
  15. * <p>
  16. * 影视科目 服务实现类
  17. * </p>
  18. *
  19. * @author xiudun
  20. * @since 2021-02-28
  21. */
  22. @Service
  23. public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
  24. @Override
  25. public void batchImport(MultipartFile file, EduSubjectService subjectService) {
  26. try {
  27. //文件输入流
  28. InputStream inputStream = file.getInputStream();
  29. //需要指定读取那个Class,读取默认sheet,文件流自动关闭
  30. EasyExcel.read(inputStream, ReadData.class,new ExcelLisener(subjectService)).sheet().doRead();
  31. }catch (Exception e){
  32. e.printStackTrace();
  33. }
  34. }
  35. @Override
  36. public List<EduSubject> listTree() {
  37. //获取所有分类数据
  38. List<EduSubject> entities = baseMapper.selectList(null);
  39. //找到一级分类
  40. return entities.stream().filter(categoryEntity->categoryEntity.getParentId().equals("0"))
  41. .map(menu-> menu.setChildren(getChildrens(menu,entities)))
  42. .sorted((m1,m2)->(m1.getSort()==null?0:m1.getSort())-(m2.getSort()==null?0:m2.getSort()))
  43. .collect(Collectors.toList());
  44. }
  45. //编写一个读取树形的递归方法
  46. private List<EduSubject> getChildrens(EduSubject root,List<EduSubject> all){
  47. //递归查找
  48. List<EduSubject> list = all.stream().filter(categoryEntity -> {
  49. return categoryEntity.getParentId().equals(root.getId());//找子节点
  50. }).map(categoryEntity -> {//子节点
  51. categoryEntity.setChildren(getChildrens(categoryEntity, all));
  52. return categoryEntity;
  53. }).sorted((m1, m2) -> {
  54. return (m1.getSort() == null ? 0 : m1.getSort()) - (m2.getSort() == null ? 0 : m2.getSort());
  55. }).collect(Collectors.toList());
  56. //返回子节点集合
  57. return list;
  58. }
  59. }
  • 编写controller
  1. package com.xiudun.controller;
  2. import com.xiudun.service.EduSubjectService;
  3. import com.xiudun.util.R;
  4. import io.swagger.annotations.Api;
  5. import io.swagger.annotations.ApiOperation;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import org.springframework.web.multipart.MultipartFile;
  12. /**
  13. * <p>
  14. * 影视科目 前端控制器
  15. * </p>
  16. *
  17. * @author xiudun
  18. * @since 2021-02-28
  19. */
  20. @Api("影片分类管理")
  21. @RestController
  22. @RequestMapping("/edu-subject")
  23. public class EduSubjectController {
  24. //注入业务
  25. @Autowired
  26. private EduSubjectService subjectService;
  27. @ApiOperation("excel批量导入数据")
  28. @PostMapping("addSubject")
  29. public R addSubject(MultipartFile file){
  30. //上传文件
  31. subjectService.batchImport(file,subjectService);
  32. return R.ok();
  33. }
  34. @ApiOperation("分类数据Tree")
  35. @GetMapping("getListTree")
  36. public R listTree(){
  37. return R.ok().data("items", subjectService.listTree());
  38. }
  39. }
  • 使用swagger测试代码

在线影视 - 图42

6.5 完成前端编写

6.5.1 配置路由数据

  1. {
  2. path: '/edu-subject',
  3. component: Layout,
  4. redirect:'/getListTree',
  5. name:'subject',
  6. meta:{title:'影片分类',icon:'form'},
  7. children: [
  8. {
  9. path: 'list',
  10. component: () => import('@/views/actor/subjectList'),
  11. name: 'subjectList',
  12. meta: { title: '影片分类列表',icon:'form' }
  13. },
  14. {
  15. path: 'import',
  16. component: () => import('@/views/actor/importExcel'),
  17. name: 'subjectImport',
  18. meta: { title: '导入影片分类数据',icon:'form' }
  19. }
  20. ]
  21. },

6.5.2 在api中编写js脚本

  1. import request from '@/utils/request'
  2. export default {
  3. //影片分类列表
  4. getSubjectList(){
  5. return request({
  6. url: '/edu-subject/getListTree',
  7. method: 'get'
  8. })
  9. }
  10. }

6.5.3 编写vue页面:list.vue

  1. <template>
  2. <div class="app-container">
  3. <!-- 添加树形控件 -->
  4. <el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input>
  5. <el-tree
  6. class="filter-tree"
  7. :data="data2"
  8. :props="defaultProps"
  9. default-expand-all
  10. :filter-node-method="filterNode"
  11. ref="tree2">
  12. </el-tree>
  13. </div>
  14. </template>
  15. <script>
  16. // 导入js
  17. import subject from '@/api/movies/subject'
  18. export default {
  19. data(){
  20. return{
  21. filterText: '',
  22. data2: [],
  23. defaultProps:{
  24. children: 'children',
  25. label: 'title'
  26. }
  27. }
  28. },
  29. created(){
  30. this.getAllSubjectList()
  31. },
  32. watch:{
  33. filterText(val){
  34. this.$refs.tree2.filter(val)
  35. }
  36. },
  37. methods:{
  38. //获取影片分类数据
  39. getAllSubjectList(){
  40. subject.getSubjectList().then((result) => {
  41. this.data2 = result.data.items
  42. }).catch((err) => {
  43. console.log("数据失败")
  44. });
  45. },
  46. //过滤节点数据
  47. filterNode(value,data){
  48. if(!value) return true
  49. return data.title.toLowerCase().indexOf(value.toLowerCase())!==-1
  50. }
  51. }
  52. }
  53. </script>
  54. <style>
  55. </style>

6.5.4 完成“影片分类”导入

  1. <template>
  2. <div class="app-container">
  3. <!-- 添加表单 -->
  4. <el-form>
  5. <el-form-item label="信息描述">
  6. <el-tag type="info">excel模板样例</el-tag>
  7. <el-tag>
  8. <i class="el-icon-download"/>
  9. <a :href="'/static/影片分类.xlsx'">模板下载</a>
  10. </el-tag>
  11. </el-form-item>
  12. <!-- excel文件上传 -->
  13. <el-form-item label="上传Excel文件">
  14. <el-upload
  15. class="upload-demo"
  16. ref="upload"
  17. :on-success="fileUploadSuccess"
  18. :on-error="fileUploadError"
  19. :disabled="importBtnDisabled"
  20. :limit="1"
  21. :action="BASE_API+'edu-subject/addSubject'"
  22. :auto-upload="false"
  23. name="file"
  24. accept="application/vnd.ms-excel">
  25. <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
  26. <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
  27. </el-upload>
  28. </el-form-item>
  29. </el-form>
  30. </div>
  31. </template>
  32. <script>
  33. export default {
  34. data(){
  35. return{
  36. BASE_API: process.env.BASE_API,
  37. importBtnDisabled: false,
  38. loading: false
  39. }
  40. },
  41. created(){},
  42. methods:{
  43. submitUpload(){//上传
  44. this.importBtnDisabled = true
  45. this.loading = true
  46. this.$refs.upload.submit()
  47. },
  48. fileUploadSuccess(){//上传成功
  49. this.loading = false
  50. this.$message({
  51. type:'success',
  52. message: '影片分类数据添加成功'
  53. })
  54. },
  55. fileUploadError(){//上传失败
  56. this.loading = false
  57. this.$message({
  58. type:'error',
  59. message: '影片分类数据添加失败'
  60. })
  61. }
  62. }
  63. }
  64. </script>
  65. <style>
  66. </style>

第七章:影片视频管理

7.1 创建一个子模块

  • xiudun_service/service_movies模块

在线影视 - 图43

  • pom
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>xiudun_service</artifactId>
  7. <groupId>com.xiudun</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>service_movies</artifactId>
  12. <dependencies>
  13. <!--阿里云视频点播-->
  14. <dependency>
  15. <groupId>com.aliyun.oss</groupId>
  16. <artifactId>aliyun-sdk-oss</artifactId>
  17. </dependency>
  18. <!--日期工具-->
  19. <dependency>
  20. <groupId>joda-time</groupId>
  21. <artifactId>joda-time</artifactId>
  22. </dependency>
  23. </dependencies>
  24. </project>
  • application.yml
  1. server:
  2. port: 8084
  3. spring:
  4. application:
  5. name: service-movies
  6. datasource:
  7. driver-class-name: com.mysql.cj.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/movies?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
  9. username: root
  10. password: root
  11. jackson:
  12. date-format: yyyy-MM-dd HH:mm:ss
  13. time-zone: GMT+8
  14. cloud:
  15. nacos:
  16. discovery:
  17. server-addr: localhost:8848
  • 主配置类
  1. package com.xiudun;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  6. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  7. @SpringBootApplication
  8. @EnableSwagger2
  9. @EnableDiscoveryClient
  10. @MapperScan("com.xiudun.mapper")
  11. public class MoviesApplication {
  12. public static void main(String[] args) {
  13. SpringApplication.run(MoviesApplication.class,args);
  14. }
  15. }

7.2 编写java功能实现

7.2.1 数据表

  • edu_movies影片表
  • edu_movies_description影片简介表
  • edu_chapter影片章节表
  • edu_video视频表
  • edu_actor主角
  • edu_subject影片分类表

7.2.2 通过代码生成器生成表及对应的层代码

7.2.3 创建需要的数据载体VO

  • 影片描述实体
  1. package com.xiudun.vo;
  2. import io.swagger.annotations.Api;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. import java.math.BigDecimal;
  6. @Api("影片描述信息")
  7. @Data
  8. public class CourseInfoVo {
  9. @ApiModelProperty("影片id")
  10. private String id;
  11. @ApiModelProperty("演员的id")
  12. private String actorId;
  13. @ApiModelProperty("影片分类id")
  14. private String subjectId;
  15. @ApiModelProperty("影片一级分类id")
  16. private String subjectParentId;
  17. @ApiModelProperty("影片名称")
  18. private String title;
  19. @ApiModelProperty("影片价格,设置0表示免费")
  20. private BigDecimal price;
  21. @ApiModelProperty("影片的总段数")
  22. private Integer videoNum;
  23. @ApiModelProperty("影片封面海报")
  24. private String cover;
  25. @ApiModelProperty("影片简介")
  26. private String description;
  27. }
  • 影片发布信息
  1. package com.xiudun.vo;
  2. import io.swagger.annotations.Api;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. @Api("影片发布信息")
  6. @Data
  7. public class CoursePublishVo {
  8. @ApiModelProperty("影片id")
  9. private String id;
  10. @ApiModelProperty("影片名称")
  11. private String title;
  12. @ApiModelProperty("影片的总段数")
  13. private Integer videoNum;
  14. @ApiModelProperty("影片封面海报")
  15. private String cover;
  16. @ApiModelProperty("演员一级头衔")
  17. private String subjectLevelOne;
  18. @ApiModelProperty("演员二级头衔")
  19. private String subjectLevelTwo;
  20. @ApiModelProperty("主演名称")
  21. private String actorName;
  22. @ApiModelProperty("影片价格,用于显示")
  23. private String price;
  24. }
  • 按照主演查询的vo
  1. package com.xiudun.vo;
  2. import io.swagger.annotations.Api;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. @Api("用于查询")
  6. @Data
  7. public class ActorQuery {
  8. @ApiModelProperty("演员名称,用于like查询")
  9. private String name;
  10. @ApiModelProperty("演员的头衔,1表示一线演员,2表示二线演员")
  11. private Integer level;
  12. @ApiModelProperty(value = "上映时间",example = "2020-12-25 10:10:00")
  13. private String begin;
  14. @ApiModelProperty(value = "上映时间",example = "2021-02-25 10:10:00")
  15. private String end;
  16. }

7.2.4 编写dao(data access object)

  1. package com.xiudun.mapper;
  2. import com.xiudun.entity.EduMovies;
  3. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4. import com.xiudun.vo.CoursePublishVo;
  5. /**
  6. * <p>
  7. * 影视 Mapper 接口
  8. * </p>
  9. *
  10. * @author xiudun
  11. * @since 2021-03-01
  12. */
  13. public interface EduMoviesMapper extends BaseMapper<EduMovies> {
  14. //发布的影片
  15. CoursePublishVo getPulishCourseInfo(String courseId);
  16. }

7.2.5 编写service业务层

  1. package com.xiudun.service;
  2. import com.xiudun.entity.EduMovies;
  3. import com.baomidou.mybatisplus.extension.service.IService;
  4. import com.xiudun.vo.CourseInfoVo;
  5. /**
  6. * <p>
  7. * 影视 服务类
  8. * </p>
  9. *
  10. * @author xiudun
  11. * @since 2021-03-01
  12. */
  13. public interface EduMoviesService extends IService<EduMovies> {
  14. //添加影片基本信息
  15. String saveCourseInfo(CourseInfoVo courseInfoVo);
  16. //根据影片id获取影片基本信息
  17. CourseInfoVo getCourseInfo(String courseId);
  18. //修改影片基本信息
  19. void updateCourseInfo(CourseInfoVo courseInfoVo);
  20. //根据影片id发布
  21. CourseInfoVo puslishCourseInfo(String id);
  22. }
  • 实现类
  1. package com.xiudun.service.impl;
  2. import com.xiudun.entity.EduMovies;
  3. import com.xiudun.entity.EduMoviesDescription;
  4. import com.xiudun.exceptionhandler.XiudunException;
  5. import com.xiudun.mapper.EduMoviesMapper;
  6. import com.xiudun.service.EduMoviesDescriptionService;
  7. import com.xiudun.service.EduMoviesService;
  8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  9. import com.xiudun.vo.CourseInfoVo;
  10. import com.xiudun.vo.CoursePublishVo;
  11. import org.springframework.beans.BeanUtils;
  12. import org.springframework.stereotype.Service;
  13. import java.util.Date;
  14. /**
  15. * <p>
  16. * 影视 服务实现类
  17. * </p>
  18. *
  19. * @author xiudun
  20. * @since 2021-03-01
  21. */
  22. @Service
  23. public class EduMoviesServiceImpl extends ServiceImpl<EduMoviesMapper, EduMovies> implements EduMoviesService {
  24. //注入影片描述
  25. private EduMoviesDescriptionService eduMoviesDescriptionService;
  26. @Override
  27. public String saveCourseInfo(CourseInfoVo courseInfoVo) {
  28. //1.添加影片基本信息 2.需要把vo的数据转换影片的bean:CouseInfoVo--EduMovies(entity)
  29. EduMovies eduMovies = new EduMovies();
  30. BeanUtils.copyProperties(courseInfoVo,eduMovies);//按照相同的属性名称对拷
  31. int insert = baseMapper.insert(eduMovies);
  32. //判断是否保存成功
  33. if (insert==0) {
  34. throw new XiudunException(20001,"影片添加失败");
  35. }
  36. //获取添加后的影片id
  37. String moviesId = eduMovies.getId();
  38. //添加影片简介(edu_movies_description)
  39. EduMoviesDescription eduMoviesDescription = new EduMoviesDescription();
  40. eduMoviesDescription.setDescription(courseInfoVo.getDescription());
  41. //影片和影片简介是one-to-one关系
  42. eduMoviesDescription.setId(moviesId);
  43. eduMoviesDescriptionService.save(eduMoviesDescription);
  44. //返回影片的id
  45. return moviesId;
  46. }
  47. @Override//根据影片的id获取影片信息
  48. public CourseInfoVo getCourseInfo(String courseId) {
  49. EduMovies eduMovies = baseMapper.selectById(courseId);
  50. //影片信息的vo
  51. CourseInfoVo courseInfoVo = new CourseInfoVo();
  52. //完成对拷
  53. BeanUtils.copyProperties(eduMovies,courseInfoVo);
  54. //获取影片的简介信息
  55. EduMoviesDescription moviesDescription = eduMoviesDescriptionService.getById(courseId);
  56. courseInfoVo.setDescription(moviesDescription.getDescription());
  57. return courseInfoVo;
  58. }
  59. @Override//更新影片信息
  60. public void updateCourseInfo(CourseInfoVo courseInfoVo) {
  61. //创建影片的实体
  62. EduMovies eduMovies = new EduMovies();
  63. BeanUtils.copyProperties(courseInfoVo,eduMovies);//对拷
  64. //添加一个修改日期
  65. eduMovies.setGmtModified(new Date());
  66. int update = baseMapper.updateById(eduMovies);
  67. //判断更新是否成功
  68. if (update==0) {
  69. throw new XiudunException(20001,"影片信息更新失败");
  70. }
  71. //修改影片简介信息
  72. EduMoviesDescription moviesDescription = new EduMoviesDescription();
  73. moviesDescription.setId(courseInfoVo.getId());
  74. moviesDescription.setDescription(courseInfoVo.getDescription());
  75. moviesDescription.setGmtModified(new Date());
  76. eduMoviesDescriptionService.updateById(moviesDescription);
  77. }
  78. @Override//影片发布信息
  79. public CoursePublishVo puslishCourseInfo(String id) {
  80. //CoursePublishVo pulishCourseInfo = baseMapper.getPulishCourseInfo(id);
  81. return baseMapper.getPulishCourseInfo(id);
  82. }
  83. }

7.2.6 编写controller控制层

package com.xiudun.controller;


import com.xiudun.service.EduMoviesService;
import com.xiudun.util.R;
import com.xiudun.vo.CourseInfoVo;
import com.xiudun.vo.CoursePublishVo;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 影视 前端控制器
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@RestController
@RequestMapping("/edu-movies")
public class EduMoviesController {

    //依赖
    @Autowired
    private EduMoviesService moviesService;

    @ApiOperation("添加影片基本信息")
    @PostMapping("addMoviesInfo")//只要post对@RequestBody有效
    public R addMoviesInfo(@RequestBody CourseInfoVo courseInfoVo){
        //添加后返回影片id
        String id = moviesService.saveCourseInfo(courseInfoVo);
        return R.ok().data("moviesId", id);
    }

    @ApiOperation("根据影片id获取基本信息")
    @GetMapping("getMoviesInfo/{moviesId}")
    public R getMoviesInfo(@PathVariable String moviesId){
        CourseInfoVo courseInfo = moviesService.getCourseInfo(moviesId);
        return R.ok().data("courseInfoVo", courseInfo);
    }

    @ApiOperation("更新影片信息")
    @PutMapping("updateMoviesInfo")
    public R updateMoviesInfo(@RequestBody CourseInfoVo courseInfoVo){
        moviesService.updateCourseInfo(courseInfoVo);
        return R.ok();
    }

    @ApiOperation("根据影片id获取发布影片信息")
    @GetMapping("getPublishMoviesInfo/{id}")
    public R getPublishMoviesInfo(@PathVariable("id") String id){
        CoursePublishVo publishVo = moviesService.puslishCourseInfo(id);
        return R.ok().data("publishVo", publishVo);
    }
}
  • 添加网关配置
- id: gateway-oss3
   uri: lb://service-movies
   predicates:
   - Path=/edu-movies/**

7.3 编写影片管理的前端

7.3.1 编写js完成前后端的操作

import request from '@/utils/request'

export default  {
    //添加影片信息
    addMoviesInfo(moviesInfo){
        return request({
        url: `/edu-movies/addMoviesInfo`,
        method: 'post',
        data: moviesInfo
        })
    },
    //影片id获取信息
    getMoviesInfoId(id){
        return request({
            url: `/edu-movies/getMoviesInfo/${id}`,
            method: 'get'
        })
    },
    //更新影片信息
    updateMovies(moviesInfo){
        return request({
            url: `/edu-movies/updateMoviesInfo`,
            method: 'put',
            data: moviesInfo
        })
    }
}

7.3.2 添加一个路由路径调用vue页面

  • src/router/index.js
 {
    path: '/edu-movies',
    component: Layout,
    redirect: '/nested/menu1',
    name: 'Nested',
    meta: {
      title: '影片管理',
      icon: 'nested'
    },
    children: [
      {
        path: 'list',
        component: () => import('@/views/actor/movies'),
        name: 'moviesName',
        meta: { title: '影片信息',icon:'form' }
      },
      {
        path: 'import',
        component: () => import('@/views/actor/'),
        name: 'subjectImport',
        meta: { title: '导入影片分类数据',icon:'form' }
      }
    ]
  },

7.3.3 编写movies.vue

  • 在页面中添加”步骤条”控件
<template>
  <div class="app-container">
        <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="active" finish-status="success">
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>

        <!-- 添加影片表单 -->
        <el-form label-width="120px">
            <el-form-item label="影片标题">
                <el-input v-model="courseInfoVo.title" placeholder="例如:战争片:战狼"/>
            </el-form-item>

            <el-form-item label="影片分类">
                <el-select v-model="courseInfoVo.subjectParentId" placeholder="请选择一级分类" @change="subjectLevelOneChanged">
                    <el-option
                        v-for="subject in sujectOneList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>

                 <el-select v-model="courseInfoVo.subjectId" placeholder="请选择二级分类" >
                    <el-option
                        v-for="subject in sujectTwoList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="影片主演">
                <el-select v-model="courseInfoVo.actorId" placeholder="请选择" >
                    <el-option
                        v-for="actor in actorList"
                        :key="actor.id"
                        :label="actor.name"
                        :value="actor.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="总视频数">
                <el-input-number :min="0" v-model="courseInfoVo.videoNum" controls-position="right" label="请填写总视频数"/>
            </el-form-item>

            <el-form-item label="影片简介">
                <!-- 第三方控件 -->
            </el-form-item>

            <el-form-item label="影片海报">
                <el-upload
                    class="upload-demo"
                    action="BASE_API+'/oss/fileoss'"
                    :show-file-list="false"
                    :on-success="handleAvatarUpload"
                    :before-upload="beforeAvatarUpload"
                   >
                    <img :src="courseInfoVo.cover">
                </el-upload>
            </el-form-item>

            <el-form-item label="影片价格">
                <el-input-number :min="0" v-model="courseInfoVo.price" controls-position="right" label="0表示免费影片"/>
            </el-form-item>

            <el-form-item>
                <el-button type="primary" :disabled="saveBtnDisbled" @click="saveOrUpdate">保存并下一步</el-button>
            </el-form-item>
        </el-form>  

  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
  • 实现基本功能
<template>
  <div class="app-container">
        <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="1" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>

        <!-- 添加影片表单 -->
        <el-form label-width="120px">
            <el-form-item label="影片标题">
                <el-input v-model="courseInfoVo.title" placeholder="例如:战争片:战狼"/>
            </el-form-item>

            <el-form-item label="影片分类">
                <el-select v-model="courseInfoVo.subjectParentId" placeholder="请选择一级分类" @change="subjectLevelOneChanged">
                    <el-option
                        v-for="subject in subjectOneList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>

                 <el-select v-model="courseInfoVo.subjectId" placeholder="请选择二级分类" >
                    <el-option
                        v-for="subject in subjectTwoList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="影片主演">
                <el-select v-model="courseInfoVo.actorId" placeholder="请选择" >
                    <el-option
                        v-for="actor in actorList"
                        :key="actor.id"
                        :label="actor.name"
                        :value="actor.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="总视频数">
                <el-input-number :min="0" v-model="courseInfoVo.videoNum" controls-position="right" label="请填写总视频数"/>
            </el-form-item>

            <el-form-item label="影片简介">
                <!-- 第三方控件 -->
            </el-form-item>

            <el-form-item label="影片海报">
                <el-upload
                    class="upload-demo"
                    action="BASE_API+'/oss/fileoss'"
                    :show-file-list="false"
                    :on-success="handleAvatarUpload"
                    :before-upload="beforeAvatarUpload"
                   >
                    <img :src="courseInfoVo.cover">
                </el-upload>
            </el-form-item>

            <el-form-item label="影片价格">
                <el-input-number :min="0" v-model="courseInfoVo.price" controls-position="right" label="0表示免费影片"/>
            </el-form-item>

            <el-form-item>
                <el-button type="primary" :disabled="saveBtnDisbled" @click="saveOrUpdate">保存并下一步</el-button>
            </el-form-item>
        </el-form>  

  </div>
</template>

<script>
//导入js
import movies from '@/api/movies/movies'
import actor from '@/api/movies/actor'
import subject from '@/api/movies/subject'

export default {
   data(){
       return{
           courseInfoVo: {
               videoNum: 0,
               price: 0,
               cover: '/static/001.jpeg'
           },
           saveBtnDisbled: false,
           subjectOneList:[],
           subjectTwoList: [],
           actorList: []
       }
   },
   created(){
       //加载演员信息
       this.getListActor()
       //加载一级分类
       this.getOneSubject()
   },
   watch(){},
   methods:{
       saveOrUpdate(){},
       handleAvatarUpload(){},
       beforeAvatarUpload(){} ,
       //获取演员的列表
       getListActor(){
           actor.getActorAll().then((result) => {
               this.actorList = result.data.items
           }).catch((err) => {
               console.log('演员数据失败')
           });
       } ,
       //获取一级分类列表数据
       getOneSubject(){
           subject.getSubjectList().then(res=>{
               this.subjectOneList = res.data.items
           })
       } ,
       //当一级分类内容变化时影响二级分类数据
       subjectLevelOneChanged(value){
           //console.log('变化的值='+value) 
           //value是一级分类的id值
            //循环分类数据
            for(var i=0;i<this.subjectOneList.length;i++){
                //每一级分类
                var oneSubject = this.subjectOneList[i]
                //根据一级id获取对应的children
                if(value===oneSubject.id){
                    this.subjectTwoList = oneSubject.children
                    //如果没有那么把二级id清空
                    this.courseInfoVo.subjectId = ''
                }
            }
       } 
   }
}
</script>

<style>

</style>

7.3.3.1 模块优化:添加可视化编辑器

  • Tinymce复制到components和static中

在线影视 - 图44

  • 添加到html的配置中
templateParameters:{
   BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}

在线影视 - 图45

  • 在总的index.html中引入
<script src=<%=BASE_URL%>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%=BASE_URL%>/tinymce4.7.5/langs/zh_CN.js></script>

在线影视 - 图46

7.3.3.2 在vue引入

<template>
  <div class="app-container">
        <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="1" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>

        <!-- 添加影片表单 -->
        <el-form label-width="120px">
            <el-form-item label="影片标题">
                <el-input v-model="courseInfoVo.title" placeholder="例如:战争片:战狼"/>
            </el-form-item>

            <el-form-item label="影片分类">
                <el-select v-model="courseInfoVo.subjectParentId" placeholder="请选择一级分类" @change="subjectLevelOneChanged">
                    <el-option
                        v-for="subject in subjectOneList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>

                 <el-select v-model="courseInfoVo.subjectId" placeholder="请选择二级分类" >
                    <el-option
                        v-for="subject in subjectTwoList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="影片主演">
                <el-select v-model="courseInfoVo.actorId" placeholder="请选择" >
                    <el-option
                        v-for="actor in actorList"
                        :key="actor.id"
                        :label="actor.name"
                        :value="actor.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="总视频数">
                <el-input-number :min="0" v-model="courseInfoVo.videoNum" controls-position="right" label="请填写总视频数"/>
            </el-form-item>

            <el-form-item label="影片简介">
                <!-- 第三方控件 -->
                <tinymce :height="300" v-model="courseInfoVo.description"/>
            </el-form-item>

            <el-form-item label="影片海报">
                <el-upload
                    class="upload-demo"
                    :action="BASE_API+'oss/fileoss'"
                    :show-file-list="false"
                    :on-success="handleAvatarUpload"
                    :before-upload="beforeAvatarUpload"
                   >
                    <img :src="courseInfoVo.cover">
                </el-upload>
            </el-form-item>

            <el-form-item label="影片价格">
                <el-input-number :min="0" v-model="courseInfoVo.price" controls-position="right" label="0表示免费影片"/>
            </el-form-item>

            <el-form-item>
                <el-button type="primary" :disabled="saveBtnDisbled" @click="saveOrUpdate">保存并下一步</el-button>
            </el-form-item>
        </el-form>  

  </div>
</template>

<script>
//导入js
import movies from '@/api/movies/movies'
import actor from '@/api/movies/actor'
import subject from '@/api/movies/subject'
//引入组件
import Tinymce from '@/components/Tinymce'

export default {
   //需要声明
   components: {Tinymce},

   data(){
       return{
           courseInfoVo: {
               videoNum: 0,
               price: 0,
               cover: '/static/001.jpeg'
           },
           saveBtnDisbled: false,
           subjectOneList:[],
           subjectTwoList: [],
           actorList: [],
           BASE_API: process.env.BASE_API
       }
   },
   created(){
       //加载演员信息
       this.getListActor()
       //加载一级分类
       this.getOneSubject()
   },
   watch(){},
   methods:{
       saveOrUpdate(){},
       handleAvatarUpload(res,file){//上传海报
            this.courseInfoVo.cover = res.data.url//获取上传后的地址
       },
       beforeAvatarUpload(file){//上传前检查
            const isJPG = file.type==='image/jpeg'
            const is2M = file.size /1024 /1024 <2
            //判断
            if(!isJPG) this.$message.error('图片的格式必须是jpeg')
            if(!is2M) this.$message.error('图片的大小不能超过2MB')

            return isJPG && is2M
       } ,
       //获取演员的列表
       getListActor(){
           actor.getActorAll().then((result) => {
               this.actorList = result.data.items
           }).catch((err) => {
               console.log('演员数据失败')
           });
       } ,
       //获取一级分类列表数据
       getOneSubject(){
           subject.getSubjectList().then(res=>{
               this.subjectOneList = res.data.items
           })
       } ,
       //当一级分类内容变化时影响二级分类数据
       subjectLevelOneChanged(value){
           //console.log('变化的值='+value) 
           //value是一级分类的id值
            //循环分类数据
            for(var i=0;i<this.subjectOneList.length;i++){
                //每一级分类
                var oneSubject = this.subjectOneList[i]
                //根据一级id获取对应的children
                if(value===oneSubject.id){
                    this.subjectTwoList = oneSubject.children
                    //如果没有那么把二级id清空
                    this.courseInfoVo.subjectId = ''
                }
            }
       } 
   }
}
</script>

<style>

</style>

7.3.3.3 完成影片信息的添加并下一步

<template>
  <div class="app-container">
        <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="1" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>

        <!-- 添加影片表单 -->
        <el-form label-width="120px">
            <el-form-item label="影片标题">
                <el-input v-model="courseInfoVo.title" placeholder="例如:战争片:战狼"/>
            </el-form-item>

            <el-form-item label="影片分类">
                <el-select v-model="courseInfoVo.subjectParentId" placeholder="请选择一级分类" @change="subjectLevelOneChanged">
                    <el-option
                        v-for="subject in subjectOneList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>

                 <el-select v-model="courseInfoVo.subjectId" placeholder="请选择二级分类" >
                    <el-option
                        v-for="subject in subjectTwoList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="影片主演">
                <el-select v-model="courseInfoVo.actorId" placeholder="请选择" >
                    <el-option
                        v-for="actor in actorList"
                        :key="actor.id"
                        :label="actor.name"
                        :value="actor.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="总视频数">
                <el-input-number :min="0" v-model="courseInfoVo.videoNum" controls-position="right" label="请填写总视频数"/>
            </el-form-item>

            <el-form-item label="影片简介">
                <!-- 第三方控件 -->
                <tinymce :height="300" v-model="courseInfoVo.description"/>
            </el-form-item>

            <el-form-item label="影片海报">
                <el-upload
                    class="upload-demo"
                    :action="BASE_API+'oss/fileoss'"
                    :show-file-list="false"
                    :on-success="handleAvatarUpload"
                    :before-upload="beforeAvatarUpload"
                   >
                    <img :src="courseInfoVo.cover">
                </el-upload>
            </el-form-item>

            <el-form-item label="影片价格">
                <el-input-number :min="0" v-model="courseInfoVo.price" controls-position="right" label="0表示免费影片"/>
            </el-form-item>

            <el-form-item>
                <el-button type="primary" :disabled="saveBtnDisbled" @click="saveOrUpdate">保存并下一步</el-button>
            </el-form-item>
        </el-form>  

  </div>
</template>

<script>
//导入js
import movies from '@/api/movies/movies'
import actor from '@/api/movies/actor'
import subject from '@/api/movies/subject'
//引入组件
import Tinymce from '@/components/Tinymce'

export default {
   //需要声明
   components: {Tinymce},

   data(){
       return{
           courseInfoVo: {
               videoNum: 0,
               price: 0,
               cover: '/static/001.jpeg'
           },
           saveBtnDisbled: false,
           subjectOneList:[],
           subjectTwoList: [],
           actorList: [],
           BASE_API: process.env.BASE_API,
           moviesId: ''
       }
   },
   created(){     

       //判断路由的参数:id如果在就更新否则添加
       if(this.$route.params && this.$route.params.id){
            this.moviesId = this.$route.params.id
            //调用查询方法把数据添加到页面
            this.getInfo()

       } else{
            //加载演员信息
            this.getListActor()
            //加载一级分类
            this.getOneSubject()
       }

   },
   watch(){},
   methods:{
       saveOrUpdate(){
           //判断
           if(this.moviesId!=''){
               this.updateCourse()
           }else{               
               this.addCourse()
           }
       },
       //根据id获取影片信息
       getInfo(){},
       //保存影片信息
       addCourse(){
           movies.addMoviesInfo(this.courseInfoVo).then(res=>{
               //提示
               this.$message({
                   type: 'success',
                   message: '影片信息添加成功'
               })
               //跳到第二步:视频章节
               this.$router.push({path:'/edu-movies/chapter/'+res.data.moviesId})

           })
       }, 
        //更新影片信息
       updateCourse(){
           movies.updateMovies(this.courseInfoVo).then(res=>{
               //提示
               this.$message({
                   type: 'success',
                   message: '影片信息更新成功'
               })
               //跳到第二步:视频章节
               this.$router.push({path:'/edu-movies/chapter/'+this.moviesId})

           })
       },

       handleAvatarUpload(res,file){//上传海报
            this.courseInfoVo.cover = res.data.url//获取上传后的地址
       },
       beforeAvatarUpload(file){//上传前检查
            const isJPG = file.type==='image/jpeg'
            const is2M = file.size /1024 /1024 <2
            //判断
            if(!isJPG) this.$message.error('图片的格式必须是jpeg')
            if(!is2M) this.$message.error('图片的大小不能超过2MB')

            return isJPG && is2M
       } ,
       //获取演员的列表
       getListActor(){
           actor.getActorAll().then((result) => {
               this.actorList = result.data.items
           }).catch((err) => {
               console.log('演员数据失败')
           });
       } ,
       //获取一级分类列表数据
       getOneSubject(){
           subject.getSubjectList().then(res=>{
               this.subjectOneList = res.data.items
           })
       } ,
       //当一级分类内容变化时影响二级分类数据
       subjectLevelOneChanged(value){
           //console.log('变化的值='+value) 
           //value是一级分类的id值
            //循环分类数据
            for(var i=0;i<this.subjectOneList.length;i++){
                //每一级分类
                var oneSubject = this.subjectOneList[i]
                //根据一级id获取对应的children
                if(value===oneSubject.id){
                    this.subjectTwoList = oneSubject.children
                    //如果没有那么把二级id清空
                    //this.courseInfoVo.subjectId = ''
                }
            }
       },
       getInfo(){//获取更新的数据回填到控件中
          movies.getMoviesInfoId(this.moviesId).then(response=>{
              //获取影片的基本信息、获取一级二级分类的信息
              this.courseInfoVo = response.data.courseInfoVo
              //根据id获取 一级二级分类的信息
              subject.getSubjectList().then(res=>{
                  //获取一级分类
                  this.subjectOneList = res.data.items
                  //遍历一级分类
                  for(var i=0;i<this.subjectOneList.length;i++){
                      //获取每个一级分类
                      var oneSubject = this.subjectOneList[i]
                      //根据id获取对应的二级分类
                      if(this.courseInfoVo.subjectParentId==oneSubject.id){
                          this.subjectTwoList = oneSubject.children
                      }

                  }  

              }) 
              //所有的演员
              this.getListActor()
          })

       } 
   }
}
</script>

<style>

</style>

7.3.3.4 编写视频章节vue

<template>
  <div class="app-container">
    <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="2" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>
    <el-button type="text" @click="openChapterDialog()">添加片段</el-button>

    <div>
        <el-button @click="prevous">上一步</el-button>
        <el-button :disabled="saveBtnDisbled" type="primary" @click="next">下一步</el-button>

    </div>
  </div>
</template>

<script>
export default {
    data(){
        return{
            saveBtnDisbled: false,
            courseId: ''//影片id
        }        

    },
    created(){
        //获取路由id
        if(this.$route.params && this.$route.params.id){
            //获取影片的id
            this.courseId = this.$route.params.id
        }
        console.log('id='+this.courseId)
    },
    methods:{
        prevous(){//上一步:更新影片信息
            this.$router.push({path:'/edu-movies/info/'+this.courseId})            
        },
        next(){}
    }
}
</script>
<style>
</style>

7.4 影片视频章节

在线影视 - 图47

7.4.1 后端编写

  • 创建视频节数vo
package com.xiudun.vo;

import lombok.Data;
import java.util.ArrayList;
import java.util.List;

@Data
public class ChapterVo {
    private String id;
    private String title ;
    //视频节数
    private List<VideoVo> children = new ArrayList<>();
}
  • 视频的描述
package com.xiudun.vo;

import lombok.Data;

@Data
public class VideoVo {
    private String id;
    private String title;
}
  • 视频节数的业务层
package com.xiudun.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.xiudun.entity.EduChapter;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiudun.vo.ChapterVo;

import java.util.List;

/**
 * <p>
 * 影视 服务类
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
public interface EduChapterService extends IService<EduChapter> {

    //根据影片id查询视频的列表
    List<ChapterVo> getChapterVideoByCourseId(String courseId);

    //根据视频id删除
    boolean deleteChapter(String chapterId);
}
  • 业务层实现
package com.xiudun.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiudun.entity.EduChapter;
import com.xiudun.entity.EduVideo;
import com.xiudun.exceptionhandler.XiudunException;
import com.xiudun.mapper.EduChapterMapper;
import com.xiudun.service.EduChapterService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiudun.service.EduVideoService;
import com.xiudun.vo.ChapterVo;
import com.xiudun.vo.VideoVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 影视 服务实现类
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

    //注入
    @Autowired
    private EduVideoService videoService;

    @Override//根据id获取视频列表
    public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
        //根据影片id,获取视频的片段数
        QueryWrapper<EduChapter> wrapperChapter = new QueryWrapper<>();
        wrapperChapter.eq("movies_id",courseId);
        List<EduChapter> eduChapterList = baseMapper.selectList(wrapperChapter);

       //获取影视视频
        QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
        wrapperVideo.eq("movies_id",courseId);
        List<EduVideo> eduVideoList = videoService.list(wrapperVideo);

        //封装视频片段数据
        List<ChapterVo> finalList = new ArrayList<>();
        //遍历片段
        for (int i = 0; i < eduChapterList.size(); i++) {
            //每个片段
            EduChapter eduChapter = eduChapterList.get(i);
            //对拷到VO中
            ChapterVo chapterVo = new ChapterVo();
            BeanUtils.copyProperties(eduChapter,chapterVo);
            //添加到集合中
            finalList.add(chapterVo);


            //创建一个存储视频信息的集合
            ArrayList<VideoVo> videoList = new ArrayList<>();
            //封装视频
            for (int n = 0; n < eduVideoList.size(); n++) {
                //获取每个视频信息
                EduVideo eduVideo = eduVideoList.get(n);
                //判断视频和片段的外键是否一致
                if (eduVideo.getChapterId().equals(eduChapter.getId())) {
                    //封装到集合中
                    VideoVo videoVo = new VideoVo();
                    //对拷
                    BeanUtils.copyProperties(eduVideo,videoVo);
                    videoList.add(videoVo);
                }
            }
            //把视频的信息列表添加到片段中
            chapterVo.setChildren(videoList);
        }
        return finalList;
    }

    @Override//删除片段
    public boolean deleteChapter(String chapterId) {
        //需要判断:因为如果含有视频了,那么就不能删除了
        QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
        wrapper.eq("chapter_id",chapterId);
        int count = videoService.count(wrapper);

        //判断
        if (count>0) {
            throw new XiudunException(20001,"不能删除含有视频的片段");
        }
        return baseMapper.deleteById(chapterId)>0;
    }
}
  • controller控制层
package com.xiudun.controller;


import com.xiudun.entity.EduChapter;
import com.xiudun.service.EduChapterService;
import com.xiudun.util.R;
import com.xiudun.vo.ChapterVo;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 * 影视 前端控制器
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@RestController
@RequestMapping("/edu-chapter")
public class EduChapterController {
    @Autowired
    private EduChapterService chapterService;

    @ApiOperation("根据id获取片段信息")
    @GetMapping("getChapterVideo/{courseId}")
    public R getChapterVideo(@PathVariable String courseId){
        List<ChapterVo> list = chapterService.getChapterVideoByCourseId(courseId);
        return R.ok().data("allChapterVideo", list);
    }

    @ApiOperation("添加影片片段信息")
    @PostMapping("addChapter")
    public R addChapter(@RequestBody EduChapter eduChapter){
        chapterService.save(eduChapter);
        return R.ok();
    }

    @ApiOperation("根据id获取视频信息")
    @GetMapping("getChapterInfo/{chapterId}")
    public R getChapterInfo(@PathVariable String chapterId){
        EduChapter eduChapter = chapterService.getById(chapterId);
        return R.ok().data("chapter", eduChapter);
    }

    @ApiOperation("修改视频片段信息")
    @PutMapping("updateChapter")
    public R updateChapter(@RequestBody EduChapter eduChapter){
        chapterService.updateById(eduChapter);
        return R.ok();
    }

    @ApiOperation("删除视频片段信息")
    @DeleteMapping("deleteChapter/{chapterId}")
    public R deleteChapter(@PathVariable String chapterId){
        return chapterService.deleteChapter(chapterId) ? R.ok() : R.error();
    }
}
  • 测试后端

在线影视 - 图48

7.4.2 编写前端

7.4.2.1 vue基础编写

  • 编写api/chapter.js
import request from '@/utils/request'

export default  {
    //根据id获取视频列表
    getAllChapterVideo(courseId){
        return request({
        url: `/edu-chapter/getChapterVideo/${courseId}`,
        method: 'get'
        })
    },
    //添加视频节数
    addChapter(chapter){
        return request({
        url: `/edu-chapter/addChapter`,
        method: 'post',
        data: chapter
        })
    },
    //根据id获取视频描述内容
    getChapter(courseId){
        return request({
        url: `/edu-chapter/getChapterInfo/${courseId}`,
        method: 'get'
        })
    },
    //修改视频片段描述内容
    updateChapter(chapter){
        return request({
        url: `/edu-chapter/updateChapter`,
        method: 'put',
        data: chapter
        })
    },
    //删除视频片段描述内容
    deleteChapter(courseId){
        return request({
        url: `/edu-chapter/deleteChapter/${courseId}`,
        method: 'delete'
        })
    }
}
  • 在index.js完成路由路径
{
    path: '/edu-movies',
    component: Layout,
    redirect: '/nested/menu1',
    name: 'Nested',
    meta: {
      title: '影片管理',
      icon: 'nested'
    },
    children: [
      {
        path: 'info',
        component: () => import('@/views/actor/movies'),
        name: 'moviesName',
        meta: { title: '影片信息',icon:'form' }
      },
      {
        path: 'list',
        component: () => import('@/views/actor/list'),
        name: 'subjectImport',
        meta: { title: '影片列表',icon:'form' }
      }, 
      {
        path: `info/:id`,
        component: () => import('@/views/actor/movies'),
        name: 'moviesName1',
        meta: { title: '影片信息',icon:'form' },
        hidden: true
      },
      {
        path: 'chapter/:id',
        component: () => import('@/views/actor/chapter'),
        name: 'chapterName',
        meta: { title: '影片章节视频',icon:'form' },
        hidden: true
      },
      {
        path: 'publish/:id',
        component: () => import('@/views/actor/publish'),
        name: 'chapterName',
        meta: { title: '影片发布',icon:'form' },
        hidden: true
      }
    ]
  }

7.4.2.2 编写chapter.vue页面

<template>
  <div class="app-container">
    <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="2" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>
    <el-button type="text" @click="openChapterDialog()">添加片段</el-button>
    <!-- 影片片段 -->
    <ul class="chanpterList">
        <li
           v-for="chapter in chapterVideoList"
            :key="chapter.id">
            <p>
                {{chapter.title}}
                <span class="acts">
                    <el-button type="text" @click="openVideo(chapter.id)">添加视频</el-button>
                    <el-button type="text" @click="openEditChapter(chapter.id)">编辑视频</el-button>
                    <el-button type="text" @click="removeChapter(chapter.id)">删除视频</el-button>
                </span>
            </p>
            <!-- 视频 -->
            <ul class="chanpterList videoList">
                <li
                   v-for="video in chapter.children"
                   :key="video.id">
                   <p>
                       {{video.title}}
                       <span class="acts">
                           <el-button type="text">编辑</el-button>
                           <el-button type="text" @click="removeVideo(video.id)">删除</el-button>
                       </span>
                   </p>

                </li>
            </ul>

        </li>        
    </ul>

    <div>
        <el-button @click="prevous">上一步</el-button>
        <el-button :disabled="saveBtnDisbled" type="primary" @click="next">下一步</el-button>
    </div>

   <!-- 弹出窗体:添加或修改片段 -->
    <el-dialog title="添加影片片段" :visible.sync="dialogChapterFormVisible">
        <el-form :model="chapter">
            <el-form-item label="片段名称" >
                <el-input v-model="chapter.title" />
            </el-form-item>
             <el-form-item label="片段排序" >
                <el-input-number v-model="chapter.sort" :min="0" controls-position="right"/>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
        </div>
    </el-dialog>

    <!-- 添加和修改视频表单 -->

  </div>
</template>

<script>
//导入js
import chapter from '@/api/movies/chapter'
export default {
    data(){
        return{
            saveBtnDisbled: false,
            courseId: '',//影片id
            chapterVideoList: [],
            chapter:{
                sort: 0
            },
            dialogChapterFormVisible: false
        }     
    },
    created(){
        //获取路由id
        if(this.$route.params && this.$route.params.id){
            //获取影片的id
            this.courseId = this.$route.params.id
            //根据影片的id获取片段
            this.getChapterVideo()
        }
        console.log('id='+this.courseId)
    },
    methods:{
        prevous(){//上一步:更新影片信息
            this.$router.push({path:'/edu-movies/info/'+this.courseId})            
        },
        //根据影片的id获取片段
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId).then((result) => {
                this.chapterVideoList = result.data.allChapterVideo
            }).catch((err) => {

            });
        },
        openChapterDialog(){//弹出窗体
            this.dialogChapterFormVisible = true
        },
        openVideo(){},
        openEditChapter(){},
        removeChapter(){},
        removeVideo(){},
        saveOrUpdate(){//保存或更新
           //判断
           if(this.chapter.id){
              this.updateChapter()  
           }else{
              this.addChapter()  
           }
        },
        //添加片段
        addChapter(){
            //影片的id为外键
            this.chapter.moviesId = this.courseId
            chapter.addChapter(this.chapter).then(res=>{
                //关闭弹出窗体
                this.dialogChapterFormVisible =false
                //提示
                this.$message({
                    type: 'success',
                    message: '片段保存成功'
                })
            })
            //刷新页面
            this.getChapterVideo()
        },
        //更新片段
        updateChapter(){
            chapter.updateChapter(this.chapter).then(res=>{
                //关闭弹出窗体
                this.dialogChapterFormVisible =false
                //提示
                this.$message({
                    type: 'success',
                    message: '片段更新成功'
                })
            })
            //刷新页面
            this.getChapterVideo()
        },

        next(){}
    }

}
</script>

<style>

</style>

7.5 阿里云的[视频点播]

在线影视 - 图49

在线影视 - 图50

  • 注意:数据库中存储视频的id,不能存储地址,因为视频地址加密后就不能播放了

7.5.1 编写后端

  • 创建一个微服务子模块:xiudun_service/service_video

在线影视 - 图51

  • pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>xiudun_service</artifactId>
        <groupId>com.xiudun</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service_video</artifactId>

    <dependencies>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--  阿里云上传的依赖-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-sdk-vod-upload</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-vod</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <classpathScope>test</classpathScope>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • 复制:aliyun-java-vod-upload-1.4.11.jar到项目中
  • 编写配置文件application.properties
server.port=8085
#服务名称
spring.application.name=service-video
#开发
spring.profiles.active=dev

#oss的网址、id、秘钥
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=LTAI4GEJWzPRvp1mSdaJ
aliyun.oss.file.keysecret=TtBQk0jzh7nABMGwlGtsG

#buket名称
aliyun.oss.file.bucketname=xiudun
#最大上传大小
spring.servlet.multipart.max-file-size=1024MB
spring.servlet.multipart.max-request-size=1024MB

#nacos地址
spring.cloud.nacos.discovery.server-addr=localhost:8848

**特别注意:因为aliyun-java-vod-upload-1.4.11.jar暂时没有开源,所以,需要1.添加到项目中 2.需要maven编译后引入项目中

mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.11 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.11.jar

在线影视 - 图52

7.5.1.1 编写一个测试

  • 编写一个初始化阿里云的类:获取客户端对象
package com.xiudun;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.profile.DefaultProfile;

public class InitObject {
    public static DefaultAcsClient initCodClient(String accessKeyId,String accessKeySecret){
        //客户端
        DefaultAcsClient client = null;
        try {
            String regionId = "cn-shanghai";
            DefaultProfile profile = DefaultProfile.getProfile(regionId,accessKeyId,accessKeySecret);
            client = new DefaultAcsClient(profile);            
        }catch (Exception e){
            e.printStackTrace();
        }
        return client;
    }
}
  • 测试
package com.xiudun;

import com.aliyun.vod.upload.impl.UploadVideoImpl;
import com.aliyun.vod.upload.req.UploadVideoRequest;
import com.aliyun.vod.upload.resp.UploadVideoResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoRequest;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoResponse;
import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthRequest;
import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthResponse;

import java.util.List;

//测试代码
public class TestVod {
    //阿里云的id
    static  String id ="LTAI4GEJWzPRvp1mSXQ9hdaJ";
    //阿里云秘钥
    static String key ="TtBQk0jzh7nAB7kkhGQ3cJMGwlGtsG";
    //阿里云的视频id
    static String videoId = "a9842a69471743bda507734e238f8655";

    //根据视频id获取播放凭证
    public static void getPlayAuth(){
        try {
            //获取客户端
            DefaultAcsClient client = InitObject.initCodClient(id, key);

            //创建请求和相应对象
            GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
            GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();

            //把视频的id添加到请求对象中
            request.setVideoId(videoId);
            response = client.getAcsResponse(request);

            System.out.println("PlayAuth="+response.getPlayAuth());

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //根据id获取视频的播放地址
    public static void getPlayUrl(){
        try {
            //获取客户端
            DefaultAcsClient client = InitObject.initCodClient(id, key);

            //创建请求和相应对象
            GetPlayInfoRequest request = new GetPlayInfoRequest();
            GetPlayInfoResponse response = new GetPlayInfoResponse();

            //把视频的id添加到请求对象中
            request.setVideoId(videoId);
            response = client.getAcsResponse(request);

            List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();
            //获取视频的播放地址
            for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {
                System.out.println("url="+playInfo.getPlayURL()+"\n");
            }
            //获取基本信息
            System.out.println("title="+response.getVideoBase().getTitle()+"\n");


        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //视频上传
    public static void uploadVideo(){
        //视频上传后的名称
        String title = "zmx";
        //上传视频的地址
        String fileName = "d:/xiudun/123.mp4";

        //上传
        UploadVideoRequest request = new UploadVideoRequest(id, key, title, fileName);
        //如果视频大可以分片上传
        request.setPartSize(2*1024*1024L);//2MB
        //指定并发线程数
        request.setTaskNum(1);

        UploadVideoImpl uploadVideo = new UploadVideoImpl();
        UploadVideoResponse response = uploadVideo.uploadVideo(request);

        //判断
        if (response.isSuccess()) {
            System.out.println("videoId="+response.getVideoId());
        }else{
            //返回错误码和错误信息
            System.out.println("errorCode="+response.getCode()+response.getMessage());
        }
    }


    public static void main(String[] args) {
        uploadVideo();
    }
}

7.5.1.2 主配置类

package com.xiudun;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableSwagger2
public class VideoApp {
    public static void main(String[] args) {
        SpringApplication.run(VideoApp.class,args);
    }
}

7.5.1.3 编写工具类

package com.xiudun.util;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.profile.DefaultProfile;

public class InitObject {
    public static DefaultAcsClient initCodClient(String accessKeyId,String accessKeySecret){
        //客户端
        DefaultAcsClient client = null;
        try {
            String regionId = "cn-shanghai";
            DefaultProfile profile = DefaultProfile.getProfile(regionId,accessKeyId,accessKeySecret);
            client = new DefaultAcsClient(profile);
        }catch (Exception e){
            e.printStackTrace();
        }
        return client;
    }
}
  • 工具:读取properties文件
package com.xiudun.util;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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

7.5.1.4 编写业务service

package com.xiudun.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xiudun.entity.EduVideo;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/**
 * <p>
 * 影视视频 服务类
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
public interface EduVideoService extends IService<EduVideo> {

    //视频上传到阿里云:视频点播
    String uploadVideoAly(MultipartFile file);

    //删除阿里云中的视频文件
    void removeMoreAlyVideo(List videoList);

    //按照id删除视频文件
    public void deleteById(String videoId)
}
  • 实现类
package com.xiudun.service;

import com.aliyun.vod.upload.impl.UploadVideoImpl;
import com.aliyun.vod.upload.req.UploadStreamRequest;
import com.aliyun.vod.upload.resp.UploadStreamResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.vod.model.v20170321.DeleteVideoRequest;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiudun.entity.EduVideo;
import com.xiudun.exceptionhandler.XiudunException;
import com.xiudun.mapper.EduVideoMapper;
import com.xiudun.util.ConstantPropertiesUtils;
import com.xiudun.util.InitObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;

/**
 * <p>
 * 影视视频 服务实现类
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@Service
public class EduVideoServiceImpl extends ServiceImpl<EduVideoMapper, EduVideo> implements EduVideoService {

    @Override//视频上传
    public String uploadVideoAly(MultipartFile file) {
        //获取视频名称
        String filename = file.getOriginalFilename();
        //视频上传后的名称
        String title = filename.substring(0, filename.lastIndexOf("."));
        //上传后的视频id
        String videoId = null;

        //完成上传
        try {
            //获取输入流
            InputStream inputStream = file.getInputStream();
            //阿里云请求对象
            UploadStreamRequest request = new UploadStreamRequest(ConstantPropertiesUtils.ACCESS_KEY_ID,
                    ConstantPropertiesUtils.ACCESS_KEY_SECRET, title, filename, inputStream);
            //阿里云响应对象
            UploadVideoImpl uploadVideo = new UploadVideoImpl();
            UploadStreamResponse response = uploadVideo.uploadStream(request);

            //判断上传是否成功
            if (response.isSuccess()) {
                videoId = response.getVideoId();
            }else{
                videoId = response.getVideoId();
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        return videoId;
    }

    @Override
    public void removeMoreAlyVideo(List videoList) {
        //获取阿里云对象客户端
        DefaultAcsClient client = InitObject.initCodClient(ConstantPropertiesUtils.ACCESS_KEY_ID,
                ConstantPropertiesUtils.ACCESS_KEY_SECRET);

        //创建阿里云请求对象
        DeleteVideoRequest request = new DeleteVideoRequest();
        //按照视频的id删除,需要把数据转为指定格式:1,2,3
        String videoIds = StringUtils.join(videoList.toArray(), ",");

        //向请求删除对象添加
        request.setVideoIds(videoIds);

        //删除
        try {
            client.getAcsResponse(request);
        } catch (ClientException e) {
            e.printStackTrace();
            throw new XiudunException(20001,"视频删除失败");
        }
    }
    @Override
    public void deleteById(String videoId) {
        //获取阿里云对象客户端
        DefaultAcsClient client = InitObject.initCodClient(ConstantPropertiesUtils.ACCESS_KEY_ID,
                ConstantPropertiesUtils.ACCESS_KEY_SECRET);

        //创建阿里云请求对象
        DeleteVideoRequest request = new DeleteVideoRequest();

        //向请求删除对象添加
        request.setVideoIds(videoId);

        //删除
        try {
            client.getAcsResponse(request);
        } catch (ClientException e) {
            e.printStackTrace();
            throw new XiudunException(20001,"视频删除失败");
        }
    }
}

7.5.1.5 控制层controller

package com.xiudun.controller;


import com.xiudun.exceptionhandler.XiudunException;
import com.xiudun.service.EduVideoService;
import com.xiudun.util.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/**
 * <p>
 * 影视视频 前端控制器
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@RestController
@RequestMapping("/edu-video")
public class EduVideoController {

    @Autowired
    private EduVideoService videoService;

    @ApiOperation("视频上传")
    @PostMapping("uploadAlyVideo")
    public R uploadAlyVideo(MultipartFile file){
        //视频id
        String id = videoService.uploadVideoAly(file);
        return R.ok().data("videoId", id);
    }

    @ApiOperation("根据id删除视频")
    @DeleteMapping("deleteVideo/{id}")
    public R deleteVideo(@PathVariable String id){
       videoService.deleteById(id);
        return R.ok();
    }


    @ApiOperation("批量删除视频")
    @DeleteMapping("deleteBatchVideo")
    public R deleteBatchVideo(@RequestParam("videoList")List<String> videoList){
        videoService.removeMoreAlyVideo(videoList);
        return R.ok();
    }
}
  • 配置网关

7.5.2 编写前端实现

  • 影片显示列表页
<template>
  <div class="app-container">
    <!-- 影片查询 -->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="moviesQuery.title" placeholder="影片名称"></el-input>
      </el-form-item>
      <el-form-item>
        <el-select v-model="moviesQuery.status" placeholder="影片状态">
          <el-option label="已发布" value="Normal"></el-option>
          <el-option label="未发布" value="Draft"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getList()">查 询</el-button>
        <el-button type="default" @click="resetData()">清 空</el-button>
      </el-form-item>
    </el-form>

    <!-- 影片列表 -->
    <el-table :data="list" style="width: 100%">
      <el-table-column label="序号" width="180">
        <template slot-scope="scope">          
          {{ (current-1)*limit+scope.$index+1 }}
        </template>
      </el-table-column>
      <el-table-column prop="title" label="影片名称" width="180"/>
      <el-table-column label="影片状态">
        <template slot-scope="scope">          
          {{ scope.row.status==='Normal'?'已发布':'未发布'}}
        </template>
      </el-table-column>
      <el-table-column prop="videoNum" label="视频数" width="180"/>
      <el-table-column prop="gmtCreate" label="影片添加时间" width="180"/>
      <el-table-column prop="viewCount" label="浏览量" width="180"/>

      <el-table-column label="操作">
        <template slot-scope="scope">
          <el-button size="mini">编辑影片基本信息</el-button>
          <el-button size="mini">编辑影片片段信息</el-button>
          <el-button  size="mini"  type="danger" @click="removeDataById(scope.row.id)">删除影片</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页插件 -->
    <el-pagination     
      @current-change="getList"
      :current-page.sync="current"
      :page-size="limit"
      layout="prev, pager, next, jumper"
      :total="total">
    </el-pagination>
  </div>
</template>

<script>
export default {
  data() {
    return {
      moviesQuery: {},
      list: [],
      current: 1,
      limit: 3,
      total: 0
    };
  },
  created() {},
  methods: {
    getlist() {
      //查询
    },
    resetData() {
      //清空查询条件
      this.moviesQuery = {};
    },
    removeDataById(){//删除影片

    }
  },
};
</script>

<style>
</style>

7.5.3 完善影片信息的功能

  • 创建一个查询的vo:MoviesQuery.java
package com.xiudun.vo;

import lombok.Data;

//查询影片使用的
@Data
public class MoviesQuery {
    private String title;
    private String status;
}
  • 完善控制层:EduMoviesController.java
package com.xiudun.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xiudun.entity.EduMovies;
import com.xiudun.service.EduMoviesService;
import com.xiudun.util.R;
import com.xiudun.vo.CourseInfoVo;
import com.xiudun.vo.CoursePublishVo;
import com.xiudun.vo.MoviesQuery;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 * 影视 前端控制器
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@RestController
@RequestMapping("/edu-movies")
public class EduMoviesController {

    //依赖
    @Autowired
    private EduMoviesService moviesService;
//------------------------完善--------------------------------------------------
   @ApiOperation("影片信息分页")
   @PostMapping("getMoviesList/{current}/{limit}")
   public R getMoviesList(@PathVariable long current, @PathVariable long limit,
                          @RequestBody(required = false) MoviesQuery moviesQuery){
       //分页
       Page<EduMovies> moviesPage = new Page<>(current,limit);
       //查询条件
       QueryWrapper<EduMovies> wrapper = new QueryWrapper<>();
       if (moviesQuery.getTitle()!=null) {
           wrapper.like("title",moviesQuery.getTitle());
       }
       if (moviesQuery.getStatus()!=null) {
           wrapper.like("status",moviesQuery.getStatus());
       }

       //添加查询条件
       moviesService.page(moviesPage,wrapper);
       //记录数
       long total = moviesPage.getTotal();
       //分页数据
       List<EduMovies> list = moviesPage.getRecords();

       return R.ok().data("total", total).data("list", list);
   }

   @ApiOperation("根据id删除影片")
   @DeleteMapping("deleteMovies/{id}")
   public R deleteMovies(@PathVariable String id){
       moviesService.removeById(id);
       return R.ok();
   }


//-----------------------------------------------------------------------------
    @ApiOperation("添加影片基本信息")
    @PostMapping("addMoviesInfo")//只要post对@RequestBody有效
    public R addMoviesInfo(@RequestBody CourseInfoVo courseInfoVo){
        System.out.println("---------------->>"+courseInfoVo);
        //添加后返回影片id
        String id = moviesService.saveCourseInfo(courseInfoVo);
        return R.ok().data("moviesId", id);
    }

    @ApiOperation("根据影片id获取基本信息")
    @GetMapping("getMoviesInfo/{moviesId}")
    public R getMoviesInfo(@PathVariable String moviesId){
        CourseInfoVo courseInfo = moviesService.getCourseInfo(moviesId);
        return R.ok().data("courseInfoVo", courseInfo);
    }

    @ApiOperation("更新影片信息")
    @PutMapping("updateMoviesInfo")
    public R updateMoviesInfo(@RequestBody CourseInfoVo courseInfoVo){
        moviesService.updateCourseInfo(courseInfoVo);
        return R.ok();
    }

    @ApiOperation("根据影片id获取发布影片信息")
    @GetMapping("getPublishMoviesInfo/{id}")
    public R getPublishMoviesInfo(@PathVariable("id") String id){
        CoursePublishVo publishVo = moviesService.puslishCourseInfo(id);
        return R.ok().data("publishVo", publishVo);
    }
}

7.5.4 编写前端,影片列表

<template>
  <div class="app-container">
    <!-- 影片查询 -->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="moviesQuery.title" placeholder="影片名称"></el-input>
      </el-form-item>
      <el-form-item>
        <el-select v-model="moviesQuery.status" placeholder="影片状态">
          <el-option label="已发布" value="Normal"></el-option>
          <el-option label="未发布" value="Draft"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getList()">查 询</el-button>
        <el-button type="default" @click="resetData()">清 空</el-button>
      </el-form-item>
    </el-form>

    <!-- 影片列表 -->
    <el-table :data="list" style="width: 100%">
      <el-table-column label="序号" width="180">
        <template slot-scope="scope">          
          {{ (current-1)*limit+scope.$index+1 }}
        </template>
      </el-table-column>
      <el-table-column prop="title" label="影片名称" width="180"/>
      <el-table-column label="影片状态">
        <template slot-scope="scope">          
          {{ scope.row.status==='Normal'?'已发布':'未发布'}}
        </template>
      </el-table-column>
      <el-table-column prop="videoNum" label="视频数" width="180"/>
      <el-table-column prop="gmtCreate" label="影片添加时间" width="180"/>
      <el-table-column prop="viewCount" label="浏览量" width="180"/>

      <el-table-column label="操作" align="center" width="200">
        <template slot-scope="scope">
          <router-link :to="'/edu-movies/info/'+scope.row.id"  >
            <el-button size="mini" type="primary">编辑影片基本信息</el-button>
          </router-link>
          <router-link :to="'/edu-movies/chapter/'+scope.row.id"  >
            <el-button size="mini" type="primary">编辑影片片段信息</el-button>
          </router-link>
          <el-button  size="mini"  type="danger" @click="removeDataById(scope.row.id)">删除影片</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页插件 -->
    <el-pagination     
      @current-change="pageChange"
      :current-page.sync="current"
      :page-size="limit"
      layout="prev, pager, next, jumper"
      :total="total">
    </el-pagination>
  </div>
</template>

<script>
//导入js
import movies from '@/api/movies/movies'

export default {
  data() {
    return {
      moviesQuery: {},
      list: [],
      current: 1,
      limit: 3,
      total: 0
    };
  },
  created() {
      this.getlist()
  },
  methods: {
    getlist(page=1) {

      //查询
      this.current = page
      movies.getListMovies(this.moviesQuery,this.current,this.limit).then((result) => {
          this.list = result.data.list
          this.total = result.data.total
      }).catch((err) => {

      });

    },
    pageChange(page){
        this.current = page
        movies.getListMovies(this.moviesQuery,this.current,this.limit).then((result) => {
          this.list = result.data.list
          this.total = result.data.total
      }).catch((err) => {

      });
    },
    resetData() {
      //清空查询条件
      this.moviesQuery = {};
    },
    removeDataById(id){//删除影片
        this.$confirm('是否删除','提示',{
            confirmButtonText:'确定',
            cancelButtonText:'取消',
            type:'warning'
        }).then(res=>{
            movies.deleteMovies(id).then(res=>{
                this.$message({
                    type: 'success',
                    message: '删除成功'
                })
                //显示数据
                this.getlist()
            })
        })
    }
  },
};
</script>

<style>
</style>

7.5.5 视频上传

  • 在video后端添加更新和保存描述方法
@RestController
@RequestMapping("/edu-video")
public class EduVideoController { 
    @ApiOperation("保存视频描述信息")
    @PostMapping("addVideo")
    public R addVideo(@RequestBody EduVideo eduVideo){
        videoService.save(eduVideo);
        return R.ok();
    }

    @ApiOperation("更新视频描述信息")
    @PutMapping("updateVideo")
    public R updateVideo(@RequestBody EduVideo eduVideo){
        videoService.updateById(eduVideo);
        return R.ok();
    }
}
  • chapter.vue页面
<template>
  <div class="app-container">
    <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="2" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>
    <el-button type="text" @click="openChapterDialog()">添加片段</el-button>
    <!-- 影片片段 -->
    <ul class="chanpterList">
        <li
           v-for="chapter in chapterVideoList"
            :key="chapter.id">
            <p>
                {{chapter.title}}
                <span class="acts">
                    <el-button type="text" @click="openVideo(chapter.id,video.id)">添加视频</el-button>
                    <el-button type="text" @click="openEditChapter(chapter.id)">编辑片段</el-button>
                    <el-button type="text" @click="removeChapter(chapter.id)">删除片段</el-button>
                </span>
            </p>
            <!-- 视频 -->
            <ul class="chanpterList videoList">
                <li
                   v-for="video in chapter.children"
                   :key="video.id">
                   <p>
                       {{video.title}}
                       <span class="acts">
                           <el-button type="text" @click="openVideo(chapter.id,video.id)">编辑</el-button>
                           <el-button type="text" @click="removeVideo(video.id)">删除</el-button>
                       </span>
                   </p>

                </li>
            </ul>

        </li>        
    </ul>

    <div>
        <el-button @click="prevous">上一步</el-button>
        <el-button :disabled="saveBtnDisbled" type="primary" @click="next">下一步</el-button>
    </div>

   <!-- 弹出窗体:添加或修改片段 -->
    <el-dialog title="添加影片片段" :visible.sync="dialogChapterFormVisible">
        <el-form :model="chapter">
            <el-form-item label="片段名称" >
                <el-input v-model="chapter.title" />
            </el-form-item>
             <el-form-item label="片段排序" >
                <el-input-number v-model="chapter.sort" :min="0" controls-position="right"/>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
        </div>
    </el-dialog>

    <!-- 添加和修改视频表单 -->
    <el-dialog title="添加视频" :visible.sync="dialogVideoFormVisible">
        <el-form :model="video" label-width="120">
            <el-form-item label="视频题目">
                <el-input v-model="video.title"/>
            </el-form-item>
            <el-form-item label="视频排序">
                <el-input-number v-model="video.sort" :min="0"/>
            </el-form-item>
            <el-form-item label="是否免费">
                 <el-radio-group v-model="video.isFree">
                    <el-radio :label="true" >免费</el-radio>
                    <el-radio :label="false">默认</el-radio>
                </el-radio-group>
            </el-form-item>

            <el-form-item label="上传视频">
                <el-upload
                    :on-success="handleVodUploadSuccess"
                    class="upload-demo"
                    :action="BASE_API+'edu-video/uploadAlyVideo'"
                    :on-preview="handlePreview"
                    :on-remove="handleRemove"
                    :before-remove="beforeRemove"                    
                    :limit="1"
                    :on-exceed="handleExceed"
                    :file-list="fileList">
                    <el-button size="small" type="primary">上传视频</el-button>
                    <div slot="tip" class="el-upload__tip">最大支持1G</div>
                </el-upload>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialogVideoFormVisible=false">取 消</el-button>
            <el-button :disabled="saveVideoBtnDisabled=false" type="primary" @click="saveOrUpdateVideo">确 定</el-button>
        </div>
    </el-dialog>

  </div>
</template>

<script>
//导入js
import chapter from '@/api/movies/chapter'
import video from '@/api/movies/video'


export default {
    data(){
        return{
            saveBtnDisbled: false,
            courseId: '',//影片id
            chapterVideoList: [],
            chapter:{
                sort: 0
            },
            dialogChapterFormVisible: false,
            dialogVideoFormVisible: false,
            video: {
                sort: 0,
                isFree: true
            },
            fileList: [],
            saveVideoBtnDisabled: false,
            BASE_API: process.env.BASE_API,
            vsid:''
        }     
    },
    created(){
        //获取路由id
        if(this.$route.params && this.$route.params.id){
            //获取影片的id
            this.courseId = this.$route.params.id
            //根据影片的id获取片段
            this.getChapterVideo()
        }
        console.log('id='+this.courseId)
    },
    methods:{
        handleVodUploadSuccess(respones,file,fileList){//视频上传成功
           //获取上传的id
           this.video.videoSourceId = respones.data.videoId
           //视频名称
           this.video.VideoOriginalName = file.name
           this.size = file.length
        },  
        handlePreview(){},  
        handleRemove(){//删除视频
            video.deleteAlyVod(this.video.videoSourceId).then(res=>{
                //提示
                this.$message({
                    type: 'success',
                    message: '删除成功'
                })
                //视频的列表清空
                this.fileList = []
                this.video.videoSourceId = ''
                this.video.VideoOriginalName = ''
            })
        },
        beforeRemove(){//删除视频前
        return this.$confirm(`确定删除${file.name}?`)

        },
        handleExceed(){
            this.$message.warning('请删除已经上传的视频后,再重新上传');
        }, 
        //-------------------------------------------------------------
        saveOrUpdateVideo(){//保存视频的描述信息
            if(this.video.id){
                this.updateByVideo()    
            }else{
                this.addVideo()    
            }
        },
        //保存视频描述
        addVideo(){
            //关联id
            this.video.moviesId = this.courseId           
            video.addVideo(this.video).then(res=>{
                this.dialogVideoFormVisible = false
                 this.$message({
                       type: 'success',
                       message: '保存成功'
                   })
                   this.getChapterVideo()
            })    

        },
        //更新视频描述
        updateByVideo(){
            video.updateVideo(this.video).then(res=>{
                this.dialogVideoFormVisible = false
                this.$message({
                       type: 'success',
                       message: '保存成功'
                   })
                   this.getChapterVideo()
            })    
        },

        //------------------------------------------------------------

        prevous(){//上一步:更新影片信息
            this.$router.push({path:'/edu-movies/info/'+this.courseId})            
        },
        //根据影片的id获取片段
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId).then((result) => {
                this.chapterVideoList = result.data.allChapterVideo
            }).catch((err) => {

            });
        },
        openChapterDialog(){//弹出窗体
            this.dialogChapterFormVisible = true
        },
        openVideo(chapterId,videoId){//添加片段
            if(chapterId){//添加
                this.dialogVideoFormVisible = true
                this.video = {}
                this.fileList = []
                this.video.chapterId = chapterId
            }
            if(chapterId && videoId){//更新
                video.updateVideo(videoId).then(res=>{

                })
            }

        },
        openEditChapter(chapterId){//编辑片段
           //弹出窗体
           this.dialogChapterFormVisible = true
           //回填数据
           chapter.getChapter(chapterId).then(res=>{
               this.chapter = res.data.chapter
           })

        },
        removeChapter(chapterId){//删除片段
           this.$confirm('删除片段信息?','提示',{
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
           }).then(res=>{
               chapter.deleteChapter(chapterId).then(res=>{
                   this.$message({
                       type: 'success',
                       message: '删除成功'
                   })
                   this.getChapterVideo()
               })
           })

        },
        removeVideo(videoId,videoSourceId){//删除视频
           video.videoById(videoId).then(res=>{
               this.vsid = res.data.vsid
           })
            this.$confirm('删除片段信息?','提示',{
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
           }).then(res=>{
               //删除视频
               video.deleteAlyVod(this.vsid).then(res=>{
                   this.$message({
                       type: 'success',
                       message: '删除成功'
                   })                 
               })
               //删除视频描述
               video.deleteVideo(videoId).then(res=>{
                   this.$message({
                       type: 'success',
                       message: '删除成功'
                   })
                   this.getChapterVideo()
               })
          })           
        },
        saveOrUpdate(){//保存或更新
           //判断
           if(this.chapter.id){
              this.updateChapter()  
           }else{
              this.addChapter()  
           }
        },
        //添加片段
        addChapter(){
            //影片的id为外键
            this.chapter.moviesId = this.courseId
            chapter.addChapter(this.chapter).then(res=>{
                //关闭弹出窗体
                this.dialogChapterFormVisible =false
                //提示
                this.$message({
                    type: 'success',
                    message: '片段保存成功'
                })
                 //根据影片的id获取片段
                 this.getChapterVideo()
            })

        },
        //更新片段
        updateChapter(){
            chapter.updateChapter(this.chapter).then(res=>{
                //关闭弹出窗体
                this.dialogChapterFormVisible =false
                //提示
                this.$message({
                    type: 'success',
                    message: '片段更新成功'
                })
                 //刷新页面
                 this.getChapterVideo()
            })           
        },

        next(){}
    }

}
</script>

<style>

</style>

7.6 影片发布

7.6.1 编写后端

在线影视 - 图53

@ApiOperation("发布影片,修改status的状态")
@PostMapping("publisMovies/{moviesId}")
public R publisMovies(@PathVariable String moviesId){
    EduMovies eduMovies = new EduMovies();
    eduMovies.setId(moviesId);
    eduMovies.setStatus("Normal");
    moviesService.updateById(eduMovies);

    return R.ok();
}
  • 影片发布涉及的sql查询语句
public interface EduMoviesMapper extends BaseMapper<EduMovies> {
    //发布的影片
    String sql = "SELECT ec.id,ec.title,ec.price,ec.video_num AS videoNum,ec.cover,\n" +
            "               et.name AS actorName,\n" +
            "               es1.title AS subjectLevelOne,\n" +
            "               es2.title AS subjectLevelTwo\n" +
            "        FROM edu_movies ec LEFT OUTER JOIN edu_movies_description ecd ON ec.id=ecd.id\n" +
            "                           LEFT OUTER JOIN edu_actor et ON ec.actor_id=et.id\n" +
            "                           LEFT OUTER JOIN edu_subject es1 ON ec.subject_parent_id=es1.id\n" +
            "                   LEFT OUTER JOIN edu_subject es2 ON ec.subject_id=es2.id\n" +
            "        WHERE ec.id=#{courseId}";
    @Select(sql)
    CoursePublishVo getPulishCourseInfo(String courseId);
}

7.6.2 编写前端影片发布

  • 编写api/movies.js文件
import request from '@/utils/request'

export default  {
    //添加影片信息
    addMoviesInfo(moviesInfo){
        return request({
        url: `/edu-movies/addMoviesInfo`,
        method: 'post',
        data: moviesInfo
        })
    },
    //影片id获取信息
    getMoviesInfoId(id){
        return request({
            url: `/edu-movies/getMoviesInfo/${id}`,
            method: 'get'
        })
    },
    //更新影片信息
    updateMovies(moviesInfo){
        return request({
            url: `/edu-movies/updateMoviesInfo`,
            method: 'put',
            data: moviesInfo
        })
    },
    //分页显示
    getListMovies(moviesQuery,current,limit){
        return request({
            url: `/edu-movies/getMoviesList/${current}/${limit}`,
            method: 'post',
            data: moviesQuery
        })
    },
    //删除影片信息
    deleteMovies(id){
        return request({
            url: `/edu-movies/deleteMovies/${id}`,
            method: 'delete'
        })
    },
    //发布影片
    publisMovies(id){
        return request({
            url: `/edu-movies/publisMovies/${id}`,
            method: 'post'
        })
    },
    //获取发布影片的列表信息
    getPublishMoviesInfo(id){
        return request({
            url: `/edu-movies/getPublishMoviesInfo/${id}`,
            method: 'get'
        })
    }
}
  • 发布信息的publish.vue
<template>
  <div class="app-container">
    <h2 style="text-align:center">发布新影片</h2>
        <!-- 步骤条 -->
        <el-steps :active="3" process-status="wait" align-center>
            <el-step title="填写影片基本信息"></el-step>
            <el-step title="创建影片视频章节"></el-step>
            <el-step title="发布影片"></el-step>
        </el-steps>

        <div class="ccinfo">
            <img :src="coursePublish.cover">
            <div class="main">
                  <h2>{{coursePublish.title}}</h2>  
                  <p class="gray"><span>共{{coursePublish.videoNum}}视频数</span> </p>
                  <p><span>所属分类{{coursePublish.subjectLevelOne}}-{{coursePublish.subjectLevelTwo}}</span></p>
                  <p>主演:{{coursePublish.actorName}}</p>
                  <h3 class="red">¥{{coursePublish.price}}</h3>
            </div>
        </div>
        <el-button @click="previous">返 回</el-button>
        <el-button @click="publish" :disabled="saveBtnDisbled" type="primary">发布影片</el-button>

  </div>      
</template>

<script>
//导入js
import movies from '@/api/movies/movies'

export default {
    data(){
        return{
            coursePublish: {},
            saveBtnDisbled: false,
            courseId: ''
        }
    },
    created(){
        //获取路由的id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            //调用函数
            this.getMoviesPublistId()
        }
    },
    methods:{
        getMoviesPublistId(){
            movies.getPublishMoviesInfo(this.courseId).then((result) => {
                this.coursePublish = result.data.publishVo
            }).catch((err) => {
                console.log('error')
            });
        },
        previous(){//返回上一级
            this.$router.push({path:'/edu-movies/info/'+this.courseId})

        },
        publish(){//发布影片
           movies.publisMovies(this.courseId).then(res=>{
               //提示
               this.$message({
                   type: 'success',
                   message: '发布成功'
               }) 
               //跳转到影片列表页面
               this.$router.push({path:'/edu-movies/list'})
           })
        }
    }

}
</script>

<style scoped>
.ccInfo {
    background: #f5f5f5;
    padding: 20px;
    overflow: hidden;
    border: 1px dashed #DDD;
    margin-bottom: 40px;
    position: relative;
}
.ccInfo img {
    background: #d6d6d6;
    width: 500px;
    height: 278px;
    display: block;
    float: left;
    border: none;
}
.ccInfo .main {
    margin-left: 520px;
}

.ccInfo .main h2 {
    font-size: 28px;
    margin-bottom: 30px;
    line-height: 1;
    font-weight: normal;
}
.ccInfo .main p {
    margin-bottom: 10px;
    word-wrap: break-word;
    line-height: 24px;
    max-height: 48px;
    overflow: hidden;
}

.ccInfo .main p {
    margin-bottom: 10px;
    word-wrap: break-word;
    line-height: 24px;
    max-height: 48px;
    overflow: hidden;
}
.ccInfo .main h3 {
    left: 540px;
    bottom: 20px;
    line-height: 1;
    font-size: 28px;
    color: #d32f24;
    font-weight: normal;
    position: absolute;
}
</style>

第八章:播放视频

8.1 添加视频列表

  • src/router/index.js添加播放列表

8.2 编写播放列表vue

<template>
  <div class="app-container">
    <!-- 视频播放列表 -->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="moviesQuery.title" placeholder="影片名称" />
      </el-form-item>
      <el-button type="primary" @click="getList()">查 询</el-button>
      <el-button type="default" @click="resetData()">清 空</el-button>
    </el-form>

    <!-- 表格 -->
    <el-table :data="list" style="width: 100%">
      <el-table-column label="序号"> 
          <template slot-scope="scope">
             {{scope.$index+1}}
          </template>
      </el-table-column>
      <el-table-column label="影片名称" prop="title"> </el-table-column>
      <el-table-column label="视频数量" prop="videoNum"> </el-table-column>
      <el-table-column label="添加时间" prop="gmtCreate"> </el-table-column>
      <el-table-column label="浏览数量" prop="viewCount"> </el-table-column>
      <el-table-column align="right" label="操作" header-align="left">
        <template slot-scope="scope">
            <router-link :to="'/movies/chapterVideo/'+scope.row.id">
                <el-button size="mini" type="primary">播放视频</el-button>
            </router-link>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页插件 -->
    <el-pagination        
      :current-page="current"
      :page-size="limit"
      :total="total"
      layout="total, prev, pager, next, jumper"
     @current-change="getList"/>   
  </div>
</template>

<script>
//导入js
import movies from '@/api/movies/movies'

export default {
  data() {
    return {
      moviesQuery: {},
      list: null,
      current:1,
      limit:3,
      total:0
    };
  },
  created() {
      this.getList()
  },
  methods: {
    getList(page=1) {//影片列表
        this.current = page
        movies.getListMovies(this.moviesQuery,this.current,this.limit).then((result) => {
            this.list = result.data.list
            this.total = result.data.total
        }).catch((err) => {
            console.log(err)
        });

    },
    resetData() {
      this.moviesQuery = {};
    }
  },
};
</script>

<style>
</style>

8.4 视频播放后端编写

  • 创建一个页面html
  • 在线影视 - 图54
package com.xiudun.controller;


import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthRequest;
import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthResponse;
import com.xiudun.entity.EduVideo;
import com.xiudun.exceptionhandler.XiudunException;
import com.xiudun.service.EduVideoService;
import com.xiudun.util.InitObject;
import com.xiudun.util.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.ws.rs.PUT;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 影视视频 前端控制器
 * </p>
 *
 * @author xiudun
 * @since 2021-03-01
 */
@RestController
@RequestMapping("/edu-video")
public class EduVideoController {

    @Autowired
    private EduVideoService videoService;

    @ApiOperation("视频上传")
    @PostMapping("uploadAlyVideo")
    public R uploadAlyVideo(MultipartFile file){
        //视频id
        String id = videoService.uploadVideoAly(file);
        return R.ok().data("videoId", id);
    }

    @ApiOperation("根据id删除视频")
    @DeleteMapping("deleteVideo/{id}")
    public R deleteVideo(@PathVariable String id){
       videoService.deleteById(id);
        return R.ok();
    }


    @ApiOperation("批量删除视频")
    @DeleteMapping("deleteBatchVideo")
    public R deleteBatchVideo(@RequestParam("videoList")List<String> videoList){
        videoService.removeMoreAlyVideo(videoList);
        return R.ok();
    }

    @ApiOperation("播放视频")
    @RequestMapping("vo/{id}")
    public ModelAndView playVideo(@PathVariable String id){
        try {
            //阿里云对象
            DefaultAcsClient client = InitObject.initCodClient("LTAI4GEJWzPR9hdaJ",
                    "TtBQk0jzh7nAB3cJMGwlGtsG");
            //创建请求和相应的对象
            GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
            GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();
            //添加视频的id
            request.setVideoId(id);
            response = client.getAcsResponse(request);
            //授权码
            String playAuth = response.getPlayAuth();
            //把视频的id和授权码传到网页中播放视频
            Map<String,String> map = new HashMap<>();
            map.put("videoId",id);
            map.put("playAuth",playAuth);
            return new ModelAndView("show", map);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
  • html网页:show.html

在线影视 - 图55

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>视频播放</title>
    <!DOCTYPE html>
    <html>
    <head>
        <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" />
        <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>
    </head>
    </html>
</head>
<body>
    <div class="prism-player" id="J_prismPlayer"></div>
    <script th:inline="javascript">
        var player = new Aliplayer({
            id: 'J_prismPlayer',
            width: '100%',
            autoplay: false,
            //视频的id
            vid : [[${videoId}]],
            playauth : [[${playAuth}]],
            cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
            encryptType:1, //当播放私有加密流时需要设置。
        },function(player){
            console.log('播放器创建好了。')
        });
    </script>
</body>
</html>

8.5 编写视频播放列表vue:chapterVideo.vue

<template>
   <div class="app-container">
       <h2>播放影片</h2>
       <ul>
          <li 
          v-for="chapter in chapterVideoList"
          :key="chapter.id">
          <p>{{chapter.title}}</p>  
          <!-- 视频 -->
          <ul>
              <li
                 v-for="video in chapter.children"
                 :key="video.id">
                <p>{{video.title}}</p> 
                <span class="acts">
                    <el-button @click="openVideo(video.id)" type="text">播放视频</el-button>
                </span>
              </li>
          </ul>
          </li>  
       </ul> 

   </div>
</template>

<script>
//导入脚本
import chapter from '@/api/movies/chapter'
import video from '@/api/movies/video'

export default {
    data(){
        return{
            chapterVideoList:[],
            courseId: '',
            sourceVideoId: ''
        }
    },
    created(){
        if(this.$route.params && this.$route.params.id){
            this. courseId = this.$route.params.id
            //根据影片id 查询对应的片段
            this.getChapterVideo()
        }
    },
    methods:{
        getChapterVideo(){//根据id获取章节片段
            chapter.getAllChapterVideo(this.courseId).then((result) => {
                this.chapterVideoList = result.data.allChapterVideo
            }).catch((err) => {
                console.log(err)
            });
        },

        openVideo(id){//根据阿里云的视频id播放视频
           video.videoById(id).then(res=>{
               this.sourceVideoId = res.data.vsid
           })
           //启动一个新窗体播放
           window.open('_blank').location.href="http://localhost:7000/edu-video/vo/"+this.sourceVideoId
        }
    }
}
</script>

<style>

</style>

第九章:Echarts图表

  • 注意:数据需要集合List形式返回,因为,可以在图表中转为数组格式。

9.1 后端编写

  • service
    ```java package com.xiudun.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.xiudun.entity.EduSubject; import com.baomidou.mybatisplus.extension.service.IService; import org.springframework.web.multipart.MultipartFile;

import java.util.List; import java.util.Map;

/**

  • 影视科目 服务类
  • *
  • @author xiudun
  • @since 2021-02-28 */ public interface EduSubjectService extends IService { //上传excel void batchImport(MultipartFile file,EduSubjectService subjectService);

    //树形 List listTree();

    //根据创建的时间统计分析图表gmtCreate Map getEcharts(String begin,String end);

}


-  实现类 

```java
package com.xiudun.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiudun.entity.EduSubject;
import com.xiudun.mapper.EduSubjectMapper;
import com.xiudun.service.EduSubjectService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiudun.util.ExcelLisener;
import com.xiudun.vo.ReadData;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 影视科目 服务实现类
 * </p>
 *
 * @author xiudun
 * @since 2021-02-28
 */
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {

    @Override
    public void batchImport(MultipartFile file, EduSubjectService subjectService) {
        try {
            //文件输入流
            InputStream inputStream = file.getInputStream();

            //需要指定读取那个Class,读取默认sheet,文件流自动关闭
            EasyExcel.read(inputStream, ReadData.class,new ExcelLisener(subjectService)).sheet().doRead();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public List<EduSubject> listTree() {
        //获取所有分类数据
        List<EduSubject> entities = baseMapper.selectList(null);
        //找到一级分类
        return entities.stream().filter(categoryEntity->categoryEntity.getParentId().equals("0"))
                .map(menu-> menu.setChildren(getChildrens(menu,entities)))
                .sorted((m1,m2)->(m1.getSort()==null?0:m1.getSort())-(m2.getSort()==null?0:m2.getSort()))
                .collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getEcharts(String begin, String end) {
        //创建集合
        Map<String, Object> map = new HashMap<>();
        //查询条件
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_id","0");
        //数据
        List<EduSubject> data = baseMapper.selectList(wrapper);
        //把数据添加到list集合中
        Stream<String> stringStream = data.stream().map(e -> e.getTitle());
        List<String> collect = stringStream.collect(Collectors.toList());
        //存储数值
        List<Integer> nums = new ArrayList<>();

        //根据data获取的影片数量
        for (EduSubject datum : data) {
            QueryWrapper<EduSubject> wrapper1 = new QueryWrapper<>();
            wrapper1.eq("parent_id",datum.getId());
            wrapper1.between("gmt_create",begin,end);
            List<EduSubject> list = baseMapper.selectList(wrapper1);
            nums.add(list.size());
        }
        //把两个集合添加到map中
        map.put("data",collect);
        map.put("nums",nums);

        return map;
    }

    //编写一个读取树形的递归方法
    private List<EduSubject> getChildrens(EduSubject root,List<EduSubject> all){
        //递归查找
        List<EduSubject> list = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentId().equals(root.getId());//找子节点
        }).map(categoryEntity -> {//子节点
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((m1, m2) -> {
            return (m1.getSort() == null ? 0 : m1.getSort()) - (m2.getSort() == null ? 0 : m2.getSort());
        }).collect(Collectors.toList());

        //返回子节点集合
        return list;
    }
}
  • 控制层:controller
package com.xiudun.controller;


import com.xiudun.service.EduSubjectService;
import com.xiudun.util.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import org.springframework.web.multipart.MultipartFile;

import java.util.Map;

/**
 * <p>
 * 影视科目 前端控制器
 * </p>
 *
 * @author xiudun
 * @since 2021-02-28
 */
@Api("影片分类管理")
@RestController
@RequestMapping("/edu-subject")
public class EduSubjectController {

    //注入业务
    @Autowired
    private EduSubjectService subjectService;

    @ApiOperation("excel批量导入数据")
    @PostMapping("addSubject")
    public R addSubject(MultipartFile file){
        //上传文件
        subjectService.batchImport(file,subjectService);
        return R.ok();
    }

    @ApiOperation("分类数据Tree")
    @GetMapping("getListTree")
    public R listTree(){
        return R.ok().data("items", subjectService.listTree());
    }

    @ApiOperation("Echarts图表")
    @GetMapping("echarts/{begin}/{end}")
    public R echarts(@PathVariable String begin,@PathVariable String end){
        Map<String, Object> map = subjectService.getEcharts(begin, end);
        return R.ok().data(map);
    }
}

9.2 前端编写

  • 需要安装Echarts

在线影视 - 图56

  • 创建show.vue
<template>
  <div class="app-container">
      <!-- 表单 -->
        <el-form :inline="true" class="demo-form-inline">
            <el-select v-model="searchObj.type" placeholder="请选择">
                <el-option label="影片分类" value="subject_num"  />
                <el-option label="演员统计" value="actor_num"  />               
            </el-select>
            <!-- 日历控件 -->
            <el-form-item>
                <el-date-picker
                    v-model="searchObj.begin"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="开始日期"/>                   
            </el-form-item>
            <el-form-item>
                <el-date-picker
                    v-model="searchObj.end"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="结束日期"/>                   
            </el-form-item>
            <el-button type="primary" :disabled="btnDisabled" @click="showChart()">查 询</el-button>
        </el-form>
        <!-- 图表需要渲染的地方 -->
        <div class="chart-container">
            <div id="chart" class="chart" style="height:500px;width:100%"></div>
        </div>

  </div>
</template>

<script>
import echarts from 'echarts'

export default {
    data(){
        return{
            searchObj:{},
            btnDisabled: false,
            begin: '',
            end: '',
            title: '',
            chart: '',
            xData: [],
            yData: []
        }
    },
    methods:{
        showChart(){

        },
        //设置图表的参数
        setChart(){
            //初始化echarts
            this.chart = echarts.init(document.getElementById('chart'))
            //设置echarts的配置项和数据
            option = {
                xAxis: {
                    type: 'category',
                    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
                },
                yAxis: {
                    type: 'value'
                },
                series: [{
                    data: [150, 230, 224, 218, 135, 147, 260],
                    type: 'line'
                }]
            };
            //添加到图表中
           this.chart.setOption(option);
        }
    }
}
</script>

<style>

</style>
  • 设置index.js路由路径

在线影视 - 图57

  • 使用固定数据
<template>
  <div class="app-container">
      <!-- 表单 -->
        <el-form :inline="true" class="demo-form-inline">
            <el-select v-model="searchObj.type" placeholder="请选择">
                <el-option label="影片分类" value="subject_num"  />
                <el-option label="演员统计" value="actor_num"  />               
            </el-select>
            <!-- 日历控件 -->
            <el-form-item>
                <el-date-picker
                    v-model="searchObj.begin"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="开始日期"/>                   
            </el-form-item>
            <el-form-item>
                <el-date-picker
                    v-model="searchObj.end"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="结束日期"/>                   
            </el-form-item>
            <el-button type="primary" :disabled="btnDisabled" @click="showChart()">查 询</el-button>
        </el-form>
        <!-- 图表需要渲染的地方 -->
        <div class="chart-container">
            <div id="chart" class="chart" style="height:500px;width:100%"></div>
        </div>

  </div>
</template>

<script>
import echarts from 'echarts'

export default {
    data(){
        return{
            searchObj:{},
            btnDisabled: false,
            begin: '',
            end: '',
            title: '',
            chart: '',
            xData: [],
            yData: []
        }
    },
    methods:{
        showChart(){
            this.initChartData()
            this.setChart()
        },
        initChartData(){},

        //设置图表的参数
        setChart(){
            //初始化echarts
            this.chart = echarts.init(document.getElementById('chart'))
            //设置echarts的配置项和数据
           var option = {
                xAxis: {
                    type: 'category',
                    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
                },
                yAxis: {
                    type: 'value'
                },
                series: [{
                    data: [150, 230, 224, 218, 135, 147, 260],
                    type: 'line'
                }]
            }
            //添加到图表中
           this.chart.setOption(option)
        }
    }
}
</script>
<style>
</style>

9.2.1 使用后台数据

  • 编写一个api/chart.js
import request from '@/utils/request'

export default  {    
    showChart(searchObj){
        return request({
        url: `/edu-subject/echarts/${searchObj.begin}/${searchObj.end}/${searchObj.type}`,
        method: 'get'
        })
    }
}
  • show.vue
<template>
  <div class="app-container">
      <!-- 表单 -->
        <el-form :inline="true" class="demo-form-inline">
            <el-select v-model="searchObj.type" placeholder="请选择">
                <el-option label="影片分类" value="subject_num"  />
                <el-option label="演员统计" value="actor_num"  />               
            </el-select>
            <!-- 日历控件 -->
            <el-form-item>
                <el-date-picker
                    v-model="searchObj.begin"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="开始日期"/>                   
            </el-form-item>
            <el-form-item>
                <el-date-picker
                    v-model="searchObj.end"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="结束日期"/>                   
            </el-form-item>
            <el-button type="primary" :disabled="btnDisabled" @click="showChart()">查 询</el-button>
        </el-form>
        <!-- 图表需要渲染的地方 -->
        <div class="chart-container">
            <div id="chart" class="chart" style="height:500px;width:100%"></div>
        </div>

  </div>
</template>

<script>
import echarts from 'echarts'
import tb from '@/api/movies/chart'

export default {
    data(){
        return{
            searchObj:{},
            btnDisabled: false,
            begin: '',
            end: '',
            title: '',
            chart: '',
            xData: [],
            yData: []
        }
    },
    methods:{
        showChart(){
            this.initChartData()
            this.setChart()
        },
        initChartData(){//和后端交换数据
            tb.showChart(this.searchObj).then(res=>{
                //x
                this.xData = res.data.data
                //y
                this.yData = res.data.nums
                //需要统计的信息数据
                switch(this.setChart.type){
                    case 'subject_num':
                        this.title = "影片分类统计"
                        break
                    case 'actor_num' :
                        this.title = '演员的统计'
                        break   
                }
                this.setChart()
            })

        },

        //设置图表的参数
        setChart(){
            //初始化echarts
            this.chart = echarts.init(document.getElementById('chart'))
            //设置echarts的配置项和数据
           var option = {
                xAxis: {
                    type: 'category',
                    data: this.xData
                },
                yAxis: {
                    type: 'value'
                },
                series: [{
                    data: this.yData,
                    type: 'line'
                }]
            }
            //添加到图表中
           this.chart.setOption(option)
        }
    }
}
</script>
<style>
</style>

在线影视 - 图58

问题及解决方案

1.1 注册的服务名称必须使用中划线,否则网关不识别

1.2 错误

在线影视 - 图59

  • 解决方法:
    • 因为springBoot启动服务需要main方法
    • 编写一个主配置类

1.3 提示common_utils获取service_base模块加载失败

  • 需要在xiudun_common的pom中添加编译插件
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>none</phase>
                    </execution>
                </executions>
                <configuration>
                    <classifier>execute</classifier>
                </configuration>
            </plugin>
        </plugins>
    </build>