简介
定义一系列的算法,把它们一个个封装起来,并且使他们可相互替换。本模式使得算法的变化可以独立于使用它的客户。
实际项目中使用(部分代码)
业务场景:通过页面功能按钮操作下发相应的指令到考勤设备(下发/获取/修改/删除人员或者考勤信息、经纬度信息、打卡范围、重启等等)。目前系统已有一种考勤(存储设备的指令到表里,定时任务获取未执行的该设备指令进行发送),现在准备接入一款新的考勤设备。
代码实现
/**
* 抽象第三方设备类
*
* @Author htl
*/
public abstract class AbstractThirdPartyEquipment {
/**
* 通过spring上下文取得已实例化的bean
*/
protected final BizProjectPersonInfoMapper bizProjectPersonInfoMapper = ApplicationContextUtil.getApplicationContext().getBean(BizProjectPersonInfoMapper.class);
protected final BizProjectPersonInfoService bizProjectPersonInfoService = ApplicationContextUtil.getApplicationContext().getBean(BizProjectPersonInfoService.class);
protected final BizEnterPrisePersonInfoMapper bizEnterPrisePersonInfoMapper = ApplicationContextUtil.getApplicationContext().getBean(BizEnterPrisePersonInfoMapper.class);
protected final BizAttendanceSettingMapper bizAttendanceSettingMapper = ApplicationContextUtil.getApplicationContext().getBean(BizAttendanceSettingMapper.class);
protected final PhysicalFileMapper physicalFileMapper = ApplicationContextUtil.getApplicationContext().getBean(PhysicalFileMapper.class);
protected final FacePinPersonMapper facePinPersonMapper = ApplicationContextUtil.getApplicationContext().getBean(FacePinPersonMapper.class);
protected final FaceServeLogMapper faceServeLogMapper = ApplicationContextUtil.getApplicationContext().getBean(FaceServeLogMapper.class);
protected final FaceDeviceItemMapMapper faceDeviceItemMapMapper = ApplicationContextUtil.getApplicationContext().getBean(FaceDeviceItemMapMapper.class);
protected final AmqpTemplate amqpTemplate = ApplicationContextUtil.getApplicationContext().getBean(AmqpTemplate.class);
/**
* 新增用户
*
* @param cmd
*/
public abstract void addUser(FaceCommand cmd);
/**
* 删除用户
*
* @param cmd
*/
public abstract void delUser(FaceCommand cmd);
/**
* 重启设备
*
* @param cmd
*/
public abstract void reboot(FaceCommand cmd);
/**
* 获取用户信息
*
* @param cmd
*/
public abstract void getUserDetails(FaceCommand cmd);
/**
* 修改用户
*
* @param cmd
*/
public abstract void editUser(FaceCommand cmd);
/**
* 设置设备位置围栏
*
* @param cmd
*/
public abstract void setFence(FaceCommand cmd);
}
/**
* 丹航设备的实现
*
* @Author htl
*/
@Slf4j
public class DanNavigationEquipmentImpl extends AbstractThirdPartyEquipment {
/**
* 丹航指令常量
*/
public final static String REBOOT = "reboot";
public final static String ADDUSER = "addUser";
public final static String DEL_USER = "delUser";
public final static String EDIT_USER = "editUser";
public final static String GET_USER_DETAIL = "getUserDetails";
public final static String SET_FENCE = "setGeofence";
@Override
public void addUser(FaceCommand cmd) {
//获取FacePinPerson的id,当中用户id
List<FacePinPerson> pinPersonList = facePinPersonMapper.select(new FacePinPerson().setDeleted(false)
.setPersonId(cmd.getPersonId()).setProPerId(cmd.getProPerId())
.setProjectId(cmd.getProjectId()).setDeviceNo(cmd.getHandledDevice()));
FacePinPerson facePinPerson = pinPersonList.get(0);
FacePinPersonDTO dto = facePinPersonMapper.selectPersonInfo(facePinPerson);
//图片完整路径 设备【验证模式】为【人脸或卡】时可以不传照片,非【人脸或卡】模式这个字段为必传
String filePath = ApplicationContextUtil.getApplicationContext()
.getBean(ApplicationProperties.class).getFileServer() + dto.getPersonImageAdr();
//转base64进行下发
// String faceTemplate = imageCoding(filePath);
//组装MQ数据
String jsonDataString = new JSONObject(ImmutableMap.of(
"cmd", ADDUSER,
"equipmentSn", dto.getDeviceNo(),
"userId", dto.getId(),
"faceTemplate", filePath,
"name", dto.getName()
)).toJSONString();
sendMessage(jsonDataString, cmd.getId().toString());
log.info("丹航设备:{},新增人员指令 -> MQ队列", cmd.getId());
}
@Override
public void delUser(FaceCommand cmd) {
//获取FacePinPerson的id,当中用户id
List<FacePinPerson> pinPersonList = facePinPersonMapper.select(new FacePinPerson().setDeleted(false)
.setPersonId(cmd.getPersonId()).setProPerId(cmd.getProPerId())
.setProjectId(cmd.getProjectId()).setDeviceNo(cmd.getHandledDevice()));
//MQ
String jsonDataString = new JSONObject(ImmutableMap.of(
"cmd", DEL_USER,
"equipmentSn", cmd.getHandledDevice(),
"userId", pinPersonList.get(0).getId()))
.toJSONString();
sendMessage(jsonDataString, cmd.getId().toString());
log.info("丹航设备:{},删除人员指令 -> MQ队列", cmd.getId());
}
@Override
public void reboot(FaceCommand cmd) {
//MQ
String jsonDataString = new JSONObject(ImmutableMap.of(
"cmd", REBOOT,
"equipmentSn", cmd.getHandledDevice()))
.toJSONString();
sendMessage(jsonDataString, cmd.getId().toString());
log.info("丹航设备:{},重启指令 -> MQ队列", cmd.getId());
}
@Override
public void getUserDetails(FaceCommand cmd) {
//获取FacePinPerson的id,当中用户id
List<FacePinPerson> pinPersonList = facePinPersonMapper.select(new FacePinPerson().setDeleted(false)
.setPersonId(cmd.getPersonId()).setProPerId(cmd.getProPerId())
.setProjectId(cmd.getProjectId()).setDeviceNo(cmd.getHandledDevice()));
FacePinPerson facePinPerson = pinPersonList.get(0);
//MQ
String jsonDataString = new JSONObject(ImmutableMap.of(
"cmd", GET_USER_DETAIL,
"equipmentSn", cmd.getHandledDevice(),
"userId", facePinPerson.getId()))
.toJSONString();
sendMessage(jsonDataString, cmd.getId().toString());
log.info("丹航设备:{},读取用户数据指令 -> MQ队列", cmd.getId());
}
@Override
public void editUser(FaceCommand cmd) {
//获取FacePinPerson的id,当中用户id
List<FacePinPerson> pinPersonList = facePinPersonMapper.select(new FacePinPerson().setDeleted(false)
.setPersonId(cmd.getPersonId()).setProPerId(cmd.getProPerId())
.setProjectId(cmd.getProjectId()).setDeviceNo(cmd.getHandledDevice()));
if (pinPersonList.size() == 0) {
return;
}
FacePinPerson facePinPerson = pinPersonList.get(0);
FacePinPersonDTO dto = facePinPersonMapper.selectPersonInfo(facePinPerson);
//图片完整路径
String filePath = ApplicationContextUtil.getApplicationContext()
.getBean(ApplicationProperties.class).getFileServer() + dto.getPersonImageAdr();
//转base64进行下发
// String faceTemplate = imageCoding(filePath);
//组装MQ数据
String jsonDataString = new JSONObject(ImmutableMap.of(
"cmd", EDIT_USER,
"equipmentSn", dto.getDeviceNo(),
"userId", dto.getId(),
"faceTemplate", filePath,
"name", dto.getName()
)).toJSONString();
sendMessage(jsonDataString, cmd.getId().toString());
log.info("丹航设备:{},修改人员指令 -> MQ队列", cmd.getId());
}
@Override
public void setFence(FaceCommand cmd) {
//获取项目考勤的经纬度及半径
List<BizAttendanceSetting> attendanceSettings = bizAttendanceSettingMapper.select(new BizAttendanceSetting().setAttendanceProject(cmd.getProjectId()).setDeleted(false));
if (attendanceSettings.size() > 0) {
BizAttendanceSetting bizAttendanceSetting = attendanceSettings.get(0);
String position = bizAttendanceSetting.getAttendancePositionCenter();
Float attendanceRadius = bizAttendanceSetting.getAttendanceRadius();
//解析经纬度, 保留小数点后8位数
String[] positions = position.split(",");
String[] latitudeStr = positions[0].split("\\.");
String[] longitudeStr = positions[1].split("\\.");
String latitude = positions[0];
String longitude = positions[1];
//判断是否存在小数点
if (latitudeStr.length > 1 && latitudeStr[1].length() >= 8) {
latitude = latitudeStr[0] + "." + latitudeStr[1].substring(0, 8);
}
if (longitudeStr.length > 1 && longitudeStr[1].length() >= 8) {
longitude = longitudeStr[0] + "." + longitudeStr[1].substring(0, 8);
}
//组装MQ数据
String jsonDataString = new JSONObject(ImmutableMap.of(
"cmd", SET_FENCE,
"equipmentSn", cmd.getHandledDevice(),
"latitude", latitude,
"longitude", longitude,
"radius", attendanceRadius
)).toJSONString();
sendMessage(jsonDataString, cmd.getId().toString());
log.info("丹航设备:{},设置设备位置围栏指令 -> MQ队列", cmd.getId());
//添加face_serve_log
FaceServeLog faceServeLog = new FaceServeLog()
.setEquipment(cmd.getHandledDevice())
.setCommandId(cmd.getId())
.setSubmitDate(cmd.getCreatedDate())
.setTransmitDate(LocalDateTime.now())
.setCreatedDate(LocalDateTime.now())
.setDeleted(false)
.setSchedule("0")
.setContent("C:" + cmd.getHandledDevice() + ":DATA UPDATE SET_FENCE" +
" latitude=" + latitude + "longitude=" + longitude + "radius=" + attendanceRadius);
faceServeLogMapper.insert(faceServeLog);
}
}
/**
* 发送消息
*
* @param jsonBody
* @param cmdId
*/
private void sendMessage(String jsonBody, String cmdId) {
Message message = MessageBuilder.withBody( jsonBody.getBytes(StandardCharsets.UTF_8))
.setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setMessageId(cmdId)
.build();
//MQ队列
amqpTemplate.send(RabbitmqConstant.DH_FACE_CMD_RECEIVED_EXCHANGE,
RabbitmqConstant.DH_FACE_CMD_RECEIVED_KEY, message);
}
/**
* 丹航设备-照片编码
*
* @param filePath
* @return
*/
private String imageCoding(String filePath) {
try (ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
FileInputStream fileInputStream = new FileInputStream(filePath)) {
byte[] bytes = new byte[1024];
int len;
while ((len = fileInputStream.read(bytes)) != -1) {
//写出到字节数组中
arrayOutputStream.write(bytes, 0, len);
}
arrayOutputStream.flush();
byte[] byteArray = arrayOutputStream.toByteArray();
//->base64 ->URLEncoder
String str = "data:image/jpeg;base64," + new BASE64Encoder().encode(byteArray);
log.info("-------------------{}------------------", "图片转换base64完成");
return URLEncoder.encode(str, "UTF-8");
} catch (Exception e) {
throw new RuntimeException("图片转换base64异常: " + e.getLocalizedMessage());
}
}
}
/**
* 管理考勤机设备的实现类 - 策略管理类
*
* @Author htl
*/
public class ThirdPartyEquipmentManage {
/**
* key - 品牌 字典表管理, value - 自定义设备实现类
*/
private static final HashMap<String, AbstractThirdPartyEquipment> PARTY_EQUIPMENT_MAP = new HashMap();
public static final FaceDeviceItemMapMapper deviceItemMapMapper = ApplicationContextUtil.getApplicationContext().getBean(FaceDeviceItemMapMapper.class);
//后续需要接入其他品牌的考勤机器,只需要编写好实现类,再到这进行初始化,就OK,不需要修改已有代码. 享元模式
static {
PARTY_EQUIPMENT_MAP.put("2", new DanNavigationEquipmentImpl());
}
/**
* 根据SN获取设备,再根据设备的品牌,获取对应的处理类
*
* @param equipmentSn
* @return
*/
public static AbstractThirdPartyEquipment getEquipmentImpl(String equipmentSn) {
FaceDeviceItemMap deviceItemMap = deviceItemMapMapper.selectOne(new FaceDeviceItemMap()
.setEquipment(equipmentSn).setDeleted(false));
if (Optional.ofNullable(deviceItemMap).isPresent()) {
String brand = deviceItemMap.getBrand();
if (PARTY_EQUIPMENT_MAP.containsKey(brand)) {
return PARTY_EQUIPMENT_MAP.get(brand);
}
}
return null;
}
}
/**
* 定时任务读取指令,解析下发到MQ里
*/
@Scheduled(cron = "0/10 * * * * ?")
public void sendThirdPartyEquipmentCommand() {
//获取到所有【非原来的设备】并且【未执行】的指令
List<FaceCommand> faceCommandList = faceCommandMapper.selectThirdPartyEquipmentCommands();
//区分SqlType
if (Optional.ofNullable(faceCommandList).isPresent() && faceCommandList.size() > 0) {
faceCommandList.parallelStream().forEach(cmd -> {
List<FaceServeLog> serveLogList = faceServeLogMapper.select(new FaceServeLog()
.setCommandId(cmd.getId()).setDeleted(false));
String sqlType = cmd.getSqlType();
try {
//调用第三方设备处理类进行处理
AbstractThirdPartyEquipment equipmentImpl = ThirdPartyEquipmentManage.getEquipmentImpl(cmd.getHandledDevice());
if (Optional.ofNullable(equipmentImpl).isPresent()) {
if (SqlTypeEnum.INSERT.getValue().equals(sqlType)) {
equipmentImpl.addUser(cmd);
}
if (SqlTypeEnum.DELETE.getValue().equals(sqlType)) {
equipmentImpl.delUser(cmd);
}
if (SqlTypeEnum.REBOOT.getValue().equals(sqlType)) {
equipmentImpl.reboot(cmd);
}
if (SqlTypeEnum.QUERY.getValue().equals(sqlType)) {
equipmentImpl.getUserDetails(cmd);
}
if (SqlTypeEnum.UPDATE.getValue().equals(sqlType)) {
if (CMD_UPDATE.equals(cmd.getCmd())) {
equipmentImpl.editUser(cmd);
}
if (CMD_FENCE.equals(cmd.getCmd())) {
equipmentImpl.setFence(cmd);
}
}
//服务器日志
if (serveLogList.size() > 0) {
serveLogList.forEach(log -> {
log.setSchedule("0");
log.setTransmitDate(LocalDateTime.now());
});
}
//下发成功
cmd.setHandledRemark("[下发成功, 等待设备响应]");
//修改设备最近传送时间
List<FaceDeviceItemMap> devices = faceDeviceItemMapMapper.select(new FaceDeviceItemMap().setEquipment(cmd.getHandledDevice()).setDeleted(false));
if (devices.size() > 0) {
devices.get(0).setLastTransmitDate(LocalDateTime.now());
faceDeviceItemMapMapper.updateByPrimaryKeySelective(devices.get(0));
}
}
} catch (Exception e) {
//日志处理
if (serveLogList.size() > 0) {
serveLogList.forEach(log -> {
log.setSchedule("100");
log.setReturnedValue("[下发异常]" + e.getMessage());
faceServeLogMapper.updateByPrimaryKeySelective(log);
});
}
cmd.setHandledRemark("[下发异常]" + e.getMessage());
log.error("第三方设备解析下发指令异常!{}", e.getLocalizedMessage());
}
//修改指令数据
cmd.setHandled(true);
cmd.setDeleted(false);
faceCommandMapper.updateByPrimaryKeySelective(cmd);
//修改服务器日志
if (serveLogList.size() > 0) {
serveLogList.forEach(log -> faceServeLogMapper.updateByPrimaryKeySelective(log));
}
});
}
}
总结
后续需要接入其他品牌的考勤机器,只需要编写好实现类,在策略管理类中添加该策略即可,不需要对原来的代码进行改动,避免使用难以维护的多重条件选择语句。
策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派 给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的 策略类里面,作为一个抽象策略类的子类。
策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系 统的基础上可以更换算法或者增加新的算法,它很好地管理算法族, 提高了代码的复用性,是一种替换继承,避免多重条件转移语句的 实现方式;