0%

验证码EasyCaptcha介绍

使用

<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
    <version>1.6.2</version>
</dependency>

也许下载不下来,可以直接下载 easy-captcha.jar 包.

使用下面的命令可以将jar包安装到本地仓库.

mvn install:install-file -Dfile=D:/easy-captcha.jar -DgroupId=com.github.whvcse -DartifactId=easy-captcha -Dversion=1.6.2 -Dpackaging=jar

开发

// PNG格式 设置长宽和验证码长度
SpecCaptcha captcha = new SpecCaptcha(180, 40, 6);
// 设置验证码字符类型,数字/大小写字母
captcha.setCharType(Captcha.TYPE_ONLY_NUMBER);

// GIF格式 设置长宽和验证码长度
GifCaptcha captcha = new GifCaptcha(180, 48, 6);

// 中文验证码 设置长宽和验证码长度
ChineseCaptcha captcha = new ChineseCaptcha(180, 48);
// 设置字体, 设置默认字体Captcha.FONT_1...好像有问题,英文可以使用默认字体
captcha.setFont(new Font("楷体", Font.PLAIN, 28));

// 使用算术验证码,设置长宽和几位数运算
ArithmeticCaptcha captcha = new ArithmeticCaptcha(180, 48, 2);
// 获取运算的公式:3+2=?
captcha.getArithmeticString()

// 通用方法
captcha.text();  // 获取验证码的字符
captcha.textChar();  // 获取验证码的字符数组

// 验证码保存到文件
FileOutputStream outputStream = new FileOutputStream(new File("C:/captcha.png"))
captcha.out(outputStream);  // 输出验证码

// 输出base64编码, 返回给前端需要用到
specCaptcha.toBase64();

// 如果不想要base64的头部data:image/png;base64,
specCaptcha.toBase64("");  // 加一个空的参数即可

测试

已知问题

同样的程序本地测试没有问题,部署到docker上测试出现问题.

  • 解决办法: 不要使用openjdk做基础镜像,采用oracle jdk.
  • 这边提供一个能用的: docker pull hub.deri.org.cn/library/oracle_jdk_1.8_131:latest

1955年,卡普耶卡(D.R.Kaprekar)研究了对四位数的一种变换:任给出四位数k0,用它的四个数字由大到小重新排列成一个四位数m,再减去它的反序数rev(m),得出数k1=m-rev(m),然后,继续对k1重复上述变换,得数k2.如此进行下去,卡普耶卡发现,无论k0是多大的四位数,只要四个数字不全相同,最多进行7次上述变换,就会出现四位数6174。因此这项研究在国际数学界又被称为“马丁猜想—6174问题”。

有趣的数字6174

  • 随机生成四个不完全一样的数字(0000,1111,2222,等排除);
  • 四个数字组成一个最大的数 和 一个最小的数,如2,5,7,3组成的最大的数7532,最小的数2357;
  • 最大的数 - 最小的数,如果不等于6174,就按照上一步将差值重新组成一个最大的数 和 一个最小的数;
  • 最后一定有一次能得到差值为6174.俗称数字黑洞.

JAVA实现

public class Test6174 {
    public static void main(String[] args) throws InterruptedException {
        // 无线循环测试
        while (true) {
            // 随机生成四个数字,考虑到出现四个一样的概率非常低 没有处理
            List<Integer> meta = new ArrayList<>();
            for (int i = 0; i < 4; i++) {
                meta.add(new Random().nextInt(10));
            }
            System.out.print("原始数字:" + meta);
            int result = 0, count = 0;
            while (result != 6174) {
                // 获取四个数字组合的最大的数 和 最小的数
                int max = getMax(meta);
                int min = getMin(meta);
                result = Math.abs(max - min);
                count++;
                // 数字为啥是6174?
                if (result == 6174) {
                    System.out.println(",次数:" + count);
                }
                meta = getMeta(result);
            }
            Thread.sleep(1000);
        }
    }

    public static int getMax(List<Integer> meta) {
        List<Integer> tmp = meta.stream().sorted().collect(Collectors.toList());
        return tmp.get(0) * 1000 + tmp.get(1) * 100 + tmp.get(2) * 10 + tmp.get(3);
    }

    public static int getMin(List<Integer> meta) {
        List<Integer> tmp = meta.stream().sorted().collect(Collectors.toList());
        return tmp.get(0) + tmp.get(1) * 10 + tmp.get(2) * 100 + tmp.get(3) * 1000;
    }

    public static List<Integer> getMeta(int num) {
        List<Integer> tmp = new ArrayList<>();
        tmp.add(num / 1000 % 10);
        tmp.add(num / 100 % 10);
        tmp.add(num / 10 % 10);
        tmp.add(num % 10);
        return tmp;
    }
}

