Explorar el Código

智能柜项目提交

skyline hace 4 meses
padre
commit
e3d65340e4

+ 5 - 0
haha-common/pom.xml

@@ -35,6 +35,11 @@
             <artifactId>spring-web</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 52 - 0
haha-common/src/main/java/com/haha/common/Assert.java

@@ -0,0 +1,52 @@
+package com.haha.common;
+
+
+import com.haha.common.exception.BaseException;
+
+/**
+ * @author skyline
+ * @description 断言
+ * @date 2023-07-02 15:06
+ */
+public interface Assert {
+    /**
+     * 创建异常
+     *
+     * @param args
+     * @return
+     */
+    BaseException baseException(Object... args);
+
+    /**
+     * 创建异常
+     *
+     * @param t
+     * @param args
+     * @return
+     */
+    BaseException baseException(Throwable t, Object... args);
+
+    /**
+     * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
+     *
+     * @param obj 待判断对象
+     */
+    default void assertNotNull(Object obj) {
+        if (obj == null) {
+            throw baseException();
+        }
+    }
+
+    /**
+     * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
+     * <p>异常信息<code>message</code>支持传递参数方式,避免在判断之前进行字符串拼接操作
+     *
+     * @param obj  待判断对象
+     * @param args message占位符对应的参数列表
+     */
+    default void assertNotNull(Object obj, Object... args) {
+        if (obj == null) {
+            throw baseException(args);
+        }
+    }
+}

+ 12 - 0
haha-common/src/main/java/com/haha/common/constant/IResponseCode.java

@@ -0,0 +1,12 @@
+package com.haha.common.constant;
+
+/**
+ * @author skyline
+ * @description
+ * @date 2023-07-08 10:34
+ */
+public interface IResponseCode {
+    Integer getCode();
+
+    String getMessage();
+}

+ 44 - 0
haha-common/src/main/java/com/haha/common/constant/ResponseEnum.java

