蘑菇大叔自留地

记录技术,分享技术


  • 首页

  • 归档

  • 搜索

typecho在微信浏览器中不能正常打开的问题分析

发表于 2021-02-24   |   分类于 Theory   |   暂无评论

博客 https://blog.hanbinit.com.cn 是用typecho搭建的。

问题描述

最近开始写公众号了,将博客的链接加到了微信的原文链接中。但是有人反馈说原文链接打开报错,遂检查,发现确实打不开,错误如图:

微信浏览器打开失败

换用其他浏览器打开都是正常的。

分析过程

首先通过在根目录下的config.inc.php中添加define('__TYPECHO_DEBUG__', true);开启typecho的debug模式。

执行service nginx restart重启nginx,使上面的配置生效。

重新使用微信浏览器打开,这次提示了详细的错误信息。

SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'ua' at row 1

按照错误提示去分别查看微信浏览器UA(使用微信开发者工具),PC浏览器UA,以及数据库的ua字段大小。

微信:Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36 wechatdevtools/1.05.2102010 MicroMessenger/7.0.4 webview/16141376613598827 webdebugger port/22325 token/7c3a0c5eb7ac1ac7ed760c7c89af0e39

EDGE: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74

表结构:

typecho_accesss表结构

ua字段长度只有255,而微信的ua长度有274。所以就有了本文的问题。

修改ua字段的长度,

alter table typecho_access modify column ua varchar(512);

再次访问正常。

最后,删除第一步添加的配置,关闭typecho的debug模式。

写在最后

这个表不是typecho原生的表,是我使用了一个Access插件引起的。 不使用这个插件应该不会有问题。

优雅关闭Spring Boot应用

发表于 2021-02-24   |   分类于 Spring Boot   |   暂无评论

最新的Spring Boot添加了一个新特性 --- 优雅停机。

官方介绍

官方文档地址:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-graceful-shutdown

这个机制会有一个超时时间,该超时时间提供一个宽限期,在此宽限期内,现有请求将被允许完成,而新请求将不被允许。 不允许新请求的确切方式因所使用的Web服务器而异。 Jetty,Reactor Netty和Tomcat将停止在网络层接受请求。 Undertow将接受请求,但会立即以服务不可用(503)响应进行响应。

本文使用的是Tomcat容器测试,Tomcat版本不低于9.0.33时这个机制才会生效。

按照官方文档的描述,这个特性用起来很简单,直接在application.properties中添加server.shutdown=graceful即可。还可以通过spring.lifecycle.timeout-per-shutdown-phase=30s来配置超时时间。
接下来我们创建一个Spring Boot项目,验证下这个优雅停机的效果。

代码测试

代码在github上: https://github.com/hanbin/learn-spring-boot/tree/main/graceful-shutdown

主要的代码就是提供一个简单的接口,每秒打印一行日志,打印20个。

/**
 * @author han_bin@outlook.com
 * @Description TODO
 */
@RestController
@Slf4j
public class TestController {

    @GetMapping("/test")
    public String test() throws InterruptedException {
        for(int index=0; index<20; index++){
            Thread.sleep(1000L);
            log.info("线程{}正在执行-{}", Thread.currentThread().getName(), index);
        }
        return "循环结束" + Thread.currentThread().getName();
    }
}

在查看效果之前,需要先了解在Idea中,使用Run模式启动项目,点击下图中的按钮等同于发送kill -15 xxx的命令。

kill -15 退出程序

1.在不开启优雅停机的时候查看效果。

不开启优雅停机

可以看到,应用会直接中断当前正在运行的服务。

2.添加配置server.shutdown=graceful开启优雅停机查看结果。

添加配置server.shutdown=graceful开启优雅停机

可以看到,效果确实如官方所描述的一样,应用在等待服务执行完毕后才退出。且从Commencing graceful shutdown. Waiting for active requests to complete之后,客户端请求接口已经直接失败了,也就是说这个时候应用已经不接受客户端新的请求了。

3.添加配置

server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s

