This commit is contained in:
ZiJIe 2025-07-15 10:20:26 +08:00
parent 90ecbd90cd
commit 1388d39376
5 changed files with 308 additions and 100 deletions

View File

@ -209,6 +209,7 @@ public class BanmaOrderController extends BaseController {
*/
@SuppressWarnings("unchecked")
private String getTrackingInfo(String trackingNumber) {
try {
R<Map<String, Object>> sagawaResult = sagawaExpressController.getTrackingInfo(trackingNumber);
if (sagawaResult != null && sagawaResult.getCode() == 200) {
Map<String, Object> sagawaData = sagawaResult.getData();
@ -223,23 +224,31 @@ public class BanmaOrderController extends BaseController {
}
}
}
try {
String url = String.format(TRACKING_URL, trackingNumber);
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
Map<String, Object> responseBody = response.getBody();
if (responseBody != null && Integer.valueOf(0).equals(responseBody.get("code"))) {
return Optional.ofNullable(responseBody.get("data"))
.map(data -> (List<Map<String, Object>>) data)
.filter(list -> !list.isEmpty())
.map(list -> list.get(0))
.map(track -> (String) track.get("track"))
.orElse(null);
// 如果从佐川获取失败尝试从斑马API获取
try {
String url = String.format(TRACKING_URL, trackingNumber);
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
Map<String, Object> responseBody = response.getBody();
if (responseBody != null && Integer.valueOf(0).equals(responseBody.get("code"))) {
return Optional.ofNullable(responseBody.get("data"))
.map(data -> (List<Map<String, Object>>) data)
.filter(list -> !list.isEmpty())
.map(list -> list.get(0))
.map(track -> (String) track.get("track"))
.orElse(null);
}
} catch (Exception e) {
logger.error("从斑马API获取物流信息失败: {}", e.getMessage());
// 继续处理不中断流程
}
} catch (Exception e) {
logger.error("获取物流信息失败: {}", e.getMessage());
// 继续处理不中断流程
}
return null;
// 如果所有尝试都失败返回默认值
return "暂无物流信息";
}
/**
@ -298,7 +307,8 @@ public class BanmaOrderController extends BaseController {
// 计算分页信息
int totalPages = (int) Math.ceil((double) totalCount / DEFAULT_PAGE_SIZE);
boolean hasMore = orders.size() == DEFAULT_PAGE_SIZE;
// 修改hasMore判断逻辑根据当前页数和总页数判断
boolean hasMore = totalCount > 0 && 1 < totalPages;
// 构建结果
Map<String, Object> resultMap = new HashMap<>();
@ -340,12 +350,26 @@ public class BanmaOrderController extends BaseController {
@ApiParam("开始日期(yyyy-MM-dd)") @RequestParam(required = false) String startDate,
@ApiParam("结束日期(yyyy-MM-dd)") @RequestParam(required = false) String endDate) {
DeferredResult<R<Map<String, Object>>> deferredResult = new DeferredResult<>(120000L);
DeferredResult<R<Map<String, Object>>> deferredResult = new DeferredResult<>(999999999L);
Thread thread = new Thread(() -> {
try {
if (deferredResult.isSetOrExpired()) return;
// 获取总页数信息
HttpEntity<String> entity = createHttpEntity();
String url = buildApiUrl(1, DEFAULT_PAGE_SIZE, startDate, endDate);
ResponseEntity<Map> countResponse = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
Map<String, Object> countResponseBody = countResponse.getBody();
int totalPages = 1;
if (countResponseBody != null && countResponseBody.containsKey("data")) {
Map<String, Object> dataMap = (Map<String, Object>) countResponseBody.get("data");
int totalCount = ((Number) dataMap.getOrDefault("total", 0)).intValue();
totalPages = (int) Math.ceil((double) totalCount / DEFAULT_PAGE_SIZE);
}
// 获取当前页数据
R<List<Map<String, Object>>> pageResult = fetchOrdersFromApi(page, DEFAULT_PAGE_SIZE, startDate, endDate);
if (pageResult.getCode() != 200) {
@ -355,10 +379,14 @@ public class BanmaOrderController extends BaseController {
List<Map<String, Object>> pageData = pageResult.getData();
// 修改hasMore判断逻辑根据当前页数和总页数判断
boolean hasMore = page < totalPages;
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("orders", pageData);
resultMap.put("hasMore", pageData != null && pageData.size() == DEFAULT_PAGE_SIZE);
resultMap.put("hasMore", hasMore);
resultMap.put("nextPage", page + 1);
resultMap.put("totalPages", totalPages);
setDeferredResult(deferredResult, R.ok(resultMap));
} catch (Exception e) {
@ -369,7 +397,6 @@ public class BanmaOrderController extends BaseController {
thread.setDaemon(true);
thread.start();
return deferredResult;
}

View File

@ -0,0 +1,160 @@
package com.ruoyi.web.controller.tool;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
/**
* 佐川急便物流查询控制器
*
* @author ruoyi
*/
@Api("佐川急便物流查询接口")
@RestController
@RequestMapping("/tool/sagawa")
@Anonymous
public class SagawaExpressController extends BaseController implements PageProcessor {
private static final Logger logger = LoggerFactory.getLogger(SagawaExpressController.class);
// 站点配置
private final Site site = Site.me()
.setRetryTimes(3)
.setSleepTime(1000)
.setTimeOut(99999999)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
// 查询结果
private Map<String, Object> resultMap = new HashMap<>();
@Override
public void process(Page page) {
try {
String pageContent = page.getHtml().toString();
// 检查是否有包裹数据未登记的信息
boolean hasNoData = pageContent.contains("お荷物データが登録されておりません");
if (hasNoData) {
resultMap.put("status", "notFound");
resultMap.put("message", "没有找到对应的包裹信息");
return;
}
String trackingTableRegex = "<table[^>]*class=\"table_basic table_okurijo_detail2\"[^>]*>\\s*<tbody>\\s*(?:<tr>.*?<th[^>]*>荷物状況</th>.*?</tr>.*?<tr>.*?</tr>.*?)+\\s*</tbody>\\s*</table>";
Pattern pattern = Pattern.compile(trackingTableRegex, Pattern.DOTALL);
Matcher matcher = pattern.matcher(pageContent);
if (matcher.find()) {
String trackingTable = matcher.group(0);
// 提取表格中的最后一行
String rowRegex = "<tr>\\s*<td>\\s*([^<]*?)\\s*</td>\\s*<td>\\s*([^<]*?)\\s*</td>\\s*<td>\\s*([^<]*?)\\s*</td>\\s*</tr>";
Pattern rowPattern = Pattern.compile(rowRegex, Pattern.DOTALL);
Matcher rowMatcher = rowPattern.matcher(trackingTable);
String status = "";
String dateTime = "";
String office = "";
// 找到所有匹配项保留最后一个
while (rowMatcher.find()) {
status = rowMatcher.group(1).trim();
dateTime = rowMatcher.group(2).trim();
office = rowMatcher.group(3).trim();
}
if (!status.isEmpty()) {
Map<String, String> trackInfo = new HashMap<>();
trackInfo.put("status", status);
trackInfo.put("dateTime", dateTime);
trackInfo.put("office", office);
resultMap.put("status", "success");
resultMap.put("trackInfo", trackInfo);
} else {
resultMap.put("status", "noRecords");
resultMap.put("message", "没有物流记录");
}
} else {
resultMap.put("status", "noTable");
resultMap.put("message", "未找到物流跟踪表格");
}
} catch (Exception e) {
logger.error("解析页面失败", e);
resultMap.put("status", "error");
resultMap.put("message", "解析页面失败: " + e.getMessage());
}
}
@Override
public Site getSite() {
return site;
}
/**
* 构建佐川急便查询URL
*/
private String buildSagawaUrl(String trackingNumber) {
return "https://k2k.sagawa-exp.co.jp/p/web/okurijosearch.do?okurijoNo=" + trackingNumber.trim();
}
/**
* 查询佐川急便物流信息
*/
@ApiOperation("查询佐川急便物流信息")
@GetMapping("/tracking/{trackingNumber}")
public R<Map<String, Object>> getTrackingInfo(@PathVariable("trackingNumber") String trackingNumber) {
try {
if (trackingNumber == null || trackingNumber.trim().isEmpty()) {
return R.fail("运单号不能为空");
}
resultMap = new HashMap<>();
String url = buildSagawaUrl(trackingNumber);
try {
Spider spider = Spider.create(this)
.addUrl(url)
.thread(1);
spider.run();
} catch (Exception e) {
logger.error("爬取物流信息失败,运单号:" + trackingNumber, e);
Map<String, String> defaultTrackInfo = new HashMap<>();
defaultTrackInfo.put("status", "处理中");
defaultTrackInfo.put("dateTime", "");
defaultTrackInfo.put("office", "");
resultMap.put("status", "success");
resultMap.put("trackInfo", defaultTrackInfo);
}
return R.ok(resultMap);
} catch (Exception e) {
logger.error("查询物流信息失败", e);
Map<String, Object> errorResult = new HashMap<>();
errorResult.put("status", "error");
errorResult.put("message", "查询物流信息失败: " + e.getMessage());
return R.ok(errorResult);
}
}
}

View File

@ -17,11 +17,13 @@ const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
timeout: 99999999
})
// request拦截器
service.interceptors.request.use(config => {
config.timeout = 99999999;
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交

View File

@ -84,8 +84,6 @@
<div class="progress-container">
<el-progress
:percentage="loadProgress.percentage"
:format="progressFormat"
status="primary"
:stroke-width="12"
class="load-progress">
</el-progress>
@ -343,15 +341,23 @@ export default {
timeout: 99999999
}).then(response => {
if (response.code === 200) {
this.orderData = [...this.orderData, ...response.data.orders];
//
this.orderData = [...this.orderData, ...(response.data.orders || [])];
this.hasMore = response.data.hasMore;
this.nextPage = response.data.nextPage;
this.currentPage++;
this.totalPages = response.data.totalPages || this.totalPages;
this.updateProgress();
this.saveToCache();
if (this.hasMore) {
console.log(`已加载第${this.currentPage}页,共${this.totalPages}hasMore=${this.hasMore}`);
// hasMorefalse
if (!this.hasMore && this.currentPage < this.totalPages) {
this.hasMore = true;
this.autoLoadNextPage();
} else if (this.hasMore) {
this.autoLoadNextPage();
} else {
this.loading = false;
@ -362,8 +368,22 @@ export default {
this.$message.error(response.msg || "获取更多订单失败");
}
}).catch(error => {
this.loading = false;
this.$message.error("获取更多订单失败: " + (error.message || error));
this.nextPage++;
this.currentPage++;
// 使
this.updateProgress();
this.saveToCache();
//
if (this.currentPage < this.totalPages) {
this.$message.warning(`加载第${this.currentPage}页失败,尝试继续加载后续页面`);
this.autoLoadNextPage();
} else {
this.loading = false;
this.$message.error("获取更多订单失败: " + (error.message || error));
}
});
}, 300);
},
@ -373,8 +393,10 @@ export default {
let percentage;
if (this.totalRecords > 0) {
percentage = Math.min(Math.round((this.orderData.length / this.totalRecords) * 100), 99);
} else {
} else if (this.totalPages > 1) {
percentage = Math.min(Math.round((this.currentPage / this.totalPages) * 100), 99);
} else {
percentage = this.currentPage > 0 ? 50 : 10;
}
this.loadProgress = {
@ -384,10 +406,7 @@ export default {
};
},
/** 进度条格式化 */
progressFormat(percentage) {
return `已加载 ${this.currentPage}/${this.totalPages} 页,${this.orderData.length}/${this.totalRecords} 条数据`;
},
/** 导出Excel */
handleExport() {
@ -670,7 +689,7 @@ export default {
request({
url: '/tool/banma/refresh-token',
method: 'get',
timeout: 60000
timeout: 99999999
}).then(response => {
if (response.code === 200) {
this.$message.success("Token刷新成功");

View File

@ -671,7 +671,7 @@ export default {
url: '/tool/webmagic/batch',
method: 'post',
data: currentBatch,
timeout: 9000000
timeout: 99999999
}).then(response => {
if (response.code === 200) {
this.productData = [...this.productData, ...response.data];
@ -735,7 +735,7 @@ export default {
url: '/tool/webmagic/batch',
method: 'post',
data: failedAsins,
timeout: 99999000
timeout: 99999999
}).then(response => {
if (response.code === 200 && response.data) {
// ASIN