學成在線筆記五:頁面發佈及課程管理

頁面發佈

技術方案

本項目使用MQ實現頁面發佈的技術方案如下:

技術方案說明:

  1. 平臺包括多個站點,頁面歸屬不同的站點。
  2. 發佈一個頁面應將該頁面發佈到所屬站點的服務器上。
  3. 每個站點服務部署cms client程序,並與交換機綁定,綁定時指定站點Id爲routingKey。指定站點id爲routingKey就可以實現cms client只能接收到所屬站點的頁面發佈消息。
  4. 頁面發佈程序向MQ發佈消息時指定頁面所屬站點Id爲routingKey,將該頁面發佈到它所在服務器上的cms client。

頁面發佈流程圖如下:

  1. 前端請求cms執行頁面發佈。
  2. cms執行靜態化程序生成html文件。
  3. cms將html文件存儲到GridFS中。
  4. cms向MQ發送頁面發佈消息。
  5. MQ將頁面發佈消息通知給Cms Client。
  6. Cms Client從GridFS中下載html文件。
  7. Cms Client將html保存到所在服務器指定目錄。

頁面發佈消費方

需求

功能分析:
創建Cms Client工程作爲頁面發佈消費方,將Cms Client部署在多個服務器上,它負責接收到頁面發佈的消息後從GridFS中下載文件在本地保存。

需求如下:

  1. 將cms Client部署在服務器,配置隊列名稱和站點ID。
  2. cms Client連接RabbitMQ並監聽各自的“頁面發佈隊列”。
  3. cms Client接收頁面發佈隊列的消息。
  4. 根據消息中的頁面id從mongodb數據庫下載頁面到本地。

工程搭建省略

pom.xml

<?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-service-manage-cms-client</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-framework-model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
    </dependencies>
</project>

配置文件

server:
  port: 31000
spring:
  application:
    name: xc-service-manage-cms-client
  data:
    mongodb:
      database: xc_cms
      host: 127.0.0.1
      port: 27017
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: xcEdu
    password: 123456
    virtualHost: /
xuecheng:
  mq:
    # cms客戶端監控的隊列名稱(不同的客戶端監控的隊列不能重複)
    queue: queue_cms_postpage_01
    # 此routingKey爲門戶站點ID
    routingKey: 5a751fab6abb5044e0d19ea1

啓動類

package com.xuecheng.cms_manage_client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@EntityScan(basePackages = "com.xuecheng.framework.domain.cms")
@ComponentScan(basePackages = "com.xuecheng.framework")
@ComponentScan(basePackages = "com.xuecheng.cms_manage_client")
public class ManageCmsClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ManageCmsClientApplication.class, args);
    }
}

RabbitmqConfig(配置類)

package com.xuecheng.cms_manage_client.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitmqConfig {
    //隊列bean的名稱
    public static final String QUEUE_CMS_POSTPAGE = "queue_cms_postpage";
    //交換機的名稱
    public static final String EX_ROUTING_CMS_POSTPAGE = "ex_routing_cms_postpage";
    //隊列的名稱
    @Value("${xuecheng.mq.queue}")
    public String queue_cms_postpage_name;
    //routingKey 即站點Id
    @Value("${xuecheng.mq.routingKey}")
    public String routingKey;

    /**
     * 交換機配置使用direct類型
     *
     * @return the exchange
     */
    @Bean(EX_ROUTING_CMS_POSTPAGE)
    public Exchange EXCHANGE_TOPICS_INFORM() {
        return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
    }

    //聲明隊列
    @Bean(QUEUE_CMS_POSTPAGE)
    public Queue QUEUE_CMS_POSTPAGE() {
        Queue queue = new Queue(queue_cms_postpage_name);
        return queue;
    }

    /**
     * 綁定隊列到交換機
     *
     * @param queue    the queue
     * @param exchange the exchange
     * @return the binding
     */
    @Bean
    public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_CMS_POSTPAGE) Queue queue,
                                            @Qualifier(EX_ROUTING_CMS_POSTPAGE) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(routingKey).noargs();
    }

}

Dao

數據訪問代碼參考xc-service-manage-cms中的dao,直接複製即可

PageService

package com.xuecheng.cms_manage_client.service;

import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.xuecheng.cms_manage_client.dao.CmsPageRepository;
import com.xuecheng.cms_manage_client.dao.CmsSiteRepository;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.response.CmsCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.Optional;

@Slf4j
@Service
public class PageService extends BaseService {

    @Autowired
    private GridFSBucket gridFSBucket;

    @Autowired
    private GridFsTemplate gridFsTemplate;

    @Autowired
    private CmsPageRepository cmsPageRepository;

    @Autowired
    private CmsSiteRepository cmsSiteRepository;

