废弃fastjson!大型项目迁移Gson保姆级实战

前言

本篇文章是我这一个多月来帮助组内废弃fastjson框架的总结,我们将大部分Java仓库从fastjson迁移至了Gson。

这么做的主要的原因是公司受够了fastjson频繁的安全漏洞问题,每一次出现漏洞都要推一次全公司的fastjson强制版本升级,很令公司头疼。

文章的前半部分,我会简单分析各种json解析框架的优劣,并给出企业级项目迁移json框架的几种解决方案。

在文章的后半部分,我会结合这一个月的经验,总结下Gson的使用问题,以及fastjson迁移到Gson踩过的深坑。

文章目录:

  • 为何要放弃fastjson?

  • fastjson替代方案

    • 三种json框架的特点

    • 性能对比

    • 最终选择方案

  • 替换依赖时的注意事项

    • 谨慎,谨慎,再谨慎

    • 做好开发团队和测试团队的沟通

    • 做好回归/接口测试

    • 考虑迁移前后的性能差异

  • 使用Gson替换fastjson

    • Json反序列化

    • 范型处理

    • List/Map写入

    • 驼峰与下划线转换

  • 迁移常见问题踩坑

    • Date序列化方式不同

    • SpringBoot异常

    • Swagger异常

    • @Mapping JsonObject作为入参异常

注意:是否使用fastjson是近年来一个争议性很大的话题,本文无意讨论框架选型的对错,只关注迁移这件事中遇到的问题进行反思和思考。大家如果有想发表的看法,可以在评论区 理 性 讨论。

本文阅读大概需要:5分钟

码字不易,欢迎关注我的个人公众号:后端技术漫谈(二维码见文章底部)

为何要放弃fastjson?

究其原因,是fastjson漏洞频发,导致了公司内部需要频繁的督促各业务线升级fastjson版本,来防止安全问题。

fastjson在2020年频繁暴露安全漏洞,此漏洞可以绕过autoType开关来实现反序列化远程代码执行并获取服务器访问权限。

从2019年7月份发布的v1.2.59一直到2020年6月份发布的 v1.2.71 ,每个版本的升级中都有关于AutoType的升级,涉及13个正式版本。

fastjson中与AutoType相关的版本历史:

1.2.59发布,增强AutoType打开时的安全性 fastjson
1.2.60发布,增加了AutoType黑名单,修复拒绝服务安全问题 fastjson
1.2.61发布,增加AutoType安全黑名单 fastjson
1.2.62发布,增加AutoType黑名单、增强日期反序列化和JSONPath fastjson
1.2.66发布,Bug修复安全加固,并且做安全加固,补充了AutoType黑名单 fastjson
1.2.67发布,Bug修复安全加固,补充了AutoType黑名单 fastjson
1.2.68发布,支持GEOJSON,补充了AutoType黑名单
1.2.69发布,修复新发现高危AutoType开关绕过安全漏洞,补充了AutoType黑名单
1.2.70发布,提升兼容性,补充了AutoType黑名单
1.2.71发布,补充安全黑名单,无新增利用,预防性补充

相比之下,其他的json框架,如Gson和Jackson,漏洞数量少很多,高危漏洞也比较少,这是公司想要替换框架的主要原因。

fastjson替代方案

本文主要讨论Gson替换fastjson框架的实战问题,所以在这里不展开详细讨论各种json框架的优劣,只给出结论。

经过评估,主要有Jackson和Gson两种json框架放入考虑范围内,与fastjson进行对比。

三种json框架的特点

FastJson

速度快

fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。

使用广泛

fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。

测试完备

fastjson有非常多的testcase,在1.2.11版本中,testcase超过3321个。每次发布都会进行回归测试,保证质量稳定。

使用简单

fastjson的API十分简洁。

Jackson

容易使用 - jackson API提供了一个高层次外观,以简化常用的用例。

无需创建映射 - API提供了默认的映射大部分对象序列化。

性能高 - 快速,低内存占用,适合大型对象图表或系统。

干净的JSON - jackson创建一个干净和紧凑的JSON结果,这是让人很容易阅读。

不依赖 - 库不需要任何其他的库,除了JDK。

Gson

提供一种机制,使得将Java对象转换为JSON或相反如使用toString()以及构造器(工厂方法)一样简单。

允许预先存在的不可变的对象转换为JSON或与之相反。

