diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventoryScheduleHandler.java b/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventoryScheduleHandler.java index 43cd43c..389c033 100644 --- a/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventoryScheduleHandler.java +++ b/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventoryScheduleHandler.java @@ -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 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 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 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> 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 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("
"); - availableStoreNameList.forEach(availableStoreName -> - pushContentBuilder.append("**") - .append(availableStoreName) - .append("**
")); - 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> buildHeaders(String baseUrl, String deviceCode, String productCode) { - - ArrayList referer = new ArrayList<>(); - referer.add(baseUrl + "/shop/buy-iphone/" + deviceCode + "/" + productCode); - - Map> 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 execute(String params) throws Exception { - if (!Boolean.TRUE.equals(appleInventoryMonitorSwitch)) { - log.info(">>>>>>>>>>apple inventory monitor switch closed!<<<<<<<<<<"); - return ReturnT.SUCCESS; - } - // 获取设备信息 - List 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 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 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 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> 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 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("
"); +// availableStoreNameList.forEach(availableStoreName -> +// pushContentBuilder.append("**") +// .append(availableStoreName) +// .append("**
")); +// 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> buildHeaders(String baseUrl, String deviceCode, String productCode) { +// +// ArrayList referer = new ArrayList<>(); +// referer.add(baseUrl + "/shop/buy-iphone/" + deviceCode + "/" + productCode); +// +// Map> 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 execute(String params) throws Exception { +// if (!Boolean.TRUE.equals(appleInventoryMonitorSwitch)) { +// log.info(">>>>>>>>>>apple inventory monitor switch closed!<<<<<<<<<<"); +// return ReturnT.SUCCESS; +// } +// // 获取设备信息 +// List 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; +// } +//} diff --git a/baogutang-common/src/main/java/top/baogutang/common/aspect/LogAspect.java b/baogutang-common/src/main/java/top/baogutang/common/aspect/LogAspect.java index 903d6e1..599c363 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/aspect/LogAspect.java +++ b/baogutang-common/src/main/java/top/baogutang/common/aspect/LogAspect.java @@ -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; diff --git a/baogutang-common/src/main/java/top/baogutang/common/config/MdcRequestIdFilter.java b/baogutang-common/src/main/java/top/baogutang/common/config/MdcRequestIdFilter.java index 5f6516f..903cfa0 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/config/MdcRequestIdFilter.java +++ b/baogutang-common/src/main/java/top/baogutang/common/config/MdcRequestIdFilter.java @@ -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(); + } }