    /**
     * 保存指定頁面ID的html到服務器
     *
     * @param pageId 頁面ID
     */
    public void savePageToServerPath(String pageId) {
        CmsPage cmsPage = null;
        // 查詢CmsPage
        Optional<CmsPage> optionalCmsPage = cmsPageRepository.findById(pageId);
        if (!optionalCmsPage.isPresent()) {
            ExceptionCast.cast(CmsCode.CMS_EDITPAGE_NOTEXISTS);
        }
        cmsPage = optionalCmsPage.get();

        // 下載文件
        InputStream inputStream = downloadFileFromMongoDB(cmsPage.getHtmlFileId());
        if (inputStream == null) {
            log.error("[頁面發佈消費方] 下載頁面失敗, 流對象爲null, fileId = [{}]", cmsPage.getHtmlFileId());
            return ;
        }

        // 寫入文件
        FileOutputStream fileOutputStream = null;

        try {
            fileOutputStream = new FileOutputStream(new File(cmsPage.getPagePhysicalPath() + cmsPage.getPageName()));
            IOUtils.copy(inputStream, fileOutputStream);
        } catch (FileNotFoundException e) {
            log.error("[頁面發佈消費方] 文件未找到, 文件路徑 = [{}]", cmsPage.getPagePhysicalPath() + cmsPage.getPageName());
        } catch (IOException e) {
            log.error("[頁面發佈消費方] 將文件寫入服務器失敗, 文件路徑 = [{}]", cmsPage.getPagePhysicalPath() + cmsPage.getPageName());
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
                inputStream.close();
            } catch (IOException e) {
                log.error("[頁面發佈消費方] 流對象關閉失敗, 錯誤信息 = ", e);
            }

        }
    }

    /**
     * 下載文件
     *
     * @param fileId 文件ID
     * @return 文件內容
     */
    private InputStream downloadFileFromMongoDB(String fileId) {
        GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
        if (gridFSFile == null) {
            ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
        }
        //打開下載流對象
        GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
        //創建gridFsResource
        GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
        //獲取流中的數據
        try {
            return gridFsResource.getInputStream();
        } catch (IOException ignored) { }
        return null;
    }

}

**注意:**我使用的這一版本的數據庫中cms_site中沒有物理路徑這一字段,但是在cms_page中的物理字段使用的是絕對路徑,所以我這裏直接使用的cms_page中的物理路徑。

ConsumerPostPage

編寫頁面發佈消費方代碼

package com.xuecheng.cms_manage_client.mq;

import com.alibaba.fastjson.JSON;
import com.xuecheng.cms_manage_client.dao.CmsPageRepository;
import com.xuecheng.cms_manage_client.service.PageService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

@Slf4j
@Component
public class ConsumerPostPage {

    @Autowired
    private CmsPageRepository cmsPageRepository;

    @Autowired
    private PageService pageService;

    @RabbitListener(queues = {"${xuecheng.mq.queue}"})
    public void postPage(String msg) {
        // 解析消息
        Map map = JSON.parseObject(msg, Map.class);
        log.info("[頁面發佈消費方] 收到消息, 消息內容爲: [{}]", map.toString());

        // 獲取pageId
        String pageId = (String) map.get("pageId");
        if (StringUtils.isBlank(pageId)) {
            log.error("[頁面發佈消費方] 收到的pageId爲空");
            return ;
        }
        // 下載頁面到服務器
        pageService.savePageToServerPath(pageId);
    }

}

頁面發佈生產方

需求

管理員通過cms系統發佈“頁面發佈”的消費,cms系統作爲頁面發佈的生產方。

需求如下:

  1. 管理員進入管理界面點擊“頁面發佈”,前端請求cms頁面發佈接口。
  2. cms頁面發佈接口執行頁面靜態化,並將靜態化頁面存儲至GridFS中。
  3. 靜態化成功後,向消息隊列發送頁面發佈的消息。
    • 獲取頁面的信息及頁面所屬站點ID。
    • 設置消息內容爲頁面ID。(採用json格式,方便日後擴展)
    • 發送消息給ex_cms_postpage交換機,並將站點ID作爲routingKey。

RabbitMQ配置

xc-service-manage-cms工程中,進行下列配置:

  1. 引入依賴(若沒有引入amqp的依賴,則需要引入)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  2. application.yml中新增Rabbitmq相關配置

    spring:
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        # 此賬號是我自行創建的,也可以直接使用默認賬號:guest
        username: xcEdu
        password: 123456
        virtualHost: /
    
  3. RabbitMQ配置類

    package com.xuecheng.manage_cms.config;
    
    import org.springframework.amqp.core.Exchange;
    import org.springframework.amqp.core.ExchangeBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class RabbitmqConfig {
    
        //交換機的名稱
        public static final String EX_ROUTING_CMS_POSTPAGE = "ex_routing_cms_postpage";
    
        /**
         * 交換機配置使用direct類型
         *
         * @return the exchange
         */
        @Bean(EX_ROUTING_CMS_POSTPAGE)
        public Exchange EXCHANGE_TOPICS_INFORM() {
            return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
        }
    
    }
    

CmsPageControllerApi

CmsPageControllerApi中新增接口定義

    @ApiOperation("頁面發佈")
    ResponseResult postPage(String pageId);

