hutools使用
官网:https://hutool.cn/docs/
简单生成验证码
使用hutools的http服务器发送验证码
private static void writeToServlet(){
ICaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100);
// captcha.write(response.getOutputStream());
//Servlet的OutputStream记得自行关闭哦!
HttpUtil.createServer(8088)
.addAction("/", (request,response)->{
captcha.write(response.getOut());
}).start();
}
如果是通过HttpServletResponse发送,修改为:
private static void writeToServlet(HttpServletResponse response){
ICaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100);
captcha.write(response.getOutputStream());
//Servlet的OutputStream记得自行关闭哦!
}
实现简单http服务
写一个简单的登录验证
public class SimpleHttpServer
{
public static void main( String[] args )
{
HttpUtil.createServer(8085)
//根据url跳转页面
.addAction("/", (request,response)->{
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
String url=request.getPath();
if(url.equals("/login")){
ListValueMap<String, String> params = request.getParams();
if(!replace(params.get("name").toString()).equals("lwf")) {
response.getHttpExchange().setAttribute("auth", null);
writer.write(message(300, "用户名不存在"));
}else {
if(!replace(params.get("password").toString()).equals("123")){
response.getHttpExchange().setAttribute("auth", null);
writer.write(message(300, "密码错误"));
}else {
response.getHttpExchange().setAttribute("auth", "lwf");
writer.write(message(200, "登录成功"));
}
}
}else {
if (request.getHttpExchange().getAttribute("auth")!=null&&request.getHttpExchange().getAttribute("auth").equals("lwf")) {
Map<String,Object> msg=new HashMap<>();
msg.put("code", 200);
msg.put("msg", "已登录用户"+request.getHttpExchange().getAttribute("auth"));
writer.write(JSONUtil.toJsonPrettyStr(msg));
}else {
writer.write(message(300, "用户未登录"));
}
}
writer.flush();
writer.close();
})
.start();
}
private static String message(Integer code,String msg){
Map<String,Object> response=new HashMap<>();
response.put("code", code);
response.put("msg", msg);
return JSONUtil.parse(response).toStringPretty();
}
public static String replace(String value){
return value.substring(1, value.lastIndexOf(']'));
}
}
上面的代码是:在8085端口监听,如果url是login就检查用户名密码,否则去首页(需要检查登录凭证)。这个小demo就是用来玩的,因为这个东西是单线程的而且多次访问共享一个凭证(相当于session),只要有一个地方登录成功,其他地方都拥有凭证;
正确密码登录:
首页
换个浏览器,访问首页(不用登录,但是首页会通过,和上面的共享了自定义的凭证;
而且这个action封装的request,response并不是servlet的HttpServletRequest和HttpServletResponse,而是hutools自己弄得,我没有发现可以发cookie的地方,所有要模仿session;
还有实现简单的文件服务器
作为文件服务器根路径
public class FileServer {
public static void main(String[] args) {
//根据url获取文件
HttpUtil.createServer(8889)
// 设置默认根目录,该文件夹下文件访问路径为 localhost:8888/文件名
.setRoot("G:\\Dockerfile\\nginx\\html\\baidu")
.start();
//上传文件到根路径
HttpUtil.createServer(8888)
// 设置默认根目录,该文件夹下文件访问路径为 localhost:8888/文件名
// .setRoot("G:\\Dockerfile\\nginx\\html\\baidu")
.addAction("/file", (request, response) -> {
final UploadFile file = request.getMultipart().getFile("file");
// 传入目录,默认读取HTTP头中的文件名然后创建文件
file.write("G:\\Dockerfile\\nginx\\html\\baidu");
response.write("OK!", ContentType.TEXT_PLAIN.toString());
}
)
.start();
}
}
8889
端口映射本地路径:G:\\Dockerfile\\nginx\\html\\baidu
,该目录下的文件,全路径将G:\\Dockerfile\\nginx\\html\\baidu
替换为http://localhost:8889
上传文件端口为8888
,上传url为file
,且表单提交文件name属性为file
上传成功,访问该图片http://localhost:8889/img1.jfif
,可以看到请求图片成功
http请求
spring boot使用的是RestTemplate
这里演示hutools的Http请求;
public class HttpRequest {
public static void main(String[] args) {
// //链式构建请求,表单数据提交
// String result2 = HttpRequest.post(url)
// .header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
// .form(paramMap)//表单内容
// .timeout(20000)//超时,毫秒
// .execute().body();
// Console.log(result2);
Panda panda=new Panda();
panda.setName("大熊猫");
panda.setPlace("四川");
String result2 = cn.hutool.http.HttpRequest.post("http://localhost:8091/test/post")
.body(JSONUtil.parse(panda).toStringPretty())
.execute().body();
System.out.println("响应数据");
System.out.println(result2);
Reciver bean = JSONUtil.toBean(result2, Reciver.class);
System.out.println(bean.getData());
}
}
控制器:http://localhost:8091/test/post,并接收json数据
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("/post")
public Map<String,Object> test(@RequestBody Panda panda){
Map<String,Object> map=new HashMap<>();
map.put("type", "json");
map.put("data", panda);
map.put("msg", "接受到数据");
return map;
}
}
这里接收到响应,并且将响应的json转为对象。
DFA关键搜索和布隆过滤
DFA
import cn.hutool.dfa.WordTree;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
/**
* DFA检索关键字
*/
public class AppTest
{
WordTree tree=new WordTree();
@Before
public void before(){
tree.addWord("大");
tree.addWord("大土豆");
tree.addWord("土豆");
tree.addWord("刚出锅");
tree.addWord("出锅");
}
/**
* 情况一:标准匹配,匹配到最短关键词,并跳过已经匹配的关键词
*/
@Test
public void shouldAnswerWithTrue()
{
//正文
String text = "我有一颗大土豆,刚出锅的";
// 匹配到【大】,就不再继续匹配了,因此【大土豆】不匹配
// 匹配到【刚出锅】,就跳过这三个字了,因此【出锅】不匹配(由于刚首先被匹配,因此长的被匹配,最短匹配只针对第一个字相同选最短)
List<String> matchAll = tree.matchAll(text, -1, false, false);
Assert.assertEquals(matchAll.toString(), "[大, 土豆, 刚出锅]");
}
/**
* 情况二:匹配到最短关键词,不跳过已经匹配的关键词
*/
@Test
public void two()
{
//正文
String text = "我有一颗大土豆,刚出锅的";
// 【大】被匹配,最短匹配原则【大土豆】被跳过,【土豆继续被匹配】
// 【刚出锅】被匹配,由于不跳过已经匹配的词,【出锅】被匹配
List<String> matchAll = tree.matchAll(text, -1, true, false);
Assert.assertEquals(matchAll.toString(), "[大, 土豆, 刚出锅, 出锅]");
}
/**
* 情况三:匹配到最长关键词,跳过已经匹配的关键词
*/
@Test
public void three()
{
//正文
String text = "我有一颗大土豆,刚出锅的";
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配
// 由于【大土豆】被匹配,【土豆】被跳过,由于【刚出锅】被匹配,【出锅】被跳过
List<String> matchAll = tree.matchAll(text, -1, false, true);
Assert.assertEquals(matchAll.toString(), "[大, 大土豆, 刚出锅]");
}
/**
* 情况四:匹配到最长关键词,不跳过已经匹配的关键词(最全关键词)
*/
@Test
public void four()
{
//正文
String text = "我有一颗大土豆,刚出锅的";
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配,由于不跳过已经匹配的关键词,土豆继续被匹配
// 【刚出锅】被匹配,由于不跳过已经匹配的词,【出锅】被匹配
List<String> matchAll = tree.matchAll(text, -1, true, true);
Assert.assertEquals(matchAll.toString(), "[大, 大土豆, 土豆, 刚出锅, 出锅]");
}
}
我们在before里初始化WrodTree;
tree.addWord(“大”);
tree.addWord(“大土豆”);
tree.addWord(“土豆”);
tree.addWord(“刚出锅”);
tree.addWord(“出锅”);对应的DFA:
布隆过滤
public class BloomFilter {
public static void main(String[] args) {
// 初始化
BitMapBloomFilter filter = new BitMapBloomFilter(10);
filter.add("123");
filter.add("abc");
filter.add("ddd");
// 查找
System.out.println(filter.contains("abc"));
}
}
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。
下面这张图是我在网上找的,这张图的模式可能存在
这里出现的构造方法:
public BitMapBloomFilter(int m) {
long mNum = NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue();
long size = mNum * 1024L * 1024L * 8L;
this.filters = new BloomFilter[]{new DefaultFilter(size), new ELFFilter(size), new JSFilter(size), new PJWFilter(size), new SDBMFilter(size)};
}
这里传入了5个hash处理类,并且使用10*223位bit数组记录hash。布隆过滤器会使用这5个类的hash()方法对加入的每个元素分别求值(作为下标),然后把bit数组相应下标的bit位置为1。(有几个过滤器就有几个这样的数据)
传入元素:filter.add("123");
new DefaultFilter(size):
该类的核心
public long hash(String str) { return (long)HashUtil.javaDefaultHash(str) % this.size; }
调用了:
public static int javaDefaultHash(String str) { int h = 0; int off = 0; int len = str.length(); for(int i = 0; i < len; ++i) { h = 31 * h + str.charAt(off++); } return h; }
HashUtil.javaDefaultHash("abc") % 10 * 1024 * 1024 * 8
结果为:33554432
每个过滤器都维护了一个BitMap
这里的BitMap是用int数组存的,而且默认数组长度很大,并且多个过滤器还不是共享同一个BitMap的(避免多个过滤器间在不同元素的hash碰撞,使用空间弥补准确率,和布隆过滤器原始的模型有优有劣吧。),所以可以见得空间耗费极为巨大,但是与运算很快,效率很高:
public class IntMap implements BitMap, Serializable {
private static final long serialVersionUID = 1L;
private final int[] ints;
public IntMap() {
this.ints = new int[93750000];
}
public IntMap(int size) {
this.ints = new int[size];
}
public void add(long i) {
int r = (int)(i / 32L);
int c = (int)(i % 32L);
this.ints[r] |= 1 << c;
}
public boolean contains(long i) {
int r = (int)(i / 32L);
int c = (int)(i % 32L);
return (this.ints[r] >>> c & 1) == 1;
}
public void remove(long i) {
int r = (int)(i / 32L);
int c = (int)(i % 32L);
int[] var10000 = this.ints;
var10000[r] &= ~(1 << c);
}
}
excel表格从对象数组的导入导出
这个东西对应数据库数据表的导入导出简单了许多;
比如上星期写的数据库表通过控制器导入导出的,开始使用了opi,从网上找的代码,改啊改,表的字段有28个,写了100多行,步骤冗余,每个字段操作差不多却要重复28次。写的代码有臭又长。
EasyPoi(个人觉得并不Easy,很容易出问题)
改进时使用了EasyPoi对表导出,但是导入却一直空指针异常,而且数据库表对应pojo类要在类上和属性上加注解ExcelTarget,Excel。代码如下:
依赖:
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.0.0</version>
</dependency>
pojo类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_employee")
@ApiModel(value="Employee对象", description="")
@ExcelTarget("employee")
public class Employee implements Serializable {
@ApiModelProperty(value = "员工编号")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@Excel(name = "员工姓名")
@ApiModelProperty(value = "员工姓名")
private String name;
@Excel(name="性别")
@ApiModelProperty(value = "性别")
private String gender;
@Excel(name="出生日期")
@ApiModelProperty(value = "出生日期")
private LocalDate birthday;
@Excel(name="身份证号")
@ApiModelProperty(value = "身份证号")
private String idCard;
@Excel(name="婚姻状况")
@ApiModelProperty(value = "婚姻状况")
private String wedlock;
@Excel(name="民族")
@ApiModelProperty(value = "民族")
private Integer nationId;
@Excel(name="籍贯")
@ApiModelProperty(value = "籍贯")
private String nativePlace;
@Excel(name="政治面貌")
@ApiModelProperty(value = "政治面貌")
private Integer politicId;
@Excel(name="邮箱")
@ApiModelProperty(value = "邮箱")
private String email;
@Excel(name="电话号码")
@ApiModelProperty(value = "电话号码")
private String phone;
@Excel(name="联系地址")
@ApiModelProperty(value = "联系地址")
private String address;
@Excel(name="所属部门")
@ApiModelProperty(value = "所属部门")
private Integer departmentId;
@Excel(name="职称ID")
@ApiModelProperty(value = "职称ID")
private Integer jobLevelId;
@Excel(name="职位ID")
@ApiModelProperty(value = "职位ID")
private Integer posId;
@Excel(name="聘用形式")
@ApiModelProperty(value = "聘用形式")
private String engageForm;
@Excel(name="最高学历")
@ApiModelProperty(value = "最高学历")
private String tiptopDegree;
@Excel(name="所属专业")
@ApiModelProperty(value = "所属专业")
private String specialty;
@Excel(name="毕业院校")
@ApiModelProperty(value = "毕业院校")
private String school;
@Excel(name="入职日期")
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@ApiModelProperty(value = "入职日期")
private LocalDate beginDate;
@Excel(name="在职状态")
@ApiModelProperty(value = "在职状态")
private String workState;
@Excel(name="工号")
@ApiModelProperty(value = "工号")
private String workID;
@Excel(name="合同期限")
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@ApiModelProperty(value = "合同期限")
private Double contractTerm;
@Excel(name="转正日期")
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@ApiModelProperty(value = "转正日期")
private LocalDate conversionTime;
@Excel(name="离职日期")
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@ApiModelProperty(value = "离职日期")
private LocalDate notWorkDate;
@Excel(name="合同起始日期")
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@ApiModelProperty(value = "合同起始日期")
private LocalDate beginContract;
@Excel(name="合同终止日期")
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
@ApiModelProperty(value = "合同终止日期")
private LocalDate endContract;
@Excel(name = "工龄")
@ApiModelProperty(value = "工龄")
private Integer workAge;
@Excel(name = "工资账套ID")
@ApiModelProperty(value = "工资账套ID")
private Integer salaryId;
@ApiModelProperty(value = "工资套账")
@TableField(exist = false)
private Salary salary;
@ApiModelProperty(value = "部门")
@TableField(exist = false)
private Department department;
}
控制层:
@ApiOperation("导出表格")
@RequestMapping(value = "/basic/export")
@ResponseBody
public void export(HttpServletResponse response) throws IOException {
List<Employee> employees=employeeService.list();
//参数:(一级标题,二级标题,表名),实体类类对象,导出的集合
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("员工列表", "632宿舍", "计算机学院宿舍信息表"),
Employee.class, employees);
response.setContentType("application/vnd.ms-excel;charset=utf-8");
OutputStream os = response.getOutputStream();
response.setHeader("Content-disposition", "attachment;filename=employee.xls");//默认Excel名称
workbook.write(os);
os.flush();
os.close();
}
很麻烦是不是;
ExcelUtil
这是utools给我们封装的表格工具:
导出
这个方法可以放到工具类里,在控制器中调用,传入数据库传入的对象列表,注入HttpServletResponse就可以了。
/**
* 通过网络导出
* @param list
* @param response
*/
private static void writerToNet(List<?> list, HttpServletResponse response){
// 通过工具类创建writer,默认创建xls格式
ExcelWriter writer = ExcelUtil.getWriter();
// 一次性写出内容,使用默认样式,强制输出标题
writer.write(list, true);
//out为OutputStream,需要写出到的目标流
//response为HttpServletResponse对象
response.setContentType("application/vnd.ms-excel;charset=utf-8");
//test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
response.setHeader("Content-Disposition","attachment;filename=test.xls");
ServletOutputStream out= null;
try {
out = response.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
writer.flush(out, true);
// 关闭writer,释放内存
writer.close();
//此处记得关闭输出Servlet流
IoUtil.close(out);
}
我这里测试使用的hutools的httpServer的写法,懒得导入servlet或者使用springboot 的控制器(这个模块是maven的quackStart,懒得导入那么多依赖);
public static void main(String[] args) throws Exception {
//要写入excel的对象列表
List<Panda> list= CollUtil.list(true);
for(int i=0;i<100;i++){
Panda panda=new Panda();
panda.setName("熊猫"+i);
panda.setPlace("成都");
list.add(panda);
}
//模拟servlet发送表格
HttpUtil.createServer(8088)
.addAction("/", (request,response)->{
// 通过工具类创建writer,默认创建xls格式
ExcelWriter writer = ExcelUtil.getWriter();
// 一次性写出内容,使用默认样式,强制输出标题
writer.write(list, true);
//out为OutputStream,需要写出到的目标流
//response为HttpServletResponse对象
response.setContentType("application/vnd.ms-excel;charset=utf-8");
//test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
response.setHeader("Content-Disposition","attachment;filename=test.xls");
OutputStream out= null;
try {
out = response.getOut();
} catch (Exception e) {
e.printStackTrace();
}
writer.flush(out, true);
// 关闭writer,释放内存
writer.close();
//此处记得关闭输出Servlet流
IoUtil.close(out);
})
.start();
}
访问http://localhost:8088/
下面就是对象列表导出的excel
我们可以看出,在导出非常多字段的表时,EasyPoi需要写非常多注解,而hutools则以不变应万变,只要传入对象数据和HttpServletResponse就可以导出;
导入
public class ImportTable
{
public static void main( String[] args )
{
HttpUtil.createServer(8088)
.addAction("/file", (request, response) -> {
final UploadFile file = request.getMultipart().getFile("file");
//关键代码,就两行
ExcelReader reader = ExcelUtil.getReader(file.getFileInputStream());
reader.readAll(Panda.class).forEach(System.out::println);
}
)
.start();
}
}
是不是很简单,传入一个excel文件的MultiFile就可以了;
//前端文件input传过来的Multipart文件
ExcelReader reader = ExcelUtil.getReader(file.getFileInputStream());
//读出Panda.class的List列表,这里我直接forEach打印了
reader.readAll(Panda.class).forEach(System.out::println);
前端:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<form action="http://localhost:8088/file" enctype="multipart/form-data" method="post">
选择文件:<input name="file" type="file" /><br>
<input type="submit" value="提交文件"/>
</form>
</body>
</html>
时钟调度(spring quartz 和CronUtil)
hutools也写了一套时钟调度;
spring quartZ
配置类:这是固定的,可以定义多个Job和多个触发器
任务执行时间(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *")
)格式:秒 分 时 星期 日 月 年
*:所有
1,12 :1和12
1-5: 1到5
星期:*表示所有 ,可以用“MON-FRI”,“MON,WED,FRI”或甚至“MON-WED,SAT”代替前一个
import com.lwf.quartz.job.MyFirstJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
@Bean
public JobDetail jobDetail1(){
return JobBuilder.newJob(MyFirstJob.class).storeDurably().build();
}
// 每5秒触发⼀次任务
@Bean
public Trigger trigger2(){
return TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
.forJob(jobDetail1())
.build();
}
}
具体调度要执行的任务(类),在excute里写要定时执行的代码;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyFirstJob implements Job {
private Logger log = LoggerFactory.getLogger(MyFirstJob.class);
@Override
public void execute(JobExecutionContext context) throws
JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info(sdf.format(new Date()) + "-->" + "Hello Spring BootQuartz...");
}
}
启动spring boot启动程序,任务就开始执行。
CronUtil
写要执行的任务:继承extends 或者实现Runable,在run里写任务:
我试过上面两种,官方文档没有说清楚,我后来试过,是个方法能用,方法名不是run也可以,只要配置文件中方法全限定路径对了就可以。当然任务方法要非静态的。如果任务方法为静态方法,程序不会报错,但是任务不能执行。
/**
* @author lwf
* @title: ShowTime
* @projectName hutoolstest
* @description: 打印农历
* @date 2020/12/2719:07
*/
public class ShowTime implements Runnable{
public static void showTime(){
ChineseDate date=new ChineseDate(new Date());
System.out.println(date);
}
@Override
public void run() {
showTime();
}
}
配置文件 resources\config\cron.setting里指定要执行的任务:
下面的写法等同:com.lwf.quartz.job.ShowTime.run= */10 * * * * * *
# 类(任务方法所在类)全限定路径
[com.lwf.quartz.job]
#每10秒执行,支持Quarzy 要加CronUtil.setMatchSecond(true); 秒 分 时 星期 日 月 年
ShowTime.run= */10 * * * * * *
任务执行时间:QuartZ的时间写法;
@SpringBootApplication
public class QuartzApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzApplication.class, args);
//hutools时钟任务
CronUtil.start();
//默认使用 分 秒 时 日 月 年
//要使用Quartz格式使用下面代码开启
CronUtil.setMatchSecond(true);
}
}