This commit is contained in:
caozehui
2024-12-09 14:43:11 +08:00
parent 843d97e367
commit 048021bb57
64 changed files with 2563 additions and 263 deletions

View File

@@ -33,6 +33,12 @@
<artifactId>user</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -13,6 +13,7 @@ import com.njcn.db.mybatisplus.constant.DbConstant;
import com.njcn.gather.system.dictionary.mapper.DictPqMapper;
import com.njcn.gather.system.dictionary.pojo.param.DictPqParam;
import com.njcn.gather.system.dictionary.pojo.po.DictPq;
import com.njcn.gather.system.dictionary.service.IDictDataService;
import com.njcn.gather.system.dictionary.service.IDictPqService;
import com.njcn.gather.system.pojo.enums.SystemResponseEnum;
import com.njcn.web.factory.PageFactory;
@@ -28,6 +29,8 @@ import java.util.List;
@RequiredArgsConstructor
public class DictPqServiceImpl extends ServiceImpl<DictPqMapper, DictPq> implements IDictPqService {
private final IDictDataService dictDataService;
@Override
public Page<DictPq> listDictPqs(DictPqParam.QueryParam queryParam) {
QueryWrapper<DictPq> queryWrapper = new QueryWrapper<>();
@@ -71,6 +74,7 @@ public class DictPqServiceImpl extends ServiceImpl<DictPqMapper, DictPq> impleme
@Override
public boolean deleteDictPq(List<String> ids) {
dictDataService.deleteDictDataByDictTypeId(ids);
return this.lambdaUpdate()
.set(DictPq::getState, DataStateEnum.DELETED.getCode())
.in(DictPq::getId, ids)

View File

@@ -0,0 +1,114 @@
package com.njcn.gather.system.log.aop;
import cn.hutool.extra.spring.SpringUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.njcn.common.bean.CustomCacheUtil;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.db.mybatisplus.constant.UserConstant;
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
import com.njcn.gather.system.log.service.ISysLogAuditService;
import com.njcn.web.utils.HttpServletUtil;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.*;
/**
* @author caozehui
* @data 2024-12-2
*/
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class LogAdvice implements ApplicationListener<ContextRefreshedEvent> {
private final ISysLogAuditService sysLogAuditService;
private BlockingQueue<SysLogAudit> logQueue = new LinkedBlockingDeque<>();
@Pointcut(value = "execution(* com.njcn.gather..controller.*(..))")
public void logPointcut() {
}
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
CustomCacheUtil customCacheUtil = SpringUtil.getBean(CustomCacheUtil.CACHE_NAME);
String username = customCacheUtil.get(UserConstant.USER_NAME, false);
// String operationType = "";
// String operateResult = "成功";
// Integer level = 0;
// Integer warn = 0;
Object result = null;
try {
result = joinPoint.proceed();
addLogToQueue(joinPoint, username, "操作日志", "成功", 0, 0);
} catch (Throwable e) {
addLogToQueue(joinPoint, username, "告警日志", "失败", 1, 1);
throw e;
}
return result;
}
private void addLogToQueue(ProceedingJoinPoint joinPoint, String username, String operationType, String result, Integer level, Integer warn) {
SysLogAudit sysLogAudit = new SysLogAudit();
sysLogAudit.setCreateBy(username);
sysLogAudit.setOperateType(operationType);
sysLogAudit.setLevel(level);
sysLogAudit.setWarn(warn);
HttpServletRequest request = HttpServletUtil.getRequest();
sysLogAudit.setIp(request.getRemoteAddr());
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
OperateInfo operateInfo = method.getAnnotation(OperateInfo.class);
if (operateInfo != null) {
//注解上的操作类型
sysLogAudit.setResult(operateInfo.operateType() + result);
}
if (apiOperation != null) {
//注解上的描述
sysLogAudit.setRemark(apiOperation.value());
}
//Object[] args = joinPoint.getArgs();
logQueue.add(sysLogAudit);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
ExecutorService pool = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setNameFormat("Save-Logs-%d").setDaemon(true).build());
pool.submit(new Runnable() {
@Override
public void run() {
while (true) {
try {
SysLogAudit log = logQueue.poll(5, TimeUnit.MILLISECONDS);
if (log != null) {
sysLogAuditService.save(log);
}
} catch (Exception e) {
log.error("insert operation log to db error", e);
}
}
}
});
}
}

View File

@@ -0,0 +1,60 @@
package com.njcn.gather.system.log.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.LogUtil;
import com.njcn.gather.system.log.pojo.param.SysLogParam;
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
import com.njcn.gather.system.log.service.ISysLogAuditService;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.HttpResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author caozehui
* @date 2024-11-29
*/
@Slf4j
@Api(tags = "日志管理")
@RestController
@RequestMapping("/sysLog")
@RequiredArgsConstructor
public class SysLogController extends BaseController {
private final ISysLogAuditService sysLogAuditService;
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/list")
@ApiOperation(value = "获取日志列表")
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
public HttpResult<Page<SysLogAudit>> list(@RequestBody @Validated SysLogParam.QueryParam param) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
Page<SysLogAudit> result = sysLogAuditService.listSysLogAudit(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
@PostMapping("/export")
@ApiOperation("导出csv文件")
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
public void export(@RequestBody @Validated SysLogParam.QueryParam param) {
String methodDescribe = getMethodDescribe("export");
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
sysLogAuditService.exportSysLogAuditData(param);
}
}

View File

@@ -0,0 +1,13 @@
package com.njcn.gather.system.log.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
/**
* @author caozehui
* @date 2024-11-29
*/
public interface SysLogAuditMapper extends MPJBaseMapper<SysLogAudit> {
}

View File

@@ -0,0 +1,13 @@
package com.njcn.gather.system.log.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import com.njcn.gather.system.log.pojo.po.SysLogRun;
/**
* @author caozehui
* @date 2024-11-29
*/
public interface SysLogRunMapper extends MPJBaseMapper<SysLogRun> {
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.gather.system.log.mapper.SysLogAuditMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.gather.system.log.mapper.SysLogRunMapper">
</mapper>

View File

@@ -0,0 +1,29 @@
package com.njcn.gather.system.log.pojo.param;
import com.njcn.common.pojo.constant.PatternRegex;
import com.njcn.gather.system.pojo.constant.SystemValidMessage;
import com.njcn.web.pojo.param.BaseParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Pattern;
/**
* @author caozehui
* @data 2024-11-25
*/
@Data
public class SysLogParam {
@Data
@EqualsAndHashCode(callSuper = true)
public static class QueryParam extends BaseParam {
@ApiModelProperty("操作类型")
private String operateType;
@ApiModelProperty("操作用户")
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.USER_ID_FORMAT_ERROR)
private String createBy;
}
}

View File

@@ -0,0 +1,74 @@
package com.njcn.gather.system.log.pojo.po;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author caozehui
* @date 2024-11-29
*/
@Data
@TableName("sys_log_audit")
public class SysLogAudit implements Serializable {
private static final long serialVersionUID = -56962081010894238L;
/**
* 审计日志Id
*/
private String id;
/**
* 操作类型
*/
private String operateType;
/**
* IP
*/
private String ip;
/**
* 事件结果
*/
private String result;
/**
* 事件描述
*/
private String remark;
/**
* 事件严重度(0.普通 1.中等 2.严重)
*/
private Integer level;
/**
* 告警标志0未告警;1已告警默认未告警
*/
private Integer warn;
/**
* 创建用户
*/
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,58 @@
package com.njcn.gather.system.log.pojo.po;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author caozehui
* @date 2024-11-29
*/
@Data
@TableName("sys_log_run")
public class SysLogRun implements Serializable {
private static final long serialVersionUID = -37126398654461376L;
/**
* 运行日志Id
*/
private String id;
/**
* 类型(数据源、被检设备)
*/
private String type;
/**
* IP
*/
private String ip;
/**
* 事件结果
*/
private String result;
/**
* 事件描述
*/
private String remark;
/**
* 告警标志0未告警;1已告警默认未告警
*/
private Integer warn;
/**
* 创建用户
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,42 @@
package com.njcn.gather.system.log.pojo.vo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author caozehui
* @data 2024-11-25
*/
@Data
public class SysLogVO {
private String id;
/**
* 操作类型,审计日志独有字段。
*/
private String operateType;
/**
* 类型(数据源、被检设备),运行日志独有字段。
*/
private String type;
private String ip;
private String result;
private String remark;
/**
* 事件严重度(0.普通 1.中等 2.严重),审计日志独有字段。
*/
private Integer level;
private Integer warn;
private String createBy;
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,37 @@
package com.njcn.gather.system.log.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.system.log.pojo.param.SysLogParam;
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
import com.njcn.gather.system.log.pojo.vo.SysLogVO;
import org.aspectj.lang.JoinPoint;
/**
* @author caozehui
* @date 2024-11-29
*/
public interface ISysLogAuditService extends IService<SysLogAudit> {
/**
* 分页查询审计日志
*
* @param param 查询参数
* @return 分页结果
*/
Page<SysLogAudit> listSysLogAudit(SysLogParam.QueryParam param);
/**
* 导出审计日志数据
* @param param 查询参数
*/
void exportSysLogAuditData(SysLogParam.QueryParam param);
/**
* 添加审计日志
*
* @param sysLogParam
* @return 成功返回true失败返回false
*/
//boolean addSysLogAudit(SysLogParam sysLogParam);
}

View File

@@ -0,0 +1,12 @@
package com.njcn.gather.system.log.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.system.log.pojo.po.SysLogRun;
/**
* @author caozehui
* @date 2024-11-29
*/
public interface ISysLogRunService extends IService<SysLogRun> {
}

View File

@@ -0,0 +1,85 @@
package com.njcn.gather.system.log.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.gather.system.log.mapper.SysLogAuditMapper;
import com.njcn.gather.system.log.pojo.param.SysLogParam;
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
import com.njcn.gather.system.log.service.ISysLogAuditService;
import com.njcn.gather.system.log.util.CSVUtil;
import com.njcn.gather.user.user.pojo.po.SysUser;
import com.njcn.gather.user.user.service.ISysUserService;
import com.njcn.web.factory.PageFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author caozehui
* @date 2024-11-29
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLogAuditServiceImpl extends ServiceImpl<SysLogAuditMapper, SysLogAudit> implements ISysLogAuditService {
private final ISysUserService sysUserService;
@Override
public Page<SysLogAudit> listSysLogAudit(SysLogParam.QueryParam param) {
QueryWrapper<SysLogAudit> queryWrapper = new QueryWrapper<>();
if (ObjectUtil.isNotNull(param)) {
queryWrapper.eq(StrUtil.isNotBlank(param.getOperateType()), "sys_log_audit.Operate_Type", param.getOperateType())
.eq(StrUtil.isNotBlank(param.getCreateBy()), "sys_log_audit.Create_By", param.getCreateBy())
.between(StrUtil.isAllNotBlank(param.getSearchBeginTime(), param.getSearchEndTime()), "sys_log_audit.Create_Time", param.getSearchBeginTime(), param.getSearchEndTime());
}
queryWrapper.orderByDesc("sys_log_audit.Create_Time");
return this.page(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), queryWrapper);
}
@Override
public void exportSysLogAuditData(SysLogParam.QueryParam param) {
String[] titles = {"日志类型", "IP", "事件结果", "描述", "日志等级", "告警标准", "操作用户", "记录时间"};
String[] keys = {"operateType", "ip", "result", "remark", "level", "warn", "createBy", "createTime"};
QueryWrapper<SysLogAudit> queryWrapper = new QueryWrapper<>();
if (ObjectUtil.isNotNull(param)) {
queryWrapper.eq(StrUtil.isNotBlank(param.getOperateType()), "sys_log_audit.Operate_Type", param.getOperateType()).eq(StrUtil.isNotBlank(param.getCreateBy()), "sys_log_audit.Create_By", param.getCreateBy()).between(StrUtil.isAllNotBlank(param.getSearchBeginTime(), param.getSearchEndTime()), "sys_log_audit.Create_Time", param.getSearchBeginTime(), param.getSearchEndTime());
}
queryWrapper.orderByDesc("sys_log_audit.Create_Time");
List<SysLogAudit> list = this.list(queryWrapper);
List<Map<String, Object>> data = list.stream().map(item -> {
Map<String, Object> map = new HashMap<>();
map.put("operateType", item.getOperateType());
map.put("ip", item.getIp());
map.put("result", item.getResult());
map.put("remark", item.getRemark());
map.put("level", item.getLevel() == 0 ? "普通" : item.getLevel() == 1 ? "中等" : "严重");
map.put("warn", item.getWarn() == 0 ? "未告警" : "告警");
SysUser user = sysUserService.getById(item.getCreateBy());
map.put("createBy", ObjectUtil.isNull(user) ? "" : user.getName());
//将 createTime 转换为 yyyy-MM-dd HH:mm:ss 格式
map.put("createTime", item.getCreateTime() == null ? "" : item.getCreateTime().toString().replace("T", " "));
return map;
}).collect(Collectors.toList());
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
CSVUtil.export("日志数据" + sdf.format(new Date()) + ".csv", titles, keys, data);
}
// @Override
// public boolean addSysLogAudit(SysLogParam sysLogParam) {
// SysLogAudit sysLogAudit = new SysLogAudit();
// BeanUtils.copyProperties(sysLogParam, sysLogAudit);
// return this.save(sysLogAudit);
// }
}

View File

@@ -0,0 +1,20 @@
package com.njcn.gather.system.log.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.gather.system.log.pojo.po.SysLogRun;
import com.njcn.gather.system.log.mapper.SysLogRunMapper;
import com.njcn.gather.system.log.service.ISysLogRunService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author caozehui
* @date 2024-11-29
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLogRunServiceImpl extends ServiceImpl<SysLogRunMapper, SysLogRun> implements ISysLogRunService {
}

View File

@@ -0,0 +1,125 @@
package com.njcn.gather.system.log.util;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.njcn.web.utils.HttpServletUtil;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
/**
* @author caozehui
* @data 2024-12-2
*/
@Slf4j
public class CSVUtil {
/**
* CSV文件列分隔符
*/
private static final String CSV_COLUMN_SEPARATOR = ",";
/**
* CSV文件行分隔符
*/
private static final String CSV_ROW_SEPARATOR = System.lineSeparator();
/**
* 导出CSV文件
*
* @param fileName 文件名
* @param titles 表头
* @param keys 字段名
* @param dataList 数据集
*/
public static void export(String fileName, String[] titles, String[] keys, List<Map<String, Object>> dataList) {
// 保证线程安全
StringBuffer buf = new StringBuffer();
// 组装表头
for (String title : titles) {
buf.append(title).append(CSV_COLUMN_SEPARATOR);
}
buf.append(CSV_ROW_SEPARATOR);
// 组装数据
if (CollectionUtils.isNotEmpty(dataList)) {
for (Map<String, Object> map : dataList) {
for (String key : keys) {
if (ObjectUtil.isEmpty(map.get(key))) {
buf.append("").append(CSV_COLUMN_SEPARATOR);
} else {
// 如果数据内容中包含逗号,则进行使用!替换
buf.append(map.get(key).toString().replaceAll(",", "!")).append(CSV_COLUMN_SEPARATOR);
}
}
buf.deleteCharAt(buf.length() - 1).append(CSV_ROW_SEPARATOR);
}
}
HttpServletResponse response = HttpServletUtil.getResponse();
try {
ServletOutputStream os = response.getOutputStream();
Throwable var1 = null;
try {
fileName = URLEncoder.encode(fileName, "UTF-8");
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.setContentType("application/octet-stream;charset=UTF-8");
// 写出响应
os.write(buf.toString().getBytes("UTF-8"));
os.flush();
} catch (Throwable var2) {
var1 = var2;
throw var2;
} finally {
if (os != null) {
if (var1 != null) {
try {
os.close();
} catch (Throwable var3) {
var1.addSuppressed(var3);
}
} else {
os.close();
}
}
}
} catch (IOException var4) {
IOException e = var4;
log.error(">>> 导出数据异常:{}", e.getMessage());
}
}
/**
* 设置Header
*
* @param fileName 文件名
* @throws
*/
// public static void responseSetProperties(String fileName) {
// HttpServletResponse response = HttpServletUtil.getResponse();
// // 设置文件后缀
// SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
// String fn = fileName + sdf.format(new Date()) + ".csv";
//
// fileName = URLEncoder.encode(fileName, "UTF-8");
//
// // 设置响应
//// response.setContentType("application/ms-txt.numberformat:@");
// response.setContentType("application/octet-stream;charset=UTF-8");
// response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
//// response.setCharacterEncoding("UTF-8");
//// response.setHeader("Pragma", "public");
//// response.setHeader("Cache-Control", "max-age=30");
// response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fn, "UTF-8"));
// }
}