同样的黑洞数字还有很多哦

不如写个程序来找出不同位数的黑洞数字吧

public class TestN {
    public static void main(String[] args) throws InterruptedException {
        // 输入几位数就是找几位数的黑洞数字
        int digits = 5;
        List<Integer> meta = new ArrayList<>();
        for (int i = 0; i < digits; i++) {
            meta.add(new Random().nextInt(10));
        }
        if (getMax(meta) - getMin(meta) == 0){
            System.out.println("数字完全一样,不符合要求");
            System.exit(0);
        }
        System.out.println("原始数字:" + meta);
        int result = 0;
        while (true) {
            int max = getMax(meta);
            int min = getMin(meta);
            result = Math.abs(max - min);
            meta = getMeta(result, digits);
            System.out.println(result);
        }
    }

    // 获取数字组合的最大数
    public static int getMax(List<Integer> meta) {
        List<Integer> tmp = meta.stream().sorted().collect(Collectors.toList());
        double result = 0;
        for (int i = 0; i < tmp.size(); i++) {
            result = result + tmp.get(i) * (Math.pow(10.0, (double) (i)));
        }
        return (int) result;
    }

    // 获取数字组合的最小数
    public static int getMin(List<Integer> meta) {
        List<Integer> tmp = meta.stream().sorted().collect(Collectors.toList());
        double result = 0;
        for (int i = 0; i < tmp.size(); i++) {
            result = result + tmp.get(i) * (Math.pow(10.0, (double) (tmp.size() - i - 1)));
        }
        return (int) result;
    }

    // 获取数的各位数
    public static List<Integer> getMeta(int num, int c) {
        List<Integer> tmp = new ArrayList<>();
        for (int i = 0; i < c; i++) {
            tmp.add(num / ((int) Math.pow(10.0, i)) % 10);
        }
        return tmp;
    }
}

如上程序测试运行:

digits(位数) 黑洞数(个) 结果
1 - -
2 5 9,81,63,27,45
3 1 495
4 1 6174
5 4 71973,83952,74943,62964
6 7 840852,860832,862632,642654,...
7 8 9529641,8719722,8649432,7519743,...
8 3 64308654,83208762,86526432
9 14 954197541,883098612,976494321,...

问题

[root@master busybox]# kubectl get pod -nkube-system -owide
NAME                             READY   STATUS    RESTARTS   AGE     IP               NODE     NOMINATED NODE   READINESS GATES
coredns-5c98db65d4-8zjps         1/1     Running   1          2d23h   10.244.0.13      master   <none>           <none>
coredns-5c98db65d4-d2kth         1/1     Running   1          2d23h   10.244.0.14      master   <none>           <none>
[root@master busybox]# kubectl get pod -owide
NAME                      READY   STATUS    RESTARTS   AGE    IP            NODE     NOMINATED NODE   READINESS GATES
curl-6bf6db5c4f-pjld9     1/1     Running   1          3d     10.244.1.2    node2    <none>           <none>
gateway-99b655cc6-np685   1/1     Running   0          44s    10.244.0.54   master   <none>           <none>
test-post-start1          1/1     Running   0          115s   10.244.1.6    node2    <none>           <none>
test-post-start2          1/1     Running   0          115s   10.244.0.52   master   <none>           <none>
test-post-start3          1/1     Running   0          115s   10.244.0.53   master   <none>           <none>

如上所示,出现部署在master节点上的pod,无法解析gateway.default.svc.cluster.local域名,但是部署在node2,确可以解析,如上curl-6bf6db5c4f-pjld9,test-post-start1通过nslookup都可以解析.

# 报错
/ # nslookup gateway
nslookup: can't resolve '(null)': Name does not resolve

nslookup: can't resolve 'gateway': Try again

/ # nslookup gateway.default.svc.cluster.local
Server:    10.244.0.10
Address 1: 10.244.0.10

nslookup: can't resolve 'gateway.default.svc.cluster.local'

分析

进入master节点pod,直接通过coredns pod ip解析测试

kubectl exec -it test-post-start2 sh
/ # nslookup gateway.default.svc.cluster.local 10.244.0.13
Server:    10.244.0.13
Address 1: 10.244.0.13 10-244-0-13.kube-dns.kube-system.svc.cluster.local

Name:      gateway.default.svc.cluster.local
Address 1: 10.244.106.29 gateway.default.svc.cluster.local

发现直接通过coredns pod ip解析可以成功,证明coredns服务本身没有问题.

查看dns clusterIP.

[root@master ~]# kubectl get svc -nkube-system
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
kube-dns        ClusterIP   10.244.0.10    <none>        53/UDP,53/TCP,9153/TCP   21m
# 通过clusterIP解析域名失败
nslookup gateway.default.svc.cluster.local 10.244.0.10

