Compare commits

...

2 Commits

Author SHA1 Message Date
lizhenhua b24275ecf0 围栏配置代码开发 2025-12-25 14:49:03 +08:00
lizhenhua 113ef841e6 围栏配置代码开发 2025-12-24 17:53:45 +08:00
16 changed files with 1185 additions and 491 deletions

View File

@ -0,0 +1,34 @@
package com.bonus.sgzb.base;
import com.bonus.sgzb.base.mapper.TerminalMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@Component
public class DeptDataScopeUtil {
@Autowired
private TerminalMapper terminalMapper;
/**
* 获取当前用户可访问的部门ID集合
*/
public List<Long> getDataScopeDeptIds(Long deptId, Long companyId) {
// 是否存在下级部门
int childCount = terminalMapper.countChildDept(deptId);
if (childCount == 0) {
// 叶子节点只能看自己
return Collections.singletonList(deptId);
}
// 非叶子节点看自己 + 所有下级
return terminalMapper.selectChildDeptIds(deptId);
}
}

View File

@ -0,0 +1,53 @@
package com.bonus.sgzb.base.controller;
import com.bonus.sgzb.base.service.ITerminalService;
import com.bonus.sgzb.common.core.web.controller.BaseController;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;
import java.util.Map;
/**
* 行政区划Controller
*/
@RestController
@RequestMapping("/jtt808/area")
public class AreaController extends BaseController {
@Autowired
private ITerminalService areaService;
/**
* 获取省份列表
*/
@GetMapping("/provinces")
public AjaxResult listProvinces() {
List<Map<String, Object>> provinceList = areaService.selectProvinceList();
return AjaxResult.success(provinceList);
}
/**
* 根据省份编码获取城市列表
*/
@GetMapping("/cities/{provinceCode}")
public AjaxResult listCities(@PathVariable Long provinceCode) {
List<Map<String, Object>> cityList = areaService.selectCityListByProvince(provinceCode);
return AjaxResult.success(cityList);
}
/**
* 根据城市编码获取区县列表
*/
@GetMapping("/counties/{cityCode}")
public AjaxResult listCounties(@PathVariable Long cityCode) {
List<Map<String, Object>> countyList = areaService.selectCountyListByCity(cityCode);
return AjaxResult.success(countyList);
}
}

View File

@ -0,0 +1,35 @@
package com.bonus.sgzb.base.controller;
import com.bonus.sgzb.base.domain.GeofenceConfigDTO;
import com.bonus.sgzb.base.service.ITerminalService;
import com.bonus.sgzb.common.core.web.controller.BaseController;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
import com.bonus.sgzb.common.log.annotation.Log;
import com.bonus.sgzb.common.log.enums.BusinessType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 电子围栏终端配置 Controller极简版
*/
@RestController
@RequestMapping("/jtt808/geofence/config")
public class Jtt808GeofenceTerminalConfigController extends BaseController {
@Autowired
private ITerminalService configService;
// 保存配置前端提交时调用
@Log(title = "电子围栏终端配置", businessType = BusinessType.UPDATE)
@PostMapping
public AjaxResult saveConfig(@RequestBody GeofenceConfigDTO dto) {
configService.saveGeofencePhones(dto.getGeofenceId(), dto.getTerminalIds());
return AjaxResult.success();
}
// 回显已配置终端前端打开弹窗时调用
@GetMapping("/{geofenceId}")
public AjaxResult getConfig(@PathVariable Long geofenceId) {
return AjaxResult.success(configService.selectPhonesByGeofenceId(geofenceId));
}
}

View File

@ -0,0 +1,148 @@
package com.bonus.sgzb.base.controller;
import com.bonus.sgzb.base.DeptDataScopeUtil;
import com.bonus.sgzb.base.domain.Terminal;
import com.bonus.sgzb.base.service.ITerminalService;
import com.bonus.sgzb.common.core.utils.StringUtils;
import com.bonus.sgzb.common.core.utils.poi.ExcelUtil;
import com.bonus.sgzb.common.core.web.controller.BaseController;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
import com.bonus.sgzb.common.core.web.page.TableDataInfo;
import com.bonus.sgzb.common.log.annotation.Log;
import com.bonus.sgzb.common.log.enums.BusinessType;
import com.bonus.sgzb.common.security.utils.SecurityUtils;
import com.bonus.sgzb.system.api.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import static com.bonus.sgzb.common.security.utils.SecurityUtils.getUsername;
/**
* 终端设备Controller完整版
*/
@RestController
@RequestMapping("/jtt808/terminal")
public class TerminalController extends BaseController {
@Autowired
private ITerminalService terminalService;
@Autowired
private DeptDataScopeUtil deptDataScopeUtil;
/**
* 查询终端设备列表
*/
@GetMapping("/list")
public TableDataInfo list(Terminal terminal) {
SysUser user = SecurityUtils.getLoginUser().getSysUser() ;
Long deptId = user.getDeptId();
Long companyId = user.getCompanyId();
List<Long> deptIds = deptDataScopeUtil.getDataScopeDeptIds(deptId, companyId);
terminal.setDeptIds(deptIds);
startPage();
List<Terminal> list = terminalService.selectTerminalList(terminal);
return getDataTable(list);
}
/**
* 导出终端设备列表
*/
@Log(title = "终端设备", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, Terminal terminal) {
List<Terminal> list = terminalService.selectTerminalList(terminal);
ExcelUtil<Terminal> util = new ExcelUtil<>(Terminal.class);
util.exportExcel(response, list, "终端设备数据");
}
/**
* 获取终端设备详细信息
*/
@GetMapping(value = "/getinfo/{terminalId}")
public AjaxResult getInfo(@PathVariable("terminalId") Long terminalId) {
return AjaxResult.success(terminalService.selectTerminalById(terminalId));
}
/**
* 新增终端设备
*/
@Log(title = "终端设备", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody Terminal terminal) {
// 校验手机号唯一性
if (!terminalService.checkPhoneNumberUnique(terminal).equals("0")) {
return AjaxResult.error("新增终端设备'" + terminal.getPhoneNumber() + "'失败,手机号已存在");
}
// 校验IMEI唯一性
if (StringUtils.isNotEmpty(terminal.getImei()) && !terminalService.checkImeiUnique(terminal).equals("0")) {
return AjaxResult.error("新增终端设备'" + terminal.getPhoneNumber() + "'失败IMEI号已存在");
}
terminal.setCreateBy(getUsername());
return toAjax(terminalService.insertTerminal(terminal));
}
/**
* 修改终端设备
*/
@Log(title = "终端设备", businessType = BusinessType.UPDATE)
@PostMapping("/updateinfo")
public AjaxResult edit(@Validated @RequestBody Terminal terminal) {
// 校验手机号唯一性
if (!terminalService.checkPhoneNumberUnique(terminal).equals("0")) {
return AjaxResult.error("修改终端设备'" + terminal.getPhoneNumber() + "'失败,手机号已存在");
}
// 校验IMEI唯一性
if (StringUtils.isNotEmpty(terminal.getImei()) && !terminalService.checkImeiUnique(terminal).equals("0")) {
return AjaxResult.error("修改终端设备'" + terminal.getPhoneNumber() + "'失败IMEI号已存在");
}
terminal.setUpdateBy(getUsername());
return toAjax(terminalService.updateTerminal(terminal));
}
/**
* 删除终端设备
*/
@Log(title = "终端设备", businessType = BusinessType.DELETE)
@DeleteMapping("/{terminalIds}")
public AjaxResult remove(@PathVariable Long[] terminalIds) {
return toAjax(terminalService.deleteTerminalByIds(terminalIds));
}
/**
* 获取终端统计信息
*/
@GetMapping("/statistics")
public AjaxResult getStatistics() {
SysUser user = SecurityUtils.getLoginUser().getSysUser() ;
Long deptId = user.getDeptId();
Long companyId = user.getCompanyId();
List<Long> deptIds = deptDataScopeUtil.getDataScopeDeptIds(deptId, companyId);
return AjaxResult.success(terminalService.getTerminalStatistics(deptIds));
}
/**
* 更新在线状态供JTT808协议处理调用
*/
@PostMapping("/updateOnlineStatus")
public AjaxResult updateOnlineStatus(@RequestParam Long terminalId,
@RequestParam Integer onlineStatus,
@RequestParam(required = false) String clientIp) {
return toAjax(terminalService.updateOnlineStatus(terminalId, onlineStatus, clientIp));
}
/**
* 更新心跳时间供JTT808协议处理调用
*/
@PostMapping("/updateHeartbeatTime")
public AjaxResult updateHeartbeatTime(@RequestParam Long terminalId) {
return toAjax(terminalService.updateHeartbeatTime(terminalId));
}
}

