微服务实战笔记-学成在线-day06

页面发布实战

使用技术:

  • springboot
  • vue
  • mongodb
  • gridfs
  • freemarker
  • rabbitmq

目标:

​ 通过我们的cms程序可以实现动态发布界面到站点服务器上,不再需要程序员手动修改界面,拷贝文件。

分析流程

  1. cms系统根据模板和dataurl静态化界面
  2. 将静态化的界面存储到mongodb中
  3. 通过rabbitmq给cms-client发送消息,通知获取内容
  4. cms-client获收到消息开始从Mongodb中下载文件
  5. cms-client下载文件完成后通过rabbitmq通知cms程序处理结果

RabbitMQ 队列和交换机定义

交换机: EXCHANGE_CMS_PAGE_POST

本次通讯使用的时rabbitmq中的路由模式,所以消息的发送和反馈都可以使用这一个交换机

消息反馈队列: QUEUE_CMS_PAGE_POST

根据业务场景分析,

  • 此处生产者为1个 消费者为多个,所以反馈部分可以使用工作模式,即反馈队列的路由键和队列名称设置为一样
  • 因为同一个站点可以能部署多个服务器进行负载均衡,所以这部分可以使用发布订阅模式,每个cms-client监听不同的队列,但是同一个站点服务器下的cms-client监听的队列绑定的路由键均为站点id
cms中的RabbitmqConfig代码
/**
 * 生产者其实在生产过程中是不知道具体有哪些消费者的
 * 所以在生产者的程序中只用设置交换机就可以了,
 * 如果消费者在生产者启动之前启动,生产者中的交换机也不用定义
 */
@Configuration
public class RabbitmqConfig {

    public static final String EXCHANGE_CMS_PAGE_POST = "EXCHANGE_CMS_PAGE_POST";
    // 反馈队列
    public static final String QUEUE_CMS_PAGE_POST = "QUEUE_CMS_PAGE_POST";

    // 构建交换机
    @Bean
    public Exchange EXCHANGE_CMS_PAGE_POST() {
        // durable(true) 持久化 消息队列重启后交换机依旧保留
        return ExchangeBuilder.directExchange(EXCHANGE_CMS_PAGE_POST).durable(true).build();
    }

    // 构建队列
    @Bean
    public Queue QUEUE_CMS_PAGE_POST() {
        return QueueBuilder.durable(QUEUE_CMS_PAGE_POST).build();
    }

    @Bean
    public Binding bindSend(@Qualifier("EXCHANGE_CMS_PAGE_POST") Exchange exchange,
                            @Qualifier("QUEUE_CMS_PAGE_POST") Queue queue) {
        return BindingBuilder.bind(queue).to(exchange).with(QUEUE_CMS_PAGE_POST).noargs();
    }
}
cms-client中的RabbitmqConfig
@Configuration
public class RabbitmqConfig {

    public static final String EXCHANGE_CMS_PAGE_POST = "EXCHANGE_CMS_PAGE_POST";

    public static final String QUEUE_CMS_PAGE_POST = "QUEUE_CMS_PAGE_POST";

    @Value("${xuecheng.mq.receiveQueue}")
    private String RECEIVE_QUEUE;

    @Value("${xuecheng.mq.routingKey}")
    private String ROUTING_KET;

    // 构建交换机
    @Bean
    public Exchange EXCHANGE_CMS_PAGE_POST() {
        // durable(true) 持久化 消息队列重启后交换机依旧保留
        return ExchangeBuilder.directExchange(EXCHANGE_CMS_PAGE_POST).durable(true).build();
    }

    // 构建队列
    @Bean
    public Queue RECEIVE_QUEUE() {
        return QueueBuilder.durable(RECEIVE_QUEUE).build();
    }

    // 绑定交换机和队列
    @Bean
    public Binding bindReceive(@Qualifier("EXCHANGE_CMS_PAGE_POST") Exchange exchange,
                           @Qualifier("RECEIVE_QUEUE") Queue queue) {
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KET).noargs();
    }

    // 构建队列
    @Bean
    public Queue QUEUE_CMS_PAGE_POST() {
        return QueueBuilder.durable(QUEUE_CMS_PAGE_POST).build();
    }

    @Bean
    public Binding bindSend(@Qualifier("EXCHANGE_CMS_PAGE_POST") Exchange exchange,
                           @Qualifier("QUEUE_CMS_PAGE_POST") Queue queue) {
        return BindingBuilder.bind(queue).to(exchange).with(QUEUE_CMS_PAGE_POST).noargs();
    }

}

cms-client中的消息监听方法实现

对于cms-client而言它只干了2件事情

  • 接受消息 下载文件
  • 发送反馈消息
@Component
public class CmsPostPage {

