202507251746

This commit is contained in:
张潘 2025-08-02 17:45:32 +08:00
parent b0653ae8ec
commit cf3eb835b4
10 changed files with 3586 additions and 0 deletions

View File

@ -0,0 +1,680 @@
package com.ruoyi.system.ControllerUtil;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.IntegralLog;
import com.ruoyi.system.domain.SiteConfig;
import com.ruoyi.system.domain.UserBenefitPoints;
import com.ruoyi.system.domain.Users;
import com.ruoyi.system.domain.UsersPayBefor;
import com.ruoyi.system.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
/**
* 积分及购物消费金处理工具类
*
* @author ruoyi
* @date 2025-01-27
*/
@Component
public class IntegralAndBenefitUtil {
private static final Logger logger = LoggerFactory.getLogger(IntegralAndBenefitUtil.class);
// 注入相关服务
private static final ISiteConfigService siteConfigService = SpringUtils.getBean(ISiteConfigService.class);
private static final IUsersService usersService = SpringUtils.getBean(IUsersService.class);
private static final IIntegralLogService integralLogService = SpringUtils.getBean(IIntegralLogService.class);
private static final IUserBenefitPointsService userBenefitPointsService = SpringUtils.getBean(IUserBenefitPointsService.class);
private static final IUsersPayBeforService usersPayBeforService = SpringUtils.getBean(IUsersPayBeforService.class);
/**
* 积分及购物消费金处理
*
* @param money 订单金额
* @param orderid 订单ID
* @return 处理结果
*/
public static JSONObject processIntegralAndBenefit(BigDecimal money, String orderid, Long uid,Long befoid) {
JSONObject result = new JSONObject();
try {
UsersPayBefor usersPayBefor = usersPayBeforService.selectUsersPayBeforById(befoid);
Users users = usersService.selectUsersById(uid);
// 1. 查询配置信息
SiteConfig config = siteConfigService.selectSiteConfigByName("config_one");
if (config == null || config.getValue() == null) {
logger.error("未找到config_one配置信息");
result.put("success", false);
result.put("message", "系统配置信息缺失");
return result;
}
// 解析配置信息
JSONObject configJson = JSONObject.parseObject(config.getValue());
BigDecimal orderScore = configJson.getBigDecimal("orderScore");
BigDecimal servicefee = configJson.getBigDecimal("servicefee");
BigDecimal consumption = configJson.getBigDecimal("consumption");
if (orderScore == null || servicefee == null || consumption == null) {
logger.error("配置信息不完整orderScore: {}, servicefee: {}, consumption: {}",
orderScore, servicefee, consumption);
result.put("success", false);
result.put("message", "系统配置信息不完整");
return result;
}
// 2. 处理用户积分
Long integralPoints = processUserIntegral(money, orderScore, orderid, users);
// 3. 处理消费金和服务金
boolean benefitResult = processUserBenefit(money, Math.toIntExact(usersPayBefor.getServicetype()), servicefee, consumption, orderid, users);
if (integralPoints > 0 && benefitResult) {
result.put("success", true);
result.put("message", "积分和消费金处理成功");
result.put("integralPoints", integralPoints);
} else {
result.put("success", false);
result.put("message", "积分或消费金处理失败");
}
} catch (Exception e) {
logger.error("积分及购物消费金处理异常", e);
result.put("success", false);
result.put("message", "处理过程中发生异常:" + e.getMessage());
}
return result;
}
/**
* 处理用户积分
*
* @param money 订单金额
* @param orderScore 积分兑换比例
* @param orderid 订单ID
* @param users 用户对象
* @return 获得的积分数量
*/
private static Long processUserIntegral(BigDecimal money, BigDecimal orderScore, String orderid, Users users) {
try {
// 计算积分money / orderScore向下取整
BigDecimal integralDecimal = money.divide(orderScore, 0, RoundingMode.DOWN);
Long integralPoints = integralDecimal.longValue();
if (integralPoints > 0) {
// 更新用户积分
Long currentIntegral = users.getIntegral() != null ? users.getIntegral() : 0L;
Long currentTotalIntegral = users.getTotalIntegral() != null ? users.getTotalIntegral() : 0L;
users.setIntegral(currentIntegral + integralPoints);
users.setTotalIntegral(currentTotalIntegral + integralPoints);
// 更新用户信息
int updateResult = usersService.updateUsers(users);
if (updateResult > 0) {
// 添加积分日志
IntegralLog integralLog = new IntegralLog();
integralLog.setOrderId(orderid);
integralLog.setTitle("订单消费获得积分");
integralLog.setMark("订单消费" + money + "元,获得积分" + integralPoints + "");
integralLog.setUid(users.getId());
integralLog.setUname(users.getName());
integralLog.setType(1L); // 1增加
integralLog.setNum(integralPoints);
integralLog.setCreatedAt(new Date());
integralLog.setUpdatedAt(new Date());
integralLogService.insertIntegralLog(integralLog);
logger.info("用户{}积分处理成功,获得积分{}点", users.getId(), integralPoints);
return integralPoints;
} else {
logger.error("更新用户积分失败用户ID: {}", users.getId());
return 0L;
}
} else {
logger.info("订单金额{}元,积分兑换比例{},计算积分{}点,不进行积分处理",
money, orderScore, integralPoints);
return 0L;
}
} catch (Exception e) {
logger.error("处理用户积分异常", e);
return 0L;
}
}
/**
* 处理用户消费金和服务金
*
* @param money 订单金额
* @param type 类型1-服务2-商城
* @param servicefee 服务金比例
* @param consumption 消费金比例
* @param orderid 订单ID
* @param users 用户对象
* @return 处理结果
*/
private static boolean processUserBenefit(BigDecimal money, Integer type, BigDecimal servicefee,
BigDecimal consumption, String orderid, Users users) {
try {
BigDecimal benefitAmount = BigDecimal.ZERO;
Long benefitType = 0L;
String benefitName = "";
Integer intype;
if (type == 1) {
intype=2;
// 服务类型增加消费金
benefitAmount = money.multiply(consumption).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
benefitType = 1L; // 消费金
benefitName = "服务消费金";
// 更新用户消费金
BigDecimal currentConsumption = users.getConsumption() != null ? users.getConsumption() : BigDecimal.ZERO;
users.setConsumption(currentConsumption.add(benefitAmount));
} else if (type == 2) {
intype=1;
// 商城类型增加服务金
benefitAmount = money.multiply(servicefee).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
benefitType = 2L; // 服务金
benefitName = "商城服务金";
// 更新用户服务金
BigDecimal currentServicefee = users.getServicefee() != null ? users.getServicefee() : BigDecimal.ZERO;
users.setServicefee(currentServicefee.add(benefitAmount));
} else {
logger.error("无效的类型参数: {}", type);
return false;
}
if (benefitAmount.compareTo(BigDecimal.ZERO) > 0) {
// 更新用户信息
int updateResult = usersService.updateUsers(users);
if (updateResult > 0) {
// 添加福利金日志
UserBenefitPoints benefitPoints = new UserBenefitPoints();
benefitPoints.setType(Long.valueOf(intype));
benefitPoints.setDotime(new Date());
benefitPoints.setOrdermoney(money);
benefitPoints.setMoney(benefitAmount);
benefitPoints.setOrdertype(1L); // 1收入
benefitPoints.setUid(users.getId());
benefitPoints.setBeformoney(benefitType == 1L ?
(users.getConsumption() != null ? users.getConsumption().subtract(benefitAmount) : BigDecimal.ZERO) :
(users.getServicefee() != null ? users.getServicefee().subtract(benefitAmount) : BigDecimal.ZERO));
benefitPoints.setAftremoney(benefitType == 1L ? users.getConsumption() : users.getServicefee());
benefitPoints.setCreatedAt(new Date());
benefitPoints.setUpdatedAt(new Date());
//benefitPoints.setOrderid(Long.parseLong(orderid));
benefitPoints.setReamk("订单"+orderid+"消费获得" + benefitName);
userBenefitPointsService.insertUserBenefitPoints(benefitPoints);
logger.info("用户{}{}处理成功,金额{}元", users.getId(), benefitName, benefitAmount);
return true;
} else {
logger.error("更新用户{}失败用户ID: {}", benefitName, users.getId());
return false;
}
} else {
logger.info("订单金额{}元,类型{},计算{}金额{}元,不进行处理",
money, type, benefitName, benefitAmount);
return true; // 金额为0也算成功
}
} catch (Exception e) {
logger.error("处理用户{}异常", type == 1 ? "消费金" : "服务金", e);
return false;
}
}
/**
* 减少积分及购物消费金处理
*
* @param money 订单金额
* @param orderid 订单ID
* @param users 用户对象
* @param type 类型1-服务2-商城
* @return 处理结果
*/
public static JSONObject reduceIntegralAndBenefit(BigDecimal money, String orderid, Users users, Integer type) {
JSONObject result = new JSONObject();
try {
// 1. 查询配置信息
SiteConfig config = siteConfigService.selectSiteConfigByName("config_one");
if (config == null || config.getValue() == null) {
logger.error("未找到config_one配置信息");
result.put("success", false);
result.put("message", "系统配置信息缺失");
return result;
}
// 解析配置信息
JSONObject configJson = JSONObject.parseObject(config.getValue());
BigDecimal orderScore = configJson.getBigDecimal("orderScore");
BigDecimal servicefee = configJson.getBigDecimal("servicefee");
BigDecimal consumption = configJson.getBigDecimal("consumption");
if (orderScore == null || servicefee == null || consumption == null) {
logger.error("配置信息不完整orderScore: {}, servicefee: {}, consumption: {}",
orderScore, servicefee, consumption);
result.put("success", false);
result.put("message", "系统配置信息不完整");
return result;
}
// 2. 处理用户积分减少
Long integralPoints = reduceUserIntegral(money, orderScore, orderid, users);
// 3. 处理消费金和服务金减少
boolean benefitResult = reduceUserBenefit(money, type, servicefee, consumption, orderid, users);
if (integralPoints > 0 && benefitResult) {
result.put("success", true);
result.put("message", "积分和消费金减少处理成功");
result.put("integralPoints", integralPoints);
} else {
result.put("success", false);
result.put("message", "积分或消费金减少处理失败");
}
} catch (Exception e) {
logger.error("减少积分及购物消费金处理异常", e);
result.put("success", false);
result.put("message", "处理过程中发生异常:" + e.getMessage());
}
return result;
}
/**
* 减少用户积分
*
* @param money 订单金额
* @param orderScore 积分兑换比例
* @param orderid 订单ID
* @param users 用户对象
* @return 减少的积分数量
*/
private static Long reduceUserIntegral(BigDecimal money, BigDecimal orderScore, String orderid, Users users) {
try {
// 计算积分money / orderScore向下取整
BigDecimal integralDecimal = money.divide(orderScore, 0, RoundingMode.DOWN);
Long integralPoints = integralDecimal.longValue();
if (integralPoints > 0) {
// 检查用户积分是否足够
Long currentIntegral = users.getIntegral() != null ? users.getIntegral() : 0L;
if (currentIntegral < integralPoints) {
logger.warn("用户{}积分不足,当前积分{},需要扣除{}", users.getId(), currentIntegral, integralPoints);
integralPoints = currentIntegral; // 只能扣除现有积分
}
if (integralPoints > 0) {
// 更新用户积分
users.setIntegral(currentIntegral - integralPoints);
// 注意total_integral不减少因为这是累计积分
// 更新用户信息
int updateResult = usersService.updateUsers(users);
if (updateResult > 0) {
// 添加积分减少日志
IntegralLog integralLog = new IntegralLog();
integralLog.setOrderId(orderid);
integralLog.setTitle("订单消费扣除积分");
integralLog.setMark("订单消费" + money + "元,扣除积分" + integralPoints + "");
integralLog.setUid(users.getId());
integralLog.setUname(users.getName());
integralLog.setType(2L); // 2减少
integralLog.setNum(integralPoints);
integralLog.setCreatedAt(new Date());
integralLog.setUpdatedAt(new Date());
integralLogService.insertIntegralLog(integralLog);
logger.info("用户{}积分扣除成功,扣除积分{}点", users.getId(), integralPoints);
return integralPoints;
} else {
logger.error("更新用户积分失败用户ID: {}", users.getId());
return 0L;
}
} else {
logger.info("用户{}积分不足,无法扣除", users.getId());
return 0L;
}
} else {
logger.info("订单金额{}元,积分兑换比例{},计算积分{}点,不进行积分扣除",
money, orderScore, integralPoints);
return 0L;
}
} catch (Exception e) {
logger.error("扣除用户积分异常", e);
return 0L;
}
}
/**
* 减少用户消费金和服务金
*
* @param money 订单金额
* @param type 类型1-服务2-商城
* @param servicefee 服务金比例
* @param consumption 消费金比例
* @param orderid 订单ID
* @param users 用户对象
* @return 处理结果
*/
private static boolean reduceUserBenefit(BigDecimal money, Integer type, BigDecimal servicefee,
BigDecimal consumption, String orderid, Users users) {
try {
BigDecimal benefitAmount = BigDecimal.ZERO;
Long benefitType = 0L;
String benefitName = "";
if (type == 1) {
// 服务类型减少消费金
benefitAmount = money.multiply(consumption).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
benefitType = 1L; // 消费金
benefitName = "服务消费金";
// 检查用户消费金是否足够
BigDecimal currentConsumption = users.getConsumption() != null ? users.getConsumption() : BigDecimal.ZERO;
if (currentConsumption.compareTo(benefitAmount) < 0) {
logger.warn("用户{}消费金不足,当前{}元,需要扣除{}元", users.getId(), currentConsumption, benefitAmount);
benefitAmount = currentConsumption; // 只能扣除现有金额
}
if (benefitAmount.compareTo(BigDecimal.ZERO) > 0) {
users.setConsumption(currentConsumption.subtract(benefitAmount));
}
} else if (type == 2) {
// 商城类型减少服务金
benefitAmount = money.multiply(servicefee).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
benefitType = 2L; // 服务金
benefitName = "商城服务金";
// 检查用户服务金是否足够
BigDecimal currentServicefee = users.getServicefee() != null ? users.getServicefee() : BigDecimal.ZERO;
if (currentServicefee.compareTo(benefitAmount) < 0) {
logger.warn("用户{}服务金不足,当前{}元,需要扣除{}元", users.getId(), currentServicefee, benefitAmount);
benefitAmount = currentServicefee; // 只能扣除现有金额
}
if (benefitAmount.compareTo(BigDecimal.ZERO) > 0) {
users.setServicefee(currentServicefee.subtract(benefitAmount));
}
} else {
logger.error("无效的类型参数: {}", type);
return false;
}
if (benefitAmount.compareTo(BigDecimal.ZERO) > 0) {
// 更新用户信息
int updateResult = usersService.updateUsers(users);
if (updateResult > 0) {
// 添加福利金减少日志
UserBenefitPoints benefitPoints = new UserBenefitPoints();
benefitPoints.setType(benefitType);
benefitPoints.setDotime(new Date());
benefitPoints.setOrdermoney(money);
benefitPoints.setMoney(benefitAmount);
benefitPoints.setOrdertype(2L); // 2支出
benefitPoints.setUid(users.getId());
benefitPoints.setBeformoney(benefitType == 1L ?
(users.getConsumption() != null ? users.getConsumption().add(benefitAmount) : benefitAmount) :
(users.getServicefee() != null ? users.getServicefee().add(benefitAmount) : benefitAmount));
benefitPoints.setAftremoney(benefitType == 1L ? users.getConsumption() : users.getServicefee());
benefitPoints.setCreatedAt(new Date());
benefitPoints.setUpdatedAt(new Date());
//benefitPoints.setOrderid(Long.parseLong(orderid));
benefitPoints.setReamk("订单"+orderid+"消费扣除" + benefitName);
userBenefitPointsService.insertUserBenefitPoints(benefitPoints);
logger.info("用户{}{}扣除成功,金额{}元", users.getId(), benefitName, benefitAmount);
return true;
} else {
logger.error("更新用户{}失败用户ID: {}", benefitName, users.getId());
return false;
}
} else {
logger.info("订单金额{}元,类型{},计算{}金额{}元,不进行扣除",
money, type, benefitName, benefitAmount);
return true; // 金额为0也算成功
}
} catch (Exception e) {
logger.error("扣除用户{}异常", type == 1 ? "消费金" : "服务金", e);
return false;
}
}
/**
* 支付后期处理
*
* @param usersPayBefor 支付前对象
* @param uid 用户ID
* @return 处理结果
*/
public static JSONObject paymentPostProcess(UsersPayBefor usersPayBefor, Long uid) {
JSONObject result = new JSONObject();
try {
Users users = usersService.selectUsersById(uid);
if (users == null) {
logger.error("未找到用户信息用户ID: {}", uid);
result.put("success", false);
result.put("message", "用户信息不存在");
return result;
}
// 1. 查询配置信息
SiteConfig config = siteConfigService.selectSiteConfigByName("config_one");
if (config == null || config.getValue() == null) {
logger.error("未找到config_one配置信息");
result.put("success", false);
result.put("message", "系统配置信息缺失");
return result;
}
// 解析配置信息
JSONObject configJson = JSONObject.parseObject(config.getValue());
BigDecimal servicefee = configJson.getBigDecimal("servicefee");
BigDecimal consumption = configJson.getBigDecimal("consumption");
if (servicefee == null || consumption == null) {
logger.error("配置信息不完整servicefee: {}, consumption: {}", servicefee, consumption);
result.put("success", false);
result.put("message", "系统配置信息不完整");
return result;
}
boolean shopResult = false;
boolean serviceResult = false;
// 2. 处理购物金扣除如果shopmoney有值
BigDecimal shopmoney = usersPayBefor.getShopmoney();
if (shopmoney != null && shopmoney.compareTo(BigDecimal.ZERO) > 0) {
try {
shopResult = processShopMoneyDeduction(shopmoney, users, consumption);
logger.info("购物金扣除处理完成,结果:{}", shopResult);
} catch (Exception e) {
logger.error("购物金扣除处理异常", e);
shopResult = false;
}
} else {
logger.info("用户{}购物金为空或为0不进行处理", users.getId());
shopResult = true;
}
// 3. 处理服务金扣除如果servicemoney有值
BigDecimal servicemoney = usersPayBefor.getServicemoney();
if (servicemoney != null && servicemoney.compareTo(BigDecimal.ZERO) > 0) {
try {
serviceResult = processServiceMoneyDeduction(servicemoney, users, servicefee);
logger.info("服务金扣除处理完成,结果:{}", serviceResult);
} catch (Exception e) {
logger.error("服务金扣除处理异常", e);
serviceResult = false;
}
} else {
logger.info("用户{}服务金为空或为0不进行处理", users.getId());
serviceResult = true;
}
// 4. 返回处理结果
result.put("success", true);
result.put("message", "支付后期处理完成");
result.put("shopResult", shopResult);
result.put("serviceResult", serviceResult);
logger.info("支付后期处理全部完成,购物金处理:{},服务金处理:{}", shopResult, serviceResult);
} catch (Exception e) {
logger.error("支付后期处理异常", e);
result.put("success", false);
result.put("message", "处理过程中发生异常:" + e.getMessage());
}
return result;
}
/**
* 处理服务金扣除
*
* @param servicemoney 服务金金额
* @param users 用户对象
* @param servicefee 服务金比例
* @return 处理结果
*/
private static boolean processServiceMoneyDeduction(BigDecimal servicemoney, Users users, BigDecimal servicefee) {
try {
// 计算扣除金额servicemoney / servicefee%
// 例如servicefee=5表示5%计算方式servicemoney / (5/100)
BigDecimal servicefeeDecimal = servicefee.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
BigDecimal deductionAmount = servicemoney.divide(servicefeeDecimal, 2, RoundingMode.HALF_UP);
// 获取当前消费金余额
BigDecimal currentConsumption = users.getServicefee() != null ? users.getServicefee() : BigDecimal.ZERO;
BigDecimal actualDeductionAmount = deductionAmount;
// 如果余额不足设置为0记录实际扣除金额
if (currentConsumption.compareTo(deductionAmount) < 0) {
logger.warn("用户{}消费金不足,当前{}元,需要扣除{}元将设置为0",
users.getId(), currentConsumption, deductionAmount);
actualDeductionAmount = currentConsumption;
users.setServicefee(BigDecimal.ZERO);
} else {
// 余额充足正常扣除
users.setServicefee(currentConsumption.subtract(deductionAmount));
}
// 更新用户信息
int updateResult = usersService.updateUsers(users);
if (updateResult > 0) {
// 添加福利金扣除日志
UserBenefitPoints benefitPoints = new UserBenefitPoints();
benefitPoints.setType(1L); // 消费金
benefitPoints.setDotime(new Date());
benefitPoints.setOrdermoney(servicemoney);
benefitPoints.setMoney(actualDeductionAmount.negate()); // 负数
benefitPoints.setOrdertype(2L); // 支出
benefitPoints.setUid(users.getId());
benefitPoints.setBeformoney(currentConsumption);
benefitPoints.setAftremoney(users.getServicefee());
benefitPoints.setCreatedAt(new Date());
benefitPoints.setUpdatedAt(new Date());
benefitPoints.setReamk("支付后期处理扣除消费金,服务金金额:" + servicemoney + "元,服务金比例:" + servicefee + "%,实际扣除:" + actualDeductionAmount + "积分");
userBenefitPointsService.insertUserBenefitPoints(benefitPoints);
logger.info("用户{}服务金扣除完成,服务金{}元,服务金比例{}%,扣除消费金{}元,实际扣除{}元",
users.getId(), servicemoney, servicefee, deductionAmount, actualDeductionAmount);
return true;
} else {
logger.error("更新用户消费金失败用户ID: {}", users.getId());
return false;
}
} catch (Exception e) {
logger.error("处理服务金扣除异常", e);
return false;
}
}
/**
* 处理购物金扣除
*
* @param shopmoney 购物金金额
* @param users 用户对象
* @param consumption 消费金比例
* @return 处理结果
*/
private static boolean processShopMoneyDeduction(BigDecimal shopmoney, Users users, BigDecimal consumption) {
try {
// 计算扣除金额shopmoney / consumption%
// 例如consumption=10表示10%计算方式shopmoney / (10/100)
BigDecimal consumptionDecimal = consumption.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
BigDecimal deductionAmount = shopmoney.divide(consumptionDecimal, 2, RoundingMode.HALF_UP);
// 获取当前服务金余额
BigDecimal currentServicefee = users.getConsumption() != null ? users.getConsumption() : BigDecimal.ZERO;
BigDecimal actualDeductionAmount = deductionAmount;
// 如果余额不足设置为0记录实际扣除金额
if (currentServicefee.compareTo(deductionAmount) < 0) {
logger.warn("用户{}服务金不足,当前{}元,需要扣除{}元将设置为0",
users.getId(), currentServicefee, deductionAmount);
actualDeductionAmount = currentServicefee;
users.setConsumption(BigDecimal.ZERO);
} else {
// 余额充足正常扣除
users.setConsumption(currentServicefee.subtract(deductionAmount));
}
// 更新用户信息
int updateResult = usersService.updateUsers(users);
if (updateResult > 0) {
// 添加福利金扣除日志
UserBenefitPoints benefitPoints = new UserBenefitPoints();
benefitPoints.setType(2L); // 服务金
benefitPoints.setDotime(new Date());
benefitPoints.setOrdermoney(shopmoney);
benefitPoints.setMoney(actualDeductionAmount.negate()); // 负数
benefitPoints.setOrdertype(2L); // 支出
benefitPoints.setUid(users.getId());
benefitPoints.setBeformoney(currentServicefee);
benefitPoints.setAftremoney(users.getConsumption());
benefitPoints.setReamk("支付后期处理扣除服务金,购物金金额:" + shopmoney + "元,消费金比例:" + consumption + "%,实际扣除:" + actualDeductionAmount + "积分");
userBenefitPointsService.insertUserBenefitPoints(benefitPoints);
logger.info("用户{}购物金扣除完成,购物金{}元,消费金比例{}%,扣除服务金{}元,实际扣除{}元",
users.getId(), shopmoney, consumption, deductionAmount, actualDeductionAmount);
return true;
} else {
logger.error("更新用户服务金失败用户ID: {}", users.getId());
return false;
}
} catch (Exception e) {
logger.error("处理购物金扣除异常", e);
return false;
}
}
}

