學成在線筆記七:註冊中心與課程預覽發佈實現

Eureka註冊中心

在前後端分離架構中,服務層被拆分成了很多的微服務,微服務的信息如何管理?Spring Cloud中提供服務註冊中心來管理微服務信息。
爲什麼要用註冊中心?

1、微服務數量衆多,要進行遠程調用就需要知道服務端的ip地址和端口,註冊中心幫助我們管理這些服務的ip和端口。

2、微服務會實時上報自己的狀態,註冊中心統一管理這些微服務的狀態,將存在問題的服務踢出服務列表,客戶端獲取到可用的服務進行調用。

註冊中心工程搭建

依賴

<?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>xc-framework-parent</artifactId>
        <groupId>com.xuecheng</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../xc-framework-parent/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>xc-govern-center</artifactId>

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

</project>

啓動類

package com.xuecheng.govern.center;

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

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

單機版配置-application.yml

server:
  port: 50101 #服務端口
spring:
  application:
    name: xc‐govern‐center #指定服務名
eureka:
  client:
    registerWithEureka: true #服務註冊,是否將自己註冊到Eureka服務中
    fetchRegistry: true #服務發現,是否從Eureka中獲取註冊信息
    serviceUrl: #Eureka客戶端與Eureka服務端的交互地址,高可用狀態配置對方的地址,單機狀態配置自己(默認本機8761端口)
      defaultZone: http://localhost:50101/eureka/
  server:
    enable‐self‐preservation: false #是否開啓自我保護模式
    eviction‐interval‐timer‐in‐ms: 60000 #服務註冊表清理間隔(單位毫秒,默認是60*1000)

高可用版配置-application.yml

server:
  port: ${PORT:50101} #服務端口
spring:
  application:
    name: xc‐govern‐center #指定服務名
eureka:
  client:
    registerWithEureka: true #服務註冊,是否將自己註冊到Eureka服務中
    fetchRegistry: true #服務發現,是否從Eureka中獲取註冊信息
    serviceUrl: #Eureka客戶端與Eureka服務端的交互地址,高可用狀態配置對方的地址,單機狀態配置自己(默認本機8761端口)
      defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/}
  server:
    enable‐self‐preservation: false #是否開啓自我保護模式
    eviction‐interval‐timer‐in‐ms: 60000 #服務註冊表清理間隔(單位毫秒,默認是60*1000)
  instance:
     hostname: ${EUREKA_DOMAIN:eureka01}

通過設置JVM運行參數,完成多個註冊中心實例的啓動。

服務註冊

依賴

在需要註冊到註冊中心的微服務中添加依賴,如:xc-service-manage-cms,其他微服務操作相同。

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

啓動類上新增註解

在啓動類上新增@EnableDiscoveryClient註解。

application.yml新增配置

eureka:
  client:
    registerWithEureka: true #服務註冊開關
    fetchRegistry: true #服務發現開關
    serviceUrl: #Eureka客戶端與Eureka服務端進行交互的地址,多箇中間用逗號分隔
      defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
  instance:
    prefer‐ip‐address: true  #將自己的ip地址註冊到Eureka服務中
    ip‐address: ${IP_ADDRESS:127.0.0.1}
    instance‐id: ${spring.application.name}:${server.port} #指定實例id

課程詳情靜態化

課程數據查詢

響應結果

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;

@Data
@ToString
@NoArgsConstructor
public class CourseView {

    private CourseBase courseBase;

    private CourseMarket courseMarket;

    private CoursePic coursePic;

    private TeachplanNode teachplanNode;
}

CourseViewControllerApi

package com.xuecheng.api.course;

import com.xuecheng.framework.domain.course.ext.CourseView;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value = "課程預覽", description = "課程預覽接口,提供課程預覽數據的查詢")
public interface CourseViewControllerApi {

    @ApiOperation("課程視圖查詢")
    CourseView courseview(String id);

}

CourseViewController

package com.xuecheng.manage_course.controller;

import com.xuecheng.api.course.CourseViewControllerApi;
import com.xuecheng.framework.domain.course.ext.CourseView;
import com.xuecheng.framework.web.BaseController;
import com.xuecheng.manage_course.service.CourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("courseview")
public class CourseViewController extends BaseController implements CourseViewControllerApi {

    @Autowired
    private CourseService courseService;


    /**
     * 查詢課程預覽所需數據
     *
     * @param id 課程ID
     * @return CourseView
     */
    @Override
    @GetMapping("{id}")
    public CourseView courseview(@PathVariable String id) {
        return courseService.getCourseView(id);
    }
}

CourseService

package com.xuecheng.manage_course.service;