允许自定义对象的表现形式

支持任意复杂的对象

输出轻量易读的JSON

性能对比

同事撰写的性能对比源码:

https://github.com/zysrxx/json-comparison

本文不详细讨论性能的差异,毕竟这其中涉及了很多各个框架的实现思路和优化,所以只给出结论:

1.序列化单对象性能Fastjson > Jackson > Gson,其中Fastjson和Jackson性能差距很小,Gson性能较差

2.序列化大对象性能Jackson> Fastjson > Gson ,序列化大Json对象时Jackson> Gson > Fastjson,Jackson序列化大数据时性能优势明显

3.反序列化单对象性能 Fastjson > Jackson > Gson , 性能差距较小

4.反序列化大对象性能 Fastjson > Jackson > Gson , 性能差距较很小

最终选择方案

  • Jackson适用于高性能场景,Gson适用于高安全性场景

  • 对于新项目仓库,不再使用fastjson。对于存量系统,考虑到Json更换成本,由以下几种方案可选:

    • 项目未使用autoType功能,建议直接切换为非fastjson,如果切换成本较大,可以考虑继续使用fastjson,关闭safemode。

    • 业务使用了autoType功能,建议推进废弃fastjson。

替换依赖注意事项

企业项目或者说大型项目的特点:

  • 代码结构复杂,团队多人维护。

  • 承担重要线上业务,一旦出现严重bug会导致重大事故。

  • 如果是老项目,可能缺少文档,不能随意修改,牵一发而动全身。

  • 项目有很多开发分支,不断在迭代上线。

所以对于大型项目,想要做到将底层的fastjson迁移到gson是一件复杂且痛苦的事情,其实对于其他依赖的替换,也都一样。

我总结了如下几个在替换项目依赖过程中要特别重视的问题。

谨慎,谨慎,再谨慎

再怎么谨慎都不为过,如果你要更改的项目是非常重要的业务,那么一旦犯下错误,代价是非常大的。并且,对于业务方和产品团队来说,没有新的功能上线,但是系统却炸了,是一件“无法忍受”的事情。尽管你可能觉得很委屈,因为只有你或者你的团队知道,虽然业务看上去没变化,但是代码底层已经发生了翻天覆地的变化。

所以,谨慎点!

做好开发团队和测试团队的沟通

在依赖替换的过程中,需要做好项目的规划,比如分模块替换,严格细分排期。

把前期规划做好,开发和测试才能有条不紊的进行工作。

开发之间,需要提前沟通好开发注意事项,比如依赖版本问题,防止由多个开发同时修改代码,最后发现使用的版本不同,接口用法都不同这种很尴尬,并且要花额外时间处理的事情。

而对于测试,更要事先沟通好。一般来说,测试不会太在意这种对于业务没有变化的技术项目,因为既不是优化速度,也不是新功能。但其实迁移涉及到了底层,很容易就出现BUG。要让测试团队了解更换项目依赖,是需要大量的测试时间投入的,成本不亚于新功能,让他们尽量重视起来。

做好回归/接口测试

上面说到测试团队需要投入大量工时,这些工时主要都用在项目功能的整体回归上,也就是回归测试。

当然,不只是业务回归测试,如果有条件的话,要做接口回归测试。

如果公司有接口管理平台,那么可以极大提高这种项目测试的效率。

打个比方,在一个模块修改完成后,在测试环境(或者沙箱环境),部署一个线上版本,部署一个修改后的版本,直接将接口返回数据进行对比。一般来说是Json对比,网上也有很多的Json对比工具:

https://www.sojson.com/

考虑迁移前后的性能差异

正如上面描述的Gson和Fastjson性能对比,替换框架需要注意框架之间的性能差异,尤其是对于流量业务,也就是高并发项目,响应时间如果发生很大的变化会引起上下游的注意,导致一些额外的后果。

使用Gson替换Fastjson

这里总结了两种json框架常用的方法,贴出详细的代码示例,帮助大家快速的上手Gson,无缝切换!

Json反序列化