View File

@ -0,0 +1,12 @@
package com.bonus.sgzb.base.domain;
import lombok.Data;
import java.util.List;
@Data
public class GeofenceConfigDTO {
private Long geofenceId;
private List<String> terminalIds;
private List<String> phoneNumbers;
}

View File

@ -0,0 +1,14 @@
package com.bonus.sgzb.base.domain;
import lombok.Data;
@Data
public class Jtt808GeofenceTerminalConfig {
private static final long serialVersionUID = 1L;
private Long id;
private Long geofenceId;
private String phoneNumber;
private String terminalId;
}

View File

@ -0,0 +1,98 @@
package com.bonus.sgzb.base.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class Terminal {
private static final long serialVersionUID = 1L;
/** 终端ID */
private Long terminalId;
/** 终端手机号(BCD编码) */
private String phoneNumber;
/** 设备IMEI号(15位唯一标识) */
private String imei;
/** 省域ID */
private String provinceId;
/** 市县域ID */
private String cityId;
/** 制造商ID */
private String manufacturerId;
/** 终端型号 */
private String terminalModel;
/** 终端设备ID */
private String terminalDeviceId;
/** 车牌颜色(0-其他,1-蓝色,2-黄色,3-黑色,4-白色,9-新能源) */
private Integer plateColor;
/** 车牌号 */
private String plateNumber;
/** 鉴权码 */
private String authCode;
/** 开始注册时间(查询条件) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date beginRegisterTime;
/** 结束注册时间(查询条件) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endRegisterTime;
/** 注册时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date registerTime;
/** 最后鉴权时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lastAuthTime;
/** 在线状态(0-离线,1-在线) */
private Integer onlineStatus;
/** 最后心跳时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lastHeartbeatTime;
/** 客户端IP地址 */
private String clientIp;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/** 省名称(非数据库字段) */
private String provinceName;
/** 市名称(非数据库字段) */
private String cityName;
/** 车牌颜色名称(非数据库字段) */
private String plateColorName;
/** 请求参数 */
private String params;
private String createBy;
private String updateBy;
private Long deptId;
private List<Long> deptIds;
}

View File

@ -0,0 +1,95 @@
package com.bonus.sgzb.base.mapper;
import com.bonus.sgzb.base.domain.Jtt808GeofenceTerminalConfig;
import com.bonus.sgzb.base.domain.Terminal;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface TerminalMapper {
/**
* 查询终端设备
*/
Terminal selectTerminalById(Long terminalId);
/**
* 查询终端设备列表
*/
List<Terminal> selectTerminalList(Terminal terminal);
/**
* 新增终端设备
*/
int insertTerminal(Terminal terminal);
/**
* 修改终端设备
*/
int updateTerminal(Terminal terminal);
/**
* 删除终端设备
*/
int deleteTerminalById(Long terminalId);
/**
* 批量删除终端设备
*/
int deleteTerminalByIds(Long[] terminalIds);
/**
* 根据手机号查询终端
*/
Terminal selectTerminalByPhoneNumber(String phoneNumber);
/**
* 根据IMEI查询终端
*/
Terminal selectTerminalByImei(String imei);
/**
* 获取终端统计信息
*/
Map<String, Object> getTerminalStatistics( @Param("deptIds") List<Long> deptIds);
/**
* 更新在线状态
*/
int updateOnlineStatus(@Param("terminalId") Long terminalId,
@Param("onlineStatus") Integer onlineStatus,
@Param("clientIp") String clientIp);
/**
* 更新心跳时间
*/
int updateHeartbeatTime(@Param("terminalId") Long terminalId);
/**
* 查询省份列表
*/
List<Map<String, Object>> selectProvinceList();
/**
* 根据省份编码查询城市列表
*/
List<Map<String, Object>> selectCityListByProvince(@Param("provinceCode") Long provinceCode);
/**
* 根据城市编码查询区县列表
*/
List<Map<String, Object>> selectCountyListByCity(@Param("cityCode") Long cityCode);
List<Jtt808GeofenceTerminalConfig> selectByGeofenceId(Long geofenceId);
int deleteByGeofenceId(Long geofenceId);
int batchInsert(List<Jtt808GeofenceTerminalConfig> list);
int countChildDept(Long deptId);
List<Long> selectChildDeptIds(Long deptId);
}

View File

@ -0,0 +1,94 @@
package com.bonus.sgzb.base.service;
import com.bonus.sgzb.base.domain.Terminal;
import java.util.List;
import java.util.Map;
public interface ITerminalService {
/**
* 查询终端设备
*/
Terminal selectTerminalById(Long terminalId);
/**
* 查询终端设备列表
*/
List<Terminal> selectTerminalList(Terminal terminal);
/**
* 新增终端设备
*/
int insertTerminal(Terminal terminal);
/**
* 修改终端设备
*/
int updateTerminal(Terminal terminal);
/**
* 批量删除终端设备
*/
int deleteTerminalByIds(Long[] terminalIds);
/**
* 删除终端设备信息
*/
int deleteTerminalById(Long terminalId);
/**
* 校验手机号是否唯一
*/
String checkPhoneNumberUnique(Terminal terminal);
/**
* 校验IMEI是否唯一
*/
String checkImeiUnique(Terminal terminal);
/**
* 获取终端统计信息
*/
Map<String, Object> getTerminalStatistics( List<Long> deptIds);
/**
* 更新在线状态
*/
int updateOnlineStatus(Long terminalId, Integer onlineStatus, String clientIp);
/**
* 更新心跳时间
*/
int updateHeartbeatTime(Long terminalId);
/**
* 根据手机号查询终端
*/
Terminal selectTerminalByPhoneNumber(String phoneNumber);
/**
* 查询省份列表
*/
List<Map<String, Object>> selectProvinceList();
/**
* 根据省份编码查询城市列表
*/
List<Map<String, Object>> selectCityListByProvince(Long provinceCode);
/**
* 根据城市编码查询区县列表
*/
List<Map<String, Object>> selectCountyListByCity(Long cityCode);
/**
* 保存围栏绑定的终端手机号先删后插
*/
int saveGeofencePhones(Long geofenceId, List<String> terminalIds);
/**
* 查询围栏已绑定的终端手机号用于回显
*/
List<String> selectPhonesByGeofenceId(Long geofenceId);
}

View File

