Springboot公共封装及工具类

6/6/2021 SpringbootHutool跨域问题自定义注解

# 1. Springboot项目的创建

# 1.1 Springboot相关基本概念

Springboot: 是简化Spring应用开发的一个框架,其核心理念是:“约定优于配置”。是整个Spring技术栈的一个大整合、JavaEE开发的一站式解决方案。

调用逻辑: 控制层调业务层,业务层调数据层。

Springboot调用逻辑

基本概念:

(1)DAO(mapper),DAO= Data Acess Object, 数据持久层,对数据库进行持久化操作,负责跟数据库打交道。通常我们在DAO层里写接口,里面有与数据打交道的方法。SQL语句通常写在mapper文件里。

(2)Service,业务层或服务层,主要负责业务模块的逻辑应用设计。 Service层的实现,具体调用到已经定义的DAO接口,封装service层的业务逻辑有利于通用的业业务逻辑的独立性和重复利用性。如果把Dao层当作积木,那么Service层则是对积木的搭建。

(3)Controller, 负责具体的业务模块流程的控制。此层要调用Service层的接口去控制业务流程。

(4)Pojo 全称Plain Ordinary Java Object ,数据库实体类,有的地方也直接写成entity。也可以理解为domain,一般是跟数据库对应好的一个实体类。

(5)Bo ,bussiness object,表示业务对象的意思。bo就是把业务逻辑封装成一个对象,这个对象可以包括一个或多个对象。通过调用dao方法,结合Po或Vo进行业务操作。

(6)Vo ,value object表示值对象的也i是,通常用于业务层之间的数据传递。

(7)Po, persistant object, 代表持久层对象的意思,对应数据库中表的字段,可以理解为一个po就是数据库中的一条记录。

(8)Impl 全称是 implement, 实现的意思,主要用于实现接口。

# 1.2 Spring Initializer快速创建项目

IDEA支持使用Spring Initializer快速创建一个Spring Boot项目,选择我们需要的模块,向导会联网创建Spring Boot项目。

Step1:创建Spring Initializr工程,填写Project Metadata相关信息,根据需要选择模块(如Spring Web)

Step2:进去之后配置maven并拉取所需jar包,写个HelloController.java测试一下

package springboot_project.springboot_01_helloworld_quick.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        return "Hello world!";
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Step3:主程序已经生成好了,剩下我们只需要写自己的逻辑,resources文件夹中目录结构如下:

  • static:保存所有的静态资源(js css images)
  • templates:保存所有的模板页面(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面),可以使用模板引擎(freemarker、thymeleaf)
  • application.properties:SpringBoot应用的配置文件,可以修改一些默认设置。

# 2. Springboot公共封装

# 2.1 解决跨域问题

# 2.1.1 跨域与CORS简介

现代浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。跨域HTTP请求是指A域上资源请求了B域上的资源,举例而言,部署在A机器上Nginx上的js代码通过ajax请求了部署在B机器Tomcat上的RESTful接口。

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。

# 2.1.2 实现全局跨域

创建一个配置类,返回一个新的WebMvcConfigurer Bean,并重写其提供的跨域请求处理的接口,目的是添加映射路径和具体的CORS配置信息。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class GlobalCorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            //重写父类提供的跨域请求处理的接口
            public void addCorsMappings(CorsRegistry registry) {
                //添加映射路径
                registry.addMapping("/**")
                        //放行哪些原始域
                        .allowedOrigins("*")
                        //是否发送Cookie信息
                        .allowCredentials(true)
                        //放行哪些原始域(请求方式)
                        .allowedMethods("GET","POST", "PUT", "DELETE")
                        //放行哪些原始域(头部信息)
                        .allowedHeaders("*")
                        //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                        .exposedHeaders("Header1", "Header2");
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

注:如果你的SpringBoot>=2.4.0,请将上述代码的.allowedOrigins替换成.allowedOriginPatterns,否则跨域配置会出错。

# 2.2 统一接口响应格式

# 2.2.1 接口标准响应格式

当前主流的 Web 应用开发通常采用前后端分离模式,前端和后端各自独立开发,然后通过数据接口沟通前后端,完成项目。

因此,定义一个统一的数据下发格式,有利于提高项目开发效率,减少各端开发沟通成本。

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "name": "zhangsan",
        "email": "[email protected]"
    }
}
1
2
3
4
5
6
7
8

# 2.2.2 实现统一响应格式

[1] 数据统一下发实体:ResponseBean.java

@Getter
@ToString
public class ResponseBean<T> {
    private int code;
    private String msg;
    private T data;

    // 成功操作
    public static <E> ResponseBean<E> success(E data) {
        return new ResponseBean<E>(ResultCode.SUCCESS, data);
    }

    // 失败操作
    public static <E> ResponseBean<E> failure(E data) {
        return new ResponseBean<E>(ResultCode.FAILURE, data);
    }

    // 设置为 private
    private ResponseBean(ResultCode result, T data) {
        this.code = result.code;
        this.msg = result.msg;
        this.data = data;
    }

    // 设置 private
    private static enum ResultCode {
        SUCCESS(200, "操作成功"),
        FAILURE(500, "操作失败");

        ResultCode(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        private int code;
        private String msg;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

[2] 转换器配置类:WebConfiguration.java

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0, new MappingJackson2HttpMessageConverter());
    }
}
1
2
3
4
5
6
7
8

[3] 数据下发拦截器:FormatResponseBodyAdvice.java

