try-with-resources 实现自动关闭资源
上传文件和JSON时 @RequestBody和Multipart不能同时存在的解决办法
解决SpringBoot中@RequestBody不能和Multipart同时传递的问题 解决办法 方法一:就是将Body参数 【转为】 表单参数,后台使用对象正常接收 如:
header添加
"Content-Type": "multipart/form-data"
方法二(和方法一差不多):前端将表单对象转为json 字符串传递给后端(也是表单参数),后端可以通过request或者方法参数直接获取对象json字符串,通过JSON转化工具转为对象即可
JWT
TODO
EasyExcel导出Excel(带自定义样式)
pom.xml引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
编写样式工具类StyleUtils(可全局生效)
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
public class StyleUtils {
/**
* 标题样式
* @return
*/
public static WriteCellStyle getHeadStyle(){
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景颜色
// headWriteCellStyle.setFillForegroundColor(IndexedColors.LIGHT_TURQUOISE1.getIndex());
// headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 字体
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontName("宋体");//设置字体名字
headWriteFont.setFontHeightInPoints((short)14);//设置字体大小
headWriteFont.setBold(true);//字体加粗
headWriteCellStyle.setWriteFont(headWriteFont); //在样式用应用设置的字体;
// 样式
headWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
headWriteCellStyle.setBottomBorderColor((short) 0);//设置底边框颜色;
headWriteCellStyle.setBorderLeft(BorderStyle.THIN); //设置左边框;
headWriteCellStyle.setLeftBorderColor((short) 0);//设置左边框颜色;
headWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
headWriteCellStyle.setRightBorderColor((short) 0);//设置右边框颜色;
headWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
headWriteCellStyle.setTopBorderColor((short) 0); //设置顶边框颜色;
headWriteCellStyle.setWrapped(true); //设置自动换行;
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//设置水平对齐的样式为居中对齐;
headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //设置垂直对齐的样式为居中对齐;
headWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适
return headWriteCellStyle;
}
/**
* 内容样式
* @return
*/
public static WriteCellStyle getContentStyle(){
// 内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 背景绿色
// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
// contentWriteCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
// contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 设置字体
WriteFont contentWriteFont = new WriteFont();
contentWriteFont.setFontHeightInPoints((short) 12);//设置字体大小
contentWriteFont.setFontName("宋体"); //设置字体名字
contentWriteCellStyle.setWriteFont(contentWriteFont);//在样式用应用设置的字体;
//设置样式;
contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
contentWriteCellStyle.setBottomBorderColor((short) 0);//设置底边框颜色;
contentWriteCellStyle.setBorderLeft(BorderStyle.THIN); //设置左边框;
contentWriteCellStyle.setLeftBorderColor((short) 0);//设置左边框颜色;
contentWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
contentWriteCellStyle.setRightBorderColor((short) 0);//设置右边框颜色;
contentWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
contentWriteCellStyle.setTopBorderColor((short) 0); ///设置顶边框颜色;
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// 水平居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直居中
contentWriteCellStyle.setWrapped(true); //设置自动换行;
// contentWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适
return contentWriteCellStyle;
}
}
编写动态处理器CustomCellWriteHandler(判断单元格内容,根据内容做样式修改等)
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
/**
* @Description: 描述
* @Author: coderzzc
* @CreateDate: 2023/8/1 17:43
* @UpdateUser: coderzzc
* @UpdateDate: 2023/8/1 17:43
* @UpdateRemark: 特殊说明
* @Version: 1.0
*/
public class CustomCellWriteHandler extends AbstractCellWriteHandler {
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
// 设置行高测试
int rowIndex = row.getRowNum();
System.out.println("当前行: " + rowIndex);
short height = 600;
row.setHeight(height);
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
Cell cell = context.getCell();
int rowIndex = cell.getRowIndex();
int cellIndex = cell.getColumnIndex();
// 自定义宽度处理
// 自定义样式处理
// 当前事件会在 数据设置到poi的cell里面才会回调
// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true
Head headData = context.getHeadData();
for (String s : headData.getHeadNameList()) {
if (s.contains("计算结果")) {
if (BooleanUtils.isNotTrue(context.getHead())) {
String stringCellValue = cell.getStringCellValue();
if (stringCellValue.chars().allMatch(Character::isDigit)) {
int parseInt = Integer.parseInt(stringCellValue);
if ((parseInt >= 15 && parseInt <= 30) || (parseInt >= 72 && parseInt <= 76) || (parseInt >= 88 && parseInt <= 96) || (parseInt >= 114 && parseInt <= 125) || (parseInt >= 167 && parseInt <= 178)) {
// 拿到poi的workbook
Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
// 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式
// 不同单元格尽量传同一个 cellStyle
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
//设置样式;
// xlsx格式
Font font = workbook.createFont();
font.setColor(IndexedColors.RED.getIndex()); // 设置字体颜色为红色
cellStyle.setFont(font);
cellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
cellStyle.setBottomBorderColor((short) 0);//设置底边框颜色;
cellStyle.setBorderLeft(BorderStyle.THIN); //设置左边框;
cellStyle.setLeftBorderColor((short) 0);//设置左边框颜色;
cellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
cellStyle.setRightBorderColor((short) 0);//设置右边框颜色;
cellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
cellStyle.setTopBorderColor((short) 0); ///设置顶边框颜色;
cellStyle.setAlignment(HorizontalAlignment.CENTER);// 水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直居中
cellStyle.setWrapText(true); //设置自动换行;
cell.setCellStyle(cellStyle);
// 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确
// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到
// cell里面去 会导致自己设置的不一样(很关键)
context.getFirstCellData().setWriteCellStyle(null);
}
}
}
}
break;
}
}
}
编写后端导出逻辑
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSON;
import edu.hebut.bone.StyleUtils;
import edu.hebut.bone.aspect.Auth;
import edu.hebut.bone.aspect.AuthAspect;
import edu.hebut.bone.config.CustomCellWriteHandler;
import edu.hebut.bone.domain.dao.CaseInfo;
import edu.hebut.bone.domain.dao.ChildCase;
import edu.hebut.bone.domain.enums.UserRoleEnum;
import edu.hebut.bone.exception.BoneException;
import edu.hebut.bone.exception.ExceptionEnum;
import edu.hebut.bone.service.CaseService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Slf4j
@RestController
@CrossOrigin
@RequestMapping("bone/case")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ExcelExport {
private final AuthAspect authAspect;
private final CaseService caseService;
@GetMapping("/exportExcel")
@Auth({UserRoleEnum.DOCTOR, UserRoleEnum.ADMIN})
public void ExportExcel(@RequestParam(value = "childCaseId")Long childCaseId, HttpServletResponse response) throws IOException, IllegalAccessException {
Long doctorId = authAspect.get("id", Long.class);
// 获取用户权限
UserRoleEnum role = UserRoleEnum.valueOf(authAspect.get("role", String.class));
ChildCase childCase = caseService.queryChildCase(childCaseId);
CaseInfo caseInfo = caseService.queryCase(childCase.getCaseId());
// 判断是否用户本人,非本人无权访问, 管理员可访问所有
if (!UserRoleEnum.ADMIN.equals(role)){
if (caseInfo == null || !caseInfo.getDoctor().equals(doctorId)){
throw new BoneException(ExceptionEnum.INSUFFICIENT_PRIVILEGES);
}
}
// 动态添加表头,适用一些表头动态变化的场景
WriteSheet sheet1 = new WriteSheet();
sheet1.setSheetName("诊断详情");
sheet1.setSheetNo(0);
response.setContentType("application/vnd.ms-excel;chartset=utf-8"); //文件扩展名为excel格式
response.setHeader("Content-Disposition", "attachment;filename=" + caseInfo.getPatient()); //触发文件名为filename的“另存为”对话框
// 设置单元格样式
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(StyleUtils.getHeadStyle(), StyleUtils.getContentStyle());
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为用户表 然后文件流会自动关闭
ExcelWriter writer = EasyExcelFactory.write(response.getOutputStream())
.inMemory(true) // 富文本
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 字体居中显示
.registerWriteHandler(new CustomCellWriteHandler())
.registerWriteHandler(horizontalCellStyleStrategy)
.autoCloseStream(true)
.excelType(ExcelTypeEnum.XLSX)
.build();
List<List<String>> head1 = Lists.newArrayList();
head1.add(Lists.newArrayList("病例信息","患者姓名"));
ArrayList<String> strings = Lists.newArrayList( "患者年龄", "身份证号", "分组", "病例号", "患者性别", "手机号","诊断", "创建日期");
for (String string : strings) {
head1.add(Lists.newArrayList("病例信息",string));
}
List<List<Object>> contentList = Lists.newArrayList();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format1 = dateTimeFormatter.format(caseInfo.getCreateBy());
for (Field field : caseInfo.getClass().getDeclaredFields()) {
field.setAccessible(true);
if (field.get(caseInfo) == null && field.getType() == String.class) {
field.set(caseInfo, "空");
}
}
contentList.add(Lists.newArrayList(
caseInfo.getPatient(),
caseInfo.getPatientAge()+"岁",
caseInfo.getIdCard(),
caseInfo.getAffectedSide()+caseInfo.getAffectedLimb()+caseInfo.getCaseType(),
caseInfo.getHospitalCaseId(),
caseInfo.getGender(),
caseInfo.getPhone(),
caseInfo.getCaseInfo(),
format1));
// 创建一个表格,用于 Sheet 中使用
WriteTable table1 = new WriteTable( );
table1.setTableNo(1);
table1.setHead(head1);
writer.write(contentList, sheet1, table1);
List<List<String>> head2 = Lists.newArrayList();
head2.add(Lists.newArrayList("畸形参数","患侧"));
for (String s : Lists.newArrayList( "正位位移", "侧位位移", "轴向位移", "参照断裂处", "正位角度", "侧位角度", "轴向角度")) {
head2.add(Lists.newArrayList("畸形参数",s));
}
List<List<Object>> contentList2 = Lists.newArrayList();
contentList2.add(Lists.newArrayList(childCase.getAffectedSide(),
childCase.getZwwyfx()+childCase.getZwwy()+"mm",childCase.getCwwyfx()+childCase.getCwwy()+"mm",
childCase.getZwwyfx()+childCase.getZwwy()+"mm",
childCase.getCzdlc(),
childCase.getZwjdfx()+childCase.getCwjd()+"°",
childCase.getCwjdfx()+childCase.getZxwy()+"°",
childCase.getZxjdfx()+childCase.getZxjd()+"°"));
WriteTable table2= new WriteTable();
table2.setTableNo(2);
table2.setHead(head2);
writer.write(contentList2, sheet1, table2);
List<List<String>> head3 = Lists.newArrayList();
head3.add(Lists.newArrayList("框架参数","近侧环直径"));
for (String s : Lists.newArrayList( "远侧环直径", "参考环中心正位位移", "参考环中心侧位位移", "参考环中心轴向位移", "参考环旋转角度")) {
head3.add(Lists.newArrayList("框架参数",s));
}
List<List<Object>> contentList3 = Lists.newArrayList();
contentList3.add(Lists.newArrayList(
childCase.getNearSideDiameter()+"mm",
childCase.getFarSideDiameter()+"mm",
childCase.getZwwyfxRef()+childCase.getZwwyRef()+"mm",
childCase.getCwwyfxRef()+childCase.getCwwyRef()+"mm",
childCase.getZwwyRef()+"mm",childCase.getSenseRotationRef()+childCase.getRotationAngleRef()+"°"));
WriteTable table3= new WriteTable();
table3.setTableNo(3);
table3.setHead(head3);
writer.write(contentList3, sheet1, table3);
String result = childCase.getResult();
result = StringUtils.replace(result, "\\\"", "\"");
List<List> parseArray = JSON.parseArray(result, List.class);
List<List<String>> head4 = Lists.newArrayList();
head4.add(Lists.newArrayList("计算结果","日期"));
for (String s : Lists.newArrayList("杆1(红)", "杆2(橙)", "杆3(黄)", "杆4(绿)", "杆5(蓝)", "杆6(紫)")) {
head4.add(Lists.newArrayList("计算结果",s));
}
List<List<Object>> contentList4 = Lists.newArrayList();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 获取当前时间
int day=0;
for (List list : parseArray) {
// 把日期往后增加一天,整数 往后推,负数往前移动
String format2 = formatter.format(caseInfo.getCreateBy().plusDays(day));
day++;
list.remove(0);
list.add(0,format2);
contentList4.add(list);
}
WriteTable table4= new WriteTable();
table4.setTableNo(4);
table4.setHead(head4);
writer.write(contentList4, sheet1, table4);
writer.finish();
}
}
编写前端下载逻辑
#api
export function exportCaseById(param) {
return request({
url: '/case/exportExcel',
method: 'get',
responseType: 'blob', //设置返回信息为二进制文件,默认为json
params: param
})
}
#页面中调用
exportCaseById({ childCaseId: this.recordId }).then(res => {
// debugger;
let blob = new Blob([res], { type: 'application/xlsx' });
let url = window.URL.createObjectURL(blob);
const link = document.createElement('a'); //创建a标签
link.href = url;
link.download = this.recordId + '.xlsx'; //重命名文件
link.click();
URL.revokeObjectURL(url);
})
文件上传下载
服务器本地文件处理(添加访问路径)
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//在windows环境下的图片存放资源路径
String winPath = System.getProperty("user.dir")+"\src\main\resources\static\files\";
//在Linux环境下的图片存放资源路径
String linuxPath = "/usr/local/my_project/files/";
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith("win")) { //windows系统
//第一个方法设置访问路径前缀,第二个方法设置资源路径
registry.addResourceHandler("/files/**").
addResourceLocations("file:"+winPath);
}else{//linux系统
registry.addResourceHandler("/files/**").
addResourceLocations("file:"+linuxPath);
}
WebMvcConfigurer.super.addResourceHandlers(registry);
}
Java日期处理的十个坑
时间处理
时间字符串自动封装为Date
在B/S开发中,前端传输到后端的时间为字符串格式,传统的方式用java类按格式转换成Date,最后入库保存,很是繁琐。我们的精力不应该放在这些参数的封装上,而是应该放在业务逻辑的实现上。所以,我们这里有一个非常有用的参数注解:@DateTimeFormat。 使用如下:
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lastLoginTime;
这样在spring-mvc自动封装过程中,会将满足pattern的字符串封装成Date。很是方便实用。
Date转换成字符串
在前后端交互中,不仅仅是从前端向后端传递数据,后端也需要向前端传递数据。此时就存在Date转换成字符串的格式问题。传统方法是在前端对参数进行格式化。那么就需要在前端写js转换方法,也很是繁琐。所以,我们这里也用注解的方式控制Date转换成字符串的格式:@JsonFormat。 使用如下:
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
public Date getLastLoginTime() {
return lastLoginTime;
}
import com.fasterxml.jackson.annotation.JsonFormat;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
private Date answerTime = new Date();
向后端发送请求返回200,但是响应内容为空
检查是否是cookie问题 前端跨域请求需要携带cookie,如果是vue项目的话配置axios
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: url,
timeout: 5000,
withCredentials: true,
});
或
axios.defaults.withCredentials = true;