即刻物联
parent
522233db35
commit
e5fbfb5395
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<String, String> 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<List<JikeTrackResult>> result = com.alibaba.fastjson2.JSON.parseObject(
|
||||
response,
|
||||
new com.alibaba.fastjson2.TypeReference<JikeResponse<List<JikeTrackResult>>>() {
|
||||
}
|
||||
);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
List<TrackPoint> 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<TrackPoint> convertToTrackPoints(List<JikeTrackResult> 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<String, String> 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<List<JikeEventResult>> result = JSON.parseObject(
|
||||
response,
|
||||
new TypeReference<JikeResponse<List<JikeEventResult>>>() {}
|
||||
);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
List<MediaItem> 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<MediaItem> extractMediaList(List<JikeEventResult> events) {
|
||||
if (events == null || events.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<MediaItem> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String> params) throws Exception {
|
||||
if (params == null || params.isEmpty()) {
|
||||
return doGet(uri, (String) null);
|
||||
}
|
||||
// 先排序,再构建字符串
|
||||
TreeMap<String, String> sorted = new TreeMap<>();
|
||||
for (Map.Entry<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> params) throws Exception {
|
||||
if (params == null || params.isEmpty()) {
|
||||
return baseUrl;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(baseUrl).append("?");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.njzscloud.supervisory.gps.jike;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 即刻物联通用响应
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class JikeResponse<T> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> 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<List<JikeTrackResult>> result = JSON.parseObject(
|
||||
response,
|
||||
new TypeReference<JikeResponse<List<JikeTrackResult>>>() {}
|
||||
);
|
||||
|
||||
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<String, String> 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<String, String> 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<List<JikeMediaResult>> result = JSON.parseObject(
|
||||
response,
|
||||
new TypeReference<JikeResponse<List<JikeMediaResult>>>() {}
|
||||
);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<JikeMediaResult> mediaList;
|
||||
private Integer speed;
|
||||
private Double lon;
|
||||
private Double lat;
|
||||
private String address;
|
||||
private Long transportId;
|
||||
private String transportBusinessId;
|
||||
private String extend;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<String, String> 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<String, String> params = (Map<String, String>) 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> params) {
|
||||
if (params == null || params.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
TreeMap<String, String> sorted = new TreeMap<>();
|
||||
for (Map.Entry<String, String> 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<String, String> 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<String, String> params, String bodyMd5) {
|
||||
TreeMap<String, String> sorted = new TreeMap<>();
|
||||
if (params != null) {
|
||||
for (Map.Entry<String, String> 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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -554,4 +554,15 @@ public class OrderPagingResult {
|
|||
* 区审批人角色名称
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* gps设备编码
|
||||
*/
|
||||
private String gps;
|
||||
|
||||
/**
|
||||
* 设备分类
|
||||
*/
|
||||
private String localizerCategory;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OrderInfoMapper, OrderInfoEnti
|
|||
private final HsoaService hsoaService;
|
||||
private final DictItemService dictItemService;
|
||||
private final AtomicBoolean test_thread_running = new AtomicBoolean(false);
|
||||
private final DeviceLocalizerService deviceLocalizerService;
|
||||
Thread test_thread = null;
|
||||
@Value("${app.check-gps:false}")
|
||||
private boolean checkGps;
|
||||
|
|
@ -1907,6 +1909,15 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
|||
result.setRoleName(roleEntity.getRoleName());
|
||||
}
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(result.getGps())) {
|
||||
List<DeviceLocalizerEntity> 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue