专栏首页IT进修之路WebFlux拨云见日之前端整合,悟了吗? 顶

WebFlux拨云见日之前端整合,悟了吗? 顶

前言

        从spring5中加入webflux的消息现世已经有一段时间了,也发现了一些朋友的公司在使用webfux,但是大多数都是用作为服务器之间的一些通讯、路由控制来使用,然而真正的把他当着一个web服务来提供给前端API的并木有。早年我就接触了bigpipe的概率了,但是在java的领域并不怎么活,单流的数据响应是否好像类似呢?于是我就研究了webflux和前端整合分享一下大家共同探讨...

WebFlux

        WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好.

实战效果展示

第一处:

        是对推送SSE API允许网页获得来自服务器的更新(HTML5),用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是text/event-stream,而且是浏览器中的JavaScript API 能解析格式输出。SSE 支持短轮询、长轮询和HTTP 流,而且能在断开连接时自动确定何时重新连接。

java代码:

@RestController
@RequestMapping("/sse")
public class SseController {

	private int count_down_sec=3*60*60;
	
	@GetMapping(value="/countDown",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<ServerSentEvent<Object>> countDown() {
		
		return Flux.interval(Duration.ofSeconds(1))
			.map(seq -> Tuples.of(seq, getCountDownSec()))
			.map(data -> ServerSentEvent.<Object>builder()
					.event("countDown")
					.id(Long.toString(data.getT1()))
					.data(data.getT2().toString())
					.build());
	}
	
	private String getCountDownSec() {
		if (count_down_sec>0) {
			int h = count_down_sec/(60*60);
			int m = (count_down_sec%(60*60))/60;
			int s = (count_down_sec%(60*60))%60;
			count_down_sec--;
			return "活动倒计时:"+h+" 小时 "+m+" 分钟 "+s+" 秒";
		}
		return "活动倒计时:0 小时 0 分钟 0 秒";
	}
}

HTML代码:

//js代码
<script>
	//记录加载次数
	var time=1;
	if (typeof (EventSource) !== "undefined") {
		var source = new EventSource("/sse/countDown");
		console.log(source);
		source.addEventListener("countDown", function(e) {
			document.getElementById("result").innerHTML = e.data;
		}, false);//使用false表示在冒泡阶段处理事件,而不是捕获阶段。
	} else {
		document.getElementById("result").innerHTML = "抱歉,你的浏览器不支持 server-sent 事件...";
	}
</script>
//html代码
<div id="result"></div><br/>

webflux中的sse并不是什么新的东西,在之前都有出现过,就不多介绍了。

第二三处就是对webflux中的Flux接口信息

java代码(主要是针对Mongo)

Entity:

@Data
@EqualsAndHashCode(callSuper=false)
public class Commodity extends ParentEntity implements Serializable{

	private static final long serialVersionUID = 6659341305838439447L;
	
	/**
	 *	店铺ID 
	 */
	private Long shopId;
	
	/**
	 *	商品名
	 */
	@NotNull(message = "商品名不能为空")
	@Pattern(regexp = "^.{0,50}$",message = "商品名必须是小于50位字符串")
	private String name;
	
	/**
	 *	商品详情
	 */
	@Pattern(regexp = "^.{0,500}$",message = "商品详情必须是小于500位字符串")
	private String details;
	
	/**
	 *	商品图片地址
	 */
	private String imageUrl;
	
	/**
	 *	商品图片地址
	 */
	private Integer type;
	
	/**
	 *	商品单价
	 */
	@Min(value = 0)
	private BigDecimal price;
	
	/**
	 *	商品库存
	 */
	@Min(value = 0)
	private Integer pcs;
}

Entity的父类:

public class ParentEntity {
	/**
	 *	商品ID 
	 */
	public Long id;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}
	
}

DAO:

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import com.flying.cattle.wf.entity.Commodity;

public interface CommodityRepository extends ReactiveMongoRepository<Commodity, Long>{

}

SERVICE:

import com.flying.cattle.wf.aid.IService;
import com.flying.cattle.wf.entity.Commodity;

public interface CommodityService extends IService<Commodity>{
	
}

SERVICE的父类:

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * <p>
 * 顶级 Service
 * </p>
 *
 * @author BianPeng
 * @since 2019/6/17
 */
public interface IService<T> {
	
	/**
     * <p>
     * 插入一条记录(选择字段,策略插入)
     * </p>
     *
     * @param entity 实体对象
     */
	Mono<T> insert(T entity);
    
    /**
     * <p>
     * 根据 ID 删除
     * </p>
     * @param id 主键ID
     */
	Mono<Boolean> deleteById(Long id);
    
    /**
     * <p>
     * 根据 ID 删除
     * </p>
     * @param id 主键ID
     */
	Mono<Boolean> delete(T entity);
    
    /**
     * <p>
     * 根据 ID 选择修改
     * </p>
     * @param entity 实体对象
     */
	Mono<T> updateById(T entity);
    
