供应商-客户中间表引用表的新增修改删除
This commit is contained in:
@ -0,0 +1,21 @@
|
||||
package org.dromara.xzd.config;
|
||||
|
||||
import org.dromara.xzd.interceptor.RequestLogInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private RequestLogInterceptor requestLogInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(requestLogInterceptor)
|
||||
.addPathPatterns("/xzd/**") // 拦截所有路径
|
||||
.order(1);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,12 @@
|
||||
package org.dromara.xzd.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 供应商-客户中间对象 xzd_customer_supplier
|
||||
@ -12,7 +16,10 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
@TableName("xzd_customer_supplier")
|
||||
public class XzdCustomerSupplier {
|
||||
public class XzdCustomerSupplier implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package org.dromara.xzd.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 供应商-客户中间对象 xzd_customer_supplier_yyb
|
||||
@ -12,7 +16,10 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
@TableName("xzd_customer_supplier_yyb")
|
||||
public class XzdCustomerSupplierYyb {
|
||||
public class XzdCustomerSupplierYyb implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package org.dromara.xzd.handler;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 存储AOP拦截到的自增ID,供拦截器使用(线程安全)
|
||||
*/
|
||||
@Component
|
||||
public class AutoIncrementIdHolder {
|
||||
// 存储当前线程新增操作生成的ID
|
||||
private static final ThreadLocal<Long> ID_HOLDER = new ThreadLocal<>();
|
||||
|
||||
// 设置ID
|
||||
public static void setId(Long id) {
|
||||
ID_HOLDER.set(id);
|
||||
}
|
||||
|
||||
// 获取ID
|
||||
public static Long getId() {
|
||||
return ID_HOLDER.get();
|
||||
}
|
||||
|
||||
// 清除ID(避免内存泄漏)
|
||||
public static void clear() {
|
||||
ID_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package org.dromara.xzd.interceptor;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 提前包装请求,确保POST/PUT请求体能被缓存
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级
|
||||
public class RequestCachingFilter extends OncePerRequestFilter {
|
||||
private static final int CACHE_SIZE = 1024 * 1024; // 1MB缓存容量
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
String method = request.getMethod();
|
||||
if ("POST".equals(method) || "PUT".equals(method)) {
|
||||
// 包装请求,支持大请求体
|
||||
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request, CACHE_SIZE);
|
||||
filterChain.doFilter(wrappedRequest, response);
|
||||
} else {
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,390 @@
|
||||
package org.dromara.xzd.interceptor;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.xzd.domain.XzdCustomerSupplierYyb;
|
||||
import org.dromara.xzd.handler.AutoIncrementIdHolder;
|
||||
import org.dromara.xzd.mapper.XzdCustomerSupplierYybMapper;
|
||||
import org.dromara.xzd.utilS.RequestParamExtractor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RequestLogInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Autowired
|
||||
private XzdCustomerSupplierYybMapper customerSupplierYybMapper;
|
||||
@Autowired
|
||||
private AutoIncrementIdHolder autoIncrementIdHolder;
|
||||
|
||||
// 需要拦截的请求方法
|
||||
private static final List<String> INTERCEPT_METHODS = Arrays.asList("POST", "PUT", "DELETE");
|
||||
|
||||
// 需要提取的请求体字段(根据实际需求修改)
|
||||
private static final List<String> EXTRACT_FIELDS = Arrays.asList("id",
|
||||
"jointInvestmentEntity",
|
||||
"constructionUnit",
|
||||
"biddingUnit",
|
||||
"bidUnit",
|
||||
"depositReceivingUnit",
|
||||
"applicationOrganization",
|
||||
"applicationUnit",
|
||||
"partyAUnit",
|
||||
"partyBUnit",
|
||||
"invoicingUnit",
|
||||
"ticketReceivingUnit",
|
||||
"invoiceIssuingUnit",
|
||||
"invoiceReceivingUnit",
|
||||
"signingOrganization",
|
||||
"partyCUnit",
|
||||
"buyerName",
|
||||
"auditUnit",
|
||||
"supervisionUnit",
|
||||
"ownerUnit",
|
||||
"partyA",
|
||||
"partyB",
|
||||
"invoiceIssuer",
|
||||
"invoiceReceiver",
|
||||
"receiptUnit",
|
||||
"settlementUnit",
|
||||
"currentInvoiceUnit",
|
||||
"owner",
|
||||
"supervision",
|
||||
"review",
|
||||
"fkdwId",
|
||||
"payer",
|
||||
"skdwId");
|
||||
|
||||
|
||||
// 缓存POST请求数据(线程安全)
|
||||
private final ThreadLocal<Map<String, Object>> postRequestCache = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String method = request.getMethod();
|
||||
if (!Arrays.asList("POST", "PUT", "DELETE").contains(method)) { // 明确拦截的方法
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理PUT/DELETE(无需等待ID)
|
||||
if ("PUT".equals(method) || "DELETE".equals(method)) {
|
||||
handlePutOrDelete(request, method);
|
||||
}
|
||||
// 缓存POST请求数据(等待后续处理)
|
||||
else if ("POST".equals(method)) {
|
||||
// 注意:需要将request包装为ContentCachingRequestWrapper才能重复读取请求体
|
||||
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
|
||||
request.setAttribute("wrappedRequest", wrappedRequest); // 存入attribute供后续使用
|
||||
cachePostRequestData(wrappedRequest);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
log.info("postHandle开始执行:请求方法={},响应状态码={}",
|
||||
request.getMethod(), response.getStatus());
|
||||
|
||||
if ("POST".equals(request.getMethod())) {
|
||||
handlePostAfterSave();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
// 清除线程缓存(必须执行,避免内存泄漏)
|
||||
postRequestCache.remove();
|
||||
AutoIncrementIdHolder.clear(); // 清除ID持有者
|
||||
}
|
||||
|
||||
// 缓存POST请求数据(优化:使用包装后的request)
|
||||
private void cachePostRequestData(ContentCachingRequestWrapper wrappedRequest) throws IOException {
|
||||
String uri = wrappedRequest.getRequestURI();
|
||||
Map<String, Object> extractedParams = RequestParamExtractor.extractParams(wrappedRequest, "POST", EXTRACT_FIELDS);
|
||||
|
||||
postRequestCache.set(Map.of(
|
||||
"uri", uri,
|
||||
"extractedParams", extractedParams
|
||||
));
|
||||
}
|
||||
|
||||
// 处理POST请求(核心:从ThreadLocal获取AOP存入的ID)
|
||||
private void handlePostAfterSave() {
|
||||
Map<String, Object> cachedData = postRequestCache.get();
|
||||
if (cachedData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String uri = (String) cachedData.get("uri");
|
||||
Map<String, Object> extractedParams = (Map<String, Object>) cachedData.get("extractedParams");
|
||||
|
||||
// 1. 尝试从请求参数获取ID(前端传入的情况,如非自增ID)
|
||||
Long newId = convertToLong(extractedParams.get("id"));
|
||||
// 2. 从AOP存入的ThreadLocal获取ID(数据库自增/雪花算法生成的情况)
|
||||
if (newId == null) {
|
||||
newId = AutoIncrementIdHolder.getId();
|
||||
}
|
||||
|
||||
if (newId == null) {
|
||||
log.warn("未获取到新增ID,跳过保存操作");
|
||||
return;
|
||||
}
|
||||
|
||||
List<XzdCustomerSupplierYyb> list = new ArrayList<>();
|
||||
// // 3. 保存到数据库(你的业务逻辑)
|
||||
for (Map.Entry<String, Object> entry : extractedParams.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
Object v = entry.getValue();
|
||||
if ("id".equals(k)){
|
||||
continue;
|
||||
}
|
||||
XzdCustomerSupplierYyb entity = new XzdCustomerSupplierYyb();
|
||||
entity.setUri(uri);
|
||||
entity.setCSId(convertToLong(v));
|
||||
entity.setMainTableId(newId); // 核心:使用AOP获取的新增ID
|
||||
list.add(entity);
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
customerSupplierYybMapper.insertBatch(list);
|
||||
}
|
||||
log.info("新增ID:{} 已关联保存至XzdCustomerSupplierYyb", newId);
|
||||
}
|
||||
|
||||
// 处理PUT/DELETE
|
||||
private void handlePutOrDelete(HttpServletRequest request, String method) {
|
||||
String uri = request.getRequestURI();
|
||||
Map<String, Object> extractedParams = RequestParamExtractor.extractParams(request, method, EXTRACT_FIELDS);
|
||||
if ("PUT".equals(method)) {
|
||||
Long mainTableId = convertToLong(extractedParams.get("id"));
|
||||
customerSupplierYybMapper.delete(new LambdaQueryWrapper<XzdCustomerSupplierYyb>().eq(XzdCustomerSupplierYyb::getMainTableId, mainTableId));
|
||||
List<XzdCustomerSupplierYyb> list = new ArrayList<>();
|
||||
// // 3. 保存到数据库(你的业务逻辑)
|
||||
for (Map.Entry<String, Object> entry : extractedParams.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
Object v = entry.getValue();
|
||||
if ("id".equals(k)){
|
||||
continue;
|
||||
}
|
||||
XzdCustomerSupplierYyb customerSupplierYyb = new XzdCustomerSupplierYyb();
|
||||
customerSupplierYyb.setUri(uri);
|
||||
customerSupplierYyb.setCSId(convertToLong(v));
|
||||
customerSupplierYyb.setMainTableId(mainTableId); // 核心:使用AOP获取的新增ID
|
||||
list.add(customerSupplierYyb);
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
customerSupplierYybMapper.insertBatch(list);
|
||||
}
|
||||
} else if ("DELETE".equals(method)) {
|
||||
Object ids = extractedParams.get("ids");
|
||||
if (ids != null) {
|
||||
List<Long> list = JSONUtil.parseArray(ids).toList(Long.class);
|
||||
customerSupplierYybMapper.delete(new LambdaQueryWrapper<XzdCustomerSupplierYyb>().in(XzdCustomerSupplierYyb::getMainTableId, list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:获取请求体
|
||||
private String getRequestBody(ContentCachingRequestWrapper request) {
|
||||
try {
|
||||
request.getInputStream();
|
||||
} catch (IOException e) {
|
||||
// 忽略异常
|
||||
}
|
||||
byte[] content = request.getContentAsByteArray();
|
||||
return content.length > 0 ? new String(content, StandardCharsets.UTF_8) : StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
// 辅助方法:转换为Long
|
||||
private Long convertToLong(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// String method = request.getMethod();
|
||||
// // 只处理指定方法,且确保请求已被过滤器包装为ContentCachingRequestWrapper
|
||||
// if (INTERCEPT_METHODS.contains(method) && request instanceof ContentCachingRequestWrapper) {
|
||||
// // 直接使用过滤器包装后的请求对象
|
||||
// ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request;
|
||||
// handleRequest(wrappedRequest);
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// private void handleRequest(ContentCachingRequestWrapper request) throws IOException {
|
||||
// String method = request.getMethod();
|
||||
// String uri = request.getRequestURI();
|
||||
// // 统一提取参数(POST/PUT从body,DELETE从URL)
|
||||
// Map<String, Object> extractedParams = extractParams(request, method, EXTRACT_FIELDS);
|
||||
// String requestBody = getRequestBody(request);
|
||||
//
|
||||
//// XzdCustomerSupplierYyb customerSupplierYyb = new XzdCustomerSupplierYyb();
|
||||
//// customerSupplierYyb.setUri(uri);
|
||||
//// customerSupplierYyb.setCSId();
|
||||
//// customerSupplierYyb.setMainTableId();
|
||||
//
|
||||
// // 根据请求方法执行不同操作
|
||||
//// switch (method) {
|
||||
//// case "POST":
|
||||
//// // 新增请求直接保存
|
||||
//// customerSupplierYybMapper.insert(customerSupplierYyb);
|
||||
//// break;
|
||||
//// case "PUT":
|
||||
//// // 修改请求先删除后新增
|
||||
//// customerSupplierYybMapper.deleteByUri(uri);
|
||||
//// customerSupplierYybMapper.insert(customerSupplierYyb);
|
||||
//// break;
|
||||
//// case "DELETE":
|
||||
//// // 删除请求直接删除记录
|
||||
//// customerSupplierYybMapper.deleteByUri(uri);
|
||||
//// break;
|
||||
//// }
|
||||
// }
|
||||
|
||||
// // 获取请求体内容
|
||||
// private String getRequestBody(ContentCachingRequestWrapper request) {
|
||||
// try {
|
||||
// // 主动读取一次请求体(触发缓存,即使不使用输入流内容)
|
||||
// request.getInputStream();
|
||||
// } catch (IOException e) {
|
||||
// // 忽略异常(读取失败时继续执行)
|
||||
// }
|
||||
//
|
||||
// // 此时缓存已生效,获取内容
|
||||
// byte[] content = request.getContentAsByteArray();
|
||||
// if (content.length > 0) {
|
||||
// return new String(content, StandardCharsets.UTF_8);
|
||||
// }
|
||||
// return StrUtil.EMPTY;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 从请求中提取指定字段(根据请求方法自动适配来源)
|
||||
// * @param request 请求对象
|
||||
// * @param method 请求方法(POST/PUT/DELETE)
|
||||
// * @param extractFields 需要提取的字段
|
||||
// * @return 提取的字段键值对
|
||||
// */
|
||||
// public static Map<String, Object> extractParams(HttpServletRequest request, String method, List<String> extractFields) {
|
||||
// Map<String, Object> params = new HashMap<>();
|
||||
// if ("POST".equals(method) || "PUT".equals(method)) {
|
||||
// // POST/PUT:从JSON请求体提取
|
||||
// String requestBody = getJsonBody(request);
|
||||
// if (StrUtil.isNotEmpty(requestBody)) {
|
||||
// JSONObject json = JSONObject.parseObject(requestBody);
|
||||
// for (String field : extractFields) {
|
||||
// if (json.containsKey(field)) {
|
||||
// params.put(field, json.get(field));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else if ("DELETE".equals(method)) {
|
||||
// // DELETE:从URL提取(路径参数 + 查询参数)
|
||||
// Map<String, String> urlParams = extractUrlParams(request);
|
||||
// for (String field : extractFields) {
|
||||
// if (urlParams.containsKey(field)) {
|
||||
// params.put(field, urlParams.get(field));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return params;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取POST/PUT的JSON请求体(支持大请求体,确保完整读取)
|
||||
// */
|
||||
// private static String getJsonBody(HttpServletRequest request) {
|
||||
// // 仅处理包装后的请求
|
||||
// if (!(request instanceof ContentCachingRequestWrapper)) {
|
||||
// log.warn("请求未被ContentCachingRequestWrapper包装,无法获取请求体");
|
||||
// return StrUtil.EMPTY;
|
||||
// }
|
||||
//
|
||||
// ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request;
|
||||
// byte[] content = null;
|
||||
//
|
||||
// try {
|
||||
// // 1. 尝试通过输入流完整读取(确保大请求体被全部缓存)
|
||||
// InputStream inputStream = wrappedRequest.getInputStream();
|
||||
// if (inputStream != null) {
|
||||
// // 读取全部字节(IOUtils确保完整读取,支持大文件)
|
||||
// byte[] fullContent = IOUtils.toByteArray(inputStream);
|
||||
// if (fullContent.length > 0) {
|
||||
// content = fullContent;
|
||||
// log.debug("通过输入流读取请求体,大小:{}字节", fullContent.length);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 2. 若输入流读取失败,尝试从缓存获取(兼容小请求体)
|
||||
// if (content == null || content.length == 0) {
|
||||
// content = wrappedRequest.getContentAsByteArray();
|
||||
// if (content.length > 0) {
|
||||
// log.debug("通过缓存获取请求体,大小:{}字节", content.length);
|
||||
// } else {
|
||||
// log.debug("请求体为空(输入流和缓存均无内容)");
|
||||
// return StrUtil.EMPTY;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 3. 转换为字符串(指定UTF-8编码)
|
||||
// String jsonBody = new String(content, StandardCharsets.UTF_8);
|
||||
// // 日志脱敏:只输出前1000字符,避免敏感信息和大内容刷屏
|
||||
// if (jsonBody.length() > 1000) {
|
||||
// log.debug("请求体内容:{}...(省略{}字符)", jsonBody.substring(0, 1000), jsonBody.length() - 1000);
|
||||
// } else {
|
||||
// log.debug("请求体内容:{}", jsonBody);
|
||||
// }
|
||||
// return jsonBody;
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// log.error("读取请求体失败", e); // 记录详细异常,便于排查
|
||||
// // 异常时最后尝试从缓存获取
|
||||
// content = wrappedRequest.getContentAsByteArray();
|
||||
// return content.length > 0 ? new String(content, StandardCharsets.UTF_8) : StrUtil.EMPTY;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 提取DELETE请求的URL参数(路径参数 + 查询参数)
|
||||
// */
|
||||
// private static Map<String, String> extractUrlParams(HttpServletRequest request) {
|
||||
// Map<String, String> params = new HashMap<>();
|
||||
//
|
||||
// // 1. 提取路径参数(如 /api/resource/{id} 中的id)
|
||||
// Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
// if (pathVariables != null) {
|
||||
// params.putAll(pathVariables);
|
||||
// }
|
||||
//
|
||||
// // 2. 提取查询参数(如 /api/resource?id=1&name=test 中的id和name)
|
||||
// UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(request.getRequestURI());
|
||||
// request.getParameterMap().forEach((key, values) -> {
|
||||
// if (values != null && values.length > 0) {
|
||||
// params.put(key, values[0]); // 取第一个值
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return params;
|
||||
// }
|
||||
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package org.dromara.xzd.interceptor;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import java.lang.reflect.Field;
|
||||
import org.dromara.xzd.handler.AutoIncrementIdHolder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 拦截Service的保存方法,获取数据库自增ID
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class SaveMethodAspect {
|
||||
|
||||
@Autowired
|
||||
private AutoIncrementIdHolder autoIncrementIdHolder;
|
||||
|
||||
// 切入点:匹配Service中所有"save"开头的方法(修改为你的Service包路径)
|
||||
@Pointcut("execution(* org.dromara.xzd.*.mapper.*Mapper.insert*(..)) || " +
|
||||
"execution(* org.dromara.xzd.*.*.mapper.*Mapper.insert*(..)) " +
|
||||
"|| execution(* org.dromara.xzd.mapper.*Mapper.insert*(..)) ")
|
||||
public void insertPointcut() {}
|
||||
|
||||
/**
|
||||
* 后置通知:方法执行后,从参数实体中提取主键 ID
|
||||
*/
|
||||
@AfterReturning(pointcut = "insertPointcut()")
|
||||
public void afterInsert(JoinPoint joinPoint) {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
if (args == null || args.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历方法参数,处理实体对象
|
||||
for (Object arg : args) {
|
||||
if (arg == null) {
|
||||
continue;
|
||||
}
|
||||
// 通过反射获取实体中的主键字段值
|
||||
Object id = getPrimaryKeyValue(arg);
|
||||
if (id != null) {
|
||||
// 处理 ID(日志、关联操作等)
|
||||
AutoIncrementIdHolder.setId((Long) id);
|
||||
System.out.println("新增操作生成的 ID:" + id + "(实体类:" + arg.getClass().getSimpleName() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射获取实体中被 @TableId 注解标识的主键值
|
||||
*/
|
||||
private Object getPrimaryKeyValue(Object entity) {
|
||||
Class<?> clazz = entity.getClass();
|
||||
// 遍历所有字段(包括父类字段)
|
||||
while (clazz != null && clazz != Object.class) {
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
// 判断字段是否被 @TableId 注解标识(MyBatis-Plus 主键)
|
||||
if (field.isAnnotationPresent(TableId.class)) {
|
||||
try {
|
||||
field.setAccessible(true); // 允许访问私有字段
|
||||
return field.get(entity); // 返回主键值
|
||||
} catch (IllegalAccessException e) {
|
||||
System.err.println("获取主键值失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
clazz = clazz.getSuperclass(); // 检查父类字段
|
||||
}
|
||||
// 若未找到主键字段,可根据需求返回 null 或抛出异常
|
||||
System.out.println("实体类 " + entity.getClass().getSimpleName() + " 未找到 @TableId 注解的主键字段");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,253 @@
|
||||
package org.dromara.xzd.utilS;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class RequestParamExtractor {
|
||||
private static final int MAX_RECURSION_DEPTH = 3;
|
||||
|
||||
/**
|
||||
* 从请求中提取指定字段(根据请求方法自动适配来源)
|
||||
* @param request 请求对象
|
||||
* @param method 请求方法(POST/PUT/DELETE)
|
||||
* @param extractFields 需要提取的字段
|
||||
* @return 提取的字段键值对
|
||||
*/
|
||||
public static Map<String, Object> extractParams(HttpServletRequest request, String method, List<String> extractFields) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if ("POST".equals(method) || "PUT".equals(method)) {
|
||||
// POST/PUT:从JSON请求体提取
|
||||
String requestBody = getJsonBody(request);
|
||||
if (StrUtil.isNotEmpty(requestBody)) {
|
||||
JSONObject json = JSONObject.parseObject(requestBody);
|
||||
for (String field : extractFields) {
|
||||
if (json.containsKey(field)) {
|
||||
params.put(field, json.get(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("DELETE".equals(method)) {
|
||||
// DELETE:从URL提取(路径参数 + 查询参数)
|
||||
String idsStr = extractUrlParams(request);
|
||||
if (idsStr != null&& !idsStr.isEmpty()) {
|
||||
// 按逗号拆分(兼容可能的空格,如"1, 2,3")
|
||||
String[] idStrArray = idsStr.split("\\s*,\\s*");
|
||||
// 转换为Long数组(处理数字格式异常)
|
||||
List<Long> idsArray = new ArrayList<>();
|
||||
for (int i = 0; i < idStrArray.length; i++) {
|
||||
String idStr = idStrArray[i];
|
||||
if (isNumeric(idStr)) { // 校验是否为数字
|
||||
idsArray.add(Long.parseLong(idStr));
|
||||
} else {
|
||||
// 处理无效ID(根据业务需求:可忽略/抛出异常/记录日志)
|
||||
throw new IllegalArgumentException("无效的ID格式:" + idStr);
|
||||
}
|
||||
}
|
||||
params.put("ids", idsArray);
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取POST/PUT的JSON请求体(支持大请求体,确保完整读取)
|
||||
*/
|
||||
private static String getJsonBody(HttpServletRequest request) {
|
||||
|
||||
// 递归获取最内层的ContentCachingRequestWrapper
|
||||
ContentCachingRequestWrapper wrappedRequest = getContentCachingWrapper(request, 0);
|
||||
if (wrappedRequest == null) {
|
||||
log.warn("未找到ContentCachingRequestWrapper(可能嵌套层数超过限制或类型不匹配)");
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
byte[] content = null;
|
||||
|
||||
try {
|
||||
// 1. 尝试通过输入流完整读取
|
||||
InputStream inputStream = wrappedRequest.getInputStream();
|
||||
if (inputStream != null) {
|
||||
byte[] fullContent = IOUtils.toByteArray(inputStream);
|
||||
if (fullContent.length > 0) {
|
||||
content = fullContent;
|
||||
log.debug("通过输入流读取请求体,大小:{}字节", fullContent.length);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 若输入流读取失败,尝试从缓存获取
|
||||
if (content == null || content.length == 0) {
|
||||
content = wrappedRequest.getContentAsByteArray();
|
||||
if (content.length > 0) {
|
||||
log.debug("通过缓存获取请求体,大小:{}字节", content.length);
|
||||
} else {
|
||||
log.debug("请求体为空");
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 转换为字符串
|
||||
String jsonBody = new String(content, StandardCharsets.UTF_8);
|
||||
if (jsonBody.length() > 1000) {
|
||||
log.debug("请求体内容:{}...(省略{}字符)", jsonBody.substring(0, 1000), jsonBody.length() - 1000);
|
||||
} else {
|
||||
log.debug("请求体内容:{}", jsonBody);
|
||||
}
|
||||
return jsonBody;
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("读取请求体失败", e);
|
||||
content = wrappedRequest.getContentAsByteArray();
|
||||
return content.length > 0 ? new String(content, StandardCharsets.UTF_8) : StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
// // 仅处理包装后的请求
|
||||
// if (!(request instanceof ContentCachingRequestWrapper)) {
|
||||
// log.warn("请求未被ContentCachingRequestWrapper包装,无法获取请求体");
|
||||
// return StrUtil.EMPTY;
|
||||
// }
|
||||
//
|
||||
// ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request;
|
||||
// byte[] content = null;
|
||||
//
|
||||
// try {
|
||||
// // 1. 尝试通过输入流完整读取(确保大请求体被全部缓存)
|
||||
// InputStream inputStream = wrappedRequest.getInputStream();
|
||||
// if (inputStream != null) {
|
||||
// // 读取全部字节(IOUtils确保完整读取,支持大文件)
|
||||
// byte[] fullContent = IOUtils.toByteArray(inputStream);
|
||||
// if (fullContent.length > 0) {
|
||||
// content = fullContent;
|
||||
// log.debug("通过输入流读取请求体,大小:{}字节", fullContent.length);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 2. 若输入流读取失败,尝试从缓存获取(兼容小请求体)
|
||||
// if (content == null || content.length == 0) {
|
||||
// content = wrappedRequest.getContentAsByteArray();
|
||||
// if (content.length > 0) {
|
||||
// log.debug("通过缓存获取请求体,大小:{}字节", content.length);
|
||||
// } else {
|
||||
// log.debug("请求体为空(输入流和缓存均无内容)");
|
||||
// return StrUtil.EMPTY;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 3. 转换为字符串(指定UTF-8编码)
|
||||
// String jsonBody = new String(content, StandardCharsets.UTF_8);
|
||||
// // 日志脱敏:只输出前1000字符,避免敏感信息和大内容刷屏
|
||||
// if (jsonBody.length() > 1000) {
|
||||
// log.debug("请求体内容:{}...(省略{}字符)", jsonBody.substring(0, 1000), jsonBody.length() - 1000);
|
||||
// } else {
|
||||
// log.debug("请求体内容:{}", jsonBody);
|
||||
// }
|
||||
// return jsonBody;
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// log.error("读取请求体失败", e); // 记录详细异常,便于排查
|
||||
// // 异常时最后尝试从缓存获取
|
||||
// content = wrappedRequest.getContentAsByteArray();
|
||||
// return content.length > 0 ? new String(content, StandardCharsets.UTF_8) : StrUtil.EMPTY;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取DELETE请求的URL参数(路径参数 + 查询参数)
|
||||
*/
|
||||
private static String extractUrlParams(HttpServletRequest request) {
|
||||
// Map<String, String> params = new HashMap<>();
|
||||
|
||||
// 1. 提取路径参数(适配 /api/resource/{ids} 中的ids,拆分为Long数组)
|
||||
Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
// 初始化参数Map(用于存储处理后的参数)
|
||||
if (pathVariables != null) {
|
||||
// 单独处理"ids"参数:拆分字符串并转换为Long数组
|
||||
return pathVariables.get("ids");
|
||||
}
|
||||
return null;
|
||||
// // 2. 提取查询参数(如 /api/resource?id=1&name=test 中的id和name)
|
||||
// UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(request.getRequestURI());
|
||||
// request.getParameterMap().forEach((key, values) -> {
|
||||
// if (values != null && values.length > 0) {
|
||||
// params.put(key, values[0]); // 取第一个值
|
||||
// }
|
||||
// });
|
||||
|
||||
}
|
||||
// 辅助方法:校验字符串是否为纯数字(避免Long.parseLong抛出异常)
|
||||
private static boolean isNumeric(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 正则匹配整数(支持正数/负数)
|
||||
return Pattern.matches("^[-+]?\\d+$", str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 递归解析多层嵌套请求,找到ContentCachingRequestWrapper
|
||||
* @param request 当前请求对象
|
||||
* @param depth 当前递归深度
|
||||
* @return 内层的ContentCachingRequestWrapper(未找到则返回null)
|
||||
*/
|
||||
private static ContentCachingRequestWrapper getContentCachingWrapper(HttpServletRequest request, int depth) {
|
||||
// 终止条件1:超过最大递归深度(防止无限循环)
|
||||
if (depth >= MAX_RECURSION_DEPTH) {
|
||||
log.warn("递归获取请求体超过最大深度{},终止解析", MAX_RECURSION_DEPTH);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 终止条件2:当前请求已是ContentCachingRequestWrapper,直接返回
|
||||
if (request instanceof ContentCachingRequestWrapper) {
|
||||
return (ContentCachingRequestWrapper) request;
|
||||
}
|
||||
|
||||
// 尝试获取内层请求(通常包装类会提供getRequest()方法)
|
||||
HttpServletRequest innerRequest = getInnerRequest(request);
|
||||
if (innerRequest == null) {
|
||||
log.debug("当前请求{}无内层请求,终止解析", request.getClass().getSimpleName());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 递归解析内层请求(深度+1)
|
||||
return getContentCachingWrapper(innerRequest, depth + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包装类中的内层请求(适配常见的包装类,如RepeatedlyRequestWrapper、RequestWrapper等)
|
||||
* 原理:通过反射或方法调用获取内层request(多数包装类会实现getRequest()方法)
|
||||
*/
|
||||
private static HttpServletRequest getInnerRequest(HttpServletRequest request) {
|
||||
try {
|
||||
// 尝试调用getRequest()方法(多数包装类的标准实现)
|
||||
return (HttpServletRequest) request.getClass().getMethod("getRequest").invoke(request);
|
||||
} catch (Exception e) {
|
||||
// 若没有getRequest()方法,检查是否有其他获取内层请求的方法(如getOriginalRequest())
|
||||
try {
|
||||
return (HttpServletRequest) request.getClass().getMethod("getOriginalRequest").invoke(request);
|
||||
} catch (Exception ex) {
|
||||
log.debug("请求{}无法获取内层请求(无getRequest()或getOriginalRequest()方法)",
|
||||
request.getClass().getSimpleName(), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user