View File

@ -0,0 +1,175 @@
package com.ruoyi.system.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* FFmpeg诊断工具
* 用于分析FFmpeg合并失败的原因
*
* @author ruoyi
*/
public class FFmpegDiagnostic {
public static void main(String[] args) {
System.out.println("=== FFmpeg诊断工具 ===");
// 1. 检查FFmpeg可用性
System.out.println("1. 检查FFmpeg可用性");
boolean isAvailable = FFmpegUtils.isFFmpegAvailable();
System.out.println(" FFmpeg可用: " + isAvailable);
if (isAvailable) {
String version = FFmpegUtils.getFFmpegVersion();
System.out.println(" FFmpeg版本: " + version);
}
// 2. 测试简单的FFmpeg命令
System.out.println("\n2. 测试简单FFmpeg命令");
testSimpleFFmpegCommand();
// 3. 检查文件路径问题
System.out.println("\n3. 检查文件路径");
checkFilePathIssues();
// 4. 测试文件格式支持
System.out.println("\n4. 测试文件格式支持");
testFileFormatSupport();
System.out.println("\n=== 诊断完成 ===");
}
/**
* 测试简单的FFmpeg命令
*/
private static void testSimpleFFmpegCommand() {
try {
String ffmpegPath = getFFmpegPath();
if (ffmpegPath == null) {
System.out.println(" 无法获取FFmpeg路径");
return;
}
System.out.println(" FFmpeg路径: " + ffmpegPath);
// 测试-help命令
ProcessBuilder pb = new ProcessBuilder(ffmpegPath, "-help");
pb.redirectErrorStream(true);
Process process = pb.start();
// 读取输出
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode = process.waitFor();
System.out.println(" 简单命令测试 - 退出码: " + exitCode);
if (exitCode == 0) {
System.out.println(" ✓ FFmpeg基本功能正常");
} else {
System.out.println(" ✗ FFmpeg基本功能异常");
}
} catch (Exception e) {
System.out.println(" 测试简单命令失败: " + e.getMessage());
}
}
/**
* 检查文件路径问题
*/
private static void checkFilePathIssues() {
String[] testPaths = {
"C:\\test\\audio.mp3",
"/tmp/test/audio.mp3",
"test_audio.mp3",
"D:\\javacode\\RuoYi-Vue-master\\test.mp3"
};
System.out.println(" 检查文件路径格式:");
for (String path : testPaths) {
File file = new File(path);
System.out.println(" " + path);
System.out.println(" 存在: " + file.exists());
System.out.println(" 可读: " + file.canRead());
System.out.println(" 绝对路径: " + file.getAbsolutePath());
}
}
/**
* 测试文件格式支持
*/
private static void testFileFormatSupport() {
try {
String ffmpegPath = getFFmpegPath();
if (ffmpegPath == null) {
System.out.println(" 无法获取FFmpeg路径");
return;
}
// 测试支持的格式
ProcessBuilder pb = new ProcessBuilder(ffmpegPath, "-formats");
pb.redirectErrorStream(true);
Process process = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
boolean foundMp3 = false;
boolean foundWav = false;
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("mp3")) foundMp3 = true;
if (line.contains("wav")) foundWav = true;
}
process.waitFor();
System.out.println(" 支持的音频格式:");
System.out.println(" MP3: " + (foundMp3 ? "" : ""));
System.out.println(" WAV: " + (foundWav ? "" : ""));
} catch (Exception e) {
System.out.println(" 测试格式支持失败: " + e.getMessage());
}
}
/**
* 获取FFmpeg路径简化版
*/
private static String getFFmpegPath() {
String[] paths = {
"ffmpeg",
"C:\\ffmpeg\\bin\\ffmpeg.exe",
"C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe",
"D:\\ffmpeg\\bin\\ffmpeg.exe",
System.getProperty("user.dir") + "/ruoyi-system/src/main/resources/ffmpeg/ffmpeg.exe"
};
for (String path : paths) {
try {
ProcessBuilder pb = new ProcessBuilder(path, "-version");
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode == 0) {
return path;
}
} catch (Exception e) {
// 忽略错误继续尝试下一个路径
}
}
return null;
}
}

