学无止境

252 阅读20分钟

学无止境,笔记记录长期更新

Hutool依赖:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.3</version>
</dependency>

Hutool常用

时间处理:

获取时间

//格式化日期输出
//new Date() 当前时间
//DatePattern.PURE_DATETIME_PATTERN 日期格式
DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN)

//获取当前的总毫秒数
System.currentTimeMillis()
/**
* 例如:
*/
 public static void main(String[] args) {
        //总毫秒数  
        long totalMilisSeconds = System.currentTimeMillis();
        //总秒数
        long totalSeconds = totalMilisSeconds / 1000;
        //当前秒数
        long currentSeconds = totalSeconds % 60;
        //总分钟
        long totalMinutes = totalSeconds / 60;
        //当前分钟
        long currentMinutes = totalMinutes % 60;
        //总小时(中国时区需加8小时)
        long totalHours = totalMinutes / 60 + 8;
        //当前小时
        long currentHours = totalHours % 24;
        //总天数
        long totalDays = totalHours / 24;
        Date date = new Date();
        System.out.println("总毫秒数:"+totalMilisSeconds);
        System.out.println("总秒数:"+totalSeconds);
        System.out.println("总分钟数:"+totalMinutes);
        System.out.println("总小时:"+totalHours);
        System.out.println("当前秒:"+currentSeconds);
        System.out.println("当前分钟数:"+currentMinutes);
        System.out.println("当前小时:"+currentHours);
        System.out.println("总天数:"+totalDays);
        SimpleDateFormat sdFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(date);
        System.out.println(sdFormatter.format(date));
    }

DateUtil

	    //当前时间
	    DateTime date = DateUtil.date();
        //本月开始时间
        DateTime thisBeginMonth = DateUtil.beginOfMonth(date);
        //本月结束时间
        DateTime thisEndMonth = DateUtil.endOfMonth(date);
        //上个月开始时间
        DateTime lastBeginMonth = DateUtil.beginOfMonth(DateUtil.lastMonth());
        //上个月结束时间
        DateTime lastEndMonth = DateUtil.endOfMonth(DateUtil.lastMonth());
        //本周开始时间
        DateTime thisBeginWeek = DateUtil.beginOfWeek(date);
        //本周结束时间
        DateTime thisEndWeek = DateUtil.endOfWeek(date);
        //上周开始时间
        DateTime lastBeginWeek = DateUtil.offsetWeek(thisBeginWeek, -1);
        //上周结束时间
        DateTime lastEndWeek = DateUtil.offsetWeek(thisEndWeek, -1);

时间转换

//将特定格式的日期转换为Date对象
// Params:
//		dateStr – 特定格式的日期
//		format – 格式,例如yyyy-MM-dd
DateUtil.parse(orderStatistics.getBeginTime(), DatePattern.NORM_DATE_PATTERN)

时间计算

//计算时间差
DateUtil.between()
//a与b的时间差,单位为秒
DateUtil.between(a, b, DateUnit.SECOND)

时间比较

compare

//Params:
//date1 – 日期1
//date2 – 日期2
//Returns:
//比较结果,如果date1 < date2,返回数小于0,date1==date2返回0,date1 > date2 大于0
DateUtil.compare(date1,date2)

时间偏移

//now:当前时间
//-30:向后偏移30分钟
DateUtil.offsetMinute(now, -30)

rangeToList

根据步进单位获取起始日期时间和结束日期时间的时间区间集合

equals

使用equals进行判断时,把要比较的值放在后面

if (要判断的内容.equals(判断的内容)) {
}

例如:

@Test
void test(){
    String name = "xxw";
    if ("xxw".equals(name)) {
        log.info("相等:{}", name);
    }
}

如果是如下情况:

@Test
void test(){
    String name = "xxw";
    if (name.equals("xxw")) {
        log.info("相等:{}", name);
    }
}

如果传入参数为空时,容易造成空指针异常

使用equals时,默认把传入的参数放在后面

Map

TreeMap 自动排序

  • TreeMap存储K-V键值对,通过红黑树(R-B tree)实现

  • 可以实现元素的自动排序。put时,自动排序

使用 new TreeMap<>() 可以put键值对 打印结果为 {test=test} 是等号连接

        Map<String, Object> paramMap = new TreeMap<>();
        paramMap.put("test","test");
        System.out.println("当前paramMap:"+paramMap);
//结果:当前paramMap:{test=test}

遍历

TreeMap的遍历可以使用map.values(), map.keySet()map.entrySet()map.forEach()

map.keySet():遍历键

map.values(): 遍历值

