202506061147

This commit is contained in:
张潘 2025-06-06 11:47:40 +08:00
parent eb0298bb00
commit 687ec12974
22 changed files with 2397 additions and 57 deletions

View File

@ -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;
}
}

View File

@ -1,5 +1,7 @@
package com.ruoyi.system.controller; package com.ruoyi.system.controller;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -7,6 +9,7 @@ import com.ruoyi.system.domain.*;
import com.ruoyi.system.service.*; import com.ruoyi.system.service.*;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; 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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@ -117,10 +120,183 @@ public class CouponsController extends BaseController
@PreAuthorize("@ss.hasPermi('system:Coupons:add')") @PreAuthorize("@ss.hasPermi('system:Coupons:add')")
@Log(title = "优惠券", businessType = BusinessType.INSERT) @Log(title = "优惠券", businessType = BusinessType.INSERT)
@PostMapping @PostMapping
public AjaxResult add(@RequestBody Coupons coupons) @Transactional(rollbackFor = Exception.class)
{ public AjaxResult add(@RequestBody Coupons coupons) {
return toAjax(couponsService.insertCoupons(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<Long> validUserIds = new ArrayList<>();
try {
// 首先尝试JSON数组格式解析
if (userids.trim().startsWith("[") && userids.trim().endsWith("]")) {
// JSON数组格式: [1,2,3] ["1","2","3"]
List<Object> 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<Users> 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());
}
}
/** /**
* 修改优惠券 * 修改优惠券
@ -128,9 +304,195 @@ public class CouponsController extends BaseController
@PreAuthorize("@ss.hasPermi('system:Coupons:edit')") @PreAuthorize("@ss.hasPermi('system:Coupons:edit')")
@Log(title = "优惠券", businessType = BusinessType.UPDATE) @Log(title = "优惠券", businessType = BusinessType.UPDATE)
@PutMapping @PutMapping
public AjaxResult edit(@RequestBody Coupons coupons) @Transactional(rollbackFor = Exception.class)
{ public AjaxResult edit(@RequestBody Coupons coupons) {
return toAjax(couponsService.updateCoupons(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<Long> validUserIds = new ArrayList<>();
try {
// 首先尝试JSON数组格式解析
if (userids.trim().startsWith("[") && userids.trim().endsWith("]")) {
// JSON数组格式: [1,2,3] ["1","2","3"]
List<Object> 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<Users> 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());
}
} }
/** /**
* 定时任务状态修改 * 定时任务状态修改

View File

@ -2,6 +2,10 @@ package com.ruoyi.system.controller;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; 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.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; 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.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.IntegralOrder; import com.ruoyi.system.domain.IntegralOrder;
import com.ruoyi.system.service.IIntegralOrderService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo; 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.common.core.domain.entity.SysUser;
import com.ruoyi.system.domain.ServiceGoods; import com.ruoyi.system.domain.ServiceGoods;
@ -41,8 +42,11 @@ public class IntegralOrderController extends BaseController
@Autowired @Autowired
private ISysUserService sysUserService; private ISysUserService sysUserService;
@Autowired @Autowired
private IUsersService usersService;
@Autowired
private IServiceGoodsService serviceGoodsService; private IServiceGoodsService serviceGoodsService;
@Autowired
private IIntegralProductService integralProductService;
/** /**
* 查询积分订单列表 * 查询积分订单列表
*/ */
@ -52,9 +56,27 @@ public class IntegralOrderController extends BaseController
{ {
startPage(); startPage();
List<IntegralOrder> list = integralOrderService.selectIntegralOrderList(integralOrder); List<IntegralOrder> list = integralOrderService.selectIntegralOrderList(integralOrder);
for(IntegralOrder data:list){
Users users = usersService.selectUsersById(data.getUid());
if (users!=null){
data.setUname(users.getName());
}
}
return getDataTable(list); return getDataTable(list);
} }
/**
* 获取积分商品下拉选择数据
*/
@PreAuthorize("@ss.hasPermi('system:IntegralOrder:query')")
@GetMapping(value = "/getIntegralProductList")
public AjaxResult getIntegralProductList()
{
return success(integralProductService.selectIntegralProductList(new IntegralProduct()));
}
/** /**
* 导出积分订单列表 * 导出积分订单列表
*/ */

View File

@ -4,8 +4,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletResponse; 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.domain.*;
import com.ruoyi.system.service.*; import com.ruoyi.system.service.*;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;

View File

@ -61,6 +61,18 @@ public class SiteDeliveryController extends BaseController
util.exportExcel(response, list, "快递公司数据"); 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)); return success(siteDeliveryService.selectSiteDeliveryById(id));
} }
/** /**
* 定时任务状态修改 * 定时任务状态修改
*/ */

