From eba790b3e043a217bf89ab6d818b3149511b5766 Mon Sep 17 00:00:00 2001 From: 18650502300 <18650502300@163.com> Date: Tue, 28 May 2024 16:55:38 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=B8=8D=E5=90=8C=E5=9C=BA=E6=99=AF=E4=B8=8B?= =?UTF-8?q?=E5=8F=91=E9=80=81=E4=B8=8D=E5=90=8C=E7=9A=84=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=202.=E6=8E=A8=E9=80=81=E7=BB=8F=E5=BA=A6?= =?UTF-8?q?=E7=BA=AC=E5=BA=A6=E6=95=B0=E6=8D=AE=E5=88=B0=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/iot/domain/BodyObj.java | 29 +++ .../web/controller/iot/domain/DataPonit.java | 31 +++ .../web/controller/iot/domain/LogEntry.java | 39 ++++ .../iot/receive/ReceiveController.java | 191 ++++++++++++++++++ .../ruoyi/web/controller/iot/util/Util.java | 141 +++++++++++++ .../controller/system/AsUserController.java | 16 ++ .../src/main/resources/application.yml | 4 +- .../com/ruoyi/common/config/WxPayConfig.java | 16 ++ .../ruoyi/common/constant/IotConstants.java | 64 +++++- .../com/ruoyi/common/utils/CommonUtil.java | 40 ++++ .../framework/config/SecurityConfig.java | 1 + .../ruoyi/system/domain/EtOperatingArea.java | 2 + .../ruoyi/system/mapper/AsDeviceMapper.java | 2 +- .../system/service/IAsDeviceService.java | 13 +- .../ruoyi/system/service/IWxPayService.java | 29 ++- .../service/impl/AsDeviceServiceImpl.java | 95 ++++++++- .../service/impl/CallbackServiceImpl.java | 42 +--- .../service/impl/EtOrderServiceImpl.java | 52 +---- .../system/service/impl/WxPayService.java | 102 +++++----- .../java/com/ruoyi/system/task/EtTask.java | 24 ++- 20 files changed, 773 insertions(+), 160 deletions(-) create mode 100644 electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/BodyObj.java create mode 100644 electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/DataPonit.java create mode 100644 electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java create mode 100644 electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java create mode 100644 electripper-admin/src/main/java/com/ruoyi/web/controller/iot/util/Util.java diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/BodyObj.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/BodyObj.java new file mode 100644 index 0000000..a42a45a --- /dev/null +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/BodyObj.java @@ -0,0 +1,29 @@ +package com.ruoyi.web.controller.iot.domain; + +import lombok.Data; + +/** + * onenet接收到的body对象 + * + * @author ruoyi + */ + +@Data +public class BodyObj { + + /** 设备推送数据,包括设备的生命周期,数据点,物模型属性、事件、服务等 */ + private Object msg; + + /** 用于计算签名字符的随机串 */ + private String nonce; + + /** 加密签名,用以校验推送客户端身份合法性,校验方法见实例验证 */ + private String signature; + + /** 推送时间戳(毫秒) */ + private Long time; + + /** 消息ID */ + private String id; + +} diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/DataPonit.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/DataPonit.java new file mode 100644 index 0000000..1aeeebc --- /dev/null +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/DataPonit.java @@ -0,0 +1,31 @@ +package com.ruoyi.web.controller.iot.domain; + +import lombok.Data; + +/** + * 数据点数据 + * + * @author ruoyi + */ + +@Data +public class DataPonit { + + /** 设备名称 */ + private String dev_name; + + /** 设备上报的时间戳 */ + private Long at; + + /** 产品id */ + private String pid; + + /** 固定值:1 */ + private Integer type; + + /** 数据点id */ + private String ds_id; + + /** 消息值 */ + private String value; +} diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java new file mode 100644 index 0000000..b04456e --- /dev/null +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.iot.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * onenet接收到的车辆定位日志对象 + * */ +@Data +public class LogEntry { + + //mac号 + @JsonProperty("dev_name") + private String devName; + + private long at; + + private String pid; + + private int type; + + @JsonProperty("ds_id") + private String dsId; + + private LocationValue value; + + @Data + public class LocationValue { + private String lon;//经度 + + private String lat;//纬度 + + private Integer status;//电动车状态 0断电,1上电运行 2轮动抱死 3超出区域断电(远程下发了qlose) + + private Integer bat;//电池电压 "bat":571 ==> 57.1V + + private Integer csq;//信号强度 + } +} diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java new file mode 100644 index 0000000..5fcc438 --- /dev/null +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java @@ -0,0 +1,191 @@ +package com.ruoyi.web.controller.iot.receive; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.constant.IotConstants; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.CommonUtil; +import com.ruoyi.common.utils.onenet.Token; +import com.ruoyi.system.domain.AsDevice; +import com.ruoyi.system.domain.EtModel; +import com.ruoyi.system.domain.EtOperatingArea; +import com.ruoyi.system.mapper.AsDeviceMapper; +import com.ruoyi.system.service.IAsDeviceService; +import com.ruoyi.system.service.IEtModelService; +import com.ruoyi.system.service.IEtOperatingAreaService; +import com.ruoyi.web.controller.iot.domain.BodyObj; +import com.ruoyi.web.controller.iot.domain.LogEntry; +import com.ruoyi.web.controller.iot.util.Util; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.UnsupportedEncodingException; + +/** + * 接收硬件参数 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/common") +public class ReceiveController { + + private static final Logger log = LoggerFactory.getLogger(ReceiveController.class); + + @Value(value = "${iot.token}") + private String token; + + + @Resource + private IAsDeviceService asDeviceService; + + @Autowired + private IEtModelService etModelService; + + @Resource + private IEtOperatingAreaService etOperatingAreaService; + + /** + * 功能描述:第三方平台数据接收。

+ *

+ * @param body 数据消息 + * @return 任意字符串。OneNet平台接收到http 200的响应,才会认为数据推送成功,否则会重发。 + */ + @RequestMapping(value = "/receive",method = RequestMethod.POST) + @ResponseBody + @SneakyThrows + @Transactional + public String receive(@RequestBody String body){ + + log.info("receive方法接收到参数: body String --- " +body); + /************************************************ + * 解析数据推送请求,非加密模式。 + * 如果是明文模式使用以下代码 + **************************************************/ + /*************明文模式 start****************/ + BodyObj obj = Util.resolveBody(body, false); + log.info("receive方法解析对象: body Object --- " + JSON.toJSONString(obj)); + if (obj != null){ + boolean dataRight = Util.checkSignature(obj, token); + if (dataRight){ + log.info("receive方法验证签名正确: content" + JSON.toJSONString(obj)); + String msg = (String)obj.getMsg(); + log.info("receive方法-获取到消息体: msg---" +msg); + LogEntry logEntry = JSONObject.parseObject(msg, LogEntry.class); + log.info("logEntry转换后的对象: logEntry---【{}】" , JSON.toJSONString(logEntry)); + LogEntry.LocationValue value = logEntry.getValue(); + if(IotConstants.ONENET_LOCATION.equals(logEntry.getDsId()) && ObjectUtil.isNotNull(value)){ + /**如果是定位日志则,获取到车辆mac,找到对应车辆 + * 1.更新车辆定位、计算续航里程 + * 2.判断是否在禁行区内,如果在,根据配置‘禁行区内断电配置’进行断电, + * 3.超出运营区外断电 + * 4.行程线路添加,更新订单中的trip_route字段 + * 5.低电量不能骑行,如果电量低则声音播报 + * */ + /** 1.更新车辆定位、电压;计算续航里程 */ + AsDevice device = asDeviceService.selectAsDeviceByMac(logEntry.getDevName()); + EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(device.getAreaId()); + if(ObjectUtil.isNotNull(device)){ + device.setLatitude(value.getLon()); + device.setLongitude(value.getLat()); + Integer bat = value.getBat(); + double voltage = (double) bat / 10;//电压 + device.setVoltage(String.valueOf(voltage));//电压 + // 根据电压计算续航里程 + EtModel model = etModelService.selectEtModelByModelId(device.getModelId()); + Integer remainingMileage = CommonUtil.getRemainingMileage(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage(), model.getFullEndurance()); + device.setRemainingMileage(remainingMileage); + int i = asDeviceService.updateAsDeviceBySn(device); + if(i>0){ + log.info("更新定位成功:" +logEntry.getDevName()); + /** 2. 判断是否在禁行区内 + * 如果在, 根据配置‘禁行区内断电配置’进行断电 + **/ + boolean noRidingArea = asDeviceService.isNoRidingArea(device.getSn(), device.getAreaId()); + if(noRidingArea){ + String noRidingOutage = area.getNoRidingOutage(); + //发送播报指令 + asDeviceService.sendCommand(device.getMac(), Token.getToken(),IotConstants.COMMAND_PLAY3,"禁行区内播报"); + if(noRidingOutage.equals("1")){//禁行区内断电 + //发送断电命令 + asDeviceService.sendCommand(device.getMac(), Token.getToken(),IotConstants.COMMAND_QLOSE,"禁行区内断电"); + log.info("禁行区内发送断电命令:" +logEntry.getDevName()); + } + } + /** 3.超出运营区外断电*/ + boolean isAreaZone = asDeviceService.isAreaZone(device.getSn(), area); + if(isAreaZone){ + String areaOutOutage = area.getAreaOutOutage(); + //发送超出营运区播报指令 + asDeviceService.sendCommand(device.getMac(), Token.getToken(),IotConstants.COMMAND_PLAY3,"超出营运区播报"); + if(areaOutOutage.equals("1")){//超出营运区断电 + //发送断电命令 + asDeviceService.sendCommand(device.getMac(), Token.getToken(),IotConstants.COMMAND_QLOSE,"超出营运区断电"); + log.info("超出营运区发送断电命令:" +logEntry.getDevName()); + } + } + /** todo 4.行程线路添加,更新订单中的trip_route字段 */ + + + /** 5.低电量不能骑行,如果电量低则声音播报 */ + Integer electricQuantity = CommonUtil.getElectricQuantity(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage()); + if(electricQuantity <=model.getLowBatteryReminder()){ + //发送低电量播报指令 + asDeviceService.sendCommand(device.getMac(), Token.getToken(),IotConstants.COMMAND_PLAY6,"低电量播报"); + log.info("低电量播报:" +logEntry.getDevName()); + } + }else{ + log.info("更新定位失败:" +logEntry.getDevName()); + } + }else{ + log.info("未找到车辆对象:" +logEntry.getDevName()); + } + + } + }else { + log.info("receive方法验证签名错误: signature error"); + } + + }else { + log.info("receive方法参数为空: body empty error"); + } + /*************明文模式 end****************/ + return "ok"; + } + + /** + * 功能说明: URL&Token验证接口。如果验证成功返回msg的值,否则返回其他值。 + * @param msg 验证消息 + * @param nonce 随机串 + * @param signature 签名 + * @return msg值 + */ + + @RequestMapping(value = "/receive", method = RequestMethod.GET) + @ResponseBody + public String check(@RequestParam(value = "msg") String msg, + @RequestParam(value = "nonce") String nonce, + @RequestParam(value = "signature") String signature) throws UnsupportedEncodingException { + + log.info("check方法接收到参数:: msg:{} nonce{} signature:{}",msg,nonce,signature); + if (Util.checkToken(msg,nonce,signature,token)){ + log.info("校验成功",msg,nonce,signature); + return msg; + }else { + log.info("校验失败",msg,nonce,signature); + return "error"; + } + + } +} diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/util/Util.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/util/Util.java new file mode 100644 index 0000000..6616b97 --- /dev/null +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/util/Util.java @@ -0,0 +1,141 @@ +package com.ruoyi.web.controller.iot.util; + +import cn.hutool.json.JSONObject; +import com.ruoyi.web.controller.iot.domain.BodyObj; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.*; + + +/** + * 功能描述: OneNet数据推送接收程序工具类。 + * + * Created by Roy on 2017/5/17. + * + */ +public class Util { + + private static Logger logger = LoggerFactory.getLogger(Util.class); + + private static MessageDigest mdInst; + + static { + try { + mdInst = MessageDigest.getInstance("MD5"); + Security.addProvider(new BouncyCastleProvider()); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + + /** + * 功能描述:在OneNet平台配置数据接收地址时,平台会发送URL&token验证请求

+ * 使用此功能函数验证token + * @param msg 请求参数 的值 + * @param nonce 请求参数 的值 + * @param signature 请求参数 的值 + * @param token OneNet平台配置页面token的值 + * @return token检验成功返回true;token校验失败返回false + */ + public static boolean checkToken(String msg,String nonce,String signature, String token) throws UnsupportedEncodingException { + + byte[] paramB = new byte[token.length() + 8 + msg.length()]; + System.arraycopy(token.getBytes(), 0, paramB, 0, token.length()); + System.arraycopy(nonce.getBytes(), 0, paramB, token.length(), 8); + System.arraycopy(msg.getBytes(), 0, paramB, token.length() + 8, msg.length()); + String sig = com.sun.org.apache.xerces.internal.impl.dv.util.Base64.encode(mdInst.digest(paramB)); + logger.info("url&token validation: result {}, detail receive:{} calculate:{}", sig.equals(signature.replace(' ','+')),signature,sig); + return sig.equals(signature.replace(' ','+')); + } + + /** + * 功能描述: 检查接收数据的信息摘要是否正确。

+ * 方法非线程安全。 + * @param obj 消息体对象 + * @param token OneNet平台配置页面token的值 + * @return + */ + public static boolean checkSignature(BodyObj obj, String token) { + //计算接受到的消息的摘要 + //token长度 + 8B随机字符串长度 + 消息长度 + byte[] signature = new byte[token.length() + 8 + obj.getMsg().toString().length()]; + System.arraycopy(token.getBytes(), 0, signature, 0, token.length()); + System.arraycopy(obj.getNonce().getBytes(), 0, signature, token.length(), 8); + System.arraycopy(obj.getMsg().toString().getBytes(), 0, signature, token.length() + 8, obj.getMsg().toString().length()); + String calSig = Base64.encodeBase64String(mdInst.digest(signature)); + logger.info("check signature: result:{} receive sig:{},calculate sig: {}",calSig.equals(obj.getSignature()),obj.getSignature(),calSig); + return calSig.equals(obj.getSignature()); + } + + /** + * 功能描述 解密消息 + * @param obj 消息体对象 + * @param encodeKey OneNet平台第三方平台配置页面为用户生成的AES的BASE64编码格式秘钥 + * @return + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidAlgorithmParameterException + * @throws InvalidKeyException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + */ + public static String decryptMsg(BodyObj obj, String encodeKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + byte[] encMsg = Base64.decodeBase64(obj.getMsg().toString()); + byte[] aeskey = Base64.decodeBase64(encodeKey + "="); + SecretKey secretKey = new SecretKeySpec(aeskey, 0, 32, "AES"); + Cipher cipher = null; + cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(aeskey, 0, 16)); + byte[] allmsg = cipher.doFinal(encMsg); + byte[] msgLenBytes = new byte[4]; + System.arraycopy(allmsg, 16, msgLenBytes, 0, 4); + int msgLen = getMsgLen(msgLenBytes); + byte[] msg = new byte[msgLen]; + System.arraycopy(allmsg, 20, msg, 0, msgLen); + return new String(msg); + } + + /** + * 功能描述 解析数据推送请求,生成code>BodyObj消息对象 + * @param body 数据推送请求body部分 + * @param encrypted 表征是否为加密消息 + * @return 生成的BodyObj消息对象 + */ + public static BodyObj resolveBody(String body, boolean encrypted) { + JSONObject jsonMsg = new JSONObject(body); + BodyObj obj = new BodyObj(); + obj.setNonce(jsonMsg.getStr("nonce")); + obj.setSignature(jsonMsg.getStr("signature")); + if (encrypted) { + if (!jsonMsg.containsKey("enc_msg")) { + return null; + } + obj.setMsg(jsonMsg.getStr("enc_msg")); + } else { + if (!jsonMsg.containsKey("msg")) { + return null; + } + obj.setMsg(jsonMsg.get("msg")); + } + return obj; + } + + private static int getMsgLen(byte[] arrays) { + int len = 0; + len += (arrays[0] & 0xFF) << 24; + len += (arrays[1] & 0xFF) << 16; + len += (arrays[2] & 0xFF) << 8; + len += (arrays[3] & 0xFF); + return len; + } + + +} diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/system/AsUserController.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/system/AsUserController.java index dea52e4..802e3c8 100644 --- a/electripper-admin/src/main/java/com/ruoyi/web/controller/system/AsUserController.java +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/system/AsUserController.java @@ -4,13 +4,17 @@ import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.AsUser; +import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.service.IAsUserService; +import com.ruoyi.system.service.ISysUserService; import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -31,6 +35,9 @@ public class AsUserController extends BaseController @Resource private IAsUserService asUserService; + @Autowired + private ISysUserService sysUserService; + /** * 获取用户列表 */ @@ -131,6 +138,15 @@ public class AsUserController extends BaseController @PutMapping("/bandSystemUser") public AjaxResult bandSystemUser(@RequestBody AsUser user) { + Long sysUserId = user.getSysUserId(); + SysUser sysUser = sysUserService.selectUserById(sysUserId); + if(sysUser.getUserName().equals(SpringUtils.getRequiredProperty("et.repairAdmin"))){ + user.setRole("2"); + }else if(sysUser.getUserName().equals(SpringUtils.getRequiredProperty("et.operateAdmin"))){ + user.setRole("3"); + }else{ + user.setRole("1"); + } return toAjax(asUserService.bandSystemUser(user)); } diff --git a/electripper-admin/src/main/resources/application.yml b/electripper-admin/src/main/resources/application.yml index b779bc3..5314e02 100644 --- a/electripper-admin/src/main/resources/application.yml +++ b/electripper-admin/src/main/resources/application.yml @@ -222,7 +222,7 @@ iot: # token过期时间 daysToExpire: 100 # 推送消息token - token: tVpNdGKrAFHfKZNgpIWQfZukrcYHNfFM + token: JZWgouXXNcgTbxCyRCLKbQkKQMhyUrfL geo: # 高德地图key web服务 手续费 key: 834f1f029671d84272554528311ff0f1 @@ -231,3 +231,5 @@ et: handlingCharge: 4 verifyUrl: https://zidv2.market.alicloudapi.com/idcheck/Post appcode: 32b6c6445b1a42ed862dd4202392c47d + repairAdmin: wx + operateAdmin: root diff --git a/electripper-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java b/electripper-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java index adbc2c7..fc5f09f 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java +++ b/electripper-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java @@ -4,9 +4,11 @@ import com.wechat.pay.java.core.Config; import com.wechat.pay.java.core.RSAAutoCertificateConfig; import com.wechat.pay.java.core.notification.NotificationConfig; import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.service.brandprofitsharing.BrandProfitSharingService; import com.wechat.pay.java.service.payments.app.AppService; import com.wechat.pay.java.service.payments.jsapi.JsapiService; import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; +import com.wechat.pay.java.service.profitsharing.ProfitsharingService; import com.wechat.pay.java.service.refund.RefundService; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; @@ -122,4 +124,18 @@ public class WxPayConfig { return new RefundService.Builder().config(config).build(); } + // 分账服务 + @Bean + public ProfitsharingService brandProfitSharingService() { + Config config = new RSAAutoCertificateConfig.Builder() + .merchantId(merchantId) + // 使用 com.wechat.pay.java.core.util中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + .privateKeyFromPath(privateKeyPath) + .merchantSerialNumber(merchantSerialNumber) + .apiV3Key(apiV3Key) + .build(); + // 初始化服务 + return new ProfitsharingService.Builder().config(config).build(); + } + } diff --git a/electripper-common/src/main/java/com/ruoyi/common/constant/IotConstants.java b/electripper-common/src/main/java/com/ruoyi/common/constant/IotConstants.java index b276dd7..1417a92 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/constant/IotConstants.java +++ b/electripper-common/src/main/java/com/ruoyi/common/constant/IotConstants.java @@ -51,6 +51,66 @@ public class IotConstants { */ public static final String COMMAND_CLOSE = "close"; + /** + * 命令 subXX@ xx是上报时间修改,例如20 则上报20秒一次, 关闭订单之后为5倍的上报间隔也就是100秒上报一次数据 + */ + public static final String COMMAND_SUB = "sub"; + + /** + * 命令 超出营运区(禁行区)断电,不进行轮动检测 + */ + public static final String COMMAND_QLOSE = "qlose"; + + /** + * 命令 临时锁车,断电会进行轮动检测 + */ + public static final String COMMAND_LLOSE = "llose"; + + /** + * 命令 0欢迎 + */ + public static final String COMMAND_PLAY0 = "play0@"; + + /** + * 命令 1报警 + */ + public static final String COMMAND_PLAY1 = "play1@"; + + /** + * 命令 2营运边界 + */ + public static final String COMMAND_PLAY2 = "play2@"; + + /** + * 命令 3超出营运边界 + */ + public static final String COMMAND_PLAY3 = "play3@"; + + /** + * 命令 4车辆未解锁 + */ + public static final String COMMAND_PLAY4 = "play4@"; + + /** + * 命令 5超速 + */ + public static final String COMMAND_PLAY5 = "play5@"; + + /** + * 命令 6电量低 + */ + public static final String COMMAND_PLAY6 = "play6@"; + + /** + * 命令 7临时停车 + */ + public static final String COMMAND_PLAY7 = "play7@"; + + /** + * 命令 8使用结束 + */ + public static final String COMMAND_PLAY8 = "play8@"; + /**----------------------------命令end----------------------------*/ @@ -74,8 +134,8 @@ public class IotConstants { /**----------------------------启动模式end----------------------------*/ /** - * ONENET日志 + * ONENET定位日志 */ - public static final String ONENET_LOG = "LOG"; + public static final String ONENET_LOCATION = "sys"; } diff --git a/electripper-common/src/main/java/com/ruoyi/common/utils/CommonUtil.java b/electripper-common/src/main/java/com/ruoyi/common/utils/CommonUtil.java index 1580cd5..76367eb 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/utils/CommonUtil.java +++ b/electripper-common/src/main/java/com/ruoyi/common/utils/CommonUtil.java @@ -8,6 +8,8 @@ import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.common.utils.spring.SpringUtils; import lombok.extern.slf4j.Slf4j; +import java.math.BigDecimal; + /** * 业务工具类 * @@ -98,4 +100,42 @@ public class CommonUtil { } return list; } + + /** + * 根据电压计算续航里程 + * + * @param voltage 电压 + * @param fullVoltage 满电电压 + * @param lowVoltage 亏电电压 + * @param fullEndurance 满电续航里程 + * @author qzz + */ + public static Integer getRemainingMileage(String voltage,Integer fullVoltage,Integer lowVoltage,Integer fullEndurance) { + // 满电电压减去亏电电压 乘以 满电续航里程 除以 满电电压 + int current = (fullVoltage - Integer.parseInt(voltage)) ; + int full = (fullVoltage - lowVoltage) ; + BigDecimal divide = new BigDecimal(current).divide(new BigDecimal(full));//当前电量百分百 + log.info("当前电量百分百:{}%",divide.multiply(new BigDecimal(100))); + BigDecimal multiply = divide.multiply(new BigDecimal(fullEndurance)); + log.info("当前剩余续航里程:{}km",multiply); + return multiply.intValue(); + } + + /** + * 根据电压计算电量百分比 + * + * @param voltage 电压 + * @param fullVoltage 满电电压 + * @param lowVoltage 亏电电压 + * @author qzz + */ + public static Integer getElectricQuantity(String voltage,Integer fullVoltage,Integer lowVoltage) { + // 满电电压减去亏电电压 乘以 满电续航里程 除以 满电电压 + int current = (fullVoltage - Integer.parseInt(voltage)) ; + int full = (fullVoltage - lowVoltage) ; + BigDecimal divide = new BigDecimal(current).divide(new BigDecimal(full));//当前电量百分百 + BigDecimal multiply = divide.multiply(new BigDecimal(100)); + log.info("当前电量百分百:{}%",multiply); + return multiply.intValue(); + } } diff --git a/electripper-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/electripper-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 5a16226..b1ab75c 100644 --- a/electripper-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/electripper-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -121,6 +121,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter "/app/**", // "/appVerify/**", "/common/upload", + "/common/receive", "/payment/callback/**", "/loginByopenid").permitAll() // 静态资源,可匿名访问 diff --git a/electripper-system/src/main/java/com/ruoyi/system/domain/EtOperatingArea.java b/electripper-system/src/main/java/com/ruoyi/system/domain/EtOperatingArea.java index 91d966c..e81bc3f 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/domain/EtOperatingArea.java +++ b/electripper-system/src/main/java/com/ruoyi/system/domain/EtOperatingArea.java @@ -103,6 +103,7 @@ public class EtOperatingArea implements Serializable @Excel(name = "区") private String county; + /** 运营区域外断电:0-关闭;1-开启 */ @Excel(name = "运营区域外断电") private String areaOutOutage; @@ -112,6 +113,7 @@ public class EtOperatingArea implements Serializable @Excel(name = "电子围栏外还车调度") private String areaOutDispatch; + /** 禁行区内断电:0-关闭;1-开启 */ @Excel(name = "禁行区内断电") private String noRidingOutage; diff --git a/electripper-system/src/main/java/com/ruoyi/system/mapper/AsDeviceMapper.java b/electripper-system/src/main/java/com/ruoyi/system/mapper/AsDeviceMapper.java index 07248d6..9523a18 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/mapper/AsDeviceMapper.java +++ b/electripper-system/src/main/java/com/ruoyi/system/mapper/AsDeviceMapper.java @@ -28,7 +28,7 @@ public interface AsDeviceMapper extends BaseMapper * @param mac 设备列表主键 * @return 设备列表 */ -// public AsDevice selectAsDeviceByMac(String mac); + public AsDevice selectAsDeviceByMac(String mac); /** * 根据SN查询设备信息 diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/IAsDeviceService.java b/electripper-system/src/main/java/com/ruoyi/system/service/IAsDeviceService.java index 7192890..00c8d58 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/IAsDeviceService.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/IAsDeviceService.java @@ -2,6 +2,7 @@ package com.ruoyi.system.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.system.domain.AsDevice; +import com.ruoyi.system.domain.EtOperatingArea; import com.ruoyi.system.domain.EtOrder; import com.ruoyi.system.domain.response.OrderResponse; import com.ruoyi.system.domain.vo.DeviceNumVo; @@ -31,7 +32,7 @@ public interface IAsDeviceService extends IService * @param mac 设备主键 * @return 设备 */ -// public AsDevice selectAsDeviceByMac(String mac); + public AsDevice selectAsDeviceByMac(String mac); /** * 根据SN查询设备信息 @@ -193,4 +194,14 @@ public interface IAsDeviceService extends IService * 根据运营区查询车辆数量 */ Integer selectCountByAreaId(Long areaId); + + /** + * 判断是否在运营区 + */ + public Boolean isAreaZone(String sn, EtOperatingArea area); + + /** + * 判断是否在禁行区内 + */ + public boolean isNoRidingArea(String sn,Long areaId); } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/IWxPayService.java b/electripper-system/src/main/java/com/ruoyi/system/service/IWxPayService.java index b41f1cc..8341ce6 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/IWxPayService.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/IWxPayService.java @@ -3,9 +3,14 @@ package com.ruoyi.system.service; import com.ruoyi.system.domain.EtOrder; import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.profitsharing.model.AddReceiverResponse; +import com.wechat.pay.java.service.profitsharing.model.CreateOrderReceiver; +import com.wechat.pay.java.service.profitsharing.model.DeleteReceiverResponse; +import com.wechat.pay.java.service.profitsharing.model.OrdersEntity; import com.wechat.pay.java.service.refund.model.Refund; import java.math.BigDecimal; +import java.util.List; /** * 微信支付服务接口 @@ -51,9 +56,23 @@ public interface IWxPayService { Refund refund(EtOrder etOrder, String reason, BigDecimal amount); -// /** -// * 微信支付通知 -// * @return 是否成功 -// */ -// void payNotify(HttpServletRequest request); + /** + * 请求分账API + * @param transactionId 微信支付单号 + * @param receivers 分账接收方 + */ + public OrdersEntity createOrder(String transactionId, List receivers); + + /** + * 添加分账接收方 + * @param wxopenid openid + */ + AddReceiverResponse addReceiver(String wxopenid); + + /** + * 删除分账接收方 + * @param wxopenid openid + */ + DeleteReceiverResponse deleteReceiver(String wxopenid); + } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/AsDeviceServiceImpl.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/AsDeviceServiceImpl.java index 18718a8..74e0101 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/AsDeviceServiceImpl.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/AsDeviceServiceImpl.java @@ -114,6 +114,17 @@ public class AsDeviceServiceImpl extends ServiceImpl i return asDeviceMapper.selectAsDeviceByDeviceId(deviceId); } + /** + * 根据mac查询车辆实时信息 + * + * @param mac 设备mac + * @return 设备 + */ + @Override + public AsDevice selectAsDeviceByMac(String mac) { + return asDeviceMapper.selectAsDeviceByMac(mac); + } + /** * 根据sn号查询车辆实时信息 * @@ -453,7 +464,14 @@ public class AsDeviceServiceImpl extends ServiceImpl i String finalOrderNo = orderNo; Boolean execute = transactionTemplate.execute(e -> { /** 2.发送命令*/ -// sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_OPEN,"编号开锁"); + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_OPEN,"编号开锁"); + //间隔1秒 + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_PLAY0,"编号开锁播报"); /** 3.更新车辆状态*/ asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN); asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_IN_USING); @@ -516,6 +534,13 @@ public class AsDeviceServiceImpl extends ServiceImpl i Boolean execute = transactionTemplate.execute(e -> { /** 2.发送命令*/ sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_OPEN,"管理员开锁"); + //间隔1秒 + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_PLAY0,"管理员开锁播报"); return Boolean.TRUE; }); if(!execute)throw new ServiceException("管理员开锁失败"); @@ -663,7 +688,7 @@ public class AsDeviceServiceImpl extends ServiceImpl i String token = Token.getToken(); Boolean execute = transactionTemplate.execute(e -> { /** 2.发送命令*/ - sendCommand(asDevice.getMac(), token,"响铃命令","响铃寻车"); + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_PLAY1,"响铃寻车"); return Boolean.TRUE; }); if(!execute)throw new ServiceException("响铃寻车失败"); @@ -689,9 +714,15 @@ public class AsDeviceServiceImpl extends ServiceImpl i String token = Token.getToken(); String finalSn = sn; Boolean execute = transactionTemplate.execute(e -> { - /** TODO 临时锁车*/ /** 2.发送命令*/ sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_CLOSE,"临时锁车"); + //间隔1秒 + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_PLAY7,"临时锁车播报"); if(StrUtil.isNotBlank(orderNo)){//有订单号,则是用户临时锁车 /** 改变车辆状态:4-临时锁车 */ asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_TEMPORARILY_LOCK);//临时锁车 @@ -741,6 +772,13 @@ public class AsDeviceServiceImpl extends ServiceImpl i /** TODO 临时解锁*/ /** 2.发送命令*/ sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_OPEN,"临时解锁"); + //间隔1秒 + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_PLAY0,"临时解锁播报"); if(StrUtil.isNotBlank(orderNo)){//有订单号,则是用户骑行中解锁 /** 改变车辆状态:3-骑行中 */ asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_IN_USING);//骑行中 @@ -811,6 +849,13 @@ public class AsDeviceServiceImpl extends ServiceImpl i //1.发送开锁命令并更新车辆状态 String token = Token.getToken(); sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_CLOSE,"取消预约关锁"); + //间隔1秒 + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + sendCommand(asDevice.getMac(), token,IotConstants.COMMAND_PLAY8,"取消预约关锁播报"); /** 5.记录行程*/ int tripLog = tripLogService.tripLog(order.getOrderNo(),order.getSn(),ServiceConstants.TRIP_LOG_TYPE_UNLOCK_RIDE); if(tripLog==0){ @@ -896,7 +941,14 @@ public class AsDeviceServiceImpl extends ServiceImpl i String token = Token.getToken(); AsDevice device = asDeviceMapper.selectAsDeviceBySn(order.getSn()); /** 2. 车辆远程关锁*/ -// sendCommand(device.getMac(), token,IotConstants.COMMAND_CLOSE,"还车关锁"); + sendCommand(device.getMac(), token,IotConstants.COMMAND_CLOSE,"还车关锁"); + //间隔1秒 + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + sendCommand(device.getMac(), token,IotConstants.COMMAND_PLAY8,"还车关锁播报"); /** 4. 更新车辆状态*/ device.setStatus(ServiceConstants.VEHICLE_STATUS_NORMAL); device.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); @@ -1170,7 +1222,8 @@ public class AsDeviceServiceImpl extends ServiceImpl i /** * 是否在运营区内 */ - private Boolean isAreaZone(String sn,EtOperatingArea area) { + @Override + public Boolean isAreaZone(String sn,EtOperatingArea area) { Boolean inCircle = false; AsDevice device = asDeviceMapper.selectAsDeviceBySn(sn); String latitude = device.getLatitude(); @@ -1216,4 +1269,36 @@ public class AsDeviceServiceImpl extends ServiceImpl i } return inCircle; } + + /** + * 是否禁行区内 + */ + @Override + public boolean isNoRidingArea(String sn,Long areaId) { + Boolean isNoRiding = false; + EtParkingArea parkingArea = new EtParkingArea(); + parkingArea.setAreaId(areaId); + List parkingAreas = parkingAreaService.selectEtParkingAreaList(parkingArea); + if(ObjectUtil.isNull(parkingAreas) || parkingAreas.size() == 0){ + log.info("运营区【{}】没有禁行区,",areaId); + throw new ServiceException("运营区【{}】没有禁行区"+areaId.toString()); + } + for (EtParkingArea etParkingArea : parkingAreas) { + if(etParkingArea.getType().equals(ServiceConstants.PARKING_AREA_TYPE_BANNED_RIDING)){ + AsDevice device = asDeviceMapper.selectAsDeviceBySn(sn); + String latitude = device.getLatitude(); + String longitude = device.getLongitude(); + Geometry geometry = GeoUtils.fromWkt(etParkingArea.getBoundary()); + isNoRiding = GeoUtils.isInCircle(longitude, latitude, geometry); + if(isNoRiding){ + log.info("车辆【{}】在禁行区【{}】内",sn,etParkingArea.getParkingName()); + isNoRiding = true; + break; + }else{ + log.info("车辆【{}】不在禁行区【{}】内",sn,etParkingArea.getParkingName()); + } + } + } + return isNoRiding; + } } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java index e1bb73f..771a314 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java @@ -37,6 +37,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.util.*; @@ -83,10 +84,7 @@ public class CallbackServiceImpl implements CallbackService { @Value("${et.handlingCharge}") private String handlingCharge; -// @Autowired -// private ISysUserService sysUserService; - - @Autowired + @Resource private SysUserMapper userMapper; @@ -101,6 +99,7 @@ public class CallbackServiceImpl implements CallbackService { logger.info("【微信支付回调】接收对象 : " + JSON.toJSONString(body)); // 解析通知数据 Notification notification = JSON.parseObject(body, Notification.class); + String outTradeNo; // 支付成功通知 if (NotifyEventType.TRANSACTION_SUCCESS.getValue().equals(notification.getEventType())) { @@ -110,7 +109,7 @@ public class CallbackServiceImpl implements CallbackService { // 充值成功后的业务处理 AttachVo attachVo = JSONObject.parseObject(transaction.getAttach(),AttachVo.class); logger.info("【微信支付回调】附加信息 : " + JSON.toJSONString(attachVo)); - String outTradeNo = transaction.getOutTradeNo(); + outTradeNo = transaction.getOutTradeNo(); EtOrder order = orderService.selectEtOrderByOutTradeNo(outTradeNo); logger.info("【微信支付回调】订单信息 : " + JSON.toJSONString(order)); @@ -194,36 +193,6 @@ public class CallbackServiceImpl implements CallbackService { order.setMark("取消预约支付"); asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_NORMAL);//取消预约支付后车辆正常运营 asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); - }else if(attachVo.getType().equals(ServiceConstants.BUSINESS_TYPE_MEAL)){//废弃 - logger.info("【微信支付回调】套餐支付"); - // 3-套餐支付 套餐支付中分为预约车辆和立即开锁骑行 - if(attachVo.getIsAppointment()){//购买套餐后预约 - order.setStatus(ServiceConstants.ORDER_STATUS_IN_APPOINTMENT); - order.setAppointmentStartTime(DateUtils.getNowDate()); - order.setMark("套餐预约支付"); - /** 2.发送命令*/ -// asDeviceService.sendCommand(asDevice.getMac(), iotToken,IotConstants.COMMAND_CLOSE,"套餐预约"); - /** 3.更新车辆状态*/ - asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_IN_APPOINTMENT);//预约中 - asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); - logger.info("【微信支付回调】套餐预约成功"); - }else{ - order.setMark("套餐骑行支付"); - order.setStatus(ServiceConstants.ORDER_STATUS_RIDING);//骑行中 - /** 2.发送命令*/ -// asDeviceService.sendCommand(asDevice.getMac(), iotToken,IotConstants.COMMAND_OPEN,"套餐开锁"); - /** 3.更新车辆状态*/ - asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN); - asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_IN_USING); - } - EtFeeRule etFeeRule = etFeeRuleService.selectEtFeeRuleByRuleId(order.getRuleId()); - if(ObjectUtil.isNull(etFeeRule)){ - throw new ServiceException("套餐不存在"); - } -// Integer time = etFeeRule.getTime();//时间:以小时为单位 -// //当前时间往后推time个小时 -// Date ruleEndTime = DateUtils.addHours(DateUtils.getNowDate(),time); -// order.setRuleEndTime(ruleEndTime); }else if(attachVo.getType().equals(ServiceConstants.BUSINESS_TYPE_DEPOSIT)){ logger.info("【微信支付回调】押金支付"); // 4-押金支付 @@ -242,6 +211,7 @@ public class CallbackServiceImpl implements CallbackService { } }else{ logger.error("【微信支付回调】 : 支付场景不存在"); + throw new ServiceException("【微信支付回调】支付场景不存在"); } if(ObjectUtil.isNotNull(asDevice)){ int device = asDeviceService.updateAsDevice(asDevice); @@ -260,6 +230,8 @@ public class CallbackServiceImpl implements CallbackService { logger.error("【微信支付回调】更新用户押金失败"); throw new ServiceException("【微信支付回调】更新用户押金失败"); } + // 调用任务调度方法处理分账 一分钟后执行 +// paymentService.scheduleProfitSharing(outTradeNo); } } } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java index 471fc14..bc56dfa 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java @@ -351,7 +351,7 @@ public class EtOrderServiceImpl implements IEtOrderService /** * 预下单 - * 类型 1骑行 2预约 3套餐 4押金 + * 类型 1骑行 2预约 4押金 * 获取到订单信息后,计算金额,调用微信支付接口,返回预支付信息 */ @Transactional @@ -378,56 +378,6 @@ public class EtOrderServiceImpl implements IEtOrderService if(i == 0){ throw new ServiceException("订单生成失败"); } - }else if(payType.equals("3")){ - log.info("【预下单】支付场景为:套餐支付"); - String orderNo = IdUtils.randomUUID2(); - // 套餐订单,生成订单后根据 - etOrder = createOrder(order, orderNo); - verify(order, order.getUserId()); - /** 1.获取token*/ - String token = Token.getToken(); - AsDevice asDevice = asDeviceService.selectAsDeviceBySn(order.getSn()); -// if(order.getIsAppointment()){//购买完套餐后 预约车辆 - etOrder.setStatus(ServiceConstants.ORDER_STATUS_IN_APPOINTMENT); - etOrder.setAppointmentStartTime(DateUtils.getNowDate()); - //校验 userI,sn,ruleId,type 3 isAppointment - Boolean execute = transactionTemplate.execute(e -> { - /** 2.发送命令*/ - deviceService.sendCommand(asDevice.getMac(), token, IotConstants.COMMAND_CLOSE,"套餐预约"); - /** 3.更新车辆状态*/ - asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_TEMPORARILY_LOCK);//临时锁车 - asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN); - int device = asDeviceService.updateAsDevice(asDevice); - if(device==0){ - log.info("【套餐预约】更新车辆状态失败"); - return Boolean.FALSE; - } - log.info("套餐预约成功"); - return Boolean.TRUE; - }); - if(!execute)throw new ServiceException("套餐预约失败"); -// }else{//购买完套餐后 立即开锁骑行 -// etOrder.setStatus(ServiceConstants.ORDER_STATUS_RIDING); -// Boolean execute = transactionTemplate.execute(e -> { -// /** 2.发送命令*/ -// deviceService.sendCommand(order.getSn(), token,IotConstants.COMMAND_OPEN,"套餐开锁"); -// /** 3.更新车辆状态*/ -// asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN); -// asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_IN_USING_STR); -// int device = asDeviceService.updateAsDevice(asDevice); -// if(device==0){ -// log.info("【套餐开锁】更新车辆状态失败"); -// return Boolean.FALSE; -// } -// log.info("套餐开锁成功"); -// return Boolean.TRUE; -// }); -// if(!execute)throw new ServiceException("套餐开锁失败"); -// } - int i = etOrderMapper.insertEtOrder(etOrder); - if(i == 0){ - throw new ServiceException("订单生成失败"); - } }else if(payType.equals("2")){ log.info("【预下单】支付场景为:取消预约支付"); etOrder = etOrderMapper.selectEtOrderByOrderNo(order.getOrderNo()); diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java index fe85f4d..978e55f 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java @@ -23,6 +23,8 @@ import com.wechat.pay.java.service.payments.jsapi.JsapiService; import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; import com.wechat.pay.java.service.payments.jsapi.model.*; import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.profitsharing.ProfitsharingService; +import com.wechat.pay.java.service.profitsharing.model.*; import com.wechat.pay.java.service.refund.RefundService; import com.wechat.pay.java.service.refund.model.AmountReq; import com.wechat.pay.java.service.refund.model.CreateRequest; @@ -32,8 +34,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionTemplate; +import javax.management.relation.RelationType; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; /** * 微信支付服务 @@ -71,6 +76,9 @@ public class WxPayService implements IWxPayService { @Autowired public RefundService refundService2; + @Autowired + public ProfitsharingService profitsharingService; + @Autowired private RedisLock redisLock; @@ -157,65 +165,45 @@ public class WxPayService implements IWxPayService { return refundService2.create(request); } -// /** -// * 微信支付通知 -// * @param request 请求 -// */ -// @Override -// @Transactional -// public void payNotify(HttpServletRequest request) { -// -// String body = HttpUtils.getBody(request); -// // 解析通知数据 -// Notification notification = JSON.parseObject(body, Notification.class); -// -// // 判断是否重复通知,重复通知则忽略 -// if (isRepeatNotify(notification.getId())) { -// return; -// } -// -// // 支付成功通知 -// if (NotifyEventType.TRANSACTION_SUCCESS.getValue().equals(notification.getEventType())) { -// // 验签、解密并转换成 Transaction -// Transaction transaction = checkAndParse(request, body, Transaction.class); -// -// if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) { -// SmTransactionBill bill = transactionBillService.selectSmTransactionBillByBillNo(transaction.getOutTradeNo()); -// ServiceUtil.assertion(bill == null, "订单不存在"); -// -// // 充值成功,修改订单状态 -// transactionBillService.rechargeSuccess(bill.getBillId(), DateUtils.getNowDate()); -// // 保存通知数据 -// saveNotifyData(bill.getBillId(), notification, transaction); -// } -// } -// -// } + /** 请求分账API */ + public OrdersEntity createOrder(String transactionId,List receivers) { + CreateOrderRequest request = new CreateOrderRequest(); + request.setAppid(wxPayConfig.getAppId()); + request.setTransactionId(transactionId);// 微信订单号 + request.setOutOrderNo(IdUtils.getOrderNo("fz"));// 商户系统内部分账单号 -// /** -// * 通知是否重复 -// * @param notifyId 通知id -// */ -// private boolean isRepeatNotify(String notifyId) { -// SmWxPayNotify repeat = smWxPayNotifyMapper.selectSmWxPayNotifyByNotifyId(notifyId); -// return repeat != null; -// } +// List receivers = new ArrayList<>(); +// CreateOrderReceiver receiver = new CreateOrderReceiver(); +// receiver.setType(ReceiverType.PERSONAL_OPENID.name()); +// receiver.setAccount("openid"); +// receiver.setAccount("0.01"); +// receiver.setDescription("描述"); +// receivers.add(receiver); + request.setReceivers(receivers); + request.setUnfreezeUnsplit(false); + return profitsharingService.createOrder(request); + } + + /** 添加分账接收方 */ + @Override + public AddReceiverResponse addReceiver(String wxopenid) { + AddReceiverRequest request = new AddReceiverRequest(); + request.setAppid(wxPayConfig.getAppId()); + request.setType(ReceiverType.PERSONAL_OPENID); + request.setAccount(wxopenid); + request.setRelationType(ReceiverRelationType.PARTNER); + return profitsharingService.addReceiver(request); + } + + /** 删除分账接收方 */ + public DeleteReceiverResponse deleteReceiver(String wxopenid) { + DeleteReceiverRequest request = new DeleteReceiverRequest(); + request.setAppid(wxPayConfig.getAppId()); + request.setType(ReceiverType.PERSONAL_OPENID); + request.setAccount(wxopenid); + return profitsharingService.deleteReceiver(request); + } -// /** -// * 保存通知数据 -// * @param billId 订单id -// * @param body 请求体 -// * @param plaintext 明文 -// */ -// private void saveNotifyData(long billId, Notification body, Object plaintext) { -// SmWxPayNotify data = new SmWxPayNotify(); -// data.setNotifyId(body.getId()); -// data.setBillId(billId); -// data.setBody(JSON.toJSONString(body)); -// data.setPlaintext(JSON.toJSONString(plaintext)); -// int i = smWxPayNotifyMapper.insertSmWxPayNotify(data); -// ServiceUtil.assertion(i != 1, "保存通知数据失败"); -// } /** * 验签并解析 diff --git a/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java b/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java index 571dc17..b7a733c 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java +++ b/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java @@ -23,6 +23,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.IntStream; /** * 定时任务调度测试 @@ -75,6 +76,7 @@ public class EtTask { order.setEndTime(endDateStr); order.setPaid(ServiceConstants.ORDER_PAY_STATUS_PAID); order.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); + order.setType(ServiceConstants.ORDER_TYPE_RIDING); List orderListByDate = etOrderMapper.selectEtOrderList(order); for(EtOrder order1:orderListByDate){ EtDividendDetail etDividendDetail = new EtDividendDetail(); @@ -89,15 +91,33 @@ public class EtTask { etDividendDetail.setPartnerId(user.getUserId()); etDividendDetail.setOrderNo(order1.getOrderNo()); etDividendDetail.setTotalAmount(order1.getTotalFee()); - etDividendDetail.setDividendAmount(order1.getTotalFee().multiply(new BigDecimal(user.getDividendProportion()).divide(new BigDecimal(100),2, BigDecimal.ROUND_HALF_UP))); - etDividendDetail.setDividendProportion(user.getDividendProportion()); etDividendDetail.setCreateTime(DateUtils.getNowDate()); + etDividendDetail.setDividendProportion(user.getDividendProportion()); + // todo 分账金额是骑行费,还是调度费,看分账项目 + etDividendDetail.setDividendAmount(order1.getTotalFee().multiply(new BigDecimal(user.getDividendProportion()).divide(new BigDecimal(100),2, BigDecimal.ROUND_HALF_UP))); etDividendDetail.setDividendItem(user.getDividendItem()); int i = dividendDetailService.insertEtDividendDetail(etDividendDetail); if(i==0){ throw new ServiceException("保存分账明细失败"); } } + int totalDividendProportion = IntStream.of(sysUsers.stream() + .mapToInt(SysUser::getDividendProportion) + .toArray()) + .sum(); + //算运营商自己的分账 + etDividendDetail.setAreaId(area.getAreaId()); + etDividendDetail.setPartnerId(0L); + etDividendDetail.setOrderNo(order1.getOrderNo()); + etDividendDetail.setTotalAmount(order1.getTotalFee()); + etDividendDetail.setCreateTime(DateUtils.getNowDate()); + etDividendDetail.setDividendAmount(order1.getTotalFee().multiply(new BigDecimal(100-totalDividendProportion).divide(new BigDecimal(100),2, BigDecimal.ROUND_HALF_UP))); + etDividendDetail.setDividendProportion(100-totalDividendProportion); + etDividendDetail.setDividendItem("运营商"); + int i = dividendDetailService.insertEtDividendDetail(etDividendDetail); + if(i==0){ + throw new ServiceException("保存分账明细失败"); + } } }