diff --git a/ruoyi-mall/pom.xml b/ruoyi-mall/pom.xml index 29d41093..0a579c49 100644 --- a/ruoyi-mall/pom.xml +++ b/ruoyi-mall/pom.xml @@ -30,6 +30,8 @@ springfox-boot-starter + + io.swagger @@ -61,6 +63,23 @@ ruoyi-generator + + + cn.hutool + hutool-all + 5.8.25 + + + com.alibaba + fastjson + 1.2.37 + + + + org.apache.httpcomponents + httpclient + 4.5.5 + diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/DeviceController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/DeviceController.java new file mode 100644 index 00000000..d315bff6 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/DeviceController.java @@ -0,0 +1,58 @@ +package com.ruoyi.web.controller; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.web.domain.Device; +import com.ruoyi.web.service.DeviceService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + + +/** + * 设备信息管理 + */ +@RestController +@RequestMapping("/mall/device") +public class DeviceController extends BaseController { + + @Resource + private DeviceService deviceService; + + /** + * 获取指定门店的设备信息 + * + * @return + */ + @Anonymous + @GetMapping(value = "/getDevice/{storeId}") + public AjaxResult getBrandTree(@PathVariable String storeId) { + return AjaxResult.success(deviceService.getDevice(storeId)); + } + + + /** + * 获取指定门店的设备信息 + * + * @return + */ + @Anonymous + @GetMapping(value = "/getSheXiangTou/{storeId}") + public AjaxResult getSheXiangTou(@PathVariable String storeId) { + return AjaxResult.success(deviceService.getSheXiangTou(storeId)); + } + + /** + * 添加门店设备 + * + * @param device + * @return + */ + @Anonymous + @PostMapping("/add") + public AjaxResult addDevice(@RequestBody Device device) { + device.setCreateBy(getUsername()); + return toAjax(deviceService.addDevice(device)); + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/DoorScanRecordController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/DoorScanRecordController.java new file mode 100644 index 00000000..a74f89d9 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/DoorScanRecordController.java @@ -0,0 +1,68 @@ +package com.ruoyi.web.controller; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.web.domain.DoorScanRecord; +import com.ruoyi.web.domain.Order; +import com.ruoyi.web.service.DoorScanRecordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.HashMap; + + +/** + * 扫码记录管理 + */ +@RestController +@RequestMapping("/mall/doorScanRecord") +public class DoorScanRecordController extends BaseController { + + @Autowired + private DoorScanRecordService doorScanRecordService; + + /** + * 新增开门记录(扫码成功时调用) + */ + @Anonymous + @Log(title = "扫码记录管理", businessType = BusinessType.INSERT) + @PostMapping("/add") + public AjaxResult addDoorScanRecord(@RequestBody DoorScanRecord doorScanRecord) { + doorScanRecord.setCreateBy(getUsername()); + doorScanRecord.setScanTime(new Date()); + return toAjax(doorScanRecordService.addDoorScanRecord(doorScanRecord)); + } + + /** + * 获取扫码开门记录 + * + * @param doorScanRecord + * @return + */ + @Anonymous + @GetMapping("/list") + public AjaxResult list(DoorScanRecord doorScanRecord) { + return AjaxResult.success(doorScanRecordService.selectScanRecordList(doorScanRecord)); + } + + + /** + * 根据年月日查询进店人数 + * + * @param date 日期 (格式: yyyy-MM-dd) + * @return 进店人数统计结果 + */ + @GetMapping("/count") + public AjaxResult getEnterCount(@RequestParam String date,@RequestParam Integer storeId) { + try { + Integer count = doorScanRecordService.getEnterCountByDate(date,storeId); + return AjaxResult.success(count); + } catch (IllegalArgumentException e) { + return AjaxResult.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/InstallTaskController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/InstallTaskController.java new file mode 100644 index 00000000..55ca47bf --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/InstallTaskController.java @@ -0,0 +1,105 @@ +package com.ruoyi.web.controller; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.utils.SecurityUtils; + +import com.ruoyi.web.domain.InstallTask; +import com.ruoyi.web.service.InstallTaskService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 安装任务查看 + */ +@RestController +@RequestMapping("/mall/install/task") +public class InstallTaskController extends BaseController { + + @Autowired + private InstallTaskService installTaskService; + + /** + * 门店提交安装任务 + */ + @PostMapping("/submit") + public AjaxResult submit(@RequestBody InstallTask installTask) { + installTask.setCreateBy(SecurityUtils.getUsername()); + return toAjax(installTaskService.insertInstallTask(installTask)); + } + + /** + * 查询安装任务列表 + */ + @Anonymous +// @PreAuthorize("@ss.hasPermi('mall:install:list')") + @GetMapping("/list") + public TableDataInfo list(InstallTask installTask) { + startPage(); + List list = installTaskService.selectInstallTaskList(installTask); + return getDataTable(list); + } + + /** + * 根据ID查询详情 + */ + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable Long id) { + return AjaxResult.success(installTaskService.selectInstallTaskById(id)); + } + + /** + * 修改安装任务 + */ + @PreAuthorize("@ss.hasPermi('mall:install:edit')") + @PutMapping("/edit") + public AjaxResult edit(@RequestBody InstallTask installTask) { + installTask.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(installTaskService.updateInstallTask(installTask)); + } + + /** + * 指派任务 + */ + @PreAuthorize("@ss.hasPermi('mall:install:assign')") + @PostMapping("/assign") + public AjaxResult assign(@RequestParam Long id, + @RequestParam Long assigneeId, + @RequestParam String assigneeName) { + return toAjax(installTaskService.assignTask(id, assigneeId, assigneeName, + SecurityUtils.getUsername())); + } + + /** + * 完成任务安装 + */ + @PreAuthorize("@ss.hasPermi('mall:install:complete')") + @PostMapping("/complete") + public AjaxResult complete(@RequestBody InstallTask installTask) { + installTask.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(installTaskService.completeTask(installTask)); + } + + /** + * 删除安装任务 + */ + @PreAuthorize("@ss.hasPermi('mall:install:remove')") + @DeleteMapping("/{id}") + public AjaxResult remove(@PathVariable Long id) { + return toAjax(installTaskService.deleteInstallTaskById(id)); + } + + /** + * 批量删除 + */ + @PreAuthorize("@ss.hasPermi('mall:install:remove')") + @DeleteMapping("/batch/{ids}") + public AjaxResult batchRemove(@PathVariable Long[] ids) { + return toAjax(installTaskService.deleteInstallTaskByIds(ids)); + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java index 3ba366cd..ed19f494 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java @@ -5,18 +5,26 @@ import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.core.page.TableDataInfo; + +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.web.domain.Order; +import com.ruoyi.web.dto.CreateOrderDTO; +import com.ruoyi.web.dto.ScanPayRequest; +import com.ruoyi.web.dto.TianquePayCallbackDTO; +import com.ruoyi.web.dto.TradeQueryDTO; import com.ruoyi.web.service.OrderService; -import org.checkerframework.checker.units.qual.A; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.util.List; +import java.util.HashMap; +import java.util.Map; /** - * 商品订单管理 + * 商品订单支付管理 */ @RestController @RequestMapping("/mall/order") @@ -25,14 +33,124 @@ public class OrderController extends BaseController { @Resource private OrderService orderService; + private static final Logger log = LoggerFactory.getLogger(OrderController.class); + + + /** + * 获取指定门店的支付订单数量,和支付金额总额 + * + * @return + */ + @Anonymous + @GetMapping(value = "/getOrderCount/{storeId}") + public AjaxResult getOrderCount(@PathVariable String storeId) { + return AjaxResult.success(orderService.getOrderCount(storeId)); + } + + + + /** + * 支付结果主动查询,前端使用去查询支付状态 + */ + @Anonymous + @PostMapping("/tradeQuery") + public AjaxResult tradeQuery(@RequestBody TradeQueryDTO tradeQueryDTO) throws Exception { + return orderService.tradeQuery(tradeQueryDTO); + } + + + + /** + * 被扫 创建订单,返回订单号 + */ + @Anonymous + @PostMapping("/addOrder") + public AjaxResult addOrder(@Validated @RequestBody CreateOrderDTO createOrderDTO) { + createOrderDTO.setCreateBy(getUsername()); + return orderService.addOrder(createOrderDTO); + } + + + /** + * 被扫 发起支付请求 + */ + @Anonymous + @PostMapping("/addOrderReverseScan") + public AjaxResult addOrderReverseScan(@RequestBody ScanPayRequest scanPayRequest) throws Exception { + scanPayRequest.setCreateBy(getUsername()); + return AjaxResult.success(orderService.addOrderReverseScan(scanPayRequest)); + } + /** * 查询订单列表 */ // @PreAuthorize("@ss.hasPermi('mall:order:list')") @Anonymous - @GetMapping("/list") - public AjaxResult list(Order order) { - return AjaxResult.success( orderService.selectOrderList(order)); + @PostMapping("/list") + public AjaxResult list(@RequestBody Order order) { + return AjaxResult.success(orderService.selectOrderList(order)); } + /** + * 创建订单并支付 + * 用户点击支付时调用 + */ + @Anonymous + @PostMapping("/addPay") + public AjaxResult addPay(@Validated @RequestBody CreateOrderDTO createOrderDTO) { + createOrderDTO.setCreateBy(getUsername()); + return orderService.addPay(createOrderDTO); + } + + /** + * 天阙平台支付回调接口 + * 支付结果异步通知 + *

+ * 接口说明: + * 交易成功后,天阙平台会将成功状态通知到交易上送的回调地址(notifyUrl) + * 合作方接收到通知结果后需要返回:{"code":"success", "msg":"成功"} + * 天阙平台收到正确应答后将不再进行通知,否则将继续推送10次 + * * 失败返回: + * { + * "code": "fail", + * "msg": "错误信息" + * } + * 注:失败的交易不推送回调,交易成功才会回调 + */ + @PostMapping("/notify") + @Anonymous + public Map notify(@RequestBody String callbackData) { + log.info("收到天阙平台支付回调, data:{}", callbackData); + Map response = new HashMap<>(); + try { + //解析回调数据 + TianquePayCallbackDTO request = orderService.parseCallback(callbackData); + //检查业务编码(0000表示成功) + if (!request.isSuccess()) { + log.info("交易失败回调, ordNo:{}, bizCode:{}, bizMsg:{}", + request.getOrdNo(), request.getBizCode(), request.getBizMsg()); + response.put("code", "fail"); + response.put("msg", "交易失败"); + return response; + } + //处理支付回调业务逻辑 + orderService.handleTianquePayCallback(request); + + //天阙平台要求的格式 + response.put("code", "success"); + response.put("msg", "成功"); + + log.info("天阙平台支付回调处理成功, ordNo:{}, uuid:{}", + request.getOrdNo(), request.getUuid()); + } catch (ServiceException e) { + log.error("天阙平台支付回调业务处理失败: {}", e.getMessage()); + response.put("code", "fail"); + response.put("msg", e.getMessage()); + } catch (Exception e) { + log.error("天阙平台支付回调处理异常", e); + response.put("code", "fail"); + response.put("msg", "系统异常"); + } + return response; + } } \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java index 05d571fc..33fedbd0 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java @@ -13,6 +13,7 @@ import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.MimeTypeUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.web.domain.Product; +import com.ruoyi.web.dto.BatchBarcodeQueryDTO; import com.ruoyi.web.dto.BatchStoreDTO; import com.ruoyi.web.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +34,22 @@ public class ProductStoreController extends BaseController { @Autowired private ProductService productService; + /** + * 批量根据条码查询商品 + * 支持一次查询多个条码,返回商品列表 + */ + @PostMapping("/getByBarcodes") + public AjaxResult getByBarcodes(@RequestBody BatchBarcodeQueryDTO dto) { + if (dto.getBarcodes() == null || dto.getBarcodes().isEmpty()) { + return AjaxResult.error("条码列表不能为空"); + } + if (dto.getStoreId() == null) { + return AjaxResult.error("门店id不能为空"); + } + return AjaxResult.success(productService.getByBarcodes(dto)); + } + + /** * 获取商品列表 */ @@ -77,7 +94,6 @@ public class ProductStoreController extends BaseController { } //todo 这里可以国码有的时候使用国码的图片路径 - // TODO: 2026/1/14 这个file 为空的时候 就使用国码里面的图片,不为空代表用户自己选择了图片上传 用自己服务器上的 //国码后续添加 if (file != null) { diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java index e5f91399..119a6fe0 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java @@ -5,6 +5,9 @@ import lombok.Data; import java.util.List; +/** + * 商品分类 + */ @Data public class Classification extends BaseEntity { private static final long serialVersionUID = 1L; diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Device.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Device.java new file mode 100644 index 00000000..1c5294c0 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Device.java @@ -0,0 +1,65 @@ +package com.ruoyi.web.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import java.util.Date; + +/** + * 设备信息表 mall_device_info + * + */ +@Data +public class Device extends BaseEntity { + + /** 设备ID */ + private Long id; + + /** 所属门店ID,关联mall_store表的store_id */ + private Long storeId; + + /** 设备编号(厂商唯一标识) */ + private String deviceCode; + + /** 设备名称 */ + private String deviceName; + + /** 设备类型:1-摄像头 2-扫码枪 3-打印机 4-收银机 */ + private Integer deviceType; + + /** 设备品牌 */ + private String deviceBrand; + + /** 设备型号 */ + private String deviceModel; + + /** 通道 */ + private String channel; + + /** MAC地址 */ + private String macAddress; + + /** 设备授权码/密钥(前端调设备用) */ + private String authCode; + + /** 安装日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date installDate; + + /** 有效期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date expireDate; + + /** 状态:0-离线 1-在线 2-故障 3-已报废 */ + private Integer deviceStatus; + + /** 最后心跳时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date lastHeartbeat; + + /** 备注 */ + private String remark; + + + +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/DoorScanRecord.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/DoorScanRecord.java new file mode 100644 index 00000000..35528646 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/DoorScanRecord.java @@ -0,0 +1,30 @@ +package com.ruoyi.web.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; + +import java.util.Date; + +/** + * 扫码记录 + */ +@Data +public class DoorScanRecord extends BaseEntity { + + private Long id; + + private String operatorName; // 扫码人姓名 + + private String operatorPhone; // 扫码人手机号 + + private Integer storeId; // 门店ID + + private String shopName; // 门店名称 + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date scanTime; // 扫码时间 + + private String scanType; //'扫码类型 0:进门 1:出门' + +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/InstallTask.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/InstallTask.java new file mode 100644 index 00000000..722852b5 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/InstallTask.java @@ -0,0 +1,107 @@ +package com.ruoyi.web.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import java.util.Date; + +/** + * 安装任务表 mall_install_task + * + * @author ruoyi + */ +@Data +public class InstallTask extends BaseEntity { + + /** 任务ID */ + private Long id; + + /** 任务编号 */ + private String taskNo; + + /** 申请门店ID */ + private Long storeId; + + /** 门店名称 */ + private String storeName; + + /** 联系人 */ + private String contactPerson; + + /** 联系电话 */ + private String contactPhone; + + /** 设备类型:1-摄像头 2-扫码枪 3-打印机 4-收银机 */ + private Integer deviceType; + + /** 期望设备型号 */ + private String deviceModel; + + /** 安装地址 */ + private String installAddress; + + /** 期望安装时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date expectedTime; + + /** 备注/特殊要求 */ + private String remark; + + /** 状态:0-待处理 1-已接单 2-安装中 3-已完成 4-已取消 */ + private Integer taskStatus; + + /** 指派人ID */ + private Long assigneeId; + + /** 指派人姓名 */ + private String assigneeName; + + /** 指派时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date assignTime; + + /** 安装的设备ID */ + private Long deviceId; + + /** 设备编号 */ + private String deviceCode; + + /** 设备授权码 */ + private String authCode; + + /** 实际安装时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date installTime; + + /** 安装照片URL */ + private String installPhoto; + + /** 完成备注 */ + private String completeRemark; + + + /** 设备类型描述 */ + public String getDeviceTypeDesc() { + if (deviceType == null) return ""; + switch (deviceType) { + case 1: return "摄像头"; + case 2: return "扫码枪"; + case 3: return "打印机"; + case 4: return "收银机"; + default: return "未知"; + } + } + + /** 任务状态描述 */ + public String getTaskStatusDesc() { + if (taskStatus == null) return ""; + switch (taskStatus) { + case 0: return "待处理"; + case 1: return "已接单"; + case 2: return "安装中"; + case 3: return "已完成"; + case 4: return "已取消"; + default: return "未知"; + } + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java index ebbdef7c..a21e49cb 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java @@ -33,7 +33,7 @@ public class Order extends BaseEntity { /** * 支付金额 */ - private BigDecimal payAmount; + private String payAmount; /** * 支付状态: 0-未支付,1-支付中,2-已支付,3-支付失败,4-已退款 @@ -46,6 +46,11 @@ public class Order extends BaseEntity { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date payTime; + /** + * 商品条码 + */ + private String productBarCode; + /** * 订单商品明细列表 */ diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java index cce9e2a3..db767d91 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java @@ -5,6 +5,8 @@ import com.ruoyi.common.core.domain.BaseEntity; import lombok.Data; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; import java.math.BigDecimal; @@ -25,9 +27,16 @@ public class OrderItem extends BaseEntity { */ private Long orderId; + + /** + * 商品条码 + */ + private String productBarCode; + /** * 商品ID */ + @NotNull(message = "商品ID不能为空") private Long productId; /** @@ -58,6 +67,8 @@ public class OrderItem extends BaseEntity { /** * 购买数量 */ + @NotNull(message = "数量不能为空") + @Min(value = 1, message = "数量必须大于0") private Integer quantity; /** diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java index bed02ce2..8d6188bc 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java @@ -85,16 +85,16 @@ public class Product extends BaseEntity { /** * 门店销售价 */ - private BigDecimal storePrice; + private String storePrice; /** * 成本价 */ - private BigDecimal costPrice; + private String costPrice; /** * 市场价 */ - private Long originalPrice; + private String originalPrice; /** * 门店ID @@ -117,4 +117,15 @@ public class Product extends BaseEntity { */ private String status; + /** + * 是否查询临期商品 + */ + private Boolean isExpiring; + + /** + * 是否查询过期商品 + */ + private Boolean isExpired; + + } diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/TianquePayResponse.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/TianquePayResponse.java new file mode 100644 index 00000000..268eaaa1 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/TianquePayResponse.java @@ -0,0 +1,79 @@ +package com.ruoyi.web.domain; + +import lombok.Data; + +/** + * 天阙支付响应 + */ +@Data +public class TianquePayResponse { + + /** 返回码:0000-成功 */ + private String code; + + /** 返回信息 */ + private String msg; + + /** 签名 */ + private String sign; + + /** 签名类型 */ + private String signType; + + /** 组织机构ID */ + private String orgId; + + /** 请求ID */ + private String reqId; + + /** 响应数据 */ + private RespData respData; + + @Data + public static class RespData { + /** 业务码:0000-成功 */ + private String bizCode; + + /** 业务信息 */ + private String bizMsg; + + /** 落单号 */ + private String sxfUuid; + + /** 商户订单号 */ + private String ordNo; + + /** 天阙平台订单号 */ + private String uuid; + + /** 预支付ID */ + private String prepayId; + + /** 支付AppID */ + private String payAppId; + + /** 时间戳 */ + private String payTimeStamp; + + /** 随机字符串 */ + private String paynonceStr; + + /** 支付包 */ + private String payPackage; + + /** 签名类型 */ + private String paySignType; + + /** 支付签名 */ + private String paySign; + + /** 商户号 */ + private String partnerId; + + /** 跳转地址 */ + private String redirectUrl; + + /** 来源 */ + private String source; + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ApiRequestBean.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ApiRequestBean.java new file mode 100644 index 00000000..48691512 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ApiRequestBean.java @@ -0,0 +1,126 @@ +package com.ruoyi.web.dto; + +import java.io.Serializable; + + +public class ApiRequestBean implements Serializable{ + + private static final long serialVersionUID = -9073910802005694017L; + + /** + * 合作机构id + */ + private String orgId; + + /** + * 请求id + */ + private String reqId; + + /** + * 请求接口版本1.0 + */ + private String version; + + /** + * 代理商身份标识 + */ + private String agentId; + + /** + * 业务员编号 + */ + private String salesCode; + + /** + * 业务数据 + */ + private T reqData; + + /** + * 签名 + */ + private String sign; + + /** + * 签名类型 + */ + private String signType; + + /** + * 请求时间 + */ + private String timestamp; + + public String getOrgId() { + return orgId; + } + + public void setOrgId(String orgId) { + this.orgId = orgId; + } + + public String getReqId() { + return reqId; + } + + public void setReqId(String reqId) { + this.reqId = reqId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getSalesCode() { + return salesCode; + } + + public void setSalesCode(String salesCode) { + this.salesCode = salesCode; + } + + public T getReqData() { + return reqData; + } + + public void setReqData(T reqData) { + this.reqData = reqData; + } + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getSignType() { + return signType; + } + + public void setSignType(String signType) { + this.signType = signType; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/BatchBarcodeQueryDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/BatchBarcodeQueryDTO.java new file mode 100644 index 00000000..d5d304b2 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/BatchBarcodeQueryDTO.java @@ -0,0 +1,15 @@ +package com.ruoyi.web.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class BatchBarcodeQueryDTO { + + //商品条码 + private List barcodes; + + // 可选:门店ID,用于过滤该门店的商品 + private Long storeId; +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/CreateOrderDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/CreateOrderDTO.java new file mode 100644 index 00000000..85e893b7 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/CreateOrderDTO.java @@ -0,0 +1,31 @@ +package com.ruoyi.web.dto; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.web.domain.OrderItem; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class CreateOrderDTO extends BaseEntity { + + /** 门店ID */ + @NotNull(message = "门店ID不能为空") + private Integer storeId; + + /** 商品列表 */ + @NotEmpty(message = "商品列表不能为空") + private List items; + + //总金额 + @NotNull(message = "订单商品总金额不能为空") + private String totalAmount; + + /** 用户openId */ + private String openId; + + /** 备注 */ + private String remark; +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ScanPayRequest.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ScanPayRequest.java new file mode 100644 index 00000000..fb445995 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ScanPayRequest.java @@ -0,0 +1,238 @@ +package com.ruoyi.web.dto; + +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.util.List; + +@Data +public class ScanPayRequest extends BaseEntity { + + /** + * 商户编号,必填,15位 + */ + private String mno; + + /** + * 商户订单号,必填,64位,字母/数字/下划线,需保证在合作方系统中不重复 + */ + private String ordNo; + + /** + * 订单总金额(元),必填,格式:#######.## + */ + private BigDecimal amt; + + /** + * 授权码(付款码),必填,64位 + * 通过扫码枪/声波获取设备获取的支付宝/微信/银联付款码 + */ + private String authCode; + + /** + * 支付渠道,当scene枚举为3或6时必填 + * WECHAT:微信, ALIPAY:支付宝, UNIONPAY:银联, DCEP:数字人民币, YZF:翼支付 + */ + private String payType; + + /** + * 支付场景,选填,默认1 + * 1:付款码, 3:刷脸, 6:离线刷脸 + */ + private String scene = "1"; + + /** + * 订单标题,必填,256位 + */ + private String subject; + + /** + * 商户交易终端ip地址,必填,16位 + */ + private String trmIp; + + /** + * 微信subAppId,选填,32位 + */ + private String subAppid; + + /** + * 微信优惠参数,选填,32位 + * 微信的商品标记,核销优惠券时必传 + */ + private String wxGoodsTag; + + /** + * 订单优惠标识,选填,2位 + * 00:是, 01:否,有优惠参数上传时必传00 + */ + private String goodsTag; + + /** + * 微信电子发票,选填,2位 + * 00:是, 01:否 + */ + private String needReceipt; + + /** + * 支付宝参与优惠金额(元),选填 + */ + private BigDecimal discountAmt; + + /** + * 支付宝不参与优惠金额(元),选填 + */ + private BigDecimal unDiscountAmt; + + /** + * 支付宝花呗分期数,选填 + * 3:分3期, 6:分6期, 12:分12期 + */ + private String hbFqNum; + + /** + * 商家承担手续费比例,选填 + * 0:用户承担, 100:商家承担 + */ + private String hbFqPercent; + + /** + * 限制卡类型,选填,默认00 + * 00:全部支持, 01:限定不能使用信用卡等 + */ + private String limitPay = "00"; + + /** + * 订单失效时间(单位分钟),选填,1-1440,默认5分钟 + */ + private Integer timeExpire = 5; + + + + /** + * 单次分账类型,选填,默认01 + * 00:单次分账, 01:不分账, 04:多次分账 + */ + private String ledgerAccountFlag = "01"; + + /** + * 分账有效时间(单位天),选填 + * ledgerAccountFlag为00或04时必传 + */ + private String ledgerAccountEffectTime; + + /** + * 同步分账规则,选填,与ledgerAccountFlag互斥 + */ + private List fusruleId; + + /** + * 支付结果通知地址,选填,256位 + * 被扫输密时上送,交易成功会异步通知 + */ + private String notifyUrl; + + /** + * 终端号,选填,32位 + * 银联:8位,支付宝微信:不超过32位 + */ + private String ylTrmNo; + + /** + * 证件类型,选填 + * identityFlag=="00"时必传,IDCARD:大陆 + */ + private String buyerIdType; + + /** + * 证件号,选填,18位 + * identityFlag=="00"时必传 + */ + private String buyerIdNo; + + /** + * 买家姓名,选填,32位 + * identityFlag=="00"时必传 + */ + private String buyerName; + + /** + * 手机号,选填,11位 + * identityFlag=="00"时必传 + */ + private String mobileNum; + + /** + * 门店编号,选填,32位,对应支付宝store_id + */ + private String storeId; + + /** + * 蚂蚁门店编号,选填 + */ + private String alipayStoreId; + + /** + * 天阙终端编号,选填,128位 + * 天阙后台绑定终端后生成的终端唯一标识 + */ + private String deviceNo; + + /** + * 天阙门店编号,选填,32位 + */ + private String storeNum; + + /** + * 外部业务号,选填 + */ + private String senceNo; + + /** + * 加密随机因子,选填,10位 + */ + private String encryptRandNum; + + /** + * 密文数据,选填,16位 + */ + private String secretText; + + /** + * 终端经度,选填,16位 + * +表示东经,-表示西经 + */ + private String longitude; + + /** + * 终端纬度,选填,16位 + * +表示北纬,-表示南纬 + */ + private String latitude; + + + + /** + * 支付宝校园ID,选填,20位,支付宝未来校园活动必传 + */ + private String eduSchoolId; + + /** + * 支付宝校园场景,选填,30位,支付宝未来校园活动必传 + */ + private String eduScene; + + /** + * 支付宝业务拓展字段,选填 + * 对应支付宝business_params字段 + */ + private String zfbBusinessParams; + + /** + * 扩展字段,选填,128位 + */ + private String extend; + + +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TianquePayCallbackDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TianquePayCallbackDTO.java new file mode 100644 index 00000000..c1aade20 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TianquePayCallbackDTO.java @@ -0,0 +1,171 @@ +package com.ruoyi.web.dto; + +import lombok.Data; +import java.math.BigDecimal; + +/** + * 天阙平台支付回调请求 + * 对应文档中的回调参数 + */ +@Data +public class TianquePayCallbackDTO { + + /** 响应时间戳 */ + private String timeStamp; + + /** 业务数据签名结果 */ + private String sign; + + /** 商户编号 */ + private String mno; + + /** 商户订单号 */ + private String ordNo; + + /** 天阙平台订单号 */ + private String uuid; + + /** 交易支付完成时间,格式:YYYYMMDDHHmmss */ + private String payTime; + + /** 订单总金额(元),格式:#######.## */ + private String amt; + + /** 支付渠道 */ + private String payType; + + /** 交易方式:00 主扫 01 被扫 02 公众号 03 小程序 */ + private String payWay; + + /** 终端号 */ + private String ylTrmNo; + + /** 原商户订单号(退款通知返回) */ + private String origOrdNo; + + /** 原天阙订单号(退款通知返回) */ + private String origUuid; + + /** 支付场景:1 付款码 2 声波 3 刷脸 */ + private String scene; + + /** 买家用户号 */ + private String buyerId; + + /** 支付宝买家登录账号 */ + private String buyerAccount; + + /** 微信/支付宝流水号 */ + private String transactionId; + + /** 借贷标识:1 借记卡 2 贷记卡 3 其他 */ + private String drType; + + /** 消费者付款金额 */ + private String totalOffstAmt; + + /** 商家入账金额 */ + private String settleAmt; + + /** 付款银行 */ + private String payBank; + + /** 代金券金额 */ + private String pointAmount; + + /** 交易手续费率 */ + private String recFeeRate; + + /** 交易手续费 */ + private String recFeeAmt; + + /** 商家出款金额 */ + private String realRefundAmount; + + /** 渠道商商户号 */ + private String channelId; + + /** 子商户号 */ + private String subMechId; + + /** 消费者到账金额 */ + private String refBuyerAmt; + + /** 微信或支付宝的身份ID */ + private String openid; + + /** 活动类型 */ + private String activityNo; + + /** 落单号 */ + private String sxfUuid; + + /** 扩展字段 */ + private String extend; + + /** 优惠信息 */ + private String promotionDetail; + + /** 天阙门店编号 */ + private String storeNum; + + /** 清算日期 */ + private String clearDt; + + /** 交易完成时间 */ + private String finishTime; + + /** 天阙终端编号 */ + private String deviceNo; + + /** 原交易金额 */ + private String origAmt; + + /** 原交易订单号 */ + private String origOrderNo; + + /** 优惠券退款金额 */ + private String couponRefundFee; + + /** 是否收支两条线标识 */ + private String szltFlag; + + /** 后收手续费 */ + private String szltRecfeeAmt; + + /** 切批时间 */ + private String settlementBatchNo; + + /** 支付宝异步刷脸支付参数 */ + private String asyncPaymentMode; + + /** 分期信息 */ + private String hbFqPayInfo; + + /** 业务编码 */ + private String bizCode; + + /** 业务信息 */ + private String bizMsg; + + /** 组织机构ID */ + private String orgId; + + /** 交易类型 */ + private String tradeType; + + /** 交易信息 */ + private String tradeMsg; + + /** 原始回调数据 */ + private String rawData; + + + + /** + * 判断是否支付成功 + */ + public boolean isSuccess() { + return "0000".equals(bizCode); + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TradeQueryDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TradeQueryDTO.java new file mode 100644 index 00000000..8eac474b --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TradeQueryDTO.java @@ -0,0 +1,12 @@ +package com.ruoyi.web.dto; + +import lombok.Data; + +@Data +public class TradeQueryDTO { + + /** + * 订单号 + */ + private String ordNo; +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DeviceMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DeviceMapper.java new file mode 100644 index 00000000..6d049e97 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DeviceMapper.java @@ -0,0 +1,17 @@ +package com.ruoyi.web.mapper; + +import com.ruoyi.web.domain.Device; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface DeviceMapper { + + List getDevice(@Param("storeId") String storeId); + + int addDevice(Device device); + + List getSheXiangTou(String storeId); +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DoorScanRecordMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DoorScanRecordMapper.java new file mode 100644 index 00000000..5313840b --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DoorScanRecordMapper.java @@ -0,0 +1,20 @@ +package com.ruoyi.web.mapper; + + +import com.ruoyi.web.domain.DoorScanRecord; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +@Mapper +public interface DoorScanRecordMapper { + + + int addDoorScanRecord(DoorScanRecord doorScanRecord); + + List selectScanRecordList(DoorScanRecord doorScanRecord); + + Integer getEnterCountByDate(@Param("date") String date,@Param("storeId") Integer storeId); +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/InstallTaskMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/InstallTaskMapper.java new file mode 100644 index 00000000..880c206b --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/InstallTaskMapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.mapper; + +import com.ruoyi.web.domain.InstallTask; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + * 安装任务Mapper接口 + */ +public interface InstallTaskMapper { + + /** + * 新增安装任务 + */ + int insertInstallTask(InstallTask installTask); + + /** + * 根据ID查询安装任务 + */ + InstallTask selectInstallTaskById(@Param("id") Long id); + + /** + * 根据任务编号查询 + */ + InstallTask selectInstallTaskByTaskNo(@Param("taskNo") String taskNo); + + /** + * 查询安装任务列表 + */ + List selectInstallTaskList(InstallTask installTask); + + /** + * 更新安装任务 + */ + int updateInstallTask(InstallTask installTask); + + /** + * 删除安装任务(逻辑删除) + */ + int deleteInstallTaskById(@Param("id") Long id); + + /** + * 批量删除安装任务 + */ + int deleteInstallTaskByIds(@Param("ids") Long[] ids); + + /** + * 指派任务 + */ + int assignTask(InstallTask installTask); + + /** + * 完成任务安装 + */ + int completeTask(InstallTask installTask); + + /** + * 查询当天最大任务编号 + */ + String selectMaxTaskNoByDate(@Param("prefix") String prefix); +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java index 188b4364..54e142f0 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java @@ -2,7 +2,10 @@ package com.ruoyi.web.mapper; import com.ruoyi.web.domain.Order; +import com.ruoyi.web.domain.OrderItem; +import com.ruoyi.web.vo.OrderCountVO; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -10,7 +13,22 @@ import java.util.List; @Mapper public interface OrderMapper { - - List selectOrderList(Order order); + + + int insert(Order order); + + + int batchInsert(@Param("list") List items); + + Order selectForUpdateByOrderNo(@Param("ordNo") String ordNo); + + List selectByOrderId(@Param("orderId") Long orderId); + + /** + * 根据ID更新订单(选择性更新) + */ + int updateById(Order order); + + OrderCountVO getOrderCount(String storeId); } \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java index 197b4cb7..a8d1995b 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java @@ -2,6 +2,8 @@ package com.ruoyi.web.mapper; import com.ruoyi.web.domain.Product; import com.ruoyi.web.domain.Store; +import com.ruoyi.web.dto.BatchBarcodeQueryDTO; +import com.ruoyi.web.vo.ProductVo; import org.apache.ibatis.annotations.Param; import java.util.List; @@ -26,4 +28,29 @@ public interface ProductMapper { int batchProductById(@Param("ids") List ids); + + + List selectBatchForUpdate(@Param("storeId") Integer storeId, + @Param("ids") List ids); + + + /** + * 扣减库存(带库存校验,带门店ID) + * @param storeId 门店ID + * @param productId 商品ID + * @param quantity 扣减数量 + * @return 影响行数(>0表示成功,=0表示库存不足或商品不存在) + */ + int reduceStock(@Param("storeId") Integer storeId, + @Param("productId") Long productId, + @Param("quantity") Integer quantity); + + /** + * 增加库存(用于回滚,带门店ID) + */ + int addStock(@Param("storeId") Integer storeId, + @Param("productId") Long productId, + @Param("quantity") Integer quantity); + + List getByBarcodes(BatchBarcodeQueryDTO dto); } diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java index 7e9720f9..a6f08705 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java @@ -2,8 +2,6 @@ package com.ruoyi.web.mapper; import com.ruoyi.web.domain.Store; -import com.ruoyi.web.vo.StoreVo; -import com.ruoyi.web.vo.UserStoreVo; import org.apache.ibatis.annotations.Param; import java.util.List; diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/DeviceService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DeviceService.java new file mode 100644 index 00000000..190410da --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DeviceService.java @@ -0,0 +1,15 @@ +package com.ruoyi.web.service; + +import com.ruoyi.web.domain.Brand; +import com.ruoyi.web.domain.Device; + +import java.util.List; + +public interface DeviceService { + + List getDevice(String storeId); + + int addDevice(Device device); + + List getSheXiangTou(String storeId); +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/DoorScanRecordService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DoorScanRecordService.java new file mode 100644 index 00000000..8f0d1459 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DoorScanRecordService.java @@ -0,0 +1,15 @@ +package com.ruoyi.web.service; + +import com.ruoyi.web.domain.DoorScanRecord; + +import java.util.List; + + +public interface DoorScanRecordService { + + int addDoorScanRecord(DoorScanRecord doorScanRecord); + + List selectScanRecordList(DoorScanRecord doorScanRecord); + + Integer getEnterCountByDate(String date,Integer storeId); +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/InstallTaskService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/InstallTaskService.java new file mode 100644 index 00000000..b514e0e2 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/InstallTaskService.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.service; + +import com.ruoyi.web.domain.InstallTask; + +import java.util.List; + +/** + * 安装任务Service接口 + */ +public interface InstallTaskService { + + /** + * 新增安装任务 + */ + int insertInstallTask(InstallTask installTask); + + /** + * 根据ID查询 + */ + InstallTask selectInstallTaskById(Long id); + + /** + * 根据任务编号查询 + */ + InstallTask selectInstallTaskByTaskNo(String taskNo); + + /** + * 查询安装任务列表 + */ + List selectInstallTaskList(InstallTask installTask); + + /** + * 更新安装任务 + */ + int updateInstallTask(InstallTask installTask); + + /** + * 删除安装任务 + */ + int deleteInstallTaskById(Long id); + + /** + * 批量删除 + */ + int deleteInstallTaskByIds(Long[] ids); + + /** + * 指派任务 + */ + int assignTask(Long id, Long assigneeId, String assigneeName, String updateBy); + + /** + * 完成任务安装 + */ + int completeTask(InstallTask installTask); + + /** + * 生成任务编号 + */ + String generateTaskNo(); +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java index 90d80c82..a2ef9d2d 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java @@ -1,7 +1,13 @@ package com.ruoyi.web.service; +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.web.domain.Order; +import com.ruoyi.web.dto.CreateOrderDTO; +import com.ruoyi.web.dto.ScanPayRequest; +import com.ruoyi.web.dto.TianquePayCallbackDTO; +import com.ruoyi.web.dto.TradeQueryDTO; +import com.ruoyi.web.vo.OrderCountVO; import java.util.List; @@ -13,4 +19,21 @@ public interface OrderService { List selectOrderList(Order order); + + AjaxResult addPay(CreateOrderDTO createOrderDTO); + + TianquePayCallbackDTO parseCallback(String callbackData); + + /** + * 处理天阙平台支付回调 + */ + void handleTianquePayCallback(TianquePayCallbackDTO request); + + AjaxResult addOrder(CreateOrderDTO createOrderDTO); + + String addOrderReverseScan(ScanPayRequest scanPayRequest) throws Exception; + + AjaxResult tradeQuery(TradeQueryDTO tradeQueryDTO) throws Exception; + + OrderCountVO getOrderCount(String storeId); } diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java index d5b8486b..81dfb206 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java @@ -1,7 +1,9 @@ package com.ruoyi.web.service; import com.ruoyi.web.domain.Product; +import com.ruoyi.web.dto.BatchBarcodeQueryDTO; import com.ruoyi.web.dto.BatchStoreDTO; +import com.ruoyi.web.vo.ProductVo; import java.util.List; @@ -35,4 +37,6 @@ public interface ProductService { int updateProduct(Product product); int batchProductById(BatchStoreDTO dto); + + List getByBarcodes(BatchBarcodeQueryDTO dto); } diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DeviceServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DeviceServiceImpl.java new file mode 100644 index 00000000..789d4e00 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DeviceServiceImpl.java @@ -0,0 +1,32 @@ +package com.ruoyi.web.service.impl; + + +import com.ruoyi.web.domain.Device; +import com.ruoyi.web.mapper.DeviceMapper; +import com.ruoyi.web.service.DeviceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +public class DeviceServiceImpl implements DeviceService { + + @Autowired + private DeviceMapper deviceMapper; + @Override + public List getDevice(String storeId) { + return deviceMapper.getDevice(storeId); + } + + @Override + public int addDevice(Device device) { + return deviceMapper.addDevice(device); + } + + @Override + public List getSheXiangTou(String storeId) { + return deviceMapper.getSheXiangTou(storeId); + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DoorScanRecordServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DoorScanRecordServiceImpl.java new file mode 100644 index 00000000..032de212 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DoorScanRecordServiceImpl.java @@ -0,0 +1,31 @@ +package com.ruoyi.web.service.impl; + +import com.ruoyi.web.domain.DoorScanRecord; +import com.ruoyi.web.mapper.DoorScanRecordMapper; +import com.ruoyi.web.service.DoorScanRecordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +public class DoorScanRecordServiceImpl implements DoorScanRecordService { + + @Autowired + private DoorScanRecordMapper doorScanRecordMapper; + @Override + public int addDoorScanRecord(DoorScanRecord doorScanRecord) { + return doorScanRecordMapper.addDoorScanRecord(doorScanRecord); + } + + @Override + public List selectScanRecordList(DoorScanRecord doorScanRecord) { + return doorScanRecordMapper.selectScanRecordList(doorScanRecord); + } + + @Override + public Integer getEnterCountByDate(String date,Integer storeId) { + return doorScanRecordMapper.getEnterCountByDate(date,storeId); + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/InstallTaskServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/InstallTaskServiceImpl.java new file mode 100644 index 00000000..6f8ea8ab --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/InstallTaskServiceImpl.java @@ -0,0 +1,119 @@ +package com.ruoyi.web.service.impl; + +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.web.domain.InstallTask; +import com.ruoyi.web.mapper.InstallTaskMapper; +import com.ruoyi.web.service.InstallTaskService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * 安装任务Service实现类 + */ +@Service +public class InstallTaskServiceImpl implements InstallTaskService { + + @Resource + private InstallTaskMapper installTaskMapper; + + @Override + public int insertInstallTask(InstallTask installTask) { + // 生成任务编号 + installTask.setTaskNo(generateTaskNo()); + // 设置初始状态为待处理 + installTask.setTaskStatus(0); + // 设置创建时间 + installTask.setCreateTime(DateUtils.getNowDate()); + return installTaskMapper.insertInstallTask(installTask); + } + + @Override + public InstallTask selectInstallTaskById(Long id) { + return installTaskMapper.selectInstallTaskById(id); + } + + @Override + public InstallTask selectInstallTaskByTaskNo(String taskNo) { + return installTaskMapper.selectInstallTaskByTaskNo(taskNo); + } + + @Override + public List selectInstallTaskList(InstallTask installTask) { + return installTaskMapper.selectInstallTaskList(installTask); + } + + @Override + public int updateInstallTask(InstallTask installTask) { + installTask.setUpdateTime(DateUtils.getNowDate()); + return installTaskMapper.updateInstallTask(installTask); + } + + @Override + public int deleteInstallTaskById(Long id) { + return installTaskMapper.deleteInstallTaskById(id); + } + + @Override + public int deleteInstallTaskByIds(Long[] ids) { + return installTaskMapper.deleteInstallTaskByIds(ids); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int assignTask(Long id, Long assigneeId, String assigneeName, String updateBy) { + InstallTask task = new InstallTask(); + task.setId(id); + task.setAssigneeId(assigneeId); + task.setAssigneeName(assigneeName); + task.setTaskStatus(1); // 1-已接单 + task.setAssignTime(DateUtils.getNowDate()); + task.setUpdateBy(updateBy); + task.setUpdateTime(DateUtils.getNowDate()); + + return installTaskMapper.assignTask(task); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int completeTask(InstallTask installTask) { + installTask.setTaskStatus(3); // 3-已完成 + installTask.setInstallTime(DateUtils.getNowDate()); + installTask.setUpdateTime(DateUtils.getNowDate()); + + return installTaskMapper.completeTask(installTask); + } + + @Override + public String generateTaskNo() { + // 格式:INS + 年月日 + 4位序号 + // 例如:INS202503160001 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String today = sdf.format(new Date()); + String prefix = "INS" + today; + + // 查询当天最大的任务编号 + String maxTaskNo = installTaskMapper.selectMaxTaskNoByDate(prefix); + + int seq = 1; + if (maxTaskNo != null && maxTaskNo.length() >= 14) { + // 截取后4位序号 + String seqStr = maxTaskNo.substring(11); + try { + seq = Integer.parseInt(seqStr) + 1; + } catch (NumberFormatException e) { + seq = 1; + } + } + + // 生成4位序号,不足补0 + String seqStr = String.format("%04d", seq); + + return prefix + seqStr; + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java index 060ac913..3015326b 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java @@ -1,26 +1,803 @@ package com.ruoyi.web.service.impl; +import cn.hutool.core.util.IdUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.web.domain.Order; +import com.ruoyi.web.domain.OrderItem; +import com.ruoyi.web.domain.Product; +import com.ruoyi.web.dto.*; import com.ruoyi.web.mapper.OrderMapper; +import com.ruoyi.web.mapper.ProductMapper; import com.ruoyi.web.service.OrderService; +import com.ruoyi.web.utils.HttpUtils; +import com.ruoyi.web.utils.RSASignature; +import com.ruoyi.web.vo.OrderCountVO; +import com.ruoyi.web.vo.OrderVo; +import com.ruoyi.web.vo.PaymentResponseVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.DigestUtils; import javax.annotation.Resource; -import java.util.List; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; /** * 商品订单 服务层实现 */ @Service public class OrderServiceImpl implements OrderService { + + private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class); @Resource private OrderMapper orderMapper; + @Resource + private ProductMapper productMapper; + @Autowired + private RedisTemplate redisTemplate; + + private static final String ORDER_LOCK_PREFIX = "order:lock:"; @Override public List selectOrderList(Order order) { return orderMapper.selectOrderList(order); } + + @Override + @Transactional(rollbackFor = Exception.class) + public AjaxResult addPay(CreateOrderDTO createOrderDTO) { + //订单号 + String orderNo = null; + //订单id + Long orderId = null; + //锁key + String lockKey = null; + //订单明细 + List orderItems = new ArrayList<>(); + //金额 + BigDecimal totalAmount = BigDecimal.ZERO; + //支付控件参数 + String pay = null; + try { + // 1. 生成锁key(基于门店ID + 商品组合) + lockKey = generateLockKey(createOrderDTO.getStoreId(), createOrderDTO.getItems()); + log.info("尝试获取锁: {}", lockKey); + + // 2. 获取分布式锁 + Boolean locked = redisTemplate.opsForValue() + .setIfAbsent(lockKey, "1", Duration.ofSeconds(6)); + + if (Boolean.FALSE.equals(locked)) { + log.warn("获取锁失败,该门店的商品组合正在被处理: {}", lockKey); + throw new ServiceException("系统繁忙,请稍后重试"); + } + + try { + // 3. 批量查询商品并加行锁(带上门店ID条件) + List productIds = createOrderDTO.getItems().stream() + .map(OrderItem::getProductId) + .collect(Collectors.toList()); + + List products = productMapper.selectBatchForUpdate( + createOrderDTO.getStoreId(), productIds); + + if (products.size() != productIds.size()) { + // 找出不存在的商品 + Set existIds = products.stream() + .map(Product::getId) + .collect(Collectors.toSet()); + + List notExistIds = productIds.stream() + .filter(id -> !existIds.contains(id)) + .collect(Collectors.toList()); + + log.error("部分商品在该门店不存在, 门店id storeId:{}, 商品id productIds:{}", + createOrderDTO.getStoreId(), notExistIds); +// return AjaxResult.error("部分商品在该门店不存在"); + throw new ServiceException("部分商品在该门店不存在"); + + } + + Map productMap = products.stream() + .collect(Collectors.toMap(Product::getId, p -> p)); + + // 5. 校验库存并计算金额 + for (OrderItem item : createOrderDTO.getItems()) { + Product product = productMap.get(item.getProductId()); + + if (product.getStockQuantity() < item.getQuantity()) { + log.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity()); + return AjaxResult.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity()); + } + + // 计算小计 + BigDecimal storePrice = new BigDecimal(product.getStorePrice()); + BigDecimal subtotal = storePrice + .multiply(new BigDecimal(item.getQuantity())) + .setScale(2, RoundingMode.HALF_UP); + + totalAmount = totalAmount.add(subtotal); + // 创建订单明细 + OrderItem orderItem = new OrderItem(); + orderItem.setProductId(product.getId()); + orderItem.setProductName(product.getProductName()); + orderItem.setMainImage(product.getMainImage()); + orderItem.setQuantity(item.getQuantity()); + orderItem.setSubtotal(item.getSubtotal()); + orderItem.setCreateBy(createOrderDTO.getCreateBy()); + orderItems.add(orderItem); + } + + // 6. 校验前端传递的金额和后端计算的金额是否一致 + BigDecimal frontendAmount = new BigDecimal(createOrderDTO.getTotalAmount()); + if (frontendAmount.compareTo(totalAmount) != 0) { + log.error("金额不一致,前端传参金额:{},后端计算金额:{}", + frontendAmount, totalAmount); + return AjaxResult.error("订单总金额异常"); + } + + // 7. 生成订单号 + orderNo = generateOrderNo(); + + // 8. 创建订单主表 + Order order = new Order(); + order.setOrderNo(orderNo); + order.setStoreId(createOrderDTO.getStoreId()); + order.setPayAmount(String.valueOf(totalAmount)); + order.setPayStatus(0); // 未支付 + order.setCreateBy(createOrderDTO.getCreateBy()); + int insert = orderMapper.insert(order); + if (insert <= 0) { + log.error("主订单创建失败"); + throw new ServiceException("主订单创建失败"); + } + orderId = order.getId(); + + // 9. 批量插入订单明细 + for (OrderItem item : orderItems) { + item.setOrderId(orderId); + } + int batchInsertResult = orderMapper.batchInsert(orderItems); + + if (batchInsertResult != orderItems.size()) { + log.error("订单明细创建失败"); + throw new ServiceException("订单明细创建失败"); + } + } finally { + // 释放锁 + redisTemplate.delete(lockKey); + log.info("释放锁: {}", lockKey); + } + // ============ 锁已释放,开始调用支付接口 ============ + pay = pay(orderNo, String.valueOf(totalAmount)); //调用第三方支付成功,返回调用支付控件参数 + } catch (Exception e) { + log.error("创建订单失败", e); + throw new ServiceException("创建订单失败" + e.getMessage()); + } + log.info("订单创建成功, orderNo:{}, 订单ID:{}, storeId:{},支付控件参数:{}", + orderNo, orderId, createOrderDTO.getStoreId(), pay); + return AjaxResult.success("订单创建成功: " + pay); + } + + @Override + public TianquePayCallbackDTO parseCallback(String callbackData) { + try { + return JSON.parseObject(callbackData, TianquePayCallbackDTO.class); + } catch (Exception e) { + log.error("解析天阙平台回调数据失败", e); + throw new RuntimeException("解析回调数据失败"); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void handleTianquePayCallback(TianquePayCallbackDTO request) { + log.info("处理天阙平台支付回调, ordNo:{}, uuid:{}, amt:{}", + request.getOrdNo(), request.getUuid(), request.getAmt()); + + // 1. 参数校验 + if (request.getOrdNo() == null || request.getUuid() == null) { + log.error("回调参数缺失"); + throw new ServiceException("回调参数缺失"); + } + + // 2. 幂等处理(防止重复回调) + String idempotentKey = "tianque:callback:" + request.getUuid(); + Boolean isFirst = redisTemplate.opsForValue() + .setIfAbsent(idempotentKey, "1", Duration.ofHours(24)); + + if (Boolean.FALSE.equals(isFirst)) { + log.info("天阙平台回调已处理过, uuid:{}", request.getUuid()); + throw new ServiceException("天阙平台回调已处理过"); + } + + // 3. 获取订单锁(防止并发处理同一个订单) + String orderLockKey = ORDER_LOCK_PREFIX + request.getOrdNo(); + Boolean locked = redisTemplate.opsForValue() + .setIfAbsent(orderLockKey, "1", Duration.ofSeconds(5)); + + if (Boolean.FALSE.equals(locked)) { + log.warn("订单正在处理中, ordNo:{}", request.getOrdNo()); + throw new ServiceException("订单正在处理中"); + } + + try { + // 4. 查询订单(加行锁) + Order order = orderMapper.selectForUpdateByOrderNo(request.getOrdNo()); + if (order == null) { + log.error("订单不存在, orderNo:{}", request.getOrdNo()); + throw new ServiceException("订单不存在"); + } + + // 5. 检查订单状态 + if (order.getPayStatus() == 2) { + log.info("订单已支付, orderNo:{}", request.getOrdNo()); + throw new ServiceException("订单已支付"); + } + + if (order.getPayStatus() != 0) { + log.error("订单状态错误, orderNo:{}, status:{}", + request.getOrdNo(), order.getPayStatus()); + throw new ServiceException("订单状态错误"); + } + + // 6. 金额校验 + if (order.getPayAmount().compareTo(request.getAmt()) != 0) { + log.error("支付金额不匹配, orderAmount:{}, payAmount:{}", + order.getPayAmount(), request.getAmt()); + throw new ServiceException("支付金额不匹配"); + } + + // 7. 查询订单明细 + List items = orderMapper.selectByOrderId(order.getId()); + + // 8. 扣减实际库存 + for (OrderItem item : items) { + int result = productMapper.reduceStock( + order.getStoreId(), + item.getProductId(), + item.getQuantity() + ); + if (result <= 0) { + log.error("库存扣减失败, storeId:{}, productId:{}, quantity:{}", + order.getStoreId(), item.getProductId(), item.getQuantity()); + throw new ServiceException("库存扣减失败"); + } + } + + // 9. 更新订单状态为已支付 + Order updateOrder = new Order(); + updateOrder.setId(order.getId()); + updateOrder.setPayStatus(2); + updateOrder.setPayTime(new Date()); + + int updated = orderMapper.updateById(updateOrder); + if (updated <= 0) { + log.error("订单状态更新失败, orderNo:{}", request.getOrdNo()); + throw new ServiceException("订单状态更新失败"); + } + log.info("支付回调处理成功, orderNo:{}, storeId:{}", + request.getOrdNo(), order.getStoreId()); + } finally { + // 释放订单锁 + redisTemplate.delete(orderLockKey); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public AjaxResult addOrder(CreateOrderDTO createOrderDTO) { + String orderNo = null; + BigDecimal totalAmount = BigDecimal.ZERO; + //锁key + String lockKey = null; + orderNo = generateOrderNo(); + try { + // 1. 生成锁key(基于门店ID + 商品组合) + lockKey = generateLockKey(createOrderDTO.getStoreId(), createOrderDTO.getItems()); + log.info("尝试获取锁: {}", lockKey); + + // 2. 获取分布式锁 + Boolean locked = redisTemplate.opsForValue() + .setIfAbsent(lockKey, "1", Duration.ofSeconds(6)); + + if (Boolean.FALSE.equals(locked)) { + log.warn("获取锁失败,该门店的商品组合正在被处理: {}", lockKey); + throw new ServiceException("系统繁忙,请稍后重试"); + } + + try { + // 3. 批量查询商品并加行锁(带上门店ID条件) + List productIds = createOrderDTO.getItems().stream() + .map(OrderItem::getProductId) + .collect(Collectors.toList()); + + List products = productMapper.selectBatchForUpdate( + createOrderDTO.getStoreId(), productIds); + + if (products.size() != productIds.size()) { + // 找出不存在的商品 + Set existIds = products.stream() + .map(Product::getId) + .collect(Collectors.toSet()); + + List notExistIds = productIds.stream() + .filter(id -> !existIds.contains(id)) + .collect(Collectors.toList()); + + log.error("部分商品在该门店不存在, 门店id storeId:{}, 商品id productIds:{}", + createOrderDTO.getStoreId(), notExistIds); + throw new ServiceException("部分商品在该门店不存在"); + } + Map productMap = products.stream() + .collect(Collectors.toMap(Product::getId, p -> p)); + // 5. 校验库存并计算金额 + for (OrderItem item : createOrderDTO.getItems()) { + Product product = productMap.get(item.getProductId()); + + if (product.getStockQuantity() < item.getQuantity()) { + log.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity()); + return AjaxResult.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity()); + } + // 计算小计 + BigDecimal storePrice = new BigDecimal(product.getStorePrice()); + BigDecimal subtotal = storePrice + .multiply(new BigDecimal(item.getQuantity())) + .setScale(2, RoundingMode.HALF_UP); + + totalAmount = totalAmount.add(subtotal); + } + + // 6. 校验前端传递的金额和后端计算的金额是否一致 + BigDecimal frontendAmount = new BigDecimal(createOrderDTO.getTotalAmount()); + if (frontendAmount.compareTo(totalAmount) != 0) { + log.error("金额不一致,前端传参金额:{},后端计算金额:{}", + frontendAmount, totalAmount); + return AjaxResult.error("订单总金额异常"); + } + + //订单id + Long orderId = null; + Order order = new Order(); + order.setOrderNo(orderNo); + order.setStoreId(createOrderDTO.getStoreId()); + order.setPayAmount(String.valueOf(createOrderDTO.getTotalAmount())); + order.setPayStatus(0); // 未支付 + order.setCreateBy(createOrderDTO.getCreateBy()); + int insert = orderMapper.insert(order); + if (insert <= 0) { + log.error("主订单创建失败"); + throw new ServiceException("主订单创建失败"); + } + orderId = order.getId(); + + for (OrderItem item : createOrderDTO.getItems()) { + item.setOrderId(orderId); + } + orderMapper.batchInsert(createOrderDTO.getItems()); + log.info("被扫订单创建成功"); + } finally { + // 释放锁 + redisTemplate.delete(lockKey); + log.info("释放锁: {}", lockKey); + } + } catch (Exception e) { + log.error("创建订单失败", e); + throw new ServiceException("创建订单失败" + e.getMessage()); + } + OrderVo orderVo = new OrderVo(); + orderVo.setOrderNo(orderNo); + orderVo.setAmount(String.valueOf(totalAmount)); + return AjaxResult.success(orderVo); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String addOrderReverseScan(ScanPayRequest scanPayRequest) throws Exception { + //再加个校验,根据订单号去查询金额,如果前端传的金额和后端存的不一样,不允许支付 + + //合作方私钥(替换成自己的) + String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM35qbWVNdBAU+oai8EVE3iSpA9wHAt4wOs+SufbkE/FNoMgp4EMrFNrdNkaKLQq4cRzqoNlJY+DZSL54dm7ekKZP/SLhWwNi9tJvhKsFpw6SRwthlKyA8lCW2mzFffFqLhGAayNoZTmHwkq8dotvecARVbiFtakzzFy/O7ATmzw3i57yaVgX2RtD8WWA7MGg5CQP1vvIDlXqd40SQcrJY6GNNv07Brv3fpyW2nXbvrxbOx+87NnG77/R7o8eFcWJF+yrqE1xeN3hDvHBFMFyf1M39Zig2I75vKRvHGqftAZuvlDG0nInwcSiMTE7wDsXvYzRnOsgJNVET7aFhzOvFAgMBAAECggEATjB4nRl2S2Whasnf0Ab77CoZSjn7HUXv2hymBkJNPq3eV0Hh5Pjjqmi7hIs0cdm9AWXd6RzySKpScQNjYQFxEOIdbayfTdzD24L2ivT6ZBca09Z9J/9Rik0wJ4ULTtny5JRc6VwGgOn9Twdsde8V4sr1nHUvpMaRH6WVxW8mX7+udgbK0Uxm5IFROeKGz0DSuG8SBpDhD2EB6P2Q+1fpna17yP1R08HtZNU6lQx7yMesaJuvKkwf4YiSjIhUtdbUXGhmbG5LFzoj3oRs25W68bIOQjT9i196hPQOYj6C7yz72nuY2rwFHhtsocfQRULWOHtsnWts9epvuFruDTdq8QKBgQD8bZhQkyQm6VTP0Hmv6sV9SXXKH0rb2V8hml7DxcpFl+l2/9UesH3qMoFKZ5C1YdPKZBNlD7fp5RxjtJ8MdwIp8MxRgMf/bZpIOx1Lfum1H+dKh5LF+oC4GGtJ5jqnwozNPog4keUn+bqKhiza9snA+6jy446A1Y8GGWlZwUGkVwKBgQDPxb7FZmOcotNnh8mS5pcx6d22UhBHIHz/X/dcjNQuI8tIRaIvkJm/4lfuxejCz3LY9k+VSQ96ZM2L5OqZGDlR7aMXQ3/FhuS4PEkNcDvccQPoig7U5zUUn2Jdqom6Lngh7Ka8B1v24ght0yXHHNChmMg/DJ42EzCnjpydWs6/QwKBgA9qGd0BvzlpEjbGgkfNzFWEQN8g3g9izL5ekN7fmyR4zFbp9He1S1sbzm1euaV53dcEGXMYbKCpzvv/sZ6vPcCV5cQsWwosBTnX8kgD7f2Tfyo78SiJzYZwZ0zR9E7+QF7gLK1Xq2ivhUakPuT4IQXZ2E1MvAz9/Yff0WEbvghtAoGAejFzC8c2yDUenaHHU+TXgNxor0Q+HIan3M0EvmJ4mxYkBMInK1AgjDBCxMOSK5gzlBPwI/0O5E2KcT7VFeqgM2XN5+2jpHi75PpXgFbEbdXtlYI0TNQZbKJ8CFg2nc+ciV8ThDvTwzOV/3kRm7N/o7ol8qaqGWVZ1QFTbFuugd8CgYBAsNVKCEHGZAwS5Csv/IBopcrJOJknmEHeAT+VwxtZK7yOJ4ZSNizzofu847YGO4uEhfcaC8lYxnj8XskuMG2KChbbxEA+qlnL/oMIwVrpE0ZbFmQ4WRTUfVQT+zCHMPuuuj5tLPTSMjQLGlq+r3XRzwUaX98J9GLKgHG7tXlphw=="; + //随行付公钥 + String sxfPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo1+KBcvwDSIo+nMYLeOJ19Ju4ii0xH66ZxFd869EWFWk/EJa3xIA2+4qGf/Ic7m7zi/NHuCnfUtUDmUdP0JfaZiYwn+1Ek7tYAOc1+1GxhzcexSJLyJlR2JLMfEM+rZooW4Ei7q3a8jdTWUNoak/bVPXnLEVLrbIguXABERQ0Ze0X9Fs0y/zkQFg8UjxUN88g2CRfMC6LldHm7UBo+d+WlpOYH7u0OTzoLLiP/04N1cfTgjjtqTBI7qkOGxYs6aBZHG1DJ6WdP+5w+ho91sBTVajsCxAaMoExWQM2ipf/1qGdsWmkZScPflBqg7m0olOD87ymAVP/3Tcbvi34bDfwIDAQAB"; + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 + String dataStr = df.format(new Date()); + + ApiRequestBean reqBean = new ApiRequestBean(); + + reqBean.setOrgId("57594783"); //机构id + reqBean.setReqId(UUID.randomUUID().toString().replaceAll("-", "")); + reqBean.setSignType("RSA"); + reqBean.setTimestamp(dataStr); + reqBean.setVersion("1.0"); + JSONObject reqData = new JSONObject(); + + //业务参数 + reqData.put("ordNo", scanPayRequest.getOrdNo()); //商户订单号 + reqData.put("mno", "399260311280126"); //商户编号 + //reqData.put("subMechId", ""); //子商户号 + reqData.put("authCode", scanPayRequest.getAuthCode()); //授权码 需要前端传 + reqData.put("amt", scanPayRequest.getAmt()); //订单总金额 + //reqData.put("discountAmt", ""); //参与优惠金额 + //reqData.put("unDiscountAmt", ""); //不参与优惠金额 + reqData.put("payType", scanPayRequest.getPayType()); //支付渠道 + reqData.put("scene", "1"); //支付场景,1: 刷卡 2:声波 3:刷脸 不上传默认为 1 + reqData.put("subject", "B扫C测试"); //订单标题 + reqData.put("tradeSource", "02"); //交易来源 01服务商,02收银台,03硬件 + reqData.put("trmIp", "172.16.2.1"); + //reqData.put("limitPay", "00"); //限制卡类型: 00-全部 01-限定不能使 用信用卡支付 默认值 00 + //reqData.put("hbFqNum", "6"); //花呗分期数,仅可上送 6 或 12 + //reqData.put("hbFqPercent", "0"); //卖家承担分期 服务费比例,仅支持上送 0 或 100 + //reqData.put("goodsTag", "00"); //订单优惠标识 00:是,01: 否 + + //reqData.put("couponDetail", ""); //优惠详情信息,见下面三个字段 + //reqData.put("costPrice", "200"); //订单原价保留两 位小数;微信 独有 + //reqData.put("receiptId ", "123456789"); //商品小票 + + //reqData.put("goodsDetail", "123456789"); //单品优惠信息使用 json 数组格式提交 + //reqData.put("goodsId", "200"); //商品编码 + //reqData.put("thirdGoodsId", "12345678"); //微信/支付宝侧商品码 + //reqData.put("goodsName", "苹果电脑"); //商品名称 + //reqData.put("quantity", "1"); //商品数量 + //reqData.put("price", "1.01"); //商品单价 + //reqData.put("goodsCategory", ""); //商品类目;支 付宝独有 + //reqData.put("categoriesTree", "124868003|126232002|126252004"); //商品类目树 + //reqData.put("goodsDesc", ""); //商品描述;支 付宝独有 + //reqData.put("showUrl", ""); //商品展示地址 url;支付宝独有 + + //reqData.put("needReceipt", "00"); //电子发票功能 微信开具电子 发票使用 + //reqData.put("ledgerAccountFlag", "00"); //是否做分账 分账交易使 用;00:做; 01:不做;不传默认为不做分账 + //reqData.put("ledgerAccountEffectTime", "00"); //分账有效时间 单位为天;是 否做分账选择 00 时该字段必传 + // TODO: 2026/3/11 后端回调地址 + reqData.put("notifyUrl", "http://193.112.94.36:8081/mall/order/notify"); //回调地址 + //reqData.put("ylTrmNo", ""); //银联终端号 + //reqData.put("terminalId", ""); //TQ机具编号 + //reqData.put("deviceNo ", ""); //设备号 + //reqData.put("identityFlag", ""); //是否是实名支付 + //reqData.put("buyerIdType", "IDCARD"); //证件类型 + //reqData.put("buyerIdNo", "410523198701054018"); //证件号 + //reqData.put("buyerName", "张三"); //买家姓名 + //reqData.put("mobileNum", ""); //手机号 + //reqData.put("extend", ""); //备用 + + + reqBean.setReqData(reqData); + String req = JSONObject.toJSONString(reqBean); + //请求数据json字符串示例 + //String req= "{\"orgId\":\"26680846\",\"reqData\":{\"ordNo\":\"35081904944040745\",\"payType\":\"WECHAT\",\"customerIp\":\"\",\"subject\":\"聚合支付测试\",\"amt\":\"0.01\",\"payWay\":\"02\",\"tradeSource\":\"02\",\"userId\":\"2088101117955611\",\"trmIp\":\"172.16.2.1\",\"mno\":\"399190910000387\"},\"reqId\":\"08794fa24dcb467992a06b126e542be4\",\"signType\":\"RSA\",\"timestamp\":\"2020-04-03 17:51:34\",\"version\":\"1.0\"}"; + System.out.println("req:" + req); + //此处不要改变reqData里面值的顺序用LinkedHashMap + HashMap reqMap = JSON.parseObject(req, LinkedHashMap.class, Feature.OrderedField); + //组装加密串 + String signContent = RSASignature.getOrderContent(reqMap); + System.out.println("拼接后的参数:" + signContent); + //sign + String sign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey)); + System.out.println("============签名:" + sign); + reqMap.put("sign", sign); + + String reqStr = JSON.toJSONString(reqMap); + System.out.println("请求参数:" + reqMap); + System.out.println("请求参数:" + reqStr); + String url = "https://openapi.tianquetech.com/order/reverseScan"; + + String resultJson = HttpUtils.connectPostUrl(url, reqStr); + log.info("返回信息" + resultJson); + System.out.println("返回信息:" + resultJson); + //不要对reqData排序 所以用LinkedHashMap + HashMap result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField); + if ("0000".equals(result.get("code"))) { + //验签 + String signResult = result.get("sign").toString(); + result.remove("sign"); + String resultStr = RSASignature.getOrderContent(result); + System.out.println(resultStr); + //sign + String resultSign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey)); + System.out.println("resultSign:" + resultSign); + //组装加密串 + if (RSASignature.doCheck(resultStr, signResult, sxfPublic)) { + log.info("===================验签成功=============="); + + String respDataStr = result.get("respData").toString(); + HashMap respData = JSON.parseObject(respDataStr, LinkedHashMap.class); + // 获取 tranSts 和 ordNo + String tranSts = Optional.ofNullable(respData) + .map(m -> m.get("tranSts")) + .map(Object::toString) + .orElse(""); + + String ordNo = Optional.ofNullable(respData) + .map(m -> m.get("ordNo")) + .map(Object::toString) + .orElse(""); + //被扫免密同步返回支付结果,不推送异步通知。 同步去修改支付状态,扣减库存 + //被扫输密,推送异步通知。 通过回调接口修改支付状态,扣减库存 + if ("SUCCESS".equals(tranSts)) { + // 4. 查询订单(加行锁) + Order order = orderMapper.selectForUpdateByOrderNo(ordNo); + if (order == null) { + log.error("订单不存在, orderNo:{}", ordNo); + throw new ServiceException("订单不存在"); + } + + // 5. 检查订单状态 + if (order.getPayStatus() == 2) { + log.info("订单已支付, orderNo:{}", ordNo); + throw new ServiceException("订单已支付"); + } + + if (order.getPayStatus() != 0) { + log.error("订单状态错误, orderNo:{}, status:{}", + ordNo, order.getPayStatus()); + throw new ServiceException("订单状态错误"); + } + + // 7. 查询订单明细 + List items = orderMapper.selectByOrderId(order.getId()); + + // 8. 扣减实际库存 + for (OrderItem item : items) { + int result1 = productMapper.reduceStock( + order.getStoreId(), + item.getProductId(), + item.getQuantity() + ); + if (result1 <= 0) { + log.error("库存扣减失败, storeId:{}, productId:{}, quantity:{}", + order.getStoreId(), item.getProductId(), item.getQuantity()); + throw new ServiceException("库存扣减失败"); + } + } + + // 9. 更新订单状态为已支付 + Order updateOrder = new Order(); + updateOrder.setId(order.getId()); + updateOrder.setPayStatus(2); + updateOrder.setPayTime(new Date()); + + int updated = orderMapper.updateById(updateOrder); + if (updated <= 0) { + log.error("订单状态更新失败, orderNo:{}",ordNo); + throw new ServiceException("订单状态更新失败"); + } + log.info("支付同步处理成功, orderNo:{}, storeId:{}", + ordNo, order.getStoreId()); + } + System.out.println("===================验签成功=============="); + } + log.info("===================被扫支付==============" + resultJson); + return resultJson; + } + throw new ServiceException(resultJson); + } + + @Override + public AjaxResult tradeQuery(TradeQueryDTO tradeQueryDTO) throws Exception { + //合作方私钥(替换成自己的) + String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM35qbWVNdBAU+oai8EVE3iSpA9wHAt4wOs+SufbkE/FNoMgp4EMrFNrdNkaKLQq4cRzqoNlJY+DZSL54dm7ekKZP/SLhWwNi9tJvhKsFpw6SRwthlKyA8lCW2mzFffFqLhGAayNoZTmHwkq8dotvecARVbiFtakzzFy/O7ATmzw3i57yaVgX2RtD8WWA7MGg5CQP1vvIDlXqd40SQcrJY6GNNv07Brv3fpyW2nXbvrxbOx+87NnG77/R7o8eFcWJF+yrqE1xeN3hDvHBFMFyf1M39Zig2I75vKRvHGqftAZuvlDG0nInwcSiMTE7wDsXvYzRnOsgJNVET7aFhzOvFAgMBAAECggEATjB4nRl2S2Whasnf0Ab77CoZSjn7HUXv2hymBkJNPq3eV0Hh5Pjjqmi7hIs0cdm9AWXd6RzySKpScQNjYQFxEOIdbayfTdzD24L2ivT6ZBca09Z9J/9Rik0wJ4ULTtny5JRc6VwGgOn9Twdsde8V4sr1nHUvpMaRH6WVxW8mX7+udgbK0Uxm5IFROeKGz0DSuG8SBpDhD2EB6P2Q+1fpna17yP1R08HtZNU6lQx7yMesaJuvKkwf4YiSjIhUtdbUXGhmbG5LFzoj3oRs25W68bIOQjT9i196hPQOYj6C7yz72nuY2rwFHhtsocfQRULWOHtsnWts9epvuFruDTdq8QKBgQD8bZhQkyQm6VTP0Hmv6sV9SXXKH0rb2V8hml7DxcpFl+l2/9UesH3qMoFKZ5C1YdPKZBNlD7fp5RxjtJ8MdwIp8MxRgMf/bZpIOx1Lfum1H+dKh5LF+oC4GGtJ5jqnwozNPog4keUn+bqKhiza9snA+6jy446A1Y8GGWlZwUGkVwKBgQDPxb7FZmOcotNnh8mS5pcx6d22UhBHIHz/X/dcjNQuI8tIRaIvkJm/4lfuxejCz3LY9k+VSQ96ZM2L5OqZGDlR7aMXQ3/FhuS4PEkNcDvccQPoig7U5zUUn2Jdqom6Lngh7Ka8B1v24ght0yXHHNChmMg/DJ42EzCnjpydWs6/QwKBgA9qGd0BvzlpEjbGgkfNzFWEQN8g3g9izL5ekN7fmyR4zFbp9He1S1sbzm1euaV53dcEGXMYbKCpzvv/sZ6vPcCV5cQsWwosBTnX8kgD7f2Tfyo78SiJzYZwZ0zR9E7+QF7gLK1Xq2ivhUakPuT4IQXZ2E1MvAz9/Yff0WEbvghtAoGAejFzC8c2yDUenaHHU+TXgNxor0Q+HIan3M0EvmJ4mxYkBMInK1AgjDBCxMOSK5gzlBPwI/0O5E2KcT7VFeqgM2XN5+2jpHi75PpXgFbEbdXtlYI0TNQZbKJ8CFg2nc+ciV8ThDvTwzOV/3kRm7N/o7ol8qaqGWVZ1QFTbFuugd8CgYBAsNVKCEHGZAwS5Csv/IBopcrJOJknmEHeAT+VwxtZK7yOJ4ZSNizzofu847YGO4uEhfcaC8lYxnj8XskuMG2KChbbxEA+qlnL/oMIwVrpE0ZbFmQ4WRTUfVQT+zCHMPuuuj5tLPTSMjQLGlq+r3XRzwUaX98J9GLKgHG7tXlphw=="; + //随行付公钥 + String sxfPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo1+KBcvwDSIo+nMYLeOJ19Ju4ii0xH66ZxFd869EWFWk/EJa3xIA2+4qGf/Ic7m7zi/NHuCnfUtUDmUdP0JfaZiYwn+1Ek7tYAOc1+1GxhzcexSJLyJlR2JLMfEM+rZooW4Ei7q3a8jdTWUNoak/bVPXnLEVLrbIguXABERQ0Ze0X9Fs0y/zkQFg8UjxUN88g2CRfMC6LldHm7UBo+d+WlpOYH7u0OTzoLLiP/04N1cfTgjjtqTBI7qkOGxYs6aBZHG1DJ6WdP+5w+ho91sBTVajsCxAaMoExWQM2ipf/1qGdsWmkZScPflBqg7m0olOD87ymAVP/3Tcbvi34bDfwIDAQAB"; + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 + String dataStr = df.format(new Date()); + + ApiRequestBean reqBean = new ApiRequestBean(); + + reqBean.setOrgId("57594783"); //机构id + reqBean.setReqId(UUID.randomUUID().toString().replaceAll("-", "")); + reqBean.setSignType("RSA"); + reqBean.setTimestamp(dataStr); + reqBean.setVersion("1.0"); + JSONObject reqData = new JSONObject(); + //业务参数 + reqData.put("ordNo", tradeQueryDTO.getOrdNo()); //商户订单号 + reqData.put("mno", "399260311280126"); //商户编号 + reqData.put("trmIp", "172.16.2.1"); + reqBean.setReqData(reqData); + String req = JSONObject.toJSONString(reqBean); + //此处不要改变reqData里面值的顺序用LinkedHashMap + HashMap reqMap = JSON.parseObject(req, LinkedHashMap.class, Feature.OrderedField); + //组装加密串 + String signContent = RSASignature.getOrderContent(reqMap); + //sign + String sign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey)); + reqMap.put("sign", sign); + + String reqStr = JSON.toJSONString(reqMap); + String url = "https://openapi.tianquetech.com/query/tradeQuery"; + String resultJson = HttpUtils.connectPostUrl(url, reqStr); + //不要对reqData排序 所以用LinkedHashMap + HashMap result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField); + if ("0000".equals(result.get("code"))) { + //验签 + String signResult = result.get("sign").toString(); + result.remove("sign"); + String resultStr = RSASignature.getOrderContent(result); + System.out.println(resultStr); + //sign + String resultSign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey)); + System.out.println("resultSign:" + resultSign); + //组装加密串 + if (RSASignature.doCheck(resultStr, signResult, sxfPublic)) { + log.info("===================验签成功=============="); + PaymentResponseVO paymentResponseVO = JSON.parseObject(resultJson, PaymentResponseVO.class); + return AjaxResult.success(paymentResponseVO); + } + } + return AjaxResult.error(resultJson); + } + + @Override + public OrderCountVO getOrderCount(String storeId) { + return orderMapper.getOrderCount(storeId); + } + + + //调用支付 + public String pay(String orderNo, String totalAmount) throws Exception { + //合作方私钥(替换成自己的) + String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM35qbWVNdBAU+oai8EVE3iSpA9wHAt4wOs+SufbkE/FNoMgp4EMrFNrdNkaKLQq4cRzqoNlJY+DZSL54dm7ekKZP/SLhWwNi9tJvhKsFpw6SRwthlKyA8lCW2mzFffFqLhGAayNoZTmHwkq8dotvecARVbiFtakzzFy/O7ATmzw3i57yaVgX2RtD8WWA7MGg5CQP1vvIDlXqd40SQcrJY6GNNv07Brv3fpyW2nXbvrxbOx+87NnG77/R7o8eFcWJF+yrqE1xeN3hDvHBFMFyf1M39Zig2I75vKRvHGqftAZuvlDG0nInwcSiMTE7wDsXvYzRnOsgJNVET7aFhzOvFAgMBAAECggEATjB4nRl2S2Whasnf0Ab77CoZSjn7HUXv2hymBkJNPq3eV0Hh5Pjjqmi7hIs0cdm9AWXd6RzySKpScQNjYQFxEOIdbayfTdzD24L2ivT6ZBca09Z9J/9Rik0wJ4ULTtny5JRc6VwGgOn9Twdsde8V4sr1nHUvpMaRH6WVxW8mX7+udgbK0Uxm5IFROeKGz0DSuG8SBpDhD2EB6P2Q+1fpna17yP1R08HtZNU6lQx7yMesaJuvKkwf4YiSjIhUtdbUXGhmbG5LFzoj3oRs25W68bIOQjT9i196hPQOYj6C7yz72nuY2rwFHhtsocfQRULWOHtsnWts9epvuFruDTdq8QKBgQD8bZhQkyQm6VTP0Hmv6sV9SXXKH0rb2V8hml7DxcpFl+l2/9UesH3qMoFKZ5C1YdPKZBNlD7fp5RxjtJ8MdwIp8MxRgMf/bZpIOx1Lfum1H+dKh5LF+oC4GGtJ5jqnwozNPog4keUn+bqKhiza9snA+6jy446A1Y8GGWlZwUGkVwKBgQDPxb7FZmOcotNnh8mS5pcx6d22UhBHIHz/X/dcjNQuI8tIRaIvkJm/4lfuxejCz3LY9k+VSQ96ZM2L5OqZGDlR7aMXQ3/FhuS4PEkNcDvccQPoig7U5zUUn2Jdqom6Lngh7Ka8B1v24ght0yXHHNChmMg/DJ42EzCnjpydWs6/QwKBgA9qGd0BvzlpEjbGgkfNzFWEQN8g3g9izL5ekN7fmyR4zFbp9He1S1sbzm1euaV53dcEGXMYbKCpzvv/sZ6vPcCV5cQsWwosBTnX8kgD7f2Tfyo78SiJzYZwZ0zR9E7+QF7gLK1Xq2ivhUakPuT4IQXZ2E1MvAz9/Yff0WEbvghtAoGAejFzC8c2yDUenaHHU+TXgNxor0Q+HIan3M0EvmJ4mxYkBMInK1AgjDBCxMOSK5gzlBPwI/0O5E2KcT7VFeqgM2XN5+2jpHi75PpXgFbEbdXtlYI0TNQZbKJ8CFg2nc+ciV8ThDvTwzOV/3kRm7N/o7ol8qaqGWVZ1QFTbFuugd8CgYBAsNVKCEHGZAwS5Csv/IBopcrJOJknmEHeAT+VwxtZK7yOJ4ZSNizzofu847YGO4uEhfcaC8lYxnj8XskuMG2KChbbxEA+qlnL/oMIwVrpE0ZbFmQ4WRTUfVQT+zCHMPuuuj5tLPTSMjQLGlq+r3XRzwUaX98J9GLKgHG7tXlphw=="; + //随行付公钥 + String sxfPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo1+KBcvwDSIo+nMYLeOJ19Ju4ii0xH66ZxFd869EWFWk/EJa3xIA2+4qGf/Ic7m7zi/NHuCnfUtUDmUdP0JfaZiYwn+1Ek7tYAOc1+1GxhzcexSJLyJlR2JLMfEM+rZooW4Ei7q3a8jdTWUNoak/bVPXnLEVLrbIguXABERQ0Ze0X9Fs0y/zkQFg8UjxUN88g2CRfMC6LldHm7UBo+d+WlpOYH7u0OTzoLLiP/04N1cfTgjjtqTBI7qkOGxYs6aBZHG1DJ6WdP+5w+ho91sBTVajsCxAaMoExWQM2ipf/1qGdsWmkZScPflBqg7m0olOD87ymAVP/3Tcbvi34bDfwIDAQAB"; + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 + String dataStr = df.format(new Date()); + + ApiRequestBean reqBean = new ApiRequestBean(); + + reqBean.setOrgId("57594783"); //机构id + reqBean.setReqId(UUID.randomUUID().toString().replaceAll("-", "")); + reqBean.setSignType("RSA"); + reqBean.setTimestamp(dataStr); + reqBean.setVersion("1.0"); + JSONObject reqData = new JSONObject(); + + //业务参数 + reqData.put("ordNo", orderNo); //商户订单号 + reqData.put("mno", "399260311280126"); //商户编号 + //reqData.put("subMechId", ""); //子商户号 + reqData.put("amt", totalAmount); //订单总金额 + //reqData.put("discountAmt", ""); //参与优惠金额 + //reqData.put("unDiscountAmt", ""); //不参与优惠金额 + // TODO: 2026/3/11 后续前端传 + reqData.put("payType", "WECHAT"); //支付渠道 + // TODO: 2026/3/11 后续前端传 + reqData.put("payWay", "02"); //支付方式 02 公众号/服 务窗/js支付 03 小程序 + //reqData.put("timeExpire", "10"); //订单失效时间, 以分钟为单位 + //reqData.put("limitPay", "00"); //限制卡类型: 00-全部 01-限定不能使 用信用卡支付 默认值 00 + reqData.put("subject", "聚合支付测试"); //订单标题 + //reqData.put("hbFqNum", "6"); //花呗分期数,仅可上送 6 或 12 + //reqData.put("hbFqPercent", "0"); //卖家承担分期 服务费比例,仅支持上送 0 或 100 + // TODO: 2026/3/11 后续前端传 + reqData.put("tradeSource", "02"); //交易来源 01服务商,02收银台,03硬件 + reqData.put("trmIp", "172.16.2.1"); + reqData.put("customerIp", ""); //持卡人ip地址,银联js支付时必传 + // TODO: 2026/3/11 后续前端传code,后端去获取对应的id + reqData.put("userId", "o3LXk25WUnC4U2lGeuEvLLu0fjBs"); //用户号 微信:openid; 支付宝:userid;银联:userid;微信&支付宝必传,银联js为非必传 + //reqData.put("subAppid", "wx24210004370ec43b"); //微信子公众号 + //reqData.put("outFrontUrl", ""); //js 支付,前台 成功通知地址 + //reqData.put("outFrontFailUrl", ""); //js 支付,前台 事变通知地址 + // TODO: 2026/3/11 后端回调地址 + reqData.put("notifyUrl", "http://193.112.94.36:8081/mall/order/notify"); //回调地址 + //reqData.put("needReceipt", "00"); //电子发票功能 微信开具电子 发票使用 + //reqData.put("ledgerAccountFlag", "00"); //是否做分账 分账交易使 用;00:做; 01:不做;不传默认为不做分账 + //reqData.put("ledgerAccountEffectTime", "00"); //分账有效时间 单位为天;是 否做分账选择 00 时该字段必传 + //reqData.put("ylTrmNo", ""); //银联终端号 + //reqData.put("terminalId", ""); //TQ机具编号 + //reqData.put("identityFlag", ""); //是否是实名支付 + //reqData.put("buyerIdType", "IDCARD"); //证件类型 + //reqData.put("buyerIdNo", "410523198701054018"); //证件号 + //reqData.put("buyerName", "张三"); //买家姓名 + //reqData.put("mobileNum", ""); //手机号 + //reqData.put("extend", ""); //备用 + //reqData.put("goodsTag", "00"); //订单优惠标识 00:是,01: 否 + + //reqData.put("couponDetail", ""); //优惠详情信息,见下面三个字段 + //reqData.put("costPrice", "200"); //订单原价保留两 位小数;微信 独有 + //reqData.put("receiptId ", "123456789"); //商品小票 + + //reqData.put("goodsDetail", "123456789"); //单品优惠信息使用 json 数组格式提交 + //reqData.put("goodsId", "200"); //商品编码 + //reqData.put("thirdGoodsId", "12345678"); //微信/支付宝侧商品码 + //reqData.put("goodsName", "苹果电脑"); //商品名称 + //reqData.put("quantity", "1"); //商品数量 + //reqData.put("price", "1.01"); //商品单价 + //reqData.put("goodsCategory", ""); //商品类目;支 付宝独有 + //reqData.put("categoriesTree", "124868003|126232002|126252004"); //商品类目树 + //reqData.put("goodsDesc", ""); //商品描述;支 付宝独有 + //reqData.put("showUrl", ""); //商品展示地址 url;支付宝独有 + + reqBean.setReqData(reqData); + String req = JSONObject.toJSONString(reqBean); + //请求数据json字符串示例 + //String req= "{\"orgId\":\"26680846\",\"reqData\":{\"ordNo\":\"35081904944040745\",\"payType\":\"WECHAT\",\"customerIp\":\"\",\"subject\":\"聚合支付测试\",\"amt\":\"0.01\",\"payWay\":\"02\",\"tradeSource\":\"02\",\"userId\":\"2088101117955611\",\"trmIp\":\"172.16.2.1\",\"mno\":\"399190910000387\"},\"reqId\":\"08794fa24dcb467992a06b126e542be4\",\"signType\":\"RSA\",\"timestamp\":\"2020-04-03 17:51:34\",\"version\":\"1.0\"}"; + System.out.println("req:" + req); + //此处不要改变reqData里面值的顺序用LinkedHashMap + HashMap reqMap = JSON.parseObject(req, LinkedHashMap.class, Feature.OrderedField); + //组装加密串 + String signContent = RSASignature.getOrderContent(reqMap); + System.out.println("拼接后的参数:" + signContent); + //sign + String sign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey)); + System.out.println("============签名:" + sign); + reqMap.put("sign", sign); + + String reqStr = JSON.toJSONString(reqMap); + System.out.println("请求参数:" + reqMap); + System.out.println("请求参数:" + reqStr); + String url = "https://openapi.tianquetech.com/order/jsapiScan"; + + String resultJson = HttpUtils.connectPostUrl(url, reqStr); + log.info("返回信息" + resultJson); + System.out.println("返回信息:" + resultJson); + //不要对reqData排序 所以用LinkedHashMap + HashMap result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField); + if ("0000".equals(result.get("code"))) { + //验签 + String signResult = result.get("sign").toString(); + result.remove("sign"); + String resultStr = RSASignature.getOrderContent(result); + System.out.println(resultStr); + //sign + String resultSign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey)); + System.out.println("resultSign:" + resultSign); + //组装加密串 + if (RSASignature.doCheck(resultStr, signResult, sxfPublic)) { + log.info("===================验签成功=============="); + + System.out.println("===================验签成功=============="); + } + return resultJson; + } + throw new ServiceException(resultJson); + } + + /** + * 生成锁key(包含门店ID) + */ + private String generateLockKey(Integer storeId, List items) { + // 对商品ID排序 + String productIds = items.stream() + .map(item -> String.valueOf(item.getProductId())) + .sorted() + .collect(Collectors.joining("_")); + + // 组合门店ID和商品ID + String originalKey = storeId + ":" + productIds; + + // 计算MD5避免key过长 + String hash = DigestUtils.md5DigestAsHex(originalKey.getBytes()); + return "lock:store:products:" + hash; + } + + /** + * 生成订单号 + */ + private String generateOrderNo() { + // 格式:年月日时分秒 + 雪花算法ID后8位 + String dateStr = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + long snowflakeId = IdUtil.getSnowflake().nextId(); + String suffix = String.valueOf(snowflakeId).substring(8); + return dateStr + suffix; + } } diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java index 39da8c9c..8a8163a7 100644 --- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java @@ -4,9 +4,11 @@ package com.ruoyi.web.service.impl; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.web.domain.Product; +import com.ruoyi.web.dto.BatchBarcodeQueryDTO; import com.ruoyi.web.dto.BatchStoreDTO; import com.ruoyi.web.mapper.ProductMapper; import com.ruoyi.web.service.ProductService; +import com.ruoyi.web.vo.ProductVo; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -79,4 +81,9 @@ public class ProductServiceImpl implements ProductService { } return productMapper.batchProductById(dto.getIds()); } + + @Override + public List getByBarcodes(BatchBarcodeQueryDTO dto) { + return productMapper.getByBarcodes(dto); + } } diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/utils/HttpUtils.java b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/HttpUtils.java new file mode 100644 index 00000000..b7843cf1 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/HttpUtils.java @@ -0,0 +1,144 @@ +package com.ruoyi.web.utils; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContextBuilder; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +public class HttpUtils { + + /** + * 创建没有证书的SSL链接工厂类 + * @return + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + public static SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws NoSuchAlgorithmException, + KeyStoreException, KeyManagementException { + SSLContextBuilder context = new SSLContextBuilder().useProtocol("TLSv1.2"); + context.loadTrustMaterial(null, new TrustStrategy() { + @Override + public boolean isTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + return true; + } + }); + return new SSLConnectionSocketFactory(context.build()); + } + + /** + * 链接GET请求 + * @param url + * @return + * @throws KeyManagementException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws ClientProtocolException + * @throws IOException + */ + public static String connectGetUrl(String url) throws KeyManagementException, + NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException { + SSLConnectionSocketFactory sslsf = getSSLConnectionSocketFactory(); + CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); + RequestConfig config = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(5000).build(); + HttpGet httpGet = null; + CloseableHttpResponse resp = null; + String jsonString = ""; + try { + httpGet = new HttpGet(url); + httpGet.setConfig(config); + resp = client.execute(httpGet); + HttpEntity entity = resp.getEntity(); + jsonString = EntityUtils.toString(entity, "UTF-8"); + } finally { + if (resp != null) { + try { + resp.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (httpGet != null) { + httpGet.releaseConnection(); + } + } + return jsonString; + } + + /** + * 链接POST请求 + * @param url + * @param jsonParm + * @return + * @throws KeyManagementException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws ClientProtocolException + * @throws IOException + */ + public static String connectPostUrl(String url, String jsonParm) throws KeyManagementException, + NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException { + SSLConnectionSocketFactory sslsf = getSSLConnectionSocketFactory(); + CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); + RequestConfig config = RequestConfig.custom().setSocketTimeout(40000).setConnectTimeout(10000).build(); + HttpPost httpPost = null; + CloseableHttpResponse resp = null; + try { + httpPost = new HttpPost(url); + httpPost.setConfig(config); + StringEntity params = new StringEntity(jsonParm,"UTF-8"); + params.setContentType("application/json"); + httpPost.setEntity(params); + resp = client.execute(httpPost); + HttpEntity entity = resp.getEntity(); + String jsonString = EntityUtils.toString(entity, "UTF-8"); + + if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + return jsonString; + } + }catch (IOException e) { + if (e instanceof org.apache.http.conn.ConnectTimeoutException) { + throw new org.apache.http.conn.ConnectTimeoutException("connect timed out"); + } + if (e instanceof java.net.SocketTimeoutException) { + throw new java.net.SocketTimeoutException("Read timed out"); + } + throw new IOException("IOException"); + } finally { + if (resp != null) { + try { + resp.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (httpPost != null) { + httpPost.releaseConnection(); + } + try { + client.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/utils/RSASignature.java b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/RSASignature.java new file mode 100644 index 00000000..ac16b84b --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/RSASignature.java @@ -0,0 +1,105 @@ +package com.ruoyi.web.utils; + +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.*; + + +/** + * RSA签名验签类 + */ +public class RSASignature { + + /** + * 签名算法 + */ + public static final String SIGN_ALGORITHMS = "SHA1WithRSA"; + + /** + * 针对参数进行排序拼装 + * @param sortedParams + * @return + */ + public static String getOrderContent(Map requestParam) { + Map sortedParams = new TreeMap(); + if ((requestParam != null) && (requestParam.size() > 0)) { + sortedParams.putAll(requestParam); + } + StringBuffer content = new StringBuffer(); + List keys = new ArrayList(sortedParams.keySet()); + Collections.sort(keys); + int index = 0; + for (int i = 0; i < keys.size(); i++) { + String key = keys.get(i); + Object value = sortedParams.get(key); + if (key!=null &&!"".equals(key)&& value != null) { + content.append((index == 0 ? "" : "&") + key + "=" + value); + index++; + } + } + return content.toString(); + } + + /** + * rsa 签名 + * @param content 待签名内容 + * @param privateKey 私钥 + * @return + */ + public static byte[] sign(String content, String privateKey) { + try { + PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decryptBASE64(privateKey)); + KeyFactory keyf = KeyFactory.getInstance("RSA"); + PrivateKey priKey = keyf.generatePrivate(priPKCS8); + java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS); + signature.initSign(priKey); + signature.update(content.getBytes("UTF-8")); + byte[] signed = signature.sign(); + return signed; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * RSA验签名检查 + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 分配给开发商公钥 + * @return 布尔值 + */ + public static boolean doCheck(String content, String sign, String publicKey) { + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + byte[] encodedKey = decryptBASE64(publicKey); + PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); + + + java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS); + + signature.initVerify(pubKey); + signature.update(content.getBytes("UTF-8")); + + boolean bverify = signature.verify(decryptBASE64(sign)); + return bverify; + + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public static String encryptBASE64(byte[] key) throws Exception { + String base64encodedString = Base64.getEncoder().encodeToString(key); + return base64encodedString.replaceAll("[\\s*\t\n\r]", ""); + } + public static byte[] decryptBASE64(String key) throws Exception { + byte[] base64decodedBytes = Base64.getDecoder().decode(key.replaceAll("[\\s*\t\n\r]", "")); + return base64decodedBytes; + } + +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderCountVO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderCountVO.java new file mode 100644 index 00000000..d93070ed --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderCountVO.java @@ -0,0 +1,13 @@ +package com.ruoyi.web.vo; + +import lombok.Data; + +@Data +public class OrderCountVO { + + //支付订单数量 + private String paidOrderCount; + + //支付订单金额 + private String totalPayAmount; +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderVo.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderVo.java new file mode 100644 index 00000000..cc6e1259 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderVo.java @@ -0,0 +1,11 @@ +package com.ruoyi.web.vo; + +import lombok.Data; + +@Data +public class OrderVo { + + private String orderNo; + + private String amount; +} diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/PaymentResponseVO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/PaymentResponseVO.java new file mode 100644 index 00000000..431d5f48 --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/PaymentResponseVO.java @@ -0,0 +1,234 @@ +package com.ruoyi.web.vo; + +import lombok.Data; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 支付响应VO + */ +@Data +public class PaymentResponseVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 消息 + */ + private String msg; + + /** + * 业务码 + */ + private String code; + + /** + * 签名 + */ + private String sign; + + /** + * 签名类型 + */ + private String signType; + + /** + * 机构ID + */ + private String orgId; + + /** + * 请求ID + */ + private String reqId; + + /** + * 响应数据 + */ + private RespData respData; + + /** + * 响应数据内部类 + */ + @Data + public static class RespData implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 支付时间 yyyyMMddHHmmss + */ + private String payTime; + + /** + * 业务码 + */ + private String bizCode; + + /** + * 商品标题 + */ + private String subject; + + /** + * 结算金额 + */ + private String settleAmt; + + /** + * 支付方式 01-微信 02-支付宝 + */ + private String payWay; + + /** + * 收单机构流水号 + */ + private String sxfUuid; + + /** + * 支付银行 + */ + private String payBank; + + /** + * 交易流水号 + */ + private String uuid; + + /** + * 场景 + */ + private String scene; + + /** + * 交易时间 yyyyMMddHHmmss + */ + private String tranTime; + + /** + * 结算批次号 + */ + private String settlementBatchNo; + + /** + * 支付类型 WECHAT/ALIPAY + */ + private String payType; + + /** + * 订单号 + */ + private String ordNo; + + /** + * 收单机构标识 + */ + private String szltFlag; + + /** + * 业务消息 + */ + private String bizMsg; + + /** + * 手续费金额 + */ + private String recFeeAmt; + + /** + * 手续费费率 + */ + private String recFeeRate; + + /** + * 收单机构手续费 + */ + private String szltRecfeeAmt; + + /** + * 原始交易金额 + */ + private String oriTranAmt; + + /** + * 渠道ID + */ + private String channelId; + + /** + * 完成时间 yyyyMMddHHmmss + */ + private String finishTime; + + /** + * 交易状态 SUCCESS-成功 + */ + private String tranSts; + + /** + * 积分金额 + */ + private String pointAmount; + + /** + * 记账标识 + */ + private String ledgerAccountFlag; + + /** + * 交易码 + */ + private String tradeCode; + + /** + * 清算日期 yyyyMMdd + */ + private String clearDt; + + /** + * 第三方交易流水号 + */ + private String transactionId; + + /** + * 子商户号 + */ + private String subMechId; + + /** + * 总抵扣金额 + */ + private String totalOffstAmt; + + /** + * 交易消息 + */ + private String tradeMsg; + + /** + * 设备类型 + */ + private String drType; + + /** + * 获取结算金额(转为BigDecimal) + */ + public BigDecimal getSettleAmtDecimal() { + return settleAmt != null ? new BigDecimal(settleAmt) : BigDecimal.ZERO; + } + + /** + * 获取原始交易金额(转为BigDecimal) + */ + public BigDecimal getOriTranAmtDecimal() { + return oriTranAmt != null ? new BigDecimal(oriTranAmt) : BigDecimal.ZERO; + } + + /** + * 判断是否支付成功 + */ + public boolean isSuccess() { + return "SUCCESS".equals(tranSts) && "0000".equals(bizCode); + } + } +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/ProductVo.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/ProductVo.java new file mode 100644 index 00000000..d9b5289b --- /dev/null +++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/ProductVo.java @@ -0,0 +1,12 @@ +package com.ruoyi.web.vo; + +import lombok.Data; + +@Data +public class ProductVo { + private Long id; + private String productName; + private String storePrice; + private String mainImage; + private String productBarCode; // 商品条码 +} \ No newline at end of file diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/DeviceMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/DeviceMapper.xml new file mode 100644 index 00000000..ae783b82 --- /dev/null +++ b/ruoyi-mall/src/main/resources/mapper/commodity/DeviceMapper.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into mall_device_info ( + store_id, + device_code, + device_name, + device_type, + device_brand, + device_model, + channel, + mac_address, + auth_code, + install_date, + expire_date, + device_status, + last_heartbeat, + remark, + create_by, + create_time + )values( + #{storeId}, + #{deviceCode}, + #{deviceName}, + #{deviceType}, + #{deviceBrand}, + #{deviceModel}, + #{channel}, + #{macAddress}, + #{authCode}, + #{installDate}, + #{expireDate}, + #{deviceStatus}, + #{lastHeartbeat}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + \ No newline at end of file diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/DoorScanRecordMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/DoorScanRecordMapper.xml new file mode 100644 index 00000000..0a659e06 --- /dev/null +++ b/ruoyi-mall/src/main/resources/mapper/commodity/DoorScanRecordMapper.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + insert into mall_scan_record ( + store_id, + operator_name , + operator_phone , + scan_type , + shop_name , + scan_time , + create_by, + create_time + )values( + #{storeId}, + #{operatorName}, + #{operatorPhone}, + #{scanType}, + #{shopName}, + #{scanTime}, + #{createBy}, + sysdate() + ) + + + + + + + + \ No newline at end of file diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/InstallTaskMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/InstallTaskMapper.xml new file mode 100644 index 00000000..d90ba813 --- /dev/null +++ b/ruoyi-mall/src/main/resources/mapper/commodity/InstallTaskMapper.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO mall_install_task ( + task_no, store_id, store_name, contact_person, contact_phone, + device_type, device_model, install_address, expected_time, remark, + task_status, del_flag, create_by, create_time + ) VALUES ( + #{taskNo}, #{storeId}, #{storeName}, #{contactPerson}, #{contactPhone}, + #{deviceType}, #{deviceModel}, #{installAddress}, #{expectedTime}, #{remark}, + 0, '0', #{createBy}, #{createTime} + ) + + + + + + + + + + + + + + UPDATE mall_install_task + + contact_person = #{contactPerson}, + contact_phone = #{contactPhone}, + device_type = #{deviceType}, + device_model = #{deviceModel}, + install_address = #{installAddress}, + expected_time = #{expectedTime}, + remark = #{remark}, + task_status = #{taskStatus}, + assignee_id = #{assigneeId}, + assignee_name = #{assigneeName}, + assign_time = #{assignTime}, + device_id = #{deviceId}, + device_code = #{deviceCode}, + auth_code = #{authCode}, + install_time = #{installTime}, + install_photo = #{installPhoto}, + complete_remark = #{completeRemark}, + update_by = #{updateBy}, + update_time = NOW() + + WHERE id = #{id} AND del_flag = '0' + + + + + UPDATE mall_install_task + + task_status = #{taskStatus}, + assignee_id = #{assigneeId}, + assignee_name = #{assigneeName}, + assign_time = #{assignTime}, + update_by = #{updateBy}, + update_time = NOW() + + WHERE id = #{id} AND del_flag = '0' + + + + + UPDATE mall_install_task + + task_status = #{taskStatus}, + device_id = #{deviceId}, + device_code = #{deviceCode}, + auth_code = #{authCode}, + install_time = #{installTime}, + install_photo = #{installPhoto}, + complete_remark = #{completeRemark}, + update_by = #{updateBy}, + update_time = NOW() + + WHERE id = #{id} AND del_flag = '0' + + + + + + + + UPDATE mall_install_task + SET del_flag = '2', update_time = NOW() + WHERE id = #{id} + + + + + UPDATE mall_install_task + SET del_flag = '2', update_time = NOW() + WHERE id IN + + #{id} + + + + \ No newline at end of file diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml index 2c405cee..bbaf6156 100644 --- a/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml +++ b/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml @@ -40,41 +40,88 @@ + + + INSERT INTO mall_order (order_no, store_id, pay_amount, pay_status, + create_time, create_by) + VALUES (#{orderNo}, #{storeId}, #{payAmount}, #{payStatus}, + NOW(), #{createBy}) + + + + + INSERT INTO mall_order_item ( + order_id, + product_id, + product_name, + product_no, + main_image, + spec, + unit_price, + quantity, + subtotal, + create_time, + create_by + ) VALUES + + ( + #{item.orderId}, + #{item.productId}, + #{item.productName}, + #{item.productNo}, + #{item.mainImage}, + #{item.spec}, + #{item.unitPrice}, + #{item.quantity}, + #{item.subtotal}, + NOW(), + #{item.createBy} + ) + + + + + + UPDATE mall_order + + order_no = #{orderNo}, + pay_time = #{payTime}, + pay_status = #{payStatus}, + update_by = #{updateBy}, + update_time = NOW() + + WHERE + id = #{id} + AND del_flag = '0' + - SELECT o.id, o.order_no, o.store_id, o.pay_amount, - o.pay_time, o.pay_status, - o.create_time, - o.update_time, - o.del_flag, + o.pay_time, o.create_by, - o.update_by, + o.create_time, oi.id as item_id, - oi.order_id, - oi.product_id, - oi.product_name, - oi.product_no, - oi.main_image, - oi.spec, - oi.unit_price, - oi.quantity, - oi.subtotal, - oi.del_flag as item_del_flag, - oi.create_by as item_create_by, - oi.create_time as item_create_time + oi.order_id as item_order_id, + oi.product_id as item_product_id, + oi.quantity as item_quantity, + oi.subtotal as item_subtotal, + p.product_bar_code as product_bar_code, + p.product_name as item_product_name, + p.main_image as item_main_image FROM mall_order o - LEFT JOIN mall_order_item oi ON o.id = oi.order_id AND oi.del_flag = '0' + LEFT JOIN mall_order_item oi ON o.id = oi.order_id + LEFT JOIN mall_product_store p ON oi.product_id = p.id o.del_flag = '0' - AND o.order_no like concat('%', #{orderNo}, '%') + AND o.order_no = #{orderNo} AND o.store_id = #{storeId} @@ -82,9 +129,56 @@ AND o.pay_status = #{payStatus} + + AND p.product_bar_code = #{productBarCode} + - ORDER BY o.create_time DESC, oi.create_time ASC + ORDER BY o.create_time DESC + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/ProductMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/ProductMapper.xml index 60d4e41a..735f124b 100644 --- a/ruoyi-mall/src/main/resources/mapper/commodity/ProductMapper.xml +++ b/ruoyi-mall/src/main/resources/mapper/commodity/ProductMapper.xml @@ -73,6 +73,21 @@ AND status = #{status} + + + AND production_date IS NOT NULL + AND shelf_life IS NOT NULL + AND DATEDIFF( + DATE_ADD(production_date, INTERVAL shelf_life DAY), + NOW() + ) BETWEEN 1 AND approaching + + + + AND production_date IS NOT NULL + AND shelf_life IS NOT NULL + AND DATE_ADD(production_date, INTERVAL shelf_life DAY) < NOW() + @@ -90,6 +105,28 @@ where id=#{id} and del_flag = '0' + + + + insert into mall_product_store ( @@ -134,7 +171,16 @@ sysdate() ) - + + + UPDATE mall_product_store + SET stock_quantity = stock_quantity + #{quantity}, + sold_quantity = sold_quantity - #{quantity}, + update_time = NOW() + WHERE store_id = #{storeId} + AND id = #{productId} + AND del_flag = 0 + update mall_product_store @@ -188,6 +234,17 @@ where product_bar_code = #{productBarCode} + + UPDATE mall_product_store + SET stock_quantity = stock_quantity - #{quantity}, -- 扣减库存 + sold_quantity = sold_quantity + #{quantity}, -- 增加销量 + update_time = NOW() -- 更新时间 + WHERE store_id = #{storeId} + AND id = #{productId} + AND stock_quantity >= #{quantity} -- 确保库存充足 + AND del_flag = 0 -- 未删除 + + delete from sys_config diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml index f4458d66..304254a7 100644 --- a/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml +++ b/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml @@ -11,6 +11,7 @@ +