    /**
     * <p>
     * 根据 ID 查询
     * </p>
     * @param id 主键ID
     */
	Mono<T> findById(Long id);
    
	/**
     * <p>
     * 查询所有
     * </p>
     */
	Flux<T> findAll();
}

SERVICE-IMPL

import org.springframework.stereotype.Service;
import com.flying.cattle.wf.aid.ServiceImpl;
import com.flying.cattle.wf.dao.CommodityRepository;
import com.flying.cattle.wf.entity.Commodity;
import com.flying.cattle.wf.service.CommodityService;

@Service
public class CommodityServiceImpl extends ServiceImpl<CommodityRepository, Commodity> implements CommodityService {

}

SERVICE-IMPL 父类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * <p>
 * IService 实现类( 泛型:M 是 mapper 对象,T 是实体 , PK 是主键泛型 )
 * </p>
 *
 * @author BianPeng
 * @since 2019/6/17
 */
public class ServiceImpl<M extends ReactiveMongoRepository<T,Long>, T> implements IService<T> {

	@Autowired
    protected M baseDao;
    
	@Override
	public Mono<T> insert(T entity) {
		return baseDao.save(entity);
	}

	@Override
	public Mono<Boolean> deleteById(Long id) {
		// TODO Auto-generated method stub
		baseDao.deleteById(id);
		return Mono.create(entityMonoSink -> entityMonoSink.success());
	}

	@Override
	public Mono<Boolean> delete(T entity) {
		// TODO Auto-generated method stub
		baseDao.delete(entity);
		return Mono.create(entityMonoSink -> entityMonoSink.success());
	}

	@Override
	public Mono<T> updateById(T entity) {
		// TODO Auto-generated method stub
		return baseDao.save(entity);
	}

	@Override
	public Mono<T> findById(Long id) {
		// TODO Auto-generated method stub
		return baseDao.findById(id);
	}

	@Override
	public Flux<T> findAll() {
		// TODO Auto-generated method stub
		return baseDao.findAll();
	}

}

CONTROLLER:

import java.math.BigDecimal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.flying.cattle.wf.aid.AbstractController;
import com.flying.cattle.wf.entity.Commodity;
import com.flying.cattle.wf.service.CommodityService;
import com.flying.cattle.wf.utils.ValidationResult;
import com.flying.cattle.wf.utils.ValidationUtils;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/commodity")
public class CommodityController extends AbstractController<CommodityService, Commodity> {

	Logger logger = LoggerFactory.getLogger(this.getClass());

	@GetMapping("/add")
	public Mono<Commodity> add() throws Exception {
		Commodity obj = new Commodity();
		Long id=super.getAutoIncrementId(obj);
		obj.setId(id);
		obj.setShopId(1l);
		obj.setName("第" + id + "个商品");
		obj.setDetails("流式商品展示");
		obj.setImageUrl("/aa/aa.png");
		obj.setPcs(1);
		obj.setPrice(new BigDecimal(998));
		obj.setType(1);

		ValidationResult vr = ValidationUtils.validateEntity(obj);
		if (!vr.isHasErrors()) {
			return baseService.insert(obj);
		} else {
			throw new Exception(vr.getFirstErrors());
		}
	}
}

CONTROLLER父类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import com.flying.cattle.wf.entity.ParentEntity;
import com.flying.cattle.wf.service.impl.RedisGenerateId;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**   
 * <p>自动生成工具:mybatis-dsc-generator</p>
 * 
 * <p>说明: 资金归集API接口层</P>
 * @version: V1.0
 * @author: BianPeng
 *
 */
public class AbstractController<S extends IService<T>,T extends ParentEntity>{
	
	@Autowired
	private RedisGenerateId redisGenerateId;

	@Autowired
    protected S baseService;
	
	@GetMapping("/getId")
	public Long getAutoIncrementId(T entity){
		return redisGenerateId.generate(entity.getClass().getName());
	}
	
	@PostMapping("/save")
	public Mono<T> save(T entity) {
		entity.setId(getAutoIncrementId(entity));
		return baseService.insert(entity);
	}

	@PostMapping("/deleteById")
	public Mono<Boolean> deleteById(Long id) {
		// TODO Auto-generated method stub
		return baseService.deleteById(id);
	}

	@PostMapping("/delete")
	public Mono<Boolean> delete(T entity) {
		// TODO Auto-generated method stub
		return baseService.delete(entity);
	}

	@PostMapping("/updateById")
	public Mono<T> updateById(T entity) {
		// TODO Auto-generated method stub
		return baseService.updateById(entity);
	}

	@GetMapping("/getById/{id}")
	public Mono<T> findById(@PathVariable("id") Long id) {
		// TODO Auto-generated method stub
		return baseService.findById(id);
	}

