Browse Source

Merge branch 'master' of http://8.138.18.76:3000/qmrb/parking-server

xlq 1 day ago
parent
commit
b48ee8fc6b

+ 120 - 4
src/main/java/com/qmrb/parking/fee/ParkingFeeCalculator.java

@@ -1,14 +1,19 @@
 package com.qmrb.parking.fee;
 
+import cn.hutool.core.date.DateUtil;
+import com.qmrb.system.pojo.entity.ContractPlaceNumberRel;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 
-import java.math.BigDecimal;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
 @Slf4j
 public class ParkingFeeCalculator {
     /**
@@ -43,6 +48,8 @@ public class ParkingFeeCalculator {
 
         public CouponRecord couponRecord = new CouponRecord(); // 优惠券记录
 
+        public String contractType;//合同类型 3包租协议
+
     }
 
     // 支付记录类
@@ -67,6 +74,7 @@ public class ParkingFeeCalculator {
         // 3. 计算计费时长
         Duration billingDuration = Duration.between(billingStartTime, billingEndTime);
 
+
         // 4. 检查离场时长宽容度
         /*if (feeRule.gracePeriodMinutes > 0) {
             for (PaymentRecord payment : parkingRecord.paymentRecords) {
@@ -139,12 +147,13 @@ public class ParkingFeeCalculator {
             if(totalFee<0){
                 return 0.0;
             }
-        } else if (StringUtils.equals(couponRecord.couponType,"2")) {//时长券
+        }
+        /*else if (StringUtils.equals(couponRecord.couponType,"2")) {//时长券
             //用计算不足24小时的部分来计算,不区分首周期
             double dicFee = calculateRemainingFeeDue(Duration.ofMinutes(new BigDecimal(couponRecord.denomination).intValue()), feeRule);
             log.info("优惠金额: "+dicFee+"元");
             return totalFee - dicFee;
-        }
+        }*/
         return totalFee;
     }
     // 计算不足 24 小时的费用