map.entrySet():遍历键值对

        Map<String, Object> paramMap = new TreeMap<>();
        paramMap.put("testKey","testValue");
        paramMap.put("testKey3","testValue3");
        paramMap.put("testKey2","testValue2");
        paramMap.put("testKey4","testValue4");
        StringBuilder str = new StringBuilder("第一个内容");
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            str.append(entry.getKey()).append(entry.getValue());
        }
        System.out.println("当前paramMap:"+paramMap);
        System.out.println("当前str:"+str);

        /* 结果:
        *  当前paramMap:{testKey=testValue, testKey2=testValue2, testKey3=testValue3, testKey4=testValue4}
        *  当前str:第一个内容testKeytestValuetestKey2testValue2testKey3testValue3testKey4testValue4
        *
        * */

Map折线图统计

//时间格式
String format = DatePattern.NORM_DATE_PATTERN;
//开始时间
DateTime beginTime = DateUtil.parse(开始时间, format);
//结束时间
DateTime endTime = DateUtil.parse(结束时间, format);
//步进单位一天
List<DateTime> dateTimes = DateUtil.rangeToList(beginTime, endTime, DateField.DAY_OF_WEEK);

//查询指定时间订单的全部创建时间
List<MallOrders> mallOrders = mallOrdersService.lambdaQuery()
        .select(BaseEntity::getCreateTime)
        .ge(BaseEntity::getCreateTime, DateUtil.beginOfDay(beginTime))
        .le(BaseEntity::getCreateTime, DateUtil.endOfDay(endTime))
        .list();
//创建Map<key,value>
Map<String, Integer> map = new HashMap<>();
for (MallOrders mallOrder : mallOrders) {
    ////时间格式转换
    String dateFormat = DateUtil.format(mallOrder.getCreateTime(), format);
    map.merge(dateFormat, 1, Integer::sum);
}

                    ^
                    | 
                    |
    		使用.merge 简化
                        
//遍历
mallOrders.forEach(mallOrders1 -> {
    //时间格式转换
    String dateFormat = DateUtil.format(mallOrders1.getCreateTime(), format);
    //判断dateFormat在map中是否有key存在
    if (map.containsKey(dateFormat)) {
        //存在则put(key,value+1)
        map.put(dateFormat,map.get(dateFormat) + 1);
    } else {
        //否则put(kye,1)
        map.put(dateFormat, 1);
    }
});

//
List<ChartLineData> chartLineDataList = dateTimes.stream().map(dateTime -> {
    	   //@Data
		  //public class ChartLineData {
		  //
		  //    private String xData;
		  //    private String yData;
		  //}
            ChartLineData data = new ChartLineData();
    		//遍历 List<DateTime> dateTimes 中的时间 设置 x 
            data.setXData(DateUtil.format(dateTime, format));
    	    // map中没有对应时间
            if (map.get(data.getXData()) == null) {
                //y 为0
                data.setYData("0");
            } else {
                //map.get(key) 获取value 设置到 y 中
                data.setYData(map.get(data.getXData()).toString());
            }
            return data;
        }).collect(Collectors.toList());

        return CommonResponse.success(chartLineDataList);

LinkedHashMap插入顺序排序

LinkedHashMap按照插入顺序排序,HashMap基于哈希表乱序

它继承自HashMap,并继承了HashMap百分之八十的功能,剩下的百分之二十的功能则是用于排序。

//创建 LinkedHashMap
Map<String, Object> paramMap = new LinkedHashMap<>();
//bean 转 map 存入
BeanUtil.beanToMap(req, paramMap, false, false);
//创建 StringBuilder
StringBuilder sb = new StringBuilder();
//遍历,按顺序取value
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
    Object value = entry.getValue();
    sb.append(ObjectUtil.isNull(value) ? "" : value).append(",");
}

字符串

对字符串进行修改的时候,特别是字符串对象经常改变的情况下,需要使用 StringBuffer 和 StringBuilder 类

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象

String

  • String是不可变的对象,每次对字符串进行 ++= 操作的时候会产生一个新的String实例。

栈:存放变量(值类型)

堆:存放对象(引用类型)

常量池:它是一个Hash表。 为了提升性能和减少内存开销,避免字符串的重复创建,所以开辟出来一个单独的内存空间,就是字符串池。 字符串常量池是由String类私有的维护。

八大基本类型: byte、short、int、long、boolean、float、double、char 四大引用类型:数组、class、interface、字符串(string)

StringBuffer

  • 方法是线程安全的(不能同步访问)

  • 在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

StringBuilder(建议使用)

  • 方法不是线程安全的(不能同步访问)
  • 由于 StringBuilder 相较于 StringBuffer 有速度优势所以多数情况下建议使用 StringBuilder 类

image-20220624093957546.png 小结:(1)如果要操作少量的数据用 String;

