0%

引入依赖

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.3.0</version>
</dependency>

编写工具类

public class QRCodeUtil {
    // 设置二维码长宽
    private static int width = 400;
    private static int height = 400;
    private static String format = "png";

    // 将文本信息封装到二维码中,并返回图片的Base64编码后的字符串
    public static String encodeQRCode(String text) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        String result = null;
        try {
            Map<EncodeHintType, Object> hints = new HashMap<>();
            // 设置字符集编码
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            // ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
            // 不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            // 设置二维码边距,单位像素,值越小,二维码距离四周越近
            hints.put(EncodeHintType.MARGIN, 1);
            // 生成二维码矩阵
            BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
            // 写入文件
            MatrixToImageWriter.writeToStream(bitMatrix, format, outputStream);
            bitMatrix.clear();
            result = "data:image/png;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outputStream.close();
            } catch (IOException e) {
            }
        }
        return result;

    }

    // 通过base64编码后的图片,解析出二维码中的文本信息
    public static String decodeQRCode(String base64) {
        Result result = null;
        BufferedImage image = null;
        ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64));
        try {
            // 读取图片
            image = ImageIO.read(inputStream);
            // 多步解析
            LuminanceSource source = new BufferedImageLuminanceSource(image);
            Binarizer binarizer = new HybridBinarizer(source);
            BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
            // 设置字符集编码
            Map<DecodeHintType, String> hints = new HashMap<>();
            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
            // 对图像进行解码
            result = new MultiFormatReader().decode(binaryBitmap, hints);
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            image.getGraphics().dispose();
            try {
                inputStream.close();
            } catch (IOException e) {
            }
        }
        return result.getText();
    }
}

测试

    // 测试准确率还是非常高的
    public static void main(String[] args) {
        // 生成二维码
        System.out.println(encodeQRCode("我叫XX勇~~~"));
        // 解码二维码
        // 注意:需要去掉data:image/png;base64,
        System.out.println(decodeQRCode("xxxxxxxxxx.....xxxxxxxx"));
    }

问题

  • openjdkoracle jdk一些情况下并不兼容;
  • openjdk镜像很小只有一百朵兆,但是oracle jdk搜到的镜像都很大.

步骤

  1. 下载最新的oracle jre 8
https://javadl.oracle.com/webapps/download/AutoDL?BundleId=242050_3d5a2bb8f8d4428bbe94aed7ec7ae784
  1. 上传到linux并解压
[root@node1 jdk8]# tar -xvzf jre-8u251-linux-x64.tar.gz 
[root@node1 jdk8]# pwd
/root/jdk8
[root@node1 jdk8]# ll
总用量 85492
drwxr-xr-x 5 10143 10143       76 7月   9 14:04 jre1.8.0_251
-rw-r--r-- 1 root  root  87543611 7月   9 13:55 jre-8u251-linux-x64.tar.gz
  1. 删除一些说明文档和无用包
[root@node1 jdk8]# cd jre1.8.0_251/