View File

@ -1,4 +1,4 @@
package com.ruoyi.system.controllerUtil; package com.ruoyi.system.ControllerUtil;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;

View File

@ -1,4 +1,4 @@
package com.ruoyi.system.controllerUtil; package com.ruoyi.system.ControllerUtil;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.Order; import com.ruoyi.system.domain.Order;

View File

@ -1,4 +1,4 @@
package com.ruoyi.system.controllerUtil; package com.ruoyi.system.ControllerUtil;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;

View File

@ -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<String, ScheduledTaskUtil.TaskStatistics> 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<String, Object> 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的实现
* - 如果需要数据库操作需要在相应方法中添加具体实现
* - 可根据实际业务需求调整各种超时时间和处理逻辑
* - 建议在生产环境中根据服务器性能调整线程池参数
*
*/

View File

@ -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<String, TaskStatistics> 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<Order> 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<Order> 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<Order> 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<String, TaskStatistics> 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<String, Object> getThreadPoolStatus() {
Map<String, Object> 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")) : "未执行"
);
}
}
}

View File

@ -1,4 +1,4 @@
package com.ruoyi.system.controllerUtil; package com.ruoyi.system.ControllerUtil;
public class VerificationResult { public class VerificationResult {

View File

@ -28,6 +28,10 @@ public class IntegralOrder extends BaseEntity
@Excel(name = "用户") @Excel(name = "用户")
private Long uid; private Long uid;
/** 用户姓名 */
@Excel(name = "用户姓名")
private String uname;
/** 姓名 */ /** 姓名 */
@Excel(name = "姓名") @Excel(name = "姓名")
private String userName; private String userName;
@ -254,6 +258,14 @@ public class IntegralOrder extends BaseEntity
return updatedAt; return updatedAt;
} }
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@ -55,6 +55,14 @@ public interface CouponUserMapper
*/ */
public int deleteCouponUserById(Long id); public int deleteCouponUserById(Long id);
/**
* 批量删除优惠券领取记录
*
* @param couponId
* @return 结果
*/
public int deleteCouponUserBycouponId (Long couponId);
/** /**
* 批量删除优惠券领取记录 * 批量删除优惠券领取记录
* *

View File

@ -19,7 +19,13 @@ public interface ICouponUserService
*/ */
public CouponUser selectCouponUserById(Long id); public CouponUser selectCouponUserById(Long id);
/**
* 批量删除优惠券领取记录
*
* @param couponId
* @return 结果
*/
public int deleteCouponUserBycouponId (Long couponId);
public int selectCountCouponUserbycouponId(Long couponId); public int selectCountCouponUserbycouponId(Long couponId);
/** /**

View File

@ -31,7 +31,16 @@ public class CouponUserServiceImpl implements ICouponUserService
return couponUserMapper.selectCouponUserById(id); return couponUserMapper.selectCouponUserById(id);
} }
/**
* 批量删除优惠券领取记录
*
* @param couponId
* @return 结果
*/
public int deleteCouponUserBycouponId (Long couponId) {
return couponUserMapper.deleteCouponUserBycouponId(couponId);
}
public int selectCountCouponUserbycouponId(Long couponId) public int selectCountCouponUserbycouponId(Long couponId)
{ {

View File

@ -119,6 +119,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<delete id="deleteCouponUserById" parameterType="Long"> <delete id="deleteCouponUserById" parameterType="Long">
delete from coupon_user where id = #{id} delete from coupon_user where id = #{id}
</delete> </delete>
<delete id="deleteCouponUserBycouponId" parameterType="Long">
delete from coupon_user where coupon_id = #{couponId}
</delete>
<delete id="deleteCouponUserByIds" parameterType="String"> <delete id="deleteCouponUserByIds" parameterType="String">
delete from coupon_user where id in delete from coupon_user where id in

View File

@ -22,6 +22,13 @@ export function getGoodsDataList() {
method: 'get' method: 'get'
}) })
} }
export function getSiteDeliveryList() {
return request({
url: '/system/SiteDelivery/getSiteDeliveryList',
method: 'get'
})
}
// 获取接单记录列表 // 获取接单记录列表
export function getUserDataList(type) { export function getUserDataList(type) {
return request({ return request({

View File

@ -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) { export function addIntegralOrder(data) {
return request({ return request({

View File

@ -244,7 +244,7 @@
class="record-button" class="record-button"
> >
<i class="el-icon-document"></i> <i class="el-icon-document"></i>
{{ scope.row.couponUserList ? scope.row.couponUserList.length : 0 }}条记录 {{ scope.row.lqjv}}条记录
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -544,9 +544,9 @@
</el-table-column> </el-table-column>
<el-table-column label="状态" prop="status" align="center"> <el-table-column label="状态" prop="status" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.status === '1'" type="warning">未使用</el-tag> <el-tag v-if="scope.row.status === 1" type="warning">未使用</el-tag>
<el-tag type="success" v-else-if="scope.row.status === '2'">已使用</el-tag> <el-tag type="success" v-else-if="scope.row.status === 2">已使用</el-tag>
<el-tag type="info" v-else>已失效</el-tag>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -253,12 +253,67 @@
<el-radio-group v-model="form.status"> <el-radio-group v-model="form.status">
<el-radio :label="1">待支付</el-radio> <el-radio :label="1">待支付</el-radio>
<el-radio :label="2">已支付</el-radio> <el-radio :label="2">已支付</el-radio>
<el-radio :label="3">发货</el-radio> <el-radio :label="3">发货</el-radio>
<el-radio :label="4">待收货</el-radio> <el-radio :label="4">评价</el-radio>
<el-radio :label="5">已完成</el-radio> <el-radio :label="5">已完成</el-radio>
</el-radio-group> </el-radio-group>
<div v-if="form.status === 3" style="margin-top: 8px;">
<el-alert
title="📦 选择发货状态后,请填写快递信息"
type="info"
:closable="false"
show-icon
style="font-size: 14px;"
/>
</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 当选择发货状态时显示的快递信息 -->
<template v-if="form.status === 3">
<el-col :span="24">
<div class="delivery-section">
<h4 style="margin: 0 0 15px 0; color: #52c41a; font-size: 16px;">📦 快递信息</h4>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="快递" prop="deliveryId">
<el-select
v-model="form.deliveryId"
placeholder="请选择快递公司"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in deliveryList"
:key="item.id"
:label="item.name || item.title"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="快递单号" prop="deliveryNum">
<el-input v-model="form.deliveryNum" placeholder="请输入快递单号" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="发货时间" prop="sendTime">
<el-date-picker
clearable
v-model="form.sendTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择发货时间"
style="width:100%"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
</template>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="关联地址" prop="addressId"> <el-form-item label="关联地址" prop="addressId">
<el-input v-model="form.addressId" placeholder="请输入关联地址" /> <el-input v-model="form.addressId" placeholder="请输入关联地址" />
@ -291,7 +346,7 @@
</template> </template>
<script> <script>
import { listGoodsOrder, getGoodsOrder, delGoodsOrder, addGoodsOrder, updateGoodsOrder, getUserDataList, getGoodsDataList } from "@/api/system/GoodsOrder" import { listGoodsOrder, getGoodsOrder, delGoodsOrder, addGoodsOrder, updateGoodsOrder, getUserDataList, getGoodsDataList ,getSiteDeliveryList} from "@/api/system/GoodsOrder"
export default { export default {
name: "GoodsOrder", name: "GoodsOrder",
@ -317,6 +372,8 @@ export default {
// //
goodsDataList : [], goodsDataList : [],
//
deliveryList: [],
// //
title: "", title: "",
// //
@ -401,6 +458,42 @@ export default {
addressId: [ addressId: [
{ required: true, message: "关联地址不能为空", trigger: "blur" } { required: true, message: "关联地址不能为空", trigger: "blur" }
], ],
deliveryId: [
{
validator: (rule, value, callback) => {
if (this.form.status === 3 && (!value || value === '')) {
callback(new Error('请选择快递公司'));
} else {
callback();
}
},
trigger: "change"
}
],
deliveryNum: [
{
validator: (rule, value, callback) => {
if (this.form.status === 3 && (!value || value.trim() === '')) {
callback(new Error('快递单号不能为空'));
} else {
callback();
}
},
trigger: "blur"
}
],
sendTime: [
{
validator: (rule, value, callback) => {
if (this.form.status === 3 && (!value || value === '')) {
callback(new Error('请选择发货时间'));
} else {
callback();
}
},
trigger: "change"
}
],
}, },
daterangePayTime: [], daterangePayTime: [],
daterangeCreatedAt: [], daterangeCreatedAt: [],
@ -410,6 +503,8 @@ export default {
this.getList(); this.getList();
this.fetchUserDataList(); this.fetchUserDataList();
this.fetchGoodsDataList(); this.fetchGoodsDataList();
this.getdeliveryList();
}, },
methods: { methods: {
/** 查询商品订单列表 */ /** 查询商品订单列表 */
@ -562,6 +657,29 @@ export default {
this.goodsDataList = response.data; this.goodsDataList = response.data;
}); });
}, },
getdeliveryList(){
getSiteDeliveryList().then(response => {
this.deliveryList = response.data;
});
}
},
watch: {
'form.status'(newVal, oldVal) {
//
if (oldVal === 3 && newVal !== 3) {
this.form.deliveryId = null;
this.form.deliveryNum = null;
this.form.sendTime = null;
//
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate(['deliveryId', 'deliveryNum', 'sendTime']);
}
});
}
}
} }
} }
</script> </script>
@ -592,4 +710,32 @@ export default {
font-size: 16px !important; font-size: 16px !important;
color: #222 !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;
}
</style> </style>

View File

@ -105,7 +105,7 @@
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" /> <el-table-column label="ID" align="center" prop="id" />
<el-table-column label="订单号" align="center" prop="orderId" /> <el-table-column label="订单号" align="center" prop="orderId" />
<el-table-column label="用户" align="center" prop="uid" /> <el-table-column label="用户" align="center" prop="uname" />
<el-table-column label="姓名" align="center" prop="userName" /> <el-table-column label="姓名" align="center" prop="userName" />
<el-table-column label="电话" align="center" prop="userPhone" /> <el-table-column label="电话" align="center" prop="userPhone" />
<el-table-column label="地址" align="center" prop="userAddress" /> <el-table-column label="地址" align="center" prop="userAddress" />
@ -154,14 +154,14 @@
/> />
<!-- 添加或修改积分订单对话框 --> <!-- 添加或修改积分订单对话框 -->
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body class="custom-dialog">
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form ref="form" :model="form" :rules="rules" label-width="80px" class="custom-form">
<el-form-item label="订单号" prop="orderId"> <el-form-item label="订单号" prop="orderId">
<el-input v-model="form.orderId" placeholder="请输入订单号" /> <el-input v-model="form.orderId" placeholder="请输入订单号" />
</el-form-item> </el-form-item>
<el-form-item label="用户" prop="uid"> <el-form-item label="用户" prop="uid">
<el-select v-model="form.uid" placeholder="请选择用户" clearable filterable style="width: 100%"> <el-select v-model="form.uid" placeholder="请选择用户" clearable filterable style="width: 100%">
<el-option v-for="item in users" :key="item.userId" :label="item.userName" :value="item.userId" /> <el-option v-for="item in userDataList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="userName"> <el-form-item label="姓名" prop="userName">
@ -175,7 +175,7 @@
</el-form-item> </el-form-item>
<el-form-item label="商品" prop="productId"> <el-form-item label="商品" prop="productId">
<el-select v-model="form.productId" placeholder="请选择商品" clearable filterable style="width: 100%"> <el-select v-model="form.productId" placeholder="请选择商品" clearable filterable style="width: 100%">
<el-option v-for="item in serviceGoods" :key="item.id" :label="item.title" :value="item.id" /> <el-option v-for="item in integralProductList" :key="item.id" :label="item.title" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="数量" prop="num"> <el-form-item label="数量" prop="num">
@ -219,7 +219,20 @@
<!-- 当选择发货状态时显示的字段 --> <!-- 当选择发货状态时显示的字段 -->
<template v-if="form.status === 2"> <template v-if="form.status === 2">
<el-form-item label="快递" prop="deliveryId"> <el-form-item label="快递" prop="deliveryId">
<el-input v-model="form.deliveryId" placeholder="请输入快递公司" /> <el-select
v-model="form.deliveryId"
placeholder="请选择快递公司"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in siteDeliveryList"
:key="item.id"
:label="item.name || item.title || item.label"
:value="item.id || item.value"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="单号" prop="deliveryNum"> <el-form-item label="单号" prop="deliveryNum">
<el-input v-model="form.deliveryNum" placeholder="请输入快递单号" /> <el-input v-model="form.deliveryNum" placeholder="请输入快递单号" />
@ -243,7 +256,7 @@
</template> </template>
<script> <script>
import { listIntegralOrder, getIntegralOrder, delIntegralOrder, addIntegralOrder, updateIntegralOrder } from "@/api/system/IntegralOrder" import { listIntegralOrder, getIntegralOrder, delIntegralOrder, addIntegralOrder, updateIntegralOrder,getUserDataList,getIntegralProductList,getSiteDeliveryList } from "@/api/system/IntegralOrder"
import request from "@/utils/request" import request from "@/utils/request"
export default { export default {
@ -262,6 +275,12 @@ export default {
showSearch: true, showSearch: true,
// //
total: 0, total: 0,
siteDeliveryList: [],
integralProductList: [],
userDataList: [],
// //
IntegralOrderList: [], IntegralOrderList: [],
// //
@ -318,13 +337,13 @@ export default {
deliveryId: [ deliveryId: [
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (this.form.status === 2 && (!value || value.trim() === '')) { if (this.form.status === 2 && (!value || value === '')) {
callback(new Error('快递公司不能为空')); callback(new Error('请选择快递公司'));
} else { } else {
callback(); callback();
} }
}, },
trigger: "blur" trigger: "change"
} }
], ],
deliveryNum: [ deliveryNum: [
@ -348,6 +367,9 @@ export default {
created() { created() {
this.getList() this.getList()
this.getOptions() this.getOptions()
this.getuserDataList()
this.getIntegralProductList()
this.getSiteDeliveryList()
}, },
methods: { methods: {
getOptions() { getOptions() {
@ -407,6 +429,27 @@ export default {
this.single = selection.length!==1 this.single = selection.length!==1
this.multiple = !selection.length this.multiple = !selection.length
}, },
getIntegralProductList(){
getIntegralProductList().then(res => {
this.integralProductList = res.data
})
},
getSiteDeliveryList(){
getSiteDeliveryList().then(res => {
this.siteDeliveryList = res.data
})
},
getuserDataList(){
getUserDataList(1).then(res => {
this.userDataList = res.data
})
},
/** 新增按钮操作 */ /** 新增按钮操作 */
handleAdd() { handleAdd() {
this.reset() this.reset()
@ -462,3 +505,169 @@ export default {
} }
} }
</script> </script>
<style scoped>
/* 自定义对话框样式 */
.custom-dialog {
border-radius: 8px;
}
/* 表单美化 */
.custom-form {
padding: 10px 20px;
}
/* 输入框样式美化 */
.custom-form .el-input__inner {
border: 1.5px solid #d9d9d9 !important;
border-radius: 6px !important;
background-color: #ffffff !important;
transition: all 0.3s ease !important;
font-size: 14px !important;
padding: 12px 15px !important;
height: 40px !important;
line-height: 16px !important;
}
/* 输入框获得焦点时的样式 */
.custom-form .el-input__inner:focus {
border-color: #409eff !important;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
outline: none !important;
}
/* 输入框悬停时的样式 */
.custom-form .el-input__inner:hover {
border-color: #c0c4cc !important;
}
/* 数字输入框样式 */
.custom-form .el-input-number .el-input__inner {
border: 1.5px solid #d9d9d9 !important;
border-radius: 6px !important;
background-color: #ffffff !important;
transition: all 0.3s ease !important;
padding: 12px 15px !important;
height: 40px !important;
line-height: 16px !important;
}
.custom-form .el-input-number .el-input__inner:focus {
border-color: #409eff !important;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
}
.custom-form .el-input-number .el-input__inner:hover {
border-color: #c0c4cc !important;
}
/* 下拉选择框样式 */
.custom-form .el-select .el-input__inner {
border: 1.5px solid #d9d9d9 !important;
border-radius: 6px !important;
background-color: #ffffff !important;
transition: all 0.3s ease !important;
padding: 12px 30px 12px 15px !important;
height: 40px !important;
line-height: 16px !important;
}
.custom-form .el-select .el-input__inner:focus {
border-color: #409eff !important;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
}
.custom-form .el-select:hover .el-input__inner {
border-color: #c0c4cc !important;
}
/* 文本域样式 */
.custom-form .el-textarea__inner {
border: 1.5px solid #d9d9d9 !important;
border-radius: 6px !important;
background-color: #ffffff !important;
transition: all 0.3s ease !important;
font-size: 14px !important;
padding: 12px 15px !important;
line-height: 1.5 !important;
}
.custom-form .el-textarea__inner:focus {
border-color: #409eff !important;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
outline: none !important;
}
.custom-form .el-textarea:hover .el-textarea__inner {
border-color: #c0c4cc !important;
}
/* 单选按钮样式 */
.custom-form .el-radio-group {
display: flex;
align-items: center;
gap: 20px;
}
.custom-form .el-radio {
margin-right: 0;
padding: 8px 16px;
border: 1.5px solid #e4e7ed;
border-radius: 6px;
background-color: #ffffff;
transition: all 0.3s ease;
}
.custom-form .el-radio:hover {
border-color: #409eff;
background-color: #f0f9ff;
}
.custom-form .el-radio.is-checked {
border-color: #409eff;
background-color: #ecf5ff;
color: #409eff;
}
/* 表单项间距 */
.custom-form .el-form-item {
margin-bottom: 20px;
}
/* 表单标签样式 */
.custom-form .el-form-item__label {
font-weight: 500;
color: #303133;
font-size: 14px;
}
/* 错误提示样式 */
.custom-form .el-form-item__error {
font-size: 12px;
color: #f56c6c;
padding-top: 4px;
}
/* 禁用状态的输入框 */
.custom-form .el-input.is-disabled .el-input__inner {
background-color: #f5f7fa !important;
border-color: #e4e7ed !important;
color: #c0c4cc !important;
cursor: not-allowed !important;
}
/* 数字输入框按钮样式 */
.custom-form .el-input-number__increase,
.custom-form .el-input-number__decrease {
border: none !important;
background-color: #f5f7fa !important;
color: #606266 !important;
transition: all 0.3s ease !important;
}
.custom-form .el-input-number__increase:hover,
.custom-form .el-input-number__decrease:hover {
background-color: #409eff !important;
color: #ffffff !important;
}
</style>

View File

@ -463,6 +463,19 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 服务进度选项 - 已取消状态 -->
<el-col :span="24" v-if="form.status === 5">
<el-form-item label="取消原因" prop="jsonStatus">
<el-radio-group v-model="form.jsonStatus" @change="handlejsonStatusChange">
<el-radio :label="10">用户取消</el-radio>
<el-radio :label="11">师傅取消</el-radio>
<el-radio :label="12">系统取消</el-radio>
<el-radio :label="13">超时取消</el-radio>
<el-radio :label="14">其他原因</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 设置上门费内容 --> <!-- 设置上门费内容 -->
<template v-if="form.jsonStatus === 3"> <template v-if="form.jsonStatus === 3">
<el-col :span="24"> <el-col :span="24">
@ -738,6 +751,169 @@
</el-col> </el-col>
</template> </template>
<!-- 取消订单内容 - 用户取消 -->
<template v-if="form.jsonStatus === 10">
<el-col :span="24">
<el-form-item label="取消时间" prop="cancelTime">
<el-date-picker
v-model="form.cancelTime"
type="datetime"
placeholder="选择取消时间"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
@change="handleCancelData"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="取消原因" prop="cancelReason">
<el-select v-model="form.cancelReason" placeholder="请选择取消原因" @change="handleCancelData">
<el-option label="不需要服务了" value="不需要服务了" />
<el-option label="服务时间不合适" value="服务时间不合适" />
<el-option label="价格问题" value="价格问题" />
<el-option label="找到其他师傅" value="找到其他师傅" />
<el-option label="其他原因" value="其他原因" />
</el-select>
</el-form-item>
</el-col>
</template>
<!-- 取消订单内容 - 师傅取消 -->
<template v-if="form.jsonStatus === 11">
<el-col :span="24">
<el-form-item label="取消时间" prop="cancelTime">
<el-date-picker
v-model="form.cancelTime"
type="datetime"
placeholder="选择取消时间"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
@change="handleCancelData"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="师傅" prop="orderLog.workerId">
<el-select v-model="form.orderLog.workerId" placeholder="请选择师傅" clearable filterable style="width: 100%" @change="handleCancelData">
<el-option
v-for="item in userGongRenList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="取消原因" prop="cancelReason">
<el-select v-model="form.cancelReason" placeholder="请选择取消原因" @change="handleCancelData">
<el-option label="时间冲突" value="时间冲突" />
<el-option label="服务范围外" value="服务范围外" />
<el-option label="技术问题无法解决" value="技术问题无法解决" />
<el-option label="客户要求过高" value="客户要求过高" />
<el-option label="其他原因" value="其他原因" />
</el-select>
</el-form-item>
</el-col>
</template>
<!-- 取消订单内容 - 系统取消 -->
<template v-if="form.jsonStatus === 12">
<el-col :span="24">
<el-form-item label="取消时间" prop="cancelTime">
<el-date-picker
v-model="form.cancelTime"
type="datetime"
placeholder="选择取消时间"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
@change="handleCancelData"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="系统取消原因" prop="cancelReason">
<el-select v-model="form.cancelReason" placeholder="请选择系统取消原因" @change="handleCancelData">
<el-option label="支付超时" value="支付超时" />
<el-option label="订单异常" value="订单异常" />
<el-option label="系统维护" value="系统维护" />
<el-option label="风控拦截" value="风控拦截" />
<el-option label="其他系统原因" value="其他系统原因" />
</el-select>
</el-form-item>
</el-col>
</template>
<!-- 取消订单内容 - 超时取消 -->
<template v-if="form.jsonStatus === 13">
<el-col :span="24">
<el-form-item label="取消时间" prop="cancelTime">
<el-date-picker
v-model="form.cancelTime"
type="datetime"
placeholder="选择取消时间"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
@change="handleCancelData"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="超时类型" prop="cancelReason">
<el-select v-model="form.cancelReason" placeholder="请选择超时类型" @change="handleCancelData">
<el-option label="接单超时" value="接单超时" />
<el-option label="服务超时" value="服务超时" />
<el-option label="支付超时" value="支付超时" />
<el-option label="响应超时" value="响应超时" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="超时时长" prop="timeoutDuration">
<el-input v-model="form.timeoutDuration" placeholder="请输入超时时长(分钟)" type="number" @change="handleCancelData">
<template slot="append">分钟</template>
</el-input>
</el-form-item>
</el-col>
</template>
<!-- 取消订单内容 - 其他原因 -->
<template v-if="form.jsonStatus === 14">
<el-col :span="24">
<el-form-item label="取消时间" prop="cancelTime">
<el-date-picker
v-model="form.cancelTime"
type="datetime"
placeholder="选择取消时间"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
@change="handleCancelData"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="详细原因" prop="cancelReason">
<el-input
v-model="form.cancelReason"
type="textarea"
placeholder="请输入具体的取消原因"
:rows="3"
@change="handleCancelData"
/>
</el-form-item>
</el-col>
</template>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="备注" prop="mark"> <el-form-item label="备注" prop="mark">
<el-input <el-input
@ -947,6 +1123,9 @@ export default {
doorFee: null, // doorFee: null, //
servicePhotos: [], // servicePhotos: [], //
nextServiceTime: null, // nextServiceTime: null, //
cancelTime: null, //
cancelReason: null, //
timeoutDuration: null, //
orderLog:{ orderLog:{
workerId:1, workerId:1,
workerName:null, workerName:null,
@ -1059,6 +1238,38 @@ export default {
}, },
trigger: 'change' trigger: 'change'
} }
],
cancelTime: [
{
validator: (rule, value, callback) => {
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, // commentDialogVisible: false, //
@ -1156,6 +1367,9 @@ export default {
doorFee: null, // doorFee: null, //
servicePhotos: [], // servicePhotos: [], //
nextServiceTime: null, // nextServiceTime: null, //
cancelTime: null, //
cancelReason: null, //
timeoutDuration: null, //
} }
this.resetForm("form") this.resetForm("form")
}, },
@ -1207,6 +1421,9 @@ export default {
this.form.baseProject = [] // this.form.baseProject = [] //
this.form.pauseReason = null // this.form.pauseReason = null //
this.form.nextServiceTime = null // this.form.nextServiceTime = null //
this.form.cancelTime = null //
this.form.cancelReason = null //
this.form.timeoutDuration = null //
this.form.orderLog = { this.form.orderLog = {
workerId: 1, workerId: 1,
workerName: null, workerName: null,
@ -1368,6 +1585,18 @@ export default {
uid: Date.now() + index 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) { } catch (e) {
console.error('解析orderLog.content失败:', 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 } const submitData = { ...this.form }
@ -1616,6 +1856,24 @@ export default {
this.form.projectCost = null; this.form.projectCost = null;
this.form.doorFee = 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) { if (value !== 6) {
// //
@ -1643,6 +1901,13 @@ export default {
this.form.completeTime = null; this.form.completeTime = null;
} }
//
if (value < 10 || value > 14) {
this.form.cancelTime = null;
this.form.cancelReason = null;
this.form.timeoutDuration = null;
}
// 789 // 789
if (value !== 7 && value !== 8 && value !== 9) { if (value !== 7 && value !== 8 && value !== 9) {
this.form.servicePhotos = []; this.form.servicePhotos = [];
@ -1995,6 +2260,90 @@ export default {
return result 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
},
} }
} }
</script> </script>