第七天课程预览和发布

一、Eureka注册中心

1.1 Eureka介绍

Spring Cloud Eureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能,Spring Cloud Eureka提供服务端与客户端,服务端即是Eureka服务注册中心,客户端完成微服务向Eureka服务的注册与发现。服务端和客户端均采用Java语言编写。下图显示了Eureka Server与Eureka Client的关系:

1.2 高可用Eureka搭建

Eureka Server 高可用环境需要部署两个Eureka server,它们互相向对方注册。如果在本机启动两个Eureka需要注意两个Eureka Server的端口要设置不一样,这里部署一个Eureka Server工程,将端口设置为可配置,制作两个Eureka Server启动脚本,启动不同的端口,如下图:

  • 在实际使用时Eureka Server至少部署两台服务器,实现高可用。
  • 两台Eureka Server互相注册。
  • 微服务需要连接两台Eureka Server注册,当其中一台Eureka死掉也不会影响服务的注册与发现。
  • 微服务会定时向Eureka server发送心跳,报告自己的状态。
  • 微服务从注册中心获取服务地址以RESTful方式发起远程调用。

1、创建xc-govern-center工程

包结构为:com.xuecheng.govern.center

2、添加依赖

在父工程中添加:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Finchley.SR1</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

在Eureka Server工程中添加:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

3、启动类

package com.xuecheng.govern.center;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @Author: 98050
 * @Time: 2019-05-10 14:43
 * @Feature: 注册中心
 */
@EnableEurekaServer
@SpringBootApplication
public class GovernCenterApplication {

    public static void main(String[] args) {
        SpringApplication.run(GovernCenterApplication.class, args);
    }
}

4、@EnableEurekaServer

需要在启动类上用@EnableEurekaServer标识此服务为Eureka服务

5、配置文件

server:
  port: ${PORT:50101} #服务端口

spring:
  application:
    name: xc-govern-center #指定服务名
    
eureka:
  client:
    register-with-eureka: true #服务注册,是否将自己注册到Eureka服务中
    fetch-registry: true #服务发现,是否从Eureka中获取注册信息
    service-url:
      defaultZone: ${EUREAK_SERVER:http://eureka02:50102/eureka/}
  server:
    enable-self-preservation: false #是否开启自我保护模式
    eviction-interval-timer-in-ms: 60000 #服务注册表清理间隔,单位为毫秒
  instance:
    hostname: ${EUREKA_DOMAIN:eureka01}

register-with-eureka:被其它服务调用时需向Eureka注册,单机版本为false,集群为true

fetch-registry:需要从Eureka中查找要调用的目标服务时需要设置为true,单机版本为false,集群为true

service-url.defaultZone 配置上报Eureka服务地址高可用状态配置对方的地址,单机状态配置自己的地址

enable-self-preservation:自保护设置。Eureka Server有一种自我保护模式,当微服务不再向Eureka Server上报状态,Eureka Server会从服务列表将此服务删除,如果出现网络异常情况(微服务正常),此时Eureka server进入自保护模式,不再将微服务从服务列表删除。在开发阶段建议关闭自保护模式

eviction-interval-timer-in-ms:清理失效结点的间隔,在这个时间段内如果没有收到该结点的上报则将结点从服务列表中剔除。

instance.hostname:Eureka 组成高可用,两个Eureka互相向对方注册,这里需要通过域名或主机名访问,这里设置两个Eureka服务的主机名分别为 eureka01、eureka02

6、启动脚本

7、启动

1557475571333

1557475622971

1.3 服务注册

1.3.1 将CMS注册到Eureka Server

1、在cms服务中添加依赖

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

2、在application.yml配置

eureka:
  client:
    register-with-eureka: true #服务注册开关
    fetch-registry: true #服务发现开关
    service-url:
      defaultZone: ${EUREAK_SERVER:http://localhost:50101/eureka/}
  instance:
    prefer-ip-address: true  #将自己的ip地址注册到Eureka
    ip-address: ${IP_ADDRESS:127.0.0.1}
    instance-id: ${spring.application.name}:${server.port} #指定实例id

3、在启动类上添加注解

@EnableDiscoveryClient

4、启动cms,查看服务注册情况

1.3.2 将manage-course注册到Eureka Server

方法同上

最终结果:

1557481868600

1557481894889

二、Feign远程调用

在前后端分离架构中,服务层被拆分成了很多的微服务,服务与服务之间难免发生交互,比如:课程发布需要调用CMS服务生成静态化页面。

1、cms服务将自己注册到注册中心。

2、课程管理服务从注册中心获取cms服务的地址。

3、课程管理服务远程调用cms服务。

2.1 Ribbon

2.1.1 Ribbon介绍

Ribbon是Netflix公司开源的一个负载均衡的项目(https://github.com/Netflix/ribbon),它是一个基于HTTP、TCP的客户端负载均衡器。

1、什么是负载均衡?

负载均衡是微服务架构中必须使用的技术,通过负载均衡来实现系统的高可用、集群扩容等功能。负载均衡可通过硬件设备及软件来实现,硬件比如:F5、Array等,软件比如:LVS、Nginx等。

如下图是负载均衡的架构图:

用户请求先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法将请求转发到微服务。负载均衡算法有:轮训、随机、加权轮训、加权随机、地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力。

2、什么是客户端负载均衡?

上图是服务端负载均衡,客户端负载均衡与服务端负载均衡的区别在于客户端要维护一份服务列表,Ribbon从Eureka Server获取服务列表,Ribbon根据负载均衡算法直接请求到具体的微服务,中间省去了负载均衡服务。

如下图是Ribbon负载均衡的流程图:

1、在消费微服务中使用Ribbon实现负载均衡,Ribbon先从Eureka Server中获取服务列表

2、Ribbon根据负载均衡的算法去调用微服务

2.1.2 Ribbon测试

Spring Cloud引入Ribbon配合 restTemplate 实现客户端负载均衡。Java中远程调用的技术有很多,如:webservice、socket、rmi、Apache HttpClient、OkHttp等,互联网项目使用基于http的客户端较多,本项目使OkHttp。

1、在客户端添加Ribbon依赖

在课程管理微服务中配置Ribbon依赖

2、配置Ribbon参数

ribbon:
  ConnectTimeout: 5000 # 连接超时时间(ms)
  ReadTimeout: 6000 # 通信超时时间(ms)
  OkToRetryOnAllOperations: false # 是否对所有操作重试,如果是get则可以,如果是post,put等操作在没有实现幂等的情况下是很危险的,所以设置为false
  MaxAutoRetriesNextServer: 3 # 同一服务不同实例的重试次数
  MaxAutoRetries: 2 # 同一实例的重试次数

3、负载均衡测试

1)启动两个CMS服务,注意端口要不一致

端口接收启动参数,然后启动两个CMS服务,启动完成后观察Eureka Server的服务列表

2)定义RestTemplate,使用@LoadBalanced注解

在课程管理服务类中定义RestTemplate

package com.xuecheng.managecourse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;

import javax.sql.DataSource;


/**
 * @author 98050
 */
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.course")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.api"})//扫描接口
@ComponentScan(basePackages={"com.xuecheng.managecourse"})
@ComponentScan(basePackages={"com.xuecheng.framework"})//扫描common下的所有类
@EnableTransactionManagement
@EnableDiscoveryClient
public class ManageCourseApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ManageCourseApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
}

