Eureka注册中心
需求分析
在后端分离架构中,服务层被拆分成了很多的微服务,微服务的信息如何管理?SpringCluod中提供服务注册中心来管理微服务信息
为什么要使用注册中心?
1.微服务数量众多,要进行远程调用就需要知道服务端的ip地址和端口,注册中兴帮助我们管理这些服务的ip和端口.
2.微服务会试试上报自己的状态,注册中心统一管理这些微服务的状态,将存在的问题服务踢出服务列表,客户端获取到可用的服务进行调用.
Eureka注册中心
Eureka介绍
SpirngCloudEureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能,SpringCloudEureka提供了服务端与客户端,服务端即是Eureka服务注册中心,客户端文成微服务向Eureka服务的注册与发现.服务端和客户端均采用Java语言编写,下图显示了EurekaServer与EurekaClient的关系:
1.EurekaServer是服务端,负责管理各各微服务节点的信息和状态.
2.在微服务上部署EurekaClient程序,远程访问EurekaServer将自己注册在EurekaServer.
3.微服务要调用另一个微服务时,从EurekaServer中获取服务调用地址,进行远程调用.
EurekaServer打击那
单机环境搭建
1.创建xc-govern-center工程:
包结构:com.xuecheng.govern.center
2.添加依赖
在父工程中添加:(有了则不用添加)
这个时父工程中的spring-cluod依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
导入依赖,在spring-parent中已经确定版本信息所以此处不再需要控制版本
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 Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2022/12/15 15:14:50
*/
@EnableEurekaServer//标识此工程为一个EurekaServer,启动很多和EurekaServer有管的组件
@SpringBootApplication
public class GovernCenterApplication {
public static void main(String[] args) {
SpringApplication.run(GovernCenterApplication.class , args);
}
}
启动类
appliocation.yml配置
server:
port: 50101 #服务端口
spring:
application:
name: xc-govern-center #指定服务名
eureka:
client:
register-with-eureka: false #服务注册名,是否将自己注册到Eureka服务中
fetch-registry: false #服务发现,是否从Eureka中获取注册信息
service-url: #Eureka客户端与Eureka服务端的交互地址,高可用状态配置对方的地址,单机状态配置自己(如果不配置则默认本地)
defaultZone: http://localhost:50101/eureka/
server:
enable-self-preservation: false #是否开启自我保护模式
eviction-interval-timer-in-ms: 60000 #服务注册表清理间隔(单位毫秒,默认是60*1000)
启动项目后访问项目端口:
这里显示的时Eureka界面,
EurekaServer 有一种自我保护模式,当服务不再向EurekaServer上报状态,EurekaServer会将服务从服务列表中删除,如果出现网络异常情况(微服务正常)此时,EurekaServer进入子保护模式,不在将微服务从服务列表删除.
在开发阶段,建议关闭子保护模式(因为一般情况下,网络是不会有问题的,进入自我保护模式,会影响开发判断).
高可用环境搭建
EurekaServer高可用黄静需要部署两个EurekaServer,他们互相向对方注册.如果在本机启动两个Eureka需要注意两个EurekaServer的端口要设置不一样,这里我们部署一个EurekaServer工程,将端口可配置,制作两个EurekaServer启动脚本,启动不同的端口,如下图:
将两个都运行起来
这就是高可用环境的配置
application.yml:
server:
port: ${PORT:50101} #服务端口
spring:
application:
name: xc-govern-center #指定服务名
eureka:
client:
register-with-eureka: true #服务注册名,是否将自己注册到Eureka服务中
fetch-registry: true #服务发现,是否从Eureka中获取注册信息
service-url: #Eureka客户端与Eureka服务端的交互地址,高可用状态配置对方的地址,单机状态配置自己(如果不配置则默认本地)
defaultZone: ${EUREKA_SERVER:http://eureka02:50102/eureka/}
server:
enable-self-preservation: false #是否开启自我保护模式
eviction-interval-timer-in-ms: 60000 #服务注册表清理间隔(单位毫秒,默认是60*1000)
instance:
hostname: ${EUREKA_DOMAIN:eureka01}
高可用配置的原理就是
双方互相进行注册
如果其中一台eureka死亡,那么另一台也可以继续使用,因为他们管理的微服务内容都是相同的
主机名或者域名一定要在hosts文件中进行配置
为什么?因为主机名或者域名最终要解析到一个ip上边
还有就是配置hostname
- 在实际中使用时Eureka Server至少部署两台服务器,实现高可用
- 两台EurekaServer互相注册
- 微服务需要连接两台EurekaServer注册,当其中一台死掉的时候也不会影响服务的注册与发现
- 微服务会定时向Eurekaserver发送心跳,报告自己的状态.
- 微服务从注册中心获取服务地址以RESTful方式发起远程调用
服务注册
将cms注册到Eureka Server
下边实现cms向EurekaServer注册
(CMS作为客户端,将自己注册到Eureka服务端)
1.在cms服务中添加客户端
依赖包
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.在application.yml中进行配置
server:
port: 31200
spring:
application:
name: xc-service-manage-course
datasource:
druid:
url: jdbc:mysql://localhost:3306/xc_course?characterEncoding=utf-8
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
pagehelper:
auto-dialect: mysql
eureka:
client:
register-with-eureka: true #服务注册开关
fetch-registry: true #服务发现开关
service-url: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号隔开
defaultZone: ${EUREKA_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
由于50101和50102互相注册,所以,向50101注册,50102也可以得到注册信息
不过,为了安全起见还是应该都写上
但是如果正在上报,但是50101突然死掉了,就上报不了了,所以,还是建议两个都写上
还有就是这里不用再去配置主机名hostname
如果配置主机名,的话,需要去解析为ip地址
这里直接将ip注册到Eureka服务中
在启动类中加入注解
@EnableDiscoveryClient
表示一个Eureka客户端从EurekaServer发现服务
微服务间进行远程调用,所以要去发现
启动起来后
cms服务添加到注册中
由于时高可用,所以,两个Eureka中都监控到
使用相同方法,在course项目中进行 添加
pom文件中的依赖
application.yaml配置
启动类中的@EnalbleDiscoveryClient
Feign远程调用
在前后端分离架构中,服务层被拆分成了很多的微服务,服务与服务中间难民啊发生交互,比如:课程发布需要调用CMS服务生成的课程祭台画页面,本节研究微服务远程调用所使用的技术.
下图是课程管理服务远程调用CMS服务的流程图:
Ribbon
Ribbon介绍
Ribbon是Netflix公司开源的一个负载均衡的项目(htt://github.com/NetFlix/ribbon),他是一个基于Http.TCP的客户端负载均衡器
.
1.什么是负载均衡?
负载均衡是为服务架构中必须使用的技术,通过负载均衡老师想系统的高可用,集群扩容等功能,负载均衡可通过硬件设备及软件来实现,硬件比如:F5,Array等,软件比如:LVS,Nginx等.
如下图是负载均衡的架构图:
服务端负载均衡
用户请求先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法将请求转发到微服务.负载均衡算法有:轮询,随机,加权轮询,甲醛随机,地址哈希等方法,负载军很气维护一份服务列表,更具负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以微微服务集群分担请求,降低系统的压力.
2.什么是客户端负载均衡?
上图是服务端负载均衡,客户端负载均衡与服务端负载均衡的区别在于客户端要维护一份服务列表,Ribbon从EurekaServer获取服务列表,Ribbon根据负载均衡算法直接请求到具体的微服务,中间省去了负载均衡服务.如下图是Ribbon负载均衡的流程图:
客户端负载均衡
那么nginx是服务端的负载均衡还是客户端的负载均衡?
ninx是服务端的负载均衡,发挥着一个代理的作用
Ribbon测试
Spirng Cloud引入Ribbon配合,restTemplate实现客户端负载均衡.Java中远程调用的技术有很多,如webservice,sockert,rmi.ApacheHttpClilent
,OkHttp等,互联网项目使用基于http的客户端较多,本项目使用OkHttp.
1.在客户端添加Ribbon依赖:
依赖关系图:
这里,在课程管理服务配置ribbon依赖
课程调用cms,调用方就是客户方
所以在courese中添加ribbon依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<!-- <version>1.0.6.RELEASE</version>-->
</dependency>
2.配置Ribbon参数
application文件中的环境配置
ribbon:
MaxAutoRetries: 2 #最大重试次数
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false # 对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #
在CMS的配置文件中配置
配置两个运行文件
此时已经定义好了
但是需要需注意的是此时ribbon还不具备负载均衡的能力,需要在启动文件中配置
@LoadBalanced
注解
负载均衡测试
添加@LoadBalanced注解之后,restTemplate会走LocalBalancerInterceptor拦截器,此拦截器中会通过RibbonLoadBalancerClient查询服务地址,可以在此类打断点观察每次调用的服务地址和端口,两个CMS服务会轮流被调用.
调试成功!
因为我们之部署了两个实例,一个是31002,一个是31001,经过负载均衡的轮询算法取访问
这里添加了一个for循环方便测试
Feign
Feign介绍
feign是Netflix公司开源的轻量级rest客户端,使用Feign可以非常方便的是心啊Http客户端,Spring Cloud引入Feign并且集成了Ribbon实现客户端负载均衡调用
用Ribbon也可以实现一个负载均衡的调用,那么Feign有什么用呢?有什么有事么?
对的
在Ribbon中,并不是Ribbon直接调用,而是通过一个SpringtestTemplate来使用的
Ribbon就是一个客户端负载均衡的作用
,但是这种方法可不可以再优化一下:
这种方法我感觉实有一点麻烦,为什么这么说呢?
这里边需要去设置url,另外,还需要去定义返回的类型
但是这个过程可不可以像调用service接口一样本地调用
像调用本地方法一样去发起远程调用
Feign集成Ribbo之后,就可以非常便捷的进行负载均衡的调用
Feign测试
1.在客户端添加依赖
子啊课程管理服务添加下边的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
这个依赖,把ribbon和feign都引入进来了.
2.定义FeignClient接口
参考Swagger文档定义FeignClient,注意接口的Url,请求参数类型,返回值类型与Swagger接口一致.
在课程管理服务中创建Client包,定义查询cms页面的客户端该用接口.
创建接口,调用时,参考Swagger参数是什么,返回值是什么
可以看出,参数是一个id,返回值是一个CmsPage
调用Feign结合Ribbon来远程调用使用Springmvc(@GetMapping)的注解,充分说明Feign集成SpringMVC的功能
所以SpringMVC是怎么做的,这里就怎么做
Feign集成远程调用,调用那个服务,要告诉Ribbon
所以这里还需要配置
在这里配置了Ribbon才知道要去EurekaServer中找Cms服务进行调用
写完之后需要在启动类中加入
注解
@EnableFeignClients
此时调试,就会发现调试成功
总结一下:
Feign是一个轻量级的rest客户端
有了Feign可以将调用方法简化,不用将restTemplate那样定义url,定义返回的结果
就像调用本地方法一样,进行远程方法调用
Feign注意点
SpringCloud对Feign进行了增强兼容SpringMVC的注解,我们在使用SpringMVC的注解时需要注意:
1.feignClient接口有参数在参数必须加@PathVariable(“xx”)和@RequestParam(“XXX”)
2.feignClient返回值为复杂对象时,其类型必须有无参构造函数.
课程预览技术方案
需求分析
课程预览是为了保证课程发布后端正确性,通过课程预览,可以直观的通过课程详情页面看到课程的信息是否正确,通过课程预览看到的也买你的内容和课程发布后的内容是一致的
下图时课程想请页面的预览图:
课程详情页面技术方案
课程预览浏览到的页面就是课程详情页面,需要现确定课程详情页面的技术方按后方可确定课程预览的技术方案.
技术需求
课程详情页面事项用户展示课程信息的窗口,课程相当于网站的商品,本页main的访问量会非常大,此页面的内容设计不仅要展示出课程核心众多内容而且用户访问也买你的速度要有保证,有统计显示打开一个页面超过4秒的用户就走掉了,所以本页面的性能要求时本页面的重要需求.
本页main另一个需求就是SEO(搜索引擎优化),要非常有利于爬虫抓取页面上的信息,并且生成页面快照,利于用户通过搜索引搜索课程信息.
解决方案
如何保证SEO的前提下提高页面的访问速度:
方案1:
对于信息获取类的需求,要想提高页面的速度,就要使用缓存来减少或者避免对数据库的访问,从而提高页面的访问速度,下图是使用缓存与不使用缓存的区别
不加缓存对tomcat压力不大
加缓存后对tomcat压力大
方案2:
对于不会频繁改变的信息,可以采用页面静态化技术,提前让页面生成html静态也买你存储在nginx服务器中,用户直接访问nginx即可,对于一些动态信息可以访问服务端获取json数据在页面渲染.
页面发布后修改很少的状态,就建议使用页面静态化技术
tomcat访问量为300-400,而是用nginx的访问量为5000-更高,不是一个量级是很多.
对于大部分修改少的页面,使用静态化技术,对于少部分需要大量修改的页面可以采用服务端使用ajax请求返回数据
而这部分数据即使蜘蛛抓取不到也没关系,因为重要的信息静态页面信息已经给获取到了.
优点:使用Nginx作为web服务器,并且直接访问html页面,性能出色.
缺点:需要维护大量的静态页面,增加了维护的难度.
课程预览技术方案
根据要求:课程详情页面采用静态化技术生成html页面,课程预览的效果与最终静态化的页面html内容一致.
所以,课程预览功能也采用静态化技术生成Html页面,课程预览页面使用的模板与课程详情页面的模板一致,这样,就可以保证课程预览的效果与最终课程详情页面的效果形同.
操作流程:
1.制作课程详情页面模板
2.开发课程详情页面数据模型的查询接口(为静态化提供数据)
3.调用cms课程预览接口通过浏览器浏览静态文件
课程详情页面静态化
静态页面测试
页面内容组成
我们在编写一个页面的时候需要知道那些信息是静态信息,那些信息为动态信息,下图是页面设计图:
除了红色边框的都是静态
红色动态信息标识一个按钮,根据用户登录状态,是否购买课程显示按钮的事件.
包括以下信息:
- 课程信息
课程标题,价格,课程等级,授课模式,课程图片,课程介绍,课程目录 - 课程统计信息
课程时长,评分,收藏人数 - 教育机构信息
公司名称,公司简介 - 教育机构统计
好评数,课程数,学生人数 - 教师信息
老师名称,老师介绍
搞清楚页面组成之后,那么页面如何实现呢?
可以采用之前学过的技术SSI,将之拆分成无数个页面,然后通过nginxSSI技术把它拼接成一个页面
页面拆分
- 页头
- 页面尾
- 课程详情主页
- 教育机构页面
- 老师信息页面
- 课程统计页面
- 教育机构统计页面
静态页面测试
页面加载思路
在课程详情模板中研究页面加载的思路
模板也买你路径如下:
静态页面目录/static/course/detail/course_main_template.html
1.主页面
需要在主页面中通过SSI加载:页头,页尾,教育机构,教师信息
2.异步加载课程统计与教育机构统计信息
课程统计信息(json),教育机构统计信息(json)
3.马上学习按钮事件
用户点击"马上学习"会根据收费情况,课程购买情况执行下一步操作.
静态资源虚拟主机
配置静态资源虚拟主机
静态资源虚拟主机负责处理课程详情、公司信息、老师信息、统计信息等页面的请求:
将课程资料中的“静态页面目录”中的目录拷贝到F:/develop/xuecheng/static下在nginx中配置静态虚拟主机如下:
#学成网静态资源server{
listen91;
server_namelocalhost;
#公司信息
location/static/company/{
aliasF:/develop/xuecheng/static/company/;
}
#老师信息
location/static/teacher/{
aliasF:/develop/xuecheng/static/teacher/;
}
#统计信息
location/static/stat/{
aliasF:/develop/xuecheng/static/stat/;
}
location/course/detail/{
aliasF:/develop/xuecheng/static/course/detail/;
}
}
通过www.xuecheng.com虚拟主机转发到静态资源
由于课程页面需要通过SSI加载页头和页尾所以需要通过www.xuecheng.com虚拟主机转发到静态资源在www.xuecheng.com虚拟主机加入如下配置:
location/static/company/{
proxy_passhttp://static_server_pool;}
location/static/teacher/{
proxy_passhttp://static_server_pool;}
location/static/stat/{
proxy_passhttp://static_server_pool;}
location/course/detail/{
proxy_passhttp://static_server_pool;}
配置upstream实现请求转发到资源服务虚拟主机:
#静态资源服务
upstreamstatic_server_pool{server127.0.0.1:91weight=10;
}
门户静态资源路径
门户中的一些图片,样式等静态资源统一通过/static路径对外提供服务,在www.xuecheng.com虚拟主机中配置如下:
#静态资源,包括系统所需的图片,js,css等静态资源
location /static/img/ {
alias F:/develop/xc_portal_static/img/;
}
location /static/css/ {
alias F:/develop/xc_portal_static/css/;
}
location /static/plugins/ {
alias F:/develop/xc_portal_static/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:允许客户端跨域访问的方法
页面预览,再页面预览这里会遇到许多问题,不过还是那句话,见招拆招.
当你知道哪里出了问题之后,就去解决,然后就成功了.
课程数据模型查询接口
静态化操作需要模型数据放可以进行静态化,课程数据模型由课程管理服务提供,进供课程静态化程序调用使 用.
接口定义
1.响应结果类型
在model里面的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;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2022/12/19 15:55:32
*/
@Data//基于lambok
@NoArgsConstructor//加入被远程调用,写一个无参构造方法
@ToString
public class CourseView implements java.io.Serializable{//防止这个信息将来被序列化
private CourseBase courseBase;
private CoursePic coursePic;
// 营销信息
private CourseMarket courseMarket;
// 用于获取教学计划的树形结构的数据类型
private TeachplanNode teachplanNode;
}
请求类型
请求类型只需要一个课程id就可以了
根据一个id返回上面的一个模型
静态化页面拿到这些信息,然后页面就可以使用了
接口定义
这个接口是用来查询课程,所以,在课程的接口下定义
@ApiOperation(("课程试图查询"))
public CourseView courseView(String id);
Controller中代码
@Override
@GetMapping("/courseview/{id}")
public CourseView courseView(@PathVariable("id") String id) {
return courseService.getCourseView(id);
}
Service代码
// 查询课程的视图,保罗基本信息,图片,营销,课程计划
public CourseView getCourseView(String id) {
CourseView courseView = new CourseView();
// 课程基本信息
Optional<CourseBase> courseBaseOptional = courseBaseRepository.findById(id);
if (courseBaseOptional.isPresent()) {
courseView.setCourseBase(courseBaseOptional.get());
}
// 查询课程的图片
Optional<CoursePic> picOptional = coursePicRepository.findById(id);
if (picOptional.isPresent()) {
CoursePic coursePic = picOptional.get();
courseView.setCoursePic(coursePic);
}
// 课程的营销信息
Optional<CourseMarket> marketOptional = courseMarketRepository.findById(id);
if (marketOptional.isPresent()){
courseView.setCourseMarket(marketOptional.get());
}
// 课程计划信息
TeachplanNode teachplanNode = teachplanMapper.selectList(id);
courseView.setTeachplanNode(teachplanNode);
return courseView;
}
课程预览功能开发
需求分析
课程预览功能将使用cms系统提供的页面预览功能,业务流程如下:
1.用户静茹课程管理界面,点击课程预览,请求到课程管理服务
2.课程管理服务远程调用cms添加页面接口,向cms添加课程详情页面
3.课程管理服务得到cms返回课程详情页面id,并拼接生成课程预览Url
4.课程管理服务将课程预览Url给前端返回
5.用户在前端页面请求课程预览Url,打开新窗口显示课程详情内容
CMS页面预览测试
CMS已经提供了页面预览功能,课程预览功能要使用cms页面预览接口实现,下边通过cms页面预览接口测试课程预览效果.
1.向cms_page表中插入一条也买你记录或者从cms_page中找一个页面进行测试.
注意:页面配置一定要正确,需要设置正确的模板id和dataUrl.
如下,是一条页面的记录:
首先将页面保存在电脑文件中,使用获取流获取数据,然后使用GridFSTemplate.store保存文件到fs.chunck中,
// 存文件
@Test
public void testStore() throws FileNotFoundException {
// 定义一个file
File file = new File("c:/course.ftl");
// 定义一个file
FileInputStream fileInputStream = new FileInputStream(file);
ObjectId objectId = gridFsTemplate.store(fileInputStream, "course.ftl");
System.out.println(objectId);
}
获取到文件的id这个id时file_id
在page中编写template的id然后再template中编写file_id
定义好之后,调用cms的静态化程序,
获取到文件的静态化效果
(bug)关于fs.chunck文件修改后报错问题
如果再fs.chunck中需要改写文件,则导出文件,修改导出文件的内容,然后导入,
此时在运行程序会报错,因为chunck中的文件数字大小发生错误,需要重新写入新文件的大小,至于数值,可以再报错中查找
CMS添加页面接口
cms服务对外提供添加页面接口,实现:乳沟不存在页面,则添加,否则,就更新页面信息.
此接口由课程管理服务再课程预览时调用.
API接口
@ApiOperation("保存页面")
public CmsPageResult save(CmsPage cmsPage);
Controller:
@Override
@PostMapping("/save")
public CmsPageResult save(@RequestBody CmsPage cmsPage) {
return pageService.save(cmsPage);
三个标注判断一个页面是否存在,这个主键是再之前配置的,防止出现错误!
Service:
// 有则跟新,没有则添加
public CmsPageResult save(CmsPage cmsPage) {
// 判断页面是否存在
// 有三个标准判断页面是否存在
CmsPage cmsPage1 = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
//得到页面cmspage1
if (cmsPage1==null) {
//没有页面,进行更新
this.update(cmsPage.getPageId(),cmsPage);
}
return this.add(cmsPage);
}
课程预览服务端
Api定义
此Api是课程管理前端请求服务端惊醒课程预览的Api
请求: 课程id
响应课程预览Url
1.定义响应类型
再model中的course下response中定义
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 Andrewer
* @version 1.0
* @project xcEduService01
* @description 课程预览
* @date 2022/12/20 13:12:03
*/
@Data
@ToString
@NoArgsConstructor
public class CoursePublishResult extends ResponseResult {
String previewUrl;
public CoursePublishResult (ResultCode resultCode,String previewUrl){
super(resultCode);
this.previewUrl=previewUrl;
}
}
在课程管理接口中写
返回值类型是上文中写到的preview,主要是返回一个view的url地址
@ApiOperation("课程预览")
public CoursePublishResult preview(String id);
Controller
@Override
@PostMapping("/preview/{id}")
public CoursePublishResult preview(@PathVariable("id") String id) {
return courseService.preview(id);
}
在Clilent中:
像调用本地方法一样去调用远程方法
// 添加页面,用于课程预览
@PostMapping("/cms/page/save")
public CmsPageResult saveCmsPage(@RequestBody CmsPage cmsPage);
}
在yaml中配置:
course-publish:
siteId: 5a751fab6abb5044e0d19ea1
templateId: 63a07860f2ca6a64247b1314
previewUrl: http://www.xuecheng.com/cms/preview/
pageWebPath: /course/detail/
pagePhysicalPath: /course/detail/
dataUrlPre: http://localhost:31200/course/courseview/
Service:
@Autowired
CmsPageClient cmsPageClient;
@Value("${course-publish.dataUrlPre}")
private String publish_dataUrlPre;
@Value("${course-pulish.pagePhysicalPath}")
private String publish_page_physicalpath;
@Value("${course-pulish.pageWebPath}")
private String publish_page_webpath;
@Value("${course-pulish.siteId}")
private String publish_siteId;
@Value("${course-pulish.templateId}")
private String publish_templateId;
@Value("${course-pulish.previewUrl}")
private String previewUrl;
//课程预览
public CoursePublishResult preview(String id) {
CourseBase courseBaseById = this.findCourseBaseById(id);
// 查询课程
// 请求cms添加页面
// 准备cmsPage信息
CmsPage cmsPage = new CmsPage();
cmsPage.setSiteId(publish_siteId);
cmsPage.setDataUrl(publish_dataUrlPre+id);
cmsPage.setPageName(id+".html");
cmsPage.setPageAliase(courseBaseById.getName());//页面的别名,就是课程名称
cmsPage.setPagePhysicalPath(publish_page_physicalpath);
cmsPage.setPageWebPath(publish_page_webpath);//页面的webpath
cmsPage.setTemplateId(publish_templateId);//页面模板的id
//添加页面,就要远程调用cms,这里需要cmspage参数所以还要准备cmspage信息
CmsPageResult cmsPageResult = cmsPageClient.saveCmsPage(cmsPage);
if (!cmsPageResult.isSuccess()) {
return new CoursePublishResult(CommonCode.FAIL,null);
// 抛出异常
}
CmsPage cmsPage1 = cmsPageResult.getCmsPage();
String pageId = cmsPage1.getPageId();
// 拼装页面预览的url
String url = previewUrl+pageId;
// 返回CoursePublishResult对象(当中包含了页面预览的url)
return new CoursePublishResult(CommonCode.SUCCESS,previewUrl);
}