(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;

(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder

substring 返回新字符串

//从 0 开始,到 -1 结束
String str = sb.substring(0, sb.length() - 1);

直接输出 paramStr

//new 一个StringBuilder,可以直接先赋值一个内容
// 再通过append 往paramStr内添加内容,会直接拼接上
        StringBuilder paramStr = new StringBuilder("test");
        paramStr.append("demo");
        System.out.println(paramStr);
//结果:testdemo

reverse() 反转字符串

public static void main(String[] args) {
    StringBuilder s1=new StringBuilder("happy new year!");
    System.out.println(s1);
    System.out.println(s1.reverse());
}
/* 结果
*    happy new year!
*	!raey wen yppah
**/

转为String输出

public static void main(String[] args) {
    StringBuilder s1=new StringBuilder("happy new year!");
    String s2 =s1.toString();
    //将StringBuilder转化为String
    System.out.println(s2);
}

String转化为StringBuilder

public static void main(String[] args) {
    String s1="happy new year!";
    StringBuilder s2=new StringBuilder(s1);
    //将String转化为StringBuilder
    System.out.println(s2);
}

Hutool:StrBuilder

StrBuilder builder = StrBuilder.create();
builder.append("aaa").append("你好").append('r');

toUpperCase() 字符串转大写

toLowerCase() 字符串转小写

Map<String, Object> paramMap = new TreeMap<>();
paramMap.put("testKey","testValue");
paramMap.put("testKey3","testValue3");
paramMap.put("testKey2","testValue2");
paramMap.put("testKey4","testValue4");
StringBuilder str = new StringBuilder("第一个内容");
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
    str.append(entry.getKey()).append(entry.getValue());
}
System.out.println("当前paramMap:"+paramMap);
System.out.println("当前str:"+str);
System.out.println("当前str.toString():"+ str.toString());
System.out.println("当前str.toString()转大写:"+ str.toString().toUpperCase());
System.out.println("当前str.toString()转大写:"+ str.toString().toLowerCase());

StrUtil

splitTrim 切分字符串

uuid() 生成随机UUID

高精度的浮点数运算处理BigDecimal

BigDecimal bigDecimal = new BigDecimal("1")//一般使用BigDecimal,都用字符形式
//另一种形式:
BigDecimal.TEN

Bigdecimal的加减乘除

BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("5");
BigDecimal c = null

//加法
c = a.add(b);
//减法
c = a.subtract(b);
//乘法
c = a.multiply(b);
//除法
c = a.divide(b);
//除b,保留几位数,四舍五入:RoundingMode.CEILING:取右边最近的整数
//					RoundingMode.DOWN:去掉小数部分取整,也就是正数取左边,负数取右边,相当于向原点靠近的方向取整
//					RoundingMode.FLOOR:取左边最近的正数
//					RoundingMode.HALF_DOWN:五舍六入,负数先取绝对值再五舍六入再负数
//					RoundingMode.HALF_UP:四舍五入,负数原理同上
//					RoundingMode.HALF_EVEN:这个比较绕,整数位若是奇数则四舍五入,若是偶数则五舍六入
c = a.divide(b,3,RoundingMode.HALF_UP)

绝对值

在做除法运算时,会出现负数,可使用

c.abs()

去除尾部所有的0

stripTrailingZeros():去除尾部所有的0,并返回一个BigDecimal类型的数据,不能保证不是科学计数法。
后加toString()把BigDecimal类型的数据转化成String类型数据,但还是不能保证不是科学计数法。
后加toPlainString()把BigDecimal类型的数据转化成String类型数据,并保证不是科学计数法。
    
例如:
//输出3E+2 以科学计数法展示且是BigDecimal类型
System.out.println( new BigDecimal("300.0000").stripTrailingZeros());
//输出3E+2 以科学计数法展示且是String类型 
System.out.println( new BigDecimal("300.0000").stripTrailingZeros().toString());
//输出300 String类型 
System.out.println( new BigDecimal("300.0000").stripTrailingZeros().toPlainString());

BigDecimal进行数值比较:compareTo

compareTo()
//与0做比较
a.compareTo(BigDecimal.ZERO);

RedisTemplate缓存

  1. pom依赖
<!--Redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置文件

env.unif.redis.host=127.0.0.1
env.unif.redis.port=6379
env.unif.redis.password=
env.unif.redis.db=3
env.unif.redis.pool.maxActive=100
env.unif.redis.pool.maxWait=1000000
env.unif.redis.pool.maxIdle=10
env.unif.redis.pool.minIdle=0
env.unif.redis.pool.testOnBorrow=true
  1. 使用@Resource注入

@Autowired和@Resource都是用来自动装配bean的。

  • @Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持。
  • @Autowired功能非常强大,但只适用于Spring框架,如果换成了JFinal等其他框架,功能就会失效。
@Resource
private RedisTemplate redisTemplate;
  1. 设置key,STATISTICS_ORDER_MONTH_KEY为调用名称,"statistics:new_order_last_month"为redis中得名称
private static final String STATISTICS_ORDER_MONTH_KEY = "statistics:new_order_last_month";

image-20220818164858967.png 5. 获取缓存

.get(key)

redisTemplate.opsForValue().get(STATISTICS_ORDER_MONTH_KEY)
  1. 设置缓存

    .set(key,value,过期时间,单位)

//key,value,过期时间为当前时间与本月结束时间相差的秒数,单位秒
redisTemplate.opsForValue().set(STATISTICS_ORDER_MONTH_KEY, countLastMonth, DateUtil.between(new Date(), thisEndMonth, DateUnit.SECOND), TimeUnit.SECONDS);

isPresent是否存在

xxxxx.isPresent

杀端口

netstat -ano | findstr 端口号

任务管理器--详细--找到对应的PID--关闭

随机数

RandomUtil.randomNumbers(12);//12位随机数
RandomUtil.random*****(12);//12位随机数

反射的学习

//www.baidu.com/sex="男"&name="xxw"
//sex:"男" name:"xxw"
// ^ 前端传的参数
//使用:
        Student student = new Student();
        student.setSex("男");
        student.setName("xxw");

//原理:
        //拿到Student类
        Class<Student> userClass = Student.class;
        //通过反射实例化对象,内部实际上调用了无参数构造方法,必须保证无参构造存在才可以
        Student student = userClass.newInstance();
        //返回一个Field对象,该对象反映由该class对象表示的类或接口的指定声明字段
        Field name = userClass.getDeclaredField("name");
        Field sex = userClass.getDeclaredField("sex");
        //赋值
        name.set(student, "xxw");
        sex.set(student, "男");
        //获取属性的值
        System.out.println(name.get(student));
        System.out.println(sex.get(student));

mybatis-plus打印sql:

  1. 方法1

依赖:

<!--p6spy-->
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.8.7</version>
</dependency>

配置:

driverClassName=com.p6spy.engine.spy.P6SpyDriver
url=jdbc:p6spy:mysql://127.0.0.1:3306/base_default?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
  1. 方法2
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

spy.properties:

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

异步调用

Spring Boot + @Async 异步调用

异步调用几乎是处理高并发Web应用性能问题的万金油,那么什么是“异步调用”?

“异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序

同步调用

下面通过一个简单示例来直观的理解什么是同步调用:

定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)