View File

@ -0,0 +1,69 @@
package com.ruoyi.system.utils;
import java.io.File;
import java.util.Arrays;
import java.util.List;
/**
* FFmpeg合并功能测试类
*
* @author ruoyi
*/
public class FFmpegMergeTest {
public static void main(String[] args) {
System.out.println("=== FFmpeg合并功能测试 ===");
// 测试FFmpeg是否可用
boolean isAvailable = FFmpegUtils.isFFmpegAvailable();
System.out.println("FFmpeg是否可用: " + isAvailable);
if (!isAvailable) {
System.out.println("FFmpeg不可用测试终止");
return;
}
// 获取FFmpeg版本
String version = FFmpegUtils.getFFmpegVersion();
System.out.println("FFmpeg版本: " + version);
// 创建测试文件路径
String testDir = System.getProperty("user.dir") + "/test_audio";
File testDirFile = new File(testDir);
if (!testDirFile.exists()) {
testDirFile.mkdirs();
}
// 模拟输入文件列表这里需要实际的音频文件
List<String> inputFiles = Arrays.asList(
testDir + "/test1.mp3",
testDir + "/test2.mp3"
);
String outputFile = testDir + "/merged_output.mp3";
System.out.println("输入文件:");
for (String file : inputFiles) {
File f = new File(file);
System.out.println(" " + file + " (存在: " + f.exists() + ")");
}
System.out.println("输出文件: " + outputFile);
// 测试合并功能
System.out.println("开始测试合并功能...");
boolean success = FFmpegUtils.mergeAudioFiles(inputFiles, outputFile);
System.out.println("合并结果: " + (success ? "成功" : "失败"));
// 检查输出文件
File output = new File(outputFile);
if (output.exists()) {
System.out.println("输出文件存在,大小: " + output.length() + " bytes");
} else {
System.out.println("输出文件不存在");
}
System.out.println("=== 测试完成 ===");
}
}