@ -0,0 +1,199 @@
package com.bonus.sgzb.base.service.impl;
import com.bonus.sgzb.base.domain.Jtt808GeofenceTerminalConfig;
import com.bonus.sgzb.base.domain.Terminal;
import com.bonus.sgzb.base.mapper.TerminalMapper;
import com.bonus.sgzb.base.service.ITerminalService;
import com.bonus.sgzb.common.core.utils.DateUtils;
import com.bonus.sgzb.common.core.utils.StringUtils;
import com.bonus.sgzb.common.security.utils.SecurityUtils;
import com.bonus.sgzb.system.api.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 终端设备Service业务层处理
*/
@Service
public class TerminalServiceImpl implements ITerminalService {
@Autowired
private TerminalMapper terminalMapper;
/**
* 查询终端设备
*/
@Override
public Terminal selectTerminalById(Long terminalId) {
return terminalMapper.selectTerminalById(terminalId);
}
/**
* 查询终端设备列表
*/
@Override
public List<Terminal> selectTerminalList(Terminal terminal) {
return terminalMapper.selectTerminalList(terminal);
}
/**
* 新增终端设备
*/
@Override
public int insertTerminal(Terminal terminal) {
//获取当前登录人员的部门id
Long deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
terminal.setDeptId(deptId);
terminal.setCreateTime(DateUtils.getNowDate());
terminal.setUpdateTime(DateUtils.getNowDate());
if (terminal.getRegisterTime() == null) {
terminal.setRegisterTime(DateUtils.getNowDate());
}
// 生成默认鉴权码
if (StringUtils.isEmpty(terminal.getAuthCode())) {
terminal.setAuthCode(generateAuthCode());
}
return terminalMapper.insertTerminal(terminal);
}
/**
* 修改终端设备
*/
@Override
public int updateTerminal(Terminal terminal) {
terminal.setUpdateTime(DateUtils.getNowDate());
return terminalMapper.updateTerminal(terminal);
}
/**
* 批量删除终端设备
*/
@Override
@Transactional
public int deleteTerminalByIds(Long[] terminalIds) {
return terminalMapper.deleteTerminalByIds(terminalIds);
}
/**
* 删除终端设备信息
*/
@Override
public int deleteTerminalById(Long terminalId) {
return terminalMapper.deleteTerminalById(terminalId);
}
/**
* 校验手机号是否唯一
*/
@Override
public String checkPhoneNumberUnique(Terminal terminal) {
Long terminalId = StringUtils.isNull(terminal.getTerminalId()) ? -1L : terminal.getTerminalId();
Terminal info = terminalMapper.selectTerminalByPhoneNumber(terminal.getPhoneNumber());
if (StringUtils.isNotNull(info) && info.getTerminalId().longValue() != terminalId.longValue()) {
return "1";
}
return "0";
}
/**
* 校验IMEI是否唯一
*/
@Override
public String checkImeiUnique(Terminal terminal) {
Long terminalId = StringUtils.isNull(terminal.getTerminalId()) ? -1L : terminal.getTerminalId();
Terminal info = terminalMapper.selectTerminalByImei(terminal.getImei());
if (StringUtils.isNotNull(info) && info.getTerminalId().longValue() != terminalId.longValue()) {
return "1";
}
return "0";
}
/**
* 获取终端统计信息
*/
@Override
public Map<String, Object> getTerminalStatistics( List<Long> deptIds) {
return terminalMapper.getTerminalStatistics(deptIds);
}
/**
* 更新在线状态
*/
@Override
public int updateOnlineStatus(Long terminalId, Integer onlineStatus, String clientIp) {
return terminalMapper.updateOnlineStatus(terminalId, onlineStatus, clientIp);
}
/**
* 更新心跳时间
*/
@Override
public int updateHeartbeatTime(Long terminalId) {
return terminalMapper.updateHeartbeatTime(terminalId);
}
/**
* 根据手机号查询终端
*/
@Override
public Terminal selectTerminalByPhoneNumber(String phoneNumber) {
return terminalMapper.selectTerminalByPhoneNumber(phoneNumber);
}
/**
* 生成鉴权码
*/
private String generateAuthCode() {
return String.valueOf((int)((Math.random() * 9 + 1) * 100000));
}
@Override
public List<Map<String, Object>> selectProvinceList() {
return terminalMapper.selectProvinceList();
}
@Override
public List<Map<String, Object>> selectCityListByProvince(Long provinceCode) {
return terminalMapper.selectCityListByProvince(provinceCode);
}
@Override
public List<Map<String, Object>> selectCountyListByCity(Long cityCode) {
return terminalMapper.selectCountyListByCity(cityCode);
}
@Override
public int saveGeofencePhones(Long geofenceId, List<String> terminalIds) {
// 先删旧的
terminalMapper.deleteByGeofenceId(geofenceId);
if (terminalIds == null || terminalIds.isEmpty()) {
return 0;
}
String username = SecurityUtils.getUsername();
List<Jtt808GeofenceTerminalConfig> list = new ArrayList<>();
for (String phone : terminalIds) {
Jtt808GeofenceTerminalConfig cfg = new Jtt808GeofenceTerminalConfig();
cfg.setGeofenceId(geofenceId);
cfg.setTerminalId( phone);
list.add(cfg);
}
return terminalMapper.batchInsert(list);
}
@Override
public List<String> selectPhonesByGeofenceId(Long geofenceId) {
List<Jtt808GeofenceTerminalConfig> list = terminalMapper.selectByGeofenceId(geofenceId);
List<String> phones = new ArrayList<>();
for (Jtt808GeofenceTerminalConfig c : list) {
phones.add(c.getTerminalId());
}
return phones;
}
}

View File

@ -1,11 +1,14 @@
package com.bonus.sgzb.material.controller;
import com.bonus.sgzb.base.DeptDataScopeUtil;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
import com.bonus.sgzb.common.security.utils.SecurityUtils;
import com.bonus.sgzb.material.domain.Jtt808Location;
import com.bonus.sgzb.material.domain.Jtt808MessageLog;
import com.bonus.sgzb.material.domain.Jtt808Terminal;
import com.bonus.sgzb.material.service.IJtt808DataService;
import com.bonus.sgzb.material.service.IJtt808CommandService;
import com.bonus.sgzb.system.api.domain.SysUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@ -21,7 +24,7 @@ import java.util.Arrays;
/**
* JTT808数据查询Controller
*
*
* @author system
* @date 2025-01-15
*/
@ -36,12 +39,20 @@ public class Jtt808DataController {
@Autowired
private IJtt808CommandService jtt808CommandService;
@Autowired
private DeptDataScopeUtil deptDataScopeUtil;
/**
* 查询终端列表
*/
@ApiOperation("查询终端列表")
@GetMapping("/terminals")
public AjaxResult getTerminals(Jtt808Terminal terminal) {
SysUser user = SecurityUtils.getLoginUser().getSysUser() ;
Long deptId = user.getDeptId();
Long companyId = user.getCompanyId();
List<Long> deptIds = deptDataScopeUtil.getDataScopeDeptIds(deptId, companyId);
terminal.setDeptIds(deptIds);
List<Jtt808Terminal> list = jtt808DataService.getTerminalList(terminal);
return AjaxResult.success(list);
}
@ -278,4 +289,4 @@ public class Jtt808DataController {
throw e;
}
}
}
}

View File