@RestControllerAdvice
public class FormatResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        boolean isResponseBeanType = ResponseBean.class.equals(returnType.getParameterType());
        // 如果返回的是 ResponseBean 类型,则无需进行拦截修改,直接返回即可
        // 其他类型则拦截,并进行 beforeBodyWrite 方法进行修改
        return !isResponseBeanType;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return ResponseBean.success(body);
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseBean<String> handleException() {
        return ResponseBean.failure("Error occured");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.3 SpringBoot实现自定义注解

# 2.3.1 Java元注解

Java中提供了四种元注解,用来注解其他的注解,当我们创建自定义注解时,就需要使用元注解来标注我们定义的注解,以标识自定义注解的一些属性信息。@Target,@Retention,@Documented,@Inherited,这四种元注解都位于java.lang.annotation包下。

@Target

用来指定当前创建的注解作用于什么地方,如@Target({ElementType.TYPE})标识自定义注解可以作用于类、解耦或枚举类型上。注解取值范围为枚举常量ElementType的所有值,有:

  • ElementType.TYPE:用于类、接口或枚举声明
  • ElementType.FIELD:用于字段声明
  • ElementType.METHOD:用于方法声明
  • ElementType.PARAMETER:用于正式的参数声明
  • ElementType.CONSTRUCTOR:用于构造函数声明
  • ElementType.LOCAL_VARIABLE:用于局部变量声明
  • ElementType.ANNOTATION_TYPE:用于注解类型声明
  • ElementType.PACKAGE:用于包声明

@Retention

@Retention注解代表了一个注解的生命周期,其值对应了注解会留存到什么时候,如编译阶段、运行阶段等。使用时与RetentionPolicy枚举常量配合,如@Retention({RetentionPolicy.Runtime}),所有取值有:

  • @Retention({RetentionPolicy.SOURCE}):表示定义注解指保存在源码级别,编译时会忽略。
  • @Retention({RetentionPolicy.CLASS}):表示注解将会在编译时保存在类文件中,运行时不保留。
  • @Retention({RetentionPolicy.Runtime}):表示注解在编译时和运行时都存在,且可以被反向读取。

@Documented

使用@Documented注解标注的自定义注解会被添加在JavaDoc中。

@Inherited

如果父类使用了被@Inherited注解标注的自定义注解,那么其子类会继承自定义注解。

# 2.3.2 Spring AOP的实现

理解了Java中元注解的代表含义,那么我们可以使用这些元注解来实现自定义注解。

Step1:创建自定义注解

创建一个自定义注解,根据需要添加元注解并指定枚举值

import java.lang.annotation.*;

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
1
2
3
4
5
6
7

Step2:实现自定义注解逻辑

[1] 定义切面类

[2] 定义切点,并以自定义注解为切入

[3] 定义使用注解的方法需要附加的通知类型和内容

具体的注解逻辑实现代码为:

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 实现注解逻辑
 * @Aspect 定义切面类
 * @Component 注册为容器组件
 */
@Aspect
@Component
public class TestAnnotationImpl {

    //定义切点
    @Pointcut("@annotation(com.yoyo.admin.common.test.annotation.TestAnnotation)")
    private void cut(){
    }

    @Before("cut()")
    public void before(){
        System.out.println("自定义注解前置通知!");
    }
    @After("cut()")
    public void after(){
        System.out.println("自定义注解后置通知!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Step3:使用自定义注解

注解定义完成并将注解的具体逻辑实现后,我们就可以在controller控制层来使用我们自定义的注解了。

import com.yoyo.admin.common.test.annotation.TestAnnotation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api/web")
public class TestController {

    /**
     * 使用自定义注解测试
     * @return
     */
    @TestAnnotation
    @RequestMapping("/testAop")
    public String hello(){
        System.out.println("hello world!");
        return "hello world!";
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我们为方法使用了自定义注解后,访问方法地址,会发现在方法输出内容的前、后出现了自定义注解的内容,这就说明我们的注解发生了作用,为方法添加了前置通知和后置通知。

自定义注解前置通知!
hello world!
自定义注解后置通知!
1
2
3

# 2.4 实现Token登录验证过滤器

过滤器 (Filter) 是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器 (也就是过滤链) 对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改,也可以对响应进行过滤,拦截或修改响应。Filter中最重要的一个方法就是doFilter(),在该方法中,chain.doFilter()前面的一般是对request执行的过滤操作,chain.doFilter()后面的一般是对response执行的操作。

首先,在yml配置文件中,设置不需要过滤的URI(如swagger、login等路由)

filter:
  config:
    excludeUrls: /swagger-ui.html,/swagger-resources,/v2,/webjars,/supports,/auth/login
1
2
3

接下来,写一个过滤器:

import com.google.common.base.Splitter;
import com.xxx.xxx.common.utils.AccessTokenHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;

import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;


@Slf4j
@Order(1)
@Component
@WebFilter(filterName = "TokenFilter", urlPatterns = {"/**"})
public class TokenFilter implements Filter {

    @Value("${filter.config.excludeUrls}")
    private String excludeUrls; // 获取配置文件中不需要过滤的uri

    private List<String> excludes;

    // token验证失败时的响应消息
    private static final String VALID_ERROR = "{\"code\": \"401\",\"msg\": \"非法请求\",\"success\": false}";

    @Override
    public void init(FilterConfig filterConfig) {
        excludes = Splitter.on(",").trimResults().splitToList(this.excludeUrls);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        response.setContentType("application/json;charset=UTF-8");
        String uri = request.getRequestURI();
        String token = request.getHeader("token");
        try {
            if (this.isExcludesUrl(uri)) {
                chain.doFilter(req, resp);
            } else {
                // 验证请求头中的token
                if (StringUtils.isBlank(token) || !AccessTokenHelper.verify(token)) {
                    response.getWriter().write(VALID_ERROR);
                    return;
                }
                chain.doFilter(request, resp);
            }
        } catch (Exception e) {
            log.error("Exception error", e);
            response.getWriter().write(VALID_ERROR);
        } finally {
            response.flushBuffer();
        }

    }

    private boolean isExcludesUrl(String path) {
        for (String v : this.excludes) {
            if (path.startsWith(v)) {
                return true;
            }
        }
        return false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

注意事项:

[1] !AccessTokenHelper.verify(token)换成自己框架中用于校验Token是否有效的方法。

[2] 添加response.setContentType("application/json;charset=UTF-8");是为了解决中文返回值乱码问题。

[3] 登录后获取Token,需要验证登录的接口都需要前端在Headers里添加token参数请求,不需要验证登录的接口后端在yml里配置放行路由。

# 2.5 外部接口代理转发

需求情景:有些外部接口是前端直接去对接的,但是又不上前端直接调用,可以用后端做一层转发。

ProxyController.java

import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

@Slf4j
@Api(tags = "接口代理转发")
@RestController
@RequestMapping(value = "/api/proxy")
public class ProxyController {

    @Value("${settings.external.api-url}")
    private String externalUrl;

    /**
     * 代理请求(适用场景:有些外部接口是前端直接去对接的,但是又不上前端直接调用,可以用后端做一层转发)
     * 使用举例:原先的外部接口是http://127.0.0.1:5000/external/doMethod,经过代理之后就变成http://127.0.0.1:8081/api/proxy/doMethod
     *
     * @param request
     * @param response
     * @throws IOException
     * @throws URISyntaxException
     */
    @RequestMapping(value = "/**", produces = MediaType.APPLICATION_JSON_VALUE)
    public void proxy(HttpServletRequest request, HttpServletResponse response) throws IOException, URISyntaxException {
        try {
            String targetAddr = this.externalUrl;
            URI uri = new URI(request.getRequestURI());
            String path = uri.getPath();
            String query = request.getQueryString();
            String target = targetAddr + path.replace("/api/proxy", "");
            if (query != null && !"".equals(query) && !"null".equals(query)) {
                target = target + "?" + query;
            }
            log.info(String.format("【接口请求转发】source:%s, target:%s", path, target));
            URI newUri = new URI(target);
            // 执行代理查询
            String methodName = request.getMethod();
            HttpMethod httpMethod = HttpMethod.resolve(methodName);
            if (httpMethod == null) {
                return;
            }
            ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod);
            Enumeration<String> headerNames = request.getHeaderNames();
            // 设置请求头
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                Enumeration<String> v = request.getHeaders(headerName);
                List<String> arr = new ArrayList<>();
                while (v.hasMoreElements()) {
                    arr.add(v.nextElement());
                }
                delegate.getHeaders().addAll(headerName, arr);
            }
            StreamUtils.copy(request.getInputStream(), delegate.getBody());
            // 执行远程调用
            ClientHttpResponse clientHttpResponse = delegate.execute();
            response.setStatus(clientHttpResponse.getStatusCode().value());
            // 设置响应头
            clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
                response.setHeader(key, it);
            }));
            StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
        } catch (Exception e) {
            log.error("接口请求转发异常", e);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

application.properties

# external interface proxy
settings.external.api-url=http://127.0.0.1:5000/external
1
2

# 2.6 Springboot的包外配置

Springboot读取核心配置文件(application.properties)的优先级依次为:Jar包同级目录的config目录、Jar包同级目录、classPath(即resources目录)的config目录、classpath目录。

上面是springboot默认去拿自己的核心配置文件的优先级,还有一种最高优先级的方式是项目启动时通过命令的方式指定项目加载核心配置文件,命令如下:

$ java –jar -Dspring.config.location=xxx/xxx/xxxx.properties xxxx.jar
1

如果Spring Boot在优先级更高的位置找到了配置,那么它会无视优先级更低的配置。

# 2.7 properties配置文件

# 2.7.1 工具类读取.properties配置文件

Springboot项目我们通常使用@value注解获取.properties配置文件的数据,而一些独立运行的工具类,无法通过该注解得到所需要的值,对此我们可以使用如下方式来获取:

// 读取properties文件
Properties properties = new Properties();
// 方式一:本机和服务器部署均可用,但读不了其他子模块的
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ClassPathResource("application.properties").getInputStream()));
// 方式二:仅本机可用,服务器部署会找不到路径
// BufferedReader bufferedReader = new BufferedReader(new FileReader("web_manage/src/main/resources/application.properties"));
properties.load(bufferedReader);
// 获取key对应的value值
System.out.println(properties.getProperty("spring.datasource.username"));
1
2
3
4
5
6
7
8
9

# 2.7.2 yml与properties的互相转换

方案一:可借助 https://www.toyaml.com/index.html (opens new window) 在线工具进行转换。

方案二:使用工具类将 yml 转换为 properties。

import lombok.Data;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Stream;


/**
 * Yaml 配置文件转 Properties 配置文件工具类
 */
public class YmlTool {

    private static final String lineSeparator = "\n";

    /**
     * 将 yml 字符串化为 properties字符串
     *
     * @param yml
     * @return
     */
    public static String yamlStr2PropStr(String yml) {
        List<YmlNode> nodeList = getNodeList(yml);
        // 去掉多余数据,并打印
        String str = printNodeList(nodeList);
        return str;
    }

    /**
     * 将 yml 文件转化为 properties 文件
     *
     * @param ymlFileName 工程根目录下(非resources目录)的 yml 文件名称(如:abc.yml)
     * @return List<Node> 每个Nyml 文件中每行对应解析的数据
     */
    public static List<YmlNode> yamlFile2PropFile(String ymlFileName) {
        if (ymlFileName == null || !ymlFileName.endsWith(".yml")) {
            throw new RuntimeException("请输入yml文件名称!!");
        }
        File ymlFile = new File(ymlFileName);
        if (!ymlFile.exists()) {
            throw new RuntimeException("工程根目录下不存在 " + ymlFileName + "文件!!");
        }
        String fileName = ymlFileName.split(".yml", 2)[0];
        // 获取文件数据
        String yml = read(ymlFile);
        List<YmlNode> nodeList = getNodeList(yml);
        // 去掉多余数据,并打印
        String str = printNodeList(nodeList);
        // 将数据写入到 properties 文件中
        String propertiesName = fileName + ".properties";
        File file = new File(propertiesName);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try (FileWriter writer = new FileWriter(file)) {
            writer.write(str);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return nodeList;
    }

    /**
     * 将yml转化为porperties文件,并获取转化后的Map键值对
     *
     * @param ymlFileName 工程根目录下的 yml 文件名称
     * @return 转化后的 porperties 文件键值对Map
     */
    public static Map<String, String> yamlFile2Map(String ymlFileName) {
        Map<String, String> map = new HashMap<>();
        List<YmlNode> list = yamlFile2PropFile(ymlFileName);
        String s = printNodeList(list);
        String[] lines = s.split(lineSeparator);
        Stream.of(lines).forEach(line -> {
            String[] split = line.split("=");
            map.put(split[0], split[1]);
        });
        return map;
    }

    public static Map<String, String> yamlStr2Map(String yaml) {
        Map<String, String> map = new HashMap<>();
        List<YmlNode> list = getNodeList(yaml);
        String s = printNodeList(list);
        String[] lines = s.split(lineSeparator);
        Stream.of(lines).forEach(line -> {
            String[] split = line.split("=");
            map.put(split[0], split[1]);
        });
        return map;
    }

    private static String read(File file) {
        if (Objects.isNull(file) || !file.exists()) {
            return "";
        }
        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] b = new byte[(int) file.length()];
            fis.read(b);
            return new String(b, StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    private static String printNodeList(List<YmlNode> nodeList) {
        StringBuilder sb = new StringBuilder();
        for (YmlNode node : nodeList) {
            if (node.getLast().equals(Boolean.FALSE)) {
                continue;
            }
            if (node.getEmptyLine().equals(Boolean.TRUE)) {
                sb.append(lineSeparator);
                continue;
            }
            // 判断是否有行级注释
            if (node.getHeadRemark().length() > 0) {
                String s = "# " + node.getHeadRemark();
                sb.append(s).append(lineSeparator);
                continue;
            }
            // 判断是否有行末注释 (properties中注释不允许末尾注释,故而放在上面)
            if (node.getTailRemark().length() > 0) {
                String s = "# " + node.getTailRemark();
                sb.append(s).append(lineSeparator);
            }
            //
            String kv = node.getKey() + "=" + node.getValue();
            sb.append(kv).append(lineSeparator);
        }
        return sb.toString();
    }

    private static List<YmlNode> getNodeList(String yml) {
        String[] lines = yml.split(lineSeparator);
        List<YmlNode> nodeList = new ArrayList<>();
        Map<Integer, String> keyMap = new HashMap<>();
        Set<String> keySet = new HashSet<>();
        for (String line : lines) {
            YmlNode node = getNode(line);
            if (node.getKey() != null && node.getKey().length() > 0) {
                int level = node.getLevel();
                if (level == 0) {
                    keyMap.clear();
                    keyMap.put(0, node.getKey());
                } else {
                    int parentLevel = level - 1;
                    String parentKey = keyMap.get(parentLevel);
                    String currentKey = parentKey + "." + node.getKey();
                    keyMap.put(level, currentKey);
                    node.setKey(currentKey);
                }
            }
            keySet.add(node.getKey() + ".");
            nodeList.add(node);
        }
        // 标识是否最后一级
        for (YmlNode each : nodeList) {
            each.setLast(getNodeLast(each.getKey(), keySet));
        }
        return nodeList;
    }

    private static boolean getNodeLast(String key, Set<String> keySet) {
        if (key.isEmpty()) {
            return true;
        }
        key = key + ".";
        int count = 0;
        for (String each : keySet) {
            if (each.startsWith(key)) {
                count++;
            }
        }
        return count == 1;
    }

    private static YmlNode getNode(String line) {
        YmlNode node = new YmlNode();
        // 初始化默认数据(防止NPE)
        node.setEffective(Boolean.FALSE);
        node.setEmptyLine(Boolean.FALSE);
        node.setHeadRemark("");
        node.setKey("");
        node.setValue("");
        node.setTailRemark("");
        node.setLast(Boolean.FALSE);
        node.setLevel(0);
        // 空行,不处理
        String trimStr = line.trim();
        if (trimStr.isEmpty()) {
            node.setEmptyLine(Boolean.TRUE);
            return node;
        }
        // 行注释,不处理
        if (trimStr.startsWith("#")) {
            node.setHeadRemark(trimStr.replaceFirst("#", "").trim());
            return node;
        }
        // 处理值
        String[] strs = line.split(":", 2);
        // 拆分后长度为0的,属于异常数据,不做处理
        if (strs.length == 0) {
            return node;
        }
        // 获取键
        node.setKey(strs[0].trim());
        // 获取值
        String value;
        if (strs.length == 2) {
            value = strs[1];
        } else {
            value = "";
        }
        // 获取行末备注
        String tailRemark = "";
        if (value.contains(" #")) {
            String[] vs = value.split("#", 2);
            if (vs.length == 2) {
                value = vs[0];
                tailRemark = vs[1];
            }
        }
        node.setTailRemark(tailRemark.trim());
        node.setValue(value.trim());
        // 获取当前层级
        int level = getNodeLevel(line);
        node.setLevel(level);
        node.setEffective(Boolean.TRUE);
        return node;
    }

    private static int getNodeLevel(String line) {
        if (line.trim().isEmpty()) {
            return 0;
        }
        char[] chars = line.toCharArray();
        int count = 0;
        for (char c : chars) {
            if (c != ' ') {
                break;
            }
            count++;
        }
        return count / 2;
    }

    public static void main(String[] args) {
        // 把 application.yml 放在项目根目录(而不是resources目录),转换完会在根目录生成对应的 application.properties 文件
        yamlFile2PropFile("application.yml");
    }

}

@Data
class YmlNode {

    /**
     * 层级关系
     */
    private Integer level;
    /**
     * 键
     */
    private String key;
    /**
     * 值
     */
    private String value;
    /**
     * 是否为空行
     */
    private Boolean emptyLine;
    /**
     * 当前行是否为有效配置
     */
    private Boolean effective;
    /**
     * 头部注释(单行注释)
     */
    private String headRemark;
    /**
     * 末尾注释
     */
    private String tailRemark;
    /**
     * 是否为最后一层配置
     */
    private Boolean last;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299

# 2.8 将文本转换为ASCII艺术字

有时我们会在加一个banner,项目启动时会展示项目的一些信息。有个现成的开源库,可以将指定文本转换为对应的ASCII艺术字。

<dependency>
    <groupId>com.github.lalyos</groupId>
    <artifactId>jfiglet</artifactId>
    <version>0.0.8</version>
</dependency>
1
2
3
4
5

使用方法:

String asciiArt= FigletFont.convertOneLine("Hello");
System.out.println(asciiArt);
1
2

效果展示:

 _   _          _   _        
 | | | |   ___  | | | |   ___  
 | |_| |  / _ \ | | | |  / _ \
 |  _  | |  __/ | | | | | (_) |
 |_| |_|  \___| |_| |_|  \___/
1
2
3
4
5

# 2.9 使用Session记录首次请求的URL

需求场景:SSO认证中心的跳转过程中,有个过滤器会被反复调用,需要记录下首次请求的原始URL,用于最后的转发。问题是一个接口分多次调用,如何记录下首次请求的URL并持久化。

解决方案:要记录首次请求的URL并在后续多次调用中保持持久化,可以使用Session来实现,在首次请求时,将需要记录的URL存储到HttpSession中。

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class MyServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 获取会话对象
        HttpSession session = request.getSession();

        // 检查会话中是否已经有存储的URL,如果没有,则为第一次请求
        if (session.getAttribute("firstRequestURL") == null) {
            // 第一次请求,记录下URL
            String firstURL = request.getRequestURL().toString();
            session.setAttribute("firstRequestURL", firstURL);
        }

        // 在后续调用中,可以从会话中获取第一次请求的URL
        String firstURL = (String) session.getAttribute("firstRequestURL");

        // 在这里处理接口的逻辑,并使用firstURL
        // ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 3. Springboot常见问题

# 3.1 方便开发的依赖库

       <!-- lombok 依赖 让代码更简洁 https://www.projectlombok.org/ -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- Hutool 工具类库依赖 https://www.hutool.cn/docs/ -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <hutool.all.version>4.1.7</hutool.all.version>
        </dependency>
	      <!-- fastjson Java对象与JSON互转依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.2 常用的正则表达式

/**
 * 正则表达式
 */
public class RegexpConstants {

    /**
     * 正则表达式:验证用户名
     */
    public static final String REGEX_USERNAME = "^[a-zA-Z]\\w{5,20}$";

    /**
     * 正则表达式:验证密码
     */
    public static final String REGEX_PASSWORD = "^[a-zA-Z0-9]{6,20}$";

    /**
     * 正则表达式:验证手机号
     */
    public static final String REGEX_MOBILE = "^(1[0-9])\\d{9}$";

    /**
     * 正则表达式:验证邮箱
     */
    public static final String REGEX_EMAIL = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";

    /**
     * 正则表达式:验证汉字
     */
    public static final String REGEX_CHINESE = "^[\u4e00-\u9fa5],{0,}$";

    /**
     * 正则表达式:验证身份证
     */
    public static final String REGEX_ID_CARD = "(^\\d{18}$)|(^\\d{15}$)";

    /**
     * 正则表达式:验证URL
     */
    public static final String REGEX_URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?";

    /**
     * 正则表达式:验证IP地址
     */
    public static final String REGEX_IP_ADDR = "(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)";
    
    /**
     * 正则表达式:验证数字类型逗号分隔
     */
    public static final String REGEX_COMMA_SEPARATED = "^[0-9]+(,[0-9]+)*$";

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 3.3 从Enum枚举获取Map列表

枚举:

public enum EducateStatusEnum {
 
    /**
     * 学生 学业状态
     */
    IN_STUDYING((short) 1, "在读"),
    UNDERGRADUATE((short) 2, "肆业"),
    SUSPENDED((short) 3, "休学"),
    DROPPED((short) 4, "辍学"),
    QUITED((short) 5, "退学"),
    GRADUATED((short) 6, "已毕业"),
    ;
    
    public short code;
    public String name;
 
    EducateStatusEnum(Short code, String name) {
        this.code = code;
        this.name = name;
    }
 
    public int getCode() {
        return this.code;
    }
 
    public String getName() {
        return this.name;
    }
 
    public static EducateStatusEnum findEnumByCode(Integer code) {
        for (EducateStatusEnum statusEnum : EducateStatusEnum.values()) {
            if (statusEnum.getCode() == code) {
                return statusEnum;
            }
        }
        throw new IllegalArgumentException("code is not support");
    }
 
    public static EducateStatusEnum findEnumByName(String name) {
        for (EducateStatusEnum statusEnum : EducateStatusEnum.values()) {
            if (statusEnum.getName().equals(name)) {
                return statusEnum;
            }
        }
        throw new IllegalArgumentException("name is not support");
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

工具类:

import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 将枚举转换为list类型
 */
public class EnumListUtil {

    private static String ENUM_CLASSPATH="java.lang.Enum";

    public static List<Map<String, Object>> enumToMapList(Class<?> enumClass) {
        List<Map<String, Object>> resultList= new ArrayList<>();
        if (!ENUM_CLASSPATH.equals(enumClass.getSuperclass().getCanonicalName())) {
            return resultList;
        }
        // 获取所有public方法
        Method[] methods = enumClass.getMethods();
        List<Field> fieldList = new ArrayList<>();
        //1.通过get方法提取字段
        Arrays.stream(methods)
                .map(Method::getName)
                .filter(
                        methodName -> methodName.startsWith("get") && !"getDeclaringClass".equals(methodName) && !"getClass".equals(methodName)
                ).forEachOrdered(methodName -> {
            try {
                Field field = enumClass.getDeclaredField(StringUtils.uncapitalize(methodName.substring(3)));
                if (null != field) {
                    fieldList.add(field);
                }
            } catch (NoSuchFieldException | SecurityException e) {
                e.printStackTrace();
            }
        });

        //2.将字段作为key,逐一把枚举值作为value 存入list
        if (CollectionUtils.isEmpty(fieldList)) {
            return resultList;
        }

        Enum<?>[] enums = (Enum[]) enumClass.getEnumConstants();
        for (Enum<?> anEnum : enums) {
            Map<String, Object> map = new HashMap<>(fieldList.size());
            for (Field field : fieldList) {
                field.setAccessible(true);
                try {
                    // 向map集合添加字段名称 和 字段值
                    map.put(field.getName(), field.get(anEnum));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            // 将Map添加到集合中
            resultList.add(map);
        }
        return resultList;
    }

    public static void main(String[] args) {
        // 转换枚举类成Map列表
        System.out.println(enumToMapList(EducateStatusEnum.class));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

# 3.4 常见报错及警告问题的解决方案

[1] pom.xml里spring-boot-maven-plugin爆红问题

该问题有多种可能导致的原因,都试一下吧。

  • 第一种可能:给它加上版本号,例如:<version>2.2.2.RELEASE</version>
  • 第二种可能:检查settings.xml是否配置正确。
  • 第三种可能:将Maven依赖仓库的地址使用IDEA默认的C:\Users\xxx\.m2\repository

注:看起来第三种可能最不靠谱,我他妈都被它整崩溃了,抱着死马当活马医的心态试了一下,没想到真的解决了!!!

[2] 主启动类运行报错Failed to load property source from location 'classpath:/application.yml'

先校验yaml语法:http://www.yamllint.com/ (opens new window),如果没问题就是编码的事儿

方法一:File-->Settings-->Editor-->File Encodings,三处编码设置为utf-8,重启项目

方法二: 删除application.yml文件中所有中文注释。

[3] 主启动类运行报错Command line is too long

报错信息:Error running 'ServiceStarter': Command line is too long. Shorten command line for ServiceStarter or also for Application default configuration.

解决办法:修改项目下 .idea\workspace.xml,找到标签<component name="PropertiesComponent">, 在标签里加一行<property name="dynamic.classpath" value="true" />

[4] SpringBoot启动时报SilentExitException

以debug方式启动springboot之后,都会在SilentExitExceptionHandler类中的throw new SilentExitException()处终止,虽然不影响程序运行,但爆红令人感觉不爽。原因是用了热部署spring-boot-devtools。

解决办法:我选择去掉spring-boot-devtools依赖,使用Jrebel插件实现热部署。

[5] 使用@AutoWired注解出现Could not autowire. No beans of 'xxxx' type found 的错误提示

只是警告,不影响使用,但看着爆红不舒服。

解决办法 1)在注解上加上:@Autowired(required = false) 2)使用 @Resource 替换 @Autowired

[6] .properties出现\u7528中文乱码问题

其中'\u'表示Unicode编码,用工具类或者在线工具转换一下即可。

在线工具:Unicode与中文互转 (opens new window)

[7] 调用接口出现 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported报错

前端请求传JSON对象则后端使用@RequestParam; 前端请求传JSON对象的字符串则后端使用@RequestBody。

[8] 使用ConfigurationProperties注解时提示“Spring Boot Configuration Annotation Processor not configured”

解决办法:在pom.xml里添加以下依赖即可解决。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
1
2
3
4
5

[9] AES的256位密钥加解密报 java.security.InvalidKeyException: Illegal key size or default parameters 异常

报错原因:

在我们安装JRE的目录下有这样一个文件夹:%JAVE_HOME%\jre\lib\security,其中包含有两个.jar文件:“local_policy.jar ”和“US_export_policy.jar”。JRE中自带的“local_policy.jar ”和“US_export_policy.jar”是支持128位密钥的加密算法,而当我们要使用256位密钥算法的时候,已经超出它的范围,无法支持,所以才会报:“java.security.InvalidKeyException: Illegal key size or default parameters”的异常。
1

解决办法:

方法一:下载单独策略文件,替换原来的文件。

其对应的JCE下载地址为:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html (opens new window)

下载完后,解压,将其中的“local_policy.jar ”和“US_export_policy.jar”两个文件替换掉自己%JAVE_HOME%\jre\lib\security文件夹下对应的原文件。

方法二:将jdk升级到JDK1.8.0-161以上版本,就不需要单独策略文件了。

[10] springboot自动注入出现Consider defining a bean of type ‘xxx‘ in your configuration问题

解决办法:将接口与对应的实现类放在与application启动类的同一个目录或者他的子目录下,这样注解就可以被扫描到了。

[11] springboot启动报错“java:程序包XXX不存在”,但实际上程序包都存在

解决办法:编译问题导致的,把 target 包删了,再重新运行即可。

[12] 项目启动时Cannot find template location(s): [classpath:/templates/] (please add some templates, check your FreeMarker configuration, or set spring.freemarker.checkTemplateLocation=false) 警告

application.properties添加如下配置,即可隐藏此警告。

spring.freemarker.checkTemplateLocation=false
1

[13] 项目启动时警告Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used

在config目录下添加如下配置文件即可解决。

import io.undertow.server.DefaultByteBufferPool;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

/**
 *  解决启动时警告 Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
 */
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
    @Override
    public void customize(UndertowServletWebServerFactory factory) {
        factory.addDeploymentInfoCustomizers(deploymentInfo -> {
            WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
            webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024));
            deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

[14] 编译项目时出现“sun.misc.BASE64Decoder是内部专用 API, 可能会在未来发行版中删除”的警告

将如下两行代码替换成最后一行的写法,替代sun.misc.BASE64Decoder的API即可。

// BASE64Decoder decoder = new BASE64Decoder();
// byte[] bytes = decoder.decodeBuffer(base64str);
byte[] bytes = Base64.decodeBase64(base64str);
1
2
3

# 4. 常用的Java工具类及代码示例

# 4.1 常用工具类

# 4.1.1 HTTP请求工具类

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.io.FileUtils;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 发送HTTP请求工具类
 */
public class HttpRequestUtils {

    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url 发送请求的URL
     * @return URL 所代表远程资源的响应结果
     */
    public static String get(String url) throws Exception {
        HttpURLConnection httpURLConnection = getHttpURLConnectionForSimpleRequest(url);
        httpURLConnection.setRequestMethod("GET");
        // 建立实际的连接
        httpURLConnection.connect();
        return getHttpURLConnectionResponse(httpURLConnection);
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url  发送请求的 URL
     * @param body 请求参数,请求参数应该是Json的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String post(String url, String body) {
        try {
            return post(url, body, null);
        } catch (Exception ex) {
            return ex.getMessage();
        }
    }

    /**
     * 向指定 URL 发送POST方法的请求(Token身份验证)
     *
     * @param url   发送请求的 URL
     * @param body  请求参数,请求参数应该是Json的形式。
     * @param token 身份验证使用的token
     * @return 远程资源的响应结果
     * @throws Exception 异常,包括远程资源返回的异常
     */
    public static String post(String url, String body, String token) throws Exception {
        OutputStream outputStream = null;
        byte[] writeBytes = body.getBytes(StandardCharsets.UTF_8);
        String response;
        try {
            HttpURLConnection httpURLConnection = getHttpURLConnectionForJson(url);
            // 设置文件长度
            httpURLConnection.setRequestProperty("Content-Length", String.valueOf(writeBytes.length));
            if (token != null && !token.isEmpty()) {
                httpURLConnection.setRequestProperty("Authorization", token);
            }
            httpURLConnection.setRequestMethod("POST");
            // 发送数据
            outputStream = httpURLConnection.getOutputStream();
            outputStream.write(writeBytes);
            outputStream.flush();
            response = getHttpURLConnectionResponse(httpURLConnection);
        } catch (Exception ex) {
            if (outputStream != null) {
                outputStream.close();
            }
            throw ex;
        }
        return response;
    }

    /**
     * 以form的方式发送post请求
     *
     * @param url    发送请求的 URL
     * @param params 请求参数,以&连接的字符串
     * @return 远程资源的响应结果
     * @throws Exception 异常,包括远程资源返回的异常
     */
    public static String postForm(String url, String params) throws Exception {
        OutputStream outputStream = null;
        byte[] writeBytes = params.getBytes(StandardCharsets.UTF_8);
        String response;
        try {
            HttpURLConnection httpURLConnection = getHttpURLConnectionForSimpleRequest(url);
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true);
            //发送数据
            outputStream = httpURLConnection.getOutputStream();
            outputStream.write(writeBytes);
            outputStream.flush();
            response = getHttpURLConnectionResponse(httpURLConnection);
        } catch (Exception ex) {
            if (outputStream != null) {
                outputStream.close();
            }
            throw ex;
        }
        return response;
    }

    /**
     * 向指定 URL 发送PUT方法的请求
     *
     * @param url  发送请求的 URL
     * @param body 请求参数,请求参数应该是Json的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String put(String url, String body) throws Exception {
        OutputStream outputStream = null;
        byte[] writeBytes = body.getBytes(StandardCharsets.UTF_8);
        String response = "";
        try {
            HttpURLConnection httpURLConnection = getHttpURLConnectionForJson(url);
            //设置文件长度
            httpURLConnection.setRequestProperty("Content-Length", String.valueOf(writeBytes.length));
            httpURLConnection.setRequestMethod("PUT");
            //发送数据
            outputStream = httpURLConnection.getOutputStream();
            outputStream.write(writeBytes);
            outputStream.flush();
            response = getHttpURLConnectionResponse(httpURLConnection);
        } catch (Exception ex) {
            if (outputStream != null) {
                outputStream.close();
            }
            throw ex;
        }
        return response;
    }

    /**
     * 向指定URL发送DELETE方法的请求
     *
     * @param url 发送请求的URL
     * @return URL 所代表远程资源的响应结果
     */
    public static String delete(String url) throws Exception {
        HttpURLConnection httpURLConnection = getHttpURLConnectionForSimpleRequest(url);
        httpURLConnection.setRequestMethod("DELETE");
        httpURLConnection.connect();//建立实际的连接
        return getHttpURLConnectionResponse(httpURLConnection);
    }

    /**
     * 为简单请求准备的url连接
     * @param urlString
     * @return
     * @throws Exception
     */
    private static HttpURLConnection getHttpURLConnectionForSimpleRequest(String urlString) throws Exception {
        URL url = new URL(urlString);
        // 打开和URL之间的连接
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
        // 设置超时
        httpURLConnection.setConnectTimeout(3000);
        httpURLConnection.setReadTimeout(6000);
        // 设置通用的请求属性
        httpURLConnection.setRequestProperty("accept", "*/*");
        return httpURLConnection;
    }

    /**
     * 为application/json准备的url连接
     * @param urlString
     * @return
     * @throws Exception
     */
    private static HttpURLConnection getHttpURLConnectionForJson(String urlString) throws Exception {
        URL url = new URL(urlString);
        // 打开和URL之间的tcp连接
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
        // 默认为false 发送post请求必须设置setDoOutput(true)
        httpURLConnection.setDoOutput(true);
        // 默认为true 所以不设置也可以
        httpURLConnection.setDoInput(true);
        // 连接超时
        httpURLConnection.setConnectTimeout(3000);
        // 读取超时
        httpURLConnection.setReadTimeout(6000);
        // 设置请求头
        httpURLConnection.setRequestProperty("accept", "*/*");
        // 设置请求头
        httpURLConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
        return httpURLConnection;
    }

    /**
     * 获取response
     * @param httpURLConnection
     * @return
     * @throws Exception
     */
    private static String getHttpURLConnectionResponse(HttpURLConnection httpURLConnection) throws Exception {
        StringBuilder response = new StringBuilder();
        int code = httpURLConnection.getResponseCode();
        InputStream inputStream;
        if (code >= 200 && code < 300) {
            inputStream = httpURLConnection.getInputStream();
        } else {
            inputStream = httpURLConnection.getErrorStream();
        }
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            response.append(line);
        }
        bufferedReader.close();
        if (code < 400) {
            return response.toString();
        } else {
            throw new Exception(response.toString());
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 以POST上传文件的方式请求接口
     * @param urlStr
     * @param textMap
     * @param fileMap
     * @param contentType 没有传入文件类型默认采用application/octet-stream
     * contentType非空采用filename匹配默认的图片类型
     * @return 返回response数据
     */
    public static String queryApiUsePostByFile(String urlStr, Map<String, String> textMap, Map<String, String> fileMap, String contentType) {

        String res = "";
        HttpURLConnection conn = null;
        // boundary就是request头和上传文件内容的分隔符
        String BOUNDARY = "---------------------------123821742118716";
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(30000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
            conn.setRequestProperty("Content-Type","multipart/form-data; boundary=" + BOUNDARY);
            OutputStream out = new DataOutputStream(conn.getOutputStream());
            // text
            if (textMap != null) {
                StringBuffer strBuf = new StringBuffer();
                Iterator iter = textMap.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry entry = (Map.Entry) iter.next();
                    String inputName = (String) entry.getKey();
                    String inputValue = (String) entry.getValue();
                    if (inputValue == null) {
                        continue;
                    }
                    strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
                    strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
                    strBuf.append(inputValue);
                }
                out.write(strBuf.toString().getBytes());
            }
            // file
            if (fileMap != null) {
                Iterator iter = fileMap.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry entry = (Map.Entry) iter.next();
                    String inputName = (String) entry.getKey();
                    String inputValue = (String) entry.getValue();
                    if (inputValue == null) {
                        continue;
                    }
                    File file = new File(inputValue);
                    String filename = file.getName();

                    //没有传入文件类型,同时根据文件获取不到类型,默认采用application/octet-stream
                    contentType = new MimetypesFileTypeMap().getContentType(file);
                    //contentType非空采用filename匹配默认的图片类型
                    if(!"".equals(contentType)){
                        if (filename.endsWith(".png")) {
                            contentType = "image/png";
                        }else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".jpe")) {
                            contentType = "image/jpeg";
                        }else if (filename.endsWith(".gif")) {
                            contentType = "image/gif";
                        }else if (filename.endsWith(".ico")) {
                            contentType = "image/image/x-icon";
                        }
                    }
                    if (contentType == null || "".equals(contentType)) {
                        contentType = "application/octet-stream";
                    }
                    StringBuffer strBuf = new StringBuffer();
                    strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
                    strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n");
                    strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
                    out.write(strBuf.toString().getBytes());
                    DataInputStream in = new DataInputStream(new FileInputStream(file));
                    int bytes = 0;
                    byte[] bufferOut = new byte[1024];
                    while ((bytes = in.read(bufferOut)) != -1) {
                        out.write(bufferOut, 0, bytes);
                    }
                    in.close();
                }
            }
            byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
            out.write(endData);
            out.flush();
            out.close();
            // 读取返回数据
            StringBuffer strBuf = new StringBuffer();
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                strBuf.append(line).append("\n");
            }
            res = strBuf.toString();
            reader.close();
            reader = null;
        } catch (Exception e) {
            System.out.println("发送POST请求出错。" + urlStr);
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
                conn = null;
            }
        }
        return res;
    }

    /**
     * 以POST方式请求接口并返回文件下载到指定目录
     * @param url
     * @param bodyJson
     * @param downloadPath
     * @param extName
     */
    public static String queryApiUsePostReturnFile(String url, String bodyJson, String downloadPath, String extName) {
        String fileName = null;
        FileOutputStream out = null;
        String resultStr = HttpUtil.post(url, bodyJson);
        InputStream inputStream = new ByteArrayInputStream(resultStr.getBytes());
        try {
            // 转化为byte数组
            byte[] data = getByteData(inputStream);
            // 建立文件存储目录
            File file = new File(downloadPath + "/" + DateUtil.formatDate(new Date()));
            if (!file.exists()) {
                file.mkdirs();
            }
            // 修改文件名(文件名不能是中文)
            fileName = IdUtil.simpleUUID() + extName;
            File res = new File(file + File.separator + fileName);
            // 写入输出流
            out = new FileOutputStream(res);
            out.write(data);
        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭输出流
            try {
                if (null != out){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return fileName;
    }

    /**
     * 从输入流中获取字节数组
     * @param in
     * @return
     * @throws IOException
     */
    private static byte[] getByteData(InputStream in) throws IOException {
        byte[] b = new byte[1024];
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int len = 0;
        while ((len = in.read(b)) != -1) {
            bos.write(b, 0, len);
        }
        if(null!=bos){
            bos.close();
        }
        return bos.toByteArray();
    }

    ////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 检查指定URL的HTTP请求状态码
     * @param checkUrl
     * @return
     */
    public static int CheckUrlCode(String checkUrl) {
        int httpCode = 0;
        try {
            URL u = new URL(checkUrl);
            try {
                HttpURLConnection uConnection = (HttpURLConnection) u.openConnection();
                try {
                    uConnection.connect();
                    httpCode = uConnection.getResponseCode();
                    //System.out.println(httpCode);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (
                MalformedURLException e) {
            e.printStackTrace();
        }
        return httpCode;
    }

    /**
     * 将网络文件下载到本地的指定文件夹下
     * @param url
     * @param directory
     * @param fileName
     */
    public static void downloadFileToLocal(String url, String directory, String fileName) {
        URL source;
        try {
            source = new URL(url);
            File destination = FileUtils.getFile(directory, fileName);
            FileUtils.copyURLToFile(source, destination);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件名进行UrlEncode加密
     *
     * @param filename 文件名
     * @return 加密后名称
     */
    public static String urlEncodeFileName(String filename) {
        try {
            return URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 设置文件下载头
     *
     * @param response 响应
     * @param filename 文件名
     */
    public static void setFileDownloadHeader(HttpServletResponse response, String filename) {
        String headerValue = "attachment;";
        headerValue += " filename=\"" + urlEncodeFileName(filename) + "\";";
        headerValue += " filename*=utf-8''" + urlEncodeFileName(filename);
        response.setHeader("Content-Disposition", headerValue);
    }

    /**
     * 从url读取输入流
     *
     * @param urlPath url路径
     * @return 输入流
     */
    public static InputStream readInputStreamFromUrl(String urlPath) {
        InputStream inStream = null;
        try {
            URL url = new URL(urlPath);
            URLConnection conn = url.openConnection();
            inStream = conn.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inStream;
    }
  
    /**
     * 拼接get请求的url请求地址
     */
    public static String spliceUrl(String baseUrl, Map<String, String> params) {
        StringBuilder builder = new StringBuilder(baseUrl);
        boolean isFirst = true;
        for (String key : params.keySet()) {
            if (key != null && params.get(key) != null) {
                if (isFirst) {
                    isFirst = false;
                    builder.append("?");
                } else {
                    builder.append("&");
                }
                builder.append(key)
                        .append("=")
                        .append(params.get(key));
            }
        }
        return builder.toString();
    }

    public static void main(String[] args) throws IOException {

        // 测试1:以POST上传文件的方式请求接口
        String url1 = "API URL";
        String filePath = "本地图片路径";
        Map<String, String> textMap = new HashMap<>();
        // 设置form入参(可选)
        textMap.put("param", "test");
        // 设置文件路径
        Map<String, String> fileMap = new HashMap<>();
        fileMap.put("image", filePath);
        String contentType = "";
        String ret = queryApiUsePostByFile(url1, textMap, fileMap, contentType);
        System.out.println(ret);

        // 测试2:以POST方式请求接口并返回文件下载到指定目录
        String url2 = "API URL";
        String downloadPath = "./downloads";
        Map<String, Object> body = new HashMap<>();
        body.put("param", "test");
        String bodyJson = JSON.toJSONString(body);
        String extName = ".pdf";
        queryApiUsePostReturnFile(url2, bodyJson, downloadPath, extName);

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550

# 4.1.2 base64及图片文件格式转换工具类

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;


/**
 * base64及图片文件格式转换工具类
 */
public class Base64Utils implements MultipartFile {

    private final byte[] imgContent;
    private final String header;

    public Base64Utils(byte[] imgContent, String header) {
        this.imgContent = imgContent;
        this.header = header.split(";")[0];
    }

    @Override
    public String getName() {
        return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
    }

    @Override
    public String getOriginalFilename() {
        return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1];
    }

    @Override
    public String getContentType() {
        return header.split(":")[1];
    }

    @Override
    public boolean isEmpty() {
        return imgContent == null || imgContent.length == 0;
    }

    @Override
    public long getSize() {
        return imgContent.length;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return imgContent;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(imgContent);
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        new FileOutputStream(dest).write(imgContent);
    }

    /**
     * base64转MultipartFile
     *
     * @param base64
     * @return
     */
    public static MultipartFile base64ToMultipart(String base64) {
        // 输入的base64字符串注意带上类似于"data:image/jpg;base64,"的前缀。
        try {
            String[] baseStrs = base64.split(",");
            byte[] b = Base64.decodeBase64(base64);

            for (int i = 0; i < b.length; ++i) {
                if (b[i] < 0) {
                    b[i] += 256;
                }
            }
            return new Base64Utils(b, baseStrs[0]);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    
    /**
     * 网络图片转换Base64的方法
     * @param netImagePath
     * @return
     */
    public static String networkImageToBase64(String netImagePath) {
        final ByteArrayOutputStream data = new ByteArrayOutputStream();
        String strNetImageToBase64 = null;
        try {
            // 创建URL
            URL url = new URL(netImagePath);
            final byte[] by = new byte[1024];
            // 创建链接
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(100);
            InputStream is = conn.getInputStream();
            // 将内容读取内存中
            int len = -1;
            while ((len = is.read(by)) != -1) {
                data.write(by, 0, len);
            }
            // 对字节数组Base64编码
            strNetImageToBase64 = new String(Base64.encodeBase64(data.toByteArray()));
            // 关闭流
            is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return strNetImageToBase64;
    }

    /**
     * 本地图片转换Base64的方法
     * @param imgPath
     * @return
     */
    public static String localImageToBase64(String imgPath) {
        byte[] data = null;
        // 读取图片字节数组
        try {
            InputStream in = Files.newInputStream(Paths.get(imgPath));
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 返回Base64编码过的字节数组字符串
        String strLocalImageToBase64 = new String(Base64.encodeBase64(Objects.requireNonNull(data)));
        return strLocalImageToBase64;
    }
    
    /**
     * base64字符串转换成图片
     *
     * @param imgStr      base64字符串
     * @param imgFilePath 图片存放路径
     * @return
     */
    public static boolean Base64ToImage(String imgStr, String imgFilePath) {

        // 图像数据判空校验
        if (StringUtils.isEmpty(imgStr)){
            return false;
        }
        try {
            // Base64解码
            byte[] b =Base64.decodeBase64(imgStr);
            for (int i = 0; i < b.length; ++i) {
                // 调整异常数据
                if (b[i] < 0) {
                    b[i] += 256;
                }
            }
            OutputStream out = Files.newOutputStream(Paths.get(imgFilePath));
            out.write(b);
            out.flush();
            out.close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static void main(String[] args) {

        //String localImgFilePath = "/root/local.jpg";
        //String localImgStr = localImageToBase64(localImgFilePath);
        //String newLocalImgFilePath = "/root/newLocal.jpg";
        //System.out.println(Base64ToImage(localImgStr,newLocalImgFilePath));

        //String netImagePath = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";
        //String netImgStr = networkImageToBase64(netImagePath);
        //String newNetImgFilePath = "/root/newNet.jpg";
        //System.out.println(Base64ToImage(netImgStr,newNetImgFilePath));

        //String base64 = networkImageToBase64("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
        //System.out.println(base64);
        //MultipartFile multipartFile = Base64Utils.base64ToMultipart("data:image/jpg;base64,"+base64);
        //System.out.println(multipartFile.getName());

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

# 4.1.3 字符串工具类

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 字符串工具类
 */
public class StringUtils {

    /**
     * 截取指定长度的字符串
     *
     * @param str 原字符串
     * @param len 长度
     * @return 如果str为null,则返回null;如果str长度小于len,则返回str;如果str的长度大于len,则返回截取后的字符串
     */
    public static String subStrByStrAndLen(String str, int len) {
        return null != str ? str.substring(0, Math.min(str.length(), len)) : null;
    }

    /**
     * 生成指定位数的数字标号
     * 0:表示前面补0
     * digit:表示保留数字位数
     * d:表示参数为正数类型
     */
    public static String fillString(int num, int digit) {
        return String.format("%0"+digit+"d", num);
    }

    /**
     * 根据总价与活动价计算折扣
     * @param totalPrice
     * @param activityPrice
     * @return
     */
    public static String calPriceDiscount(int totalPrice, int activityPrice) {
        BigDecimal totalPriceBigDecimal = new BigDecimal(totalPrice);
        BigDecimal activityPriceBigDecimal = new BigDecimal(activityPrice);
        double num = activityPriceBigDecimal.divide(totalPriceBigDecimal, 10, BigDecimal.ROUND_HALF_UP).doubleValue();
        DecimalFormat df=new DecimalFormat("0.0");
        return df.format(num*10);
    }

    /**
     * 检查字符串中是否包含某子串
     */
    public static boolean checkSubString(String wholeString, String subString){
        boolean flag = false;
        int result = wholeString.indexOf(subString);
        if(result != -1){
            flag = true;
        }
        return flag;
    }

    /**
     * 将List转化成指定字符分隔的字符串
     * @param strList
     * @param delimiter
     * @return
     */
    public static String listToStr(List<String> strList, String delimiter) {
        String result = "";
        try{
            result = String.join(delimiter, strList);
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 去除字符串里的所有空格
     * @param str
     * @return
     */
    public static String replaceAllSpace(String str) {
        return str.replaceAll(" +","");
    }

    /**
     * 检查是否为空
     */
    public static boolean isBlank(String string) {
        if (string == null || "".equals(string.trim())) {
            return true;
        }
        return false;
    }

    /**
     * 检查是否不为空
     */
    public static boolean isNotBlank(String string) {
        return !isBlank(string);
    }

    /**
     * 取某分隔符(第一处)之前的字符串
     * @param str
     * @param delimiter
     * @return
     */
    public static String substringBefore(String str, String delimiter) {
        String result = "";
        try{
            result = StringUtils.substringBefore(str, delimiter);
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 取某分隔符(最后一处)之前的字符串
     * @param str
     * @param delimiter
     * @return
     */
    public static String substringBeforeLast(String str, String delimiter) {
        String result = "";
        try{
            result = StringUtils.substringBeforeLast(str, delimiter);
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除html标签
     */
    public static String delHtmlTag(String htmlStr) {
        //定义script的正则表达式
        String regExScript = "<script[^>]*?>[\\s\\S]*?<\\/script>";
        //定义style的正则表达式
        String regExStyle = "<style[^>]*?>[\\s\\S]*?<\\/style>";
        //定义HTML标签的正则表达式
        String regExHtml = "<[^>]+>";
        //定义HTML标签的正则表达式
        String regExHtml2 = "\\{[^\\}]+\\}";
        Pattern pScript = Pattern.compile(regExScript, Pattern.CASE_INSENSITIVE);
        Matcher mScript = pScript.matcher(htmlStr);
        //过滤script标签
        htmlStr = mScript.replaceAll("");
        Pattern pStyle = Pattern.compile(regExStyle, Pattern.CASE_INSENSITIVE);
        Matcher mStyle = pStyle.matcher(htmlStr);
        //过滤style标签
        htmlStr = mStyle.replaceAll("");
        Pattern pHtml = Pattern.compile(regExHtml, Pattern.CASE_INSENSITIVE);
        Matcher mHtml = pHtml.matcher(htmlStr);
        //过滤html标签
        htmlStr = mHtml.replaceAll("");
        Pattern pHtml2 = Pattern.compile(regExHtml2, Pattern.CASE_INSENSITIVE);
        Matcher mHtml2 = pHtml2.matcher(htmlStr);
        //过滤html标签
        htmlStr = mHtml2.replaceAll("");
        //返回文本字符串
        return htmlStr.trim();
    }

    public static void main(String[] args) {

        System.out.println(subStrByStrAndLen("test",2));

        List<String> strList = new ArrayList<>();
        strList.add("zhangsan");
        strList.add("lisi");
        strList.add("wanger");
        System.out.println(listToStr(strList,","));

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

# 4.1.4 数学计算工具类

import java.math.BigDecimal;

/**
 * 数学计算工具类
 */
public class MathUtils {

    // 默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;
    // PI的取值精度
    public static double PI = 3.1415926535897932384626;

    /**
     * 提供精确的加法运算
     * @param v1 被加数
     * @param v2 加数
     * @return v1加v2
     */
    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精确的减法运算
     * @param v1
     * @param v2
     * @return v1减v2
     */
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精确的乘法运算
     * @param v1
     * @param v2
     * @return v1乘v2
     */
    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供相对精确的除法运算,当发生除不尽的情况,精确到小数点后10位
     * @param v1
     * @param v2
     * @return v1除v2
     */
    public static double div(double v1, double v2) {
        if(DEF_DIV_SCALE < 0){
            throw new IllegalArgumentException(" the scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, DEF_DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 计算两点之间的距离
     * @param point1
     * @param point2
     * @return
     */
    public static double distanceToPoint(double[] point1, double[] point2) {
        double x1 = point1[0];
        double y1 = point1[1];
        double x2 = point2[0];
        double y2 = point2[1];
        double lineLength = 0;
        lineLength = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
        return lineLength;
    }

    /**
     * 点到直线的最短距离的判断
     * @param start (x1,y1)
     * @param end    (x2,y2)
     * @param point (x0,y0)
     * @return
     */
    public static double distanceToSegment(double[] start, double[] end, double[] point) {
        double space = 0;
        double a, b, c;
        a = distanceToPoint(start, end); // 线段的长度
        b = distanceToPoint(start, point); // (x1,y1)到点的距离
        c = distanceToPoint(end, point); // (x2,y2)到点的距离
        if (c+b == a) { //点在线段上
            space = 0;
            return space;
        }
        if (a <= 0.000001) { //不是线段,是一个点
            space = b;
            return space;
        }
        if (c * c >= a * a + b * b) { //组成直角三角形或钝角三角形,(x1,y1)为直角或钝角
            space = b;
            return space;
        }
        if (b * b >= a * a + c * c) { //组成直角三角形或钝角三角形,(x2,y2)为直角或钝角
            space = c;
            return space;
        }
        // 组成锐角三角形,则求三角形的高
        double p = (a + b + c) / 2; // 半周长
        double s = Math.sqrt(p * (p - a) * (p - b) * (p - c)); // 海伦公式求面积
        space = 2 * s / a; // 返回点到线的距离(利用三角形面积公式求高)
        return space;
    }

    public static void main(String[] args) {
        double[] start = {0,2};
        double[] end = {2,0};
        double[] point = {0,0};
        System.out.println(distanceToSegment(start, end, point));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

# 4.1.5 判断数值是否在某字符串区间内

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;


/**
 * 判断一个数是否在某数学区间内
 */
public class IntervalUtil {

    /**
     * 判断data_value是否在interval区间范围内
     * @param dataValue 数值类型的
     * @param interval 正常的数学区间,包括无穷大等,如:(1,3)、>5%、(-∞,6]、(125%,135%)U(70%,80%)
     * @return true:表示data_value在区间interval范围内,false:表示data_value不在区间interval范围内
     */
    public static boolean isInTheInterval(String dataValue, String interval) {
        //将区间和data_value转化为可计算的表达式
        String formula = getFormulaByAllInterval(dataValue,interval,"||");
        ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
        try {
            //计算表达式
            return (Boolean) jse.eval(formula);
        } catch (Exception t) {
            return false;
        }
    }

    /**
     * 将所有阀值区间转化为公式:如
     * [75,80) =》 dateValue < 80 && dateValue >= 75
     * (125%,135%)U(70%,80%) =》  (dateValue < 1.35 && dateValue > 1.25) || (dateValue < 0.8 && dateValue > 0.7)
     * @param dateValue
     * @param interval  形式如:(125%,135%)U(70%,80%)
     * @param connector 连接符 如:") || ("
     */
    public static String getFormulaByAllInterval(String dateValue, String interval, String connector) {
        StringBuffer buff = new StringBuffer();
        for(String limit:interval.split("U")){  //如:(125%,135%)U (70%,80%)
            buff.append("(").append(getFormulaByInterval(dateValue, limit," && ")).append(")").append(connector);
        }
        String allLimitInvel = buff.toString();
        int index = allLimitInvel.lastIndexOf(connector);
        allLimitInvel = allLimitInvel.substring(0,index);
        return allLimitInvel;
    }

    /**
     * 将整个阀值区间转化为公式:如
     * 145)      =》         dateValue < 145
     * [75,80)   =》        dateValue < 80 && dateValue >= 75
     * @param dateValue
     * @param interval  形式如:145)、[75,80)
     * @param connector 连接符 如:&&
     */
    public static String getFormulaByInterval(String dateValue, String interval, String connector) {
        StringBuffer buff = new StringBuffer();
        for(String halfInterval:interval.split(",")){//如:[75,80)、≥80
            buff.append(getFormulaByHalfInterval(halfInterval, dateValue)).append(connector);
        }
        String limitInvel = buff.toString();
        int index = limitInvel.lastIndexOf(connector);
        limitInvel = limitInvel.substring(0,index);
        return limitInvel;
    }

    /**
     * 将半个阀值区间转化为公式:如
     * 145)   =》    dateValue < 145
     * ≥80%   =》    dateValue >= 0.8
     * [130   =》    dateValue >= 130
     * <80%   =》    dateValue < 0.8
     * @param halfInterval  形式如:145)、≥80%、[130、<80%
     * @param dateValue
     * @return dateValue < 145
     */
    public static String getFormulaByHalfInterval(String halfInterval, String dateValue) {
        halfInterval = halfInterval.trim();
        if(halfInterval.contains("∞")){  //包含无穷大则不需要公式
            return "1 == 1";
        }
        StringBuffer formula = new StringBuffer();
        String data = "";
        String opera = "";
        if(halfInterval.matches("^([<>≤≥\\[\\(]{1}(-?\\d+.?\\d*\\%?))$")){  //表示判断方向(如>)在前面 如:≥80%
            opera = halfInterval.substring(0,1);
            data = halfInterval.substring(1);
        }else{//[130、145)
            opera = halfInterval.substring(halfInterval.length()-1);
            data = halfInterval.substring(0,halfInterval.length()-1);
        }
        double value = dealPercent(data);
        formula.append(dateValue).append(" ").append(opera).append(" ").append(value);
        String a = formula.toString();
        //转化特定字符
        return a.replace("[", ">=").replace("(", ">").replace("]", "<=").replace(")", "<").replace("≤", "<=").replace("≥", ">=");
    }

    /**
     * 去除百分号,转为小数
     * @param str 可能含百分号的数字
     * @return
     */
    public static double dealPercent(String str){
        double d = 0.0;
        if(str.contains("%")){
            str = str.substring(0,str.length()-1);
            d = Double.parseDouble(str)/100;
        }else{
            d = Double.parseDouble(str);
        }
        return d;
    }

    public static void main(String[] args) {
        System.out.println(isInTheInterval("6.1", "(-∞,6]"));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

# 4.1.6 解析四则运算表达式并计算结果工具类

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * 解析四则运算表达式并计算结果
 */
public class ParseCalArithmeticUtils {

    /**
     * 校验的基本思路:
     * 1.对公式进行校验,使用正则表达式进行常规性的检查。
     * 2.对公式的格式进行优化,比如去掉公式中的空格、对负数和正数进行补零(例如 -a 优化为 0-a)、补充两个括号之间的乘法运算符(例如 (a+b)(b+c) 优化为 (a+b)*(b+c))
     *
     * 计算的基本思路:
     * 1.翻译输入的数学表达式,也就是中缀表达式转后缀表达式。例如 a+b*(c-d) 转为后缀表达式就是 abcd-*+
     * 2.对后缀表达式计算结果。这里用到了栈存储计算结果,每次都是对两个数计算,例如 abcd-*+,计算方法是先从头遍历,数字直接入栈,当遇到计算符,则从栈顶取出来两个数计算然后再把结果压栈,最终全部计算完之后栈里面只剩下一个元素就是结果。
     */

    /**
     * 使用正则来校验数学公式
     *
     * @param expression 数学公式,包含变量
     * @param variables  内置变量集合
     */
    public static String validate(String expression, List<String> variables) {
        //if (variables == null || variables.isEmpty()) {
        //    throw new RuntimeException("内置变量为空");
        //}
        // 去空格
        expression = expression.replaceAll(" ", "");
        // 连续运算符处理
        if (expression.split("[\\+\\-\\*\\/]{2,}").length > 1) {
            throw new RuntimeException("公式不合法,包含连续运算符");
        }
        if (expression.contains("()")) {
            throw new RuntimeException("公式不合法,包含空括号");
        }
        expression = expression.replaceAll("\\)\\(", "\\)*\\(");
        expression = expression.replaceAll("\\(\\-", "\\(0-");
        expression = expression.replaceAll("\\(\\+", "\\(0+");
        // 校验变量
        String[] splits = expression.split("\\+|\\-|\\*|\\/|\\(|\\)");
        for (String split : splits) {
            if (StringUtils.isBlank(split) || Pattern.matches("-?(0|([1-9]\\d*))(\\.\\d+)?", split)) {
                continue;
            }
            if (!variables.contains(split)) {
                throw new RuntimeException("公式不合法,包含非法变量或字符");
            }
        }
        // 校验括号
        Character preChar = null;
        Stack<Character> stack = new Stack<>();
        String resultExpression = expression;
        for (int i = 0; i < expression.length(); i++) {
            char currChar = expression.charAt(i);
            if (i == 0) {
                if (Pattern.matches("\\*|\\/", String.valueOf(currChar))) {
                    throw new RuntimeException("公式不合法,以错误运算符开头");
                }
                if (currChar == '+') {
                    resultExpression = expression.substring(1);
                }
                if (currChar == '-') {
                    resultExpression = "0" + expression;
                }
            }
            if ('(' == currChar) {
                stack.push('(');
            } else if (')' == currChar) {
                if (stack.size() > 0) {
                    stack.pop();
                } else {
                    throw new RuntimeException("公式不合法,括号不配对");
                }
            }
            if (preChar != null && preChar == '(' && Pattern.matches("[\\+\\-\\*\\/]+", String.valueOf(currChar))) {
                throw new RuntimeException("公式不合法,左括号后是运算符");
            }
            if (preChar != null && preChar == ')' && !Pattern.matches("[\\+\\-\\*\\/]+", String.valueOf(currChar))) {
                throw new RuntimeException("公式不合法,右括号后面不是运算符");
            }
            if (i == expression.length() - 1) {
                if (Pattern.matches("\\+|\\-|\\*|\\/", String.valueOf(currChar))) {
                    throw new RuntimeException("公式不合法,以运算符结尾");
                }
            }
            preChar = currChar;
        }
        if (stack.size() > 0) {
            throw new RuntimeException("公式不合法,括号不配对");
        }
        return resultExpression;
    }

    /**
     * 比较优先级
     * 返回 true 表示 curr 优先级大于 stackTop
     */
    private static boolean compare(char curr, char stackTop) {
        // 左括号会直接入栈,这里是其他运算符与栈顶左括号对比
        if (stackTop == '(') {
            return true;
        }
        // 乘除法的优先级大于加减法
        if (curr == '*' || curr == '/') {
            return stackTop == '+' || stackTop == '-';
        }
        // 运算符优先级相同时,先入栈的优先级更高
        return false;
    }

    /**
     * 将中缀表达式,转换为后缀表达式,支持多位数、小数
     */
    private static LinkedList<String> getPostfix(String mathStr) {
        // 后缀表达式链
        LinkedList<String> postfixList = new LinkedList<>();
        // 运算符栈
        Stack<Character> optStack = new Stack<>();
        // 多位数链
        LinkedList<Character> multiDigitList = new LinkedList<>();
        char[] arr = mathStr.toCharArray();
        for (char c : arr) {
            if (Character.isDigit(c) || '.' == c) {
                multiDigitList.addLast(c);
            } else {
                // 处理当前的运算符之前,先处理多位数链中暂存的数据
                if (!multiDigitList.isEmpty()) {
                    StringBuilder temp = new StringBuilder();
                    while (!multiDigitList.isEmpty()) {
                        temp.append(multiDigitList.removeFirst());
                    }
                    postfixList.addLast(temp.toString());
                }
            }
            // 如果当前字符是左括号,将其压入运算符栈
            if ('(' == c) {
                optStack.push(c);
            }
            // 如果当前字符为运算符
            else if ('+' == c || '-' == c || '*' == c || '/' == c) {
                while (!optStack.isEmpty()) {
                    char stackTop = optStack.pop();
                    // 若当前运算符的优先级高于栈顶元素,则一起入栈
                    if (compare(c, stackTop)) {
                        optStack.push(stackTop);
                        break;
                    }
                    // 否则,弹出栈顶运算符到后缀表达式,继续下一次循环
                    else {
                        postfixList.addLast(String.valueOf(stackTop));
                    }
                }
                optStack.push(c);
            }
            // 如果当前字符是右括号,反复将运算符栈顶元素弹出到后缀表达式,直到栈顶元素是左括号"("为止,并将左括号从栈中弹出丢弃。
            else if (c == ')') {
                while (!optStack.isEmpty()) {
                    char stackTop = optStack.pop();
                    if (stackTop != '(') {
                        postfixList.addLast(String.valueOf(stackTop));
                    } else {
                        break;
                    }
                }
            }
        }
        // 遍历结束时,若多位数链中具有数据,说明公式是以数字结尾
        if (!multiDigitList.isEmpty()) {
            StringBuilder temp = new StringBuilder();
            while (!multiDigitList.isEmpty()) {
                temp.append(multiDigitList.removeFirst());
            }
            postfixList.addLast(temp.toString());
        }
        // 遍历结束时,运算符栈若有数据,说明是由括号所致,需要补回去
        while (!optStack.isEmpty()) {
            postfixList.addLast(String.valueOf(optStack.pop()));
        }
        return postfixList;
    }

    /**
     * 根据后缀表达式,得到计算结果
     */
    private static BigDecimal doCalculate(LinkedList<String> postfixList) {
        // 操作数栈
        Stack<BigDecimal> numStack = new Stack<>();
        while (!postfixList.isEmpty()) {
            String item = postfixList.removeFirst();
            BigDecimal a, b;
            switch (item) {
                case "+":
                    a = numStack.pop();
                    b = numStack.pop();
                    numStack.push(b.add(a));
                    break;
                case "-":
                    a = numStack.pop();
                    b = numStack.pop();
                    numStack.push(b.subtract(a));
                    break;
                case "*":
                    a = numStack.pop();
                    b = numStack.pop();
                    numStack.push(b.multiply(a));
                    break;
                case "/":
                    a = numStack.pop();
                    b = numStack.pop();
                    numStack.push(b.divide(a, 2, RoundingMode.HALF_UP));
                    break;
                default:
                    numStack.push(new BigDecimal(item));
                    break;
            }
        }
        return numStack.pop();
    }

    /**
     * 1. 将中缀表达式转后缀表达式
     * 2. 根据后缀表达式进行计算
     */
    public static BigDecimal calculate(String mathStr) {
        if (mathStr == null || mathStr.length() == 0) {
            return null;
        }
        LinkedList<String> postfixList = getPostfix(mathStr);
        //System.out.println("后缀表达式:" + postfixList);
        return doCalculate(postfixList);
    }

    public static void main(String[] args) {

        // 校验测试
        List<String> variables = Arrays.asList("height", "length", "width", "num");
        String result01 = validate("(height+length)(width+num)", variables);
        System.out.println("result01 = " + result01);
        String result02 = validate("-num+100", variables);
        System.out.println("result02 = " + result02);
        String result03 = validate("(length*(1+width)/height)*num", variables);
        System.out.println("result03 = " + result03);

        // 功能测试
        String str1 = "5-1*(5+6)+2";
        System.out.println(str1 + " = " + calculate(str1));
        str1 = "50-1*(5+6)+2";
        System.out.println(str1 + " = " + calculate(str1));
        str1 = "(50.5-1)*(5+6)+2";
        System.out.println(str1 + " = " + calculate(str1));
        str1 = "1+2*(3-4*(5+6))";
        System.out.println(str1 + " = " + calculate(str1));
        str1 = "1/2*(3-4*(5+6))*10";
        System.out.println(str1 + " = " + calculate(str1));
        str1 = "43*(2+1)+2*32+98";
        System.out.println(str1 + " = " + calculate(str1));
        str1 = "3-10*2+5";
        System.out.println(str1 + " = " + calculate(str1));

        // 性能测试
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            String str2 = "43*(2+1.4)+2*32/(3-2.1)" + "+" + i;
            BigDecimal result = calculate(str2);
        }
        System.out.println("耗时:" + (System.currentTimeMillis() - start));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276

# 4.1.7 日期时间获取转换工具类

import cn.hutool.core.date.DateUtil;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 日期时间工具类
 */
public class DateTimeUtils{

    private static final List<String> FORMATS = new ArrayList<>();

    static {
        FORMATS.add("yyyy-MM-dd HH:mm:ss");
        FORMATS.add("yyyy-MM-dd");
        FORMATS.add("yyyy-MM-dd HH:mm");
        FORMATS.add("yyyy-MM");
        FORMATS.add("yyyy/MM/dd HH:mm:ss");
        FORMATS.add("yyyy/MM/dd");
        FORMATS.add("yyyy/MM/dd HH:mm");
        FORMATS.add("yyyy/MM");
        FORMATS.add("yyyyMMdd");
        FORMATS.add("yyyyMM");
        FORMATS.add("yyyy-MM-dd HH:mm:ss.SSS");
        FORMATS.add("yyyy/MM/dd HH:mm:ss.SSS");
    }

    /**
     * 将字符串按指定格式解析成日期
     * @param dateStr
     * @param format
     * @return
     */
    public static Date parseDate(String dateStr, String format) {
        Date date = null;
        try {
            DateFormat dateFormat = new SimpleDateFormat(format);
            date = dateFormat.parse(dateStr);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return date;
    }

    /**
     * 将日期按指定格式解析成字符串
     * @param date
     * @param format
     * @return
     */
    public static String parseStr(Date date, String format) {
        String dateStr = null;
        try {
            DateFormat dateFormat = new SimpleDateFormat(format);
            dateStr = dateFormat.format(date);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return dateStr;
    }

    /**
     * 自动将字符串解析成日期
     * @param source
     * @return
     */
    public static Date autoParseDate(String source) {
        if (source == null) {
            return null;
        }
        String dateStr = source.trim();
        if (dateStr.isEmpty()) {
            return null;
        }
        if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(0));
        }
        if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(1));
        }
        if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(2));
        }
        if (dateStr.matches("^\\d{4}-\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(3));
        }
        if (dateStr.matches("^\\d{4}/\\d{1,2}/\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(4));
        }
        if (dateStr.matches("^\\d{4}/\\d{1,2}/\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(5));
        }
        if (dateStr.matches("^\\d{4}/\\d{1,2}/\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(6));
        }
        if (dateStr.matches("^\\d{4}/\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(7));
        }
        if (dateStr.matches("^\\d{4}\\d{1,2}\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(8));
        }
        if (dateStr.matches("^\\d{4}\\d{1,2}$")) {
            return parseDate(dateStr, FORMATS.get(9));
        }
        if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}.\\d{1,3}$")) {
            return parseDate(dateStr, FORMATS.get(10));
        }
        if (dateStr.matches("^\\d{4}/\\d{1,2}/\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}.\\d{1,3}$")) {
            return parseDate(dateStr, FORMATS.get(4));
        }
        try {
            long timestamp = Long.parseLong(dateStr);
            return new Date(timestamp);
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 指定年份的当前日期
     * @param date
     * @param yearNumber
     * @return
     */
    public static Date setYear(Date date, Integer yearNumber) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.YEAR, yearNumber);
        return calendar.getTime();
    }

    /**
     * 获取当年的开始时间
     * @param date
     * @return
     */
    public static Date getYearStart(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.DAY_OF_YEAR, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * 获取当年的结束时间
     * @param date
     * @return
     */
    public static Date getYearEnd(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.MONTH, 11);
        calendar.set(Calendar.DAY_OF_MONTH, 31);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 29);
        return calendar.getTime();
    }

    /**
     * 获取今天零时
     * @return
     */
    public static Date todayBegin() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * 获取今天中午12时
     * @return
     */
    public static Date todayNoon() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR_OF_DAY, 12);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * 获取指定日期零时
     * @param date
     * @return
     */
    public static Date dayBegin(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * 获取指定日期23时59分29秒
     * @param date
     * @return
     */
    public static Date dayEnd(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        return calendar.getTime();
    }

    /**
     * 获取明天
     * @param date
     * @return
     */
    public static Date tomorrow(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, 1);
        return calendar.getTime();
    }

    /**
     * 获取一天前
     * @param date
     * @return
     */
    public static Date yesterday(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, -1);
        return calendar.getTime();
    }

    /**
     * 获取相隔指定秒的日期
     * @param date
     * @param seconds
     * @return
     */
    public static Date secondAdd(Date date, Integer seconds) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.SECOND, seconds);
        return calendar.getTime();
    }

    /**
     * 获取相隔指定分钟的日期
     * @param date
     * @param minutes
     * @return
     */
    public static Date minuteAdd(Date date, Integer minutes) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MINUTE, minutes);
        return calendar.getTime();
    }

    /**
     * 获取相隔指定小时的日期
     * @param date
     * @param hours
     * @return
     */
    public static Date hourAdd(Date date, Integer hours) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.HOUR, hours);
        return calendar.getTime();
    }

    /**
     * 获取相隔指定天数的日期
     * @param date
     * @param days
     * @return
     */
    public static Date dayAdd(Date date, Integer days) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, days);
        return calendar.getTime();
    }

    /**
     * 获取相隔指定月数的日期
     * @param date
     * @param months
     * @return
     */
    public static Date monthAdd(Date date, Integer months) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, months);
        return calendar.getTime();
    }

    /**
     * 获取相隔指定年数的日期
     * @param date
     * @param years
     * @return
     */
    public static Date yearAdd(Date date, Integer years) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.YEAR, years);
        return calendar.getTime();
    }

    /**
     * 获取一周前
     * @param date
     * @return
     */
    public static Date lastWeek(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.WEEK_OF_YEAR, -1);
        return calendar.getTime();
    }

    /**
     * 获取一个月前
     * @param date
     * @return
     */
    public static Date lastMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, -1);
        return calendar.getTime();
    }

    /**
     * 获取季度
     * @param date
     * @return
     */
    public static Integer getSeason(Date date) {
        Calendar calendar = Calendar.getInstance();
        int season = 0;
        calendar.setTime(date);
        int month = calendar.get(Calendar.MONTH);
        switch (month) {
            case Calendar.JANUARY:
            case Calendar.FEBRUARY:
            case Calendar.MARCH:
                season = 1;
                break;
            case Calendar.APRIL:
            case Calendar.MAY:
            case Calendar.JUNE:
                season = 2;
                break;
            case Calendar.JULY:
            case Calendar.AUGUST:
            case Calendar.SEPTEMBER:
                season = 3;
                break;
            case Calendar.OCTOBER:
            case Calendar.NOVEMBER:
            case Calendar.DECEMBER:
                season = 4;
                break;
            default:
                break;
        }
        return season;
    }

    /**
     * 计算时间差
     * @param date1
     * @param date2
     * @return
     */
    public static String getDiffTime(Date date1, Date date2, String formatType){

        Long milliseconds = date1.getTime() - date2.getTime();  // 相差的毫秒值
        long nd = 1000 * 24 * 60 * 60;// 一天的毫秒数
        long nh = 1000 * 60 * 60;// 一小时的毫秒数
        long nm = 1000 * 60;// 一分钟的毫秒数
        long ns = 1000;// 一秒钟的毫秒数

        long day = milliseconds / nd; // 计算相差多少天
        long hour = milliseconds % nd / nh; // 计算相差剩余多少小时
        long min = milliseconds % nd % nh / nm; // 计算相差剩余多少分钟
        long sec = milliseconds % nd % nh % nm / ns; // 计算相差剩余多少秒
        long hourAll = milliseconds / nh; // 计算相差多少小时
        long min2 = milliseconds / nm; // 计算相差多少分钟

        String diffTime = null;
        if(Objects.equals(formatType, "day")){
            diffTime = day + "天" + hour + "小时" + min + "分钟" + sec + "秒";
        }else if(Objects.equals(formatType, "hour")){
            diffTime = hourAll + "小时" + min + "分钟" + sec + "秒";
        }else if(Objects.equals(formatType, "minute")){
            diffTime = min2 + "分钟" + sec + "秒";
        }

        return diffTime;
    }

    /**
     * 获取默认统计时间间隔,用于mysql
     * @param beginTime
     * @param endTime
     * @return
     */
    public static String getDefaultCountInterval(Date beginTime, Date endTime) {
        Calendar calendar = Calendar.getInstance();
        //默认以天为单位统计
        String interval = "%Y-%m%-%d";
        if (beginTime == null || endTime == null) {
            return interval;
        }
        Date tempTime;
        //如果时间间隔大于4周,以周为单位统计
        calendar.setTime(beginTime);
        calendar.add(Calendar.WEEK_OF_YEAR, 4);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "%X年第%V周";
        }
        //如果时间间隔大于6个月,以月为单位统计
        calendar.setTime(beginTime);
        calendar.add(Calendar.MONTH, 6);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "%Y年%c月";
        }
        //如果时间间隔大于3年,以年为单位统计
        calendar.setTime(beginTime);
        calendar.add(Calendar.YEAR, 3);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "%Y年";
        }
        return interval;
    }

    /**
     * 自动判定聚合interval,用于es
     * @param beginTime
     * @param endTime
     * @return
     */
    public static String getDefaultAggregationInterval(Date beginTime, Date endTime) {
        Calendar calendar = Calendar.getInstance();
        String interval = "year";
        if (beginTime == null || endTime == null) {
            return interval;
        }
        Date tempTime;
        //如果时间间隔大于3天
        calendar.setTime(beginTime);
        calendar.add(Calendar.DAY_OF_YEAR, 3);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "day";
        }
        //如果时间间隔大于3周
        calendar.setTime(beginTime);
        calendar.add(Calendar.WEEK_OF_YEAR, 3);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "week";
        }
        //如果时间间隔大于3个月
        calendar.setTime(beginTime);
        calendar.add(Calendar.MONTH, 3);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "month";
        }
        //如果时间间隔大于12个月
        calendar.setTime(beginTime);
        calendar.add(Calendar.MONTH, 12);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "quarter";
        }
        //如果时间间隔大于6年
        calendar.setTime(beginTime);
        calendar.add(Calendar.YEAR, 5);
        tempTime = calendar.getTime();
        if (tempTime.getTime() < endTime.getTime()) {
            interval = "year";
        }
        return interval;
    }

    public static void main(String[] args) {

        // 获取当前时间 2022-02-15 09:55:01
        Date date = DateUtil.date();
        System.out.println(date);

        // 获取当前时间 Tue Feb 15 09:43:55 CST 2022
        Date dateCST = new Date();
        System.out.println(dateCST);

        // 获取当前时间戳 1644890101230
        Long timestamp = Calendar.getInstance().getTimeInMillis();
        System.out.println(timestamp);

        // 将时间戳转成字符串 2022-02-15 09:55:01
        SimpleDateFormat format =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String timestampStr = format.format(timestamp);
        System.out.println(timestampStr);

        // 将字符串按指定格式解析成日期 Tue Feb 15 09:43:55 CST 2022
        System.out.println(parseDate("2022-02-15 09:43:55","yyyy-MM-dd HH:mm:ss"));

        // 将日期按指定格式解析成字符串 2022-02-15 09:55:01
        System.out.println(parseStr(date,"yyyy-MM-dd HH:mm:ss"));

        // 自动将字符串解析成日期 Tue Feb 15 09:43:55 CST 2022
        System.out.println(autoParseDate("2022-02-15 09:43:55"));

        // 指定年份的当前日期 Tue Feb 15 09:43:55 CST 2022
        System.out.println(setYear(date,2022));

        // 获取当年开始时间 Sat Jan 01 00:00:00 CST 2022
        System.out.println(getYearStart(date));

        // 获取当年结束时间 Sat Dec 31 23:59:29 CST 2022
        System.out.println(getYearEnd(date));

        // 获取当日零时 Tue Feb 15 00:00:00 CST 2022
        System.out.println(todayBegin());

        // 获取当日12时 Tue Feb 15 12:00:00 CST 2022
        System.out.println(todayNoon());

        // 获取指定日期0时 Tue Feb 15 00:00:00 CST 2022
        System.out.println(dayBegin(date));

        // 获取指定日期23时59分29秒 Tue Feb 15 23:59:59 CST 2022
        System.out.println(dayEnd(date));

        // 获取明天 Wed Feb 16 09:55:01 CST 2022
        System.out.println(tomorrow(date));

        // 获取一天前 Mon Feb 14 09:55:01 CST 2022
        System.out.println(yesterday(date));

        // 获取相隔指定分钟的日期 Mon Feb 15 09:56:01 CST 2022
        System.out.println(minuteAdd(date,1));

        // 获取相隔指定小时的日期 Mon Feb 15 10:55:01 CST 2022
        System.out.println(hourAdd(date,1));

        // 获取相隔指定天数的日期 Wed Feb 16 09:55:01 CST 2022
        System.out.println(dayAdd(date,1));

        // 获取相隔指定月数的日期 Sat Jan 15 09:55:01 CST 2022
        System.out.println(monthAdd(date,-1));

        // 获取相隔指定年数的日期 Tue Feb 15 09:55:01 CST 2022
        System.out.println(yearAdd(date,0));

        // 获取一周前 Tue Feb 08 09:55:01 CST 2022
        System.out.println(lastWeek(date));

        // 获取一月前 Sat Jan 15 09:55:01 CST 2022
        System.out.println(lastMonth(date));

        // 获取季度 1
        System.out.println(getSeason(date));

        // 获取时间差 2天1小时10分钟1秒
        System.out.println(getDiffTime(dateCST, parseDate("2022-02-13 08:33:54","yyyy-MM-dd HH:mm:ss"), "day"));

        // 获取默认统计时间间隔,用于mysql %X年第%V周
        System.out.println(getDefaultCountInterval(date,dayAdd(date,100)));

        // 自动判定聚合interval,用于es month
        System.out.println(getDefaultAggregationInterval(date,dayAdd(date,100)));

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592

# 4.1.8 Object解析转换工具类

import java.lang.reflect.Field;
import java.util.*;

/**
 * Object类型转换工具类
 */
public class ObjectConvertUtils {

    /**
     * 将Object转换成Integer
     * @param object
     * @return
     */
    public static Integer parseInteger(Object object) {
        if (object == null) {
            return null;
        }
        try {
            return Integer.parseInt(object.toString());
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    /**
     * 将Object转换成Long
     * @param object
     * @return
     */
    public static Long parseLong(Object object) {
        if (object == null) {
            return null;
        }
        try {
            return Long.parseLong(object.toString());
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    /**
     * 将Object转换成Long或默认值
     * @param object
     * @param defaultValue
     * @return
     */
    public static Long parseLongOrDefault(Object object, Long defaultValue) {
        if (object == null) {
            return defaultValue;
        }
        try {
            return Long.parseLong(object.toString());
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }

    /**
     * 将Object转换成Double
     * @param object
     * @return
     */
    public static Double parseDouble(Object object) {
        if (object == null) {
            return null;
        }
        try {
            return Double.parseDouble(object.toString());
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    /**
     * 将Object转换成Float
     * @param object
     * @return
     */
    public static Float parseFloat(Object object) {
        if (object == null) {
            return null;
        }
        try {
            return Float.parseFloat(object.toString());
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    /**
     * 将Object转换成Date
     * @param object
     * @return
     */
    public static Date getAsDate(Object object) {
        if (object instanceof Date) {
            return (Date) object;
        } else {
            return null;
        }
    }

    /**
     * 将Object转换成String
     * @param object
     * @return
     */
    public static String parseString(Object object) {
        if (object == null) {
            return null;
        } else {
            return object.toString();
        }
    }

    /**
     * 将非空Object转换成String
     * @param object
     * @return
     */
    public static String getAsStringExceptEmpty(Object object) {
        if (object != null && !object.toString().isEmpty()) {
            return object.toString();
        } else {
            return null;
        }
    }

    /**
     * 将Object转换成List<Long>
     * @param object
     * @return
     */
    public static List<Long> parseLongList(Object object) {
        if (!(object instanceof List)) {
            return null;
        }
        List<?> tmpList = (List<?>) object;
        List<Long> longList = new ArrayList<>();
        if (tmpList.size() > 0) {
            try {
                tmpList.forEach(v -> {
                    longList.add(Long.parseLong(v.toString()));
                });
            } catch (NumberFormatException ex) {
                ex.printStackTrace();
            }
        }
        return longList;
    }

    /**
     * 将Object转换成List<String>
     * @param object
     * @return
     */
    public static List<String> parseStringList(Object object) {
        if (!(object instanceof List)) {
            return null;
        }
        List<?> tmpList = (List<?>) object;
        List<String> stringList = new ArrayList<>();
        if (tmpList.size() > 0) {
            try {
                tmpList.forEach(v -> {
                    stringList.add(v.toString());
                });
            } catch (NumberFormatException ex) {
                ex.printStackTrace();
            }
        }
        return stringList;
    }

    /**
     * 将Object转换成List<Map<String, Object>>
     * @param object
     * @return
     */
    public static List<Map<String, Object>> parseMapList(Object object) {
        if (!(object instanceof List)) {
            return null;
        }
        List<?> objectList = (List<?>) object;
        List<Map<String, Object>> list = new ArrayList<>();
        for (int i = 0; i <= objectList.size() - 1; i++) {
            Object o = objectList.get(i);
            if (!(o instanceof Map)) {
                continue;
            }
            Map<?, ?> objectMap = (Map<?, ?>) o;
            Map<String, Object> map = new HashMap<>();
            for (Map.Entry<?, ?> entry : objectMap.entrySet()) {
                map.put(entry.getKey().toString(), entry.getValue());
            }
            list.add(map);
        }
        return list;
    }

    /**
     * 将各种类型的是否转成0或1
     * @param object
     * @return
     */
    public static Integer parseYesOrNoToInteger(Object object) {
        if (object == null) {
            return null;
        }
        if ("1".equals(object.toString()) || "是".equals(object.toString()) || "yes".equals(object.toString().toLowerCase())) {
            return 1;
        }
        if ("0".equals(object.toString()) || "否".equals(object.toString()) || "no".equals(object.toString().toLowerCase())) {
            return 0;
        }
        return null;
    }
  
    /**
     * 判断实体类是否为空
     * @param obj
     * @return
     * @throws IllegalAccessException
     */
    public static boolean isEntityEmpty(Object obj) throws IllegalAccessException {
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if (field.get(obj) != null) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {

        // 制造测试数据
        Map<String, Object> map = new HashMap<>();
        List<Map<String, Object>> relationMapList = new ArrayList<>();
        Map<String, Object> fatherMap = new HashMap<>();
        fatherMap.put("name","lisi");
        fatherMap.put("sex",1);
        Map<String, Object> motherMap = new HashMap<>();
        motherMap.put("name","wanger");
        motherMap.put("sex",2);
        relationMapList.add(fatherMap);
        relationMapList.add(motherMap);
        List<Long> expenditureList = new ArrayList<>();
        expenditureList.add(1000L);
        expenditureList.add(2000L);
        List<String> nickNameList = new ArrayList<>();
        nickNameList.add("aaa");
        nickNameList.add("bbb");
        map.put("id", 10000L);
        map.put("name", "zhangsan");
        map.put("age", 25);
        map.put("boy", true);
        map.put("birthday", new Date());
        map.put("married", "是");
        map.put("phone", null);
        map.put("email", "");
        map.put("income", 5000.0f);
        map.put("assets", 100000.00d);
        map.put("relation", relationMapList);
        map.put("expenditure", expenditureList);
        map.put("nickName", nickNameList);
        System.out.println(map);

        // parseInteger
        Integer age = parseInteger(map.get("age"));
        System.out.println(age);

        // parseLong
        Long id = parseLong(map.get("id"));
        System.out.println(id);

        // parseLongOrDefault
        Long phone = parseLongOrDefault(map.get("phone"),0L);
        System.out.println(phone);

        // parseDouble
        Double assets = parseDouble(map.get("assets"));
        System.out.println(assets);

        // parseFloat
        Float income = parseFloat(map.get("income"));
        System.out.println(income);

        // getAsDate
        Date birthday = getAsDate(map.get("birthday"));
        System.out.println(birthday);

        // parseString
        String name = parseString(map.get("name"));
        System.out.println(name);

        // getAsStringExceptEmpty
        String email = getAsStringExceptEmpty(map.get("email"));
        System.out.println(email);

        // parseLongList
        List<Long> expenditure = parseLongList(map.get("expenditure"));
        System.out.println(expenditure);

        // parseStringList
        List<String> nickName = parseStringList(map.get("nickName"));
        System.out.println(nickName);

        // parseMapList
        List<Map<String, Object>> relation = parseMapList(map.get("relation"));
        System.out.println(relation);

        // parseYesOrNoToInteger
        Integer married = parseYesOrNoToInteger(map.get("married"));
        System.out.println(married);

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319

# 4.1.9 List转换处理工具类

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * List相关转换处理工具类
 */
public class ListUtils {

    /**
     * 按照指定长度拆分list
     * @param list
     * @param groupSize
     * @return
     */
    public static List<List<String>> splitList(List<String> list, int groupSize){
        int length = list.size();
        // 计算可以分成多少组
        int num = ( length + groupSize - 1 )/groupSize ;
        List<List<String>> newList = new ArrayList<>(num);
        for (int i = 0; i < num; i++) {
            // 开始位置
            int fromIndex = i * groupSize;
            // 结束位置
            int toIndex = Math.min((i + 1) * groupSize, length);
            newList.add(list.subList(fromIndex,toIndex)) ;
        }
        return  newList ;
    }

    /**
     * 合并两个List<Map<String, Object>>
     * @param list1
     * @param list2
     * @return
     */
    public static List<Map<String, Object>> mergeMapList(List<Map<String, Object>> list1, List<Map<String, Object>> list2){
        list1.addAll(list2);
        Set<String> set = new HashSet<>();
        return list1.stream()
                .collect(Collectors.groupingBy(o->{
                    //暂存所有key
                    set.addAll(o.keySet());
                    //按a_id分组
                    return o.get("id");
                })).values().stream().map(maps -> {
                    //合并
                    Map<String, Object> map = maps.stream().flatMap(m -> {
                        return m.entrySet().stream();
                    }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b));
                    //为没有的key赋值0
                    set.forEach(k -> {
                        if (!map.containsKey(k)) {
                            map.put(k, 0);
                        }
                    });
                    return map;
                }).collect(Collectors.toList());
    }

    /**
     * 排列组合(字符重复排列)
     * @param list 待排列组合字符集合
     * @param length 排列组合生成长度
     * @return 指定长度的排列组合后的字符串集合
     */
    public static List<String> permutation(List<String> list, int length) {
        Stream<String> stream = list.stream();
        for (int n = 1; n < length; n++) {
            stream = stream.flatMap(i -> list.stream().map(i::concat));
        }
        return stream.collect(Collectors.toList());
    }

    /**
     * 排列组合(字符不重复排列)
     * @param list 待排列组合字符集合(忽略重复字符)
     * @param length 排列组合生成长度
     * @return 指定长度的排列组合后的字符串集合
     */
    public static List<String> permutationNoRepeat(List<String> list, int length) {
        Stream<String> stream = list.stream().distinct();
        for (int n = 1; n < length; n++) {
            stream = stream.flatMap(i -> list.stream().filter(j -> !i.contains(j)).map(i::concat));
        }
        return stream.collect(Collectors.toList());
    }

    /**
     * 生成数值列表
     * @param start
     * @param end
     * @return
     */
    public static List<Integer> generateOrdinalList(int start, int end) {
        List<Integer> range = IntStream.rangeClosed(start, end)
                .boxed().collect(Collectors.toList());
        return range;
    }

    /**
     * 将List转成指定分隔符分隔的字符串
     * @param list
     * @param format
     * @return
     */
    public static String listToString(List<String> list, String format) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            if (i == list.size() - 1) {
                str.append(list.get(i));
            } else {
                str.append(list.get(i)).append(format);
            }
        }
        return str.toString();
    }

    /**
     * 将指定分隔符分隔的字符串转成List
     * @param str
     * @param format
     * @return
     */
    public static List<String> stringToList(String str, String format) {
        return Arrays.asList(str.split(format));
    }

    /**
     * 计算并集并去重
     * @param list1
     * @param list2
     * @return
     */
    public static List<String> union(List<String> list1, List<String> list2){
        List<String> list = new LinkedList<>();
        list.addAll(list1);
        list.addAll(list2);
        HashSet<String> hs = new HashSet<>(list);
        list.clear();
        list.addAll(hs);
        return list;
    }

    /**
     * 计算交集
     * @param list1
     * @param list2
     * @return
     */
    public static List<String> retainAll(List<String> list1, List<String> list2){
        List<String> list = new LinkedList<>();
        list.addAll(list1);
        list.retainAll(list2);
        return list;
    }

    /**
     * 计算差集
     * @param list1
     * @param list2
     * @return
     */
    public static List<String> subtraction(List<String> list1, List<String> list2) {
        List<String> list = new LinkedList<>();
        list.addAll(list1);
        list.removeAll(list2);
        return list;
    }


    public static void main(String[] args) {
        List<String> list = Arrays.asList("1","2","3");

        System.out.println(splitList(list,2));
        System.out.println(permutation(list, 2));
        System.out.println(permutationNoRepeat(list, 2));

        List<Integer> lengthList = generateOrdinalList(1,list.size());
        List<String> resultList = new ArrayList<>();
        for(Integer length:lengthList){
            List<String> itemList = permutationNoRepeat(list,length);
            for(String item:itemList){
                item = listToString(stringToList(item,""),",");
                resultList.add(item);
            }
        }
        System.out.println(resultList);

        String str = listToString(list,",");
        System.out.println(str);
        System.out.println(stringToList(str,","));

        List<String> list1 = Arrays.asList("a","b","c");
        List<String> list2 = Arrays.asList("c","d","e");
        System.out.println(union(list1,list2));
        System.out.println(retainAll(list1,list2));
        System.out.println(subtraction(list1,list2));

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

# 4.1.10 Map转换处理工具类

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Maps;
import org.springframework.cglib.beans.BeanMap;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * Map相关转换处理工具类
 */
public class MapUtils {

    /**
     * 将字符串转换成Map<String, Object>
     * @param str
     * @return
     */
    public static Map<String, Object> strToMap(String str) {
        Map<String, Object> result = new HashMap<>();
        try{
            result = JSON.parseObject(str, new TypeReference<Map<String, Object>>(){});
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 将字符串转成List<Map<String,Object>>
     * @param str
     * @return
     */
    public static List<Map<String, Object>> strToListOfMap(String str) {
        List<Map<String, Object>> result = new ArrayList<>();
        try{
            result = JSON.parseObject(str, new TypeReference<List<Map<String, Object>>>() {});
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 合并两个Map
     * @param map1
     * @param map2
     * @return
     */
    public static Map<String, Object> mergeHashMap(Map<String, Object> map1, Map<String, Object> map2) {
        HashMap<String, Object> combineResultMap = new HashMap<>();
        combineResultMap.putAll(map1);
        combineResultMap.putAll(map2);
        return combineResultMap;
    }

    /**
     * 使用BeanMap将实体类转成Map
     * @param bean
     * @param <T>
     * @return
     */
    public static <T> Map<String, Object> beanToMap(T bean) {
        Map<String, Object> map = Maps.newHashMap();
        if (bean != null) {
            BeanMap beanMap = BeanMap.create(bean);
            for (Object key : beanMap.keySet()) {
                map.put(key + "", beanMap.get(key));
            }
        }
        return map;
    }

    /**
     * 将 map 转成实体类
     * @param clazz
     * @param map
     * @param <T>
     * @return
     */
    public static <T> T convertMapToBean(Class<T> clazz, Map<String, Object> map) {
        T obj = null;
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            // 创建 JavaBean 对象
            obj = clazz.newInstance();
            // 给 JavaBean 对象的属性赋值
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (int i = 0; i < propertyDescriptors.length; i++) {
                PropertyDescriptor descriptor = propertyDescriptors[i];
                String propertyName = descriptor.getName();
                if (map.containsKey(propertyName)) {
                    Object value = map.get(propertyName);
                    if ("".equals(value)) {
                        value = null;
                    }
                    Object[] args = new Object[1];
                    args[0] = value;
                    descriptor.getWriteMethod().invoke(obj, args);
                }
            }
        } catch (IllegalAccessException e) {
            System.out.println(StrUtil.format("convertMapToBean 实例化JavaBean失败 Error{}",e));
        } catch (IntrospectionException e) {
            System.out.println(StrUtil.format("convertMapToBean 分析类属性失败 Error{}" ,e));
        } catch (IllegalArgumentException e) {
            System.out.println(StrUtil.format("convertMapToBean 映射错误 Error{}" ,e));
        } catch (InstantiationException e) {
            System.out.println(StrUtil.format("convertMapToBean 实例化 JavaBean 失败 Error{}" ,e));
        }catch (InvocationTargetException e){
            System.out.println(StrUtil.format("convertMapToBean字段映射失败 Error{}" ,e));
        }catch (Exception e){
            System.out.println(StrUtil.format("convertMapToBean Error{}" ,e));
        }
        return (T) obj;
    }

    /**
     * 按照value对Map排序
     * @param map
     * @param isAsc 是否升序排序
     * @return ArrayList 有序存储着Map的元素
     */
    public static ArrayList<Map.Entry<String, Double>> sortMapByValue(Map<String, Double> map, final boolean isAsc) {
        //把Map转换为List
        List<Map.Entry<String, Double>> entries = new ArrayList<>(
                map.entrySet());
        //利用Collections.sort进行排序
        Collections.sort(entries, new Comparator<Map.Entry<String, Double>>() {
            @Override
            public int compare(Map.Entry<String, Double> obj1,
                               Map.Entry<String, Double> obj2) {
                if(isAsc) {
                    return obj1.getValue().compareTo(obj2.getValue());
                } else {
                    return obj2.getValue().compareTo(obj1.getValue());
                }
            }
        });
        return (ArrayList<Map.Entry<String, Double>>) entries;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

# 4.1.11 JSON相关工具类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class JsonUtils {

    /**
     * 读取json文件,返回json字符串
     * @param fileName
     * @return
     */
    public static String readJsonFile(String fileName) {
        String jsonStr = "";
        try {
            File jsonFile = new File(fileName);
            FileReader fileReader = new FileReader(jsonFile);
            Reader reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
            return jsonStr;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将json字符串转成List<Map<String, Object>>
     * @param jsonStr
     * @return
     */
    public static List<Map<String, Object>> jsonToListOfMap(String jsonStr) {
        List<Map<String, Object>> result = new ArrayList<>();
        try{
            result = (List<Map<String, Object>>) JSONArray.parse(jsonStr);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * JSON字符串美化
     * @param jsonStr
     * @return
     */
    public static String prettifyJson(String jsonStr) {
        JSONObject object = JSONObject.parseObject(jsonStr);
        return JSON.toJSONString(object, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteDateUseDateFormat);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

另注:JSONArray和JSONOject的基本用法

[1] JSONArray

  • 格式

    	本质是数组,必须是数组格式,用 [ ] 包裹数据 
    	格式:   [{key:value},{key:value}... ]
        	    ["str1","str2","str3",...]
    
    1
    2
    3
  • 解析字符串

    JSONArray jsonarr = JSONArray.parseArray(str);
    
    1
  • 存值取值

    	(1) 存值
      jsonarr.add(obj);
    
      (2) 取值
      for(int i =0; i <= jsonarr.size(); i++){
    		 jsonarr[i].get(key);
    	} 
    
    1
    2
    3
    4
    5
    6
    7

[2] JSONOject

  • 格式

    	本质是对象, 用 {} 表示
    	格式:  {key:value}
    
    1
    2
  • 解析字符串

    JSONObject obj = JSONArray.parseObject(str);
    
    1
  • 存值取值

      (1) 存值
    	obj.put("key", value);
    
      (2) 取值
      value = obj.get(key);
    
    1
    2
    3
    4
    5

# 4.1.12 Tree相关工具类

import com.alibaba.fastjson.JSON;
import lombok.Data;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Tree相关工具类
 */
public class TreeUtils {

    /**
     * list转tree--主方法
     *
     * @param tags
     * @return
     */
    public static List<TreeNodeTag> listToTree(List<Tag> tags) {

        List<TreeNodeTag> lists = new ArrayList<>(tags.size());
        //先将查询出来的数据转换为TreeNodeTag节点的集合
        for (Tag tag : tags) {
            TreeNodeTag node = new TreeNodeTag();
            node.setId(tag.getId());
            node.setName(tag.getName());
            node.setParentId(tag.getParentId());
            lists.add(node);
        }
        //在关联孩子节点(若lists中只有一个顶级root节点,那么tree中只会有一个元素节点)
        List<TreeNodeTag> trees = new ArrayList<>();
        for (TreeNodeTag node : lists) {
            //寻找顶级root节点(一个List中,只有一个节点的父节点为null),如果取中间级作为root节点,需要传入中间级id
            if (node.getParentId() == null) {
                //添加孩子节点
                trees.add(findChildren(node, lists));
            }
        }

        return trees;
    }

    /**
     * list转tree--找到根节点下所有的孩子节点
     *
     * @param root
     * @param lists
     * @return
     */
    private static TreeNodeTag findChildren(TreeNodeTag root, List<TreeNodeTag> lists) {
        //遍历集合,寻找root节点的子节点(若节点的父节点为root节点id,则证明是root子节点)
        for (TreeNodeTag node : lists) {
            if (root.getId().equals(node.getParentId())) {
                //第一个节点匹配时,创造一个List集合,只会创建一次
                if (root.getChildren() == null) {
                    root.setChildren(new ArrayList<>());
                }
                //递归方式,创建该子节点。
                root.getChildren().add(findChildren(node, lists));
            }
        }
        return root;
    }

    /**
     * tree转list--主方法
     *
     * @param tree
     * @return
     */
    public static List<Tag> treeToList(List<TreeNodeTag> tree) {
        ArrayList<Tag> list = new ArrayList<>();
        treeToList(tree, list);
        return list;
    }

    /**
     * 将tree转换成list
     *
     * @param tree
     * @param result
     */
    private static void treeToList(List<TreeNodeTag> tree, List<Tag> result) {
        for (TreeNodeTag node : tree) {
            //读取到根节点时,将数据放入到list中
            Tag tag = new Tag();
            tag.setId(node.getId());
            tag.setName(node.getName());
            tag.setParentId(node.getParentId());
            result.add(tag);
            List<TreeNodeTag> children = node.getChildren();
            //递归出口,节点不为空时,一直去遍历
            if (!CollectionUtils.isEmpty(children)) {
                treeToList(children, result);
            }
        }
    }

    /**
     * 根据id找到对应的node节点--主方法
     *
     * @param id
     * @param tree
     * @return
     */
    public static TreeNodeTag findChildrenNodes(String id, List<TreeNodeTag> tree) {
        //遍历tree找到节点
        for (TreeNodeTag node : tree) {
            if (node.getId().equals(id)) {
                return node;
            } else {
                //获取子节点列表
                List<TreeNodeTag> children = node.getChildren();
                if (!CollectionUtils.isEmpty(children)) {
                    TreeNodeTag childrenNodes = findChildrenNodes(id, children);
                    //没有找到节点(即等于null),那么就继续for循环去兄弟节点处寻找
                    if (childrenNodes != null) {
                        return childrenNodes;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 查询给定节点列表的所有孩子节点id
     *
     * @param ids
     * @param tree
     * @return
     */
    public static List<String> findChildNodes(List<String> ids, List<TreeNodeTag> tree) {
        //查找给定id对应的node节点
        List<String> result = new ArrayList<>();
        for (String id : ids) {
            //根据id获取到node节点
            TreeNodeTag root = findChildrenNodes(id, tree);
            //获取到node节点上的所以孩子节点信息
            if (root == null) {
                continue;
            }
            //获取到节点信息
            List<TreeNodeTag> children = root.getChildren();
            List<String> res = new ArrayList<>();
            if (!CollectionUtils.isEmpty(children)) {
                //前序遍历节点信息
                getSubNodeId(children, res);
            }
            //获取到数据集合
            result.addAll(res);

        }
        return result;
    }

    /**
     * 前序遍历获取子节点信息
     *
     * @param roots
     * @param res
     */
    private static void getSubNodeId(List<TreeNodeTag> roots, List<String> res) {
        if (!CollectionUtils.isEmpty(roots)) {
            for (TreeNodeTag node : roots) {
                //第一次获取到根节点时处理数据
                res.add(node.getId());
                getSubNodeId(node.getChildren(), res);
            }
        }
    }

    /**
     * 根据传入的多叉树集合获取到所有叶子节点信息--主方法
     *
     * @param trees
     * @return
     */
    public static List<TreeNodeTag> findLeafNodes(List<TreeNodeTag> trees) {

        //生成一个结果集保存叶子节点
        List<TreeNodeTag> leafNodes = new ArrayList<>();
        findLeafNodes(trees, leafNodes);
        return leafNodes;

    }

    /**
     * 根据传入的多叉树集合获取到所有叶子节点信息
     *
     * @param trees
     * @param result
     */
    public static void findLeafNodes(List<TreeNodeTag> trees, List<TreeNodeTag> result) {

        //在trees不为null的情况下,foreach遍历
        if (trees != null) {
            for (TreeNodeTag node : trees) {
                //若是node节点的子节点为空,那么将结果保存到集合中
                if (CollectionUtils.isEmpty(node.getChildren())) {
                    result.add(node);
                }
                //继续遍历
                findLeafNodes(node.getChildren(), result);
            }

        }
    }

    /**
     * 实体类,List中的元素(数据库存储的实体)
     */
    @Data
    private static class Tag {
        private String id;  //节点编号
        private String name; //节点名字
        private String parentId;  //节点父级编号
    }

    /**
     * 树中的节点信息
     */
    @Data
    private static class TreeNodeTag {

        private String id;
        private String name;
        private String parentId;
        private List<TreeNodeTag> children;

    }


    public static void main(String[] args) {

        // 初始化测试数据
        Tag tag = new Tag();
        tag.setId("1");
        tag.setName("河北省");
        Tag tag1 = new Tag();
        tag1.setId("1-1");
        tag1.setName("沧州市");
        tag1.setParentId("1");
        Tag tag2 = new Tag();
        tag2.setId("1-2");
        tag2.setName("邯郸市");
        tag2.setParentId("1");
        Tag tag3 = new Tag();
        tag3.setId("1-3");
        tag3.setName("衡水市");
        tag3.setParentId("1");
        Tag tag4 = new Tag();
        tag4.setId("1-3-1");
        tag4.setName("景县");
        tag4.setParentId("1-3");
        Tag tag5 = new Tag();
        tag5.setId("1-3-2");
        tag5.setName("桃城区");
        tag5.setParentId("1-3");
        List<Tag> tagList=new ArrayList<>();
        tagList.add(tag);
        tagList.add(tag1);
        tagList.add(tag2);
        tagList.add(tag3);
        tagList.add(tag4);
        tagList.add(tag5);

        System.out.println("1. 测试list转tree");
        // 可以看做是多叉树的生成,采用后序遍历的方式,即先遍历子节点,在填充根节点。
        List<TreeNodeTag> tree = listToTree(tagList);
        System.out.println(JSON.toJSON(tree));

        System.out.println("2. 测试tree转list");
        // 采用的是前序遍历的方式,第一次遍历到根节点时,将数据放入到list集合中。
        List<Tag> list = treeToList(tree);
        System.out.println(list);

        System.out.println("3. 根据id查找对应的node节点");
        // 对元素采取的是前序遍历的方式,即第一次遍历到根节点时,判断根节点的值是否为传入的id值。
        // 若不是,那么获取到根节点的子节点。然后遍历各个子节点。只有到子节点返回真正数据时,才return出去,否则的话去兄弟节点寻找数据。
        TreeNodeTag treeNodeTag = findChildrenNodes("1-3-2", tree);
        System.out.println(treeNodeTag);

        System.out.println("4. 根据节点编号查询节点的子节点信息");
        // 根据id找到对应node节点后,使用前序遍历获取节点信息。
        List<String> ids = new ArrayList<>();
        ids.add("1-2");
        ids.add("1-3");
        List<String> childrenIds = findChildNodes(ids, tree);
        System.out.println(childrenIds);

        System.out.println("5. 查询节点的叶子节点信息");
        // 叶子节点特征是其的子节点集合为空。
        List<TreeNodeTag> leafNodes = findLeafNodes(tree);
        System.out.println(leafNodes);

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

# 4.1.13 MD5通用工具类

import org.apache.commons.codec.digest.DigestUtils;

/**
 * MD5通用类
 */
public class Md5Util {

    /**
     * MD5方法
     *
     * @param text 明文
     * @param key  密钥
     * @return 密文
     * @throws Exception
     */
    public static String md5(String text, String key) throws Exception {
        // 加密后的字符串
        String encodeStr = DigestUtils.md5Hex(text + key);
        return encodeStr;
    }

    /**
     * MD5验证方法
     *
     * @param text 明文
     * @param key  密钥
     * @param md5  密文
     * @return true/false
     * @throws Exception
     */
    public static boolean verify(String text, String key, String md5) throws Exception {
        // 根据传入的密钥进行验证
        String md5Text = md5(text, key);
        if (md5Text.equalsIgnoreCase(md5)) {
            System.out.println("MD5验证通过");
            return true;
        }
        return false;
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 4.1.14 AES加密解密工具类

AES加密为最常见的对称加密算法(微信小程序的加密传输就是用的这个加密算法)。对称加密算法也就是加密和解密用相同的密钥,具体的加密流程如下图:

AES加密传输流程

AES加密解密工具类代码如下:

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Base64;

/**
 * AES加密工具类
 */
public class AesUtility {

    /**
     * 偏移量  AES 为16bytes. DES
     */
    public static final String VIPARA = "0845762876543456";

    /**
     * 编码方式
     */
    public static final String CODE_TYPE = "UTF-8";

    /**
     * 填充类型
     */
    public static final String AES_TYPE = "AES/ECB/PKCS5Padding";

    /**
     * 字符补全
     */
    private static final String[] CONSULT = new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
            "C", "D", "E", "F", "G"};

    /**
     * AES加密
     *
     * @param cleartext 明文
     * @param aesKey    密钥
     * @return 密文
     */
    public static String encrypt(String cleartext, String aesKey) {
        // 加密方式: AES128(CBC/PKCS5Padding) + Base64
        try {
            if ("AES/ECB/NoPadding".equals(AES_TYPE)) {
                cleartext = completionCodeFor16Bytes(cleartext);
            }
            aesKey = md5(aesKey);
            System.out.println(aesKey);
            // 两个参数,第一个为私钥字节数组, 第二个为加密方式 AES或者DES
            SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), "AES");
            // 实例化加密类,参数为加密方式,要写全
            // PKCS5Padding比PKCS7Padding效率高,PKCS7Padding可支持IOS加解密
            Cipher cipher = Cipher.getInstance(AES_TYPE);
            // 初始化,此方法可以采用三种方式,按加密算法要求来添加。
            // (1)无第三个参数
            // (2)第三个参数为SecureRandom random = new SecureRandom();中random对象,随机数。(AES不可采用这种方法)
            // (3)采用此代码中的IVParameterSpec 加密时使用:ENCRYPT_MODE; 解密时使用:DECRYPT_MODE;
            // CBC类型的可以在第三个参数传递偏移量zeroIv,ECB没有偏移量
            cipher.init(Cipher.ENCRYPT_MODE, key);
            // 加密操作,返回加密后的字节数组,然后需要编码。主要编解码方式有Base64, HEX,
            // UUE,7bit等等。此处看服务器需要什么编码方式
            byte[] encryptedData = cipher.doFinal(cleartext.getBytes(CODE_TYPE));
            Base64.Encoder encoder = Base64.getMimeEncoder();
            return encoder.encodeToString(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 解密
     *
     * @param encrypted
     * @return
     */
    public static String decrypt(String encrypted, String aesKey) {
        try {
            aesKey = md5(aesKey);
            Base64.Decoder decoder = Base64.getMimeDecoder();
            byte[] byteMi = decoder.decode(encrypted);
            SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance(AES_TYPE);
            // 与加密时不同MODE:Cipher.DECRYPT_MODE
            // CBC类型的可以在第三个参数传递偏移量zeroIv,ECB没有偏移量
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] decryptedData = cipher.doFinal(byteMi);
            String content = new String(decryptedData, CODE_TYPE);
            if ("AES/ECB/NoPadding".equals(AES_TYPE)) {
                content = resumeCodeOf16Bytes(content);
            }
            return content;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 补全字符
     * @param str
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String completionCodeFor16Bytes(String str) throws UnsupportedEncodingException {
        int num = str.getBytes(CODE_TYPE).length;
        int index = num % 16;
        // 进行加密内容补全操作, 加密内容应该为 16字节的倍数, 当不足16*n字节是进行补全, 差一位时 补全16+1位
        // 补全字符 以 $ 开始,$后一位代表$后补全字符位数,之后全部以0进行补全;
        if (index != 0) {
            StringBuilder stringBuilder = new StringBuilder(str);
            if (16 - index == 1) {
                stringBuilder.append("$").append(CONSULT[16 - 1]).append(addStr(16 - 1 - 1));
            } else {
                stringBuilder.append("$").append(CONSULT[16 - index - 1]).append(addStr(16 - index - 1 - 1));
            }
            str = stringBuilder.toString();
        }
        return str;
    }

    /**
     * 追加字符
     * @param num
     * @return
     */
    public static String addStr(int num) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < num; i++) {
            stringBuilder.append("0");
        }
        return stringBuilder.toString();
    }

    /**
     * 还原字符(进行字符判断)
     * @param str
     * @return
     */
    public static String resumeCodeOf16Bytes(String str) {
        int indexOf = str.lastIndexOf("$");
        if (indexOf == -1) {
            return str;
        }
        String trim = str.substring(indexOf + 1, indexOf + 2).trim();
        int num = 0;
        for (int i = 0; i < CONSULT.length; i++) {
            if (trim.equals(CONSULT[i])) {
                num = i;
            }
        }
        if (num == 0) {
            return str;
        }
        return str.substring(0, indexOf).trim();
    }

    /**
     * md5
     * @param dateString
     * @return
     * @throws Exception
     */
    public static String md5(String dateString) throws Exception {
        byte[] digest = MessageDigest.getInstance("md5").digest(dateString.getBytes(CODE_TYPE));
        StringBuilder md5code = new StringBuilder(new BigInteger(1, digest).toString(16));
        // 如果生成数字未满32位,需要前面补0
        for (int i = 0; i < 32 - md5code.length(); i++) {
            md5code.insert(0, "0");
        }
        return md5code.toString();
    }

    public static String sampleEncrypt(String clearText, String aesKey) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            Cipher cipher = Cipher.getInstance(AES_TYPE);
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey.getBytes(CODE_TYPE), "AES"));
            byte[] b = cipher.doFinal(clearText.getBytes(CODE_TYPE));
            return Base64.getMimeEncoder().encodeToString(b);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public static String sampleDecrypt(String encrypted, String aesKey) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            Cipher cipher = Cipher.getInstance(AES_TYPE);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(aesKey.getBytes(CODE_TYPE), "AES"));
            byte[] b = cipher.doFinal(Base64.getMimeDecoder().decode(encrypted));
            return new String(b, CODE_TYPE);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {

        // AES加密算法
        String encrypt = sampleEncrypt("{\"username\":\"admin\",\"password\":\"12345678\"}", "bbbvccc22cabdcbaf399dffff48604fv");
        System.out.println("result:" + encrypt);
        String decrypt = sampleDecrypt("/C3gUT4IKEBdf70O7sjqupwZSRhNKiArQAynSu3+LoCKmiFIb963ZIV2QlbzuQWW", SessionHolder.getWebAesKey());
        System.out.println(decrypt);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

# 4.1.15 RSA加密解密工具类

RSA加密算法是一种非对称加密算法,所谓非对称,就是指该算法加密和解密使用不同的密钥,即使用加密密钥进行加密、解密密钥进行解密。在RSA算法中,加密密钥PK是公开信息,而解密密钥SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,由于无法计算出大数n的欧拉函数phi(N),所以不能根据PK计算出SK。

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA工具类
 */
public class RsaUtility {

    /**
     * 使用公钥字符串加密
     *
     * @param plainText       明文
     * @param publicKeyString 公钥字符串
     * @return 密文
     * @throws Exception 异常
     */
    public static String encryptByPublicString(String plainText, String publicKeyString) throws Exception {
        PublicKey publicKey = getPublicKeyByString(publicKeyString);
        return encrypt(plainText, publicKey);
    }

    /**
     * 使用私钥字符串解密
     *
     * @param cipherText       密文
     * @param privateKeyString 私钥字符串
     * @return 明文
     * @throws Exception 异常
     */
    public static String decryptByPrivateString(String cipherText, String privateKeyString) throws Exception {
        PrivateKey privateKey = getPrivateKeyByString(privateKeyString);
        return decrypt(cipherText, privateKey);
    }

    /**
     * 使用私钥字符串签名
     *
     * @param plainText        明文
     * @param privateKeyString 私钥字符串
     * @return 签名
     * @throws Exception 异常
     */
    public static String signByPrivateString(String plainText, String privateKeyString) throws Exception {
        PrivateKey privateKey = getPrivateKeyByString(privateKeyString);
        return sign(plainText, privateKey);
    }

    /**
     * 使用公钥字符串验签
     *
     * @param plainText       明文
     * @param signature       签名
     * @param publicKeyString 公钥字符串
     * @return 是否通过签名验证
     * @throws Exception 异常
     */
    public static boolean verifyByPublicString(String plainText, String signature, String publicKeyString) throws Exception {
        PublicKey publicKey = getPublicKeyByString(publicKeyString);
        return verify(plainText, signature, publicKey);
    }

    /**
     * 生成2048位的RSA密钥对
     *
     * @return 密钥对
     * @throws Exception 异常
     */
    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048, new SecureRandom());
        return generator.generateKeyPair();
    }

    /**
     * 从RSA密钥对中获取私钥字符串
     *
     * @param keyPair RSA密钥对
     * @return 私钥字符串
     */
    public static String getPrivateKeyString(KeyPair keyPair) {
        return getPrivateKeyString(keyPair.getPrivate());
    }

    public static String getPrivateKeyString(PrivateKey privateKey) {
        return Base64.encodeBase64String(privateKey.getEncoded());
    }

    /**
     * 从RSA密钥对中获取公钥字符串
     *
     * @param keyPair RSA密钥对
     * @return 公钥字符串
     */
    public static String getPublicKeyString(KeyPair keyPair) {
        return Base64.encodeBase64String(keyPair.getPublic().getEncoded());
    }

    /**
     * 将私钥字符串还原为私钥
     *
     * @param privateKeyString 私钥字符串
     * @return 私钥
     * @throws Exception 异常
     */
    public static PrivateKey getPrivateKeyByString(String privateKeyString) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(privateKeyString);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 将公钥字符串还原为公钥
     *
     * @param publicKeyString 公钥字符串
     * @return 公钥
     * @throws Exception 异常
     */
    public static PublicKey getPublicKeyByString(String publicKeyString) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKeyString);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * RSA加密
     *
     * @param plainText 明文
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 异常
     */
    public static String encrypt(String plainText, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        return Base64.encodeBase64String(cipherText);
    }

    /**
     * RSA解密
     *
     * @param cipherText 密文
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception 异常
     */
    public static String decrypt(String cipherText, PrivateKey privateKey) throws Exception {
        byte[] bytes = Base64.decodeBase64(cipherText);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(bytes), StandardCharsets.UTF_8);
    }

    /**
     * RSA签名
     *
     * @param plainText  明文
     * @param privateKey 私钥
     * @return 签名
     * @throws Exception 异常
     */
    public static String sign(String plainText, PrivateKey privateKey) throws Exception {
        Signature privateSignature = Signature.getInstance("SHA256withRSA");
        privateSignature.initSign(privateKey);
        privateSignature.update(plainText.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = privateSignature.sign();
        return Base64.encodeBase64String(signedBytes);
    }

    /**
     * RSA验签
     *
     * @param plainText 明文
     * @param signature 签名
     * @param publicKey 公钥
     * @return 是否通过验证
     * @throws Exception 异常
     */
    public static boolean verify(String plainText, String signature, PublicKey publicKey) throws Exception {
        Signature publicSignature = Signature.getInstance("SHA256withRSA");
        publicSignature.initVerify(publicKey);
        publicSignature.update(plainText.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = Base64.decodeBase64(signature);
        return publicSignature.verify(signedBytes);
    }

    public static void main(String[] args) throws Exception {

        // 生成2048位密钥对
        KeyPair keyPair = generateKeyPair();
        System.out.println("=====公钥=====");
        System.out.println(keyPair.getPublic());
        System.out.println("=====私钥=====");
        System.out.println(keyPair.getPrivate());

        // RSA公钥加密
        System.out.println("=====测试RSA公钥加密=====");
        String testText = "测试文本";
        String encryptText = encrypt(testText, keyPair.getPublic());
        System.out.println(encryptText);

        // RSA私钥解密
        System.out.println("=====测试RSA私钥解密=====");
        String decryptText = decrypt(encryptText, keyPair.getPrivate());
        System.out.println(decryptText);

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

# 4.1.16 身份证号校验工具类

import java.util.HashMap;

/**
 * 身份证号校验工具类
 * 第一代身份证15位,第二代身份证18位
 */
public class IdCardVerifyUtils {

    private static String _codeError;

    // wi=2(n-1)(mod 11)
    final static int[] wi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
    // verify digit
    final static int[] vi = {1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2};
    private static int[] ai = new int[18];
    private static String[] _areaCode = {"11", "12", "13", "14", "15", "21", "22"
            , "23", "31", "32", "33", "34", "35", "36", "37", "41", "42", "43", "44"
            , "45", "46", "50", "51", "52", "53", "54", "61", "62", "63", "64", "65", "71", "81", "82", "91"};
    private static HashMap<String, Integer> dateMap;
    private static HashMap<String, String> areaCodeMap;

    static {
        dateMap = new HashMap<String, Integer>();
        dateMap.put("01", 31);
        dateMap.put("02", null);
        dateMap.put("03", 31);
        dateMap.put("04", 30);
        dateMap.put("05", 31);
        dateMap.put("06", 30);
        dateMap.put("07", 31);
        dateMap.put("08", 31);
        dateMap.put("09", 30);
        dateMap.put("10", 31);
        dateMap.put("11", 30);
        dateMap.put("12", 31);
        areaCodeMap = new HashMap<String, String>();
        for (String code : _areaCode) {
            areaCodeMap.put(code, null);
        }
    }

    // 验证身份证位数,15位和18位身份证
    public static boolean verifyLength(String code) {
        int length = code.length();
        if (length == 15 || length == 18) {
            return true;
        } else {
            _codeError = "错误:输入的身份证号不是15位和18位的";
            return false;
        }
    }

    // 判断地区码
    public static boolean verifyAreaCode(String code) {
        String areaCode = code.substring(0, 2);
        if (areaCodeMap.containsKey(areaCode)) {
            return true;
        } else {
            _codeError = "错误:输入的身份证号的地区码(1-2位)[" + areaCode + "]不符合中国行政区划分代码规定(GB/T2260-1999)";
            return false;
        }
    }

    // 判断月份和日期
    public static boolean verifyBirthdayCode(String code) {
        // 验证月份
        String month = code.substring(10, 12);
        boolean isEighteenCode = (18 == code.length());
        if (!dateMap.containsKey(month)) {
            _codeError = "错误:输入的身份证号" + (isEighteenCode ? "(11-12位)" : "(9-10位)") + "不存在[" + month + "]月份,不符合要求(GB/T7408)";
            return false;
        }
        // 验证日期
        String dayCode = code.substring(12, 14);
        Integer day = dateMap.get(month);
        String yearCode = code.substring(6, 10);
        Integer year = Integer.valueOf(yearCode);

        // 非2月的情况
        if (day != null) {
            if (Integer.valueOf(dayCode) > day || Integer.valueOf(dayCode) < 1) {
                _codeError = "错误:输入的身份证号" + (isEighteenCode ? "(13-14位)" : "(11-13位)") + "[" + dayCode + "]号不符合小月1-30天大月1-31天的规定(GB/T7408)";
                return false;
            }
        }
        // 2月的情况
        else {
            // 闰月的情况
            if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
                if (Integer.valueOf(dayCode) > 29 || Integer.valueOf(dayCode) < 1) {
                    _codeError = "错误:输入的身份证号" + (isEighteenCode ? "(13-14位)" : "(11-13位)") + "[" + dayCode + "]号在" + year + "闰年的情况下未符合1-29号的规定(GB/T7408)";
                    return false;
                }
            }
            // 非闰月的情况
            else {
                if (Integer.valueOf(dayCode) > 28 || Integer.valueOf(dayCode) < 1) {
                    _codeError = "错误:输入的身份证号" + (isEighteenCode ? "(13-14位)" : "(11-13位)") + "[" + dayCode + "]号在" + year + "平年的情况下未符合1-28号的规定(GB/T7408)";
                    return false;
                }
            }
        }
        return true;
    }

    // 验证身份除了最后位其他的是否包含字母
    public static boolean containsAllNumber(String code) {
        String str = "";
        if (code.length() == 15) {
            str = code.substring(0, 15);
        } else if (code.length() == 18) {
            str = code.substring(0, 17);
        }
        char[] ch = str.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            if (!(ch[i] >= '0' && ch[i] <= '9')) {
                _codeError = "错误:输入的身份证号第" + (i + 1) + "位包含字母";
                return false;
            }
        }
        return true;
    }

    public static String getCodeError() {
        return _codeError;
    }

    // 验证18位校验码,校验码采用ISO 7064:1983,MOD 11-2 校验码系统
    public static boolean verifyMOD(String code) {
        String verify = code.substring(17, 18);
        if ("x".equals(verify)) {
            code = code.replaceAll("x", "X");
            verify = "X";
        }
        String verifyIndex = getVerify(code);
        if (verify.equals(verifyIndex)) {
            return true;
        }
        _codeError = "错误:输入的身份证号最末尾的数字验证码错误";
        return false;
    }

    // 获得校验位
    public static String getVerify(String eightcardid) {
        int remaining = 0;

        if (eightcardid.length() == 18) {
            eightcardid = eightcardid.substring(0, 17);
        }
        if (eightcardid.length() == 17) {
            int sum = 0;
            for (int i = 0; i < 17; i++) {
                String k = eightcardid.substring(i, i + 1);
                ai[i] = Integer.parseInt(k);
            }
            for (int i = 0; i < 17; i++) {
                sum = sum + wi[i] * ai[i];
            }
            remaining = sum % 11;
        }
        return remaining == 2 ? "X" : String.valueOf(vi[remaining]);
    }

    // 15位转18位身份证
    public static String uptoeighteen(String fifteencardid) {
        String eightcardid = fifteencardid.substring(0, 6);
        eightcardid = eightcardid + "19";
        eightcardid = eightcardid + fifteencardid.substring(6, 15);
        eightcardid = eightcardid + getVerify(eightcardid);
        return eightcardid;
    }

    // 验证身份证
    public static boolean verify(String idcard) {
        _codeError = "";
        // 验证身份证位数,15位和18位身份证
        if (!verifyLength(idcard)) {
            return false;
        }
        // 验证身份除了最后位其他的是否包含字母
        if (!containsAllNumber(idcard)) {
            return false;
        }
        // 如果是15位的就转成18位的身份证
        String eifhteencard = "";
        if (idcard.length() == 15) {
            eifhteencard = uptoeighteen(idcard);
        } else {
            eifhteencard = idcard;
        }
        // 验证身份证的地区码
        if (!verifyAreaCode(eifhteencard)) {
            return false;
        }
        // 判断月份和日期
        if (!verifyBirthdayCode(eifhteencard)) {
            return false;
        }
        // 验证18位校验码,校验码采用ISO 7064:1983,MOD 11-2 校验码系统
        if (!verifyMOD(eifhteencard)) {
            return false;
        }
        return true;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

# 4.1.17 敏感信息脱敏工具类

import org.apache.commons.lang3.StringUtils;

/**
 * 字段脱敏工具类
 */
public class SensitiveFieldUtils {

    /**
     * 用户名脱敏
     * @param userName 名字
     * @return 脱敏结果
     */
    public static String chineseName(String userName) {
        if (StringUtils.isEmpty(userName)) {
            return "";
        }
        String name = StringUtils.left(userName, 1);
        return StringUtils.rightPad(name, StringUtils.length(userName), "*");
    }

    /**
     * 身份证号脱敏
     * @param idCard 身份证号
     * @return 脱敏结果
     */
    public static String idCard(String idCard) {
        if (StringUtils.isEmpty(idCard)) {
            return "";
        }
        String id = StringUtils.right(idCard, 4);
        return StringUtils.leftPad(id, StringUtils.length(idCard), "*");
    }

    /**
     * 手机号脱敏
     * @param phone 手机号
     * @return 脱敏结果
     */
    public static String telephone(String phone) {
        if (StringUtils.isEmpty(phone)) {
            return "";
        }
        return StringUtils.left(phone, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(phone, 4), StringUtils.length(phone), "*"), "***"));
    }

    /**
     * 地址信息脱敏
     * @param address 地址信息
     * @param sensitiveSize 敏感信息长度
     * @return 脱敏结果
     */
    public static String address(String address, int sensitiveSize) {
        if (StringUtils.isBlank(address)) {
            return "";
        }
        int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }

    /**
     * 邮箱信息脱敏
     * @param email 邮箱
     * @return 脱敏结果
     */
    public static String email(String email) {
        if (StringUtils.isBlank(email)) {
            return "";
        }
        int index = StringUtils.indexOf(email, "@");
        if (index <= 1) {
            return email;
        } else {
            return StringUtils.rightPad(StringUtils.left(email, 1), index, "*").concat(StringUtils.mid(email, index, StringUtils.length(email)));
        }
    }
    
    /**
     * 银行卡号脱敏
     * @param cardNum 银行卡号
     * @return 脱敏结果
     */
    public static String bankCard(String cardNum) {
        if (StringUtils.isBlank(cardNum)) {
            return "";
        }
        return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"), "******"));
    }

    public static void main(String[] args) {

        // 测试姓名脱敏(张**)
        String name = chineseName("张三丰");
        System.out.println("name = " + name);

        // 测试身份证号脱敏(**************7812)
        String idCard = idCard("123456781234567812");
        System.out.println("idCard = " + idCard);

        // 测试手机号脱敏(186****0000)
        String telephone = telephone("18600000000");
        System.out.println("telephone = " + telephone);

        // 测试地址脱敏(天津市滨海新区*********)
        String address = address("天津市滨海新区经济开发区第三大街", 9);
        System.out.println("address = " + address);

        // 测试邮箱脱敏(t***@163.com)
        String email = email("[email protected]");
        System.out.println("email = " + email);

        // 测试银行卡号脱敏(622316******6887)
        String bankCard = bankCard("6223165905596887");
        System.out.println("bankCard = " + bankCard);

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

# 4.1.18 下划线与驼峰转换工具类

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 驼峰字符串工具类
 */
public class CamelTool {

    /**
     * 下划线转驼峰
     * @param underscore
     * @return
     */
    public static String underscoreToCamel(String underscore){
        String[] ss = underscore.split("_");
        if(ss.length ==1){
            return underscore;
        }
        StringBuffer sb = new StringBuffer();
        sb.append(ss[0]);
        for (int i = 1; i < ss.length; i++) {
            sb.append(upperFirstCase(ss[i]));
        }
        return sb.toString();
    }

    /**
     * 驼峰转下划线
     * @param camelCase
     * @return
     */
    public static String camelToUnderscore(String camelCase){
        Pattern humpPattern = Pattern.compile("[A-Z]");
        Matcher matcher = humpPattern.matcher(camelCase);
        StringBuffer sb = new StringBuffer();
        while(matcher.find()){
            matcher.appendReplacement(sb, "_"+matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 首字母 转小写
     * @param str
     * @return
     */
    private static String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 首字母 转大写
     * @param str
     * @return
     */
    private static String upperFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }

    public static void main(String[] args) {
        // 下划线转驼峰
        String camelStr = underscoreToCamel("cteate_time");
        System.out.println(camelStr);
        // 驼峰转下划线
        String underscoreStr = camelToUnderscore("cteateTime");
        System.out.println(underscoreStr);        
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 4.1.19 中文乱码恢复

import java.io.UnsupportedEncodingException;

public class EncodeConvert {

    private static String[] charsetArr = {"UTF-8","GB18030","GB2312","GBK","Windows-1252","ISO8859-1"};

    public static void testAllCharset(String text) throws UnsupportedEncodingException {
        if (text == null || text.length() == 0) {
            System.out.println("文本不能为空");
            return;
        }

        System.out.println("假设当前编码       假设原始编码          编码后的内容");
        printSeparator();

        for (String curCharset : charsetArr) {
            byte[] btArr = text.getBytes(curCharset);
            for (String originCharset : charsetArr) {
                if (originCharset.equals(curCharset)) {
                    continue;
                }
                String encodeText = new String(btArr,originCharset);
                printTable(curCharset, originCharset, encodeText);
            }
            printSeparator();
        }
    }

    private static void printSeparator() {
        System.out.println("--------------------------------------------------------");
    }

    private static void printTable(String curCharset, String originCharset, String encodeText) {
        System.out.print(curCharset);
        for (int i = 0; i < 15 - curCharset.length(); i++) {
            System.out.print(" ");
        }
        System.out.print("|   " + originCharset);
        for (int i = 0; i < 16 - originCharset.length(); i++) {
            System.out.print(" ");
        }
        System.out.println("|     " + encodeText);
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        //测试乱码
        testAllCharset("浣犲ソ");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 4.1.20 使用jfreechart绘制图表

引入jfreechart依赖

<dependency>
    <groupId>org.jfree</groupId>
    <artifactId>jfreechart</artifactId>
    <version>1.5.0</version>
</dependency>
1
2
3
4
5

绘制代码:

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.chart.ui.TextAnchor;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;

import java.awt.*;
import java.io.File;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 使用jfreechart绘制图表(折线图/饼图)
 */
@SuppressWarnings("rawtypes")
public class ChartUtils {

    private static String NO_DATA_MSG = "暂无数据";
    private static Font FONT = new Font("宋体", Font.PLAIN, 20);
    public static Color[] CHART_COLORS = {
            new Color(31,129,188), new Color(92,92,97), new Color(144,237,125), new Color(255,188,117),
            new Color(153,158,255), new Color(255,117,153), new Color(253,236,109), new Color(128,133,232),
            new Color(158,90,102),new Color(255, 204, 102) };// 颜色

    static{
        setChartTheme();
    }
    /**
     * 中文主题样式 解决乱码
     */
    public static void setChartTheme() {
        // 设置中文主题样式 解决乱码
        StandardChartTheme chartTheme = new StandardChartTheme("CN");
        // 设置标题字体
        chartTheme.setExtraLargeFont(FONT);
        // 设置图例的字体
        chartTheme.setRegularFont(FONT);
        // 设置轴向的字体
        chartTheme.setLargeFont(FONT);
        chartTheme.setSmallFont(FONT);
        chartTheme.setTitlePaint(new Color(51, 51, 51));
        chartTheme.setSubtitlePaint(new Color(85, 85, 85));

        chartTheme.setLegendBackgroundPaint(Color.WHITE);// 设置标注
        chartTheme.setLegendItemPaint(Color.BLACK);//
        chartTheme.setChartBackgroundPaint(Color.WHITE);

        Paint[] OUTLINE_PAINT_SEQUENCE = new Paint[] { Color.WHITE };
        // 绘制器颜色源
        DefaultDrawingSupplier drawingSupplier = new DefaultDrawingSupplier(CHART_COLORS, CHART_COLORS, OUTLINE_PAINT_SEQUENCE,
                DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE, DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
                DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE);
        chartTheme.setDrawingSupplier(drawingSupplier);

        chartTheme.setPlotBackgroundPaint(Color.WHITE);// 绘制区域
        chartTheme.setPlotOutlinePaint(Color.WHITE);// 绘制区域外边框
        chartTheme.setLabelLinkPaint(new Color(8, 55, 114));// 链接标签颜色
        chartTheme.setLabelLinkStyle(PieLabelLinkStyle.CUBIC_CURVE);

        chartTheme.setAxisOffset(new RectangleInsets(5, 12, 5, 12));
        chartTheme.setDomainGridlinePaint(new Color(192, 208, 224));// X坐标轴垂直网格颜色
        chartTheme.setRangeGridlinePaint(new Color(192, 192, 192));// Y坐标轴水平网格颜色

        chartTheme.setBaselinePaint(Color.WHITE);
        chartTheme.setCrosshairPaint(Color.BLUE);// 不确定含义
        chartTheme.setAxisLabelPaint(new Color(51, 51, 51));// 坐标轴标题文字颜色
        chartTheme.setTickLabelPaint(new Color(67, 67, 72));// 刻度数字
        chartTheme.setBarPainter(new StandardBarPainter());// 设置柱状图渲染
        chartTheme.setXYBarPainter(new StandardXYBarPainter());// XYBar 渲染

        chartTheme.setItemLabelPaint(Color.black);
        chartTheme.setThermometerPaint(Color.white);// 温度计

        ChartFactory.setChartTheme(chartTheme);
    }

    /**
     * 必须设置文本抗锯齿
     */
    public static void setAntiAlias(JFreeChart chart) {
        chart.setTextAntiAlias(false);

    }

    /**
     * 设置图例无边框,默认黑色边框
     */
    public static void setLegendEmptyBorder(JFreeChart chart) {
        chart.getLegend().setFrame(new BlockBorder(Color.WHITE));
    }

    /**
     * 提供静态方法:获取报表图形1:饼状图
     * @param title    标题
     * @param datas    数据
     * @param
     */
    public static void createPiePort(String title,Map<String,Double> datas,String url){
        try {
            // 如果不使用Font,中文将显示不出来
            DefaultPieDataset pds = new DefaultPieDataset();

            // 获取迭代器:
            Set<Entry<String, Double>> set =  datas.entrySet();
            Iterator iterator=(Iterator) set.iterator();
            Entry entry=null;
            while(iterator.hasNext()){
                entry=(Entry) iterator.next();
                pds.setValue(entry.getKey().toString(),Double.parseDouble(entry.getValue().toString()));
            }
            /**
             * 生成一个饼图的图表
             * 分别是:显示图表的标题、需要提供对应图表的DateSet对象、是否显示图例、是否生成贴士以及是否生成URL链接
             */
            JFreeChart chart = ChartFactory.createPieChart(title, pds, true, false, true);
            setPieRender((PiePlot) chart.getPlot());

            //将内存中的图片写到本地硬盘
            org.jfree.chart.ChartUtils.saveChartAsPNG(new File(url), chart,800,500);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 提供静态方法:获取报表图形3:折线图
     * @param title        标题
     * @param datas        数据
     */
    public static void createLinePort(String title,Map<String,Double> datas,String xName,String yName,String url){
        try {
            //种类数据集
            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
            //获取迭代器:
            Set<Entry<String, Double>> set =  datas.entrySet();
            Iterator iterator=(Iterator) set.iterator();
            Entry entry=null;
            while(iterator.hasNext()){
                entry=(Entry) iterator.next();
                dataset.setValue(Double.parseDouble(entry.getValue().toString()),//y
                        title,                         //名称
                        entry.getKey().toString());      //x
            }
            //创建折线图,折线图分水平显示和垂直显示两种
            JFreeChart chart = ChartFactory.createLineChart(title, xName, yName, dataset,//2D折线图
                    PlotOrientation.VERTICAL,
                    false, // 是否显示图例(对于简单的柱状图必须是false)
                    true, // 是否生成工具
                    true);// 是否生成URL链接
            //得到绘图区
            setLineRender((CategoryPlot)chart.getPlot(),true,true);
            org.jfree.chart.ChartUtils.saveChartAsPNG(new File(url), chart, 1000,600);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置折线图样式
     *
     * @param plot
     * @param isShowDataLabels  是否显示数据标签
     */
    public static void setLineRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
        plot.setNoDataMessage(NO_DATA_MSG);
        plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
        LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
        renderer.setDefaultStroke(new BasicStroke(1.5F));
        if (isShowDataLabels) {
            renderer.setDefaultItemLabelsVisible(true);
            renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
                    NumberFormat.getInstance()));
            renderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));// weizhi
        }
        renderer.setDefaultShapesVisible(isShapesVisible);// 数据点绘制形状
        setXAixs(plot);
        setYAixs(plot);
    }

    /**
     * 设置饼状图渲染
     */
    public static void setPieRender(Plot plot) {
        plot.setNoDataMessage(NO_DATA_MSG);
        plot.setInsets(new RectangleInsets(10, 10, 5, 10));
        PiePlot piePlot = (PiePlot) plot;
        piePlot.setInsets(new RectangleInsets(0, 0, 0, 0));
        piePlot.setCircular(true);// 圆形
        // piePlot.setSimpleLabels(true);// 简单标签
        piePlot.setLabelGap(0.01);
        piePlot.setInteriorGap(0.05D);
        piePlot.setLegendItemShape(new Rectangle(10, 10));// 图例形状
        piePlot.setIgnoreNullValues(true);
        piePlot.setLabelBackgroundPaint(null);// 去掉背景色
        piePlot.setLabelShadowPaint(null);// 去掉阴影
        piePlot.setLabelOutlinePaint(null);// 去掉边框
        piePlot.setShadowPaint(null);
        // 0:category 1:value:2 :percentage
        piePlot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{2}"));// 显示标签数据
    }

    /**
     * 设置类别图表(CategoryPlot) X坐标轴线条颜色和样式
     *
     * @param
     */
    public static void setXAixs(CategoryPlot plot) {
        Color lineColor = new Color(31, 121, 170);
        plot.getDomainAxis().setCategoryLabelPositions(CategoryLabelPositions.UP_45);
        plot.getDomainAxis().setAxisLinePaint(lineColor); // X坐标轴颜色
        plot.getDomainAxis().setTickMarkPaint(lineColor); // X坐标轴标记|竖线颜色

    }

    /**
     * 设置类别图表(CategoryPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
     *
     * @param
     */
    public static void setYAixs(CategoryPlot plot) {
        Color lineColor = new Color(31, 121, 170);
        ValueAxis axis = plot.getRangeAxis();
        axis.setAxisLinePaint(lineColor);// Y坐标轴颜色
        axis.setTickMarkPaint(lineColor);// Y坐标轴标记|竖线颜色
        // false隐藏Y刻度
        axis.setAxisLineVisible(true);
        axis.setTickMarksVisible(true);
        // Y轴网格线条
        plot.setRangeGridlinePaint(new Color(192, 192, 192));
        plot.setRangeGridlineStroke(new BasicStroke(1));

        plot.getRangeAxis().setUpperMargin(0.1);// 设置顶部Y坐标轴间距,防止数据无法显示
        plot.getRangeAxis().setLowerMargin(0.1);// 设置底部Y坐标轴间距

    }

    /**
     * 设置XY图表(XYPlot) X坐标轴线条颜色和样式
     *
     * @param
     */
    public static void setXY_XAixs(XYPlot plot) {
        Color lineColor = new Color(31, 121, 170);
        plot.getDomainAxis().setAxisLinePaint(lineColor);// X坐标轴颜色
        plot.getDomainAxis().setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
    }

    /**
     * 设置XY图表(XYPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
     *
     * @param
     */
    public static void setXY_YAixs(XYPlot plot) {
        Color lineColor = new Color(192, 208, 224);
        ValueAxis axis = plot.getRangeAxis();
        axis.setAxisLinePaint(lineColor);// X坐标轴颜色
        axis.setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
        // 隐藏Y刻度
        axis.setAxisLineVisible(false);
        axis.setTickMarksVisible(false);
        // Y轴网格线条
        plot.setRangeGridlinePaint(new Color(192, 192, 192));
        plot.setRangeGridlineStroke(new BasicStroke(1));
        plot.setDomainGridlinesVisible(false);

        plot.getRangeAxis().setUpperMargin(0.12);// 设置顶部Y坐标轴间距,防止数据无法显示
        plot.getRangeAxis().setLowerMargin(0.12);// 设置底部Y坐标轴间距

    }

    public static void main(String[] args) {

        // 绘制饼图
        Map<String, Double> map = new HashMap<String, Double>();
        map.put("天使", (double) 1000);
        map.put("雄兵连", (double) 700);
        map.put("太阳之光", (double) 600);
        map.put("辅助", (double) 400);
        createPiePort("投票结果", map,"/Users/yoyo/Temp/pieChart.jpg");

        // 绘制折线图
        Map<String, Double> map1 = new HashMap<String, Double>();
        map1.put("2020-02-03", (double) 700);
        map1.put("2020-02-04", (double) 1000);
        map1.put("2020-02-05", (double) 600);
        map1.put("2020-02-06", (double) 400);
        map1.put("2020-02-07", (double) 4000);
        map1.put("2020-02-08", (double) 1200);
        map1.put("2020-02-09", (double) 800);
        createLinePort("近7日金额",map1,"日期","金额(元)","/Users/yoyo/Temp/lineChart.jpg");

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311

# 4.1.21 基于zxing库的二维码生成及解析工具类

引入依赖:

        <!-- Google ZXing 操作二维码,生成二维码必备 https://mvnrepository.com/artifact/com.google.zxing/core -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--Google ZXing 操作二维码,解析二维码必备,如果不解析二维码可以不导该包-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12

生成二维码:

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.EnumMap;

/**
 *  在指定路径生成一张二维码,内容类型为字符串,可以是普通文本或者是超链接
 *
 *  生成二维码的步骤
 *      1.根据Zxingjar包生成二维码,这只是生成二维码,并不是生成二维码图片
 *      2.创建一张图片,大小和二维码的大小一致。
 *      3.二维码可以看成是二维数组,所以遍历二维码,然后把二维码的每一个点添加到图片上,添加的时候,判断是黑色点,还是白色点。
 *          encode.get(i,j)?BLACK:WHITE
 *      4.最后把真正的二维码图片生成到指定路径即可。
 */
public class EncodeQRCodeUtils {

    //0x:代表是16进制,ff:代表完全不透明度,其余6位代表颜色值(000000:代表黑色)
    //定义二维码的颜色
    private static final int BLACK = 0xff000000;
    //定义二维码的背景颜色
    private static final int WHITE = 0xffffffff;

    //二维码格式参数集合
    private static final EnumMap<EncodeHintType,Object> hints =
            new EnumMap<EncodeHintType, Object>(EncodeHintType.class);

    //静态代码块初始化参数,在类加载的时候就初始化,而且在类的生命周期内只初始化一次
    static {
        /*
         *  二维码的纠错级别(排错率),4个级别: L(7%)、M(15%)、Q (25%)、H(30%)
         *  最高级别为:H
         *  纠错信息同样存储在二维码中,纠错级别越高,纠错信息占用的空间越多,那么能存储的有用信息就越少;
         */
        hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.L);
        // 二维码边界空白大小:1,2,3,4(4为默认,最大)
        hints.put(EncodeHintType.MARGIN,1);
        //设置二维码存储内容的字符集
        hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
    }

    public static void createZXingCodeSaveToDisk(String content,int width,int height,String savePath,String imageType){
        try {
            BitMatrix encode = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
            //获得二维码的宽高
            int codeWidth = encode.getWidth();
            int codeHeight = encode.getHeight();
            //创建图片
            BufferedImage image = new BufferedImage(codeWidth, codeHeight, BufferedImage.TYPE_INT_RGB);
            //把二维码里面的信息写到图片里面
            for (int i = 0; i < codeWidth; i++) {
                for (int j = 0; j < codeHeight; j++) {
                    image.setRGB(i,j,encode.get(i,j)?BLACK:WHITE);
                }
            }
            //保存图片到指定位置
            File file = new File(savePath);
            if(!file.exists()){
                //文件不存在则创建
                file.createNewFile();
            }
            ImageIO.write(image,imageType,file);
            //打印一句话,给个提示是否成功
            System.out.println("二维码生成成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // 将字符串"hello world"存储在二维码中
        createZXingCodeSaveToDisk("hello world",300,300,"./text.jpeg","JPEG");
        // 将我的博客地址存储在二维码中
        createZXingCodeSaveToDisk("https://www.eula.club/",300,300,"./blog.jpeg","JPEG");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

解析二维码:

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.EnumMap;

/**
 *  该类主要解析二维码
 *      步骤:
 *          1.读取二维码图片
 *          2.读取二维码图片中的二维码
 *          3.读取二维码中的信息
 */
public class DecodeQRCodeUtils {

    //二维码格式参数集合
    private static final EnumMap<DecodeHintType,Object> HINTS = new EnumMap<DecodeHintType, Object>(DecodeHintType.class);

    static {
        //设置解析二维码后信息的字符集
        HINTS.put(DecodeHintType.CHARACTER_SET,"UTF-8");
    }

    /**
     *  解析二维码
     * @param path 二维码图片路径
     * @return 二维码中的文本内容
     */
    public static String decodeQRCodeForPath(String path){
        //该文件对象映射二维码图片
        File file = new File(path);
        if(file.exists()){
            try {
                return decodeQRCodeStreamForStream(new FileInputStream(file));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }

    /**
     *  解析二维码
     * @param ins 读取二维码图片的流
     * @return 二维码中的文本内容
     */
    public static String decodeQRCodeStreamForStream(InputStream ins) {
        if(ins != null){
            try {
                //将读取二维码图片的流转为图片对象
                BufferedImage image = ImageIO.read(ins);
                BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
                HybridBinarizer binarizer = new HybridBinarizer(source);
                BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
                MultiFormatReader reader = new MultiFormatReader();
                Result result = reader.decode(binaryBitmap, HINTS);
                //返回二维码中的文本内容
                String content = result.getText();
                System.out.println("二维码解析成功");
                return content;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        String myName = decodeQRCodeForPath("./text.jpeg");
        System.out.println(myName);
        String myBlog = decodeQRCodeForPath("./blog.jpeg");
        System.out.println(myBlog);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# 4.1.22 邮件发送工具类

引入依赖:

        <!-- 邮箱通知 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
1
2
3
4
5

配置文件:

## JavaMailSender config
# smtp.163.com,smtp.qq.com,(smtp.gmail.com is unavailable)
spring.mail.host=smtp.163.com
spring.mail.username=[email protected]
# use authorization code
spring.mail.password=your_authorization_code
spring.mail.port=465
spring.mail.default-encoding=utf-8
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.protocol=smtp
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.socketFactory.port=465
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
# mail debug
spring.mail.properties.mail.debug=false
# check connectivity at startup
spring.mail.test-connection=false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

注:这里的密码要去申请单独的授权码,而不是你的邮箱普通登录密码,另外gmail邮箱我没有测试通过,总是连不上服务器。

邮件发送封装:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

@Service
public class MailService {

    private static final Logger logger = LoggerFactory.getLogger(MailService.class);

    @Autowired
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String sender;

    /**
     * 发送普通邮件
     *
     * @param to
     * @param subject
     * @param content
     */
    public void sendSimpleMailMessge(String to, String subject, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(sender);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(content);
        try {
            javaMailSender.send(message);
            logger.info("send success.................");
        } catch (Exception e) {
            logger.error("发送简单邮件时发生异常!", e);
        }
    }

    /**
     * 发送 HTML 邮件
     *
     * @param to      收件人
     * @param subject 主题
     * @param content 内容(html)
     */
    public void sendHtmlMessge(String to, String subject, String content) {
        MimeMessage message = javaMailSender.createMimeMessage();
        try {
            //true表示需要创建一个multipart message
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(sender);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            javaMailSender.send(message);
            logger.info("send success.................");
        } catch (MessagingException e) {
            logger.error("发送MimeMessge时发生异常!", e);
        }
    }

    /**
     * 发送带附件的邮件
     *
     * @param to       收件人
     * @param subject  主题
     * @param content  内容
     * @param filePath 附件路径
     */
    public void sendAttachmentMessge(String to, String subject, String content, String filePath) {
        MimeMessage message = javaMailSender.createMimeMessage();
        try {
            // true表示需要创建一个multipart message
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(sender);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            FileSystemResource file = new FileSystemResource(filePath);
            String fileName = file.getFilename();
            helper.addAttachment(fileName, file);
            javaMailSender.send(message);
            logger.info("send success.................");
        } catch (MessagingException e) {
            logger.error("发送带附件的MimeMessge时发生异常!", e);
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

# 4.1.23 渲染生成PDF报告工具类

iText是是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。

pom.xml依赖引入:

    <!-- 导出PDF itextpdf https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
    <dependency>
      <groupId>com.itextpdf</groupId>
      <artifactId>itextpdf</artifactId>
      <version>5.5.13</version>
    </dependency>
    <!-- 导出PDF itext-asian https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
    <dependency>
      <groupId>com.itextpdf</groupId>
      <artifactId>itext-asian</artifactId>
      <version>5.2.0</version>
    </dependency>
1
2
3
4
5
6
7
8
9
10
11
12

PdfReportTool.java

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.draw.DottedLineSeparator;
import com.itextpdf.text.pdf.draw.LineSeparator;

import java.io.File;
import java.nio.file.Files;

public class PdfReportTool {

    // 定义全局的字体静态变量
    private static Font titlefont;
    private static Font headfont;
    private static Font keyfont;
    private static Font textfont;
    // 最大宽度
    private static int maxWidth = 520;
    // 静态代码块
    static {
        try {
            // 不同字体(这里定义为同一种字体:包含不同字号、不同style)
            BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            titlefont = new Font(bfChinese, 16, Font.BOLD);
            headfont = new Font(bfChinese, 14, Font.BOLD);
            keyfont = new Font(bfChinese, 10, Font.BOLD);
            textfont = new Font(bfChinese, 10, Font.NORMAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 生成PDF文件
    public void generatePDF(Document document) throws Exception {

        // 段落
        Paragraph paragraph = new Paragraph("美好的一天从早起开始", titlefont);
        paragraph.setAlignment(1); //设置文字居中 0靠左 1居中 2靠右
        paragraph.setIndentationLeft(12); //设置左缩进
        paragraph.setIndentationRight(12); //设置右缩进
        paragraph.setFirstLineIndent(24); //设置首行缩进
        paragraph.setLeading(20f); //行间距
        paragraph.setSpacingBefore(5f); //设置段落上空白
        paragraph.setSpacingAfter(10f); //设置段落下空白

        // 点线
        Paragraph p1 = new Paragraph();
        p1.add(new Chunk(new DottedLineSeparator()));

        // 空行
        Chunk newline = Chunk.NEWLINE;

        // 直线
        Paragraph p2 = new Paragraph();
        p2.add(new Chunk(new LineSeparator()));

        // 超链接
        Anchor anchor = new Anchor("baidu");
        anchor.setReference("https://www.baidu.com");

        // 添加图片
        Image image = Image.getInstance("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
        image.setAlignment(Image.ALIGN_CENTER);
        image.scalePercent(40); //依照比例缩放

        // 表格
        PdfPTable table = createTable(new float[] {120, 80, 120, 80, 120, 80, 120});
        table.addCell(createCell("", keyfont));
        table.addCell(createCell("早上9 : 00", keyfont, Element.ALIGN_CENTER));
        table.addCell(createCell("中午11 : 00", keyfont, Element.ALIGN_CENTER));
        table.addCell(createCell("中午13 : 00", keyfont, Element.ALIGN_CENTER));
        table.addCell(createCell("下午15 : 00", keyfont, Element.ALIGN_CENTER));
        table.addCell(createCell("下午17 : 00", keyfont, Element.ALIGN_CENTER));
        table.addCell(createCell("晚上19 : 00", keyfont, Element.ALIGN_CENTER));
        String[] weekDays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
        for (int i = 0; i < 7; i++) {
            table.addCell(createCell(weekDays[i], textfont));
            table.addCell(createCell("起床", textfont));
            table.addCell(createCell("吃午饭", textfont));
            table.addCell(createCell("午休", textfont));
            table.addCell(createCell("下午茶", textfont));
            table.addCell(createCell("回家", textfont));
            table.addCell(createCell("吃晚饭", textfont));
        }
        table.addCell(createCell("总计", keyfont));
        table.addCell(createCell("6 件事", textfont, Element.ALIGN_CENTER, 6));

        document.add(paragraph);
        document.add(anchor);
        document.add(p1);
        document.add(newline);
        document.add(table);
        document.add(image);
        document.add(p2);
    }

    /**------------------------创建表格单元格的方法start----------------------------*/
    /**
     * 创建单元格(指定字体)
     * @param value
     * @param font
     * @return
     */
    public PdfPCell createCell(String value, Font font) {
        PdfPCell cell = new PdfPCell();
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        cell.setPhrase(new Phrase(value, font));
        return cell;
    }
    /**
     * 创建单元格(指定字体、水平对齐)
     * @param value
     * @param font
     * @param align
     * @return
     */
    public PdfPCell createCell(String value, Font font, int align) {
        PdfPCell cell = new PdfPCell();
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        cell.setHorizontalAlignment(align);
        cell.setPhrase(new Phrase(value, font));
        return cell;
    }
    /**
     * 创建单元格(指定字体、水平对齐、单元格跨x列合并)
     * @param value
     * @param font
     * @param align
     * @param colspan
     * @return
     */
    public PdfPCell createCell(String value, Font font, int align, int colspan) {
        PdfPCell cell = new PdfPCell();
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        cell.setHorizontalAlignment(align);
        cell.setColspan(colspan);
        cell.setPhrase(new Phrase(value, font));
        return cell;
    }
    /**
     * 创建单元格(指定字体、水平..、边框宽度:0表示无边框、内边距)
     * @param value
     * @param font
     * @param align
     * @param borderWidth
     * @param paddingSize
     * @param flag
     * @return
     */
    public PdfPCell createCell(String value, Font font, int align, float[] borderWidth, float[] paddingSize, boolean flag) {
        PdfPCell cell = new PdfPCell();
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        cell.setHorizontalAlignment(align);
        cell.setPhrase(new Phrase(value, font));
        cell.setBorderWidthLeft(borderWidth[0]);
        cell.setBorderWidthRight(borderWidth[1]);
        cell.setBorderWidthTop(borderWidth[2]);
        cell.setBorderWidthBottom(borderWidth[3]);
        cell.setPaddingTop(paddingSize[0]);
        cell.setPaddingBottom(paddingSize[1]);
        if (flag) {
            cell.setColspan(2);
        }
        return cell;
    }
    /**------------------------创建表格单元格的方法end----------------------------*/


    /**--------------------------创建表格的方法start------------------- ---------*/
    /**
     * 创建默认列宽,指定列数、水平(居中、右、左)的表格
     * @param colNumber
     * @param align
     * @return
     */
    public PdfPTable createTable(int colNumber, int align) {
        PdfPTable table = new PdfPTable(colNumber);
        try {
            table.setTotalWidth(maxWidth);
            table.setLockedWidth(true);
            table.setHorizontalAlignment(align);
            table.getDefaultCell().setBorder(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return table;
    }
    /**
     * 创建指定列宽、列数的表格
     * @param widths
     * @return
     */
    public PdfPTable createTable(float[] widths) {
        PdfPTable table = new PdfPTable(widths);
        try {
            table.setTotalWidth(maxWidth);
            table.setLockedWidth(true);
            table.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.getDefaultCell().setBorder(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return table;
    }
    /**
     * 创建空白的表格
     * @return
     */
    public PdfPTable createBlankTable() {
        PdfPTable table = new PdfPTable(1);
        table.getDefaultCell().setBorder(0);
        table.addCell(createCell("", keyfont));
        table.setSpacingAfter(20.0f);
        table.setSpacingBefore(20.0f);
        return table;
    }
    /**--------------------------创建表格的方法end------------------- ---------*/


    public static void main(String[] args) throws Exception {
        try {
            // 1.新建document对象
            Document document = new Document(PageSize.A4);// 建立一个Document对象

            // 2.建立一个PdfWriter对象,与document对象关联
            File file = new File("/root/PDFDemo.pdf");
            PdfWriter writer = PdfWriter.getInstance(document, Files.newOutputStream(file.toPath()));
            // writer.setPageEvent(new Watermark("HELLO ITEXTPDF"));// 水印
            // writer.setPageEvent(new HeaderFooter());// 页眉/页脚

            // 3.打开文档
            document.open();
            document.addTitle("Title@PDF-Java");// 标题
            document.addAuthor("Author@yoyo");// 作者
            document.addSubject("Subject@iText-pdf-sample");// 主题
            document.addKeywords("Keywords@iTextpdf");// 关键字
            document.addCreator("Creator@yoyo`s");// 创建者

            // 4.向文档中添加内容
            new PdfReportTool().generatePDF(document);

            // 5.关闭文档
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

如果需要页眉页脚、添加水印,需要调用以下工具类。

HeaderFooter.java

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import java.io.IOException;

public class HeaderFooter extends PdfPageEventHelper {

    PdfTemplate totalPage;
    Font hfFont;
    {
        try {
            hfFont = new Font(BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED), 8, Font.NORMAL);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 打开文档时,创建一个总页数的模版
    public void onOpenDocument(PdfWriter writer, Document document) {
        PdfContentByte cb =writer.getDirectContent();
        totalPage = cb.createTemplate(30, 16);
    }

    // 一页加载完成触发,写入页眉和页脚
    public void onEndPage(PdfWriter writer, Document document) {
        PdfPTable table = new PdfPTable(3);
        try {
            table.setTotalWidth(PageSize.A4.getWidth() - 100);
            table.setWidths(new int[] { 24, 24, 3});
            table.setLockedWidth(true);
            table.getDefaultCell().setFixedHeight(-10);
            table.getDefaultCell().setBorder(Rectangle.BOTTOM);

            table.addCell(new Paragraph("我是页眉/页脚", hfFont));// 可以直接使用addCell(str),不过不能指定字体,中文无法显示
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
            table.addCell(new Paragraph("第" + writer.getPageNumber() + "页/", hfFont));
            // 总页数
            PdfPCell cell = new PdfPCell(Image.getInstance(totalPage));
            cell.setBorder(Rectangle.BOTTOM);
            table.addCell(cell);
            // 将页眉写到document中,位置可以指定,指定到下面就是页脚
            table.writeSelectedRows(0, -1, 50,PageSize.A4.getHeight() - 20, writer.getDirectContent());
        } catch (Exception de) {
            throw new ExceptionConverter(de);
        }
    }

    // 全部完成后,将总页数的pdf模版写到指定位置
    public void onCloseDocument(PdfWriter writer,Document document) {
        String text = "总" + (writer.getPageNumber()) + "页";
        ColumnText.showTextAligned(totalPage, Element.ALIGN_LEFT, new Paragraph(text,hfFont), 2, 2, 0);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

Watermark.java

import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.GrayColor;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;

public class Watermark extends PdfPageEventHelper {

    Font font = new Font(Font.FontFamily.HELVETICA, 30, Font.BOLD, new GrayColor(0.95f));
    // 水印内容
    private String waterCont;
    public Watermark() {

    }
    public Watermark(String waterCont) {
        this.waterCont = waterCont;
    }

    @Override
    public void onEndPage(PdfWriter writer, Document document) {
        for(int i=0 ; i<5; i++) {
            for(int j=0; j<5; j++) {
                ColumnText.showTextAligned(writer.getDirectContentUnder(),
                        Element.ALIGN_CENTER,
                        new Phrase(this.waterCont == null ? "HELLO WORLD" : this.waterCont, font),
                        (50.5f+i*350),
                        (40.0f+j*150),
                        writer.getPageNumber() % 2 == 1 ? 45 : -45);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

最终效果图:

iText渲染生成PDF

# 4.1.24 IP地址工具类

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;
import java.util.concurrent.*;

/**
 * IP地址工具类
 */
public class IpUtils {

    /**
     * IP地址校验的正则表达式
     */
    private static final Pattern IPV4_PATTERN = Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");

    /**
     * 从HttpServletRequest里获取客户端请求IP
     * @param request
     * @return
     */
    public static String getRequestIpAddress(HttpServletRequest request) {
        String ipAddress;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if ("127.0.0.1".equals(ipAddress)) {
                    // 根据网卡取本机配置的IP
                    try {
                        InetAddress inetAddress = InetAddress.getLocalHost();
                        ipAddress = inetAddress.getHostAddress();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            // "***.***.***.***".length()= 15
            if (ipAddress != null && ipAddress.length() > 15) {
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }


    /**
     * 获取本机的内网IP
     * @return
     * @throws UnknownHostException
     */
    public static String getLocalHostIpAddress() throws UnknownHostException {
        try {
            InetAddress candidateAddress = null;
            // 遍历所有的网络接口
            for (Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements();) {
                NetworkInterface iface = ifaces.nextElement();
                // 在所有的接口下再遍历IP
                for (Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) {
                    InetAddress inetAddr = inetAddrs.nextElement();
                    if (!inetAddr.isLoopbackAddress()) { // 排除loopback类型地址
                        if (inetAddr.isSiteLocalAddress()) {
                            // 如果是site-local地址,就是它了
                            return inetAddr.getHostAddress();
                        } else if (candidateAddress == null) {
                            // site-local类型的地址未被发现,先记录候选地址
                            candidateAddress = inetAddr;
                        }
                    }
                }
            }
            if (candidateAddress != null) {
                return candidateAddress.getHostAddress();
            }
            // 如果没有发现 non-loopback地址.只能用最次选的方案
            InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
            if (jdkSuppliedAddress == null) {
                throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null.");
            }
            return jdkSuppliedAddress.getHostAddress();
        } catch (Exception e) {
            UnknownHostException unknownHostException = new UnknownHostException(
                    "Failed to determine LAN address: " + e);
            unknownHostException.initCause(e);
            throw unknownHostException;
        }
    }

    /**
     * 获取本机的公网IP(调用第三方服务,不一定稳定可靠)
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String getExternalIp() throws ExecutionException, InterruptedException {

        // 获取公网IP地址的服务列表(第三方接口服务不一定稳定,并发访问这些服务接口,并返回第一个成功的结果)
        String[] ipServices = {
                "http://checkip.amazonaws.com/",
                "https://ipv4.icanhazip.com/",
                "http://bot.whatismyipaddress.com/"
        };

        List<Callable<String>> callables = new ArrayList<>();
        for (String ipService : ipServices) {
            callables.add(() -> getIpServiceUrl(ipService));
        }

        ExecutorService executorService = Executors.newCachedThreadPool();
        try {
            // 返回第一个成功获取的IP
            return executorService.invokeAny(callables);
        } finally {
            executorService.shutdown();
        }
    }

    private static String getIpServiceUrl(String url) throws IOException {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) {
            String ip = in.readLine();
            if (IPV4_PATTERN.matcher(ip).matches()) {
                return ip;
            } else {
                throw new IOException("invalid IPv4 address: " + ip);
            }
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

另注:也可通过 HttpServletRequest 获取本机IP及端口号,正式环境获取的对不对要检查一下。

System.out.println(request.getLocalAddr());
System.out.println(request.getLocalPort());
1
2

# 4.2 常见基本写法

# 4.2.1 常见基本语法

[1] 使用try-catch捕捉异常

try {
	// 异常模块
} catch (Exception e) {
	e.printStackTrace();
}
1
2
3
4
5

[2] 出现异常时数据库回滚

增删改接口都要加这个异常时数据库回滚,在service的接口处加如下注解即可。

@Transactional(rollbackFor = Exception.class)
1

[3] 使用字符串模板代替字符串拼接

使用了第三方工具类库Hutool里的format方法

String template = "{}爱{},就像老鼠爱大米";
String str = StrUtil.format(template, "我", "你"); //str -> 我爱你,就像老鼠爱大米
1
2

[4] 请求路径的动态获取

在请求里用{}包裹需要动态获取的路径参数,如果下面需要用到它的话,在入参那里用@PathVariable Integer id获取即可。

@PostMapping("/g_config/{id}/request")   
public void getConfig(@RequestBody Map<String, Object> map, @PathVariable Integer id) {...}
1
2

[5] 按行读取txt文件内容

FileInputStream inputStream = new FileInputStream("../data/fileName.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String str = null;
while((str = bufferedReader.readLine()) != null)
{
    System.out.println(str);
}
inputStream.close();
bufferedReader.close();
1
2
3
4
5
6
7
8
9

[6] Google Guava的HashBasedTable类库

Google Guava的HashBasedTable类库可以实现相当于两个key的Map的数据结构,下面为基本使用示例。

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

// 创建
Table<String, String, Float> weightTable = HashBasedTable.create();

// 新增
weightTable.put("aaa", "bbb", 1.0f);
weightTable.put("ccc", "ddd", 2.0f);
weightTable.put("ddd", "eee", 3.0f);

// 获取
float weight = weightTable.get("aaa", "bbb");

// 包含
boolean exist = weightTable.contains("aaa", "bbb")

// 遍历
Set<Table.Cell<String,String,String>> tableSet = weightTable.cellSet();
Iterator<Table.Cell<String,String,String>> it = tableSet.iterator();
while (it.hasNext()){
    Table.Cell<String,String,String> data = it.next();
    String rowCompany = data.getRowKey();
    String columnId = data.getColumnKey();
    float valueName = data.getValue();
    System.out.println("行号:"+rowCompany+",列号:"+columnId+",值:"+valueName);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

[7] 将文件转换成输出流提供给前端下载

将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存,通过响应输出流输出到前端

/**
* @param path     指想要下载的文件的路径
* @param response
* @功能描述 下载文件:将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存
*/
@RequestMapping("/downloadLocal")
public void downloadLocal(String path, HttpServletResponse response) throws IOException {
   // 读到流中
   InputStream inputStream = new FileInputStream(path);// 文件的存放路径
   response.reset();
   response.setContentType("application/octet-stream");
   String filename = new File(path).getName();
   response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
   ServletOutputStream outputStream = response.getOutputStream();
   byte[] b = new byte[1024];
   int len;
   //从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
   while ((len = inputStream.read(b)) > 0) {
       outputStream.write(b, 0, len);
   }
   inputStream.close();
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

[8] 读取JSON文件并转换

需要fastjson依赖

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.47</version>
</dependency>
1
2
3
4
5

读取JSON文件工具类

    /**
     * 读取json文件,返回json串
     * @param fileName
     * @return
     */
    public static String readJsonFile(String fileName) {
        String jsonStr = "";
        try {
            File jsonFile = new File(fileName);
            FileReader fileReader = new FileReader(jsonFile);

            Reader reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }

            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
            return jsonStr;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

调用工具类读取并转换成List<Map<String, Object>>

String path = "./test.json";
String s = CommonUtils.readJsonFile(path);
List<Map<String, Object>> result = (List<Map<String, Object>>) JSONArray.parse(s);
1
2
3

[9] 价格折扣计算

    /**
     * 根据总价与活动价计算折扣
     * @param totalPrice
     * @param activityPrice
     * @return
     */
    public static String calPriceDiscount(int totalPrice, int activityPrice) {
        BigDecimal totalPriceBigDecimal = new BigDecimal(totalPrice);
        BigDecimal activityPriceBigDecimal = new BigDecimal(activityPrice);
        double num = activityPriceBigDecimal.divide(totalPriceBigDecimal, 10, BigDecimal.ROUND_HALF_UP).doubleValue();
        DecimalFormat df=new DecimalFormat("0.0");
        return df.format(num*10);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

[10] 生成数值列表

    /**
     * 生成数值列表
     * @param start
     * @param end
     * @return
     */
    public static List<Integer> generateOrdinalList(int start, int end) {
        List<Integer> range = IntStream.rangeClosed(start, end)
                .boxed().collect(Collectors.toList());
        return range;
    }
1
2
3
4
5
6
7
8
9
10
11

[11] 计算百分比 并保留两位小数

// 计算百分比 并保留两位小数
int num = 3, val = 5;
System.out.println(String.format("%.2f", (float) num / (float) val * 100) + "%");

// 直接对double类型四舍五入保留两位小数
double changeRate = 0.3456789
System.out.println(Math.round(changeRate * 100) / 100.0);
1
2
3
4
5
6
7

[12] 转换成中文大写数字

// 大小写转换,转换为大写只能精确到分(小数点儿后两位),之后的数字会被忽略。
double a = 67556.32;
String digitUppercase = Convert.digitToChinese(a);
System.out.println(digitUppercase);   // 结果为:"陆万柒仟伍佰伍拾陆元叁角贰分"
1
2
3
4

[13] 计时器的实现

// 计时器
TimeInterval timer = DateUtil.timer();
//---------------------------------
//-------这是执行过程
//---------------------------------
System.out.println(timer.interval());  //花费毫秒数
System.out.println(timer.intervalRestart());  //返回花费的毫秒数,并重置开始时间
System.out.println(timer.intervalMinute());  //花费分钟数
1
2
3
4
5
6
7
8

# 4.2.2 类型转换遍历

[1] 后端将Map转换成JSON字符串 及 前端将JSON字符串转换成JSON对象

后端将Map转换成JSON字符串:String strData = JSON.toJSONString(mapData); 
前端将JSON字符串转换成JSON对象:this.count = JSON.parse(strData).count; 
1
2

[2] 将字符串转换成Map

方式一:泛型的反序列化,使用 TypeReference 传入类型信息

Map<String, Object> map = JSON.parseObject(resultStr, new TypeReference<Map<String, Object>>(){});
1

方式二:将字符串先转成JSON字符串再转成Map

Map<String, Object> map = JSONObject.parseObject(JSON.toJSONString(resultStr));
1

说明:将JSON字符串转成Map那步如果报错,请检查以下两点。

1)字符串内有斜杠转义导致。

在pom.xml里引入如下包:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.9</version>
</dependency>
1
2
3
4
5
6

再调用如下方法进行处理:

String str = StringEscapeUtils.unescapeJava(str);
1

2){}两边多了“”双引号导致。

String str = str.substring(1, str.length() - 1);  // 字符串去掉第一位和最后一位
1

[3] 将字符串转成List<Map<String,Object>>

List<Map<String, Object>> mapList = JSON.parseObject(jsonStr, new TypeReference<List<Map<String, Object>>>() {});
1

注:需要fastjson依赖,用于Java对象与JSON互转。

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.62</version>
</dependency>
1
2
3
4
5

[4] Object相关转换

1)将Object转成Map

Map<String, Object> map = JSON.parseObject(JSON.toJSONString(obj), Map.class);  
1

注:需要fastjson依赖,用于Java对象与JSON互转。

2)将Object转成float(其他基本类型同理)

float a = Float.parseFloat(obj.toString());  
1

3)将Object转成boolean

boolean result = (Boolean)ResultMap.get("result");
1

4)将Object转成List

/**
 * 将Object转List
 * @param obj
 * @param <T>
 * @return
 */
public static <T> List<T> objectToList(Object obj, Class<T> clazz) {
    List<T> result = new ArrayList<T>();
    if(obj instanceof List<?>)
    {
        for (Object o : (List<?>) obj)
        {
            result.add(clazz.cast(o));
        }
        return result;
    }
    return null;
}

// 调用示例
List<Object> testList = objectToList((obj), Object.class);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

[5] Map的遍历

map.entrySet().stream().forEach(x -> {
	System.out.println("key: "+x.getKey()+", value: "+x.getValue());
});
1
2
3

[6] 字符串处理

// 将数组转成逗号分割的字符串
String arrayStr = String.join(",", array);   

// 去除字符串中的所有空格
String result = result.replaceAll(" +",""); 
1
2
3
4
5

[7] JSON字符串的转义与反转义

// 转义
StringEscapeUtils.escapeJson()

// 反转义
StringEscapeUtils.unescapeJson()
1
2
3
4
5

注:如果出现“常量字符串过长”的问题,File -> Settings -> Build,Execution,Deployment -> Compiler -> 将 javac 改成 eclipse 即可。

[8] 逗号分割的字符串与List互转

// 将逗号分割的字符串转List
String str1 = "a,b,c";
List<String> list = Arrays.asList(str1.split(","));
System.out.println(list);
// 将List转成逗号分割的字符串
String str2 = Joiner.on(",").join(list);
System.out.println(str2);    
// 逗号分隔字符串转List<Long>,再将List转Set
Set<Long> stateSet = new HashSet<>(Arrays.asList(state.split(",")).stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList()));
1
2
3
4
5
6
7
8
9

[9] List 快速取最大值和最小值

double max = Collections.max(list);
double min = Collections.min(list);
1
2

[10] 过滤出符合条件的List

// 存放过滤结果的列表
List<SysCode> result;

// 创建测试数据
ArrayList<String> codes = new ArrayList<>();
codes.add("SEX");
ArrayList<String> values = new ArrayList<>();
values.add("1");

// 使用lambda表达式(java8支持)过滤出结果并放到result列表里
result = sysCodeList.stream()
.filter((SysCode b) -> codes.contains(b.getCode()))
.filter((SysCode b) -> values.contains(b.getValue()))
.collect(Collectors.toList());

System.out.println(result);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

[11] Map与实体类的互相转换

    /**
     * 使用BeanMap将实体类转成Map
     * @param bean
     * @param <T>
     * @return
     */
    public static <T> Map<String, Object> beanToMap(T bean) {
        Map<String, Object> map = Maps.newHashMap();
        if (bean != null) {
            BeanMap beanMap = BeanMap.create(bean);
            for (Object key : beanMap.keySet()) {
                map.put(key + "", beanMap.get(key));
            }
        }
        return map;
    }

    /**
     * 将 map 转成实体类
     * @param clazz
     * @param map
     * @param <T>
     * @return
     */
    public static <T> T convertMapToBean(Class<T> clazz, Map<String, Object> map) {
        T obj = null;
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            // 创建 JavaBean 对象
            obj = clazz.newInstance();
            // 给 JavaBean 对象的属性赋值
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (int i = 0; i < propertyDescriptors.length; i++) {
                PropertyDescriptor descriptor = propertyDescriptors[i];
                String propertyName = descriptor.getName();
                if (map.containsKey(propertyName)) {
                    Object value = map.get(propertyName);
                    if ("".equals(value)) {
                        value = null;
                    }
                    Object[] args = new Object[1];
                    args[0] = value;
                    descriptor.getWriteMethod().invoke(obj, args);
                }
            }
        } catch (IllegalAccessException e) {
            System.out.println(StrUtil.format("convertMapToBean 实例化JavaBean失败 Error{}",e));
        } catch (IntrospectionException e) {
            System.out.println(StrUtil.format("convertMapToBean 分析类属性失败 Error{}" ,e));
        } catch (IllegalArgumentException e) {
            System.out.println(StrUtil.format("convertMapToBean 映射错误 Error{}" ,e));
        } catch (InstantiationException e) {
            System.out.println(StrUtil.format("convertMapToBean 实例化 JavaBean 失败 Error{}" ,e));
        }catch (InvocationTargetException e){
            System.out.println(StrUtil.format("convertMapToBean字段映射失败 Error{}" ,e));
        }catch (Exception e){
            System.out.println(StrUtil.format("convertMapToBean Error{}" ,e));
        }
        return (T) obj;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

[12] 合并两个Map

    /**
     * 合并两个Map
     * @param map1
     * @param map2
     * @return
     */
    public static Map<String, Object> mergeHashMap(Map<String, Object> map1, Map<String, Object> map2) {
        HashMap<String, Object> combineResultMap = new HashMap<>();
        combineResultMap.putAll(map1);
        combineResultMap.putAll(map2);
        return combineResultMap;
    }
1
2
3
4
5
6
7
8
9
10
11
12

[13] 合并两个List<Map<String, Object>>

    /**
     * 合并两个List<Map<String, Object>>
     * @param list1
     * @param list2
     * @return
     */
    public static List<Map<String, Object>> mergeMapList(List<Map<String, Object>> list1, List<Map<String, Object>> list2){
        list1.addAll(list2);
        Set<String> set = new HashSet<>();
        return list1.stream()
                .collect(Collectors.groupingBy(o->{
                    //暂存所有key
                    set.addAll(o.keySet());
                    //按a_id分组
                    return o.get("id");
                })).entrySet().stream().map(o->{
                    //合并
                    Map<String, Object> map = o.getValue().stream().flatMap(m->{
                        return m.entrySet().stream();
                    }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b)->b));
                    //为没有的key赋值0
                    set.stream().forEach(k->{
                        if(!map.containsKey(k)) {
                            map.put(k, 0);
                        }
                    });
                    return map;
                }).collect(Collectors.toList());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

[14] 取某分隔符之前的字符串

/**
 * 取某分隔符(第一处)之前的字符串
 * @param str
 * @param delimiter
 * @return
 */
public static String substringBefore(String str, String delimiter) {
    String result = "";
    try{
        result = StringUtils.substringBefore(str, delimiter);
    }catch(Exception e){
        e.printStackTrace();
    }
    return result;
}

/**
 * 取某分隔符(最后一处)之前的字符串
 * @param str
 * @param delimiter
 * @return
 */
public static String substringBeforeLast(String str, String delimiter) {
    String result = "";
    try{
        result = StringUtils.substringBeforeLast(str, delimiter);
    }catch(Exception e){
        e.printStackTrace();
    }
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

[15] 将实体类转换成JSON字符串

// 将实体类转换成JSON字符串
ObjectMapper mapper = new ObjectMapper();
User user = new User();
String result = mapper.writeValueAsString(user);
1
2
3
4

# 4.2.3 文件及目录操作

可使用 java.io.File 包进行文件和目录操作,常用方法如下:

  • public String getName() 返回由此抽象路径名表示的文件或目录的名称。
  • public String getParent() 返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null。
  • public File getParentFile() 返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null。
  • public String getPath() 将此抽象路径名转换为一个相对路径名字符串。
  • public boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。
  • public String getAbsolutePath() 返回抽象路径名的绝对路径名字符串。
  • public boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。
  • public boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。
  • public boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
  • public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。
  • public boolean isFile() 测试此抽象路径名表示的文件是否是一个文件。
  • public long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。
  • public long length() 返回由此抽象路径名表示的文件的长度。
  • public boolean createNewFile() throws IOException 当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建一个新的空文件。
  • public boolean delete() 删除此抽象路径名表示的文件或目录。
  • public void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。
  • public String[] list() 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组。
  • public String[] list(FilenameFilter filter) 返回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的
  • public File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。
  • public File[] listFiles(FileFilter filter) 返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。
  • public boolean mkdir() 创建指定目录,只能创建一个父目录(如:c:\java)。
  • public boolean mkdirs() 创建目录,同时创建多个目录(如:c:\java\nn)。
  • public boolean renameTo(File dest) 重新命名此抽象路径名表示的文件。
  • public boolean setLastModified(long time) 设置由此抽象路径名所指定的文件或目录的最后一次修改时间。
  • public boolean setReadOnly() 标记此抽象路径名指定的文件或目录,以便只可对其进行读操作。
  • public static File createTempFile(String prefix, String suffix, File directory) throws IOException 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
  • public static File createTempFile(String prefix, String suffix) throws IOException 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。
  • public int compareTo(File pathname) 按字母顺序比较两个抽象路径名。
  • public int compareTo(Object o) 按字母顺序比较抽象路径名与给定对象。
  • public boolean equals(Object obj) 测试此抽象路径名与给定对象是否相等。
  • public String toString() 返回此抽象路径名的路径名字符串。

用于测试的代码示例:

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;

/**
 * 文件及目录操作示例
 */
public class FileOperate {

    public static void main(String[] args) throws IOException {

        // ================ 【创建目录或文件夹】
        String nDir ="D:\\Java_File_Test";
        File n = new File(nDir);
        if(!n.exists()){
            n.mkdir();  // mkdir只能创建一个文件夹
        }
        String mDir ="D:\\Java_File_Test\\demo";
        File m = new File(mDir);
        if(!m.exists()){
            m.mkdirs();  // mkdirs一次性创建多级目录
        }


        // ================ 【创建一个txt文件】
        String mFile ="D:\\Java_File_Test\\demo\\nn.txt";
        File f=new File(mFile);
        if(!f.exists()){
            f.createNewFile();
        }


        // ================ 【获取文件的信息】
        System.out.println("===== 获取文件信息 =====");
        String pFile ="D:\\Java_File_Test\\demo\\nn.txt";
        File p=new File(pFile);
        // 判断文件和文件夹是否存在
        if(p.exists()) {
            // 判断是否是一个文件
            if(p.isFile()){
                System.out.println("文件名:"+p.getName());
                System.out.println("文件绝对路径:"+p.getAbsolutePath());
                System.out.println("文件相对路径:"+p.getPath());
                System.out.println("文件大小(字节数):"+p.length());
            }
            // 是否是文件夹
            if(p.isDirectory()) {
                System.out.println("这个文件夹存在!");
            }
        }


        // ================ 【文件重命名】
        String rFile ="D:\\Java_File_Test\\demo\\nn.txt";
        File r = new File(rFile);
        if(r.exists()){
            if(r.isFile()) {
                r.renameTo(new File("D:\\Java_File_Test\\demo\\demo.txt"));	// 重命名指定文件(注意加路径)
                System.out.println(rFile + "文件重命名成功!");
            }
        }


        // ================ 【删除指定文件】
        String dFile ="D:\\Java_File_Test\\demo\\demo.txt";  // 指定目录
        File d = new File(dFile);  // 创建一个文件操作对象
        if(d.exists()){
            if(d.isFile()) {
                //d.delete();	// 删除指定文件(为了后续测试先注释掉)
                System.out.println(dFile + "文件删除成功!");
            }
        }


        // ================ 【向文本文件写入内容】
        try {
            String w = "D:\\Java_File_Test\\demo\\demo.txt";
            FileOutputStream wFile = new FileOutputStream(w);  // 写入内容 - 【覆盖内容】
            //FileOutputStream wFile = new FileOutputStream(w,true);  // 写入内容 - 【追加写入内容】
            String mWrite="Hello world";
            byte[] mess = mWrite.getBytes();
            wFile.write(mess,0,mess.length);
        }catch(FileNotFoundException e) {
            e.printStackTrace();
        }
        catch(IOException e){
            e.printStackTrace();
        }


        // ================ 【读取文件内容】
        try {
            String uu="D:\\Java_File_Test\\demo\\demo.txt";
            FileInputStream readFile = new FileInputStream(uu);
            // 读取文件的字节数
            int len = readFile.available();
            System.out.println("文件【"+uu+"】的字节数是"+len);
            // ---------- 读取文本文件内容
            System.out.println("----- 读取文本文件内容 -----");
            FileReader fReader = new FileReader(uu);
            char[] chars = new char[len];
            fReader.read(chars,0,len); // 读取全部文本文件内容
            for(char aaa:chars){
                System.out.print(aaa);
            }
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }
        catch(IOException e){
            e.printStackTrace();
        }

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

# 4.3 完整代码示例

# 4.3.1 生成指定位数的随机密码

可以生成指定位数的随机密码,密码中包含大写字母、小写字母、数字和特殊字符中至少三种类型。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class RandPassword {
    public static final char[] allowedSpecialCharactors = {
            '`', '~', '@', '#', '$', '%', '^', '&',
            '*', '(', ')', '-', '_', '=', '+', '[',
            '{', '}', ']', '\\', '|', ';', ':', '"',
            '\'', ',', '<', '.', '>', '/', '?'}; //密码能包含的特殊字符
    private static final int letterRange = 26;
    private static final int numberRange = 10;
    private static final int spCharactorRange = allowedSpecialCharactors.length;
    private static final Random random = new Random();
    private int passwordLength; //密码的长度
    private int minVariousType; //密码包含字符的最少种类

    public RandPassword(int passwordLength, int minVariousType) {
        if (minVariousType > CharactorType.values().length) minVariousType = CharactorType.values().length;
        if (minVariousType > passwordLength) minVariousType = passwordLength;
        this.passwordLength = passwordLength;
        this.minVariousType = minVariousType;
    }

    public String generateRandomPassword() {
        char[] password = new char[passwordLength];
        List<Integer> pwCharsIndex = new ArrayList();
        for (int i = 0; i < password.length; i++) {
            pwCharsIndex.add(i);
        }
        List<CharactorType> takeTypes = new ArrayList(Arrays.asList(CharactorType.values()));
        List<CharactorType> fixedTypes = Arrays.asList(CharactorType.values());
        int typeCount = 0;
        while (pwCharsIndex.size() > 0) {
            int pwIndex = pwCharsIndex.remove(random.nextInt(pwCharsIndex.size())); //随机填充一位密码
            Character c;
            if (typeCount < minVariousType) { //生成不同种类字符
                c = generateCharacter(takeTypes.remove(random.nextInt(takeTypes.size())));
                typeCount++;
            } else { //随机生成所有种类密码
                c = generateCharacter(fixedTypes.get(random.nextInt(fixedTypes.size())));
            }
            password[pwIndex] = c.charValue();
        }
        return String.valueOf(password);
    }

    private Character generateCharacter(CharactorType type) {
        Character c = null;
        int rand;
        switch (type) {
            case LOWERCASE://随机小写字母
                rand = random.nextInt(letterRange);
                rand += 97;
                c = new Character((char) rand);
                break;
            case UPPERCASE://随机大写字母
                rand = random.nextInt(letterRange);
                rand += 65;
                c = new Character((char) rand);
                break;
            case NUMBER://随机数字
                rand = random.nextInt(numberRange);
                rand += 48;
                c = new Character((char) rand);
                break;
            case SPECIAL_CHARACTOR://随机特殊字符
                rand = random.nextInt(spCharactorRange);
                c = new Character(allowedSpecialCharactors[rand]);
                break;
        }
        return c;
    }

    public static void main(String[] args) {
        System.out.println(new RandPassword(32, 4).generateRandomPassword());
    }
}

enum CharactorType {
    LOWERCASE,
    UPPERCASE,
    NUMBER,
    SPECIAL_CHARACTOR
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

# 4.3.2 对txt进行等量拆分

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SplitTxt {

    private static int count = 10;  // 拆分个数
    private static String inputFile = "H:/input.txt"; //输入文件
    private static String outFile = "H:/output"; // 输出文件

    public static void main(String[] args) {
        try {
            FileReader read = new FileReader(inputFile);
            BufferedReader br = new BufferedReader(read);
            String row;
            List<FileWriter> flist = new ArrayList<FileWriter>();
            for (int i = 0; i < count; i++) {
                flist.add(new FileWriter(outFile + i + ".txt"));
            }
            int rownum = 1;
            while ((row = br.readLine()) != null) {
                flist.get(rownum % count).append(row + "\r\n");
                rownum++;
            }
            for (int i = 0; i < flist.size(); i++) {
                flist.get(i).close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("Txt file split end!");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 4.3.3 将某目录下所有文件的名称保存到txt里

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

public class GetFileName {

    /**
     * 递归读取文件路径下的所有文件
     *
     * @param path
     * @param fileNameList
     * @return
     */
    public static ArrayList<String> readFiles(String path, ArrayList<String> fileNameList) {
        File file = new File(path);
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    readFiles(files[i].getPath(), fileNameList);
                } else {
                    String filePath = files[i].getPath();
                    String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1);
                    fileNameList.add(fileName);
                }
            }
        } else {
            String path1 = file.getPath();
            String fileName = path1.substring(path1.lastIndexOf("\\") + 1);
            fileNameList.add(fileName);
        }
        return fileNameList;
    }

    /**
     * 将内容输出到(追加)txt文件保存
     *
     * @param content
     * @throws IOException
     */
    public static void outputToTxt(String content, String outputPath) throws IOException {
        FileWriter fw = new FileWriter(outputPath, true);  //追加内容
        PrintWriter pw = new PrintWriter(fw);
        pw.println(content);
        pw.close();
        fw.close();
        pw.flush();
    }

    public static void main(String[] args) {
        String filePath = "../data/fileDic";
        String outputPath = "../data/fileName.txt";
        try {
            ArrayList<String> fileNameList = readFiles1(filePath, new ArrayList<String>());
            System.out.println(fileNameList.size());
            for (int i = 0; i < fileNameList.size(); i++) {
                outputToTxt(fileNameList.get(i), outputPath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 4.3.4 Excel的上传导入及导出下载

以接口的形式导入导出--使用Hutool的工具类

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <hutool.all.version>4.1.7</hutool.all.version>
</dependency>
1
2
3
4
5

代码示例:

Excel.java

package com.yoyo.admin.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义导出Excel数据注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel {

    /**
     * 导出到Excel中的名字.
     */
    public String  name() default "";

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

ExcelUtils.java

package com.yoyo.admin.common.utils;

import com.yoyo.admin.common.annotation.Excel;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;

public class ExcelUtils {

    private static final char DELIMITER = ',';
    private static final char RECORD_SEPARATOR = '\n';
    private static final Logger logger = LoggerFactory.getLogger(ExcelUtils.class);

    /**
     * 获取文件的第一个sheet
     *
     * @param inputStream 输入流
     * @return sheet
     */
    public static Sheet getFirstSheet(InputStream inputStream) {
        return getSheet(inputStream, 0);
    }

    /**
     * 获取sheet
     *
     * @param inputStream 输入流
     * @param sheetIndex  sheet序号(从0开始)
     * @return sheet
     */
    public static Sheet getSheet(InputStream inputStream, int sheetIndex) {
        Workbook workbook;
        try {
            workbook = WorkbookFactory.create(inputStream);
        } catch (Exception ex) {
            logger.error("load excel file error");
            return null;
        }
        if (workbook.getNumberOfSheets() >= sheetIndex + 1) {
            return workbook.getSheetAt(sheetIndex);
        } else {
            return null;
        }
    }

    /**
     * 获取Excel表头
     *
     * @param sheet sheet
     * @return excel表头
     */
    public static Map<String, Integer> getHeaders(Sheet sheet) {
        Iterator<Row> rowIterator = sheet.rowIterator();
        if (!rowIterator.hasNext()) {
            return Collections.emptyMap();
        }
        Map<String, Integer> headerMap = new HashMap<>();
        Row row = rowIterator.next();
        Iterator<Cell> cellIterator = row.cellIterator();
        int index = 0;
        while (cellIterator.hasNext()) {
            Object value = getCellValue(cellIterator.next());
            headerMap.put(value == null ? "" : value.toString().trim(), index++);
        }
        return headerMap;
    }

    /**
     * 获取除表头外的所有行的列表
     *
     * @param sheet     sheet
     * @param headerMap 表头
     * @return 所有行的列表
     */
    public static List<Map<String, Object>> getRows(Sheet sheet, Map<String, Integer> headerMap) {
        Iterator<Row> rowIterator = sheet.rowIterator();
        List<Map<String, Object>> rows = new ArrayList<>();
        while (rowIterator.hasNext()) {
            Row row = rowIterator.next();
            if (row.getRowNum() == 0) {
                continue;//过滤掉header所在的行
            }
            boolean allRowIsNull = true;
            Iterator<Cell> cellIterator = row.cellIterator();
            while (cellIterator.hasNext()) {
                Object cellValue = getCellValue(cellIterator.next());
                if (cellValue != null) {
                    allRowIsNull = false;
                    break;
                }
            }
            if (allRowIsNull) {
                continue;//整行都空就跳过
            }
            Map<String, Object> rowMap = new HashMap<>();
            for (String k : headerMap.keySet()) {
                Integer index = headerMap.get(k);
                Cell cell = row.getCell(index);
                rowMap.put(k.trim(), getCellValue(cell));
            }
            rows.add(rowMap);
        }
        return rows;
    }

    /**
     * 获取单元格数据
     *
     * @param cell 单元格
     * @return 单元格数据
     */
    private static Object getCellValue(Cell cell) {
        if (cell == null) {
            return null;
        }
        CellType cellType = cell.getCellType();
        if (cellType == CellType.BLANK) {
            return null;
        } else if (cellType == CellType.BOOLEAN) {
            return cell.getBooleanCellValue();
        } else if (cellType == CellType.ERROR) {
            return cell.getErrorCellValue();
        } else if (cellType == CellType.FORMULA) {
            try {
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue();
                } else {
                    return cell.getNumericCellValue();
                }
            } catch (IllegalStateException ex) {
                return cell.getRichStringCellValue();
            }
        } else if (cellType == CellType.NUMERIC) {
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue();
            } else {
                return cell.getNumericCellValue();
            }
        } else if (cellType == CellType.STRING && cell.getStringCellValue() != null && !cell.getStringCellValue().isEmpty()) {
            return cell.getStringCellValue();
        } else {
            return null;
        }
    }

    /**
     * 设置单元格数据
     *
     * @param cell      单元格
     * @param value     单元格数据
     * @param dateStyle 日期格式(可为空)
     */
    private static void setCellValue(XSSFCell cell, Object value, CellStyle dateStyle) {
        if (value instanceof Integer) {
            cell.setCellValue((Integer) value);
        } else if (value instanceof Float) {
            cell.setCellValue((Float) value);
        } else if (value instanceof Double) {
            cell.setCellValue((Double) value);
        } else if (value instanceof Long) {
            cell.setCellValue((Long) value);
        } else if (value instanceof Boolean) {
            cell.setCellValue((Boolean) value);
        } else if (value instanceof Date) {
            cell.setCellValue((Date) value);
            if (dateStyle == null) {
                cell.setCellStyle(createDefaultDateCellStyle(cell.getSheet().getWorkbook()));
            } else {
                cell.setCellStyle(dateStyle);
            }
        } else {
            String textValue = value == null ? "" : value.toString();
            if (textValue.length() > 32767) {
                textValue = textValue.substring(0, 32766);
            }
            XSSFRichTextString richTextString = new XSSFRichTextString(textValue);
            cell.setCellValue(richTextString);
        }
    }

    /**
     * 创建默认的时间类型单元格格式
     *
     * @param workbook 工作簿
     * @return 单元格格式
     */
    private static CellStyle createDefaultDateCellStyle(XSSFWorkbook workbook) {
        XSSFCreationHelper creationHelper = workbook.getCreationHelper();
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setDataFormat(creationHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
        return cellStyle;
    }

    /**
     * 从sheet中获取所有的图片数据
     *
     * @param sheet sheet
     * @return 图片数据
     */
    public static Map<String, PictureData> getSheetPictures(Sheet sheet) {
        if (sheet instanceof XSSFSheet) {
            return getSheetPicturesFromXlsx((XSSFSheet) sheet);
        } else if (sheet instanceof HSSFSheet) {
            return getSheetPicturesFromXls((HSSFSheet) sheet);
        }
        return new HashMap<>();
    }

    /**
     * 从sheet中获取所有的图片数据(.xlsx)
     *
     * @param sheet sheet
     * @return 图片数据
     */
    private static Map<String, PictureData> getSheetPicturesFromXlsx(XSSFSheet sheet) {
        int i = 0;
        Map<String, PictureData> pictures = new HashMap<>();
        List<POIXMLDocumentPart> list = sheet.getRelations();
        for (POIXMLDocumentPart part : list) {
            if (part instanceof XSSFDrawing) {
                XSSFDrawing drawing = (XSSFDrawing) part;
                List<XSSFShape> shapes = drawing.getShapes();
                for (XSSFShape shape : shapes) {
                    XSSFPicture picture = (XSSFPicture) shape;
                    XSSFClientAnchor anchor = picture.getPreferredSize();
                    CTMarker marker = anchor.getFrom();
                    String key = marker.getRow() + "-" + marker.getCol() + "-" + i++;
                    pictures.put(key, picture.getPictureData());
                }
            }
        }
        return pictures;
    }

    /**
     * 从sheet中获取所有的图片数据(.xls)
     *
     * @param sheet sheet
     * @return 图片数据
     */
    private static Map<String, PictureData> getSheetPicturesFromXls(HSSFSheet sheet) {
        int i = 0;
        Map<String, PictureData> pictures = new HashMap<>();
        if (sheet.getDrawingPatriarch() != null) {
            List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
            for (HSSFShape shape : list) {
                if (shape instanceof HSSFPicture) {
                    HSSFPicture picture = (HSSFPicture) shape;
                    HSSFClientAnchor anchor = (HSSFClientAnchor) picture.getAnchor();
                    PictureData pictureData = picture.getPictureData();
                    String key = anchor.getRow1() + "-" + anchor.getCol1() + "-" + i++;
                    pictures.put(key, pictureData);
                }
            }
        }
        return pictures;
    }

    /**
     * 导出Excel工作簿
     *
     * @param headers 表头(key是标识,value是显示名称)
     * @param lines   行数据
     * @return Excel数据流
     */
    public static ByteArrayResource exportExcel(Map<String, Object> headers, List<Map<String, Object>> lines) throws IOException {
        return exportExcel(headers, lines, null);
    }

    /**
     * 导出Excel工作簿
     *
     * @param headers   表头(key是标识,value是显示名称)
     * @param lines     行数据
     * @param dateStyle 日期格式
     * @return Excel数据流
     */
    public static ByteArrayResource exportExcel(Map<String, Object> headers, List<Map<String, Object>> lines, CellStyle dateStyle) throws IOException {
        XSSFWorkbook workbook = exportExcelWorkBook(headers, lines, dateStyle);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);
        return new ByteArrayResource(outputStream.toByteArray());
    }

    /**
     * 导出Excel工作簿
     *
     * @param headers 表头(key是标识,value是显示名称)
     * @param lines   行数据
     * @return Excel工作簿
     */
    private static XSSFWorkbook exportExcelWorkBook(Map<String, Object> headers, List<Map<String, Object>> lines) {
        return exportExcelWorkBook(headers, lines, null);
    }

    /**
     * 导出Excel工作簿
     *
     * @param headers   表头(key是标识,value是显示名称)
     * @param lines     行数据
     * @param dateStyle 日期格式
     * @return Excel工作簿
     */
    private static XSSFWorkbook exportExcelWorkBook(Map<String, Object> headers, List<Map<String, Object>> lines, CellStyle dateStyle) {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet();
        if (dateStyle == null) {
            dateStyle = createDefaultDateCellStyle(workbook);
        }
        writeSheetData(sheet, headers, lines, dateStyle);
        return workbook;
    }

    /**
     * 将数据写入到sheet
     *
     * @param sheet     sheet
     * @param headers   表头(key是标识,value是显示名称)
     * @param lines     行数据
     * @param dateStyle 日期格式
     */
    private static void writeSheetData(XSSFSheet sheet, Map<String, Object> headers, List<Map<String, Object>> lines, CellStyle dateStyle) {
        //生成表格标题行
        XSSFRow headerRow = sheet.createRow(0);
        List<String> columnNames = new ArrayList<>();
        XSSFCellStyle style = getHeaderStyle(sheet.getWorkbook());
        int columnNum = 0;
        for (Map.Entry<String, Object> entry : headers.entrySet()) {
            columnNames.add(entry.getKey());
            XSSFCell cell = headerRow.createCell(columnNum);
            cell.setCellStyle(style);
            Object title = entry.getValue();
            setCellValue(cell, title, dateStyle);
            sheet.setColumnWidth(columnNum, entry.getValue().toString().getBytes().length * 600);
            columnNum++;
        }
        //生成表格数据行
        if (lines == null || lines.size() == 0) {
            return;
        }
        for (int i = 0; i <= lines.size() - 1; i++) {
            Map<String, Object> record = lines.get(i);
            XSSFRow row = sheet.createRow(i + 1);
            for (int j = 0; j <= columnNames.size() - 1; j++) {
                XSSFCell cell = row.createCell(j);
                setCellValue(cell, record.get(columnNames.get(j)), dateStyle);
            }
        }
        // 设定自动宽度
        //for (int i = 0; i <= columnNames.size(); i++) {
        //    sheet.autoSizeColumn(i);
        //}
    }

    /**
     * 获取默认的表头样式
     *
     * @param workbook 工作薄
     * @return 表头样式
     */
    private static XSSFCellStyle getHeaderStyle(XSSFWorkbook workbook) {
        XSSFCellStyle style = workbook.createCellStyle();
        style.setFillForegroundColor(IndexedColors.LIGHT_TURQUOISE.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        return style;
    }

    /**
     * 导入时验证Excel表头
     *
     * @param excelHeader 表头Map
     * @return 错误消息
     */
    public static Optional<String> validateExcelHeader(Map<String, Integer> excelHeader, Map<String, Object> standardHeader) {
        if (excelHeader.isEmpty()) {
            return Optional.of("Excel不包含表头");
        }
        for (Map.Entry<String, Object> entry : standardHeader.entrySet()) {
            String title = entry.getValue().toString();
            if (excelHeader.get(title) == null) {
                return Optional.of("Excel表头列内容不正确,其不包含:" + title);
            }
        }
        return Optional.empty();
    }

    /**
     * 导入时验证Excel表头
     *
     * @param header 表头Map
     * @return 错误消息
     */
    public static Optional<String> validateExcelHeaderString(Map<String, String> header, Map<String, String> excelHeaders) {
        if (header.isEmpty()) {
            return Optional.of("Excel不包含表头");
        }
        for (Map.Entry<String, String> entry : excelHeaders.entrySet()) {
            String title = entry.getKey();
            if (header.get(title) == null) {
                return Optional.of("Excel表头列内容不正确,其不包含:" + title);
            }
        }
        return Optional.empty();
    }

    /**
     * 获取导出字段
     *
     * @return
     */
    public static Map<String, String> getExportHeaderAlias(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Map<String, String> map = new LinkedHashMap<>();
        for (Field field : fields) {
            Excel annotation = field.getAnnotation(Excel.class);
            if (annotation == null) {
                continue;
            }
            map.put(field.getName(), annotation.name());
        }
        return map;
    }

    /**
     * 获取导入字段
     *
     * @return
     */
    public static Map<String, String> getImportHeaderAlias(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Map<String, String> map = new LinkedHashMap<>();
        for (Field field : fields) {
            Excel annotation = field.getAnnotation(Excel.class);
            if (annotation == null) {
                continue;
            }
            map.put(annotation.name(), field.getName());
        }
        return map;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458

ExcelDemoService.java

package com.yoyo.admin.common.service.demo;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.yoyo.admin.common.domain.Base;
import com.yoyo.admin.common.domain.repository.BaseRepository;
import com.yoyo.admin.common.utils.ExcelUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

import static com.yoyo.admin.common.utils.ExcelUtils.*;

@Service
public class ExcelDemoService {

    private BaseRepository baseRepository;

    @Autowired
    public void setBaseRepository(BaseRepository baseRepository) {
        this.baseRepository = baseRepository;
    }

    /**
     * Excel导入示例
     * @param file
     * @throws Exception
     */
    public void importExcelData(MultipartFile file) throws Exception {

        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());

        // Excel表头定义
        Map<String, String> headerAlias = ExcelUtils.getImportHeaderAlias(Base.class);
        reader.setHeaderAlias(headerAlias);
        headerAlias.put("备注", "remark");
        headerAlias.put("是否删除", "is_deleted");
        List<Map<String, Object>> lists = reader.readAll();
        if (lists.size() == 0) {
            throw new Exception("导入数据不能为空!");
        }

        // 获取并验证表头
        Map<String, String> headerMap = reader.getHeaderAlias();
        Optional<String> errorMessage = validateExcelHeaderString(headerMap, headerAlias);
        if (errorMessage.isPresent()) {
            throw new RuntimeException(errorMessage.get());
        }
        List<Base> saveList = new ArrayList<>();
        for (Map<String, Object> map : lists) {
            String remark = map.get("remark") != null ? map.get("remark").toString() : "";
            String isDeleted = map.get("is_deleted") != null ? map.get("is_deleted").toString() : "";
            Base base = new Base();
            base.setRemark(remark);
            base.setIsDeleted(Integer.valueOf(isDeleted));
            saveList.add(base);
        }
        baseRepository.saveAll(saveList);
    }


    /**
     * Excel导出示例
     * @param response
     */
    public void exportExcelData(HttpServletResponse response) {

        List<Base> dataList = baseRepository.findAllByIsDeleted(0);

        try {
            // 下载文件名称
            String uuid = IdUtil.randomUUID();
            String fileName = StrUtil.format("{}.xls",uuid);
            ExcelWriter writer = ExcelUtil.getWriter();

            // 标题别名
            Map<String, String> headerAlias = ExcelUtils.getExportHeaderAlias(HashMap.class);
            headerAlias.put("id", "主键");
            headerAlias.put("createTime", "创建时间");
            headerAlias.put("updateTime", "更新时间");
            headerAlias.put("remark", "备注");
            headerAlias.put("isDeleted", "是否删除");

            writer.setHeaderAlias(headerAlias);
            writer.setOnlyAlias(true);
            int num = 0;
            for (String key : headerAlias.keySet()) {
                writer.setColumnWidth(num, 20);
                num++;
            }

            // response为HttpServletResponse对象
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            // 弹出下载对话框的文件名,不能为中文,中文请自行编码
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            // out为OutputStream,需要写出到的目标流
            ServletOutputStream out = response.getOutputStream();
            writer.write(dataList, true);
            writer.flush(out);
            // 关闭writer,释放内存
            writer.close();
            // 关闭输出Servlet流
            IoUtil.close(out);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

ExcelDemoController.java

package com.yoyo.admin.web_manage.controller.demo;

import com.yoyo.admin.common.service.demo.ExcelDemoService;
import com.yoyo.admin.common.utils.ResultDataUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;


@Api(tags = "Excel导入导出示例")
@RestController
@RequestMapping(value = "/api/demo/excel")
public class ExcelDemoController {

    private ExcelDemoService excelDemoService;

    @Autowired
    public void setExcelDemoService(ExcelDemoService excelDemoService) {
        this.excelDemoService = excelDemoService;
    }

    @ApiOperation("Excel导入示例")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "file", value = "上传文件", required = true, dataType = "File", paramType = "form")
    })
    @PostMapping(value = "/importExcelData")
    public ResponseEntity<?> importExcelData(@RequestParam(name = "file") MultipartFile file) {
        try {
            excelDemoService.importExcelData(file);
            return ResultDataUtils.success();
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation("Excel导出示例")
    @PostMapping(value = "/exportExcelData")
    public ResponseEntity<?> exportExcelData(HttpServletResponse response) {
        try {
            excelDemoService.exportExcelData(response);
            return ResultDataUtils.success();
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

注:实体类的定义Base.java及JPA查询文件BaseRepository.java此处略去。

# 4.3.5 Excel的本地导出

将结果导出本地Excel文件--使用easyexcel库

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>2.2.4</version>
</dependency>
1
2
3
4
5

代码示例:

import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;

public class ExportData {

    public static void main(String[] args) {

        // 设置标题
        List<List<String>> headList = new ArrayList<>();
        List<String> head0 = new ArrayList<>();
        head0.add("姓名");
        List<String> head1 = new ArrayList<>();
        head1.add("年龄");
        headList.add(head0);
        headList.add(head1);

        // 设置内容
        List<List<Object>> dataList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            List<Object> data = new ArrayList<>();
            data.add("张三");
            data.add(25);
            dataList.add(data);
        }

        // 导出Excel
        String fileName = "./demo.xlsx";
        EasyExcel.write(fileName).head(headList).sheet("示例").doWrite(dataList);

    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 4.3.6 将文件夹打包压缩后下载

ZipUtils

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipUtils {

    private static final int  BUFFER_SIZE = 2 * 1024;

    /**
     * 压缩成ZIP
     * @param srcDir 压缩文件夹路径
     * @param out    压缩文件输出流
     * @param KeepDirStructure  是否保留原来的目录结构,true:保留目录结构;
     * 							false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void dirToZip(String srcDir, OutputStream out, boolean KeepDirStructure)
            throws RuntimeException{

        long start = System.currentTimeMillis();
        ZipOutputStream zos = null ;
        try {
            zos = new ZipOutputStream(out);
            File sourceFile = new File(srcDir);
            compress(sourceFile,zos,sourceFile.getName(),KeepDirStructure);
            long end = System.currentTimeMillis();
            System.out.println("压缩完成,耗时:" + (end - start) +" ms");
        } catch (Exception e) {
            throw new RuntimeException("zip error from ZipUtils",e);
        }finally{
            if(zos != null){
                try {
                    zos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 递归压缩方法
     * @param sourceFile 源文件
     * @param zos		 zip输出流
     * @param name		 压缩后的名称
     * @param KeepDirStructure  是否保留原来的目录结构,true:保留目录结构;
     * 							false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws Exception
     */
    private static void compress(File sourceFile, ZipOutputStream zos, String name,
                                 boolean KeepDirStructure) throws Exception{
        byte[] buf = new byte[BUFFER_SIZE];
        if(sourceFile.isFile()){
            // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            FileInputStream in = new FileInputStream(sourceFile);
            while ((len = in.read(buf)) != -1){
                zos.write(buf, 0, len);
            }
            // Complete the entry
            zos.closeEntry();
            in.close();
        } else {
            File[] listFiles = sourceFile.listFiles();
            if(listFiles == null || listFiles.length == 0){
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if(KeepDirStructure){
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件,不需要文件的copy
                    zos.closeEntry();
                }

            }else {
                for (File file : listFiles) {
                    // 判断是否需要保留原来的文件结构
                    if (KeepDirStructure) {
                        // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
                        compress(file, zos, name + "/" + file.getName(),KeepDirStructure);
                    } else {
                        compress(file, zos, file.getName(),KeepDirStructure);
                    }

                }
            }
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

Controller:

    /**
     * 批量打包下载文件
     * @param fileName 文件名,多个用英文逗号分隔
     */
    @GetMapping("/downTogether")
    public void downTogether(@RequestParam(value = "filename", required = false) String fileName) {
        logger.info("批量打包文件下载接口入参:[filename={}]", fileName);
        if (fileName.isEmpty()){
            return;
        } 
        List<String> fileNameList = Arrays.asList(fileName.split(","));
        if (CollectionUtils.isEmpty(fileNameList)) {
            return;
        }
        try {
            downLoadService.downTogetherAndZip(fileNameList);
        } catch (Exception e) {
            logger.error("批量打包文件下载接口异常:{fileName={},ex={}}", fileName, e);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Service:

    @Override
    public void downTogetherAndZip(List<String> fileNameList) throws IOException {
        HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        // 压缩下载后的文件名不能为中文
        resp.setHeader("Content-Disposition", StrUtil.format("attachment;filename={}.zip",IdUtil.randomUUID()));
        ZipUtils.dirToZip(tempDirPath, resp.getOutputStream(),false);
    }
1
2
3
4
5
6
7

# 4.3.7 读取JSON文件并实现接口查询

需求情景:在Springboot项目启动时读取json文件并保存到List<模型>中,然后需要业务中去使用,比如打卡系统给个别人添加白名单。

将user.json文件放置到resource目录下

[
  {
    "id":1,
    "name": "小李",
    "gender": "男",
    "age": 18
  },
  {
    "id":2,
    "name": "小王",
    "gender": "女",
    "age": 18
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

编写User模型

@Data
public class User {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
}
1
2
3
4
5
6
7

编写UserDao

@Component
public class UserDao {
    private static List<User> userList = new ArrayList<>();

    @Value("classpath:user.json")
    private Resource userResource;

    @PostConstruct
    public List<User> getUser(){
        try {
            String json = IOUtils.toString(userResource.getInputStream(), Charset.forName("UTF-8"));
            userList = JSONObject.parseArray(json, User.class);
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return userList;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

编写UserController

@RestController
public class UserController {
    @Autowired
    UserDao userDao;

    /**
     * 根据ID查询用户
     * @param id
     * @return
     */
    @GetMapping("/user/{id}")
    public Result<User> getUserById(@PathVariable Integer id){
        List<User> list = userDao.getUser();
        User user = null;
        for (User u:
                list) {
            if (u.getId().equals(id)){
                user = u;
                break;
            }
        }

        return ResultUtil.ok(user);
    }

    /**
     * 查询用户列表
     * @return
     */
    @GetMapping("/users")
    public Result<List<User>> getUserById(){
        return ResultUtil.ok(userDao.getUser());
    }
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 4.3.8 递归统计项目的代码行数

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * 递归统计项目的代码行数
 */
public class CountCode {

    static int count = 0;

    /**
     * 递归统计代码总行数
     * @param file
     * @param ext
     * @throws IOException
     */
    public static void myCodeCount(File file, String ext) throws IOException {
        // 文件是否为普通文件并以.java为后缀结尾
        BasicFileAttributes basicFileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
        if (basicFileAttributes.isRegularFile() && (file.getName().endsWith(ext))) {
            int i = readData(file);
            count += i;
        }
        // 测试此抽象路径名表示的文件是否为目录
        if (basicFileAttributes.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            for (File f : files) {
                f.listFiles(f1 -> f1.getName().endsWith(ext));
                myCodeCount(f, ext);
            }
        }
    }

    /**
     * 统计单个文件中的行数
     */
    public static int readData(File file) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath())));
        int i = 0;
        while ((br.readLine())!=null) {
            i++;
        }
        br.close();
        System.out.println(file.getName()+"的行数为"+i);
        return i;
    }

    public static void main(String[] args) throws IOException {
        String path = "/root/yoyo-admin";
        String ext = ".java";
        myCodeCount(new File(path), ext);
        System.out.println("总代码行数为"+count);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 4.3.9 在Java程序里调用Python脚本

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class RuntimeFunction {
    public static void main(String[] args) {
        Process proc;
        try {
            proc = Runtime.getRuntime().exec("python3 /root/test.py");
            BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                System.out.println(line);
            }
            in.close();
            proc.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 5. 参考资料

[1] 尚硅谷-使用向导快速创建Springboot项目 from Bilibili (opens new window)

[2] Springboot+Vue整合笔记【超详细】from 知乎 (opens new window)

[3] 看起来高大上的滑块验证码是怎样实现的 from 稀土掘金 (opens new window)

[4] JSONString(json字符串)的转义与反转义 from CSDN (opens new window)

[5] SpringBoot实现文件下载的几种方式 from CSDN (opens new window)

[6] java 读取本地json文件 from CSDN (opens new window)

[7] JSON数组形式字符串转换为List<Map<String,String>>的8种方法 from CSDN (opens new window)

[8] 可能是java界最好的开源行为验证码(滑块验证码、点选验证码、行为验证码、旋转验证码, 滑动验证码)from Gitea (opens new window)

[9] Java字段脱敏处理 from 稀土掘金 (opens new window)

[10] idea java常量字符串过长解决办法 from cnblogs (opens new window)

[11] Java - 格式化输出JSON字符串的两种方式 from 博客园 (opens new window)

[12] java list与tree的相互转换 DB数据<->树状表单 from 简书 (opens new window)

[13] springboot 解决跨域 from segmentfault (opens new window)

[14] Spring Boot - 统一数据下发接口格式 from 简书 (opens new window)

[15] spring boot项目启动报错:Failed to load property source from location 'classpath:/application.yml' from CSDN (opens new window)

[16] Intellij IDEA运行报Command line is too long解法 from CSDN (opens new window)

[17] IDEA springboot “spring-boot-maven-plugin“报红问题的解决方法 from 博客园 (opens new window)

[18] 关于@Slf4j中log爆红的问题 from CSDN (opens new window)

[19] 解决IntelliJ IDEA使用@AutoWired注解出现Could not autowire. No beans of 'xxxx' type found 的错误提示 from CSDN (opens new window)

[20] UTF-8编码下\u7528\u6237转换为中文汉字 from CSDN (opens new window)

[21] Java用ZXing的方式生成与解析二维码 from CSDN (opens new window)

[22] SpringBoot 发送邮件功能(包含网易、QQ、Gmail邮箱)from CSDN (opens new window)

[23] dao、pojo、service、controller、mapper、Impl、bo、vo、po、domain都是什么?from 每天进步一点点 (opens new window)

[24] 关于spring boot自动注入出现Consider defining a bean of type ‘xxx‘ in your configuration问题解决方案 from CSDN (opens new window)

[25] SpringBoot自定义注解实现 from 稀土掘金 (opens new window)

[26] BASE64Encoder是内部专用 API, 可能会在未来发行版中删除 from 代码先锋网 (opens new window)

[27] springboot 启动报错Field XXX required a bean of type XXX that could not be found. from CSDN (opens new window)

[28] spring-boot如何去获取前端传递的参数 from 华为云 (opens new window)

[29] 使用ConfigurationProperties注解时提示“Spring Boot Configuration Annotation Processor not configured” from 博客园 (opens new window)

[30] 解决SpringBoot使用Undertow启动时警告日志:Undertow Buffer pool was not set on WebSocketDeploymentInfo from Springboot官方社区 (opens new window)

[31] 微服务指南 from martinfowler官网 (opens new window)

[32] Java将base64字符串转成MultipartFile类型 from CSDN (opens new window)

[33] spring boot读取json文件并实现接口查询 from CSDN (opens new window)

[34] Java递归读取文件路径下所有文件名称并保存为Txt文档 from CSDN (opens new window)

[35] Java发送Http请求并获取状态码 from CSDN (opens new window)

[36] java判断一个数值是否在数学区间范围内 from CSDN (opens new window)

[37] 通过File类操作文件夹(目录创建、删除)和文本文件(增/删/改/查)from CSDN (opens new window)

[38] Java中如何将一个数组分割成多个等长度的数组 from CSDN (opens new window)

[39] 使用Java将图片转成Base64编码,并压缩至40k from CSDN (opens new window)

[40] 中文乱码恢复 from 博客园 (opens new window)

[41] 两个List<Map<String,Object>>快速合并 from CSDN (opens new window)

[42] SpringBoot升级2.4.0所出现的问题:When allowCredentials is true, allowedOrigins cannot contain the specia from CSDN (opens new window)

[43] 如何优雅的将Object转换成List from 博客园 (opens new window)

[44] AES的256位密钥加解密报 java.security.InvalidKeyException: Illegal key size or default parameters 异常的处理及处理工具 from CSDN (opens new window)

[45] java yaml转properties工具类 from CSDN (opens new window)

[46] Java 获取Enum枚举中的值,以列表方式返回 from CSDN (opens new window)

[47] Java Map按key排序和按value排序 from CSDN (opens new window)

[48] Java读写Excel原来这么简单 from segmentfault (opens new window)

[49] java的List中使用filter过滤出符合特定条件的元素List from CSDN (opens new window)

[50] Java怎样创建两个KEY(key-pair)的MAP from crazyant (opens new window)

[51] java实现文件打包压缩下载接口(附上可实际运行的代码)from CSDN (opens new window)

[52] JAVA 图片地址路径转换 Base64 工具类 from CSDN (opens new window)

[53] Java实现将文件或者文件夹压缩成zip from 博客园 (opens new window)

[54] Java读取.properties配置文件的几种方式 from 1024搜 (opens new window)

[55] Java 实现数学公式的正则校验和结果计算 from segmentfault (opens new window)

[56] 浅聊过滤器 + 拦截器 + JWT from 稀土掘金 (opens new window)

[57] Java 获取本机的外网 IP from segmentfault (opens new window)

[58] 详谈再论JAVA获取本机IP地址 from 博客园 (opens new window)

[59] Java获取服务器IP及端口的方法实例分析 from eolink (opens new window)

[60] java调用python的几种用法(看这篇就够了)from 博客园 (opens new window)

[61] springboot+websocket实时读取日志文件到浏览器 from Gitee (opens new window)

[62] 可同时监控多个本地文件,发布成web给浏览器实时查看 from Gitee (opens new window)

Last Updated: 11/18/2023, 9:42:30 PM