开启优雅停机,查看结果。

可以看到,服务在运行结束后就退出了,并没有坚持等到30s才彻底关闭进程。

4.添加配置

server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=3s

开启优雅停机,查看结果。

可以看到,当服务还在执行,但是已经到了配置的超时时间时,应用会尝试关闭,但是因为有服务还没处理完,所以关闭失败,接下来优雅停机就被中断进行强制停机了。

小结

之前的时候,我们为了实现微服务的优雅下线,有手动实现过类似的功能。不过官方有提供终归是好事情。开启这个功能很方便,但是这个超时时间的配置还是要根据具体业务来进行配置。以免造成服务长时间不能终止或者服务过早终止,导致后台业务没有处理完成。

Quick Starter For Spring Boot Application

发表于 2021-02-21   |   分类于 Spring Boot   |   暂无评论

Spring Boot已经很流行了。几乎所有的Java开发人员都会用到,最起码也听说过了。

Spring Boot可以用来快速构建一个web程序,但是如果要优雅一点,处理好基本权限控制、做好全局异常处理、定义好统一返回格式,这些都做好,还是要做一些重复工作的。而且,每个开发人员的实现有可能不一样。Github上开源了一大波Spring Cloud完整例子,几乎把所有Spring Cloud的组件都用到了。但是那个对一些小程序来说,却有点大了。

有时候,我们的需求很简单,就是要写个web程序,对外提供一些接口。还没有达到要上Spring Cloud的程度。

基于这个需求,我搞了个Spring Boot的Quick Starter。第一个版本仅提供了一些基础支持。

GITHUB:https://github.com/hanbin/quick-start/releases/tag/v1.0
GITEE: https://gitee.com/coder_hanbin/quick-start/releases/v1.0

Release Notes:

  • 封装了基础的CommonResponse类;
  • 配置了Swagger配置;
  • 添加了一个极简的token验证AuthInterceptor,并且在Swagger页面中可以直接填写测试;
  • 提供了CustomApplicationReadyEvent和PrintSomethingBean ,分别实现了ApplicationListener和CommandLineRunner,参考这两个类可以在项目启动过程中做一些初始化动作和日志打印。
  • 提供了全局异常拦截,封装了自定义异常ApplicationException, 建议在主动抛异常时使用这个。
  • 提供了全局异常处理。

Spring Boot工程结构

发表于 2021-01-30   |   分类于 Spring Boot   |   暂无评论

官方推荐

在官方文档中, Spring官方建议我们正确使用“default” 包,将Main Application Class放置在“default” 包下。推荐结构如下:

com
 +- example
     +- myapplication
         +- Application.java
         |
         +- customer
         |   +- Customer.java
         |   +- CustomerController.java
         |   +- CustomerService.java
         |   +- CustomerRepository.java
         |
         +- order
             +- Order.java
             +- OrderController.java
             +- OrderService.java
             +- OrderRepository.java

在这个结构的基础上,Main函数强烈建议放置在com.example.myapplication这个"default" 包路径下。这种情况下,Main方法的类只需要如下书写即可:

package com.example.myapplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

原因探究

这个结构有什么好处呢?可以看到上面启动类中,类上面只有一个注解@SpringBootApplication,这个注解提供了多个能力。请看下面的代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {...}

官方文档对@SpringBootApplication有做比较详细的介绍,点这里查看。

  • @EnableAutoConfiguration: 开启自动配置。
  • @ComponentScan: 开启包路径扫描,默认情况下会扫描当前包和子包的@Service, @Controller,@Component等注解。
  • @Configuration: 允许在上下文中注册其他的Bean或者导入其他配置类。

强调Main Class一定要放在“default” 包下,就是因为只有这样,@ComponentScan才能扫描到所有的注解。

再看@SpringBootApplication的内容代码,使用@SpringBootApplication的属性exclude,excludeName,scanBasePackages, scanBasePackageClasses, nameGenerator, proxyBeanMethods就可以完成@EnableAutoConfiguration, @ComponentScan以及@Configuration的属性自定义。