3)测试代码

在课程管理服务工程创建单元测试代码,远程调用cms的查询页面接口:

@Autowired
private RibbonLoadBalancerClient client;
//负载均衡调用
@Test
public void testRibbon(){
    //服务id
    String serviceId = "XC-SERVICE-MANAGE-CMS";
    for (int i = 0; i < 10; i++) {
        ServiceInstance instance = this.client.choose(serviceId);
        System.out.println(instance.getHost() + ":" + instance.getPort());
    }
}

4)负载均衡测试

添加@LoadBalanced注解后,通过RibbonLoadBalancerClient查询服务地址,然后调用10次,查看输出结果:

默认负载均衡策略是轮询。

2.2 Feign

2.2.1 Feign介绍

Feign是Netflix公司开源的轻量级rest客户端,使用Feign可以非常方便的实现Http 客户端。Spring Cloud引入Feign并且集成了Ribbon实现客户端负载均衡调用。

2.2.2 Feign测试

1、在客户端添加依赖

在课程管理服务添加以下依赖:

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

<dependency>
    <groupId>com.netflix.feign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

2、定义FeignClient接口

package com.xuecheng.managecourse.client;

import com.xuecheng.api.cms.CmsPageControllerApi;
import com.xuecheng.framework.client.XcServiceList;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * @Time: 2019-05-11 18:18
 * @Feature:
 */
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)
public interface CmsPageClient extends CmsPageControllerApi{
}

直接继承接口文档即可~

3、启动类添加@EnableFeignClients注解

4、测试

@Autowired
private CmsPageClient cmsPageClient;

@Test
public void testFeign(){
    CmsPageResult cmsPageResult = cmsPageClient.findById("5a795ac7dd573c04508f3a56");
    System.out.println(cmsPageResult);
}

三、课程预览技术方案

3.1 需求分析

课程预览是为了保证课程发布后的正确性,通过课程预览可以直观的通过课程详情页面看到课程的信息是否正确,通过课程预览看到的页面内容和课程发布后的页面内容是一致的。

下图是课程详情页面的预览图:

3.2 课程详情页面技术方案

课程预览所浏览到的页面就是课程详情页面,需要先确定课程详情页面的技术方案后方可确定课程预览的技术方案。

3.2.1 技术需求

课程详情页面是向用户展示课程信息的窗口,课程相当于网站的商品,本页面的访问量会非常大。此页面的内容设计不仅要展示出课程核心重要的内容而且用户访问页面的速度要有保证,有统计显示打开一个页面超过4秒用户就走掉了,所以本页面的性能要求是本页面的重要需求。

本页面另一个需求就是SEO,要非常有利于爬虫抓取页面上信息,并且生成页面快照,利于用户通过搜索引擎搜索课程信息。

3.2.2 解决方案

如何在保证SEO的前提下提高页面的访问速度 :

方案1:

对于信息获取类的需求,要想提高页面速度就要使用缓存来减少或避免对数据库的访问,从而提高页面的访问速度。下图是使用缓存与不使用缓存的区别

此页面为动态页面,会根据课程的不同而不同,方案一采用传统的JavaEE Servlet/jsp的方式在Tomcat完成页面渲染,相比不加缓存速度会有提升。

优点:使用redis作为缓存,速度有提升。

缺点:采用Servlet/jsp动态页面渲染技术,服务器使用Tomcat,面对高并发量的访问存在性能瓶颈。

方案2:

对于不会频繁改变的信息可以采用页面静态化的技术,提前让页面生成html静态页面存储在nginx服务器,用户直接访问nginx即可,对于一些动态信息可以访问服务端获取json数据在页面渲染。

优点:使用Nginx作为web服务器,并且直接访问html页面,性能出色。

缺点:需要维护大量的静态页面,增加了维护的难度。

最终选择方案2作为课程详情页面的技术解决方案,将课程详情页面生成Html静态化页面,并发布到Nginx上。

3.3 课程预览技术方案

根据要求:课程详情页面采用静态化技术生成Html页面,课程预览的效果要与最终静态化的Html页面内容一致。所以,课程预览功能也采用静态化技术生成Html页面,课程预览使用的模板与课程详情页面模板一致,这样就可以保证课程预览的效果与最终课程详情页面的效果一致。

操作流程:

1、制作课程详情页面模板

2、开发课程详情页面数据模型的查询接口(为静态化提供数据)

3、调用cms课程预览接口通过浏览器浏览静态文件

四、课程详情页面静态化

4.1 静态页面测试

4.1.1 页面内容组成

我们在编写一个页面时需要知道哪些信息是静态信息,哪些信息为动态信息,下图是页面的设计图:

打开静态页面,观察每部分的内容。

红色表示动态信息,红色以外表示静态信息。

红色动态信息:表示一个按钮,根据用户的登录状态、课程的购买状态显示按钮的名称及按钮的事件。
包括以下信息内容:

1、课程信息

课程标题、价格、课程等级、授课模式、课程图片、课程介绍、课程目录。

