您好,登錄后才能下訂單哦!
本篇內容主要講解“java開發如何實現訂閱到貨通知”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“java開發如何實現訂閱到貨通知”吧!
為什么每次到貨通知進去看都沒貨呢?猜想可能有幾種情況,可能這個通知并不是實時的一有貨就通知,也可能是訂閱的人太多了沒有全部發。總之,這個到貨通知不靠譜,那就只能自己實現一個到貨通知了。
實現步驟:
分析商品信息api
定時請求商品信息api查看商品庫存
發送消息通知
先用Charles或者Fiddler等工具分析查看商品數據時請求的api數據,之前有寫過Charles的具體使用方法,有興趣的同學可以看一下,這邊就不再細說了。
手機wifi代理配置Charles主機地址,查看api數據,根據api名稱和返回內容,可以判斷接口路徑是:/api/v1/xxx/goods-portal/spu/queryDetail
分析下api的返回數據內容,可以看到具體的庫存信息(刪除了許多沒用的數據),通過名稱分析可以定位到庫存字段為:stockQuantity,所以我們就可以通過這個api來查看具體商品的庫存數據了
{ "data": { "spuId": "1277934", "hostItem": "980033855", "storeId": "6782", "title": "Member's Mark 精選鮮雞蛋 30枚裝", "masterBizType": 1, "viceBizType": 1, "categoryIdList": [ "10003023", "10003228", "10004626", "10012102" ], "isAvailable": true, "isPutOnSale": true, "sevenDaysReturn": false, "intro": "MM 精選鮮雞蛋 30枚", "subTitle": "(粉殼雞蛋/褐殼雞蛋, 兩種隨機發貨, 不影響雞蛋品質) 精心培育 每一顆雞蛋都可溯源 口感香醇 做法多樣 懶人早餐", "brandId": "10194688", "weight": 1.5, "desc": "", "priceInfo": [ { "priceType": 2, "price": "0", "priceTypeName": "原始價" }, { "priceType": 1, "price": "2380", "priceTypeName": "銷售價" } ], "stockInfo": { "stockQuantity": 68, "safeStockQuantity": 0, "soldQuantity": 0 }, "limitInfo": [ { "limitType": 3, "limitNum": 5, "text": "限購2件", "cycleDays": 1 } ], "deliveryAttr": 3, "favorite": false, "giveaway": false, "beltInfo": [ ], "isStoreExtent": false, "isTicket": false }, "code": "Success", "msg": "", "errorMsg": "", "traceId": "a80e1d3df8f7f216", "requestId": "54c25d584f8a4b39b95ba7bdd1331da6.182.16740102252700000", "rt": 0, "success": true }
確定完接口返回數據后,我們還要獲取接口的請求數據request params(如上圖所示),因為請求數據中帶有商品的信息和個人的位置信息,不同的位置可能會查詢到不同的倉庫庫存(待驗證)。
本文以Java為例,代碼僅供參考和學習討論。
獲取到api信息后,我們就可以使用OkHttp或者webclient等請求工具類定時訪問api,查看商品庫存信息。
引入pom依賴
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.10.0</version> </dependency>
OkHttpUtils代碼示例:
package util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.val; import okhttp3.*; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.URLEncoder; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class OkHttpUtils { private static volatile OkHttpClient okHttpClient = null; private static volatile Semaphore semaphore = null; private Map<String, String> headerMap; private Map<String, String> paramMap; private String url; private Request.Builder request; /** * 初始化okHttpClient,并且允許https訪問 */ private OkHttpUtils() { if (okHttpClient == null) { synchronized (OkHttpUtils.class) { if (okHttpClient == null) { TrustManager[] trustManagers = buildTrustManagers(); okHttpClient = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0]) .hostnameVerifier((hostName, session) -> true) .retryOnConnectionFailure(true) .build(); addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); } } } } /** * 用于異步請求時,控制訪問線程數,返回結果 * * @return */ private static Semaphore getSemaphoreInstance() { //只能1個線程同時訪問 synchronized (OkHttpUtils.class) { if (semaphore == null) { semaphore = new Semaphore(0); } } return semaphore; } /** * 創建OkHttpUtils * * @return */ public static OkHttpUtils builder() { return new OkHttpUtils(); } /** * 添加url * * @param url * @return */ public OkHttpUtils url(String url) { this.url = url; return this; } /** * 添加參數 * * @param key 參數名 * @param value 參數值 * @return */ public OkHttpUtils addParam(String key, String value) { if (paramMap == null) { paramMap = new LinkedHashMap<>(16); } paramMap.put(key, value); return this; } /** * 添加參數 * * @param data * @return */ public OkHttpUtils addParam(String data) { if (paramMap == null) { paramMap = new LinkedHashMap<>(16); } val hashMap = JSONObject.parseObject(data, HashMap.class); paramMap.putAll(hashMap); return this; } /** * 添加請求頭 * * @param key 參數名 * @param value 參數值 * @return */ public OkHttpUtils addHeader(String key, String value) { if (headerMap == null) { headerMap = new LinkedHashMap<>(16); } headerMap.put(key, value); return this; } /** * 初始化get方法 * * @return */ public OkHttpUtils get() { request = new Request.Builder().get(); StringBuilder urlBuilder = new StringBuilder(url); if (paramMap != null) { urlBuilder.append("?"); try { for (Map.Entry<String, String> entry : paramMap.entrySet()) { urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")). append("="). append(URLEncoder.encode(entry.getValue(), "utf-8")). append("&"); } } catch (Exception e) { e.printStackTrace(); } urlBuilder.deleteCharAt(urlBuilder.length() - 1); } request.url(urlBuilder.toString()); return this; } /** * 初始化post方法 * * @param isJsonPost true等于json的方式提交數據,類似postman里post方法的raw * false等于普通的表單提交 * @return */ public OkHttpUtils post(boolean isJsonPost) { RequestBody requestBody; if (isJsonPost) { String json = ""; if (paramMap != null) { json = JSON.toJSONString(paramMap); } requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json); } else { FormBody.Builder formBody = new FormBody.Builder(); if (paramMap != null) { paramMap.forEach(formBody::add); } requestBody = formBody.build(); } request = new Request.Builder().post(requestBody).url(url); return this; } /** * 同步請求 * * @return */ public String sync() { setHeader(request); try { Response response = okHttpClient.newCall(request.build()).execute(); assert response.body() != null; return response.body().string(); } catch (IOException e) { e.printStackTrace(); return "請求失敗:" + e.getMessage(); } } /** * 異步請求,有返回值 */ public String async() { StringBuilder buffer = new StringBuilder(""); setHeader(request); okHttpClient.newCall(request.build()).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { buffer.append("請求出錯:").append(e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { assert response.body() != null; buffer.append(response.body().string()); getSemaphoreInstance().release(); } }); try { getSemaphoreInstance().acquire(); } catch (InterruptedException e) { e.printStackTrace(); } return buffer.toString(); } /** * 異步請求,帶有接口回調 * * @param callBack */ public void async(ICallBack callBack) { setHeader(request); okHttpClient.newCall(request.build()).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { callBack.onFailure(call, e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { assert response.body() != null; callBack.onSuccessful(call, response.body().string()); } }); } /** * 為request添加請求頭 * * @param request */ private void setHeader(Request.Builder request) { if (headerMap != null) { try { for (Map.Entry<String, String> entry : headerMap.entrySet()) { request.addHeader(entry.getKey(), entry.getValue()); } } catch (Exception e) { e.printStackTrace(); } } } /** * 生成安全套接字工廠,用于https請求的證書跳過 * * @return */ private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) { SSLSocketFactory ssfFactory = null; try { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new SecureRandom()); ssfFactory = sc.getSocketFactory(); } catch (Exception e) { e.printStackTrace(); } return ssfFactory; } private static TrustManager[] buildTrustManagers() { return new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } }; } /** * 自定義一個接口回調 */ public interface ICallBack { void onSuccessful(Call call, String data); void onFailure(Call call, String errorMsg); } }
定時查詢邏輯示例:
import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSONObject; import entity.EmailDto; import lombok.SneakyThrows; import lombok.val; import org.junit.Test; import util.EmailUtil; import util.OkHttpUtils; /** * TODO * * @author Huangshaoyang * @date 2022-08-12 15:58:04 */ public class OkHttpTest { @Test @SneakyThrows public void t1() { // request params String data = ""; while (true) { String res = OkHttpUtils.builder().url("https://xxxx/api/v1/xxx/goods-portal/spu/queryDetail") // 有參數的話添加參數,可多個 .addParam(data) // 也可以添加多個 .addHeader("Content-Type", "application/json; charset=utf-8") // 如果是true的話,會類似于postman中post提交方式的raw,用json的方式提交,不是表單 // 如果是false的話傳統的表單提交 .post(true) .sync(); // System.out.println(res); JSONObject json = JSONObject.parseObject(res); val stockQuantity = json.getJSONObject("data").getJSONObject("stockInfo").getIntValue("stockQuantity"); System.out.println(DateUtil.now() + " 庫存:" + stockQuantity); if (stockQuantity > 0 ) { sendNotify(); } else { Thread.sleep(10000); } } } @SneakyThrows private void sendNotify() { for (int i = 0; i < 3; i++) { System.out.println("send email"); EmailUtil.sendTextEmail(EmailDto.builder() .subject("有貨了快來搶購!!!") .context("有貨了快來搶購!!!") .build()); Thread.sleep(60000); } } }
注意點:
請求不要太頻繁,不要違背爬蟲規則
短信通知大部分是需要收費的,所以使用郵件通知
本次案例使用的是qq郵件通知,qq郵箱發送需要進入設置中開啟pop3服務,開啟后會有一個獨立密碼用來發送郵件。
發送郵件工具類示例:
package util; import entity.EmailDto; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; import javax.mail.*; import javax.mail.Message.RecipientType; import javax.mail.internet.*; import java.io.*; import java.util.Date; import java.util.Properties; /** * 使用SMTP協議發送電子郵件 */ public class EmailUtil1 { // 郵箱賬號 private final static String USERNAME = "xxx@qq.com"; // 郵箱密碼 private final static String PASSWORD = "xxx"; // 郵件發送協議 private final static String PROTOCOL = "smtp"; // SMTP郵件服務器 private final static String HOST = "smtp.qq.com"; // SMTP郵件服務器默認端口 private final static String PORT = "587"; // 發件人 private static String from = "xxx@qq.com"; // 是否要求身份認證 private final static String IS_AUTH = "true"; // 是否啟用調試模式(啟用調試模式可打印客戶端與服務器交互過程時一問一答的響應消息) private final static String IS_ENABLED_DEBUG_MOD = "false"; // 收件人 private static String to = "aaa@qq.com"; // 初始化連接郵件服務器的會話信息 private static Properties props = null; static { props = new Properties(); props.setProperty("mail.transport.protocol", PROTOCOL); props.setProperty("mail.smtp.host", HOST); props.setProperty("mail.smtp.port", PORT); props.setProperty("mail.smtp.auth", IS_AUTH); props.setProperty("mail.debug",IS_ENABLED_DEBUG_MOD); // props.setProperty("mail.smtp.ssl.enable", "true"); } /** * 發送簡單的文本郵件 */ public static void sendTextEmail(EmailDto dto) throws Exception { // 創建Session實例對象 Session session = Session.getDefaultInstance(props); // 創建MimeMessage實例對象 MimeMessage message = new MimeMessage(session); // 設置發件人 message.setFrom(new InternetAddress(from)); // 設置郵件主題 message.setSubject(dto.getSubject()); // 設置收件人 message.setRecipient(RecipientType.TO, new InternetAddress(to)); // 設置發送時間 message.setSentDate(new Date()); // 設置純文本內容為郵件正文 message.setText(dto.getContext()); // 保存并生成最終的郵件內容 message.saveChanges(); // 獲得Transport實例對象 Transport transport = session.getTransport(); // 打開連接 transport.connect(USERNAME, PASSWORD); // 將message對象傳遞給transport對象,將郵件發送出去 transport.sendMessage(message, message.getAllRecipients()); // 關閉連接 transport.close(); } }
到此,相信大家對“java開發如何實現訂閱到貨通知”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。