hello world
+ + +梦开始的地方
+ + ++
diff --git a/2022/08/18/hello-world/index.html b/2022/08/18/hello-world/index.html new file mode 100644 index 00000000..52a90454 --- /dev/null +++ b/2022/08/18/hello-world/index.html @@ -0,0 +1,755 @@ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +梦开始的地方
+ + +在java生态中有着众多的工具类,而有的工具类是我们常用而且如果用的好,会极大提高代码的优雅性与效率的,同时也能避免重复的造轮子。接下来我们一起了解一下这些工具类。
+Objects
+Objects是jdk官方自带的工具类,首先我们来看看Objects的官方简介:
+++This class consists of static utility methods for operating on objects. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, and comparing two objects.Since: 1.7
+
这是一个私有的工具类,用来提供一些对Object的操作方法,包括null值的安全操作,一级hash值的计算等实用方法。jdk1.7 就提供了。
+Objects的提供的静态方法如下:
equals
+用来比较两个对象的是否相等,null安全的,可以替换繁琐的判空校验。实现逻辑:
+1 |
|
deepEquals
+数组的深度比较,同样null安全的。实现逻辑:
+1 |
|
hashCode
+获取一个对象的hashCode,同样null安全的。实现逻辑:
+1 |
|
+hash
+对一个数组求hash值,同样null安全的。实现逻辑:
+1 |
|
toString
+调用对象o的toString方法,同样null安全的。不过这里要注意一下,如果是null的话,会返回null字符串。
+相对应的,还有一个重载方法,toString(Object o, String nullDefault) 。 能够自定义null值的返回值,这个挺实用的。
+实现逻辑:
+1 |
|
compare
+自定义实现大小比较逻辑,同样null安全的。实现逻辑:
+1 |
|
isNull
+判断对象是null, 可以用来替换 obj==null 的写法,更优雅。 实现逻辑:
+1 |
|
+nonNull
+判断对象非null, 可以用来替换 obj!=null 的写法,更优雅。 实现逻辑:
+1 |
|
+requireNonNull
+断言判断,要求一个对象是非空的。如果为空则抛出异常,有三个重载方法,可以自定义不同的异常message。
+实现逻辑:
+1 |
|
好了,jdk自动的Objects方法就介绍这些,有时间 我们再聊聊其他的常用工具类。
+ + +System工具类也是java中常用的一个工具类,并且从java 1.0 时代就存在了。它是不能被实例化的。我们这里介绍一下它的简单用法.
+下面是官方介绍
+++The System class contains several useful class fields and methods. It cannot be instantiated.
+
Among the facilities provided by the System class are standard input, standard output, and error output streams; access to externally defined properties and environment variables; a means of loading files and libraries; and a utility method for quickly copying a portion of an array.
以下介绍System的常用方法
+1 |
|
以上的流就会在System的标准流中使用
+1 |
|
获取一个基础虚拟机的流通道
+1 |
|
设置或获取 java虚拟机的安全管理器。这个我用的比较少,大家看注释吧。
+1 |
|
获取java的控制台对象,关于控制台命令交互可以通过这里获取
+1 |
|
这应该是大家使用最频繁的几个接口。
+1 |
|
获取当前系统的纳秒精度的数(基于当前虚拟机)。这个数是依据当前虚拟机的一个原点计算出来的一个高精度值,与具体的时间无关(比如毫秒方法获取的是1970到今天的时间戳,但纳秒的原点不是具体的某个时间点)
所以,这个方法的用处也就有了,就是基于同一个虚拟机的纳秒级精度的前后时间比较。
因为数据比较大,可以会有越界风险
1 |
|
说实话,不太理解为什么这个方法会放在这里。可能在虚拟机底层里,数组copy是一个基础功能吧。
很常用。
1 |
|
返回对象的默认hashcode(即时被覆盖也不执行覆盖方法)
null 返回0
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
linux/unix : \n
windows: \r\n
1 |
|
1 |
|
以某个状态终止虚拟机:非0 code 表示非正常结束
+1 |
|
执行垃圾回收
+1 |
|
1 |
|
平时在刷算法题的时候经常会遇到一些套路, 也就是一些类似的小技巧. 这类技巧能够快速的帮我们解决某一类的算法问题. 这里就是整理一下自己遇到的,总结的算法技巧, 以及这些技巧可以应用与哪些场景.
+个人理解,滑动窗口 的基础就是双指针. 即一个快指针代指窗口的头部, 一个慢指针代指窗口的尾部. 滑动窗口经过的地方就是我们要处理的数据.
+ +如图所示,我们的滑动创建运动方向从左往右, 已经经过了区域1,正在经历区域2,3,4. 还没有经历区域5.
+通过上面分析可知, 滑动窗口是一个窗口经过一段连续的区域,并实时计算统计经过区域内的一些值. 所以滑动窗口很适用的场景特点也就出来了:
+要回答这个问题,先提出一个说法:我们写的代码是给人看的,不是给机器看的.
如果你不认可这个说法,下面关于代码注释的一些总结想必对你也没啥帮助.
如果你认可这个说法, 那么下面的文章希望会给你一些帮助.
回到本段的主题,什么是java注释呢?
+首先,注释是面向代码维护者的: 注释是对代码逻辑的一段说明, 方便后续的代码维护者能够快速的了解这段代码的含义,并依次为基础能够在现有代码的基础上做修改与维护.
+其次,注释是面向系统使用者的: 要知道类似于java,或者spring这样偏向于底层,框架类的代码.全世界有数以万计的使用者的,这些人没精力也没有必要在调用api的时候去了解这段代码的底层实现逻辑,所以这段代码的api说明文档就显得很重要了.而代码上的注释就是相关api文档的主要来源.
+通过javadoc命令,可以将注释抽离生成html文档,比如官方的java16API文档
+想要写好代码的注释,我们要从注释的面向用户来考虑.不同的用户关注点还是有点差异的.
+作为一名代码的维护者,当我们拿到一份没有任何注释的代码的时候,相信大家内心的感受是相同的.毕竟人类的悲欢并不相通, 除非看到没有注释的代码.
+如果有了解设计模式的朋友,相信一定听说过一个设计原则:开闭原则, 对扩展开发,对修改关闭. 那么大家有没有想过为啥会有这个原则呢?
+翻译一下: 修改现有的代码是有风险的,但是如果是扩展写新代码的话,作为开发者的你,是了解当前功能的前后背景的,而且你无论怎么写都不会对历史功能产生影响.所以对我们的代码设计能力提出来要求.
+再翻译一下: 原来的代码既然跑的好好的,为啥要修改它呢, 改出问题来算谁的?
+再翻译一下: 为啥我们不敢改以前的代码呢?谁知道当时为啥要写这段逻辑,谁知道这段逻辑有谁在用?改好了没有夸, 改崩了有锅背.
+这就是我们维护老代码的困境!
+作为一名程序员,我们要有一个概念,我们的代码是我们某个时刻的思维逻辑的固化.
如果是一个比较简单的逻辑,注释可以简单写写.
如果是一段比较复杂的逻辑,那么我们的注释要能够描述清楚我们当时的这段逻辑的背景(为什么要写这段逻辑), 意图(这段逻辑要实现什么效果), 用法(这段逻辑改如何使用,以及谁在用), 最好能有修改建议.
在业界开源的代码里有很多比较好的例子,来个spring的:
+1 |
|
对于api的使用者,注释文档的要求相对简单些.
+他们只关心这个方法的功能是什么, 以及入参有哪些,出参会有哪些, 会不会抛异常等, 会不会返回null等. 他们不关心这个方法的内部实现逻辑是啥, 比如一个排序方法, 使用者不关心这个方法内部使用冒泡排序,还是快排, 只要能完成诉求就行.
+所以,面向api的使用者的注释,原则就是让他知道这个方法怎么使用就行了.
+1 |
|
{@literal}
1 |
|
1 |
|
1 |
|
1 |
|
一个正常方法必然会用到的注释tag,
@param 代表方法的入参,
@return 代表方法的返回值.
@throws 代表可能引发方法中断的异常. 等同与 @exception
这里需要特别说明一下, 有些人可能觉得只有那些受检异常(也就是必须在方法签名里声明的异常)才需要在注释里声明@throws. 其实不是的. 所有引发程序中断的异常, 包括运行时异常都可以在注释里说明, 也可以在方法签名里添加. 尤其是自定义的异常.
举个例子:
1 |
|
这里的IllegalStateException 是一个RuntimeException异常, 我们在方法里可能抛出这个异常, 最好是在方法签名里声明一下, 然后在代码注释里说明一下.
+当前代码可以参考的其他代码, 后面跟代码的全路径,可以是类,方法,属性等.具体参考如下:
+1 |
|
标识当前代码的作者是谁, 可以一个,也可以有多个.
+都是版本相关的tag
@version 标识当前版本好, 编译的时候会用到. 符合SCCS规范.
@since 标识这段代码的引入版本
序列化相关的属性, 标识哪些字段可以序列化,哪些不行.
+废弃某段代码,表示不再维护,并在一段时间后会被删除. 标记后, 相关的引用位置会被标记为删除线.
个人认为这个tag还是很有用的:
1 |
|
1 |
|
想在JAVA的注释中添加一段代码,并且可以优雅的编译出来还是挺麻烦的.下面提供一种方法
+1 |
|
官方文档
javadoc - The Java API Documentation Generator
+
How and When To Deprecate APIs
++Beaudar 名称源于粤语“表达”的发音,是 Utterances 的中文版本,是一款基于github issue 的一个评论插件
+
本站的评论模块就是使用的 beaudar
+选择 Beaudar 将要连接的仓库, 也就是网站对应的github仓库。
+
3. 将网站对应的分支配置到配置中,并确保 Issues 功能已打开。
4. 将以下脚本标记添加到博客的模板中。 将其放置在要显示注释的位置。 使用 .beaudar 和 .beaudar-frame 选择器自定义布局。
1 |
|
配置解读
+
原因: 现在github的默认主分支是main, 不在是master. 所以分支找不到.
配置的时候指定分支为main就可以了.
将上述的 script脚本 在_config.yml
中按如下方式添加即可
1 |
|
但是效果比较丑,需要自己去调整一下css才与整个主题适配, 建议使用fluid主题已经集成好的组件。
众所周知, es是一种高效的数据查询,检索引擎。 可以对海量的数据进行快速的查询。但是在使用es的时候,经常会遇到一个比较尴尬的问题,那就是es索引里的字段类型是固定的,不可修改的。而es的索引又是可以自动扩展字段的。 这个时候自动扩展出来的字段就是使用了默认的字段类型(通常是text类型)。
+惊不惊喜!
+查看索引字段的方法:
+1 |
|
聪明的你肯定想到了, 如果我们忘记加字段了, 或者字段加错了要修改字段类型咋办? 这个时候场景的办法就只有一个了:
+重建索引!
+从数据库角度来看,也就是新增新表, 迁移数据, 删除老表.
+Elasticsearch 版本 : v 6.6
目标: 在my_index 索引上新增一个integer字段. 且中间不间断服务.
使用aliases api
创建一个别名my_index 指向实际的索引my_idex.
这样,我们通过my_index访问数据时, 就不会直接访问my_index索引了, 而是通过别名指向对应的索引.
1 |
|
使用 put mapping api
打算新增一个字段 fiedl_d, 数据类型.
因为默认的string类型是keyword (假设, 意会即可), 所以如果想要新增一个整型类型, 就不能通过自动的扩展字段使用默认类型的方式. 所以必须显式的指定字段类型.
+1 |
|
使用 Reindex api
将老的my_index 数据迁移到新的my_index2上.
这一步比较耗时, 做好心理准备.
1 |
|
使用aliases api
将别名从老的index, 指向新的index
1 |
|
处理迁移期间my_index 发生的增量数据
+1 |
|
使用 delete api
+1 |
|
至此, 整个重建索引过程完毕.
很麻烦吧.
为了不这么麻烦, 以后要给es 增加新字段时, 一定要先通过maping的方式先新增字段. 然后再在代码里处理相对应的逻辑.
over!
+可以通过put mapping的方式, 增量补充索引的字段
+1 |
|
相信已经成功使用github建站的朋友们肯定会有一个想法, 就是可不可以将文章仓库私有也能实现建站的功能呢?
+答案是可以的, 本文就是一个将一个公共仓库站点私有化后的例子. 并将过程总结一下, 方便大家参考使用.
+首先,使用github pages建站, 项目名就固定死了,必然是<用户名>.github.io
其次,如果有域名的话,像域名配置等一些配置也是需要放到 外露站点下的.
除此之外, 其他的内容就就是可以变的了.因为github.io站点最终暴露的就是一个编译好的web静态网站.
至于源码是不是在这里,无所谓的.之所以源码与静态站点放到一起,核心还是方便github的 action自动编译.
好了, 了解了这些核心要素, 我们可以操作的部分也就有了.
就像我们正常的开发流程一样,我们可以把整个网站的发布流程拆分为两部分: 打包 与 发布.
其中打包可以在我们的私有仓库中进行, 然后将打包后的静态网站 发布到github.io公仓中.
这里就用到了github的 action 自动编译功能. 其会在我们提交代码的时候触发一个操作,
调用一段脚本命令执行.
而这段脚本的核心就是通过git 将编译后的静态站点push 到github.io公仓中.
+流程如下:
这一步一定要记住创建好的token, 因为后面要用,而且这个页面刷新后就没了.
+其实有过使用ssh经验的朋友们应该已经意识到了, 这一步其实就是一个创建ssh公钥的过程. 只不过这个公钥是放到github服务器上了.
+有了token, 正常来说就能提交代码了. 但是要知道这个东西是放到脚本里的, 明文暴露还是不太安全.
所以我们要在私有库里将这个token配置成一个环境变量,通过环境变量引用的方式使用,这样就安全多了.
1 |
|
可以尝试在私有库private_repo里提交文章了.
+同时你会发现, github.io公仓里的内部全部被静态网站内容覆盖了. 不用担心,这是正常的.
+1 |
|
在github的action模块里创建了一个脚本后, 就会在项目的.github/workflows里创建一个对应的文件.
+通过workflow 大致可以理解这个模块的功能, 就是一个工作流.
在一些操作执行(通常是提交代码)后, 触发一系列的后续自动化操作, 这些操作可以是并行的,也可以是串行的.
而这些后续操作是通过一些action脚本组织起来的. 整体上看,一个action由以下部分组成.
+无论是job,还是step, 都可以理解一组指令集合. 他们都是可以配置id的. 可以通过id来互相依赖, 互相调用.
+其他的可以参考里的官方文档.
+最近在练习编程的时候,发现一个问题. 就是在测试一个排序功能时, 如果写入大批量的参数, 会报 “java: 代码过长” 的问题.
+编程这么多年了, 第一次遇到这种问题, 还挺神奇的. 记录和总结一下.
+1 |
|
很简单的代码, 没事走两步
+1 |
|
首先, 顾名思义, 很明细就是字符长度超过了java 一个类文件的最大长度限制导致的.
+那么java类文件的长度是多少呢?
经过一番不是很辛苦的查询, 定位到了原因:
++There is a 64K byte-code size limit on a method
+
java中一个方法的最大长度是64Kb
很明细, 我们的这个方法长度超了.
+原因找到了, 要解决也就很简单了. 只要做到规避方法的最大长度就行了.
+其实只要符合正常的java编码规范, 我们基本上是遇不到上面的问题的.
只有在一些极限测试的时候, 我们通常会将测试内容放到代码里, 才可能碰到.
只能说: 没用的小知识又增多了🐶
+redis内置了Lua 5.1引擎. 可以很方便的执行lua脚本.
有了这个利器, 我们就可以在redis命令的基础上自己组合命令并原子性执行了.
如果要调用
+1 |
|
demo
+1 |
|
最常用的很是就是call方法了
+1 |
|
demo
+1 |
|
不定期更新中…
+普通的k-v操作
+redis是不支持批量删除指令的, 因为redis是单线程的. 批量删除会占用redis的主线程.影响性能.
+但是我认为现在redis既然已经支持异步线程操作一些后台数据了, 也就可以支持在不影响主线程性能的情况下实现 正则批量删除数据的命令了. 不知为何还是没有.
+这里记录一下使用lua脚本, 在redis命令行里 通过正则批量删除缓存的功能. 核心是使用 keys命令. 这个会阻塞主线程, 如果量比较大, 慎用.
+1 |
|
将最后的’key正则表达式’换成自己想删除的keys的表达式即可.
+关于量的问题, 我删除线上5w条缓存,秒删,无任何影响.各位同学自行参考量级.
+不定期更新中…
+本人初次接触鸿蒙系统, 想着自己在手机上开发个app玩玩, 结果第一步就遇到坑了~~
+因为是个demo, 其实就是从官方demo中copy过来的, 然后换成自己的域名地址
+1 |
|
大家可以从参考里的官方demo里比对一下, 啥都没变, 然后本地调试, 报
+1 |
|
多说一句, 因为是初次使用这个ide, 日志的打印入口都找了半天. 下面是日志的路径
+++底部导航log-> HiLog -> 选中自己的设备 -> 选择日志级别
+
++需要权限:ohos.permission.INTERNET
+
然后就去查权限相关的文档, 在模块的config.json下面把权限加上, 还是不行. 配置如下:
+1 |
|
请求结果还是老样子.
4. 最终定位: http明文请求被限制了
这是我在一位老哥的帖子里发现的一个关键节点.
原来正常的http请求会被系统默认禁掉, 而且官方还不给个提示, 太坑了. 官方更推荐使用https的请求方式.
知道了问题所在, 解决问题就好了.config.json添加如下配置即可
1 |
|
注意, 3里的网络权限也要配置哈, 不然会报没有权限的错误. 不过这个还好, 官方有异常提示了, 就很好定位了.
+记一次dubbo zk集群平滑迁移的操作方案,也可以用户dubbo双注册中心的实现方案。
+pom 依赖
+1 |
|
dubbo配置:
+1 |
|
然后就能正常启动了,通过日志,会发现正常注册了两个zk中心,并且zk上也能发现注册的服务。
+http://dubbo.apache.org/zh-cn/docs/user/references/registry/zookeeper.html
+ + +JDK 19在2022年10月18日发布了, 本文就是简单介绍一下这次更新的新特性与改动.
+一句话总结, 这仍然是jdk的一个短期支持版本, 而且官方承诺,这次升级将尽可能的保证版本的向下兼容.
+Support Unicode 14.0 (JDK-8268081) : 支持Unicode14.0 .
+New system properties for System.out
and System.err
(JDK-8283620) : 新增了stdout.encoding
和 stderr.encoding
两个属性来支持标准输出和错误输出的字符集编码. 可以有效解决标准输出的乱码问题.
HTTPS Channel Binding Support for Java GSS/Kerberos (JDK-8279842) : 在https连接的时候, 支持绑定token来增强安全.
+Additional Date-Time Formats (JDK-8176706) : java.time.format.DateTimeFormatter
增加了一些新的默认格式
New Methods to Create Preallocated HashMaps and HashSets (JDK-8186958) : HashMap, HashSet新增了一些静态创建方法. 比如
+1 |
|
upport for PAC-RET Protection on Linux/AArch64 (JDK-8277204) : 通过支持ARMv8.3 的PAC功能来防御RET的攻击
+++When enabled, OpenJDK will use hardware features from the ARMv8.3 Pointer Authentication Code (PAC) extension to protect against Return Orientated Programming (ROP) attacks. For more information on the PAC extension see “Providing protection for complex software” or the “Pointer authentication in AArch64 state” section in the Arm ARM.
+
Automatic Generation of the CDS Archive (JDK-8261455) : CDS的自动打包功能
+Windows KeyStore Updated to Include Access to the Local Machine Location (JDK-6782021) : Windows新增了一些密钥储库支持访问本地位置.
+Break Up SEQUENCE in X509Certificate and X509Certificate in otherName (JDK-8277976) : 如题, 这两个方法的增强.
+++The JDK implementation of
+X509Certificate::getSubjectAlternativeNames
andX509Certificate::getIssuerAlternativeNames
has been enhanced to additionally return thetype-id
andvalue
fields of anotherName
. Thevalue
field is returned as a String if it is encoded as a character string or otherwise as a byte array, which is helpful as it avoids having to parse the ASN.1 DER encoded form of the name.
(D)TLS Signature Schemes (JDK-8280494): 增加了一套新的获取TLS签名的方法.
+Add a -providerPath Option to jarsigner (JDK-8281175) : jarsigner新增了一个选项, 用来指定备用密钥库路径
+New Options for ktab to Provide Non-default Salt (JDK-8279064) : ktab命令增加了一些选项来支持自定义’盐’值.
+++ktab -a username password -s altsalt
+
New XML Processing Limits (JDK-8270504 (not public)) : xml库解析执行增加了3个限制
++++
jdk.xml.xpathExprOpLimit
: XPath表达式中group数量的限制+
jdk.xml.xpathExprOpLimit
: XPath表达式中operator数量的限制+
jdk.xml.xpathTotalOpLimit
: XPath文件中operator总数量的限制
Locale.of()
方法替代这里主要介绍一些小的优化, 太长可以不看.
+JDK19中, 在macOS上将Metal作为图形渲染的默认配置.
+像mac或linux系统中, user.home
存在就用系统提供的配置 , 如果不存在, 则使用$HOME
的配置设置这个系统属性.
线程的ClassLoader 被处理为一个特殊的可继承的Thread-local
+java.lang.Thread 新增了一些方法, 如果现在代码继承了Thread, 可能会导致不兼容.
+++Thread.Builder模式
+Thread.isVirtual()
+Thread.threadId()
+Thread.join(Duration)
+
Double.toString(double) and Float.toString(float) 针对科学记数法的一些优化
+注解的toString方法针对常量和枚举, 做了一些优化, 输出更友好.
+HTTP签名鉴权时, MD5和SHA-1 因为不太安全默认被禁用.
+Windows 上http多代理选择的优化
+java.net.InetAddress 针对一些有歧义的IPv4输入, 精细了异常提示.
+HttpURLConnection 的默认keep alive 可配置
+FileChannel.transferFrom 返回的bytes 可能会小于预订值, 这是允许的.
+++FileChannel.transferFrom()的性能在Linux内核4.5及更高版本上得到了显著提高
+
InputStream and FilterInputStream 的mark和reset 移出了 synchronized 关键字
+Files.copy 方法支持将 POSIX
属性跨文件系统复制, 前提是两个系统都支持 POSIX
FileChannel.lock(long position, long size, boolean shared) 当size指定为0时, 表示锁定 整个文件, 无论这个文件流被扩展或继承
+java.time.chrono 新增了3个标准时间
+ForkJoinPool 和 ThreadPoolExecutor 线程池不再使用Thread.start来启动工作线程.
+正则 \b
可以匹配ASCII 字符, 像 \w
一样.
支持 CLDR Version 41
+LDAP, DNS, 和 RMI 的URL解析更加严格
+> -Dcom.sun.jndi.ldapURLParsing="legacy" | "compat" | "strict" (to control "ldap:" URLs)
+>
+> -Dcom.sun.jndi.dnsURLParsing="legacy" | "compat" | "strict" (to control "dns:" URLs)
+> -Dcom.sun.jndi.rmiURLParsing="legacy" | "compat" | "strict" (to control "rmi:" URLs)
+
+VM Tool Interface (JVM TI) 支持虚拟线程
+cpu计算时不再考虑cpu.share
, 因为会导致jvm对当前cpu使用情况的误算, 进而影响cpu使用率.
++之前的JDK版本对Linux cgroups参数“cpu.shares”使用了错误的解释。这可能会导致JVM使用的CPU少于可用,导致JVM在容器内使用时CPU资源利用率不足。
+从此JDK版本开始,默认情况下,JVM在决定各种线程池使用的线程数量时不再考虑“cpu.shares”。-XX:+UseContainerCpuShares命令行选项可用于恢复到之前的行为。此选项已被弃用,可能会在未来的JDK版本中删除。
+
此后, macOS中,相同大版本的jdk将默认安装到同一目录
+++Oracle JDK安装目录从
++
/Library/Java/JavaVirtualMachines/jdk-${VERSION}.jdk
迁移到+
/Library/Java/JavaVirtualMachines/jdk-${FEATURE}.jdk
比如: 19.0.1 and 19.0.2 版本都将安装到
+/Library/Java/JavaVirtualMachines/jdk-19.jdk
中
在macOS上,只有用户钥匙串中具有正确信任设置的证书才会在钥匙串类型的钥匙库中作为受信任证书条目公开
+RC2和ARCFOUR(RC4)算法已添加到java.security配置文件中的jdk.security.legacyAlgorithms安全属性中。当弱RC2或ARCFOUR算法用于与密钥存储中的秘密密钥条目关联的命令时,密钥工具会发出警告。
+加密算法位数增强
+++RSA, RSASSA-PSS, DH: from 2048 to 3072
+EC: from 256 to 384
+AES: from 128 to 256 (if permitted by crypto policy), falls back to 128 otherwise.
+
null
. 参考 RFC 5758 Section 3.2++TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
+TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
+SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
+TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
+SSL_RSA_WITH_3DES_EDE_CBC_SHA
+
30 修复了一个StringBuilder bug. 例如, 下面代码输出”foofoobar” 而不是 “foobarfoobar”
+1 |
|
JavaDoc生成的API文档现在提供了一个独立的搜索页面,搜索语法已得到增强,允许多个搜索词。
+JShell现在标记不建议使用的元素,并在控制台中突出显示变量和关键字。
+实际的java线程堆栈大小可能与-Xss命令行选项指定的值不同;当操作系统要求时,它可以四舍五入到系统页面大小的倍数。
+HarfBuzz: 一个开源的用于文字塑形的软件开发库,亦即用于转换Unicode文本到字形指标及方位的过程
+FreeType: 同HarfBuzz一样, 也是一个开源字体库. 它是一个用C语言实现的一个字体光栅化库。它可以用来将字符栅格化并映射成位图以及提供其他字体相关业务的支持
+Preview(预览): 功能已经基本完整, 可以试用了. 可以简单理解为beta公测版. 来源于 JEP12
+++功能以预览版的形式发布,以收集有关它们的反馈而不承诺保持其向后兼容性——这意味着鼓励每个人尝试它们,但同时不鼓励在生产中使用它们。
+预览功能不是开箱即用的,为了访问它们,需要使用*–enable-preview*编译器标志。
+
Incubator(孵化) : 实验性 API已经到了一定阶段, 已经计划开发出一整套完整的功能.以独立模块的形式发布. 来源于 JEP11
+Experimental(实验) : vm级的早期功能, 不稳定, 功能不完整. 实验性质.
+++实验性功能代表(主要是)VM 级功能的早期版本,这些功能可能是有风险的、不完整的,甚至是不稳定的。在大多数情况下,需要使用专用标志启用它们
+出于比较的目的,如果一个实验功能被认为是 25%“完成”,那么一个预览功能应该至少 95%“完成”。
+预览,孵化,实验三者的关系大致: 实验 => 孵化 => 预览 => 合并jdk主体功能.
+
JEP : JDK Enhancement Proposal , jdk增强建议. 也就是我们常说的jdk新特性的来源. JEP大全
+最近, mac推送了新版本系统, macOS 13 ventura版本. 然后忍不住升级了. 升级过程不赘述, 等着就行了.
+升级完成后, 打开idea, 发现git
无法使用了, 提示如下:
1 |
|
不用说, 一定是升级导致的问题.
+经过一番排查, 发现是mac的开发工具Xcode需要升级到14才行. 可以使用如下命令解决, 实测有效
+1 |
|
基本到这里, 问题就解决了.
如何还不行, 可以自行到官网下载软件安装即可.
没事别乱升级系统
+想提供一个web接口, 入参就是es的查询DSL json. 然后服务器用这个DSL透传转发到Elasticsearch服务期, 将数据返回给接口.
简单来说, 就是想在自己服务器上实现类似kibana上查询Elasticsearch数据的功能.
比如, 我们正常的查询DSL如下:
+1 |
|
如果是单纯的查询, 官方提供了一个透传查询DSL的实现类 QueryBuilders.wrapperQuery()
, 可以通过这个QueryBuilders来实现我们自定义DSL的透传.
让我们先看看官方怎么说
++A query that accepts any other query as base64 encoded string.
+
This query is more useful in the context of the Java high-level REST client or transport client to also accept queries as json formatted string. In these cases queries can be specified as a json or yaml formatted string or as a query builder (which is a available in the Java high-level REST client).
官方示例:
+1 |
|
由此可见, 官方是将wrapper当做一个关键字类型来处理的, 将我们的查询DSL转成base64,交给es服务器解析处理.
好,下面就是我们将上述DSL 通过QueryBuilders.wrapperQuery()
处理后的sql.
1 |
|
大家可以找个base64解码网站处理一下, 发现和我们的DSL一样. 然后用官方的方式处理下
+1 |
|
唉, 发现查询报错了, 为什么呢?
其实仔细分析一下,很容易发现问题. 我们将base64还原,看看最后的查询DSL的样子
+1 |
|
发现没, query里套query, 明显查询有问题. 其实这里es解析的时候, 发现wrapper关键字, 就会将下面的query查询替换原始query.
+所以我们要将wrapper的不是原始完整的DSL,而是query下面的DSL. 我们将bool关键字的JSON重新单独拿出来处理下
+1 |
|
搞定, 把上述DSL放到任何一个es的web客户端查询, 都有效.
+关于聚合的功能, 官方没有提供现成的解析方法.
不过我觉得, 既然es的web端可以调用任意DSL, 通过java肯定能, 大不了就跳过RestHighLevelClient, 在RestLowLevelClient上想办法.
// todo 等我找到合适的办法再补充.
+1 |
|
1 |
|
nginx上面没有显示✓,表示nginx没有安装
1 |
|
如图所示,显示了nginx的版本. 也明确说明了系统没有安装nginx
3. 安装nginx
1 |
|
安装nginx成功.
nginx默认监听8080端口, 配置文件路径也给出来了 /opt/homebrew/etc/nginx/nginx.conf
同时也给了启动nginx的命令
1 |
|
4.验证安装成功
此时,nginx命令已经生效了.
1 |
|
当然, 也可以执行下面命令查看更详细的信息
+1 |
|
至此,nginx已经安装成功了. 可以通过去配置文件修改相应配置, 启动nginx了
+1 |
|
启动成功了,此时可以通过 http://localhost:8080 验证一下
1 |
|
好的, 接下来尽情使用自己的nginx服务器吧!
+ + +让我们从0开始搭建一个很酷的内网穿透功能.
+作为一名程序员,谁不想拥有一个自己的云服务器呢. 但是当我们看了各大云服务器厂商的价格后, 不禁陷入了沉思:我好像对云服务器的诉求也没那么大!
等我们回家看到家里闲置的老旧电脑时,又会想,我为啥要用云服务器呢,家里的电脑改吧改吧, 不也就可以用了. 我们又不用做大流量的网站,
最多搞搞博客,跑跑爬虫啥的,只要能让我们可以随时在外面访问我家里的电脑,控制家里的电脑就行了.
好的,说干就干, 经过一番调研, 我们发现, 好像没自己想象的那么简单. 一个最直接的问题就是, 我们没有一个自己的ip,因为众所周知的ipv4资源耗尽的问题,
我们家里实际上网的ip其实不是固定的, 而是多家用户共享一个ip地址,也就是NAT(Network Address Translation)技术. 所以,从外网我们是无法直接访问家里的电脑的.
这个问题怎么解决呢? 就是我们今天要聊的话题, 内网穿透技术.
内网穿透,也即NAT穿透,进行NAT穿透是为了使具有某一个特定源IP地址和源端口号的数据包不被NAT设备屏蔽而正确路由到内网主机.
大致流程如图:
参考官方安装frp说明, 下载合适自己服务器的frp版本, 解压到自定义的frp服务目录.
因为是服务器部分, 我们直接启用frps 即可启动成功frp的服务部分.
1 |
|
配置部分参考官方配置样例. 我们demo样例只使用ssh基本服务, 就用了默认配置.
+1 |
|
为了方便, 我们一般会将frps配置成系统重启自动加载. 这样我们就不用担心服务器系统重启导致内网穿透服务掉线了.
方法如下:
systemd
, 如果不支持请安装1 |
|
1 |
|
写入下面内容
+1 |
|
1 |
|
我家里的电脑是windows10, 当然, Linux更好. 本次内网穿透的目的是可以通过远程ssh控制我的家用电脑.
+windows系统作为家用本, 对网络权限限制的比较多, 需要我们额外做一些操作.
a. 安装windows的ssh服务端
win10默认是不支持ssh的服务端的, 需要我们安装相应的OpenSSH服务器.
操作步骤: 设置->应用-> 应用与功能 -> 可选功能 -> 添加OpenSSH服务器
最后开启ssh服务,以管理员身份打开cmd终端,执行如下命令
1 |
|
b. 防火墙可能会将frpc识别为危险文件, 记得去防火墙那里将工具恢复.
+从官网下载下frpc客户端后,且成功从防火墙那里逃生, 就可以继续后面的操作了
首先同样先修改配置文件
1 |
|
然后打开cmd终端使用命令行的方式启动frpc工具
最后就可以在外网使用ssh命令访问你的系统了
1 |
|
frp 会将请求 x.x.x.x:6000 的流量转发到内网机器的 22 端口
+windows的默认终端为cmd, 很多命令不与linux兼容. 所以建议将windows的默认终端改为powerShell, 更方便.
+1 |
|
按照JDK每半年发布一次版本的节奏, JDK20在2023年3月发布了, 本文就是简单介绍一下这次更新的新特性与改动.
一句话总结, 这仍然是jdk的一个短期支持版本,此版本包括7个 JEP(jdk增强建议),以及数百个较小的功能增强和数千个错误修复.
+jdk20 工具箱全集
这里主要介绍一些小的优化, 太长可以不看.
+equals
改为 ==
##
)将元素名称与URI片段分开。HarfBuzz: 一个开源的用于文字塑形的软件开发库,亦即用于转换Unicode文本到字形指标及方位的过程
+FreeType: 同HarfBuzz一样, 也是一个开源字体库. 它是一个用C语言实现的一个字体光栅化库。它可以用来将字符栅格化并映射成位图以及提供其他字体相关业务的支持
+Preview(预览): 功能已经基本完整, 可以试用了. 可以简单理解为beta公测版. 来源于 JEP12
+++功能以预览版的形式发布,以收集有关它们的反馈而不承诺保持其向后兼容性——这意味着鼓励每个人尝试它们,但同时不鼓励在生产中使用它们。
+预览功能不是开箱即用的,为了访问它们,需要使用*–enable-preview*编译器标志。
+
Incubator(孵化) : 实验性 API已经到了一定阶段, 已经计划开发出一整套完整的功能.以独立模块的形式发布. 来源于 JEP11
+Experimental(实验) : vm级的早期功能, 不稳定, 功能不完整. 实验性质.
+++实验性功能代表(主要是)VM 级功能的早期版本,这些功能可能是有风险的、不完整的,甚至是不稳定的。在大多数情况下,需要使用专用标志启用它们
+出于比较的目的,如果一个实验功能被认为是 25%“完成”,那么一个预览功能应该至少 95%“完成”。
+预览,孵化,实验三者的关系大致: 实验 => 孵化 => 预览 => 合并jdk主体功能.
+
JEP : JDK Enhancement Proposal , jdk增强建议. 也就是我们常说的jdk新特性的来源. JEP大全
+jdk20官方文档
jdk20新特性文档
jdk20升级指南
jdk20安装指南
jdk20的开发计划
jdk20虚拟机简介
jdk20垃圾回收机制
jdk20安全机制
当我们写的bash脚本,有的时候需要分享给别人用,但是又不太想让别人看到实现逻辑。没到这个时候,就想着要是能给这个脚本加密一下就好了。
仅加密脚本的内容,又不影响脚本的运行,多好。要知道在计算机的世界,只有想不到,没有做不到!
你不是第一个遇到这个问题的人,而且有比你更有行动力的人把这个问题解决了。
今天我们就介绍两个工具来解决这个问题。
+mac系统上自带的一个工具,可以在mac系统上将一个可执行的bash脚本加密。
使用方式:
1 |
|
我们以hello.sh 脚本为例。
+1 |
|
操作:
+1 |
|
但是我们把hello.sh放到其他系统上执行时,会发现失败。但是在mac本地系统执行时,还是可以的.
+shell(bash) 脚本编译指令。mac系统原生不支持,可以通过下面方式安装。
+1 |
|
使用方式:
+1 |
|
同样以hello.sh脚本为例
+1 |
|
同gzexe一样,加密后的脚本一样不支持跨平台运行,即使是编译成c语言的二进制脚本。因为可执行文件会依赖不同系统的动态链接库。
+今天开发遇到一个奇怪的问题,前端的form表单数据提交的时候,数据量比较小的时候,内容能正常保存。
+当数据量达到1.6M的时候,后端就开始报npe异常,参数字段就开始接收不到了。
+经过排查,发现这个和编程无关。
springboot 默认集成了tomcat容器,tomcat对form表单的大小有限制,默认2M。
知道了问题所在,解决起来就很简单了。
方案1: 增加tomcat的form表单容量配置
1 |
|
方案2:去掉tomcat的form容量配置
+1 |
|
spring路径匹配,在spring5以后,有了AntPathMatcher 和 PathPattern 两种方式。
+PathPattern是在spring 5 以后新增的。本文就介绍 AntPathMatcher 和 PathPattern 的异同,以及这两者的使用场景和方式。
+AntPathMatcher在1.x版本就存在了,在spring5以前,一直作为spring的路径匹配器存在。
+AntPathMatcher在spring中是作为工具类存在的,所以直接new
对象即可。
PathPattern出现在spring5.x版本中, 旨在用于替换掉较为“古老”的AntPathMatcher。
+PathPattern是spring一个内部类,不能通过public的方式来创建。不过spring 官方提供了一个parser
开提供PathPattern的默认实例。
1 |
|
AntPathMatcher 官方文档:
+1 |
|
PathPattern 官方文档
+1 |
|
可见,AntPathMatcher 和 PathPattern 的匹配规则是差不多的,都支持?
*
**
已经部分正则匹配。
但PathPattern增加了路径匹配并提取结果的能力,同时为了消除歧义,不再支持路径中间的**
匹配,其他一样。
demo:
+*匹配
+1 |
|
PathPattern 路径提取
+1 |
|
官方说PathPattern 比 AntPathMatcher 更快一些,所以咱们验证一下:
+测试代码
+1 |
|
分别跑了5次。
AntPathMatcher 平均耗时:392ms
PathPattern 平均耗时:323.4ms
官方所言不虚!
开发过程中的一些信息带到了线上,要及时修复,不如坏人就顺藤摸瓜找来了。
+以下是漏洞的一些内容:
+信息泄露主要是由于开发人员或运维管理人员的疏忽所导致。如未及时删除调试页面、未关闭程序调试功能、未屏蔽程序错误信息、备份文件未删除、数据库备份文件未删除、未屏蔽敏感数据信息等多个方面所导致的不同严重程度的信息泄露。攻击者可通过所掌握的信息进一步分析攻击目标,从而有效发起下一步的有效攻击。
+常见的问题有:
+关闭相关接口的线上权限,或增加权限控制
+1,swagger 关闭入口的方式
方法a: 使用注解 @Value() 推荐使用, 然后在不同的配置文件里指定开关
1 |
|
方法b: 使用注解@Profile({“dev”,“test”}) 表示在开发或测试环境开启,而在生产关闭
其实ab 的方式是一样的,都是不同的环境开关不同。
2, druid关闭监控台的方式
+1 |
|
3, 一些其他接口增加权限认证
通过spring的httpSecurity 增加路径鉴权
1 |
|
项目中下载方法没有安全校验,导致脚本注入,可以下载服务器任意路径下的文件。
对此总结了一下修复办法,方便大家参考,修复。
以下是漏洞的一些内容:
+任意文件下载或读取漏洞主要是由于应用系统在提供文件下载或读取功能时,在文件路径参数中直接指定文件路径的同时并没有对文件路径的合法性进行校验,
导致攻击者可通过目录跳转(..\或../)的方式下载或读取到原始指定路径之外的文件。
攻击者最终可通过该漏洞下载或读取系统上的任意文件,如数据库文件、应用系统源代码、密码配置信息等重要敏感信息,造成系统的敏感信息泄露。
对存在文件下载或文件读取功能的页面进行测试,查看所提交的参数中是否包含文件名或文件目录,尝试提交参数值查看是否可下载或读取其他目录的文件内容;
+如:
+原始下载功能路径为
http://www.example.com/donwload.jsp?filename=test123456789.pdf
其中文件路径参数为filename,通过../对路径进行跳转尝试下载其他目录下的文件,修改filename参数为../../WEB-INF/web.xml尝试下载JSP网站的配置文件(测试过程中需适当增加../跳转字符串);如提交
http://www.example.com/donwload.jsp?filename=../../WEB-INF/web.xml
查看是否成功下载web.xml文件。
+另一种情况如下:
http://www.example.com/donwload.jsp?filepath=uploadfile&filename=test123.pdf
该功能通过filepath以及filename指定下载目录以及下载文件名,可修改filepath参数值进行路径跳转,
同时修改filename值指定文件名;如提交
http://www.example.com/donwload.jsp?filepath=../../WEB-INF&filename=web.xml
查看是否成功下载web.xml文件。
+知道了问题,一般可以通过在代码层级增加关键字符过滤来修复。
比如:
1 |
|
或者,在服务部署的时候,通过下面的目录固化部署路径,令web请求不能逃逸到整个服务器。
+项目中遇到http host头攻击的解决办法,支持nginx,apache 两种办法。
+以下是漏洞的一些内容:
+为了方便的获得网站域名,开发人员一般依赖于HTTP Host header。例如,在php里用_SERVER[“HTTP_HOST”]。
但是这个header是不可信赖的,如果应用程序没有对host header值进行处理,就有可能造成恶意代码的传入。
web应用程序应该使用SERVER_NAME而不是host header。 在Apache和Nginx里可以通过设置一个虚拟机来记录所有的非法host header。
在Nginx里还可以通过指定一个SERVER_NAME名单,Apache也可以通过指定一个SERVER_NAME名单并开启UseCanonicalName选项。
本质上就是在服务侧鉴定请求来源, 非法来源直接过滤掉。
+打开nginx的网站配置文件,一般为 /etc/nginx/conf.d/xxx.conf
添加如下配置
1 |
|
重启nginx即可
+1 |
|
打开Apache的配置 , 一般为 /conf/httpd.conf
添加如下配置:
1 |
|
重启apache即可
+CORS跨域的逆向操作,如何收回放开的跨域权限。
+以下是CORS漏洞的一些内容:
+Web服务端CORS(跨域资源共享)错误配置,无法正确验证Origin头,任何网站都可以发出使用用户凭据发出的请求,并读取对这些请求的响应,信任任意来源可以有效地禁用同源策略,从而允许第三方网站进行双向交互,易导致敏感信息泄露
+ +需要修复两处:
1,代码级别(必修)
程序默认是禁止跨域的,也可以去掉相关逻辑。
1 |
|
2,nginx服务
增加相关头配置
1 |
|
恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页时,嵌入其中Web里面的Script代码会被执行,进而窃取到管理员用户权限等。
+一段简易的字符过滤程序
+1 |
|
1 |
|
在wrapper里过滤参数
+1 |
|
xssUtil
+1 |
|
前后端组合使用,可解决xss问题
+Jdbc反序列化漏洞发生在后台代码使用mysql-connector组件连接数据库且攻击者可控制连接参数的情况下。攻击者可以构造恶意的mysql数据库使服务器去访问链接它从而触发反序列化漏洞导致命令执行
+mysql 连接参数风险点:autoDeserialize,statementInterceptors
+1 |
|
1 |
|
在我们开发项目的时候,经常会遇到将一个git仓库迁移到一个新的git仓库的情况。 这个时候很多人会把原始的仓库最新代码复制到新库目录下,然后force push即可。
+但是这样会产生一个问题,就是新库会把我们的历史提交记录等信息全部丢失了,当做一个全新的仓库来处理,这样给我们后续的问题追踪回溯带来了一些问题。
+下面我们介绍一下git优雅迁库的三种方式。
+1 |
|
1 |
|
镜像库是裸库的一种简写,等同于:
+1 |
|
由此可知,裸库方式clone下的是git仓库的基本信息。镜像库不仅把基本信息clone下来了,还拉去了最新的分支代码。
+1 |
|
数据库升级,用docker容器安装oracle11g数据库,应用连接的时候报ORA-01882错误
+1 |
|
环境:
+问题本质上是java应用程序启动时, 找不到本地系统的时区环境变量导致。 我们只有把时区环境变量配置上就可以了。
+解决办法有二:
+1 |
|
1 |
|
如果是docker的话,将宿主机改为上述配置后,docker容器启动(run)增加挂载配置就可以了
+1 |
|
最近在搞一个比较复杂的crontab 定时任务, 核心思路就是通过crontab触发一个bash脚本,调用docker内的一个指令,来实现docker积压日志的删除清理操作。
+然后问题就来了:我通过命令行调用脚本,能够正常执行,但是通过crontab,脚本就死活调用不成功。
+以下就是记录的排查crontab调用不成功的过程:
+crontab上配置定时任务,把该有的环境变量都配置上,脚本的开头也加上了source /etc/profile
为了方便排查,调度改为2分钟一次
1 |
|
不出意外的话,定时任务调度失败了。
+通过tail -f /var/log/cron
去cron调度日志里查看调用情况:
1 |
|
日志表示,我们的cron任务调度了,但是没有成功。 失败原因本来想通过mail发给当前root用户的,但发送失败了,原因是获得了一个失败状态吗。
+如何解决这个mail发送失败的问题呢?
+去/etc/postfix/main.cf
配置文件,搜索inet_interfaces,改成如下配置
1 |
|
重启postfix
+1 |
|
此时,我们就可以收到cron调度失败的系统通知邮件了。
+tail -f /var/spool/mail/root
查看邮件信息:
1 |
|
会发现,失败的原因是我们的脚本调度器不是TTY(终端),所以失败了。
+++注意,问题定位后记得把postfix配置恢复原状
+
找到日志了,任务调度失败的原因也找到了,分析一下我们的任务指令,发现我们在脚本调度命令里存在选项 docker exec -it
配置。
问题就在这个-it
上面了,这要求我们开启一个交互式终端来控制docker指令,而crontab调度器明显不是一个终端,所以失败了。
把命令行里的-it
去掉,改为-d
后端调用就可以了。以下是最终版配置
1 |
|
最终指令改了两点:
+-it
改为-d
, 解决crontab调度失败的问题。>/dev/null 2>$1
配置,这是为了取消任务调度的系统日志产生,防止系统日志爆炸,也可以不加。授之以渔不如授之以渔, 出现问题不可怕,只要我们掌握了一些解决问题的方法论, 任何问题都能迎刃而解。
+在项目中经常使用swagger来作为项目接口的自动化api文档,尤其是springboot项目,一般都是通过springfox-boot-starter
来集成swagger的使用。springfox-boot-starter
在2.x时代用着很方便,但是随着项目的发展,尤其是spring项目的不断推出新版本,springfox-boot-starter
在3.0版本就已经明显存在兼容性问题了,而且已经3年多没维护了,所以寻找替代品是一种虽然不紧迫但很必须的事项了。
而springdoc-openapi就是springfox-boot-starter的替代品,且获得了swagger的官方支持,算是springfox在springboot高版本的天热替代品。
本文就介绍springdoc-openapi的集成与常见问题。
+如果你的项目是springboot2.x版本,请使用springdoc-openapi的1.x版本。
如果你的项目是springboot3.x版本,请使用springdoc-openapi的2.x版本。
本文案例的环境是:
+使用idea创建一个新的maven springboot项目,添加如下依赖
+1 |
|
创建一个Controller
+1 |
|
然后就可以直接启动了,不用任何配置,就可以看到swagger的界面了。
swagger默认地址: http://localhost:8080/swagger-ui/index.html#/
如果你不想使用默认的配置,可以通过配置文件修改相关配置。比如我通过下面配置对swagger的界面进行了微调。
+1 |
|
因为修改了swagger页面的访问地址,所以我们的访问地址变成了:
http://localhost:8080/v3/swagger-ui/index.html#/
效果如图:
如果我们想要更自有的配置,可以通过自定义配置类来修改相关配置。
+1 |
|
还是默认访问地址:http://localhost:8080/swagger-ui/index.html#/
除了将老版本的注解替换成新的注解之外,没有任何其他改的。
我在集成过程中遇到的最大的问题就是明明集成好了,但是swagger的访问页面还是swagger的默认petStore的页面,而不是我配置的controller的页面。这个问题让我排查了好久。
首先,我们我们要先了解的是,swagger-ui是一套api展示页面,他的数据是从v3/api-docs
接口获取的json数据。
所以排查问题,我们首先要看页面有没有调用这个接口(有可能我们在配置文件了将这个接口改了),再次看看这个接口有没有返回正常的json结果。
如果这这个接口返回了正常的数据,且是我们的配置的接口数据,那么问题很可能是我们的一些安全配置将swagger的相关接口给屏蔽了。
比如WebSecurityConfigurerAdapter
, 我就是在这个环节上出的问题
1 |
|
集成了application,java bean config 两种方式的demo
springdoc-demo
我们辛辛苦苦的通过github pages建好我们的博客之后。接下来可能会想要更多的人来访问我们的博客,
然后一顿操作后发现,github 把百度的爬虫屏蔽了,所以我们的网站也就不能被百度搜索了,我们的博客也就没法被百度搜索到了。
这怎么行呢? 我们的博客主要群体还是国内的程序员的,少了百度这个来源,访问量一下子少了一大半。
+作为一名程序员,我们怎么能被这个事情难倒呢?
+让我们来分析一下,github 把百度的爬虫屏蔽了,这导致了我们的博客不能被百度收录。既然百度不能爬github了,我们可以让百度去别的地方爬我们的博客呀。
+思路一开天地宽,我们可以通过CDN的机制来把我们的博客搬到CDN上,然后让百度访问CDN就可以了呀。
+可惜,试过七牛云等一些国内的CDN,发现同步网站还要备案,这个我要是有,也就不用这么麻烦的想办法同步内容到百度了,这一步是走不通了。
+最后,我发现了vercel这个网站,可以镜像我们的github 博客,而又没有屏蔽百度的爬虫,完美的解决了我的问题。
+blog.hancher.top
去域名的解析后台,添加域名的CNAME解析,记录值cname-china.vercel-dns.com
此时,通过blog.hancher.top
就可以访问vercel CDN上的静态网站了。
当然,也可以将记录值改为Vercel的自己项目的vercel.app 域名。将线路解析类型改为百度
亦可。
blog.hancher.top
域名的网站,即可以抓取我们的博客站点了。site:blog.hancher.top
来验证我们的配置是否生效,此时大概率就能搜到自己的博客文章了。因为Vercel被墙(dns污染方式), 该方法暂时失效。等待后续解决。
+在一个普通的日子里,寒澈在操作他负责的一个内部系统的时候,突然发现一个修改状态的操作不能操作了。 查看服务器日志,发现数据库连接超时了:
第一反应是数据库挂了,导致数据库连不上了。
可是,神奇的是,接下来发现数据库的查询功能还能用,但是所有的状态修改类的写操作都不能用了。 这下知道遇到麻烦了,肯定是mysql数据库的产生锁了,把所有的写操作阻塞了。
+按照现在的这个情况,mysql读没有问题,写被阻塞,出问题的锁肯定是读锁(共享锁),而且还是全局性质的,因为所有的写都被影响了。
+按照上面的思路去数据库里执行一下SHOW ENGINE INNODB STATUS
, 查看一下数据库的状态。 结果如下:
1 |
|
发现了大量的如上的事务在积压,没有执行。 与预期一致,就是某个锁导致事务阻塞了。
+注意这里不会是死锁问题,因为死锁是锁冲突,mysql会自动解决的,不会导致事务长时间阻塞。
+查询一下mysql的执行进程,看看情况:
+1 |
|
发现了如下场景
由此我们知道:
+waiting for global read lock
导致了后续写操作的进行。 后面的事务都在等待这个读锁的释放。既然发现了这个锁产生的原因,直接把这个进程kill掉就可以了
+1 |
|
binlog的进程杀掉后,后面阻塞的任务果然都没了。
+记录一下排查问题过程中常用的sql
+1 |
|
发现问题,解决问题,避免问题才是我们排查问题最终要的目的。
+我们这个问题是flink cdc 数据的时候导致的,后续的解决办法很简单:
第一: 以后flink 不要cdc主库了,避免对业务产生影响。
第二:flink cdc 一定要做好异常拦截处理,我们这就是因为异常导致fink退出了,无法释放锁
第三:如果可以的话,尽量使用flink的无锁cdc版本
global read lock
是怎么产生的当mysql在dump整个数据库的时候,会对数据库执行一个FLUSH TABLES WITH READ LOCK
命令来加锁,这个命令会停止对所有表的写操作,并允许读操作继续。
等备份完成,会使用UNLOCK TABLES
来解锁,
最近在一个项目上要求在linux服务器上安装杀毒软件,然后开启系统扫描,然后删除病毒。
+然后找了找了一些资料,比较常用的有ClamAV 和 Comodo, 因为ClamAV开源,且使用比较广泛,最终选择了ClamAV。
+我们的服务器是centos7.9, 下面就是在CentOS上安装ClamAV的过程,比较简单。
+以下是在 CentOS 上安装 ClamAV 步骤:
+ClamAV 通常通过 EPEL(Extra Packages for Enterprise Linux)仓库提供,因此首先需要确保 EPEL 仓库已安装。
+如果失败可以考虑使用阿里云的源 yum 仓库更换阿里云
+1 |
|
安装 EPEL 仓库后,你可以通过以下命令安装 ClamAV:
+1 |
|
clamav
包含命令行工具(如 clamscan
)。clamav-update
是更新病毒库所需的工具(即 freshclam
)。clamd
是clam的系统服务。验证安装是否成功:
+1 |
|
安装完成后,首先需要更新 ClamAV 的病毒库,以确保它能识别最新的恶意软件。运行以下命令:
+1 |
|
freshclam
会从 ClamAV 的服务器下载并更新病毒库,如果超时,等一会再多试几次,我试了3次成功了。
1 |
|
如果你希望使用 ClamAV 的守护进程(clamd
),你需要启动它。守护进程会提高扫描效率,特别是对于较大的文件。
1 |
|
放开如下的注释
+1 |
|
1 |
|
1 |
|
启动 clamd
服务:
1 |
|
设置 clamd
在系统启动时自动启动:
1 |
|
你可以使用 clamscan
执行病毒扫描。以下是一些常见的用法:
扫描单个文件:
+1 |
|
扫描整个目录:
+1 |
|
扫描并删除感染文件:
+1 |
|
扫描并显示详细信息:
+1 |
|
将结果输出到文件:
+1 |
|
结果:
+1 |
|
如果需要,你可以配置 ClamAV 来调整其行为。ClamAV 的配置文件位于:
+clamd
配置文件:/etc/clamd.d/scan.conf
freshclam
配置文件:/etc/freshclam.conf
你可以编辑这些配置文件,调整日志文件路径、扫描选项等。例如,编辑 clamd
配置文件来启用详细日志:
1 |
|
修改以下内容:
+1 |
|
为了确保 ClamAV 能及时识别最新的威胁,你需要定期更新病毒库。你可以通过设置 cron 任务 来自动更新病毒库。
+编辑 crontab
文件:
1 |
|
添加以下行来每天更新病毒库(此例为每天凌晨 2 点):
+1 |
|
yum
安装 ClamAV。freshclam
更新病毒库。clamd
守护进程(可选)。clamscan
执行病毒扫描。通过这些步骤,你可以在 CentOS 上成功安装和配置 ClamAV,帮助你扫描和清理系统中的病毒和恶意软件。
+俗套的现象:买了一个西部数据的移动硬盘,然后又买了一个mac电脑,将硬盘插入到电脑后发现硬盘里的数据可以读,但是无法将电脑里的文件写入。
+其实本质上的原因就是 wb的硬盘是ntfs格式的, mac电脑不支持ntfs格式的读写.
+本人电脑:macbook pro m1版
+系统版本:macos 14
+如果为了保持和windows系统的兼容性,wb的硬盘必须使用ntfs格式,那么通过安装ntfs转换软件也能实现。mac应用商店里有很多这种软件,但是要收费。
+应用商店之外常用的软件就是 uxera
和Paragon
,也要收费。
后来找了好久,在西部数据的官方帖子上找到一个免费实现的法子,原来西部数据官方也提供了一套mac版读写数据的驱动。
+以下是官方帖子的链接:
Steps to Install Paragon NTFS Driver for macOS for WD and SanDisk Professional
下载驱动
Paragon_NTFS_Driver_for_Mac
解压后双击软件开始安装。
选择:安装ntfs for mac
然后接着按照步骤一直下一步就可以了
在最后一步,会要求在系统配置里代开可信任权限,关机重启。
需要注意的是,这里不仅仅是要求在mac的系统安全设置里打开第三方软件信任项,还要在系统的安全模型里开启对第三方软件的信任项。
如果以前没有修改过安全模型下的配置,大概率会提示需要在“恢复”环境中修改安全性设置。
+这个需要在电脑关机后,长按触控ID或电源按钮就能进入安全配置环境,选择设置配置,然后在上面的菜单选择里找到安全配置相关菜单,勾选信任第三方软件修改系统内核就可以了(因为是安全模式,没有截图,按照这个步骤找就可以了)。
+如果对电脑安全有洁癖的用户,这一步操作要慎重,也可以转投付费阵营。
+因为网络的原因,上面的驱动可能是下载失败,这里提供一个备用地址:
Paragon_NTFS_Driver_for_Mac.zip 17.0.246 (访问密码: 45td)
2024年已经过去,2025年已经到来。
+在过去的一年了,因为经历了很多重大的事情,比如换工作、比如结婚、再比如公众号等原因,导致这个博客更新的频率比较慢。
+现在,2025年了,新的开始,我也打算重新恢复博客的更新计划,并制定一些规划,既是我这一年努力的方向,也方便在2025年末的时候回来复盘。
+发现我就是一个喜欢折腾的人
+ + +Hexo 是一个快速、简洁且高效的博客框架(使用NodeJs)。 Hexo 使用 Markdown(或其他标记语言)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。
+我安装的是2025年1月最新lts版本的nodejs: v22.12.0
1 |
|
因为我的nodejs是最新版本,所以我的hexo版本是也是当前最新版本: 7.3.0
选择一个你想创建项目代码的位置,执行以下命令创建Hexo博客网站:
+1 |
|
然后我们就得到了一个默认的博客项目my_blog
,其中的package.json文件如下:
1 |
|
到了这一步,我们就可以启动最原始版本的项目了:
+1 |
|
然后访问 http://localhost:4000
就可以看到默认的博客了。
官方给我们创建了一个hello world
的博客. 我们可以参考这个来写我们自己的博客了。
常用的hexo命令参考这里
+当然这里可以使用任何你喜欢的主题,安装模式大同小异。
+1 |
|
1 |
|
1 |
|
创建成功后修改 /source/about/index.md,添加 layout 属性,。
+1 |
|
++ +layout: about 必须存在,并且不能修改成其他值,否则不会显示头像等样式。
+
1 |
|
原先我的所有的图片资源都是在项目的根目录下的 /images
路径下,然后在文章里通过markdown的图片引用方式引用。
迁移到hexo后,只需将images
目录 移动到 source
路径下,其他的都不用动,图片就可以自动解析了。
因为hexo 会将source目录下的所有文件都移动到网站的根路径下,所以图片的最终引用地址是一样的。
+通用hexo配置按照如下配置修改 _config.yml
文件:
1 |
|
fluid主题配置特性配置修改如下文件_config.fluid.yml
, 会根据主题配置自动选择相应主题的配置。
而且这里的配置优先级最高,会覆盖_config.yml
配置。
1 |
|
我是用的私有项目,然后通过github action的方式来自动编译部署github pages的, 具体可以参考我的历史文章,有记录。
+这里要做的是在github 上重新配置hexo环境和 打包脚本。
+进入本地项目的 .github/workflows
目录下,删除以前的workflow文件,然后新建一个文件,文件名随意,内容如下:
1 |
|
如果不是私有项目部署的话,使用官方的配置即可。官方配置如下:
+1 |
|
然后就可以到 hanchers.github.io 仓库下查看博客内容是否更新成功了。
+如果博客内容更新了,就可以安装你的博客地址去访问博客了。
+使用的是beaudar插件, 在更新到hexo-fluid 博客架构后仍然可以使用,
但是需要代码侵入式改造,不如fluid适配的评论体系更优雅,所以这次我们尝试一些其他的评论插件。
评论插件的挑选原则:
+根据以上原则,我们对fluid官方支持的评论插件进行了筛选与评测:
+基于 GitHub Issues.博客的每次评论都会在github主站上生成一个issue,评论内容会以markdown格式保存在issue的评论区。
参考以前的文章:beaudar评论插件安装
基于 GitHub Issues. 通beaudar一样。
+这种方式完全免费,且适合那些通过github pages 部署的博客。
+_config.yml
里撇嘴gitalk的配置1 |
|
效果如图,然后我们就可以评论了
同样也是基于GitHub Issues实现,原理通上面的一样
+同beaudar一样,点击这里在github上安装应用。
+选择你的github博客仓库。
安装好后,在_config.yml
里配置utterances的配置即可
1 |
|
效果:
giscuss是利用 GitHub Discussions 实现的评论系统。
+同上级几个评论插件不同的是,前面的是利用的GitHub Issues,而giscuss是利用GitHub Discussions。
giscus 加载时,会使用 GitHub Discussions 搜索 API 根据选定的映射方式(如 URL、pathname、title 等)来查找与当前页面关联的 discussion。如果找不到匹配的 discussion,giscus bot 就会在第一次有人留下评论或回应时自动创建一个 discussion。
访客如果想要评论,必须按照 GitHub OAuth 流程授权 giscus app 代表他发布,或者可以直接在 GitHub Discussion 里评论。你可以在 GitHub 上管理评论。
+启用github仓库的Discussions功能
+点击这里安装giscus应用,然后选择你开启讨论模块的仓库
+点击这里按下图配置仓库名称和你的评论所有类型
giscus 会自动从github获取对应的id
拿到id后,我们就可以在_config.yml
配置了
1 |
|
效果:
Q:前面都配置好后,发现评论模块加载不出来,打开F12,发现请求接口报错了:Discussion not found
.
A: 很多人都遇到过这个问题,解决办法就是去仓库下手动加一条评论,等一会就好了。个人理解这里是github延迟导致的。
至此,基于github,完全免费的评论插件都比较完了,接下来就是需要第三方网站托管数据或者自建服务托管数据的评论插件了。
+来必力,没想到是一个韩国的网站,用来做评论数据托管等内容,需要注册。
+来必力各版本的功能比较(2025-01-09):
注册并登录成功后,在这个页面选择city模块,然后点击“安装使用”
安装成功后,会得到一个脚本注入代码。
将上面的data-uid复制出来放到_config.yml
配置里即可。
1 |
|
效果:
但是点击后,会弹出第三方账号登录窗口。
畅言,一个国内比较老的评论插件,需要注册,可以免费存储数据。
+将上面的id和密钥复制出来,放到_config.yml
配置里即可。
1 |
|
效果:
一个简洁、安全、免费的静态网站评论系统。
+这是一款私有化部署的评论插件,可以在自己的服务器上部署,官方也提供了一整套云部署的免费方案,可以自行选择。
因为是私有化部署的方案,这就涉及到数据存储的问题,官方提供的方案是使用mongodb的免费存储服务,需要自行注册。
+无论哪一种方案都设计到mongodb数据库的申请,所以第一步就是要申请mongodb免费版。
+参考官方文章,写的很详细了。
+因为网速和流量的原因,我选择的是netlify做我的评论插件托管云服务。
+MONGODB_URI
,值就是mongodb 申请里获得的nodejs 连接urlhttps://xxxxx.netlify.app/
,如果看到如下内容,说明部署成功。1 |
|
_config.yml
配置twikoo了。1 |
|
效果:
基于腾讯云的twikoo部署方案, 注意因为腾讯云免费额度已经没有了,这个方案已经失效,仅供学习参考。
+折腾了两天,终于把几款评论插件验证完了。
+整体来看,有两款效果比较好,分别是twikoo和giscus。
+最后,您觉得还有什么要补充的吗?
+ + +共计 39 篇文章
+2022
+ + + +共计 39 篇文章
+2022
+ + + +共计 39 篇文章
+2022
+ + + +共计 39 篇文章
+2022
+ + + +共计 39 篇文章
+2022
+ + + +共计 39 篇文章
+2022
+ + + +共计 39 篇文章
+2023
+ + + +共计 39 篇文章
+2023
+ + + +共计 39 篇文章
+2023
+ + + +共计 39 篇文章
+2023
+ + + +共计 39 篇文章
+2024
+ + + +共计 39 篇文章
+2024
+ + + +共计 39 篇文章
+2025
+ + + +共计 39 篇文章
+2025
+ + + +共计 39 篇文章
+2025
+ + + +2024
+ + + +2023
+ + + +共计 39 篇文章
+2023
+ + + +共计 39 篇文章
+2023
+ + + +2022
+ + + +共计 39 篇文章
+2022
+ + + +共计 4 篇文章
+2023
+ + + +2022
+ + + +共计 2 篇文章
+2023
+ + + +共计 7 篇文章
+2024
+ + + +2023
+ + + +共计 2 篇文章
+2022
+ + + +共计 2 篇文章
+2023
+ + + +2022
+ + + +共计 10 篇文章
+2023
+ + + +2022
+ + + +共计 7 篇文章
+2025
+ + + +2024
+ + + +2022
+ + + +' + match_content + '...
'; + } + } + }); + if (resultHTML.indexOf('list-group-item') === -1) { + return $input.addClass('invalid').removeClass('valid'); + } + $input.addClass('valid').removeClass('invalid'); + $result.html(resultHTML); + }); + } + }); + } + + function localSearchReset(searchSelector, resultSelector) { + 'use strict'; + var $input = jQuery(searchSelector); + var $result = jQuery(resultSelector); + + if ($input.length === 0) { + // eslint-disable-next-line no-console + throw Error('No element selected by the searchSelector'); + } + if ($result.length === 0) { + // eslint-disable-next-line no-console + throw Error('No element selected by the resultSelector'); + } + + $input.val('').removeClass('invalid').removeClass('valid'); + $result.html(''); + } + + var modal = jQuery('#modalSearch'); + var searchSelector = '#local-search-input'; + var resultSelector = '#local-search-result'; + modal.on('show.bs.modal', function() { + var path = CONFIG.search_path || '/local-search.xml'; + localSearchFunc(path, searchSelector, resultSelector); + }); + modal.on('shown.bs.modal', function() { + jQuery('#local-search-input').focus(); + }); + modal.on('hidden.bs.modal', function() { + localSearchReset(searchSelector, resultSelector); + }); +})(); diff --git a/js/plugins.js b/js/plugins.js new file mode 100644 index 00000000..2a364b04 --- /dev/null +++ b/js/plugins.js @@ -0,0 +1,164 @@ +/* global Fluid, CONFIG */ + +HTMLElement.prototype.wrap = function(wrapper) { + this.parentNode.insertBefore(wrapper, this); + this.parentNode.removeChild(this); + wrapper.appendChild(this); +}; + +Fluid.plugins = { + + typing: function(text) { + if (!('Typed' in window)) { return; } + + var typed = new window.Typed('#subtitle', { + strings: [ + ' ', + text + ], + cursorChar: CONFIG.typing.cursorChar, + typeSpeed : CONFIG.typing.typeSpeed, + loop : CONFIG.typing.loop + }); + typed.stop(); + var subtitle = document.getElementById('subtitle'); + if (subtitle) { + subtitle.innerText = ''; + } + jQuery(document).ready(function() { + typed.start(); + }); + }, + + fancyBox: function(selector) { + if (!CONFIG.image_zoom.enable || !('fancybox' in jQuery)) { return; } + + jQuery(selector || '.markdown-body :not(a) > img, .markdown-body > img').each(function() { + var $image = jQuery(this); + var imageUrl = $image.attr('data-src') || $image.attr('src') || ''; + if (CONFIG.image_zoom.img_url_replace) { + var rep = CONFIG.image_zoom.img_url_replace; + var r1 = rep[0] || ''; + var r2 = rep[1] || ''; + if (r1) { + if (/^re:/.test(r1)) { + r1 = r1.replace(/^re:/, ''); + var reg = new RegExp(r1, 'gi'); + imageUrl = imageUrl.replace(reg, r2); + } else { + imageUrl = imageUrl.replace(r1, r2); + } + } + } + var $imageWrap = $image.wrap(` + ` + ).parent('a'); + if ($imageWrap.length !== 0) { + if ($image.is('.group-image-container img')) { + $imageWrap.attr('data-fancybox', 'group').attr('rel', 'group'); + } else { + $imageWrap.attr('data-fancybox', 'default').attr('rel', 'default'); + } + + var imageTitle = $image.attr('title') || $image.attr('alt'); + if (imageTitle) { + $imageWrap.attr('title', imageTitle).attr('data-caption', imageTitle); + } + } + }); + + jQuery.fancybox.defaults.hash = false; + jQuery('.fancybox').fancybox({ + loop : true, + helpers: { + overlay: { + locked: false + } + } + }); + }, + + imageCaption: function(selector) { + if (!CONFIG.image_caption.enable) { return; } + + jQuery(selector || `.markdown-body > p > img, .markdown-body > figure > img, + .markdown-body > p > a.fancybox, .markdown-body > figure > a.fancybox`).each(function() { + var $target = jQuery(this); + var $figcaption = $target.next('figcaption'); + if ($figcaption.length !== 0) { + $figcaption.addClass('image-caption'); + } else { + var imageTitle = $target.attr('title') || $target.attr('alt'); + if (imageTitle) { + $target.after(` `); + } + } + }); + }, + + codeWidget() { + var enableLang = CONFIG.code_language.enable && CONFIG.code_language.default; + var enableCopy = CONFIG.copy_btn && 'ClipboardJS' in window; + if (!enableLang && !enableCopy) { + return; + } + + function getBgClass(ele) { + return Fluid.utils.getBackgroundLightness(ele) >= 0 ? 'code-widget-light' : 'code-widget-dark'; + } + + var copyTmpl = ''; + copyTmpl += ' '; + jQuery('.markdown-body pre').each(function() { + var $pre = jQuery(this); + if ($pre.find('code.mermaid').length > 0) { + return; + } + if ($pre.find('span.line').length > 0) { + return; + } + + var lang = ''; + + if (enableLang) { + lang = CONFIG.code_language.default; + if ($pre[0].children.length > 0 && $pre[0].children[0].classList.length >= 2 && $pre.children().hasClass('hljs')) { + lang = $pre[0].children[0].classList[1]; + } else if ($pre[0].getAttribute('data-language')) { + lang = $pre[0].getAttribute('data-language'); + } else if ($pre.parent().hasClass('sourceCode') && $pre[0].children.length > 0 && $pre[0].children[0].classList.length >= 2) { + lang = $pre[0].children[0].classList[1]; + $pre.parent().addClass('code-wrapper'); + } else if ($pre.parent().hasClass('markdown-body') && $pre[0].classList.length === 0) { + $pre.wrap(''); + } + lang = lang.toUpperCase().replace('NONE', CONFIG.code_language.default); + } + $pre.append(copyTmpl.replace('LANG', lang).replace('code-widget">', + getBgClass($pre[0]) + (enableCopy ? ' code-widget copy-btn" data-clipboard-snippet>' : ' code-widget">'))); + + if (enableCopy) { + var clipboard = new ClipboardJS('.copy-btn', { + target: function(trigger) { + var nodes = trigger.parentNode.childNodes; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].tagName === 'CODE') { + return nodes[i]; + } + } + } + }); + clipboard.on('success', function(e) { + e.clearSelection(); + e.trigger.innerHTML = e.trigger.innerHTML.replace('icon-copy', 'icon-success'); + setTimeout(function() { + e.trigger.innerHTML = e.trigger.innerHTML.replace('icon-success', 'icon-copy'); + }, 2000); + }); + } + }); + } +}; diff --git a/js/umami-view.js b/js/umami-view.js new file mode 100644 index 00000000..6ee3f6a8 --- /dev/null +++ b/js/umami-view.js @@ -0,0 +1,99 @@ +// 从配置文件中获取 umami 的配置 +const website_id = CONFIG.web_analytics.umami.website_id; +// 拼接请求地址 +const request_url = `${CONFIG.web_analytics.umami.api_server}/websites/${website_id}/stats`; + +const start_time = new Date(CONFIG.web_analytics.umami.start_time).getTime(); +const end_time = new Date().getTime(); +const token = CONFIG.web_analytics.umami.token; + +// 检查配置是否为空 +if (!website_id) { + throw new Error("Umami website_id is empty"); +} +if (!request_url) { + throw new Error("Umami request_url is empty"); +} +if (!start_time) { + throw new Error("Umami start_time is empty"); +} +if (!token) { + throw new Error("Umami token is empty"); +} + +// 构造请求参数 +const params = new URLSearchParams({ + startAt: start_time, + endAt: end_time, +}); +// 构造请求头 +const request_header = { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-umami-api-key": "oZKCH3msvqt10VlXKwoJvHclmaS4bVx0", + }, +}; + +// 获取站点统计数据 +async function siteStats() { + try { + const response = await fetch(`${request_url}?${params}`, request_header); + const data = await response.json(); + const uniqueVisitors = data.uniques.value; // 获取独立访客数 + const pageViews = data.pageviews.value; // 获取页面浏览量 + + let pvCtn = document.querySelector("#umami-site-pv-container"); + if (pvCtn) { + let ele = document.querySelector("#umami-site-pv"); + if (ele) { + ele.textContent = pageViews; // 设置页面浏览量 + pvCtn.style.display = "inline"; // 将元素显示出来 + } + } + + let uvCtn = document.querySelector("#umami-site-uv-container"); + if (uvCtn) { + let ele = document.querySelector("#umami-site-uv"); + if (ele) { + ele.textContent = uniqueVisitors; + uvCtn.style.display = "inline"; + } + } + } catch (error) { + console.error(error); + return "-1"; + } +} + +// 获取页面浏览量 +async function pageStats(path) { + try { + const response = await fetch(`${request_url}?${params}&url=${path}`, request_header); + const data = await response.json(); + const pageViews = data.pageviews.value; + + let viewCtn = document.querySelector("#umami-page-views-container"); + if (viewCtn) { + let ele = document.querySelector("#umami-page-views"); + if (ele) { + ele.textContent = pageViews; + viewCtn.style.display = "inline"; + } + } + } catch (error) { + console.error(error); + return "-1"; + } +} + +siteStats(); + +// 获取页面容器 +let viewCtn = document.querySelector("#umami-page-views-container"); +// 如果页面容器存在,则获取页面浏览量 +if (viewCtn) { + let path = window.location.pathname; + let target = decodeURI(path.replace(/\/*(index.html)?$/, "/")); + pageStats(target); +} diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 00000000..d61bc264 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,245 @@ +/* global Fluid, CONFIG */ + +window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; + +Fluid.utils = { + + listenScroll: function(callback) { + var dbc = new Debouncer(callback); + window.addEventListener('scroll', dbc, false); + dbc.handleEvent(); + return dbc; + }, + + unlistenScroll: function(callback) { + window.removeEventListener('scroll', callback); + }, + + listenDOMLoaded(callback) { + if (document.readyState !== 'loading') { + callback(); + } else { + document.addEventListener('DOMContentLoaded', function () { + callback(); + }); + } + }, + + scrollToElement: function(target, offset) { + var of = jQuery(target).offset(); + if (of) { + jQuery('html,body').animate({ + scrollTop: of.top + (offset || 0), + easing : 'swing' + }); + } + }, + + elementVisible: function(element, offsetFactor) { + offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0; + var rect = element.getBoundingClientRect(); + const viewportHeight = window.innerHeight || document.documentElement.clientHeight; + return ( + (rect.top >= 0 && rect.top <= viewportHeight * (1 + offsetFactor) + rect.height / 2) || + (rect.bottom >= 0 && rect.bottom <= viewportHeight * (1 + offsetFactor) + rect.height / 2) + ); + }, + + waitElementVisible: function(selectorOrElement, callback, offsetFactor) { + var runningOnBrowser = typeof window !== 'undefined'; + var isBot = (runningOnBrowser && !('onscroll' in window)) + || (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent)); + if (!runningOnBrowser || isBot) { + return; + } + + offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0; + + function waitInViewport(element) { + Fluid.utils.listenDOMLoaded(function() { + if (Fluid.utils.elementVisible(element, offsetFactor)) { + callback(); + return; + } + if ('IntersectionObserver' in window) { + var io = new IntersectionObserver(function(entries, ob) { + if (entries[0].isIntersecting) { + callback(); + ob.disconnect(); + } + }, { + threshold : [0], + rootMargin: (window.innerHeight || document.documentElement.clientHeight) * offsetFactor + 'px' + }); + io.observe(element); + } else { + var wrapper = Fluid.utils.listenScroll(function() { + if (Fluid.utils.elementVisible(element, offsetFactor)) { + Fluid.utils.unlistenScroll(wrapper); + callback(); + } + }); + } + }); + } + + if (typeof selectorOrElement === 'string') { + this.waitElementLoaded(selectorOrElement, function(element) { + waitInViewport(element); + }); + } else { + waitInViewport(selectorOrElement); + } + }, + + waitElementLoaded: function(selector, callback) { + var runningOnBrowser = typeof window !== 'undefined'; + var isBot = (runningOnBrowser && !('onscroll' in window)) + || (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent)); + if (!runningOnBrowser || isBot) { + return; + } + + if ('MutationObserver' in window) { + var mo = new MutationObserver(function(records, ob) { + var ele = document.querySelector(selector); + if (ele) { + callback(ele); + ob.disconnect(); + } + }); + mo.observe(document, { childList: true, subtree: true }); + } else { + Fluid.utils.listenDOMLoaded(function() { + var waitLoop = function() { + var ele = document.querySelector(selector); + if (ele) { + callback(ele); + } else { + setTimeout(waitLoop, 100); + } + }; + waitLoop(); + }); + } + }, + + createScript: function(url, onload) { + var s = document.createElement('script'); + s.setAttribute('src', url); + s.setAttribute('type', 'text/javascript'); + s.setAttribute('charset', 'UTF-8'); + s.async = false; + if (typeof onload === 'function') { + if (window.attachEvent) { + s.onreadystatechange = function() { + var e = s.readyState; + if (e === 'loaded' || e === 'complete') { + s.onreadystatechange = null; + onload(); + } + }; + } else { + s.onload = onload; + } + } + var ss = document.getElementsByTagName('script'); + var e = ss.length > 0 ? ss[ss.length - 1] : document.head || document.documentElement; + e.parentNode.insertBefore(s, e.nextSibling); + }, + + createCssLink: function(url) { + var l = document.createElement('link'); + l.setAttribute('rel', 'stylesheet'); + l.setAttribute('type', 'text/css'); + l.setAttribute('href', url); + var e = document.getElementsByTagName('link')[0] + || document.getElementsByTagName('head')[0] + || document.head || document.documentElement; + e.parentNode.insertBefore(l, e); + }, + + loadComments: function(selector, loadFunc) { + var ele = document.querySelector('#comments[lazyload]'); + if (ele) { + var callback = function() { + loadFunc(); + ele.removeAttribute('lazyload'); + }; + Fluid.utils.waitElementVisible(selector, callback, CONFIG.lazyload.offset_factor); + } else { + loadFunc(); + } + }, + + getBackgroundLightness(selectorOrElement) { + var ele = selectorOrElement; + if (typeof selectorOrElement === 'string') { + ele = document.querySelector(selectorOrElement); + } + var view = ele.ownerDocument.defaultView; + if (!view) { + view = window; + } + var rgbArr = view.getComputedStyle(ele).backgroundColor.replace(/rgba*\(/, '').replace(')', '').split(/,\s*/); + if (rgbArr.length < 3) { + return 0; + } + var colorCast = (0.213 * rgbArr[0]) + (0.715 * rgbArr[1]) + (0.072 * rgbArr[2]); + return colorCast === 0 || colorCast > 255 / 2 ? 1 : -1; + }, + + retry(handler, interval, times) { + if (times <= 0) { + return; + } + var next = function() { + if (--times >= 0 && !handler()) { + setTimeout(next, interval); + } + }; + setTimeout(next, interval); + } + +}; + +/** + * Handles debouncing of events via requestAnimationFrame + * @see http://www.html5rocks.com/en/tutorials/speed/animations/ + * @param {Function} callback The callback to handle whichever event + */ +function Debouncer(callback) { + this.callback = callback; + this.ticking = false; +} + +Debouncer.prototype = { + constructor: Debouncer, + + /** + * dispatches the event to the supplied callback + * @private + */ + update: function() { + this.callback && this.callback(); + this.ticking = false; + }, + + /** + * ensures events don't get stacked + * @private + */ + requestTick: function() { + if (!this.ticking) { + requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))); + this.ticking = true; + } + }, + + /** + * Attach this as the event listeners + */ + handleEvent: function() { + this.requestTick(); + } +}; diff --git a/links/index.html b/links/index.html new file mode 100644 index 00000000..9a66a362 --- /dev/null +++ b/links/index.html @@ -0,0 +1,460 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用的是beaudar插件, 在更新到hexo-fluid 博客架构后仍然可以使用,
但是需要代码侵入式改造,不如fluid适配的评论体系更优雅,所以这次我们尝试一些其他的评论插件。
评论插件的挑选原则:
根据以上原则,我们对fluid官方支持的评论插件进行了筛选与评测:
基于 GitHub Issues.博客的每次评论都会在github主站上生成一个issue,评论内容会以markdown格式保存在issue的评论区。
参考以前的文章:beaudar评论插件安装
基于 GitHub Issues. 通beaudar一样。
这种方式完全免费,且适合那些通过github pages 部署的博客。
_config.yml
里撇嘴gitalk的配置1 |
|
效果如图,然后我们就可以评论了
同样也是基于GitHub Issues实现,原理通上面的一样
同beaudar一样,点击这里在github上安装应用。
选择你的github博客仓库。
安装好后,在_config.yml
里配置utterances的配置即可
1 |
|
效果:
giscuss是利用 GitHub Discussions 实现的评论系统。
同上级几个评论插件不同的是,前面的是利用的GitHub Issues,而giscuss是利用GitHub Discussions。
giscus 加载时,会使用 GitHub Discussions 搜索 API 根据选定的映射方式(如 URL、pathname、title 等)来查找与当前页面关联的 discussion。如果找不到匹配的 discussion,giscus bot 就会在第一次有人留下评论或回应时自动创建一个 discussion。
访客如果想要评论,必须按照 GitHub OAuth 流程授权 giscus app 代表他发布,或者可以直接在 GitHub Discussion 里评论。你可以在 GitHub 上管理评论。
启用github仓库的Discussions功能
点击这里安装giscus应用,然后选择你开启讨论模块的仓库
点击这里按下图配置仓库名称和你的评论所有类型
giscus 会自动从github获取对应的id
拿到id后,我们就可以在_config.yml
配置了
1 |
|
效果:
Q:前面都配置好后,发现评论模块加载不出来,打开F12,发现请求接口报错了:Discussion not found
.
A: 很多人都遇到过这个问题,解决办法就是去仓库下手动加一条评论,等一会就好了。个人理解这里是github延迟导致的。
至此,基于github,完全免费的评论插件都比较完了,接下来就是需要第三方网站托管数据或者自建服务托管数据的评论插件了。
来必力,没想到是一个韩国的网站,用来做评论数据托管等内容,需要注册。
来必力各版本的功能比较(2025-01-09):
注册并登录成功后,在这个页面选择city模块,然后点击“安装使用”
安装成功后,会得到一个脚本注入代码。
将上面的data-uid复制出来放到_config.yml
配置里即可。
1 |
|
效果:
但是点击后,会弹出第三方账号登录窗口。
畅言,一个国内比较老的评论插件,需要注册,可以免费存储数据。
将上面的id和密钥复制出来,放到_config.yml
配置里即可。
1 |
|
效果:
一个简洁、安全、免费的静态网站评论系统。
这是一款私有化部署的评论插件,可以在自己的服务器上部署,官方也提供了一整套云部署的免费方案,可以自行选择。
因为是私有化部署的方案,这就涉及到数据存储的问题,官方提供的方案是使用mongodb的免费存储服务,需要自行注册。
无论哪一种方案都设计到mongodb数据库的申请,所以第一步就是要申请mongodb免费版。
参考官方文章,写的很详细了。
因为网速和流量的原因,我选择的是netlify做我的评论插件托管云服务。
MONGODB_URI
,值就是mongodb 申请里获得的nodejs 连接urlhttps://xxxxx.netlify.app/
,如果看到如下内容,说明部署成功。1 |
|
_config.yml
配置twikoo了。1 |
|
效果:
基于腾讯云的twikoo部署方案, 注意因为腾讯云免费额度已经没有了,这个方案已经失效,仅供学习参考。
折腾了两天,终于把几款评论插件验证完了。
整体来看,有两款效果比较好,分别是twikoo和giscus。
最后,您觉得还有什么要补充的吗?
]]>Hexo 是一个快速、简洁且高效的博客框架(使用NodeJs)。 Hexo 使用 Markdown(或其他标记语言)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。
我安装的是2025年1月最新lts版本的nodejs: v22.12.0
1 |
|
因为我的nodejs是最新版本,所以我的hexo版本是也是当前最新版本: 7.3.0
选择一个你想创建项目代码的位置,执行以下命令创建Hexo博客网站:
1 |
|
然后我们就得到了一个默认的博客项目my_blog
,其中的package.json文件如下:
1 |
|
到了这一步,我们就可以启动最原始版本的项目了:
1 |
|
然后访问 http://localhost:4000
就可以看到默认的博客了。
官方给我们创建了一个hello world
的博客. 我们可以参考这个来写我们自己的博客了。
常用的hexo命令参考这里
当然这里可以使用任何你喜欢的主题,安装模式大同小异。
1 |
|
1 |
|
1 |
|
创建成功后修改 /source/about/index.md,添加 layout 属性,。
1 |
|
layout: about 必须存在,并且不能修改成其他值,否则不会显示头像等样式。
1 |
|
原先我的所有的图片资源都是在项目的根目录下的 /images
路径下,然后在文章里通过markdown的图片引用方式引用。
迁移到hexo后,只需将images
目录 移动到 source
路径下,其他的都不用动,图片就可以自动解析了。
因为hexo 会将source目录下的所有文件都移动到网站的根路径下,所以图片的最终引用地址是一样的。
通用hexo配置按照如下配置修改 _config.yml
文件:
1 |
|
fluid主题配置特性配置修改如下文件_config.fluid.yml
, 会根据主题配置自动选择相应主题的配置。
而且这里的配置优先级最高,会覆盖_config.yml
配置。
1 |
|
我是用的私有项目,然后通过github action的方式来自动编译部署github pages的, 具体可以参考我的历史文章,有记录。
这里要做的是在github 上重新配置hexo环境和 打包脚本。
进入本地项目的 .github/workflows
目录下,删除以前的workflow文件,然后新建一个文件,文件名随意,内容如下:
1 |
|
如果不是私有项目部署的话,使用官方的配置即可。官方配置如下:
1 |
|
然后就可以到 hanchers.github.io 仓库下查看博客内容是否更新成功了。
如果博客内容更新了,就可以安装你的博客地址去访问博客了。
2024年已经过去,2025年已经到来。
在过去的一年了,因为经历了很多重大的事情,比如换工作、比如结婚、再比如公众号等原因,导致这个博客更新的频率比较慢。
现在,2025年了,新的开始,我也打算重新恢复博客的更新计划,并制定一些规划,既是我这一年努力的方向,也方便在2025年末的时候回来复盘。
发现我就是一个喜欢折腾的人
]]>俗套的现象:买了一个西部数据的移动硬盘,然后又买了一个mac电脑,将硬盘插入到电脑后发现硬盘里的数据可以读,但是无法将电脑里的文件写入。
其实本质上的原因就是 wb的硬盘是ntfs格式的, mac电脑不支持ntfs格式的读写.
本人电脑:macbook pro m1版
系统版本:macos 14
如果为了保持和windows系统的兼容性,wb的硬盘必须使用ntfs格式,那么通过安装ntfs转换软件也能实现。mac应用商店里有很多这种软件,但是要收费。
应用商店之外常用的软件就是 uxera
和Paragon
,也要收费。
后来找了好久,在西部数据的官方帖子上找到一个免费实现的法子,原来西部数据官方也提供了一套mac版读写数据的驱动。
以下是官方帖子的链接:
Steps to Install Paragon NTFS Driver for macOS for WD and SanDisk Professional
下载驱动
Paragon_NTFS_Driver_for_Mac
解压后双击软件开始安装。
选择:安装ntfs for mac
然后接着按照步骤一直下一步就可以了
在最后一步,会要求在系统配置里代开可信任权限,关机重启。
需要注意的是,这里不仅仅是要求在mac的系统安全设置里打开第三方软件信任项,还要在系统的安全模型里开启对第三方软件的信任项。
如果以前没有修改过安全模型下的配置,大概率会提示需要在“恢复”环境中修改安全性设置。
这个需要在电脑关机后,长按触控ID或电源按钮就能进入安全配置环境,选择设置配置,然后在上面的菜单选择里找到安全配置相关菜单,勾选信任第三方软件修改系统内核就可以了(因为是安全模式,没有截图,按照这个步骤找就可以了)。
如果对电脑安全有洁癖的用户,这一步操作要慎重,也可以转投付费阵营。
因为网络的原因,上面的驱动可能是下载失败,这里提供一个备用地址:
Paragon_NTFS_Driver_for_Mac.zip 17.0.246 (访问密码: 45td)
最近在一个项目上要求在linux服务器上安装杀毒软件,然后开启系统扫描,然后删除病毒。
然后找了找了一些资料,比较常用的有ClamAV 和 Comodo, 因为ClamAV开源,且使用比较广泛,最终选择了ClamAV。
我们的服务器是centos7.9, 下面就是在CentOS上安装ClamAV的过程,比较简单。
以下是在 CentOS 上安装 ClamAV 步骤:
ClamAV 通常通过 EPEL(Extra Packages for Enterprise Linux)仓库提供,因此首先需要确保 EPEL 仓库已安装。
如果失败可以考虑使用阿里云的源 yum 仓库更换阿里云
1 |
|
安装 EPEL 仓库后,你可以通过以下命令安装 ClamAV:
1 |
|
clamav
包含命令行工具(如 clamscan
)。clamav-update
是更新病毒库所需的工具(即 freshclam
)。clamd
是clam的系统服务。验证安装是否成功:
1 |
|
安装完成后,首先需要更新 ClamAV 的病毒库,以确保它能识别最新的恶意软件。运行以下命令:
1 |
|
freshclam
会从 ClamAV 的服务器下载并更新病毒库,如果超时,等一会再多试几次,我试了3次成功了。
1 |
|
如果你希望使用 ClamAV 的守护进程(clamd
),你需要启动它。守护进程会提高扫描效率,特别是对于较大的文件。
1 |
|
放开如下的注释
1 |
|
1 |
|
1 |
|
启动 clamd
服务:
1 |
|
设置 clamd
在系统启动时自动启动:
1 |
|
你可以使用 clamscan
执行病毒扫描。以下是一些常见的用法:
扫描单个文件:
1 |
|
扫描整个目录:
1 |
|
扫描并删除感染文件:
1 |
|
扫描并显示详细信息:
1 |
|
将结果输出到文件:
1 |
|
结果:
1 |
|
如果需要,你可以配置 ClamAV 来调整其行为。ClamAV 的配置文件位于:
clamd
配置文件:/etc/clamd.d/scan.conf
freshclam
配置文件:/etc/freshclam.conf
你可以编辑这些配置文件,调整日志文件路径、扫描选项等。例如,编辑 clamd
配置文件来启用详细日志:
1 |
|
修改以下内容:
1 |
|
为了确保 ClamAV 能及时识别最新的威胁,你需要定期更新病毒库。你可以通过设置 cron 任务 来自动更新病毒库。
编辑 crontab
文件:
1 |
|
添加以下行来每天更新病毒库(此例为每天凌晨 2 点):
1 |
|
yum
安装 ClamAV。freshclam
更新病毒库。clamd
守护进程(可选)。clamscan
执行病毒扫描。通过这些步骤,你可以在 CentOS 上成功安装和配置 ClamAV,帮助你扫描和清理系统中的病毒和恶意软件。
在一个普通的日子里,寒澈在操作他负责的一个内部系统的时候,突然发现一个修改状态的操作不能操作了。 查看服务器日志,发现数据库连接超时了:
第一反应是数据库挂了,导致数据库连不上了。
可是,神奇的是,接下来发现数据库的查询功能还能用,但是所有的状态修改类的写操作都不能用了。 这下知道遇到麻烦了,肯定是mysql数据库的产生锁了,把所有的写操作阻塞了。
按照现在的这个情况,mysql读没有问题,写被阻塞,出问题的锁肯定是读锁(共享锁),而且还是全局性质的,因为所有的写都被影响了。
按照上面的思路去数据库里执行一下SHOW ENGINE INNODB STATUS
, 查看一下数据库的状态。 结果如下:
1 |
|
发现了大量的如上的事务在积压,没有执行。 与预期一致,就是某个锁导致事务阻塞了。
注意这里不会是死锁问题,因为死锁是锁冲突,mysql会自动解决的,不会导致事务长时间阻塞。
查询一下mysql的执行进程,看看情况:
1 |
|
发现了如下场景
由此我们知道:
waiting for global read lock
导致了后续写操作的进行。 后面的事务都在等待这个读锁的释放。既然发现了这个锁产生的原因,直接把这个进程kill掉就可以了
1 |
|
binlog的进程杀掉后,后面阻塞的任务果然都没了。
记录一下排查问题过程中常用的sql
1 |
|
发现问题,解决问题,避免问题才是我们排查问题最终要的目的。
我们这个问题是flink cdc 数据的时候导致的,后续的解决办法很简单:
第一: 以后flink 不要cdc主库了,避免对业务产生影响。
第二:flink cdc 一定要做好异常拦截处理,我们这就是因为异常导致fink退出了,无法释放锁
第三:如果可以的话,尽量使用flink的无锁cdc版本
global read lock
是怎么产生的当mysql在dump整个数据库的时候,会对数据库执行一个FLUSH TABLES WITH READ LOCK
命令来加锁,这个命令会停止对所有表的写操作,并允许读操作继续。
等备份完成,会使用UNLOCK TABLES
来解锁,
这怎么行呢? 我们的博客主要群体还是国内的程序员的,少了百度这个来源,访问量一下子少了一大半。
作为一名程序员,我们怎么能被这个事情难倒呢?
让我们来分析一下,github 把百度的爬虫屏蔽了,这导致了我们的博客不能被百度收录。既然百度不能爬github了,我们可以让百度去别的地方爬我们的博客呀。
思路一开天地宽,我们可以通过CDN的机制来把我们的博客搬到CDN上,然后让百度访问CDN就可以了呀。
可惜,试过七牛云等一些国内的CDN,发现同步网站还要备案,这个我要是有,也就不用这么麻烦的想办法同步内容到百度了,这一步是走不通了。
最后,我发现了vercel这个网站,可以镜像我们的github 博客,而又没有屏蔽百度的爬虫,完美的解决了我的问题。
blog.hancher.top
去域名的解析后台,添加域名的CNAME解析,记录值cname-china.vercel-dns.com
此时,通过blog.hancher.top
就可以访问vercel CDN上的静态网站了。
当然,也可以将记录值改为Vercel的自己项目的vercel.app 域名。将线路解析类型改为百度
亦可。
blog.hancher.top
域名的网站,即可以抓取我们的博客站点了。site:blog.hancher.top
来验证我们的配置是否生效,此时大概率就能搜到自己的博客文章了。因为Vercel被墙(dns污染方式), 该方法暂时失效。等待后续解决。
springfox-boot-starter
来集成swagger的使用。springfox-boot-starter
在2.x时代用着很方便,但是随着项目的发展,尤其是spring项目的不断推出新版本,springfox-boot-starter
在3.0版本就已经明显存在兼容性问题了,而且已经3年多没维护了,所以寻找替代品是一种虽然不紧迫但很必须的事项了。本文就介绍springdoc-openapi的集成与常见问题。
如果你的项目是springboot2.x版本,请使用springdoc-openapi的1.x版本。
如果你的项目是springboot3.x版本,请使用springdoc-openapi的2.x版本。
本文案例的环境是:
使用idea创建一个新的maven springboot项目,添加如下依赖
1 |
|
创建一个Controller
1 |
|
然后就可以直接启动了,不用任何配置,就可以看到swagger的界面了。
swagger默认地址: http://localhost:8080/swagger-ui/index.html#/
如果你不想使用默认的配置,可以通过配置文件修改相关配置。比如我通过下面配置对swagger的界面进行了微调。
1 |
|
因为修改了swagger页面的访问地址,所以我们的访问地址变成了:
http://localhost:8080/v3/swagger-ui/index.html#/
效果如图:
如果我们想要更自有的配置,可以通过自定义配置类来修改相关配置。
1 |
|
还是默认访问地址:http://localhost:8080/swagger-ui/index.html#/
除了将老版本的注解替换成新的注解之外,没有任何其他改的。
我在集成过程中遇到的最大的问题就是明明集成好了,但是swagger的访问页面还是swagger的默认petStore的页面,而不是我配置的controller的页面。这个问题让我排查了好久。
首先,我们我们要先了解的是,swagger-ui是一套api展示页面,他的数据是从v3/api-docs
接口获取的json数据。
所以排查问题,我们首先要看页面有没有调用这个接口(有可能我们在配置文件了将这个接口改了),再次看看这个接口有没有返回正常的json结果。
如果这这个接口返回了正常的数据,且是我们的配置的接口数据,那么问题很可能是我们的一些安全配置将swagger的相关接口给屏蔽了。
比如WebSecurityConfigurerAdapter
, 我就是在这个环节上出的问题
1 |
|
集成了application,java bean config 两种方式的demo
springdoc-demo
最近在搞一个比较复杂的crontab 定时任务, 核心思路就是通过crontab触发一个bash脚本,调用docker内的一个指令,来实现docker积压日志的删除清理操作。
然后问题就来了:我通过命令行调用脚本,能够正常执行,但是通过crontab,脚本就死活调用不成功。
以下就是记录的排查crontab调用不成功的过程:
crontab上配置定时任务,把该有的环境变量都配置上,脚本的开头也加上了source /etc/profile
为了方便排查,调度改为2分钟一次
1 |
|
不出意外的话,定时任务调度失败了。
通过tail -f /var/log/cron
去cron调度日志里查看调用情况:
1 |
|
日志表示,我们的cron任务调度了,但是没有成功。 失败原因本来想通过mail发给当前root用户的,但发送失败了,原因是获得了一个失败状态吗。
如何解决这个mail发送失败的问题呢?
去/etc/postfix/main.cf
配置文件,搜索inet_interfaces,改成如下配置
1 |
|
重启postfix
1 |
|
此时,我们就可以收到cron调度失败的系统通知邮件了。
tail -f /var/spool/mail/root
查看邮件信息:
1 |
|
会发现,失败的原因是我们的脚本调度器不是TTY(终端),所以失败了。
注意,问题定位后记得把postfix配置恢复原状
找到日志了,任务调度失败的原因也找到了,分析一下我们的任务指令,发现我们在脚本调度命令里存在选项 docker exec -it
配置。
问题就在这个-it
上面了,这要求我们开启一个交互式终端来控制docker指令,而crontab调度器明显不是一个终端,所以失败了。
把命令行里的-it
去掉,改为-d
后端调用就可以了。以下是最终版配置
1 |
|
最终指令改了两点:
-it
改为-d
, 解决crontab调度失败的问题。>/dev/null 2>$1
配置,这是为了取消任务调度的系统日志产生,防止系统日志爆炸,也可以不加。授之以渔不如授之以渔, 出现问题不可怕,只要我们掌握了一些解决问题的方法论, 任何问题都能迎刃而解。
数据库升级,用docker容器安装oracle11g数据库,应用连接的时候报ORA-01882错误
1 |
|
环境:
问题本质上是java应用程序启动时, 找不到本地系统的时区环境变量导致。 我们只有把时区环境变量配置上就可以了。
解决办法有二:
1 |
|
1 |
|
如果是docker的话,将宿主机改为上述配置后,docker容器启动(run)增加挂载配置就可以了
1 |
|
但是这样会产生一个问题,就是新库会把我们的历史提交记录等信息全部丢失了,当做一个全新的仓库来处理,这样给我们后续的问题追踪回溯带来了一些问题。
下面我们介绍一下git优雅迁库的三种方式。
1 |
|
1 |
|
镜像库是裸库的一种简写,等同于:
1 |
|
由此可知,裸库方式clone下的是git仓库的基本信息。镜像库不仅把基本信息clone下来了,还拉去了最新的分支代码。
1 |
|
Jdbc反序列化漏洞发生在后台代码使用mysql-connector组件连接数据库且攻击者可控制连接参数的情况下。攻击者可以构造恶意的mysql数据库使服务器去访问链接它从而触发反序列化漏洞导致命令执行
mysql 连接参数风险点:autoDeserialize,statementInterceptors
1 |
|
1 |
|
以下是CORS漏洞的一些内容:
Web服务端CORS(跨域资源共享)错误配置,无法正确验证Origin头,任何网站都可以发出使用用户凭据发出的请求,并读取对这些请求的响应,信任任意来源可以有效地禁用同源策略,从而允许第三方网站进行双向交互,易导致敏感信息泄露
需要修复两处:
1,代码级别(必修)
程序默认是禁止跨域的,也可以去掉相关逻辑。
1 |
|
2,nginx服务
增加相关头配置
1 |
|
恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页时,嵌入其中Web里面的Script代码会被执行,进而窃取到管理员用户权限等。
一段简易的字符过滤程序
1 |
|
1 |
|
在wrapper里过滤参数
1 |
|
xssUtil
1 |
|
前后端组合使用,可解决xss问题
以下是漏洞的一些内容:
任意文件下载或读取漏洞主要是由于应用系统在提供文件下载或读取功能时,在文件路径参数中直接指定文件路径的同时并没有对文件路径的合法性进行校验,
导致攻击者可通过目录跳转(..\或../)的方式下载或读取到原始指定路径之外的文件。
攻击者最终可通过该漏洞下载或读取系统上的任意文件,如数据库文件、应用系统源代码、密码配置信息等重要敏感信息,造成系统的敏感信息泄露。
对存在文件下载或文件读取功能的页面进行测试,查看所提交的参数中是否包含文件名或文件目录,尝试提交参数值查看是否可下载或读取其他目录的文件内容;
如:
原始下载功能路径为
http://www.example.com/donwload.jsp?filename=test123456789.pdf
其中文件路径参数为filename,通过../对路径进行跳转尝试下载其他目录下的文件,修改filename参数为../../WEB-INF/web.xml尝试下载JSP网站的配置文件(测试过程中需适当增加../跳转字符串);如提交
http://www.example.com/donwload.jsp?filename=../../WEB-INF/web.xml
查看是否成功下载web.xml文件。
另一种情况如下:
http://www.example.com/donwload.jsp?filepath=uploadfile&filename=test123.pdf
该功能通过filepath以及filename指定下载目录以及下载文件名,可修改filepath参数值进行路径跳转,
同时修改filename值指定文件名;如提交
http://www.example.com/donwload.jsp?filepath=../../WEB-INF&filename=web.xml
查看是否成功下载web.xml文件。
知道了问题,一般可以通过在代码层级增加关键字符过滤来修复。
比如:
1 |
|
或者,在服务部署的时候,通过下面的目录固化部署路径,令web请求不能逃逸到整个服务器。
以下是漏洞的一些内容:
为了方便的获得网站域名,开发人员一般依赖于HTTP Host header。例如,在php里用_SERVER[“HTTP_HOST”]。
但是这个header是不可信赖的,如果应用程序没有对host header值进行处理,就有可能造成恶意代码的传入。
web应用程序应该使用SERVER_NAME而不是host header。 在Apache和Nginx里可以通过设置一个虚拟机来记录所有的非法host header。
在Nginx里还可以通过指定一个SERVER_NAME名单,Apache也可以通过指定一个SERVER_NAME名单并开启UseCanonicalName选项。
本质上就是在服务侧鉴定请求来源, 非法来源直接过滤掉。
打开nginx的网站配置文件,一般为 /etc/nginx/conf.d/xxx.conf
添加如下配置
1 |
|
重启nginx即可
1 |
|
打开Apache的配置 , 一般为 /conf/httpd.conf
添加如下配置:
1 |
|
重启apache即可
以下是漏洞的一些内容:
信息泄露主要是由于开发人员或运维管理人员的疏忽所导致。如未及时删除调试页面、未关闭程序调试功能、未屏蔽程序错误信息、备份文件未删除、数据库备份文件未删除、未屏蔽敏感数据信息等多个方面所导致的不同严重程度的信息泄露。攻击者可通过所掌握的信息进一步分析攻击目标,从而有效发起下一步的有效攻击。
常见的问题有:
关闭相关接口的线上权限,或增加权限控制
1,swagger 关闭入口的方式
方法a: 使用注解 @Value() 推荐使用, 然后在不同的配置文件里指定开关
1 |
|
方法b: 使用注解@Profile({“dev”,“test”}) 表示在开发或测试环境开启,而在生产关闭
其实ab 的方式是一样的,都是不同的环境开关不同。
2, druid关闭监控台的方式
1 |
|
3, 一些其他接口增加权限认证
通过spring的httpSecurity 增加路径鉴权
1 |
|
PathPattern是在spring 5 以后新增的。本文就介绍 AntPathMatcher 和 PathPattern 的异同,以及这两者的使用场景和方式。
AntPathMatcher在1.x版本就存在了,在spring5以前,一直作为spring的路径匹配器存在。
AntPathMatcher在spring中是作为工具类存在的,所以直接new
对象即可。
PathPattern出现在spring5.x版本中, 旨在用于替换掉较为“古老”的AntPathMatcher。
PathPattern是spring一个内部类,不能通过public的方式来创建。不过spring 官方提供了一个parser
开提供PathPattern的默认实例。
1 |
|
AntPathMatcher 官方文档:
1 |
|
PathPattern 官方文档
1 |
|
可见,AntPathMatcher 和 PathPattern 的匹配规则是差不多的,都支持?
*
**
已经部分正则匹配。
但PathPattern增加了路径匹配并提取结果的能力,同时为了消除歧义,不再支持路径中间的**
匹配,其他一样。
demo:
*匹配
1 |
|
PathPattern 路径提取
1 |
|
官方说PathPattern 比 AntPathMatcher 更快一些,所以咱们验证一下:
测试代码
1 |
|
分别跑了5次。
AntPathMatcher 平均耗时:392ms
PathPattern 平均耗时:323.4ms
官方所言不虚!
今天开发遇到一个奇怪的问题,前端的form表单数据提交的时候,数据量比较小的时候,内容能正常保存。
当数据量达到1.6M的时候,后端就开始报npe异常,参数字段就开始接收不到了。
经过排查,发现这个和编程无关。
springboot 默认集成了tomcat容器,tomcat对form表单的大小有限制,默认2M。
知道了问题所在,解决起来就很简单了。
方案1: 增加tomcat的form表单容量配置
1 |
|
方案2:去掉tomcat的form容量配置
1 |
|
今天我们就介绍两个工具来解决这个问题。
mac系统上自带的一个工具,可以在mac系统上将一个可执行的bash脚本加密。
使用方式:
1 |
|
我们以hello.sh 脚本为例。
1 |
|
操作:
1 |
|
但是我们把hello.sh放到其他系统上执行时,会发现失败。但是在mac本地系统执行时,还是可以的.
shell(bash) 脚本编译指令。mac系统原生不支持,可以通过下面方式安装。
1 |
|
使用方式:
1 |
|
同样以hello.sh脚本为例
1 |
|
同gzexe一样,加密后的脚本一样不支持跨平台运行,即使是编译成c语言的二进制脚本。因为可执行文件会依赖不同系统的动态链接库。
一句话总结, 这仍然是jdk的一个短期支持版本,此版本包括7个 JEP(jdk增强建议),以及数百个较小的功能增强和数千个错误修复.
jdk20 工具箱全集
这里主要介绍一些小的优化, 太长可以不看.
equals
改为 ==
##
)将元素名称与URI片段分开。HarfBuzz: 一个开源的用于文字塑形的软件开发库,亦即用于转换Unicode文本到字形指标及方位的过程
FreeType: 同HarfBuzz一样, 也是一个开源字体库. 它是一个用C语言实现的一个字体光栅化库。它可以用来将字符栅格化并映射成位图以及提供其他字体相关业务的支持
Preview(预览): 功能已经基本完整, 可以试用了. 可以简单理解为beta公测版. 来源于 JEP12
功能以预览版的形式发布,以收集有关它们的反馈而不承诺保持其向后兼容性——这意味着鼓励每个人尝试它们,但同时不鼓励在生产中使用它们。
预览功能不是开箱即用的,为了访问它们,需要使用*–enable-preview*编译器标志。
Incubator(孵化) : 实验性 API已经到了一定阶段, 已经计划开发出一整套完整的功能.以独立模块的形式发布. 来源于 JEP11
Experimental(实验) : vm级的早期功能, 不稳定, 功能不完整. 实验性质.
实验性功能代表(主要是)VM 级功能的早期版本,这些功能可能是有风险的、不完整的,甚至是不稳定的。在大多数情况下,需要使用专用标志启用它们
出于比较的目的,如果一个实验功能被认为是 25%“完成”,那么一个预览功能应该至少 95%“完成”。
预览,孵化,实验三者的关系大致: 实验 => 孵化 => 预览 => 合并jdk主体功能.
JEP : JDK Enhancement Proposal , jdk增强建议. 也就是我们常说的jdk新特性的来源. JEP大全
jdk20官方文档
jdk20新特性文档
jdk20升级指南
jdk20安装指南
jdk20的开发计划
jdk20虚拟机简介
jdk20垃圾回收机制
jdk20安全机制
作为一名程序员,谁不想拥有一个自己的云服务器呢. 但是当我们看了各大云服务器厂商的价格后, 不禁陷入了沉思:我好像对云服务器的诉求也没那么大!
等我们回家看到家里闲置的老旧电脑时,又会想,我为啥要用云服务器呢,家里的电脑改吧改吧, 不也就可以用了. 我们又不用做大流量的网站,
最多搞搞博客,跑跑爬虫啥的,只要能让我们可以随时在外面访问我家里的电脑,控制家里的电脑就行了.
好的,说干就干, 经过一番调研, 我们发现, 好像没自己想象的那么简单. 一个最直接的问题就是, 我们没有一个自己的ip,因为众所周知的ipv4资源耗尽的问题,
我们家里实际上网的ip其实不是固定的, 而是多家用户共享一个ip地址,也就是NAT(Network Address Translation)技术. 所以,从外网我们是无法直接访问家里的电脑的.
这个问题怎么解决呢? 就是我们今天要聊的话题, 内网穿透技术.
内网穿透,也即NAT穿透,进行NAT穿透是为了使具有某一个特定源IP地址和源端口号的数据包不被NAT设备屏蔽而正确路由到内网主机.
大致流程如图:
参考官方安装frp说明, 下载合适自己服务器的frp版本, 解压到自定义的frp服务目录.
因为是服务器部分, 我们直接启用frps 即可启动成功frp的服务部分.
1 |
|
配置部分参考官方配置样例. 我们demo样例只使用ssh基本服务, 就用了默认配置.
1 |
|
为了方便, 我们一般会将frps配置成系统重启自动加载. 这样我们就不用担心服务器系统重启导致内网穿透服务掉线了.
方法如下:
systemd
, 如果不支持请安装1 |
|
1 |
|
写入下面内容
1 |
|
1 |
|
我家里的电脑是windows10, 当然, Linux更好. 本次内网穿透的目的是可以通过远程ssh控制我的家用电脑.
windows系统作为家用本, 对网络权限限制的比较多, 需要我们额外做一些操作.
a. 安装windows的ssh服务端
win10默认是不支持ssh的服务端的, 需要我们安装相应的OpenSSH服务器.
操作步骤: 设置->应用-> 应用与功能 -> 可选功能 -> 添加OpenSSH服务器
最后开启ssh服务,以管理员身份打开cmd终端,执行如下命令
1 |
|
b. 防火墙可能会将frpc识别为危险文件, 记得去防火墙那里将工具恢复.
从官网下载下frpc客户端后,且成功从防火墙那里逃生, 就可以继续后面的操作了
首先同样先修改配置文件
1 |
|
然后打开cmd终端使用命令行的方式启动frpc工具
最后就可以在外网使用ssh命令访问你的系统了
1 |
|
frp 会将请求 x.x.x.x:6000 的流量转发到内网机器的 22 端口
windows的默认终端为cmd, 很多命令不与linux兼容. 所以建议将windows的默认终端改为powerShell, 更方便.
1 |
|
1 |
|
nginx上面没有显示✓,表示nginx没有安装
1 |
|
如图所示,显示了nginx的版本. 也明确说明了系统没有安装nginx
3. 安装nginx
1 |
|
安装nginx成功.
nginx默认监听8080端口, 配置文件路径也给出来了 /opt/homebrew/etc/nginx/nginx.conf
同时也给了启动nginx的命令
1 |
|
4.验证安装成功
此时,nginx命令已经生效了.
1 |
|
当然, 也可以执行下面命令查看更详细的信息
1 |
|
至此,nginx已经安装成功了. 可以通过去配置文件修改相应配置, 启动nginx了
1 |
|
启动成功了,此时可以通过 http://localhost:8080 验证一下
1 |
|
好的, 接下来尽情使用自己的nginx服务器吧!
]]>想提供一个web接口, 入参就是es的查询DSL json. 然后服务器用这个DSL透传转发到Elasticsearch服务期, 将数据返回给接口.
简单来说, 就是想在自己服务器上实现类似kibana上查询Elasticsearch数据的功能.
比如, 我们正常的查询DSL如下:
1 |
|
如果是单纯的查询, 官方提供了一个透传查询DSL的实现类 QueryBuilders.wrapperQuery()
, 可以通过这个QueryBuilders来实现我们自定义DSL的透传.
让我们先看看官方怎么说
A query that accepts any other query as base64 encoded string.
This query is more useful in the context of the Java high-level REST client or transport client to also accept queries as json formatted string. In these cases queries can be specified as a json or yaml formatted string or as a query builder (which is a available in the Java high-level REST client).
官方示例:
1 |
|
由此可见, 官方是将wrapper当做一个关键字类型来处理的, 将我们的查询DSL转成base64,交给es服务器解析处理.
好,下面就是我们将上述DSL 通过QueryBuilders.wrapperQuery()
处理后的sql.
1 |
|
大家可以找个base64解码网站处理一下, 发现和我们的DSL一样. 然后用官方的方式处理下
1 |
|
唉, 发现查询报错了, 为什么呢?
其实仔细分析一下,很容易发现问题. 我们将base64还原,看看最后的查询DSL的样子
1 |
|
发现没, query里套query, 明显查询有问题. 其实这里es解析的时候, 发现wrapper关键字, 就会将下面的query查询替换原始query.
所以我们要将wrapper的不是原始完整的DSL,而是query下面的DSL. 我们将bool关键字的JSON重新单独拿出来处理下
1 |
|
搞定, 把上述DSL放到任何一个es的web客户端查询, 都有效.
关于聚合的功能, 官方没有提供现成的解析方法.
不过我觉得, 既然es的web端可以调用任意DSL, 通过java肯定能, 大不了就跳过RestHighLevelClient, 在RestLowLevelClient上想办法.
// todo 等我找到合适的办法再补充.
1 |
|
最近, mac推送了新版本系统, macOS 13 ventura版本. 然后忍不住升级了. 升级过程不赘述, 等着就行了.
升级完成后, 打开idea, 发现git
无法使用了, 提示如下:
1 |
|
不用说, 一定是升级导致的问题.
经过一番排查, 发现是mac的开发工具Xcode需要升级到14才行. 可以使用如下命令解决, 实测有效
1 |
|
基本到这里, 问题就解决了.
如何还不行, 可以自行到官网下载软件安装即可.
没事别乱升级系统
一句话总结, 这仍然是jdk的一个短期支持版本, 而且官方承诺,这次升级将尽可能的保证版本的向下兼容.
Support Unicode 14.0 (JDK-8268081) : 支持Unicode14.0 .
New system properties for System.out
and System.err
(JDK-8283620) : 新增了stdout.encoding
和 stderr.encoding
两个属性来支持标准输出和错误输出的字符集编码. 可以有效解决标准输出的乱码问题.
HTTPS Channel Binding Support for Java GSS/Kerberos (JDK-8279842) : 在https连接的时候, 支持绑定token来增强安全.
Additional Date-Time Formats (JDK-8176706) : java.time.format.DateTimeFormatter
增加了一些新的默认格式
New Methods to Create Preallocated HashMaps and HashSets (JDK-8186958) : HashMap, HashSet新增了一些静态创建方法. 比如
1 |
|
upport for PAC-RET Protection on Linux/AArch64 (JDK-8277204) : 通过支持ARMv8.3 的PAC功能来防御RET的攻击
When enabled, OpenJDK will use hardware features from the ARMv8.3 Pointer Authentication Code (PAC) extension to protect against Return Orientated Programming (ROP) attacks. For more information on the PAC extension see “Providing protection for complex software” or the “Pointer authentication in AArch64 state” section in the Arm ARM.
Automatic Generation of the CDS Archive (JDK-8261455) : CDS的自动打包功能
Windows KeyStore Updated to Include Access to the Local Machine Location (JDK-6782021) : Windows新增了一些密钥储库支持访问本地位置.
Break Up SEQUENCE in X509Certificate and X509Certificate in otherName (JDK-8277976) : 如题, 这两个方法的增强.
The JDK implementation of
X509Certificate::getSubjectAlternativeNames
andX509Certificate::getIssuerAlternativeNames
has been enhanced to additionally return thetype-id
andvalue
fields of anotherName
. Thevalue
field is returned as a String if it is encoded as a character string or otherwise as a byte array, which is helpful as it avoids having to parse the ASN.1 DER encoded form of the name.
(D)TLS Signature Schemes (JDK-8280494): 增加了一套新的获取TLS签名的方法.
Add a -providerPath Option to jarsigner (JDK-8281175) : jarsigner新增了一个选项, 用来指定备用密钥库路径
New Options for ktab to Provide Non-default Salt (JDK-8279064) : ktab命令增加了一些选项来支持自定义’盐’值.
ktab -a username password -s altsalt
New XML Processing Limits (JDK-8270504 (not public)) : xml库解析执行增加了3个限制
jdk.xml.xpathExprOpLimit
: XPath表达式中group数量的限制
jdk.xml.xpathExprOpLimit
: XPath表达式中operator数量的限制
jdk.xml.xpathTotalOpLimit
: XPath文件中operator总数量的限制
Locale.of()
方法替代这里主要介绍一些小的优化, 太长可以不看.
JDK19中, 在macOS上将Metal作为图形渲染的默认配置.
像mac或linux系统中, user.home
存在就用系统提供的配置 , 如果不存在, 则使用$HOME
的配置设置这个系统属性.
线程的ClassLoader 被处理为一个特殊的可继承的Thread-local
java.lang.Thread 新增了一些方法, 如果现在代码继承了Thread, 可能会导致不兼容.
Thread.Builder模式
Thread.isVirtual()
Thread.threadId()
Thread.join(Duration)
Double.toString(double) and Float.toString(float) 针对科学记数法的一些优化
注解的toString方法针对常量和枚举, 做了一些优化, 输出更友好.
HTTP签名鉴权时, MD5和SHA-1 因为不太安全默认被禁用.
Windows 上http多代理选择的优化
java.net.InetAddress 针对一些有歧义的IPv4输入, 精细了异常提示.
HttpURLConnection 的默认keep alive 可配置
FileChannel.transferFrom 返回的bytes 可能会小于预订值, 这是允许的.
FileChannel.transferFrom()的性能在Linux内核4.5及更高版本上得到了显著提高
InputStream and FilterInputStream 的mark和reset 移出了 synchronized 关键字
Files.copy 方法支持将 POSIX
属性跨文件系统复制, 前提是两个系统都支持 POSIX
FileChannel.lock(long position, long size, boolean shared) 当size指定为0时, 表示锁定 整个文件, 无论这个文件流被扩展或继承
java.time.chrono 新增了3个标准时间
ForkJoinPool 和 ThreadPoolExecutor 线程池不再使用Thread.start来启动工作线程.
正则 \b
可以匹配ASCII 字符, 像 \w
一样.
支持 CLDR Version 41
LDAP, DNS, 和 RMI 的URL解析更加严格
> -Dcom.sun.jndi.ldapURLParsing="legacy" | "compat" | "strict" (to control "ldap:" URLs)>> -Dcom.sun.jndi.dnsURLParsing="legacy" | "compat" | "strict" (to control "dns:" URLs)> -Dcom.sun.jndi.rmiURLParsing="legacy" | "compat" | "strict" (to control "rmi:" URLs)
VM Tool Interface (JVM TI) 支持虚拟线程
cpu计算时不再考虑cpu.share
, 因为会导致jvm对当前cpu使用情况的误算, 进而影响cpu使用率.
之前的JDK版本对Linux cgroups参数“cpu.shares”使用了错误的解释。这可能会导致JVM使用的CPU少于可用,导致JVM在容器内使用时CPU资源利用率不足。
从此JDK版本开始,默认情况下,JVM在决定各种线程池使用的线程数量时不再考虑“cpu.shares”。-XX:+UseContainerCpuShares命令行选项可用于恢复到之前的行为。此选项已被弃用,可能会在未来的JDK版本中删除。
此后, macOS中,相同大版本的jdk将默认安装到同一目录
Oracle JDK安装目录从
/Library/Java/JavaVirtualMachines/jdk-${VERSION}.jdk
迁移到
/Library/Java/JavaVirtualMachines/jdk-${FEATURE}.jdk
比如: 19.0.1 and 19.0.2 版本都将安装到
/Library/Java/JavaVirtualMachines/jdk-19.jdk
中
在macOS上,只有用户钥匙串中具有正确信任设置的证书才会在钥匙串类型的钥匙库中作为受信任证书条目公开
RC2和ARCFOUR(RC4)算法已添加到java.security配置文件中的jdk.security.legacyAlgorithms安全属性中。当弱RC2或ARCFOUR算法用于与密钥存储中的秘密密钥条目关联的命令时,密钥工具会发出警告。
加密算法位数增强
RSA, RSASSA-PSS, DH: from 2048 to 3072
EC: from 256 to 384
AES: from 128 to 256 (if permitted by crypto policy), falls back to 128 otherwise.
null
. 参考 RFC 5758 Section 3.2TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
SSL_RSA_WITH_3DES_EDE_CBC_SHA
30 修复了一个StringBuilder bug. 例如, 下面代码输出”foofoobar” 而不是 “foobarfoobar”
1 |
|
JavaDoc生成的API文档现在提供了一个独立的搜索页面,搜索语法已得到增强,允许多个搜索词。
JShell现在标记不建议使用的元素,并在控制台中突出显示变量和关键字。
实际的java线程堆栈大小可能与-Xss命令行选项指定的值不同;当操作系统要求时,它可以四舍五入到系统页面大小的倍数。
HarfBuzz: 一个开源的用于文字塑形的软件开发库,亦即用于转换Unicode文本到字形指标及方位的过程
FreeType: 同HarfBuzz一样, 也是一个开源字体库. 它是一个用C语言实现的一个字体光栅化库。它可以用来将字符栅格化并映射成位图以及提供其他字体相关业务的支持
Preview(预览): 功能已经基本完整, 可以试用了. 可以简单理解为beta公测版. 来源于 JEP12
功能以预览版的形式发布,以收集有关它们的反馈而不承诺保持其向后兼容性——这意味着鼓励每个人尝试它们,但同时不鼓励在生产中使用它们。
预览功能不是开箱即用的,为了访问它们,需要使用*–enable-preview*编译器标志。
Incubator(孵化) : 实验性 API已经到了一定阶段, 已经计划开发出一整套完整的功能.以独立模块的形式发布. 来源于 JEP11
Experimental(实验) : vm级的早期功能, 不稳定, 功能不完整. 实验性质.
实验性功能代表(主要是)VM 级功能的早期版本,这些功能可能是有风险的、不完整的,甚至是不稳定的。在大多数情况下,需要使用专用标志启用它们
出于比较的目的,如果一个实验功能被认为是 25%“完成”,那么一个预览功能应该至少 95%“完成”。
预览,孵化,实验三者的关系大致: 实验 => 孵化 => 预览 => 合并jdk主体功能.
JEP : JDK Enhancement Proposal , jdk增强建议. 也就是我们常说的jdk新特性的来源. JEP大全
pom 依赖
1 |
|
dubbo配置:
1 |
|
然后就能正常启动了,通过日志,会发现正常注册了两个zk中心,并且zk上也能发现注册的服务。
http://dubbo.apache.org/zh-cn/docs/user/references/registry/zookeeper.html
]]>本人初次接触鸿蒙系统, 想着自己在手机上开发个app玩玩, 结果第一步就遇到坑了~~
因为是个demo, 其实就是从官方demo中copy过来的, 然后换成自己的域名地址
1 |
|
大家可以从参考里的官方demo里比对一下, 啥都没变, 然后本地调试, 报
1 |
|
多说一句, 因为是初次使用这个ide, 日志的打印入口都找了半天. 下面是日志的路径
底部导航log-> HiLog -> 选中自己的设备 -> 选择日志级别
需要权限:ohos.permission.INTERNET
然后就去查权限相关的文档, 在模块的config.json下面把权限加上, 还是不行. 配置如下:
1 |
|
请求结果还是老样子.
4. 最终定位: http明文请求被限制了
这是我在一位老哥的帖子里发现的一个关键节点.
原来正常的http请求会被系统默认禁掉, 而且官方还不给个提示, 太坑了. 官方更推荐使用https的请求方式.
知道了问题所在, 解决问题就好了.config.json添加如下配置即可
1 |
|
注意, 3里的网络权限也要配置哈, 不然会报没有权限的错误. 不过这个还好, 官方有异常提示了, 就很好定位了.
普通的k-v操作
redis是不支持批量删除指令的, 因为redis是单线程的. 批量删除会占用redis的主线程.影响性能.
但是我认为现在redis既然已经支持异步线程操作一些后台数据了, 也就可以支持在不影响主线程性能的情况下实现 正则批量删除数据的命令了. 不知为何还是没有.
这里记录一下使用lua脚本, 在redis命令行里 通过正则批量删除缓存的功能. 核心是使用 keys命令. 这个会阻塞主线程, 如果量比较大, 慎用.
1 |
|
将最后的’key正则表达式’换成自己想删除的keys的表达式即可.
关于量的问题, 我删除线上5w条缓存,秒删,无任何影响.各位同学自行参考量级.
不定期更新中…
如果要调用
1 |
|
demo
1 |
|
最常用的很是就是call方法了
1 |
|
demo
1 |
|
不定期更新中…
编程这么多年了, 第一次遇到这种问题, 还挺神奇的. 记录和总结一下.
1 |
|
很简单的代码, 没事走两步
1 |
|
首先, 顾名思义, 很明细就是字符长度超过了java 一个类文件的最大长度限制导致的.
那么java类文件的长度是多少呢?
经过一番不是很辛苦的查询, 定位到了原因:
There is a 64K byte-code size limit on a method
java中一个方法的最大长度是64Kb
很明细, 我们的这个方法长度超了.
原因找到了, 要解决也就很简单了. 只要做到规避方法的最大长度就行了.
其实只要符合正常的java编码规范, 我们基本上是遇不到上面的问题的.
只有在一些极限测试的时候, 我们通常会将测试内容放到代码里, 才可能碰到.
只能说: 没用的小知识又增多了🐶
相信已经成功使用github建站的朋友们肯定会有一个想法, 就是可不可以将文章仓库私有也能实现建站的功能呢?
答案是可以的, 本文就是一个将一个公共仓库站点私有化后的例子. 并将过程总结一下, 方便大家参考使用.
首先,使用github pages建站, 项目名就固定死了,必然是<用户名>.github.io
其次,如果有域名的话,像域名配置等一些配置也是需要放到 外露站点下的.
除此之外, 其他的内容就就是可以变的了.因为github.io站点最终暴露的就是一个编译好的web静态网站.
至于源码是不是在这里,无所谓的.之所以源码与静态站点放到一起,核心还是方便github的 action自动编译.
好了, 了解了这些核心要素, 我们可以操作的部分也就有了.
就像我们正常的开发流程一样,我们可以把整个网站的发布流程拆分为两部分: 打包 与 发布.
其中打包可以在我们的私有仓库中进行, 然后将打包后的静态网站 发布到github.io公仓中.
这里就用到了github的 action 自动编译功能. 其会在我们提交代码的时候触发一个操作,
调用一段脚本命令执行.
而这段脚本的核心就是通过git 将编译后的静态站点push 到github.io公仓中.
流程如下:
这一步一定要记住创建好的token, 因为后面要用,而且这个页面刷新后就没了.
其实有过使用ssh经验的朋友们应该已经意识到了, 这一步其实就是一个创建ssh公钥的过程. 只不过这个公钥是放到github服务器上了.
有了token, 正常来说就能提交代码了. 但是要知道这个东西是放到脚本里的, 明文暴露还是不太安全.
所以我们要在私有库里将这个token配置成一个环境变量,通过环境变量引用的方式使用,这样就安全多了.
1 |
|
可以尝试在私有库private_repo里提交文章了.
同时你会发现, github.io公仓里的内部全部被静态网站内容覆盖了. 不用担心,这是正常的.
1 |
|
在github的action模块里创建了一个脚本后, 就会在项目的.github/workflows里创建一个对应的文件.
通过workflow 大致可以理解这个模块的功能, 就是一个工作流.
在一些操作执行(通常是提交代码)后, 触发一系列的后续自动化操作, 这些操作可以是并行的,也可以是串行的.
而这些后续操作是通过一些action脚本组织起来的. 整体上看,一个action由以下部分组成.
无论是job,还是step, 都可以理解一组指令集合. 他们都是可以配置id的. 可以通过id来互相依赖, 互相调用.
其他的可以参考里的官方文档.
众所周知, es是一种高效的数据查询,检索引擎。 可以对海量的数据进行快速的查询。但是在使用es的时候,经常会遇到一个比较尴尬的问题,那就是es索引里的字段类型是固定的,不可修改的。而es的索引又是可以自动扩展字段的。 这个时候自动扩展出来的字段就是使用了默认的字段类型(通常是text类型)。
惊不惊喜!
查看索引字段的方法:
1 |
|
聪明的你肯定想到了, 如果我们忘记加字段了, 或者字段加错了要修改字段类型咋办? 这个时候场景的办法就只有一个了:
重建索引!
从数据库角度来看,也就是新增新表, 迁移数据, 删除老表.
Elasticsearch 版本 : v 6.6
目标: 在my_index 索引上新增一个integer字段. 且中间不间断服务.
使用aliases api
创建一个别名my_index 指向实际的索引my_idex.
这样,我们通过my_index访问数据时, 就不会直接访问my_index索引了, 而是通过别名指向对应的索引.
1 |
|
使用 put mapping api
打算新增一个字段 fiedl_d, 数据类型.
因为默认的string类型是keyword (假设, 意会即可), 所以如果想要新增一个整型类型, 就不能通过自动的扩展字段使用默认类型的方式. 所以必须显式的指定字段类型.
1 |
|
使用 Reindex api
将老的my_index 数据迁移到新的my_index2上.
这一步比较耗时, 做好心理准备.
1 |
|
使用aliases api
将别名从老的index, 指向新的index
1 |
|
处理迁移期间my_index 发生的增量数据
1 |
|
使用 delete api
1 |
|
至此, 整个重建索引过程完毕.
很麻烦吧.
为了不这么麻烦, 以后要给es 增加新字段时, 一定要先通过maping的方式先新增字段. 然后再在代码里处理相对应的逻辑.
over!
可以通过put mapping的方式, 增量补充索引的字段
1 |
|
Beaudar 名称源于粤语“表达”的发音,是 Utterances 的中文版本,是一款基于github issue 的一个评论插件
本站的评论模块就是使用的 beaudar
选择 Beaudar 将要连接的仓库, 也就是网站对应的github仓库。
3. 将网站对应的分支配置到配置中,并确保 Issues 功能已打开。
4. 将以下脚本标记添加到博客的模板中。 将其放置在要显示注释的位置。 使用 .beaudar 和 .beaudar-frame 选择器自定义布局。
1 |
|
配置解读
原因: 现在github的默认主分支是main, 不在是master. 所以分支找不到.
配置的时候指定分支为main就可以了.
将上述的 script脚本 在_config.yml
中按如下方式添加即可
1 |
|
但是效果比较丑,需要自己去调整一下css才与整个主题适配, 建议使用fluid主题已经集成好的组件。
要回答这个问题,先提出一个说法:我们写的代码是给人看的,不是给机器看的.
如果你不认可这个说法,下面关于代码注释的一些总结想必对你也没啥帮助.
如果你认可这个说法, 那么下面的文章希望会给你一些帮助.
回到本段的主题,什么是java注释呢?
首先,注释是面向代码维护者的: 注释是对代码逻辑的一段说明, 方便后续的代码维护者能够快速的了解这段代码的含义,并依次为基础能够在现有代码的基础上做修改与维护.
其次,注释是面向系统使用者的: 要知道类似于java,或者spring这样偏向于底层,框架类的代码.全世界有数以万计的使用者的,这些人没精力也没有必要在调用api的时候去了解这段代码的底层实现逻辑,所以这段代码的api说明文档就显得很重要了.而代码上的注释就是相关api文档的主要来源.
通过javadoc命令,可以将注释抽离生成html文档,比如官方的java16API文档
想要写好代码的注释,我们要从注释的面向用户来考虑.不同的用户关注点还是有点差异的.
作为一名代码的维护者,当我们拿到一份没有任何注释的代码的时候,相信大家内心的感受是相同的.毕竟人类的悲欢并不相通, 除非看到没有注释的代码.
如果有了解设计模式的朋友,相信一定听说过一个设计原则:开闭原则, 对扩展开发,对修改关闭. 那么大家有没有想过为啥会有这个原则呢?
翻译一下: 修改现有的代码是有风险的,但是如果是扩展写新代码的话,作为开发者的你,是了解当前功能的前后背景的,而且你无论怎么写都不会对历史功能产生影响.所以对我们的代码设计能力提出来要求.
再翻译一下: 原来的代码既然跑的好好的,为啥要修改它呢, 改出问题来算谁的?
再翻译一下: 为啥我们不敢改以前的代码呢?谁知道当时为啥要写这段逻辑,谁知道这段逻辑有谁在用?改好了没有夸, 改崩了有锅背.
这就是我们维护老代码的困境!
作为一名程序员,我们要有一个概念,我们的代码是我们某个时刻的思维逻辑的固化.
如果是一个比较简单的逻辑,注释可以简单写写.
如果是一段比较复杂的逻辑,那么我们的注释要能够描述清楚我们当时的这段逻辑的背景(为什么要写这段逻辑), 意图(这段逻辑要实现什么效果), 用法(这段逻辑改如何使用,以及谁在用), 最好能有修改建议.
在业界开源的代码里有很多比较好的例子,来个spring的:
1 |
|
对于api的使用者,注释文档的要求相对简单些.
他们只关心这个方法的功能是什么, 以及入参有哪些,出参会有哪些, 会不会抛异常等, 会不会返回null等. 他们不关心这个方法的内部实现逻辑是啥, 比如一个排序方法, 使用者不关心这个方法内部使用冒泡排序,还是快排, 只要能完成诉求就行.
所以,面向api的使用者的注释,原则就是让他知道这个方法怎么使用就行了.
1 |
|
{@literal}
1 |
|
1 |
|
1 |
|
1 |
|
一个正常方法必然会用到的注释tag,
@param 代表方法的入参,
@return 代表方法的返回值.
@throws 代表可能引发方法中断的异常. 等同与 @exception
这里需要特别说明一下, 有些人可能觉得只有那些受检异常(也就是必须在方法签名里声明的异常)才需要在注释里声明@throws. 其实不是的. 所有引发程序中断的异常, 包括运行时异常都可以在注释里说明, 也可以在方法签名里添加. 尤其是自定义的异常.
举个例子:
1 |
|
这里的IllegalStateException 是一个RuntimeException异常, 我们在方法里可能抛出这个异常, 最好是在方法签名里声明一下, 然后在代码注释里说明一下.
当前代码可以参考的其他代码, 后面跟代码的全路径,可以是类,方法,属性等.具体参考如下:
1 |
|
标识当前代码的作者是谁, 可以一个,也可以有多个.
都是版本相关的tag
@version 标识当前版本好, 编译的时候会用到. 符合SCCS规范.
@since 标识这段代码的引入版本
序列化相关的属性, 标识哪些字段可以序列化,哪些不行.
废弃某段代码,表示不再维护,并在一段时间后会被删除. 标记后, 相关的引用位置会被标记为删除线.
个人认为这个tag还是很有用的:
1 |
|
1 |
|
想在JAVA的注释中添加一段代码,并且可以优雅的编译出来还是挺麻烦的.下面提供一种方法
1 |
|
官方文档
javadoc - The Java API Documentation Generator
How and When To Deprecate APIs
个人理解,滑动窗口 的基础就是双指针. 即一个快指针代指窗口的头部, 一个慢指针代指窗口的尾部. 滑动窗口经过的地方就是我们要处理的数据.
如图所示,我们的滑动创建运动方向从左往右, 已经经过了区域1,正在经历区域2,3,4. 还没有经历区域5.
通过上面分析可知, 滑动窗口是一个窗口经过一段连续的区域,并实时计算统计经过区域内的一些值. 所以滑动窗口很适用的场景特点也就出来了:
下面是官方介绍
The System class contains several useful class fields and methods. It cannot be instantiated.
Among the facilities provided by the System class are standard input, standard output, and error output streams; access to externally defined properties and environment variables; a means of loading files and libraries; and a utility method for quickly copying a portion of an array.
以下介绍System的常用方法
1 |
|
以上的流就会在System的标准流中使用
1 |
|
获取一个基础虚拟机的流通道
1 |
|
设置或获取 java虚拟机的安全管理器。这个我用的比较少,大家看注释吧。
1 |
|
获取java的控制台对象,关于控制台命令交互可以通过这里获取
1 |
|
这应该是大家使用最频繁的几个接口。
1 |
|
获取当前系统的纳秒精度的数(基于当前虚拟机)。这个数是依据当前虚拟机的一个原点计算出来的一个高精度值,与具体的时间无关(比如毫秒方法获取的是1970到今天的时间戳,但纳秒的原点不是具体的某个时间点)
所以,这个方法的用处也就有了,就是基于同一个虚拟机的纳秒级精度的前后时间比较。
因为数据比较大,可以会有越界风险
1 |
|
说实话,不太理解为什么这个方法会放在这里。可能在虚拟机底层里,数组copy是一个基础功能吧。
很常用。
1 |
|
返回对象的默认hashcode(即时被覆盖也不执行覆盖方法)
null 返回0
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
linux/unix : \n
windows: \r\n
1 |
|
1 |
|
以某个状态终止虚拟机:非0 code 表示非正常结束
1 |
|
执行垃圾回收
1 |
|
1 |
|
Objects
Objects是jdk官方自带的工具类,首先我们来看看Objects的官方简介:
This class consists of static utility methods for operating on objects. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, and comparing two objects.Since: 1.7
这是一个私有的工具类,用来提供一些对Object的操作方法,包括null值的安全操作,一级hash值的计算等实用方法。jdk1.7 就提供了。
Objects的提供的静态方法如下:
equals
用来比较两个对象的是否相等,null安全的,可以替换繁琐的判空校验。实现逻辑:
1 |
|
deepEquals
数组的深度比较,同样null安全的。实现逻辑:
1 |
|
hashCode
获取一个对象的hashCode,同样null安全的。实现逻辑:
1 |
|
hash
对一个数组求hash值,同样null安全的。实现逻辑:
1 |
|
toString
调用对象o的toString方法,同样null安全的。不过这里要注意一下,如果是null的话,会返回null字符串。
相对应的,还有一个重载方法,toString(Object o, String nullDefault) 。 能够自定义null值的返回值,这个挺实用的。
实现逻辑:
1 |
|
compare
自定义实现大小比较逻辑,同样null安全的。实现逻辑:
1 |
|
isNull
判断对象是null, 可以用来替换 obj==null 的写法,更优雅。 实现逻辑:
1 |
|
nonNull
判断对象非null, 可以用来替换 obj!=null 的写法,更优雅。 实现逻辑:
1 |
|
requireNonNull
断言判断,要求一个对象是非空的。如果为空则抛出异常,有三个重载方法,可以自定义不同的异常message。
实现逻辑:
1 |
|
好了,jdk自动的Objects方法就介绍这些,有时间 我们再聊聊其他的常用工具类。
]]>共计 2 篇文章
+2022
+ + + +共计 2 篇文章
+2023
+ + + +2022
+ + + +共计 9 篇文章
+2023
+ + + +2022
+ + + +共计 3 篇文章
+2024
+ + + +2023
+ + + +共计 2 篇文章
+2024
+ + + +2023
+ + + +共计 2 篇文章
+2023
+ + + +共计 7 篇文章
+2023
+ + + +共计 7 篇文章
+2024
+ + + +2023
+ + + +共计 7 篇文章
+2025
+ + + +2024
+ + + +2022
+ + + +共计 4 篇文章
+2023
+ + + +2022
+ + + +