Reading view

There are new articles available, click to refresh the page.

关于 MathJax 渲染中断的问题

今天发现页面在通过 PJAX 的方式加载的时候,包含 MathJax 的文章渲染过程中会出现:

Error Preparing CommonHTML output (postProcess)

文章中的数学公式也只渲染了一半,这个问题的可能的原因是 MathJax 渲染过程中,由外部修改了 DOM 导致的。

具体到我这边的话,主要是通过 AJAX 的方式加载一个表情列表,在加载完成后,会将文章中的占位符替换为相应的表情,而替换的时候 MathJax 还没有渲染完成,所以导致了报错。

解决方法:

将表情最终的替换过程加到 MathJax 的队列中:

MathJax.Hub.Queue(() => {
    // 会操作 DOM 的代码
});

如何对 Java 中的集合进行去重

整体去重

如果是普通的去重,就用最常见的 HashSet 就好:

Set<Employee> employeeSet = new HashSet<>(employeeList);
employeeList.clear();
employeeList.addAll(employeeSet);

或者使用 Java8 的 Stream API:

List<Employee> uniqueList = employeeList.stream().distinct().collect(Collectors.toList());

Employee 类需要实现 hashCodeequals 方法。

根据对象中的某个属性进行去重

例如:不重写 equals 方法的情况下,根据 Employee 的 id 字段进行去重处理

方式 1:

List<Employee> uniqueList = employeeList.stream().collect(
        Collectors.collectingAndThen(
                Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparingLong(Employee::getId))
                ),
                ArrayList::new
        )
);

如果是依照两个字段进行去重,则重写 Comparator 方法即可。

方式 2:

HashSet<Object> idSet = new HashSet<>();
employeeList.removeIf(employee -> !idSet.add(employee.getId()));

参考

如何使网页自动适配 Mac 的 Dark Mode

前段时间 Safari Technology Preview 中添加了对媒体查询 prefers-color-scheme 的支持,该特性可以帮助我们的网页自动切换到深色模式以适配 Mac 中的 Dark Mode(深色模式)。虽然目前 Mojave 10.14.3 版本的 Safari 中尚未添加该特性,但其将随着 Safari 12 在下个小版本(macOS 10.14.4, 10.13.6 and 10.12.6)更新中获得支持。目前最新版本的 Safari 及 Chrome 均已支持该特性。

prefers-color-scheme 可以检测到用户是否已请求操作系统使用浅色或深色主题,其有三个可选的选项:

  • no-preference:未能检测到用户的选择
  • light:用户倾向于使用浅色的主题
  • dark:用户倾向于使用深色的主题,例如用户开启了 Mac 中的深色模式

所以,在这里我们可以使用 prefers-color-scheme: dark 来检测用户是否开启了 Dark Mode。

开始使用

prefers-color-scheme 的使用很简单,和普通的 Media Query 一样:

/* 操作系统及浏览器未支持或用户未开启 Dark Mode */
body {
    background-color: white;
    color: black;
}

@media (prefers-color-scheme: dark) {
    /* 操作系统及浏览器支持且用户开启了 Dark Mode */
    body {
        background-color: black;
        color: white;
    }
}

这样,用户在平时看到的是白色背景,而开启了深色模式后,页面就会自动变为黑色背景。当然,夜间模式的适配需要做的工作还很多,需要你慢慢调整各元素的颜色等。

顺便说一下,目前版本中,如果使用 @supports (prefers-color-scheme: dark) 包裹上述 Media Query 代码,则可能不会生效,这个看上去应该是目前版本浏览器的 Bug。

通过 JavaScript 获取是否开启了深色模式 Dark Mode

很多网站已经有了夜间模式,使用 prefers-color-scheme 适配可能比较麻烦,那么可以使用 JS 获取用户是否开启了 Dark Mode (深色模式),然后按照旧的逻辑开启夜间模式。

这种模式有个缺点:无法像直接使用 Media Query 的情况一样,在用户开启深色模式的时候,已打开的浏览器页面会自动变为深色模式,只有刷新页面或新进入的用户才会使用深色模式,不过个人感觉体验上影响不大。

当然,JS Api 是没有的,这里只能通过迂回的方式通过 JS 获取。

评论区的大佬告诉我有

用过 matchMedia 方法可以直接判断浏览器当前是否倾向于使用深色模式:

let prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDarkMode) {
    // 搞事情
}
监听事件

通过监听 matchMediachange 事件,可以在用户切换深色/浅色模式的时候,将浏览器中已打开的页面自动切换为系统对应的模式。

let media = window.matchMedia('(prefers-color-scheme: dark)');
let callback = (e) => {
    let prefersDarkMode = e.matches;
    if (prefersDarkMode) {
        // 搞事情
    }
};
if (typeof media.addEventListener === 'function') {
    media.addEventListener('change', callback);
} else if (typeof media.addListener === 'function') {
    media.addListener(callback);
}

参考

呐~ 自用 Typora 皮肤一个

Typora 这个 Markdown 编辑器是我非常喜欢的一个了,很久以前就写过一篇文章介绍他。

