ws、套餐金额计算
This commit is contained in:
parent
62cef22f1f
commit
95aedfeb9c
|
@ -26,4 +26,9 @@ public enum BonusStatus {
|
|||
return CollectionUtils.map(BonusStatus::getStatus, WAIT_DIVIDE, DIVIDEND);
|
||||
}
|
||||
|
||||
// 允许打款的分成状态
|
||||
public static List<String> canPay() {
|
||||
return CollectionUtils.map(BonusStatus::getStatus, WAIT_DIVIDE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -98,6 +98,13 @@ public interface BonusService
|
|||
*/
|
||||
public int payBonus(BonusVO bonus);
|
||||
|
||||
/**
|
||||
* 分成打款
|
||||
* @param id 分成明细ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int payBonus(Long id);
|
||||
|
||||
/**
|
||||
* 进行分成打款指定时间之前的分成
|
||||
* @param time 时间
|
||||
|
|
|
@ -265,9 +265,9 @@ public class BonusServiceImpl implements BonusService
|
|||
|
||||
@Override
|
||||
public int payBonus(BonusVO bonus) {
|
||||
if (bonus == null) {
|
||||
return 0;
|
||||
}
|
||||
ServiceUtil.assertion(bonus == null, "待打款的分成不存在");
|
||||
ServiceUtil.assertion(!BonusStatus.canPay().contains(bonus.getStatus()), "ID为%s的分成当前状态不允许打款", bonus.getId());
|
||||
|
||||
Integer result = transactionTemplate.execute(status -> {
|
||||
// 更新分成状态为已分成
|
||||
int pay = bonusMapper.pay(bonus.getId(), bonus.getWaitAmount(), LocalDateTime.now());
|
||||
|
@ -288,6 +288,16 @@ public class BonusServiceImpl implements BonusService
|
|||
return result == null ? 0 : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int payBonus(Long id) {
|
||||
if (id == null) {
|
||||
return 0;
|
||||
}
|
||||
BonusVO bonus = this.selectBonusById(id);
|
||||
return this.payBonus(bonus);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean refundByBst(BonusBstType bstType, Long bstId, BigDecimal refundAmount, BigDecimal payAmount, String reason) {
|
||||
if (bstType == null || bstId == null || refundAmount == null || payAmount == null) {
|
||||
|
|
|
@ -128,13 +128,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
and #{query.radius} >= round(st_distance_sphere(point(#{query.center[0]}, #{query.center[1]}), point(bd.longitude, bd.latitude)))
|
||||
</if>
|
||||
<if test="query.powerRange != null and query.powerRange.size() == 2">
|
||||
and bd.remaining_power >= #{query.powerRange[0]}
|
||||
and bd.remaining_power >= #{query.powerRange[0]}
|
||||
and bd.remaining_power <= #{query.powerRange[1]}
|
||||
</if>
|
||||
<if test="query.nonOrOrderStatusList != null and query.nonOrOrderStatusList.size() > 0">
|
||||
and (
|
||||
bd.order_device_id is null
|
||||
or bo.status in
|
||||
or bo.status in
|
||||
<foreach item="item" collection="query.nonOrOrderStatusList" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
|
@ -381,12 +381,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</where>
|
||||
</select>
|
||||
|
||||
<!-- resetModel -->
|
||||
<!-- resetModel -->
|
||||
|
||||
<update id="resetModel">
|
||||
update bst_device
|
||||
set model_id = null
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
|
||||
</mapper>
|
||||
|
|
|
@ -162,5 +162,12 @@ public interface DeviceService
|
|||
*/
|
||||
public DeviceVO selectAvaliableDevice(Long id, String sn);
|
||||
|
||||
/**
|
||||
* 根据mac查询设备
|
||||
* @param mac
|
||||
* @return
|
||||
*/
|
||||
public DeviceVO selectByMac(String mac);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,4 +18,12 @@ public interface DeviceValidator {
|
|||
*/
|
||||
boolean canDeleteAll(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 用户是否有权限访问设备的websocket链接
|
||||
* @param mac
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
boolean canLink(String mac, Long userId);
|
||||
|
||||
}
|
||||
|
|
|
@ -465,4 +465,14 @@ public class DeviceServiceImpl implements DeviceService
|
|||
}
|
||||
return this.selectOne(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceVO selectByMac(String mac) {
|
||||
if (StringUtils.isBlank(mac)) {
|
||||
return null;
|
||||
}
|
||||
DeviceQuery query = new DeviceQuery();
|
||||
query.setEqMac(mac);
|
||||
return this.selectOne(query);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ruoyi.bst.device.domain.DeviceQuery;
|
||||
import com.ruoyi.bst.device.domain.DeviceVO;
|
||||
import com.ruoyi.bst.device.mapper.DeviceMapper;
|
||||
import com.ruoyi.bst.device.service.DeviceService;
|
||||
import com.ruoyi.bst.device.service.DeviceValidator;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.collection.CollectionUtils;
|
||||
|
||||
@Service
|
||||
|
@ -18,6 +21,9 @@ public class DeviceValidatorImpl implements DeviceValidator {
|
|||
@Autowired
|
||||
private DeviceMapper deviceMapper;
|
||||
|
||||
@Autowired
|
||||
private DeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public boolean canEdit(Long id) {
|
||||
return canOperate(Collections.singletonList(id));
|
||||
|
@ -42,4 +48,38 @@ public class DeviceValidatorImpl implements DeviceValidator {
|
|||
return new HashSet<>(idList).containsAll(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canLink(String mac, Long userId) {
|
||||
if (StringUtils.isBlank(mac) || userId == null) {
|
||||
return false;
|
||||
}
|
||||
DeviceVO device = deviceService.selectByMac(mac);
|
||||
return canView(device, userId);
|
||||
}
|
||||
|
||||
// 是否可以查看设备
|
||||
private boolean canView(DeviceVO device, Long userId) {
|
||||
return isMch(device, userId) || isAreaUser(device, userId) || isOrderUser(device, userId) || isAreaAgent(device, userId);
|
||||
}
|
||||
|
||||
// 是否是商户
|
||||
private boolean isMch(DeviceVO device, Long userId) {
|
||||
return device != null && device.getMchId() != null && device.getMchId().equals(userId);
|
||||
}
|
||||
|
||||
// 是否是运营区管理员
|
||||
private boolean isAreaUser(DeviceVO device, Long userId) {
|
||||
return device.getAreaUserId() != null && device.getAreaUserId().equals(userId);
|
||||
}
|
||||
|
||||
// 是否是订单用户
|
||||
private boolean isOrderUser(DeviceVO device, Long userId) {
|
||||
return device.getOrderUserId() != null && device.getOrderUserId().equals(userId);
|
||||
}
|
||||
|
||||
// 是否是运营区代理
|
||||
private boolean isAreaAgent(DeviceVO device, Long userId) {
|
||||
return device.getAreaAgentId() != null && device.getAreaAgentId().equals(userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -87,12 +87,15 @@ public class SuitValidatorImpl implements SuitValidator {
|
|||
ServiceUtil.assertion(rule.getFee() == null, "费用不能为空");
|
||||
ServiceUtil.assertion(rule.getStart() < 0, "区间开始时间不能小于0");
|
||||
ServiceUtil.assertion(rule.getEachUnit() <= 0, "计费间隔必须大于0");
|
||||
ServiceUtil.assertion(rule.getFee().compareTo(BigDecimal.ZERO) <= 0, "费用不能小于等于0");
|
||||
ServiceUtil.assertion(rule.getFee().compareTo(BigDecimal.ZERO) <= 0, "费用不能小于或者等于0");
|
||||
if (i < intervalRules.size() - 1) {
|
||||
SuitIntervalFeeRule nextRule = intervalRules.get(i + 1);
|
||||
ServiceUtil.assertion(rule.getEnd() == null, "区间结束时间不能为空");
|
||||
ServiceUtil.assertion(rule.getStart() > rule.getEnd(), "区间的结束时间不允许小于开始时间");
|
||||
ServiceUtil.assertion(rule.getEnd() != nextRule.getStart(), "区间的结束时间必须等于下一个区间的开始时间");
|
||||
int eachUnit = rule.getEachUnit();
|
||||
int duration = rule.getEnd() - rule.getStart();
|
||||
ServiceUtil.assertion(duration % eachUnit != 0, "区间间隔必须能够被区间整除");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("第" + (i + 1) + "条收费规则校验失败:" + e.getMessage());
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.ruoyi.bst.suit.utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.bst.suit.domain.SuitVO;
|
||||
|
@ -35,15 +37,22 @@ public class SuitUtil {
|
|||
return 0;
|
||||
}
|
||||
|
||||
long seconds = 0;
|
||||
// 起步价计费
|
||||
if (SuitRidingRule.START_FEE.getCode().equals(suit.getRidingRule())) {
|
||||
return calcTotalTimeForStartFee(depositAmount, suit.getStartRule(), rentalUnit);
|
||||
seconds = calcTotalTimeForStartFee(depositAmount, suit.getStartRule(), rentalUnit);
|
||||
}
|
||||
// 区间计费
|
||||
else if (SuitRidingRule.INTERVAL_FEE.getCode().equals(suit.getRidingRule())) {
|
||||
return calcTotalTimeForIntervalFee(depositAmount, suit.getIntervalRule(), rentalUnit);
|
||||
seconds = calcTotalTimeForIntervalFee(depositAmount, suit.getIntervalRule(), rentalUnit);
|
||||
}
|
||||
return 0;
|
||||
|
||||
// 不允许时间低于免费时长
|
||||
if (suit.getFreeRideTime() != null) {
|
||||
seconds = Math.max(seconds, suit.getFreeRideTime() * 60);
|
||||
}
|
||||
|
||||
return seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,252 +111,117 @@ public class SuitUtil {
|
|||
}
|
||||
|
||||
// 按照区间开始时间排序
|
||||
intervalFeeRules.sort((a, b) -> Integer.compare(a.getStart(), b.getStart()));
|
||||
intervalFeeRules.sort(Comparator.comparingInt(SuitIntervalFeeRule::getStart));
|
||||
|
||||
BigDecimal remainingAmount = depositAmount;
|
||||
long totalTime = 0;
|
||||
BigDecimal amount = depositAmount; // 剩余金额
|
||||
int totalTime = 0; // 总时长
|
||||
|
||||
// 获取单位对应的秒数
|
||||
long unitSeconds = rentalUnit.getSeconds();
|
||||
|
||||
for (SuitIntervalFeeRule rule : intervalFeeRules) {
|
||||
// 处理最后一个区间的end可能为空的情况
|
||||
if ( intervalFeeRules.indexOf(rule) == intervalFeeRules.size() - 1) {
|
||||
// 如果是最后一个区间且end为0,可以认为是无限长
|
||||
// 这里我们可以计算剩余金额能买多少时间
|
||||
BigDecimal eachUnitFee = rule.getFee();
|
||||
int eachUnitTime = rule.getEachUnit();
|
||||
|
||||
if (eachUnitFee.compareTo(BigDecimal.ZERO) > 0 && eachUnitTime > 0) {
|
||||
// 计算剩余金额可以购买的完整单位数
|
||||
BigDecimal completeUnits = remainingAmount.divide(eachUnitFee, 0, BigDecimal.ROUND_DOWN);
|
||||
totalTime += completeUnits.multiply(BigDecimal.valueOf(eachUnitTime * unitSeconds)).longValue();
|
||||
|
||||
// 计算最后不足一个计费单位的时间(按比例计算)
|
||||
BigDecimal remainingAmountForLastUnit = remainingAmount.remainder(eachUnitFee);
|
||||
if (remainingAmountForLastUnit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
totalTime += remainingAmountForLastUnit
|
||||
.multiply(BigDecimal.valueOf(eachUnitTime * unitSeconds))
|
||||
.divide(eachUnitFee, 0, BigDecimal.ROUND_DOWN)
|
||||
.longValue();
|
||||
}
|
||||
}
|
||||
|
||||
break; // 处理完最后一个区间后退出循环
|
||||
}
|
||||
|
||||
// 计算当前区间的时间范围(单位)
|
||||
int end = rule.getEnd();
|
||||
int intervalDuration = end - rule.getStart();
|
||||
if (intervalDuration <= 0) {
|
||||
continue; // 跳过无效区间
|
||||
}
|
||||
|
||||
// 计算当前区间内可以购买的完整计费单位数量
|
||||
BigDecimal eachUnitFee = rule.getFee();
|
||||
int eachUnitTime = rule.getEachUnit();
|
||||
|
||||
if (eachUnitFee.compareTo(BigDecimal.ZERO) <= 0 || eachUnitTime <= 0) {
|
||||
for (int i = 0; i < intervalFeeRules.size(); i++) {
|
||||
SuitIntervalFeeRule rule = intervalFeeRules.get(i);
|
||||
BigDecimal fee = rule.getFee(); // 每个时间段的金额
|
||||
int eachUnitTime = rule.getEachUnit(); // 每个时间段的时长
|
||||
if (fee.compareTo(BigDecimal.ZERO) <= 0 || eachUnitTime <= 0) {
|
||||
continue; // 跳过无效计费规则
|
||||
}
|
||||
// 计算可以购买几个区间,金额不足也按1个单位计算
|
||||
int count = amount.divide(fee, 0, RoundingMode.UP).intValue();
|
||||
|
||||
// 计算当前区间内可以购买的完整单位数
|
||||
int maxUnitsInInterval = intervalDuration / eachUnitTime;
|
||||
BigDecimal maxCostForInterval = eachUnitFee.multiply(BigDecimal.valueOf(maxUnitsInInterval));
|
||||
// 非最后节点,限制最大购买数量
|
||||
if (i < intervalFeeRules.size() - 1) {
|
||||
int duration =rule.getEnd() - rule.getStart();
|
||||
// 计算最大金额
|
||||
int maxCount = BigDecimal.valueOf(duration).divide(BigDecimal.valueOf(eachUnitTime), 0, RoundingMode.UP).intValue();
|
||||
count = Math.min(count, maxCount);
|
||||
}
|
||||
|
||||
// 如果剩余金额足够支付整个区间
|
||||
if (remainingAmount.compareTo(maxCostForInterval) >= 0) {
|
||||
totalTime += intervalDuration * unitSeconds;
|
||||
remainingAmount = remainingAmount.subtract(maxCostForInterval);
|
||||
} else {
|
||||
// 计算剩余金额可以购买的完整单位数
|
||||
BigDecimal completeUnits = remainingAmount.divide(eachUnitFee, 0, BigDecimal.ROUND_DOWN);
|
||||
totalTime += completeUnits.multiply(BigDecimal.valueOf(eachUnitTime * unitSeconds)).longValue();
|
||||
// 记入时长,并扣减金额
|
||||
totalTime += count * eachUnitTime;
|
||||
amount = amount.subtract(fee.multiply(BigDecimal.valueOf(count)));
|
||||
|
||||
// 计算最后不足一个计费单位的时间(按比例计算)
|
||||
BigDecimal remainingAmountForLastUnit = remainingAmount.remainder(eachUnitFee);
|
||||
if (remainingAmountForLastUnit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
totalTime += remainingAmountForLastUnit
|
||||
.multiply(BigDecimal.valueOf(eachUnitTime * unitSeconds))
|
||||
.divide(eachUnitFee, 0, BigDecimal.ROUND_DOWN)
|
||||
.longValue();
|
||||
}
|
||||
|
||||
// 已经用完所有金额,退出循环
|
||||
// 金额不足则跳出循环
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算已使用时长所需的金额
|
||||
* @param suit 套餐
|
||||
* @param usedTime 已使用时长(秒)
|
||||
* @return 所需金额
|
||||
*/
|
||||
public static BigDecimal calcAmount(SuitVO suit, long usedTime) {
|
||||
if (suit == null || usedTime <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 租赁单位
|
||||
SuitRentalUnit rentalUnit = SuitRentalUnit.parse(suit.getRentalUnit());
|
||||
if (rentalUnit == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 起步价计费
|
||||
if (SuitRidingRule.START_FEE.getCode().equals(suit.getRidingRule())) {
|
||||
return calcAmountForStartFee(usedTime, suit.getStartRule(), rentalUnit);
|
||||
}
|
||||
// 区间计费
|
||||
else if (SuitRidingRule.INTERVAL_FEE.getCode().equals(suit.getRidingRule())) {
|
||||
return calcAmountForIntervalFee(usedTime, suit.getIntervalRule(), rentalUnit);
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
return totalTime * unitSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算起步价计费所需金额
|
||||
* @param usedTime 已使用时长(秒)
|
||||
* @param startRule 起步价计费规则
|
||||
* @param seconds 已使用时长(秒)
|
||||
* @param rule 起步价计费规则
|
||||
* @param rentalUnit 租赁单位
|
||||
* @return 所需金额
|
||||
*/
|
||||
public static BigDecimal calcAmountForStartFee(long usedTime, SuitStartFeeRule startRule, SuitRentalUnit rentalUnit) {
|
||||
if (usedTime <= 0 || startRule == null || rentalUnit == null) {
|
||||
public static BigDecimal calcAmountForStartFee(long seconds, SuitStartFeeRule rule, SuitRentalUnit rentalUnit) {
|
||||
if (seconds <= 0 || rule == null || rentalUnit == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 获取单位对应的秒数
|
||||
long unitSeconds = rentalUnit.getSeconds();
|
||||
|
||||
// 将秒转换为计费单位
|
||||
BigDecimal usedTimeInUnit = BigDecimal.valueOf(usedTime).divide(BigDecimal.valueOf(unitSeconds), 0, BigDecimal.ROUND_UP);
|
||||
BigDecimal startingPrice = rule.getStartingPrice(); // 起步价(元)
|
||||
long startSeconds = rule.getStartingTime() * unitSeconds; // 起步时长(秒)
|
||||
|
||||
// 起步价和起步时长
|
||||
BigDecimal startingPrice = startRule.getStartingPrice();
|
||||
int startingTime = startRule.getStartingTime();
|
||||
|
||||
// 如果使用时长小于等于起步时长,按比例计算费用
|
||||
if (usedTimeInUnit.intValue() <= startingTime) {
|
||||
return startingPrice.multiply(usedTimeInUnit).divide(BigDecimal.valueOf(startingTime), 2, BigDecimal.ROUND_UP);
|
||||
// 如果使用时长小于等于起步时长,返回起步价费用
|
||||
if (seconds <= startSeconds) {
|
||||
return rule.getStartingPrice();
|
||||
}
|
||||
|
||||
// 超出起步时长的部分
|
||||
BigDecimal extraTime = usedTimeInUnit.subtract(BigDecimal.valueOf(startingTime));
|
||||
// 超出起步的时长
|
||||
long extraTime = seconds - startSeconds; // 超出的时长(秒)
|
||||
long outTime = rule.getTimeoutTime() * unitSeconds; // 超出时长周期(秒)
|
||||
long extraRange = extraTime / outTime + (extraTime % outTime > 0 ? 1 : 0); // 超出的周期
|
||||
BigDecimal extraFee = MathUtils.mulDecimal(rule.getTimeoutPrice(), BigDecimal.valueOf(extraRange));
|
||||
|
||||
// 超时费用和超时时长
|
||||
BigDecimal timeoutPrice = startRule.getTimeoutPrice();
|
||||
int timeoutTime = startRule.getTimeoutTime();
|
||||
|
||||
// 计算完整的超时周期数
|
||||
BigDecimal fullTimeoutPeriods = extraTime.divide(BigDecimal.valueOf(timeoutTime), 0, BigDecimal.ROUND_DOWN);
|
||||
|
||||
// 计算最后不足一个超时周期的部分
|
||||
BigDecimal remainingTime = extraTime.remainder(BigDecimal.valueOf(timeoutTime));
|
||||
BigDecimal lastPeriodFee = BigDecimal.ZERO;
|
||||
if (remainingTime.compareTo(BigDecimal.ZERO) > 0) {
|
||||
lastPeriodFee = timeoutPrice.multiply(remainingTime).divide(BigDecimal.valueOf(timeoutTime), 2, BigDecimal.ROUND_UP);
|
||||
}
|
||||
|
||||
// 总费用 = 起步价 + 完整超时周期费用 + 最后一个不完整周期费用
|
||||
return startingPrice
|
||||
.add(fullTimeoutPeriods.multiply(timeoutPrice))
|
||||
.add(lastPeriodFee);
|
||||
// 总费用 = 起步价 + 超出费用
|
||||
return MathUtils.addDecimal(startingPrice, extraFee);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算区间计费所需金额
|
||||
* @param usedTime 已使用时长(秒)
|
||||
* @param intervalFeeRules 区间计费规则列表
|
||||
* @param seconds 已使用时长(秒)
|
||||
* @param rules 区间计费规则列表
|
||||
* @param rentalUnit 租赁单位
|
||||
* @return 所需金额
|
||||
*/
|
||||
public static BigDecimal calcAmountForIntervalFee(long usedTime, List<SuitIntervalFeeRule> intervalFeeRules, SuitRentalUnit rentalUnit) {
|
||||
if (usedTime <= 0 || CollectionUtils.isEmptyElement(intervalFeeRules) || rentalUnit == null) {
|
||||
public static BigDecimal calcAmountForIntervalFee(long seconds, List<SuitIntervalFeeRule> rules, SuitRentalUnit rentalUnit) {
|
||||
if (seconds <= 0 || CollectionUtils.isEmptyElement(rules) || rentalUnit == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 获取单位对应的秒数
|
||||
long unitSeconds = rentalUnit.getSeconds();
|
||||
|
||||
// 将秒转换为计费单位
|
||||
int usedTimeInUnit = (int) Math.ceil((double) usedTime / unitSeconds);
|
||||
|
||||
// 按照区间开始时间排序
|
||||
intervalFeeRules.sort((a, b) -> Integer.compare(a.getStart(), b.getStart()));
|
||||
rules.sort(Comparator.comparingInt(SuitIntervalFeeRule::getStart));
|
||||
|
||||
// 总金额
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
int remainingTime = usedTimeInUnit;
|
||||
|
||||
for (SuitIntervalFeeRule rule : intervalFeeRules) {
|
||||
// 如果没有剩余时间需要计费,退出循环
|
||||
if (remainingTime <= 0) {
|
||||
for (int i = 0; i < rules.size(); i++) {
|
||||
SuitIntervalFeeRule rule = rules.get(i);
|
||||
long end = seconds;
|
||||
if (i < rules.size() - 1) {
|
||||
end = Math.min(end, rule.getEnd() * unitSeconds); // 取区间最小的终值
|
||||
}
|
||||
|
||||
// 计算价格,并扣减剩余时长
|
||||
long duration = end - rule.getStart() * unitSeconds; // 区间使用时长(秒)
|
||||
long eachUnit = rule.getEachUnit() * unitSeconds; // 间隔(秒)
|
||||
long range = duration / eachUnit + (duration % eachUnit > 0 ? 1 : 0); // 当前区间使用的周期,不满一个周期也按一个周期算
|
||||
BigDecimal partAmount = MathUtils.mulDecimal(rule.getFee(), BigDecimal.valueOf(range));
|
||||
totalAmount = MathUtils.addDecimal(totalAmount, partAmount);
|
||||
|
||||
// 当前已使用时长大于等于最大时长,跳出循环
|
||||
if (end >= seconds) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 处理最后一个区间的end可能为0的情况
|
||||
boolean isLastInterval = intervalFeeRules.indexOf(rule) == intervalFeeRules.size() - 1;
|
||||
if (isLastInterval) {
|
||||
// 最后一个区间且end为0,表示无限长
|
||||
BigDecimal eachUnitFee = rule.getFee();
|
||||
int eachUnitTime = rule.getEachUnit();
|
||||
|
||||
if (eachUnitFee.compareTo(BigDecimal.ZERO) > 0 && eachUnitTime > 0) {
|
||||
// 计算完整单位数
|
||||
int completeUnits = remainingTime / eachUnitTime;
|
||||
totalAmount = totalAmount.add(eachUnitFee.multiply(BigDecimal.valueOf(completeUnits)));
|
||||
|
||||
// 计算最后不足一个单位的费用
|
||||
int remainingUnitTime = remainingTime % eachUnitTime;
|
||||
if (remainingUnitTime > 0) {
|
||||
totalAmount = totalAmount.add(
|
||||
eachUnitFee.multiply(BigDecimal.valueOf(remainingUnitTime))
|
||||
.divide(BigDecimal.valueOf(eachUnitTime), 2, BigDecimal.ROUND_UP)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
remainingTime = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算当前区间的时间范围
|
||||
int end = rule.getEnd();
|
||||
int intervalDuration = end - rule.getStart();
|
||||
if (intervalDuration <= 0) {
|
||||
continue; // 跳过无效区间
|
||||
}
|
||||
|
||||
// 计算在当前区间内的使用时长
|
||||
int timeInCurrentInterval = Math.min(remainingTime, intervalDuration);
|
||||
|
||||
// 计算当前区间内的费用
|
||||
BigDecimal eachUnitFee = rule.getFee();
|
||||
int eachUnitTime = rule.getEachUnit();
|
||||
|
||||
if (eachUnitFee.compareTo(BigDecimal.ZERO) <= 0 || eachUnitTime <= 0) {
|
||||
continue; // 跳过无效计费规则
|
||||
}
|
||||
|
||||
// 计算完整单位数
|
||||
int completeUnits = timeInCurrentInterval / eachUnitTime;
|
||||
totalAmount = totalAmount.add(eachUnitFee.multiply(BigDecimal.valueOf(completeUnits)));
|
||||
|
||||
// 计算最后不足一个单位的费用
|
||||
int remainingUnitTime = timeInCurrentInterval % eachUnitTime;
|
||||
if (remainingUnitTime > 0) {
|
||||
totalAmount = totalAmount.add(
|
||||
eachUnitFee.multiply(BigDecimal.valueOf(remainingUnitTime))
|
||||
.divide(BigDecimal.valueOf(eachUnitTime), 2, BigDecimal.ROUND_UP)
|
||||
);
|
||||
}
|
||||
|
||||
// 更新剩余需要计费的时间
|
||||
remainingTime -= timeInCurrentInterval;
|
||||
}
|
||||
|
||||
return totalAmount;
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.time.LocalDateTime;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ruoyi.ws.service.DeviceWebSocketService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -63,6 +64,9 @@ public class IotReceiveServiceImpl implements IotReceiveService {
|
|||
@Autowired
|
||||
private DeviceIotService deviceIotService;
|
||||
|
||||
@Autowired
|
||||
private DeviceWebSocketService deviceWebSocketService;
|
||||
|
||||
@Override
|
||||
public void handleReceive(ReceiveMsg msg) {
|
||||
if (msg == null) {
|
||||
|
@ -222,5 +226,8 @@ public class IotReceiveServiceImpl implements IotReceiveService {
|
|||
|
||||
// 暂存到Redis缓存
|
||||
redisCache.rightPush(CacheConstants.LOCATION_LOG_QUEUE, po);
|
||||
|
||||
// 发送消息给ws服务
|
||||
deviceWebSocketService.sendMessageToDevice(po, device.getMac());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package com.ruoyi.ws.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DeviceWebSocketService {
|
||||
|
||||
// MAC地址到Session集合的映射
|
||||
private static final Map<String, Set<Session>> MAC_TO_SESSIONS = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 添加设备WebSocket会话的MAC地址
|
||||
*/
|
||||
public void addMacToSession(String mac, Session session) {
|
||||
if (mac == null || session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加MAC到Session的映射
|
||||
Set<Session> sessions = MAC_TO_SESSIONS.computeIfAbsent(mac, k -> new HashSet<>());
|
||||
synchronized (sessions) {
|
||||
sessions.add(session);
|
||||
}
|
||||
|
||||
log.info("Session添加MAC地址{}成功,当前Session关联的MAC地址数量:{}", mac, sessions.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Session中移除指定的MAC地址
|
||||
*/
|
||||
public void removeMacFromSession(String mac, Session session) {
|
||||
if (mac == null || session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从MAC到Session的映射中移除
|
||||
Set<Session> sessions = MAC_TO_SESSIONS.get(mac);
|
||||
if (sessions != null) {
|
||||
synchronized (sessions) {
|
||||
sessions.remove(session);
|
||||
if (sessions.isEmpty()) {
|
||||
MAC_TO_SESSIONS.remove(mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("从Session中移除MAC地址{}成功", mac);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除设备WebSocket会话
|
||||
*/
|
||||
public void removeSession(Session session) {
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历所有MAC地址,移除对应的session
|
||||
for (Map.Entry<String, Set<Session>> entry : MAC_TO_SESSIONS.entrySet()) {
|
||||
String mac = entry.getKey();
|
||||
Set<Session> sessions = entry.getValue();
|
||||
if (sessions != null) {
|
||||
synchronized (sessions) {
|
||||
sessions.remove(session);
|
||||
if (sessions.isEmpty()) {
|
||||
MAC_TO_SESSIONS.remove(mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("WebSocket会话已移除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MAC地址关联的所有WebSocket会话
|
||||
*/
|
||||
public Set<Session> getSessions(String mac) {
|
||||
if (mac == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
Set<Session> sessions = MAC_TO_SESSIONS.get(mac);
|
||||
if (sessions == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
synchronized (sessions) {
|
||||
return new HashSet<>(sessions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定MAC地址发送消息
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @param macs 目标MAC地址数组
|
||||
*/
|
||||
public void sendMessageToDevice(Object message, String... macs) {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
this.sendMessageToDevice(message, Arrays.asList(macs));
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定MAC地址发送消息
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @param macs 目标MAC地址集合
|
||||
*/
|
||||
public void sendMessageToDevice(Object message, Collection<String> macs) {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String msg = JSON.toJSONString(message);
|
||||
for (String mac : macs) {
|
||||
Set<Session> sessions = this.getSessions(mac);
|
||||
for (Session session : sessions) {
|
||||
if (session != null) {
|
||||
try {
|
||||
if (session.isOpen()) {
|
||||
session.getBasicRemote().sendText(msg);
|
||||
log.info("向MAC地址{}发送消息成功: {}", mac, msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("发送消息给MAC地址{}失败", mac, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -12,11 +12,9 @@ import java.util.concurrent.Executors;
|
|||
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.ruoyi.system.user.service.UserService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
@ -25,10 +23,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class WebSocketService {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
public class UserWebSocketService {
|
||||
|
||||
// 使用ConcurrentHashMap存储会话
|
||||
private static final Map<Long, Session> SESSION_POOL = new ConcurrentHashMap<>();
|
|
@ -131,7 +131,7 @@ public class AppOrderController extends BaseController {
|
|||
|
||||
@ApiOperation("操作订单设备关闭")
|
||||
@PutMapping("/closeDevice")
|
||||
public AjaxResult closeDevice(@RequestBody @Validated OrderCloseDeviceDTO dto) {
|
||||
public AjaxResult closeDevice(@Validated OrderCloseDeviceDTO dto) {
|
||||
OrderVO order = orderService.selectOrderById(dto.getOrderId());
|
||||
ServiceUtil.assertion(order == null, "订单不存在");
|
||||
ServiceUtil.assertion(!orderValidator.canCloseDevice(order, getUserId()), "您无权操作ID为%s的订单设备关闭", order.getId());
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
@ -82,4 +83,14 @@ public class BonusController extends BaseController
|
|||
return success(bonusService.preview(deviceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付分成
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('bst:bonus:pay')")
|
||||
@PutMapping("/pay")
|
||||
@Log(title = "支付分成", businessType = BusinessType.OTHER)
|
||||
public AjaxResult pay(Long id) {
|
||||
return success(bonusService.payBonus(id));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import java.util.List;
|
|||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.ruoyi.bst.suit.domain.enums.SuitRentalUnit;
|
||||
import com.ruoyi.bst.suit.domain.enums.SuitRidingRule;
|
||||
import com.ruoyi.bst.suit.utils.SuitUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
@ -144,4 +147,16 @@ public class SuitController extends BaseController
|
|||
{
|
||||
return toAjax(suitService.deleteSuitByIds(ids));
|
||||
}
|
||||
|
||||
// @GetMapping("/test")
|
||||
// public AjaxResult test(Long seconds, Long suitId)
|
||||
// {
|
||||
// SuitVO suit = suitService.selectSuitById(suitId);
|
||||
// SuitRentalUnit rentalUnit = SuitRentalUnit.parse(suit.getRentalUnit());
|
||||
// if (SuitRidingRule.INTERVAL_FEE.getCode().equals(suit.getRidingRule())) {
|
||||
// return success(SuitUtil.calcAmountForIntervalFee(seconds, suit.getIntervalRule(), rentalUnit));
|
||||
// } else {
|
||||
// return success(SuitUtil.calcAmountForStartFee(seconds, suit.getStartRule(), rentalUnit));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -8,24 +8,26 @@ import javax.websocket.OnOpen;
|
|||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ruoyi.bst.device.service.DeviceValidator;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.framework.web.service.TokenService;
|
||||
import com.ruoyi.ws.config.WebSocketAuthConfigurator;
|
||||
import com.ruoyi.ws.service.WebSocketService;
|
||||
import com.ruoyi.ws.service.DeviceWebSocketService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* WebSocket服务类
|
||||
*/
|
||||
@ServerEndpoint(value = "/ws/message", configurator = WebSocketAuthConfigurator.class)
|
||||
@ServerEndpoint(value = "/ws/device", configurator = WebSocketAuthConfigurator.class)
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageWebSocket {
|
||||
public class DeviceWebSocket {
|
||||
|
||||
/**
|
||||
* 建立连接
|
||||
*/
|
||||
|
@ -33,15 +35,24 @@ public class MessageWebSocket {
|
|||
public void onOpen(Session session, EndpointConfig config) {
|
||||
try {
|
||||
String token = (String) config.getUserProperties().get("token");
|
||||
String mac = (String) config.getUserProperties().get("mac");
|
||||
if (token != null) {
|
||||
TokenService tokenService = SpringUtils.getBean(TokenService.class);
|
||||
WebSocketService webSocketService = SpringUtils.getBean(WebSocketService.class);
|
||||
DeviceWebSocketService webSocketService = SpringUtils.getBean(DeviceWebSocketService.class);
|
||||
DeviceValidator deviceValidator = SpringUtils.getBean(DeviceValidator.class);
|
||||
LoginUser loginUser = tokenService.getLoginUser(token);
|
||||
tokenService.verifyToken(loginUser);
|
||||
if (loginUser != null) {
|
||||
Long userId = loginUser.getUserId();
|
||||
webSocketService.addSession(userId, session);
|
||||
log.info("用户{}连接WebSocket成功", userId);
|
||||
// 校验用户是否有权限连接设备
|
||||
if (!deviceValidator.canLink(mac, userId)) {
|
||||
String reason = String.format("用户%s无权访问%s的数据", userId, mac);
|
||||
session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, reason));
|
||||
return;
|
||||
}
|
||||
// 添加设备WebSocket会话的MAC地址
|
||||
webSocketService.addMacToSession(mac, session);
|
||||
log.info("用户 {} 连接Mac {} 成功", userId, mac);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -55,15 +66,11 @@ public class MessageWebSocket {
|
|||
* 关闭连接
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
public void onClose(Session session) {
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
Long userId = loginUser.getUserId();
|
||||
WebSocketService webSocketService = SpringUtils.getBean(WebSocketService.class);
|
||||
webSocketService.removeSession(userId);
|
||||
log.info("用户{}断开WebSocket连接", userId);
|
||||
}
|
||||
DeviceWebSocketService webSocketService = SpringUtils.getBean(DeviceWebSocketService.class);
|
||||
webSocketService.removeSession(session);
|
||||
log.info("用户{}断开WebSocket连接", session);
|
||||
} catch (Exception e) {
|
||||
log.error("WebSocket关闭异常", e);
|
||||
}
|
|
@ -10,10 +10,11 @@ import javax.websocket.server.ServerEndpointConfig;
|
|||
public class WebSocketAuthConfigurator extends ServerEndpointConfig.Configurator {
|
||||
@Override
|
||||
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
|
||||
String token = null;
|
||||
if (!request.getParameterMap().isEmpty()) {
|
||||
token = request.getParameterMap().get("token").get(0);
|
||||
String token = request.getParameterMap().get("token").get(0);
|
||||
String mac = request.getParameterMap().get("mac").get(0);
|
||||
sec.getUserProperties().put("token", token);
|
||||
sec.getUserProperties().put("mac", mac);
|
||||
}
|
||||
sec.getUserProperties().put("token", token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user