CmsPageController

    /**
     * 頁面發佈
     *
     * @param pageId 頁面ID
     */
    @Override
    @GetMapping("post/{pageId}")
    public ResponseResult postPage(@PathVariable String pageId) {
        return cmsPageService.postPage(pageId);
    }

CmsPageService

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 頁面發佈
     *
     * @param pageId 頁面ID
     */
    @Transactional
    public CmsPageResult postPage(String pageId) {
        // 執行靜態化
        String htmlContent = genHtml(pageId);
        isNullOrEmpty(htmlContent, CmsCode.CMS_GENERATEHTML_HTMLISNULL);

        // 保存頁面
        CmsPage cmsPage = saveHtml(pageId, htmlContent);
        
        // 發送消息到MQ
        sendMessage(cmsPage.getPageId());
    }

    /**
     * 發送消息到MQ
     * 
     * @param pageId 頁面ID
     */
    private void sendMessage(String pageId) {
        // 查詢CmsPage
        CmsPage cmsPage = findByPageId(pageId);
        isNullOrEmpty(cmsPage, CmsCode.CMS_EDITPAGE_NOTEXISTS);
        
        // 構造消息
        JSONObject message = new JSONObject();
        message.put("pageId", pageId);

        // 發送消息
        rabbitTemplate.convertAndSend(RabbitmqConfig.EX_ROUTING_CMS_POSTPAGE, cmsPage.getSiteId(), message.toJSONString());
    }

    /**
     * 保存靜態頁面
     *
     * @param pageId      頁面ID
     * @param htmlContent 頁面內容
     */
    private CmsPage saveHtml(String pageId, String htmlContent) {
        // 查詢CmsPage
        CmsPage cmsPage = findByPageId(pageId);
        isNullOrEmpty(cmsPage, CmsCode.CMS_EDITPAGE_NOTEXISTS);

        // 刪除原有html文件
        String htmlFileId = cmsPage.getHtmlFileId();
        if(StringUtils.isNotEmpty(htmlFileId)){
            gridFsTemplate.delete(Query.query(Criteria.where("_id").is(htmlFileId)));
        }
        
        // 保存生成的html
        InputStream inputStream = IOUtils.toInputStream(htmlContent);
        ObjectId objectId = gridFsTemplate.store(inputStream, cmsPage.getPageName());

        // 保存文件ID到CmsPage
        cmsPage.setHtmlFileId(objectId.toString());
        return cmsPageRepository.save(cmsPage);
    }

前端

用戶操作流程:

  • 用戶進入cms頁面列表。
  • 點擊“發佈”請求服務端接口,發佈頁面。
  • 提示“發佈成功”,或發佈失敗。

代碼實現如下

  1. cms.js中新增接口定義

    /**
     * 頁面發佈
     */
    export const postPage = (pageId) => { 
        return http.requestQuickGet(apiUrl + '/cms/page/post/'+ pageId)
    }
    
  2. page_list.vue中新增發佈按鈕,與頁面預覽按鈕類似

    <el-button
        size="small"
        type="text"
        @click="postPage(scope.$index, scope.row)">發佈
    </el-button>
    
  3. page_list.vue的方法區中,新增postPage方法,用於調用頁面發佈接口

    postPage:function(index, data) {
        this.$confirm('確認發佈此頁面?', '提示', {
            confirmButtonText: '確定',
            cancelButtonText: '取消',
            type: 'warning'
        }).then(() => {
            // 頁面發佈
            cmsApi.postPage(data.pageId).then(res => {
                // 提示消息
                this.$message({
                    showClose: true,
                    message: res.message,
                    type: 'success'
                })
            })
        })
    }
    

測試

  1. 設置頁面信息

  2. 點擊發布

  3. 查看是否生成文件

    OK,文件成功發佈並保存。

課程管理

微服務工程導入(省略)

前端工程導入排坑

我這裏導入前端工程後,運行報錯!!!

錯誤信息:

Node Sass does not yet support your current environment: Windows 64-bit with Unsupported runtime (64)

???what f**k

然後我去node-sass的github看了下,好像也有蠻多人遇到類似問題的。

https://github.com/microsoft/PartsUnlimited/issues/134

解決方法:

  1. 使用npm update更新版本,需要注意從npm v2.6.1 開始,npm update只更新頂層模塊,而不更新依賴的模塊,以前版本是遞歸更新的。如果想取到老版本的效果,要使用下面的命令

    npm --depth 9999 update
    
  2. 重新build一下node-sass

    npm rebuild node-sass
    

然後就可以直接:npm run dev

成功運行。

課程管理實現