View File

@ -0,0 +1,104 @@
package com.ruoyi.system.utils;
import java.io.File;
import java.util.Arrays;
import java.util.List;
/**
* FFmpeg Opus转MP3测试工具
*
* @author ruoyi
*/
public class FFmpegOpusTest {
public static void main(String[] args) {
System.out.println("=== FFmpeg Opus转MP3测试 ===");
// 测试FFmpeg是否可用
boolean isAvailable = FFmpegUtilsSimple.isFFmpegAvailable();
System.out.println("FFmpeg是否可用: " + isAvailable);
if (!isAvailable) {
System.out.println("FFmpeg不可用测试终止");
return;
}
// 获取FFmpeg版本
String version = FFmpegUtilsSimple.getFFmpegVersion();
System.out.println("FFmpeg版本: " + version);
// 测试音频格式检测
System.out.println("\n=== 音频格式检测测试 ===");
testAudioFormatDetection();
// 测试合并功能
System.out.println("\n=== 合并功能测试 ===");
testMergeFunction();
System.out.println("\n=== 测试完成 ===");
}
/**
* 测试音频格式检测
*/
private static void testAudioFormatDetection() {
// 这里可以添加一些测试文件路径
String[] testFiles = {
"D:/ruoyi/uploadPath/upload/sound/2329/2025-08-02/wDoBh4bMpIXZcb9cc77fe118c54e5cba9ff87ec3ba12.durationTime=9472_20250802155052A017.mp3"
};
for (String filePath : testFiles) {
File file = new File(filePath);
if (file.exists()) {
String format = FFmpegUtilsSimple.detectAudioFormat(filePath);
System.out.println("文件: " + filePath);
System.out.println(" 存在: " + file.exists());
System.out.println(" 大小: " + file.length() + " bytes");
System.out.println(" 格式: " + format);
System.out.println();
} else {
System.out.println("文件不存在: " + filePath);
}
}
}
/**
* 测试合并功能
*/
private static void testMergeFunction() {
// 使用实际的音频文件路径
List<String> inputFiles = Arrays.asList(
"D:/ruoyi/uploadPath/upload/sound/2329/2025-08-02/wDoBh4bMpIXZcb9cc77fe118c54e5cba9ff87ec3ba12.durationTime=9472_20250802155052A017.mp3"
);
String outputFile = "C:/Users/user/AppData/Local/Temp/test_merged_output.mp3";
System.out.println("输入文件:");
for (String file : inputFiles) {
File f = new File(file);
String format = FFmpegUtilsSimple.detectAudioFormat(file);
System.out.println(" " + file);
System.out.println(" 存在: " + f.exists());
System.out.println(" 大小: " + f.length() + " bytes");
System.out.println(" 格式: " + format);
}
System.out.println("输出文件: " + outputFile);
// 测试合并功能
System.out.println("开始测试合并功能...");
boolean success = FFmpegUtilsSimple.mergeAudioFiles(inputFiles, outputFile);
System.out.println("合并结果: " + (success ? "成功" : "失败"));
// 检查输出文件
File output = new File(outputFile);
if (output.exists()) {
System.out.println("输出文件存在,大小: " + output.length() + " bytes");
String outputFormat = FFmpegUtilsSimple.detectAudioFormat(outputFile);
System.out.println("输出文件格式: " + outputFormat);
} else {
System.out.println("输出文件不存在");
}
}
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.system.utils;
/**
* FFmpeg集成测试类
*
* @author ruoyi
*/
public class FFmpegTest {
public static void main(String[] args) {
System.out.println("=== FFmpeg集成测试 ===");
// 测试FFmpeg是否可用
boolean isAvailable = FFmpegUtils.isFFmpegAvailable();
System.out.println("FFmpeg是否可用: " + isAvailable);
if (isAvailable) {
// 获取FFmpeg版本
String version = FFmpegUtils.getFFmpegVersion();
System.out.println("FFmpeg版本: " + version);
} else {
System.out.println("FFmpeg不可用请检查安装");
}
System.out.println("=== 测试完成 ===");
}
}

View File

@ -0,0 +1,349 @@
package com.ruoyi.system.utils;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.ArrayList;
/**
* FFmpeg工具类
* 用于音频文件合并等操作
*
* @author ruoyi
*/
public class FFmpegUtils {
private static final Logger logger = LoggerFactory.getLogger(FFmpegUtils.class);
/**
* 查找FFmpeg路径
*/
private static String findFFmpegPath() {
// 首先尝试项目内部的FFmpeg
String projectFFmpegPath = getProjectFFmpegPath();
if (projectFFmpegPath != null && isExecutable(projectFFmpegPath)) {
logger.info("使用项目内部FFmpeg: {}", projectFFmpegPath);
return projectFFmpegPath;
}
// 如果项目内部没有尝试系统安装的FFmpeg
String[] systemPaths = {
"ffmpeg",
"C:\\ffmpeg\\bin\\ffmpeg.exe",
"C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe",
"D:\\ffmpeg\\bin\\ffmpeg.exe",
"/usr/bin/ffmpeg",
"/usr/local/bin/ffmpeg"
};
for (String path : systemPaths) {
if (isExecutable(path)) {
logger.info("使用系统FFmpeg: {}", path);
return path;
}
}
logger.error("未找到可用的FFmpeg");
return null;
}
/**
* 获取项目内部的FFmpeg路径
*/
private static String getProjectFFmpegPath() {
try {
// 获取项目资源目录中的FFmpeg路径
String resourcePath = "/ffmpeg/ffmpeg.exe";
java.net.URL resourceUrl = FFmpegUtils.class.getResource(resourcePath);
if (resourceUrl != null) {
// 如果是jar包中的资源需要提取到临时目录
if (resourceUrl.getProtocol().equals("jar")) {
return extractFFmpegFromJar();
} else {
// 如果是文件系统中的资源直接返回路径
return new java.io.File(resourceUrl.toURI()).getAbsolutePath();
}
}
// 尝试从classpath中查找
String classpathPath = System.getProperty("user.dir") + "/ruoyi-system/src/main/resources/ffmpeg/ffmpeg.exe";
java.io.File classpathFile = new java.io.File(classpathPath);
if (classpathFile.exists()) {
return classpathFile.getAbsolutePath();
}
} catch (Exception e) {
logger.error("获取项目内部FFmpeg路径失败", e);
}
return null;
}
/**
* 从jar包中提取FFmpeg到临时目录
*/
private static String extractFFmpegFromJar() {
try {
// 创建临时目录
java.io.File tempDir = java.io.File.createTempFile("ffmpeg", "");
tempDir.delete();
tempDir.mkdirs();
// 提取FFmpeg
String ffmpegPath = tempDir.getAbsolutePath() + "/ffmpeg.exe";
java.io.File ffmpegFile = new java.io.File(ffmpegPath);
// 从jar包中复制文件
java.io.InputStream inputStream = FFmpegUtils.class.getResourceAsStream("/ffmpeg/ffmpeg.exe");
if (inputStream != null) {
java.io.FileOutputStream outputStream = new java.io.FileOutputStream(ffmpegFile);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
// 设置可执行权限
ffmpegFile.setExecutable(true);
logger.info("从jar包提取FFmpeg到: {}", ffmpegPath);
return ffmpegPath;
}
} catch (Exception e) {
logger.error("从jar包提取FFmpeg失败", e);
}
return null;
}
/**
* 检查文件是否可执行
*/
private static boolean isExecutable(String path) {
try {
ProcessBuilder pb = new ProcessBuilder(path, "-version");
Process process = pb.start();
int exitCode = process.waitFor();
return exitCode == 0;
} catch (Exception e) {
return false;
}
}
/**
* 合并音频文件
*
* @param inputFiles 输入文件列表
* @param outputFile 输出文件路径
* @return 是否成功
*/
public static boolean mergeAudioFiles(List<String> inputFiles, String outputFile) {
try {
logger.info("开始合并音频文件,输入文件数量: {}, 输出文件: {}", inputFiles.size(), outputFile);
// 检查输入文件
for (String inputFile : inputFiles) {
if (!validateAudioFile(inputFile)) {
logger.error("输入文件验证失败: {}", inputFile);
return false;
}
}
// 创建输出目录
File outputDir = new File(outputFile).getParentFile();
if (!outputDir.exists()) {
outputDir.mkdirs();
}
// 使用系统命令方式合并
return mergeWithSystemCommand(inputFiles, outputFile);
} catch (Exception e) {
logger.error("合并音频文件失败", e);
return false;
}
}
/**
* 使用系统命令方式合并
*/
private static boolean mergeWithSystemCommand(List<String> inputFiles, String outputFile) throws IOException {
// 创建输入文件列表
Path inputListFile = Files.createTempFile("input_list", ".txt");
try {
// 写入文件列表
StringBuilder content = new StringBuilder();
for (String inputFile : inputFiles) {
content.append("file '").append(inputFile).append("'\n");
logger.info("添加输入文件: {}", inputFile);
}
Files.write(inputListFile, content.toString().getBytes());
// 记录输入文件列表内容
logger.info("输入文件列表内容:\n{}", content.toString());
// 构建系统命令
String ffmpegPath = findFFmpegPath();
if (ffmpegPath == null) {
logger.error("未找到可用的FFmpeg");
return false;
}
// 构建FFmpeg命令
List<String> command = new ArrayList<>();
command.add(ffmpegPath);
command.add("-f");
command.add("concat");
command.add("-safe");
command.add("0");
command.add("-i");
command.add(inputListFile.toString());
command.add("-c");
command.add("copy");
command.add(outputFile);
ProcessBuilder pb = new ProcessBuilder(command);
// 记录完整的FFmpeg命令
logger.info("执行FFmpeg命令: {}", String.join(" ", command));
// 设置工作目录
pb.directory(new File(System.getProperty("user.dir")));
// 合并错误和标准输出
pb.redirectErrorStream(true);
// 执行命令
Process process = pb.start();
// 读取输出
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode;
try {
exitCode = process.waitFor();
} catch (InterruptedException e) {
logger.error("FFmpeg进程被中断", e);
Thread.currentThread().interrupt();
return false;
}
if (exitCode != 0) {
logger.error("FFmpeg合并失败退出码: {}, 输出: {}", exitCode, output.toString());
// 分析错误原因
String errorOutput = output.toString();
if (errorOutput.contains("No such file or directory")) {
logger.error("错误原因: 输入文件不存在或路径错误");
} else if (errorOutput.contains("Permission denied")) {
logger.error("错误原因: 权限不足,无法读取输入文件或写入输出文件");
} else if (errorOutput.contains("Invalid data found")) {
logger.error("错误原因: 输入文件格式无效或损坏");
} else if (errorOutput.contains("No such file or directory")) {
logger.error("错误原因: 输出目录不存在或无法创建");
} else {
logger.error("错误原因: 未知错误请检查FFmpeg输出信息");
}
return false;
}
// 验证输出文件
File outputFileObj = new File(outputFile);
if (outputFileObj.exists() && outputFileObj.length() > 0) {
logger.info("音频文件合并成功: {}", outputFile);
return true;
} else {
logger.error("合并后的文件不存在或为空: {}", outputFile);
return false;
}
} finally {
// 清理临时文件
Files.deleteIfExists(inputListFile);
}
}
/**
* 检查FFmpeg是否可用
*/
public static boolean isFFmpegAvailable() {
return findFFmpegPath() != null;
}
/**
* 验证音频文件格式
*/
private static boolean validateAudioFile(String filePath) {
try {
File file = new File(filePath);
if (!file.exists()) {
logger.error("文件不存在: {}", filePath);
return false;
}
if (!file.canRead()) {
logger.error("文件无法读取: {}", filePath);
return false;
}
// 检查文件扩展名
String fileName = file.getName().toLowerCase();
if (!fileName.endsWith(".mp3") && !fileName.endsWith(".wav") &&
!fileName.endsWith(".m4a") && !fileName.endsWith(".aac")) {
logger.warn("文件格式可能不支持: {}", filePath);
}
// 检查文件大小
long fileSize = file.length();
if (fileSize == 0) {
logger.error("文件为空: {}", filePath);
return false;
}
logger.info("文件验证通过: {} (大小: {} bytes)", filePath, fileSize);
return true;
} catch (Exception e) {
logger.error("验证文件失败: {}", filePath, e);
return false;
}
}
/**
* 获取FFmpeg版本信息
*/
public static String getFFmpegVersion() {
try {
String ffmpegPath = findFFmpegPath();
if (ffmpegPath != null) {
ProcessBuilder pb = new ProcessBuilder(ffmpegPath, "-version");
Process process = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
return reader.readLine();
}
} catch (Exception e) {
logger.error("获取FFmpeg版本失败", e);
}
return "FFmpeg未安装";
}
}

View File

@ -0,0 +1,321 @@
package com.ruoyi.system.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
/**
* 简化版FFmpeg工具类
* 用于音频文件合并等操作
*
* @author ruoyi
*/
public class FFmpegUtilsSimple {
private static final Logger logger = LoggerFactory.getLogger(FFmpegUtilsSimple.class);
/**
* 查找FFmpeg路径
*/
private static String findFFmpegPath() {
// 首先尝试项目内部的FFmpeg
String projectFFmpegPath = getProjectFFmpegPath();
if (projectFFmpegPath != null && isExecutable(projectFFmpegPath)) {
logger.info("使用项目内部FFmpeg: {}", projectFFmpegPath);
return projectFFmpegPath;
}
// 如果项目内部没有尝试系统安装的FFmpeg
String[] systemPaths = {
"ffmpeg",
"C:\\ffmpeg\\bin\\ffmpeg.exe",
"C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe",
"D:\\ffmpeg\\bin\\ffmpeg.exe",
"/usr/bin/ffmpeg",
"/usr/local/bin/ffmpeg"
};
for (String path : systemPaths) {
if (isExecutable(path)) {
logger.info("使用系统FFmpeg: {}", path);
return path;
}
}
logger.error("未找到可用的FFmpeg");
return null;
}
/**
* 获取项目内部的FFmpeg路径
*/
private static String getProjectFFmpegPath() {
try {
// 尝试从classpath中查找
String classpathPath = System.getProperty("user.dir") + "/ruoyi-system/src/main/resources/ffmpeg/ffmpeg.exe";
File classpathFile = new File(classpathPath);
if (classpathFile.exists()) {
return classpathFile.getAbsolutePath();
}
// 尝试从资源中获取
String resourcePath = "/ffmpeg/ffmpeg.exe";
java.net.URL resourceUrl = FFmpegUtilsSimple.class.getResource(resourcePath);
if (resourceUrl != null) {
return new File(resourceUrl.toURI()).getAbsolutePath();
}
} catch (Exception e) {
logger.error("获取项目内部FFmpeg路径失败", e);
}
return null;
}
/**
* 检查文件是否可执行
*/
private static boolean isExecutable(String path) {
try {
ProcessBuilder pb = new ProcessBuilder(path, "-version");
Process process = pb.start();
int exitCode = process.waitFor();
return exitCode == 0;
} catch (Exception e) {
return false;
}
}
/**
* 合并音频文件
*/
public static boolean mergeAudioFiles(List<String> inputFiles, String outputFile) {
try {
logger.info("开始合并音频文件,输入文件数量: {}, 输出文件: {}", inputFiles.size(), outputFile);
// 检查输入文件
for (String inputFile : inputFiles) {
File file = new File(inputFile);
if (!file.exists()) {
logger.error("输入文件不存在: {}", inputFile);
return false;
}
if (!file.canRead()) {
logger.error("输入文件无法读取: {}", inputFile);
return false;
}
// 检测音频格式
String format = detectAudioFormat(inputFile);
logger.info("验证输入文件: {} (大小: {} bytes, 格式: {})", inputFile, file.length(), format);
// 如果检测到Opus格式记录警告
if ("Opus".equals(format)) {
logger.warn("检测到Opus格式音频文件: {},将进行转码", inputFile);
}
}
// 创建输出目录
File outputDir = new File(outputFile).getParentFile();
if (!outputDir.exists()) {
outputDir.mkdirs();
}
// 使用系统命令方式合并
return mergeWithSystemCommand(inputFiles, outputFile);
} catch (Exception e) {
logger.error("合并音频文件失败", e);
return false;
}
}
/**
* 使用系统命令方式合并
*/
private static boolean mergeWithSystemCommand(List<String> inputFiles, String outputFile) throws IOException {
// 创建输入文件列表
Path inputListFile = Files.createTempFile("input_list", ".txt");
try {
// 写入文件列表
StringBuilder content = new StringBuilder();
for (String inputFile : inputFiles) {
content.append("file '").append(inputFile).append("'\n");
logger.info("添加输入文件: {}", inputFile);
}
Files.write(inputListFile, content.toString().getBytes());
// 记录输入文件列表内容
logger.info("输入文件列表内容:\n{}", content.toString());
// 构建系统命令
String ffmpegPath = findFFmpegPath();
if (ffmpegPath == null) {
logger.error("未找到可用的FFmpeg");
return false;
}
// 构建FFmpeg命令 - 使用转码而不是复制
String[] command = {
ffmpegPath,
"-f", "concat",
"-safe", "0",
"-i", inputListFile.toString(),
"-c:a", "libmp3lame", // 使用MP3编码器
"-b:a", "128k", // 设置比特率
"-ar", "44100", // 设置采样率
"-ac", "1", // 设置为单声道
outputFile
};
ProcessBuilder pb = new ProcessBuilder(command);
// 记录完整的FFmpeg命令
logger.info("执行FFmpeg命令: {}", String.join(" ", command));
// 设置工作目录
pb.directory(new File(System.getProperty("user.dir")));
// 合并错误和标准输出
pb.redirectErrorStream(true);
// 执行命令
Process process = pb.start();
// 读取输出
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode;
try {
exitCode = process.waitFor();
} catch (InterruptedException e) {
logger.error("FFmpeg进程被中断", e);
Thread.currentThread().interrupt();
return false;
}
if (exitCode != 0) {
logger.error("FFmpeg合并失败退出码: {}, 输出: {}", exitCode, output.toString());
// 分析错误原因
String errorOutput = output.toString();
if (errorOutput.contains("No such file or directory")) {
logger.error("错误原因: 输入文件不存在或路径错误");
} else if (errorOutput.contains("Permission denied")) {
logger.error("错误原因: 权限不足,无法读取输入文件或写入输出文件");
} else if (errorOutput.contains("Invalid data found")) {
logger.error("错误原因: 输入文件格式无效或损坏");
} else if (errorOutput.contains("opus") && errorOutput.contains("Invalid audio stream")) {
logger.error("错误原因: 输入文件包含Opus音频流需要转码为MP3格式");
} else if (errorOutput.contains("Invalid audio stream")) {
logger.error("错误原因: 音频流格式不兼容,需要转码");
} else {
logger.error("错误原因: 未知错误请检查FFmpeg输出信息");
}
return false;
}
// 验证输出文件
File outputFileObj = new File(outputFile);
if (outputFileObj.exists() && outputFileObj.length() > 0) {
logger.info("音频文件合并成功: {} (大小: {} bytes)", outputFile, outputFileObj.length());
return true;
} else {
logger.error("合并后的文件不存在或为空: {}", outputFile);
return false;
}
} finally {
// 清理临时文件
Files.deleteIfExists(inputListFile);
}
}
/**
* 检查FFmpeg是否可用
*/
public static boolean isFFmpegAvailable() {
return findFFmpegPath() != null;
}
/**
* 获取FFmpeg版本信息
*/
public static String getFFmpegVersion() {
try {
String ffmpegPath = findFFmpegPath();
if (ffmpegPath != null) {
ProcessBuilder pb = new ProcessBuilder(ffmpegPath, "-version");
Process process = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
return reader.readLine();
}
} catch (Exception e) {
logger.error("获取FFmpeg版本失败", e);
}
return "FFmpeg未安装";
}
/**
* 检测音频文件格式
*/
public static String detectAudioFormat(String filePath) {
try {
String ffmpegPath = findFFmpegPath();
if (ffmpegPath == null) {
return "未知";
}
ProcessBuilder pb = new ProcessBuilder(
ffmpegPath,
"-i", filePath,
"-hide_banner"
);
pb.redirectErrorStream(true);
Process process = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
process.waitFor();
String outputStr = output.toString();
if (outputStr.contains("Audio: mp3")) {
return "MP3";
} else if (outputStr.contains("Audio: opus")) {
return "Opus";
} else if (outputStr.contains("Audio: aac")) {
return "AAC";
} else if (outputStr.contains("Audio: wav")) {
return "WAV";
} else {
return "未知格式";
}
} catch (Exception e) {
logger.error("检测音频格式失败: {}", filePath, e);
return "检测失败";
}
}
}

Binary file not shown.

View File

@ -0,0 +1,303 @@
<template>
<div>
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="订单号" prop="orderId">
<el-input
v-model="queryParams.orderId"
placeholder="请输入订单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="金额" prop="price">
<el-input
v-model="queryParams.price"
placeholder="请输入金额"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="3">
<el-button
type="danger"
plain
icon="el-icon-edit"
size="medium"
style="font-weight:bold;font-size:16px;"
@click="showMarginChangeDialog"
>质保金变动</el-button>
</el-col>
<el-col :span="15"></el-col>
<el-col :span="3" style="text-align:right;">
<el-button @click="$emit('close')">关闭</el-button>
</el-col>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="workerMarginLogList" @selection-change="handleSelectionChange" element-loading-text="正在加载师傅质保金明细数据...">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="订单号" align="center" prop="orderId" min-width="120" />
<el-table-column label="金额" align="center" prop="price" width="120">
<template slot-scope="scope">
<span style="color: #E6A23C; font-weight: bold; font-size: 14px;">
¥{{ formatMoney(scope.row.price) }}
</span>
</template>
</el-table-column>
<el-table-column label="原因" align="center" prop="reamk" min-width="120" />
<el-table-column label="创建时间" align="center" prop="createdAt" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="更新时间" align="center" prop="updatedAt" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.updatedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 空数据提示 -->
<div v-if="!loading && workerMarginLogList.length === 0" style="text-align: center; padding: 40px; color: #909399;">
<i class="el-icon-warning" style="font-size: 48px; margin-bottom: 16px;"></i>
<p>暂无师傅质保金明细数据</p>
<p v-if="queryParams.uid">师傅ID: {{ queryParams.uid }}</p>
</div>
<!-- 质保金变动弹窗 -->
<el-dialog :title="'质保金变动'" :visible.sync="marginChangeDialogVisible" width="500px" append-to-body>
<el-form :model="marginChangeForm" :rules="marginChangeRules" ref="marginChangeForm" label-width="100px">
<el-form-item label="变动金额" prop="price">
<el-input
v-model="marginChangeForm.price"
placeholder="请输入变动金额(正数为增加,负数为减少)"
type="number"
step="0.01"
clearable
/>
</el-form-item>
<el-form-item label="变动原因" prop="reamk">
<el-input
v-model="marginChangeForm.reamk"
type="textarea"
:rows="3"
placeholder="请输入变动原因"
clearable
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="marginChangeDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitMarginChange" :loading="marginChangeLoading"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listWorkerMarginLog, addWorkerMarginLog } from '@/api/system/WorkerMarginLog'
export default {
name: 'WorkerMarginLogDetailTable',
props: {
workerId: {
type: [String, Number],
required: true
}
},
data() {
return {
loading: false,
total: 0,
workerMarginLogList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
uid: null,
orderId: null,
price: null
},
ids: [],
single: true,
multiple: true,
//
marginChangeDialogVisible: false,
marginChangeLoading: false,
marginChangeForm: {
price: null,
reamk: ''
},
marginChangeRules: {
reamk: [
{ required: true, message: '请输入变动原因', trigger: 'blur' },
{ min: 1, max: 200, message: '变动原因长度在 1 到 200 个字符', trigger: 'blur' }
]
}
}
},
watch: {
workerId: {
immediate: true,
handler(newVal) {
console.log('WorkerMarginLogDetailTable - workerId changed:', newVal);
if (newVal) {
this.queryParams.uid = newVal;
this.getList();
}
}
}
},
mounted() {
console.log('WorkerMarginLogDetailTable - component mounted');
console.log('组件挂载成功workerId:', this.workerId);
},
methods: {
getList() {
console.log('开始加载师傅质保金明细数据workerId:', this.queryParams.uid);
console.log('查询参数:', this.queryParams);
if (!this.queryParams.uid) {
console.warn('workerId为空无法加载数据');
this.$message.warning('师傅ID不能为空');
return;
}
this.loading = true;
listWorkerMarginLog(this.queryParams).then(response => {
console.log('师傅质保金明细数据加载成功:', response);
this.workerMarginLogList = response.rows || [];
this.total = response.total || 0;
this.loading = false;
console.log('设置数据:', this.workerMarginLogList);
console.log('总数:', this.total);
if (this.workerMarginLogList.length === 0) {
console.log('没有找到质保金明细数据');
this.$message.info('该师傅暂无质保金明细记录');
}
}).catch(error => {
console.error('师傅质保金明细数据加载失败:', error);
console.error('错误详情:', error.response);
this.loading = false;
if (error.response && error.response.status === 403) {
this.$message.error('没有权限查看师傅质保金明细');
} else if (error.response && error.response.status === 404) {
this.$message.error('接口不存在,请检查后端服务');
} else {
this.$message.error('数据加载失败: ' + (error.message || '未知错误'));
}
});
},
formatMoney(money) {
if (!money) return '0.00';
return parseFloat(money).toFixed(2);
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.queryParams.orderId = null;
this.queryParams.price = null;
this.handleQuery();
},
parseTime(time, pattern) {
if (!time) return '';
const date = new Date(time);
const y = date.getFullYear();
const m = ('0' + (date.getMonth() + 1)).slice(-2);
const d = ('0' + date.getDate()).slice(-2);
const h = ('0' + date.getHours()).slice(-2);
const i = ('0' + date.getMinutes()).slice(-2);
const s = ('0' + date.getSeconds()).slice(-2);
if (pattern === '{y}-{m}-{d} {h}:{i}:{s}') {
return `${y}-${m}-${d} ${h}:${i}:${s}`;
} else if (pattern === '{y}-{m}-{d}') {
return `${y}-${m}-${d}`;
}
return `${y}-${m}-${d}`;
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
//
showMarginChangeDialog() {
this.marginChangeForm = {
price: null,
reamk: ''
};
this.marginChangeDialogVisible = true;
this.$nextTick(() => {
this.$refs.marginChangeForm && this.$refs.marginChangeForm.clearValidate();
});
},
//
submitMarginChange() {
this.$refs.marginChangeForm.validate((valid) => {
if (valid) {
this.marginChangeLoading = true;
const data = {
uid: this.queryParams.uid,
price: 0-parseFloat(this.marginChangeForm.price),
reamk: this.marginChangeForm.reamk
};
console.log('提交质保金变动数据:', data);
addWorkerMarginLog(data).then(response => {
this.$message.success('质保金变动成功');
this.marginChangeDialogVisible = false;
this.marginChangeLoading = false;
//
this.getList();
//
this.$emit('margin-changed');
}).catch(error => {
console.error('质保金变动失败:', error);
this.marginChangeLoading = false;
if (error.response && error.response.status === 403) {
this.$message.error('没有权限进行质保金变动');
} else {
this.$message.error('质保金变动失败: ' + (error.message || '未知错误'));
}
});
}
});
}
}
}
</script>
<style scoped>
.el-table .el-table__body-wrapper {
max-height: 400px;
overflow-y: auto;
}
</style>