@@ -0,0 +1,44 @@
+package com.haha.common.constant;
+
+import com.haha.common.exception.BusinessExceptionAssert;
+import lombok.Getter;
+
+/**
+ * 返回码信息
+ *
+ * @author skyline
+ */
+@Getter
+public enum ResponseEnum implements BusinessExceptionAssert {
+    SUCCESS(200, "ok"),
+    FAILED(500, "failed"),
+
+    HTTP_STATUS_200(200, "ok"),
+    HTTP_STATUS_202(202, "accepted"),
+    HTTP_STATUS_400(400, "request error"),
+    HTTP_STATUS_401(401, "no authentication"),
+    HTTP_STATUS_403(403, "no authorities"),
+    HTTP_STATUS_500(500, "server error"),
+
+    DB_ERROR(999, "数据异常"),
+    PARAMS_ERROR(1000, "参数异常"),
+
+
+    // 微信小程序
+    WX_MP_LOGIN_ERROR(301, "微信登录异常"),
+
+    // 微信支付
+    WX_PAY_AMOUNT_ERROR(300001,"微信支付金额异常"),
+
+    // 登录 权限
+    LOGIN_FAILED(10001, "用户名或密码错误"),
+    NO_PERMISSION(10002, "无访问权限");
+
+    private final Integer code;
+    private final String message;
+
+    ResponseEnum(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+}

+ 41 - 0
haha-common/src/main/java/com/haha/common/exception/BaseException.java

@@ -0,0 +1,41 @@
+package com.haha.common.exception;
+
+import com.haha.common.constant.IResponseCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author skyline
+ * @description 自定义异常
+ * @date 2023-07-02 15:08
+ */
+@AllArgsConstructor
+@Getter
+public class BaseException extends RuntimeException {
+    Integer code;
+    String message;
+
+    BaseException(IResponseCode responseEnum) {
+        this.code = responseEnum.getCode();
+        this.message = responseEnum.getMessage();
+    }
+
+    BaseException(IResponseCode responseEnum, Object[] args, String message) {
+        this.code = responseEnum.getCode();
+        this.message = message;
+        // TODO: 2023-07-04 args处理
+    }
+
+    BaseException(IResponseCode responseEnum, Object[] args, String message, Throwable cause) {
+        this.code = responseEnum.getCode();
+        this.message = message;
+        // TODO: 2023-07-04 args cause 处理
+    }
+
+    BaseException(int code,String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+
+}

+ 36 - 0
haha-common/src/main/java/com/haha/common/exception/BusinessException.java

@@ -0,0 +1,36 @@
+package com.haha.common.exception;
+
+
+import com.haha.common.constant.IResponseCode;
+import com.haha.common.constant.ResponseEnum;
+
+/**
+ * @author skyline
+ * @description 业务异常
+ * @date 2023-07-04 15:44
+ */
+public class BusinessException extends BaseException {
+
+    private static final long serialVersionUID = 1L;
+
+    public BusinessException(IResponseCode responseEnum) {
+        super(responseEnum);
+    }
+
+    public BusinessException(IResponseCode responseEnum, Object[] args, String message) {
+        super(responseEnum, args, message);
+    }
+
+    public BusinessException(IResponseCode responseEnum, Object[] args, String message, Throwable cause) {
+        super(responseEnum, args, message, cause);
+    }
+
+    public BusinessException(int code, String message) {
+        super(code, message);
+    }
+
+    public BusinessException(String message) {
+        super(ResponseEnum.HTTP_STATUS_500.getCode(), message);
+    }
+}
+

+ 26 - 0
haha-common/src/main/java/com/haha/common/exception/BusinessExceptionAssert.java

@@ -0,0 +1,26 @@
+package com.haha.common.exception;
+
+
+import com.haha.common.Assert;
+import com.haha.common.constant.IResponseCode;
+
+import java.text.MessageFormat;
+
+/**
+ * @author skyline
+ * @description 业务异常断言
+ * @date 2023-07-08 10:30
+ */
+public interface BusinessExceptionAssert extends IResponseCode, Assert {
+    @Override
+    default BaseException baseException(Object... args) {
+        String msg = MessageFormat.format(this.getMessage(), args);
+        return new BusinessException(this, args, msg);
+    }
+
+    @Override
+    default BaseException baseException(Throwable t, Object... args) {
+        String msg = MessageFormat.format(this.getMessage(), args);
+        return new BusinessException(this, args, msg, t);
+    }
+}

+ 89 - 0
haha-common/src/main/java/com/haha/common/utils/OrderUtils.java

@@ -0,0 +1,89 @@
+package com.haha.common.utils;
+
+
+import cn.hutool.core.util.RandomUtil;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author skyline
+ * @description 订单号生成
+ * @date 2023-08-12 17:26
+ */
+public class OrderUtils {
+
+    // 生成24位唯一订单号码,格式:YYYY-MMDD-HHII-SS-NNNN,NNNN-CC,
+    // 其中:YYYY=年份,MM=月份,DD=日期,HH=24格式小时,II=分,SS=秒,NNNNNNNN=随机数,CC=检查码
+
+    /**
+     * 生成订单号 32位
+     *
+     * @param str 一般只会传OperatorID
+     * @return
+     */
+    public static String getOrderNo(String... str) {
+        var orderNoMain = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).concat(String.valueOf(RandomUtil.randomInt(10000000, 99999999)));
+        var orderNoSum = 0;
+        for (int i = 0; i < orderNoMain.length(); i++) {
+            orderNoSum += Integer.parseInt(orderNoMain.substring(orderNoMain.length() - 1));
+        }
+        var res = orderNoMain.concat(str_pad(String.valueOf((100 - orderNoSum % 100) % 100), 2, "0", "STR_PAD_LEFT"));
+        return str.length > 0 ? str[0].substring(0, 8).concat(res) : res;
+    }
+
+
+    public static String str_pad(String input, int length, String pad, String sense) {
+        int resto_pad = length - input.length();
+        String padded = "";
+
+        if (resto_pad <= 0) {
+            return input;
+        }
+
+        if (sense.equals("STR_PAD_RIGHT")) {
+            padded = input;
+            padded += _fill_string(pad, resto_pad);
+        } else if (sense.equals("STR_PAD_LEFT")) {
+            padded = _fill_string(pad, resto_pad);
+            padded += input;
+        } else // STR_PAD_BOTH
+        {
+            int pad_left = (int) Math.ceil(resto_pad / 2);
+            int pad_right = resto_pad - pad_left;
+
+            padded = _fill_string(pad, pad_left);
+            padded += input;
+            padded += _fill_string(pad, pad_right);
+        }
+        return padded;
+    }
+
+
+    protected static String _fill_string(String pad, int resto) {
+        boolean first = true;
+        String padded = "";
+
+        if (resto >= pad.length()) {
+            for (int i = resto; i >= 0; i = i - pad.length()) {
+                if (i >= pad.length()) {
+                    if (first) {
+                        padded = pad;
+                    } else {
+                        padded += pad;
+                    }
+                } else {
+                    if (first) {
+                        padded = pad.substring(0, i);
+                    } else {
+                        padded += pad.substring(0, i);
+                    }
+                }
+                first = false;
+            }
+        } else {
+            padded = pad.substring(0, resto);
+        }
+        return padded;
+    }
+}

+ 5 - 5
haha-common/src/main/java/com/haha/common/vo/OpenDoorVO.java

@@ -4,14 +4,14 @@ import lombok.Builder;
 import lombok.Data;
 
 /**
- * 寮€闂ㄧ粨鏋滃睍绀哄璞?
+ * 开门结果展示对象
  */
 @Data
 @Builder
 public class OpenDoorVO {
-    private boolean doorOpened;
-    private String outTradeNo;
-    private String orderNo;
+    private Boolean doorOpened;
+    private String activityId;
+    private String userId;
     private String deviceId;
-    private Integer doorIndex;
+    private String doorIndex;
 }

+ 15 - 6
haha-entity/src/main/java/com/haha/entity/Order.java

@@ -10,6 +10,17 @@ import java.time.LocalDateTime;
 @Data
 @TableName("t_order")
 public class Order implements Serializable {
+
+    public static final String PAY_STATUS_未支付 = "UNPAID";
+    public static final String PAY_STATUS_已支付 = "PAID";
+    public static final String PAY_STATUS_已退款 = "REFUND";
+
+    // 订单状态:0-待支付,1-已完成,0-已取消,3-已关闭
+    public static final int ORDER_STATUS_待支付 = 0;
+    public static final int ORDER_STATUS_已完成 = 1;
+    public static final int ORDER_STATUS_已取消 = 2;
+    public static final int ORDER_STATUS_已关闭 = 3;
+
     private static final long serialVersionUID = 1L;
 
     @TableId(type = IdType.AUTO)
@@ -17,23 +28,21 @@ public class Order implements Serializable {
 
     private String orderNo;
 
-    private String outTradeNo;
+    private String activityId;
 
-    private String hahaOrderNo;
+    private String outTradeNo;
 
     private Long userId;
 
-    private String deviceSn;
+    private String deviceId;
 
     private Double totalAmount;
 
     private String payStatus;
 
-    private String videoUrl;
-
     private Double confidence;
 
-    private String itemsJson;
+    private String items;
 
     private LocalDateTime createTime;
 

+ 0 - 4
haha-miniapp/pom.xml

@@ -56,10 +56,6 @@
         </dependency>
 
         <!-- 工具类 -->
-        <dependency>
-            <groupId>cn.hutool</groupId>
-            <artifactId>hutool-all</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>

+ 27 - 112
haha-miniapp/src/main/java/com/haha/miniapp/controller/DeviceController.java

@@ -1,12 +1,10 @@
 package com.haha.miniapp.controller;
 
 import cn.dev33.satoken.stp.StpUtil;
-import com.haha.entity.Order;
-import com.haha.service.OrderService;
-import com.haha.sdk.HahaClient;
+import com.haha.common.vo.OpenDoorVO;
+import com.haha.common.vo.Result;
+import com.haha.service.DeviceService;
 import com.haha.sdk.exception.HahaException;
-import com.haha.sdk.model.DeviceOnlineStatus;
-import com.haha.sdk.model.OpenDoorResult;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -14,13 +12,11 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.time.LocalDateTime;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
  * 设备控制相关接口
- * 基于哈哈SDK实现
+ * Controller层仅负责接收请求、参数校验、调用Service、返回响应
  */
 @Slf4j
 @RestController
@@ -28,125 +24,44 @@ import java.util.Map;
 public class DeviceController {
 
     @Autowired
-    private HahaClient hahaClient;
-    
-    @Autowired
-    private OrderService orderService;
+    private DeviceService deviceService;
 
     /**
      * 处理小程序扫码开门
-     * 
-     * 业务流程:
-     * 1. 参数校验
-     * 2. 用户身份验证
-     * 3. 检查设备在线状态
-     * 4. 检查设备是否多门单开
-     * 5. 调用SDK开门接口
-     * 6. 返回开门结果
      *
      * @param params 包含 deviceId(设备ID)
      * @return 操作结果
      */
     @PostMapping("/scan-open")
-    public Map<String, Object> scanOpen(@RequestBody Map<String, String> params) {
-        Map<String, Object> result = new HashMap<>();
-        
-        try {
-            // ========== 1. 参数校验 ==========
-            String deviceId = params.get("deviceId");
-            if (deviceId == null || deviceId.trim().isEmpty()) {
-                result.put("code", 400);
-                result.put("message", "参数错误:deviceId 不能为空");
-                return result;
-            }
-
-            // ========== 2. 获取用户信息 ==========
-            Long userId = StpUtil.getLoginIdAsLong();
-            log.info("用户 {} 请求打开设备 {}", userId, deviceId);
-
-            // ========== 3. 检查设备在线状态 ==========
-            DeviceOnlineStatus onlineStatus = hahaClient.getDeviceApi().getOnlineStatus(deviceId);
-            
-            if (onlineStatus.getIsOnline() != 1) {
-                log.warn("设备 {} 当前离线", deviceId);
-                result.put("code", 400);
-                result.put("message", "设备当前离线,请稍后再试");
-                result.put("data", Map.of(
-                    "online", false,
-                    "deviceId", deviceId
-                ));
-                return result;
-            }
+    public Result<OpenDoorVO> scanOpen(@RequestBody Map<String, String> params) {
+        // 1. 参数校验
+        String deviceId = params.get("deviceId");
+        if (deviceId == null || deviceId.trim().isEmpty()) {
+            return Result.error(400, "参数错误:deviceId 不能为空");
+        }
 
-            // ========== 4. 检查是否多门单开 ==========
-            Integer multiDoorType = hahaClient.getDeviceApi().isMultiDoorUnique(deviceId);
-            String doorIndex = null;
-            
-            // 如果是多门单开设备,默认打开A门
-            if (multiDoorType != null && multiDoorType > 0) {
-                doorIndex = "A"; // A门或B门
-                log.info("设备 {} 是多门单开设备,类型: {},将打开A门", deviceId, multiDoorType);
-            }
+        // 2. 获取用户信息
+        Long userId = StpUtil.getLoginIdAsLong();
+        log.info("用户 {} 扫码请求打开设备 {}", userId, deviceId);
 
-            // ========== 5. 生成商户用户编号并开门 ==========
-            String outUserId = "USER" + userId;
-            String openType = "OUT"; // OUT表示消费,IN表示上货
-            String source = "MINIAPP"; // 请求来源
+        try {
+            // 3. 调用Service层处理业务逻辑
+            OpenDoorVO result = deviceService.scanOpenDoor(deviceId, userId);
+            return Result.success("开门成功,请取货", result);
             
-            OpenDoorResult openResult = hahaClient.getDeviceApi()
-                .openDoor(deviceId, outUserId, openType, doorIndex, source);
-
-            // ========== 6. 创建本地订单记录 ==========
-            Order order = new Order();
-            order.setOutTradeNo(outUserId);
-            order.setHahaOrderNo(openResult.getActivityId()); // 使用活动ID作为订单号
-            order.setUserId(userId);
-            order.setDeviceSn(deviceId);
-            order.setStatus(0); // 0-待支付(等待识别结果)
-            order.setPayStatus("pending");
-            order.setCreateTime(LocalDateTime.now());
+        } catch (Exception e) {
+            // 处理异常
+            log.error("开门异常", e);
             
-            boolean saved = orderService.save(order);
-            if (!saved) {
-                log.warn("订单 {} 保存失败,但开门已成功", outUserId);
+            // 判断是否为SDK异常
+            if (e instanceof HahaException) {
+                HahaException hahaEx = (HahaException) e;
+                String message = getErrorMessage(hahaEx.getErrorCode(), hahaEx.getMessage());
+                return Result.error(500, message);
             }
             
-            // ========== 7. 返回成功结果 ==========
-            log.info("开门成功 - 设备: {}, 活动ID: {}, 哈哈用户ID: {}", 
-                deviceId, openResult.getActivityId(), openResult.getUserId());
-            
-            result.put("code", 200);
-            result.put("message", "开门成功,请取货");
-            result.put("data", Map.of(
-                "doorOpened", true,
-                "activityId", openResult.getActivityId(),
-                "userId", openResult.getUserId(),
-                "deviceId", deviceId,
-                "doorIndex", doorIndex != null ? doorIndex : "A"
-            ));
-
-        } catch (HahaException e) {
-            // 处理SDK异常
-            Integer errorCode = e.getErrorCode();
-            String errorMsg = e.getMessage();
-            
-            log.error("开门失败 - 错误码: {}, 错误信息: {}", errorCode, errorMsg, e);
-            
-            // 根据错误码返回具体的错误信息
-            String message = getErrorMessage(errorCode, errorMsg);
-            
-            result.put("code", 500);
-            result.put("message", message);
-            result.put("errorCode", errorCode);
-            
-        } catch (Exception e) {
-            // 处理其他异常
-            log.error("开门异常", e);
-            result.put("code", 500);
-            result.put("message", "系统繁忙,请稍后重试");
+            return Result.error(500, "系统繁忙,请稍后重试");
         }
-        
-        return result;
     }
 
     /**

+ 41 - 41
haha-miniapp/src/main/java/com/haha/miniapp/controller/OrderController.java

@@ -28,7 +28,7 @@ public class OrderController {
 
     /**
      * 获取订单列表
-     * 
+     *
      * 业务流程:
      * 1. 获取当前登录用户ID
      * 2. 根据状态筛选查询订单列表
@@ -41,21 +41,21 @@ public class OrderController {
     @PostMapping("/list")
     public Map<String, Object> getOrderList(@RequestBody(required = false) Map<String, Object> params) {
         Map<String, Object> result = new HashMap<>();
-        
+
         try {
             // 获取当前登录用户ID
             Long userId = StpUtil.getLoginIdAsLong();
             log.info("用户 {} 请求订单列表", userId);
-            
+
             // 获取状态参数
             Integer status = null;
             if (params != null && params.containsKey("status")) {
                 status = Integer.valueOf(params.get("status").toString());
             }
-            
+
             // 查询订单列表
             List<Order> orders = orderService.getOrderListByUserId(userId, status);
-            
+
             // 转换为前端需要的格式
             List<Map<String, Object>> orderList = new ArrayList<>();
             for (Order order : orders) {
@@ -64,7 +64,7 @@ public class OrderController {
                 orderMap.put("orderNo", order.getOrderNo());
                 orderMap.put("outTradeNo", order.getOutTradeNo());
                 orderMap.put("hahaOrderNo", order.getHahaOrderNo());
-                orderMap.put("deviceSn", order.getDeviceSn());
+                orderMap.put("deviceSn", order.getDeviceId());
                 orderMap.put("totalAmount", order.getTotalAmount());
                 orderMap.put("payStatus", order.getPayStatus());
                 orderMap.put("status", order.getStatus());
@@ -72,11 +72,11 @@ public class OrderController {
                 orderMap.put("payTime", order.getPayTime());
                 orderMap.put("videoUrl", order.getVideoUrl());
                 orderMap.put("confidence", order.getConfidence());
-                
+
                 // 解析商品信息
-                if (order.getItemsJson() != null && !order.getItemsJson().isEmpty()) {
+                if (order.getItems() != null && !order.getItems().isEmpty()) {
                     try {
-                        JSONArray items = JSON.parseArray(order.getItemsJson());
+                        JSONArray items = JSON.parseArray(order.getItems());
                         List<Map<String, Object>> products = new ArrayList<>();
                         for (int i = 0; i < items.size(); i++) {
                             JSONObject item = items.getJSONObject(i);
@@ -96,28 +96,28 @@ public class OrderController {
                 } else {
                     orderMap.put("products", new ArrayList<>());
                 }
-                
+
                 orderList.add(orderMap);
             }
-            
+
             result.put("code", 200);
             result.put("message", "查询成功");
             result.put("data", orderList);
-            
+
             log.info("用户 {} 查询到 {} 条订单", userId, orderList.size());
-            
+
         } catch (Exception e) {
             log.error("查询订单列表失败", e);
             result.put("code", 500);
             result.put("message", "查询订单列表失败");
         }
-        
+
         return result;
     }
 
     /**
      * 获取订单详情
-     * 
+     *
      * 业务流程:
      * 1. 参数校验
      * 2. 获取当前登录用户ID
@@ -132,14 +132,14 @@ public class OrderController {
     @PostMapping("/detail")
     public Map<String, Object> getOrderDetail(@RequestBody Map<String, Object> params) {
         Map<String, Object> result = new HashMap<>();
-        
+
         try {
             // 获取当前登录用户ID
             Long userId = StpUtil.getLoginIdAsLong();
-            
+
             // 查询订单
             Order order = null;
-            
+
             // 支持三种查询方式
             if (params.containsKey("orderId")) {
                 Long orderId = Long.valueOf(params.get("orderId").toString());
@@ -160,14 +160,14 @@ public class OrderController {
                 result.put("message", "参数错误:必须提供 orderId、orderNo 或 outTradeNo");
                 return result;
             }
-            
+
             // 检查订单是否存在
             if (order == null) {
                 result.put("code", 404);
                 result.put("message", "订单不存在");
                 return result;
             }
-            
+
             // 验证订单归属
             if (!order.getUserId().equals(userId)) {
                 log.warn("用户 {} 尝试访问不属于自己的订单 {}", userId, order.getId());
@@ -175,14 +175,14 @@ public class OrderController {
                 result.put("message", "无权访问该订单");
                 return result;
             }
-            
+
             // 构建订单详情
             Map<String, Object> orderDetail = new HashMap<>();
             orderDetail.put("id", order.getId());
             orderDetail.put("orderNo", order.getOrderNo());
             orderDetail.put("outTradeNo", order.getOutTradeNo());
             orderDetail.put("hahaOrderNo", order.getHahaOrderNo());
-            orderDetail.put("deviceSn", order.getDeviceSn());
+            orderDetail.put("deviceSn", order.getDeviceId());
             orderDetail.put("totalAmount", order.getTotalAmount());
             orderDetail.put("payStatus", order.getPayStatus());
             orderDetail.put("status", order.getStatus());
@@ -190,11 +190,11 @@ public class OrderController {
             orderDetail.put("payTime", order.getPayTime());
             orderDetail.put("videoUrl", order.getVideoUrl());
             orderDetail.put("confidence", order.getConfidence());
-            
+
             // 解析商品信息
-            if (order.getItemsJson() != null && !order.getItemsJson().isEmpty()) {
+            if (order.getItems() != null && !order.getItems().isEmpty()) {
                 try {
-                    JSONArray items = JSON.parseArray(order.getItemsJson());
+                    JSONArray items = JSON.parseArray(order.getItems());
                     List<Map<String, Object>> products = new ArrayList<>();
                     for (int i = 0; i < items.size(); i++) {
                         JSONObject item = items.getJSONObject(i);
@@ -215,49 +215,49 @@ public class OrderController {
             } else {
                 orderDetail.put("products", new ArrayList<>());
             }
-            
+
             // 返回状态文本
             String statusText = getStatusText(order.getStatus());
             orderDetail.put("statusText", statusText);
-            
+
             result.put("code", 200);
             result.put("message", "查询成功");
             result.put("data", orderDetail);
-            
+
             log.info("用户 {} 查询订单详情成功: {}", userId, order.getOrderNo());
-            
+
         } catch (Exception e) {
             log.error("查询订单详情失败", e);
             result.put("code", 500);
             result.put("message", "查询订单详情失败");
         }
-        
+
         return result;
     }
 
     /**
      * 取消订单
-     * 
+     *
      * @param params 包含 orderId(订单ID)
      * @return 操作结果
      */
     @PostMapping("/cancel")
     public Map<String, Object> cancelOrder(@RequestBody Map<String, Object> params) {
         Map<String, Object> result = new HashMap<>();
-        
+
         try {
             // 获取当前登录用户ID
             Long userId = StpUtil.getLoginIdAsLong();
-            
+
             // 参数校验
             if (!params.containsKey("orderId")) {
                 result.put("code", 400);
                 result.put("message", "参数错误:orderId 不能为空");
                 return result;
             }
-            
+
             Long orderId = Long.valueOf(params.get("orderId").toString());
-            
+
             // 查询订单
             Order order = orderService.getById(orderId);
             if (order == null) {
@@ -265,7 +265,7 @@ public class OrderController {
                 result.put("message", "订单不存在");
                 return result;
             }
-            
+
             // 验证订单归属
             if (!order.getUserId().equals(userId)) {
                 log.warn("用户 {} 尝试取消不属于自己的订单 {}", userId, orderId);
@@ -273,17 +273,17 @@ public class OrderController {
                 result.put("message", "无权操作该订单");
                 return result;
             }
-            
+
             // 检查订单状态(只能取消待支付的订单)
             if (order.getStatus() != 0) {
                 result.put("code", 400);
                 result.put("message", "该订单不能取消");
                 return result;
             }
-            
+
             // 取消订单
             boolean success = orderService.cancelOrder(orderId);
-            
+
             if (success) {
                 result.put("code", 200);
                 result.put("message", "订单已取消");
@@ -292,13 +292,13 @@ public class OrderController {
                 result.put("code", 500);
                 result.put("message", "取消订单失败");
             }
-            
+
         } catch (Exception e) {
             log.error("取消订单失败", e);
             result.put("code", 500);
             result.put("message", "取消订单失败");
         }
-        
+
         return result;
     }
 

+ 13 - 0
haha-service/src/main/java/com/haha/service/DeviceService.java

@@ -2,6 +2,9 @@ package com.haha.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.haha.entity.Device;
+import com.haha.common.vo.OpenDoorVO;
+import com.haha.sdk.exception.HahaException;
+
 import java.util.List;
 
 public interface DeviceService extends IService<Device> {
@@ -10,4 +13,14 @@ public interface DeviceService extends IService<Device> {
     boolean updateDeviceStatus(String deviceSn, Integer status);
     List<Device> getDeviceListByArea(String province, String city, String district);
     boolean updateInventoryHash(String deviceSn, String inventoryHash);
+    
+    /**
+     * 扫码开门业务逻辑
+     * 
+     * @param deviceId 设备ID
+     * @param userId 用户ID
+     * @return 开门结果
+     * @throws HahaException 开门失败时抛出
+     */
+    OpenDoorVO scanOpenDoor(String deviceId, Long userId) throws HahaException;
 }

+ 73 - 0
haha-service/src/main/java/com/haha/service/impl/DeviceServiceImpl.java

@@ -1,16 +1,35 @@
 package com.haha.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.haha.common.exception.BusinessException;
 import com.haha.entity.Device;
+import com.haha.entity.Order;
 import com.haha.mapper.DeviceMapper;
 import com.haha.service.DeviceService;
+import com.haha.service.OrderService;
+import com.haha.common.utils.OrderUtils;
+import com.haha.common.vo.OpenDoorVO;
+import com.haha.sdk.HahaClient;
+import com.haha.sdk.exception.HahaException;
+import com.haha.sdk.model.DeviceOnlineStatus;
+import com.haha.sdk.model.OpenDoorResult;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
+@Slf4j
 @Service
 public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {
 
+    @Autowired
+    private HahaClient hahaClient;
+
+    @Autowired
+    private OrderService orderService;
+
     @Override
     public List<Device> getNearbyDevices(Double longitude, Double latitude, Double distance) {
         // 这里需要实现获取附近设备的逻辑,暂时返回所有设备
@@ -37,4 +56,58 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
     public boolean updateInventoryHash(String deviceSn, String inventoryHash) {
         return lambdaUpdate().eq(Device::getDeviceSn, deviceSn).set(Device::getCurrentInventoryHash, inventoryHash).update();
     }
+
+    @Override
+    public OpenDoorVO scanOpenDoor(String deviceId, Long userId) throws HahaException {
+        log.info("用户 {} 请求打开设备 {}", userId, deviceId);
+
+        // 1. 检查设备在线状态
+        DeviceOnlineStatus onlineStatus = hahaClient.getDeviceApi().getOnlineStatus(deviceId);
+        if (onlineStatus.getIsOnline() != 1) {
+            log.warn("设备 {} 当前离线", deviceId);
+            throw new BusinessException("设备当前离线,请稍后再试");
+        }
+
+        // 2. 检查是否多门单开
+        Integer multiDoorType = hahaClient.getDeviceApi().isMultiDoorUnique(deviceId);
+        String doorIndex = null;
+
+        // 如果是多门单开设备,默认打开A门
+        if (multiDoorType != null && multiDoorType > 0) {
+            doorIndex = "A";
+            log.info("设备 {} 是多门单开设备,类型: {},将打开A门", deviceId, multiDoorType);
+        }
+
+        // 3. 生成商户用户编号并开门
+        String openType = "OUT"; // OUT表示消费,IN表示上货
+        String source = "MINIAPP"; // 请求来源
+
+        OpenDoorResult openResult = hahaClient.getDeviceApi().openDoor(deviceId, String.valueOf(userId), openType, doorIndex, source);
+
+        // 4. 创建本地订单记录
+        Order order = new Order();
+        order.setOutTradeNo(OrderUtils.getOrderNo());
+        // order.setHahaOrderNo(); todo 收到支付完成的通知时更新为支付订单号
+        order.setUserId(userId);
+        order.setDeviceId(deviceId);
+        order.setStatus(Order.ORDER_STATUS_待支付);
+        order.setPayStatus(Order.PAY_STATUS_未支付);
+        order.setCreateTime(LocalDateTime.now());
+
+        boolean saved = orderService.save(order);
+        if (!saved) {
+            log.warn("订单 {} 保存失败,但开门已成功", order.getOrderNo());
+        }
+
+        // 5. 返回成功结果
+        log.info("开门成功 - 设备: {}, 活动ID: {}, 用户ID: {}", deviceId, openResult.getActivityId(), openResult.getUserId());
+
+        return OpenDoorVO.builder()
+            .doorOpened(true)
+            .activityId(openResult.getActivityId())
+            .userId(openResult.getUserId())
+            .deviceId(deviceId)
+            .doorIndex(doorIndex != null ? doorIndex : "A")
+            .build();
+    }
 }

+ 1 - 1
haha-service/src/main/java/com/haha/service/impl/OrderServiceImpl.java

@@ -37,7 +37,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
 
     @Override
     public Order getOrderByDeviceSn(String deviceSn) {
-        return lambdaQuery().eq(Order::getDeviceSn, deviceSn).orderByDesc(Order::getCreateTime).one();
+        return lambdaQuery().eq(Order::getDeviceId, deviceSn).orderByDesc(Order::getCreateTime).one();
     }
 
     @Override