package modules.order; import com.alibaba.fastjson.JSONObject; import com.jfinal.aop.Inject; import com.jfinal.kit.StrKit; import com.jfinal.plugin.activerecord.Db; import com.jfinal.plugin.activerecord.Record; import common.model.DepositLog; import common.model.Order; import common.model.OrderLog; import common.model.User; import common.utils.http.MyRet; import modules.nftt.NfttService; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class OrderService { @Inject NfttService nfttService; public String hello() { return "Hello order"; } public MyRet create(long nfttId, long userId, int orderType) { float quantity = 1; try { Db.tx(() -> { // 1. SELECT ... FOR UPDATE: 查询商品库存,并对该商品行加排他锁 Record item = Db.findFirst("SELECT purchased_quantity, max_quantity, price FROM t_nftt WHERE id = ? FOR UPDATE", nfttId); if (item == null) { throw new RuntimeException("商品不存在: " + nfttId); } int stock = item.getInt("max_quantity") - item.getInt("purchased_quantity"); if (stock <= 0) { throw new RuntimeException("库存不足"); } float unitPrice = item.getFloat("price"); float totalPrice = unitPrice * quantity; // 判断用户余额并预扣 User user = User.dao.findById(userId); if (user.getBalance() < totalPrice) { throw new RuntimeException("花火余额不足,请充值。需要: " + totalPrice + ",你有:" + user.getBalance()); } user.setBalance(user.getBalance() - totalPrice); if (!user.update()) { throw new RuntimeException("创建信息时,用户信息更新失败 user id: " + user.getId()); } DepositLog l = new DepositLog(); l.set("create_time", System.currentTimeMillis()); l.set("is_deleted", 0); l.set("description", "用户购买NFT:" + nfttId + ",消费"); // 描述中带上百分比 l.set("amount", -totalPrice); l.set("user_id", user.getLong("id")); if (!l.save()) { throw new RuntimeException("消费记录创建失败: " + user.getId()); } // 2. 更新库存(在锁定的行上进行) Db.update("UPDATE t_nftt SET purchased_quantity = purchased_quantity + 1 WHERE id = ?", nfttId); // 3. 创建订单 Order order = new Order(); String orderSn = generateOrderSn(); // 生成唯一订单号 order.set("order_sn", orderSn); order.set("user_id", userId); order.set("nftt_id", nfttId); order.set("order_status", OrderController.OrderStatus.PAID.code); // 初始状态:待支付 order.set("order_type", orderType); // 订单类型:1=正式购买, 2=预购 order.set("quantity", quantity); // ... 获取单价、计算总价 ... order.set("unit_price", unitPrice); order.set("total_price", totalPrice); // ... 支付方式 ... order.set("payment_method", 1); // ... 交付状态 ... order.set("delivery_status", 0); // ... nft铸造状态 ... order.set("nft_mint_status", 0); // 退款状态 order.set("refund_status", 0); // 相关时间戳 order.set("create_time", System.currentTimeMillis()); order.set("update_time", System.currentTimeMillis()); order.set("is_deleted", 0); if (!order.save()) { throw new RuntimeException("创建订单失败, nftt id=" + nfttId + ", user id=" + userId + ", order type=" + orderType); } // 3. 记录订单创建日志 OrderLog log = new OrderLog(); log.set("order_id", order.getId()); log.set("from_status", OrderController.OrderStatus.INIT.code); log.set("to_status", OrderController.OrderStatus.PAID.code); log.set("operator_id", userId); // 由用户创建 log.set("operator_type", 1); log.set("change_reason", "订单创建"); log.set("create_time", System.currentTimeMillis()); if (!log.save()) { throw new RuntimeException("订单日志创建失败"); // 抛出异常触发回滚 } return true; }); return MyRet.ok(orderType == 1 ? "抢购成功" : "预购成功"); } catch (Exception e) { return MyRet.fail(e.getMessage()); } } public MyRet cancelByUser(long userId, long orderId) { try { Db.tx(() -> { // 1. 获取order和nfttId String sql = "SELECT * FROM t_order WHERE id=? FOR UPDATE"; Order order = Order.dao.findFirst(sql, orderId); // 订单状态判断 if (order.getOrderStatus() == OrderController.OrderStatus.COMPLETED.code || order.getOrderStatus() == OrderController.OrderStatus.CANCELED.code || order.getOrderStatus() == OrderController.OrderStatus.REFUNDED.code) { throw new RuntimeException("无法取消已完成的订单,请刷新后尝试"); } // 已转账订单判断 if (StrKit.notBlank(order.getBsnTransferCode())) { throw new RuntimeException("该订单的NFT已转出,无法取消"); } long nfttId = order.getNfttId(); // 2. 更新库存(在锁定的行上进行) if (Db.update("UPDATE t_nftt SET purchased_quantity = purchased_quantity - 1 WHERE id = ?", nfttId) != 1) { throw new RuntimeException("更新库存失败导致的取消失败"); } // 3. 取消订单 order.set("order_status", OrderController.OrderStatus.CANCELED.code); order.set("update_time", System.currentTimeMillis()); if (!order.update()) { throw new RuntimeException("订单更新失败导致的取消失败"); } // 4. 记录订单创建日志 OrderLog log = new OrderLog(); log.set("order_id", order.getId()); log.set("from_status", order.getOrderStatus()); log.set("to_status", OrderController.OrderStatus.CANCELED.code); log.set("operator_id", userId); // 由用户取消 log.set("operator_type", 1); log.set("change_reason", "用户手动取消"); log.set("create_time", System.currentTimeMillis()); if (!log.save()) { throw new RuntimeException("订单日志创建失败"); // 抛出异常触发回滚 } // 5. 火花还给人家 User user = User.dao.findById(userId); user.setBalance(user.getBalance() + order.getTotalPrice()); if (!user.update()) { throw new RuntimeException("火花归还失败"); // 抛出异常触发回滚 } // 6. 火花归还记录 DepositLog l = new DepositLog(); l.set("create_time", System.currentTimeMillis()); l.set("is_deleted", 0); l.set("description", "用户取消订单:" + orderId + ",退还"); // 描述中带上百分比 l.set("amount", order.getTotalPrice()); l.set("user_id", user.getLong("id")); if (!l.save()) { throw new RuntimeException("取消订单归还记录创建失败: " + user.getId()); } return true; }); return MyRet.ok("取消成功"); } catch (Exception e) { return MyRet.fail(e.getMessage()); } } public MyRet findOrderById(long orderId, long userId) { List orderList = Order.dao.find("SELECT * FROM t_order WHERE id=? and user_id=?", orderId, userId); // // 给用户转账NFT // doNftTransfer(userId, orderList); // 给用户订单封装商品信息 for (Order order : orderList) { order.put("nftt", nfttService.findNfttById(order.getNfttId() + "")); } List> resultList = encapsulateOrdersWithLogs(orderList); if (!resultList.isEmpty()) { return MyRet.ok("查询成功").setData(resultList.get(0)); } else { return MyRet.fail("未找到订单号对应的订单"); } } public MyRet findNftListByUser(long userId) { String columns = "id, nftt_id, order_status, order_type, delivery_status, delivery_time, nft_mint_status, bsn_transfer_code, bsn_transfer_status"; String sql = "SELECT " + columns + " FROM t_order WHERE user_id = ? ORDER BY create_time DESC"; List allOrderList = Order.dao.find(sql, userId); // // // 给用户转账NFT // doNftTransfer(userId, allOrderList); // 创建一个新的列表来存储过滤后的订单 List filteredOrders = new ArrayList<>(); // 控制给用户的返回值 for (Order order : allOrderList) { if (order.getDeliveryStatus() == 1 && order.getOrderStatus() == OrderController.OrderStatus.COMPLETED.code && order.getNftMintStatus() == 4) { filteredOrders.add(order); } } // 给放入txHash,移除bsh_transfer_status for (Order order : filteredOrders) { JSONObject bsn = JSONObject.parseObject(order.getBsnTransferStatus()); String hash = bsn.getJSONObject("data").getString("txHash"); order.remove("bsn_transfer_status"); order.put("tx_hash", hash); order.put("tx_query_url", String.format("https://taianexplorer.bsnbase.com/transaction-detail?data=%s&type=some", hash)); } // 给用户订单封装商品信息 for (Order order : filteredOrders) { order.put("nftt", nfttService.findNfttById(order.getNfttId() + "")); } return MyRet.ok("查询成功").setData(filteredOrders); } /** * 根据用户ID查询订单列表,并封装订单流转状态 * @param userId 用户ID * @param status 需要的状态 * @return 包含订单列表及状态流转信息的 MyRet 对象 */ public MyRet findOrderByUser(long userId, int status) { List allOrderList = Order.dao.find("SELECT * FROM t_order WHERE user_id = ? ORDER BY create_time DESC", userId); if (allOrderList.isEmpty()) { return MyRet.ok("查询成功").setData(new ArrayList>()); } // // 给用户转账NFT // doNftTransfer(userId, allOrderList); // 只需要指定状态的status List filteredOrderList = new ArrayList<>(); // 状态过滤 for (Order order : allOrderList) { // -1就不用过滤了 if (status == -1) { filteredOrderList.add(order); continue; } // 否则就按指定状态来 if (order.getOrderStatus() == status) { filteredOrderList.add(order); } } // 给用户订单封装商品信息 for (Order order : filteredOrderList) { order.put("nftt", nfttService.findNfttById(order.getNfttId() + "")); } // 调用重用方法封装日志 List> resultList = encapsulateOrdersWithLogs(filteredOrderList); return MyRet.ok("查询成功").setData(resultList); } /** * 管理员查询订单列表(分页,按更新时间倒序,根据订单状态筛选) * 按照用户提供的分页逻辑计算 total_row 和 total_page * @param pageNumber 页码 * @param pageSize 页面大小 * @param orderStatus 订单状态码 (用于筛选,可选,如果为null或小于0则不筛选) * @return 包含分页订单列表及状态流转信息的 MyRet 对象 */ public MyRet findForAdmin(int pageNumber, int pageSize, Integer orderStatus) { // 接收 Integer 类型的 orderStatus // 构建用于查询当前页订单列表的 SQL String columns = "id, order_sn, user_id, nftt_id, order_status, order_type, quantity, unit_price, total_price, payment_method, payment_sn, pay_time, delivery_status, delivery_time, cancel_time, closed_time, refund_status, refund_amount, refund_reason, create_time, update_time, is_deleted, memo"; String select = "SELECT " + columns; String fromWhere = "FROM t_order"; // 基础 from 子句 String orderBy = "ORDER BY update_time DESC"; List paramsForList = new ArrayList<>(); // 用于 SELECT 列表查询的参数 // ✅ 根据 orderStatus 筛选 if (orderStatus != null && orderStatus >= 0) { // 确保 orderStatus 有效,0 通常表示“已取消”,也作为有效状态 fromWhere += " WHERE order_status = ?"; paramsForList.add(orderStatus); } // 计算 LIMIT 的 offset (偏移量) int offset = (pageNumber - 1) * pageSize; // 构建查询当前页订单列表的最终 SQL (手动添加 LIMIT) String listSql = select + " " + fromWhere + " " + orderBy + " LIMIT ?, ?"; paramsForList.add(offset); paramsForList.add(pageSize); // 获取当前页的订单列表 List orderList = Order.dao.find(listSql, paramsForList.toArray()); // 如果列表为空,直接返回 if (orderList.isEmpty()) { Map response = new HashMap<>(); // 准备返回结构 response.put("list", new ArrayList<>()); response.put("total_row", 0); response.put("total_page", 0); response.put("page_size", pageSize); response.put("page_number", pageNumber); response.put("total_order_count", 0); // 与你的 total_user_count 对应 return MyRet.ok("查询成功").setData(response); } // 重用封装日志的逻辑 List> encapsulatedOrderList = encapsulateOrdersWithLogs(orderList); // 获取符合搜索条件的总行数 (total_row) // 调用新的 countOrders 方法,传入 orderStatus 进行筛选计数 long totalRowLong = countOrders(orderStatus); // 手动计算 total_page int totalPage = (int) Math.ceil((double) totalRowLong / pageSize); if (totalPage == 0 && totalRowLong > 0) { // 解决总行数大于0但总页数为0(pageSize大于totalRow)的问题 totalPage = 1; } // 封装最终响应 Map response = new HashMap<>(); response.put("list", encapsulatedOrderList); response.put("total_row", totalRowLong); // 符合条件的订单总数 response.put("total_page", totalPage); // 手动计算的总页数 response.put("page_size", pageSize); response.put("page_number", pageNumber); response.put("total_order_count", totalRowLong); // 对应 total_user_count,这里是符合订单状态筛选的总数 return MyRet.ok("查询成功").setData(response); } /** * 【重用方法】封装订单列表及其状态流转日志 * @param orderList 原始订单列表 * @return 封装了日志和描述信息的订单列表 */ private List> encapsulateOrdersWithLogs(List orderList) { if (orderList.isEmpty()) { return new ArrayList<>(); } // 2. 获取所有订单的ID,用于批量查询订单日志 List orderIds = orderList.stream() .map(order -> order.getLong("id")) .collect(Collectors.toList()); // 确保 orderIds 不为空,避免 SQL 错误 if (orderIds.isEmpty()) { return new ArrayList<>(); } // 3. 一次性查询所有订单相关的状态日志 StringBuilder inClause = new StringBuilder(); for (int i = 0; i < orderIds.size(); i++) { inClause.append("?"); if (i < orderIds.size() - 1) { inClause.append(","); } } String sql = "SELECT * FROM t_order_log WHERE order_id IN (" + inClause + ") ORDER BY order_id, create_time ASC"; List orderLogs = OrderLog.dao.find(sql, orderIds.toArray()); // 4. 将日志按 order_id 分组 Map>> orderLogsMap = new HashMap<>(); for (OrderLog log : orderLogs) { long orderId = log.getLong("order_id"); orderLogsMap.computeIfAbsent(orderId, k -> new ArrayList<>()) .add(convertOrderLogToMap(log)); } // 5. 遍历订单列表,封装日志和状态描述 List> resultList = new ArrayList<>(); for (Order order : orderList) { Map orderData = order.toMap(); orderData.put("order_status_desc", getStatusDescByCode(order.getInt("order_status"))); List> currentOrderLogs = orderLogsMap.getOrDefault(order.getLong("id"), new ArrayList<>()); orderData.put("status_history", currentOrderLogs); resultList.add(orderData); } return resultList; } public long countOrders(Integer orderStatus) { // 接收 Integer 类型的 orderStatus String sql = "SELECT COUNT(id) FROM t_order"; // COUNT(id) 更清晰 List params = new ArrayList<>(); // ✅ 根据 orderStatus 筛选 if (orderStatus != null && orderStatus >= 0) { sql += " WHERE order_status = ?"; params.add(orderStatus); } return Db.queryLong(sql, params.toArray()); } public boolean isOrderBelongToUser(long userId, long orderId) { String sql = "SELECT COUNT(1) FROM t_order" + " WHERE id=? and user_id=?"; return Db.queryInt(sql, orderId, userId) != 1; } /** * 辅助方法:将 OrderLog Model 转换为 Map,并添加状态描述 * @param log OrderLog 对象 * @return 包含描述信息的 Map */ private Map convertOrderLogToMap(OrderLog log) { Map logMap = log.toMap(); // 获取原始日志属性 // 添加状态描述 logMap.put("from_status_desc", getStatusDescByCode(log.getInt("from_status"))); logMap.put("to_status_desc", getStatusDescByCode(log.getInt("to_status"))); return logMap; } /** * 根据状态码获取状态描述 * 假设你已经定义了 OrderStatus 枚举 * * @param statusCode 状态码 * @return 状态描述字符串 */ private String getStatusDescByCode(int statusCode) { for (OrderController.OrderStatus status : OrderController.OrderStatus.values()) { if (status.code == statusCode) { return status.desc; } } return "未知状态"; // 如果找不到对应的状态码 } private String generateOrderSn() { return System.currentTimeMillis() + "" + (int)(Math.random() * 10000); } }