From 2919d50eb6d71f9096d6ec4cbfd86752da7e822e Mon Sep 17 00:00:00 2001 From: JiyangTang Date: Fri, 13 Oct 2023 17:31:17 +0800 Subject: [PATCH] add apple inventory monitor schedule --- .../domain/AppleInventoryPushDetails.java | 382 ++++++++++++++++++ .../admin/domain/IphoneProductDto.java | 37 ++ .../schedule/AppleInventorySchedule.java | 193 +++++++++ .../admin/utils/IphoneProductParserUtils.java | 98 +++++ 4 files changed, 710 insertions(+) create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/domain/AppleInventoryPushDetails.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/domain/IphoneProductDto.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventorySchedule.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/utils/IphoneProductParserUtils.java diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/domain/AppleInventoryPushDetails.java b/baogutang-admin/src/main/java/top/baogutang/admin/domain/AppleInventoryPushDetails.java new file mode 100644 index 0000000..2b7f907 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/domain/AppleInventoryPushDetails.java @@ -0,0 +1,382 @@ +package top.baogutang.admin.domain; + +public class AppleInventoryPushDetails { + private String title; + private String body; + private String level; + private String badge; + private String autoCopy; + private String copy; + private String sound; + private String icon; + private String group; + private String isArchive; + private String category; + + AppleInventoryPushDetails(String title, String body, String level, String badge, String autoCopy, String copy, String sound, String icon, String group, String isArchive, String category) { + this.title = title; + this.body = body; + this.level = level; + this.badge = badge; + this.autoCopy = autoCopy; + this.copy = copy; + this.sound = sound; + this.icon = icon; + this.group = group; + this.isArchive = isArchive; + this.category = category; + } + + public static PushDetailsBuilder builder() { + return new PushDetailsBuilder(); + } + + public String getTitle() { + return this.title; + } + + public String getBody() { + return this.body; + } + + public String getLevel() { + return this.level; + } + + public String getBadge() { + return this.badge; + } + + public String getAutoCopy() { + return this.autoCopy; + } + + public String getCopy() { + return this.copy; + } + + public String getSound() { + return this.sound; + } + + public String getIcon() { + return this.icon; + } + + public String getGroup() { + return this.group; + } + + public String getIsArchive() { + return this.isArchive; + } + + public String getCategory() { + return this.category; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setBody(String body) { + this.body = body; + } + + public void setLevel(String level) { + this.level = level; + } + + public void setBadge(String badge) { + this.badge = badge; + } + + public void setAutoCopy(String autoCopy) { + this.autoCopy = autoCopy; + } + + public void setCopy(String copy) { + this.copy = copy; + } + + public void setSound(String sound) { + this.sound = sound; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public void setGroup(String group) { + this.group = group; + } + + public void setIsArchive(String isArchive) { + this.isArchive = isArchive; + } + + public void setCategory(String category) { + this.category = category; + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (!(o instanceof AppleInventoryPushDetails)) { + return false; + } else { + AppleInventoryPushDetails other = (AppleInventoryPushDetails)o; + if (!other.canEqual(this)) { + return false; + } else { + label143: { + Object this$title = this.getTitle(); + Object other$title = other.getTitle(); + if (this$title == null) { + if (other$title == null) { + break label143; + } + } else if (this$title.equals(other$title)) { + break label143; + } + + return false; + } + + Object this$body = this.getBody(); + Object other$body = other.getBody(); + if (this$body == null) { + if (other$body != null) { + return false; + } + } else if (!this$body.equals(other$body)) { + return false; + } + + Object this$level = this.getLevel(); + Object other$level = other.getLevel(); + if (this$level == null) { + if (other$level != null) { + return false; + } + } else if (!this$level.equals(other$level)) { + return false; + } + + label122: { + Object this$badge = this.getBadge(); + Object other$badge = other.getBadge(); + if (this$badge == null) { + if (other$badge == null) { + break label122; + } + } else if (this$badge.equals(other$badge)) { + break label122; + } + + return false; + } + + label115: { + Object this$autoCopy = this.getAutoCopy(); + Object other$autoCopy = other.getAutoCopy(); + if (this$autoCopy == null) { + if (other$autoCopy == null) { + break label115; + } + } else if (this$autoCopy.equals(other$autoCopy)) { + break label115; + } + + return false; + } + + Object this$copy = this.getCopy(); + Object other$copy = other.getCopy(); + if (this$copy == null) { + if (other$copy != null) { + return false; + } + } else if (!this$copy.equals(other$copy)) { + return false; + } + + Object this$sound = this.getSound(); + Object other$sound = other.getSound(); + if (this$sound == null) { + if (other$sound != null) { + return false; + } + } else if (!this$sound.equals(other$sound)) { + return false; + } + + label94: { + Object this$icon = this.getIcon(); + Object other$icon = other.getIcon(); + if (this$icon == null) { + if (other$icon == null) { + break label94; + } + } else if (this$icon.equals(other$icon)) { + break label94; + } + + return false; + } + + label87: { + Object this$group = this.getGroup(); + Object other$group = other.getGroup(); + if (this$group == null) { + if (other$group == null) { + break label87; + } + } else if (this$group.equals(other$group)) { + break label87; + } + + return false; + } + + Object this$isArchive = this.getIsArchive(); + Object other$isArchive = other.getIsArchive(); + if (this$isArchive == null) { + if (other$isArchive != null) { + return false; + } + } else if (!this$isArchive.equals(other$isArchive)) { + return false; + } + + Object this$category = this.getCategory(); + Object other$category = other.getCategory(); + if (this$category == null) { + if (other$category != null) { + return false; + } + } else if (!this$category.equals(other$category)) { + return false; + } + + return true; + } + } + } + + protected boolean canEqual(Object other) { + return other instanceof AppleInventoryPushDetails; + } + + public int hashCode() { + int result = 1; + Object $title = this.getTitle(); + result = result * 59 + ($title == null ? 43 : $title.hashCode()); + Object $body = this.getBody(); + result = result * 59 + ($body == null ? 43 : $body.hashCode()); + Object $level = this.getLevel(); + result = result * 59 + ($level == null ? 43 : $level.hashCode()); + Object $badge = this.getBadge(); + result = result * 59 + ($badge == null ? 43 : $badge.hashCode()); + Object $autoCopy = this.getAutoCopy(); + result = result * 59 + ($autoCopy == null ? 43 : $autoCopy.hashCode()); + Object $copy = this.getCopy(); + result = result * 59 + ($copy == null ? 43 : $copy.hashCode()); + Object $sound = this.getSound(); + result = result * 59 + ($sound == null ? 43 : $sound.hashCode()); + Object $icon = this.getIcon(); + result = result * 59 + ($icon == null ? 43 : $icon.hashCode()); + Object $group = this.getGroup(); + result = result * 59 + ($group == null ? 43 : $group.hashCode()); + Object $isArchive = this.getIsArchive(); + result = result * 59 + ($isArchive == null ? 43 : $isArchive.hashCode()); + Object $category = this.getCategory(); + result = result * 59 + ($category == null ? 43 : $category.hashCode()); + return result; + } + + public String toString() { + String var10000 = this.getTitle(); + return "PushDetails(title=" + var10000 + ", body=" + this.getBody() + ", level=" + this.getLevel() + ", badge=" + this.getBadge() + ", autoCopy=" + this.getAutoCopy() + ", copy=" + this.getCopy() + ", sound=" + this.getSound() + ", icon=" + this.getIcon() + ", group=" + this.getGroup() + ", isArchive=" + this.getIsArchive() + ", category=" + this.getCategory() + ")"; + } + + public static class PushDetailsBuilder { + private String title; + private String body; + private String level; + private String badge; + private String autoCopy; + private String copy; + private String sound; + private String icon; + private String group; + private String isArchive; + private String category; + + PushDetailsBuilder() { + } + + public PushDetailsBuilder title(String title) { + this.title = title; + return this; + } + + public PushDetailsBuilder body(String body) { + this.body = body; + return this; + } + + public PushDetailsBuilder level(String level) { + this.level = level; + return this; + } + + public PushDetailsBuilder badge(String badge) { + this.badge = badge; + return this; + } + + public PushDetailsBuilder autoCopy(String autoCopy) { + this.autoCopy = autoCopy; + return this; + } + + public PushDetailsBuilder copy(String copy) { + this.copy = copy; + return this; + } + + public PushDetailsBuilder sound(String sound) { + this.sound = sound; + return this; + } + + public PushDetailsBuilder icon(String icon) { + this.icon = icon; + return this; + } + + public PushDetailsBuilder group(String group) { + this.group = group; + return this; + } + + public PushDetailsBuilder isArchive(String isArchive) { + this.isArchive = isArchive; + return this; + } + + public PushDetailsBuilder category(String category) { + this.category = category; + return this; + } + + public AppleInventoryPushDetails build() { + return new AppleInventoryPushDetails(this.title, this.body, this.level, this.badge, this.autoCopy, this.copy, this.sound, this.icon, this.group, this.isArchive, this.category); + } + + public String toString() { + return "PushDetails.PushDetailsBuilder(title=" + this.title + ", body=" + this.body + ", level=" + this.level + ", badge=" + this.badge + ", autoCopy=" + this.autoCopy + ", copy=" + this.copy + ", sound=" + this.sound + ", icon=" + this.icon + ", group=" + this.group + ", isArchive=" + this.isArchive + ", category=" + this.category + ")"; + } + } +} \ No newline at end of file diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/domain/IphoneProductDto.java b/baogutang-admin/src/main/java/top/baogutang/admin/domain/IphoneProductDto.java new file mode 100644 index 0000000..d2fa0db --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/domain/IphoneProductDto.java @@ -0,0 +1,37 @@ +package top.baogutang.admin.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * @description: + * @author: nikooh + * @date: 2023/10/13 : 15:45 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class IphoneProductDto { + + private String type; + + private String model; + + private String color; + + private String capacity; + + private String colorDisplay; + + private double price; + + private String priceDisplay; + + private String priceCurrency; + + private String carrierModel; + + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventorySchedule.java b/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventorySchedule.java new file mode 100644 index 0000000..0517ed9 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/schedule/AppleInventorySchedule.java @@ -0,0 +1,193 @@ +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 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 AppleInventorySchedule { + + @Value("${baogutang.apple.country_code:cn}") + private String countryCode; + + @Value("${baogutang.apple.device_code:15-pro}") + private String deviceCode; + + @Value("${baogutang.apple.location:'上海 上海 闵行区'}") + private String location; + + // @Value("${baogutang.apple.storeList}") + private List storeList = new ArrayList<>(); + + @Resource + private DingTalkMsgPushUtils dingTalkMsgPushUtils; + + @Scheduled(cron = "0 0/5 * * * ? ") + public void appleInventoryMonitor() { + // 获取设备信息 + List products = IphoneProductParserUtils.getProducts(deviceCode, countryCode); + //监视机型型号 + products.forEach(product -> { + this.doMonitor(product); + try { + Thread.sleep(1000); + } 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.debug(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 storeJson = (JSONObject) store; + return storeJson.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("苹果商店监控", 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); + } + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/utils/IphoneProductParserUtils.java b/baogutang-admin/src/main/java/top/baogutang/admin/utils/IphoneProductParserUtils.java new file mode 100644 index 0000000..3abf527 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/utils/IphoneProductParserUtils.java @@ -0,0 +1,98 @@ +package top.baogutang.admin.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import top.baogutang.admin.domain.IphoneProductDto; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * @description: + * @author: nikooh + * @date: 2023/10/13 : 15:47 + */ +public class IphoneProductParserUtils { + + public static List getProducts(String code, String country) { + String url = "https://www.apple.com/" + country + "/shop/buy-iphone/iphone-" + code; + String content = sendHttpRequest(url); + + assert content.contains("productSelectionData"); + + return parseProducts(content); + } + + private static String sendHttpRequest(String url) { + try { + URL urlObj = new URL(url); + HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0"); + + int responseCode = connection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + StringBuilder response = new StringBuilder(); + String inputLine; + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + return response.toString(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + private static List parseProducts(String content) { + String selectText = null; + String[] parts = content.split("window.PRODUCT_SELECTION_BOOTSTRAP = "); + if (parts.length > 1) { + selectText = parts[1].split("")[0].replace("productSelectionData", "\"productSelectionData\""); + } + + if (selectText != null) { + JSONObject selectData = JSON.parseObject(selectText); + JSONObject productSelectionData = selectData.getJSONObject("productSelectionData"); + JSONArray productsArray = productSelectionData.getJSONArray("products"); + JSONObject displayValues = productSelectionData.getJSONObject("displayValues"); + JSONObject prices = displayValues.getJSONObject("prices"); + JSONObject colors = displayValues.getJSONObject("dimensionColor"); + + List products = new ArrayList<>(); + + for (int i = 0; i < productsArray.size(); i++) { + JSONObject productObject = productsArray.getJSONObject(i); + String priceTag = productObject.getString("fullPrice"); + JSONObject priceData = prices.getJSONObject(priceTag); + + IphoneProductDto product = new IphoneProductDto( + productObject.getString("familyType"), + productObject.getString("partNumber"), + productObject.getString("dimensionColor"), + productObject.getString("dimensionCapacity"), + colors.getJSONObject(productObject.getString("dimensionColor")).getString("value"), + priceData.getJSONObject("currentPrice").getDouble("raw_amount"), + priceData.getJSONObject("currentPrice").getString("amount"), + priceData.getString("priceCurrency"), + productObject.containsKey("carrierModel") ? productObject.getString("carrierModel") : "" + ); + + products.add(product); + } + return products; + } + return new ArrayList<>(); + } + +}