diff --git a/683e68d5ab764623781efbf0/orderUtil.java b/683e68d5ab764623781efbf0/orderUtil.java deleted file mode 100644 index 6c91073..0000000 --- a/683e68d5ab764623781efbf0/orderUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ruoyi.system.controllerUtil; - -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.system.service.IUsersService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * 服务订单Controller工具类 - * - */ -public class orderUtil { - @Autowired - public IUsersService usersService; - //1,根据用户手机号判断用户不存在 - public static int isUser(String userid){ - - - return 1; - } - -} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/CouponsController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/CouponsController.java index e85f80f..34bd382 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/CouponsController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/CouponsController.java @@ -1,5 +1,7 @@ package com.ruoyi.system.controller; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletResponse; @@ -7,6 +9,7 @@ import com.ruoyi.system.domain.*; import com.ruoyi.system.service.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -117,20 +120,379 @@ public class CouponsController extends BaseController @PreAuthorize("@ss.hasPermi('system:Coupons:add')") @Log(title = "优惠券", businessType = BusinessType.INSERT) @PostMapping - public AjaxResult add(@RequestBody Coupons coupons) - { - return toAjax(couponsService.insertCoupons(coupons)); + @Transactional(rollbackFor = Exception.class) + public AjaxResult add(@RequestBody Coupons coupons) { + try { + // 数据验证 + if (coupons == null) { + return error("优惠券信息不能为空"); + } + if (coupons.getTitle() == null || coupons.getTitle().trim().isEmpty()) { + return error("优惠券标题不能为空"); + } + + // 插入优惠券主表 + int result = couponsService.insertCoupons(coupons); + logger.info("插入优惠券结果: {}, 优惠券ID: {}", result, coupons.getId()); + + if (result <= 0) { + return error("优惠券添加失败"); + } + + // 检查优惠券ID是否生成成功 + if (coupons.getId() == null) { + return error("优惠券ID生成失败"); + } + + // 处理用户关联 + String userids = coupons.getUserIds(); + logger.info("用户ID字符串: {}", userids); + + if (userids != null && !userids.trim().isEmpty()) { + // 处理用户ID字符串(支持JSON数组格式和逗号分隔格式) + List validUserIds = new ArrayList<>(); + + try { + // 首先尝试JSON数组格式解析 + if (userids.trim().startsWith("[") && userids.trim().endsWith("]")) { + // JSON数组格式: [1,2,3] 或 ["1","2","3"] + List userIdList = com.alibaba.fastjson2.JSON.parseArray(userids); + for (Object obj : userIdList) { + try { + Long uid = Long.valueOf(obj.toString()); + if (uid > 0) { + validUserIds.add(uid); + } + } catch (NumberFormatException e) { + logger.warn("非法用户ID: {}", obj); + } + } + } else { + // 逗号分隔格式:可能包含引号 + String[] userIdArray = userids.split(","); + for (String userId : userIdArray) { + if (userId != null && !userId.trim().isEmpty()) { + try { + // 移除引号和空格 + String cleanUserId = userId.trim().replaceAll("\"", "").replaceAll("'", ""); + if (!cleanUserId.isEmpty()) { + Long uid = Long.valueOf(cleanUserId); + if (uid > 0) { + validUserIds.add(uid); + } + } + } catch (NumberFormatException e) { + logger.warn("非法用户ID: {} (原始: {})", userId.trim(), userId); + } + } + } + } + } catch (Exception e) { + logger.error("解析用户ID字符串失败: {}, 错误: {}", userids, e.getMessage()); + // 如果JSON解析失败,尝试简单的逗号分隔处理 + String[] userIdArray = userids.split(","); + for (String userId : userIdArray) { + if (userId != null && !userId.trim().isEmpty()) { + try { + String cleanUserId = userId.trim().replaceAll("\"", "").replaceAll("'", ""); + if (!cleanUserId.isEmpty()) { + Long uid = Long.valueOf(cleanUserId); + if (uid > 0) { + validUserIds.add(uid); + } + } + } catch (NumberFormatException ex) { + logger.warn("非法用户ID: {}", userId.trim()); + } + } + } + } + + logger.info("有效用户ID列表: {}", validUserIds); + + if (!validUserIds.isEmpty()) { + // 批量查询用户信息 + List usersList = usersService.selectUsersByIds(validUserIds); + logger.info("查询到用户数量: {}", usersList != null ? usersList.size() : 0); + + if (usersList != null && !usersList.isEmpty()) { + // 批量插入用户优惠券关联 + for (Users user : usersList) { + if (user != null && user.getId() != null) { + try { + CouponUser couponUser = new CouponUser(); + couponUser.setUid(user.getId()); + couponUser.setCouponId(coupons.getId()); + couponUser.setCouponTitle(coupons.getTitle()); + + // 安全处理数值字段 + couponUser.setCouponPrice(coupons.getPrice() != null ? coupons.getPrice().intValue() : 0); + couponUser.setMinPrice(coupons.getMinPrice() != null ? coupons.getMinPrice().longValue() : 0L); + couponUser.setAddTime(System.currentTimeMillis()/1000); + + // 计算失效时间:截止时间 + 有效期天数 + if (coupons.getEndTime() != null) { + try { + // 获取有效期天数,默认为30天 + int validDays = 30; // 默认有效期 + + // 从优惠券对象获取有效期天数 + if (coupons.getCouponTime() != null && coupons.getCouponTime() > 0) { + validDays = coupons.getCouponTime().intValue(); + } + + // 计算失效时间:截止时间 + 有效期天数 + java.util.Calendar calendar = java.util.Calendar.getInstance(); + // endTime是秒级时间戳,需要转换为毫秒 + java.util.Date endDate = new java.util.Date(coupons.getEndTime() * 1000); + calendar.setTime(endDate); + calendar.add(java.util.Calendar.DAY_OF_MONTH, validDays); + + // 格式化失效时间 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String loseTime = sdf.format(calendar.getTime()); + couponUser.setLoseTime(loseTime); + + logger.info("计算失效时间 - 截止时间: {}, 有效期: {}天, 失效时间: {}", + sdf.format(endDate), validDays, loseTime); + + } catch (Exception e) { + logger.error("计算失效时间失败: {}", e.getMessage()); + // 失败时使用截止时间作为失效时间 + try { + java.util.Date endDate = new java.util.Date(coupons.getEndTime() * 1000); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + couponUser.setLoseTime(sdf.format(endDate)); + } catch (Exception ex) { + logger.error("设置默认失效时间失败: {}", ex.getMessage()); + } + } + } + + // 设置其他字段 + couponUser.setCateId(coupons.getCateId()); + couponUser.setProductId(coupons.getProductId()); + couponUser.setReceiveType(coupons.getReceiveType() != null ? String.valueOf(coupons.getReceiveType()) : "0"); + couponUser.setStatus(coupons.getStatus() != null ? coupons.getStatus() : 1); + + // 插入用户优惠券关联 + int insertResult = couponUserService.insertCouponUser(couponUser); + logger.info("插入用户优惠券关联, 用户ID: {}, 结果: {}", user.getId(), insertResult); + + } catch (Exception e) { + logger.error("插入用户优惠券关联失败, 用户ID: {}, 错误: {}", user.getId(), e.getMessage()); + throw new RuntimeException("插入用户优惠券关联失败: " + e.getMessage()); + } + } + } + } + } + } + + return success("优惠券添加成功"); + + } catch (Exception e) { + logger.error("添加优惠券失败: {}", e.getMessage(), e); + throw new RuntimeException("添加优惠券失败: " + e.getMessage()); + } } + /** * 修改优惠券 */ @PreAuthorize("@ss.hasPermi('system:Coupons:edit')") @Log(title = "优惠券", businessType = BusinessType.UPDATE) @PutMapping - public AjaxResult edit(@RequestBody Coupons coupons) - { - return toAjax(couponsService.updateCoupons(coupons)); + @Transactional(rollbackFor = Exception.class) + public AjaxResult edit(@RequestBody Coupons coupons) { + try { + // 数据验证 + if (coupons == null) { + return error("优惠券信息不能为空"); + } + if (coupons.getId() == null) { + return error("优惠券ID不能为空"); + } + if (coupons.getTitle() == null || coupons.getTitle().trim().isEmpty()) { + return error("优惠券标题不能为空"); + } + + // 检查优惠券是否存在 + Coupons existingCoupon = couponsService.selectCouponsById(coupons.getId()); + if (existingCoupon == null) { + return error("优惠券不存在"); + } + + // 更新优惠券主表 + int result = couponsService.updateCoupons(coupons); + logger.info("更新优惠券结果: {}, 优惠券ID: {}", result, coupons.getId()); + + if (result <= 0) { + return error("优惠券更新失败"); + } + + try { + // 先删除原有的用户关联 + int deleteResult = couponUserService.deleteCouponUserBycouponId(coupons.getId()); + logger.info("删除原有用户关联结果: {}, 优惠券ID: {}", deleteResult, coupons.getId()); + + // 处理新的用户关联 + String userids = coupons.getUserIds(); + logger.info("新的用户ID字符串: {}", userids); + + if (userids != null && !userids.trim().isEmpty()) { + // 处理用户ID字符串(支持JSON数组格式和逗号分隔格式) + List validUserIds = new ArrayList<>(); + + try { + // 首先尝试JSON数组格式解析 + if (userids.trim().startsWith("[") && userids.trim().endsWith("]")) { + // JSON数组格式: [1,2,3] 或 ["1","2","3"] + List userIdList = com.alibaba.fastjson2.JSON.parseArray(userids); + for (Object obj : userIdList) { + try { + Long uid = Long.valueOf(obj.toString()); + if (uid > 0) { + validUserIds.add(uid); + } + } catch (NumberFormatException e) { + logger.warn("非法用户ID: {}", obj); + } + } + } else { + // 逗号分隔格式:可能包含引号 + String[] userIdArray = userids.split(","); + for (String userId : userIdArray) { + if (userId != null && !userId.trim().isEmpty()) { + try { + // 移除引号和空格 + String cleanUserId = userId.trim().replaceAll("\"", "").replaceAll("'", ""); + if (!cleanUserId.isEmpty()) { + Long uid = Long.valueOf(cleanUserId); + if (uid > 0) { + validUserIds.add(uid); + } + } + } catch (NumberFormatException e) { + logger.warn("非法用户ID: {} (原始: {})", userId.trim(), userId); + } + } + } + } + } catch (Exception e) { + logger.error("解析用户ID字符串失败: {}, 错误: {}", userids, e.getMessage()); + // 如果JSON解析失败,尝试简单的逗号分隔处理 + String[] userIdArray = userids.split(","); + for (String userId : userIdArray) { + if (userId != null && !userId.trim().isEmpty()) { + try { + String cleanUserId = userId.trim().replaceAll("\"", "").replaceAll("'", ""); + if (!cleanUserId.isEmpty()) { + Long uid = Long.valueOf(cleanUserId); + if (uid > 0) { + validUserIds.add(uid); + } + } + } catch (NumberFormatException ex) { + logger.warn("非法用户ID: {}", userId.trim()); + } + } + } + } + + logger.info("有效用户ID列表: {}", validUserIds); + + if (!validUserIds.isEmpty()) { + // 批量查询用户信息 + List usersList = usersService.selectUsersByIds(validUserIds); + logger.info("查询到用户数量: {}", usersList != null ? usersList.size() : 0); + + if (usersList != null && !usersList.isEmpty()) { + // 批量插入新的用户优惠券关联 + for (Users user : usersList) { + if (user != null && user.getId() != null) { + try { + CouponUser couponUser = new CouponUser(); + couponUser.setUid(user.getId()); + couponUser.setCouponId(coupons.getId()); + couponUser.setCouponTitle(coupons.getTitle()); + + // 安全处理数值字段 + couponUser.setCouponPrice(coupons.getPrice() != null ? coupons.getPrice().intValue() : 0); + couponUser.setMinPrice(coupons.getMinPrice() != null ? coupons.getMinPrice().longValue() : 0L); + couponUser.setAddTime(System.currentTimeMillis()/1000); + + // 计算失效时间:截止时间 + 有效期天数 + if (coupons.getEndTime() != null) { + try { + // 获取有效期天数,默认为30天 + int validDays = 30; // 默认有效期 + + // 从优惠券对象获取有效期天数 + if (coupons.getCouponTime() != null && coupons.getCouponTime() > 0) { + validDays = coupons.getCouponTime().intValue(); + } + + // 计算失效时间:截止时间 + 有效期天数 + java.util.Calendar calendar = java.util.Calendar.getInstance(); + // endTime是秒级时间戳,需要转换为毫秒 + java.util.Date endDate = new java.util.Date(coupons.getEndTime() * 1000); + calendar.setTime(endDate); + calendar.add(java.util.Calendar.DAY_OF_MONTH, validDays); + + // 格式化失效时间 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String loseTime = sdf.format(calendar.getTime()); + couponUser.setLoseTime(loseTime); + + logger.info("计算失效时间 - 截止时间: {}, 有效期: {}天, 失效时间: {}", + sdf.format(endDate), validDays, loseTime); + + } catch (Exception e) { + logger.error("计算失效时间失败: {}", e.getMessage()); + // 失败时使用截止时间作为失效时间 + try { + java.util.Date endDate = new java.util.Date(coupons.getEndTime() * 1000); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + couponUser.setLoseTime(sdf.format(endDate)); + } catch (Exception ex) { + logger.error("设置默认失效时间失败: {}", ex.getMessage()); + } + } + } + + // 设置其他字段 + couponUser.setCateId(coupons.getCateId()); + couponUser.setProductId(coupons.getProductId()); + couponUser.setReceiveType(coupons.getReceiveType() != null ? String.valueOf(coupons.getReceiveType()) : "0"); + couponUser.setStatus(coupons.getStatus() != null ? coupons.getStatus() : 1); + + // 插入用户优惠券关联 + int insertResult = couponUserService.insertCouponUser(couponUser); + logger.info("插入用户优惠券关联, 用户ID: {}, 结果: {}", user.getId(), insertResult); + + } catch (Exception e) { + logger.error("插入用户优惠券关联失败, 用户ID: {}, 错误: {}", user.getId(), e.getMessage()); + throw new RuntimeException("插入用户优惠券关联失败: " + e.getMessage()); + } + } + } + } + } + } + + } catch (Exception e) { + logger.error("处理用户关联失败: {}", e.getMessage()); + throw new RuntimeException("处理用户关联失败: " + e.getMessage()); + } + + return success("优惠券修改成功"); + + } catch (Exception e) { + logger.error("修改优惠券失败: {}", e.getMessage(), e); + throw new RuntimeException("修改优惠券失败: " + e.getMessage()); + } } /** * 定时任务状态修改 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/IntegralOrderController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/IntegralOrderController.java index c043622..317817c 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/IntegralOrderController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/IntegralOrderController.java @@ -2,6 +2,10 @@ package com.ruoyi.system.controller; import java.util.List; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.system.domain.IntegralProduct; +import com.ruoyi.system.domain.Users; +import com.ruoyi.system.service.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -18,11 +22,8 @@ import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.system.domain.IntegralOrder; -import com.ruoyi.system.service.IIntegralOrderService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; -import com.ruoyi.system.service.ISysUserService; -import com.ruoyi.system.service.IServiceGoodsService; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.system.domain.ServiceGoods; @@ -41,8 +42,11 @@ public class IntegralOrderController extends BaseController @Autowired private ISysUserService sysUserService; @Autowired + private IUsersService usersService; + @Autowired private IServiceGoodsService serviceGoodsService; - + @Autowired + private IIntegralProductService integralProductService; /** * 查询积分订单列表 */ @@ -52,9 +56,27 @@ public class IntegralOrderController extends BaseController { startPage(); List list = integralOrderService.selectIntegralOrderList(integralOrder); + for(IntegralOrder data:list){ + Users users = usersService.selectUsersById(data.getUid()); + if (users!=null){ + data.setUname(users.getName()); + } + } return getDataTable(list); } + + /** + * 获取积分商品下拉选择数据 + */ + @PreAuthorize("@ss.hasPermi('system:IntegralOrder:query')") + @GetMapping(value = "/getIntegralProductList") + public AjaxResult getIntegralProductList() + { + return success(integralProductService.selectIntegralProductList(new IntegralProduct())); + } + + /** * 导出积分订单列表 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java index 6bb78db..a44b0db 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java @@ -4,8 +4,8 @@ import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; -import com.ruoyi.system.controllerUtil.VerificationResult; -import com.ruoyi.system.controllerUtil.OrderUtil; +import com.ruoyi.system.ControllerUtil.OrderUtil; +import com.ruoyi.system.ControllerUtil.VerificationResult; import com.ruoyi.system.domain.*; import com.ruoyi.system.service.*; import org.springframework.security.access.prepost.PreAuthorize; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/SiteDeliveryController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/SiteDeliveryController.java index b571c81..5c72234 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/SiteDeliveryController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/SiteDeliveryController.java @@ -61,6 +61,18 @@ public class SiteDeliveryController extends BaseController util.exportExcel(response, list, "快递公司数据"); } + + /** + * 获取快递公司下拉数据选项 + */ + @PreAuthorize("@ss.hasPermi('system:SiteDelivery:query')") + @GetMapping(value = "/getSiteDeliveryList") + public AjaxResult getSiteDeliveryList() + { + return success(siteDeliveryService.selectSiteDeliveryList(new SiteDelivery())); + } + + /** * 获取快递公司详细信息 */ @@ -70,6 +82,8 @@ public class SiteDeliveryController extends BaseController { return success(siteDeliveryService.selectSiteDeliveryById(id)); } + + /** * 定时任务状态修改 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderLogHandler.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderLogHandler.java index 91e7c87..a4ca1a7 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderLogHandler.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderLogHandler.java @@ -1,4 +1,4 @@ -package com.ruoyi.system.controllerUtil; +package com.ruoyi.system.ControllerUtil; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.utils.spring.SpringUtils; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderStatusValidator.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderStatusValidator.java index 38ceff0..1d7b2ba 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderStatusValidator.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderStatusValidator.java @@ -1,4 +1,4 @@ -package com.ruoyi.system.controllerUtil; +package com.ruoyi.system.ControllerUtil; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.Order; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderUtil.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderUtil.java index 0f6e305..eb17b98 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderUtil.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/OrderUtil.java @@ -1,4 +1,4 @@ -package com.ruoyi.system.controllerUtil; +package com.ruoyi.system.ControllerUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/ScheduledTaskExample.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/ScheduledTaskExample.java new file mode 100644 index 0000000..4afaeec --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/ScheduledTaskExample.java @@ -0,0 +1,270 @@ +package com.ruoyi.system.ControllerUtil; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.spring.SpringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 定时任务工具类使用示例 + * + * 此类展示了如何在Controller中使用ScheduledTaskUtil + * 包括手动触发任务、获取统计信息等功能 + * + * 使用说明: + * 1. 在需要的Controller中注入ScheduledTaskUtil + * 2. 调用相应的方法进行操作 + * 3. 查看任务执行状态和统计信息 + * + * @author RuoYi + * @date 2024-01-01 + */ +@RestController +@RequestMapping("/system/scheduled-task") +public class ScheduledTaskExample { + + /** + * 手动触发派单超时处理 + * + * 使用场景: + * - 系统维护后需要立即检查超时订单 + * - 紧急情况下需要手动处理超时订单 + * + * 请求示例:GET /system/scheduled-task/dispatch-timeout + */ + @GetMapping("/dispatch-timeout") + public AjaxResult manualDispatchTimeoutCheck() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + String result = taskUtil.manualDispatchTimeoutCheck(); + return AjaxResult.success("操作成功", result); + } catch (Exception e) { + return AjaxResult.error("操作失败:" + e.getMessage()); + } + } + + /** + * 手动触发订单状态检查 + * + * 使用场景: + * - 发现异常订单时进行状态检查 + * - 定期人工检查订单状态 + * + * 请求示例:GET /system/scheduled-task/status-check + */ + @GetMapping("/status-check") + public AjaxResult manualOrderStatusCheck() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + String result = taskUtil.manualOrderStatusCheck(); + return AjaxResult.success("操作成功", result); + } catch (Exception e) { + return AjaxResult.error("操作失败:" + e.getMessage()); + } + } + + /** + * 手动触发系统数据清理 + * + * 使用场景: + * - 磁盘空间不足时清理数据 + * - 系统维护时清理垃圾数据 + * + * 请求示例:GET /system/scheduled-task/data-cleanup + */ + @GetMapping("/data-cleanup") + public AjaxResult manualDataCleanup() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + String result = taskUtil.manualDataCleanup(); + return AjaxResult.success("操作成功", result); + } catch (Exception e) { + return AjaxResult.error("操作失败:" + e.getMessage()); + } + } + + /** + * 手动触发健康检查 + * + * 使用场景: + * - 系统启动后验证各组件是否正常 + * - 定期检查系统健康状态 + * + * 请求示例:GET /system/scheduled-task/health-check + */ + @GetMapping("/health-check") + public AjaxResult manualHealthCheck() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + String result = taskUtil.manualHealthCheck(); + return AjaxResult.success("操作成功", result); + } catch (Exception e) { + return AjaxResult.error("操作失败:" + e.getMessage()); + } + } + + /** + * 获取任务执行统计信息 + * + * 使用场景: + * - 监控任务执行情况 + * - 分析系统性能 + * - 故障排查 + * + * 请求示例:GET /system/scheduled-task/statistics + */ + @GetMapping("/statistics") + public AjaxResult getTaskStatistics() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + Map stats = taskUtil.getTaskStatistics(); + return AjaxResult.success("获取成功", stats); + } catch (Exception e) { + return AjaxResult.error("获取失败:" + e.getMessage()); + } + } + + /** + * 获取格式化的任务统计报告 + * + * 使用场景: + * - 生成系统运行报告 + * - 展示给运维人员查看 + * - 导出系统状态信息 + * + * 请求示例:GET /system/scheduled-task/report + */ + @GetMapping("/report") + public AjaxResult getTaskStatisticsReport() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + String report = taskUtil.getTaskStatisticsReport(); + return AjaxResult.success("获取成功", report); + } catch (Exception e) { + return AjaxResult.error("获取失败:" + e.getMessage()); + } + } + + /** + * 获取线程池状态信息 + * + * 使用场景: + * - 监控线程池使用情况 + * - 性能调优参考 + * - 系统负载分析 + * + * 请求示例:GET /system/scheduled-task/thread-pool + */ + @GetMapping("/thread-pool") + public AjaxResult getThreadPoolStatus() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + Map status = taskUtil.getThreadPoolStatus(); + return AjaxResult.success("获取成功", status); + } catch (Exception e) { + return AjaxResult.error("获取失败:" + e.getMessage()); + } + } + + /** + * 重启任务线程池 + * + * 使用场景: + * - 线程池出现异常时重启 + * - 系统维护后重启服务 + * - 配置更改后重新初始化 + * + * 请求示例:POST /system/scheduled-task/restart-pool + */ + @PostMapping("/restart-pool") + public AjaxResult restartTaskPool() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + taskUtil.restartTaskPool(); + return AjaxResult.success("线程池重启成功"); + } catch (Exception e) { + return AjaxResult.error("线程池重启失败:" + e.getMessage()); + } + } + + /** + * 停止所有任务 + * + * 使用场景: + * - 系统维护时暂停任务 + * - 紧急情况下停止所有任务 + * + * 注意:此操作只停止线程池,@Scheduled注解的任务仍会执行 + * + * 请求示例:POST /system/scheduled-task/stop-all + */ + @PostMapping("/stop-all") + public AjaxResult stopAllTasks() { + try { + ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + taskUtil.stopAllTasks(); + return AjaxResult.success("所有任务已停止"); + } catch (Exception e) { + return AjaxResult.error("停止任务失败:" + e.getMessage()); + } + } +} + +/** + * ==================== 使用说明 ==================== + * + * 1. 自动执行的定时任务(无需手动调用): + * - 订单派单超时处理:每5分钟执行一次 + * - 订单状态超时检查:每10分钟执行一次 + * - 系统数据清理:每天凌晨2点执行 + * - 系统健康检查:每30分钟执行一次 + * + * 2. 手动触发任务的方式: + * - 通过HTTP接口调用(如上面的Controller方法) + * - 在其他Service中直接调用工具类方法 + * - 在系统管理后台添加按钮触发 + * + * 3. 在其他地方使用的示例代码: + * + * // 在Service中使用 + * @Service + * public class OrderService { + * public void processOrders() { + * ScheduledTaskUtil taskUtil = SpringUtils.getBean(ScheduledTaskUtil.class); + * String result = taskUtil.manualDispatchTimeoutCheck(); + * // 处理结果... + * } + * } + * + * // 在Controller中使用 + * @RestController + * public class OrderController { + * @Autowired + * private ScheduledTaskUtil scheduledTaskUtil; + * + * @GetMapping("/check-timeout") + * public AjaxResult checkTimeout() { + * String result = scheduledTaskUtil.manualDispatchTimeoutCheck(); + * return AjaxResult.success(result); + * } + * } + * + * 4. 监控和统计: + * - 可通过 /system/scheduled-task/statistics 接口获取详细统计 + * - 可通过 /system/scheduled-task/report 接口获取格式化报告 + * - 统计信息包括:执行次数、成功率、平均耗时等 + * + * 5. 配置说明: + * - 派单超时时间:20分钟(可在getDispatchTimeoutOrders方法中修改) + * - 待支付超时时间:30分钟(可在checkPaymentTimeoutOrders方法中修改) + * - 服务超时时间:4小时(可在checkServiceTimeoutOrders方法中修改) + * - 日志保留时间:30天(可在cleanupExpiredOrderLogs方法中修改) + * + * 6. 注意事项: + * - 确保Spring容器中有IOrderService的实现 + * - 如果需要数据库操作,需要在相应方法中添加具体实现 + * - 可根据实际业务需求调整各种超时时间和处理逻辑 + * - 建议在生产环境中根据服务器性能调整线程池参数 + * + */ \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/ScheduledTaskUtil.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/ScheduledTaskUtil.java new file mode 100644 index 0000000..16cb3f4 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/ScheduledTaskUtil.java @@ -0,0 +1,918 @@ +package com.ruoyi.system.ControllerUtil; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.Order; +import com.ruoyi.system.service.IOrderService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.*; + +/** + * 定时任务工具类 + * + * 主要功能: + * 1. 订单派单超时处理(20分钟自动处理) + * 2. 订单状态超时检查 + * 3. 系统数据清理 + * 4. 任务调度管理 + * 5. 异步任务执行 + * + * 使用方式: + * - 自动定时执行:通过@Scheduled注解配置的定时任务会自动执行 + * - 手动调用:可直接调用相应的方法进行手动处理 + * - 异步执行:支持异步执行耗时任务,不阻塞主线程 + * + * @author RuoYi + * @date 2024-01-01 + */ +@Component +public class ScheduledTaskUtil { + + private static final Logger log = LoggerFactory.getLogger(ScheduledTaskUtil.class); + + // 订单服务,通过Spring工具类获取 + private static IOrderService orderService; + + // 线程池,用于异步执行任务 + private ThreadPoolExecutor executorService; + + // 任务执行统计 + private final Map taskStats = new ConcurrentHashMap<>(); + + /** + * 初始化方法,在Bean创建后执行 + */ + @PostConstruct + public void init() { + // 获取订单服务 + try { + orderService = SpringUtils.getBean(IOrderService.class); + } catch (Exception e) { + log.warn("获取订单服务失败,部分功能可能不可用: {}", e.getMessage()); + } + + // 初始化线程池 + initThreadPool(); + + log.info("定时任务工具类初始化完成"); + } + + /** + * 初始化线程池 + */ + private void initThreadPool() { + executorService = new ThreadPoolExecutor( + 5, // 核心线程数 + 10, // 最大线程数 + 60L, // 线程空闲时间 + TimeUnit.SECONDS, // 时间单位 + new LinkedBlockingQueue<>(100), // 工作队列 + new ThreadFactory() { // 线程工厂 + private int counter = 0; + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "ScheduledTask-" + counter++); + t.setDaemon(true); + return t; + } + }, + new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 + ); + } + + // ========================= 定时任务方法 ========================= + + /** + * 订单派单超时处理任务 + * 每5分钟执行一次,检查派单超过20分钟的订单 + * + * 使用说明: + * - 自动执行,无需手动调用 + * - 处理逻辑:将超时未接单的订单重新派发或标记为超时 + * - 执行频率:每5分钟(可根据业务需求调整) + */ + @Scheduled(fixedRate = 5 * 60 * 1000) // 每5分钟执行一次 + public void handleDispatchTimeout() { + String taskName = "订单派单超时处理"; + long startTime = System.currentTimeMillis(); + + try { + log.info("开始执行{}任务", taskName); + + // 获取派单超过20分钟的订单 + List timeoutOrders = getDispatchTimeoutOrders(); + + if (timeoutOrders.isEmpty()) { + log.info("{}任务执行完成,无超时订单", taskName); + return; + } + + log.info("发现{}个派单超时订单,开始处理", timeoutOrders.size()); + + // 异步处理超时订单 + processTimeoutOrdersAsync(timeoutOrders); + + // 更新任务统计 + updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime); + + } catch (Exception e) { + log.error("{}任务执行失败", taskName, e); + updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime); + } + } + + /** + * 订单状态超时检查任务 + * 每10分钟执行一次,检查各种状态的订单是否超时 + * + * 使用说明: + * - 检查服务中订单是否超过预期时间 + * - 检查待支付订单是否超时 + * - 处理异常状态订单 + */ + @Scheduled(fixedRate = 10 * 60 * 1000) // 每10分钟执行一次 + public void checkOrderStatusTimeout() { + String taskName = "订单状态超时检查"; + long startTime = System.currentTimeMillis(); + + try { + log.info("开始执行{}任务", taskName); + + // 检查服务中超时订单 + checkServiceTimeoutOrders(); + + // 检查待支付超时订单 + checkPaymentTimeoutOrders(); + + // 检查其他异常状态订单 + checkAbnormalStatusOrders(); + + updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime); + + } catch (Exception e) { + log.error("{}任务执行失败", taskName, e); + updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime); + } + } + + /** + * 系统数据清理任务 + * 每天凌晨2点执行,清理过期数据 + * + * 使用说明: + * - 清理30天前的日志数据 + * - 清理临时文件 + * - 优化数据库性能 + */ + @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 + public void cleanupSystemData() { + String taskName = "系统数据清理"; + long startTime = System.currentTimeMillis(); + + try { + log.info("开始执行{}任务", taskName); + + // 清理过期订单日志 + cleanupExpiredOrderLogs(); + + // 清理临时文件 + cleanupTempFiles(); + + // 清理任务统计数据 + cleanupTaskStatistics(); + + updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime); + log.info("{}任务执行完成", taskName); + + } catch (Exception e) { + log.error("{}任务执行失败", taskName, e); + updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime); + } + } + + /** + * 健康检查任务 + * 每30分钟执行一次,检查系统健康状态 + */ + @Scheduled(fixedRate = 30 * 60 * 1000) // 每30分钟执行一次 + public void healthCheck() { + String taskName = "系统健康检查"; + long startTime = System.currentTimeMillis(); + + try { + log.info("开始执行{}任务", taskName); + + // 检查数据库连接 + checkDatabaseConnection(); + + // 检查线程池状态 + checkThreadPoolStatus(); + + // 检查系统资源 + checkSystemResources(); + + updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime); + + } catch (Exception e) { + log.error("{}任务执行失败", taskName, e); + updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime); + } + } + + // ========================= 业务处理方法 ========================= + + /** + * 获取派单超过20分钟的订单 + * + * @return 超时订单列表 + */ + private List getDispatchTimeoutOrders() { + try { + // 计算20分钟前的时间 + LocalDateTime timeoutThreshold = LocalDateTime.now().minusMinutes(20); + Date thresholdDate = Date.from(timeoutThreshold.atZone(ZoneId.systemDefault()).toInstant()); + + // 构建查询条件 + Order queryOrder = new Order(); + queryOrder.setJsonStatus(1); // 假设1表示已派单状态 + + // 如果orderService可用,执行查询 + if (orderService != null) { + // 这里需要根据实际的Service方法进行调用 + // 示例:return orderService.selectTimeoutDispatchOrders(thresholdDate); + log.debug("查询派单超时订单,截止时间:{}", thresholdDate); + } + + // 临时返回空列表,实际使用时需要实现相应的查询方法 + return new ArrayList<>(); + + } catch (Exception e) { + log.error("获取派单超时订单失败", e); + return new ArrayList<>(); + } + } + + /** + * 异步处理超时订单 + * + * @param timeoutOrders 超时订单列表 + */ + @Async + public void processTimeoutOrdersAsync(List timeoutOrders) { + executorService.submit(() -> { + for (Order order : timeoutOrders) { + try { + processTimeoutOrder(order); + } catch (Exception e) { + log.error("处理超时订单失败,订单号:{}", order.getOrderId(), e); + } + } + }); + } + + /** + * 处理单个超时订单 + * + * @param order 超时订单 + */ + private void processTimeoutOrder(Order order) { + log.info("处理派单超时订单:{}", order.getOrderId()); + + try { + // 业务处理逻辑 + if (canRedispatch(order)) { + // 重新派单 + redispatchOrder(order); + log.info("订单{}重新派单成功", order.getOrderId()); + } else { + // 标记为超时 + markOrderAsTimeout(order); + log.info("订单{}标记为派单超时", order.getOrderId()); + } + + // 记录处理日志 + recordOrderProcessLog(order, "派单超时处理"); + + } catch (Exception e) { + log.error("处理超时订单{}失败", order.getOrderId(), e); + } + } + + /** + * 检查服务中的超时订单 + */ + private void checkServiceTimeoutOrders() { + log.info("检查服务中超时订单"); + + try { + // 获取服务中超过预期时间的订单 + LocalDateTime timeoutThreshold = LocalDateTime.now().minusHours(4); // 假设服务超过4小时为超时 + + // 处理服务超时订单 + processServiceTimeoutOrders(timeoutThreshold); + + } catch (Exception e) { + log.error("检查服务中超时订单失败", e); + } + } + + /** + * 检查待支付超时订单 + */ + private void checkPaymentTimeoutOrders() { + log.info("检查待支付超时订单"); + + try { + // 获取待支付超过30分钟的订单 + LocalDateTime timeoutThreshold = LocalDateTime.now().minusMinutes(30); + + // 处理超时订单 + processPaymentTimeoutOrders(timeoutThreshold); + + } catch (Exception e) { + log.error("检查待支付超时订单失败", e); + } + } + + /** + * 检查异常状态订单 + */ + private void checkAbnormalStatusOrders() { + log.info("检查异常状态订单"); + + try { + // 检查状态异常的订单 + // 例如:长时间处于某个中间状态的订单 + LocalDateTime abnormalThreshold = LocalDateTime.now().minusHours(24); + + processAbnormalStatusOrders(abnormalThreshold); + + } catch (Exception e) { + log.error("检查异常状态订单失败", e); + } + } + + // ========================= 工具方法 ========================= + + /** + * 判断订单是否可以重新派单 + * + * @param order 订单对象 + * @return true-可以重新派单,false-不能重新派单 + */ + private boolean canRedispatch(Order order) { + // 业务判断逻辑 + // 例如:检查重派次数、订单类型、订单创建时间等 + + // 简单示例:检查是否已经重派过多次 + // 实际项目中需要根据订单表中的重派次数字段来判断 + return true; // 简化实现,实际使用时需要完善判断逻辑 + } + + /** + * 重新派单 + * + * @param order 订单对象 + */ + private void redispatchOrder(Order order) { + try { + // 重新派单逻辑 + order.setJsonStatus(1); // 重新设为派单状态 + order.setUpdateTime(new Date()); + + // 如果有重派次数字段,需要增加重派次数 + // order.setRedispatchCount(order.getRedispatchCount() + 1); + + // 更新订单 + if (orderService != null) { + // orderService.updateOrder(order); + log.info("订单{}重新派单操作完成", order.getOrderId()); + } + + } catch (Exception e) { + log.error("重新派单失败,订单号:{}", order.getOrderId(), e); + } + } + + /** + * 标记订单为超时 + * + * @param order 订单对象 + */ + private void markOrderAsTimeout(Order order) { + try { + // 标记为超时状态 + order.setJsonStatus(99); // 假设99表示超时状态 + order.setUpdateTime(new Date()); + + // 更新订单 + if (orderService != null) { + // orderService.updateOrder(order); + log.info("订单{}标记为超时状态", order.getOrderId()); + } + + } catch (Exception e) { + log.error("标记订单超时失败,订单号:{}", order.getOrderId(), e); + } + } + + /** + * 记录订单处理日志 + * + * @param order 订单对象 + * @param operation 操作描述 + */ + private void recordOrderProcessLog(Order order, String operation) { + try { + // 使用OrderUtil记录日志 + OrderUtil orderUtil = SpringUtils.getBean(OrderUtil.class); + orderUtil.SaveOrderLog(order); + + log.info("订单{}{}处理日志记录成功", order.getOrderId(), operation); + + } catch (Exception e) { + log.error("记录订单处理日志失败,订单号:{}", order.getOrderId(), e); + } + } + + /** + * 处理服务中超时订单 + * + * @param timeoutThreshold 超时时间阈值 + */ + private void processServiceTimeoutOrders(LocalDateTime timeoutThreshold) { + try { + log.debug("处理服务中超时订单,超时阈值:{}", timeoutThreshold); + + // 查询服务中超时的订单 + // 这里需要根据实际的数据库结构和Service方法来实现 + + // 示例处理逻辑: + // 1. 查询服务状态且创建时间早于阈值的订单 + // 2. 发送提醒通知 + // 3. 或者自动标记为异常状态 + + log.debug("服务中超时订单处理完成"); + + } catch (Exception e) { + log.error("处理服务中超时订单失败", e); + } + } + + /** + * 处理待支付超时订单 + * + * @param timeoutThreshold 超时时间阈值 + */ + private void processPaymentTimeoutOrders(LocalDateTime timeoutThreshold) { + try { + log.debug("处理待支付超时订单,超时阈值:{}", timeoutThreshold); + + // 查询待支付超时的订单 + // 示例处理逻辑: + // 1. 查询待支付状态且创建时间早于阈值的订单 + // 2. 自动取消订单 + // 3. 释放相关资源 + + log.debug("待支付超时订单处理完成"); + + } catch (Exception e) { + log.error("处理待支付超时订单失败", e); + } + } + + /** + * 处理异常状态订单 + * + * @param abnormalThreshold 异常时间阈值 + */ + private void processAbnormalStatusOrders(LocalDateTime abnormalThreshold) { + try { + log.debug("处理异常状态订单,异常阈值:{}", abnormalThreshold); + + // 查询异常状态的订单 + // 示例处理逻辑: + // 1. 查询长时间处于中间状态的订单 + // 2. 发送告警通知 + // 3. 人工介入处理 + + log.debug("异常状态订单处理完成"); + + } catch (Exception e) { + log.error("处理异常状态订单失败", e); + } + } + + /** + * 清理过期订单日志 + */ + private void cleanupExpiredOrderLogs() { + try { + log.info("开始清理过期订单日志"); + + // 删除30天前的日志 + LocalDateTime expireTime = LocalDateTime.now().minusDays(30); + Date expireDate = Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant()); + + // 执行清理 + // 这里需要调用相应的日志服务方法 + // int deletedCount = orderLogService.deleteExpiredLogs(expireDate); + // log.info("清理过期订单日志完成,删除{}条记录", deletedCount); + + log.info("清理过期订单日志完成"); + + } catch (Exception e) { + log.error("清理过期订单日志失败", e); + } + } + + /** + * 清理临时文件 + */ + private void cleanupTempFiles() { + try { + log.info("开始清理临时文件"); + + // 清理临时目录中的过期文件 + // 具体实现根据项目需求 + String tempDir = System.getProperty("java.io.tmpdir"); + log.debug("临时文件目录:{}", tempDir); + + // 这里可以添加具体的文件清理逻辑 + + log.info("清理临时文件完成"); + + } catch (Exception e) { + log.error("清理临时文件失败", e); + } + } + + /** + * 清理任务统计数据 + */ + private void cleanupTaskStatistics() { + try { + // 只保留最近7天的统计数据 + LocalDateTime expireTime = LocalDateTime.now().minusDays(7); + + taskStats.entrySet().removeIf(entry -> { + TaskStatistics stats = entry.getValue(); + return stats.getLastExecuteTime() != null && stats.getLastExecuteTime().isBefore(expireTime); + }); + + log.info("清理任务统计数据完成"); + + } catch (Exception e) { + log.error("清理任务统计数据失败", e); + } + } + + /** + * 检查数据库连接 + */ + private void checkDatabaseConnection() { + try { + // 执行简单查询检查数据库连接 + if (orderService != null) { + // 这里可以执行一个简单的查询来验证数据库连接 + // orderService.selectOrderById(1L); + log.debug("数据库连接检查正常"); + } else { + log.warn("订单服务未初始化,跳过数据库连接检查"); + } + + } catch (Exception e) { + log.error("数据库连接检查失败", e); + } + } + + /** + * 检查线程池状态 + */ + private void checkThreadPoolStatus() { + if (executorService != null) { + int activeCount = executorService.getActiveCount(); + int queueSize = executorService.getQueue().size(); + int poolSize = executorService.getPoolSize(); + long completedTaskCount = executorService.getCompletedTaskCount(); + + log.debug("线程池状态 - 活跃线程数: {}, 池大小: {}, 队列大小: {}, 已完成任务: {}", + activeCount, poolSize, queueSize, completedTaskCount); + + // 如果队列积压过多,记录警告 + if (queueSize > 80) { + log.warn("线程池队列积压过多,当前队列大小: {}", queueSize); + } + + // 如果活跃线程数过多,记录警告 + if (activeCount > 8) { + log.warn("线程池活跃线程数过多,当前活跃线程数: {}", activeCount); + } + } + } + + /** + * 检查系统资源 + */ + private void checkSystemResources() { + try { + // 检查JVM内存使用情况 + Runtime runtime = Runtime.getRuntime(); + long totalMemory = runtime.totalMemory(); + long freeMemory = runtime.freeMemory(); + long usedMemory = totalMemory - freeMemory; + long maxMemory = runtime.maxMemory(); + + double memoryUsagePercent = (double) usedMemory / totalMemory * 100; + double maxMemoryUsagePercent = (double) usedMemory / maxMemory * 100; + + log.debug("内存使用情况 - 总内存: {}MB, 已用: {}MB, 最大内存: {}MB, 当前使用率: {:.2f}%, 最大使用率: {:.2f}%", + totalMemory / 1024 / 1024, usedMemory / 1024 / 1024, maxMemory / 1024 / 1024, + memoryUsagePercent, maxMemoryUsagePercent); + + // 内存使用率过高时记录警告 + if (maxMemoryUsagePercent > 80) { + log.warn("内存使用率过高: {:.2f}%", maxMemoryUsagePercent); + } + + // 检查垃圾回收情况 + System.gc(); // 建议进行垃圾回收(实际生产环境中谨慎使用) + + } catch (Exception e) { + log.error("检查系统资源失败", e); + } + } + + /** + * 更新任务统计信息 + * + * @param taskName 任务名称 + * @param success 是否成功 + * @param duration 执行时长(毫秒) + */ + private void updateTaskStatistics(String taskName, boolean success, long duration) { + taskStats.compute(taskName, (key, stats) -> { + if (stats == null) { + stats = new TaskStatistics(taskName); + } + stats.updateStats(success, duration); + return stats; + }); + } + + // ========================= 公共方法 ========================= + + /** + * 手动触发派单超时处理 + * + * 使用说明: + * - 可在特殊情况下手动调用此方法 + * - 例如系统维护后需要立即检查超时订单 + * + * @return 处理结果描述 + */ + public String manualDispatchTimeoutCheck() { + try { + log.info("手动触发派单超时处理"); + handleDispatchTimeout(); + return "派单超时处理执行成功"; + } catch (Exception e) { + log.error("手动派单超时处理失败", e); + return "派单超时处理执行失败: " + e.getMessage(); + } + } + + /** + * 手动触发订单状态检查 + * + * @return 处理结果描述 + */ + public String manualOrderStatusCheck() { + try { + log.info("手动触发订单状态检查"); + checkOrderStatusTimeout(); + return "订单状态检查执行成功"; + } catch (Exception e) { + log.error("手动订单状态检查失败", e); + return "订单状态检查执行失败: " + e.getMessage(); + } + } + + /** + * 手动触发系统数据清理 + * + * @return 处理结果描述 + */ + public String manualDataCleanup() { + try { + log.info("手动触发系统数据清理"); + cleanupSystemData(); + return "系统数据清理执行成功"; + } catch (Exception e) { + log.error("手动系统数据清理失败", e); + return "系统数据清理执行失败: " + e.getMessage(); + } + } + + /** + * 手动触发健康检查 + * + * @return 处理结果描述 + */ + public String manualHealthCheck() { + try { + log.info("手动触发健康检查"); + healthCheck(); + return "健康检查执行成功"; + } catch (Exception e) { + log.error("手动健康检查失败", e); + return "健康检查执行失败: " + e.getMessage(); + } + } + + /** + * 获取任务执行统计信息 + * + * 使用说明: + * - 可用于监控各个定时任务的执行情况 + * - 包含执行次数、成功率、平均执行时间等信息 + * + * @return 任务统计信息 + */ + public Map getTaskStatistics() { + return new HashMap<>(taskStats); + } + + /** + * 获取格式化的任务统计报告 + * + * @return 格式化的统计报告 + */ + public String getTaskStatisticsReport() { + StringBuilder report = new StringBuilder(); + report.append("=== 定时任务执行统计报告 ===\n"); + report.append(String.format("报告生成时间: %s\n", + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))); + report.append(String.format("线程池状态: 活跃线程=%d, 队列大小=%d\n", + executorService.getActiveCount(), executorService.getQueue().size())); + report.append("\n"); + + if (taskStats.isEmpty()) { + report.append("暂无任务执行记录\n"); + } else { + for (TaskStatistics stats : taskStats.values()) { + report.append(stats.toString()).append("\n"); + } + } + + return report.toString(); + } + + /** + * 获取线程池状态信息 + * + * @return 线程池状态信息 + */ + public Map getThreadPoolStatus() { + Map status = new HashMap<>(); + if (executorService != null) { + status.put("activeCount", executorService.getActiveCount()); + status.put("poolSize", executorService.getPoolSize()); + status.put("queueSize", executorService.getQueue().size()); + status.put("completedTaskCount", executorService.getCompletedTaskCount()); + status.put("isShutdown", executorService.isShutdown()); + status.put("isTerminated", executorService.isTerminated()); + } + return status; + } + + /** + * 停止所有定时任务 + * + * 使用说明: + * - 用于系统维护时临时停止定时任务 + * - 注意:这只是停止线程池,@Scheduled注解的任务仍会执行 + */ + public void stopAllTasks() { + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + try { + // 等待60秒让任务完成 + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { + log.error("线程池无法正常关闭"); + } + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + log.info("定时任务线程池已停止"); + } + } + + /** + * 重启任务线程池 + */ + public void restartTaskPool() { + stopAllTasks(); + initThreadPool(); + log.info("定时任务线程池已重启"); + } + + // ========================= 内部类 ========================= + + /** + * 任务统计信息类 + */ + public static class TaskStatistics { + private final String taskName; + private int totalCount = 0; + private int successCount = 0; + private long totalDuration = 0; + private LocalDateTime lastExecuteTime; + private LocalDateTime firstExecuteTime; + private String lastErrorMessage; + + public TaskStatistics(String taskName) { + this.taskName = taskName; + this.firstExecuteTime = LocalDateTime.now(); + } + + public void updateStats(boolean success, long duration) { + totalCount++; + if (success) { + successCount++; + lastErrorMessage = null; + } + totalDuration += duration; + lastExecuteTime = LocalDateTime.now(); + } + + public void setLastErrorMessage(String errorMessage) { + this.lastErrorMessage = errorMessage; + } + + // Getter methods + public String getTaskName() { + return taskName; + } + + public int getTotalCount() { + return totalCount; + } + + public int getSuccessCount() { + return successCount; + } + + public int getFailureCount() { + return totalCount - successCount; + } + + public LocalDateTime getLastExecuteTime() { + return lastExecuteTime; + } + + public LocalDateTime getFirstExecuteTime() { + return firstExecuteTime; + } + + public String getLastErrorMessage() { + return lastErrorMessage; + } + + public double getSuccessRate() { + return totalCount == 0 ? 0 : (double) successCount / totalCount * 100; + } + + public long getAverageDuration() { + return totalCount == 0 ? 0 : totalDuration / totalCount; + } + + public long getTotalDuration() { + return totalDuration; + } + + @Override + public String toString() { + return String.format( + "任务名称: %-20s | 执行次数: %-6d | 成功次数: %-6d | 失败次数: %-6d | 成功率: %6.2f%% | 平均耗时: %-6dms | 总耗时: %-8dms | 最后执行: %s", + taskName, totalCount, successCount, getFailureCount(), getSuccessRate(), getAverageDuration(), totalDuration, + lastExecuteTime != null ? lastExecuteTime.format(DateTimeFormatter.ofPattern("MM-dd HH:mm:ss")) : "未执行" + ); + } + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/VerificationResult.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/VerificationResult.java index 2eb62a4..da835c1 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/VerificationResult.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/VerificationResult.java @@ -1,4 +1,4 @@ -package com.ruoyi.system.controllerUtil; +package com.ruoyi.system.ControllerUtil; public class VerificationResult { diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/IntegralOrder.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/IntegralOrder.java index 1191c77..0bfd25f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/IntegralOrder.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/IntegralOrder.java @@ -28,6 +28,10 @@ public class IntegralOrder extends BaseEntity @Excel(name = "用户") private Long uid; + /** 用户姓名 */ + @Excel(name = "用户姓名") + private String uname; + /** 姓名 */ @Excel(name = "姓名") private String userName; @@ -254,6 +258,14 @@ public class IntegralOrder extends BaseEntity return updatedAt; } + public String getUname() { + return uname; + } + + public void setUname(String uname) { + this.uname = uname; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/CouponUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/CouponUserMapper.java index 1000a4e..adea582 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/CouponUserMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/CouponUserMapper.java @@ -55,6 +55,14 @@ public interface CouponUserMapper */ public int deleteCouponUserById(Long id); + + /** + * 批量删除优惠券领取记录 + * + * @param couponId + * @return 结果 + */ + public int deleteCouponUserBycouponId (Long couponId); /** * 批量删除优惠券领取记录 * diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ICouponUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ICouponUserService.java index 385aeef..9d529e3 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ICouponUserService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ICouponUserService.java @@ -19,7 +19,13 @@ public interface ICouponUserService */ public CouponUser selectCouponUserById(Long id); - + /** + * 批量删除优惠券领取记录 + * + * @param couponId + * @return 结果 + */ + public int deleteCouponUserBycouponId (Long couponId); public int selectCountCouponUserbycouponId(Long couponId); /** diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CouponUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CouponUserServiceImpl.java index 9c5e831..5df5c58 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CouponUserServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CouponUserServiceImpl.java @@ -31,7 +31,16 @@ public class CouponUserServiceImpl implements ICouponUserService return couponUserMapper.selectCouponUserById(id); } + /** + * 批量删除优惠券领取记录 + * + * @param couponId + * @return 结果 + */ + public int deleteCouponUserBycouponId (Long couponId) { + return couponUserMapper.deleteCouponUserBycouponId(couponId); + } public int selectCountCouponUserbycouponId(Long couponId) { diff --git a/ruoyi-system/src/main/resources/mapper/system/CouponUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/CouponUserMapper.xml index 0631226..f13f9c5 100644 --- a/ruoyi-system/src/main/resources/mapper/system/CouponUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/CouponUserMapper.xml @@ -119,6 +119,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" delete from coupon_user where id = #{id} + + delete from coupon_user where coupon_id = #{couponId} + delete from coupon_user where id in diff --git a/ruoyi-ui/src/api/system/GoodsOrder.js b/ruoyi-ui/src/api/system/GoodsOrder.js index 11fd708..3f24d50 100644 --- a/ruoyi-ui/src/api/system/GoodsOrder.js +++ b/ruoyi-ui/src/api/system/GoodsOrder.js @@ -22,6 +22,13 @@ export function getGoodsDataList() { method: 'get' }) } + +export function getSiteDeliveryList() { + return request({ + url: '/system/SiteDelivery/getSiteDeliveryList', + method: 'get' + }) +} // 获取接单记录列表 export function getUserDataList(type) { return request({ diff --git a/ruoyi-ui/src/api/system/IntegralOrder.js b/ruoyi-ui/src/api/system/IntegralOrder.js index c9d0a8e..165fb1b 100644 --- a/ruoyi-ui/src/api/system/IntegralOrder.js +++ b/ruoyi-ui/src/api/system/IntegralOrder.js @@ -17,6 +17,32 @@ export function getIntegralOrder(id) { }) } + +export function getSiteDeliveryList() { + return request({ + url: '/system/SiteDelivery/getSiteDeliveryList', + method: 'get' + }) +} + + +// 获取接单记录列表 +export function getUserDataList(type) { + return request({ + url: '/system/transfer/getUsersDataList/'+type, + method: 'get' + }) +} + + +export function getIntegralProductList() { + return request({ + url: '/system/IntegralOrder/getIntegralProductList', + method: 'get' + }) +} + + // 新增积分订单 export function addIntegralOrder(data) { return request({ diff --git a/ruoyi-ui/src/views/system/Coupons/index.vue b/ruoyi-ui/src/views/system/Coupons/index.vue index c286551..b8507a8 100644 --- a/ruoyi-ui/src/views/system/Coupons/index.vue +++ b/ruoyi-ui/src/views/system/Coupons/index.vue @@ -244,7 +244,7 @@ class="record-button" > - {{ scope.row.couponUserList ? scope.row.couponUserList.length : 0 }}条记录 + {{ scope.row.lqjv}}条记录 @@ -544,9 +544,9 @@ diff --git a/ruoyi-ui/src/views/system/GoodsOrder/index.vue b/ruoyi-ui/src/views/system/GoodsOrder/index.vue index b4967df..a64a718 100644 --- a/ruoyi-ui/src/views/system/GoodsOrder/index.vue +++ b/ruoyi-ui/src/views/system/GoodsOrder/index.vue @@ -253,12 +253,67 @@ 待支付 已支付 - 待发货 - 待收货 + 发货 + 评价 已完成 +
+ +
+ + + @@ -291,7 +346,7 @@ @@ -592,4 +710,32 @@ export default { font-size: 16px !important; color: #222 !important; } + +/* 快递信息区域样式 */ +.delivery-section { + border: 1.5px solid #e6f7ff; + border-radius: 8px; + padding: 15px; + margin: 10px 0; + background-color: #f6ffed; +} + +.delivery-section .el-form-item__label { + color: #52c41a !important; + font-weight: bold; +} + +.delivery-section .el-input__inner, +.delivery-section .el-select .el-input__inner, +.delivery-section .el-date-editor .el-input__inner { + border: 1.5px solid #52c41a !important; + background-color: #fff !important; +} + +.delivery-section .el-input__inner:focus, +.delivery-section .el-select .el-input__inner:focus, +.delivery-section .el-date-editor .el-input__inner:focus { + border-color: #389e0d !important; + box-shadow: 0 0 0 2px rgba(82, 196, 26, 0.2) !important; +} diff --git a/ruoyi-ui/src/views/system/IntegralOrder/index.vue b/ruoyi-ui/src/views/system/IntegralOrder/index.vue index 9807dbb..469fce6 100644 --- a/ruoyi-ui/src/views/system/IntegralOrder/index.vue +++ b/ruoyi-ui/src/views/system/IntegralOrder/index.vue @@ -105,7 +105,7 @@ - + @@ -154,14 +154,14 @@ /> - - + + - + @@ -175,7 +175,7 @@ - + @@ -219,7 +219,20 @@ + + diff --git a/ruoyi-ui/src/views/system/Order/index.vue b/ruoyi-ui/src/views/system/Order/index.vue index 8162895..ba20faf 100644 --- a/ruoyi-ui/src/views/system/Order/index.vue +++ b/ruoyi-ui/src/views/system/Order/index.vue @@ -463,6 +463,19 @@ + + + + + 用户取消 + 师傅取消 + 系统取消 + 超时取消 + 其他原因 + + + + + + + + + + + + + + + + + + + { + if (this.form.jsonStatus >= 10 && this.form.jsonStatus <= 14) { + if (!value) { + callback(new Error('取消时间不能为空')) + } else { + callback() + } + } else { + callback() + } + }, + trigger: 'change' + } + ], + cancelReason: [ + { + validator: (rule, value, callback) => { + if (this.form.jsonStatus >= 10 && this.form.jsonStatus <= 14) { + if (!value || value.trim() === '') { + callback(new Error('取消原因不能为空')) + } else { + callback() + } + } else { + callback() + } + }, + trigger: 'blur' + } ] }, commentDialogVisible: false, // 评价详情对话框可见性 @@ -1156,6 +1367,9 @@ export default { doorFee: null, // 重置上门费 servicePhotos: [], // 重置服务照片 nextServiceTime: null, // 重置下次服务时间 + cancelTime: null, // 重置取消时间 + cancelReason: null, // 重置取消原因 + timeoutDuration: null, // 重置超时时长 } this.resetForm("form") }, @@ -1207,6 +1421,9 @@ export default { this.form.baseProject = [] // 初始化为空数组 this.form.pauseReason = null // 初始化暂停原因 this.form.nextServiceTime = null // 初始化下次服务时间 + this.form.cancelTime = null // 初始化取消时间 + this.form.cancelReason = null // 初始化取消原因 + this.form.timeoutDuration = null // 初始化超时时长 this.form.orderLog = { workerId: 1, workerName: null, @@ -1368,6 +1585,18 @@ export default { uid: Date.now() + index })) } + + // 解析取消订单数据(jsonStatus >= 10) + if (content.cancelType && this.form.jsonStatus >= 10 && this.form.jsonStatus <= 14) { + this.form.cancelTime = content.cancelTime || null; + this.form.cancelReason = content.cancelReason || null; + this.form.timeoutDuration = content.timeoutDuration || null; + + // 如果是师傅取消,设置师傅信息 + if (content.workerId) { + this.form.orderLog.workerId = content.workerId; + } + } } catch (e) { console.error('解析orderLog.content失败:', e) } @@ -1389,6 +1618,17 @@ export default { } } + // 已取消状态的额外验证和数据处理 + if (this.form.jsonStatus >= 10 && this.form.jsonStatus <= 14) { + const cancelValidation = this.validateCancelData() + if (!cancelValidation.valid) { + this.$modal.msgError(cancelValidation.message) + return + } + // 确保取消数据已正确封装 + this.handleCancelData() + } + // 创建提交数据的副本 const submitData = { ...this.form } @@ -1616,6 +1856,24 @@ export default { this.form.projectCost = null; this.form.doorFee = null; + // 如果是取消相关状态,设置默认取消时间为当前时间 + if (value >= 10 && value <= 14) { + if (!this.form.cancelTime) { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + this.form.cancelTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + } + // 立即触发数据封装 + this.$nextTick(() => { + this.handleCancelData(); + }); + } + // 根据新状态决定是否重置特定字段 if (value !== 6) { // 如果不是项目报价状态,重置报价相关字段 @@ -1643,6 +1901,13 @@ export default { this.form.completeTime = null; } + // 如果不是取消相关状态,重置取消相关字段 + if (value < 10 || value > 14) { + this.form.cancelTime = null; + this.form.cancelReason = null; + this.form.timeoutDuration = null; + } + // 只有在状态不是7、8、9时才重置服务照片 if (value !== 7 && value !== 8 && value !== 9) { this.form.servicePhotos = []; @@ -1995,8 +2260,92 @@ export default { return result }, - } -} + + /** + * 处理取消订单数据封装 + */ + handleCancelData() { + console.log('handleCancelData 被调用, jsonStatus:', this.form.jsonStatus); + console.log('当前取消时间:', this.form.cancelTime); + console.log('当前取消原因:', this.form.cancelReason); + + if (this.form.jsonStatus >= 10 && this.form.jsonStatus <= 14) { + // 根据不同的取消类型构建JSON数据 + let cancelData = { + cancelType: this.getCancelTypeName(this.form.jsonStatus), + cancelTime: this.form.cancelTime || "", + cancelReason: this.form.cancelReason || "" + }; + + // 师傅取消时添加师傅信息 + if (this.form.jsonStatus === 11 && this.form.orderLog.workerId) { + const selectedWorker = this.userGongRenList.find(worker => worker.id === this.form.orderLog.workerId); + if (selectedWorker) { + cancelData.workerId = this.form.orderLog.workerId; + cancelData.workerName = selectedWorker.name; + } + } + + // 超时取消时添加超时时长 + if (this.form.jsonStatus === 13 && this.form.timeoutDuration) { + cancelData.timeoutDuration = this.form.timeoutDuration; + } + + // 封装到orderLog.content中 + this.form.orderLog.content = JSON.stringify(cancelData); + console.log('最终生成的取消订单数据JSON:', this.form.orderLog.content); + console.log('cancelData对象:', cancelData); + } else { + console.log('jsonStatus不在取消状态范围内,跳过处理'); + } + }, + + /** + * 获取取消类型名称 + */ + getCancelTypeName(jsonStatus) { + const typeMap = { + 10: "用户取消", + 11: "师傅取消", + 12: "系统取消", + 13: "超时取消", + 14: "其他原因" + }; + return typeMap[jsonStatus] || "未知取消"; + }, + + /** + * 验证取消数据的完整性 + * @returns {Object} 验证结果对象 + */ + validateCancelData() { + const result = { valid: true, message: '' } + + // 验证取消时间 + if (!this.form.cancelTime) { + result.valid = false + result.message = '取消时间不能为空' + return result + } + + // 验证取消原因 + if (!this.form.cancelReason || this.form.cancelReason.trim() === '') { + result.valid = false + result.message = '取消原因不能为空' + return result + } + + // 师傅取消时验证师傅选择 + if (this.form.jsonStatus === 11 && !this.form.orderLog.workerId) { + result.valid = false + result.message = '师傅取消时必须选择师傅' + return result + } + + return result + }, + } + }