分析

  1. 課程列表查詢,該功能前端基本上沒什麼需要修改的地方了,直接寫後端的分頁查詢接口即可。
  2. 課程的新增和編輯(重點),前端已經幫我把課程中的課程分類適用等級以及學習模式的查詢調用給我寫好了,我們只需要完成響應接口的編寫。
    • 課程分類的查詢比較簡單,在課程微服務中Category表中就能夠取到數據,結果只需要封裝在CategoryNode中即可。都是已經寫好的實體類。
    • 適用等級和學習模式查詢,都在mongodb數據庫中,也就是之前的CMS相關的數據庫中,我們需要在CMS微服務中新增數據字典數據的查詢,實體類分別是:SysDictionarySysDictionaryValue
    • 編輯的時候,還需要數據的回顯,這就需要完成查詢。

數據字典查詢實現

  1. DictionaryController

    package com.xuecheng.system.controller;
    
    import com.xuecheng.framework.domain.system.SysDictionary;
    import com.xuecheng.framework.model.response.CommonCode;
    import com.xuecheng.framework.web.BaseController;
    import com.xuecheng.system.service.DictionaryService;
    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("sys/dictionary")
    public class DictionaryController extends BaseController {
    
        @Autowired
        private DictionaryService dictionaryService;
    
        /**
         * 按type獲取數據字段值
         *
         * @param type 數據類型
         * @return SysDictionary
         */
        @GetMapping("get/{type}")
        public SysDictionary getDictionaryByType(@PathVariable String type) {
            SysDictionary sysDictionary = dictionaryService.findByType(type);
            isNullOrEmpty(sysDictionary, CommonCode.PARAMS_ERROR);
            return sysDictionary;
        }
    
    }
    
  2. DictionaryService

    package com.xuecheng.system.service;
    
    import com.xuecheng.framework.domain.system.SysDictionary;
    import com.xuecheng.framework.service.BaseService;
    import com.xuecheng.manage_cms.dao.DictionaryRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Slf4j
    @Service
    public class DictionaryService extends BaseService {
    
        @Autowired
        private DictionaryRepository dictionaryRepository;
    
        /**
         * 按type獲取數據字段值
         *
         * @param type 數據類型
         * @return SysDictionary
         */
        public SysDictionary findByType(String type) {
            return dictionaryRepository.findByDType(type);
        }
    }
    
  3. DictionaryRepository

    package com.xuecheng.manage_cms.dao;
    
    import com.xuecheng.framework.domain.system.SysDictionary;
    import org.springframework.data.mongodb.repository.MongoRepository;
    
    public interface DictionaryRepository extends MongoRepository<SysDictionary, String> {
    
        SysDictionary findByDType(String type);
    
    }
    

注意:

因爲前端已經寫好了接口調用而且調用的端口號是cms微服務對應端口號,所以如果不想修改前端的調用端口的話,建議直接將數據字典查詢的代碼寫在cms微服務中。

