Commit d7f3b33d by 杨浩

网络安全修复

parent a9506ff9
...@@ -78,6 +78,11 @@ ...@@ -78,6 +78,11 @@
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
...@@ -37,6 +37,8 @@ import org.springframework.data.redis.cache.RedisCacheManager; ...@@ -37,6 +37,8 @@ import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
...@@ -194,4 +196,15 @@ public class FoodnexusTenantAutoConfiguration { ...@@ -194,4 +196,15 @@ public class FoodnexusTenantAutoConfiguration {
return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches()); return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches());
} }
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
// 设置HttpOnly(关键配置,此方法无版本限制)
serializer.setUseHttpOnlyCookie(true);
// 补充其他安全配置
serializer.setSameSite("Lax"); // 防御CSRF
serializer.setCookiePath("/"); // 作用域
return serializer;
}
} }
...@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; ...@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import java.util.HashMap; import java.util.HashMap;
......
...@@ -15,4 +15,7 @@ public class SupplierMonthOrderPageReqVO extends PageParam { ...@@ -15,4 +15,7 @@ public class SupplierMonthOrderPageReqVO extends PageParam {
@Schema(description = "月度(yyyy-MM)") @Schema(description = "月度(yyyy-MM)")
private String month; private String month;
@Schema(description = "供应商id")
private Long supplierId;
} }
...@@ -76,6 +76,9 @@ public class ErpPurchaseReturnPageReqVO extends PageParam { ...@@ -76,6 +76,9 @@ public class ErpPurchaseReturnPageReqVO extends PageParam {
@Schema(description = "客户订单code") @Schema(description = "客户订单code")
private String customerOrderCode; private String customerOrderCode;
@Schema(description = "客户订单code")
private String orderCode;
@Schema(description = "客户名称") @Schema(description = "客户名称")
private String customerName; private String customerName;
......
...@@ -41,4 +41,7 @@ public class ErpSupplierPageReqVO extends PageParam { ...@@ -41,4 +41,7 @@ public class ErpSupplierPageReqVO extends PageParam {
@Schema(description = "状态") @Schema(description = "状态")
private Integer status; private Integer status;
@Schema(description = "")
private String businessGoodsType;
} }
\ No newline at end of file
...@@ -27,6 +27,9 @@ public class ErpSaleReturnPageReqVO extends PageParam { ...@@ -27,6 +27,9 @@ public class ErpSaleReturnPageReqVO extends PageParam {
@Schema(description = "客户编号", example = "1724") @Schema(description = "客户编号", example = "1724")
private Long customerId; private Long customerId;
@Schema(description = "客户名称")
private String customerName;
@Schema(description = "退货时间") @Schema(description = "退货时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] returnTime; private LocalDateTime[] returnTime;
...@@ -64,4 +67,7 @@ public class ErpSaleReturnPageReqVO extends PageParam { ...@@ -64,4 +67,7 @@ public class ErpSaleReturnPageReqVO extends PageParam {
@Schema(description = "客户订单id") @Schema(description = "客户订单id")
private Long customerOrderId; private Long customerOrderId;
@Schema(description = "客户订单编号")
private String customerOrderCode;
} }
\ No newline at end of file
...@@ -164,7 +164,7 @@ public interface ErpPurchaseOrderMapper extends BaseMapperX<ErpPurchaseOrderDO> ...@@ -164,7 +164,7 @@ public interface ErpPurchaseOrderMapper extends BaseMapperX<ErpPurchaseOrderDO>
wrapper.eq(CommonUtil.isNotEmpty(supplierId), "t.supplier_id", supplierId); wrapper.eq(CommonUtil.isNotEmpty(supplierId), "t.supplier_id", supplierId);
wrapper.in("t.delivery_status", CommonUtil.asList(ErpDeliveryStatus.RECONCILIATION.getStatus(), wrapper.in("t.delivery_status", CommonUtil.asList(ErpDeliveryStatus.RECONCILIATION.getStatus(),
ErpDeliveryStatus.ARRIVAL.getStatus())); ErpDeliveryStatus.ARRIVAL.getStatus()));
wrapper.in("t1.order_status", CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey()), CustomerOrderStatus.FINISH.getKey()); wrapper.in("t1.order_status", CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey(), CustomerOrderStatus.FINISH.getKey(), CustomerOrderStatus.RETURN.getKey()));
wrapper.eq(CommonUtil.isNotBlank(pageReqVO.getMonth()), "DATE_FORMAT(t.create_time, '%Y-%m') ", pageReqVO.getMonth()); wrapper.eq(CommonUtil.isNotBlank(pageReqVO.getMonth()), "DATE_FORMAT(t.create_time, '%Y-%m') ", pageReqVO.getMonth());
wrapper.orderByDesc("t.id"); wrapper.orderByDesc("t.id");
......
...@@ -76,6 +76,9 @@ public interface ErpPurchaseReturnMapper extends BaseMapperX<ErpPurchaseReturnDO ...@@ -76,6 +76,9 @@ public interface ErpPurchaseReturnMapper extends BaseMapperX<ErpPurchaseReturnDO
query.betweenIfPresent(ErpSaleReturnDO::getDeliveryTime, reqVO.getDeliveryTime()); query.betweenIfPresent(ErpSaleReturnDO::getDeliveryTime, reqVO.getDeliveryTime());
query.groupBy(ErpPurchaseReturnDO::getId); query.groupBy(ErpPurchaseReturnDO::getId);
} }
if (CommonUtil.isNotBlank(reqVO.getOrderCode())) {
reqVO.setCustomerOrderCode(reqVO.getOrderCode());
}
if (CommonUtil.isNotBlank(reqVO.getCustomerOrderCode())) { if (CommonUtil.isNotBlank(reqVO.getCustomerOrderCode())) {
query.leftJoin("order_customer_order oco3 on oco3.id = t.customer_order_id"); query.leftJoin("order_customer_order oco3 on oco3.id = t.customer_order_id");
query.eq("oco3.code", reqVO.getCustomerOrderCode()); query.eq("oco3.code", reqVO.getCustomerOrderCode());
......
...@@ -29,6 +29,7 @@ public interface ErpSupplierMapper extends BaseMapperX<ErpSupplierDO> { ...@@ -29,6 +29,7 @@ public interface ErpSupplierMapper extends BaseMapperX<ErpSupplierDO> {
.likeIfPresent(ErpSupplierDO::getContact, reqVO.getContact()) .likeIfPresent(ErpSupplierDO::getContact, reqVO.getContact())
.eqIfPresent(ErpSupplierDO::getAuditStatus, reqVO.getAuditStatus()) .eqIfPresent(ErpSupplierDO::getAuditStatus, reqVO.getAuditStatus())
.eqIfPresent(ErpSupplierDO::getStatus, reqVO.getStatus()) .eqIfPresent(ErpSupplierDO::getStatus, reqVO.getStatus())
.likeIfPresent(ErpSupplierDO::getBusinessGoodsType, reqVO.getBusinessGoodsType())
.orderByDesc(ErpSupplierDO::getId)); .orderByDesc(ErpSupplierDO::getId));
} }
......
...@@ -6,6 +6,7 @@ import cn.iocoder.foodnexus.framework.common.util.CommonUtil; ...@@ -6,6 +6,7 @@ import cn.iocoder.foodnexus.framework.common.util.CommonUtil;
import cn.iocoder.foodnexus.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.foodnexus.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.foodnexus.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.foodnexus.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.foodnexus.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnPageReqVO; import cn.iocoder.foodnexus.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnPageReqVO;
import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpCustomerDO;
import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpSaleOutDO; import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpSaleOutDO;
import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpSaleReturnDO; import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpSaleReturnDO;
import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO; import cn.iocoder.foodnexus.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO;
...@@ -60,6 +61,16 @@ public interface ErpSaleReturnMapper extends BaseMapperX<ErpSaleReturnDO> { ...@@ -60,6 +61,16 @@ public interface ErpSaleReturnMapper extends BaseMapperX<ErpSaleReturnDO> {
.like(ProductSpuDO::getName, reqVO.getProductName()) .like(ProductSpuDO::getName, reqVO.getProductName())
.groupBy(ErpSaleReturnDO::getId); .groupBy(ErpSaleReturnDO::getId);
} }
if (CommonUtil.isNotEmpty(reqVO.getCustomerName())) {
query.leftJoin(ErpCustomerDO.class, ErpCustomerDO::getId, ErpSaleReturnDO::getCustomerId);
query.like(ErpCustomerDO::getName, reqVO.getCustomerName());
query.groupBy(ErpSaleReturnDO::getId);
}
if (CommonUtil.isNotEmpty(reqVO.getCustomerOrderCode())) {
query.leftJoin("order_customer_order oco on t.customer_order_id = oco.id");
query.eq("oco.code", reqVO.getCustomerOrderCode());
query.groupBy(ErpSaleReturnDO::getId);
}
return selectJoinPage(reqVO, ErpSaleReturnDO.class, query); return selectJoinPage(reqVO, ErpSaleReturnDO.class, query);
} }
......
...@@ -195,7 +195,7 @@ public class ErpPurchaseReturnServiceImpl implements ErpPurchaseReturnService { ...@@ -195,7 +195,7 @@ public class ErpPurchaseReturnServiceImpl implements ErpPurchaseReturnService {
private List<ErpPurchaseReturnItemDO> validatePurchaseReturnItems(List<ErpPurchaseReturnSaveReqVO.Item> list) { private List<ErpPurchaseReturnItemDO> validatePurchaseReturnItems(List<ErpPurchaseReturnSaveReqVO.Item> list) {
// 1. 校验产品存在 // 1. 校验产品存在
List<ProductSpuDO> productList = productService.validProductList( List<ProductSpuDO> productList = productService.getSpuList(
convertSet(list, ErpPurchaseReturnSaveReqVO.Item::getProductId)); convertSet(list, ErpPurchaseReturnSaveReqVO.Item::getProductId));
Map<Long, ProductSpuDO> productMap = convertMap(productList, ProductSpuDO::getId); Map<Long, ProductSpuDO> productMap = convertMap(productList, ProductSpuDO::getId);
// 2. 转化为 ErpPurchaseReturnItemDO 列表 // 2. 转化为 ErpPurchaseReturnItemDO 列表
......
...@@ -10,6 +10,7 @@ import cn.iocoder.foodnexus.framework.tenant.core.aop.TenantIgnore; ...@@ -10,6 +10,7 @@ import cn.iocoder.foodnexus.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.*; import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.foodnexus.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.foodnexus.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.foodnexus.module.infra.service.file.FileService; import cn.iocoder.foodnexus.module.infra.service.file.FileService;
import cn.iocoder.foodnexus.module.infra.service.util.FileUploadValidator;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
...@@ -20,6 +21,7 @@ import jakarta.servlet.http.HttpServletRequest; ...@@ -20,6 +21,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
...@@ -45,9 +47,10 @@ public class FileController { ...@@ -45,9 +47,10 @@ public class FileController {
@Operation(summary = "上传文件", description = "模式一:后端上传文件") @Operation(summary = "上传文件", description = "模式一:后端上传文件")
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception { public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile(); MultipartFile file = uploadReqVO.getFile();
String fileName = FileUploadValidator.validateFileFormat(file);
byte[] content = IoUtil.readBytes(file.getInputStream()); byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(), return success(fileService.createFile(content, fileName,
uploadReqVO.getDirectory(), file.getContentType())); null, file.getContentType()));
} }
@GetMapping("/presigned-url") @GetMapping("/presigned-url")
...@@ -112,6 +115,26 @@ public class FileController { ...@@ -112,6 +115,26 @@ public class FileController {
writeAttachment(response, path, content); writeAttachment(response, path, content);
} }
@GetMapping("/downland/{id}")
@PermitAll
@TenantIgnore
@Operation(summary = "下载文件")
@Parameter(name = "configId", description = "配置编号", required = true)
public void downland(HttpServletRequest request,
HttpServletResponse response,
@PathVariable("id") Long id) throws Exception {
FileDO file = fileService.getFile(id);
// 读取内容
byte[] content = fileService.getFileContent(file.getConfigId(), file.getPath());
if (content == null) {
log.warn("[getFileContent][configId({}) path({}) 文件不存在]", file.getConfigId(), file.getPath());
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
writeAttachment(response, file.getPath(), content);
}
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得文件分页") @Operation(summary = "获得文件分页")
@PreAuthorize("@ss.hasPermission('infra:file:query')") @PreAuthorize("@ss.hasPermission('infra:file:query')")
......
...@@ -16,13 +16,13 @@ public class FileUploadReqVO { ...@@ -16,13 +16,13 @@ public class FileUploadReqVO {
@NotNull(message = "文件附件不能为空") @NotNull(message = "文件附件不能为空")
private MultipartFile file; private MultipartFile file;
@Schema(description = "文件目录", example = "XXX/YYY") /*@Schema(description = "文件目录", example = "XXX/YYY")
private String directory; private String directory;
@AssertTrue(message = "文件目录不正确") @AssertTrue(message = "文件目录不正确")
@JsonIgnore @JsonIgnore
public boolean isDirectoryValid() { public boolean isDirectoryValid() {
return !StrUtil.containsAny(directory, "..", "/", "\\"); return !StrUtil.containsAny(directory, "..", "/", "\\");
} }*/
} }
...@@ -6,6 +6,7 @@ import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.FileCreat ...@@ -6,6 +6,7 @@ import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.FileCreat
import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.iocoder.foodnexus.module.infra.controller.app.file.vo.AppFileUploadReqVO; import cn.iocoder.foodnexus.module.infra.controller.app.file.vo.AppFileUploadReqVO;
import cn.iocoder.foodnexus.module.infra.service.file.FileService; import cn.iocoder.foodnexus.module.infra.service.file.FileService;
import cn.iocoder.foodnexus.module.infra.service.util.FileUploadValidator;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
...@@ -35,9 +36,10 @@ public class AppFileController { ...@@ -35,9 +36,10 @@ public class AppFileController {
@PermitAll @PermitAll
public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception { public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile(); MultipartFile file = uploadReqVO.getFile();
String fileName = FileUploadValidator.validateFileFormat(file);
byte[] content = IoUtil.readBytes(file.getInputStream()); byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(), return success(fileService.createFile(content, fileName,
uploadReqVO.getDirectory(), file.getContentType())); null, file.getContentType()));
} }
@GetMapping("/presigned-url") @GetMapping("/presigned-url")
......
...@@ -16,13 +16,13 @@ public class AppFileUploadReqVO { ...@@ -16,13 +16,13 @@ public class AppFileUploadReqVO {
@NotNull(message = "文件附件不能为空") @NotNull(message = "文件附件不能为空")
private MultipartFile file; private MultipartFile file;
@Schema(description = "文件目录", example = "XXX/YYY") /*@Schema(description = "文件目录", example = "XXX/YYY")
private String directory; private String directory;
@AssertTrue(message = "文件目录不正确") @AssertTrue(message = "文件目录不正确")
@JsonIgnore @JsonIgnore
public boolean isDirectoryValid() { public boolean isDirectoryValid() {
return !StrUtil.containsAny(directory, "..", "/", "\\"); return !StrUtil.containsAny(directory, "..", "/", "\\");
} }*/
} }
...@@ -33,6 +33,7 @@ public interface ErrorCodeConstants { ...@@ -33,6 +33,7 @@ public interface ErrorCodeConstants {
ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在"); ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在");
ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在"); ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在");
ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空"); ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空");
ErrorCode FILE_NOT_ALLOWED = new ErrorCode(1_001_003_003, "文件格式不支持");
// ========== 代码生成器 1-001-004-000 ========== // ========== 代码生成器 1-001-004-000 ==========
ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, "表定义已经存在"); ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, "表定义已经存在");
......
package cn.iocoder.foodnexus.module.infra.framework.file.core.client; package cn.iocoder.foodnexus.module.infra.framework.file.core.client;
import cn.hutool.core.util.StrUtil;
/** /**
* 文件客户端 * 文件客户端
* *
......
...@@ -53,4 +53,8 @@ public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> { ...@@ -53,4 +53,8 @@ public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {
return config.getBasePath() + File.separator + path; return config.getBasePath() + File.separator + path;
} }
public String getDomain() {
return config.getDomain();
}
} }
...@@ -83,8 +83,9 @@ public class FileTypeUtils { ...@@ -83,8 +83,9 @@ public class FileTypeUtils {
String contentType = getMineType(content, filename); String contentType = getMineType(content, filename);
response.setContentType(contentType); response.setContentType(contentType);
// 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html // 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html
if (StrUtil.containsIgnoreCase(contentType, "image/")) { if (filename.toLowerCase().endsWith(".svg")) {
// 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论 response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
} else if (StrUtil.containsIgnoreCase(contentType, "image/")) {
response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename)); response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename));
}// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题 }// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
else if (StrUtil.containsIgnoreCase(contentType, "video")) { else if (StrUtil.containsIgnoreCase(contentType, "video")) {
...@@ -94,6 +95,7 @@ public class FileTypeUtils { ...@@ -94,6 +95,7 @@ public class FileTypeUtils {
response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Accept-Ranges", "bytes");
} else { } else {
response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
response.setHeader("X-Content-Type-Options", "nosniff");
} }
// 输出附件 // 输出附件
......
...@@ -85,4 +85,5 @@ public interface FileService { ...@@ -85,4 +85,5 @@ public interface FileService {
*/ */
byte[] getFileContent(Long configId, String path) throws Exception; byte[] getFileContent(Long configId, String path) throws Exception;
FileDO getFile(Long id);
} }
...@@ -14,6 +14,7 @@ import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.FilePresi ...@@ -14,6 +14,7 @@ import cn.iocoder.foodnexus.module.infra.controller.admin.file.vo.file.FilePresi
import cn.iocoder.foodnexus.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.foodnexus.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.foodnexus.module.infra.dal.mysql.file.FileMapper; import cn.iocoder.foodnexus.module.infra.dal.mysql.file.FileMapper;
import cn.iocoder.foodnexus.module.infra.framework.file.core.client.FileClient; import cn.iocoder.foodnexus.module.infra.framework.file.core.client.FileClient;
import cn.iocoder.foodnexus.module.infra.framework.file.core.client.local.LocalFileClient;
import cn.iocoder.foodnexus.module.infra.framework.file.core.utils.FileTypeUtils; import cn.iocoder.foodnexus.module.infra.framework.file.core.utils.FileTypeUtils;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
...@@ -86,9 +87,15 @@ public class FileServiceImpl implements FileService { ...@@ -86,9 +87,15 @@ public class FileServiceImpl implements FileService {
String url = client.upload(content, path, type); String url = client.upload(content, path, type);
// 3. 保存到数据库 // 3. 保存到数据库
fileMapper.insert(new FileDO().setConfigId(client.getId()) FileDO fileDO = new FileDO().setConfigId(client.getId())
.setName(name).setPath(path).setUrl(url) .setName(name).setPath(path).setUrl(url)
.setType(type).setSize(content.length)); .setType(type).setSize(content.length);
fileMapper.insert(fileDO);
Long id = fileDO.getId();
if (client instanceof LocalFileClient) {
return StrUtil.format("{}/admin-api/infra/file/downland/{}", ((LocalFileClient) client).getDomain(), id);
}
return url; return url;
} }
...@@ -198,4 +205,9 @@ public class FileServiceImpl implements FileService { ...@@ -198,4 +205,9 @@ public class FileServiceImpl implements FileService {
return client.getContent(path); return client.getContent(path);
} }
@Override
public FileDO getFile(Long id) {
return validateFileExists(id);
}
} }
package cn.iocoder.foodnexus.module.infra.service.util;
import cn.iocoder.foodnexus.framework.common.exception.ServiceException;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import static cn.iocoder.foodnexus.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.foodnexus.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
import static cn.iocoder.foodnexus.module.infra.enums.ErrorCodeConstants.FILE_NOT_ALLOWED;
/**
* @author : yanghao
* create at: 2025/11/17 17:22
* @description:
*/
public class FileUploadValidator {
// 允许的文件后缀(新增视频格式)
private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList(
// 图片
"jpg", "jpeg", "png", "gif", "bmp", "webp",
// 文档
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", "txt",
// 压缩包
"zip", "rar",
// 新增:视频格式
"mp4", "avi", "mov", "wmv", "flv", "mkv", "mpeg", "mpg"
));
// 允许的MIME类型(新增视频MIME)
private static final Set<String> ALLOWED_MIME_TYPES = new HashSet<>(Arrays.asList(
// 图片MIME
"image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp",
// 文档MIME
"application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/pdf", "text/plain",
// 压缩包MIME
"application/zip", "application/x-rar-compressed",
// 新增:视频MIME类型
"video/mp4", // mp4
"video/x-msvideo", // avi
"video/quicktime", // mov
"video/x-ms-wmv", // wmv
"video/x-flv", // flv
"video/x-matroska", // mkv
"video/mpeg" // mpeg/mpg
));
/**
* 校验文件内容(通过文件签名)
* 参考:https://en.wikipedia.org/wiki/List_of_file_signatures
*/
public static void validateFileContent(MultipartFile file, String extension) {
try (InputStream is = file.getInputStream()) {
byte[] header = new byte[8]; // 读取文件前8字节(足够识别多数类型)
int read = is.read(header);
if (read == -1) {
throw new IllegalArgumentException("文件内容为空");
}
// 根据扩展名校验对应签名(示例:校验jpg/png/pdf)
switch (extension.toLowerCase()) {
case "jpg", "jpeg":
// JPG签名:FF D8 FF
if (!(header[0] == (byte) 0xFF && header[1] == (byte) 0xD8 && header[2] == (byte) 0xFF)) {
throw new IllegalArgumentException("文件内容与jpg格式不匹配");
}
break;
case "png":
// PNG签名:89 50 4E 47 0D 0A 1A 0A
byte[] pngHeader = {(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
if (!Arrays.equals(Arrays.copyOf(header, 8), pngHeader)) {
throw new IllegalArgumentException("文件内容与png格式不匹配");
}
break;
case "pdf":
// PDF签名:25 50 44 46
if (!(header[0] == 0x25 && header[1] == 0x50 && header[2] == 0x44 && header[3] == 0x46)) {
throw new IllegalArgumentException("文件内容与pdf格式不匹配");
}
break;
// 其他格式同理,添加对应签名校验
default:
// 对于未覆盖的格式,至少保证扩展名在白名单内
}
} catch (Exception e) {
throw exception(FILE_NOT_ALLOWED);
}
}
// 允许的字符:字母(a-zA-Z)、数字(0-9)、下划线(_)、短横线(-)、点(.)、空格( )
private static final Pattern SAFE_PATTERN =
Pattern.compile("^[\\p{L}0-9_.\\- ]+$");
/**
* 校验输入是否合法
* @param input 用户输入的字符串(如文件名、业务参数)
* @param fieldName 字段名(用于错误提示)
* @throws IllegalArgumentException 输入不合法时抛出
*/
public static void validate(String input, String fieldName) {
if (input == null || input.isEmpty()) {
throw new ServiceException(1_001_003_004, fieldName + "不能为空");
}
if (!SAFE_PATTERN.matcher(input).matches()) {
throw new ServiceException(1_001_003_004, fieldName + "包含非法字符,特殊符号仅允许 _ - . 空格");
}
}
// 校验逻辑不变(复用原有方法)
public static String validateFileFormat(MultipartFile file) {
// 1. 校验文件是否为空
if (file.isEmpty()) {
throw exception(FILE_IS_EMPTY);
}
// 2. 校验文件后缀
String originalFilename = file.getOriginalFilename();
if (!originalFilename.contains(".")) {
throw exception(FILE_NOT_ALLOWED);
}
// 获取文件后缀(小写)
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
if (!ALLOWED_EXTENSIONS.contains(fileExtension)) {
throw exception(FILE_NOT_ALLOWED);
}
// 3. 校验MIME类型
String contentType = file.getContentType();
if (!ALLOWED_MIME_TYPES.contains(contentType)) {
throw exception(FILE_NOT_ALLOWED);
}
validate(originalFilename, "文件名");
// 5. 文件内容校验(签名验证)
validateFileContent(file, fileExtension);
return UUID.randomUUID().toString().replace("-", "") + fileExtension;
}
}
...@@ -112,7 +112,7 @@ public class InquireCustomerPushServiceImpl implements InquireCustomerPushServic ...@@ -112,7 +112,7 @@ public class InquireCustomerPushServiceImpl implements InquireCustomerPushServic
* @param id * @param id
*/ */
@Override @Override
@CacheEvict(cacheNames = RedisKeyConstants.CUSTOMER_VISIBLE_PRODUCT, key = "#id") @CacheEvict(cacheNames = RedisKeyConstants.CUSTOMER_VISIBLE_PRODUCT, allEntries = true)
public void confirm(Long id) { public void confirm(Long id) {
validateInquireCustomerPushExists(id); validateInquireCustomerPushExists(id);
inquireCustomerPushMapper.update(Wrappers.<InquireCustomerPushDO>lambdaUpdate() inquireCustomerPushMapper.update(Wrappers.<InquireCustomerPushDO>lambdaUpdate()
......
...@@ -65,7 +65,7 @@ public class OperaSupplierPurchaseOrderController { ...@@ -65,7 +65,7 @@ public class OperaSupplierPurchaseOrderController {
ErpPurchaseOrderPageReqVO orderPageReqVo = new ErpPurchaseOrderPageReqVO(); ErpPurchaseOrderPageReqVO orderPageReqVo = new ErpPurchaseOrderPageReqVO();
orderPageReqVo.setPageSize(pageReqVO.getPageSize()); orderPageReqVo.setPageSize(pageReqVO.getPageSize());
orderPageReqVo.setPageNo(pageReqVO.getPageNo()); orderPageReqVo.setPageNo(pageReqVO.getPageNo());
// orderPageReqVo.setSupplierId(supplierApi.querySupplierIdByUserId(getLoginUserId())); orderPageReqVo.setSupplierId(pageReqVO.getSupplierId());
orderPageReqVo.setCreateMonth(pageReqVO.getMonth()); orderPageReqVo.setCreateMonth(pageReqVO.getMonth());
orderPageReqVo.setDeliveryStatusList(CommonUtil.asList(ErpDeliveryStatus.ARRIVAL.getStatus(), ErpDeliveryStatus.RECONCILIATION.getStatus())); orderPageReqVo.setDeliveryStatusList(CommonUtil.asList(ErpDeliveryStatus.ARRIVAL.getStatus(), ErpDeliveryStatus.RECONCILIATION.getStatus()));
PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(orderPageReqVo); PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(orderPageReqVo);
......
...@@ -110,7 +110,7 @@ public interface CustomerOrderMapper extends BaseMapperX<CustomerOrderDO> { ...@@ -110,7 +110,7 @@ public interface CustomerOrderMapper extends BaseMapperX<CustomerOrderDO> {
String end = year + "-12-31 23:59:59"; String end = year + "-12-31 23:59:59";
MPJQueryWrapper<CustomerOrderDO> queryWrapperX = new MPJQueryWrapper<>(); MPJQueryWrapper<CustomerOrderDO> queryWrapperX = new MPJQueryWrapper<>();
queryWrapperX.select("DATE_FORMAT(create_time, '%Y年%m月') as 'yearMonth',count(*) as 'orderCount',SUM(order_amount) as 'orderAmount',SUM(actual_amount) as 'payableAmount'"); queryWrapperX.select("DATE_FORMAT(create_time, '%Y年%m月') as 'yearMonth',count(*) as 'orderCount',SUM(order_amount) as 'orderAmount',SUM(actual_amount) as 'payableAmount'");
queryWrapperX.select("CASE WHEN SUM(CASE WHEN order_status='SIGN_RECEIPT' THEN 1 ELSE 0 END)> 0 THEN 0 ELSE 1 END AS 'status'"); queryWrapperX.select("CASE WHEN SUM(CASE WHEN has_finish = 0 THEN 1 ELSE 0 END)> 0 THEN 0 ELSE 1 END AS 'status'");
queryWrapperX.between("create_time", begin, end); queryWrapperX.between("create_time", begin, end);
queryWrapperX.in("order_status", CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey(), queryWrapperX.in("order_status", CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey(),
CustomerOrderStatus.FINISH.getKey(), CustomerOrderStatus.FINISH.getKey(),
...@@ -156,7 +156,8 @@ public interface CustomerOrderMapper extends BaseMapperX<CustomerOrderDO> { ...@@ -156,7 +156,8 @@ public interface CustomerOrderMapper extends BaseMapperX<CustomerOrderDO> {
wrapperX.select("IFNULL(sum(t.actual_amount),0) as 'actualAmount'"); wrapperX.select("IFNULL(sum(t.actual_amount),0) as 'actualAmount'");
wrapperX.select("CASE WHEN SUM(CASE WHEN t.order_status='SIGN_RECEIPT' THEN 1 ELSE 0 END)> 0 THEN 0 ELSE 1 END AS 'isFinish'"); wrapperX.select("CASE WHEN SUM(CASE WHEN t.order_status='SIGN_RECEIPT' THEN 1 ELSE 0 END)> 0 THEN 0 ELSE 1 END AS 'isFinish'");
wrapperX.in(CustomerOrderDO::getOrderStatus, CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey(), wrapperX.in(CustomerOrderDO::getOrderStatus, CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey(),
CustomerOrderStatus.FINISH.getKey())); CustomerOrderStatus.FINISH.getKey(),
CustomerOrderStatus.RETURN.getKey()));
wrapperX.likeIfPresent(ErpCustomerDO::getName, pageReqVO.getCustomerName()); wrapperX.likeIfPresent(ErpCustomerDO::getName, pageReqVO.getCustomerName());
if (CommonUtil.isNotBlank(pageReqVO.getYearMonth())) { if (CommonUtil.isNotBlank(pageReqVO.getYearMonth())) {
String createMonth = pageReqVO.getYearMonth().trim(); String createMonth = pageReqVO.getYearMonth().trim();
......
package cn.iocoder.foodnexus.module.order.job;
import cn.iocoder.foodnexus.framework.common.enums.UserSystemEnum;
import cn.iocoder.foodnexus.framework.common.util.CommonUtil;
import cn.iocoder.foodnexus.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.foodnexus.module.operations.dal.dataobject.scoringweight.ScoringWeightDO;
import cn.iocoder.foodnexus.module.operations.service.scoringweight.ScoringWeightService;
import cn.iocoder.foodnexus.module.order.controller.app.customerOrder.vo.AppCustomerOrderScoreReqVO;
import cn.iocoder.foodnexus.module.order.dal.dataobject.customerorder.CustomerOrderDO;
import cn.iocoder.foodnexus.module.order.dal.dataobject.customerorderrecord.CustomerOrderRecordDO;
import cn.iocoder.foodnexus.module.order.dal.mysql.customerorder.CustomerOrderMapper;
import cn.iocoder.foodnexus.module.order.dto.CustomerOrderRemark;
import cn.iocoder.foodnexus.module.order.enums.CustomerOrderStatus;
import cn.iocoder.foodnexus.module.order.service.customerorder.CustomerOrderService;
import cn.iocoder.foodnexus.module.order.service.orderScore.OrderScoreService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.yulichang.query.MPJLambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
/**
* @author : yanghao
* create at: 2025/11/14 09:41
* @description: 客户订单定时任务
*/
@Component
@Slf4j
public class CustomerOrderScheduledTasks {
@Autowired
private CustomerOrderService customerOrderService;
@Autowired
private CustomerOrderMapper customerOrderMapper;
@Autowired
private ScoringWeightService scoringWeightService;
public static final int DAYS = 7;
public static final int SCORE = 4;
// 每天凌晨3点执行一次
// 查询签收、完成、退款订单中没有评价的订单,并给出默认订单评价
@Scheduled(cron = "0 0 3 * * ?")
public void defaultOrderScore() {
// 默认时间
LocalDate targetDate = LocalDate.now().minusDays(DAYS);
LocalDateTime startTime = targetDate.atStartOfDay(); // xxxx-xx-xx 00:00:00
LocalDateTime endTime = targetDate.atTime(LocalTime.MAX); // xxxx-xx-xx 23:59:59.999
MPJLambdaWrapperX<CustomerOrderDO> query = new MPJLambdaWrapperX<>();
query.eq(CustomerOrderDO::getHasScore, Boolean.FALSE)
.in(CustomerOrderDO::getOrderStatus, CommonUtil.asList(CustomerOrderStatus.SIGN_RECEIPT.getKey(), CustomerOrderStatus.FINISH.getKey(), CustomerOrderStatus.RETURN.getKey()));
query.leftJoin(CustomerOrderRecordDO.class, CustomerOrderRecordDO::getCustomerOrderId, CustomerOrderDO::getId);
query.ge(CustomerOrderRecordDO::getCreateTime, startTime) // 大于等于起始时间
.le(CustomerOrderRecordDO::getCreateTime, endTime);
query.eq(CustomerOrderRecordDO::getOrderStatus, CustomerOrderStatus.SIGN_RECEIPT.getKey());
List<CustomerOrderDO> customerOrderDOS = customerOrderMapper.selectList(query);
log.info("每日默认订单评价,查询到订单:{}条", customerOrderDOS.size());
if (CommonUtil.isEmpty(customerOrderDOS)) {
return ;
}
int success = 0;
for (CustomerOrderDO order : customerOrderDOS) {
AppCustomerOrderScoreReqVO scoreReqVO = new AppCustomerOrderScoreReqVO();
scoreReqVO.setOrderId(order.getId());
List<ScoringWeightDO> scoringWeightDOS = scoringWeightService.queryByUserSystem(UserSystemEnum.CUSTOMER);
scoreReqVO.setItems(CommonUtil.listConvert(scoringWeightDOS, item -> {
AppCustomerOrderScoreReqVO.Item scoreItem = new AppCustomerOrderScoreReqVO.Item();
scoreItem.setScoreId(item.getId());
scoreItem.setScore(SCORE);
return scoreItem;
}));
try {
customerOrderService.score(scoreReqVO);
success ++;
} catch (Exception e) {
log.error("每日默认订单评价,执行失败,客户订单编码【{}】", order.getCode(), e);
}
}
log.info("每日默认订单评价,查询到订单:{}条,执行成功:{}条", customerOrderDOS.size(), success);
}
}
...@@ -114,7 +114,7 @@ public interface CustomerOrderService { ...@@ -114,7 +114,7 @@ public interface CustomerOrderService {
* 评价订单 * 评价订单
* @param reqVO * @param reqVO
*/ */
void score(AppCustomerOrderScoreReqVO reqVO); void score(@Valid AppCustomerOrderScoreReqVO reqVO);
Map<String, Long> queryStatusCount(Long loginUserId); Map<String, Long> queryStatusCount(Long loginUserId);
......
...@@ -51,6 +51,7 @@ import cn.iocoder.foodnexus.module.system.dal.dataobject.user.AdminUserDO; ...@@ -51,6 +51,7 @@ import cn.iocoder.foodnexus.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.foodnexus.module.system.service.user.AdminUserService; import cn.iocoder.foodnexus.module.system.service.user.AdminUserService;
import cn.iocoder.foodnexus.module.system.util.GenCodeUtils; import cn.iocoder.foodnexus.module.system.util.GenCodeUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists; import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
...@@ -82,6 +83,7 @@ import static cn.iocoder.foodnexus.module.order.enums.ErrorCodeConstants.*; ...@@ -82,6 +83,7 @@ import static cn.iocoder.foodnexus.module.order.enums.ErrorCodeConstants.*;
*/ */
@Service @Service
@Validated @Validated
@Slf4j
public class CustomerOrderServiceImpl implements CustomerOrderService, CustomerOrderApi { public class CustomerOrderServiceImpl implements CustomerOrderService, CustomerOrderApi {
@Resource @Resource
...@@ -603,6 +605,7 @@ public class CustomerOrderServiceImpl implements CustomerOrderService, CustomerO ...@@ -603,6 +605,7 @@ public class CustomerOrderServiceImpl implements CustomerOrderService, CustomerO
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void score(AppCustomerOrderScoreReqVO reqVO) { public void score(AppCustomerOrderScoreReqVO reqVO) {
log.info("评价订单:{}", reqVO);
Long id = reqVO.getOrderId(); Long id = reqVO.getOrderId();
CustomerOrderDO customerOrder = getCustomerOrder(id); CustomerOrderDO customerOrder = getCustomerOrder(id);
if (CommonUtil.isEmpty(customerOrder)) { if (CommonUtil.isEmpty(customerOrder)) {
......
package cn.iocoder.foodnexus.module.system.aspect;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author : yanghao
* create at: 2025/11/18 13:41
* @description:
*/
@Configuration
public class CookieConfig {
@Bean
public CookieSameSiteSupplier cookieSameSiteSupplier() {
// 全局设置 SameSite=Strict(或 Lax,根据业务调整)
return CookieSameSiteSupplier.ofStrict();
}
// 自定义 Cookie 处理器,添加 HttpOnly 和 Secure
@Bean
public ServletContextInitializer servletContextInitializer() {
return servletContext -> {
// 对所有 Cookie 生效
servletContext.getSessionCookieConfig().setHttpOnly(true); // 禁止 JS 访问
// servletContext.getSessionCookieConfig().setSecure(true); // 仅 HTTPS 传输(生产环境启用)
// 可选:设置 Cookie 有效期(如 30 分钟)
// servletContext.getSessionCookieConfig().setMaxAge(1800);
};
}
}
\ No newline at end of file
package cn.iocoder.foodnexus.module.system.aspect;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author : yanghao
* create at: 2025/11/18 13:57
* @description:
*/
@Component
public class GlobalCookieSecurityFilter implements Filter {
private static final String HTTP_ONLY = "HttpOnly";
private static final String SECURE = "Secure";
private static final String SAME_SITE = "SameSite=Strict"; // 可按需改为 Lax
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResp = (HttpServletResponse) response;
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(httpResp) {
@Override
public void addHeader(String name, String value) {
if ("Set-Cookie".equalsIgnoreCase(name)) {
String lower = value.toLowerCase();
// HttpOnly
if (!lower.contains("httponly")) {
value += "; " + HTTP_ONLY;
}
// Secure(仅在 HTTPS 生效)
if (!lower.contains("secure")) {
value += "; " + SECURE;
}
// SameSite
if (!lower.contains("samesite")) {
value += "; " + SAME_SITE;
}
}
super.addHeader(name, value);
}
};
chain.doFilter(request, wrapper);
}
}
...@@ -13,9 +13,9 @@ spring: ...@@ -13,9 +13,9 @@ spring:
datasource: datasource:
druid: # Druid 【监控】相关的全局配置 druid: # Druid 【监控】相关的全局配置
web-stat-filter: web-stat-filter:
enabled: true enabled: false
stat-view-servlet: stat-view-servlet:
enabled: true enabled: false
allow: # 设置白名单,不填则允许所有访问 allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/* url-pattern: /druid/*
login-username: # 控制台管理用户名和密码 login-username: # 控制台管理用户名和密码
...@@ -228,3 +228,9 @@ iot: ...@@ -228,3 +228,9 @@ iot:
# 插件配置 # 插件配置
pf4j: pf4j:
pluginsDir: ${user.home}/plugins # 插件目录 pluginsDir: ${user.home}/plugins # 插件目录
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment