From d467ca1d892ff64288ec6e05c01a151984896b6b Mon Sep 17 00:00:00 2001 From: hanrenchun Date: Wed, 3 Dec 2025 08:49:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E4=B8=8E=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gather-app/pom.xml | 11 ++ .../controller/AlipayNotifyController.java | 58 ++++-- .../controller/PaymentController.java | 54 ----- .../controller/SmallProgramController.java | 186 ++++++++++++++---- .../controller/WxPayNotifyController.java | 53 +++-- .../java/com/ruoyi/database/domain/Order.java | 49 ----- .../database/domain/ParkingBillInfo.java | 4 + .../domain/ParkingBillPaymentInfo.java | 18 +- .../database/domain/vo/ParkingBillInfoVO.java | 96 +++++++++ .../database/service/AlipayPhoneService.java | 59 +----- .../ruoyi/database/service/AlipayService.java | 13 +- .../ruoyi/database/service/OrderService.java | 50 ----- .../service/ParkingBillInfoService.java | 7 + .../impl/ParkingBillInfoServiceImpl.java | 45 +++++ .../ruoyi/database/util/QRCodeGenerator.java | 170 ++++++++++++++++ .../framework/config/SecurityConfig.java | 2 +- 16 files changed, 584 insertions(+), 291 deletions(-) delete mode 100644 gather-app/src/main/java/com/ruoyi/database/controller/PaymentController.java delete mode 100644 gather-app/src/main/java/com/ruoyi/database/domain/Order.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/domain/vo/ParkingBillInfoVO.java delete mode 100644 gather-app/src/main/java/com/ruoyi/database/service/OrderService.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator.java diff --git a/gather-app/pom.xml b/gather-app/pom.xml index 4c10c7a..c22ecf7 100644 --- a/gather-app/pom.xml +++ b/gather-app/pom.xml @@ -54,5 +54,16 @@ 2.3.0 + + com.google.zxing + core + 3.5.1 + + + com.google.zxing + javase + 3.5.1 + + diff --git a/gather-app/src/main/java/com/ruoyi/database/controller/AlipayNotifyController.java b/gather-app/src/main/java/com/ruoyi/database/controller/AlipayNotifyController.java index 20b5088..0b61e33 100644 --- a/gather-app/src/main/java/com/ruoyi/database/controller/AlipayNotifyController.java +++ b/gather-app/src/main/java/com/ruoyi/database/controller/AlipayNotifyController.java @@ -2,6 +2,11 @@ package com.ruoyi.database.controller; import com.alipay.api.internal.util.AlipaySignature; import com.ruoyi.config.AlipayRYConfig; +import com.ruoyi.database.domain.ParkingBillInfo; +import com.ruoyi.database.domain.ParkingBillPaymentInfo; +import com.ruoyi.database.service.ParkingBillInfoService; +import com.ruoyi.database.service.ParkingBillPaymentInfoService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; @@ -13,8 +18,15 @@ import java.util.Map; public class AlipayNotifyController { private final AlipayRYConfig alipayConfig; - // 假设你有一个处理订单业务的服务 - // private final OrderService orderService; + + + @Autowired + private ParkingBillPaymentInfoService parkingBillPaymentInfoService; + + @Autowired + private ParkingBillInfoService parkingBillInfoService; + + public AlipayNotifyController(AlipayRYConfig alipayConfig) { this.alipayConfig = alipayConfig; @@ -53,27 +65,39 @@ public class AlipayNotifyController { String tradeStatus = params.get("trade_status"); String outTradeNo = params.get("out_trade_no"); // 你的商户订单号 String tradeNo = params.get("trade_no"); // 支付宝交易号 + String gmtPayment = params.get("gmt_payment"); // 支付宝交易号 // 4. 判断交易状态 if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISH".equals(tradeStatus)) { // 支付成功 - - // 4.1 幂等性处理:查询本地订单是否已处理,避免重复通知导致业务重复执行[citation:5] - // if (orderService.isOrderPaid(outTradeNo)) { - // return "success"; - // } - - // 4.2 校验金额(重要!防止金额被篡改)[citation:5] - // String totalAmount = params.get("total_amount"); - // if (!orderService.verifyOrderAmount(outTradeNo, totalAmount)) { - // return "failure"; - // } - - // 4.3 更新本地订单状态为已支付 - // orderService.updateOrderToPaid(outTradeNo, tradeNo); + ParkingBillPaymentInfo one = parkingBillPaymentInfoService.lambdaQuery() + .eq(ParkingBillPaymentInfo::getOrderId, outTradeNo) + .last("limit 1") + .one(); + if (one == null) { + return "failure"; + } + one.setPayStatus(1); + one.setPayTime(gmtPayment); + one.setTransactionId(tradeNo); + boolean b = parkingBillPaymentInfoService.updateById(one); + if (!b){ + return "failure"; + } + ParkingBillInfo parkingBillInfo = parkingBillInfoService.lambdaQuery() + .eq(ParkingBillInfo::getBillCode, one.getBillCode()) + .last("limit 1") + .one(); + if (parkingBillInfo == null) { + return "failure"; + } + parkingBillInfo.setIsPay(1); + boolean b1 = parkingBillInfoService.updateById(parkingBillInfo); + if (!b1) { + return "failure"; + } System.out.println("订单支付成功: " + outTradeNo + ", 支付宝交易号: " + tradeNo); } - // 5. 处理成功,返回 'success' 字符串给支付宝,支付宝将停止重复通知[citation:5] return "success"; } catch (Exception e) { diff --git a/gather-app/src/main/java/com/ruoyi/database/controller/PaymentController.java b/gather-app/src/main/java/com/ruoyi/database/controller/PaymentController.java deleted file mode 100644 index d1902b1..0000000 --- a/gather-app/src/main/java/com/ruoyi/database/controller/PaymentController.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.ruoyi.database.controller; - -import com.ruoyi.database.domain.Order; -import com.ruoyi.database.domain.PayFees; -import com.ruoyi.database.service.OrderService; -import com.ruoyi.database.service.WechatMiniProgramPayService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; - -@RestController -@RequestMapping("/api/payment") -public class PaymentController { - - @Autowired - private WechatMiniProgramPayService wechatPayService; - - @Autowired - private OrderService orderService; - - /** - * 创建支付订单 - */ - @PostMapping("/create") - public Map createPayment(@RequestBody PayFees payFees, - HttpServletRequest request) { - try { - // 1. 创建业务订单 - orderService.createOrder(payFees); - - // 2. 获取客户端IP - String clientIp = wechatPayService.getClientIpAddress(request); - - // 3. 创建支付订单 -// Map result = wechatPayService.createJsapiOrder( -// openid, orderId, amount, description, clientIp); - -// return result; - - - } catch (Exception e) { -// System.err.println("创建支付订单异常: " + e.getMessage()); -// e.printStackTrace(); -// -// Map result = new HashMap<>(); -// result.put("success", false); -// result.put("message", "系统异常: " + e.getMessage()); -// return result; - } - return null; - } -} diff --git a/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java b/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java index 1930a33..155271d 100644 --- a/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java +++ b/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java @@ -1,35 +1,30 @@ package com.ruoyi.database.controller; -import cn.hutool.core.util.ReUtil; -import cn.hutool.json.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.model.LoginUserByPhone; -import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.database.domain.*; -import com.ruoyi.database.mapper.ParkingBillInfoMapper; -import com.ruoyi.database.service.CustomerPlateNoInfoService; -import com.ruoyi.database.service.ParkingBillInfoService; -import com.ruoyi.database.service.ParkingBillPaymentInfoService; -import com.ruoyi.database.util.HaiKangApiUtils; +import com.ruoyi.database.domain.vo.ParkingBillInfoVO; +import com.ruoyi.database.enums.PayType; +import com.ruoyi.database.service.*; import com.ruoyi.database.util.LicensePlateValidator; import com.ruoyi.framework.web.service.TokenService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @Api(tags = "小程序接口") @@ -39,39 +34,30 @@ import java.util.stream.Collectors; public class SmallProgramController extends BaseController { private final ParkingBillInfoService parkingBillInfoService; + private final AlipayService alipayService; + private final WechatPhoneService wechatPhoneService; + private final AlipayPhoneService alipayPhoneService; + private final WechatMiniProgramPayService wechatMiniProgramPayService; private final ParkingBillPaymentInfoService parkingBillPaymentInfoService; private final CustomerPlateNoInfoService customerPlateNoInfoService; private final TokenService tokenService; - private final HaiKangApiUtils haiKangApiUtils; - private final ObjectMapper objectMapper = new ObjectMapper(); @GetMapping("/getOrder") @ApiOperation("根据车牌号查询缴费订单") + @Transactional public AjaxResult getOrder(String plateNo) { - String parkingPaymentInfo = haiKangApiUtils.getParkingPaymentInfo(plateNo); - if(parkingPaymentInfo==null){ - return AjaxResult.error("未查询到停车信息"); - } - JSONObject jsonObject = new JSONObject(parkingPaymentInfo); - String data = jsonObject.getJSONObject("data").toString(); - if(data==null){ - return AjaxResult.error("未查询到停车信息"); - } - try { - ParkingBill vehicleRecord = objectMapper.readValue(data, ParkingBill.class); - ParkingBillInfo parkingBillInfo = new ParkingBillInfo(); - BeanUtils.copyProperties(vehicleRecord,parkingBillInfo); - parkingBillInfo.setPayMoneyYuan(vehicleRecord.getPayMoneyYuan()); - parkingBillInfo.setDeductMoneyYuan(vehicleRecord.getDeductMoneyYuan()); - parkingBillInfo.setTotalCostYuan(vehicleRecord.getTotalCostYuan()); - LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); - updateWrapper.eq(ParkingBillInfo::getBillCode,parkingBillInfo.getBillCode()); - parkingBillInfoService.saveOrUpdate(parkingBillInfo,updateWrapper); - return AjaxResult.success(parkingBillInfo); - } catch (JsonProcessingException e) { - return AjaxResult.error("未查询到停车信息"); + ParkingBillInfo b = parkingBillInfoService.saveOrUpdateParkingBillInfo(plateNo); + if (b == null) { + return AjaxResult.error("未查询到订单"); + }else { + ParkingBillInfoVO parkingBillInfoVO = new ParkingBillInfoVO(); + BeanUtils.copyProperties(b, parkingBillInfoVO); + parkingBillInfoVO.setTotalCostYuan(b.getTotalCostYuan().toString() + "元"); + parkingBillInfoVO.setPayMoneyYuan(b.getPayMoneyYuan().toString() + "元"); + parkingBillInfoVO.setDeductMoneyYuan(b.getDeductMoneyYuan().toString() + "元"); + return AjaxResult.success(parkingBillInfoVO); } } @@ -109,8 +95,27 @@ public class SmallProgramController extends BaseController { @ApiOperation("查询历史未缴费订单") public AjaxResult getHistoryNoPayOrder(HttpServletRequest request){ LoginUserByPhone loginUserByPhone = tokenService.getLoginUserByPhone(request); - - return AjaxResult.success(); + List list = customerPlateNoInfoService.lambdaQuery() + .eq(CustomerPlateNoInfo::getPhone, loginUserByPhone.getPhone()) + .list(); + List collect = list.stream().map(CustomerPlateNoInfo::getPlateNo).collect(Collectors.toList()); + if (collect.isEmpty()){ + return AjaxResult.success(collect); + } + List list1 = parkingBillInfoService.lambdaQuery() + .in(ParkingBillInfo::getPlateNo, collect) + .eq(ParkingBillInfo::getIsPay, 0) + .list(); + List parkingBillInfoVOS = new ArrayList<>(); + list1.forEach(b -> { + ParkingBillInfoVO parkingBillInfoVO = new ParkingBillInfoVO(); + BeanUtils.copyProperties(b, parkingBillInfoVO); + parkingBillInfoVO.setTotalCostYuan(b.getTotalCostYuan().toString() + "元"); + parkingBillInfoVO.setPayMoneyYuan(b.getPayMoneyYuan().toString() + "元"); + parkingBillInfoVO.setDeductMoneyYuan(b.getDeductMoneyYuan().toString() + "元"); + parkingBillInfoVOS.add(parkingBillInfoVO); + }); + return AjaxResult.success(parkingBillInfoVOS); } @@ -158,9 +163,110 @@ public class SmallProgramController extends BaseController { @PostMapping("/payFees") @ApiOperation("缴费") + @Transactional public AjaxResult payFees(@RequestBody PayFees payFees,HttpServletRequest request){ LoginUserByPhone loginUserByPhone = tokenService.getLoginUserByPhone(request); - return AjaxResult.success(); + + ParkingBillInfo one = parkingBillInfoService.lambdaQuery() + .eq(ParkingBillInfo::getBillCode, payFees.getBillCode()) + .last("limit 1") + .one(); + if (one == null) { + return AjaxResult.error("订单查询失败"); + } + one = parkingBillInfoService.saveOrUpdateParkingBillInfo(one.getPlateNo()); + if (one == null) { + return AjaxResult.error("订单查询失败"); + } + String generate = generate(loginUserByPhone.getPhone()); + String openId = getOpenId(payFees); + String clientIp = wechatMiniProgramPayService.getClientIpAddress(request); + Map order = createOrder(payFees, generate, openId, one, clientIp); + + ParkingBillPaymentInfo parkingBillPaymentInfo = new ParkingBillPaymentInfo(); + parkingBillPaymentInfo.setBillCode(one.getBillCode()); + parkingBillPaymentInfo.setPlateNo(one.getPlateNo()); + parkingBillPaymentInfo.setPlateColor(one.getPlateColor()); + parkingBillPaymentInfo.setOpenId(openId); + parkingBillPaymentInfo.setPayType(payFees.getPayType()); + parkingBillPaymentInfo.setPhone(loginUserByPhone.getPhone()); + parkingBillPaymentInfo.setPayStatus(0); + parkingBillPaymentInfo.setOrderId(generate); + boolean b = parkingBillPaymentInfoService.saveOrUpdate(parkingBillPaymentInfo); + if (b) { + return AjaxResult.success(order); + }else { + return AjaxResult.error("创建订单出错"); + } + + } + /** + * 获取用户openid + * @param payFees + * @return + */ + private String getOpenId(PayFees payFees) { + PayType payType = PayType.getByCode(payFees.getPayType()); + + switch (payType) { + case WECHAT: + return wechatPhoneService.getOpenId(payFees.getCode()); + + case ALIPAY: + return alipayPhoneService.getAlipayUserId(payFees.getCode()); + + default: + throw new IllegalArgumentException("不支持的登录方式: " + payType); + } + } + + /** + * 创建支付宝 微信 订单 + * @param payFees + * @param orderId + * @param openId + * @param parkingBillInfo + * @param clientIp + * @return + */ + private Map createOrder(PayFees payFees, + String orderId, + String openId, + ParkingBillInfo parkingBillInfo, + String clientIp) { + PayType payType = PayType.getByCode(payFees.getPayType()); + + switch (payType) { + case WECHAT: + return wechatMiniProgramPayService.createJsapiOrder( + openId,orderId,parkingBillInfo.getPayMoney(), + "常客隆智慧停车缴费",clientIp); + case ALIPAY: + return alipayService.createOrder(orderId, + parkingBillInfo.getPayMoneyYuan().toString(), + "常客隆智慧停车缴费",openId); + + default: + throw new IllegalArgumentException("不支持的登录方式: " + payType); + } + } + + + + + public static String generate(String phone) { + // 1. 参数校验 + if (phone == null || phone.length() < 4) { + throw new IllegalArgumentException("Invalid phone number"); + } + // 2. 获取手机尾号(后4位更常见) + String phoneSuffix = phone.substring(phone.length() - 4); + // 3. 使用更精确的时间戳 + String timePart = Instant.now().toEpochMilli() + ""; // 毫秒时间戳 + // 4. 增加更多随机性 + int random = ThreadLocalRandom.current().nextInt(1000, 9999); + return phoneSuffix + timePart.substring(timePart.length() - 10) + random; + } } diff --git a/gather-app/src/main/java/com/ruoyi/database/controller/WxPayNotifyController.java b/gather-app/src/main/java/com/ruoyi/database/controller/WxPayNotifyController.java index 723e77f..5331b55 100644 --- a/gather-app/src/main/java/com/ruoyi/database/controller/WxPayNotifyController.java +++ b/gather-app/src/main/java/com/ruoyi/database/controller/WxPayNotifyController.java @@ -3,7 +3,10 @@ package com.ruoyi.database.controller; import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.service.WxPayService; -import com.ruoyi.database.service.OrderService; +import com.ruoyi.database.domain.ParkingBillInfo; +import com.ruoyi.database.domain.ParkingBillPaymentInfo; +import com.ruoyi.database.service.ParkingBillInfoService; +import com.ruoyi.database.service.ParkingBillPaymentInfoService; import com.ruoyi.database.service.WechatMiniProgramPayService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; @@ -18,6 +21,12 @@ public class WxPayNotifyController { @Autowired private WechatMiniProgramPayService wechatPayService; + @Autowired + private ParkingBillPaymentInfoService parkingBillPaymentInfoService; + + @Autowired + private ParkingBillInfoService parkingBillInfoService; + /** * 微信支付结果通知回调 @@ -43,25 +52,35 @@ public class WxPayNotifyController { System.out.println("解析通知数据: orderId=" + orderId + ", transactionId=" + transactionId + ", totalFee=" + totalFee); + ParkingBillPaymentInfo one = parkingBillPaymentInfoService.lambdaQuery() + .eq(ParkingBillPaymentInfo::getOrderId, orderId) + .last("limit 1") + .one(); + if (one == null) { + return WxPayNotifyResponse.fail("处理失败"); + } + one.setPayStatus(1); + one.setPayTime(timeEnd); + one.setTransactionId(transactionId); + boolean b = parkingBillPaymentInfoService.updateById(one); + if (!b){ + return WxPayNotifyResponse.fail("处理失败"); + } -// // 4. 幂等性处理:检查订单是否已处理 -// if (orderService.isOrderPaid(orderId)) { -// System.out.println("订单已处理,直接返回成功"); -// return WxPayNotifyResponse.success("OK"); -// } -// -// // 5. 校验订单金额(重要!防止资金损失) -// if (!orderService.verifyOrderAmount(orderId, totalFee)) { -// System.out.println("订单金额校验失败"); -// return WxPayNotifyResponse.fail("订单金额不一致"); -// } -// -// // 6. 处理业务逻辑(更新订单状态等) -// orderService.updateOrderToPaid(orderId, transactionId); - + ParkingBillInfo parkingBillInfo = parkingBillInfoService.lambdaQuery() + .eq(ParkingBillInfo::getBillCode, one.getBillCode()) + .last("limit 1") + .one(); + if (parkingBillInfo == null) { + return WxPayNotifyResponse.fail("处理失败"); + } + parkingBillInfo.setIsPay(1); + boolean b1 = parkingBillInfoService.updateById(parkingBillInfo); + if (!b1){ + return WxPayNotifyResponse.fail("处理失败"); + } System.out.println("支付通知处理成功"); System.out.println("========== 支付通知处理完成 =========="); - // 7. 返回成功响应(必须返回success) return WxPayNotifyResponse.success("OK"); diff --git a/gather-app/src/main/java/com/ruoyi/database/domain/Order.java b/gather-app/src/main/java/com/ruoyi/database/domain/Order.java deleted file mode 100644 index 37cfa4f..0000000 --- a/gather-app/src/main/java/com/ruoyi/database/domain/Order.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.ruoyi.database.domain; - - -import com.ruoyi.database.enums.OrderStatus; -import lombok.Data; -import java.time.LocalDateTime; - -@Data -public class Order { - /** - * 商户订单号 - */ - private String orderId; - - /** - * 订单金额(单位:分) - */ - private Integer amount; - - /** - * 订单描述 - */ - private String description; - - /** - * 用户openid - */ - private String openid; - - /** - * 订单状态 - */ - private OrderStatus status; - - /** - * 创建时间 - */ - private LocalDateTime createTime; - - /** - * 支付时间 - */ - private LocalDateTime payTime; - - /** - * 微信支付订单号 - */ - private String transactionId; -} diff --git a/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillInfo.java b/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillInfo.java index b07cd14..6da3b8c 100644 --- a/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillInfo.java +++ b/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillInfo.java @@ -166,4 +166,8 @@ public class ParkingBillInfo { private Date updateTime; + + @ApiModelProperty("是否支付") + @Excel(name = "是否支付") + private Integer isPay; } diff --git a/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillPaymentInfo.java b/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillPaymentInfo.java index 1d93770..99b2e59 100644 --- a/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillPaymentInfo.java +++ b/gather-app/src/main/java/com/ruoyi/database/domain/ParkingBillPaymentInfo.java @@ -82,11 +82,11 @@ public class ParkingBillPaymentInfo { private String openId; /** - * 支付宝用户ID + * 支付方式 */ - @ApiModelProperty("支付宝用户ID") - @Excel(name = "支付宝用户ID") - private String alipayUserId; + @ApiModelProperty("支付方式") + @Excel(name = "支付方式") + private String payType; /** * 手机号 @@ -116,9 +116,17 @@ public class ParkingBillPaymentInfo { */ @ApiModelProperty("支付时间") @Excel(name = "支付时间") - private Long payTime; + private String payTime; @TableField(exist = false) private String payTimeCn; + /** + * 商品订单id + */ + @ApiModelProperty("商品订单id") + @Excel(name = "商品订单id") + private String orderId; + + } diff --git a/gather-app/src/main/java/com/ruoyi/database/domain/vo/ParkingBillInfoVO.java b/gather-app/src/main/java/com/ruoyi/database/domain/vo/ParkingBillInfoVO.java new file mode 100644 index 0000000..77a0af1 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/domain/vo/ParkingBillInfoVO.java @@ -0,0 +1,96 @@ +package com.ruoyi.database.domain.vo; + + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 停车场账单表(ParkingBillInfo)实体类 + * + * @author makejava + * @since 2025-12-01 15:37:06 + */ +@Data +public class ParkingBillInfoVO { + + /** + * 主键ID + */ + private Long id; + + /** + * 账单编号 + */ + private String billCode; + + /** + * 停车场编码 + */ + private String parkCode; + + /** + * 停车场名称 + */ + private String parkName; + + /** + * 车牌号 + */ + private String plateNo; + + /** + * 车牌颜色 + */ + private Integer plateColor; + + private String plateColorCn; + + /** + * 入场时间戳(毫秒) + */ + private Long enterTime; + + private String enterTimeCn; + + /** + * 离场时间戳(毫秒) + */ + private Long costTime; + + private String costTimeCn; + + /** + * 停车时长(分钟) + */ + private Integer parkPeriodTime; + + + private String totalCostYuan; + + + private String deductMoneyYuan; + + + private String payMoneyYuan; + + + private String inUnid; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/AlipayPhoneService.java b/gather-app/src/main/java/com/ruoyi/database/service/AlipayPhoneService.java index e2b8986..2ea671b 100644 --- a/gather-app/src/main/java/com/ruoyi/database/service/AlipayPhoneService.java +++ b/gather-app/src/main/java/com/ruoyi/database/service/AlipayPhoneService.java @@ -82,9 +82,9 @@ public class AlipayPhoneService { } /** - * 第一步:使用授权码获取访问令牌 + * 获取用户id */ - private String getAccessToken(String authCode) { + private String getOpenId(String authCode) { try { Map params = buildBaseParams(); params.put("method", "alipay.system.oauth.token"); @@ -113,7 +113,7 @@ public class AlipayPhoneService { throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: 响应格式错误"); } - String accessToken = (String) tokenResponse.get("access_token"); + String accessToken = (String) tokenResponse.get("open_id"); if (accessToken == null || accessToken.trim().isEmpty()) { throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: access_token为空"); } @@ -127,59 +127,6 @@ public class AlipayPhoneService { } } - /** - * 第二步:使用access_token获取用户信息(包含手机号) - */ - private String getUserPhoneNumber(String accessToken) { - try { - Map params = buildBaseParams(); - params.put("method", "alipay.user.info.share"); - params.put("auth_token", accessToken); - - log.info("获取用户信息请求参数: {}", params); - - String sign = generateSign(params); - params.put("sign", sign); - - String responseBody = callAlipayApiWithCharset(params); - log.info("获取用户信息响应: {}", responseBody); - - Map responseMap = objectMapper.readValue(responseBody, Map.class); - Map userResponse = (Map) responseMap.get("alipay_user_info_share_response"); - - if (userResponse == null) { - Map errorResponse = (Map) responseMap.get("error_response"); - if (errorResponse != null) { - String subCode = (String) errorResponse.get("sub_code"); - String subMsg = (String) errorResponse.get("sub_msg"); - throw new BusinessException("ALIPAY_USER_INFO_ERROR", - String.format("获取用户信息失败[%s]: %s", subCode, subMsg)); - } - throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息失败: 响应格式错误"); - } - - // 提取手机号 - String mobile = (String) userResponse.get("mobile"); - if (mobile == null || mobile.trim().isEmpty()) { - // 有些用户可能没有绑定手机号 - log.warn("用户未绑定手机号或手机号为空"); - throw new BusinessException("ALIPAY_PHONE_EMPTY", "用户未绑定手机号"); - } - - // 验证手机号格式 - if (!validatePhoneNumber(mobile)) { - log.warn("获取到的手机号格式可能不正确: {}", mobile); - } - - log.info("成功获取用户手机号: {}", mobile); - return mobile; - - } catch (Exception e) { - log.error("获取用户信息异常", e); - throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息异常: " + e.getMessage()); - } - } - /** * 修复后的API调用方法 - 正确处理charset参数 */ diff --git a/gather-app/src/main/java/com/ruoyi/database/service/AlipayService.java b/gather-app/src/main/java/com/ruoyi/database/service/AlipayService.java index 8851a4b..2dfa5da 100644 --- a/gather-app/src/main/java/com/ruoyi/database/service/AlipayService.java +++ b/gather-app/src/main/java/com/ruoyi/database/service/AlipayService.java @@ -9,11 +9,15 @@ import com.alipay.api.response.AlipayTradeCreateResponse; import com.ruoyi.config.AlipayRYConfig; import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.Map; + @Service public class AlipayService { private final AlipayRYConfig alipayRYConfig; + public AlipayService(AlipayRYConfig alipayRYConfig) { this.alipayRYConfig = alipayRYConfig; } @@ -27,7 +31,7 @@ public class AlipayService { * @param buyerId 支付宝用户ID(相当于OpenID) * @return 支付订单字符串,用于小程序前端调起支付 */ - public String createOrder(String orderId, String amount, String subject, String buyerId) { + public Map createOrder(String orderId, String amount, String subject, String buyerId) { try { AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); // 构造请求参数以调用接口 @@ -54,7 +58,12 @@ public class AlipayService { if (response.isSuccess()) { System.out.println("支付宝预下单成功,交易号: " + response.getTradeNo()); // 返回给前端的订单字符串,用于调起支付 - return response.getOutTradeNo(); + Map maps = new HashMap<>(); + maps.put("success", response.isSuccess()); + maps.put("tradeNO", response.getTradeNo()); + maps.put("outTradeNo", response.getOutTradeNo()); + return maps; +// return response.getOutTradeNo(); // 注意:在实际开发中,你可能需要返回一个包含更多信息的对象或重新构造一个支付参数字符串。 // 具体返回格式需与前端调用 my.tradePay 时所需的参数保持一致。 } else { diff --git a/gather-app/src/main/java/com/ruoyi/database/service/OrderService.java b/gather-app/src/main/java/com/ruoyi/database/service/OrderService.java deleted file mode 100644 index a56323c..0000000 --- a/gather-app/src/main/java/com/ruoyi/database/service/OrderService.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.ruoyi.database.service; -import java.util.Date; - -import com.ruoyi.database.domain.Order; -import com.ruoyi.database.domain.ParkingBillInfo; -import com.ruoyi.database.domain.ParkingBillPaymentInfo; -import com.ruoyi.database.domain.PayFees; -import com.ruoyi.database.enums.OrderStatus; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Service -@RequiredArgsConstructor -public class OrderService { - - private final ParkingBillPaymentInfoService parkingBillPaymentInfoService; - private final ParkingBillInfoService parkingBillInfoService; - - /** - * 创建订单 - */ - public void createOrder(PayFees payFees) { - ParkingBillInfo one = parkingBillInfoService.lambdaQuery() - .eq(ParkingBillInfo::getBillCode, payFees.getBillCode()) - .last("limit 1") - .one(); - if (one == null) { - throw new RuntimeException("未查询到订单"); - } - System.out.println("收到创建支付订单请求: orderId=" + payFees.getBillCode() + - ", amount=" + one.getTotalCostYuan()); -// ParkingBillPaymentInfo parkingBillPaymentInfo = new ParkingBillPaymentInfo(); -// parkingBillPaymentInfo.setBillCode(payFees.getBillCode()); -// parkingBillPaymentInfo.setPlateNo(one.getPlateNo()); -// parkingBillPaymentInfo.setPlateColor(one.getPlateColor()); -// parkingBillPaymentInfo.setOpenId(""); -// parkingBillPaymentInfo.setAlipayUserId(""); -// parkingBillPaymentInfo.setPhone(); -// parkingBillPaymentInfo.setPayStatus(0); - - -// orderCache.put(orderId, order); - - - } - -} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/ParkingBillInfoService.java b/gather-app/src/main/java/com/ruoyi/database/service/ParkingBillInfoService.java index feaa92d..325cc5a 100644 --- a/gather-app/src/main/java/com/ruoyi/database/service/ParkingBillInfoService.java +++ b/gather-app/src/main/java/com/ruoyi/database/service/ParkingBillInfoService.java @@ -11,4 +11,11 @@ import com.ruoyi.database.domain.ParkingBillInfo; */ public interface ParkingBillInfoService extends IService { + /** + * (车在停车场内时) + * 根据车牌查询并更新停车账单 + * @param plateNo 车牌 + */ + public ParkingBillInfo saveOrUpdateParkingBillInfo(String plateNo); + } diff --git a/gather-app/src/main/java/com/ruoyi/database/service/impl/ParkingBillInfoServiceImpl.java b/gather-app/src/main/java/com/ruoyi/database/service/impl/ParkingBillInfoServiceImpl.java index bfd1844..8638cab 100644 --- a/gather-app/src/main/java/com/ruoyi/database/service/impl/ParkingBillInfoServiceImpl.java +++ b/gather-app/src/main/java/com/ruoyi/database/service/impl/ParkingBillInfoServiceImpl.java @@ -1,10 +1,19 @@ package com.ruoyi.database.service.impl; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.bean.BeanUtils; +import com.ruoyi.database.domain.ParkingBill; import com.ruoyi.database.domain.ParkingBillInfo; import com.ruoyi.database.mapper.ParkingBillInfoMapper; import com.ruoyi.database.service.ParkingBillInfoService; +import com.ruoyi.database.util.HaiKangApiUtils; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; /** @@ -14,6 +23,42 @@ import org.springframework.stereotype.Service; * @since 2025-12-01 15:37:06 */ @Service +@RequiredArgsConstructor public class ParkingBillInfoServiceImpl extends ServiceImpl implements ParkingBillInfoService { + + private final HaiKangApiUtils haiKangApiUtils; + private final ObjectMapper objectMapper = new ObjectMapper(); + + + @Override + public ParkingBillInfo saveOrUpdateParkingBillInfo(String plateNo) { + String parkingPaymentInfo = haiKangApiUtils.getParkingPaymentInfo(plateNo); + if(parkingPaymentInfo==null){ + return null; + } + JSONObject jsonObject = new JSONObject(parkingPaymentInfo); + String data = jsonObject.getJSONObject("data").toString(); + if(data==null){ + return null; + } + try { + ParkingBill vehicleRecord = objectMapper.readValue(data, ParkingBill.class); + ParkingBillInfo parkingBillInfo = new ParkingBillInfo(); + BeanUtils.copyProperties(vehicleRecord,parkingBillInfo); + parkingBillInfo.setPayMoneyYuan(vehicleRecord.getPayMoneyYuan()); + parkingBillInfo.setDeductMoneyYuan(vehicleRecord.getDeductMoneyYuan()); + parkingBillInfo.setTotalCostYuan(vehicleRecord.getTotalCostYuan()); + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ParkingBillInfo::getBillCode,parkingBillInfo.getBillCode()); + boolean b = saveOrUpdate(parkingBillInfo, updateWrapper); + if (b) { + return parkingBillInfo; + }else { + return null; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator.java b/gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator.java new file mode 100644 index 0000000..d106cef --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator.java @@ -0,0 +1,170 @@ +package com.ruoyi.database.util; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.client.j2se.MatrixToImageWriter; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +public class QRCodeGenerator { + + /** + * 生成二维码并保存到本地 + * @param content 二维码内容 + * @param width 宽度 + * @param height 高度 + * @param filePath 保存路径 + */ + public static void generateQRCode(String content, int width, int height, String filePath) { + try { + // 设置二维码参数 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + hints.put(EncodeHintType.MARGIN, 1); // 设置白边 + + // 生成二维码矩阵 + BitMatrix bitMatrix = new MultiFormatWriter().encode( + content, + BarcodeFormat.QR_CODE, + width, + height, + hints + ); + + // 方法1:使用MatrixToImageWriter直接保存(最简单) + String format = filePath.substring(filePath.lastIndexOf(".") + 1); + MatrixToImageWriter.writeToPath(bitMatrix, format, Paths.get(filePath)); + + System.out.println("二维码已保存到: " + new File(filePath).getAbsolutePath()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 生成带logo的二维码 + */ + public static void generateQRCodeWithLogo(String content, int width, int height, + String filePath, String logoPath) { + try { + // 1. 生成基础二维码 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + hints.put(EncodeHintType.MARGIN, 1); + + BitMatrix bitMatrix = new MultiFormatWriter().encode( + content, BarcodeFormat.QR_CODE, width, height, hints); + + // 2. 转换为BufferedImage + BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix); + + // 3. 添加logo + if (logoPath != null && new File(logoPath).exists()) { + BufferedImage logoImage = ImageIO.read(new File(logoPath)); + qrImage = addLogo(qrImage, logoImage); + } + + // 4. 保存图片 + String format = filePath.substring(filePath.lastIndexOf(".") + 1); + ImageIO.write(qrImage, format, new File(filePath)); + + System.out.println("带Logo的二维码已保存到: " + new File(filePath).getAbsolutePath()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 添加Logo到二维码中心 + */ + private static BufferedImage addLogo(BufferedImage qrImage, BufferedImage logoImage) { + // 计算logo尺寸(二维码的1/5) + int logoWidth = logoImage.getWidth() > qrImage.getWidth() * 2 / 10 ? + qrImage.getWidth() * 2 / 10 : logoImage.getWidth(); + int logoHeight = logoImage.getHeight() > qrImage.getHeight() * 2 / 10 ? + qrImage.getHeight() * 2 / 10 : logoImage.getHeight(); + + // 缩放logo + BufferedImage scaledLogo = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_ARGB); + scaledLogo.getGraphics().drawImage(logoImage.getScaledInstance(logoWidth, logoHeight, + java.awt.Image.SCALE_SMOOTH), 0, 0, null); + + // 计算logo位置(居中) + int x = (qrImage.getWidth() - logoWidth) / 2; + int y = (qrImage.getHeight() - logoHeight) / 2; + + // 将logo绘制到二维码上 + qrImage.getGraphics().drawImage(scaledLogo, x, y, null); + + return qrImage; + } + + /** + * 根据您提供的坐标数据生成二维码 + */ + public static void generateFromCoordinates(String filePath) { + try { + // 假设这是您的坐标数据 + String[] coordinates = { + "10:1", + "11:1", + "12:1" + // 这里应该包含完整的坐标数据 + }; + + // 解析坐标数据 + int size = 1080; // 二维码尺寸 + BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB); + + // 白色背景 + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + image.setRGB(x, y, 0xFFFFFF); // 白色 + } + } + + // 根据坐标绘制黑色模块 + for (String coord : coordinates) { + String[] parts = coord.split(":"); + if (parts.length == 2) { + int row = Integer.parseInt(parts[0].trim()); + int col = Integer.parseInt(parts[1].trim()); + if (row < size && col < size) { + image.setRGB(col, row, 0x000000); // 黑色 + } + } + } + + // 保存图片 + ImageIO.write(image, "PNG", new File(filePath)); + System.out.println("根据坐标生成的二维码已保存到: " + new File(filePath).getAbsolutePath()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + // 示例1:生成普通二维码 + String content = "C:\\Users\\28758\\Desktop\\222.png"; + String filePath = "C:\\Users\\28758\\Desktop\\666.png"; + + // 生成二维码 + generateQRCode(content, 1080, 640, filePath); + + // 示例2:生成带logo的二维码 + String logoPath = "C:\\Users\\28758\\Desktop\\222.png"; + String qrWithLogoPath = "C:\\Users\\28758\\Desktop\\777.png"; + generateQRCodeWithLogo(content, 1080, 640, qrWithLogoPath, logoPath); + + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index baa98b5..86e70ea 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -111,7 +111,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - .antMatchers("/login","/loginByPhone", "/register", "/captchaImage").permitAll() + .antMatchers("/login","/loginByPhone","/alipay/notify","/wxpay/notify", "/register", "/captchaImage").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()