2、课程统计信息

课程时长、评分、收藏人数

3、教育机构信息

公司名称、公司简介

4、教育机构统计信息

好评数、课程数、学生人数

5、教师信息

老师名称、老师介绍

4.1.2 页面拆分

将页面拆分成如下页面:

1、页头

本页头文件和门户使用的页头为同一个文件。
参考:代码\页面与模板\include\header.html

2、页面尾
本页尾文件和门户使用的页尾为同一个文件。
参考:代码\页面与模板\include\footer.html

3、课程详情主页面
每个课程对应一个文件,命名规则为:课程id.html(课程id动态变化)
模板页面参考:\代码\页面与模板\course\detail\course_main_template.html

4、教育机构页面
每个教育机构对应一个文件,文件的命名规则为:company_info_公司id.html(公司id动态变化)
参考:代码\页面与模板\company\company_info_template.html

5、老师信息页面
每个教师信息对应一个文件,文件的命名规则为:teacher_info_教师id.html(教师id动态变化)

参考:代码\页面与模板\teacher\teacher_info_template01.html

6、课程统计页面
每个课程对应一个文件,文件的命名规则为:course_stat_课程id.json(课程id动态变化)
参考:\代码\页面与模板\stat\course\course_stat_template.json

7、教育机构统计页面
每个教育机构对应一个文件,文件的命名规则为:company_stat_公司id.json(公司id动态变化)
参考:\代码\页面与模板\stat\company\company_stat_template.json

4.1.3 静态页面测试

4.1.3.1 页面加载思路

页面路径如下:

静态页面目录\static\course\detail\course_main_template.html

1、主页面

我们需要在主页面中通过SSI加载:页头、页尾、教育机构、教师信息

2、异步加载课程统计与教育机构统计信息

课程统计信息(json)、教育机构统计信息(json)

3、马上学习按钮事件

用户点击“马上学习”会根据课程收费情况、课程购买情况执行下一步操作。

4.1.3.2 静态资源虚拟主机

静态资源虚拟主机负责处理课程详情、公司信息、老师信息、统计信息等页面的请求:

将课程资料中的“静态页面目录”中的目录拷贝到任意一个文件夹下

在nginx中配置静态虚拟主机如下:

    server {
        listen      91;
        server_name localhost;

        #公司信息
        location /static/company/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static/company/;
        }

         #老师信息
        location /static/teacher/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static/teacher/;
        }

         #统计信息
        location /static/stat/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static/stat/;
        }

         #公司信息
        location /course/detail/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static//course/detail/;
        } 
    }

2、通过www.xuecheng.com虚拟主机转发静态资源

由于课程页面需要通过SSI加载页头和页尾所以需要通过www.xuecheng.com虚拟主机转发到静态资源
在www.xuecheng.com虚拟主机加入如下配置:

location /static/company/{
            proxy_pass http://static_server_pool;
        }
        location /static/teacher/{
            proxy_pass http://static_server_pool;
        }
        location /static/stat/{
            proxy_pass http://static_server_pool;
        }
        location /course/detail/{
            proxy_pass http://static_server_pool;
        }

配置upstream实现请求转发到资源服务虚拟机:

	#静态资源服务
    upstream static_server_pool{
        server 127.0.0.1:91 weight=10;
    }

4.1.3.3 门户静态资源路径

门户中的一些图片、样式等静态资源统一通过/static路径对外提供服务,在www.xuecheng.com虚拟主机中配置如下:

        location /static/img/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/img/;
        }
        location /static/css/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/css/;
        }
        location /static/js/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/js/;
        }
        location /static/plugins/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/plugins/;
            add_header Access‐Control‐Allow‐Origin http://ucenter.xuecheng.com;
            add_header Access‐Control‐Allow‐Credentials true;
            add_header Access‐Control‐Allow‐Methods GET;
        }

cors跨域参数:

  • Access-Control-Allow-Origin:允许跨域访问的外域地址,如果允许任何站点跨域访问则设置为*,通常这是不建议的。
  • Access-Control-Allow-Credentials: 允许客户端携带证书访问
  • Access-Control-Allow-Methods:允许客户端跨域访问的方法

4.1.3.4 页面测试

重启nginx,请求http://www.xuecheng.com/course/detail/course_main_template.html

4.1.3.5 页面动态脚本

为了方便日后的维护,我们将javascript实现的动态部分单独编写一个html 文件,在门户的include目录下定义course_detail_dynamic.html文件,此文件通过ssi包含在课程详情页面中.

所有的课程公用一个 页面动态脚本。

在课程详情主页面下端添加如下代码,通过SSI技术包含课程详情页面动态脚本文件:

本页面使用vue.js动态获取信息,vue实例创建的代码如下:

主要查看 created钩子函数的内容。

var body= new Vue({   //创建一个Vue的实例
     el: "#body", //挂载点是id="app"的地方

     data: {
         editLoading: false,
         title:'测试',
         courseId:'',
         charge:'',//203001免费,203002收费
         learnstatus:0,//课程状态,1:马上学习,2:立即报名、3:立即购买
         course:{},
         companyId:'template',
         company_stat:[],
         course_stat:{"s601001":"","s601002":"","s601003":""}


     },
     methods: {
         //学习报名
         addopencourse(){
            
         },
         //立即购买
         buy(){
             
         },
         createOrder(){
             
         },
         getLearnstatus(){//获取学习状态

         }

     },
    created() {
        this.courseId = courseId;
        this.charge = charge
        this.getLearnstatus();
        //获取教育机构的统计数据
        queryCompanyStat(this.companyId).then((res)=>{
            console.log(res)
            if(res.stat){
                this.company_stat = res.stat
                console.log(this.company_stat)
            }

        })
        //获取课程的统计数据
        queryCourseStat(this.courseId).then((res)=>{
            console.log(res)
            if(res.stat){
                let stat = res.stat
               for(var i=0;i<stat.length;i++){
                   this.course_stat['s'+stat[i].id] = stat[i].value
               }
            }
            console.log(this.course_stat)

        })
     },
     mounted(){
        // alert(courseId)

     }
 })

4.2 课程数据模型查询接口

