由于业务需要,获取多个pdf的时候需要将多个pdf存入zip类型的压缩包中实现下载,导出的过程使用轮询的方式实现进度查询,每次操作将当前进度存储入redis中,获取进度的接口从redis查询信息,下面只详细展开zip下载的代码
这个操作步骤主要分两步
返回查询进度的id,操作完成后将把流信息存储入redis,提供下载使用
javaif (!redisUtil.isPing()) {
throw new BusinessException(ResultCodeEnum.ERR_0x1000.getCode(), "导出失败,Redis服务未连接!");
}
String id = StringUtil.generateId();
redisUtil.set(markKey, id, 3600);
Map<String, Object> map = new HashMap<>();
map.put("current", 0);
map.put("total", list.size());
String mapkey = markKey + id + ":";
redisUtil.set(mapkey, map, 3600);
//单线程线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
try {
poolExecutor.execute(() -> {
List<byte[]> pdfBytesList = new ArrayList<>();
List<String> filenames=new ArrayList<>();
for (PatientRequest p : list) {
byte[] bytes = null;
try {
bytes = getPdfByte(p);
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
Map<String, Object> map1 = redisUtil.get(mapkey, Map.class);
map1.put("current", (Integer) map1.get("current") + 1);
if (ArrayUtil.isNotEmpty(bytes)) {
filenames.add(String.format("%s_%s_%s_%s.pdf", province, name, p.getPatientId(), DateUtil.format(new Date(), "yyyyMMdd")));
pdfBytesList.add(bytes);
}
if (list.indexOf(p) == list.size() - 1) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos)) {
for (int i = 0; i < pdfBytesList.size(); i++) {
byte[] pdfBytes = pdfBytesList.get(i);
ZipEntry entry = new ZipEntry(filenames.get(i));
zos.putNextEntry(entry);
zos.write(pdfBytes);
zos.closeEntry();
}
zos.flush();
zos.close();
baos.flush();
map1.put("data", Base64.getEncoder().encodeToString(baos.toByteArray()));
} catch (IOException e) {
// 处理可能的异常
e.printStackTrace(); // 实际应用中应使用合适的日志记录方式
map1.put("data", null);
}
}
redisUtil.set(mapkey, map1);
}
});
} catch (Exception e) {
throw new BusinessException(ResultCodeEnum.ERR_0x1000.getCode(), e.getMessage());
} finally {
poolExecutor.shutdown();
}
return id;
oracle 和mysql不同,没有自增主键,需要自己定义主键
可以使用mybatisplus的UUID工具根据自己要求生成主键
mybatisPlus官方文档说明
UUid有个弊端就是太长的话,不利于索引的建立,会影响查询效率
使用序列也有两种方式
比如我之前这篇文章
sql
CREATE TABLE rec_supplement_record (
ID NUMBER(10) PRIMARY KEY,
patient_id VARCHAR2(255) NOT NULL,
visit_no VARCHAR2(255) NOT NULL,
doc_type VARCHAR2(255) NOT NULL,
supplement_send_id NUMBER(10) NOT NULL,
supplement_send_name VARCHAR2(255) NOT NULL,
supplement_receive_id NUMBER(10),
supplement_receive_name VARCHAR2(255),
supplement_receive_time Date,
supplement_send_reason VARCHAR2(400)
);
CREATE SEQUENCE rec_supplement_record_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 999999999999999999999999999
NOCYCLE
NOCACHE;
CREATE OR REPLACE TRIGGER rec_supplement_record_trg
BEFORE INSERT ON rec_supplement_record
FOR EACH ROW
WHEN (NEW.ID IS NULL)
BEGIN
SELECT rec_supplement_record_seq.NEXTVAL INTO :NEW.ID FROM dual;
END;
这样的话,很像mysql的自增主键的形式,每次插入数据时不需要定义主键,即可自动生成
业务需要,得通过javaweb下载用户的个人信息的pdf,多个人批量下载时就需要将pdf文件存放在zip类型中,但是下载得到的文件解压时提示错误,里面的文件大小为空
首先,生成zip文件时服务器并未提示,系统错误,导致的文件异常,上网查找发现,可能是流未关闭的问题,然后我使用的是try(reason) catch的方式,流会自动关闭 代码如下
java
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ZipOutputStream zipOut = new ZipOutputStream(baos)) {
// 遍历文件数据列表,将每个byte数组添加为zip条目
for (int i = 0; i < fileDataList.size(); i++) {
byte[] fileData = JSON.toJSONBytes(fileDataList.get(i).get("bytes"));
String entryName = (String) fileDataList.get(i).get("fileName");
ZipEntry zipEntry = new ZipEntry(entryName);
zipOut.putNextEntry(zipEntry);
zipOut.write(fileData);
zipOut.closeEntry();
}
zipOut.flush();
baos.flush();
// 设置HTTP响应头
response.setContentType("application/zip");
String fileName = String.format("%s_%s_全病案_%s.zip", province, name, DateUtil.format(new Date(), "yyyyMMdd"));
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 将zip数据写入响应流
IoUtil.copy(new ByteArrayInputStream(baos.toByteArray()), response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
深入挖掘
自动关闭流是在整个业务流程走完之后才会一一关闭流,但是zipout这个流使用完毕之后就需要立即关闭, 即在 zipOut.flush();之后就需要关闭
修改代码后就不会提示了
java
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ZipOutputStream zipOut = new ZipOutputStream(baos)) {
// 遍历文件数据列表,将每个byte数组添加为zip条目
for (int i = 0; i < fileDataList.size(); i++) {
byte[] fileData = JSON.toJSONBytes(fileDataList.get(i).get("bytes"));
String entryName = (String) fileDataList.get(i).get("fileName");
ZipEntry zipEntry = new ZipEntry(entryName);
zipOut.putNextEntry(zipEntry);
zipOut.write(fileData);
zipOut.closeEntry();
}
zipOut.flush();
zipOut.close();
baos.flush();
// 设置HTTP响应头
response.setContentType("application/zip");
String fileName = String.format("%s_%s_全病案_%s.zip", province, name, DateUtil.format(new Date(), "yyyyMMdd"));
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 将zip数据写入响应流
IoUtil.copy(new ByteArrayInputStream(baos.toByteArray()), response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
现场服务可能会出现Redis服务异常的问题,每次使用redis之前都需要对redis进行连通性判断
java
//判断redis是否可用
public boolean isPing(){
try {
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
String response = connection.ping();
return "PONG".equals(response);
} catch (Exception e) {
log.error(e.getMessage)
return false;
}
}
mybatisPlus更新时,如果想让一个字段为空,实体类字段设置为null时,mybatisPlus默认会直接忽略这个字段的内容,就是不会把空值更新到数据库中,避免出现问题。
application.yml文件修改配置,但是不推荐,每次使用实体类更新时都需要把所有字段的内容都填上,不然就会出现空值替换的问题
ymlmybatis-plus:
global-config:
#字段策略 0:"忽略判断",1:"非 NULL 判断",2:"非空判断"
field-strategy: 0
java
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(User::getName,null);
userMapper.updateById(null,updateWrapper)
这个问题和全局配置一样,使用时需要非常的小心
//不忽略null @TableField(strategy = FieldStrategy.IGNORED) private Integer keyStroke; //键盘快捷键
在mapper.xml,编写更新sql
UPDATE REC_TYPE_DICT SET key_stroke=? WHERE id=?