String jsonCase = "[{\"id\":10001,\"date\":1609316794600,\"name\":\"小明\"},{\"id\":10002,\"date\":1609316794600,\"name\":\"小李\"}]";// fastjson
JSONArray jsonArray = JSON.parseArray(jsonCase);
System.out.println(jsonArray);
System.out.println(jsonArray.getJSONObject(0).getString("name"));
System.out.println(jsonArray.getJSONObject(1).getString("name"));
// 输出:
// [{"date":1609316794600,"name":"小明","id":10001},{"date":1609316794600,"name":"小李","id":10002}]
// 小明
// 小李// Gson
JsonArray jsonArrayGson = gson.fromJson(jsonCase, JsonArray.class);
System.out.println(jsonArrayGson);
System.out.println(jsonArrayGson.get(0).getAsJsonObject().get("name").getAsString());
System.out.println(jsonArrayGson.get(1).getAsJsonObject().get("name").getAsString());
// 输出:
// [{"id":10001,"date":1609316794600,"name":"小明"},{"id":10002,"date":1609316794600,"name":"小李"}]
// 小明
// 小李

看得出,两者区别主要在get各种类型上,Gson调用方法有所改变,但是变化不大。

那么,来看下空对象反序列化会不会出现异常:

String jsonObjectEmptyCase = "{}";// fastjson
JSONObject jsonObjectEmpty = JSON.parseObject(jsonObjectEmptyCase);
System.out.println(jsonObjectEmpty);
System.out.println(jsonObjectEmpty.size());
// 输出:
// {}
// 0// Gson
JsonObject jsonObjectGsonEmpty = gson.fromJson(jsonObjectEmptyCase, JsonObject.class);
System.out.println(jsonObjectGsonEmpty);
System.out.println(jsonObjectGsonEmpty.size());
// 输出:
// {}
// 0

没有异常,开心。

看看空数组呢,毕竟[]感觉比{}更加容易出错。

String jsonArrayEmptyCase = "[]";// fastjson
JSONArray jsonArrayEmpty = JSON.parseArray(jsonArrayEmptyCase);
System.out.println(jsonArrayEmpty);
System.out.println(jsonArrayEmpty.size());
// 输出:
// []
// 0// Gson
JsonArray jsonArrayGsonEmpty = gson.fromJson(jsonArrayEmptyCase, JsonArray.class);
System.out.println(jsonArrayGsonEmpty);
System.out.println(jsonArrayGsonEmpty.size());
// 输出:
// []
// 0

两个框架也都没有问题,完美解析。

范型处理

解析泛型是一个非常常用的功能,我们项目中大部分fastjson代码就是在解析json和Java Bean。

// 实体类
User user = new User();
user.setId(1L);
user.setUserName("马云");// fastjson
List<User> userListResultFastjson = JSONArray.parseArray(JSON.toJSONString(userList), User.class);
List<User> userListResultFastjson2 = JSON.parseObject(JSON.toJSONString(userList), new TypeReference<List<User>>(){});
System.out.println(userListResultFastjson);
System.out.println("userListResultFastjson2" + userListResultFastjson2);
// 输出:
// userListResultFastjson[User [Hash = 483422889, id=1, userName=马云], null]
// userListResultFastjson2[User [Hash = 488970385, id=1, userName=马云], null]// Gson
List<User> userListResultTrue = gson.fromJson(gson.toJson(userList), new TypeToken<List<User>>(){}.getType());
System.out.println("userListResultGson" + userListResultGson);
// 输出:
// userListResultGson[User [Hash = 1435804085, id=1, userName=马云], null]

可以看出,Gson也能支持泛型。

List/Map写入

这一点fastjson和Gson有区别,Gson不支持直接将List写入value,而fastjson支持。

所以Gson只能将List解析后,写入value中,详见如下代码:

// 实体类
User user = new User();
user.setId(1L);
user.setUserName("马云");// fastjson
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("user", user);
jsonObject1.put("userList", userList);
System.out.println(jsonObject1);
// 输出:
// {"userList":[{"id":1,"userName":"马云"},null],"user":{"id":1,"userName":"马云"}}// Gson
JsonObject jsonObject = new JsonObject();
jsonObject.add("user", gson.toJsonTree(user));
System.out.println(jsonObject);
// 输出:
// {"user":{"id":1,"userName":"马云"},"userList":[{"id":1,"userName":"马云"},null]}

如此一来,Gson看起来就没有fastjson方便,因为放入List是以gson.toJsonTree(user)的形式放入的。这样就不能先入对象,在后面修改该对象了。(有些同学比较习惯先放入对象,再修改对象,这样的代码就得改动)

驼峰与下划线转换