静态化操作需要模型数据方可进行静态化,课程数据模型由课程管理服务提供,仅供课程静态化程序调用使用。

4.2.1 接口定义

1、响应结果类型

package com.xuecheng.framework.domain.course.ext;

import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.domain.course.CourseMarket;
import com.xuecheng.framework.domain.course.CoursePic;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

/**
 * @Author: 98050
 * @Time: 2019-05-15 21:46
 * @Feature:
 */
@Data
@ToString
@NoArgsConstructor
public class CourseView implements Serializable {

    private static final long serialVersionUID = -915749022529361861L;
    /**
     * 课程基础信息
     */
    private CourseBase courseBase;
    /**
     * 课程营销信息
     */
    private CourseMarket courseMarket;
    /**
     * 课程图片
     */
    private CoursePic coursePic;
    /**
     * 教学计划
     */
    private TeachplanNode teachplanNode;
}

2、请求类型

String:课程id

3、接口定义

/**
 * 课程视图查询
 * @param id 课程id
 * @return
 */
@ApiOperation("课程视图查询")
@ApiImplicitParams({
        @ApiImplicitParam(name = "id",value = "课程id",required = true,paramType = "path",dataType = "String"),
})
@GetMapping("/courseview/{id}")
CourseView courseView(@PathVariable("id") String id);

4.2.2 DAO

无需新建dao

4.2.3 Service

接口:

在CourseService中添加方法:

/**
 * 查询课程详情
 * @param id 课程id
 * @return
 */
CourseView getCourseView(String id);

实现:

/**
 * 查询课程详情
 * @param id 课程id
 * @return
 */
@Override
public CourseView getCourseView(String id) {
    CourseView courseView = new CourseView();
    final CountDownLatch countDownLatch = new CountDownLatch(4);
    ExecutorService executorService = Executors.newFixedThreadPool(4);
    try {
        CourseBase courseBaseResult = executorService.submit(() -> {
            //1.查询课程基础信息
            countDownLatch.countDown();
            Optional<CourseBase> courseBase = this.courseBaseRepository.findById(id);
            return courseBase.orElse(null);
        }).get();
        CourseMarket courseMarketResult = executorService.submit(() -> {
            //2.查询课程营销信息
            countDownLatch.countDown();
            Optional<CourseMarket> courseMarket = this.courseMarketRepository.findById(id);
            return courseMarket.orElse(null);
        }).get();
        CoursePic coursePicResult = executorService.submit(() -> {
            //3.查询课程图片
            countDownLatch.countDown();
            Optional<CoursePic> coursePic = this.coursePicRepository.findById(id);
            return coursePic.orElse(null);
        }).get();
        TeachplanNode teachplanNodeResult = executorService.submit(() -> {
            //4.查询课程教学计划
            countDownLatch.countDown();
            return this.teachPlanMapper.selectList(id);
        }).get();
        countDownLatch.await();
        courseView.setCourseBase(courseBaseResult);
        courseView.setCourseMarket(courseMarketResult);
        courseView.setCoursePic(coursePicResult);
        courseView.setTeachplanNode(teachplanNodeResult);
        return courseView;
    }catch (Exception e){
        ExceptionCast.cast(CourseCode.COURSE_VIEW_QUERY_ERROR);
    }
    return courseView;
}

采用多线程的方式去查询,用线程池来管理线程,线程使用Callable,用submit来提交。

如果查询使用其他服务的话这样可以缩短查询时间,模板代码!!!!!!!!!!

4.2.4 Controller

@Override
public CourseView courseView(@PathVariable("id") String id) {
    return this.courseService.getCourseView(id);
}

4.2.5 测试

4.3 课程信息模板设计

在确定了静态化所需要的数据模型之后,就可以编写页面模板了,课程详情页面由多个静态化页面组成,所以我们需要创建多个页面模板,本章节创建课程详情页面的主模板,即课程信息模板。

4.3.1 模板内容

完整的模板请参考 “课程详情页面模板\course.ftl“ 文件,下边列出模板中核心的内容:

1.课程基本信息

<div class="banner-left">
            <p class="tit">${courseBase.name}</p>
            <p class="pic"><span class="new-pic">特惠价格¥${(courseMarket.price)!""}</span> <span class="old-pic">原价¥${(courseMarket.price_old)!""}</span></p>
            <p class="info">
                <a href="http://ucenter.xuecheng.com/#/learning/${courseBase.id}/0"  target="_blank" v-if="learnstatus == 1" v-cloak>马上学习</a>
                <a href="#"  @click="addopencourse" v-if="learnstatus == 2" v-cloak>立即报名</a>
                <a href="#"  @click="buy" v-if="learnstatus == 3" v-cloak>立即购买</a>
                <span><em>难度等级</em>
		 <#if courseBase.grade=='200001'>
		低级
                <#elseif courseBase.grade=='200002'>
		中级
		 <#elseif courseBase.grade=='200003'>
		高级
		</#if>
                </span>
                <span><em>课程时长</em><stat v-text="course_stat.s601001"></stat>
                </span>
                <span><em>评分</em><stat v-text="course_stat.s601002"></stat></span>
                <span><em>授课模式</em>
                  <#if courseBase.studymodel=='201001'>
		自由学习
                <#elseif courseBase.studymodel=='201002'>
		任务式学习
		</#if>
                </span>
            </p>
        </div>
        <div class="banner-rit">
	    
	    <#if (coursePic.pic)??>
	     <p><img src="http://img.xuecheng.com/${coursePic.pic}" alt="" width="270" height="156"> </p>
	     <#else>
		 <p><img src="/static/img/widget-video.png" alt="" width="270" height="156"> </p>
	    </#if>
           
            <p class="vid-act"><span> <i class="i-heart"></i>收藏 <stat v-text="course_stat.s601003"></stat> </span> <span>分享 <i class="i-weixin"></i><i class="i-qq"></i></span></p>
        </div>

2.课程计划