課程管理實現

  1. CourseController

    package com.xuecheng.manage_course.controller;
    
    import com.xuecheng.api.course.CourseBaseControllerApi;
    import com.xuecheng.framework.domain.course.CourseBase;
    import com.xuecheng.framework.domain.course.request.CourseListRequest;
    import com.xuecheng.framework.domain.course.response.AddCourseResult;
    import com.xuecheng.framework.domain.course.response.CourseBaseResult;
    import com.xuecheng.framework.domain.course.response.CourseCode;
    import com.xuecheng.framework.model.response.CommonCode;
    import com.xuecheng.framework.model.response.QueryResponseResult;
    import com.xuecheng.framework.web.BaseController;
    import com.xuecheng.manage_course.service.CourseBaseService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("course/coursebase")
    public class CourseBaseController extends BaseController implements CourseBaseControllerApi {
    
        @Autowired
        private CourseBaseService courseBaseService;
    
    
        @Override
        @GetMapping("list/{page}/{size}")
        public QueryResponseResult findList(@PathVariable int page,
                                            @PathVariable int size,
                                            CourseListRequest queryPageRequest) {
            return courseBaseService.findList(page, size, queryPageRequest);
        }
    
        /**
         * 新增課程基本信息
         *
         * @param courseBase 課程基本信息
         */
        @Override
        @PostMapping("add")
        public AddCourseResult addCourse(@RequestBody CourseBase courseBase) {
            isNullOrEmpty(courseBase, CommonCode.PARAMS_ERROR);
            CourseBase add = courseBaseService.add(courseBase);
            isNullOrEmpty(add, CommonCode.SERVER_ERROR);
            return new AddCourseResult(CommonCode.SUCCESS, add.getId());
        }
    
        /**
         * 編輯課程基本信息
         *
         * @param courseBase 課程基本信息
         */
        @Override
        @PutMapping("edit")
        public AddCourseResult editCourse(@RequestBody CourseBase courseBase) {
            isNullOrEmpty(courseBase, CommonCode.PARAMS_ERROR);
            courseBaseService.edit(courseBase);
            return null;
        }
    
        /**
         * 按課程ID查詢課程基本信息
         *
         * @param courseId
         */
        @Override
        @GetMapping("{courseId}")
        public CourseBaseResult findById(@PathVariable String courseId) {
            isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
            CourseBase courseBase = courseBaseService.findById(courseId);
            isNullOrEmpty(courseBase, CourseCode.COURSE_NOT_EXIST);
            return CourseBaseResult.SUCCESS(courseBase);
        }
    }
    
  2. CourseBaseService

    package com.xuecheng.manage_course.service;
    
    import com.xuecheng.framework.domain.course.CourseBase;
    import com.xuecheng.framework.domain.course.request.CourseListRequest;
    import com.xuecheng.framework.domain.course.response.CourseCode;
    import com.xuecheng.framework.model.response.CommonCode;
    import com.xuecheng.framework.model.response.QueryResponseResult;
    import com.xuecheng.framework.model.response.QueryResult;
    import com.xuecheng.framework.service.BaseService;
    import com.xuecheng.manage_course.dao.CourseBaseRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Example;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.stereotype.Service;
    
    @Slf4j
    @Service
    public class CourseBaseService extends BaseService {
    
        @Autowired
        private CourseBaseRepository courseBaseRepository;
    
        /**
         * 新增課程基本信息
         *
         * @param courseBase 課程基本信息
         * @return CourseBase
         */
        public CourseBase add(CourseBase courseBase) {
            // 新增
            return courseBaseRepository.save(courseBase);
        }
    
        /**
         * 編輯課程基本信息
         *
         * @param courseBase 課程基本信息
         * @return CourseBase
         */
        public CourseBase edit(CourseBase courseBase) {
            CourseBase _courseBase = findById(courseBase.getId());
            isNullOrEmpty(_courseBase, CourseCode.COURSE_NOT_EXIST);
            // 更新
            _courseBase.setName(courseBase.getName());
            _courseBase.setMt(courseBase.getMt());
            _courseBase.setSt(courseBase.getSt());
            _courseBase.setGrade(courseBase.getGrade());
            _courseBase.setStudymodel(courseBase.getStudymodel());
            _courseBase.setDescription(courseBase.getDescription());
    
            return courseBaseRepository.save(_courseBase);
        }
    
        /**
         * 按ID查詢課程基本信息
         *
         * @param courseBaseId 課程ID
         * @return CourseBase
         */
        public CourseBase findById(String courseBaseId) {
            return courseBaseRepository.findById(courseBaseId).orElse(null);
        }
    
        /**
         * 分頁查詢課程基本信息
         *
         * @param page             當前頁碼
         * @param size             每頁記錄數
         * @param queryPageRequest 查詢條件
         * @return QueryResponseResult
         */
        public QueryResponseResult findList(int page, int size, CourseListRequest queryPageRequest) {
            if (page < 0) {
                page = 1;
            }
    
            // 頁碼下標從0開始
            page = page - 1;
            CourseBase params = new CourseBase();
            if (StringUtils.isNotBlank(queryPageRequest.getCompanyId())) {
                params.setCompanyId(queryPageRequest.getCompanyId());
            }
            Example<CourseBase> courseBaseExample = Example.of(params);
    
            // 分頁查詢
            Page<CourseBase> pageResult = courseBaseRepository.findAll(courseBaseExample, PageRequest.of(page, size));
    
            QueryResult<CourseBase> queryResult = new QueryResult<>();
            queryResult.setTotal(pageResult.getTotalElements());
            queryResult.setList(pageResult.getContent());
    
            return new QueryResponseResult(CommonCode.SUCCESS, queryResult);
        }
    }
    
  3. CourseBaseRepository

    package com.xuecheng.manage_course.dao;
    
    import com.xuecheng.framework.domain.course.CourseBase;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    /**
     * Created by Administrator.
     */
    public interface CourseBaseRepository extends JpaRepository<CourseBase,String> {
    }
    

