Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
007e4eed3a
|
@ -26,4 +26,9 @@ public enum BonusStatus {
|
||||||
return CollectionUtils.map(BonusStatus::getStatus, WAIT_DIVIDE, DIVIDEND);
|
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);
|
public int payBonus(BonusVO bonus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分成打款
|
||||||
|
* @param id 分成明细ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int payBonus(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进行分成打款指定时间之前的分成
|
* 进行分成打款指定时间之前的分成
|
||||||
* @param time 时间
|
* @param time 时间
|
||||||
|
|
|
@ -265,9 +265,9 @@ public class BonusServiceImpl implements BonusService
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int payBonus(BonusVO bonus) {
|
public int payBonus(BonusVO bonus) {
|
||||||
if (bonus == null) {
|
ServiceUtil.assertion(bonus == null, "待打款的分成不存在");
|
||||||
return 0;
|
ServiceUtil.assertion(!BonusStatus.canPay().contains(bonus.getStatus()), "ID为%s的分成当前状态不允许打款", bonus.getId());
|
||||||
}
|
|
||||||
Integer result = transactionTemplate.execute(status -> {
|
Integer result = transactionTemplate.execute(status -> {
|
||||||
// 更新分成状态为已分成
|
// 更新分成状态为已分成
|
||||||
int pay = bonusMapper.pay(bonus.getId(), bonus.getWaitAmount(), LocalDateTime.now());
|
int pay = bonusMapper.pay(bonus.getId(), bonus.getWaitAmount(), LocalDateTime.now());
|
||||||
|
@ -288,6 +288,16 @@ public class BonusServiceImpl implements BonusService
|
||||||
return result == null ? 0 : result;
|
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
|
@Override
|
||||||
public boolean refundByBst(BonusBstType bstType, Long bstId, BigDecimal refundAmount, BigDecimal payAmount, String reason) {
|
public boolean refundByBst(BonusBstType bstType, Long bstId, BigDecimal refundAmount, BigDecimal payAmount, String reason) {
|
||||||
if (bstType == null || bstId == null || refundAmount == null || payAmount == null) {
|
if (bstType == null || bstId == null || refundAmount == null || payAmount == null) {
|
||||||
|
|
|
@ -162,5 +162,12 @@ public interface DeviceService
|
||||||
*/
|
*/
|
||||||
public DeviceVO selectAvaliableDevice(Long id, String sn);
|
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);
|
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);
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.ruoyi.bst.device.domain.DeviceQuery;
|
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.mapper.DeviceMapper;
|
||||||
|
import com.ruoyi.bst.device.service.DeviceService;
|
||||||
import com.ruoyi.bst.device.service.DeviceValidator;
|
import com.ruoyi.bst.device.service.DeviceValidator;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.collection.CollectionUtils;
|
import com.ruoyi.common.utils.collection.CollectionUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -18,6 +21,9 @@ public class DeviceValidatorImpl implements DeviceValidator {
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeviceMapper deviceMapper;
|
private DeviceMapper deviceMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DeviceService deviceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canEdit(Long id) {
|
public boolean canEdit(Long id) {
|
||||||
return canOperate(Collections.singletonList(id));
|
return canOperate(Collections.singletonList(id));
|
||||||
|
@ -42,4 +48,38 @@ public class DeviceValidatorImpl implements DeviceValidator {
|
||||||
return new HashSet<>(idList).containsAll(ids);
|
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.getFee() == null, "费用不能为空");
|
||||||
ServiceUtil.assertion(rule.getStart() < 0, "区间开始时间不能小于0");
|
ServiceUtil.assertion(rule.getStart() < 0, "区间开始时间不能小于0");
|
||||||
ServiceUtil.assertion(rule.getEachUnit() <= 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) {
|
if (i < intervalRules.size() - 1) {
|
||||||
SuitIntervalFeeRule nextRule = intervalRules.get(i + 1);
|
SuitIntervalFeeRule nextRule = intervalRules.get(i + 1);
|
||||||
ServiceUtil.assertion(rule.getEnd() == null, "区间结束时间不能为空");
|
ServiceUtil.assertion(rule.getEnd() == null, "区间结束时间不能为空");
|
||||||
ServiceUtil.assertion(rule.getStart() > rule.getEnd(), "区间的结束时间不允许小于开始时间");
|
ServiceUtil.assertion(rule.getStart() > rule.getEnd(), "区间的结束时间不允许小于开始时间");
|
||||||
ServiceUtil.assertion(rule.getEnd() != nextRule.getStart(), "区间的结束时间必须等于下一个区间的开始时间");
|
ServiceUtil.assertion(rule.getEnd() != nextRule.getStart(), "区间的结束时间必须等于下一个区间的开始时间");
|
||||||
|
int eachUnit = rule.getEachUnit();
|
||||||
|
int duration = rule.getEnd() - rule.getStart();
|
||||||
|
ServiceUtil.assertion(duration % eachUnit != 0, "区间间隔必须能够被区间整除");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServiceException("第" + (i + 1) + "条收费规则校验失败:" + e.getMessage());
|
throw new ServiceException("第" + (i + 1) + "条收费规则校验失败:" + e.getMessage());
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.ruoyi.bst.suit.utils;
|
package com.ruoyi.bst.suit.utils;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.ruoyi.bst.suit.domain.SuitVO;
|
import com.ruoyi.bst.suit.domain.SuitVO;
|
||||||
|
@ -35,15 +37,22 @@ public class SuitUtil {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long seconds = 0;
|
||||||
// 起步价计费
|
// 起步价计费
|
||||||
if (SuitRidingRule.START_FEE.getCode().equals(suit.getRidingRule())) {
|
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())) {
|
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;
|
BigDecimal amount = depositAmount; // 剩余金额
|
||||||
long totalTime = 0;
|
int totalTime = 0; // 总时长
|
||||||
|
|
||||||
// 获取单位对应的秒数
|
// 获取单位对应的秒数
|
||||||
long unitSeconds = rentalUnit.getSeconds();
|
long unitSeconds = rentalUnit.getSeconds();
|
||||||
|
|
||||||
for (SuitIntervalFeeRule rule : intervalFeeRules) {
|
for (int i = 0; i < intervalFeeRules.size(); i++) {
|
||||||
// 处理最后一个区间的end可能为空的情况
|
SuitIntervalFeeRule rule = intervalFeeRules.get(i);
|
||||||
if ( intervalFeeRules.indexOf(rule) == intervalFeeRules.size() - 1) {
|
BigDecimal fee = rule.getFee(); // 每个时间段的金额
|
||||||
// 如果是最后一个区间且end为0,可以认为是无限长
|
int eachUnitTime = rule.getEachUnit(); // 每个时间段的时长
|
||||||
// 这里我们可以计算剩余金额能买多少时间
|
if (fee.compareTo(BigDecimal.ZERO) <= 0 || eachUnitTime <= 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) {
|
|
||||||
continue; // 跳过无效计费规则
|
continue; // 跳过无效计费规则
|
||||||
}
|
}
|
||||||
|
// 计算可以购买几个区间,金额不足也按1个单位计算
|
||||||
|
int count = amount.divide(fee, 0, RoundingMode.UP).intValue();
|
||||||
|
|
||||||
// 计算当前区间内可以购买的完整单位数
|
// 非最后节点,限制最大购买数量
|
||||||
int maxUnitsInInterval = intervalDuration / eachUnitTime;
|
if (i < intervalFeeRules.size() - 1) {
|
||||||
BigDecimal maxCostForInterval = eachUnitFee.multiply(BigDecimal.valueOf(maxUnitsInInterval));
|
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 += count * eachUnitTime;
|
||||||
totalTime += intervalDuration * unitSeconds;
|
amount = amount.subtract(fee.multiply(BigDecimal.valueOf(count)));
|
||||||
remainingAmount = remainingAmount.subtract(maxCostForInterval);
|
|
||||||
} else {
|
|
||||||
// 计算剩余金额可以购买的完整单位数
|
|
||||||
BigDecimal completeUnits = remainingAmount.divide(eachUnitFee, 0, BigDecimal.ROUND_DOWN);
|
|
||||||
totalTime += completeUnits.multiply(BigDecimal.valueOf(eachUnitTime * unitSeconds)).longValue();
|
|
||||||
|
|
||||||
// 计算最后不足一个计费单位的时间(按比例计算)
|
// 金额不足则跳出循环
|
||||||
BigDecimal remainingAmountForLastUnit = remainingAmount.remainder(eachUnitFee);
|
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
if (remainingAmountForLastUnit.compareTo(BigDecimal.ZERO) > 0) {
|
|
||||||
totalTime += remainingAmountForLastUnit
|
|
||||||
.multiply(BigDecimal.valueOf(eachUnitTime * unitSeconds))
|
|
||||||
.divide(eachUnitFee, 0, BigDecimal.ROUND_DOWN)
|
|
||||||
.longValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 已经用完所有金额,退出循环
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalTime;
|
return totalTime * unitSeconds;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算已使用时长所需的金额
|
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算起步价计费所需金额
|
* 计算起步价计费所需金额
|
||||||
* @param usedTime 已使用时长(秒)
|
* @param seconds 已使用时长(秒)
|
||||||
* @param startRule 起步价计费规则
|
* @param rule 起步价计费规则
|
||||||
* @param rentalUnit 租赁单位
|
* @param rentalUnit 租赁单位
|
||||||
* @return 所需金额
|
* @return 所需金额
|
||||||
*/
|
*/
|
||||||
public static BigDecimal calcAmountForStartFee(long usedTime, SuitStartFeeRule startRule, SuitRentalUnit rentalUnit) {
|
public static BigDecimal calcAmountForStartFee(long seconds, SuitStartFeeRule rule, SuitRentalUnit rentalUnit) {
|
||||||
if (usedTime <= 0 || startRule == null || rentalUnit == null) {
|
if (seconds <= 0 || rule == null || rentalUnit == null) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取单位对应的秒数
|
// 获取单位对应的秒数
|
||||||
long unitSeconds = rentalUnit.getSeconds();
|
long unitSeconds = rentalUnit.getSeconds();
|
||||||
|
|
||||||
// 将秒转换为计费单位
|
BigDecimal startingPrice = rule.getStartingPrice(); // 起步价(元)
|
||||||
BigDecimal usedTimeInUnit = BigDecimal.valueOf(usedTime).divide(BigDecimal.valueOf(unitSeconds), 0, BigDecimal.ROUND_UP);
|
long startSeconds = rule.getStartingTime() * unitSeconds; // 起步时长(秒)
|
||||||
|
|
||||||
// 起步价和起步时长
|
// 如果使用时长小于等于起步时长,返回起步价费用
|
||||||
BigDecimal startingPrice = startRule.getStartingPrice();
|
if (seconds <= startSeconds) {
|
||||||
int startingTime = startRule.getStartingTime();
|
return rule.getStartingPrice();
|
||||||
|
|
||||||
// 如果使用时长小于等于起步时长,按比例计算费用
|
|
||||||
if (usedTimeInUnit.intValue() <= startingTime) {
|
|
||||||
return startingPrice.multiply(usedTimeInUnit).divide(BigDecimal.valueOf(startingTime), 2, BigDecimal.ROUND_UP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 超出起步时长的部分
|
// 超出起步的时长
|
||||||
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();
|
return MathUtils.addDecimal(startingPrice, extraFee);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算区间计费所需金额
|
* 计算区间计费所需金额
|
||||||
* @param usedTime 已使用时长(秒)
|
* @param seconds 已使用时长(秒)
|
||||||
* @param intervalFeeRules 区间计费规则列表
|
* @param rules 区间计费规则列表
|
||||||
* @param rentalUnit 租赁单位
|
* @param rentalUnit 租赁单位
|
||||||
* @return 所需金额
|
* @return 所需金额
|
||||||
*/
|
*/
|
||||||
public static BigDecimal calcAmountForIntervalFee(long usedTime, List<SuitIntervalFeeRule> intervalFeeRules, SuitRentalUnit rentalUnit) {
|
public static BigDecimal calcAmountForIntervalFee(long seconds, List<SuitIntervalFeeRule> rules, SuitRentalUnit rentalUnit) {
|
||||||
if (usedTime <= 0 || CollectionUtils.isEmptyElement(intervalFeeRules) || rentalUnit == null) {
|
if (seconds <= 0 || CollectionUtils.isEmptyElement(rules) || rentalUnit == null) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取单位对应的秒数
|
// 获取单位对应的秒数
|
||||||
long unitSeconds = rentalUnit.getSeconds();
|
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;
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||||
int remainingTime = usedTimeInUnit;
|
|
||||||
|
|
||||||
for (SuitIntervalFeeRule rule : intervalFeeRules) {
|
for (int i = 0; i < rules.size(); i++) {
|
||||||
// 如果没有剩余时间需要计费,退出循环
|
SuitIntervalFeeRule rule = rules.get(i);
|
||||||
if (remainingTime <= 0) {
|
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;
|
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;
|
return totalAmount;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.ruoyi.ws.service.DeviceWebSocketService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -63,6 +64,9 @@ public class IotReceiveServiceImpl implements IotReceiveService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeviceIotService deviceIotService;
|
private DeviceIotService deviceIotService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DeviceWebSocketService deviceWebSocketService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleReceive(ReceiveMsg msg) {
|
public void handleReceive(ReceiveMsg msg) {
|
||||||
if (msg == null) {
|
if (msg == null) {
|
||||||
|
@ -222,5 +226,8 @@ public class IotReceiveServiceImpl implements IotReceiveService {
|
||||||
|
|
||||||
// 暂存到Redis缓存
|
// 暂存到Redis缓存
|
||||||
redisCache.rightPush(CacheConstants.LOCATION_LOG_QUEUE, po);
|
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 javax.websocket.Session;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.ruoyi.system.user.service.UserService;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@ -25,10 +23,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WebSocketService {
|
public class UserWebSocketService {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserService userService;
|
|
||||||
|
|
||||||
// 使用ConcurrentHashMap存储会话
|
// 使用ConcurrentHashMap存储会话
|
||||||
private static final Map<Long, Session> SESSION_POOL = new ConcurrentHashMap<>();
|
private static final Map<Long, Session> SESSION_POOL = new ConcurrentHashMap<>();
|
|
@ -131,7 +131,7 @@ public class AppOrderController extends BaseController {
|
||||||
|
|
||||||
@ApiOperation("操作订单设备关闭")
|
@ApiOperation("操作订单设备关闭")
|
||||||
@PutMapping("/closeDevice")
|
@PutMapping("/closeDevice")
|
||||||
public AjaxResult closeDevice(@RequestBody @Validated OrderCloseDeviceDTO dto) {
|
public AjaxResult closeDevice(@Validated OrderCloseDeviceDTO dto) {
|
||||||
OrderVO order = orderService.selectOrderById(dto.getOrderId());
|
OrderVO order = orderService.selectOrderById(dto.getOrderId());
|
||||||
ServiceUtil.assertion(order == null, "订单不存在");
|
ServiceUtil.assertion(order == null, "订单不存在");
|
||||||
ServiceUtil.assertion(!orderValidator.canCloseDevice(order, getUserId()), "您无权操作ID为%s的订单设备关闭", order.getId());
|
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.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@ -82,4 +83,14 @@ public class BonusController extends BaseController
|
||||||
return success(bonusService.preview(deviceId));
|
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 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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
@ -144,4 +147,16 @@ public class SuitController extends BaseController
|
||||||
{
|
{
|
||||||
return toAjax(suitService.deleteSuitByIds(ids));
|
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.Session;
|
||||||
import javax.websocket.server.ServerEndpoint;
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.ruoyi.bst.device.service.DeviceValidator;
|
||||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||||
import com.ruoyi.framework.web.service.TokenService;
|
import com.ruoyi.framework.web.service.TokenService;
|
||||||
import com.ruoyi.ws.config.WebSocketAuthConfigurator;
|
import com.ruoyi.ws.config.WebSocketAuthConfigurator;
|
||||||
import com.ruoyi.ws.service.WebSocketService;
|
import com.ruoyi.ws.service.DeviceWebSocketService;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket服务类
|
* WebSocket服务类
|
||||||
*/
|
*/
|
||||||
@ServerEndpoint(value = "/ws/message", configurator = WebSocketAuthConfigurator.class)
|
@ServerEndpoint(value = "/ws/device", configurator = WebSocketAuthConfigurator.class)
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MessageWebSocket {
|
public class DeviceWebSocket {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建立连接
|
* 建立连接
|
||||||
*/
|
*/
|
||||||
|
@ -33,15 +35,24 @@ public class MessageWebSocket {
|
||||||
public void onOpen(Session session, EndpointConfig config) {
|
public void onOpen(Session session, EndpointConfig config) {
|
||||||
try {
|
try {
|
||||||
String token = (String) config.getUserProperties().get("token");
|
String token = (String) config.getUserProperties().get("token");
|
||||||
|
String mac = (String) config.getUserProperties().get("mac");
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
TokenService tokenService = SpringUtils.getBean(TokenService.class);
|
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);
|
LoginUser loginUser = tokenService.getLoginUser(token);
|
||||||
tokenService.verifyToken(loginUser);
|
tokenService.verifyToken(loginUser);
|
||||||
if (loginUser != null) {
|
if (loginUser != null) {
|
||||||
Long userId = loginUser.getUserId();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,15 +66,11 @@ public class MessageWebSocket {
|
||||||
* 关闭连接
|
* 关闭连接
|
||||||
*/
|
*/
|
||||||
@OnClose
|
@OnClose
|
||||||
public void onClose() {
|
public void onClose(Session session) {
|
||||||
try {
|
try {
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
DeviceWebSocketService webSocketService = SpringUtils.getBean(DeviceWebSocketService.class);
|
||||||
if (loginUser != null) {
|
webSocketService.removeSession(session);
|
||||||
Long userId = loginUser.getUserId();
|
log.info("用户{}断开WebSocket连接", session);
|
||||||
WebSocketService webSocketService = SpringUtils.getBean(WebSocketService.class);
|
|
||||||
webSocketService.removeSession(userId);
|
|
||||||
log.info("用户{}断开WebSocket连接", userId);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("WebSocket关闭异常", e);
|
log.error("WebSocket关闭异常", e);
|
||||||
}
|
}
|
|
@ -10,10 +10,11 @@ import javax.websocket.server.ServerEndpointConfig;
|
||||||
public class WebSocketAuthConfigurator extends ServerEndpointConfig.Configurator {
|
public class WebSocketAuthConfigurator extends ServerEndpointConfig.Configurator {
|
||||||
@Override
|
@Override
|
||||||
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
|
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
|
||||||
String token = null;
|
|
||||||
if (!request.getParameterMap().isEmpty()) {
|
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