<div class="content">
    <#if (teachplanNode.children)??>
        <#list teachplanNode.children as firstNode>
            <div class="item">
                <div class="title act"><i class="i-chevron-top"></i>${firstNode.pname}</div>
                <div class="about">${firstNode.description!}</div>
                <div class="drop-down" style="height: ${firstNode.children?size * 50}px;">
                    <ul class="list-box">
                        <#list firstNode.children as secondNode>
                            <li>${secondNode.pname}</li>
                            </#list>
                    </ul>
                </div>
            </div>
            </#list>
        </#if>
</div>

3.页头

局部代码如下:

<body data-spy="scroll" data-target="#articleNavbar" data-offset="150">
<!-- 页面头部 -->
<!--#include virtual="/include/header.html"-->

4.页尾

局部代码如下:

<!-- 页面底部 -->
<!--底部版权-->
<!--#include virtual="/include/footer.html"-->

5.动态脚本文件

<script>var courseId = "${courseBase.id}"</script>
<!--#include virtual="/include/course_detail_dynamic.html"-->

6.教师信息文件

从课程数据中获取课程所属的教师Id,这里由于教师信息管理功能没有开发我们使用固定的教师信息文件:

<div class="content-com course">
<div class="title"><span>课程制作</span></div>
<!--#include virtual="/teacher/teacher_info_template01.html"-->
</div>

7.教育机构文件

同教师信息一样,由于教育机构功能模块没有开发,这里我们使用固定的教育机构文件:

<div class="about-teach">
<!--机构信息-->
<!--#include virtual="/company/company_info_template.html"-->
</div>

4.3.2 模板测试

使用test-freemarker工程测试模板

编写模板过程采用test-freemarker工程测试模板。

将course.ftl拷贝到test-freemarker工程的resources/templates下,并在test-freemarker工程的controller中添加测试方法

@RequestMapping("/course")
public String course(Map<String,Object> map){
    String dataUrl = "http://localhost:31200/course/courseview/4028e58161bd3b380161bd3bcd2f0000";
    ResponseEntity<Map> entity = restTemplate.getForEntity(dataUrl,Map.class);
    Map body = entity.getBody();
    map.putAll(body);
    return "course";
}

结果:

注意:上边的测试页面不显示样式,原因是页面通过SSI包含了页面头,而使用test-freemarker工程无法加载页头,测试模板主要查看html页面内容是否正确,待课程预览时解决样式不显示问题。

4.3.3 模板保存

模板编写并测试通过后要在数据库保存:

1、模板信息保存在xc_cms数据库(mongodb)的cms_template表

2、模板文件保存在mongodb的GridFS中。

第一步:将模板文件上传到GridFS中

由于本教学项目中模板管理模块没有开发,所以我们使用Junit代码向GridFS中保存:

/**
 * 存储文件
 * @throws FileNotFoundException
 */
@Test
public void testGridFs2() throws FileNotFoundException {
    //1.要存储的文件
    File file = new File("D:\\course.ftl");
    //2.定义输入流
    FileInputStream inputStream = new FileInputStream(file);
    //3.向GridFS存储文件
    ObjectId objectId = gridFsTemplate.store(inputStream, "课程详情模板文件","");
    //4.得到文件ID
    String fileId = objectId.toString();
    System.out.println(fileId);
}

第二步:向cms_template表添加模板记录:

{ 
    "_class" : "com.xuecheng.framework.domain.cms.CmsTemplate", 
    "siteId" : "5a751fab6abb5044e0d19ea1", 
    "templateName" : "课程详情正式模板", 
    "templateParameter" : "courseid", 
    "templateFileId" : "5cde5670591f428a74df26e7"
}

4.3.4 其他模板

除了课程详情主页面需要设计模板所有静态化的页面都要设计模板,如下:

教育机构页面模板、教师信息页面模板、课程统计信息json模板、教育机构统计信息json模板。

在这里只实现课程详情主页面模板的制作和测试,其它页面模板的开发参考课程详情页面去实现。

五、课程预览功能开发

5.1 需求分析

课程预览功能将使用cms系统提供的页面预览功能,业务流程如下:

1、用户进入课程管理页面,点击课程预览,请求到课程管理服务

2、课程管理服务远程调用cms添加页面接口向cms添加课程详情页面

3、课程管理服务得到cms返回课程详情页面id,并拼接生成课程预览Url

4、课程管理服务将课程预览Url给前端返回

5、用户在前端页面请求课程预览Url,打开新窗口显示课程详情内容

5.2 CMS页面预览测试

CMS已经提供了页面预览功能,课程预览功能要使用CMS页面预览接口实现,下边通过cms页面预览接口测试课程预览的效果。

1、启动CMS内容管理页面,新增课程详情页面。

选择课程详情模板,然后填写dataUrl,结果:

2、课程详情页面使用SSI

由于Nginx先请求cms的课程预览功能得到html页面,再解析页面中的ssi标签,这里必须保证cms页面预览返回的页面的Content-Type为text/html;charset=utf-8

在cms页面预览的controller方法中添加:

response.setHeader("Content-type", "text/html;charset=utf-8");

3、测试

访问:http://www.xuecheng.com/cms/preview/5cde60e0591f4218d484e013

总结:课程预览的时候只要把课程信息对应的cms_page插入到数据库即可,然后调用cms服务提供的页面预览接口完成页面预览!

5.3 CMS添加页面接口

现有的接口是为前端CMS管理提供的接口

/**
 * 页面新增
 * @param cmsPage
 * @return
 */
@ApiOperation("页面添加")
@ApiResponses({
        @ApiResponse(code = 10000,message = "操作成功"),
        @ApiResponse(code = 11111,message = "操作失败")
})
@PostMapping("/add")
CmsPageResult add(@RequestBody CmsPage cmsPage);

现在要做的是为课程预览时,要向cms_page中添加或更新课程详情页面。为什么不直接调用add接口呢?

因为对那些不存在的页面执行添加操作,已存在的页面执行更新操作。所以要专门开发一个接口,此接口

由课程管理服务在课程预览时调用。

5.3.1 Api接口

/**
 * 此接口由课程管理服务在课程预览时调用,更新或者新增课程详情页面
 * @param cmsPage 页面内容
 * @return
 */
@ApiOperation("页面保存")
@ApiResponses({
        @ApiResponse(code = 10000,message = "操作成功"),
        @ApiResponse(code = 11111,message = "操作失败")
})
@PostMapping("/save")
CmsPageResult save(CmsPage cmsPage);