@@ -210,6 +219,40 @@ public class ParkingFeeCalculator {
     }
 
     /**
+     * 包租协议测试
+     */
+    public static void e3(){
+        // 示例计费规则
+        FeeRule feeRule = new FeeRule();
+        feeRule.freeDurationMinutes = 0;//免费时长
+        feeRule.firstCycleFee = 20.0;//首周期计费金额
+        feeRule.firstCycleDurationMinutes = 60;//首周期计费时长
+        feeRule.cycleDurationMinutes = 30;//计费周期
+        feeRule.cycleFee = 2;//每周期计费金额
+        feeRule.dailyCapFee = 30;//24小时封顶费用
+        feeRule.gracePeriodMinutes = 15;// 离场时长宽容度(分钟)
+
+        // 示例停车记录
+        ParkingRecord parkingRecord = new ParkingRecord();
+        parkingRecord.entryTime = LocalDateTime.of(2025, 3, 28, 14, 0);
+        parkingRecord.exitTime = LocalDateTime.of(2025, 3, 29, 15, 50);
+        parkingRecord.monthlyCardExpiryTime = null;
+        /*parkingRecord.paidAmount = 0.01;//已支付金额
+        parkingRecord.paymentRecords=new ArrayList<>();*/
+        /*PaymentRecord paymentRecord = new PaymentRecord();
+        paymentRecord.paymentTime = LocalDateTime.of(2025, 3, 14, 10, 0);
+        parkingRecord.paymentRecords.add(paymentRecord);*/
+        List<ContractPlaceNumberRel> registrations = new ArrayList<>();
+        ContractPlaceNumberRel placeNumberRel = new ContractPlaceNumberRel();
+        placeNumberRel.setStartTime(DateUtil.parse("2025-03-28 12:00:00"));
+        placeNumberRel.setEndTime(DateUtil.parse(" 2025-03-29 14:00:00"));
+        registrations.add(placeNumberRel);
+        // 计算费用
+        Map<String, Object> result = calculateParkingFee(parkingRecord, feeRule, registrations);
+        System.out.println("停车费用: " + result.toString());
+    }
+
+    /**
      * 设【免费时长】=0;【24小时收费金额】=30元;【首周期计费时长】=60分钟;【首周期计费金额】=20元;【计费周期】=30分钟;【每周期计费金额】=2元
      */
     public static void e1(){
@@ -234,11 +277,84 @@ public class ParkingFeeCalculator {
         double fee = calculateParkingFee(parkingRecord, feeRule, false);
         log.info("停车费用: " + fee + " 元");
     }
+
+    /**
+     * 包租协议计算停车费用
+     * @param registrations 车牌登记记录列表(已按车牌筛选)
+     * @return 包含停车时长和费用的Map
+     */
+    public static Map<String, Object> calculateParkingFee(ParkingRecord parkingRecord,FeeRule feeRule,
+                                                   List<ContractPlaceNumberRel> registrations) {
+        LocalDateTime entryTime = parkingRecord.entryTime;
+        LocalDateTime currentTime = LocalDateTime.now();
+        Map<String, Object> result = new HashMap<>();
+
+        // 1. 计算总停车时长(分钟)
+        long totalMinutes = Duration.between(entryTime, currentTime).toMinutes();
+
+        // 2. 计算登记记录覆盖的有效时长(分钟)
+        long coveredMinutes = calculateCoveredMinutes(entryTime, currentTime, registrations);
+
+        // 3. 计算实际计费时长
+        long chargedMinutes = Math.max(0, totalMinutes - coveredMinutes);
+
+        // 4. 计算费用(不足1小时按1小时计算)
+//        double hours = Math.ceil(chargedMinutes / 60.0);
+//        double fee = hours * HOURLY_RATE;
+
+        result.put("totalMinutes", totalMinutes);
+        result.put("coveredMinutes", coveredMinutes);
+        result.put("chargedMinutes", chargedMinutes);
+        //实际收费的金额为入场时间+收费时长
+        parkingRecord.exitTime = parkingRecord.entryTime.plusMinutes(chargedMinutes);
+        result.put("fee", calculateParkingFee(parkingRecord, feeRule, false));
+        log.info("包租协议收费:"+result.toString());
+        return result;
+    }
+
+    /**
+     * 计算所有登记记录覆盖的有效时长
+     */
+    private static long calculateCoveredMinutes(LocalDateTime entryTime,
+                                         LocalDateTime currentTime,
+                                         List<ContractPlaceNumberRel> registrations) {
+        return registrations.stream()
+                .mapToLong(reg -> calculateSingleRegistrationMinutes(entryTime, currentTime, reg))
+                .sum();
+    }
+
+    /**
+     * 计算单条登记记录的有效时长
+     */
+    private static long calculateSingleRegistrationMinutes(LocalDateTime entryTime,
+                                                    LocalDateTime currentTime,
+                                                    ContractPlaceNumberRel registration) {
+        LocalDateTime regStartTime = registration.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime regEndTime = registration.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+
+        // 有效开始时间 = max(登记起始时间, 入场时间)
+        LocalDateTime effectiveStart = regStartTime.isAfter(entryTime)
+                ? regStartTime
+                : entryTime;
+
+        // 有效结束时间 = min(登记到期时间, 当前时间)
+        LocalDateTime effectiveEnd = regEndTime.isBefore(currentTime)
+                ? regEndTime
+                : currentTime;
+
+        // 计算有效时长(分钟)
+        if (effectiveStart.isBefore(effectiveEnd)) {
+            return Duration.between(effectiveStart, effectiveEnd).toMinutes();
+        }
+        return 0; // 无有效覆盖
+    }
+
     /**
      * @param args
      */
     public static void main(String[] args) {
 //        e1();
-        e2();
+//        e2();
+        e3();
     }
 }

+ 14 - 6
src/main/java/com/qmrb/system/controller/CaSignController.java