@Component
public class Task {

    public static Random random =new Random();

    public void doTaskOne() throws Exception {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskThree() throws Exception {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }

}

在单元测试用例中,注入Task对象,并在测试用例中执行doTaskOne、doTaskTwo、doTaskThree三个函数。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {

    @Autowired
    private Task task;

    @Test
    public void test() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }

}

执行单元测试,可以看到类似如下输出:

开始做任务一
完成任务一,耗时:4256毫秒

开始做任务二
完成任务二,耗时:4957毫秒

开始做任务三
完成任务三,耗时:7173毫秒

任务一、任务二、任务三顺序的执行完了,换言之doTaskOne、doTaskTwo、doTaskThree三个函数顺序的执行完成。

异步调用

上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。

在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,Task类改在为如下模式:

@Component
public class Task {

    @Async
    public void doTaskOne() throws Exception {
        // 同上内容,省略
    }

    @Async
    public void doTaskTwo() throws Exception {
        // 同上内容,省略
    }

    @Async
    public void doTaskThree() throws Exception {
        // 同上内容,省略
    }

}

为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync,如下所示:

@SpringBootApplication
@EnableAsync
public class Application {

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

}

此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:

  • 没有任何任务相关的输出
  • 有部分任务相关的输出
  • 乱序的任务相关的输出

原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。

主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。

注:@Async所修饰的函数不要定义为static类型,这样异步调用不会生效

异步回调

为了让doTaskOne、doTaskTwo、doTaskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。

那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future来返回异步调用的结果,就像如下方式改造doTaskOne函数:

@Async
public Future<String> doTaskOne() throws Exception {
    System.out.println("开始做任务一");
    long start = System.currentTimeMillis();
    Thread.sleep(random.nextInt(10000));
    long end = System.currentTimeMillis();
    System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    return new AsyncResult<>("任务一完成");
}

按照如上方式改造一下其他两个异步函数之后,下面我们改造一下测试用例,让测试在等待完成三个异步调用之后来做一些其他事情。

@Test
public void test() throws Exception {

    long start = System.currentTimeMillis();

    Future<String> task1 = task.doTaskOne();
    Future<String> task2 = task.doTaskTwo();
    Future<String> task3 = task.doTaskThree();

    while(true) {
        if(task1.isDone() && task2.isDone() && task3.isDone()) {
            // 三个任务都调用完成,退出循环等待
            break;
        }

        Thread.sleep(1000);

    }

    long end = System.currentTimeMillis();

    System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}

看看我们做了哪些改变:

  • 在测试用例一开始记录开始时间
  • 在调用三个异步函数的时候,返回Future类型的结果对象
  • 在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。

跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。

执行一下上述的单元测试,可以看到如下结果:

开始做任务一
开始做任务二
开始做任务三

完成任务三,耗时:37毫秒
完成任务二,耗时:3661毫秒
完成任务一,耗时:7149毫秒

任务全部完成,总耗时:8025毫秒

可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间

hutool->ThreadUtil.execAsync()

执行有返回值的异步方法

ThreadUtil.execAsync(() -> {
    //异步执行内容
});

ThreadUtil.execAsync(() -> articleService.saveViews(id));
/**
 * @author Created by xxw on 2022-09-07 13:36
 * @Description 异步增加浏览量
 */
public void saveViews(String id) {
    ThreadUtil.sleep(10000);
    this.lambdaUpdate()
            .setSql("views = views + 1")
            .eq(BaseEntity::getId, id)
            .update();
}

http请求相关

加密解密工具-SecureUtil

对称加密

  • SecureUtil.aes
  • SecureUtil.des

摘要算法

  • SecureUtil.md5
  • SecureUtil.sha1
  • SecureUtil.hmac
  • SecureUtil.hmacMd5
  • SecureUtil.hmacSha1

非对称加密

  • SecureUtil.rsa
  • SecureUtil.dsa

UUID

  • SecureUtil.simpleUUID 方法提供无“-”的UUID

密钥生成

  • SecureUtil.generateKey 针对对称加密生成密钥
  • SecureUtil.generateKeyPair 生成密钥对(用于非对称加密)
  • SecureUtil.generateSignature 生成签名(用于非对称加密)

MD5:

String md5 = SecureUtil.md5(加密内容);

RSA:

对于加密和解密可以完全分开,对于RSA对象,如果只使用公钥或私钥,另一个参数可以为null

//通过私钥公钥生成RSA
RSA rsa = SecureUtil.rsa("私钥","公钥");
或者
RSA rsa = new RSA(null,"公钥");
RSA rsa = new RSA("私钥", null);
//md5私钥加密
byte[] encrypt = rsa.encrypt(md5, KeyType.PrivateKey);
//md5公钥解密
byte[] decrypt = rsa.decrypt(encrypt, KeyType.PublicKey);

//公钥加密
byte[] encrypt = rsa.encrypt(md5, KeyType.PublicKey);
//私钥解密
byte[] decrypt = rsa.decrypt(encrypt, KeyType.PrivateKey);

byte[] 转 16进制 String

byte[] b 为十进制

//转为16进制并去除空格
public static String byte2HexStr(byte[] b) {
    String stmp = "";
    StringBuilder sb = new StringBuilder("");
    for (int n = 0; n < b.length; n++) {
        //Integer.toHexString(int)是将一个整型转成一个十六进制数
        stmp = Integer.toHexString(b[n] & 0xFF);
        sb.append((stmp.length() == 1) ? "0" + stmp : stmp);
    }
    return sb.toString().toUpperCase().trim();
}

BeanUtil

benToMap 将一个Bean对象转为Map对象。

SubPerson person = new SubPerson();
person.setAge(14);
person.setOpenid("11213232");
person.setName("测试A11");
person.setSubName("sub名字");

Map<String, Object> map = BeanUtil.beanToMap(person);

isBean 判定是否是一个Bean对象

根据是否存在只有一个参数的setXXX方法或者public类型的字段来判定是否是一个Bean对象。这样的判定方法主要目的是保证至少有一个setXXX方法用于属性注入。

Http

HttpUtil.toParams

将Map参数转为URL参数字符串 键值对之间自动加上 &

HttpUtil.decodeParams

将URL参数字符串转为Map对象

HTTPS Post请求

带Header消息内容

例:https://117.161.2.75:8009/HttpServices/GetRouteInfo.ashx

拆分为:

private static final String DOMAIN = "https://117.161.2.75:8009";
private static final String URL = "/HttpServices/GetRouteInfo.ashx";
HttpRequest post = HttpUtil.createPost(DOMAIN + URL)//给请求地址
post.header("","");//Header

请求体转json:

String body = JSONUtil.toJsonStr(routerQueryReq);//生成报文

添加body:

post.body(body);

发送:

String res = post.execute().body();

一般的

String res = HttpUtil.post(拼接后的请求地址, 报文);

URLEncodeUtil

.encode 十六进制转换

将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。