通过以上测试证明问题出现在coredns service上.

解决

导出现有kube-dns service配置

kubectl get svc -nkube-system kube-dns -oyaml > kube-dns-svc.yaml

修改kube-dns-svc.yaml.

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: KubeDNS
  name: kube-dns
  namespace: kube-system
spec:
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  - name: metrics
    port: 9153
    protocol: TCP
    targetPort: 9153
  selector:
    k8s-app: kube-dns
  sessionAffinity: None
  type: ClusterIP
kubectl apply -f kube-dns-svc.yaml

查看最新的coredns clusterIP,当前为10.244.47.231.

[root@master ~]# kubectl get svc -nkube-system
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
kube-dns        ClusterIP   10.244.47.231    <none>        53/UDP,53/TCP,9153/TCP   21m

进去之前无法解析的pod中测试,证明新的clusterIP没有问题.

nslookup gateway.default.svc.cluster.local 10.244.47.231

修改kubelet --clusterDNS,这样新创建的pod /etc/resolv.confnameserver为新的coredns clusterIP.

# 修改kubelet配置
vim  /var/lib/kubelet/config.yaml

# 找到clusterDNS
clusterDNS:
- 10.244.47.231

# 重启kubelet生效,注意k8s中所有节点都需要修改重启
systemctl restart kubelet.service

最后测试,新的pod/etc/resolv.conf.解析没有问题.

/ # cat /etc/resolv.conf 
nameserver 10.244.47.231
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

查询series cardinality命令

InfluxDB maintains an in-memory index of every series in the system. As the number of unique series grows, so does the RAM usage. High series cardinality can lead to the operating system killing the InfluxDB process with an out of memory (OOM) exception. See SHOW CARDINALITY to learn about the InfluxSQL commands for series cardinality.

// 翻译
InfluxDB会在系统上为每个series维护一个内存索引,而随着这些series的增加,RAM内存使用率也会增加。如果series cardinality如果太高,就会导致操作系统触发OOMKiller机制,将Influxdb进程KILL掉. 使用 SHOW CARDINALITY 命令可以查看到 series cardinality。
-- show estimated cardinality of the series on current database
SHOW SERIES CARDINALITY
-- show estimated cardinality of the series on specified database
SHOW SERIES CARDINALITY ON mydb
-- show exact series cardinality
SHOW SERIES EXACT CARDINALITY
-- show series cardinality of the series on specified database
SHOW SERIES EXACT CARDINALITY ON mydb
To reduce series cardinality, series must be dropped from the index. DROP DATABASE, DROP MEASUREMENT, and DROP SERIES will all remove series from the index and reduce the overall series cardinality.

//大意
要减少或者删除series cardinality, 需要删除库//series

将series由保存到内存改为保存到TSI文件

修改配置

这里我们需要修改的配置是index-version项,可以在influxdb.conf[data]下修改,也可以通过环境变量INFLUXDB_DATA_INDEX_VERSION修改.

[data]
  # The type of shard index to use for new shards.  The default is an in-memory index that is
  # recreated at startup.  A value of "tsi1" will use a disk based index that supports higher
  # cardinality datasets.
  # 这个配置项默认值inmem,可以取消注释修改为tsi1,那么后续的index将会保存在TSI文件中了.重启生效.
  # index-version = "inmem"

重构TSI索引

  1. 停止InfluxDB服务
  2. 删除所有_series文件夹
    默认情况下,_series保存在/data/<dbName>/_series,检查并删除/data目录下所有_series
  3. 删除所有index文件夹
    默认情况下,index文件夹在/data/<dbName/<rpName>/<shardID>/index
  4. 使用influx_inspect重构TSI index
# 格式
influx_inspect buildtsi -datadir <data_dir> -waldir <wal_dir>
# 示例
influx_inspect buildtsi -datadir /data -waldir /wal
  1. 启动influxDB服务

问题

k8singress使用的是ingress-nginx.

项目中用到了spring cloud gatewayAPI网关,部署环境有dockerk8s等. 测试出现一个问题:
请求的URL路径中如果出现了中文参数,如http://localhost:8080/test?param=你好,如果部署在docker或者裸机上,后台服务接收到的参数param你好,但是如果部署在k8s上,后台接收到的参数可能是UrlEncode之后的%E4%BD%A0%E5%A5%BD,这不是后台需要的. 虽然可以在后台通过URLDecoder.decode(name, "UTF-8")强制解码,不过比较麻烦.

分析