	@GetMapping(value="/findAll",produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
	public Flux<T> findAll() {
		// TODO Auto-generated method stub
		return baseService.findAll();
	}
}

HTML代码:

<html>
<head>
<meta charset="UTF-8">
<title>商品展示</title>
<script src="/js/jquery-2.1.1.min.js"></script>
<script>
	//记录加载次数
	var time=1;
	if (typeof (EventSource) !== "undefined") {
		var source = new EventSource("/sse/countDown");
		console.log(source);
		source.addEventListener("countDown", function(e) {
			document.getElementById("result").innerHTML = e.data;
		}, false);//使用false表示在冒泡阶段处理事件,而不是捕获阶段。
	} else {
		document.getElementById("result").innerHTML = "抱歉,你的浏览器不支持 server-sent 事件...";
	}
	/************************以上是SSE的JS************************/
	$(function() {
		var xhr = new XMLHttpRequest();
		xhr.open('GET', '/commodity/findAll');
		xhr.send(null);//发送请求
		xhr.onreadystatechange = function() {
			//2是空响应,3是响应一部分,4是响应完成
			if (xhr.readyState > 2) {
				//这儿可以使用response与responseText,因为我接口返回的就是String数据,所以选择responseText
				var newData = xhr.response.substr(xhr.seenBytes);
				newData = newData.replace(/\n/g, "#");
				newData = newData.substring(0, newData.length - 1);
				var data = newData.split("#");
				//显示加载次数,和大小
				$("#dataModule").append("第"+time+"次数据响应"+data.length+"条          ");
				
				$("#note").append("<div style='clear: both;'>第"+time+"次数据响应"+data.length+"条</div><div id='note"+time+"' style='width: 100%;'></div>");
				var html="";
				for (var i = 0; i < data.length; i++) {
					 var obj = JSON.parse(data[i])
					 html=html + "<div style='margin-left: 10px;margin-top: 10px; width: 80px;height: 80px;background-color: gray;float: left;'>"+obj.name+"</div>";
				}
				$("#note"+time).html(html);
				time++;
				xhr.seenBytes = xhr.response.length;
			}
		}
	})
</script>
</head>
<body>
	<div id="result"></div><br/>
	<div id="dataModule"></div><br/>
	<div id="note" style="width: 100%;" >
	</div>
</body>
</html>

前端的代码就这个样子,展示出来的数据就是开始那张图,数据反应多少条前端马上就展示数据,不需要等待接口反应完成并接收完所以数据才展示数据。现在webflux的使用其实还并不是太普及,很多东西都得需要我自己去挖掘,有兴趣的朋友可以加群:340697945大家一起交流,相互学习。

源码地址:https://gitee.com/flying-cattle/infrastructure/tree/master/webFluxTest

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • mysql8+mybatis-plus3.1自动生成带lombok和swagger和增删改查接口

    完美集成lombok,swagger的代码生成工具,让你不再为繁琐的注释和简单的接口实现而烦恼:entity集成,格式校验,swagger; dao自动加@ m...

    边鹏 [进阶者]
  • SpringBoot 自动代码生成三层

    虽然mybatis已经有了代码生成,但是对于SpringBoot 项目来说生成的还是需要改动,而且也没得逻辑层,和控制层。但是这些东西是逃避不了,所以我就针对单...

    边鹏 [进阶者]
  • Spring Boot 优雅的配置拦截器方式

    其实spring boot拦截器的配置方式和springMVC差不多,只有一些小的改变需要注意下就ok了。下面主要介绍两种常用的拦截器:

    边鹏 [进阶者]
  • SpringWebFlux(下篇)

    这篇文章承接上篇文章,没有看过的小伙伴可以先去看上篇SpringFlux入门(上篇),涉及到的两个重要的对象,Flux和Mono来看下官网是怎么介绍的

    tanoak
  • MyBatis多表查询

    MyBatis的多表查询只需要DAO接口和sql语句即可,主要的是mapper里的返回字段要正确

    用户3112896
  • JavaWeb(九)AJAX

    Ajax ajax:AJAX 是与服务器交换数据的艺术,它在不重载全部页面的情况下,实现了对部分网页的更新 AJAX:Asynchronous JavaScri...

    二十三年蝉
  • (二)Java高并发秒杀API之Service层

    首先在编写Service层代码前,我们应该首先要知道这一层到底时干什么的,这里摘取来自ITEYE一位博主的原话

    Java团长
  • spring的简易实现(一)

    在第一部分我们实现读取xml的配置,然后实例化xml中的bean 首先定义一个xml和相关的class类

    Theone67
  • springmvc-mybatis

    本文基于原文http://doc.okbase.net/fengshizty/archive/126397.html配置环境。 首先说说几个问题 1.关于Myb...

    DuncanZhou
  • CentOS7上安装NextCloud个人网盘

    个人网盘(私有云盘),常用的开源框架包括ownCloud,Seafile,Nextcloud

    yuanfan2012

扫码关注云+社区

领取腾讯云代金券