import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.domain.course.CourseMarket;
import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.domain.course.ext.CourseView;
import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import com.xuecheng.framework.service.BaseService;
import com.xuecheng.manage_course.dao.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Slf4j
@Service
public class CourseService extends BaseService {

    @Autowired
    private CourseBaseRepository courseBaseRepository;

    @Autowired
    private CoursePicRepository coursePicRepository;

    @Autowired
    private CoursePlanMapper coursePlanMapper;

    @Autowired
    private CoursePlanRepository coursePlanRepository;

    @Autowired
    private CourseMarketRepository courseMarketRepository;

    /**
     * 查詢課程預覽所需數據
     *
     * @param id 課程ID
     * @return CourseView
     */
    public CourseView getCourseView(String id) {
        CourseView result = new CourseView();

        // 查詢課程基本信息
        Optional<CourseBase> courseBaseOptional = courseBaseRepository.findById(id);
        courseBaseOptional.ifPresent(result::setCourseBase);

        // 查詢課程圖片
        Optional<CoursePic> coursePicOptional = coursePicRepository.findById(id);
        coursePicOptional.ifPresent(result::setCoursePic);

        // 查詢課程營銷信息
        Optional<CourseMarket> courseMarketOptional = courseMarketRepository.findById(id);
        courseMarketOptional.ifPresent(result::setCourseMarket);

        // 查詢課程計劃信息
        TeachplanNode teachplanNode = coursePlanMapper.findList(id);
        result.setTeachplanNode(teachplanNode);

        return result;
    }
}

課程頁面模板

新增課程頁面模板(模板文件在資料裏面提供的有)。

課程預覽實現

需求分析

  1. 用戶進入課程管理頁面,點擊課程預覽,請求到課程管理服務。
  2. 課程管理服務遠程調用cms添加頁面接口向cms添加課程詳情頁面。
  3. 課程管理服務得到cms返回課程詳情頁面id,並拼接生成課程預覽Url。
  4. 課程管理服務將課程預覽Url給前端返回。
  5. 用戶在前端頁面請求課程預覽Url,打開新窗口顯示課程詳情內容。

CMS頁面預覽測試

我這裏使用CMS管理頁面手動添加了一個頁面

在頁面預覽Controller中新增代碼,設置響應頭信息

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

點擊頁面預覽即可。

CMS添加課程頁面

功能說明:提供API,當課程前端點擊頁面預覽時先添加課程詳情頁面。

CmsPageControllerApi

新增API接口

    @ApiOperation("保存頁面")
    CmsPageResult save(CmsPage cmsPage);

CmsPageController

新增接口實現

    @Override
	@PostMapping("save")
    public CmsPageResult save(@RequestBody CmsPage cmsPage) {
        CmsPage save = cmsPageService.save(cmsPage);
        if (save == null) {
            ExceptionCast.cast(CommonCode.FAIL);
        }
        return new CmsPageResult(CommonCode.SUCCESS, save);
    }

CmsPageService

新增保存方法

    public CmsPage save(CmsPage cmsPage) {
        CmsPage _cmsPage = cmsPageRepository
                .findBySiteIdAndPageNameAndPageWebPath(cmsPage.getSiteId(), cmsPage.getPageName(), cmsPage.getPageWebPath());
        if (_cmsPage == null) {
            // 新增
            cmsPage = add(cmsPage);
        } else {
            // 更新
            cmsPage.setPageId(_cmsPage.getPageId());
            cmsPage = edit(cmsPage);
        }

        return cmsPage;
    }

課程預覽調用

編寫Feign Client

package com.xuecheng.manage_course.client;

import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.response.CmsPageResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * CMS PAGE API
 */
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)
public interface CmsPageClient {

    @PostMapping("cms/page/save")
    CmsPageResult save(@RequestBody CmsPage cmsPage);

}

響應結果實體類

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;

@Data
@ToString
@NoArgsConstructor
public class CoursePublishResult extends ResponseResult {
    private String previewUrl;

    public CoursePublishResult(ResultCode resultCode, String previewUrl) {
        super(resultCode);
        this.previewUrl = previewUrl;
    }
}

新增配置

主要爲Cms Page相關參數

course‐publish:
  siteId: 5d8cd1d35f31573b5c6e4f11
  templateId: 5d8ccd635f31573b5c6e4f0e
  previewUrl: http://www.xuecheng.com/cms/preview/
  pageWebPath: /course/detail/
  pagePhysicalPath: F:/xcEdu/xcEdu_ui/static/course/detail/
  dataUrlPre: http://localhost:31200/course/courseview/  

Cms Page配置類

主要用於讀取Cms Page相關參數