@@ -2451,7 +2451,7 @@ public class CaSignController {
      * @return
      */
     @GetMapping("fetchVehicleQrcode")
-    public Result<?> fetchVehicleQrcode(){
+    public ResponseEntity<?> fetchVehicleQrcode(){
         if(SecurityUtils.getUserId() == null){
             return null;
         }
@@ -2461,13 +2461,21 @@ public class CaSignController {
         try {
             String key = "fetchVehicle";
             String redisKey = "pickUp" + RandomUtil.randomString(10);
-            String base64 = iUserService.getCouponQRCode(toolWxConfigService.findConf(),key,redisKey);
+            // String base64 = iUserService.getCouponQRCodePng(toolWxConfigService.findConf(),key,redisKey);
+            // //保存当前时间戳
+            // redisTemplate.opsForValue().set(redisKey,System.currentTimeMillis(),fetchVehicleTime,TimeUnit.SECONDS);
+            //
+            // map.put("data",base64);
+            //
+            // return Result.success(map);
+
+            byte[] imageBytes = iUserService.getCouponQRCodePng(toolWxConfigService.findConf(),key,redisKey);
             //保存当前时间戳
             redisTemplate.opsForValue().set(redisKey,System.currentTimeMillis(),fetchVehicleTime,TimeUnit.SECONDS);
-            
-            map.put("data",base64);
-            
-            return Result.success(map);
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.IMAGE_JPEG);
+            headers.setContentLength(imageBytes.length);
+            return new ResponseEntity<>(imageBytes, headers, HttpStatus.OK);
         }catch (Exception e){
             log.error(e.getMessage(),e);
         }

+ 6 - 3
src/main/java/com/qmrb/system/service/IContractPlaceNumberRelService.java

@@ -2,14 +2,15 @@ package com.qmrb.system.service;
 
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.qmrb.system.pojo.entity.BarnRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmrb.system.pojo.entity.ContractPlaceNumberRel;
 import com.qmrb.system.pojo.form.ContractPlaceNumberRelForm;
-import com.qmrb.system.pojo.vo.ContractPlaceNumberRelVO;
 import com.qmrb.system.pojo.query.ContractPlaceNumberRelQuery;
-import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmrb.system.pojo.vo.ContractPlaceNumberRelVO;
 import jakarta.validation.Valid;
 
+import java.time.LocalDateTime;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -48,4 +49,6 @@ public interface IContractPlaceNumberRelService extends IService<ContractPlaceNu
 	 * @return
 	 */
 	Map<String,Object> getDenomination(Long recordId);
+
+	public List<ContractPlaceNumberRel> findValidRegistrations(String plateNumber, LocalDateTime entryTime);
 }

+ 2 - 0
src/main/java/com/qmrb/system/service/UserService.java

@@ -35,4 +35,6 @@ public interface UserService extends IService<Order> {
     public String getUserPhoneNumber(ToolWxConfig wxConfig, String code);
 
     String getCouponQRCode(ToolWxConfig conf, String key, String redisKey);
+
+    byte[] getCouponQRCodePng(ToolWxConfig conf, String key, String redisKey);
 }

+ 39 - 26
src/main/java/com/qmrb/system/service/impl/CarParkChargingRulesServiceImpl.java

@@ -1,37 +1,37 @@
 package com.qmrb.system.service.impl;
 
-import java.math.BigDecimal;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.util.*;
-import java.util.stream.Collectors;
-
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Assert;
 import com.alibaba.fastjson.JSON;
-import com.qmrb.parking.fee.ParkingFeeCalculator;
-import com.qmrb.system.mapper.*;
-import com.qmrb.system.pojo.entity.*;
-import com.qmrb.system.service.ICarParkStoredCardService;
-import com.qmrb.system.service.IContractPlaceNumberRelService;
-import com.qmrb.system.service.OrderService;
-import org.springframework.beans.factory.annotation.Autowired;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.qmrb.system.common.enums.StatusEnum;
-import com.qmrb.system.pojo.vo.Option;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmrb.parking.fee.ParkingFeeCalculator;
 import com.qmrb.system.converter.CarParkChargingRulesConverter;
+import com.qmrb.system.mapper.BarnRecordMapper;
+import com.qmrb.system.mapper.CarParkChargingRulesMapper;
+import com.qmrb.system.mapper.CouponMapper;
+import com.qmrb.system.mapper.OrderMapper;
+import com.qmrb.system.pojo.entity.*;
 import com.qmrb.system.pojo.form.CarParkChargingRulesForm;
-import com.qmrb.system.pojo.vo.CarParkChargingRulesVO;
 import com.qmrb.system.pojo.query.CarParkChargingRulesQuery;
+import com.qmrb.system.pojo.vo.CarParkChargingRulesVO;
 import com.qmrb.system.service.ICarParkChargingRulesService;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmrb.system.service.ICarParkStoredCardService;
+import com.qmrb.system.service.IContractPlaceNumberRelService;
+import jakarta.validation.Valid;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
-import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.core.lang.Assert;
-import jakarta.validation.Valid;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /**
  * <p>
@@ -298,12 +298,15 @@ public class CarParkChargingRulesServiceImpl extends ServiceImpl<CarParkCharging
 				Map<String,Object> map = contractPlaceNumberRelService.getDenomination(recordId);
 				System.out.println("包租协议 = " + JSON.toJSONString(map));
 				if(CollectionUtil.isNotEmpty(map)){
+					parkingRecord.contractType = "3";//包租协议
+				}
+				/*if(CollectionUtil.isNotEmpty(map)){
 					ParkingFeeCalculator.CouponRecord couponRecord = new ParkingFeeCalculator.CouponRecord();
 					couponRecord.denomination = (Double) map.get("denomination");
 					couponRecord.expireTime = (LocalDateTime) map.get("expireTime");
 					couponRecord.couponType = "2";
 					parkingRecord.couponRecord = couponRecord;
-				}
+				}*/
 			}
 		}
 		if(CollectionUtil.isNotEmpty(orderList)){
@@ -327,8 +330,18 @@ public class CarParkChargingRulesServiceImpl extends ServiceImpl<CarParkCharging
 			// 计算费用
 			fee = ParkingFeeCalculator.calculateParkingFee(parkingRecord, feeRule, isMonthlyCardMode);
 		}else{
-			//直接计算费用
-			fee = ParkingFeeCalculator.calculateParkingFee(parkingRecord, feeRule, isMonthlyCardMode);
+			//判断是否为包租协议
+			if(StringUtils.equals("3",parkingRecord.contractType)){
+				// 查找有效的车牌登记记录(已按车牌筛选)
+				List<ContractPlaceNumberRel> registrations = contractPlaceNumberRelService.findValidRegistrations(carNumber, parkingRecord.entryTime);
+				//包租协议只计算临停费用
+				Map<String,Object> result  = ParkingFeeCalculator.calculateParkingFee(parkingRecord,feeRule, registrations);
+				fee = Double.parseDouble(result.get("fee").toString());
+			}else{
+				//直接计算费用
+				//1.临停费用 2.扫码协议
+				fee = ParkingFeeCalculator.calculateParkingFee(parkingRecord, feeRule, isMonthlyCardMode);
+			}
 		}
 		
 		return new BigDecimal(fee);

