| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- 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.BalanceLog;
- 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());
- }
- BalanceLog l = new BalanceLog();
- 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. 火花归还记录
- BalanceLog l = new BalanceLog();
- 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<Order> 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<Map<String, Object>> 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<Order> allOrderList = Order.dao.find(sql, userId);
- //
- // // 给用户转账NFT
- // doNftTransfer(userId, allOrderList);
- // 创建一个新的列表来存储过滤后的订单
- List<Order> 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<Order> 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<Map<String, Object>>());
- }
- // // 给用户转账NFT
- // doNftTransfer(userId, allOrderList);
-
- // 只需要指定状态的status
- List<Order> 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<Map<String, Object>> 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<Object> 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<Order> orderList = Order.dao.find(listSql, paramsForList.toArray());
- // 如果列表为空,直接返回
- if (orderList.isEmpty()) {
- Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> encapsulateOrdersWithLogs(List<Order> orderList) {
- if (orderList.isEmpty()) {
- return new ArrayList<>();
- }
- // 2. 获取所有订单的ID,用于批量查询订单日志
- List<Long> 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<OrderLog> orderLogs = OrderLog.dao.find(sql, orderIds.toArray());
- // 4. 将日志按 order_id 分组
- Map<Long, List<Map<String, Object>>> orderLogsMap = new HashMap<>();
- for (OrderLog log : orderLogs) {
- long orderId = log.getLong("order_id");
- orderLogsMap.computeIfAbsent(orderId, k -> new ArrayList<>())
- .add(convertOrderLogToMap(log));
- }
- // 5. 遍历订单列表,封装日志和状态描述
- List<Map<String, Object>> resultList = new ArrayList<>();
- for (Order order : orderList) {
- Map<String, Object> orderData = order.toMap();
- orderData.put("order_status_desc", getStatusDescByCode(order.getInt("order_status")));
- List<Map<String, Object>> 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<Object> 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<String, Object> convertOrderLogToMap(OrderLog log) {
- Map<String, Object> 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 "未知状态"; // 如果找不到对应的状态码
- }
-
- public static String generateOrderSn() {
- return System.currentTimeMillis() + "" + (int)(Math.random() * 10000);
- }
- }
|