目录
[1. 技术栈](#1. 技术栈)
[2. 核心数据库表(简化版)](#2. 核心数据库表(简化版))
[3. 统一返回结果](#3. 统一返回结果)
[二、接口 1:设备状态查询](#二、接口 1:设备状态查询)
[1. DTO(请求参数)](#1. DTO(请求参数))
[2. VO(返回参数)](#2. VO(返回参数))
[3. Controller 接口](#3. Controller 接口)
[4. Service 实现(核心业务逻辑)](#4. Service 实现(核心业务逻辑))
[三、接口 2:远程控枪(核心)](#三、接口 2:远程控枪(核心))
[1. 枚举:远程控制指令类型](#1. 枚举:远程控制指令类型)
[2. DTO(请求参数)](#2. DTO(请求参数))
[3. Controller 接口](#3. Controller 接口)
[4. Service 实现(核心业务逻辑)](#4. Service 实现(核心业务逻辑))
[四、接口 3:设备故障查询](#四、接口 3:设备故障查询)
[1. DTO(请求参数)](#1. DTO(请求参数))
[2. VO(返回参数)](#2. VO(返回参数))
[3. Controller 接口](#3. Controller 接口)
[4. Service 实现](#4. Service 实现)
基于SpringBoot + MyBatis-Plus + Redis + RocketMQ (充电桩行业主流技术栈),实现设备状态查询、远程控枪、设备故障查询 三个核心对外接口,包含完整的接口定义、业务逻辑、代码实现、数据库设计,直接可落地使用。
一、前置核心定义
1. 技术栈
- 框架:SpringBoot 3.x + Spring MVC
- ORM:MyBatis-Plus
- 缓存:Redis(存储设备在线状态、实时数据)
- 消息队列:RocketMQ(下发远程控制指令)
- 协议:OCPP 1.6J/2.0.1
2. 核心数据库表(简化版)
-- 设备基础表
CREATE TABLE device (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
device_sn VARCHAR(64) NOT NULL COMMENT '设备唯一编号(主键)',
gun_num VARCHAR(32) NOT NULL COMMENT '枪号',
imei VARCHAR(64) COMMENT '设备IMEI',
status TINYINT NOT NULL COMMENT '设备状态 0-离线 1-在线',
work_status TINYINT NOT NULL COMMENT '工作状态 0-空闲 1-充电中 2-占用 3-故障',
voltage DECIMAL(10,2) COMMENT '实时电压',
current DECIMAL(10,2) COMMENT '实时电流',
temperature DECIMAL(10,2) COMMENT '温度',
power DECIMAL(10,2) COMMENT '功率',
create_time DATETIME,
update_time DATETIME
);
-- 设备故障表
CREATE TABLE device_fault (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
device_sn VARCHAR(64) NOT NULL COMMENT '设备编号',
gun_num VARCHAR(32) NOT NULL COMMENT '枪号',
fault_code VARCHAR(32) NOT NULL COMMENT '故障码',
fault_msg VARCHAR(255) NOT NULL COMMENT '故障描述',
fault_status TINYINT DEFAULT 0 COMMENT '故障状态 0-待处理 1-已处理',
create_time DATETIME
);
3. 统一返回结果
import lombok.Data;
/**
* 统一接口返回值
*/
@Data
public class Result<T> {
private int code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> fail(String msg) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMsg(msg);
return result;
}
}
二、接口 1:设备状态查询
业务说明
- 核心业务 :根据设备编号 / 枪号,查询设备在线状态、工作状态、实时电气参数(电压 / 电流 / 温度等);
- 数据来源:Redis(实时在线状态) + MySQL(基础信息);
- 校验规则:设备必须存在,支持单设备 / 批量查询。
1. DTO(请求参数)
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 设备状态查询请求
*/
@Data
public class DeviceStatusQueryDTO {
@NotBlank(message = "设备编号不能为空")
private String deviceSn;
// 非必填,不填默认查询所有枪
private String gunNum;
}
2. VO(返回参数)
import lombok.Data;
import java.math.BigDecimal;
/**
* 设备状态返回视图
*/
@Data
public class DeviceStatusVO {
private String deviceSn; // 设备编号
private String gunNum; // 枪号
private Integer status; // 在线状态 0-离线 1-在线
private Integer workStatus;// 工作状态 0-空闲 1-充电中 2-占用 3-故障
private BigDecimal voltage;// 电压
private BigDecimal current;// 电流
private BigDecimal temperature;// 温度
private BigDecimal power; // 功率
private String statusDesc; // 状态描述
}
3. Controller 接口
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 设备管理接口
*/
@RestController
@RequestMapping("/device/service")
public class DeviceController {
@Resource
private DeviceService deviceService;
/**
* 1. 设备状态查询
*/
@PostMapping("/status/query")
public Result<DeviceStatusVO> queryDeviceStatus(@RequestBody DeviceStatusQueryDTO dto) {
return Result.success(deviceService.getDeviceStatus(dto));
}
}
4. Service 实现(核心业务逻辑)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class DeviceServiceImpl implements DeviceService {
// Redis存储设备在线状态 key: device:online:{deviceSn}
private static final String DEVICE_ONLINE_KEY = "device:online:%s";
// 心跳超时时间(30s+10s缓冲)
private static final long HEARTBEAT_TIMEOUT = 40;
@Resource
private DeviceMapper deviceMapper;
@Resource
private StringRedisTemplate redisTemplate;
@Override
public DeviceStatusVO getDeviceStatus(DeviceStatusQueryDTO dto) {
// 1. 校验设备是否存在
Device device = deviceMapper.selectById(dto.getDeviceSn());
if (device == null) {
throw new RuntimeException("设备不存在");
}
// 2. 从Redis获取实时在线状态(心跳机制:设备30s上报一次)
String onlineKey = String.format(DEVICE_ONLINE_KEY, dto.getDeviceSn());
Boolean hasKey = redisTemplate.hasKey(onlineKey);
// 有key=在线,无key=离线
device.setStatus(Boolean.TRUE.equals(hasKey) ? 1 : 0);
// 3. 组装返回数据
DeviceStatusVO vo = new DeviceStatusVO();
BeanUtils.copyProperties(device, vo);
// 填充枪号(不填则用设备默认枪号)
vo.setGunNum(StringUtils.isBlank(dto.getGunNum()) ? device.getGunNum() : dto.getGunNum());
// 状态描述
vo.setStatusDesc(getStatusDesc(device.getStatus(), device.getWorkStatus()));
return vo;
}
/**
* 状态描述转换
*/
private String getStatusDesc(Integer status, Integer workStatus) {
if (status == 0) return "设备离线";
return switch (workStatus) {
case 0 -> "空闲";
case 1 -> "充电中";
case 2 -> "占用";
case 3 -> "故障";
default -> "未知状态";
};
}
}
三、接口 2:远程控枪(核心)
业务说明
- 核心业务 :远程启动充电 / 停止充电 / 锁枪 / 解锁 / 重启设备(OCPP 协议核心指令);
- 执行流程:参数校验 → 设备状态校验 → 消息队列下发指令 → 返回指令下发结果;
- 校验规则 :
- 设备必须在线才能下发指令;
- 空闲状态才能启动充电,充电中才能停止充电;
- 指令类型必须合法。
1. 枚举:远程控制指令类型
import lombok.Getter;
/**
* 远程控枪指令枚举
*/
@Getter
public enum RemoteControlEnum {
START_CHARGE(1, "启动充电"),
STOP_CHARGE(2, "停止充电"),
LOCK_GUN(3, "锁枪"),
UNLOCK_GUN(4, "解锁枪"),
REBOOT(5, "设备重启");
private final int code;
private final String desc;
RemoteControlEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
2. DTO(请求参数)
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 远程控枪请求
*/
@Data
public class RemoteControlDTO {
@NotBlank(message = "设备编号不能为空")
private String deviceSn;
@NotBlank(message = "枪号不能为空")
private String gunNum;
@NotNull(message = "指令类型不能为空")
private Integer command; // 对应RemoteControlEnum
private String operator; // 操作人
}
3. Controller 接口
/**
* 2. 远程控枪接口
*/
@PostMapping("/remote/control")
public Result<String> remoteControlGun(@RequestBody @Valid RemoteControlDTO dto) {
deviceService.remoteControl(dto);
return Result.success("指令下发成功,设备正在执行");
}
4. Service 实现(核心业务逻辑)
import org.apache.rocketmq.spring.core.RocketMQTemplate;
@Service
public class DeviceServiceImpl implements DeviceService {
// MQ指令下发主题(设备侧监听该主题执行指令)
private static final String REMOTE_CONTROL_TOPIC = "ocpp_remote_control_topic";
@Resource
private RocketMQTemplate rocketMQTemplate;
@Override
public void remoteControl(RemoteControlDTO dto) {
// 1. 校验设备是否存在
Device device = deviceMapper.selectById(dto.getDeviceSn());
if (device == null) {
throw new RuntimeException("设备不存在");
}
// 2. 校验设备在线状态
String onlineKey = String.format(DEVICE_ONLINE_KEY, dto.getDeviceSn());
if (!Boolean.TRUE.equals(redisTemplate.hasKey(onlineKey))) {
throw new RuntimeException("设备离线,无法下发远程指令");
}
// 3. 指令合法性校验
RemoteControlEnum command = null;
for (RemoteControlEnum e : RemoteControlEnum.values()) {
if (e.getCode() == dto.getCommand()) {
command = e;
break;
}
}
if (command == null) {
throw new RuntimeException("不支持的指令类型");
}
// 4. 业务状态校验(核心)
validWorkStatus(device, command);
// 5. 下发指令到MQ(设备侧监听MQ,解析OCPP协议执行)
rocketMQTemplate.convertAndSend(REMOTE_CONTROL_TOPIC, dto);
// 6. 日志记录/操作记录(可选)
log.info("设备{},枪{},下发指令:{}", dto.getDeviceSn(), dto.getGunNum(), command.getDesc());
}
/**
* 工作状态业务校验(核心业务规则)
*/
private void validWorkStatus(Device device, RemoteControlEnum command) {
Integer workStatus = device.getWorkStatus();
switch (command) {
case START_CHARGE:
if (workStatus != 0) throw new RuntimeException("设备非空闲,无法启动充电");
break;
case STOP_CHARGE:
if (workStatus != 1) throw new RuntimeException("设备未充电,无法停止");
break;
case LOCK_GUN:
case UNLOCK_GUN:
case REBOOT:
// 锁枪/解锁/重启 仅需在线即可,无工作状态限制
break;
default:
throw new RuntimeException("指令校验失败");
}
}
}
四、接口 3:设备故障查询
业务说明
- 核心业务 :查询设备历史故障、当前未处理故障,支持分页、故障状态筛选;
- 业务场景:运维人员查看设备故障,生成工单;
- 数据来源:MySQL 设备故障表。
1. DTO(请求参数)
import lombok.Data;
/**
* 设备故障查询请求
*/
@Data
public class DeviceFaultQueryDTO {
@NotBlank(message = "设备编号不能为空")
private String deviceSn;
private String gunNum;
private Integer faultStatus; // 0-待处理 1-已处理
private Integer pageNum = 1;
private Integer pageSize = 10;
}
2. VO(返回参数)
import lombok.Data;
import java.time.LocalDateTime;
/**
* 设备故障返回视图
*/
@Data
public class DeviceFaultVO {
private String deviceSn;
private String gunNum;
private String faultCode; // 故障码
private String faultMsg; // 故障描述
private Integer faultStatus;// 故障状态
private String statusDesc; // 状态描述
private LocalDateTime createTime; // 故障上报时间
}
3. Controller 接口
/**
* 3. 设备故障查询
*/
@PostMapping("/fault/query")
public Result<Page<DeviceFaultVO>> queryDeviceFault(@RequestBody DeviceFaultQueryDTO dto) {
return Result.success(deviceService.getDeviceFaultList(dto));
}
4. Service 实现
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@Service
public class DeviceServiceImpl implements DeviceService {
@Resource
private DeviceFaultMapper deviceFaultMapper;
@Override
public Page<DeviceFaultVO> getDeviceFaultList(DeviceFaultQueryDTO dto) {
// 1. 分页查询
Page<DeviceFault> page = new Page<>(dto.getPageNum(), dto.getPageSize());
LambdaQueryWrapper<DeviceFault> wrapper = Wrappers.lambdaQuery();
wrapper.eq(DeviceFault::getDeviceSn, dto.getDeviceSn());
// 枪号筛选
if (StringUtils.isNotBlank(dto.getGunNum())) {
wrapper.eq(DeviceFault::getGunNum, dto.getGunNum());
}
// 故障状态筛选
if (dto.getFaultStatus() != null) {
wrapper.eq(DeviceFault::getFaultStatus, dto.getFaultStatus());
}
// 按故障时间倒序
wrapper.orderByDesc(DeviceFault::getCreateTime);
IPage<DeviceFault> faultPage = deviceFaultMapper.selectPage(page, wrapper);
// 2. 转换VO
Page<DeviceFaultVO> resultPage = new Page<>();
BeanUtils.copyProperties(faultPage, resultPage);
List<DeviceFaultVO> voList = faultPage.getRecords().stream().map(fault -> {
DeviceFaultVO vo = new DeviceFaultVO();
BeanUtils.copyProperties(fault, vo);
vo.setStatusDesc(fault.getFaultStatus() == 0 ? "待处理" : "已处理");
return vo;
}).collect(Collectors.toList());
resultPage.setRecords(voList);
return resultPage;
}
}
五、核心业务总结(三个接口)
| 接口 | 核心业务逻辑 | 技术要点 |
|---|---|---|
| 设备状态查询 | 1. 校验设备存在2. Redis 校验在线状态(心跳机制)3. 拼接实时状态返回 | Redis+MySQL 双数据源 |
| 远程控枪 | 1. 设备在线校验2. 指令合法性校验3. 工作状态业务校验4. MQ 下发 OCPP 指令 | 状态机校验 + 消息队列异步指令 |
| 设备故障查询 | 1. 多条件筛选故障2. 分页查询3. 状态描述转换 | 分页查询 + 数据转换 |
六、扩展说明
- 协议适配 :设备侧监听 MQ 后,会将指令转换为OCPP 1.6J/2.0.1协议报文发送给充电桩硬件;
- 心跳机制:设备每 30s 上报心跳,Redis 缓存心跳 key,超时 40s 判定为离线;
- 高可用 :接口添加熔断、限流、日志埋点,适配充电桩高并发场景;
- 安全 :生产环境需添加接口鉴权(Token)、操作日志、敏感指令二次确认。
总结
- 三个接口完全匹配设备管理服务 的对外接口需求,覆盖状态监控、远程控制、运维故障三大核心场景;
- 代码遵循业务校验优先、数据分层、异步解耦的充电桩行业最佳实践;
- 可直接集成到充电系统中,搭配 OCPP 协议解析模块即可对接物理充电桩。