首页
壁纸
留言板
友链
更多
统计归档
Search
1
TensorBoard:训练日志及网络结构可视化工具
12,588 阅读
2
主板开机跳线接线图【F_PANEL接线图】
7,037 阅读
3
Linux使用V2Ray 原生客户端
6,152 阅读
4
移动光猫获取超级密码&开启公网ipv6
4,689 阅读
5
NVIDIA 显卡限制功率
3,131 阅读
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
登录
/
注册
Search
标签搜索
好物分享
学习笔记
linux
MySQL
nvidia
typero
内网穿透
webdav
vps
java
cudann
gcc
cuda
树莓派
CNN
图像去雾
ssh安全
nps
暗通道先验
阿里云
jupiter
累计撰写
354
篇文章
累计收到
71
条评论
首页
栏目
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
页面
壁纸
留言板
友链
统计归档
搜索到
354
篇与
的结果
2023-09-17
EasyExcel学习笔记
1.简介1.1 EasyExcel简介Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便2.SpringBoot集成easyexcel2.1 pom依赖<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> </dependency>2.2 简单使用实体类@Data public class ArticleScoreData { @ExcelProperty("姓名") private String name; @ExcelProperty("文章") private String title; @ExcelProperty("得分") private Double score; }读操作待读取article.xlsx姓名文章得分张三张三的文章87李四李四的文章34王五王五的文章99读操作代码@Test public void testRead() { String pathName = "C:\\Users\\jupiter\\Desktop\\article.xlsx"; // PageReadListener:excel一行一行的回调监听器 EasyExcel.read(pathName, ArticleScoreData.class, new PageReadListener<ArticleScoreData>(dataList -> { for (ArticleScoreData demoData : dataList) { log.info("读取到一条数据{}", "姓名:" + demoData.getName() + " 文章:" + demoData.getTitle() + " 得分:" + demoData.getScore()); } })).sheet().doRead(); }运行结果2023-09-14T21:41:36.957+08:00 INFO 81220 --- [ main] c.e.e.EasyExcelStudyApplicationTests : 读取到一条数据姓名:张三 文章:张三的文章 得分:87.0 2023-09-14T21:41:36.962+08:00 INFO 81220 --- [ main] c.e.e.EasyExcelStudyApplicationTests : 读取到一条数据姓名:李四 文章:李四的文章 得分:34.0 2023-09-14T21:41:36.962+08:00 INFO 81220 --- [ main] c.e.e.EasyExcelStudyApplicationTests : 读取到一条数据姓名:王五 文章:王五的文章 得分:99.0写操作代码@Test public void testWrite() { String xlsxPath = "C:\\Users\\jupiter\\Desktop\\output.xls"; List<ArticleScoreData> dataList = new ArrayList<>(); for (int i = 0; i < 5; i++) { ArticleScoreData data = new ArticleScoreData(); data.setName("姓名" + i) data.setTitle("文章" + i); data.setScore(80.0+i); dataList.add(data); } EasyExcel.write(xlsxPath, ArticleScoreData.class) .sheet("文章得分表") .doWrite(() -> dataList); }运行效果output.xls姓名文章得分姓名0文章080姓名1文章181姓名2文章282姓名3文章383姓名4文章4842.3 单独实现最简单的读的监听器进行文件读取待读取article.xlsx姓名文章得分张三张三的文章87李四李四的文章34王五王五的文章99实体类@Data public class ArticleScoreData { @ExcelProperty("姓名") private String name; @ExcelProperty("文章") private String title; @ExcelProperty("得分") private Double score; }SimpleDataListenerimport cn.hutool.json.JSONUtil; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.metadata.data.ReadCellData; import com.alibaba.excel.read.listener.ReadListener; import com.example.easyexcelstudy.domain.entity.ArticleScoreData; import lombok.extern.slf4j.Slf4j; import java.util.Map; @Slf4j public class SimpleDataListener implements ReadListener<ArticleScoreData> { /** * 解析excel的表头-第一行 */ @Override public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) { ReadListener.super.invokeHead(headMap, context); log.info("读取到表头:{}",JSONUtil.toJsonStr(headMap)); } /** * 读取excel的每一行都会调用该方法 */ @Override public void invoke(ArticleScoreData articleScoreData, AnalysisContext analysisContext) { log.info("解析到一条数据:{}", JSONUtil.toJsonStr(articleScoreData)); } /** * 所有数据解析完成了,都会来调用 */ @Override public void doAfterAllAnalysed(AnalysisContext context) { log.info("所有数据解析完成!"); } } test @Test public void testReadBySimpleDataListener() { String xlsxPath = "C:\\Users\\jupiter\\Desktop\\article.xlsx"; EasyExcel.read(xlsxPath,new SimpleDataListener()).sheet().doRead(); } 运行结果2023-09-14T22:39:47.596+08:00 INFO 186196 --- [ main] c.e.e.util.excel.SimpleDataListener : 读取到表头: { "0": { "dataFormatData": { "index": 0, "format": "General" }, "type": "STRING", "stringValue": "姓名", "rowIndex": 0, "columnIndex": 0 }, "1": { "dataFormatData": { "index": 0, "format": "General" }, "type": "STRING", "stringValue": "文章", "rowIndex": 0, "columnIndex": 1 }, "2": { "dataFormatData": { "index": 0, "format": "General" }, "type": "STRING", "stringValue": "得分", "rowIndex": 0, "columnIndex": 2 } } 2023-09-14T22:39:47.689+08:00 INFO 186196 --- [ main] c.e.e.util.excel.SimpleDataListener : 解析到一条数据:{"title":"张三的文章","score":87,"name":"张三"} 2023-09-14T22:39:47.690+08:00 INFO 186196 --- [ main] c.e.e.util.excel.SimpleDataListener : 解析到一条数据:{"title":"李四的文章","score":34,"name":"李四"} 2023-09-14T22:39:47.690+08:00 INFO 186196 --- [ main] c.e.e.util.excel.SimpleDataListener : 解析到一条数据:{"title":"王五的文章","score":99,"name":"王五"} 2023-09-14T22:39:47.691+08:00 INFO 186196 --- [ main] c.e.e.util.excel.SimpleDataListener : 所有数据解析完成!2.4 (★★★)读取超级版本:无需实体类,实现任意excel文件的读取待读取excel文件sheet1sheet2正常情况2 表头1表头2表头3表头4表头5表头1表头2表头3表头4表头5列头1 列头2 列头3 ExcelSheetDataReadListenerpackage com.example.excelstudy.utils.excel; import cn.hutool.json.JSONUtil; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.enums.CellExtraTypeEnum; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.metadata.CellExtra; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @author LuoJia * @version 1.0 * @description: 万能excel的单个sheet读取Listener * @date 2023/9/15 11:24 */ @Slf4j public class ExcelSheetDataReadListener extends AnalysisEventListener<Map<Integer,String>> { // 表格sheet编号 int sheetNo; // 表格行数 int rowCount=0; // 表格列数 int colCount=0; // 用于存储原生读取到的数据 List<Map<Integer, String>> lineDataList = new ArrayList<>(); // 用于存储表格的合并单元格的区域列表 List<CellExtra> mergeAreaList = new ArrayList<>(); /** * 解析excel的表头-即读取第一行 */ @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { // 设置不忽略空行 context.readWorkbookHolder().setIgnoreEmptyRow(false); // 获取sheetNo sheetNo = context.readSheetHolder().getSheetNo(); // 更新表格行列数 rowCount += 1; colCount = Math.max(colCount, headMap.size()); // 保存原始的单行数据 lineDataList.add(headMap); //log.info("读取到表头:{}", JSONUtil.toJsonStr(headMap)); } /** * 读取excel的每一行都会调用该方法 */ @Override public void invoke(Map<Integer,String> lineData, AnalysisContext context) { // 更新表格行列数 rowCount += 1; colCount = Math.max(colCount, lineData.size()); // 保存原始的单行数据 lineDataList.add(lineData); //log.info("解析到一条数据:{}", JSONUtil.toJsonStr(lineData)); } /** * 获取合并单元格的范围 */ @Override public void extra(CellExtra extra, AnalysisContext context) { if (extra.getType() != CellExtraTypeEnum.MERGE) { return ; } mergeAreaList.add(extra); } /** * 所有数据解析完成了,都会来调用 */ @Override public void doAfterAllAnalysed(AnalysisContext context) { log.info("============================================================="); log.info("sheet{}-所有数据解析完成!sheet总行数:{},总列数:{}",sheetNo+1,rowCount,colCount); // 处理mergerList--即处理所有的合并单元格 for(CellExtra mergeArea:mergeAreaList){ // 获取填充部分的单元格的有效值 String value = lineDataList.get(mergeArea.getFirstRowIndex()).get(mergeArea.getFirstColumnIndex()); // 对合并单元格的为null值部分的数据进行有效填充 for (int i = mergeArea.getFirstRowIndex(); i <= mergeArea.getLastRowIndex(); i++) { for (int j = mergeArea.getFirstColumnIndex(); j <= mergeArea.getLastColumnIndex(); j++) { // 合并单元格的最最左上角已经被有效填充了,跳过 if(i==mergeArea.getFirstRowIndex()&&j== mergeArea.getFirstColumnIndex()){ continue; } // 对合并单元格的其他单元格进行数据填充 lineDataList.get(i).put(j,value); } } } // 打印表格数据 for (int i = 0; i < rowCount; i++) { log.info("sheet第{}行数据:{}",(i+1),JSONUtil.toJsonStr(lineDataList.get(i))); } log.info("============================================================="); } }test @Test public void testRead() throws FileNotFoundException { String pathName = "C:\\Users\\LuoJia\\Desktop\\test.xlsx"; InputStream inputStream = new FileInputStream(new File(pathName)); // 创建excel读取reader ExcelReader excelReader = EasyExcel.read(inputStream).extraRead(CellExtraTypeEnum.MERGE).ignoreEmptyRow(false).build(); // 创建每个sheet的读取listener并执行读取 List<ReadSheet> readSheets = excelReader.excelExecutor().sheetList(); List<ExcelSheetDataReadListener> listenerList = new ArrayList<>(readSheets.size()); // 用于进行数据和合并单元格区域保存 //读取多个sheet List<ReadSheet> sheetList = readSheets.stream().map(sheet -> { ExcelSheetDataReadListener listener = new ExcelSheetDataReadListener(); ReadSheet readSheet = EasyExcel.readSheet(sheet.getSheetName()).registerReadListener(listener).build(); listenerList.add(listener); return readSheet; }).collect(Collectors.toList()); excelReader.read(sheetList); // 释放资源 excelReader.finish(); }运行结果xxxx: ============================================================= xxxx: sheet1-所有数据解析完成!sheet总行数:10,总列数:6 xxxx: sheet第1行数据:{} xxxx: sheet第2行数据:{"0":"异常情况","1":"异常情况","2":"异常情况","3":"异常情况","4":"异常情况"} xxxx: sheet第3行数据:{"0":"表头1","1":"表头2","2":"表头3","3":"表头4","4":"表头5"} xxxx: sheet第4行数据:{"0":"表头1","1":"表头2","2":"表头3","3":"表头4","4":"表头5"} xxxx: sheet第5行数据:{} xxxx: sheet第6行数据:{} xxxx: sheet第7行数据:{"3":"dasdada","4":"dasdada"} xxxx: sheet第8行数据:{"3":"dasdada","4":"dasdada"} xxxx: sheet第9行数据:{} xxxx: sheet第10行数据:{"5":"saSASA"} xxxx: ============================================================= xxxx: ============================================================= xxxx: sheet2-所有数据解析完成!sheet总行数:2,总列数:5 xxxx: sheet第1行数据:{"0":"正常情况1","1":"正常情况1","2":"正常情况1","3":"正常情况1","4":"正常情况1"} xxxx: sheet第2行数据:{"0":"表头1","1":"表头2","2":"表头3","3":"表头4","4":"表头5"} xxxx: ============================================================= xxxx: ============================================================= xxxx: sheet3-所有数据解析完成!sheet总行数:5,总列数:5 xxxx: sheet第1行数据:{"0":"正常情况1","1":"正常情况1","2":"正常情况1","3":"正常情况1","4":"正常情况1"} xxxx: sheet第2行数据:{"0":"表头1","1":"表头2","2":"表头3","3":"表头4","4":"表头5"} xxxx: sheet第3行数据:{"0":"列头1"} xxxx: sheet第4行数据:{"0":"列头2"} xxxx: sheet第5行数据:{"0":"列头3"} xxxx: =============================================================2.5(★★★)带合并单元格的写入ExcelCustomMergeHandler(处理单元格合并的handler) /** * @author jupiter * @version 1.0 * @description: TODO * @date 2023/9/16 23:01 */ @Data @NoArgsConstructor @AllArgsConstructor @Slf4j public class ExcelCustomMergeHandler implements CellWriteHandler { // 表格行数 int rowCount; // 表格列数 int colCount; // 用于存储表格的合并单元格的区域列表 List<CellExtra> mergeAreaList = new ArrayList<>(); /** * @description: 在单元格被创建之前的处理 * @author jupiter * @date: 2023/9/16 23:12 */ @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { CellWriteHandler.super.beforeCellCreate(writeSheetHolder, writeTableHolder, row, head, columnIndex, relativeRowIndex, isHead); } /** * @description: 在单元格被创建之后的处理 * @author jupiter * @date: 2023/9/16 23:12 */ @Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { CellWriteHandler.super.afterCellCreate(writeSheetHolder, writeTableHolder, cell, head, relativeRowIndex, isHead); } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 获取当前的单元格 Sheet sheet = writeSheetHolder.getSheet(); // 设置单元格居中 CellStyle cellStyle = cell.getCellStyle(); cellStyle.setAlignment(HorizontalAlignment.CENTER); //log.info("当前处理的单元格序号:{},{}",cell.getRowIndex(),cell.getColumnIndex()); // 在最后一个单元格处理单元格合并 if(cell.getRowIndex()==rowCount-1&&cell.getColumnIndex()==colCount-1){ for(CellExtra mergeArea:mergeAreaList){ CellRangeAddress cellAddresses = new CellRangeAddress(mergeArea.getFirstRowIndex(),mergeArea.getLastRowIndex(),mergeArea.getFirstColumnIndex(),mergeArea.getLastColumnIndex()); log.info("写入添加合并区域:{}", JSONUtil.toJsonStr(mergeArea)); sheet.addMergedRegion(cellAddresses); } } } }test(这里为了避免构建数据直接衔接了2.4用的读取后的数据)@Test public void testCustomWrite() throws FileNotFoundException { String pathName = "C:\\Users\\jupiter\\Desktop\\test.xlsx"; // 创建excel读取reader ExcelReader excelReader = EasyExcel.read(pathName).extraRead(CellExtraTypeEnum.MERGE).ignoreEmptyRow(false).build(); // 创建每个sheet的读取listener并执行读取 List<ReadSheet> readSheets = excelReader.excelExecutor().sheetList(); List<ExcelSheetDataReadListener> listenerList = new ArrayList<>(readSheets.size()); // 用于进行数据和合并单元格区域保存 //读取多个sheet List<ReadSheet> sheetList = readSheets.stream().map(sheet -> { ExcelSheetDataReadListener listener = new ExcelSheetDataReadListener(); ReadSheet readSheet = EasyExcel.readSheet(sheet.getSheetName()).registerReadListener(listener).build(); listenerList.add(listener); return readSheet; }).collect(Collectors.toList()); excelReader.read(sheetList); // 释放资源 excelReader.finish(); // 开始执行excel写入 pathName = "C:\\Users\\jupiter\\Desktop\\testWrite.xlsx"; // 创建excel写入writer ExcelWriter excelWriter = EasyExcel.write(pathName).build(); // 写入多个sheetList for (int i = 0; i < sheetList.size(); i++) { // 单元格总行数 int rowCount = listenerList.get(i).getRowCount(); // 单元格总列数 int colCount = listenerList.get(i).getColCount(); // sheet的逐行数据 List<Map<Integer, String>> lineDataList = listenerList.get(i).getLineDataList(); // 需要合并的单元格区域 List<CellExtra> mergeAreaList = listenerList.get(i).getMergeAreaList(); // 处理单元格合并的handle ExcelCustomMergeHandler writeHandle = new ExcelCustomMergeHandler(rowCount,colCount,mergeAreaList); // 构建sheet写入对象 WriteSheet writeSheet = EasyExcel.writerSheet(i,sheetList.get(i).getSheetName()).registerWriteHandler(writeHandle).build(); // 执行sheet数据写入 excelWriter.write(lineDataList,writeSheet); } // 释放资源 excelWriter.finish(); }执行效果test.xlsxsheet1sheet2testWrite.xlsxsheet1sheet2参考资料EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)EasyExcel全面教程快速上手_easeexcel_知春秋的博客-CSDN博客解决EasyExcel工具读取Excel空数据行的问题_easyexcel空行导入问题_流沙QS的博客-CSDN博客EasyExcel导入(含表头验证+空白行读取)_easyexcel导入表头校验_MMO_的博客-CSDN博客阿里的easyExcel_easyexcel aftercelldispose_一直想成为大神的菜鸟的博客-CSDN博客easyexcel导出中自定义合并单元格,通过重写AbstractRowWriteHandler_easyexcel合并单元格策略_阿莫西林的博客-CSDN博客EasyExcel导出自定义合并单元格的策略_我可能在扯淡的博客-CSDN博客
2023年09月17日
251 阅读
0 评论
0 点赞
2023-09-16
idea配置优化-默认maven/自动导包/方法类注释模板
1.配置默认mavenSTEP1:配置本项目File-->Setting配置所有新项目File-->New Projects Setup-->Setting for New Projects...STEP2:2.配置自动导包STEP1:配置本项目File-->Setting配置所有新项目File-->New Projects Setup-->Setting for New Projects...STEP2:3.配置注释3.1 新建类的时候自动添加类注释STEP1:File-->SettingSTEP2:如上图所示添加注释:/** * @description: TODO * @author ${USER} * @date ${DATE} ${TIME} * @version 1.0 */给接口和枚举加上的方式同理。3.2 自定义模版配置(类,方法)按照上图的提示,找到位置1的Live Templates找到位置2,选择下拉框中的Enter选项到位置3点击“+”号,首先选择Template Group,新建一个自己的分组鼠标选中新建的分组,如位置4的ybyGroup,然后在点击位置3的“+”号,选择Live Template给模版添加快捷提示的字符,描述,和模版,比如我这里新增了两个,方法的注释,类注释*在位置5处的Template text里面贴上模版内容在位置6选择应用的范围,一般选择EveryWhere里面的Java就可以了在位置7配置Template Text里面用$修饰的属性,具体配置截图如下:params的default value:groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {result+='' + params[i] + ((i < params.size() - 1) ? '\\n':'')}; return result", methodParameters())方法注释模版:** * @description: TODO * @param: $params$ * @return: $returns$ * @author $USER$ * @date: $date$ $time$ */ 类注释模版:** * @description: TODO * @author $user$ * @date $date$ $time$ * @version 1.0 */参考资料idea注释模版配置(吐血推荐!!!)_ida注释模板_骑着乌龟漫步的博客-CSDN博客IDEA设置方法注释模板_idea设置注释模板_布丁吖的博客-CSDN博客
2023年09月16日
90 阅读
1 评论
0 点赞
2023-09-05
JWT(JSON Web Token)学习笔记
1.简介1.1 什么是JWT?官网地址: https://jwt.io/introduction/定义: Json Web Token(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。 jwt可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名通俗解释:JSON Web Token,简称JWT,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。1.2 JWT具体的作用/什么时候使用 JWT ?授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用。信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确定发送方就是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。1.3 为什么选择了使用JWT1.3.1 基于传统的 Session 认证认证过程http协议本身是一种无状态的协议,这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,即使验证通过后,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。存在的问题每个用户经过应用认证之后,应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。在前后端分离系统中就更加痛苦,前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session 每次携带sessionid 到服务器,服务器还要查询用户信息。同时如果用户很多,这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF攻击(跨站伪造请求攻击),session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是sessionid就是一个特征值,表达的信息不够丰富,不容易扩展。如果你后端应用是多节点部署。那么就需要实现session共享机制。集群应用的搭建将变得十分困难。1.3.2 基于JWT认证基于JWT认证首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同xxxxx.yyyyy.zzzzz的字符串, token = head.payload.singurater后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。jwt的优点简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,由于其数据量小,并不会占用过多带宽自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库,减轻了数据库的压力因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。不需要在服务端保存会话信息,特别适用于分布式微服务。2.JWT的结构JWT 由三部分组成2.1 Header(头部)是一个JSON 对象, 描述JWT的元数据,eg:# alg是签名算法,默认是HS256, # typ是token类型,一般JWT默认为JWT { "alg": "HS256", "typ": "JWT" }2.2 载荷-payload是一个JSON 对象, 用来存放实际需要传递的数据,eg:{ "iss": "songshu", "sub": "1234567890", "name": "haha", "admin": true }其中payload官方规定了7个字段:iss (issuer):签发人、 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号$\color{red}{ 注意:对于已签名的令牌,此信息虽然受到保护以防止篡改,但任何人都可以读取。不要将机密信息放在 JWT 的有效负载或标头元素中,除非它已加密。}$2.3 签名(Signature)signature是对前两部分的签名,防止数据篡改。需要指定一个密钥(secret)这个密钥只有服务器才知道,不能泄露给客户端使用 Header 里面指定的签名算法(例如,如果要使用 HMAC SHA256 算法),按照下面的公式产生签名:HMACSHA256( base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)把 Header、Payload、Signature 三个部分拼成一个字符串:Header.Payload.Signature = xxxx.yyy.zzz3.使用JWT3.1 引入jwt依赖<!--引入jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>3.2 使用测试生成JWT令牌@Test public void testGetJWTToken() { Calendar instance = Calendar.getInstance(); instance.add(Calendar.SECOND, 900);//Calendar.SECOND代表单位为秒 //生成令牌 String token = JWT.create() .withClaim("username", "李四")//设置payload,保存自定义用户名,可设置多个 .withExpiresAt(instance.getTime())//设置过期时间 .sign(Algorithm.HMAC256("jupiter"));//设置加密算法及签名密钥,这里使用了默认的HMAC256 //输出令牌 System.out.println(token); }eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTM4OTcyOTUsInVzZXJuYW1lIjoi5p2O5ZubIn0.vO3kvqrFx4AlEs1qnFP7ghFeCIUAXFod4lH3SLhc5RE验证JWT令牌@Test public void testDecodeJWTToken(){ String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTM4OTcyOTUsInVzZXJuYW1lIjoi5p2O5ZubIn0.vO3kvqrFx4AlEs1qnFP7ghFeCIUAXFod4lH3SLhc5RE"; JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("jupiter")).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token);//验证token System.out.println("用户名: " + decodedJWT.getClaim("username").asString()); // 存的是时候是什么类型,取得时候就是什么类型,否则取出来的是null。 System.out.println("过期时间: "+decodedJWT.getExpiresAt()); }用户名: 李四 过期时间: Tue Sep 05 15:01:35 CST 20233.3 验证过程中可能会出现的异常SignatureVerificationException: 签名不一致异常:系统中并未签发过该token,一般是恶意用户伪造出来的 TokenExpiredException: 令牌过期异常 AlgorithmMismatchException: 算法不匹配异常:签名算法和验签算法不一致 InvalidClaimException: 失效的payload异常:token无效3.4 封装工具类public class JWTUtils { private static final String SIGNATURE="jupiter";//加密密钥 /** * 生成token header.payload.signatureResult * @param map : 需要保存的payload,也就是用户信息 * @return */ public static String getToken(Map<String,String> map){ Calendar instance = Calendar.getInstance(); instance.add(Calendar.MINUTE,4320);//3天过期 JWTCreator.Builder builder = JWT.create(); //设置payload map.forEach((k,v)->{ builder.withClaim(k,v); }); //设置过期时间 builder.withExpiresAt(instance.getTime()); //设置加密算法和加密密钥 String token = builder.sign(Algorithm.HMAC256(SIGNATURE)); return token; } /** * 验证token的合法性,若验证不通过,直接抛出异常 * @param token */ public static DecodedJWT verify(String token){ return JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token); } }4.SpringBoot项目整合使用在实际场景当中,我们需要对用户每一次的请求做验证,如果将每次验证的代码都要写一遍,这将造成大量代码的冗余,因此采用springmvc当中的拦截器InterceptorJWTInterceptorpublic class JWTInterceptor implements HandlerInterceptor { /** * 这里我们只需要实现请求之前的验证即可,因此只要实现preHandle()方法即可 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //一般我们要求将token放在请求的header中,所以这里我们从request中拿token String token = request.getHeader("token"); HashMap<Object, Object> map = new HashMap<>(); try { JWTUtils.verify(token); return true;//验证通过,放行 }catch (SignatureVerificationException e){ e.printStackTrace(); map.put("msg","签名错误"); }catch (AlgorithmMismatchException e){ e.printStackTrace(); map.put("msg","加密算法不一致"); }catch (TokenExpiredException e){ e.printStackTrace(); map.put("msg","token已过期"); }catch (Exception e) { e.printStackTrace(); map.put("msg","token无效"); } map.put("state",false);//设置状态 //这里返回json格式的map,只能手动将map处理成json 使用jackson String json = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); return false; } }然后对拦截器进行配置JWTInterceptorConfig@Configuration public class JWTInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JWTInterceptor()) .addPathPatterns("/**")//拦截所有请求 .excludePathPatterns("/user/login");//放行登录请求 } }参考资料JWT的学习笔记_algorithmmismatchexception 算法不匹配是什么原因_'小竹子'的博客-CSDN博客JWT(JSON Web Token) 学习笔记(整合Spring Boot)_jwt.getclaims_songshu。的博客-CSDN博客JWT 的结构详解_jwt结构_荆茗Scaler的博客-CSDN博客
2023年09月05日
31 阅读
0 评论
0 点赞
2023-09-05
JVM GC日志输出配置
1.命令格式java <GC日志参数> -jar <your_application.jar>2. JDK8 具体的GC日志参数基本(必备)JVM配置描述-Xloggc:/path/to/gc.log写入 GC 日志的路径-XX:+UseGCLogFileRotation启用 GC 日志文件轮换-XX:NumberOfGCLogFiles=5要保留的轮换 GC 日志文件数-XX:GCLogFileSize=104857600用于启动轮换的每个 GC 日志文件的大小-XX:+PrintGCDetails详细的GC日志-XX:+PrintGCDateStamps实际日期和时间戳-XX:+PrintGCApplicationStoppedTime应用程序在 GC 期间停止的时间量-XX:+PrintGCApplicationConcurrentTime应用程序在 GC 之间运行的时间量-XX:-PrintCommandLineFlags打印 GC 日志中的所有命令行标志增强JVM配置描述-XX:+PrintAdaptiveSizePolicy有关GC工程的详细信息-XX:+PrintTenuringDistribution幸存者空间的使用和分配-XX:+PrintReferenceGC处理引用所花费的时间3.JDK17具体的GC日志参数基本(必备)-Xlog参数\JVM配置描述:file=/opt/gc-%t.log写入 GC 日志的路径,%t表示当前时间:filesize=104857600,filecount=5启用日志分割,保留分割 GC 日志文件数+单个GC日志文件的大小<br/>超过了限制将会执行循环写入,先进先出式写入gc*详细的GC日志level,tags,time,uptime,pid实际日期和时间戳 与关键信息safepoint应用程序在 GC 期间停止的时间量-XX:-PrintCommandLineFlags打印 GC 日志中的所有命令行标志java -Xlog:gc*,safepoint:file=gc-%t.log:level,tags,time,uptime,pid:filesize=104857600,filecount=5 -jar <your_application.jar> 增强-Xlog参数\JVM配置描述gc+ergo*=trace有关GC工程的详细信息gc+age=trace幸存者空间的使用和分配gc+phases*=trace处理引用所花费的时间java -Xlog:gc*,safepoint,gc+ergo*=trace,gc+age=trace,gc+phases*=trace:file=gc-%t.log:level,tags,time,uptime,pid:filesize=104857600,filecount=5 -jar <your_application.jar>参考资料Java中的GC(垃圾回收)log ,以及 JVM 介绍_gc java命令_sun0322的博客-CSDN博客Java GC算法——日志解读与分析(GC参数基础配置分析)-腾讯云开发者社区-腾讯云 (tencent.com)JVM 配置GC日志_jvm打印gc日志_Coco_淳的博客-CSDN博客一篇带你搞定⭐《生产环境JVM日志配置》⭐_不学会Ⅳ的博客-CSDN博客
2023年09月05日
112 阅读
0 评论
0 点赞
2023-09-01
SpringBoot集成参数校验@Validated学习笔记|内含SpringBoot全局异常处理
1.为什么需要参数校验在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验,例如登录的时候需要校验用户名密码是否为空,创建用户的时候需要校验邮件、手机号码格式是否准确。靠代码对接口参数一个个校验的话就太繁琐了,代码可读性极差。Validator框架就是为了解决开发人员在开发的时候少写代码,提升开发效率;Validator专门用来进行接口参数校验,例如常见的必填校验,email格式校验,用户名必须位于6到12之间 等等...Validator校验框架遵循了JSR-303验证规范(参数校验规范), JSR是Java Specification Requests的缩写。2.SpringBoot中集成参数校验2.1 添加依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>注:从springboot-2.3开始,校验包被独立成了一个starter组件,所以需要引入validation和web,而springboot-2.3之前的版本只需要引入 web 依赖就可以了。2.2 定义要参数校验的实体类在实际开发中对于需要校验的字段都需要设置对应的业务提示,即message属性。常见的约束注解如下:注解功能@AssertFalse可以为null,如果不为null的话必须为false@AssertTrue可以为null,如果不为null的话必须为true@DecimalMax设置不能超过最大值@DecimalMin设置不能超过最小值@Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范围内@Future日期必须在当前日期的未来@Past日期必须在当前日期的过去@Max最大不得超过此最大值@Min最大不得小于此最小值@NotNull不能为null,可以是空@Null必须为null@Pattern必须满足指定的正则表达式@Size集合、数组、map等的size()值必须在指定范围内@Email必须是email格式@Length长度必须在指定范围内@NotBlank字符串不能为null,字符串trim()后也不能等于“”@NotEmpty不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“”@Range值必须在指定范围内@URL必须是一个URL@Data public class ValidVO { private String id; @Length(min = 6,max = 12,message = "appId长度必须位于6到12之间") private String appId; @NotBlank(message = "名字为必填项") private String name; @Email(message = "请填写正确的邮箱地址") private String email; private String sex; @NotEmpty(message = "级别不能为空") private String level; }2.3 定义Controller类进行测试$\color{red}{注意,当使用单参数校验时需要在Controller上加上@Validated注解,否则不生效。}$@RestController @Slf4j @Validated public class ValidController { /** * RequestBody校验,使用了@RequestBody注解,用于接受前端发送的json数据 * @param validVO * @return */ @PostMapping("/valid/test1") public String test1(@Validated @RequestBody ValidVO validVO){ log.info("validEntity is {}", validVO); return "test1 valid success"; } /** * Form校验,模拟表单提交 * @param validVO * @return */ @PostMapping(value = "/valid/test2") public String test2(@Validated ValidVO validVO){ log.info("validEntity is {}", validVO); return "test2 valid success"; } /** * 单参数校验,模拟单参数提交,注意,当使用单参数校验时需要在Controller上加上@Validated注解,否则不生效。 * @param email * @return */ @PostMapping(value = "/valid/test3") public String test3(@Email String email){ log.info("email is {}", email); return "email valid success"; } }2.4 调用测试2.4.1 test1请求参数POST http://localhost:8080/valid/test1 Content-Type: application/json { "id": 1, "appId": "add3", "email": "3131243242", "level": "12" }返回结果{ "timestamp": "2023-09-01T02:10:41.310+00:00", "status": 400, "error": "Bad Request", "path": "/valid/test1" }控制台输出2023-09-01T10:10:41.310+08:00 WARN 9016 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.validatedstudy.domain.controller.ValidController.test1(com.example.validatedstudy.domain.vo.ValidVO) with 3 errors: [Field error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; default message [名字为必填项]] [Field error in object 'validVO' on field 'email': rejected value [3131243242]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@4115d833,.*]; default message [请填写正确的邮箱地址]] [Field error in object 'validVO' on field 'appId': rejected value [add3]; codes [Length.validVO.appId,Length.appId,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.appId,appId]; arguments []; default message [appId],12,6]; default message [appId长度必须位于6到12之间]] ]2.4.2 test2请求参数POST http://localhost:8080/valid/test2 Content-Type: application/x-www-form-urlencoded id=1&level=12&email=21434242341&appId=dsad返回结果{ "timestamp": "2023-09-01T02:13:52.296+00:00", "status": 400, "error": "Bad Request", "path": "/valid/test2" }控制台输出2023-09-01T10:14:16.059+08:00 WARN 9016 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.validatedstudy.domain.controller.ValidController.test2(com.example.validatedstudy.domain.vo.ValidVO) with 3 errors: [Field error in object 'validVO' on field 'email': rejected value [21434242341]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@4115d833,.*]; default message [请填写正确的邮箱地址]] [Field error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; default message [名字为必填项]] [Field error in object 'validVO' on field 'appId': rejected value [dsad]; codes [Length.validVO.appId,Length.appId,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.appId,appId]; arguments []; default message [appId],12,6]; default message [appId长度必须位于6到12之间]] ]2.4.3 test3请求参数POST http://localhost:8080/valid/test3 Content-Type: application/x-www-form-urlencoded email=476938977返回结果{ "timestamp": "2023-09-01T01:46:03.227+00:00", "status": 500, "error": "Internal Server Error", "path": "/valid/test3" }控制台输出akarta.validation.ConstraintViolationException: test3.email: 不是一个合法的电子邮件地址 at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:138) ~[spring-context-6.0.10.jar:6.0.10] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.10.jar:6.0.10] at org. ······2.5 增加全局异常处理(★★★)2.5.1 代码实现vopackage com.example.validatedstudy.domain.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class ResultDataVO<T> { // 状态码 private int code; // 错误消息 private String errorMsg; // 消息体数据 private T data; /** * 返回默认的调用成功的响应 */ public static <T> ResultDataVO<T> success(){ ResultDataVO<T> resultDataVO = new ResultDataVO<>(); resultDataVO.setCode(200); return resultDataVO; } /** * 调用成功返回T类型的对象数据响应 * @param data */ public static <T> ResultDataVO<T> success(T data){ return new ResultDataVO<T>(200,"",data); } /** * 返回默认的调用失败的响应 */ public static <T> ResultDataVO<T> error( ){ return error(400, "操作失败"); } /** * 返回带msg的调用失败的响应 */ public static <T> ResultDataVO<T> error( String msg){ return error(400, msg); } /** * 返回指定code带msg的调用失败的响应 */ public static <T> ResultDataVO<T> error(int code,String msg){ return new ResultDataVO<>(code,msg,null); } }exception/** * 全局异常处理类 */ @RestControllerAdvice @Slf4j public class GlobalExceptionHandler{ /** * 处理所有不可知的异常 * @param e * @return */ @ExceptionHandler(RuntimeException.class) @ResponseBody public ResultDataVO handle(Exception e) { log.error("系统未知异常>>>:" + e.getMessage(), e); return ResultDataVO.error(e.getMessage()); } /** * 处理参数对象javax注解异常 */ @ResponseBody @ExceptionHandler(MethodArgumentNotValidException.class) public ResultDataVO<Object> exceptionHandler(MethodArgumentNotValidException e) { log.error("参数错误>>>:" + e.getMessage(), e); return ResultDataVO.error( e.getBindingResult().getFieldError().getDefaultMessage()); } /** * 处理controller的@Validated注解异常 */ @ResponseBody @ExceptionHandler(ConstraintViolationException.class) public ResultDataVO<Object> exceptionHandler(ConstraintViolationException e) { log.error("参数错误>>>:" + e.getMessage(), e); Set<ConstraintViolation<?>> violations = e.getConstraintViolations(); String message = violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(";")); return ResultDataVO.error("参数错误:" + message); } }2.5.2 调用测试test1请求参数POST http://localhost:8080/valid/test1 Content-Type: application/json { "id": 1, "appId": "add3", "email": "3131243242", "level": "12" }返回结果{ "code": 400, "errorMsg": "请填写正确的邮箱地址", "data": null }test2请求参数POST http://localhost:8080/valid/test2 Content-Type: application/x-www-form-urlencoded id=1&level=12&email=21434242341&appId=dsad返回结果{ "code": 400, "errorMsg": "请填写正确的邮箱地址", "data": null }test3请求参数POST http://localhost:8080/valid/test3 Content-Type: application/x-www-form-urlencoded email=476938977返回结果{ "code": 400, "errorMsg": "参数错误:不是一个合法的电子邮件地址", "data": null }3.分组校验一个VO对象在新增的时候某些字段为必填,在更新的时候又非必填。如上面的ValidVO中 id 和 appId 属性在新增操作时都是非必填,而在编辑操作时都为必填,name在新增操作时为必填,面对这种场景你会怎么处理呢?在实际开发中我见到很多同学都是建立两个VO对象,ValidCreateVO,ValidEditVO来处理这种场景,这样确实也能实现效果,但是会造成类膨胀,而且极其容易被开发老鸟们嘲笑。其实Validator校验框架已经考虑到了这种场景并且提供了解决方案,就是分组校验。要使用分组校验,只需要三个步骤:3.1 定义分组接口定义一个分组接口ValidGroup让其继承javax.validation.groups.Default,再在分组接口中定义出多个不同的操作类型,Create,Update,Query,Delete。public interface ValidGroup extends Default { interface Crud extends ValidGroup{ interface Create extends Crud{ } interface Update extends Crud{ } interface Query extends Crud{ } interface Delete extends Crud{ } } }3.2 在模型中给参数分配分组@Data public class ValidVO { @Null(groups = ValidGroup.Crud.Create.class) @NotNull(groups = ValidGroup.Crud.Update.class, message = "id不能为空") private String id; @NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字为必填项") private String name; @Email(message = "请填写正确的邮箱地址") private String email; private String sex; }3.3 给需要参数校验的方法指定分组@RestController @Slf4j @Validated public class ValidController { /** * 参数分组校验-add * @param validVO * @return */ @PostMapping(value = "/valid/add") public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){ log.info("validEntity is {}", validVO); return "test4 valid success"; } /** * 参数分组校验-update * @param validVO * @return */ @PostMapping(value = "/valid/update") public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){ log.info("validEntity is {}", validVO); return "test5 valid success"; } }3.4 调用测试add在Create时我们没有传递appId参数,校验通过。POST http://localhost:8080/valid/add Content-Type: application/x-www-form-urlencoded name=javadaily&email=522246447@qq.com&sex=Mtest4 valid successupdate当我们使用同样的参数调用update方法时则提示参数校验错误。POST http://localhost:8080/valid/add Content-Type: application/x-www-form-urlencoded name=javadaily&email=522246447@qq.com&sex=M{ "code": 400, "errorMsg": "id不能为空", "data": null }注意事项:eg-email校验由于email属于默认分组,而分组接口ValidGroup已经继承了Default分组,所以也是可以对email字段作参数校验的。如:POST http://localhost:8080/valid/add Content-Type: application/x-www-form-urlencoded name=javadaily&email=522246447&sex=M{ "code": 400, "errorMsg": "请填写正确的邮箱地址", "data": null }但是如果ValidGroup没有继承Default分组,那在代码属性上就需要加上@Validated(value = {ValidGroup.Crud.Create.class, Default.class}才能让email字段的校验生效。4.自定义参数校验虽然Spring Validation 提供的注解基本上够用,但是面对复杂的定义,还是需要自己定义相关注解来实现自动校验。比如IP地址校验如何实现呢?4.1 自定义的注解(@interface)主要需要初始化三个参数和指定执行验证的类message定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制groups这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作payload主要是针对bean的,使用不多。@Target({ElementType.FIELD}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = IPAddressValidator.class) // 指定验证实现类 public @interface IPAddress { String message() default "{ipaddress is invalid}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }4.2 自定义Validator,这个是真正进行验证的逻辑代码主要是需要实现ConstraintValidator这个接口,以及其中的两个泛型参数,第一个为注解名称,第二个为实际字段的数据类型。package com.example.validatedstudy.validation; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.regex.Pattern; public class IPAddressValidator implements ConstraintValidator<IPAddress, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if ((value != null) && (!value.isEmpty())) { return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", value); } return false; } }4.3 验证测试vo@Data public class IPAddressVO { @IPAddress private String ip; }controller@RestController @Slf4j @Validated public class ValidController { /** * 自定义参数校验 - ip校验 * @param ipAddressVO * @return */ @PostMapping(value = "/valid/ip") public String update(@Validated IPAddressVO ipAddressVO){ log.info("validEntity is {}", ipAddressVO); return "test ip Validated success"; } } 调用测试-失败POST http://localhost:8080/valid/ip Content-Type: application/x-www-form-urlencoded ip=2.45.6{ "code": 400, "errorMsg": "{ipaddress is invalid}", "data": null }调用测试-成功POST http://localhost:8080/valid/ip Content-Type: application/x-www-form-urlencoded ip=127.0.0.1test ip Validated success参考资料SpringBoot 如何进行参数校验,老鸟们都这么玩的!-阿里云开发者社区 (aliyun.com)SpringBoot 的请求参数校验注解_springboot 校验长度注解_千筠Wyman的博客-CSDN博客BindException、ConstraintViolationException、MethodArgumentNotValidException入参验证异常分析和全局异常处理解决方法_wzq_55552的博客-CSDN博客Spring的全局(统一)异常处理_spring全局异常处理_第1缕阳光的博客-CSDN博客Spring Boot之Validation自定义实现总结(亲测,好用)_spring boot validation 自定义_HD243608836的博客-CSDN博客
2023年09月01日
215 阅读
0 评论
0 点赞
2023-08-25
ActiveMQ消息中间件学习笔记
1.消息中间件简介及作用两个系统或两个客户端之间进行消息传送,利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。消息中间件,总结起来作用有三个:异步化提升性能、降低耦合度、流量削峰。2 消息中间件的应用场景2.1 异步通信有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。2.2 缓冲在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。2.3 解耦降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。2.4 冗余有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。2.5 扩展性因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。2.6 可恢复性系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。2.7 顺序保证在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。2.8 过载保护在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。2.9 数据流处理分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。3.常用消息队列特性ActiveMQRabbitMQRocketMQKafka生产者消费者模式支持支持支持支持发布订阅模式支持支持支持支持请求回应模式支持支持不支持不支持Api完备性高高高高多语言支持支持支持java支持单机吞吐量万级万级万级十万级消息延迟无微秒级毫秒级毫秒级可用性高(主从)高(主从)非常高(分布式)非常高(分布式)消息丢失低低理论上不会丢失理论上不会丢失文档的完备性高高高高提供快速入门有有有有社区活跃度高高有高商业支持无无商业云商业云4.消息中间件的角色Queue: 队列存储,常用与点对点消息模型 ,默认只能由唯一的一个消费者处理。一旦处理消息删除。Topic: 主题存储,用于订阅/发布消息模型,主题中的消息,会发送给所有的消费者同时处理。只有在消息可以重复处 理的业务场景中可使用,Queue/Topic都是 Destination 的子接口ConnectionFactory: 连接工厂,客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQConnectionFactoryConnection: JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。Destination: 消息的目的地,目的地是客户用来指定它生产的消息的目标和它消费的消息的来源的对象。JMS1.0.2规范中定义了两种消息传递域:点对点(PTP)消息传递域和发布/订阅消息传递域。点对点消息传递域的特点如下:每个消息只能有一个消费者。消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,它都可以提取消息。发布/订阅消息传递域的特点如下:每个消息可以有多个消费者。生产者和消费者之间有时间上的相关性。订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求 。持久订阅允许消费者消费它在未处于激活状态时发送的消息。在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)。5.JMS的消息格式JMS消息由以下三部分组成的:消息头:每个消息头字段都有相应的getter和setter方法。消息属性:如果需要除消息头字段以外的值,那么可以使用消息属性。消息体:JMS定义的消息类型有TextMessage、MapMessage、BytesMessage、StreamMessage和ObjectMessage。消息类型:属性类型TextMessage文本消息MapMessagek/vBytesMessage字节流StreamMessagejava原始的数据流ObjectMessage序列化的java对象6.消息可靠性机制只有在被确认之后,才认为已经被成功地消费了,消息的成功消费通常包含三个阶段 :客户接收消息、客户处理消息和消息被确认。在事务性会话中,当一个事务被提交的时候,确认自动发生。在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)。该参数有以下三个可选值:Session.AUTO_ACKNOWLEDGE:当客户成功的从receive方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息。Session.CLIENT_ACKNOWLEDGE:客户通过消息的acknowledge方法确认消息。需要注意的是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认。Session.DUPS_ACKNOWLEDGE:该选择只是会话迟钝的确认消息的提交。如果JMS Provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS Provider必须把消息头的JMSRedelivered字段设置为true。6.1 优先级可以使用消息优先级来指示JMS Provider首先提交紧急的消息。优先级分10个级别,从0(最低)到9(最高)。如果不指定优先级,默认级别是4。需要注意的是,JMS Provider并不一定保证按照优先级的顺序提交消息。6.2 消息过期可以设置消息在一定时间后过期,默认是永不过期。6.3 临时目的地可以通过会话上的createTemporaryQueue方法和createTemporaryTopic方法来创建临时目的地。它们的存在时间只限于创建它们的连接所保持的时间。只有创建该临时目的地的连接上的消息消费者才能够从临时目的地中提取消息。7.ActiveMQ7.1 简介ActiveMQ是一种开源的基于JMS(Java Message Servie)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的,面向消息的,能够跨越多语言和多系统的应用集成消息通信中间件。官网地址:http://activemq.apache.org/ActiveMQ主要特点:支持多语言、多协议客户端。语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。应用协议: OpenWire, Stomp REST, WS Notification, XMPP, AMQP对Spring的支持,ActiveMQ可以很容易整合到Spring的系统里面去。支持高可用、高性能的集群模式。7.2 存储方式1. KahaDB存储: KahaDB是默认的持久化策略,所有消息顺序添加到一个日志文件中,同时另外有一个索引文件记录指向这些日志的存储地址,还有一个事务日志用于消息回复操作。是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化特性:1、日志形式存储消息;2、消息索引以 B-Tree 结构存储,可以快速更新;3、 完全支持 JMS 事务;4、支持多种恢复机制kahadb 可以限制每个数据文件的大小。不代表总计数据容量。2. AMQ 方式: 只适用于 5.3 版本之前。 AMQ 也是一个文件型数据库,消息信息最终是存储在文件中。内存中也会有缓存数据。3. JDBC存储 : 使用JDBC持久化方式,数据库默认会创建3个表,每个表的作用如下:activemq_msgs:queue和topic的消息都存在这个表中activemq_acks:存储持久订阅的信息和最后一个持久订阅接收的消息IDactivemq_lock:跟kahadb的lock文件类似,确保数据库在某一时刻只有一个broker在访问4. LevelDB存储 : LevelDB持久化性能高于KahaDB,但是在ActiveMQ官网对LevelDB的表述:LevelDB官方建议使用以及不再支持,推荐使用的是KahaDB5.Memory 消息存储: 顾名思义,基于内存的消息存储,就是消息存储在内存中。persistent=”false”,表示不设置持 久化存储,直接存储到内存中,在broker标签处设置。8.ActiveMQ安装下载地址:http://activemq.apache.org/components/classic/download/下载完成后直接解压执行即可。activemq.bat 查看web控制台:http://127.0.0.1:8161/9.使用原生java进行交互9.1 项目依赖ActiveMQ 的解压包中,提供了运行 ActiveMQ 需要的 jar 包。ActiveMQ 是实现了 JMS 规范的。在实现消息服务的时候,必须基于 API 接口规范。maven依赖:<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all --> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.18.2</version> </dependency>9.2 JMS 常用的 API 说明下述 API 都是接口类型,定义在 javax.jms 包中,是 JMS 标准接口定义。ActiveMQ 完全实现这一套 api 标准。接口作用ConnectionFactory链接工厂, 用于创建链接的工厂类型.Connection链接. 用于建立访问 ActiveMQ 连接的类型, 由链接工厂创建.Session会话, 一次持久有效有状态的访问. 由链接创建.Destination & Queue目的地, 用于描述本次访问 ActiveMQ 的消息访问目的地. 即 ActiveMQ 服务中的具体队 列. 由会话创建. interface Queue extends DestinationMessageProducer消息生成者, 在一次有效会话中, 用于发送消息给 ActiveMQ 服务的工具. 由会话创建MessageConsumer消息消费者【消息订阅者,消息处理者】, 在一次有效会话中, 用于从 ActiveMQ 服务中 获取消息的工具. 由会话创建Message消息, 通过消息生成者向 ActiveMQ 服务发送消息时使用的数据载体对象或消息消费者 从 ActiveMQ 服务中获取消息时使用的数据载体对象. 是所有消息【文本消息,对象消息等】 具体类型的顶级接口. 可以通过会话创建或通过会话从 ActiveMQ 服务中获取. . .9.3 创建消息生成者,发送消息public class JmsProducer { public static void main(String[] args) throws JMSException { /* * 创建连接工厂,由 ActiveMQ 实现。构造方法参数 * userName 用户名 * password 密码 * brokerURL 访问 ActiveMQ 服务的路径地址,结构为: 协议名://主机地址:端口号 */ ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin", "tcp://127.0.0.1:61616"); //创建连接对象 Connection connection = connectionFactory.createConnection(); //启动连接 connection.start(); /* * 创建会话,参数含义: * 1.transacted - 是否使用事务 * 2.acknowledgeMode - 消息确认机制,可选机制为: * 1)Session.AUTO_ACKNOWLEDGE - 自动确认消息 * 2)Session.CLIENT_ACKNOWLEDGE - 客户端确认消息机制 * 3)Session.DUPS_OK_ACKNOWLEDGE - 有副本的客户端确认消息机制 */ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //创建目的地,也就是队列名 Destination destination = session.createQueue("mq_test"); //创建消息生成者,该生成者与目的地绑定 MessageProducer messageProducer = session.createProducer(destination); //创建消息 Message message = session.createTextMessage("Hello, ActiveMQ"); //发送消息 messageProducer.send(message); } }运行后查看web控制台管理界面可以看到生成了对应的消息队列和消息。9.4 创建消息消费者,接收消息public class JmsConsumer { public static void main(String[] args) throws JMSException { /* * 创建连接工厂,由 ActiveMQ 实现。构造方法参数 * userName 用户名 * password 密码 * brokerURL 访问 ActiveMQ 服务的路径地址,结构为: 协议名://主机地址:端口号 */ ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin", "tcp://127.0.0.1:61616"); //创建连接对象 Connection connection = connectionFactory.createConnection(); //启动连接 connection.start(); /* * 创建会话,参数含义: * 1.transacted - 是否使用事务 * 2.acknowledgeMode - 消息确认机制,可选机制为: * 1)Session.AUTO_ACKNOWLEDGE - 自动确认消息 * 2)Session.CLIENT_ACKNOWLEDGE - 客户端确认消息机制 * 3)Session.DUPS_OK_ACKNOWLEDGE - 有副本的客户端确认消息机制 */ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //创建目的地,也就是队列名 Destination destination = session.createQueue("mq_test"); //创建消息生成者,该生成者与目的地绑定 MessageConsumer messageConsumer = session.createConsumer(destination); //创建消息 Message message = session.createTextMessage("Hello, ActiveMQ"); //读取消息 while(true){ TextMessage textMessage = (TextMessage)messageConsumer.receive(10000); if(textMessage != null){ System.out.println("Accept msg : "+textMessage.getText()); }else{ break; } } } }Accept msg : Hello, ActiveMQ运行后查看web控制台管理界面可以看到对应的消息已经被消费了。10.springboot3整合ActiveMQ10.1 项目依赖创建标准的Spring Boot项目,并在项目中引入以下依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> <version>3.1.3</version> </dependency>此时如果不需要web或其他相关处理,只引入该依赖即可。如果使用pool的话, 就需要在pom中加入以下依赖:<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-pool</artifactId> <version>5.12.1</version> </dependency>10.2 配置文件spring: activemq: broker-url: tcp://127.0.0.1:61616 #ActiveMQ通讯地址 user: admin #用户名 password: admin #密码 in-memory: false #是否启用内存模式(就是不安装MQ,项目启动时同时启动一个MQ实例) packages: trust-all: true #信任所有的包 # pool: # enabled: true #连接池启动 # max-connections: 10 #最大连接数 pool: enabled: false jms: pub-sub-domain: false #设置是Queue队列还是Topic,false为Queue,true为Topic,默认false-Queue10.3 在SpringBoot的启动类,类上添加注解@EnableJms10.4 创建配置类ActiveMQConfig,读取yml中的内容,并且创建对象@Configuration public class ActiveMQConfig { @Value("${spring.activemq.broker-url}") private String brokerUrl; @Value("${spring.activemq.user}") private String userName; @Value("${spring.activemq.password}") private String password; @Bean(name = "queue") public Queue queue() { return new ActiveMQQueue("springboot.queue"); } @Bean(name = "topic") public Topic topic(){ return new ActiveMQTopic("springboot.topic"); } @Bean public ConnectionFactory connectionFactory(){ return new ActiveMQConnectionFactory(userName, password, brokerUrl); } // 在Queue模式中,对消息的监听需要对containerFactory进行配置 @Bean("queueListener") public JmsListenerContainerFactory<?> queueJmsListenerContainerFactory(ConnectionFactory connectionFactory){ SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setPubSubDomain(false); return factory; } // 在Topic模式中,对消息的监听需要对containerFactory进行配置 @Bean("topicListener") public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory(ConnectionFactory connectionFactory){ SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setPubSubDomain(true); return factory; } }10.5 创建生产者@RestController public class Producer { @Autowired private JmsMessagingTemplate jmsTemplate; @Autowired private Queue queue; @Autowired private Topic topic; //发送queue类型消息 @GetMapping("/queue") public void sendQueueMsg(String msg){ jmsTemplate.convertAndSend(queue, msg); } //发送topic类型消息 @GetMapping("/topic") public void sendTopicMsg(String msg){ jmsTemplate.convertAndSend(topic, msg); } }10.6 创建消费者@Component public class Consumer { //接收queue类型消息 //destination对应配置类中ActiveMQQueue("springboot.queue")设置的名字 @JmsListener(destination="springboot.queue", containerFactory = "queueListener") public void ListenQueue(String msg){ System.out.println("接收到queue消息:" + msg); } //接收topic类型消息 //destination对应配置类中ActiveMQTopic("springboot.topic")设置的名字 //containerFactory对应配置类中注册JmsListenerContainerFactory的bean名称 @JmsListener(destination="springboot.topic", containerFactory = "topicListener") public void ListenTopic(String msg){ System.out.println("接收到topic消息:" + msg); } }10.7 运行测试queue测试地址:localhost:8080/queue?msg=hellotopic测试地址:localhost:8080/topic?msg=hello注:测试topic模式的时候需要把配置文件的 jms. pub-sub-domain设置为true参考资料ActiveMQ 入门指引 - 知乎 (zhihu.com)ActiveMQ详细入门教程系列(一) - 牧小农 - 博客园 (cnblogs.com)从入门到精通的ActiveMQ(一) - 知乎 (zhihu.com)ActiveMQ (apache.org)ActiveMQ——Java连接ActiveMQ(点对点) - 知乎 (zhihu.com)消息中间件系列三、JMS和activeMQ的简单使用-阿里云开发者社区 (aliyun.com)springboot整合ActiveMQ(点对点+发布订阅)-阿里云开发者社区 (aliyun.com)SpringBoot集成ActiveMQ实例详解 - 知乎 (zhihu.com)SpringBoot使用activeMq(绝对可用!亲测)_码学弟的博客-CSDN博客(★★★)Springboot整合ActiveMQ(Queue和Topic两种模式)_码学弟的博客-CSDN博客
2023年08月25日
56 阅读
0 评论
0 点赞
2023-08-24
SpringBoot 整合 jasypt 对配置项进行加密
1.jasypt简介和为什么要对配置文件进行加密1.1 jasypt 简介Jasypt 是一个 Java 库,它允许开发人员以最小的努力为项目添加基本的加密功能,而无需深入了解密码学的工作原理。1.2 为什么要对配置文件进行加密先看一份典型的配置文件spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=CTT&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 redis: host: 127.0.0.1 port: 6379 password: 123456 ...像这样将项目的数据库密码、redis密码等直接写在项目中会有潜在的风险,比如项目源码泄漏,员工一不小心将公司源码上传到公有仓库,导致公司数据库密码泄漏。这时候对配置文件的关键信息进行加密就变得非常有必要了。2.Jasypt加密场景及对应的工具类加密算法3.SpringBoot3 整合 jasypt3.1 引入依赖用的springboot3.0.8版本<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.4</version> </dependency>3.2 生成加密字符串PBEWITHHMACSHA512ANDAES_256 算法@Test public void testJasypt() { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); SimpleStringPBEConfig config = new SimpleStringPBEConfig(); // 加密方式 config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); // 盐值 config.setPassword("jupiter"); config.setKeyObtentionIterations("1000"); config.setPoolSize("1"); config.setProviderName("SunJCE"); config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); config.setStringOutputType("base64"); encryptor.setConfig(config); String username = encryptor.encrypt("root"); String password = encryptor.encrypt("123456"); System.out.println("username:" + username); System.out.println("password:" + password); username = encryptor.decrypt(username); password = encryptor.decrypt(password); System.out.println("username:" + username); System.out.println("password:" + password); }username:o+GwMZViEUGlI9IrXRQ4Osyyue2xt/XdNWZZv/WNUXa1evDd1aBLR+jWqtKiuJ6n password:8mpyKrDyXMUi/iTNjWBDy1JhY5LKqdkhwza6NowBmjx3BP6NX7Z1mm7/ZAtCrV6U username:root password:1234563.3 写入配置文件并读取测试application.ymlspring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=CTT&useSSL=false&allowPublicKeyRetrieval=true username: ENC(o+GwMZViEUGlI9IrXRQ4Osyyue2xt/XdNWZZv/WNUXa1evDd1aBLR+jWqtKiuJ6n) password: ENC(8mpyKrDyXMUi/iTNjWBDy1JhY5LKqdkhwza6NowBmjx3BP6NX7Z1mm7/ZAtCrV6U) jasypt: encryptor: password: jupiter algorithm: PBEWithHmacSHA512AndAES_256读取测试$\color{red}{注意:springboot类上要加@EnableEncryptableProperties注解,否则不会进行自动解密!!!}$@Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Test public void testReadENCText() { System.out.println("username=" + username + ",password=" + password); }username=root,password=1234563.4 线上使用注意事项回到开头,我们加密配置项的目的是为了防止在配置文件泄漏的时候,把配置信息一起泄漏出去。配置我们是加密了,但密钥还是保存在配置文件中,别人还是能拿到密钥在解密出配置信息,这就相当于我们把门给锁了,但是钥匙还是插在锁上,所以需要将配置跟密钥分开存储。推荐采用环境变量的方式:#!/bin/bash export ENCRYPTOR_PASSWORD=jupiter java -jar -Djasypt.encryptor.password=$ENCRYPTOR_PASSWORD$\color{red}{注意:设置环境变量后解密会加载环境变量中设置的值,即使在配置文件中写了也会不生效被覆盖掉!!!!}$参考资料Jasypt加密工具整合SpringBoot使用 - 简书 (jianshu.com)SpringBoot 使用 jasypt 对配置项进行加密 - 掘金 (juejin.cn)jasypt 加解密的各个版本支持,看这一篇文章就够了_jasypt 3.0.3_Ramboooooooo的博客-CSDN博客Spring Boot Jasypt 3.0.4 报错---算法加解密使用不一致_pbewithhmacsha512andaes_256_神韵499的博客-CSDN博客
2023年08月24日
83 阅读
0 评论
0 点赞
2023-08-24
[Bug]:springboot自动注入被系统环境变量影响
0.背景下午在学习一下jasypt对配置文件进行加密,然后在配置文件里配置了jasypt.encryptor.password的值,然后以此为基准进行试验,结果一直各种报错,自动解密错误。最后发现之前在系统环境变量里面设置了jasypt.encryptor.password的系统环境变量且二者的值不一样,最后运行的时候一直是以系统环境变量为准的。1.实验论证ation.properties` 包含如下属性:my.property=value然后,您使用@Value注解来注入该属性值:@Value("${my.property}") private String property;如果设置了名为 my.property的系统环境变量,并赋予其一个值,那么该值将优先用于注入,而不是配置文件中的值:export my.property=new-value在这种情况下,property 将被注入为 "new-value" 而不是配置文件中的值 "value"。因此在Spring Boot中,使用@Value注解自动注入属性时,默认情况下系统环境变量会影响注入的值。当存在与@Value注解中指定的属性相匹配的系统环境变量时,它们将覆盖配置文件中的属性值。注意:配置系统环境变量需要重启才能生效!!
2023年08月24日
38 阅读
0 评论
0 点赞
2023-08-24
springcloud学习笔记(Alibaba)
1.微服务概述1.1 什么是微服务?微服务(Microservice Architecture) 是近几年流行的一种架构思想,关于它的概念很难一言以蔽之。究竟什么是微服务呢?我们在此引用ThoughtWorks 公司的首席科学家 Martin Fowler 于2014年提出的一段话:原文:https://martinfowler.com/articles/microservices.html汉化:https://www.cnblogs.com/liuning8023/p/4493156.html就目前而言,对于微服务,业界并没有一个统一的,标准的定义。但通常而言,微服务架构是一种架构模式,或者说是一种架构风格,它体长将单一的应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值,服务之间采用轻量级的通信机制(HTTP)互相沟通,每个服务都围绕着具体的业务进行构建,并且能狗被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应该根据业务上下文,选择合适的语言,工具(Maven)对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。再来从技术维度角度理解下:微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事情,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库。1.2 微服务与微服务架构微服务强调的是服务的大小,它关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,狭义的看,可以看作是IDEA中的一个个微服务工程,或者Moudel。IDEA 工具里面使用Maven开发的一个个独立的小Moudel,它具体是使用SpringBoot开发的一个小模块,专业的事情交给专业的模块来做,一个模块就做着一件事情。强调的是一个个的个体,每个个体完成一个具体的任务或者功能。微服务架构是一种架构模式,由Martin Fowler 于2014年提出。它通常将单一应用程序划分成一组小的服务,服务之间相互协调,互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制(如HTTP)互相协作,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具(如Maven)对其进行构建。1.3 微服务优缺点优点单一职责原则;每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个- 指定的业务功能或业务需求;开发简单,开发效率高,一个服务可能就是专一的只干一件事;微服务能够被小团队单独开发,这个团队只需2-5个开发人员组成;微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的;微服务能使用不同的语言开发;易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如jenkins,Hudson,bamboo;微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果,无需通过合作才能体现价值;微服务允许利用和融合最新技术;微服务只是业务逻辑的代码,不会和HTML,CSS,或其他的界面混合;每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库;缺点开发人员要处理分布式系统的复杂性;多服务运维难度,随着服务的增加,运维的压力也在增大;系统部署依赖问题;服务间通信成本问题;数据一致性问题;系统集成测试问题;性能和监控问题;1.4 微服务技术栈有那些? DubboSpringCloud服务注册中心ZookeeperSpring Cloud Netfilx Eureka服务调用方式RPCREST API服务监控Dubbo-monitorSpring Boot Admin断路器不完善Spring Cloud Netfilx Hystrix服务网关无Spring Cloud Netfilx Zuul分布式配置无Spring Cloud Config服务跟踪无Spring Cloud Sleuth消息总栈无Spring Cloud Bus数据流无Spring Cloud Stream批量任务无Spring Cloud Task2.SpringCloud概述2.1 Spring Cloud 是什么?Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。Spring官网:https://spring.io/2.2 SpringCloud和SpringBoot的关系SpringBoot专注于快速、方便的开发单个个体微服务;SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务,整合并管理起来,为各个微服务之间提供:配置管理、服务发现、断路器、路由、为代理、事件总栈、全局锁、决策竞选、分布式会话等等集成服务;SpringBoot可以离开SpringCloud独立使用,开发项目,但SpringCloud离不开SpringBoot,属于依赖关系;SpringBoot专注于快速、方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架;2.3 SpringCloud能干嘛?Distributed/versioned configuration 分布式/版本控制配置Service registration and discovery 服务注册与发现Routing 路由Service-to-service calls 服务到服务的调用Load balancing 负载均衡配置Circuit Breakers 断路器Distributed messaging 分布式消息管理2.4 自学参考书SpringCloud Netflix 中文文档:https://springcloud.cc/spring-cloud-netflix.htmlSpringCloud 中文API文档(官方文档翻译版):https://springcloud.cc/spring-cloud-dalston.htmlSpringCloud中国社区:http://springcloud.cn/SpringCloud中文网:https://springcloud.cc2.5 总体架构图注册中心:nacos,替代方案eureka、consul、zookeeper配置中心: nacos ,替代方案sc config、consul config服务调用:feign,替代方案:resttempate熔断:sentinel,替代方案:Resilience4j熔断监控:sentinel dashboard负载均衡:sc loadbalancer网关:spring cloud gateway链路:spring cloud sleuth+zipkin,替代方案:skywalking等。3.使用nacos作为注册中心3.1 下载nacos,并启动Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。下载地址https://github.com/alibaba/nacos/releases,下载最新版的2.0版本。下载完成后,解压,在解压后的文件的/bin目录下,windows系统点击startup.cmd就可以启动nacos。linux或mac执行以下命令启动nacos。sh startup.sh -m standalone登陆页面:http://localhost:8848/nacos/,登陆用户nacos,登陆密码为nacos。极致省事版-docker安装# 拉取nacos容器镜像 docker pull nacos/nacos-server # 快速启动nacos服务容器 docker run -d --name nacos -p 8848:8848 -e MODE=standalone nacos/nacos-server3.2 工程案例父工程pom文件引入相关的依赖 <!-- spring boot 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2022.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud alibaba 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2022.0.0.0-RC1</version> <type>pom</type> <scope>import</scope> </dependency>服务提供者provider在provider的pom文件引入依赖: <!-- spring-boot-starter-web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos注册中心--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2022.0.0.0-RC1</version> </dependency>配置文件application.yml:server: port: 8762 spring: application: name: provider cloud: nacos: discovery: server-addr: 172.19.112.2:8848写一个接口:@SpringBootApplication @RestController @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } @Value("${server.port}") String port; @GetMapping("/hi") public String hi(@RequestParam(value = "name", defaultValue = "feign",required = false) String name) { return "hello " + name + ", i'm provider ,my port:" + port; } }运行并查看nacos管理界面服务消费者consumer在pom文件引入以下依赖:需要注意的是引入openfeign,必须要引入loadbalancer,否则无法启动。<!-- spring-boot-starter-web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos-discovery依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2022.0.0.0-RC1</version> </dependency> <!-- openfeign依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>4.0.4</version> </dependency> <!-- loadbalancer依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <version>4.0.4</version> </dependency>配置文件application.yml:server: port: 8763 spring: application: name: consumer cloud: nacos: discovery: server-addr: 172.19.112.2:8848在工程的启动文件开启FeignClient的功能:@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }写一个FeignClient,去调用provider服务的接口:@FeignClient(value = "provider" ) public interface ProviderClient { @GetMapping("/hi") String hi(@RequestParam(value = "name", defaultValue = "feifn", required = false) String name); 写一个接口,让consumer去调用provider服务的接口:@RestController public class ConsumerController { @Autowired ProviderClient providerClient; @GetMapping("/hi-feign") public String hiFeign(){ return providerClient.hi("feign"); } }运行并查看nacos管理界面3.3 服务调用在浏览器上输入http://localhost:8763/hi-feign,浏览器返回响应:hello feign, i'm provider ,my port:8762可见浏览器的请求成功调用了consumer服务的接口,consumer服务也成功地通过feign成功的调用了provider服务的接口。3.4使用sc loadbanlancer作为负载均衡其实feign使用了spring cloud loadbanlancer作为负载均衡器。 可以通过修改provider的端口,再在本地启动一个新的provider服务,那么本地有2个provider 服务,端口分别为8760和8762。在浏览器上多次调用http://localhost:8763/hi-feign,浏览器会交替显示:hello feign, i'm provider ,my port:8762 hello feign, i'm provider ,my port:87604.使用nacos作为配置中心Nacos除了可以作为服务注册中心,它还有服务配置中心的功能。类似于consul config,Nacos 是支持热加载的。配置中心和注册中心的依赖包是不同的,注册中心的依赖包是 nacos discovery,而配置中心的依赖包是 nacos config,它的具体如下。4.1 工程案例在工程的pom文件引入nacos-config的Spring cloud依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-config</artifactId> <version>0.9.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring-cloud服务模块必须依赖,否则无法启动 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>4.0.2</version> </dependency> <!-- nacos配置中心 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2022.0.0.0-RC1</version> </dependency>在bootstrap.yml(一定是bootstrap.yml文件,不是application.yml文件)文件配置以下内容:spring: application: name: nacos-config cloud: nacos: config: server-addr: 172.19.112.2:8848 file-extension: yaml prefix: nacos-config profiles: active: dev在上面的配置中,配置了nacos config server的地址,配置的扩展名是ymal(目前仅支持ymal和properties)。注意是没有配置server.port的,sever.port的属性在nacos中配置。上面的配置是和Nacos中的dataId 的格式是对应的,nacos的完整格式如下:${prefix}-${spring.profile.active}.${file-extension}prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。启动nacos,创建一个data id :nacos-config-dev.yaml,完整的配置为:server: port: 8761 nacosvar: jupiter-nacos-config-dev写一个RestController,在Controller上添加 @RefreshScope 实现配置的热加载。代码如下:@RestController @RefreshScope public class ConfigController { @Value("${nacosvar:null}") private String nacosvar; @RequestMapping("/nacosvar") public String get() { return "nacosvar="+nacosvar; } }启动工程nacos-config,在浏览器上访问localhost:8761/nacosvar:5.OpenFeignTODO参考资料【狂神说Java】SpringCloud笔记(5万字保姆级笔记)_一只小逸白的博客-CSDN博客SpringCloud从入门到精通(超详细文档一)_cuiqwei的博客-CSDN博客SpringCloud 2020版本教程1:使用nacos作为注册中心和配置中心 - 方志朋的博客 (fangzhipeng.com)一文快速上手 Nacos 注册中心+配置中心!-阿里云开发者社区 (aliyun.com)spring cloud alibaba springboot nacos 版本对应_FH-Admin的博客-CSDN博客Nacos Spring Boot 快速开始springboot3.0集成nacos2.2.1(一)_nacos和springboot版本对应_魔锋剑上缺的博客-CSDN博客
2023年08月24日
55 阅读
0 评论
0 点赞
2023-08-24
[Bug解决]:spring boot+mybatis-plus+sqlite 代码执行成功但数据库却没有变化
1.问题描述用spring boot整合了mybatis-plus+sqlite3,对数据库进行查询、插入、删除都成功,但数据库内容却没有改变。2.原因问题出在数据库路径使用了相对路径spring: datasource: driver-class-name: org.sqlite.JDBC url: jdbc:sqlite::resource:db/office.db项目编译后,数据库文件和配置文件到了target下,使用相对路径只修改了target下的数据库文件。3.解决方法开发使用绝对路径,上线使用相对路径。spring: profiles: active: dev --- # development environment spring: profiles: dev datasource: driver-class-name: org.sqlite.JDBC url: jdbc:sqlite:src/main/resource/db/office.db --- # production environment spring: profiles: prod datasource: driver-class-name: org.sqlite.JDBC url: jdbc:sqlite::resource:db/office.db
2023年08月24日
201 阅读
0 评论
0 点赞
2023-08-22
openvpn配置客户端静态IP
1.应用背景有多个内网及公网的机器需要打通然后部署了openvpn服务,但是一旦有机器重启就好导致ip发生变化,因此需要想办法固定IP。查找了一些资料后记录如下。2.配置步骤在 VPN 服务器上创建一个客户端配置文件目录。例如,可以在/etc/openvpn/ccd 目录下创建一个子目录。sudo mkdir /etc/openvpn/ccd编辑 VPN 服务器配置文件,添加以下内容:client-config-dir /etc/openvpn/ccd ifconfig-pool-persist ipp.txt这样配置将告诉 OpenVPN 使用 /etc/openvpn/ccd 目录中的配置文件为每个客户端分配固定的 IP 地址,并将 IP 地址持久保存在 ipp.txt 文件中。对于每个客户端,创建一个与其名称对应的配置文件,并指定需要分配给该客户端的固定 IP 地址。例如,如果有一个名为 client1 的客户端,可以创建一个名为 /etc/openvpn/ccd/client1 的文件,并在其中写入以下内容:ifconfig-push 10.8.0.10 255.255.255.0这将分配 IP 地址 10.8.0.10 给 client1。你可以为每个客户端创建类似的配置文件,只需更改 IP 地址即可。重启 OpenVPN 服务,使配置生效。sudo service openvpn restart现在,每个客户端连接到 VPN 服务器时,将被分配其对应的固定 IP 地址。请注意,每个客户端的配置文件将根据其名称匹配到相应的 IP 地址。参考资料OpenVPN设置客户端固定IP - 知乎 (zhihu.com)OpenVPN 添加用户分配静态ip (liuyingguang.cn)
2023年08月22日
2,283 阅读
0 评论
0 点赞
2023-08-22
Maven “Blocked mirror for repositories” 错误解决办法
1.问题描述:Maven执行“mvn clean package”后报如下错误maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories错误原因是因为Maven在升级到3.8.1以后,从安全角度考虑,默认将非https的远端仓库屏蔽掉了。2.解决方案:把Maven版本降到3.8.1以下让远端仓库支持https为每一个非http源增加如下mirror配置(源比较多的话会比较麻烦)(但本人测试方法4和5没解决,最后用这个方法解决掉的)<mirror> <id>insecure-repo</id> <mirrorOf>external:http:*</mirrorOf> <url>http://www.ebi.ac.uk/intact/maven/nexus/content/repositories/ebi-repo/</url> <blocked>false</blocked> </mirror>注释掉默认配置文件$MAVEN\_HOME/conf/settings.xml中的相关block设置<mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:http:*</mirrorOf> <name>Pseudo repository to mirror external repositories initially using HTTP.</name> <url>http://0.0.0.0/</url> <blocked>true</blocked> </mirror>使用dummy镜像覆盖掉默认配置中的镜像配置。在~/.m2/settings.xml中添加如下mirror配置<mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:dummy:*</mirrorOf> <name>Pseudo repository to mirror external repositories initially using HTTP.</name> <url>http://0.0.0.0/</url> <blocked>false</blocked> </mirror>参考资料maven编译报错Blocked mirror for repositories解决_Menardღ的博客-CSDN博客maven报错Blocked mirror for repositories解决方案 - ArielMeng - 博客园 (cnblogs.com)Maven “Blocked mirror for repositories” 错误解决办法_blocked mirror for repositories:_Code Talk的博客-CSDN博客
2023年08月22日
538 阅读
1 评论
0 点赞
2023-08-21
nmap主机&端口扫描工具学习笔记
1.Nmap简介Nmap是一款非常强大的主机发现和端口扫描工具,而且nmap运用自带的脚本,还能完成漏洞检测,同时支持多平台。官网为:www.nmap.org。一般情况下,Nmap用于列举网络主机清单、管理服务升级调度、监控主机或服务运行状况。Nmap可以检测目标机是否在线、端口开放情况、侦测运行的服务类型及版本信息、侦测操作系统与设备类型等信息。1.1 Nmap的优点:灵活。支持数十种不同的扫描方式,支持多种目标对象的扫描强大。Nmap可以用于扫描互联网上大规模的计算机可移植。支持主流操作系统:Windows/Linux/Unix/MacOS等等;源码开放,方便移植简单。提供默认的操作能覆盖大部分功能,基本端口扫描nmap targetip,全面的扫描nmap –A targetip自由。Nmap作为开源软件,在GPL License的范围内可以自由的使用文档丰富。Nmap官网提供了详细的文档描述。Nmap作者及其他安全专家编写了多部Nmap参考书籍社区支持。Nmap背后有强大的社区团队支持1.2 Nmap包含四项基本功能:主机发现 (Host Discovery)端口扫描 (Port Scanning)版本侦测 (Version Detection)操作系统侦测 (Operating System Detection)而这四项功能之间,又存在大致的依赖关系(通常情况下的顺序关系,但特殊应用另外考虑),首先需要进行主机发现,随后确定端口状态,然后确定端口上运行的具体应用程序和版本信息,然后可以进行操作系统的侦测。而在这四项功能的基础上,nmap还提供防火墙和 IDS 的规避技巧,可以综合运用到四个基本功能的各个阶段。另外nmap还提供强大的NSE(Nmap Scripting Language)脚本引擎功能,脚本可以对基本功能进行补充和扩展。2.常用命令2.1 端口扫描2.1.1 默认扫描1000个端口扫描主机的「开放端口」,在nmap后面直接跟主机IP(默认扫描1000个端口)alpine1:~# nmap 127.0.0.1 Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-21 08:19 UTC Nmap scan report for alpine1.mshome.net (127.0.0.1) Host is up (0.0000070s latency). Not shown: 998 closed ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http2.1.2 指定端口扫描nmap 192.168.31.180 -p 80 nmap 192.168.31.180 -p 1-80 nmap 192.168.31.180 -p 80,3389,22,21 nmap 192.168.31.180 -p 1-655352.1.3 设置扫描方式-sS 使用TCP的SYN进行扫描 -sT 使用TCP进行扫描 -sA 使用TCP的ACK进行扫描 -sU UDP扫描 -sI Idle扫描 -sF FIN扫描 -b<FTP中继主机> FTP反弹扫描2.2 主机探测扫描网段中有哪些主机在线,使用 -sP 参数,不扫描端口,只扫描「存活主机」。本质上是Ping扫描,能Ping通有回包,就判定主机在线。alpine1:~# nmap -sP 172.31.126.0/24 Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-21 08:29 UTC Nmap scan report for alpine1.mshome.net (172.31.126.219) Host is up. Nmap done: 256 IP addresses (1 host up) scanned in 10.44 seconds2.3 服务识别扫描端口时,默认显示端口对应的服务,但不显示服务版本。想要识别具体的「服务版本」,可以使用 -sV 参数。nmap 127.0.0.1 -p 80 -sV2.4 操作系统识别想要识别「操作系统版本」,可以使用 -O 参数。alpine1:~# nmap -p 80 -O 127.0.0.1 Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-21 08:37 UTC Nmap scan report for alpine1.mshome.net (127.0.0.1) Host is up (0.000051s latency). PORT STATE SERVICE 80/tcp open http Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type: general purpose Running: Linux 2.6.X OS CPE: cpe:/o:linux:linux_kernel:2.6.32 OS details: Linux 2.6.32 Network Distance: 0 hops OS detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 1.74 seconds参考资料黑客工具之Nmap详细使用教程 - 掘金 (juejin.cn)Nmap使用教程图文教程(超详细) - 知乎 (zhihu.com)nmap命令-----基础用法 - nmap - 博客园 (cnblogs.com)
2023年08月21日
39 阅读
0 评论
0 点赞
2023-08-21
java数据随机初始化&bean随机初始化
0.背景最近在开发一个web小demo,其中进行数据测试需要对一些字段特别多的java bean进行初始化,一番百度之后查找到了几个解决方案,记录如下。1.Jpopulator1.1 pom依赖<dependency> <groupId>io.github.benas</groupId> <artifactId>jpopulator</artifactId> <version>1.0.1</version> </dependency>1.2 使用方式及测试实体类import lombok.Data; @Data public class Person { private int id; private String name; private String gender; }测试代码@Test public void testJpopulator(){ Populator populator = new PopulatorBuilder().build(); Person person = (Person) populator.populateBean(Person.class); System.out.println(person); List<Person> persons = populator.populateBeans(Person.class, 2); System.out.println(persons); }Person(id=-1042426959, name=nOBdJpkFbB, gender=bCwLDjOxXb) [Person(id=813095571, name=zxDkvjecWM, gender=SztfWBTZEj), Person(id=1664076867, name=ckMadNdfcX, gender=qmxsXZOiOI)]2.PODAM2.1 pom依赖<dependency> <groupId>uk.co.jemos.podam</groupId> <artifactId>podam</artifactId> <version>7.1.1.RELEASE</version> </dependency>2.2 使用方式及测试实体类import lombok.Data; @Data public class Person { private int id; private String name; private String gender; }基本使用@Test public void testPodam(){ PodamFactory factory = new PodamFactoryImpl(); Person person = factory.manufacturePojo(Person.class); System.out.println(person); }Person(id=1889825458, name=s6tv8LgsPD, gender=zY5put8aZT)2.3 进阶自定义数据生成策略比较复杂,不推荐。参考文档:Java Unit Tests make easy - Random Values with PODAM (onloadcode.com)3.common-random参考文档:https://github.com/yindz/common-random支持的数据类型:数字(int/long/double)汉字(简体)邮箱地址中文人名(简体)英文人名虚拟身份证号码(中国大陆)虚拟信用卡号码(Visa/Mastercard/JCB/银联/AmericanExpress)手机号码(中国大陆)省份和城市(中国大陆)邮编(中国大陆)联系地址(中国大陆)车牌号(中国大陆,包括新能源车型)域名静态URL日期(特定日期之前/特定日期之后)时间(过去/未来)时间戳强密码网络昵称(登录名)拼音网络昵称(登录名)IPv4地址端口号QQ号码非主流QQ网名学历小学名称、年级、班级中学名称、年级、班级高校名称(数据取自教育部网站)公司名称经纬度(中国)中文短句User-Agent(PC/Android/iOS)网卡MAC地址RGB颜色值HEX颜色值股票名称+股票代码开放式基金名称+基金代码缺点:只能逐一生成单个的随机字段,数据、生成对象需要逐一对对象的属性进行填充吗。对于字段较多的对象生成比较麻烦4.Jmockdata(★★★)(推荐)支持类型:Java基本类型字符串枚举日期数组多维数组集合[List|Set|Map]Java对象4.1 pom依赖<dependency> <groupId>com.github.jsonzou</groupId> <artifactId>jmockdata</artifactId> <version>4.2.0</version> </dependency>4.2 Java常用类型的生成@Test public void testJmockdata(){ int randomInt = JMockData.mock(int.class); long randomLong = JMockData.mock(long.class); double randomDouble = JMockData.mock(double.class); float randomFloat = JMockData.mock(float.class); String randomString = JMockData.mock(String.class); BigDecimal randomBigDecimal = JMockData.mock(BigDecimal.class); System.out.println("randomInt:"+randomInt); System.out.println("randomLong:"+randomLong); System.out.println("randomDouble:"+randomDouble); System.out.println("randomFloat:"+randomFloat); System.out.println("randomString:"+randomString); System.out.println("randomBigDecimal:"+randomBigDecimal); }randomInt:1116 randomLong:6722 randomDouble:1303.83 randomFloat:9700.79 randomString:cggMvc randomBigDecimal:3029.84.3 对象的生成实体类@Data public class Student { private int id; private String name; private int age; private LocalDateTime createTime; }生成测试数据@Test public void testJmockdata(){ Student student1 = JMockData.mock(Student.class); System.out.println("默认生成策略生成结果:"+student1); //按照规则定义Student对象里面单个字段生成,没配置的就按默认的生成邪恶了 MockConfig mockConfig = new MockConfig() .subConfig("id").intRange(1,10) .subConfig("name").subConfig("[a-zA-Z_]{1}[a-z0-9_]{5,15}") .subConfig("age").intRange(18,21) .globalConfig(); Student student2 = JMockData.mock(Student.class,mockConfig); System.out.println("自定义生成策略生成结果:"+student2); }执行结果默认生成策略生成结果:Student(id=7105, name=s, age=9755, createTime=2039-09-15T13:53:47.294) 自定义生成策略生成结果:Student(id=1, name=akXrp, age=18, createTime=2050-12-04T07:05:53.299)4.4 根据正则模拟数据配合4.3共同完成对象属性的初始化。/** * 根据正则模拟数据 * 正则优先于其他规则 */ @Test public void testRegexMock() { MockConfig mockConfig = new MockConfig() // 随机段落字符串 .stringRegex("I'am a nice man\\.And I'll just scribble the characters, like:[a-z]{2}-[0-9]{2}-[abc123]{2}-\\w{2}-\\d{2}@\\s{1}-\\S{1}\\.?-.") // 邮箱 .subConfig(RegexTestDataBean.class,"userEmail") .stringRegex("[a-z0-9]{5,15}\\@\\w{3,5}\\.[a-z]{2,3}") // 用户名规则 .subConfig(RegexTestDataBean.class,"userName") .stringRegex("[a-zA-Z_]{1}[a-z0-9_]{5,15}") // 年龄 .subConfig(RegexTestDataBean.class,"userAge") .numberRegex("[1-9]{1}\\d?") // 用户现金 .subConfig(RegexTestDataBean.class,"userMoney") .numberRegex("[1-9]{2}\\.\\d?") // 用户的得分 .subConfig(RegexTestDataBean.class,"userScore") .numberRegex("[1-9]{1}\\d{1}") // 用户身价 .subConfig(RegexTestDataBean.class,"userValue") .numberRegex("[1-9]{1}\\d{3,8}") .globalConfig(); }参考资料Jpopulator测试数据生成工具-腾讯云开发者社区-腾讯云 (tencent.com)Podam 一个Pojo填充随机值利器_这个程序员像只猴的博客-CSDN博客jmockdata: Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架。单元测试的利器 (gitee.com)GitHub - yindz/common-random: 简单易用的随机数据生成器。生成各种比较真实的假数据。一般用于开发和测试阶段的数据填充模拟。支持各类中国特色本地化的数据格式。An easy-to use random data generator. Generally used for data filling, simulation, demonstration and other scenarios in the development and test phase.JmockData---jfairy 学习记录(生成测试数据)_"小王"的博客-CSDN博客Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架-面圈网 (mianshigee.com)
2023年08月21日
45 阅读
0 评论
0 点赞
2023-08-16
Swagger学习笔记
1.简介1.1 产生背景故事还是要从前后端分离讲起啊前后端分离:VUE+SpringBoot 基本上都用这一套后端时代:前端只用管理静态页面,html===》后端,使用模版引擎 jsp=》后端主力前后端分离时代:后端:后端控制层,服务层,数据访问层【后端团队】前端:前端控制层,视图层,【前端团队】伪造后端数据,json,已经存在数据,不需要后端,前端工程依旧可以跑起来前后端如何交互 ====》API前后端相对独立,松耦合前后端甚至可以部署在不同的服务器上产生一个问题:前后端联调,前端和后端人员无法做到及时协商,解决问题,导致问题爆发需要一个东西可以解决这个问题解决问题:首先指定计划,实时更新API,较低集成风险早些年:指定word计划文档1.2 Swagger介绍Swagger是一组围绕 OpenAPI 规范构建的开源工具,可帮助您设计、构建、记录和使用 REST API。主要的 Swagger 工具包括:Swagger Editor – 基于浏览器的编辑器,您可以在其中编写 OpenAPI 规范。Swagger UI – 将 OpenAPI 规范呈现为交互式 API 文档。Swagger2于17年停止维护,现在最新的版本为 Swagger3(Open Api3)2.Swagger注解2.1 常用注解-swagger2版本swagger2是通过扫描很多的注解来获取数据帮我们展示在ui界面上的,下面就介绍下常用的注解。注解类方法属性@Api(tags)标注一个类为 Swagger 资源, 设置资源名称, 默认是类名 @ApiOperation(value) 标注一个请求, 设置该请求的名称, 默认是方法名 @ApiParam (不常用) 仅用于 JAX-RS @ApiImplicitParam (常用) 功能同 @ApiParame, 可用于 Servlet @ApiImplicitParams 包裹多个参数描述注解 @ApiModel标注一个实体类 @ApiModelProperty 标注实体属性, 设置属性的备注信息@ApiResponse 描述响应码,以及备注信息 @ApiResponses 包裹多个响应描述注解 @ApiIgnore使swagger忽略某个资源使swagger忽略某个接口使swagger忽略某个属性1、@Api():用在请求的类上,表示对类的说明,也代表了这个类是swagger2的资源参数:tags:说明该类的作用,参数是个数组,可以填多个。 value="该参数没什么意义,在UI界面上不显示,所以不用配置" description = "用户基本信息操作"2、@ApiOperation():用于方法,表示一个http请求访问该方法的操作参数:value="方法的用途和作用" notes="方法的注意事项和备注" tags:说明该方法的作用,参数是个数组,可以填多个。 格式:tags={"作用1","作用2"} (在这里建议不使用这个参数,会使界面看上去有点乱,前两个常用)3、@ApiModel():用于响应实体类上,用于说明实体作用参数:description="描述实体的作用" 4、@ApiModelProperty:用在属性上,描述实体类的属性参数:value="用户名" 描述参数的意义 name="name" 参数的变量名 required=true 参数是否必选5、@ApiImplicitParams:用在请求的方法上,包含多@ApiImplicitParam6、@ApiImplicitParam:用于方法,表示单独的请求参数参数:name="参数ming" value="参数说明" dataType="数据类型" paramType="query" 表示参数放在哪里 · header 请求参数的获取:@RequestHeader · query 请求参数的获取:@RequestParam · path(用于restful接口) 请求参数的获取:@PathVariable · body(不常用) · form(不常用) defaultValue="参数的默认值" required="true" 表示参数是否必须传7、@ApiParam():用于方法,参数,字段说明 表示对参数的要求和说明参数:name="参数名称" value="参数的简要说明" defaultValue="参数默认值" required="true" 表示属性是否必填,默认为false8、@ApiResponses:用于请求的方法上,根据响应码表示不同响应一个@ApiResponses包含多个@ApiResponse9、@ApiResponse:用在请求的方法上,表示不同的响应参数:code="404" 表示响应码(int型),可自定义 message="状态码对应的响应信息" 10、@ApiIgnore():用于类或者方法上,不被显示在页面上11、@Profile({"dev", "test"}):用于配置类上,表示只对开发和测试环境有用2.2 使用 swagger3 注解代替 swagger2 的(可选)这一步是可选的,因为改动太大,故 springfox对旧版的 swagger做了兼容处理。 但不知道未来会不会不兼容,这里列出如何用 swagger 3 的注解(已经在上面引入)代替 swagger 2 的 (注意修改 swagger 3 注解的包路径为io.swagger.v3.oas.annotations.)对应关系为:swagger2OpenAPI 3注解位置@Api@Tag(name = “接口类描述”)Controller 类上@ApiOperation@Operation(summary =“接口方法描述”)Controller 方法上@ApiImplicitParams@ParametersController 方法上@ApiImplicitParam@Parameter(description=“参数描述”)Controller 方法上 @Parameters 里@ApiParam@Parameter(description=“参数描述”)Controller 方法的参数上@ApiIgnore@Parameter(hidden = true) 或 @Operation(hidden = true) 或 @Hidden-@ApiModel@SchemaDTO类上@ApiModelProperty@SchemaDTO属性上Swagger2 的注解命名以易用性切入,全是 Api 开头,在培养出使用者依赖注解的习惯后,Swagger 3将注解名称规范化,工程化。3.SpringBoot集成Swagger23.1 pom.xml依赖<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>3.2 简单集成简单编写一个controller@RestController @RequestMapping("/") public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello springboot!"; } }编写 Swagger 配置类,默认什么都不写会扫描所有@Configuration @EnableSwagger2 // 开启Swagger2 public class SwaggerConfig { }访问接口文档:http://localhost:8080/swagger-ui.html备注:这里springboot3会不支持(版本太高),改为了2.6.1,否则运行会报如下错误,org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'apiDocumentationScanner' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/ApiDocumentationScanner.class]: Unsatisfied dependency expressed through constructor parameter 1: Error creating bean with name 'apiListingScanner' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/ApiListingScanner.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'apiDescriptionReader' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/ApiDescriptionReader.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'cachingOperationReader' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/CachingOperationReader.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'apiOperationReader' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/readers/operation/ApiOperationReader.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'documentationPluginsManager': Unsatisfied dependency expressed through field 'documentationPlugins': Error creating bean with name 'documentationPluginRegistry': FactoryBean threw exception on object creation at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-6.0.11.jar:6.0.11] at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:245) ~[spring-beans-6.0.11.jar:6.0.11] ···3.3 高级集成3.3.1 相关注解SwaggerConfig相关的相关注解注解说明@Configuration用于SwaggerConfig类前表明这是个配置类,项目会自动把Swagger页面加载进来@EnableSwagger2开启Swagger页面的使用@Bean用于返回Docket的方法前,Docket持有对各个接口的具体处理。3.3.2 配置基本信息和swagger扫描范围新建config包,创建SwaggerConfig类,重点注意基于.apis和.paths配置swagger扫描范围的配置。package com.example.testspringboot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagge2Config { // 配置swagger页面的头部 即api文档的详细信息介绍 private ApiInfo setApiInfo() { return new ApiInfoBuilder() .title("XX平台API接口文档") //页面标题 .contact(new Contact("jupiter", "https://blog.inat.top", "xxxxxx@qq.com"))//联系人 .version("1.0")//版本号 .description("系统API描述")//描述 .build(); } @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) // 指定swagger2版本 .enable(true) // 开关 .apiInfo(setApiInfo())// 配置swagger页面的头部信息 .select()// 配置扫描接口,使用过滤,必须先调用select方法; /** * 通过apis方法,basePackage可以根据包路径来生成特定类的API, * any() // 扫描所有,项目中的所有接口都会被扫描到 * none() // 不扫描接口 * withMethodAnnotation(final Class<? extends Annotation> annotation) * 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求 * withClassAnnotation(final Class<? extends Annotation> annotation) * 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口 * basePackage(final String basePackage) // 根据包路径扫描接口 */ .apis(RequestHandlerSelectors.basePackage("com.example.testspringboot.controller")) /** *除此之外,我们还可以通过.paths方法配置接口扫描过滤 * any() // 任何请求都扫描 * none() // 任何请求都不扫描 * regex(final String pathRegex) // 通过正则表达式控制 * ant(final String antPattern) // 通过ant()控制 */ .paths(PathSelectors.any()) .build(); // 使用了select()方法后必须进行build } } 3.3.3 swagger分组如果项目大了之后可能有几百上千个接口。如果全在一个组内,找起来特别麻烦。swagger可以配置多个Docket,每个Docket都可以设置一个分组,并设定每个Docket的单独过滤规则。这样就完美设置成了一个大的功能模块对应一个分组。可以通过.groupName方法设置分组名。 @Bean public Docket docket1(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("group1") .enable(swaggerModel.isEnable()) .select() .paths(PathSelectors.any()) .build(); } @Bean public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("group2") .enable(swaggerModel.isEnable()) .select() .paths(PathSelectors.any()) .build(); } @Bean public Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("group3") .enable(swaggerModel.isEnable()) .select() .paths(PathSelectors.any()) .build(); }3.3.4 Swagger换皮肤皮肤的使用非常简单,只需简单的引入依赖即可。bootstrap-ui <!-- 引入swagger-bootstrap-ui包 /doc.html--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.1</version> </dependency>swagger-mg-ui<dependency> <groupId>com.zyplayer</groupId> <artifactId>swagger-mg-ui</artifactId> <version>1.0.6</version> </dependency>knife4j<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-ui</artifactId> <version>2.0.6</version> </dependency>4.SpringBoot集成Swagger34.1 配置文件pom.xml<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency>application.ymlspringdoc: swagger-ui: path: /swagger-ui.html tags-sorter: alpha operations-sorter: alpha enabled: true api-docs: path: /v3/api-docs enabled: true group-configs: group: platform paths-to-match: /** packages-to-scan: com.license4j.license knife4j: enable: true setting: language: zh_cn然后,就可以启动测试输入地址http://ip:port/doc.html参考资料API Documentation & Design Tools for Teams | Swaggerswagger:快速入门_冷环渊的博客-CSDN博客Swagger3学习笔记_swagger3 @apiresponses_C.kai的博客-CSDN博客Swagger3 学习笔记 - xtyuns - 博客园 (cnblogs.com)Swagger笔记—Swagger3详细配置-腾讯云开发者社区-腾讯云 (tencent.com)齐全的swagger注解介绍 - 知乎 (zhihu.com)Swagger与Docket - ShineLe - 博客园 (cnblogs.com)SpringBoot集成swagger2报错‘apiDocumentationScanner‘ defined in URL_吃啥?的博客-CSDN博客swagger配置扫描接口、扫描路径条件_swagger docket该路径_呐呐呐-的博客-CSDN博客SpringBoot集成Swagger(六)玩转groupName()分组 | Java随笔记 - 掘金 (juejin.cn)springBoo3.0集成knife4j4.1.0(swagger3)_华义辰的博客-CSDN博客Springboot3.0.0+集成SpringDoc并配置knife4j的UI_Anakki的博客-CSDN博客
2023年08月16日
269 阅读
0 评论
0 点赞
1
...
3
4
5
...
24