spring webscoket服务端使用记录

记录spring4中websocket的使用方式

pom jar包配置

<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-websocket</artifactId>  <version>${spring.version}</version>  
</dependency> 
<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-messaging</artifactId>  <version>${spring.version}</version>  
</dependency>  

其中spring.version的配置是:

<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>4.0.0.RELEASE</spring.version><java.version>1.8</java.version><druid.version>1.1.6</druid.version></properties>

涉及到json消息的支持jar用的是alibaba提供的:

 <!-- json -->  <dependency>  <groupId>com.alibaba</groupId>  <artifactId>fastjson</artifactId>  <version>1.2.28</version>  </dependency>  

配置websocket服务

在spring webscoket中有两种方式配置webscoket服务,一种是xml中配置,一种是使用代码继承WebSocketConfigurer,这里使用第二种:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;/*** spring websocket配置* @author ThatWay* 2018-5-8*/
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {//注册webscoket处理类、webscocket的访问地址、过滤处理类registry.addHandler(webSocketHandler(), "/ws").addInterceptors(webSocketInterceptor());}/*** websocket请求处理* @return*/@Beanpublic WebSocketHandler webSocketHandler() {return new WebScoketHandler();}/*** websocket拦截器* @return*/@Beanpublic WebSocketInterceptor webSocketInterceptor(){return new WebSocketInterceptor();}}

webscoket请求过滤

在上一步的服务配置中,使用的webSocketInterceptor是实现了HandshakeInterceptor接口的过滤处理类,它将拦截所有到达服务端的websocket请求,可websocket消息处理前和处理后插入动作。
这里面主要做的事是,客户端创建连接时传递的参数可以取出来,放入到创建连接后产生的session中,在服务端下发消息时可以通过参数来区分session,下面代码中作为session标识的是pageFlag参数。客户端请求的地址是这样的:ws://localhost:8080/integrate_pipe/ws?pageFlag=p1&actionFlag=simple

import javax.servlet.http.HttpServletRequest;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;/*** websocket请求过滤器* @author ThatWay* 2018-5-8*/
public class WebSocketInterceptor implements HandshakeInterceptor {private static Logger logger = LoggerFactory.getLogger(WebSocketInterceptor.class); @Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {logger.info("webscoket处理后过滤回调触发");}@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {logger.info("webscoket处理前过滤回调触发");boolean flag = true;//在调用handler前处理方法if (request instanceof ServletServerHttpRequest) {ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; HttpServletRequest req = serverHttpRequest.getServletRequest();// 从请求中获取页面标志String pageFlag = req.getParameter("pageFlag");// 获取初始化需要的数据String actionFlag = req.getParameter("actionFlag");if(StringUtils.isEmpty(pageFlag) || StringUtils.isEmpty(actionFlag) ){flag = false;logger.info("webscoket连接请求,页面标志pageFlag:"+pageFlag+",动作标志:"+actionFlag+",参数不正确,请求拒绝");} else {logger.info("webscoket连接请求,页面标志pageFlag:"+pageFlag+",动作标志:"+actionFlag);// 将页面标识放入参数中,之后的session将根据这个值来区分attributes.put("pageFlag", pageFlag.trim());attributes.put("actionFlag", actionFlag.trim());}} else {flag = false;}return flag;}
}

消息处理

在服务配置中,使用的WebSocketHandler是继承了TextWebSocketHandler的消息处理类,将由这个类来处理消息,spring中将webscoket相关的生命周期回调也封装到了这里。另外,通过@Service将此类注解为服务,在其他业务controller中就可以使用此类方法触发消息下发了。

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;import cn.qingk.entity.User;/*** 消息处理类* @author ThatWay* 2018-5-5*/
@Service
public class WebScoketHandler extends TextWebSocketHandler {private static Logger logger = LoggerFactory.getLogger(WebSocketHandler.class); // 页面标识名称private final String CLIENT_ID = "pageFlag";// 初始化动作标识名称private final String ACTION_INIT = "actionFlag";// 页面集合private static Map<String, WebSocketSession> clients = new ConcurrentHashMap<String, WebSocketSession>(); // 静态变量,用来记录当前在线连接数private static final AtomicInteger connectCount = new AtomicInteger(0);/***********/*** 连接建立成功后的回调*/@Override    public void afterConnectionEstablished(WebSocketSession session) throws Exception { logger.info("wescoket成功建立连接");  // 页面标识String pageFlag = getAttributeFlag(session,this.CLIENT_ID);// 初始化动作标识String reqAction = getAttributeFlag(session,this.ACTION_INIT);// 返回结果int code = WebSocketStatus.CODE_FAIL;String msg = WebSocketStatus.MSG_FAIL;String returnJson = "";if (!StringUtils.isEmpty(pageFlag)) {// 连接数加一,为了保证多个同页面标识的请求能被处理addOnlineCount();int onlineCount = getOnlineCount();String key = pageFlag+"_"+onlineCount;//管理已连接的sessionclients.put(key, session);logger.info("在线屏数:"+onlineCount);// 从数据库里查询需要信息返回code = WebSocketStatus.CODE_SUCCESS;msg = WebSocketStatus.MSG_SUCCESS;// 查询数据库得到typeString type = WebSocketStatus.TYPE_BDXW;if (reqAction.toLowerCase().equals(WebSocketStatus.ACTION_SIMPLE)) {// DB基本数据logger.info("数据库查询【"+pageFlag+"】的基本数据");Map<String, Object> infoMap = new HashMap<String, Object>();infoMap.put("type", "qwzx");infoMap.put("title", "全网资讯");returnJson = this.makeInfoResponseJson(code, type,reqAction, msg, infoMap);} else if (reqAction.toLowerCase().equals(WebSocketStatus.ACTION_DETAIL)) {// DB数据列表logger.info("数据库查询【"+pageFlag+"】的列表数据");int totalCount = 1;List<Object> userList = new ArrayList<Object>();User user1 = new User();user1.setAddress("address 1");user1.setAge(18);user1.setId(1);user1.setName("name 1");userList.add(user1);returnJson = this.makeListResponseJson(code, type,reqAction, msg, totalCount,userList);} else {code = WebSocketStatus.CODE_FAIL;msg = WebSocketStatus.MSG_FAIL;logger.error("客户端请求的action为:"+reqAction);}// 返回信息TextMessage returnMessage = new TextMessage(returnJson); session.sendMessage(returnMessage); } else {session.sendMessage(new TextMessage("无页面标识,连接关闭!")); session.close();}}    /*** 接收消息处理* 客户端发送消息需遵循的格式:{"pageFlag": "p1","actionFlag": "simple/detail"}*/@Override    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {   long start = System.currentTimeMillis();// 返回结果int code = WebSocketStatus.CODE_FAIL;String msg = WebSocketStatus.MSG_FAIL;String returnJson = "";//接收终端发过来的消息String reqMsg = message.getPayload();// 根据页面标识进行逻辑处理,提取需要的数据if (!StringUtils.isEmpty(msg)) {JSONObject terminalMsg = JSONObject.parseObject(reqMsg);if (!terminalMsg.isEmpty()) {if (terminalMsg.containsKey("pageFlag") && terminalMsg.containsKey("actionFlag")) {//pageFlagString reqPageFlag = terminalMsg.getString("pageFlag");String reqAction = terminalMsg.getString("actionFlag");// 从数据库里查询需要信息返回code = WebSocketStatus.CODE_SUCCESS;msg = WebSocketStatus.MSG_SUCCESS;// 查询数据库得到typeString type = WebSocketStatus.TYPE_BDXW;if (reqAction.toLowerCase().equals(WebSocketStatus.ACTION_SIMPLE)) {// DB基本数据logger.info("数据库查询【"+reqPageFlag+"】的基本数据");Map<String, Object> infoMap = new HashMap<String, Object>();infoMap.put("type", "qwzx");infoMap.put("title", "全网资讯");returnJson = this.makeInfoResponseJson(code, type,reqAction, msg, infoMap);} else if (reqAction.toLowerCase().equals(WebSocketStatus.ACTION_DETAIL)) {// DB数据列表logger.info("数据库查询【"+reqPageFlag+"】的列表数据");int totalCount = 1;List<Object> userList = new ArrayList<Object>();User user1 = new User();user1.setAddress("address 1");user1.setAge(18);user1.setId(1);user1.setName("name 1");userList.add(user1);returnJson = this.makeListResponseJson(code, type,reqAction, msg, totalCount,userList);} else {code = WebSocketStatus.CODE_FAIL;msg = WebSocketStatus.MSG_FAIL;logger.error("客户端请求的action为:"+reqAction);}}} else {logger.error("客户端请求的消息转换json为空");}} else {logger.error("客户端请求的消息为空");}// 返回信息TextMessage returnMessage = new TextMessage(returnJson); long pass = System.currentTimeMillis() - start;logger.info("接收终端请求返回:" + returnMessage.toString()+",耗时:"+pass+"ms");// 向终端发送信息session.sendMessage(returnMessage);    }    /*** 出现异常时的回调*/@Override  public void handleTransportError(WebSocketSession session, Throwable thrwbl) throws Exception {    if(session.isOpen()){    session.close();  }    logger.info("websocket 连接出现异常准备关闭");}    /*** 连接关闭后的回调*/@Override    public void afterConnectionClosed(WebSocketSession session, CloseStatus cs) throws Exception {    // 连接数减1for (Entry<String, WebSocketSession> entry : clients.entrySet()) {String clientKey = entry.getKey();WebSocketSession closeSession = entry.getValue();if(closeSession == session){logger.info("移除clientKey:"+clientKey);clients.remove(clientKey);decOnlineCount();int leftOnlineCount = getOnlineCount();logger.info("剩余在线屏数:"+leftOnlineCount);}}logger.info("websocket 连接关闭了");    }    @Override    public boolean supportsPartialMessages() {    return false;    }  /*** 发送信息给指定页面* @param clientId* @param message* @return*/public boolean sendMessageToPage(String pageFlag, TextMessage message) {boolean flag = false;int all_counter = 0;int send_counter = 0;long start = System.currentTimeMillis();if(!StringUtils.isEmpty(pageFlag)){for (Entry<String, WebSocketSession> entry : clients.entrySet()) {String clientKey = entry.getKey();// 给所有以此id标识开头的终端发送消息if(clientKey.startsWith(pageFlag)){all_counter++;WebSocketSession session = entry.getValue();if (!session.isOpen()) {flag = false;} else {try {session.sendMessage(message);send_counter++;flag =  true;logger.info("sendMessageToPage:[clientKey:"+clientKey+"],flag:"+flag);} catch (IOException e) {e.printStackTrace();flag = false;}}}}}long pass = System.currentTimeMillis() - start;logger.info("sendMessageToPage:"+pageFlag+",flag:"+flag+",all_counter:"+all_counter+",send_counter:"+send_counter+",pass:"+pass+"ms");   return flag;}/*** 发送信息给所有页面* @param clientId* @param message* @return*/public boolean sendMessageToAll(TextMessage message) {boolean flag = false;int all_counter = 0;int send_counter = 0;long start = System.currentTimeMillis();for (Entry<String, WebSocketSession> entry : clients.entrySet()) {  all_counter++;String clientKey = entry.getKey();WebSocketSession session = entry.getValue();if (!session.isOpen()) {flag =  false;} else {try {session.sendMessage(message);flag = true;send_counter++;logger.info("sendMessageToAll:[clientKey:"+clientKey+"],flag:"+flag);} catch (IOException e) {e.printStackTrace();flag = false;}} }  long pass = System.currentTimeMillis() - start;logger.info("sendMessageToAll,flag:"+flag+",all_counter:"+all_counter+",send_counter:"+send_counter+",pass:"+pass+"ms"); return flag;}/*** 给指定的精准发送消息* @param message* @param toUser* @throws IOException*/public boolean sendMessageToId(String clientId,TextMessage message) throws IOException {  boolean flag = false;int all_counter = 0;int send_counter = 0;long start = System.currentTimeMillis();if(!StringUtils.isEmpty(clientId)){all_counter++;WebSocketSession session = clients.get(clientId);if (!session.isOpen()) {flag = false;} else {try {session.sendMessage(message);flag = true;send_counter++;} catch (IOException e) {e.printStackTrace();flag = false;}} }long pass = System.currentTimeMillis() - start;logger.info("sendMessageToId:"+clientId+",flag:"+flag+",all_counter:"+all_counter+",send_counter:"+send_counter+",pass:"+pass+"ms");return flag;}  /*** 获取参数标识* @param session* @return*/private String getAttributeFlag(WebSocketSession session,String flagName) {String flag = null;try {flag = (String) session.getHandshakeAttributes().get(flagName);} catch (Exception e) {logger.error(e.getMessage());}return flag;}/*** 当前连接数* @return*/private synchronized int getOnlineCount() {  return connectCount.get();  }  /*** 新增连接数*/private synchronized void addOnlineCount() {  connectCount.getAndIncrement();}  /*** 减连接数*/private synchronized void decOnlineCount() {  connectCount.getAndDecrement();}  /*** 生成列表响应json* @param code 状态码* @param type 数据类型* @param action 操作类选* @param msg 提示信息* @param totalCount 总数量* @param dataList 数据列表* @return json*/public synchronized String makeListResponseJson(int code,String type,String action,String msg,int totalCount,List<Object> dataList){JSONObject jsonObj = new JSONObject();jsonObj.put("code", code);jsonObj.put("type", type);jsonObj.put("action", action);jsonObj.put("msg", msg);JSONObject contentObj = new JSONObject();contentObj.put("totalCount", totalCount);JSONArray listArray = new JSONArray(dataList);contentObj.put("list", listArray);jsonObj.put("body", contentObj);logger.info("生成list json:" + jsonObj.toString());return jsonObj.toString();}/***  生成详情响应json* @param code 状态* @param type 数据类型* @param action 操作类型* @param msg 提示消息* @param info 数据详情* @return json*/public synchronized String makeInfoResponseJson(int code,String type,String action,String msg,Object info){JSONObject jsonObj = new JSONObject();jsonObj.put("code", code);jsonObj.put("type", type);jsonObj.put("action", action);jsonObj.put("msg", msg);jsonObj.put("body", info);logger.info("生成info json:" + jsonObj.toString());return jsonObj.toString();}}

状态辅助类

在消息处理类中用到了一些状态码、下发消息等静态变量主要是为了和客户端交互时定义好消息格式的。这个类不一定需要。

public class WebSocketStatus {/*********************状态码 开始**********************///需要根据业务具体情况扩展状态码// 处理成功public static final int CODE_SUCCESS = 200;// 处理失败public static final int CODE_FAIL = 200;/*********************状态码 结束**********************//*********************信息 开始**********************///需要根据业务具体情况扩展信息// 处理成功public static final String MSG_SUCCESS = "OK";// 处理失败public static final String MSG_FAIL = "FAIL";/*********************信息 结束**********************//*********************数据类型 开始**********************/// 全网热点public static final String TYPE_QWRD = "qwrd";// 本地新闻public static final String TYPE_BDXW = "bdxw";// 网络热搜public static final String TYPE_WLRS = "wlrs";// 地方舆论public static final String TYPE_DFYL = "dfyl";// 新闻选题public static final String TYPE_XWXT = "xwxt";// 外采调度public static final String TYPE_WCDD = "wcdd";// 生产力统计public static final String TYPE_SCLTJ = "scltj";// 影响力统计public static final String TYPE_YXLTJ = "yxltj";// 任务统计public static final String TYPE_RWTJ = "rwtj";// 资讯热榜public static final String TYPE_ZXRB = "zxrb";// 视频热榜public static final String TYPE_SPRB = "sprb";// 列表自定义public static final String TYPE_LBZDY = "lbzdy";// 图表自定义public static final String TYPE_TBZDY = "tbzdy";/*********************数据类型 结束**********************//*********************动作类型 开始**********************/// 基本信息public static final String ACTION_SIMPLE = "simple";// 详情信息public static final String ACTION_DETAIL = "detail";/*********************动作类型 开始**********************/}

控制器中调用

这里主要是模拟了控制器中由于某个动作需要触发给指定的session发送消息。

@Controller
@RequestMapping("/testController")
public class TestController {public static final Logger LOGGER = Logger.getLogger(TestController.class);@Autowiredprivate TestService testService;@Autowiredprivate WebScoketHandler handler;@RequestMapping("/test")public void test(HttpServletRequest request, HttpServletResponse response) {try {Map<String, Object> infoMap = new HashMap<String, Object>();infoMap.put("type", "qwzx");infoMap.put("title", "全网资讯");TextMessage infoMessage = new TextMessage(handler.makeInfoResponseJson(WebSocketStatus.CODE_SUCCESS, WebSocketStatus.TYPE_QWRD,WebSocketStatus.ACTION_SIMPLE, WebSocketStatus.MSG_SUCCESS, infoMap));int totalCount = 3;User user1 = new User();user1.setAddress("address 1");user1.setAge(18);user1.setId(1);user1.setName("name 1");User user2 = new User();user2.setAddress("address 2");user2.setAge(18);user2.setId(1);user2.setName("name 2");User user3 = new User();user3.setAddress("address 3");user3.setAge(18);user3.setId(1);user3.setName("name 3");List<Object> userList = new ArrayList<Object>();userList.add(user1);userList.add(user2);userList.add(user3);TextMessage listMessage = new TextMessage(handler.makeListResponseJson(WebSocketStatus.CODE_SUCCESS, WebSocketStatus.TYPE_QWRD,WebSocketStatus.ACTION_DETAIL, WebSocketStatus.MSG_SUCCESS, totalCount,userList));String pageFlag = "p1";//向所有打开P1的浏览器发送消息boolean sendFlag1 = this.handler.sendMessageToPage(pageFlag, infoMessage);System.out.println("sendFlag1:"+sendFlag1);response.getWriter().print(sendFlag1);boolean sendFlag2 = this.handler.sendMessageToPage(pageFlag, listMessage);System.out.println("sendFlag1:"+sendFlag2);response.getWriter().print(sendFlag2);} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/247582.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MVC是什么?(转载)

MVC (Modal View Controler)本来是存在于Desktop程序中的&#xff0c;M是指数据模型&#xff0c;V是指用户界面&#xff0c;C则是控制器。使用MVC的目的是将M和V的实现代码分离&#xff0c;从而使同一个程序可以使用不同的表现形式。比如一批统计数据你可以分别用柱状图、饼图来…

CSRF攻击原理及防御

CSRF攻击原理及防御 一、CSRF攻击原理 CSRF是什么呢&#xff1f;CSRF全名是Cross-site request forgery&#xff0c;是一种对网站的恶意利用&#xff0c;CSRF比XSS更具危险性。想要深入理解CSRF的攻击特性我们有必要了解一下网站session的工作原理。   session我想大家都不…

H3C FTP配置示例

转载于:https://www.cnblogs.com/fanweisheng/p/11156596.html

用dotnet自带的mail类发邮件出现的问题

在使用dotnet自带的mail类发送邮件的时候&#xff0c;因为默认的smtp端口是25&#xff0c;如果更改了smtp的端口号&#xff0c;则需加上MailMessage msg &#xff1d; new MailMessage();msg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport&…

Angular CLI ng 指令指南

Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装

spring源码阅读(1/4) - Bean生成

上午去缴了上次没带驾驶证的扣分罚款&#xff0c;最近在图书馆没事就看曾国藩家书&#xff0c;曾国藩说人要明强。光强没有用&#xff0c;你要明强。也就是说要强的有道理。曾国藩又说&#xff0c;做学问不能做死学问&#xff0c;做学问其实很重要的事就是能懂得孝悌&#xff0…

NodeJS解决跨域问题:Access-Control-Allow-Origin

今天在玩vue-resource时&#xff0c;后台使用nodejs来提供数据&#xff0c;由于需要跨域&#xff0c;在网上也找到了解决方法。 vue-resource代码(其实就是ajax技术)&#xff1a; this.$http.get({url:"http://localhost:3000/getdata"}) .then(function (data) {co…

windows10系统下MongoDB的安装及环境配置

windows10系统下MongoDB的安装及环境配置&#xff1a; MongoDB的安装 下载地址&#xff1a; https://www.mongodb.com/download-center (这是windows10环境下的教程&#xff01;请注意&#xff01;) 下载后&#xff0c;我们点击mongodb-win32-x86_64-2008plus-ssl-3.4.3-signed…

Net EF to MySQL生成edmx文件时报错:StrongTypingException:表“TableDetails中列“IsPrimaryKey的值为DBNull...

使用Net写项目&#xff0c;数据库用的MySQL&#xff0c;EF生成edmx文件时&#xff0c;报错&#xff0c;StrongTypingException:表“TableDetails"中列“IsPrimaryKey"的值为DBNull。 解决方法&#xff1a; 1.重启MySQL服务 2.MySQL中运行下以下命令&#xff1a; use …

MongoDB之在mac上设置环境变量

要下班&#xff0c;简介做个笔记。设置环境变量在基于unix/linux的操作系统下进行程序开发&#xff0c;使用环境变量将会方便。通过设置环境变量将可以在任意目录通过输入程序名来执行设定目录下的程序。不需要通过cd将工作目录改变到程序目录再执行程序。而且免去了输入"…

popup a new windows

popup a new windows window.open(url, newwindow, height500, width850, top0, left0, toolbarno, menubarno, scrollbarsno, resizableno,locationno, statusno); 转载于:https://www.cnblogs.com/sandy_liao/archive/2010/06/24/1764533.html

CSS clip:rect矩形剪裁功能

CSS中有一个属性叫做clip&#xff0c;为修剪&#xff0c;剪裁之意。配合其属性关键字rect可以实现元素的矩形裁剪效果。此属性安安稳稳地存在于CSS2.1中&#xff0c;且使用上基本上没有类似于max-height/display:table-cell等浏览器的兼容性问题。 根据Dreamweaver的自动提示&a…

CSS隐藏元素的十四种方法

通过设置width:0或者height:0隐藏一个元素&#xff0c;文字隐藏可以设置color为背景色或transparent&#xff0c;但内容还在&#xff0c;所以用font-size:0&#xff1b; 将元素的opacity设置为0&#xff0c;元素本身还在&#xff0c;只是看不见&#xff1b; 通过绝对定位将元…

jquery.lazyload.js详解

简介lazyload.js用于长页面图片的延迟加载&#xff0c;视口外的图片会在窗口滚动到它的位置时再进行加载&#xff0c;这是与预加载相反的。优点&#xff1a;它可以提高页面加载速度&#xff1b;在某些情况清晰它也可以帮助减少服务器负载。安装bower安装&#xff1a;$ bower in…

Spring Boot Cache使用与整合

参考&#xff1a; 史上最全的Spring Boot Cache使用与整合Spring Cache扩展&#xff1a;注解失效时间主动刷新缓存 项目地址使用本地Caffeine缓存 引入依赖包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starte…

vue-cli的打包配置文件

转载原文: 详解 vue-cli 的打包配置文件代码&#xff08;给大家写写注释&#xff09;. 一、vue-cli都做了什么 1、build/dev-server.js 文件 项目node的启动文件&#xff0c;这里面做了webpack配置和node操作&#xff0c; 2、build/webpack.base.conf.js webpack基本配置文件…

Node.js 部署免费/自动续订 HTTPS

统计了使用 Chrome 浏览器&#xff0c;访问的站点统计中&#xff0c;HTTPS 使用率的增长情况&#xff1a;而在今年 2 月份&#xff0c;Chrome 团队也宣布&#xff0c;将在 2018 年 7 月份发布的 Chrome 68 中&#xff0c;将没有部署 HTTPS 的网站标记为 "不安全"。简…

GSON 循环引用的对象转为 JSON 造成栈溢出

对象转 JSON 可能引发栈溢出的异常&#xff0c;一般是因为对象中的循环引用引起不断递归。 常见的作法就是&#xff1a; 换一种 JSON 的序列化工具&#xff0c;比如 fastjson 默认支持消除对同一对象循环引用transient 修饰属性显式排除对象的某些属性1. java对象引用成环说明 …

一些杂七杂八的前端知识1

一、this指向 this是函数运行时自动生成的一个内部对象&#xff0c;只能在函数内部使用 1. 指向全局变量 纯粹的函数调用 2. 作为对象方法的调用 对象调用某个函数&#xff0c;这个函数里面所包含的this也就指向使用这个函数的对象了 3. 函数构造新对象时调用 new 4. a…

最新的vue webpack模板没有dev-server.js文件,进行后台数据模拟笔记

最新的vue里dev-server.js被替换成了webpack-dev-conf.js 在模拟后台数据的时候直接在webpack-dev-conf.js文件中修改 第一步&#xff0c;在const portfinder require(‘portfinder’)后添加//第一步 const express require(express) const app express()//请求server var a…