From e5fbfb5395037d9639bf123e845a44231ee52497 Mon Sep 17 00:00:00 2001 From: ljw <706814450@qq.com> Date: Mon, 25 May 2026 13:36:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=B3=E5=88=BB=E7=89=A9=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../supervisory/config/AppConfiguration.java | 2 +- .../supervisory/config/JikeProperties.java | 39 +++ .../device/contant/LocalizerCategory.java | 2 +- .../gps/controller/GPSController.java | 203 ++++++++++++++- .../supervisory/gps/jike/JikeApiClient.java | 231 ++++++++++++++++++ .../supervisory/gps/jike/JikeResponse.java | 28 +++ .../jike/controller/JikeTestController.java | 162 ++++++++++++ .../gps/jike/result/JikeEventResult.java | 36 +++ .../gps/jike/result/JikeMediaResult.java | 20 ++ .../gps/jike/result/JikeTrackResult.java | 28 +++ .../jike/support/JikeRequestInterceptor.java | 77 ++++++ .../gps/jike/support/JikeSignUtil.java | 136 +++++++++++ .../order/pojo/result/OrderPagingResult.java | 11 + .../order/service/OrderInfoService.java | 11 + .../src/main/resources/application-dev.yml | 7 + .../mapper/order/OrderInfoMapper.xml | 1 + 16 files changed, 991 insertions(+), 3 deletions(-) create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/JikeProperties.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeApiClient.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeResponse.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/controller/JikeTestController.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeEventResult.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeMediaResult.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeTrackResult.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeRequestInterceptor.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeSignUtil.java diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/AppConfiguration.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/AppConfiguration.java index c0b2ecb..0779171 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/AppConfiguration.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/AppConfiguration.java @@ -4,6 +4,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Configuration; @Configuration -@EnableConfigurationProperties({AppProperties.class, JimiProperties.class}) +@EnableConfigurationProperties({AppProperties.class, JimiProperties.class, JikeProperties.class}) public class AppConfiguration { } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/JikeProperties.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/JikeProperties.java new file mode 100644 index 0000000..596dfab --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/config/JikeProperties.java @@ -0,0 +1,39 @@ +package com.njzscloud.supervisory.config; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +@ConfigurationProperties("app.jike") +public class JikeProperties { + /** + * 应用ID + */ + private String appId; + /** + * 应用密钥 + */ + private String secretKey; + /** + * 沙箱环境地址 + */ + private String sandboxUrl = "https://open-api.test.jkwlw.com.cn"; + /** + * 生产环境地址 + */ + private String prodUrl = "https://open-api.jkwlw.com.cn"; + /** + * 是否使用沙箱环境 + */ + private Boolean sandbox = true; + + public String getEndpoint() { + return Boolean.TRUE.equals(sandbox) ? sandboxUrl : prodUrl; + } +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/contant/LocalizerCategory.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/contant/LocalizerCategory.java index 88ac6f3..7326d7a 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/contant/LocalizerCategory.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/contant/LocalizerCategory.java @@ -12,7 +12,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum LocalizerCategory implements DictStr { Tuqiang("Tuqiang", "途强定位器"), - HangTianZiJin("HangTianZiJin", "航天紫金定位器"), + JKWL("JKWL", "即刻物联"), ; private final String val; private final String txt; diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/controller/GPSController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/controller/GPSController.java index cddebe0..a085890 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/controller/GPSController.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/controller/GPSController.java @@ -1,20 +1,45 @@ package com.njzscloud.supervisory.gps.controller; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; import com.njzscloud.common.core.utils.R; +import com.njzscloud.supervisory.config.JikeProperties; +import com.njzscloud.supervisory.gps.jike.JikeApiClient; +import com.njzscloud.supervisory.gps.jike.JikeResponse; +import com.njzscloud.supervisory.gps.jike.result.JikeEventResult; +import com.njzscloud.supervisory.gps.jike.result.JikeMediaResult; +import com.njzscloud.supervisory.gps.jike.result.JikeTrackResult; import com.njzscloud.supervisory.gps.tuqiang.jimi.DeviceLocationGetParam; import com.njzscloud.supervisory.gps.tuqiang.jimi.DeviceLocationGetResult; import com.njzscloud.supervisory.gps.tuqiang.jimi.DeviceTrackListParam; import com.njzscloud.supervisory.gps.tuqiang.jimi.DeviceTrackListResult; import com.njzscloud.supervisory.gps.tuqiang.jimi.util.JimiUtil; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + /** * GPS */ +@Slf4j @RestController @RequestMapping("/gps") public class GPSController { + + @Autowired + private JikeProperties jikeProperties; + /** * 实时定位 */ @@ -28,7 +53,7 @@ public class GPSController { } /** - * 轨迹查询 + * 轨迹查询(图强) */ @RequestMapping("/track_list") public R trackList(DeviceTrackListParam param) { @@ -38,4 +63,180 @@ public class GPSController { } return R.failed(); } + + /** + * 历史轨迹查询(即刻物联) + * + * @param imei 设备号 + * @param stime 开始时间(格式: yyyy-MM-dd HH:mm:ss) + * @param etime 结束时间(格式: yyyy-MM-dd HH:mm:ss) + * @return 返回前端期望的格式 {latitude, longitude, direction} + */ + @RequestMapping("/jike_track_list") + public R jikeTrackList(@RequestParam String imei, + @RequestParam String stime, + @RequestParam String etime) { + try { + JikeApiClient client = new JikeApiClient(jikeProperties); + + Map params = new HashMap<>(); + params.put("imei", imei); + params.put("stime", String.valueOf(DateUtil.parse(stime).getTime())); + params.put("etime", String.valueOf(DateUtil.parse(etime).getTime())); + + log.info("即刻物联轨迹查询请求: {}", params); + + String response = client.doGet("/open-api/gps-trace/list", params); + log.info("即刻物联轨迹查询响应: {}", response); + + JikeResponse> result = com.alibaba.fastjson2.JSON.parseObject( + response, + new com.alibaba.fastjson2.TypeReference>>() { + } + ); + + if (result.isSuccess()) { + List trackPoints = convertToTrackPoints(result.getData()); + return R.success(trackPoints); + } else { + log.error("即刻物联轨迹查询失败: " + result.getHeader().getMessage()); + return R.failed(); + } + } catch (Exception e) { + log.error("即刻物联轨迹查询异常", e); + return R.failed(); + } + } + + /** + * 将即刻轨迹转换为前端期望的格式 + */ + private List convertToTrackPoints(List jikeResults) { + if (jikeResults == null || jikeResults.isEmpty()) { + return Collections.emptyList(); + } + return jikeResults.stream().map(track -> { + TrackPoint point = new TrackPoint(); + // gpsText 格式: "lng,lat" + String gpsText = track.getGpsText(); + if (gpsText != null && gpsText.contains(",")) { + String[] parts = gpsText.split(","); + try { + point.setLongitude(Double.parseDouble(parts[0])); + if (parts.length > 1) { + point.setLatitude(Double.parseDouble(parts[1])); + } + } catch (NumberFormatException e) { + log.warn("GPS坐标解析失败: {}", gpsText); + } + } + point.setDirection(track.getDirection() != null ? track.getDirection() : 0); + return point; + }).collect(Collectors.toList()); + } + + /** + * 轨迹点 + */ + @Data + public static class TrackPoint { + private Double latitude; + private Double longitude; + private Integer direction; + } + + /** + * 历史报警事件查询(即刻物联) + * @param imei 设备号 + * @param stime 开始时间(格式: yyyy-MM-dd HH:mm:ss) + * @param etime 结束时间(格式: yyyy-MM-dd HH:mm:ss) + */ + @RequestMapping("/event_list") + public R eventList(@RequestParam String imei, + @RequestParam String stime, + @RequestParam String etime) { + try { + JikeApiClient client = new JikeApiClient(jikeProperties); + + Map params = new HashMap<>(); + params.put("imei", imei); + params.put("eventTypeList", "25840,58648"); + params.put("stime", String.valueOf(DateUtil.parse(stime).getTime())); + params.put("etime", String.valueOf(DateUtil.parse(etime).getTime())); + + String response = client.doGet("/open-api/v2/event/list", params); + log.info("即刻物联历史报警事件查询响应: {}", response); + + JikeResponse> result = JSON.parseObject( + response, + new TypeReference>>() {} + ); + + if (result.isSuccess()) { + List mediaItems = extractMediaList(result.getData()); + return R.success(mediaItems); + } else { + log.error("即刻物联历史报警事件查询失败: " + result.getHeader().getMessage()); + return R.failed(); + } + } catch (Exception e) { + log.error("即刻物联历史报警事件查询异常", e); + return R.failed(); + } + } + + /** + * 从报警事件中提取所有mediaList并转换mediaType为中文 + */ + private List extractMediaList(List events) { + if (events == null || events.isEmpty()) { + return Collections.emptyList(); + } + List allMedia = new ArrayList<>(); + for (JikeEventResult event : events) { + if (event.getMediaList() != null) { + for (JikeMediaResult media : event.getMediaList()) { + MediaItem item = new MediaItem(); + item.setId(media.getId()); + item.setChannel(media.getChannel()); + item.setMedia(media.getMedia()); + item.setMediaType(media.getMediaType()); + item.setMediaTypeText(getMediaTypeText(media.getMediaType())); + allMedia.add(item); + } + } + } + return allMedia; + } + + /** + * 获取媒体类型中文说明 + * mediaType: -1未知,0图片,1音频,2视频,3文本,4其它 + */ + private String getMediaTypeText(Integer mediaType) { + if (mediaType == null) { + return "未知"; + } + switch (mediaType) { + case -1: return "未知"; + case 0: return "图片"; + case 1: return "音频"; + case 2: return "视频"; + case 3: return "文本"; + case 4: return "其它"; + default: return "未知"; + } + } + + /** + * 媒体项 + */ + @Data + public static class MediaItem { + private Long id; + private Integer channel; + private String media; + private Integer mediaType; + private String mediaTypeText; + } } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeApiClient.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeApiClient.java new file mode 100644 index 0000000..9126b2d --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeApiClient.java @@ -0,0 +1,231 @@ +package com.njzscloud.supervisory.gps.jike; + +import com.njzscloud.supervisory.config.JikeProperties; +import com.njzscloud.supervisory.gps.jike.support.JikeSignUtil; +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.TreeMap; + +/** + * 即刻物联HTTP客户端 + */ +@Slf4j +public class JikeApiClient { + + private final JikeProperties properties; + + public JikeApiClient(JikeProperties properties) { + this.properties = properties; + } + + /** + * GET请求 + * @param uri 接口路径,如 /open-api/gps-trace/list + * @param queryString 参数字符串,如 carNum=京A12345&stime=1746777600000&etime=1746864000000 + */ + public String doGet(String uri, String queryString) throws Exception { + String endpoint = properties.getEndpoint(); + String url = endpoint + uri; + + long timestamp = System.currentTimeMillis(); + String appId = properties.getAppId(); + String secret = properties.getSecretKey(); + + // 计算参数签名 - 使用原始值(不编码) + String paramMd5 = JikeSignUtil.buildParamSignature(queryString); + // 计算最终签名 - GET请求不包含body-signature + String finalSign = JikeSignUtil.buildGetFinalSignature(appId, paramMd5, secret, timestamp); + + log.info("Jike GET request queryString: {}", queryString); + log.info("Jike param-signature: {}", paramMd5); + log.info("Jike final-signature: {}", finalSign); + + // 构建URL,参数值需要URL编码 + String finalUrl = url; + if (queryString != null && !queryString.isEmpty()) { + StringBuilder encodedUrl = new StringBuilder(url).append("?"); + String[] pairs = queryString.split("&"); + for (int i = 0; i < pairs.length; i++) { + if (i > 0) { + encodedUrl.append("&"); + } + String pair = pairs[i]; + int eqIdx = pair.indexOf("="); + if (eqIdx > 0) { + String key = pair.substring(0, eqIdx); + String value = pair.substring(eqIdx + 1); + encodedUrl.append(key).append("=").append(URLEncoder.encode(value, "UTF-8")); + } else { + encodedUrl.append(pair); + } + } + finalUrl = encodedUrl.toString(); + } + + log.info("Jike GET final url: {}", finalUrl); + + HttpURLConnection conn = (HttpURLConnection) new URL(finalUrl).openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(30000); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("x-jk-appid", appId); + conn.setRequestProperty("x-jk-timestamp", String.valueOf(timestamp)); + conn.setRequestProperty("x-jk-param-signature", paramMd5); + conn.setRequestProperty("x-jk-signature", finalSign); + + return readResponse(conn); + } + + /** + * GET请求 - 使用Map参数 + */ + public String doGet(String uri, Map params) throws Exception { + if (params == null || params.isEmpty()) { + return doGet(uri, (String) null); + } + // 先排序,再构建字符串 + TreeMap sorted = new TreeMap<>(); + for (Map.Entry entry : params.entrySet()) { + if (entry.getValue() == null || entry.getValue().isEmpty()) { + continue; + } + sorted.put(entry.getKey(), entry.getValue()); + } + + String endpoint = properties.getEndpoint(); + String url = endpoint + uri; + + long timestamp = System.currentTimeMillis(); + String appId = properties.getAppId(); + String secret = properties.getSecretKey(); + + // 构建URL(参数值需要URL编码) + StringBuilder urlSb = new StringBuilder(url).append("?"); + boolean first = true; + for (Map.Entry entry : sorted.entrySet()) { + if (!first) { + urlSb.append("&"); + } + urlSb.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8")); + first = false; + } + String finalUrl = urlSb.toString(); + + // 计算参数签名 - 使用原始值(不编码),按文档说明 + StringBuilder signSb = new StringBuilder(); + first = true; + for (Map.Entry entry : sorted.entrySet()) { + if (!first) { + signSb.append("&"); + } + signSb.append(entry.getKey()).append("=").append(entry.getValue()); + first = false; + } + String signQueryString = signSb.toString(); + + String paramMd5 = JikeSignUtil.buildParamSignature(signQueryString); + // 计算最终签名 - GET请求不包含body-signature + String finalSign = JikeSignUtil.buildGetFinalSignature(appId, paramMd5, secret, timestamp); + + log.info("Jike GET request queryString (for sign): {}", signQueryString); + log.info("Jike GET final url: {}", finalUrl); + log.info("Jike param-signature: {}", paramMd5); + log.info("Jike final-signature: {}", finalSign); + + HttpURLConnection conn = (HttpURLConnection) new URL(finalUrl).openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(30000); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("x-jk-appid", appId); + conn.setRequestProperty("x-jk-timestamp", String.valueOf(timestamp)); + conn.setRequestProperty("x-jk-param-signature", paramMd5); + conn.setRequestProperty("x-jk-signature", finalSign); + + return readResponse(conn); + } + + /** + * POST请求 + */ + public String doPost(String uri, Map params, String jsonBody) throws Exception { + String endpoint = properties.getEndpoint(); + String url = endpoint + uri; + + log.info("Jike POST request: {}, body: {}", url, jsonBody); + + long timestamp = System.currentTimeMillis(); + String appId = properties.getAppId(); + String secret = properties.getSecretKey(); + + // 计算body签名 + String bodyMd5 = JikeSignUtil.md5Hex(jsonBody); + // 计算参数签名(POST时body参与签名) + String paramMd5 = JikeSignUtil.buildPostParamSignature(params, bodyMd5); + // 计算最终签名 + String finalSign = JikeSignUtil.buildFinalSignature(appId, bodyMd5, paramMd5, secret, timestamp); + + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setConnectTimeout(10000); + conn.setReadTimeout(30000); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + conn.setRequestProperty("x-jk-appid", appId); + conn.setRequestProperty("x-jk-timestamp", String.valueOf(timestamp)); + conn.setRequestProperty("x-jk-param-signature", paramMd5); + conn.setRequestProperty("x-jk-body-signature", bodyMd5); + conn.setRequestProperty("x-jk-signature", finalSign); + + if (jsonBody != null && !jsonBody.isEmpty()) { + try (OutputStream os = conn.getOutputStream()) { + os.write(jsonBody.getBytes(StandardCharsets.UTF_8)); + } + } + + return readResponse(conn); + } + + private String buildUrl(String baseUrl, Map params) throws Exception { + if (params == null || params.isEmpty()) { + return baseUrl; + } + StringBuilder sb = new StringBuilder(baseUrl).append("?"); + boolean first = true; + for (Map.Entry entry : params.entrySet()) { + if (entry.getValue() == null || entry.getValue().isEmpty()) { + continue; + } + if (!first) { + sb.append("&"); + } + sb.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8")); + first = false; + } + return sb.toString(); + } + + private String readResponse(HttpURLConnection conn) throws Exception { + int code = conn.getResponseCode(); + InputStream is = (code >= 200 && code < 400) ? conn.getInputStream() : conn.getErrorStream(); + StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + sb.append(line).append("\n"); + } + } + return sb.toString(); + } +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeResponse.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeResponse.java new file mode 100644 index 0000000..9254c0c --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/JikeResponse.java @@ -0,0 +1,28 @@ +package com.njzscloud.supervisory.gps.jike; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 即刻物联通用响应 + */ +@Data +@Accessors(chain = true) +public class JikeResponse { + + private Header header; + private T data; + + @Data + public static class Header { + private Integer code; + private String message; + private String requestId; + private Long ts; + private String rts; + } + + public boolean isSuccess() { + return header != null && header.getCode() != null && header.getCode() == 0; + } +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/controller/JikeTestController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/controller/JikeTestController.java new file mode 100644 index 0000000..eaf6ae5 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/controller/JikeTestController.java @@ -0,0 +1,162 @@ +package com.njzscloud.supervisory.gps.jike.controller; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; +import com.njzscloud.common.core.utils.R; +import com.njzscloud.supervisory.config.JikeProperties; +import com.njzscloud.supervisory.gps.jike.JikeApiClient; +import com.njzscloud.supervisory.gps.jike.JikeResponse; +import com.njzscloud.supervisory.gps.jike.result.JikeMediaResult; +import com.njzscloud.supervisory.gps.jike.result.JikeTrackResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 即刻物联测试接口 + */ +@Slf4j +@RestController +@RequestMapping("/gps/jike") +public class JikeTestController { + + private final JikeProperties jikeProperties; + + public JikeTestController(JikeProperties jikeProperties) { + this.jikeProperties = jikeProperties; + } + + /** + * 测试接口连通性 + */ + @RequestMapping("/ping") + public R ping() { + try { + JikeApiClient client = new JikeApiClient(jikeProperties); + String response = client.doGet("/open-api/health/ping1", ""); + log.info("Jike ping response: {}", response); + return R.success(JSON.parseObject(response)); + } catch (Exception e) { + log.error("Jike ping error", e); + return R.failed(); + } + } + + /** + * 历史轨迹查询测试 + * /open-api/gps-trace/list + * + * @param carNum 车牌号 + * @param stime 开始时间(格式: yyyy-MM-dd HH:mm:ss) + * @param etime 结束时间(格式: yyyy-MM-dd HH:mm:ss) + */ + @RequestMapping("/track_list_test") + public R trackListTest( + @RequestParam(required = false) String carNum, + @RequestParam(required = false) String imei, + @RequestParam String stime, + @RequestParam String etime) { + try { + JikeApiClient client = new JikeApiClient(jikeProperties); + + Map params = new HashMap<>(); + if (carNum != null && !carNum.isEmpty()) { + params.put("carNum", carNum); + } + if (imei != null && !imei.isEmpty()) { + params.put("imei", imei); + } + // 转换为毫秒时间戳 + params.put("stime", String.valueOf(DateUtil.parse(stime).getTime())); + params.put("etime", String.valueOf(DateUtil.parse(etime).getTime())); + + log.info("Jike track list request params: {}", params); + + String response = client.doGet("/open-api/gps-trace/list", params); + log.info("Jike track list response: {}", response); + + JikeResponse> result = JSON.parseObject( + response, + new TypeReference>>() {} + ); + + if (result.isSuccess()) { + return R.success(result.getData()); + } else { + log.error("调用失败: " + result.getHeader().getMessage()); + return R.failed(); + } + } catch (Exception e) { + log.error("Jike track list error", e); + return R.failed(); + } + } + + /** + * 多媒体查询测试 + * /open-api/event/interval/media + * + * @param carNum 车牌号 + * @param stime 开始时间(格式: yyyy-MM-dd HH:mm:ss) + * @param etime 结束时间(格式: yyyy-MM-dd HH:mm:ss) + */ + @RequestMapping("/media_list_test") + public R mediaListTest( + @RequestParam(required = false) String carNum, + @RequestParam(required = false) String imei, + @RequestParam String stime, + @RequestParam String etime) { + try { + JikeApiClient client = new JikeApiClient(jikeProperties); + + Map params = new HashMap<>(); + if (carNum != null && !carNum.isEmpty()) { + params.put("carNum", carNum); + } + if (imei != null && !imei.isEmpty()) { + params.put("imei", imei); + } + // 转换为毫秒时间戳 + params.put("stime", String.valueOf(DateUtil.parse(stime).getTime())); + params.put("etime", String.valueOf(DateUtil.parse(etime).getTime())); + + // 构建参数字符串 + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry entry : params.entrySet()) { + if (!first) { + sb.append("&"); + } + sb.append(entry.getKey()).append("=").append(entry.getValue()); + first = false; + } + String queryString = sb.toString(); + + log.info("Jike media list queryString: {}", queryString); + + String response = client.doGet("/open-api/event/interval/media", queryString); + log.info("Jike media list response: {}", response); + + JikeResponse> result = JSON.parseObject( + response, + new TypeReference>>() {} + ); + + if (result.isSuccess()) { + return R.success(result.getData()); + } else { + log.error("调用失败: " + result.getHeader().getMessage()); + return R.failed(); + } + } catch (Exception e) { + log.error("Jike media list error", e); + return R.failed(); + } + } +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeEventResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeEventResult.java new file mode 100644 index 0000000..23edfdf --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeEventResult.java @@ -0,0 +1,36 @@ +package com.njzscloud.supervisory.gps.jike.result; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 即刻物联报警事件结果 + */ +@Data +@Accessors(chain = true) +public class JikeEventResult { + + private Long ukid; + private Long eventId; + private String orgRoot; + private String orgCode; + private Long truckId; + private String carNum; + private String imei; + private Integer eventType; + private String eventTypeText; + private Long eventTime; + private Long eventEndTime; + private Long orderId; + private Integer eventState; + private List mediaList; + private Integer speed; + private Double lon; + private Double lat; + private String address; + private Long transportId; + private String transportBusinessId; + private String extend; +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeMediaResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeMediaResult.java new file mode 100644 index 0000000..9da750b --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeMediaResult.java @@ -0,0 +1,20 @@ +package com.njzscloud.supervisory.gps.jike.result; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 即刻物联多媒体查询结果 + */ +@Data +@Accessors(chain = true) +public class JikeMediaResult { + + private Long id; + private Integer channel; + private Integer logicChannel; + private String media; + private Integer mediaType; + private String createTime; + private String updateTime; +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeTrackResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeTrackResult.java new file mode 100644 index 0000000..4da0cdb --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/result/JikeTrackResult.java @@ -0,0 +1,28 @@ +package com.njzscloud.supervisory.gps.jike.result; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 即刻物联历史轨迹结果 + */ +@Data +@Accessors(chain = true) +public class JikeTrackResult { + + private Long id; + private Long truckId; + private String imei; + private String carNum; + private String orgRoot; + private String orgCode; + private String gpsText; + private Integer speed; + private String address; + private Integer direction; + private Integer valid; + private String gpsTime; + private String createTime; + private String extend; +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeRequestInterceptor.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeRequestInterceptor.java new file mode 100644 index 0000000..f435926 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeRequestInterceptor.java @@ -0,0 +1,77 @@ +package com.njzscloud.supervisory.gps.jike.support; + +import cn.hutool.extra.spring.SpringUtil; +import com.njzscloud.common.http.constant.HttpMethod; +import com.njzscloud.common.http.interceptor.RequestInterceptor; +import com.njzscloud.supervisory.config.JikeProperties; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * 即刻物联请求拦截器 - 处理签名 + */ +public class JikeRequestInterceptor implements RequestInterceptor { + + @Override + public Object[] process(HttpMethod method, String url, Object[] args) { + JikeProperties jikeProperties = SpringUtil.getBean(JikeProperties.class); + String appId = jikeProperties.getAppId(); + String secret = jikeProperties.getSecretKey(); + + // 获取时间戳 + long timestamp = System.currentTimeMillis(); + + // 构建请求头 + Map headers = new HashMap<>(); + headers.put("x-jk-appid", appId); + headers.put("x-jk-timestamp", String.valueOf(timestamp)); + + // 处理参数签名 + String paramMd5 = ""; + String bodyMd5 = ""; + + if (args != null && args.length > 0) { + Object param = args[0]; + if (param instanceof Map) { + @SuppressWarnings("unchecked") + Map params = (Map) param; + + if (method == HttpMethod.GET) { + // GET请求: x-jk-param-signature = MD5(参数拼接) + paramMd5 = JikeSignUtil.buildParamSignature(params); + headers.put("x-jk-param-signature", paramMd5); + } else if (method == HttpMethod.POST) { + // POST请求: 需要body签名 + if (args.length > 1 && args[1] instanceof String) { + bodyMd5 = JikeSignUtil.md5Hex((String) args[1]); + } + headers.put("x-jk-body-signature", bodyMd5); + // POST带body时,body参与签名 + paramMd5 = JikeSignUtil.buildPostParamSignature(params, bodyMd5); + headers.put("x-jk-param-signature", paramMd5); + } + } + } else { + // 无参数时 + headers.put("x-jk-param-signature", ""); + if (method == HttpMethod.POST) { + headers.put("x-jk-body-signature", ""); + } + } + + // 计算最终签名 + String finalSign = JikeSignUtil.buildFinalSignature(appId, bodyMd5, paramMd5, secret, timestamp); + headers.put("x-jk-signature", finalSign); + + // 返回 headers 和原始参数 + Object[] result = new Object[args != null ? args.length + 1 : 2]; + result[0] = headers; + for (int i = 0; i < (args != null ? args.length : 0); i++) { + result[i + 1] = args[i]; + } + + return result; + } +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeSignUtil.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeSignUtil.java new file mode 100644 index 0000000..b4aeaad --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/gps/jike/support/JikeSignUtil.java @@ -0,0 +1,136 @@ +package com.njzscloud.supervisory.gps.jike.support; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.TreeMap; + +/** + * 即刻物联签名工具类 + */ +@Slf4j +public class JikeSignUtil { + + /** + * 计算GET请求签名 - 从Map参数 + * x-jk-param-signature = MD5(按参数名升序拼接的k=v) + * x-jk-signature = MD5(x-jk-appid={appId}&x-jk-param-signature={paramSign}&x-jk-secret={secret}&x-jk-timestamp={timestamp}) + */ + public static String buildParamSignature(Map params) { + if (params == null || params.isEmpty()) { + return ""; + } + TreeMap sorted = new TreeMap<>(); + for (Map.Entry entry : params.entrySet()) { + String v = entry.getValue(); + if (v == null || v.isEmpty()) { + continue; + } + sorted.put(entry.getKey(), v); + } + if (sorted.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry entry : sorted.entrySet()) { + if (!first) { + sb.append("&"); + } + sb.append(entry.getKey()).append("=").append(entry.getValue()); + first = false; + } + String result = sb.toString(); + log.debug("Jike param-signature origin: {}", result); + return DigestUtil.md5Hex(result, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 计算GET请求签名 - 从字符串 + * 直接对queryString签名,如 carNum=京A12345&stime=xxx&etime=xxx + */ + public static String buildParamSignature(String queryString) { + if (queryString == null || queryString.isEmpty()) { + return ""; + } + log.debug("Jike param-signature origin: {}", queryString); + return DigestUtil.md5Hex(queryString, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 计算POST请求的签名 + * x-jk-body-signature = MD5(body) + * x-jk-param-signature = MD5(按参数名升序拼接的k=v, 其中body=md5(body)作为参数参与) + * x-jk-signature = MD5(x-jk-appid={appId}&x-jk-body-signature={bodySign}&x-jk-param-signature={paramSign}&x-jk-secret={secret}&x-jk-timestamp={timestamp}) + */ + public static String buildPostParamSignature(Map params, String bodyMd5) { + TreeMap sorted = new TreeMap<>(); + if (params != null) { + for (Map.Entry entry : params.entrySet()) { + String v = entry.getValue(); + if (v == null || v.isEmpty()) { + continue; + } + sorted.put(entry.getKey(), v); + } + } + sorted.put("body", StrUtil.emptyToDefault(bodyMd5, "")); + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry entry : sorted.entrySet()) { + if (!first) { + sb.append("&"); + } + sb.append(entry.getKey()).append("=").append(entry.getValue()); + first = false; + } + String result = sb.toString(); + log.debug("Jike POST param-signature origin: {}", result); + return DigestUtil.md5Hex(result, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 计算body的MD5 + */ + public static String md5Hex(String content) { + if (content == null || content.isEmpty()) { + return ""; + } + return DigestUtil.md5Hex(content, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 计算GET请求的最终签名(不包含body-signature) + */ + public static String buildGetFinalSignature(String appId, String paramMd5, String secret, long timestamp) { + String finalContent = String.format( + "x-jk-appid=%s&x-jk-param-signature=%s&x-jk-secret=%s&x-jk-timestamp=%d", + appId, + StrUtil.emptyToDefault(paramMd5, ""), + secret, + timestamp + ); + log.debug("Jike GET final-signature origin: {}", finalContent); + return DigestUtil.md5Hex(finalContent, StandardCharsets.UTF_8); + } + + /** + * 计算POST请求的最终签名(包含body-signature) + */ + public static String buildFinalSignature(String appId, String bodyMd5, String paramMd5, String secret, long timestamp) { + String finalContent = String.format( + "x-jk-appid=%s&x-jk-body-signature=%s&x-jk-param-signature=%s&x-jk-secret=%s&x-jk-timestamp=%d", + appId, + StrUtil.emptyToDefault(bodyMd5, ""), + StrUtil.emptyToDefault(paramMd5, ""), + secret, + timestamp + ); + log.debug("Jike POST final-signature origin: {}", finalContent); + return DigestUtil.md5Hex(finalContent, StandardCharsets.UTF_8); + } +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/OrderPagingResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/OrderPagingResult.java index a1e732f..5e51d5d 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/OrderPagingResult.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/OrderPagingResult.java @@ -554,4 +554,15 @@ public class OrderPagingResult { * 区审批人角色名称 */ private String roleName; + + /** + * gps设备编码 + */ + private String gps; + + /** + * 设备分类 + */ + private String localizerCategory; + } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java index 98f6fa4..a53f057 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java @@ -37,6 +37,7 @@ import com.njzscloud.supervisory.biz.pojo.entity.*; import com.njzscloud.supervisory.biz.service.*; import com.njzscloud.supervisory.constant.Constant; import com.njzscloud.supervisory.device.pojo.entity.DeviceLocalizerEntity; +import com.njzscloud.supervisory.device.service.DeviceLocalizerService; import com.njzscloud.supervisory.discount.pojo.DiscountManageEntity; import com.njzscloud.supervisory.discount.service.DiscountManageService; import com.njzscloud.supervisory.expense.contant.*; @@ -124,6 +125,7 @@ public class OrderInfoService extends ServiceImpl list = deviceLocalizerService.list(Wrappers.lambdaQuery(DeviceLocalizerEntity.class). + eq(DeviceLocalizerEntity::getTerminalId, result.getGps()) + .eq(DeviceLocalizerEntity::getDeleted, Boolean.FALSE)); + if (null != list && !list.isEmpty()) { + result.setLocalizerCategory(list.get(0).getLocalizerCategory().getVal()); + } + } + return result; } diff --git a/njzscloud-svr/src/main/resources/application-dev.yml b/njzscloud-svr/src/main/resources/application-dev.yml index bb5c3bb..e5929f6 100644 --- a/njzscloud-svr/src/main/resources/application-dev.yml +++ b/njzscloud-svr/src/main/resources/application-dev.yml @@ -31,6 +31,7 @@ spring: - /wechatTemplateMessage/key - /hsoa/push_order - /operationManual/appShow + - /gps/** app: default-place: province: 340000 @@ -103,3 +104,9 @@ hsoa: base-url: http://60.173.195.121:9908 username: chuz_trajectory password: e9t2YsgM5ug%2FkpIZpMdY9e9uXq60jyEQ30zQX%2BBzphI%3D + +# 即刻物联 +app.jike: + appId: appid_wvsey5zyk2wqt0 + secretKey: s7xejsskalwqjyqptekgahrdcpkcfwa + sandbox: false diff --git a/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml b/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml index 6420ef2..041faed 100644 --- a/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml +++ b/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml @@ -100,6 +100,7 @@ e.tare_weight history_tare_weight, p.txt truck_category, e.picture truck_picture, + e.gps, f.user_id driver_user_id, f.driver_name, f.phone driver_phone,