SpringBoot將業(yè)務(wù)模塊數(shù)據(jù)導(dǎo)出為PDF的示例代碼
問題:在管理系統(tǒng)開發(fā)中,經(jīng)常會遇到這樣一類需求:
- 頁面展示的是數(shù)據(jù)庫中的業(yè)務(wù)數(shù)據(jù)
- 用戶希望把這些數(shù)據(jù)導(dǎo)出成正式的 PDF 打印單
- 一條業(yè)務(wù)數(shù)據(jù)是一張單據(jù)
- 列表頁可以勾選多條,導(dǎo)出成一個 PDF 文件
- PDF 上半部分展示主表唯一信息
- PDF 下半部分展示這條業(yè)務(wù)對應(yīng)的多條明細(xì)記錄
- 導(dǎo)出后還希望保存到附件表,便于追溯和下載
一、需求拆解
這次實現(xiàn)的業(yè)務(wù)背景是:
- 主表:
t_reagent - 明細(xì)表:
t_reagent_record - 領(lǐng)用接口:
/control/reagent/receiveReagents - 新增導(dǎo)出接口:
/control/reagent/exportPrintPdf
導(dǎo)出的 PDF 頁面結(jié)構(gòu)如下:
上半部分展示試劑唯一信息:物料名稱、物料貨號、物料分類、物料類型、規(guī)格、型號、批號、生產(chǎn)商、品牌、存儲條件、現(xiàn)庫存量、保管人、備注
下半部分展示這條試劑對應(yīng)的多條操作記錄:操作類型、操作時間、數(shù)量、單位、單價、部門操作人、批號、備注、審核人
實現(xiàn):單條導(dǎo)出、批量導(dǎo)出多條數(shù)據(jù)、合并到同一個 PDF 中、可選保存到附件表 t_base_file
二、如何設(shè)計
拆成四步,為了可復(fù)用不可合并一步
Controller -> Service -> Assembler(組裝器) -> PDF Render Tool(渲染工具) -> Optional Attachment Save(可選附件保存)
三、整體架構(gòu)圖
前端 / ApiPost
|
| POST /control/reagent/exportPrintPdf
v
ReagentController
|
v
ReagentServiceImpl.exportPrintPdf(...)
|
|-- 查詢 t_reagent 主表
|-- 查詢 t_reagent_record 明細(xì)表
|-- 按 reagentId 分組
v
ReagentPrintAssembler
|
|-- 組裝 ReagentPrintDTO
v
PdfRenderUtil
|
|-- 生成 PDF byte[]
|
|-- 可選:保存到 t_base_file
v
HttpServletResponse 輸出 PDF 下載四、加 PDF 依賴
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.39</version>
</dependency>五、接口設(shè)計
basemanage/platform/main/reagent/controller/ReagentController.java
新增接口
@PostMapping("/exportPrintPdf")
void exportPrintPdf(@RequestBody ReagentPrintRequest request, HttpServletResponse response);請求對象
文件位置:
basemanage/platform/main/reagent/bean/ReagentPrintRequest.java
@Data
public class ReagentPrintRequest {
private List<String> ids;
private Boolean saveAttachment;
}請求示例
{
"ids": ["試劑主表ID1", "試劑主表ID2"],
"saveAttachment": true
}這里傳的是t_reagent.id,不是t_reagent_record.id。
六、為什么接口返回void
一開始很容易寫成這樣:
JsonResult exportPrintPdf(...)
但這個接口本質(zhì)是文件下載接口,而不是普通 JSON 接口。
如果你一邊往HttpServletResponse輸出 PDF,一邊又返回JsonResult,在@RestController環(huán)境下很容易出現(xiàn):
- 二次寫響應(yīng)
- 文件流和 JSON 混在一起
- 前端下載失敗
所以最后改成:
void exportPrintPdf(...)
Controller 實現(xiàn)如下。
文件位置
basemanage/platform/main/reagent/controller/ReagentControllerImpl.java
@Override
public void exportPrintPdf(ReagentPrintRequest request, HttpServletResponse response) {
getService().exportPrintPdf(request, response);
}七、Service 接口定義
basemanage/platform/main/reagent/service/ReagentService.java
void exportPrintPdf(ReagentPrintRequest request, HttpServletResponse response);
八、批量查詢明細(xì)記錄
因為是批量導(dǎo)出,所以一定不要一條試劑查一次記錄,那樣很容易變成 N+1 查詢。
正確做法是:
- 一次查所有主表數(shù)據(jù)
- 一次查所有記錄數(shù)據(jù)
- 內(nèi)存里按reagentId分組
所以先給ReagentRecordMapper增加一個批量查詢方法。
1. Mapper 接口
文件位置:
basemanage/platform/main/reagent/dao/ReagentRecordMapper.java
List<ReagentRecordBean> selectListByReagentIds(@Param("reagentIds") List<String> reagentIds);2. Mapper XML
文件位置:
src/main/resources/mapper/ReagentRecordMapper.xml
<select id="selectListByReagentIds" resultType="com.px.basemanage.platform.main.reagent.bean.ReagentRecordBean">
select t.*, r.batchNo
from t_reagent_record t
left join t_reagent r on r.id = t.reagentId
where t.reagentId in
<foreach collection="reagentIds" item="reagentId" open="(" close=")" separator=",">
#{reagentId}
</foreach>
order by t.reagentId, t.operateTime desc, t.createTime desc
</select>為什么這里聯(lián)查了t_reagent
因為ReagentRecordBean里有一個非持久化字段:
@TableField(exist = false) private String batchNo;
這樣在打印記錄時,可以直接展示批號。
九、定義打印用 DTO,而不是直接拿實體渲染
不要直接拿 ReagentBean 和 ReagentRecordBean 去畫 PDF,而應(yīng)該先定義打印模型。
1. 打印主 DTO
basemanage/platform/main/reagent/service/ReagentPrintAssembler.java
@Data
public class ReagentPrintDTO {
private String title;
private String reagentId;
private List<Map.Entry<String, String>> summaryFields = new ArrayList<>();
private List<ReagentPrintRecordDTO> records = new ArrayList<>();
}2. 打印明細(xì) DTO
文件位置:
basemanage/platform/main/reagent/bean/ReagentPrintRecordDTO.java
@Data
public class ReagentPrintRecordDTO {
private String operateType;
private String operateTime;
private String reagentNumber;
private String reagentUnit;
private String reagentPrice;
private String deptName;
private String operater;
private String batchNo;
private String remark;
private String handledUserName;
private String auditUserName;
}優(yōu)點:
- 實體和打印結(jié)構(gòu)解耦
- 字段格式化集中處理
- 后續(xù) PDF 工具就不依賴具體業(yè)務(wù)表
- 以后可以復(fù)用到別的模塊
十、用 Assembler 組裝打印字段
這一層專門負(fù)責(zé)把數(shù)據(jù)庫對象轉(zhuǎn)成打印對象。
basemanage/core/pdf/PdfRenderUtil.java
@Component
public class ReagentPrintAssembler {
private static final DateTimeFormatter DATETIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public ReagentPrintDTO assemble(ReagentBean reagentBean, List<ReagentRecordBean> recordBeans) {
ReagentPrintDTO dto = new ReagentPrintDTO();
dto.setTitle("試劑領(lǐng)用打印單");
dto.setReagentId(reagentBean.getId());
addField(dto, "物料名稱", reagentBean.getReagentName());
addField(dto, "物料貨號", reagentBean.getReagentNo());
addField(dto, "物料分類", reagentBean.getReagentClass());
addField(dto, "物料類型", reagentBean.getReagentType());
addField(dto, "規(guī)格型號", reagentBean.getSpecification());
addField(dto, "批號", reagentBean.getBatchNo());
addField(dto, "生產(chǎn)商", reagentBean.getManufacturer());
addField(dto, "品牌", reagentBean.getReagentBrand());
addField(dto, "存儲條件", reagentBean.getStorageCondition());
addField(dto, "試劑形態(tài)", reagentBean.getReagentShape());
addField(dto, "供應(yīng)商", reagentBean.getSupplier());
addField(dto, "有效期", reagentBean.getValidityDate());
addField(dto, "現(xiàn)庫存量", formatNumber(reagentBean.getInventoryCount()));
addField(dto, "數(shù)量單位", reagentBean.getInventoryUnit());
addField(dto, "保管人", reagentBean.getCustodianName());
addField(dto, "備注", reagentBean.getRemark());
dto.setRecords(recordBeans.stream().map(this::toRecordDto).collect(Collectors.toList()));
return dto;
}
private ReagentPrintRecordDTO toRecordDto(ReagentRecordBean bean) {
ReagentPrintRecordDTO dto = new ReagentPrintRecordDTO();
dto.setOperateType(bean.getOperateType());
dto.setOperateTime(bean.getOperateTime() == null ? "--" : bean.getOperateTime().format(DATETIME_FORMATTER));
dto.setReagentNumber(formatNumber(bean.getReagentNumber()));
dto.setReagentUnit(defaultText(bean.getReagentUnit()));
dto.setReagentPrice(formatNumber(bean.getReagentPrice()));
dto.setDeptName(defaultText(bean.getDeptName()));
dto.setOperater(defaultText(bean.getOperater()));
dto.setBatchNo(defaultText(bean.getBatchNo()));
dto.setRemark(defaultText(bean.getRemark()));
dto.setHandledUserName(defaultText(bean.getHandledUserName()));
dto.setAuditUserName(defaultText(bean.getAuditUserName()));
return dto;
}
private void addField(ReagentPrintDTO dto, String label, String value) {
dto.getSummaryFields().add(new AbstractMap.SimpleEntry<>(label, defaultText(value)));
}
private String formatNumber(Double value) {
return value == null ? "--" : String.valueOf(value);
}
private String defaultText(String value) {
return value == null || value.trim().isEmpty() ? "--" : value;
}
}十一、封裝通用 PDF 渲染工具
這是整套功能里最值得沉淀復(fù)用的部分
basemanage/core/pdf/PdfRenderUtil.java
public final class PdfRenderUtil {
private PdfRenderUtil() {
}
public static byte[] renderReagentPrintPdf(List<ReagentPrintDTO> documents) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4, 32, 32, 36, 36);
PdfWriter.getInstance(document, outputStream);
document.open();
BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font titleFont = new Font(baseFont, 16, Font.BOLD);
Font labelFont = new Font(baseFont, 10, Font.BOLD);
Font valueFont = new Font(baseFont, 10, Font.NORMAL);
Font tableHeadFont = new Font(baseFont, 9, Font.BOLD);
Font tableBodyFont = new Font(baseFont, 9, Font.NORMAL);
for (int i = 0; i < documents.size(); i++) {
ReagentPrintDTO doc = documents.get(i);
if (i > 0) {
document.newPage();
}
Paragraph title = new Paragraph(doc.getTitle(), titleFont);
title.setAlignment(Element.ALIGN_CENTER);
title.setSpacingAfter(10f);
document.add(title);
PdfPTable summaryTable = new PdfPTable(4);
summaryTable.setWidthPercentage(100);
summaryTable.setWidths(new float[]{15f, 35f, 15f, 35f});
summaryTable.setSpacingAfter(12f);
for (Map.Entry<String, String> entry : doc.getSummaryFields()) {
summaryTable.addCell(createCell(entry.getKey(), labelFont, new Color(242, 242, 242)));
summaryTable.addCell(createCell(entry.getValue(), valueFont, Color.WHITE));
}
if (doc.getSummaryFields().size() % 2 != 0) {
summaryTable.addCell(createCell("", labelFont, Color.WHITE));
summaryTable.addCell(createCell("", valueFont, Color.WHITE));
}
document.add(summaryTable);
Paragraph recordTitle = new Paragraph("領(lǐng)用記錄", labelFont);
recordTitle.setSpacingAfter(8f);
document.add(recordTitle);
PdfPTable recordTable = new PdfPTable(10);
recordTable.setWidthPercentage(100);
recordTable.setWidths(new float[]{10f, 16f, 9f, 7f, 8f, 12f, 10f, 9f, 12f, 7f});
addHeader(recordTable, tableHeadFont,
"操作類型", "操作時間", "數(shù)量", "單位", "單價", "部門", "操作人", "批號", "備注", "審核人");
if (doc.getRecords().isEmpty()) {
PdfPCell emptyCell = createCell("暫無領(lǐng)用記錄", tableBodyFont, Color.WHITE);
emptyCell.setColspan(10);
emptyCell.setHorizontalAlignment(Element.ALIGN_CENTER);
emptyCell.setMinimumHeight(28f);
recordTable.addCell(emptyCell);
} else {
for (ReagentPrintRecordDTO record : doc.getRecords()) {
recordTable.addCell(createCell(record.getOperateType(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getOperateTime(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getReagentNumber(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getReagentUnit(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getReagentPrice(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getDeptName(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getOperater(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getBatchNo(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getRemark(), tableBodyFont, Color.WHITE));
recordTable.addCell(createCell(record.getAuditUserName(), tableBodyFont, Color.WHITE));
}
}
document.add(recordTable);
}
document.close();
return outputStream.toByteArray();
} catch (Exception e) {
throw new RuntimeException("生成PDF失?。? + e.getMessage(), e);
}
}
private static void addHeader(PdfPTable table, Font font, String... headers) {
for (String header : headers) {
PdfPCell cell = createCell(header, font, new Color(230, 230, 230));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
}
}
private static PdfPCell createCell(String text, Font font, Color backgroundColor) {
PdfPCell cell = new PdfPCell(new Phrase(text == null ? "--" : text, font));
cell.setPadding(6f);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setBackgroundColor(backgroundColor);
return cell;
}
}十二、核心 Service 實現(xiàn)
把查詢、組裝、渲染、附件保存串起來。
basemanage/platform/main/reagent/service/ReagentServiceImpl.java
1. 注入新增依賴
@Resource
private ReagentPrintAssembler reagentPrintAssembler;
@Resource
private BaseFileService baseFileService;
private static final String EXPORT_TABLE_NAME = "t_reagent_export";
private static final DateTimeFormatter EXPORT_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMddHHmmss");2. 導(dǎo)出核心方法
@Override
public void exportPrintPdf(ReagentPrintRequest request, HttpServletResponse response) {
if (request == null || PxListUtils.isEmpty(request.getIds())) {
throw new RuntimeException("參數(shù)錯誤,試劑id不能為空!");
}
List<String> ids = request.getIds().stream()
.filter(PxStringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (PxListUtils.isEmpty(ids)) {
throw new RuntimeException("參數(shù)錯誤,試劑id不能為空!");
}
List<ReagentBean> reagentBeans = getMapper().selectBatchIds(ids);
if (PxListUtils.isEmpty(reagentBeans)) {
throw new RuntimeException("未找到對應(yīng)的試劑信息!");
}
List<ReagentRecordBean> recordBeans = reagentRecordMapper.selectListByReagentIds(ids);
Map<String, List<ReagentRecordBean>> recordMap = recordBeans.stream()
.collect(Collectors.groupingBy(ReagentRecordBean::getReagentId));
List<ReagentPrintDTO> printDocs = new ArrayList<>();
for (String id : ids) {
ReagentBean reagentBean = reagentBeans.stream()
.filter(t -> id.equals(t.getId()))
.findFirst()
.orElse(null);
if (reagentBean == null) {
continue;
}
printDocs.add(reagentPrintAssembler.assemble(
reagentBean,
recordMap.getOrDefault(id, Collections.emptyList())
));
}
if (PxListUtils.isEmpty(printDocs)) {
throw new RuntimeException("未找到可導(dǎo)出的試劑信息!");
}
byte[] pdfBytes = PdfRenderUtil.renderReagentPrintPdf(printDocs);
String fileName = "試劑領(lǐng)用打印單_" + LocalDateTime.now().format(EXPORT_TIME_FORMATTER) + ".pdf";
if (Boolean.TRUE.equals(request.getSaveAttachment())) {
String batchId = PxBeanUtils.newStrId();
BaseFileBean fileBean = new BaseFileBean();
fileBean.setObjId(batchId);
fileBean.setTableName(EXPORT_TABLE_NAME);
fileBean.setFileType("pdf");
fileBean.setOriginalName(fileName);
fileBean.setRemark(String.join(",", ids));
BaseFileBean savedFile = baseFileService.saveGeneratedFile(pdfBytes, fileBean);
response.setHeader("X-Export-File-Id", savedFile.getId());
response.setHeader("X-Export-Obj-Id", batchId);
}
writePdfResponse(response, pdfBytes, fileName);
}3. 輸出 PDF 到瀏覽器
private void writePdfResponse(HttpServletResponse response, byte[] pdfBytes, String fileName) {
try {
response.setContentType("application/pdf");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));
response.setContentLength(pdfBytes.length);
response.getOutputStream().write(pdfBytes);
response.getOutputStream().flush();
} catch (IOException e) {
throw new RuntimeException("PDF下載失??!");
}
}十三、批量導(dǎo)出時使用“導(dǎo)出批次號”保存附件
當(dāng)用戶勾選多個試劑時,導(dǎo)出的是一個 PDF 文件,那這個文件應(yīng)該掛在哪個業(yè)務(wù)對象下?
錯誤做法:直接掛在某一個試劑 ID 下。
問題:
- 這個 PDF 實際包含多條試劑
- 只掛在一個試劑下面語義不準(zhǔn)確
更合理的做法
生成一個“導(dǎo)出批次號”作為objId。
String batchId = PxBeanUtils.newStrId();
fileBean.setObjId(batchId);
fileBean.setTableName("t_reagent_export");這樣保存到附件表時,語義是:
- 這是一份“試劑導(dǎo)出批次文件”
- 不是某一條單獨試劑的從屬文件
這在批量導(dǎo)出場景下更清晰。
十四、擴(kuò)展附件服務(wù):支持保存生成文件
項目原來的BaseFileService#upload只支持MultipartFile上傳,不適合保存程序動態(tài)生成的 PDF。
所以需要擴(kuò)展一個新方法:
文件位置
basemanage/platform/main/basefile/service/BaseFileService.java
BaseFileBean saveGeneratedFile(byte[] fileBytes, BaseFileBean fileBean);
實現(xiàn)類
文件位置:basemanage/platform/main/basefile/service/BaseFileServiceImpl.java
@Override
public BaseFileBean saveGeneratedFile(byte[] fileBytes, BaseFileBean fileBean) {
if (fileBytes == null || fileBytes.length == 0) {
throw new RuntimeException("文件內(nèi)容不能為空!");
}
if (PxObjectUtils.isEmpty(fileBean)
|| PxObjectUtils.isEmpty(fileBean.getTableName())
|| PxObjectUtils.isEmpty(fileBean.getObjId())) {
throw new RuntimeException("參數(shù)錯誤,【tableName,objId】不能為空!");
}
String originalName = fileBean.getOriginalName();
if (PxObjectUtils.isEmpty(originalName)) {
originalName = "generated-file.pdf";
fileBean.setOriginalName(originalName);
}
String suffix = originalName.contains(".")
? originalName.substring(originalName.lastIndexOf("."))
: ".pdf";
fileBean.setSuffix(suffix);
String uuidName = getUuid() + suffix;
String filePath = System.getProperty("user.dir")
+ File.separator + "UPLOADFILE"
+ File.separator + fileBean.getTableName()
+ File.separator + getYearMonth()
+ File.separator + uuidName;
fileBean.setFileName(uuidName);
fileBean.setFilePath(filePath);
File file = new File(filePath);
if (!file.getParentFile().exists()) {
boolean mkdirs = file.getParentFile().mkdirs();
if (!mkdirs) {
throw new RuntimeException("創(chuàng)建文件夾失??!");
}
}
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(fileBytes);
UserBean loginUser = PxLocalContextHelper.getLoginUser();
fileBean.setFileSize(Math.round(file.length() / 1024.0 * 100.0) / 100.0);
if (fileBean.getSort() == null) {
fileBean.setSort(1);
}
if (loginUser != null) {
fileBean.setCreatedById(loginUser.getId());
fileBean.setCreatedByName(loginUser.getRealName());
}
fileBean.setCreateTime(LocalDateTime.now());
fileBean.setId(PxBeanUtils.newStrId());
getMapper().insert(fileBean);
return fileBean;
} catch (IOException e) {
throw new RuntimeException("文件存儲失敗!");
}
}十五、ApiPost 測試方式
請求地址
POST /control/reagent/exportPrintPdf
請求頭
Content-Type: application/json Authorization: token
請求體
{
"ids": ["試劑主表ID1", "試劑主表ID2"],
"saveAttachment": true
}預(yù)期結(jié)果
正常時:
- 返回文件流
Content-Type: application/pdf- 瀏覽器或 ApiPost 可直接保存為 PDF
保存后的文件位置
如果保存附件成功,文件會落到本地目錄:
項目根目錄/UPLOADFILE/t_reagent_export/yyyyMM/
數(shù)據(jù)庫記錄會寫到:
t_base_file
src/main/java
├── com/px/basemanage/core/pdf
│ └── PdfRenderUtil.java
│
├── com/px/basemanage/platform/main/reagent/bean
│ ├── ReagentPrintRequest.java
│ ├── ReagentPrintDTO.java
│ └── ReagentPrintRecordDTO.java
│
├── com/px/basemanage/platform/main/reagent/controller
│ ├── ReagentController.java
│ └── ReagentControllerImpl.java
│
├── com/px/basemanage/platform/main/reagent/service
│ ├── ReagentService.java
│ ├── ReagentServiceImpl.java
│ └── ReagentPrintAssembler.java
│
├── com/px/basemanage/platform/main/reagent/dao
│ └── ReagentRecordMapper.java
│
└── com/px/basemanage/platform/main/basefile/service
├── BaseFileService.java
└── BaseFileServiceImpl.java以上就是SpringBoot將業(yè)務(wù)模塊數(shù)據(jù)導(dǎo)出為PDF的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot業(yè)務(wù)模塊數(shù)據(jù)導(dǎo)出為PDF的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot 應(yīng)用程序測試實現(xiàn)方案
這篇文章主要介紹了SpringBoot 應(yīng)用程序測試實現(xiàn)方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
JDK內(nèi)置工具之常用工具和實戰(zhàn)指令詳解
jdk提供了很多強(qiáng)大的工具有命令行也有可視化的,這篇文章主要介紹了JDK內(nèi)置工具之常用工具和實戰(zhàn)指令的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-08-08
@Scheduled注解不能同時執(zhí)行多個定時任務(wù)的解決方案
這篇文章主要介紹了@Scheduled注解不能同時執(zhí)行多個定時任務(wù)的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
SpringBoot項目執(zhí)行腳本 自動拉取最新代碼并重啟的實例內(nèi)容
在本篇文章里小編給大家整理的是一篇關(guān)于SpringBoot項目執(zhí)行腳本 自動拉取最新代碼并重啟的實例內(nèi)容,有需要的朋友們參考下。2019-12-12
springboot使用之多個filter的執(zhí)行順序以及配置方式
這篇文章主要介紹了springboot使用之多個filter的執(zhí)行順序以及配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