@ -15,7 +15,7 @@ import java.util.Arrays;
/**
* JTT808协议测试控制器
* 用于测试和调试JTT808消息处理功能
*
*
* @author system
*/
@Api(tags = "JTT808协议测试")
@ -41,43 +41,6 @@ public class TestJTT808Controller {
}
}
@ApiOperation("测试去转义逻辑")
@GetMapping("/test-unescape")
public String testUnescape() {
try {
// 测试完全转义格式的数据来自实际日志
String hexData = "7D 02 01 00 00 2C 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 00 00 00 01 00 01 54 45 53 54 31 54 45 53 54 5F 4D 4F 44 45 4C 00 00 00 00 00 00 00 00 00 00 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 0C 7D 02";
String[] hexBytes = hexData.split(" ");
byte[] testData = new byte[hexBytes.length];
for (int i = 0; i < hexBytes.length; i++) {
testData[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("========== 去转义逻辑测试 ==========");
logger.info("原始数据长度: {} bytes", testData.length);
logger.info("原始数据: {}", hexData);
// 分析期望结果
logger.info("期望消息ID: 0x0100 (终端注册)");
logger.info("期望手机号: 868120322495257");
// 调用处理方法观察详细过程
byte[] response = messageProcessor.processMessage(testData, "UNESCAPE_TEST");
if (response != null) {
logger.info("测试成功:生成应答消息");
return "去转义测试完成,生成了应答消息,详情请查看日志";
} else {
logger.warn("测试结果:未生成应答消息");
return "去转义测试完成,但未生成应答消息,请查看日志分析";
}
} catch (Exception e) {
logger.error("去转义测试异常: {}", e.getMessage(), e);
return "去转义测试失败: " + e.getMessage();
}
}
@ApiOperation("手动分析70字节数据")
@GetMapping("/analyze-data")
@ -85,26 +48,26 @@ public class TestJTT808Controller {
try {
// 原始70字节数据
String hexData = "7D 02 01 00 00 2C 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 00 00 00 01 00 01 54 45 53 54 31 54 45 53 54 5F 4D 4F 44 45 4C 00 00 00 00 00 00 00 00 00 00 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 0C 7D 02";
String[] hexBytes = hexData.split(" ");
byte[] data = new byte[hexBytes.length];
for (int i = 0; i < hexBytes.length; i++) {
data[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("========== 手动数据分析 ==========");
logger.info("原始数据: {}", hexData);
logger.info("数据长度: {} bytes", data.length);
// 分析起始和结束
logger.info("起始2字节: 0x{} 0x{} -> 这是转义的0x7E",
String.format("%02X", data[0] & 0xFF),
logger.info("起始2字节: 0x{} 0x{} -> 这是转义的0x7E",
String.format("%02X", data[0] & 0xFF),
String.format("%02X", data[1] & 0xFF));
logger.info("结束2字节: 0x{} 0x{} -> 这也是转义的0x7E",
String.format("%02X", data[data.length-2] & 0xFF),
logger.info("结束2字节: 0x{} 0x{} -> 这也是转义的0x7E",
String.format("%02X", data[data.length-2] & 0xFF),
String.format("%02X", data[data.length-1] & 0xFF));
// 手动去转义跳过首尾的 7D 02
logger.info("去转义后的消息内容 (跳过首尾7D02):");
StringBuilder content = new StringBuilder();
@ -112,26 +75,26 @@ public class TestJTT808Controller {
content.append(String.format("%02X ", data[i] & 0xFF));
}
logger.info(" {}", content.toString().trim());
// 分析消息头
logger.info("消息结构分析:");
logger.info(" 消息ID: 0x{}{} = 0x{}",
logger.info(" 消息ID: 0x{}{} = 0x{}",
String.format("%02X", data[2] & 0xFF),
String.format("%02X", data[3] & 0xFF),
String.format("%04X", ((data[2] & 0xFF) << 8) | (data[3] & 0xFF)));
logger.info(" 消息体属性: 0x{}{} = {}",
logger.info(" 消息体属性: 0x{}{} = {}",
String.format("%02X", data[4] & 0xFF),
String.format("%02X", data[5] & 0xFF),
((data[4] & 0xFF) << 8) | (data[5] & 0xFF));
// 分析手机号 (BCD码位置6-11)
StringBuilder phoneHex = new StringBuilder();
for (int i = 6; i < 12; i++) {
phoneHex.append(String.format("%02X ", data[i] & 0xFF));
}
logger.info(" 手机号BCD(前6字节): {}", phoneHex.toString().trim());
// BCD解码前6字节
StringBuilder phone = new StringBuilder();
for (int i = 6; i < 12; i++) {
@ -141,23 +104,23 @@ public class TestJTT808Controller {
if (low <= 9) phone.append(low);
}
logger.info(" 解析手机号(前6字节): {}", phone.toString());
// 分析流水号 (位置12-13)
int serialNumber = ((data[12] & 0xFF) << 8) | (data[13] & 0xFF);
logger.info(" 流水号: {}", serialNumber);
// 分析消息体长度
int bodyLength = ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
bodyLength = bodyLength & 0x03FF; // 取低10位
logger.info(" 消息体长度: {} bytes", bodyLength);
// 检查消息体中是否有手机号的后续部分
// 从消息体内容看后面还有: 32 34 39 35 32 35 37
// 这部分可能是手机号的后续BCD编码
if (bodyLength > 0 && data.length > 14) {
StringBuilder fullPhoneHex = new StringBuilder();
StringBuilder fullPhone = new StringBuilder();
// 前6字节 (标准位置)
for (int i = 6; i < 12; i++) {
fullPhoneHex.append(String.format("%02X ", data[i] & 0xFF));
@ -166,17 +129,17 @@ public class TestJTT808Controller {
if (high <= 9) fullPhone.append(high);
if (low <= 9) fullPhone.append(low);
}
// 检查消息体开始位置的手机号后续部分 (位置14开始的几个字节)
logger.info(" 消息体开始的BCD数据: {} {} {} {} {} {} {}",
logger.info(" 消息体开始的BCD数据: {} {} {} {} {} {} {}",
String.format("%02X", data[14] & 0xFF),
String.format("%02X", data[15] & 0xFF),
String.format("%02X", data[15] & 0xFF),
String.format("%02X", data[16] & 0xFF),
String.format("%02X", data[17] & 0xFF),
String.format("%02X", data[18] & 0xFF),
String.format("%02X", data[19] & 0xFF),
String.format("%02X", data[20] & 0xFF));
// 尝试解析消息体开始的3.5字节作为手机号后续部分
for (int i = 14; i <= 17; i++) { // 解析3.5字节
fullPhoneHex.append(String.format("%02X ", data[i] & 0xFF));
@ -185,32 +148,32 @@ public class TestJTT808Controller {
if (high <= 9) fullPhone.append(high);
if (low <= 9) fullPhone.append(low);
}
logger.info(" 完整手机号BCD: {}", fullPhoneHex.toString().trim());
logger.info(" 完整解析手机号: {}", fullPhone.toString());
if (fullPhone.toString().equals("868120322495257")) {
logger.info(" ✅ 手机号解析正确!");
} else {
logger.warn(" ❌ 手机号解析不匹配期望值");
}
}
if (bodyLength > 0) {
logger.info(" 消息体开始位置: 14");
logger.info(" 消息体结束位置: {}", 14 + bodyLength - 1);
StringBuilder bodyHex = new StringBuilder();
for (int i = 14; i < Math.min(14 + bodyLength, data.length - 2); i++) {
bodyHex.append(String.format("%02X ", data[i] & 0xFF));
}
logger.info(" 消息体内容: {}", bodyHex.toString().trim());
}
logger.info("========== 分析完成 ==========");
return "数据分析完成详情请查看日志。关键发现这是0x0100终端注册消息手机号是" + phone.toString();
} catch (Exception e) {
logger.error("数据分析异常: {}", e.getMessage(), e);
return "数据分析失败: " + e.getMessage();
@ -218,11 +181,11 @@ public class TestJTT808Controller {
}
@ApiOperation("详细分析消息体数据结构")
@GetMapping("/test-body-analysis")
@GetMapping("/test-body-analysis")
public String testBodyAnalysis() {
try {
logger.info("========== 消息体数据结构分析 ==========");
// 消息体数据
String bodyHex = "32 34 39 35 32 35 37 00 00 00 01 00 01 54 45 53 54 31 54 45 53 54 5F 4D 4F 44 45 4C 00 00 00 00 00 00 00 00 00 00 38 36 38 31 32 30";
String[] hexBytes = bodyHex.split(" ");
@ -230,31 +193,31 @@ public class TestJTT808Controller {
for (int i = 0; i < hexBytes.length; i++) {
bodyData[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("消息体总长度: {} bytes", bodyData.length);
logger.info("完整数据: {}", bodyHex);
// 根据JTT808终端注册消息体结构分析
// 0-1: 省域ID
// 2-3: 市县域ID
// 2-3: 市县域ID
// 4-8: 制造商ID (5字节)
// 9-16: 终端型号 (8字节)
// 17-23: 终端ID (7字节)
// 24: 车牌颜色
// 25开始: 车牌号
logger.info("=== 按JTT808标准解析 ===");
// 省域ID (2字节)
int provinceId = ((bodyData[0] & 0xFF) << 8) | (bodyData[1] & 0xFF);
logger.info("省域ID (0-1): 0x{} = {}",
String.format("%02X%02X", bodyData[0], bodyData[1]), provinceId);
// 市县域ID (2字节)
logger.info("省域ID (0-1): 0x{} = {}",
String.format("%02X%02X", bodyData[0], bodyData[1]), provinceId);
// 市县域ID (2字节)
int cityId = ((bodyData[2] & 0xFF) << 8) | (bodyData[3] & 0xFF);
logger.info("市县域ID (2-3): 0x{} = {}",
String.format("%02X%02X", bodyData[2], bodyData[3]), cityId);
logger.info("市县域ID (2-3): 0x{} = {}",
String.format("%02X%02X", bodyData[2], bodyData[3]), cityId);
// 制造商ID (5字节)
StringBuilder manufacturerId = new StringBuilder();
for (int i = 4; i < 9; i++) {
@ -263,7 +226,7 @@ public class TestJTT808Controller {
}
}
logger.info("制造商ID (4-8): {}", manufacturerId.toString());
// 终端型号 (8字节)
StringBuilder terminalModel = new StringBuilder();
for (int i = 9; i < 17; i++) {
@ -272,7 +235,7 @@ public class TestJTT808Controller {
}
}
logger.info("终端型号 (9-16): {}", terminalModel.toString());
// 终端ID (7字节)
StringBuilder terminalId = new StringBuilder();
for (int i = 17; i < 24; i++) {
@ -281,11 +244,11 @@ public class TestJTT808Controller {
}
}
logger.info("终端ID (17-23): {}", terminalId.toString());
// 车牌颜色 (1字节)
int plateColor = bodyData[24] & 0xFF;
logger.info("车牌颜色 (24): {}", plateColor);
// 车牌号 (剩余字节)
StringBuilder plateNumber = new StringBuilder();
for (int i = 25; i < bodyData.length; i++) {
@ -298,7 +261,7 @@ public class TestJTT808Controller {
}
}
logger.info("车牌号内容 (25+): {}", plateNumber.toString());
// 分析末尾的数据作为可能的手机号
logger.info("=== 末尾数据分析 ===");
StringBuilder endData = new StringBuilder();
@ -306,367 +269,20 @@ public class TestJTT808Controller {
endData.append(String.format("%02X ", bodyData[i] & 0xFF));
}
logger.info("末尾6字节: {}", endData.toString().trim());
// BCD解码末尾6字节
StringBuilder endBCD = new StringBuilder();
for (int i = bodyData.length - 6; i < bodyData.length; i++) {
int high = (bodyData[i] >> 4) & 0x0F;
int low = bodyData[i] & 0x0F;
if (high <= 9) endBCD.append(high);
if (low <= 9) endBCD.append(low);
}
logger.info("末尾6字节BCD解码: {}", endBCD.toString());
return "数据结构分析完成,详情请查看日志";
} catch (Exception e) {
logger.error("数据结构分析异常: {}", e.getMessage(), e);
return "分析异常: " + e.getMessage();
}
}
}}
@ApiOperation("测试消息格式验证")
@GetMapping("/test-validation")
public String testValidation() {
try {
// 测试标准格式
byte[] standardData = {0x7E, 0x01, 0x00, 0x00, 0x2C, 0x38, 0x36, 0x38, 0x31, 0x32, 0x30, 0x33, 0x32, 0x32, 0x34, 0x39, 0x35, 0x32, 0x35, 0x37, 0x00, 0x00, 0x00, 0x01, 0x7E};
// 测试转义格式
byte[] escapedData = {0x7D, 0x02, 0x01, 0x00, 0x00, 0x2C, 0x38, 0x36, 0x38, 0x31, 0x32, 0x30, 0x33, 0x32, 0x32, 0x34, 0x39, 0x35, 0x32, 0x35, 0x37, 0x00, 0x00, 0x00, 0x01, 0x7D, 0x02};
logger.info("测试标准格式数据...");
byte[] response1 = messageProcessor.processMessage(standardData, "TEST_STANDARD");
logger.info("测试转义格式数据...");
byte[] response2 = messageProcessor.processMessage(escapedData, "TEST_ESCAPED");
return String.format("验证测试完成 - 标准格式: %s, 转义格式: %s",
response1 != null ? "成功" : "失败",
response2 != null ? "成功" : "失败");
} catch (Exception e) {
logger.error("验证测试中发生异常: {}", e.getMessage(), e);
return "验证测试失败: " + e.getMessage();
}
}
@ApiOperation("测试完整注册流程")
@GetMapping("/test-full-register")
public String testFullRegister() {
try {
// 使用实际的70字节数据测试完整流程
String hexData = "7D 02 01 00 00 2C 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 00 00 00 01 00 01 54 45 53 54 31 54 45 53 54 5F 4D 4F 44 45 4C 00 00 00 00 00 00 00 00 00 00 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 0C 7D 02";
String[] hexBytes = hexData.split(" ");
byte[] testData = new byte[hexBytes.length];
for (int i = 0; i < hexBytes.length; i++) {
testData[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("========== 完整注册流程测试 ==========");
logger.info("测试目标:");
logger.info("1. 正确解析手机号: 868120322495257");
logger.info("2. 生成正确的注册应答");
logger.info("3. 应答中包含匹配的手机号");
// 处理注册消息
byte[] response = messageProcessor.processMessage(testData, "FULL_REGISTER_TEST");
if (response != null) {
logger.info("✅ 成功生成注册应答");
logger.info("应答长度: {} bytes", response.length);
// 转换应答数据为十六进制字符串
StringBuilder responseHex = new StringBuilder();
for (byte b : response) {
responseHex.append(String.format("%02X ", b & 0xFF));
}
logger.info("应答数据: {}", responseHex.toString().trim());
// 解析应答中的手机号 (6字节BCD, 位置5-10)
StringBuilder responsePhone = new StringBuilder();
for (int i = 5; i < 11 && i < response.length; i++) {
int high = (response[i] >> 4) & 0x0F;
int low = response[i] & 0x0F;
// BCD编码每个半字节表示一个数字(0-9)
if (high <= 9) responsePhone.append(high);
if (low <= 9) responsePhone.append(low);
}
logger.info("应答中的手机号: {}", responsePhone.toString());
// 验证手机号匹配
String expectedPhone = "8681202495257";
String actualPhone = responsePhone.toString();
if (actualPhone.equals(expectedPhone) || actualPhone.equals(expectedPhone.substring(0, 12))) {
logger.info("✅ 手机号匹配成功!");
return "注册流程测试成功 - 手机号: " + actualPhone;
} else {
logger.warn("⚠️ 手机号不匹配: 期望={}, 实际={}", expectedPhone, actualPhone);
return "注册流程测试 - 手机号不完全匹配,但核心逻辑正常";
}
} else {
logger.error("❌ 未能生成注册应答");
return "注册流程测试失败,未生成应答";
}
} catch (Exception e) {
logger.error("完整注册流程测试异常: {}", e.getMessage(), e);
return "注册流程测试异常: " + e.getMessage();
}
}
@ApiOperation("测试手机号提取逻辑")
@GetMapping("/test-phone-extract")
public String testPhoneExtract() {
try {
// 使用实际的70字节数据测试手机号提取
String hexData = "7D 02 01 00 00 2C 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 00 00 00 01 00 01 54 45 53 54 31 54 45 53 54 5F 4D 4F 44 45 4C 00 00 00 00 00 00 00 00 00 00 38 36 38 31 32 30 33 32 32 34 39 35 32 35 37 0C 7D 02";
String[] hexBytes = hexData.split(" ");
byte[] testData = new byte[hexBytes.length];
for (int i = 0; i < hexBytes.length; i++) {
testData[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("========== 手机号提取逻辑测试 ==========");
logger.info("原始数据长度: {} bytes", testData.length);
// 手动去转义模拟处理过程
byte[] unescapedData = new byte[testData.length - 4]; // 去掉首尾的7D 02
System.arraycopy(testData, 2, unescapedData, 0, testData.length - 4);
logger.info("去转义后数据长度: {} bytes", unescapedData.length);
// 手动解析消息头
int messageId = ((unescapedData[0] & 0xFF) << 8) | (unescapedData[1] & 0xFF);
int bodyLength = ((unescapedData[2] & 0xFF) << 8) | (unescapedData[3] & 0xFF);
bodyLength = bodyLength & 0x03FF;
logger.info("消息ID: 0x{}", String.format("%04X", messageId));
logger.info("消息体长度: {} bytes", bodyLength);
logger.info("MSG_TERMINAL_REGISTER = 0x{}", String.format("%04X", 0x0100));
logger.info("条件检查: messageId == MSG_TERMINAL_REGISTER? {}", messageId == 0x0100);
logger.info("条件检查: bodyLength > 20? {}", bodyLength > 20);
if (messageId == 0x0100 && bodyLength > 20) {
logger.info("满足提取条件,开始提取消息体...");
int bodyStart = 12;
int bodyEnd = bodyStart + bodyLength;
logger.info("消息体范围: {}~{}", bodyStart, bodyEnd);
if (bodyEnd <= unescapedData.length) {
byte[] bodyData = Arrays.copyOfRange(unescapedData, bodyStart, bodyEnd);
logger.info("消息体数据长度: {} bytes", bodyData.length);
// 转换为十六进制查看
StringBuilder bodyHex = new StringBuilder();
for (byte b : bodyData) {
bodyHex.append(String.format("%02X ", b & 0xFF));
}
logger.info("消息体数据: {}", bodyHex.toString().trim());
// 检查是否包含目标手机号
String targetPattern = "86 81 20 32 24 95 25 7";
boolean contains = bodyHex.toString().contains(targetPattern);
logger.info("是否包含目标手机号模式 '{}': {}", targetPattern, contains);
return "手机号提取测试完成,满足条件: " + contains + ",详情请查看日志";
} else {
logger.warn("消息体范围越界");
return "手机号提取测试失败:消息体范围越界";
}
} else {
logger.warn("不满足提取条件");
return "手机号提取测试失败:不满足提取条件";
}
} catch (Exception e) {
logger.error("手机号提取测试异常: {}", e.getMessage(), e);
return "手机号提取测试异常: " + e.getMessage();
}
}
@ApiOperation("测试修复后的手机号提取")
@GetMapping("/test-phone-extract-fixed")
public String testPhoneExtractFixed() {
try {
logger.info("========== 修复后手机号提取测试 ==========");
// 模拟消息体数据
String bodyHex = "32 34 39 35 32 35 37 00 00 00 01 00 01 54 45 53 54 31 54 45 53 54 5F 4D 4F 44 45 4C 00 00 00 00 00 00 00 00 00 00 38 36 38 31 32 30";
String[] hexBytes = bodyHex.split(" ");
byte[] bodyData = new byte[hexBytes.length];
for (int i = 0; i < hexBytes.length; i++) {
bodyData[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("消息体数据: {}", bodyHex);
// 测试直接模式匹配
boolean hasFirstPattern = bodyHex.contains("32 34 39 35 32 35 37");
boolean hasSecondPattern = bodyHex.contains("38 36 38 31 32 30");
logger.info("包含第一部分 '32 34 39 35 32 35 37': {}", hasFirstPattern);
logger.info("包含第二部分 '38 36 38 31 32 30': {}", hasSecondPattern);
if (hasFirstPattern && hasSecondPattern) {
logger.info("✅ 找到分段手机号模式,重组手机号: 8681202495257");
// 验证BCD解析
// 第一部分32 34 39 35 32 35 37
StringBuilder firstPart = new StringBuilder();
for (int i = 0; i < 7; i++) {
byte b = bodyData[i];
int high = (b >> 4) & 0x0F;
int low = b & 0x0F;
if (high <= 9) firstPart.append(high);
if (low <= 9) firstPart.append(low);
}
// 最后部分38 36 38 31 32 30
StringBuilder lastPart = new StringBuilder();
int endStart = bodyData.length - 6;
for (int i = endStart; i < bodyData.length; i++) {
byte b = bodyData[i];
int high = (b >> 4) & 0x0F;
int low = b & 0x0F;
if (high <= 9) lastPart.append(high);
if (low <= 9) lastPart.append(low);
}
logger.info("BCD解析 - 第一部分: {}", firstPart.toString());
logger.info("BCD解析 - 最后部分: {}", lastPart.toString());
String reconstructedPhone = lastPart.toString() + firstPart.toString().substring(0, 7);
logger.info("重组手机号: {}", reconstructedPhone);
return "修复后手机号提取成功: " + reconstructedPhone;
} else {
logger.warn("❌ 未找到预期的手机号模式");
return "修复后手机号提取失败:未找到预期模式";
}
} catch (Exception e) {
logger.error("修复后手机号提取测试异常: {}", e.getMessage(), e);
return "测试异常: " + e.getMessage();
}
}
@ApiOperation("测试厂家参考数据格式")
@GetMapping("/test-vendor-data")
public String testVendorData() {
try {
logger.info("========== 厂家参考数据测试 ==========");
// 厂家提供的原始数据
String vendorData = "7E 01 00 00 34 02 03 22 49 52 57 02 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 34 39 35 32 35 37 00 00 00 00 00 00 00 00 00 00 7B 7E";
logger.info("厂家原始数据: {}", vendorData);
logger.info("期望手机号: 020322495257");
logger.info("期望终端ID: 2495257");
// 转换为字节数组
String[] hexBytes = vendorData.split(" ");
byte[] data = new byte[hexBytes.length];
for (int i = 0; i < hexBytes.length; i++) {
data[i] = (byte) Integer.parseInt(hexBytes[i], 16);
}
logger.info("数据长度: {} bytes", data.length);
// 测试JTT808MessageProcessor的处理
byte[] result = messageProcessor.processMessage(data, "VENDOR_DATA_TEST");
logger.info("处理结果: {}", result);
return "厂家数据测试完成,详情请查看日志";
} catch (Exception e) {
logger.error("厂家数据测试异常: {}", e.getMessage(), e);
return "厂家数据测试异常: " + e.getMessage();
}
}
@ApiOperation("测试手机号标准化功能")
@GetMapping("/test-phone-normalization")
public String testPhoneNormalization() {
try {
logger.info("========== 手机号标准化测试 ==========");
// 测试用例
String[] testPhones = {
"8681202495257", // 13位格式
"020322495257", // 12位标准格式
"02032249525700", // 14位格式
"322495257", // 9位格式
"1234567890", // 其他10位格式
"", // 空字符串
null, // null值
"abc123def456" // 包含非数字字符
};
String[] expectedResults = {
"020322495257", // 13位 8681202495257 -> 12位标准格式
"020322495257", // 保持12位格式
"020322495257", // 14位包含495257模式 -> 标准格式
"020322495257", // 9位 322495257 -> 12位标准格式
"020322567890", // 10位 -> 12位标准格式 (取后6位)
"", // 空字符串保持
null, // null保持
"123456" // 清理后保持原样 (长度不足6位)
};
logger.info("开始测试手机号标准化...");
for (int i = 0; i < testPhones.length; i++) {
String input = testPhones[i];
String expected = expectedResults[i];
try {
// 使用反射调用私有方法进行测试
java.lang.reflect.Method method = messageProcessor.getClass()
.getDeclaredMethod("normalizePhoneNumber", String.class);
method.setAccessible(true);
String result = (String) method.invoke(messageProcessor, input);
boolean isCorrect = (result == null && expected == null) ||
(result != null && result.equals(expected));
logger.info("测试 {}: 输入='{}', 期望='{}', 实际='{}', 结果={}",
i + 1, input, expected, result, isCorrect ? "✅通过" : "❌失败");
} catch (Exception e) {
logger.error("测试 {} 异常: 输入='{}' - {}", i + 1, input, e.getMessage());
}
}
// 测试终端查找功能
logger.info("\n========== 终端查找测试 ==========");
String[] searchPhones = {"8681202495257", "020322495257"};
for (String phone : searchPhones) {
try {
java.lang.reflect.Method method = messageProcessor.getClass()
.getDeclaredMethod("findTerminalByPhone", String.class);
method.setAccessible(true);
Object terminal = method.invoke(messageProcessor, phone);
logger.info("查找终端: 手机号='{}', 结果={}",
phone, terminal != null ? "找到" : "未找到");
} catch (Exception e) {
logger.error("查找终端异常: 手机号='{}' - {}", phone, e.getMessage());
}
}
return "手机号标准化测试完成,详情请查看日志";
} catch (Exception e) {
logger.error("手机号标准化测试异常: {}", e.getMessage(), e);
return "测试异常: " + e.getMessage();
}
}
}

View File

@ -6,10 +6,11 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* JTT808终端设备实体类
*
*
* @author system
* @date 2025-01-15
*/
@ -27,10 +28,10 @@ public class Jtt808Terminal {
private String imei;
@ApiModelProperty("省域ID")
private Integer provinceId;
private String provinceId;
@ApiModelProperty("市县域ID")
private Integer cityId;
private String cityId;
@ApiModelProperty("制造商ID")
private String manufacturerId;
@ -90,6 +91,7 @@ public class Jtt808Terminal {
default: return "未知";
}
}
private List<Long> deptIds;
/**
* 获取在线状态名称
@ -97,4 +99,4 @@ public class Jtt808Terminal {
public String getOnlineStatusName() {
return onlineStatus != null && onlineStatus == 1 ? "在线" : "离线";
}
}
}

View File

@ -27,7 +27,7 @@ import java.util.stream.Collectors;
/**
* JTT808数据服务实现类
*
*
* @author system
* @date 2025-01-15
*/
@ -51,21 +51,21 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
@Override
@Transactional
public boolean registerTerminal(String phoneNumber, String clientIp,
Integer provinceId, Integer cityId,
String manufacturerId, String terminalModel,
String terminalDeviceId, Integer plateColor,
public boolean registerTerminal(String phoneNumber, String clientIp,
Integer provinceId, Integer cityId,
String manufacturerId, String terminalModel,
String terminalDeviceId, Integer plateColor,
String plateNumber) {
return registerTerminalWithImei(phoneNumber, null, clientIp, provinceId, cityId,
return registerTerminalWithImei(phoneNumber, null, clientIp, provinceId, cityId,
manufacturerId, terminalModel, terminalDeviceId, plateColor, plateNumber);
}
@Override
@Transactional
public boolean registerTerminalWithImei(String phoneNumber, String imei, String clientIp,
Integer provinceId, Integer cityId,
String manufacturerId, String terminalModel,
String terminalDeviceId, Integer plateColor,
public boolean registerTerminalWithImei(String phoneNumber, String imei, String clientIp,
Integer provinceId, Integer cityId,
String manufacturerId, String terminalModel,
String terminalDeviceId, Integer plateColor,
String plateNumber) {
try {
// 检查终端是否已存在
@ -75,8 +75,8 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
if (imei != null && !imei.isEmpty()) {
existingTerminal.setImei(imei);
}
existingTerminal.setProvinceId(provinceId);
existingTerminal.setCityId(cityId);
existingTerminal.setProvinceId(provinceId+"");
existingTerminal.setCityId(cityId+"");
existingTerminal.setManufacturerId(manufacturerId);
existingTerminal.setTerminalModel(terminalModel);
existingTerminal.setTerminalDeviceId(terminalDeviceId);
@ -84,7 +84,7 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
existingTerminal.setPlateNumber(plateNumber);
existingTerminal.setOnlineStatus(1);
existingTerminal.setClientIp(clientIp);
terminalMapper.updateTerminal(existingTerminal);
logger.info("更新终端注册信息: {} (IMEI: {})", phoneNumber, imei);
return true;
@ -95,8 +95,8 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
if (imei != null && !imei.isEmpty()) {
terminal.setImei(imei);
}
terminal.setProvinceId(provinceId);
terminal.setCityId(cityId);
terminal.setProvinceId(provinceId+"");
terminal.setCityId(cityId+"");
terminal.setManufacturerId(manufacturerId);
terminal.setTerminalModel(terminalModel);
terminal.setTerminalDeviceId(terminalDeviceId);
@ -104,11 +104,11 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
terminal.setPlateNumber(plateNumber);
terminal.setOnlineStatus(1);
terminal.setClientIp(clientIp);
// 生成鉴权码
String authCode = "AUTH_" + phoneNumber + "_" + System.currentTimeMillis();
terminal.setAuthCode(authCode);
int result = terminalMapper.insertTerminal(terminal);
logger.info("新增终端注册: {} (IMEI: {}) -> {}", phoneNumber, imei, result > 0 ? "成功" : "失败");
return result > 0;
@ -188,8 +188,8 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
try {
Jtt808Location location = convertToJtt808Location(phoneNumber, locationInfo, clientIp);
locationMapper.insertLocation(location);
logger.debug("保存位置信息: {} -> 纬度={:.6f}, 经度={:.6f}, 速度={:.1f}km/h",
logger.debug("保存位置信息: {} -> 纬度={:.6f}, 经度={:.6f}, 速度={:.1f}km/h",
phoneNumber, location.getLatitude(), location.getLongitude(), location.getSpeed());
} catch (Exception e) {
logger.error("保存位置信息异常: {} - {}", phoneNumber, e.getMessage(), e);
@ -204,7 +204,7 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
for (LocationInfo locationInfo : locationInfos) {
locations.add(convertToJtt808Location(phoneNumber, locationInfo, clientIp));
}
for (Jtt808Location location : locations) {
locationMapper.insertLocation(location);
}
@ -246,8 +246,8 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
}
@Override
public List<Jtt808Location> getLocationsByArea(Double minLat, Double maxLat,
Double minLng, Double maxLng,
public List<Jtt808Location> getLocationsByArea(Double minLat, Double maxLat,
Double minLng, Double maxLng,
Date startTime, Date endTime) {
try {
return locationMapper.selectLocationsByArea(null, minLat, maxLat, minLng, maxLng, startTime, endTime);
@ -276,8 +276,8 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
@Override
@Transactional
public void logMessage(String phoneNumber, String messageId, String messageType,
Integer serialNumber, Integer messageDirection,
String messageContent, Integer processResult,
Integer serialNumber, Integer messageDirection,
String messageContent, Integer processResult,
String errorMessage, String clientIp) {
try {
Jtt808MessageLog messageLog = new Jtt808MessageLog();
@ -290,9 +290,9 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
messageLog.setProcessResult(processResult);
messageLog.setErrorMessage(errorMessage);
messageLog.setClientIp(clientIp);
messageLogMapper.insertMessageLog(messageLog);
logger.debug("记录消息日志: {} -> {} [{}]", phoneNumber, messageType,
logger.debug("记录消息日志: {} -> {} [{}]", phoneNumber, messageType,
processResult == 0 ? "成功" : "失败");
} catch (Exception e) {
logger.error("记录消息日志异常: {} - {}", phoneNumber, e.getMessage());
@ -348,7 +348,7 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
java.time.LocalDate today = java.time.LocalDate.now();
String dateStr = today.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
List<Jtt808Location> locations = locationMapper.selectLocationsByDate(phoneNumber, dateStr);
// 简单计算里程 (实际应该使用MileageStatisticsService)
double totalDistance = 0.0;
for (int i = 1; i < locations.size(); i++) {
@ -378,7 +378,7 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
java.time.LocalDate today = java.time.LocalDate.now();
String dateStr = today.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
Map<String, Object> speedStats = locationMapper.selectSpeedStatsByDate(phoneNumber, dateStr);
if (speedStats != null && speedStats.get("max_speed") != null) {
return ((Number) speedStats.get("max_speed")).doubleValue();
}
@ -594,4 +594,4 @@ public class Jtt808DataServiceImpl implements IJtt808DataService {
return isInChina;
}
}
}

View File

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bonus.sgzb.base.mapper.TerminalMapper">
<resultMap type="com.bonus.sgzb.base.domain.Terminal" id="TerminalResult">
<id property="terminalId" column="terminal_id"/>
<result property="phoneNumber" column="phone_number"/>
<result property="imei" column="imei"/>
<result property="provinceId" column="province_id"/>
<result property="cityId" column="city_id"/>
<result property="manufacturerId" column="manufacturer_id"/>
<result property="terminalModel" column="terminal_model"/>
<result property="terminalDeviceId" column="terminal_device_id"/>
<result property="plateColor" column="plate_color"/>
<result property="plateNumber" column="plate_number"/>
<result property="authCode" column="auth_code"/>
<result property="registerTime" column="register_time"/>
<result property="lastAuthTime" column="last_auth_time"/>
<result property="onlineStatus" column="online_status"/>
<result property="lastHeartbeatTime" column="last_heartbeat_time"/>
<result property="clientIp" column="client_ip"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="provinceName" column="province_name"/>
<result property="cityName" column="city_name"/>
</resultMap>
<sql id="selectTerminalVo">
select t.*,
p.name as province_name,
c.name as city_name
from jtt808_terminal t
left join sys_cnarea p on t.province_id = p.area_code and p.level = 1
left join sys_cnarea c on t.city_id = c.area_code and c.level = 2
</sql>
<select id="selectTerminalById" parameterType="Long" resultMap="TerminalResult">
<include refid="selectTerminalVo"/>
where t.terminal_id = #{terminalId}
</select>
<select id="selectTerminalList" parameterType="com.bonus.sgzb.base.domain.Terminal" resultMap="TerminalResult">
<include refid="selectTerminalVo"/>
<where>
<if test="phoneNumber != null and phoneNumber != ''">
AND t.phone_number like concat('%', #{phoneNumber}, '%')
</if>
<if test="imei != null and imei != ''">
AND t.imei like concat('%', #{imei}, '%')
</if>
<if test="plateNumber != null and plateNumber != ''">
AND t.plate_number like concat('%', #{plateNumber}, '%')
</if>
<if test="terminalModel != null and terminalModel != ''">
AND t.terminal_model like concat('%', #{terminalModel}, '%')
</if>
<if test="manufacturerId != null and manufacturerId != ''">
AND t.manufacturer_id like concat('%', #{manufacturerId}, '%')
</if>
<if test="onlineStatus != null">
AND t.online_status = #{onlineStatus}
</if>
<if test="provinceId != null">
AND t.province_id = #{provinceId}
</if>
<if test="cityId != null">
AND t.city_id = #{cityId}
</if>
<if test="beginRegisterTime != null and beginRegisterTime != ''">
AND t.register_time &gt;= #{beginRegisterTime}
</if>
<if test="endRegisterTime != null and endRegisterTime != ''">
AND t.register_time &lt;= #{endRegisterTime}
</if>
<if test="deptIds != null and deptIds.size() > 0">
AND t.dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
order by t.update_time desc
</select>
<insert id="insertTerminal" parameterType="com.bonus.sgzb.base.domain.Terminal" useGeneratedKeys="true" keyProperty="terminalId">
insert into jtt808_terminal
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="phoneNumber != null and phoneNumber != ''">phone_number,</if>
<if test="imei != null and imei != ''">imei,</if>
<if test="provinceId != null">province_id,</if>
<if test="cityId != null">city_id,</if>
<if test="manufacturerId != null and manufacturerId != ''">manufacturer_id,</if>
<if test="terminalModel != null and terminalModel != ''">terminal_model,</if>
<if test="terminalDeviceId != null and terminalDeviceId != ''">terminal_device_id,</if>
<if test="plateColor != null">plate_color,</if>
<if test="plateNumber != null and plateNumber != ''">plate_number,</if>
<if test="authCode != null and authCode != ''">auth_code,</if>
<if test="registerTime != null">register_time,</if>
<if test="lastAuthTime != null">last_auth_time,</if>
<if test="onlineStatus != null">online_status,</if>
<if test="lastHeartbeatTime != null">last_heartbeat_time,</if>
<if test="clientIp != null and clientIp != ''">client_ip,</if>
<if test="deptId != null and deptId != ''">dept_id,</if>
create_time,
update_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="phoneNumber != null and phoneNumber != ''">#{phoneNumber},</if>
<if test="imei != null and imei != ''">#{imei},</if>
<if test="provinceId != null">#{provinceId},</if>
<if test="cityId != null">#{cityId},</if>
<if test="manufacturerId != null and manufacturerId != ''">#{manufacturerId},</if>
<if test="terminalModel != null and terminalModel != ''">#{terminalModel},</if>
<if test="terminalDeviceId != null and terminalDeviceId != ''">#{terminalDeviceId},</if>
<if test="plateColor != null">#{plateColor},</if>
<if test="plateNumber != null and plateNumber != ''">#{plateNumber},</if>
<if test="authCode != null and authCode != ''">#{authCode},</if>
<if test="registerTime != null">#{registerTime},</if>
<if test="lastAuthTime != null">#{lastAuthTime},</if>
<if test="onlineStatus != null">#{onlineStatus},</if>
<if test="lastHeartbeatTime != null">#{lastHeartbeatTime},</if>
<if test="clientIp != null and clientIp != ''">#{clientIp},</if>
<if test="deptId != null and deptId != ''">#{deptId},</if>
sysdate(),
sysdate()
</trim>
</insert>
<update id="updateTerminal" parameterType="com.bonus.sgzb.base.domain.Terminal">
update jtt808_terminal
<set>
<if test="phoneNumber != null and phoneNumber != ''">phone_number = #{phoneNumber},</if>
<if test="imei != null and imei != ''">imei = #{imei},</if>
<if test="provinceId != null">province_id = #{provinceId},</if>
<if test="cityId != null">city_id = #{cityId},</if>
<if test="manufacturerId != null and manufacturerId != ''">manufacturer_id = #{manufacturerId},</if>
<if test="terminalModel != null and terminalModel != ''">terminal_model = #{terminalModel},</if>
<if test="terminalDeviceId != null and terminalDeviceId != ''">terminal_device_id = #{terminalDeviceId},</if>
<if test="plateColor != null">plate_color = #{plateColor},</if>
<if test="plateNumber != null and plateNumber != ''">plate_number = #{plateNumber},</if>
<if test="authCode != null and authCode != ''">auth_code = #{authCode},</if>
<if test="registerTime != null">register_time = #{registerTime},</if>
<if test="lastAuthTime != null">last_auth_time = #{lastAuthTime},</if>
<if test="onlineStatus != null">online_status = #{onlineStatus},</if>
<if test="lastHeartbeatTime != null">last_heartbeat_time = #{lastHeartbeatTime},</if>
<if test="clientIp != null and clientIp != ''">client_ip = #{clientIp},</if>
update_time = sysdate()
</set>
where terminal_id = #{terminalId}
</update>
<delete id="deleteTerminalById" parameterType="Long">
delete from jtt808_terminal where terminal_id = #{terminalId}
</delete>
<delete id="deleteTerminalByIds" parameterType="Long">
delete from jtt808_terminal where terminal_id in
<foreach item="terminalId" collection="array" open="(" separator="," close=")">
#{terminalId}
</foreach>
</delete>
<select id="selectTerminalByPhoneNumber" parameterType="String" resultMap="TerminalResult">
<include refid="selectTerminalVo"/>
where t.phone_number = #{phoneNumber}
</select>
<select id="selectTerminalByImei" parameterType="String" resultMap="TerminalResult">
<include refid="selectTerminalVo"/>
where t.imei = #{imei}
</select>
<select id="getTerminalStatistics" resultType="java.util.HashMap">
select
count(*) as total,
sum(case when online_status = 1 then 1 else 0 end) as onlineCount,
sum(case when online_status = 0 then 1 else 0 end) as offlineCount
from jtt808_terminal
<where>
1=1
<if test="deptIds != null and deptIds.size() > 0">
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
<update id="updateOnlineStatus">
update jtt808_terminal
set online_status = #{onlineStatus},
client_ip = #{clientIp},
update_time = sysdate()
where terminal_id = #{terminalId}
</update>
<update id="updateHeartbeatTime">
update jtt808_terminal
set last_heartbeat_time = sysdate(),
online_status = 1,
update_time = sysdate()
where terminal_id = #{terminalId}
</update>
<!-- 查询省份列表 -->
<select id="selectProvinceList" resultType="java.util.HashMap">
select area_code, name
from sys_cnarea
where level = 1 and parent_code = 0
order by area_code
</select>
<!-- 根据省份编码查询城市列表 -->
<select id="selectCityListByProvince" parameterType="Long" resultType="java.util.HashMap">
select area_code, name
from sys_cnarea
where level = 2 and parent_code = #{provinceCode}
order by area_code
</select>
<!-- 根据城市编码查询区县列表 -->
<select id="selectCountyListByCity" parameterType="Long" resultType="java.util.HashMap">
select area_code, name
from sys_cnarea
where level = 2 and parent_code = #{cityCode}
order by area_code
</select>
<resultMap id="rateBaseResultMap" type="com.bonus.sgzb.base.domain.Jtt808GeofenceTerminalConfig">
<id property="id" column="id"/>
<result property="geofenceId" column="geofence_id"/>
<result property="phoneNumber" column="phone_number"/>
</resultMap>
<select id="selectByGeofenceId" resultMap="rateBaseResultMap">
select * from jtt808_geofence_terminal_config
where geofence_id = #{geofenceId}
</select>
<delete id="deleteByGeofenceId">
delete from jtt808_geofence_terminal_config
where geofence_id = #{geofenceId}
</delete>
<insert id="batchInsert">
insert into jtt808_geofence_terminal_config
(geofence_id, terminal_id)
values
<foreach collection="list" item="item" separator=",">
(#{item.geofenceId}, #{item.terminalId})
</foreach>
</insert>
<select id="selectChildDeptIds" resultType="java.lang.Long">
SELECT dept_id
FROM sys_dept
WHERE del_flag = '0'
AND (
dept_id = #{0}
OR FIND_IN_SET(#{0}, ancestors)
)
</select>
<select id="countChildDept" resultType="int">
SELECT COUNT(1)
FROM sys_dept
WHERE del_flag = '0'
AND parent_id = #{deptId}
</select>
</mapper>

View File

@ -26,8 +26,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectJtt808TerminalVo">
select terminal_id, phone_number, imei, province_id, city_id, manufacturer_id, terminal_model,
terminal_device_id, plate_color, plate_number, auth_code, register_time,
select terminal_id, phone_number, imei, province_id, city_id, manufacturer_id, terminal_model,
terminal_device_id, plate_color, plate_number, auth_code, register_time,
last_auth_time, online_status, last_heartbeat_time, client_ip, create_time, update_time
from jtt808_terminal
</sql>
@ -45,24 +45,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectTerminalList" parameterType="com.bonus.sgzb.material.domain.Jtt808Terminal" resultMap="Jtt808TerminalResult">
<include refid="selectJtt808TerminalVo"/>
<where>
<if test="phoneNumber != null and phoneNumber != ''">
<if test="phoneNumber != null and phoneNumber != ''">
and phone_number like concat('%', #{phoneNumber}, '%')
</if>
<if test="plateNumber != null and plateNumber != ''">
<if test="plateNumber != null and plateNumber != ''">
and plate_number like concat('%', #{plateNumber}, '%')
</if>
<if test="onlineStatus != null">
<if test="onlineStatus != null">
and online_status = #{onlineStatus}
</if>
<if test="plateColor != null">
<if test="plateColor != null">
and plate_color = #{plateColor}
</if>
<if test="provinceId != null">
<if test="provinceId != null">
and province_id = #{provinceId}
</if>
<if test="cityId != null">
<if test="cityId != null">
and city_id = #{cityId}
</if>
<if test="deptIds != null and deptIds.size() > 0">
AND dept_id IN
<foreach collection="deptIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
order by register_time desc
</select>
@ -123,14 +129,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<delete id="deleteTerminalByIds" parameterType="String">
delete from jtt808_terminal where terminal_id in
delete from jtt808_terminal where terminal_id in
<foreach item="terminalId" collection="terminalIds" open="(" separator="," close=")">
#{terminalId}
</foreach>
</delete>
<update id="updateOnlineStatus">
update jtt808_terminal
update jtt808_terminal
set online_status = #{onlineStatus},
client_ip = #{clientIp},
update_time = now()
@ -138,7 +144,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<update id="updateLastHeartbeatTime">
update jtt808_terminal
update jtt808_terminal
set last_heartbeat_time = #{heartbeatTime},
online_status = 1,
update_time = now()
@ -146,7 +152,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<update id="updateAuthInfo">
update jtt808_terminal
update jtt808_terminal
set auth_code = #{authCode},
last_auth_time = #{authTime},
update_time = now()
@ -154,10 +160,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<update id="batchUpdateOfflineStatus">
update jtt808_terminal
set online_status = 0,
update jtt808_terminal
set online_status = 0,
update_time = now()
where online_status = 1
where online_status = 1
and last_heartbeat_time &lt; DATE_SUB(now(), INTERVAL #{timeoutMinutes} MINUTE)
</update>
@ -166,8 +172,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="countTodayRegistered" resultType="int">
select count(*) from jtt808_terminal
select count(*) from jtt808_terminal
where DATE(register_time) = CURDATE()
</select>
</mapper>
</mapper>