完整请求实例

 @Getter
    @AllArgsConstructor
    public enum AopPropertyEnum {
        //入参:xxReq 出参:xxRes
        TRADE_COMMIT("OPC_TradeCommit", "终端销售订单提交", "800000000003", "92015154"),
        SEND_COMMODITY("OPC_SendCommodity", "子订单配货、发货", "800000000003", "92015167"),
        OPC_TAKE_COMMODITY_CONFIRM("OPC_TakeCommodityConfirm", "子订单收货确认", "800000000003", "92015168"),
        QUERY_TRADE_LIST("OPC_QueryUserTradeList", "终端订单列表查询", "100000000240", "92015169"),
        QUERY_TRADE_DETAIL("OPC_QueryUserTradeDetail", "终端订单详情查询", "100000000240", "92015170"),
        TRADE_CANCEL("OPC_TradeCancel", "订单撤销", "800000000003", "92014630"),
        TRADE_COMMIT_REFUND("OPC_TradeCommit", "终端退款/退货订单提交", "800000000003", "92015165"),
        TRADE_COMMIT_CHANGE("OPC_TradeCommit", "终端换货订单提交", "800000000003", "92015166"),
        RETURN_COMMODITY_CONFIRM("OPC_ReturnCommodityConfirm", "子订单退货确认", "800000000003", "92015172"),
        TRADE_REINVOICE("OPC_TradeReinvoice", "订单发票重开", "800000000003", "92015173"),
        QRY_TRADE_INVOICE("OPC_QryTradeInvoice", "订单发票信息查询", "100000000240", "92015174"),
        GET_INVOICE_FILE("AF_GetInvoiceFile", "电子发票文件获取", "800000000003", "92014721"),
        UPDATE_LOGISTICS_INFO("OPC_UpdateLogisticsInfo", "物流信息更新", "800000000003", "92015192"),
        MALL_STOCK_QUERY("POC_MallStockQuery", "商品库存查询", "100000000240", "92006920"),
        MALL_GOODS_LOAD("POC_MallGoodsLoad", "智能终端商品入库", "800000000003", "92015155"),
        TERMINAL_DAMAGED_LOAD("RM_TerminalDamagedLoad", "智能终端设备报损报丢", "800000000003", "92006937"),
        RESULTS_QUERY("PayResultsQuery", "支付结果查询接口", "100000002401", "300000025"),
        ;

        private String name;
        private String businessName;
        private String abilityId;
        private String interfaceId;
    }

public OperationOutRes callAop(AopPropertyEnum type, Map<String, Object> businessParam) {
        //构造URL参数map
        Map<String, Object> paramMap = createParamMap(type);
        //构造报文map
        Map<String, Object> commonMap = createCommonMap(type, businessParam);
        //将Map参数转为URL参数字符串 键值对之间自动加上 `&`
        String urlParamStr = HttpUtil.toParams(paramMap);
        //请求体转json
        String body = JSONUtil.toJsonStr(commonMap);
        log.info("当前调用接口为:{}", type.getBusinessName());
        //URLEncodeUtil.encode(urlParamStr)将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头
        String url = MallSdkConstant.aopUrlDomain + "?" + URLEncodeUtil.encode(urlParamStr);
        log.info("最终请求地址为:{}", url);
        log.info("报文为:{}", body);
        //开始
        TimeInterval timer = DateUtil.timer();
    	//post请求
        String res = HttpUtil.post(url, body);
        //花费毫秒数
        log.info("响应结果为:{},耗时:{}ms", res, timer.interval());
        OperationOutRes operationOutRes;
        try {
            //转bean
            operationOutRes = JSONUtil.toBean(res, OperationOutRes.class);
        } catch (Exception e) {
            log.error("响应结果转bean失败,响应结果;{},原因:", res, e);

            throw UnifI18NException.of("API接口调用失败,请联系开发人员");
        }

        return operationOutRes;
    }


    /**
     * @author Created by zk on 2022-06-22 16:31
     * @Description 构造报文map
     */
    private Map<String, Object> createCommonMap(AopPropertyEnum type, Map<String, Object> businessParam) {
        Date now = new Date();
        Map<String, Object> map = new HashMap<>();
        Map<String, Object> commonMap = new HashMap<>();
        commonMap.put("service_name", type.getName());//
        commonMap.put("verify_code", MallSdkConstant.aopBodyVerifyCode); //验证码,非必填
        commonMap.put("request_type", MallSdkConstant.aopBodyRequestType);//请求类型 先写死,后续看文档
        commonMap.put("sysfunc_id", Long.valueOf(type.getInterfaceId()));//功能代码
        commonMap.put("operator_id", MallSdkConstant.aopBodyOperatorId);//操作员工号 非必填
        commonMap.put("organ_id", MallSdkConstant.aopBodyOrganId);//外部机构号 非必填
        commonMap.put("request_time", DateUtil.format(now, DatePattern.PURE_DATETIME_PATTERN));//请求时间
        commonMap.put("request_seq", System.currentTimeMillis() + RandomUtil.randomNumbers(3));//
        commonMap.put("request_source", MallSdkConstant.aopBodyRequestSource);//请求来源 先写死,后续看文档
        commonMap.put("request_target", MallSdkConstant.aopBodyRequestTarget);//请求来源 非必填 先写死,后续看文档
        commonMap.put("msg_version", MallSdkConstant.aopBodyMsgVersion);
        commonMap.put("cont_version", MallSdkConstant.aopBodyContVersion);
        Map<String, Object> content = new HashMap<>();
        content.put("request", businessParam);
        commonMap.put("content", content);
        map.put("operation_in", commonMap);
        return map;
    }

    public String param(String name) {
        String property = UnifProperties.getProperty(name);
        return property;
    }

    /**
     * @author Created by zk on 2022-06-22 16:31
     * @Description 构造URL参数map
     */
    private Map<String, Object> createParamMap(AopPropertyEnum type) {
        Map<String, Object> paramMap = new TreeMap<>();
        paramMap.put("time_stamp", DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN));
        paramMap.put("format", "json");
        paramMap.put("version", MallSdkConstant.aopUrlVersion);
        paramMap.put("app_id", MallSdkConstant.aopUrlApp_id);
        paramMap.put("ability_id", type.abilityId);
        paramMap.put("interface_id", type.interfaceId);
        paramMap.put("sign_method", "md5");
        paramMap.put("test_flag", "1");
        log.debug("签名前参数:{}", paramMap);
        //创建StringBuilder,开头为secret
        StringBuilder paramStr = new StringBuilder("secret");
        //把map的键值映射成Set集合 遍历
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            //添加key,value
            paramStr.append(entry.getKey()).append(entry.getValue());
        }
        //最后以secret结尾
        paramStr.append("secret");
        log.debug("待签名前生成的字符:{}", paramStr.toString());
        //toUpperCase转大写,生成md5
        String md5 = SecureUtil.md5(paramStr.toString()).toUpperCase();
        log.debug("待签名字符串转MD5:{}", md5);
        //url私钥生成ras私钥
        RSA rsa = SecureUtil.rsa(MallSdkConstant.aopUrlPrivate_key, null);
        //使用md5,私钥方式进行rsa加密
        byte[] encrypt = rsa.encrypt(md5, KeyType.PrivateKey);
        //加密后转为16进制并去除空格
        String rsaStr = byte2HexStr(encrypt);
        //sign
        paramMap.put("sign", rsaStr);
        log.debug("MD5转rsa结果:{}", rsaStr);
        return paramMap;
    }

    //转为16进制并去除空格
    private static String byte2HexStr(byte[] b) {
        String stmp = "";
        StringBuilder sb = new StringBuilder("");
        for (int n = 0; n < b.length; n++) {
            //Integer.toHexString(int)是将一个整型转成一个十六进制数
            stmp = Integer.toHexString(b[n] & 0xFF);
            sb.append((stmp.length() == 1) ? "0" + stmp : stmp);
            // sb.append(" ");
        }
        return sb.toString().toUpperCase().trim();
    }