+ 37 - 21
src/main/java/com/qmrb/system/service/impl/ContractPlaceNumberRelServiceImpl.java

@@ -4,49 +4,37 @@ package com.qmrb.system.service.impl;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUnit;
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.qmrb.system.common.constant.SystemConstants;
-import com.qmrb.system.common.result.PageResult;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmrb.parking.fee.ParkingFeeCalculator;
 import com.qmrb.system.converter.ContractPlaceNumberRelConverter;
 import com.qmrb.system.framework.security.util.SecurityUtils;
 import com.qmrb.system.mapper.BarnRecordMapper;
+import com.qmrb.system.mapper.ContractPlaceNumberRelMapper;
 import com.qmrb.system.pojo.entity.*;
-import com.qmrb.system.pojo.entity.ContractPlaceNumberRel;
 import com.qmrb.system.pojo.form.ContractPlaceNumberRelForm;
-import com.qmrb.system.pojo.vo.BarnRecordVO;
-import com.qmrb.system.pojo.vo.ContractPlaceNumberRelHisVO;
-import com.qmrb.system.pojo.vo.ContractPlaceNumberRelVO;
 import com.qmrb.system.pojo.query.ContractPlaceNumberRelQuery;
-import com.qmrb.system.mapper.ContractPlaceNumberRelMapper;
-import com.qmrb.system.pojo.vo.PayOrderVO;
+import com.qmrb.system.pojo.vo.ContractPlaceNumberRelVO;
 import com.qmrb.system.service.IContractPlaceNumberRelHisService;
 import com.qmrb.system.service.IContractPlaceNumberRelService;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmrb.system.service.IContractService;
 import com.qmrb.system.service.SysUserService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import cn.hutool.core.lang.Assert;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-
 import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * <p>