分類列表查詢

  1. CategoryController

    package com.xuecheng.manage_course.controller;
    
    import com.xuecheng.api.course.CategoryControllerApi;
    import com.xuecheng.framework.domain.course.ext.CategoryNode;
    import com.xuecheng.framework.web.BaseController;
    import com.xuecheng.manage_course.service.CategoryService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("category")
    public class CategoryController extends BaseController implements CategoryControllerApi {
    
        @Autowired
        private CategoryService categoryService;
    
        /**
         * 查詢分類列表
         *
         * @return CategoryNode
         */
        @Override
        @GetMapping("list")
        public CategoryNode findCategoryList() {
            return categoryService.findCategoryList();
        }
    }
    
  2. CategoryService

    package com.xuecheng.manage_course.service;
    
    import com.xuecheng.framework.domain.course.Category;
    import com.xuecheng.framework.domain.course.ext.CategoryNode;
    import com.xuecheng.manage_course.dao.CategoryRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Slf4j
    @Service
    public class CategoryService {
    
        @Autowired
        private CategoryRepository categoryRepository;
    
        /**
         * 查詢分類列表
         *
         * @return CategoryNode
         */
        public CategoryNode findCategoryList() {
            CategoryNode sourceCategoryNode = buildCategoryNode(categoryRepository.findByParentid("0").get(0));
    
            // 查詢第二級分類列表
            List<Category> secondaryCategoryList = categoryRepository.findByParentid(sourceCategoryNode.getId());
            List<CategoryNode> secondaryCategoryNodeList = buildCategoryNodeList(secondaryCategoryList);
    
            sourceCategoryNode.setChildren(secondaryCategoryNodeList);
    
            return sourceCategoryNode;
        }
    
        /**
         * 構造分類節點列表
         *
         * @param categoryList
         * @return List<CategoryNode>
         */
        private List<CategoryNode> buildCategoryNodeList(List<Category> categoryList) {
            return categoryList.stream().map(secondaryCategory -> {
                CategoryNode categoryNode = buildCategoryNode(secondaryCategory);
                // 查詢子節點
                List<Category> children = categoryRepository.findByParentid(categoryNode.getId());
                List<CategoryNode> categoryNodes = buildCategoryNodeList(children);
                if (!categoryNodes.isEmpty()) {
                    categoryNode.setChildren(categoryNodes);
                }
    
                return categoryNode;
            }).collect(Collectors.toList());
        }
    
    
        /**
         * 構造分類節點數據
         *
         * @param category 分類
         * @return CategoryNode
         */
        private CategoryNode buildCategoryNode(Category category) {
            CategoryNode categoryNode = new CategoryNode();
            categoryNode.setId(category.getId());
            categoryNode.setIsleaf(category.getIsleaf());
            categoryNode.setLabel(category.getLabel());
            categoryNode.setName(category.getName());
    
            return categoryNode;
        }
    }
    
  3. CategoryRepository

    package com.xuecheng.manage_course.dao;
    
    import com.xuecheng.framework.domain.course.Category;
    import org.springframework.data.repository.CrudRepository;
    
    import java.util.List;
    
    public interface CategoryRepository extends CrudRepository<Category, String> {
    
        List<Category> findByParentid(String parentId);
    }
    

前端

前端基本沒什麼需要改動的地方,因爲我在課程管理中,使用了自己新建的返回對象CourseBaseResult,所以前端就需要改一點東西,費力不討好啊,建議各位直接使用CourseBase作爲查詢的返回值!

還有就是前端使用element-ui版本,屬實有點太低了,很多效果是出不來的,所以我進行了版本更換,我更換到2.10.1之後,課程的新增和編輯頁的頭部導航菜單出現了樣式錯誤,我修改了一下

修改之前,是用router-link標籤包裹的el-menu-item標籤。

基本就這些了。

課程計劃

課程計劃查詢

CoursePlanController

package com.xuecheng.manage_course.controller;

import com.xuecheng.api.course.CoursePlanControllerApi;
import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import com.xuecheng.manage_course.service.CoursePlanService;
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("course/teachplan")
public class CoursePlanController implements CoursePlanControllerApi {

    @Autowired
    private CoursePlanService coursePlanService;

    /**
     * 查詢指定課程的課程ID
     *
     * @param courseId 課程ID
     * @return TeachPlanNode
     */
    @Override
    @GetMapping("list/{courseId}")
    public TeachplanNode findList(@PathVariable String courseId) {
        return coursePlanService.findList(courseId);
    }
}

CoursePlanService

package com.xuecheng.manage_course.service;

import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import com.xuecheng.framework.service.BaseService;
import com.xuecheng.manage_course.dao.CoursePlanMapper;
import com.xuecheng.manage_course.dao.CoursePlanRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 課程計劃Service
 */
@Slf4j
@Service
public class CoursePlanService extends BaseService {

    @Autowired
    private CoursePlanRepository coursePlanRepository;

    @Autowired
    private CoursePlanMapper coursePlanMapper;


    /**
     * 查詢指定課程的課程ID
     *
     * @param courseId 課程ID
     * @return TeachPlanNode
     */
    public TeachplanNode findList(String courseId) {
        return coursePlanMapper.findList(courseId);
    }
}

CoursePlanMapper

package com.xuecheng.manage_course.dao;

import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CoursePlanMapper {

    /**
     * 查詢課程計劃列表
     *
     * param courseId 課程ID
     * @return TeachplanNode
     */
    TeachplanNode findList(String courseId);

}

TeachplanMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xuecheng.manage_course.dao.CoursePlanMapper">

    <resultMap id="teachplanMap" type="com.xuecheng.framework.domain.course.ext.TeachplanNode">
        <id column="one_id" property="id" />
        <result column="one_name" property="pname" />
        <collection property="children" ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
            <id column="two_id" property="id" />
            <result column="two_name" property="pname" />
            <collection property="children" ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
                <id column="three_id" property="id" />
                <result column="three_name" property="pname" />
            </collection>
        </collection>
    </resultMap>


    <select id="findList" parameterType="java.lang.String"
            resultMap="teachplanMap">
        SELECT
            a.id one_id,
            a.pname one_name,
            b.id two_id,
            b.pname two_name,
            c.id three_id,
            c.pname three_name
        FROM
            teachplan a LEFT JOIN teachplan b
            ON a.id = b.parentid
            LEFT JOIN teachplan c
            ON b.id = c.parentid
        WHERE a.parentid = '0'
            <if test="_parameter!=null and _parameter!=''">
                and a.courseid=#{courseId}
            </if>

        ORDER BY a.orderby,
            b.orderby,
            c.orderby
    </select>