Stream

Java 8 API添加了一个新的抽象称为流Stream,可以以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

跟Linux的|管道符的思想如出一辙。但是教学代码都是基于String列表进行演示,考虑到实际情况百分之80的时候都是对PO、VO进行处理,因此以下通过一个PO进行讲解。

对比起for循环操作list,最大的弊端就是代码太长太乱了,如果涉及3-4张表的操作,也就是涉及多个PO操作,那个括号简直就是俄罗斯套娃,写到最后真的自己都不知道在写什么。

+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+

PO代码

@Data
public class UserPo {
    private String name;
    private Double score;

    public UserPo(String name, Double score) {
        this.name = name;
        this.score = score;
    }
}

以下操作均以UserPo进行讲解

filter过滤

filter:过滤,就是过滤器,符合条件的通过,不符合条件的过滤掉

// 筛选出成绩不为空的学生人数
count = list.stream().filter(p -> null != p.getScore()).count();

map映射

map:映射,他将原集合映射成为新的集合,在VO、PO处理的过程中较常见。在本例子中,原集合就是PO集合,新集合可以自定义映射为成绩集合,同时也可以对新集合进行相关操作。

// 取出所有学生的成绩
List<Double> scoreList = list.stream().map(p ->p.getScore()).collect(Collectors.toList());
// 将学生姓名集合串成字符串,用逗号分隔
String nameString = list.stream().map(p ->p.getName()).collect(Collectors.joining(","));

sorted排序

sorted:排序,可以根据指定的字段进行排序

// 按学生成绩逆序排序 正序则不需要加.reversed()
filterList = list.stream()
    .filter(p -> null!=p.getScore()).sorted(Comparator.comparing(UserPo::getScore).reversed()).collect(Collectors.toList());

forEach循环

forEach:这个应该是最常用的,也就是为每一个元素进行自定义操作

除了forEach操作会改变原集合的数据,其他的操作均不会改变原集合,这点务必引起注意

// 学生成绩太差了,及格率太低,给每个学生加10分,放个水
// forEach
filterList.stream().forEach(p -> p.setScore(p.getScore() + 10));

collect聚合

collect:聚合,可以用于GroudBy按指定字段分类,也可以用于返回列表或者拼凑字符串