    @Autowired
    private CmsPageService cmsPageService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = "${xuecheng.mq.receiveQueue}")
    public void postPage(String msg){
        CmsPagePostLog log = new CmsPagePostLog();
        Map map = JSON.parseObject(msg, Map.class);
        String pageId = (String) map.get("pageId");
        log.setHost(getLocalHost());
        log.setCreateTime(new Date());
        log.setPageId(pageId);
        try {
            cmsPageService.savePageToServerPath(pageId);
            log.setSuccess("1");
            log.setMsg("发布成功!");
        }catch (Exception e){
            e.printStackTrace();
            log.setSuccess("0");
            if(e instanceof CustomException){
                log.setMsg(((CustomException) e).getResultCode().toString());
            }else{
                ResultCode resultCode = ExceptionCatch.EXCEPTIONS.get(e);
                if(resultCode != null){
                    log.setMsg(resultCode.message());
                }else{
                    log.setMsg(e.getMessage());
                }
            }
        }
        // 将反馈消息写如反馈队列
        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_CMS_PAGE_POST,
                RabbitmqConfig.QUEUE_CMS_PAGE_POST, JSONObject.toJSONString(log));
    }

    // 获取当前cms客户端服务所在的ip
    private String getLocalHost(){
        String localHost = "";
        try {
            localHost = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return localHost;
    }
}
 public void savePageToServerPath(String pageId) {
        // 根据页面Id查找页面信息
        CmsPage cmsPage = findCmsPageById(pageId);
        //根据页面信息获取站点信息
        CmsSite cmsSite = findCmsSiteById(cmsPage.getSiteId());
        //根据页面信息和站点信息拼接出文件应该下载到服务器的哪个位置
        String filePath = cmsSite.getSitePhysicalPath() + cmsPage.getPagePhysicalPath() + cmsPage.getPageName();
        //根据页面信息内的静态化界面Id 从gridFS中下载内容
        InputStream is = downHtmlContent(cmsPage.getHtmlFileId());
        //检查文件是否存在,如果不存在级联创建文件和上级文件夹
        FileOutputStream fos = null;
        try {
            checkFileExits(filePath,true);
            fos = new FileOutputStream(new File(filePath));
            IOUtils.copy(is,fos);
            fos.flush();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

cms系统中页面发布的实现

public ResponseResult postPage(String pageId) {
        // 1.获取预览html
        String htmlContent = this.previewPage(pageId);
        // 2.查找cmsPage信息
        CmsPage cmsPage = cmsPageService.getById(pageId).getCmsPage();
        String newHtmlFileId = "";
        try (InputStream is = IOUtils.toInputStream(htmlContent, "utf-8")) {
            // 3..将预览的html存储到gridFS中
            ObjectId store = gridFsTemplate.store(is, cmsPage.getPageName());
            newHtmlFileId = store.toString();
        } catch (IOException e) {
            ExceptionCast.cast(CmsCode.CMS_TEMPLATE_FILE_UPLOAD_FAILD);
        }
        String htmlFileId = cmsPage.getHtmlFileId();
        if (StringUtils.isNotEmpty(htmlFileId)) {
            // 删除旧的文件信息
            gridFsTemplate.delete(Query.query(Criteria.where("_id").is(htmlFileId)));
        }
        // 4.更新page中的htmlFileId字段
        cmsPage.setHtmlFileId(newHtmlFileId);
        cmsPageRepository.save(cmsPage);
        // 5. 清空上一次的发布日志
        cmsPagePostLogRepository.deleteByPageId(pageId);
        // 6.发送消息提醒客户端程序响应
        Map map = new HashMap();
        map.put("pageId", pageId);
        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_CMS_PAGE_POST, cmsPage.getSiteId(),
                JSON.toJSONString(map));
        return new ResponseResult(CommonCode.SUCCESS);
    }

cms系统中对于反馈消息的处理

@Component
public class CmsPostPage {

    @Autowired
    private CmsPagePostLogRepository cmsPagePostLogRepository;

    // 接收到日志信息
    @RabbitListener(queues = RabbitmqConfig.QUEUE_CMS_PAGE_POST)
    public void receiveLog(String msg) {
        CmsPagePostLog log = JSON.parseObject(msg, CmsPagePostLog.class);
        cmsPagePostLogRepository.save(log);
    }
}

总结

​ 到此为止,cms系统部分告一段落,经过页面发布功能的实现,重新梳理了前面几节学到的知识点。
知识点之外的东西也很重要:

  • 代码规范问题,良好的编码风格可以让代码靓起来
  • 前后端分离优点太多,但是缺点也是有的,比如模块太多,单人开发比较繁琐
  • 使用mq进行异步通讯,可以对服务进行解耦
  • 发现了很多以前没用过的神器,比如:
    • org.apache.commons.io.IOUtils 一个操作Io流很方便的工具类
    • swagger
    • es6中的箭头函数

所以还是要多看东西,见多才能识广
完整代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值