You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

productInfoExample.js 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. const ProductApiClient = require("../src/services/productApiClient");
  2. const axios = require("axios");
  3. // 本地服务端实例
  4. const localClient = new ProductApiClient({
  5. // 根据实际部署环境修改baseURL
  6. baseURL: "http://localhost:8991",
  7. });
  8. // 外网服务端实例
  9. const serverClient = {
  10. baseURL: "http://192.168.1.107:8080",
  11. timeout: 10000 * 30, // 30秒
  12. params: {
  13. pageNum: 1,
  14. pageSize: 500,
  15. },
  16. };
  17. axios.defaults.baseURL = serverClient.baseURL;
  18. axios.defaults.timeout = serverClient.timeout;
  19. // 获取商品列表和抓取配置的执行频率
  20. const frequency = 1000 * 60 * 60 * 24; // 24小时
  21. // 设置抓取配置
  22. const config = {
  23. platform: "amazon", // 平台
  24. needScreenshot: true, // 是否需要截图
  25. warnTimeRange: 2, // 监控频率(小时)
  26. goodsList: [], // 商品列表
  27. isRunning: false, // 是否正在执行抓取任务
  28. timer: null, // 定时器
  29. };
  30. /**
  31. * 获取商品信息
  32. * @param {Object} goods - 商品对象
  33. * @param {boolean} isRetry - 是否为重试操作
  34. * @returns {Promise<Object|null>} - 返回商品信息或null
  35. */
  36. async function fetchProductInfo(goods, isRetry = false) {
  37. try {
  38. console.log(`${isRetry ? "重试" : "开始"}抓取商品: ${goods.goodsSkuSn}`);
  39. const productInfo = await localClient.getProductInfo({
  40. url: goods.goodsSkuUrl,
  41. platform: goods.platform,
  42. needScreenshot: config.needScreenshot,
  43. });
  44. console.log(
  45. `${isRetry ? "重试" : "商品"} 抓取成功: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`
  46. );
  47. console.log(productInfo);
  48. return productInfo;
  49. } catch (error) {
  50. console.error(
  51. `抓取失败: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`,
  52. error.message
  53. );
  54. return null;
  55. }
  56. }
  57. /**
  58. * 保存商品信息到服务器
  59. * @param {Object} goods - 商品对象
  60. * @param {Object} productInfo - 抓取到的商品信息
  61. * @returns {Promise<boolean>} - 是否保存成功
  62. */
  63. async function saveProductInfo(goods, productInfo) {
  64. try {
  65. console.log(
  66. `开始保存商品信息: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`
  67. );
  68. const { title, price, sku, remark, screenshotUrl } = productInfo[0];
  69. const res = await axios.post(
  70. serverClient.baseURL +
  71. "/system/operationWarnresult/receiveLatestGoodsInfo",
  72. {
  73. title,
  74. price: price.toString(),
  75. sku,
  76. url: goods.goodsSkuUrl,
  77. remark,
  78. screenshotUrl: screenshotUrl,
  79. }
  80. );
  81. console.log(res.data);
  82. return true;
  83. } catch (saveError) {
  84. console.error(
  85. `商品信息保存失败: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`,
  86. saveError.message
  87. );
  88. return false;
  89. }
  90. }
  91. /**
  92. * 处理单个商品的抓取和保存
  93. * @param {Object} goods - 商品对象
  94. * @returns {Promise<void>}
  95. */
  96. async function processProduct(goods) {
  97. // 第一次尝试抓取
  98. let productInfo = await fetchProductInfo(goods);
  99. // 如果第一次抓取成功,保存结果
  100. if (productInfo) {
  101. await saveProductInfo(goods, productInfo);
  102. return;
  103. }
  104. // 第一次失败,进行重试
  105. productInfo = await fetchProductInfo(goods, true);
  106. // 如果重试成功,保存结果
  107. if (productInfo) {
  108. await saveProductInfo(goods, productInfo);
  109. }
  110. // 重试失败,跳过该商品
  111. }
  112. /**
  113. * 获取抓取配置
  114. * @returns {Promise<void>}
  115. */
  116. async function fetchConfig() {
  117. try {
  118. console.log(`开始获取抓取配置: ${new Date().toLocaleString()}`);
  119. const res = await axios.get(serverClient.baseURL + "/system/operationWarnconfig/noVerifyList", {
  120. params: serverClient.params,
  121. });
  122. console.log(res.data);
  123. const { rows } = res.data;
  124. if (rows.length > 0) {
  125. config.warnTimeRange = rows[0].warnTimeRange;
  126. } else {
  127. config.warnTimeRange = 2; // 默认2小时
  128. }
  129. console.log(`抓取频率设置为 ${config.warnTimeRange} 小时`);
  130. return true;
  131. } catch (error) {
  132. console.error(`获取抓取配置失败: ${new Date().toLocaleString()}`, error.message);
  133. return false;
  134. }
  135. }
  136. /**
  137. * 获取商品列表并处理
  138. * @returns {Promise<void>}
  139. */
  140. async function fetchGoodsListAndProcess() {
  141. if (config.isRunning) {
  142. console.log(`上一次任务尚未完成,跳过本次执行: ${new Date().toLocaleString()}`);
  143. return;
  144. }
  145. config.isRunning = true;
  146. console.log(`开始执行抓取任务: ${new Date().toLocaleString()}`);
  147. try {
  148. const res = await axios.get(serverClient.baseURL + "/system/operationGoods/noVerifyList", {
  149. params: {
  150. ...serverClient.params,
  151. isDisabled: 1,
  152. },
  153. });
  154. console.log(res.data);
  155. const { rows } = res.data;
  156. config.goodsList = rows;
  157. // 使用for...of循环按顺序处理每个商品
  158. for (const goods of config.goodsList) {
  159. await processProduct(goods);
  160. }
  161. console.log("所有商品抓取完成", new Date().toLocaleString());
  162. } catch (error) {
  163. console.error(`获取商品列表失败: ${new Date().toLocaleString()}`, error.message);
  164. } finally {
  165. config.isRunning = false;
  166. }
  167. }
  168. /**
  169. * 启动定时任务
  170. */
  171. async function startScheduler() {
  172. // 先获取配置
  173. await fetchConfig();
  174. // 立即执行一次
  175. await fetchGoodsListAndProcess();
  176. // 清除之前的定时器(如果存在)
  177. if (config.timer) {
  178. clearInterval(config.timer);
  179. }
  180. // 设置定时器,根据warnTimeRange的小时数定时执行
  181. const intervalMs = config.warnTimeRange * 60 * 60 * 1000; // 转换为毫秒
  182. console.log(`设置定时任务,每 ${config.warnTimeRange} 小时执行一次,下次执行时间: ${new Date(Date.now() + intervalMs).toLocaleString()}`);
  183. config.timer = setInterval(async () => {
  184. console.log(`定时任务触发: ${new Date().toLocaleString()}`);
  185. // 重新获取配置(频率可能会改变)
  186. await fetchConfig();
  187. // 执行抓取处理
  188. await fetchGoodsListAndProcess();
  189. // 如果warnTimeRange发生变化,重新设置定时器
  190. const newIntervalMs = config.warnTimeRange * 60 * 60 * 1000;
  191. if (newIntervalMs !== intervalMs) {
  192. console.log(`监控频率已变更为 ${config.warnTimeRange} 小时,重新设置定时器`);
  193. clearInterval(config.timer);
  194. startScheduler(); // 重新启动调度器
  195. }
  196. }, intervalMs);
  197. // 添加防止程序崩溃的错误处理
  198. process.on('uncaughtException', (error) => {
  199. console.error(`未捕获的异常: ${new Date().toLocaleString()}`, error);
  200. // 尝试继续执行定时任务
  201. if (!config.timer) {
  202. startScheduler();
  203. }
  204. });
  205. }
  206. // 启动调度器
  207. startScheduler();
  208. // 输出启动信息
  209. console.log(`抓取服务已启动: ${new Date().toLocaleString()}`);
  210. console.log(`初始监控频率: ${config.warnTimeRange} 小时`);