package com.xuecheng.manage_course.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "course-publish")
public class CoursePublishConfig {
    private String siteId;
    private String templateId;
    private String previewUrl;
    private String pageWebPath;
    private String pagePhysicalPath;
    private String dataUrlPre;
}

CourseViewControllerApi

    @ApiOperation("課程視圖預覽")
    CoursePublishResult coursePreview(String id);

CourseViewController

    /**
     * 預覽課程
     *
     * @param id 課程ID
     * @return CoursePublishResult
     */
    @Override
    @PostMapping("courseview/preview/{id}")
    public CoursePublishResult coursePreview(@PathVariable String id) {
        String preview = courseService.preview(id);
        if (StringUtils.isBlank(preview)) {
            return new CoursePublishResult(CommonCode.FAIL, null);
        }
        return new CoursePublishResult(CommonCode.SUCCESS, preview);
    }

CourseService

    @Autowired
    private CoursePublishConfig coursePublishConfig;
	
	/**
     * 課程預覽
     *
     * @param id 課程id
     * @return previewUrl
     */
    public String preview(String id) {
        // 查詢課程基本信息
        Optional<CourseBase> courseBaseOptional = courseBaseRepository.findById(id);
        if (!courseBaseOptional.isPresent()) {
            ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
        }
        CourseBase courseBase = courseBaseOptional.get();

        CmsPage cmsPage = new CmsPage();
        cmsPage.setSiteId(coursePublishConfig.getSiteId());
        cmsPage.setTemplateId(coursePublishConfig.getTemplateId());
        cmsPage.setPageAliase(courseBase.getName());
        cmsPage.setPageName(courseBase.getId() + ".html");
        cmsPage.setPageWebPath(coursePublishConfig.getPageWebPath());
        cmsPage.setPagePhysicalPath(coursePublishConfig.getPagePhysicalPath());
        cmsPage.setDataUrl(coursePublishConfig.getDataUrlPre() + courseBase.getId());

        CmsPageResult save = cmsPageClient.save(cmsPage);
        if (save.isSuccess()) {
            return coursePublishConfig.getPreviewUrl() + save.getCmsPage().getPageId();
        }

        return null;
    }

前端修改

我這裏的課程預覽API鏈接事:course/courseview/preview/{id}

前端調用的確實:course/preview/{id}

所以這裏我需要修改一下前端API接口地址就OK了,其他內容基本上全部是正常的。

排坑

我調用CmsPageClient時報錯。