目前见到的编辑器基本都是输入与预览分离的设计,像 Mou、MWeb 等等。而 Typora 最明显的优点也在于此。它将编辑与预览合并了,就像那些所见即所得编辑器一样,但又使用着 Markdown 简洁的语法,从而使我们不需要像传统的所见即所得编辑器一样,经常需要使用鼠标点击来调整各种格式。「虽然看上去不像」,但它确实是一款 Markdown 编辑器,一款优秀的 Markdown 编辑器,甚至我觉得,这才应该是一款 Markdown 编辑器所应有的样子。

这是我当时的评价,毫不夸张的说,为了它,我放弃了很多功能更加强大的 Markdown 编辑器。

关于 Typora 的更多介绍可以百度 Google 或看我以前写的一篇:《Markdown 编辑器:Typora

今天呢,放出来的是一款自用 N 久的皮肤,最开始的时候超懒,就只支持配备 Retina 显示器的 macOS,最近稍作修改,在 Windows 上效果也还可以,即便是低分屏。

皮肤风格的话,大概和 Mirages 主题很像,代码高亮什么风格都一样。所以这款皮肤的名字也叫 Mirages。

最初其实是有夜间模式的,但后面因为懒得更新,所以算是放弃了。

概览

原谅我懒,用了 MWeb 的介绍哈哈,MWeb 也是款功能超级强大的 Markdown 编辑器,而且有 iOS 版。

以上。

下载地址

Mirages For Typora

使用方法

下载解压后,将 mirages 文件夹和 mirages.css 文件丢进 Typora 的主题文件夹(可以在 Typora 的设置里找到)内,然后主题选择启用 Mirages 即可。

Nginx 反向代理后 Spring Boot 下 WebSocket 会无法连接的问题

最近在瞎搞点东西,用上了Spring Boot 的 WebSocket (SockJS) 服务,但因为只有一台乞丐🐔,而且还挂着 PHP 博客,所以就让某汪帮忙用 Nginx 做反代,将来自某个子域名的请求全都转到 8080 端口的 Tomcat 上,以上为背景。

瞎搞的东西本地开发一直没有问题,但做了反代以后发现 WebSocket 连接不上了,浏览器端连接请求是报 403,而且服务器会有如下错误:

Handshake failed due to invalid Upgrade header: null

以及警告:

o.s.w.s.s.t.h.DefaultSockJsService: Origin check enabled but transport 'jsonp' does not support it.

这个很好解决,搜索一下就会有解决方案 —— 只要添加如下配置就好了(然而还是某汪帮我加的):

nginx.confhttp 模块添加如下内容

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

在反代的 location 设置中添加如下内容:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

配置完以上内容即可正常访问websocket,经过测试没有发现问题。

再然后,项目准备对外开放之前,想了下是要加 HTTPS 证书的,然后让某汪帮我搞了下看首页没啥问题以为 OK 了,就没做啥测试,结果第二天发现 WebSocket 又特么崩了,访问直接返回403,还以为某汪又改配置了?我看了一下配置文件,没变,还是之前的样子,相关配置也都在,但浏览器访问就是返回 403,同时 SockJS 报下面的异常(后续找的,当时的完整的找不到了,也不想再试一遍了。。。):

in a frame because it set multiple 'x-frame-options' headers with conflicting values ('deny, sameorigin'). falling back to 'deny'.

然后服务端报警告:

o.s.w.s.s.t.h.DefaultSockJsService: Origin check enabled but transport 'jsonp' does not support it.

同时过几秒后 Freemarker 会大量抛出异常(这个可能是我异常处理的锅):

java.lang.IllegalStateException: getOutputStream() has already been called for this response

最后经过测试发现是 HTTPS 的问题。HTTP 就木有问题,联想到反代以后 Tomcat 获取不到用户 IP 的问题,所以我觉得可能是 Tomcat 没有获取到正确的协议的问题,遂让某汪帮我加了X-Forwarded-Proto 头,为了避免后续的未知问题,把X-Forwarded-Port也加进来了。

Nginx 添加的配置如下:

proxy_set_header X-Forwarded-Port $Server_port;
proxy_set_header X-Forwarded-Proto $scheme;

同时,Spring Boot 配置文件里也要加上如下配置:

server: 
    use-forward-headers: true

这里多说几句,因为我用的是内嵌的 Tomcat 容器,所以只要在 Spring Boot 的配置文件里加上这个配置就好了,如果是打包成 War 包的形式,可以搜索并参考 Tomcat 在 Nginx 反代的情况下获取用户真实 IP 的做法。

如: Jetty/Tomcat + Nginx反向代理获取客户端真实IP、域名、协议、端口

另外,这里添加这一句配置是使用默认的 Forward 请求头,如果有需要使用自定义请求头的情况,可以使用如下配置(根据情况自定义即可):

server: 
    tomcat:
        remote-ip-header: X-Forwarded-For
        port-header: X-Forwarded-Port
        protocol-header: X-Forwarded-Proto
        protocol-header-https-value: https

配置完以后,WebSocket 就可以正常使用了😆

最后附上完整的 Nginx Location 配置:

location / {

    proxy_set_header Host $Host;
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Port $Server_port;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

}

以上。

参考

  1. Spring WebSocket: Handshake failed due to invalid Upgrade header: null
  2. Jetty/Tomcat + Nginx反向代理获取客户端真实IP、域名、协议、端口
  3. Spring Boot Use Forward Headers
❌