</mapper>

測試

排坑

調用mapper方法的時候報錯:

Invalid bound statement (not found): com.xuecheng.manage_course.dao.CoursePlanMapper.findList

原因是mybatis沒有正確的加載到mapper.xml文件,在application.yml中加入下方配置

mybatis:
  mapper-locations: classpath:com/xuecheng/manage_course/dao/*Mapper.xml

添加課程計劃

CoursePlanController

    /**
     * 新增課程計劃
     *
     * @param teachplan 課程計劃
     * @return ResponseResult
     */
    @Override
    @PostMapping("add")
    public ResponseResult add(@RequestBody Teachplan teachplan) {
        if (teachplan == null || StringUtils.isBlank(teachplan.getCourseid())) {
            return new ResponseResult(CommonCode.PARAMS_ERROR);
        }
        // 新增
        Teachplan add = coursePlanService.add(teachplan);
        isNullOrEmpty(add, CourseCode.COURSE_PLAN_ADD_ERROR);
        return ResponseResult.SUCCESS();
    }

CoursePlanService

    /**
     * 新增課程計劃
     *
     * @param teachplan 課程計劃
     * @return Teachplan
     */
    public Teachplan add(Teachplan teachplan) {
        Teachplan root = getTeachplanRoot(teachplan.getCourseid());

        // 設置父ID
        if (StringUtils.isBlank(teachplan.getParentid())) {
            teachplan.setParentid(root.getId());
        }
        // 設置節點級別
        if (root.getId().equals(teachplan.getParentid())) {// 第二級別
            teachplan.setGrade("2");
        } else {// 第三級別
            teachplan.setGrade("3");
        }

        return coursePlanRepository.save(teachplan);
    }

    /**
     * 查詢指定課程的課程計劃根節點
     *
     * @param courseId 課程計劃
     * @return Teachplan
     */
    public Teachplan getTeachplanRoot(String courseId) {
        // 查詢課程基本信息
        Optional<CourseBase> courseBase = courseBaseRepository.findById(courseId);
        if (!courseBase.isPresent()) {
            ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
        }

        // 查詢根節點下的所有二級節點
        List<Teachplan> secondaryTeachplanList = coursePlanRepository.findByCourseidAndParentid(courseBase.get().getId(), "0");
        if (secondaryTeachplanList == null || secondaryTeachplanList.isEmpty()) {// 若不存在根節點
            // 創建根節點
            Teachplan root = new Teachplan();
            root.setCourseid(courseBase.get().getId());
            root.setPname(courseBase.get().getName());
            root.setParentid("0");// 根節點
            root.setGrade("1");// 1級菜單
            root.setStatus("0");// 未發佈

            coursePlanRepository.save(root);
            return root;
        }

        return secondaryTeachplanList.get(0);
    }

CoursePlanRepository

package com.xuecheng.manage_course.dao;

import com.xuecheng.framework.domain.course.Teachplan;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface CoursePlanRepository extends CrudRepository<Teachplan, String> {

    /**
     * 查詢課程指定父ID的所有節點列表
     *
     * @param courseId 課程ID
     * @param parentId 父ID
     * @return List<Teachplan>
     */
    List<Teachplan> findByCourseidAndParentid(String courseId, String parentId);
}

前端

修改保存成功後,關閉表單輸入窗口,修改course_pan.vue中的addTeachplan方法

addTeachplan(){
    //校驗表單
    this.$refs.teachplanForm.validate((valid) => {
        if (valid) {
            //調用api方法
            //將課程id設置到teachplanActive
            this.teachplanActive.courseid = this.courseid
            courseApi.addTeachplan(this.teachplanActive).then(res=>{
                if(res.success){
                    this.$message({
                        showClose: true,
                        message: res.message,
                        type: 'success'
                    })
                    //關閉課程計劃信息錄入窗口
                    this.teachplayFormVisible = false
                    //刷新樹
                    this.findTeachplan()
                }else{
                    this.$message.error(res.message)
                }
            })
        }
    })
}

編輯/刪除課程計劃