/**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    /**
     * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
     * for a type-safe alternative to String-based package names.
     * <p>
     * <strong>Note:</strong> this setting is an alias for
     * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
     * scanning or Spring Data {@link Repository} scanning. For those you should add
     * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
     * {@code @Enable...Repositories} annotations.
     * @return base packages to scan
     * @since 1.3.0
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    /**
     * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
     * scan for annotated components. The package of each class specified will be scanned.
     * <p>
     * Consider creating a special no-op marker class or interface in each package that
     * serves no purpose other than being referenced by this attribute.
     * <p>
     * <strong>Note:</strong> this setting is an alias for
     * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
     * scanning or Spring Data {@link Repository} scanning. For those you should add
     * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
     * {@code @Enable...Repositories} annotations.
     * @return base packages to scan
     * @since 1.3.0
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    /**
     * The {@link BeanNameGenerator} class to be used for naming detected components
     * within the Spring container.
     * <p>
     * The default value of the {@link BeanNameGenerator} interface itself indicates that
     * the scanner used to process this {@code @SpringBootApplication} annotation should
     * use its inherited bean name generator, e.g. the default
     * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
     * application context at bootstrap time.
     * @return {@link BeanNameGenerator} to use
     * @see SpringApplication#setBeanNameGenerator(BeanNameGenerator)
     * @since 2.3.0
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    /**
     * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
     * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
     * case of direct {@code @Bean} method calls in user code. This feature requires
     * method interception, implemented through a runtime-generated CGLIB subclass which
     * comes with limitations such as the configuration class and its methods not being
     * allowed to declare {@code final}.
     * <p>
     * The default is {@code true}, allowing for 'inter-bean references' within the
     * configuration class as well as for external calls to this configuration's
     * {@code @Bean} methods, e.g. from another configuration class. If this is not needed
     * since each of this particular configuration's {@code @Bean} methods is
     * self-contained and designed as a plain factory method for container use, switch
     * this flag to {@code false} in order to avoid CGLIB subclass processing.
     * <p>
     * Turning off bean method interception effectively processes {@code @Bean} methods
     * individually like when declared on non-{@code @Configuration} classes, a.k.a.
     * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
     * equivalent to removing the {@code @Configuration} stereotype.
     * @since 2.2
     * @return whether to proxy {@code @Bean} methods
     */
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;

小结

Spring Boot官方推荐的工程结构在大部分情况下都适用,用这个结构也是最保险的。如果有特殊情况,务必要记得配置@ComponentScan 的包路径。

也来聊下Spring Boot开启SSL

发表于 2020-12-18   |   分类于 Spring Boot   |   2 条评论

这部分的内容在官方文档中有提到。链接在这儿:Configure SSL 。

网络上关于Spring Boot开启SSL访问的文章有很多。希望这篇文章能带来一点不一样的。

生成证书

首先,开启SSL访问得有证书,因为是本地访问,那么我们就使用jdk自带的keytool生成一个。

PS C:\Program Files\Java\jdk1.8.0_241\bin> .\keytool -genkey -keyalg RSA -keysize 2048 -keystore D:\keystore.jks
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  hanbin
您的组织单位名称是什么?
  [Unknown]:  home
您的组织名称是什么?
  [Unknown]:  hanbin-pc
您所在的城市或区域名称是什么?
  [Unknown]:  Xi'an
您所在的省/市/自治区名称是什么?
  [Unknown]:  Shaanxi
该单位的双字母国家/地区代码是什么?
  [Unknown]:  China
CN=hanbin, OU=home, O=hanbin-pc, L=Xi'an, ST=Shaanxi, C=China是否正确?
  [否]:  y

输入 <mykey> 的密钥口令
        (如果和密钥库口令相同, 按回车):

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore D:\keystore.jks -destkeystore D:\keystore.jks -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

基础配置

本文示例项目使用 https://blog.hanbinit.com.cn/archives/146 中的demo application。将D盘下面生成的keystore.jks复制到项目的resources目录下。