驼峰转换下划线依靠的是修改Gson的序列化模式,修改为LOWER_CASE_WITH_UNDERSCORES

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
Gson gsonUnderScore = gsonBuilder.create();
System.out.println(gsonUnderScore.toJson(user));
// 输出:
// {"id":1,"user_name":"马云"}

常见问题排雷

下面整理了我们在公司项目迁移Gson过程中,踩过的坑,这些坑现在写起来感觉没什么技术含量。但是这才是我写这篇文章的初衷,帮助大家把这些很难发现的坑避开。

这些问题有的是在测试进行回归测试的时候发现的,有的是在自测的时候发现的,有的是在上线后发现的,比如Swagger挂了这种不会去测到的问题。

Date序列化方式不同

不知道大家想过一个问题没有,如果你的项目里有缓存系统,使用fastjson写入的缓存,在你切换Gson后,需要用Gson解析出来。所以就一定要保证两个框架解析逻辑是相同的,但是,显然这个愿望是美好的。

在测试过程中,发现了Date类型,在两个框架里解析是不同的方式。

  • fastjson:Date直接解析为Unix

  • Gson:直接序列化为标准格式Date

导致了Gson在反序列化这个json的时候,直接报错,无法转换为Date。

解决方案:

新建一个专门用于解析Date类型的类:

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;import java.io.IOException;
import java.util.Date;public class MyDateTypeAdapter extends TypeAdapter<Date> {@Overridepublic void write(JsonWriter out, Date value) throws IOException {if (value == null) {out.nullValue();} else {out.value(value.getTime());}}@Overridepublic Date read(JsonReader in) throws IOException {if (in != null) {return new Date(in.nextLong());} else {return null;}}
}

接着,在创建Gson时,把他放入作为Date的专用处理类:

Gson gson = new GsonBuilder().registerTypeAdapter(Date.class,new MyDateTypeAdapter()).create();

这样就可以让Gson将Date处理为Unix。

当然,这只是为了兼容老的缓存,如果你觉得你的仓库没有这方面的顾虑,可以忽略这个问题。

SpringBoot异常

切换到Gson后,使用SpringBoot搭建的Web项目的接口直接请求不了了。报错类似:

org.springframework.http.converter.HttpMessageNotWritableException

因为SpringBoot默认的Mapper是Jackson解析,我们切换为了Gson作为返回对象后,Jackson解析不了了。

解决方案:

application.properties里面添加:

#Preferred JSON mapper to use for HTTP message conversion
spring.mvc.converters.preferred-json-mapper=gson

Swagger异常

这个问题和上面的SpringBoot异常类似,是因为在SpringBoot中引入了Gson,导致 swagger 无法解析 json。

采用类似下文的解决方案(添加Gson适配器):

http://yuyublog.top/2018/09/03/springboot%E5%BC%95%E5%85%A5swagger/

  1. GsonSwaggerConfig.java

@Configuration
public class GsonSwaggerConfig {//设置swagger支持gson@Beanpublic IGsonHttpMessageConverter IGsonHttpMessageConverter() {return new IGsonHttpMessageConverter();}
}
  1. IGsonHttpMessageConverter.java