rm -rf COPYRIGHT LICENSE README release THIRDPARTYLICENSEREADME-JAVAFX.txt THIRDPARTYLICENSEREADME.txt Welcome.html
rm -rf   lib/plugin.jar \
           lib/ext/jfxrt.jar \
           bin/javaws \
           lib/javaws.jar \
           lib/desktop \
           plugin \
           lib/deploy* \
           lib/*javafx* \
           lib/*jfx* \
           lib/amd64/libdecora_sse.so \
           lib/amd64/libprism_*.so \
           lib/amd64/libfxplugins.so \
           lib/amd64/libglass.so \
           lib/amd64/libgstreamer-lite.so \
           lib/amd64/libjavafx*.so \
           lib/amd64/libjfx*.so

[root@node1 jre1.8.0_251]# ll
总用量 4
drwxr-xr-x  2 10143 10143  208 7月   9 13:56 bin
drwxr-xr-x 13 10143 10143 4096 7月   9 13:56 lib
drwxr-xr-x  4 10143 10143   47 3月  12 14:33 man
  1. 重新打包
[root@node1 jre1.8.0_251]# tar zcvf jre8.tar.gz *
[root@node1 jre1.8.0_251]# ll
总用量 43896
drwxr-xr-x  2 10143 10143      208 7月   9 13:56 bin
-rw-r--r--  1 root  root  44942683 7月   9 14:04 jre8.tar.gz
drwxr-xr-x 13 10143 10143     4096 7月   9 13:56 lib
drwxr-xr-x  4 10143 10143       47 3月  12 14:33 man
  1. 直接在当前目录下创建Dockerfile
[root@node1 jre1.8.0_251]# vim Dockerfile

编写Dockerfile

FROM docker.io/jeanblanchard/alpine-glibc
MAINTAINER wuzhiyong
ADD jre8.tar.gz /usr/java/jdk/
ENV JAVA_HOME /usr/java/jdk
ENV PATH ${PATH}:${JAVA_HOME}/bin
WORKDIR /opt
  1. 生成镜像
# 生成镜像
[root@node1 jre1.8.0_251]# docker build -t wuzhiyong/java8.0_251 .
Sending build context to Docker daemon  161.1MB
Step 1/6 : FROM docker.io/jeanblanchard/alpine-glibc
 ---> c1bfe6541128
Step 2/6 : MAINTAINER wuzhiyong
 ---> Using cache
 ---> 5e4699308c35
Step 3/6 : ADD jre8.tar.gz /usr/java/jdk/
 ---> 8b6b66ec0f68
Step 4/6 : ENV JAVA_HOME /usr/java/jdk
 ---> Running in 26f286188aa8
Removing intermediate container 26f286188aa8
 ---> 7d1501133f4c
Step 5/6 : ENV PATH ${PATH}:${JAVA_HOME}/bin
 ---> Running in 418be370f8b9
Removing intermediate container 418be370f8b9
 ---> 8a920d8285df
Step 6/6 : WORKDIR /opt
 ---> Running in 107687a0007d
Removing intermediate container 107687a0007d
 ---> e7a62387e585
Successfully built e7a62387e585
Successfully tagged wuzhiyong/java8.0_251:latest
#查看镜像大小
[root@node1 jre1.8.0_251]# docker images | grep java8
wuzhiyong/java8.0_251                                            latest                    e7a62387e585        15 seconds ago      133MB
  1. 上传到镜像仓库,可以下载使用
docker pull hub.deri.org.cn/library/oracle_jdk_1.8_251:latest

引入依赖

<!-- 注意版本号,测试2.0.3版本有严重问题,启动失败 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>

注册邮箱

  • 163,126,qq都行,这边测试126邮箱;
  • 开启IMAP/SMTP服务;
  • 获取授权密码,注意保存.

配置application.yaml

spring:
  mail:
    # 注册的邮箱
    username: xxxxxx@126.com
    # 密码为授权码
    password: XXXXXXXXXXXXXXX
    # smtp服务器地址
    host: smtp.126.com
    # 安全相关配置,非必须
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true

测试发送

@Service
public class MailService {
    // 这个在IDEA中会提示没有bean,注入失败,不用管~
    @Autowired
    JavaMailSender mailSender;

    public void send(){
        // 普通文本邮件
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        //邮件发送方,需要写完整邮箱地址
        simpleMailMessage.setFrom("XXXXX@126.com");
        //邮件接收方邮箱
        simpleMailMessage.setTo("1154365135@qq.com");
        //邮件标题
        simpleMailMessage.setSubject("测试主题~~");
        //邮件内容
        simpleMailMessage.setText("测试内容~~");
        //发送邮件
        mailSender.send(simpleMailMessage);
    }

    public void sendHtml() throws MessagingException {
        // html邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        //邮件发送方
        helper.setFrom("authservice@126.com");
        //邮件接收方邮箱
        helper.setTo("1154365135@qq.com");
        //邮件标题
        helper.setSubject("测试HTML~~");
        //邮件内容
        helper.setText("<html>\n" +
                "<head>\n" +
                "<meta charset=\"utf-8\">\n" +
                "<title>菜鸟教程(runoob.com)</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <h1>我的第一个标题</h1>\n" +
                "    <p style=\"color: blue;\">我的第一个段落。</p>\n" +
                "</body>\n" +
                "</html>", true);
        //发送邮件
        mailSender.send(mimeMessage);
    }

        //附件邮件, 未测试
    public void sendAttachmentMail(String to,String subject,String content,String filePath) throws MessagingException {
        MimeMessage mimeMessage=mailSender.createMimeMessage();
        MimeMessageHelper helper=new MimeMessageHelper(mimeMessage,true);
        helper.setTo(to);
        helper.setSubject(subject);
        helper.setText(content,true);
        helper.setFrom(form);
        FileSystemResource file=new FileSystemResource(new File(filePath));
        String fileName=file.getFilename();
        helper.addAttachment(fileName,file);
        //helper.addAttachment(fileName+"02",file);  如果是多个附件的话,可以这样写。但是开发中一般都是把filepath做成一个数组,这样在这里遍历就可以了
        //helper.addAttachment(fileName+"03",file);

        mailSender.send(mimeMessage);
    }

    //图片邮件,未测试
    public void sendInlineResourceMail(String to,String subject,String content,String rscPath,String rscId) throws MessagingException {
        MimeMessage mimeMessage=mailSender.createMimeMessage();
        MimeMessageHelper helper=new MimeMessageHelper(mimeMessage,true);
        helper.setTo(to);
        helper.setSubject(subject);
        helper.setText(content,true);
        helper.setFrom(form);
        FileSystemResource file=new FileSystemResource(new File(rscPath));
        helper.addInline(rscId,file);

        mailSender.send(mimeMessage);
    }
}

验证码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