学无止境,笔记记录长期更新
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 类
小结:(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缓存
- 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
- 使用@Resource注入
@Autowired和@Resource都是用来自动装配bean的。
@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持。@Autowired功能非常强大,但只适用于Spring框架,如果换成了JFinal等其他框架,功能就会失效。
@Resource
private RedisTemplate redisTemplate;
- 设置key,
STATISTICS_ORDER_MONTH_KEY为调用名称,"statistics:new_order_last_month"为redis中得名称
private static final String STATISTICS_ORDER_MONTH_KEY = "statistics:new_order_last_month";
5. 获取缓存
.get(key)
redisTemplate.opsForValue().get(STATISTICS_ORDER_MONTH_KEY)
-
设置缓存
.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
依赖:
<!--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
- 方法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.aesSecureUtil.des
摘要算法
SecureUtil.md5SecureUtil.sha1SecureUtil.hmacSecureUtil.hmacMd5SecureUtil.hmacSha1
非对称加密
SecureUtil.rsaSecureUtil.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);
}
}