// 按成绩进行归集
Map<Double, List<UserPo>> groupByScoreMap = list.stream()
    .filter(p -> null != p.getScore()).collect(Collectors.groupingBy(UserPo::getScore));
for (Map.Entry<Double, List<UserPo>> entry : groupByScoreMap.entrySet()) {
    System.out.println("成绩:" + entry.getKey() + " 人数:" + entry.getValue().size());
}

// 返回list
List<Double> scoreList = list.stream().map(p -> p.getScore()).collect(Collectors.toList());
// 返回string用逗号分隔
String nameString = list.stream().map(p -> p.getName()).collect(Collectors.joining(","));

statistics统计

summaryStatistics:统计,可以统计中位数,平均值,最大最小值

DoubleSummaryStatistics statistics = filterList.stream().mapToDouble(p -> p.getScore()).summaryStatistics();
System.out.println("列表中最大的数 : " + statistics.getMax());
System.out.println("列表中最小的数 : " + statistics.getMin());
System.out.println("所有数之和 : " + statistics.getSum());
System.out.println("平均数 : " + statistics.getAverage());

parallelStream并行流

parallelStream:并行流,可以利用多线程进行流的操作,提升效率。但是其不具备线程传播性,因此使用时需要充分评估是否需要用并行流操作

// 并行流
count = list.parallelStream().filter(p -> null != p.getScore()).count();

完整代码

@Data
public class UserPo {
    private String name;
    private Double score;

    public UserPo(String name, Double score) {
        this.name = name;
        this.score = score;
    }
}
public class StreamTest {
// +--------------------+ +------+ +------+ +---+ +-------+
// | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
// +--------------------+ +------+ +------+ +---+ +-------+
    public static void main(String args[]){
        List<UserPo> list = new ArrayList<>();
        list.add(new UserPo("小一", 10.d));
        list.add(new UserPo("小五", 50.d));
        list.add(new UserPo("小六", 60.d));
        list.add(new UserPo("小6", 60.d));
        list.add(new UserPo("小空", null));
        list.add(new UserPo("小九", 90.d));

        long count = 0;
        List<UserPo> filterList = null;

        // filter 过滤器的使用
        // 筛选出成绩不为空的学生人数
        count = list.stream().filter(p -> null != p.getScore()).count();
        System.out.println("参加考试的学生人数:" + count);

        // collect
        // 筛选出成绩不为空的学生集合
        filterList = list.stream().filter(p -> null != p.getScore()).collect(Collectors.toList());
        System.out.println("参加考试的学生信息:");
        filterList.stream().forEach(System.out::println);

        // map 将集合映射为另外一个集合
        // 取出所有学生的成绩
        List<Double> scoreList = list.stream().map(p -> p.getScore()).collect(Collectors.toList());
        System.out.println("所有学生的成绩集合:" + scoreList);

        // 将学生姓名集合串成字符串,用逗号分隔
        String nameString = list.stream().map(p -> p.getName()).collect(Collectors.joining(","));
        System.out.println("所有学生的姓名字符串:" + nameString);

        // sorted排序
        // 按学生成绩逆序排序 正序则不需要加.reversed()
        filterList = list.stream()
            .filter(p -> null !=p.getScore())
            .sorted(Comparator.comparing(UserPo::getScore).reversed())
            .collect(Collectors.toList());
        System.out.println("所有学生的成绩集合,逆序排序:");
        filterList.stream().forEach(System.out::println);

        System.out.println("按学生成绩归集:");
        Map<Double, List<UserPo>> groupByScoreMap = list.stream()
            .filter(p -> null != p.getScore())
            .collect(Collectors.groupingBy(UserPo::getScore));
        for (Map.Entry<Double, List<UserPo>> entry : groupByScoreMap.entrySet()) {
            System.out.println("成绩:" + entry.getKey() + " 人数:" + entry.getValue().size());
        }
        
        // forEach
        filterList.stream().forEach(p -> p.setScore(p.getScore() + 10));
        System.out.println("及格人数太少,给每个人加10分");
        filterList.stream().forEach(System.out::println);
        
        // count
        count = filterList.stream().filter(p -> p.getScore() >= 60).count();
        System.out.println("最后及格人数" + count);

        //summaryStatistics统计
        DoubleSummaryStatistics statistics = filterList.stream().mapToDouble(p -> p.getScore()).summaryStatistics();
        System.out.println("列表中最大的数 : " + statistics.getMax());
        System.out.println("列表中最小的数 : " + statistics.getMin());
        System.out.println("所有数之和 : " + statistics.getSum());
        System.out.println("平均数 : " + statistics.getAverage());
       
        // 并行流 使用
        count = list.parallelStream().filter(p -> null != p.getScore()).count();
        System.out.println("并行流处理参加考试的学生人数:" + count);
    }
}