public class IGsonHttpMessageConverter extends GsonHttpMessageConverter {public IGsonHttpMessageConverter() {//自定义Gson适配器super.setGson(new GsonBuilder().registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()).serializeNulls()//空值也参与序列化.create());}
}
  1. SpringfoxJsonToGsonAdapter.java

public class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> {@Overridepublic JsonElement serialize(Json json, Type type, JsonSerializationContext jsonSerializationContext) {return new JsonParser().parse(json.value());}
}

@Mapping JsonObject作为入参异常

有时候,我们会在入参使用类似:

public ResponseResult<String> submitAudit(@RequestBody JsonObject jsonObject) {}

如果使用这种代码,其实就是使用Gson来解析json字符串。但是这种写法的风险是很高的,平常请大家尽量避免使用JsonObject直接接受参数。

在Gson中,JsonObject若是有数字字段,会统一序列化为double,也就是会把count = 0这种序列化成count = 0.0

为何会有这种情况?简单的来说就是Gson在将json解析为Object类型时,会默认将数字类型使用double转换。

如果Json对应的是Object类型,最终会解析为Map<String, Object>类型;其中Object类型跟Json中具体的值有关,比如双引号的""值翻译为STRING。我们可以看下数值类型(NUMBER)全部转换为了Double类型,所以就有了我们之前的问题,整型数据被翻译为了Double类型,比如30变为了30.0。

可以看下Gson的ObjectTypeAdaptor类,它继承了Gson的TypeAdaptor抽象类:

具体的源码分析和原理阐述,大家可以看这篇拓展阅读:

https://www.jianshu.com/p/eafce9689e7d

解决方案:

第一个方案:把入参用实体类接收,不要使用JsonObject

第二个方案:与上面的解决Date类型问题类似,自己定义一个Adaptor,来接受数字,并且处理。这种想法我觉得可行但是难度较大,可能会影响到别的类型的解析,需要在设计适配器的时候格外注意。

总结

这篇文章主要是为了那些需要将项目迁移到Gson框架的同学们准备的。

一般来说,个人小项目,是不需要费这么大精力去做迁移,所以这篇文章可能目标人群比较狭窄。

但文章中也提到了不少通用问题的解决思路,比如怎么评估迁移框架的必要性。其中需要考虑到框架兼容性,两者性能差异,迁移耗费的工时等很多问题。

希望文章对你有所帮助。

参考

《如何从Fastjson迁移到Gson》

https://juejin.im/post/6844904089281626120

《FastJson迁移至Jackson》此文作者自己封装了工具类来完成迁移

https://mxcall.github.io/posts/%E5%B7%A5%E4%BD%9C/%E7%A8%8B%E5%BA%8F%E5%91%98/javaSE/FastJson%E8%BF%81%E7%A7%BB%E8%87%B3Jackson/

《你真的会用Gson吗?Gson使用指南》

https://www.jianshu.com/p/e740196225a4

json性能对比

https://github.com/zysrxx/json-comparison/tree/master/src/main/java/json/comparison

fastjson官方文档

https://github.com/alibaba/fastjson/wiki

易百教程

https://www.yiibai.com/jackson

往期精彩文章:

绝了,几款主流的 JSON 库性能对比!

Java中常用的4个Json库,哪个性能更牛逼?

这个 bug 让我更加理解 Spring 单例了

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

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

相关文章

网页如何做到适应在手机上浏览

http://bbs.php100.com/read-htm-tid-482066.html 目前有很多不错的mobile开发框架可以使用&#xff0c;这些框架已经为手机端的特殊性提供了很好的支持和效果插件&#xff0c;比如&#xff1a;jquery mobile、kendoui等~~ 不过&#xff0c;谢谢框架因为其开源性或商业化&…

学到了!MySQL 8 新增的「隐藏索引」真不错

MySQL 8.0 虽然发布很久了&#xff0c;但可能大家都停留在 5.7.x&#xff0c;甚至更老&#xff0c;其实 MySQL 8.0 新增了许多重磅新特性&#xff0c;比如栈长今天要介绍的 "隐藏索引" 或者 "不可见索引"。隐藏索引是什么鬼&#xff1f; 隐藏索引 字面意思…

Java ClassLoader findLibrary()方法与示例

ClassLoader类findLibrary()方法 (ClassLoader Class findLibrary() method) findLibrary() method is available in java.lang package. findLibrary()方法在java.lang包中可用。 findLibrary() method is used to find the absolute pathname of the given native library. f…

iOS开发-自定义UIAlterView(iOS 7)

App中不可能少了弹框&#xff0c;弹框是交互的必要形式&#xff0c;使用起来也非常简单&#xff0c;不过最近需要自定义一个弹框&#xff0c;虽然iOS本身的弹框已经能满足大部分的需求&#xff0c;但是不可避免还是需要做一些自定义的工作。iOS7之前是可以自定义AlterView的&am…

jsp中redirect和forward的区别

在网上看到一些帖子&#xff0c;总结了一些区别&#xff0c;可以从以下几个方面来看&#xff1a;1.从地址栏显示来说 forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来…

Spring 事务失效的 8 大场景,面试官直呼666...

前几天发了一篇文章里面有一个关于事务失效的问题&#xff1a;用 Spring 的 Transactional 注解控制事务有哪些不生效的场景&#xff1f;其中有个热心粉丝留言分享了下&#xff0c;我觉得总结得有点经验&#xff0c;给置顶了&#xff1a;但是我觉得还是总结得不够全&#xff0c…

python 示例_Python条件类| release()方法与示例

python 示例Python Condition.release()方法 (Python Condition.release() Method) release() is an inbuilt method of the Condition class of the threading module in Python. release()是Python中线程模块的Condition类的内置方法。 Condition class implements conditio…

Java BigInteger类| hashCode()方法与示例

BigInteger类hashCode()方法 (BigInteger Class hashCode() method) hashCode() method is available in java.math package. hashCode()方法在java.math包中可用。 hashCode() method is used to return the hash code value of this BigInteger. hashCode()方法用于返回此Big…

认真聊一下MySQL索引的底层实现!

前言当我们发现SQL执行很慢的时候&#xff0c;自然而然想到的就是加索引&#xff0c;当然他也是高频的面试问题&#xff0c;所以今天我们一起来学习一下MySQL索引的底层实现&#xff1a;B树。树简介、树种类B-树、B树简介B树插入B树查找B树删除B树经典面试题树的简介树的简介树…

js的四舍五入

Math.ceil求最小的整数但不小于本身. Math.round求本身的四舍五入。 Math.floor求最大的整数但不大于本身.

Java BigInteger类| bitCount()方法与示例

BigInteger类的bitCount()方法 (BigInteger Class bitCount() method) bitCount() method is available in java.math package. bitCount()方法在java.math包中可用。 bitCount() method is used to count the number of bits in 2’s complement denotation of this BigIntege…

head first python(第三章)–学习笔记

1.介绍基础文件&#xff0c;输入&#xff0c;输出 open() 打开文件&#xff0c;一次传入一行数据&#xff0c;可以结合for循环和readline()来使用 close() 用来关闭open打开的文件 the_file open(sketch.txt)the_file.close()例子&#xff1a; >>> data open(/root/…

最新大厂面试真题集锦

年后又是一波求职季&#xff0c;正是“金三银四”这个求职黄金期&#xff0c;很多人扎堆在这个时间段跳槽&#xff0c;找工作&#xff0c;程序员也不例外。春节刚过&#xff0c;各公司企业都开始启动了新一年的招聘计划&#xff0c;招聘岗位倍增&#xff0c;求职人数远超于岗位…

java.lang.IllegalThreadStateException 线程运行报错

写程序线程再运行第二遍的时候报java.lang.IllegalThreadStateException。发现一个Thread不能重复用start方法。 解决方法&#xff1a; 1、将extends Thread线程类改成implements Runnable&#xff0c;或者将Thread a new Thread改为Runnable a new Runnable&#xff1b; …

使用OpenCV在Python中进行人脸和眼睛检测

Modules Used: 使用的模块&#xff1a; python-opencv(cv2)python-opencv(cv2) python-opencv(cv2) Opencv(Open source computer vision) is a python library that will help us to solve computer vision problems. Opencv(开源计算机视觉)是一个Python库&#xff0c;可帮…

Java打造一款SSH客户端,已开源!

最近由于项目需求&#xff0c;项目中需要实现一个WebSSH连接终端的功能&#xff0c;由于自己第一次做这类型功能&#xff0c;所以首先上了GitHub找了找有没有现成的轮子可以拿来直接用&#xff0c;当时看到了很多这方面的项目&#xff0c;例如&#xff1a;GateOne、webssh、she…

html中可以自定义属性,,,妈的竟然才知道..

html中可以自定义属性,,,妈的竟然才知道.. <input userinfo"没见过帅哥呀" />

Js实现动态插入删除文本框

自己做了个Js插入文本框的例子&#xff0c;扔上别忘了。 <html><head><title>Untitled Document</title><meta http-equiv"Content-Type" content"text/html; charsetutf-8"><script language"javascript"&g…

ruby 数组元素替换_从Ruby中的集合中删除并替换元素

ruby 数组元素替换Ruby has various specific methods to fulfil specific tasks. At several places, you may need to replace or delete an element from an instance of set class provided that there are certain elements present in the Set. You can perform deletion…

我去,这几个Linux指令太装B了|动图展示

1. sl先看一下呼啸而过的火车&#xff1b;安装指令如下&#xff1b;sduo apt-get install sl执行结果如下&#xff1a;2. htop图形化Linux系统性能监测工具&#xff0c;屌不屌:安装指令如下:sduo apt-get install htop执行结果如下&#xff1b;3. gcp以前用cp复制文件总是看不懂…