测试中发现,

  • 如果直接将后台服务部署到k8s上,通过service -> ingress暴露出来,不管是通过k8sClusterIP,如http://10.96.240.18:8080/test?param=你好, 还是通过ingress域名http://local.com/test?param=你好请求后台服务,后台能够正确接收到参数为你好,证明问题可能不是因为部署在k8s上造成的,只可能是spring cloud gatewayAPI网关原因;
  • 但是部署在docker或者裸机上,通过spring cloud gateway网关请求后台服务,后台能够正确接收到参数为你好,证明网关也是没有问题的,问题只可能是部署环境k8s的原因;
  • 如果将网关服务部署到k8s上,又有两种情况:
    • 通过ingress域名请求网关 -> 后台,后台接收到的参数是%E4%BD%A0%E5%A5%BD,看似是网关的问题;
    • 通过ClusterIP请求网关 -> 后台,后台接收到的参数是你好,这又证明网关没问题,看似是ingress的问题,但前面也证明了ingress可以正确传递中文字符,非常的奇怪!
  • spring cloud gateway中所有的Filter全部注释掉,发现通过ingress域名请求网关 -> 后台,后台也能正确的接收到中文参数,证明还是网关的问题,而且这个问题只有部署在k8s中搭配ingress-nginx使用时才会暴露.

解决

依次将注释掉的Filter加回去,证明问题出现在class XXXX extends ForwardedHeaderFilter这个类上.这个类用于设置网关跨域访问和网关本身接口权限验证.

测试发现,即使只注释成下面这样,还是会有问题,但是注释掉@Component,就没有问题.应该是super.filter(exchange, chain)父类这个方法有问题.

@Component
@Slf4j
public class AuthTokenFilter extends ForwardedHeaderFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        //跨域问题 接口权限验证 TODO
        return super.filter(exchange, chain);
    }
}

改成下面这样测试可以解决问题.

@Component
@Slf4j
public class AuthTokenFilter extends ForwardedHeaderFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        //跨域问题 接口权限验证 TODO
        return chain.filter(exchange);
    }
}

问题

spring cloud gateway集成了redis后,一直出现重连的日志,如下:

gateway        | 2020-06-28 10:56:11.133  INFO 1 --- [xecutorLoop-2-2] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.171.0.9/192.171.0.9:6379
gateway        | 2020-06-28 10:56:11.148  INFO 1 --- [llEventLoop-4-6] i.l.core.protocol.ReconnectionHandler    : Reconnected to 192.171.0.9:6379
gateway        | 2020-06-28 11:01:12.431  INFO 1 --- [xecutorLoop-2-3] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.171.0.9/192.171.0.9:6379
gateway        | 2020-06-28 11:01:12.441  INFO 1 --- [llEventLoop-4-8] i.l.core.protocol.ReconnectionHandler    : Reconnected to 192.171.0.9:6379
gateway        | 2020-06-28 11:06:13.231  INFO 1 --- [xecutorLoop-2-4] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.171.0.9/192.171.0.9:6379
gateway        | 2020-06-28 11:06:13.237  INFO 1 --- [llEventLoop-4-2] i.l.core.protocol.ReconnectionHandler    : Reconnected to 192.171.0.9:6379

分析

spring boot 2.0之后spring-boot-starter-data-redis默认不再使用jedis连接redis,而是lettuce.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 排除lettuce包,使用jedis代替-->
    <!--<exclusions>-->
        <!--<exclusion>-->
            <!--<groupId>io.lettuce</groupId>-->
            <!--<artifactId>lettuce-core</artifactId>-->
        <!--</exclusion>-->
    <!--</exclusions>-->
</dependency>
<!-- jedis -->
<!--<dependency>-->
    <!--<groupId>redis.clients</groupId>-->
    <!--<artifactId>jedis</artifactId>-->
    <!--<version>2.9.0</version>-->
<!--</dependency>-->

考虑是不是程序长时间没有使用redis,而导致连接断开.修改连接池最小空闲连接数:

spring.redis.host=localhost
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0, 修改为1
spring.redis.lettuce.pool.min-idle=1

注意修改完之后需要增加新的依赖包,不然会报错

<!--配置lettuce.pool.min-idle需要增加的依赖-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

重启完程序之后,发现每5分钟打印重连日志依然存在.

解决

  • 这个不是错误,只是一个INFO级别的日志,可以日志级别调高,例如:
<logger name="io.lettuce.core.protocol" level="ERROR">
    <appender-ref ref="ERROR_FILE" />
</logger>
  • 原因:这是lettuce-core的实现里,有类似心跳机制的保持长连接方式,不过心跳机制是不停的来回发心跳包直到连接不可用再去被动重新连接,而lettuce的方案是将连接池里处于空闲(idle)状态的client每隔一段时间就主动断开,然后再重新连接。

参考链接