按照官方文档中的示例在application.properties中配置如下:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=123456
server.ssl.key-password=123456

启动的时候可以看到如图1所示日志。

图1

可以看到,系统启动的时候只启动了8443端口,且是https协议的。

访问https://localhost:8443/get_name,结果如图2所示。

图2

server.ssl.enabled默认就是true,配置了证书信息后,SSL就算是被激活了。这个时候server.port配置的就不是http的端口了。官方有如下说明:

  • Using configuration such as the preceding example means the application no longer supports a plain HTTP connector at port 8080. Spring Boot does not support the configuration of both an HTTP connector and an HTTPS connector through application.properties. If you want to have both, you need to configure one of them programmatically. We recommend using application.properties to configure HTTPS, as the HTTP connector is the easier of the two to configure programmatically.

如果只配置了上面的信息,就相当于开启了https,禁用了http。

支持https后http去哪儿了

如果还需要支持http,需要通过代码实现。官方文档在这儿说明了如何开启多个Connector。

  • You can add an org.apache.catalina.connector.Connector to the TomcatServletWebServerFactory, which can allow multiple connectors, including HTTP and HTTPS connectors, as shown in the following example:
@Bean
public ServletWebServerFactory servletContainer() {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    tomcat.addAdditionalTomcatConnectors(createSslConnector());
    return tomcat;
}

private Connector createSslConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    try {
        File keystore = new ClassPathResource("keystore").getFile();
        File truststore = new ClassPathResource("keystore").getFile();
        connector.setScheme("https");
        connector.setSecure(true);
        connector.setPort(8443);
        protocol.setSSLEnabled(true);
        protocol.setKeystoreFile(keystore.getAbsolutePath());
        protocol.setKeystorePass("changeit");
        protocol.setTruststoreFile(truststore.getAbsolutePath());
        protocol.setTruststorePass("changeit");
        protocol.setKeyAlias("apitester");
        return connector;
    }
    catch (IOException ex) {
        throw new IllegalStateException("can't access keystore: [" + keystore
                + "] or truststore: [" + truststore + "]", ex);
    }
}

上面是官方提供的示例代码,它开启了一个SSL Connector。我们要在默认开启https的基础上至此http访问。应该对上面的代码进行改造。在启动类中添加如下代码:

@Value("${server.http.port}")
    private int httpPort;

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(createHttpConnector());
        return tomcat;
    }

    private Connector createHttpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setSecure(false);
        connector.setPort(httpPort);
        return connector;
    }

在application.properties中添加配置项: server.http.port=8080

重新启动应用。可以看到日志和刚才不一样了,如图3。
图3
通过启动日志可以看到多出了8080的http支持。这个时候使用http://localhost:8080/get_name访问也能正常拿到结果了。

最后,说说http转发到https, 直接在代码中添加下面的代码是不会自动跳转的。

@Value("${server.port}")
private int httpsPort;
...
connector.setRedirectPort(httpsPort);
http的redirectPort只有在所使用的接口一定要使用https访问的时候才会跳转。所以还要再添加一些代码,指定默认Https Connector的拦截url。修改前面的servletContainer方法

@Bean
public ServletWebServerFactory servletContainer() {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(){
        @Override
        protected void postProcessContext(Context context) {
            SecurityConstraint securityConstraint = new SecurityConstraint();
            securityConstraint.setUserConstraint("CONFIDENTIAL");
            SecurityCollection collection = new SecurityCollection();
            collection.addPattern("/*");
            securityConstraint.addCollection(collection);
            context.addConstraint(securityConstraint);
        }
    };
    tomcat.addAdditionalTomcatConnectors(createHttpConnector());
    return tomcat;
}

接下来访问http://localhost:8080/get_name 会直接跳转到 https://localhost:8443/get_name 。

123456

一个高端大气上档次的网站

27 文章
4 分类
25 标签
RSS
Gitee 知乎
© 2021 蘑菇大叔自留地
Typecho
主题 - NexT.Pisces
沪ICP备12020779号-2