add client ip info

This commit is contained in:
N1KO 2025-12-28 10:59:36 +08:00
parent e5f5e6ca75
commit 4351c0ef03
3 changed files with 312 additions and 225 deletions

View File

@ -1,216 +1,216 @@
package top.baogutang.admin.schedule;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import top.baogutang.admin.domain.IphoneProductDto;
import top.baogutang.admin.utils.DingTalkMsgPushUtils;
import top.baogutang.admin.utils.IphoneProductParserUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @description: 苹果库存监控
* @author: nikooh
* @date: 2023/10/13 : 15:12
*/
@Slf4j
@Component
@RefreshScope
public class AppleInventoryScheduleHandler extends IJobHandler {
@Value("${baogutang.apple.country_code:cn}")
private String countryCode;
@Value("${baogutang.apple.device_code:17-pro}")
private String deviceCode;
@Value("${baogutang.apple.location:'上海 上海 闵行区'}")
private String location;
@Value("${baogutang.apple.switch:true}")
private Boolean appleInventoryMonitorSwitch;
// @Value("${baogutang.apple.storeList}")
private List<String> storeList = new ArrayList<>();
@Resource
private IphoneProductParserUtils iphoneProductParserUtils;
@Resource
private DingTalkMsgPushUtils dingTalkMsgPushUtils;
@Scheduled(cron = "0 0/1 * * * ? ")
public void appleInventoryMonitor() {
if (!Boolean.TRUE.equals(appleInventoryMonitorSwitch)) {
log.info(">>>>>>>>>>apple inventory monitor switch closed!<<<<<<<<<<");
return;
}
// 获取设备信息
List<IphoneProductDto> products = iphoneProductParserUtils.getProducts(deviceCode, countryCode);
//监视机型型号
products.forEach(product -> {
this.doMonitor(product);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.error(">>>>>>>>>>apple inventory monitor error:{}<<<<<<<<<<", e.getMessage(), e);
}
});
}
private void doMonitor(IphoneProductDto product) {
Map<String, Object> queryMap = new HashMap<>(5);
queryMap.put("pl", "true");
queryMap.put("mts.0", "regular");
queryMap.put("parts.0", product.getModel());
queryMap.put("location", location);
String baseUrl = String.format("https://www.apple.com.%s", countryCode);
Map<String, List<String>> headers = buildHeaders(baseUrl, deviceCode, product.getModel());
String url = baseUrl + "/shop/fulfillment-messages?" + URLUtil.buildQuery(queryMap, CharsetUtil.CHARSET_UTF_8);
try {
HttpResponse httpResponse = HttpRequest.get(url)
.header(headers)
.execute();
if (!httpResponse.isOk()) {
log.warn(">>>>>>>>>>请求可能过于频繁,请稍后再试~<<<<<<<<<<");
return;
}
JSONObject responseJsonObject = JSON.parseObject(httpResponse.body());
JSONObject pickupMessage = responseJsonObject.getJSONObject("body").getJSONObject("content").getJSONObject("pickupMessage");
JSONArray stores = pickupMessage.getJSONArray("stores");
if (stores == null) {
// log.info(pickupMessage.toString());
return;
}
if (stores.isEmpty()) {
log.info("您所在的 {} 附近没有Apple直营店请检查您的地址是否正确", location);
return;
}
StringBuilder pushContentBuilder = new StringBuilder();
List<String> availableStoreNameList = stores.stream()
.filter(store -> filterStore((JSONObject) store))
.filter(k -> judgingStoreInventory((JSONObject) k, product.getModel()))
.map(store -> ((JSONObject) store).getString("storeName")
.trim())
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(availableStoreNameList)) {
return;
}
JSONObject storeJson = (JSONObject) stores.get(0);
JSONObject partsAvailability = storeJson.getJSONObject("partsAvailability");
String deviceName = partsAvailability.getJSONObject(product.getModel()).getJSONObject("messageTypes").getJSONObject("regular").getString("storePickupProductTitle");
pushContentBuilder.append("**")
.append(deviceName)
.append("**")
.append("今天可取货:")
.append("<br />");
availableStoreNameList.forEach(availableStoreName ->
pushContentBuilder.append("**")
.append(availableStoreName)
.append("** <br />"));
dingTalkMsgPushUtils.robotMarkdownMsgPush("苹果商店监控", "前往购买", "https://www.apple.com/" + countryCode + "/shop/buy-iphone/iphone-" + deviceCode, pushContentBuilder.toString());
} catch (Exception e) {
log.error("AppleMonitor error", e);
}
}
/**
* build request headers
*
* @param baseUrl base country url
* @param productCode product code
* @return headers
*/
private Map<String, List<String>> buildHeaders(String baseUrl, String deviceCode, String productCode) {
ArrayList<String> referer = new ArrayList<>();
referer.add(baseUrl + "/shop/buy-iphone/" + deviceCode + "/" + productCode);
Map<String, List<String>> headers = new HashMap<>(10);
headers.put(Header.REFERER.getValue(), referer);
return headers;
}
private boolean filterStore(JSONObject storeInfo) {
if (CollectionUtils.isEmpty(storeList)) {
return Boolean.TRUE;
}
String storeName = storeInfo.getString("storeName");
return storeList.stream()
.anyMatch(k -> storeName.contains(k) || k.contains(storeName));
}
/**
* check store inventory
*
* @param storeJson store json
* @param productCode product code
* @return boolean
*/
private boolean judgingStoreInventory(JSONObject storeJson, String productCode) {
JSONObject partsAvailability = storeJson.getJSONObject("partsAvailability");
String status = partsAvailability.getJSONObject(productCode).getString("pickupDisplay");
return "available".equals(status);
}
/**
* build pickup information
*
* @param retailStore retailStore
* @return pickup message
*/
private String buildPickupInformation(JSONObject retailStore) {
String distanceWithUnit = retailStore.getString("distanceWithUnit");
String twoLineAddress = retailStore.getJSONObject("address").getString("twoLineAddress");
String daytimePhone = retailStore.getJSONObject("address").getString("daytimePhone");
String messageTemplate = "\n取货地址:{},电话:{},距离{}:{}";
return CharSequenceUtil.format(messageTemplate, twoLineAddress.replace("\n", " "), daytimePhone, location, distanceWithUnit);
}
@Override
@XxlJob("appleInventoryScheduleHandler")
public ReturnT<String> execute(String params) throws Exception {
if (!Boolean.TRUE.equals(appleInventoryMonitorSwitch)) {
log.info(">>>>>>>>>>apple inventory monitor switch closed!<<<<<<<<<<");
return ReturnT.SUCCESS;
}
// 获取设备信息
List<IphoneProductDto> products = iphoneProductParserUtils.getProducts(deviceCode, countryCode);
//监视机型型号
products.forEach(product -> {
this.doMonitor(product);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.error(">>>>>>>>>>apple inventory monitor error:{}<<<<<<<<<<", e.getMessage(), e);
}
});
return ReturnT.SUCCESS;
}
}
//package top.baogutang.admin.schedule;
//
//import cn.hutool.core.text.CharSequenceUtil;
//import cn.hutool.core.util.CharsetUtil;
//import cn.hutool.core.util.URLUtil;
//import cn.hutool.http.Header;
//import cn.hutool.http.HttpRequest;
//import cn.hutool.http.HttpResponse;
//import com.alibaba.fastjson.JSON;
//import com.alibaba.fastjson.JSONArray;
//import com.alibaba.fastjson.JSONObject;
//import com.xxl.job.core.biz.model.ReturnT;
//import com.xxl.job.core.handler.IJobHandler;
//import com.xxl.job.core.handler.annotation.XxlJob;
//import lombok.extern.slf4j.Slf4j;
//import org.apache.commons.collections.CollectionUtils;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.cloud.context.config.annotation.RefreshScope;
//import org.springframework.scheduling.annotation.Scheduled;
//import org.springframework.stereotype.Component;
//import top.baogutang.admin.domain.IphoneProductDto;
//import top.baogutang.admin.utils.DingTalkMsgPushUtils;
//import top.baogutang.admin.utils.IphoneProductParserUtils;
//
//import javax.annotation.Resource;
//import java.util.ArrayList;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//import java.util.stream.Collectors;
//
///**
// * @description: 苹果库存监控
// * @author: nikooh
// * @date: 2023/10/13 : 15:12
// */
//@Slf4j
//@Component
//@RefreshScope
//public class AppleInventoryScheduleHandler extends IJobHandler {
//
// @Value("${baogutang.apple.country_code:cn}")
// private String countryCode;
//
// @Value("${baogutang.apple.device_code:17-pro}")
// private String deviceCode;
//
// @Value("${baogutang.apple.location:'上海 上海 闵行区'}")
// private String location;
//
// @Value("${baogutang.apple.switch:true}")
// private Boolean appleInventoryMonitorSwitch;
//
// // @Value("${baogutang.apple.storeList}")
// private List<String> storeList = new ArrayList<>();
//
// @Resource
// private IphoneProductParserUtils iphoneProductParserUtils;
//
// @Resource
// private DingTalkMsgPushUtils dingTalkMsgPushUtils;
//
// @Scheduled(cron = "0 0/1 * * * ? ")
// public void appleInventoryMonitor() {
// if (!Boolean.TRUE.equals(appleInventoryMonitorSwitch)) {
// log.info(">>>>>>>>>>apple inventory monitor switch closed!<<<<<<<<<<");
// return;
// }
// // 获取设备信息
// List<IphoneProductDto> products = iphoneProductParserUtils.getProducts(deviceCode, countryCode);
// //监视机型型号
// products.forEach(product -> {
// this.doMonitor(product);
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// log.error(">>>>>>>>>>apple inventory monitor error:{}<<<<<<<<<<", e.getMessage(), e);
// }
// });
// }
//
// private void doMonitor(IphoneProductDto product) {
//
//
// Map<String, Object> queryMap = new HashMap<>(5);
// queryMap.put("pl", "true");
// queryMap.put("mts.0", "regular");
// queryMap.put("parts.0", product.getModel());
// queryMap.put("location", location);
//
// String baseUrl = String.format("https://www.apple.com.%s", countryCode);
// Map<String, List<String>> headers = buildHeaders(baseUrl, deviceCode, product.getModel());
// String url = baseUrl + "/shop/fulfillment-messages?" + URLUtil.buildQuery(queryMap, CharsetUtil.CHARSET_UTF_8);
// try {
// HttpResponse httpResponse = HttpRequest.get(url)
// .header(headers)
// .execute();
// if (!httpResponse.isOk()) {
// log.warn(">>>>>>>>>>请求可能过于频繁,请稍后再试~<<<<<<<<<<");
// return;
// }
// JSONObject responseJsonObject = JSON.parseObject(httpResponse.body());
// JSONObject pickupMessage = responseJsonObject.getJSONObject("body").getJSONObject("content").getJSONObject("pickupMessage");
// JSONArray stores = pickupMessage.getJSONArray("stores");
// if (stores == null) {
//// log.info(pickupMessage.toString());
// return;
// }
// if (stores.isEmpty()) {
// log.info("您所在的 {} 附近没有Apple直营店请检查您的地址是否正确", location);
// return;
// }
// StringBuilder pushContentBuilder = new StringBuilder();
// List<String> availableStoreNameList = stores.stream()
// .filter(store -> filterStore((JSONObject) store))
// .filter(k -> judgingStoreInventory((JSONObject) k, product.getModel()))
// .map(store -> ((JSONObject) store).getString("storeName")
// .trim())
// .collect(Collectors.toList());
// if (CollectionUtils.isEmpty(availableStoreNameList)) {
// return;
// }
// JSONObject storeJson = (JSONObject) stores.get(0);
// JSONObject partsAvailability = storeJson.getJSONObject("partsAvailability");
// String deviceName = partsAvailability.getJSONObject(product.getModel()).getJSONObject("messageTypes").getJSONObject("regular").getString("storePickupProductTitle");
// pushContentBuilder.append("**")
// .append(deviceName)
// .append("**")
// .append("今天可取货:")
// .append("<br />");
// availableStoreNameList.forEach(availableStoreName ->
// pushContentBuilder.append("**")
// .append(availableStoreName)
// .append("** <br />"));
// dingTalkMsgPushUtils.robotMarkdownMsgPush("苹果商店监控", "前往购买", "https://www.apple.com/" + countryCode + "/shop/buy-iphone/iphone-" + deviceCode, pushContentBuilder.toString());
// } catch (Exception e) {
// log.error("AppleMonitor error", e);
// }
// }
//
// /**
// * build request headers
// *
// * @param baseUrl base country url
// * @param productCode product code
// * @return headers
// */
// private Map<String, List<String>> buildHeaders(String baseUrl, String deviceCode, String productCode) {
//
// ArrayList<String> referer = new ArrayList<>();
// referer.add(baseUrl + "/shop/buy-iphone/" + deviceCode + "/" + productCode);
//
// Map<String, List<String>> headers = new HashMap<>(10);
// headers.put(Header.REFERER.getValue(), referer);
//
// return headers;
// }
//
// private boolean filterStore(JSONObject storeInfo) {
// if (CollectionUtils.isEmpty(storeList)) {
// return Boolean.TRUE;
// }
// String storeName = storeInfo.getString("storeName");
// return storeList.stream()
// .anyMatch(k -> storeName.contains(k) || k.contains(storeName));
// }
//
// /**
// * check store inventory
// *
// * @param storeJson store json
// * @param productCode product code
// * @return boolean
// */
// private boolean judgingStoreInventory(JSONObject storeJson, String productCode) {
// JSONObject partsAvailability = storeJson.getJSONObject("partsAvailability");
// String status = partsAvailability.getJSONObject(productCode).getString("pickupDisplay");
// return "available".equals(status);
//
// }
//
// /**
// * build pickup information
// *
// * @param retailStore retailStore
// * @return pickup message
// */
// private String buildPickupInformation(JSONObject retailStore) {
// String distanceWithUnit = retailStore.getString("distanceWithUnit");
// String twoLineAddress = retailStore.getJSONObject("address").getString("twoLineAddress");
// String daytimePhone = retailStore.getJSONObject("address").getString("daytimePhone");
// String messageTemplate = "\n取货地址:{},电话:{},距离{}:{}";
// return CharSequenceUtil.format(messageTemplate, twoLineAddress.replace("\n", " "), daytimePhone, location, distanceWithUnit);
// }
//
// @Override
// @XxlJob("appleInventoryScheduleHandler")
// public ReturnT<String> execute(String params) throws Exception {
// if (!Boolean.TRUE.equals(appleInventoryMonitorSwitch)) {
// log.info(">>>>>>>>>>apple inventory monitor switch closed!<<<<<<<<<<");
// return ReturnT.SUCCESS;
// }
// // 获取设备信息
// List<IphoneProductDto> products = iphoneProductParserUtils.getProducts(deviceCode, countryCode);
// //监视机型型号
// products.forEach(product -> {
// this.doMonitor(product);
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// log.error(">>>>>>>>>>apple inventory monitor error:{}<<<<<<<<<<", e.getMessage(), e);
// }
// });
// return ReturnT.SUCCESS;
// }
//}