@@ -75,6 +63,7 @@ public class ContractPlaceNumberRelServiceImpl extends ServiceImpl<ContractPlace
     IContractPlaceNumberRelHisService contractPlaceNumberRelHisService;
 
 
+
     /**
      * 分页查询
      */
@@ -302,4 +291,31 @@ public class ContractPlaceNumberRelServiceImpl extends ServiceImpl<ContractPlace
         return null;
     }
 
+
+    /**
+     * 计算停车费用
+     * @param plateNumber 车牌号(仅用于查询)
+     * @return 停车费用信息
+     */
+  /*  public Map<String, Object> calculateParkingFee(ParkingFeeCalculator.ParkingRecord parkingRecord, ParkingFeeCalculator.FeeRule feeRule, String plateNumber) {
+        // 查找有效的车牌登记记录(已按车牌筛选)
+        List<ContractPlaceNumberRel> registrations = findValidRegistrations(plateNumber, parkingRecord.entryTime);
+
+        // 计算费用
+        return parkingFeeCalculator.calculateParkingFee(parkingRecord,feeRule, registrations);
+    }*/
+
+    /**
+     * 查找有效的车牌登记记录
+     */
+    public List<ContractPlaceNumberRel> findValidRegistrations(String plateNumber, LocalDateTime entryTime) {
+        LambdaQueryWrapper<ContractPlaceNumberRel> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ContractPlaceNumberRel::getPlateNumber, plateNumber);
+        queryWrapper.eq(ContractPlaceNumberRel::getStatus, "1"); // 启用状态
+        queryWrapper.lt(ContractPlaceNumberRel::getStartTime, LocalDateTime.now()); // 起始时间 < 当前时间
+        queryWrapper.gt(ContractPlaceNumberRel::getEndTime, entryTime); // 到期时间 > 入场时间
+
+        return this.list(queryWrapper);
+    }
+
 }

+ 75 - 0
src/main/java/com/qmrb/system/service/impl/UserServiceImpl.java

@@ -209,6 +209,81 @@ public class UserServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         return result;
     }
 
+    @Override
+    public byte[] getCouponQRCodePng(ToolWxConfig conf, String key, String redisKey) {
+        String token =  getWxAccessToken(conf);
+        
+        Map<String, Object> resultObj = new HashMap();
+        PrintWriter out = null;
+        InputStream in = null;
+        String result = "";
+        try {
+            URL realUrl = new URL("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token="+token);
+            // 打开和URL之间的连接
+            URLConnection conn = realUrl.openConnection();
+            // 设置通用的请求属性
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent",
+                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            // 发送POST请求必须设置如下两行
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            // 获取URLConnection对象对应的输出流
+            out = new PrintWriter(conn.getOutputStream());
+            // 发送请求参数
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("scene", key + "=" + redisKey); //参数自定义
+            jsonObject.put("page","pages/index/index");//要生成小程序码的链接
+            jsonObject.put("width",500);
+            jsonObject.put("env_version", conf.getEnvVersion());
+            out.print(jsonObject);
+            // flush输出流的缓冲
+            out.flush();
+            in = conn.getInputStream();
+            byte[] data = null;
+            // 读取图片字节数组
+            try {
+                ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
+                byte[] buff = new byte[100];
+                int rc = 0;
+                while ((rc = in.read(buff, 0, 100)) > 0) {
+                    swapStream.write(buff, 0, rc);
+                }
+                data = swapStream.toByteArray();
+            } catch (IOException e) {
+                log.error("获取领券二维码出错",e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        log.error("获取领券二维码出错",e);
+                    }
+                }
+            }
+            return data;
+        } catch (Exception e) {
+            System.out.println("发送 POST 请求出现异常!" + e);
+            log.error("发送 POST 请求出现异常!",e);
+        }
+        // 使用finally块来关闭输出流、输入流
+        finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ex) {
+                log.error("关闭输出流、输入流!",ex);
+            }
+        }
+//            resultObj.put("result",result);
+        return null;
+    }
+
     public UserInfo getMyInfo(UserParam userParam){
         UserInfo userInfo = new UserInfo();
         userInfo.setId("1");