5.3.2 Service

接口:

/**
 * 保存页面,更新或者新增课程详情页面
 * @param cmsPage
 * @return
 */
CmsPageResult save(CmsPage cmsPage);

实现:

/**
 * 保存页面,更新或者新增课程详情页面
 * @param cmsPage
 * @return
 */
@Override
public CmsPageResult save(CmsPage cmsPage) {
    //1.检查页面是否存在,根据页面名称、站点Id、页面webPath查询
    CmsPage cmsPage1 = this.cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
    if (cmsPage1 != null){
        //更新
        return this.update(cmsPage1.getPageId(), cmsPage);
    }else {
        //添加
        return this.add(cmsPage);
    }
}

5.3.3 Controller

@Override
public CmsPageResult save(@RequestBody CmsPage cmsPage) {
    return this.cmsService.save(cmsPage);
}

5.4 课程预览服务端

5.4.1 Api定义

此Api是课程管理前端请求服务端进行课程预览的Api

请求:课程Id

响应:课程预览Url

1、定义响应类型

package com.xuecheng.framework.domain.course.response;

import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * @Author: 98050
 * @Time: 2019-05-17 16:46
 * @Feature:
 */
@Data
@ToString
@NoArgsConstructor
public class CoursePublicResult extends ResponseResult {
    private String previewUrl;
    public CoursePublicResult(ResultCode resultCode,String previewUrl){
        super(resultCode);
        this.previewUrl = previewUrl;
    }
}

2、接口定义

/**
 * 课程详情页面预览
 * @param id 课程id
 * @return
 */
@ApiOperation("课程预览")
@ApiImplicitParams({
        @ApiImplicitParam(name = "id",value = "课程id",required = true,paramType = "path",dataType = "String"),
})
@PostMapping("/preview/{id}")
CoursePublicResult preview(@PathVariable("id") String id);

5.4.2 创建Feign Client

在课程管理工程创建CMS服务的Feign Client,通过此Client远程请求cms添加页面

package com.xuecheng.managecourse.client;

import com.xuecheng.api.cms.CmsPageControllerApi;
import com.xuecheng.framework.client.XcServiceList;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * @Time: 2019-05-11 18:18
 * @Feature:
 */
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)
public interface CmsPageClient extends CmsPageControllerApi{
}

5.4.3 Service

1、配置信息

因为在进行课程预览的时候需要添加cms_page信息,所以将一个固定的信息通过配置文件的方式进行获取,而不是直接写死。

course‐publish:
  siteId: 5a751fab6abb5044e0d19ea1  #站点id
  templateId: 5aec5dd70e661808240ab7a6 #模板id
  previewUrl: http://www.xuecheng.com/cms/preview/  #预览地址
  pageWebPath: /course/detail/  #访问路径
  pagePhysicalPath: /course/detail/ #物理路径
  dataUrlPre: http://localhost:31200/course/courseview/  #数据地址

2、接口

/**
 * 课程详情页面预览
 * @param courseId 课程id
 * @return
 */
CoursePublicResult preview(String courseId);

3、实现

/**
 * 课程详情页面预览
 * @param courseId 课程id
 * @return
 */
@Override
public CoursePublicResult preview(String courseId) {

    CourseBase courseBase = this.getCourseById(courseId);

    //1.发布课程预览页面,构建cmsPage对象
    CmsPage cmsPage = new CmsPage();
    cmsPage.setSiteId(publish_siteId);
    cmsPage.setTemplateId(publish_templateId);
    cmsPage.setPageWebPath(publish_page_webPath);
    cmsPage.setDataUrl(publish_dataUrlPre + courseId);
    cmsPage.setPagePhysicalPath(publish_page_physicalPath);
    cmsPage.setPageName(courseId+".html");
    cmsPage.setPageAliase(courseBase.getName());
    //2.保存cms信息
    CmsPageResult save = this.cmsPageClient.save(cmsPage);
    if (!save.isSuccess()){
        return new CoursePublicResult(CommonCode.FAIL,null);
    }
    //3.页面id
    String pageId = save.getCmsPage().getPageId();
    //4.页面url
    String pageUrl = previewUrl + pageId;
    return new CoursePublicResult(CommonCode.SUCCESS,pageUrl);
}

5.4.4 Controller

@Override
public CoursePublicResult preview(@PathVariable("id") String id) {
    return this.courseService.preview(id);
}

5.5 前端开发

5.5.1 api方法

// 预览课程
export const preview = id => {
  return http.requestPost(apiUrl + '/course/preview/' + id)
}

5.5.2 页面

<template>
  <div>
    <template>
      <div>
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>课程预览</span>
          </div>
          <div class="text item">
            <el-button type="primary"  @click.native="preview" >课程预览</el-button>
            <br/><br/>
            <span v-if="previewurl && previewurl!=''"><a :href="previewurl" target="_blank">点我查看课程预览页面 </a> </span>
          </div>
        </el-card>
      </div>
    </template>
  </div>
</template>
<script>
  import * as sysConfig from '@/../config/sysConfig';
  import * as courseApi from '../../api/course';
  import utilApi from '../../../../common/utils';
  import * as systemApi from '../../../../base/api/system';
export default{

  data() {
    return {
      dotype: '',
      courseid: '',
      course: {"id": "", "name": "", "status": ""},
      previewurl: ''
    }
  },
  methods:{
    //预览
    preview(){
        //调用课程管理服务的预览接口,得到课程预览url
      courseApi.preview(this.courseid).then((res) => {
        if(res.success){
          this.$message.error('预览页面生成成功,请点击下方预览链接');
          if(res.previewUrl){
            //预览url
            this.previewurl = res.previewUrl
          }
        }else{
          this.$message.error(res.message);
        }
      });
    },
  },
  mounted(){
    //课程id
    this.courseid = this.$route.params.courseid;
  }

  }
</script>
<style>

</style>

六、课程发布

6.1 需求分析

课程发布后将生成正式的课程详情页面,课程发布后用户即可浏览课程详情页面,并开始课程的学习。
课程发布生成课程详情页面的流程与课程预览业务流程相同,如下:

1、用户进入教学管理中心,进入某个课程的管理界面

2、点击课程发布,前端请求到课程管理服务

3、课程管理服务远程调用CMS生成课程发布页面,CMS将课程详情页面发布到服务器

4、课程管理服务修改课程发布状态为 “已发布”,并向前端返回发布成功

5、用户在教学管理中心点击“课程详情页面”链接,查看课程详情页面内容

6.2 CMS一键发布

6.2.1 需求分析

根据需求分析内容,需要在cms服务增加页面发布接口供课程管理服务调用,此接口的功能如下:

1、接收课程管理服务发布的页面信息

2、将页面信息添加到 数据库(mongodb)

3、对页面信息进行静态化

4、将页面信息发布到服务器

6.2.2 接口定义

1、创建响应结果类型

页面发布成功cms返回页面的url

页面URL=cmsSite.siteDomain+cmsSite.siteWebPath+ cmsPage.pageWebPath + cmsPage.pageName

package com.xuecheng.framework.domain.cms.response;

import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author: 98050
 * @Time: 2019-05-20 22:27
 * @Feature: 课程详情页面发布返回结果
 */
@Data
@NoArgsConstructor
public class CmsPostPageResult extends ResponseResult {
    private String pageUrl;

    public CmsPostPageResult(ResultCode resultCode,String pageUrl) {
        super(resultCode);
        this.pageUrl = pageUrl;
    }
}

2、在api工程定义页面发布接口

/**
 * 课程页面详情发布
 * @param cmsPage
 * @return
 */
@ApiOperation("一键发布页面")
@ApiResponses({
        @ApiResponse(code = 10000,message = "操作成功"),
        @ApiResponse(code = 11111,message = "操作失败")
})
@PostMapping("/postPageQuick")
CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage);

6.2.3 Service

接口:

/**
 * 发布课程详情页面
 * @param cmsPage
 * @return
 */
CmsPostPageResult postCoursePage(CmsPage cmsPage);

实现:

/**
 * 发布课程详情页面
 * @param cmsPage
 * @return
 */
@Override
public CmsPostPageResult postCoursePage(CmsPage cmsPage) {
    //1.添加页面
    CmsPageResult result = this.save(cmsPage);
    if (!result.isSuccess()){
        return new CmsPostPageResult(CommonCode.FAIL,null);
    }
    CmsPage cmsPage1 = result.getCmsPage();
    //2.要发布的页面Id
    String pageId = cmsPage1.getPageId();
    //3.发布页面
    ResponseResult responseResult = this.postPage(pageId);
    if (!responseResult.isSuccess()){
        return new CmsPostPageResult(CommonCode.FAIL,null);
    }
    //4.得到页面的url,url = 站点域名+站点webpath+页面webpath+页面名称
    //4.1获取站点信息
    Optional<CmsSite> optional = this.cmsSiteRepository.findById(cmsPage.getSiteId());
    if (!optional.isPresent()){
        ExceptionCast.cast(CmsCode.CMS_SITE_NOTEXISTS);
    }
    CmsSite cmsSite = optional.get();
    //4.2拼装url
    return new CmsPostPageResult(CommonCode.SUCCESS, cmsSite.getSiteDomain() + cmsSite.getSiteWebPath() + cmsPage1.getPageWebPath() + cmsPage1.getPageName());
}

6.2.4 Controller

@Override
public CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage) {
    return  this.cmsService.postCoursePage(cmsPage);
}

6.3 课程发布接口

课程发布就是将静态化的页面保存到服务器指定路径下,并且修改课程的当前状态、课程索引、课程缓存等。

课程状态的数据字典如下:

6.3.1 API接口

此Api接口由课程管理提供,由课程管理前端调用此Api接口,实现课程发布。

在api工程下课程管理包下定义接口:

/**
 * 课程详情页面发布
 * @param courseId
 * @return
 */
@ApiOperation("发布课程")
@PostMapping("/publish/{id}")
CoursePublicResult publish(@PathVariable("id") String courseId);

6.3.2 Service

1、配置课程发布页面的参数

因为要把页面发布到服务器指定的路径下,所以要对配置参数进行修改。先看一下cm_client服务中的页面发布功能的实现,需要获取页面最后的存放路径:

//3.拼接页面的物理
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(pagePhysicalPath).append(sitePhysicalPath).append(cmsPage.getPageName());

所以要对pagePhysicalPath和sitePhysicalPath进行修改

course‐publish:
  siteId: 5ce3ecba591f42a4e44ce10b  #站点id
  templateId: 5aec5dd70e661808240ab7a6 #模板id
  previewUrl: http://www.xuecheng.com/cms/preview/  #预览地址
  pageWebPath: /course/detail/  #访问路径
  pagePhysicalPath: E:/Java_Demo/xuecheng/develop/xuecheng/static #页面的物理路径
  dataUrlPre: http://localhost:31200/course/courseview/  #数据地址

2、接口

/**
 * 课程详情页面发布
 * @param courseId 课程id
 * @return
 */
CoursePublicResult publish(String courseId);

3、实现

/**
 * 课程详情页面发布
 * @param courseId 课程id
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public CoursePublicResult publish(String courseId) {
    //1.发布课程详情页面
    CmsPostPageResult cmsPostPageResult = publishPage(courseId);
    if (!cmsPostPageResult.isSuccess()){
        ExceptionCast.cast(CourseCode.COURSE_PUBLISH_ERROR);
    }
    //2.更新课程状态
    CourseBase courseBase = saveCoursePubState(courseId);
    //3.课程索引
    //4.课程缓存
    //5.封装返回结果
    return new CoursePublicResult(CommonCode.SUCCESS,cmsPostPageResult.getPageUrl());
}
private CmsPostPageResult publishPage(String courseId) {
    //构造cms_page对象
    CmsPage cmsPage = constructCmsPage(courseId);
    return cmsPageClient.postPageQuick(cmsPage);
}
private CmsPage constructCmsPage(String courseId) {
    CourseBase courseBase = this.getCourseById(courseId);

    CmsPage cmsPage = new CmsPage();
    cmsPage.setSiteId(publish_siteId);
    cmsPage.setTemplateId(publish_templateId);
    cmsPage.setPageWebPath(publish_page_webPath);
    cmsPage.setDataUrl(publish_dataUrlPre + courseId);
    cmsPage.setPagePhysicalPath(publish_page_physicalPath);
    cmsPage.setPageName(courseId+".html");
    cmsPage.setPageAliase(courseBase.getName());

    return cmsPage;
}
private CourseBase saveCoursePubState(String courseId) {
    CourseBase courseBase = this.getCourseById(courseId);
    //更新发布状态
    courseBase.setStatus("202002");
    return this.courseBaseRepository.save(courseBase);
}

6.3.3 Controller

@Override
public CoursePublicResult publish(@PathVariable("id") String courseId) {
    return this.courseService.publish(courseId);
}

6.4 测试

6.4.1 Nginx中进行配置

在nginx配置课程详情页面的虚拟主机,实现访问:http://www.xuecheng.com/course/detail/4028e58161bd3b380161bd3bcd2f0000.html

1558492889063

静态资源服务器:

cms会将课程预览页面发布到服务器的E:/Java_Demo/xuecheng/develop/xuecheng/static/course/detail/下,然后通过http://www.xuecheng.com/course/detail/进行访问!

6.4.2 准备工作

1、检查课程详情页面的站点信息

这里面的站点id一定要与配置文件中的一致:

2、检查课程详情模板信息

通过测试代码添加模板,如果有则不用重复添加,这里返回的模板id一定要与上面的配置文件中的模板id一致。

/**
 * 存储文件
 * @throws FileNotFoundException
 */
@Test
public void testGridFs2() throws FileNotFoundException {
    //1.要存储的文件
    File file = new File("D:\\course.ftl");
    //2.定义输入流
    FileInputStream inputStream = new FileInputStream(file);
    //3.向GridFS存储文件
    ObjectId objectId = gridFsTemplate.store(inputStream, "课程详情模板文件","");
    //4.得到文件ID
    String fileId = objectId.toString();
    System.out.println(fileId);
}

6.4.3 cms_client启动

启动两个cms_client服务,分别对不同的队列进行监听

监听门户主站和课程详情

配置文件:

server:
  port: ${PORT:31000}
spring:
  application:
    name: xc-service-manage-cms-client
  data:
    mongodb:
      uri: mongodb://root:root@localhost:27017
      database: xc_cms
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
xuecheng:
  mq:
    #cms客户端监控的队列名称(不同的客户端监控的队列不能重复)
#    queue: queue_cms_postpage_01
#    routingKey: 5a751fab6abb5044e0d19ea1  #此routingKey为门户站点ID
    queue: ${QUEUE:queue_cms_postpage_03}
    routingKey: ${ROUTINGKEY:5ce3ecba591f42a4e44ce10b} #此routingKey为课程详情站点ID

启动配置:

1558496121135

1558496140303

6.4.4 单元测试

1、启动RabbitMQ服务

2、启动cms服务

3、启动cms_client

页面发布前:

1558583164684

Swagger接口测试:

1558583206628

结果:

1558583228955

1558583241640

访问:http://www.xuecheng.com//course/detail/4028e58161bd3b380161bd3bcd2f0000.html

1558583261292

6.5 前端开发

6.5.1 API方法

// 发布课程
export const publish = id => {
  return http.requestPost(apiUrl + '/course/publish/' + id)
}

6.5.2 页面

<template>
  <div>
    <template>
      <div>
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>课程预览</span>
          </div>
          <div class="text item">
            <el-button type="primary"  @click.native="preview" >课程预览</el-button>
            <br/><br/>
            <span v-if="previewurl && previewurl!=''"><a :href="previewurl" target="_blank">点我查看课程预览页面 </a> </span>
          </div>
        </el-card>
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>课程发布</span>
          </div>
          <div class="text item">
            <div v-if="course.status == '202001'">
              状态:制作中<br/>
              <el-button type="primary"  @click.native="publish" >新课程发布</el-button>
            </div>
            <div v-else-if="course.status == '202003'">
              状态:已下线
              <br/><br/>
              <span><a :href="'http://www.xuecheng.com/course/detail/'+this.courseid+'.html'" target="_blank">点我查看课程详情页面 </a> </span>
            </div>
            <div v-else-if="course.status == '202002'">
              状态:已发布<br/>
              <el-button type="primary"  @click.native="publish" >修改发布</el-button>
              <br/><br/>
              <span><a :href="'http://www.xuecheng.com/course/detail/'+this.courseid+'.html'" target="_blank">点我查看课程详情页面 </a> </span>
            </div>
          </div>
        </el-card>
      </div>
    </template>
  </div>
</template>
<script>
  import * as sysConfig from '@/../config/sysConfig';
  import * as courseApi from '../../api/course';
  import utilApi from '../../../../common/utils';
  import * as systemApi from '../../../../base/api/system';
export default{

  data() {
    return {
      dotype: '',
      courseid: '',
      course: {"id": "", "name": "", "status": ""},
      previewurl: ''
    }
  },
  methods:{
    //预览
    preview(){
        //调用课程管理服务的预览接口,得到课程预览url
      courseApi.preview(this.courseid).then((res) => {
        if(res.success){
          this.$message.error('预览页面生成成功,请点击下方预览链接');
          if(res.previewUrl){
            //预览url
            this.previewurl = res.previewUrl
          }
        }else{
          this.$message.error(res.message);
        }
      });
    },
    publish(){
      //课程发布
      courseApi.publish(this.courseid).then(res=>{
          if(res.success){
              this.$message.success("发布成功,请点击下边的链接查询课程详情页面")

          }else{
            this.$message.error(res.message)
          }

      })
    },
    getCourseView(){
      courseApi.findCourseView(this.courseid).then(res=>{
        if(res && res.courseBase){
            //获取课程状态
            this.course.status = res.courseBase.status;
        }

      })
    }

  },
  mounted(){
    //课程id
    this.courseid = this.$route.params.courseid;
    //查询课程信息
    this.getCourseView();
  }

  }
</script>
<style>

</style>

发布成功重新查询课程,如果课程状态已更改则显示课程详情页面的链接。

在钩子方法中查询课程信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章