View File

@ -14,6 +14,10 @@ import top.baogutang.common.domain.Results;
import top.baogutang.common.utils.JacksonUtil;
import javax.servlet.http.HttpServletRequest;
import static top.baogutang.common.config.MdcRequestIdFilter.REQUEST_ID_KEY;
import static top.baogutang.common.config.MdcRequestIdFilter.CLIENT_IP_KEY;
/**
* @description:
@ -39,23 +43,36 @@ public class LogAspect {
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
assert sra != null;
HttpServletRequest request = sra.getRequest();
String requestId = MDC.get("X-Request-Id");
String requestId = MDC.get(REQUEST_ID_KEY);
String clientIp = MDC.get(CLIENT_IP_KEY);
long startMills = System.currentTimeMillis();
long cost = 0;
try {
result = pjp.proceed();
cost = System.currentTimeMillis() - startMills;
log.info("请求结束!本次请求耗时:{},url: {}, method: {}, params: {},user:{}, token: {}, 响应结果:{}",
cost, request.getRequestURL().toString(),
request.getMethod(), pjp.getArgs(), JacksonUtil.toJson(request.getAttribute("user")), request.getHeader("authorization"),
log.info("请求结束! client ip:{},本次请求耗时:{},url: {}, method: {}, params: {},user:{}, token: {}, 响应结果:{}",
clientIp,
cost,
request.getRequestURL().toString(),
request.getMethod(),
pjp.getArgs(),
JacksonUtil.toJson(request.getAttribute("user")),
request.getHeader("authorization"),
JacksonUtil.toJson(result));
if (result instanceof Results) {
Results r = (Results) result;
Results<?> r = (Results<?>) result;
r.setRid(requestId);
}
} catch (Exception e) {
log.error("请求异常!!!本次请求耗时:{},error:{},url: {}, method: {}, params: {},user:{}, token: {}", cost, e, request.getRequestURL().toString(),
request.getMethod(), pjp.getArgs(), JacksonUtil.toJson(request.getAttribute("user")), request.getHeader("token"));
log.error("请求异常!!! client ip:{},本次请求耗时:{},error:{},url: {}, method: {}, params: {},user:{}, token: {}",
clientIp,
cost,
e.getMessage(),
request.getRequestURL().toString(),
request.getMethod(),
pjp.getArgs(),
JacksonUtil.toJson(request.getAttribute("user")),
request.getHeader("token"));
throw e;
}
return result;

View File

@ -23,22 +23,92 @@ import java.util.UUID;
@Order(1)
public class MdcRequestIdFilter extends OncePerRequestFilter {
private static final String REQUEST_ID_KEY = "X-Request-Id";
public static final String REQUEST_ID_KEY = "X-Request-Id";
public static final String CLIENT_IP_KEY = "clientIp";
// 静态资源后缀列表
private static final String[] STATIC_RESOURCES = {
".css", ".js", ".jpg", ".jpeg", ".png", ".gif", ".ico", ".svg", ".woff", ".woff2", ".ttf", ".eot", ".map", ".gz", ".rar", ".zip", ".7z", ".pdf",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".mp3", ".mp4", ".avi", ".wmv", ".flv", ".swf", ".exe", ".dll", ".ico", ".psd", ".ai",
".eps", ".svg", ".ttf", ".woff", ".woff2", ".eot", ".otf", ".otc", ".ttc", ".ttf", ".woff", ".woff2", ".eot", ".otf", "json"
};
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 判断是否为静态资源请求
String uri = request.getRequestURI();
if (isStaticResource(uri)) {
// 静态资源请求直接放行不处理
filterChain.doFilter(request, response);
return;
}
try {
// 获取真实客户端IP
String clientIp = getClientIp(request);
MDC.put(CLIENT_IP_KEY, clientIp);
String traceId = request.getHeader(REQUEST_ID_KEY);
if (traceId == null) {
traceId = UUID.randomUUID().toString().replace("-", "");
log.info("requestId为空自动生成 {}", traceId);
log.info("requestId为空,自动生成 {}, client ip: {}", traceId, clientIp);
request.setAttribute(REQUEST_ID_KEY, traceId);
}
MDC.put(REQUEST_ID_KEY, traceId);
filterChain.doFilter(request, response);
} finally {
MDC.remove(REQUEST_ID_KEY);
MDC.remove(CLIENT_IP_KEY);
}
}
/**
* 判断是否为静态资源请求
*/
private boolean isStaticResource(String uri) {
if (uri == null || uri.isEmpty()) {
return false;
}
String lowerUri = uri.toLowerCase();
for (String suffix : STATIC_RESOURCES) {
if (lowerUri.endsWith(suffix)) {
return true;
}
}
return false;
}
/**
* 获取客户端真实IP地址
* 优先从代理请求头中获取如果没有则使用remoteAddr
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// X-Forwarded-For可能包含多个IP取第一个
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip.trim();
}
ip = request.getHeader("X-Real-IP");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip.trim();
}
ip = request.getHeader("Proxy-Client-IP");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip.trim();
}
ip = request.getHeader("WL-Proxy-Client-IP");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip.trim();
}
// 如果所有代理头都没有使用remoteAddr
return request.getRemoteAddr();
}
}