後端

  1. CoursePlanController

        /**
         * 編輯課程計劃
         *
         * @param teachplan 課程計劃
         * @return ResponseResult
         */
        @Override
        @PutMapping("edit")
        public ResponseResult edit(@RequestBody Teachplan teachplan) {
            if (teachplan == null
                    || StringUtils.isBlank(teachplan.getCourseid())
                    || StringUtils.isBlank(teachplan.getId())) {
                return new ResponseResult(CommonCode.PARAMS_ERROR);
            }
            // 編輯
            Teachplan edit = coursePlanService.edit(teachplan);
            isNullOrEmpty(edit, CourseCode.COURSE_PLAN_ADD_ERROR);
            return ResponseResult.SUCCESS();
        }
    
        /**
         * 刪除課程計劃
         *
         * @param teachplanId 課程計劃ID
         * @return ResponseResult
         */
        @Override
        @DeleteMapping("{teachplanId}")
        public ResponseResult delete(@PathVariable String teachplanId) {
            coursePlanService.deleteById(teachplanId);
            return ResponseResult.SUCCESS();
        }
    
  2. CoursePlanService

        /**
         * 編輯課程計劃
         *
         * @param teachplan 課程計劃
         * @return Teachplan
         */
        public Teachplan edit(Teachplan teachplan) {
            Optional<Teachplan> optionalTeachplan = coursePlanRepository.findById(teachplan.getId());
            if (!optionalTeachplan.isPresent()) {
                ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
            }
            Teachplan _teachplan = optionalTeachplan.get();
            _teachplan.setGrade(teachplan.getGrade());
            _teachplan.setPname(teachplan.getPname());
            _teachplan.setCourseid(teachplan.getCourseid());
            _teachplan.setDescription(teachplan.getDescription());
            _teachplan.setOrderby(teachplan.getOrderby());
            _teachplan.setStatus(teachplan.getStatus());
            _teachplan.setTimelength(teachplan.getTimelength());
    
            Teachplan root = getTeachplanRoot(teachplan.getCourseid());
    
            // 設置父ID
            if (StringUtils.isBlank(teachplan.getParentid())) {
                _teachplan.setParentid(root.getId());
            }
    
            return coursePlanRepository.save(_teachplan);
        }
    
    	/**
         * 查詢指定ID的課程計劃
         *
         * @param teachplanId 課程計劃ID
         * @return Teachplan
         */
        public Teachplan findById(String teachplanId) {
            Optional<Teachplan> optionalTeachplan = coursePlanRepository.findById(teachplanId);
            if (!optionalTeachplan.isPresent()) {
                ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
            }
            return optionalTeachplan.get();
        }
    
        /**
         * 刪除指定ID的課程計劃
         *
         * @param teachplanId 課程計劃ID
         */
        public void deleteById(String teachplanId) {
            coursePlanRepository.deleteById(teachplanId);
        }
    

前端

  1. 新增API定義,修改course.js

    /*查詢課程計劃*/
    export const findTeachplanById = teachplanById => {
      return http.requestQuickGet(apiUrl+'/course/teachplan/'+teachplanById)
    }
    /*編輯課程計劃*/
    export const editTeachplan = teachplah => {
      return http.requestPut(apiUrl+'/course/teachplan/edit',teachplah)
    }
    /*刪除課程計劃*/
    export const deleteTeachplan = teachplahId => {
      return http.requestDelete(apiUrl+'/course/teachplan/' + teachplahId)
    }
    
  2. 頁面修改,修改內容主要爲

    • 點擊修改按鈕,彈出修改框並回顯數據,提交API調用
    • 點擊刪除按鈕,彈出確認框,確認後API調用
    	edit(data){
            // 查詢
            courseApi.findTeachplanById(data.id).then(res => {
              this.teachplanActive = res
              if (res.grade === '2') {
                this.teachplanActive.parentid = ''
              }
              this.teachplayFormVisible = true
            })
            //校驗表單
            this.$refs.teachplanForm.validate((valid) => {
                if (valid) {
                    //調用api方法
                  //將課程id設置到teachplanActive
                  this.teachplanActive.courseid = this.courseid
                  courseApi.editTeachplan(this.teachplanActive).then(res=>{
                    if(res.success){
                        this.$message({
                            showClose: true,
                            message: res.message,
                            type: 'success'
                        })
                        this.teachplayFormVisible = false
                        //刷新樹
                        this.findTeachplan()
                    }else{
                      this.$message.error(res.message)
                    }
    
                  })
                }
            })
          },
          remove(node, data) {
            // 執行刪除
            this.$confirm('確認刪除該課程計劃?', '提示', {
                  confirmButtonText: '確定',
                  cancelButtonText: '取消',
                  type: 'warning'
              }).then(() => {
                  // 刪除
                  courseApi.deleteTeachplan(data.id).then(res => {
                    // 提示消息
                    this.$message({
                      showClose: true,
                      message: res.message,
                      type: 'success'
                    })
    				//刷新樹
                    this.findTeachplan()
                  })
                  
              })
    
          }
    

    注意:

    由於新增和編輯用的同一個表單按鈕,所以要做判斷,若ID爲空時調用新增,否則調用編輯

    // 將按鈕調用改爲save方法
    <el-button type="primary" v-on:click="save">提交</el-button>
    
    save() {
        if (this.teachplanActive.id) {
            this.edit(this.teachplanActive)
        } else {
            this.addTeachplan()
        }
    }
    

因爲我沒看課程目錄,後一天的內容居然是課程管理的實戰。。我在這一筆記裏面就自己把課程管理的實戰給做了,但是缺少課程營銷相關的內容,我就不做了,懶得做(懶人就是我😜😜😜)

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