Type definition error: [simple type, class com.xuecheng.framework.domain.cms.response.CmsPageResult]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.xuecheng.framework.domain.cms.response.CmsPageResult`, problem: null
 at [Source: (PushbackInputStream); line: 1, column: 558]

因爲這裏我之前編寫CmsPageResult實體類,忘記添加無參構造了,所以這裏Spring反序列化對象的時候出錯了。

解決:

CmsPageResult的類上添加@NoArgsConstructor或者手寫一個無參構造函數即可。

課程發佈實現

課程發佈後將生成正式的課程詳情頁面,課程發佈後用戶即可瀏覽課程詳情頁面,並開始課程的學習。

需求分析

課程發佈生成課程詳情頁面的流程與課程預覽業務流程相同,如下:

  1. 用戶進入教學管理中心,進入某個課程的管理界面。
  2. 點擊課程發佈,前端請求到課程管理服務。
  3. 課程管理服務遠程調用CMS生成課程發佈頁面,CMS將課程詳情頁面發佈到服務器。
  4. 課程管理服務修改課程發佈狀態爲“已發佈”,並向前端返回發佈成功。
  5. 用戶在教學管理中心點擊“課程詳情頁面”鏈接,查看課程詳情頁面內容。

CMS課程發佈接口

響應結果實體類

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;

@Data
@NoArgsConstructor
public class CmsPostPageResult extends ResponseResult {
    private String pageUrl;
    
    public CmsPostPageResult(ResultCode resultCode, String pageUrl) {
        super(resultCode);
        this.pageUrl = pageUrl;
    }
}

CmsPageControllerApi

新增接口定義

    @ApiOperation("頁面一鍵發佈")
    CmsPostPageResult postPageQuick(CmsPage cmsPage);

CmsPageController

新增接口實現

    /**
     * cms page頁面一鍵發佈
     * 
     * @param cmsPage 頁面信息
     * @return CmsPostPageResult
     */
    @Override
    @PostMapping("postPageQuick")
    public CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage) {
        String url = cmsPageService.postPageQuick(cmsPage);
        if (StringUtils.isBlank(url)) {
            ExceptionCast.cast(CommonCode.FAIL);
        }
        return new CmsPostPageResult(CommonCode.SUCCESS, url);
    }

CmsPageService

    /**
     * 靜態頁面一鍵發佈
     *
     * @param cmsPage 頁面信息
     * @return 頁面路徑url
     */
    public String postPageQuick(CmsPage cmsPage) {
        // 保存Cms Page數據
        CmsPage save = save(cmsPage);
        isNullOrEmpty(save, CommonCode.FAIL);

        // 靜態化並保存頁面文件
        ResponseResult postPage = postPage(save.getPageId());
        if (!postPage.isSuccess()) {
            ExceptionCast.cast(CommonCode.FAIL);
        }

        // 生成url
        StringBuffer buffer = new StringBuffer();
        Optional<CmsSite> cmsSiteOptional = cmsSiteRepository.findById(save.getSiteId());
        CmsSite cmsSite = cmsSiteOptional.orElse(null);
        isNullOrEmpty(cmsSite, CommonCode.FAIL);

        assert cmsSite != null;
        String siteDomain = cmsSite.getSiteDomain();
        String siteWebPath = cmsSite.getSiteWebPath();
        String pageWebPath = cmsPage.getPageWebPath();
        String pageName = cmsPage.getPageName();

        return buffer
                .append(siteDomain)
                .append(siteWebPath)
                .append(pageWebPath)
                .append(pageName)
                .toString();
    }

課程發佈接口

Feign Client

新增遠程調用的方法定義

    /**
     * cms page一鍵發佈
     *
     * @param cmsPage CMS PAGE信息
     * @return CmsPostPageResult
     */
    @PostMapping("cms/page/postPageQuick")
    CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage);

CourseViewControllerApi

新增接口定義

    @ApiOperation("課程發佈")
    CoursePublishResult coursePublish(String id);

CourseViewController

新增接口實現

    /**
     * 課程發佈
     *
     * @param id 課程ID
     * @return CoursePublishResult
     */
    @Override
    @PostMapping("publish/{id}")
    public CoursePublishResult coursePublish(@PathVariable String id) {
        String publishUrl = courseService.publish(id);
        isNullOrEmpty(publishUrl, CommonCode.FAIL);
        return new CoursePublishResult(CommonCode.SUCCESS, publishUrl);
    }

CourseService

新增方法

    @Autowired
    private CourseBaseService courseBaseService;

	/**
     * 課程發佈
     *
     * @param id 課程ID
     * @return 課程頁面路徑url
     */
    @Transactional
    public String publish(String id) {
        // 構造cmsPage信息
        CmsPage cmsPage = buildCmsPage(id);

        // 發佈
        CmsPostPageResult cmsPostPageResult = cmsPageClient.postPageQuick(cmsPage);
        if (!cmsPostPageResult.isSuccess()) {
            ExceptionCast.cast(CourseCode.COURSE_PUBLISH_VIEWERROR);
        }

        // 更新課程狀態
        saveCoursePubState(id, "202002");

        return cmsPostPageResult.getPageUrl();
    }

    /**
     * 更新課程狀態
     * 狀態值列表:
     *      製作中:202001
     *      已發佈:202002
     *      已下線:202003
     *
     * @param courseId 課程ID
     * @param status   狀態值
     * @return CourseBase
     */
    private CourseBase saveCoursePubState(String courseId, String status) {
        CourseBase courseBase = courseBaseService.findById(courseId);
        //更新發布狀態
        courseBase.setStatus(status);
        return courseBaseRepository.save(courseBase);
    }


    /**
     * 使用課程ID構造Cms Page信息
     *
     * @param id 課程ID
     * @return CmsPage
     */
    private CmsPage buildCmsPage(String id) {
        // 查詢課程基本信息
        Optional<CourseBase> courseBaseOptional = courseBaseRepository.findById(id);
        if (!courseBaseOptional.isPresent()) {
            ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
        }
        CourseBase courseBase = courseBaseOptional.get();

        CmsPage cmsPage = new CmsPage();
        cmsPage.setSiteId(coursePublishConfig.getSiteId());
        cmsPage.setTemplateId(coursePublishConfig.getTemplateId());
        cmsPage.setPageAliase(courseBase.getName());
        cmsPage.setPageName(courseBase.getId() + ".html");
        cmsPage.setPageWebPath(coursePublishConfig.getPageWebPath());
        cmsPage.setPagePhysicalPath(coursePublishConfig.getPagePhysicalPath());
        cmsPage.setDataUrl(coursePublishConfig.getDataUrlPre() + courseBase.getId());

        return cmsPage;
    }

這裏由於預覽和發佈的時候構造Cms Page的內容是一致的,所以我這裏抽取了一下代碼。

前端

修改course_pub.vue中的publish方法,完成調用後查詢一下最新數據就OK了。

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