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

View File

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

View File

@ -23,22 +23,92 @@ import java.util.UUID;
@Order(1) @Order(1)
public class MdcRequestIdFilter extends OncePerRequestFilter { 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 @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 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 { try {
// 获取真实客户端IP
String clientIp = getClientIp(request);
MDC.put(CLIENT_IP_KEY, clientIp);
String traceId = request.getHeader(REQUEST_ID_KEY); String traceId = request.getHeader(REQUEST_ID_KEY);
if (traceId == null) { if (traceId == null) {
traceId = UUID.randomUUID().toString().replace("-", ""); traceId = UUID.randomUUID().toString().replace("-", "");
log.info("requestId为空自动生成 {}", traceId); log.info("requestId为空,自动生成 {}, client ip: {}", traceId, clientIp);
request.setAttribute(REQUEST_ID_KEY, traceId); request.setAttribute(REQUEST_ID_KEY, traceId);
} }
MDC.put(REQUEST_ID_KEY, traceId); MDC.put(REQUEST_ID_KEY, traceId);
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} finally { } finally {
MDC.remove(REQUEST_ID_KEY); 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();
}
} }