同步 Date 转换 LocalDateTime 的修改~

pull/12/head
YunaiV 2022-11-11 00:20:57 +08:00
parent 2460e492cf
commit 19d9d66a38
336 changed files with 3536 additions and 1176 deletions

View File

@ -1,8 +1,9 @@
package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.Duration;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
@ -25,6 +26,34 @@ public class DateUtils {
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
/**
* LocalDateTime Date
*
* @param date LocalDateTime
* @return LocalDateTime
*/
public static Date of(LocalDateTime date) {
// 将此日期时间与时区相结合以创建 ZonedDateTime
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
Instant instant = zonedDateTime.toInstant();
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
return Date.from(instant);
}
/**
* Date LocalDateTime
*
* @param date Date
* @return LocalDateTime
*/
public static LocalDateTime of(Date date) {
// 转为时间戳
Instant instant = date.toInstant();
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
public static Date addTime(Duration duration) {
return new Date(System.currentTimeMillis() + duration.toMillis());
}
@ -33,6 +62,11 @@ public class DateUtils {
return System.currentTimeMillis() > time.getTime();
}
public static boolean isExpired(LocalDateTime time) {
LocalDateTime now = LocalDateTime.now();
return now.isAfter(time);
}
public static long diff(Date endTime, Date startTime) {
return endTime.getTime() - startTime.getTime();
}
@ -40,24 +74,28 @@ public class DateUtils {
/**
*
*
* @param year
* @param mouth
* @param day
* @param year
* @param mouth
* @param day
* @return
*/
public static Date buildTime(int year, int mouth, int day) {
return buildTime(year, mouth, day, 0, 0, 0);
}
public static LocalDateTime buildLocalDateTime(int year, int mouth, int day) {
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
}
/**
*
*
* @param year
* @param mouth
* @param day
* @param hour
* @param minute
* @param second
* @param year
* @param mouth
* @param day
* @param hour
* @param minute
* @param second
* @return
*/
public static Date buildTime(int year, int mouth, int day,
@ -83,6 +121,16 @@ public class DateUtils {
return a.compareTo(b) > 0 ? a : b;
}
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.isAfter(b) ? a : b;
}
public static boolean beforeNow(Date date) {
return date.getTime() < System.currentTimeMillis();
}
@ -91,6 +139,10 @@ public class DateUtils {
return date.getTime() >= System.currentTimeMillis();
}
public static boolean afterNow(LocalDateTime localDateTime) {
return localDateTime.isAfter(LocalDateTime.now());
}
/**
*
*
@ -135,4 +187,14 @@ public class DateUtils {
return DateUtil.isSameDay(date, new Date());
}
/**
*
*
* @param date
* @return
*/
public static boolean isToday(LocalDateTime date) {
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
}
}

View File

@ -10,7 +10,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -41,7 +41,7 @@ public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
/**
*
*/
private Date maxUpdateTime;
private LocalDateTime maxUpdateTime;
@EventListener(ApplicationReadyEvent.class)
public void loadErrorCodes() {

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.operatelog.core.aop;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
@ -32,6 +33,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.IntStream;
@ -94,7 +96,7 @@ public class OperateLogAspect {
}
// 记录开始时间
Date startTime = new Date();
LocalDateTime startTime = LocalDateTime.now();
try {
// 执行原有方法
Object result = joinPoint.proceed();
@ -128,7 +130,7 @@ public class OperateLogAspect {
private void log(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
ApiOperation apiOperation,
Date startTime, Object result, Throwable exception) {
LocalDateTime startTime, Object result, Throwable exception) {
try {
// 判断不记录的情况
if (!isLogEnable(joinPoint, operateLog)) {
@ -145,7 +147,7 @@ public class OperateLogAspect {
private void log0(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
ApiOperation apiOperation,
Date startTime, Object result, Throwable exception) {
LocalDateTime startTime, Object result, Throwable exception) {
OperateLog operateLogObj = new OperateLog();
// 补全通用字段
operateLogObj.setTraceId(TracerUtils.getTraceId());
@ -226,7 +228,7 @@ public class OperateLogAspect {
private static void fillMethodFields(OperateLog operateLogObj,
ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
Date startTime, Object result, Throwable exception) {
LocalDateTime startTime, Object result, Throwable exception) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
operateLogObj.setJavaMethod(methodSignature.toString());
if (operateLog == null || operateLog.logArgs()) {
@ -235,7 +237,7 @@ public class OperateLogAspect {
if (operateLog == null || operateLog.logResultData()) {
operateLogObj.setResultData(obtainResultData(result));
}
operateLogObj.setDuration((int) (System.currentTimeMillis() - startTime.getTime()));
operateLogObj.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
// (正常)处理 resultCode 和 resultMsg 字段
if (result instanceof CommonResult) {
CommonResult<?> commonResult = (CommonResult<?>) result;

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.operatelog.core.service;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.Map;
/**
@ -85,7 +85,7 @@ public class OperateLog {
/**
*
*/
private Date startTime;
private LocalDateTime startTime;
/**
*

View File

@ -5,7 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.time.LocalDateTime;
/**
* Response DTO
@ -33,7 +33,7 @@ public class PayOrderNotifyRespDTO {
/**
*
*/
private Date successTime;
private LocalDateTime successTime;
/**
*

View File

@ -7,7 +7,7 @@ import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.Map;
/**
@ -68,7 +68,7 @@ public class PayOrderUnifiedReqDTO {
*
*/
@NotNull(message = "支付过期时间不能为空")
private Date expireTime;
private LocalDateTime expireTime;
// ========== 拓展参数 ==========
/**

View File

@ -5,7 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
/**
* 退Notify DTO
@ -57,7 +57,7 @@ public class PayRefundNotifyDTO {
/**
* 退
*/
private Date refundSuccessTime;
private LocalDateTime refundSuccessTime;
}

View File

@ -0,0 +1,38 @@
package pay.config;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
@ConfigurationProperties(prefix = "yudao.pay")
@Validated
@Data
public class PayProperties {
/**
*
* payNotifyUrl PayAppDO.payNotifyUrl
*/
@NotEmpty(message = "支付回调地址不能为空")
@URL(message = "支付回调地址的格式必须是 URL")
private String payNotifyUrl;
/**
* 退
* {@link #payNotifyUrl}
*/
@NotEmpty(message = "退款回调地址不能为空")
@URL(message = "退款回调地址的格式必须是 URL")
private String refundNotifyUrl;
/**
*
*/
@URL(message = "支付返回的地址的格式必须是 URL")
@NotEmpty(message = "支付返回的地址不能为空")
private String payReturnUrl;
}

View File

@ -0,0 +1,24 @@
package pay.config;
import cn.iocoder.yudao.framework.pay.config.PayProperties;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
*
* @author
*/
@Configuration
@EnableConfigurationProperties(PayProperties.class)
public class YudaoPayAutoConfiguration {
@Bean
public PayClientFactory payClientFactory() {
return new PayClientFactoryImpl();
}
}

View File

@ -0,0 +1,34 @@
package pay.core.client;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
import lombok.extern.slf4j.Slf4j;
/**
* API
*
* @see PayCommonResult
* @see PayFrameworkErrorCodeConstants
*
* @author
*/
@Slf4j
public abstract class AbstractPayCodeMapping {
public final ErrorCode apply(String apiCode, String apiMsg) {
if (apiCode == null) {
log.error("[apply][API 错误码为空,请排查]");
return PayFrameworkErrorCodeConstants.EXCEPTION;
}
ErrorCode errorCode = this.apply0(apiCode, apiMsg);
if (errorCode == null) {
log.error("[apply][API 错误码({}) 错误提示({}) 无法匹配]", apiCode, apiMsg);
return PayFrameworkErrorCodeConstants.PAY_UNKNOWN;
}
return errorCode;
}
protected abstract ErrorCode apply0(String apiCode, String apiMsg);
}

View File

@ -0,0 +1,74 @@
package pay.core.client;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
/**
* SDK退
*
* @author
*/
public interface PayClient {
/**
*
*
* @return
*/
Long getId();
/**
*
*
* @param reqDTO
* @return
*/
cn.iocoder.yudao.framework.pay.core.client.PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
/**
*
*
* @param data
* @return
* @throws Exception
*/
PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception;
/**
* 退
* @param reqDTO 退
* @return
*/
PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
/**
* 退
* @param notifyData 退
* @return 退Notify DTO
*/
PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
// TODO @芋艿:后续改成非 default避免不知道去实现
/**
*
*
* @param notifyData
* @return true
*/
default boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
return true;
}
// TODO @芋艿:后续改成非 default避免不知道去实现
/**
* 退
*
* @param notifyData
* @return false
*/
default boolean isRefundNotify(PayNotifyDataDTO notifyData){
return false;
}
}

View File

@ -0,0 +1,42 @@
package pay.core.client;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Set;
/**
*
*
*
* @author
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
// @JsonTypeInfo 注解的作用Jackson 多态
// 1. 序列化到时数据库时,增加 @class 属性。
// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
public interface PayClientConfig {
/**
*
*
* @param validator
* @return
*/
Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
// TODO @aquan貌似抽象一个 validation group 就好了!
/**
*
*
* @param validator
*/
default void validate(Validator validator) {
Set<ConstraintViolation<PayClientConfig>> violations = verifyParam(validator);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}

View File

@ -0,0 +1,31 @@
package pay.core.client;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
/**
*
*
* @author
*/
public interface PayClientFactory {
/**
*
*
* @param channelId
* @return
*/
PayClient getPayClient(Long channelId);
/**
*
*
* @param channelId
* @param channelCode
* @param config
*/
<Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
Config config);
}

View File

@ -0,0 +1,58 @@
package pay.core.client;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* CommonResult
*
* code msg {@link #apiCode} {@link #apiMsg}
*
* @author
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayCommonResult<T> extends CommonResult<T> {
/**
* API
*
* 使 String
*/
private String apiCode;
/**
* API
*/
private String apiMsg;
private PayCommonResult() {
}
public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, AbstractPayCodeMapping codeMapping) {
Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
PayCommonResult<T> result = new PayCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg);
result.setData(data);
// 翻译错误码
if (codeMapping != null) {
ErrorCode errorCode = codeMapping.apply(apiCode, apiMsg);
result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
}
return result;
}
public static <T> PayCommonResult<T> error(Throwable ex) {
PayCommonResult<T> result = new PayCommonResult<>();
result.setCode(PayFrameworkErrorCodeConstants.EXCEPTION.getCode());
result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
return result;
}
}

View File

@ -0,0 +1,29 @@
package pay.core.client.dto;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.Map;
/**
* 退
*/
@Data
@ToString
@Builder
public class PayNotifyDataDTO {
/**
* HTTP request body
*/
private String body;
/**
* HTTP content type application/x-www-form-urlencoded
*/
private Map<String,String> params;
}

View File

@ -0,0 +1,54 @@
package pay.core.client.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* Response DTO
*
* @author
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderNotifyRespDTO {
/**
*
*/
private String orderExtensionNo;
/**
*
*/
private String channelOrderNo;
/**
*
*/
private String channelUserId;
/**
*
*/
private LocalDateTime successTime;
/**
*
*
* 便
*/
private String data;
/**
* TODO @jason ,
* alipay
* TRADE_CLOSED,退
* TRADE_SUCCESS,
* TRADE_FINISHED 退
*/
private String tradeStatus;
}

View File

@ -0,0 +1,81 @@
package pay.core.client.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Map;
/**
* Request DTO
*
* @author
*/
@Data
public class PayOrderUnifiedReqDTO {
/**
* IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
// ========== 商户相关字段 ==========
/**
*
*/
@NotEmpty(message = "商户订单编号不能为空")
private String merchantOrderId;
/**
*
*/
@NotEmpty(message = "商品标题不能为空")
@Length(max = 32, message = "商品标题不能超过 32")
private String subject;
/**
*
*/
@NotEmpty(message = "商品描述信息不能为空")
@Length(max = 128, message = "商品描述信息长度不能超过128")
private String body;
/**
* notify
*/
@NotEmpty(message = "支付结果的回调地址不能为空")
@URL(message = "支付结果的 notify 回调地址必须是 URL 格式")
private String notifyUrl;
/**
* return
*/
@URL(message = "支付结果的 return 回调地址必须是 URL 格式")
private String returnUrl;
// ========== 订单相关字段 ==========
/**
*
*/
@NotNull(message = "支付金额不能为空")
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
private Long amount;
/**
*
*/
@NotNull(message = "支付过期时间不能为空")
private LocalDateTime expireTime;
// ========== 拓展参数 ==========
/**
*
*
* openid
*/
private Map<String, String> channelExtras;
}

View File

@ -0,0 +1,63 @@
package pay.core.client.dto;
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.time.LocalDateTime;
/**
* 退Notify DTO
*
* @author jason
*/
@Data
@ToString
@Builder
public class PayRefundNotifyDTO {
/**
*
*/
private String channelOrderNo;
/**
*
* 使
* 1. https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
* 2. https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
* pay_extension no
* P202110132239124200055
*/
private String tradeNo;
/**
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
* 退
* 退退
* 退退
* 退退退
* 退
*
* RR202109181134287570000
*/
private String reqNo;
/**
* 退
*/
private PayNotifyRefundStatusEnum status;
/**
* 退
*/
private LocalDateTime refundSuccessTime;
}

View File

@ -0,0 +1,74 @@
package pay.core.client.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 退 Request DTO
*
* @author jason
*/
@Accessors(chain = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PayRefundUnifiedReqDTO {
/**
* IP
*/
private String userIp;
// TODO @jason这个是否为非必传字段呀只需要传递 payTradeNo 字段即可。尽可能精简
/**
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 transaction_id
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 trade_no
*
*/
private String channelOrderNo;
/**
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
* {PayOrderExtensionDO no}
*/
private String payTradeNo;
/**
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
* 退 退退
* 使 退{PayRefundDO merchantRefundNo}
*/
@NotEmpty(message = "退款请求单号")
private String merchantRefundId;
/**
* 退
*/
@NotEmpty(message = "退款原因不能为空")
private String reason;
/**
* 退
*/
@NotNull(message = "退款金额不能为空")
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
private Long amount;
/**
* 退 notify 退
*/
@URL(message = "支付结果的 notify 回调地址必须是 URL 格式")
private String notifyUrl;
}

View File

@ -0,0 +1,24 @@
package pay.core.client.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 退 Response DTO
*
* @author jason
*/
@Accessors(chain = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PayRefundUnifiedRespDTO {
/**
* 退
*/
private String channelRefundId;
}

View File

@ -0,0 +1,115 @@
package pay.core.client.impl;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
import lombok.extern.slf4j.Slf4j;
import javax.validation.Validation;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
*
*
* @author
*/
@Slf4j
public abstract class AbstractPayClient<Config extends PayClientConfig> implements PayClient {
/**
*
*/
private final Long channelId;
/**
*
*/
private final String channelCode;
/**
*
*/
protected AbstractPayCodeMapping codeMapping;
/**
*
*/
protected Config config;
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
this.channelId = channelId;
this.channelCode = channelCode;
this.codeMapping = codeMapping;
this.config = config;
}
/**
*
*/
public final void init() {
doInit();
log.info("[init][配置({}) 初始化完成]", config);
}
/**
*
*/
protected abstract void doInit();
public final void refresh(Config config) {
// 判断是否更新
if (config.equals(this.config)) {
return;
}
log.info("[refresh][配置({})发生变化,重新初始化]", config);
this.config = config;
// 初始化
this.init();
}
protected Double calculateAmount(Long amount) {
return amount / 100.0;
}
@Override
public Long getId() {
return channelId;
}
@Override
public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
// 执行短信发送
PayCommonResult<?> result;
try {
result = doUnifiedOrder(reqDTO);
} catch (Throwable ex) {
// 打印异常日志
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
// 封装返回
return PayCommonResult.error(ex);
}
return result;
}
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
throws Throwable;
@Override
public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
PayCommonResult<PayRefundUnifiedRespDTO> resp;
try {
resp = doUnifiedRefund(reqDTO);
} catch (Throwable ex) {
// 记录异常日志
log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
resp = PayCommonResult.error(ex);
}
return resp;
}
protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
}

View File

@ -0,0 +1,81 @@
package pay.core.client.impl;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPcPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXLitePayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
*
*
* @author
*/
@Slf4j
public class PayClientFactoryImpl implements PayClientFactory {
/**
* Map
* key
*/
private final ConcurrentMap<Long, cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<?>> clients = new ConcurrentHashMap<>();
@Override
public PayClient getPayClient(Long channelId) {
cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<?> client = clients.get(channelId);
if (client == null) {
log.error("[getPayClient][渠道编号({}) 找不到客户端]", channelId);
}
return client;
}
@Override
@SuppressWarnings("unchecked")
public <Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
Config config) {
cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config> client = (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) clients.get(channelId);
if (client == null) {
client = this.createPayClient(channelId, channelCode, config);
client.init();
clients.put(client.getId(), client);
} else {
client.refresh(config);
}
}
@SuppressWarnings("unchecked")
private <Config extends PayClientConfig> cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config> createPayClient(
Long channelId, String channelCode, Config config) {
PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode);
Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelEnum));
// 创建客户端
// TODO @芋艿 WX_LITE WX_APP 如果不添加在 项目启动的时候去初始化会报错无法启动。所以我手动加了两个,具体需要你来配
switch (channelEnum) {
case WX_PUB: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
case WX_LITE: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new WXLitePayClient(channelId, (WXPayClientConfig) config); //微信小程序请求支付
case WX_APP: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
case WX_NATIVE: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
case ALIPAY_WAP: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_QR: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_APP: return (cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config));
}
}

View File

@ -0,0 +1,157 @@
package pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* 退
*
* @author jason
*/
@Slf4j
public abstract class AbstractAlipayClient extends AbstractPayClient<cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig> {
protected DefaultAlipayClient client;
public AbstractAlipayClient(Long channelId, String channelCode,
AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) {
super(channelId, channelCode, config, codeMapping);
}
@Override
@SneakyThrows
protected void doInit() {
AlipayConfig alipayConfig = new AlipayConfig();
BeanUtil.copyProperties(config, alipayConfig, false);
this.client = new DefaultAlipayClient(alipayConfig);
}
/**
* PayOrderNotifyRespDTO,
* //https://opendocs.alipay.com/open/203/105286
* @param data
* @return PayOrderNotifyRespDTO
* @throws Exception
*/
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
Map<String, String> params = strToMap(data.getBody());
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
.tradeStatus(params.get("trade_status"))
.successTime(LocalDateTimeUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
.data(data.getBody()).build();
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
Map<String, String> params = strToMap(notifyData.getBody());
PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
.tradeNo(params.get("out_trade_no"))
.reqNo(params.get("out_biz_no"))
.status(PayNotifyRefundStatusEnum.SUCCESS)
.refundSuccessTime(LocalDateTimeUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
.build();
return notifyDTO;
}
@Override
public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
if (notifyData.getParams().containsKey("refund_fee")) {
return true;
} else {
return false;
}
}
@Override
public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
boolean verifyResult = false;
try {
verifyResult = AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getAlipayPublicKey(), StandardCharsets.UTF_8.name(), "RSA2");
} catch (AlipayApiException e) {
log.error("[AlipayClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e);
}
return verifyResult;
}
/**
* 退 alipay.trade.refund
* @param reqDTO 退 request DTO
* @return 退 Response
*/
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
AlipayTradeRefundModel model=new AlipayTradeRefundModel();
model.setTradeNo(reqDTO.getChannelOrderNo());
model.setOutTradeNo(reqDTO.getPayTradeNo());
model.setOutRequestNo(reqDTO.getMerchantRefundId());
model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
model.setRefundReason(reqDTO.getReason());
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
refundRequest.setBizModel(model);
try {
AlipayTradeRefundResponse response = client.execute(refundRequest);
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
if (response.isSuccess()) {
//退款导致触发的异步通知是发送到支付接口中设置的notify_url
//支付宝不返回退款单号,设置为空
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
respDTO.setChannelRefundId("");
return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
}
// 失败。需要抛出异常
return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
} catch (AlipayApiException e) {
// TODO 记录异常日志
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
}
}
/**
* str map
*
* @param s
* @return map
*/
public static Map<String, String> strToMap(String s) {
// TODO @zxy这个可以使用 hutool 的 HttpUtil decodeParams 方法么?
Map<String, String> stringStringMap = new HashMap<>();
// 调整时间格式
String s3 = s.replaceAll("%3A", ":");
// 获取 map
String s4 = s3.replace("+", " ");
String[] split = s4.split("&");
for (String s1 : split) {
String[] split1 = s1.split("=");
stringStringMap.put(split1[0], split1[1]);
}
return stringStringMap;
}
}

View File

@ -0,0 +1,117 @@
package pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import lombok.Data;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Set;
// TODO 芋艿:参数校验
/**
* PayClientConfig
* {@link com.alipay.api.AlipayConfig}
*
* @author
*/
@Data
public class AlipayPayClientConfig implements PayClientConfig {
/**
* - 线
*/
public static final String SERVER_URL_PROD = "https://openapi.alipay.com/gateway.do";
/**
* -
*/
public static final String SERVER_URL_SANDBOX = "https://openapi.alipaydev.com/gateway.do";
/**
* -
*/
public static final Integer MODE_PUBLIC_KEY = 1;
/**
* -
*/
public static final Integer MODE_CERTIFICATE = 2;
/**
* - RSA
*/
public static final String SIGN_TYPE_DEFAULT = "RSA2";
/**
*
* 1. {@link #SERVER_URL_PROD}
* 2. {@link #SERVER_URL_SANDBOX}
*/
@NotBlank(message = "网关地址不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
private String serverUrl;
/**
* ID
*/
@NotBlank(message = "开放平台上创建的应用的 ID不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
private String appId;
/**
* RSA2
* <p>
* {@link #SIGN_TYPE_DEFAULT}
*/
@NotBlank(message = "签名算法类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
private String signType;
/**
*
* 1. {@link #MODE_PUBLIC_KEY} privateKey + alipayPublicKey
* 2. {@link #MODE_CERTIFICATE} appCertContent + alipayPublicCertContent + rootCertContent
*/
@NotNull(message = "公钥类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
private Integer mode;
// ========== 公钥模式 ==========
/**
*
*/
@NotBlank(message = "商户私钥不能为空", groups = {ModePublicKey.class})
private String privateKey;
/**
*
*/
@NotBlank(message = "支付宝公钥字符串不能为空", groups = {ModePublicKey.class})
private String alipayPublicKey;
// ========== 证书模式 ==========
/**
*
*/
@NotBlank(message = "指定商户公钥应用证书内容不能为空", groups = {ModeCertificate.class})
private String appCertContent;
/**
*
*/
@NotBlank(message = "指定支付宝公钥证书内容不能为空", groups = {ModeCertificate.class})
private String alipayPublicCertContent;
/**
*
*/
@NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class})
private String rootCertContent;
public interface ModePublicKey {
}
public interface ModeCertificate {
}
@Override
public Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator) {
return validator.validate(this,
MODE_PUBLIC_KEY.equals(this.getMode()) ? ModePublicKey.class : ModeCertificate.class);
}
}

View File

@ -0,0 +1,28 @@
package pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import java.util.Objects;
/**
* PayCodeMapping
*
* @author
*/
public class AlipayPayCodeMapping extends AbstractPayCodeMapping {
@Override
protected ErrorCode apply0(String apiCode, String apiMsg) {
if (Objects.equals(apiCode, "10000")) {
return GlobalErrorCodeConstants.SUCCESS;
}
// alipay wap api code 返回为null, 暂时定为-9999
if (Objects.equals(apiCode, "-9999")) {
return GlobalErrorCodeConstants.SUCCESS;
}
return null;
}
}

View File

@ -0,0 +1,63 @@
package pay.core.client.impl.alipay;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AbstractAlipayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import lombok.extern.slf4j.Slf4j;
/**
* PC PayClient
* https://opendocs.alipay.com/open/270/105898
*
* @author XGD
*/
@Slf4j
public class AlipayPcPayClient extends AbstractAlipayClient {
public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping());
}
@Override
public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradePagePayModel 请求
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
// 构建 AlipayTradePagePayRequest
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setBizModel(model);
JSONObject bizContent = new JSONObject();
// 参数说明可查看: https://opendocs.alipay.com/open/028r8t?scene=22
bizContent.put("out_trade_no", reqDTO.getMerchantOrderId());
bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
bizContent.put("subject", reqDTO.getSubject());
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
// PC扫码支付的方式支持前置模式和跳转模式。4: 订单码-可定义宽度的嵌入式二维码
bizContent.put("qr_pay_mode", "4");
// 自定义二维码宽度
bizContent.put("qrcode_width", "150");
request.setBizContent(bizContent.toJSONString());
request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl("");
// 执行请求
AlipayTradePagePayResponse response;
try {
response = client.pageExecute(request);
} catch (AlipayApiException e) {
log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
}
// 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
}
}

View File

@ -0,0 +1,55 @@
package pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AbstractAlipayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.extern.slf4j.Slf4j;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* PayClient
* https://opendocs.alipay.com/apis/02890k
*
* @author
*/
@Slf4j
public class AlipayQrPayClient extends AbstractAlipayClient {
public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping());
}
@Override
public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradePrecreateModel 请求
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(reqDTO.getMerchantOrderId());
model.setSubject(reqDTO.getSubject());
model.setBody(reqDTO.getBody());
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
// TODO 芋艿userIp + expireTime
// 构建 AlipayTradePrecreateRequest
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl());
// 执行请求
AlipayTradePrecreateResponse response;
try {
response = client.execute(request);
} catch (AlipayApiException e) {
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e);
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
}
// TODO 芋艿sub Code 需要测试下各种失败的情况
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
}
}

View File

@ -0,0 +1,78 @@
package pay.core.client.impl.alipay;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AbstractAlipayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/**
* PayClient
* https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
*
* @author
*/
@Slf4j
public class AlipayWapPayClient extends AbstractAlipayClient {
public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping());
}
@Override
public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradeWapPayModel 请求
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(reqDTO.getMerchantOrderId());
model.setSubject(reqDTO.getSubject());
model.setBody(reqDTO.getBody());
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿:这里咋整
//TODO 芋艿:这里咋整 jason @芋艿 可以去掉吧,
// TODO 芋艿 似乎这里不用传sellerId
// https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
//model.setSellerId("2088102147948060");
model.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(),"yyyy-MM-dd HH:mm:ss"));
// TODO 芋艿userIp
// 构建 AlipayTradeWapPayRequest
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl());
// 执行请求
AlipayTradeWapPayResponse response;
try {
response = client.pageExecute(request);
} catch (AlipayApiException e) {
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
}
// TODO 芋艿sub Code
if(response.isSuccess() && Objects.isNull(response.getCode()) && Objects.nonNull(response.getBody())){
//成功alipay wap 成功 code 为 null , body 为form 表单
return PayCommonResult.build("-9999", "Success", response, codeMapping);
}else {
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
}
}
}

View File

@ -0,0 +1,56 @@
package pay.core.client.impl.wx;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import java.util.Objects;
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.*;
/**
* PayCodeMapping
*
* @author
*/
public class WXCodeMapping extends AbstractPayCodeMapping {
/**
* -
* weixin-java-pay Result code
*/
public static final String CODE_SUCCESS = "SUCCESS";
/**
* -
*/
public static final String MESSAGE_SUCCESS = "成功";
@Override
protected ErrorCode apply0(String apiCode, String apiMsg) {
if (Objects.equals(apiCode, CODE_SUCCESS)) {
return GlobalErrorCodeConstants.SUCCESS;
}
if (Objects.equals(apiCode, "FAIL")) {
if (Objects.equals(apiMsg, "AppID不存在请检查后再试")) {
return PAY_CONFIG_APP_ID_ERROR;
}
if (Objects.equals(apiMsg, "签名错误,请检查后再试")
|| Objects.equals(apiMsg, "签名错误")) {
return PAY_CONFIG_SIGN_ERROR;
}
}
if (Objects.equals(apiCode, "PARAM_ERROR")) {
if (Objects.equals(apiMsg, "无效的openid")) {
return PAY_OPENID_ERROR;
}
}
if (Objects.equals(apiCode, "CustomErrorCode")) {
if (StrUtil.contains(apiMsg, "必填字段")) {
return PAY_PARAM_MISSING;
}
}
return null;
}
}

View File

@ -0,0 +1,206 @@
package pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
/**
*
*
* @author zwy
*/
@Slf4j
public class WXLitePayClient extends AbstractPayClient<cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig> {
private WxPayService client;
public WXLitePayClient(Long channelId, cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig config) {
super(channelId, PayChannelEnum.WX_LITE.getCode(), config, new WXCodeMapping());
}
@Override
protected void doInit() {
WxPayConfig payConfig = new WxPayConfig();
BeanUtil.copyProperties(config, payConfig, "keyContent");
payConfig.setTradeType(WxPayConstants.TradeType.JSAPI); // 设置使用 JS API 支付方式
// if (StrUtil.isNotEmpty(config.getKeyContent())) {
// payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
// }
if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
// weixin-pay-java 存在 BUG无法直接设置内容所以创建临时文件来解决
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
}
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
// weixin-pay-java 存在 BUG无法直接设置内容所以创建临时文件来解决
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
}
// 真实客户端
this.client = new WxPayServiceImpl();
client.setConfig(payConfig);
}
@Override
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
WxPayMpOrderResult response;
try {
switch (config.getApiVersion()) {
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V2:
response = this.unifiedOrderV2(reqDTO);
break;
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V3:
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
response = new WxPayMpOrderResult();
BeanUtil.copyProperties(responseV3, response, true);
break;
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
}
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
}
private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.outTradeNo(reqDTO.getMerchantOrderId())
.body(reqDTO.getBody())
.totalFee(reqDTO.getAmount().intValue()) // 单位分
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) // v2的时间格式
.spbillCreateIp(reqDTO.getUserIp())
.openid(getOpenid(reqDTO))
.notifyUrl(reqDTO.getNotifyUrl())
.build();
// 执行请求
return client.createOrder(request);
}
private WxPayUnifiedOrderV3Result.JsapiResult unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
request.setOutTradeNo(reqDTO.getMerchantOrderId());
request.setDescription(reqDTO.getBody());
request.setAmount(new WxPayUnifiedOrderV3Request
.Amount()
.setTotal(reqDTO
.getAmount()
.intValue())); // 单位分
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
request.setNotifyUrl(reqDTO.getNotifyUrl());
// 执行请求
return client.createOrderV3(TradeTypeEnum.JSAPI, request);
}
private static String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
if (StrUtil.isEmpty(openid)) {
throw new IllegalArgumentException("支付请求的 openid 不能为空!");
}
return openid;
}
/**
*
* v2 v3
*
* @param data
* @return
* @throws WxPayException
*/
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody());
// 微信支付 v2 回调结果处理
switch (config.getApiVersion()) {
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V2:
return parseOrderNotifyV2(data);
case WXPayClientConfig.API_VERSION_V3:
return parseOrderNotifyV3(data);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
}
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
// 转换结果
Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"),
"支付结果非 SUCCESS");
return PayOrderNotifyRespDTO
.builder()
.orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build();
}
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
// 转换结果
return PayOrderNotifyRespDTO
.builder()
.orderExtensionNo(notifyResult.getOutTradeNo())
.channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid())
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build();
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
//TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
//TODO 需要实现
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,189 @@
package pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
/**
* App
*
* @author zwy
*/
@Slf4j
public class WXNativePayClient extends AbstractPayClient<cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig> {
private WxPayService client;
public WXNativePayClient(Long channelId, cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig config) {
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping());
}
@Override
protected void doInit() {
WxPayConfig payConfig = new WxPayConfig();
BeanUtil.copyProperties(config, payConfig, "keyContent");
payConfig.setTradeType(WxPayConstants.TradeType.NATIVE); // 设置使用 native 支付方式
// if (StrUtil.isNotEmpty(config.getKeyContent())) {
// payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
// }
if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
// weixin-pay-java 存在 BUG无法直接设置内容所以创建临时文件来解决
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
}
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
// weixin-pay-java 存在 BUG无法直接设置内容所以创建临时文件来解决
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
}
// 真实客户端
this.client = new WxPayServiceImpl();
client.setConfig(payConfig);
}
@Override
public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 这里原生的返回的是支付的 url 所以直接使用string接收
// "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
String responseV3;
try {
switch (config.getApiVersion()) {
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V2:
responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
break;
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V3:
responseV3 = this.unifiedOrderV3(reqDTO);
break;
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
}
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
}
private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
//前端
String tradeType = reqDTO.getChannelExtras().get("trade_type");
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest
.newBuilder()
.outTradeNo(reqDTO.getMerchantOrderId())
.body(reqDTO.getBody())
.totalFee(reqDTO.getAmount().intValue()) // 单位分
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.spbillCreateIp(reqDTO.getUserIp())
.notifyUrl(reqDTO.getNotifyUrl())
.productId(tradeType)
.build();
// 执行请求
return client.createOrder(request);
}
private String unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
request.setOutTradeNo(reqDTO.getMerchantOrderId());
request.setDescription(reqDTO.getBody());
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
request.setNotifyUrl(reqDTO.getNotifyUrl());
// 执行请求
return client.createOrderV3(TradeTypeEnum.NATIVE, request);
}
/**
*
* v2 v3
*
* @param data
* @return
* @throws WxPayException
*/
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
log.info("微信支付回调data数据:{}", data.getBody());
// 微信支付 v2 回调结果处理
switch (config.getApiVersion()) {
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V2:
return parseOrderNotifyV2(data);
case WXPayClientConfig.API_VERSION_V3:
return parseOrderNotifyV3(data);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
}
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
// 转换结果
Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"),
"支付结果非 SUCCESS");
return PayOrderNotifyRespDTO
.builder()
.orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build();
}
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
// 转换结果
return PayOrderNotifyRespDTO
.builder()
.orderExtensionNo(notifyResult.getOutTradeNo())
.channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid())
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build();
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
// TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
// TODO 需要实现
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,111 @@
package pay.core.client.impl.wx;
import cn.hutool.core.io.IoUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import lombok.Data;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.constraints.NotBlank;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Set;
/**
* PayClientConfig
* {@link com.github.binarywang.wxpay.config.WxPayConfig}
*
* @author
*/
@Data
public class WXPayClientConfig implements PayClientConfig {
/**
* API - V2
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1
*/
public static final String API_VERSION_V2 = "v2";
/**
* API - V3
* https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
*/
public static final String API_VERSION_V3 = "v3";
/**
* appid
*/
@NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class})
private String appId;
/**
*
*/
@NotBlank(message = "商户号 不能为空", groups = {V2.class, V3.class})
private String mchId;
/**
* API
*/
@NotBlank(message = "API 版本 不能为空", groups = {V2.class, V3.class})
private String apiVersion;
// ========== V2 版本的参数 ==========
/**
*
*/
@NotBlank(message = "商户密钥 不能为空", groups = V2.class)
private String mchKey;
/**
* apiclient_cert.p12 classpath: .
*
*
* {@link #main(String[])}
*/
/// private String keyContent;
// ========== V3 版本的参数 ==========
/**
* apiclient_key.pem classpath: .
*
* {@link #main(String[])}
*/
@NotBlank(message = "apiclient_key 不能为空", groups = V3.class)
private String privateKeyContent;
/**
* apiclient_cert.pem classpath: .
*
* <p>
* {@link #main(String[])}
*/
@NotBlank(message = "apiclient_cert 不能为空", groups = V3.class)
private String privateCertContent;
/**
* apiV3
*/
@NotBlank(message = "apiV3 密钥值 不能为空", groups = V3.class)
private String apiV3Key;
/**
* v2
*/
public interface V2 {
}
/**
* v3
*/
public interface V3 {
}
@Override
public Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator) {
return validator.validate(this, this.getApiVersion().equals(API_VERSION_V2) ? V2.class : V3.class);
}
public static void main(String[] args) throws FileNotFoundException {
String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.p12";
/// String path = "/Users/yunai/Downloads/wx_pay/apiclient_key.pem";
/// String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.pem";
System.out.println(IoUtil.readUtf8(new FileInputStream(path)));
}
}

View File

@ -0,0 +1,199 @@
package pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
/**
* PayClient
*
* @author
*/
@Slf4j
public class WXPubPayClient extends AbstractPayClient<cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig> {
private WxPayService client;
public WXPubPayClient(Long channelId, cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig config) {
super(channelId, PayChannelEnum.WX_PUB.getCode(), config, new WXCodeMapping());
}
@Override
protected void doInit() {
WxPayConfig payConfig = new WxPayConfig();
BeanUtil.copyProperties(config, payConfig, "keyContent");
payConfig.setTradeType(WxPayConstants.TradeType.JSAPI); // 设置使用 JS API 支付方式
// if (StrUtil.isNotEmpty(config.getKeyContent())) {
// payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
// }
if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
// weixin-pay-java 存在 BUG无法直接设置内容所以创建临时文件来解决
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
}
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
// weixin-pay-java 存在 BUG无法直接设置内容所以创建临时文件来解决
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
}
// 真实客户端
this.client = new WxPayServiceImpl();
client.setConfig(payConfig);
}
@Override
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
WxPayMpOrderResult response;
try {
switch (config.getApiVersion()) {
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V2:
response = this.unifiedOrderV2(reqDTO);
break;
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V3:
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
response = new WxPayMpOrderResult();
BeanUtil.copyProperties(responseV3, response, true);
break;
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
}
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
}
private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.outTradeNo(reqDTO.getMerchantOrderId())
.body(reqDTO.getBody())
.totalFee(reqDTO.getAmount().intValue()) // 单位分
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.spbillCreateIp(reqDTO.getUserIp())
.openid(getOpenid(reqDTO))
.notifyUrl(reqDTO.getNotifyUrl())
.build();
// 执行请求
return client.createOrder(request);
}
private WxPayUnifiedOrderV3Result.JsapiResult unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
request.setOutTradeNo(reqDTO.getMerchantOrderId());
request.setDescription(reqDTO.getBody());
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
request.setNotifyUrl(reqDTO.getNotifyUrl());
// 执行请求
return client.createOrderV3(TradeTypeEnum.JSAPI, request);
}
private static String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
if (StrUtil.isEmpty(openid)) {
throw new IllegalArgumentException("支付请求的 openid 不能为空!");
}
return openid;
}
/**
*
* v2 v3
*
* @param data
* @return
* @throws WxPayException
*/
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody());
// 微信支付 v2 回调结果处理
switch (config.getApiVersion()) {
case cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig.API_VERSION_V2:
return parseOrderNotifyV2(data);
case WXPayClientConfig.API_VERSION_V3:
return parseOrderNotifyV3(data);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
}
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
// 转换结果
Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"),
"支付结果非 SUCCESS");
return PayOrderNotifyRespDTO
.builder()
.orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build();
}
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
// 转换结果
return PayOrderNotifyRespDTO
.builder()
.orderExtensionNo(notifyResult.getOutTradeNo())
.channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid())
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build();
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
// TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
// TODO 需要实现
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,60 @@
package pay.core.enums;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*
*
* @author
*/
@Getter
@AllArgsConstructor
public enum PayChannelEnum {
WX_PUB("wx_pub", "微信 JSAPI 支付", WXPayClientConfig.class), // 公众号网页
WX_LITE("wx_lite", "微信小程序支付", WXPayClientConfig.class),
WX_APP("wx_app", "微信 App 支付", WXPayClientConfig.class),
WX_NATIVE("wx_native", "微信 native 支付", WXPayClientConfig.class),
ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class);
/**
*
* <p>
* https://www.pingxx.com/api/支付渠道属性值.html
*/
private final String code;
/**
*
*/
private final String name;
/**
*
*/
private final Class<? extends PayClientConfig> configClass;
/**
*
*/
public static final String WECHAT = "WECHAT";
/**
*
*/
public static final String ALIPAY = "ALIPAY";
public static PayChannelEnum getByCode(String code) {
return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
}
}

View File

@ -0,0 +1,23 @@
package pay.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 退
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum PayChannelRefundRespEnum {
SUCCESS(1, "退款成功"),
FAILURE(2, "退款失败"),
PROCESSING(3,"退款处理中"),
CLOSED(4, "退款关闭");
private final Integer status;
private final String name;
}

View File

@ -0,0 +1,27 @@
package pay.core.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
*
*
* 使 2-002-000-000
*
* @author
*/
public interface PayFrameworkErrorCodeConstants {
ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
// ========== 配置相关相关 2002000100 ==========
ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说,微信支付,配置错了 mchId 或者 mchKey
// ========== 其它相关 2002000900 开头 ==========
ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说,支付少传了金额
ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
}

View File

@ -0,0 +1,20 @@
package pay.core.enums;
/**
* 退, 退
*
* @author jason
*/
public enum PayNotifyRefundStatusEnum {
/**
* 退 trade_status=TRADE_CLOSED 退 trade_status=TRADE_SUCCESS
* 退
*/
SUCCESS,
/**
* 退
* 退
*/
ABNORMAL;
}

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.sms.core.client.dto;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
/**
* Response DTO
@ -32,7 +32,7 @@ public class SmsReceiveRespDTO {
/**
*
*/
private Date receiveTime;
private LocalDateTime receiveTime;
/**
* API

View File

@ -4,6 +4,8 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
@ -11,8 +13,6 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.AcsResponse;
import com.aliyuncs.DefaultAcsClient;
@ -28,7 +28,7 @@ import com.google.common.annotations.VisibleForTesting;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
@ -166,13 +166,13 @@ public class AliyunSmsClient extends AbstractSmsClient {
*/
@JsonProperty("send_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date sendTime;
private LocalDateTime sendTime;
/**
*
*/
@JsonProperty("report_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date reportTime;
private LocalDateTime reportTime;
/**
*
*/

View File

@ -22,7 +22,7 @@ import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.*;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
@ -240,7 +240,7 @@ public class TencentSmsClient extends AbstractSmsClient {
*/
@JsonProperty("user_receive_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date receiveTime;
private LocalDateTime receiveTime;
/**
*

View File

@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
@ -12,7 +13,6 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
@ -23,6 +23,7 @@ import com.yunpian.sdk.model.Template;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
@ -198,7 +199,7 @@ public class YunpianSmsClient extends AbstractSmsClient {
*/
@JsonProperty("user_receive_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date userReceiveTime;
private LocalDateTime userReceiveTime;
}

View File

@ -7,7 +7,7 @@ import lombok.Data;
import org.apache.ibatis.type.JdbcType;
import java.io.Serializable;
import java.util.Date;
import java.time.LocalDateTime;
/**
*
@ -21,12 +21,12 @@ public abstract class BaseDO implements Serializable {
*
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
private LocalDateTime updateTime;
/**
* 使 SysUser id
*

View File

@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.Objects;
/**
@ -22,7 +22,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
Date current = new Date();
LocalDateTime current = LocalDateTime.now();
// 创建时间为空,则以当前时间为插入时间
if (Objects.isNull(baseDO.getCreateTime())) {
baseDO.setCreateTime(current);
@ -49,7 +49,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
// 更新时间为空,则以当前时间为更新时间
Object modifyTime = getFieldValByName("updateTime", metaObject);
if (Objects.isNull(modifyTime)) {
setFieldValByName("updateTime", new Date(), metaObject);
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.test.core.util;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
@ -8,6 +9,7 @@ import uk.co.jemos.podam.api.PodamFactory;
import uk.co.jemos.podam.api.PodamFactoryImpl;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -76,6 +78,10 @@ public class RandomUtils {
return RandomUtil.randomDay(0, RANDOM_DATE_MAX);
}
public static LocalDateTime randomLocalDateTime() {
return LocalDateTimeUtil.of(randomDate());
}
public static Short randomShort() {
return (short) RandomUtil.randomInt(0, Short.MAX_VALUE);
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.apilog.core.filter;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.servlet.ServletUtil;
@ -7,7 +8,6 @@ import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLog;
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.config.WebProperties;
@ -20,7 +20,8 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@ -47,7 +48,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 获得开始时间
Date beginTim = new Date();
LocalDateTime beginTime = LocalDateTime.now();
// 提前获得参数,避免 XssFilter 过滤处理
Map<String, String> queryString = ServletUtil.getParamMap(request);
String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtil.getBody(request) : null;
@ -56,15 +57,15 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
// 继续过滤器
filterChain.doFilter(request, response);
// 正常执行,记录日志
createApiAccessLog(request, beginTim, queryString, requestBody, null);
createApiAccessLog(request, beginTime, queryString, requestBody, null);
} catch (Exception ex) {
// 异常执行,记录日志
createApiAccessLog(request, beginTim, queryString, requestBody, ex);
createApiAccessLog(request, beginTime, queryString, requestBody, ex);
throw ex;
}
}
private void createApiAccessLog(HttpServletRequest request, Date beginTime,
private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime,
Map<String, String> queryString, String requestBody, Exception ex) {
ApiAccessLog accessLog = new ApiAccessLog();
try {
@ -75,7 +76,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
}
}
private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, Date beginTime,
private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, LocalDateTime beginTime,
Map<String, String> queryString, String requestBody, Exception ex) {
// 处理用户信息
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
@ -103,8 +104,8 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
accessLog.setUserIp(ServletUtil.getClientIP(request));
// 持续时间
accessLog.setBeginTime(beginTime);
accessLog.setEndTime(new Date());
accessLog.setDuration((int) DateUtils.diff(accessLog.getEndTime(), accessLog.getBeginTime()));
accessLog.setEndTime(LocalDateTime.now());
accessLog.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.SECONDS));
}
}

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.apilog.core.service;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
/**
* API 访
@ -61,12 +61,12 @@ public class ApiAccessLog {
*
*/
@NotNull(message = "开始请求时间不能为空")
private Date beginTime;
private LocalDateTime beginTime;
/**
*
*/
@NotNull(message = "结束请求时间不能为空")
private Date endTime;
private LocalDateTime endTime;
/**
*
*/

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.apilog.core.service;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
/**
* API
@ -61,7 +61,7 @@ public class ApiErrorLog {
*
*/
@NotNull(message = "异常时间不能为空")
private Date exceptionTime;
private LocalDateTime exceptionTime;
/**
*
*/

View File

@ -30,7 +30,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Date;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
@ -267,7 +268,7 @@ public class GlobalExceptionHandler {
errorLog.setRequestMethod(request.getMethod());
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
errorLog.setUserIp(ServletUtil.getClientIP(request));
errorLog.setExceptionTime(new Date());
errorLog.setExceptionTime(LocalDateTime.now());
}
}

View File

@ -5,8 +5,7 @@ import org.springframework.cloud.gateway.route.Route;
import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMap;
import java.util.Date;
import java.util.Map;
import java.time.LocalDateTime;
/**
* 访
@ -80,11 +79,11 @@ public class AccessLog {
/**
*
*/
private Date startTime;
private LocalDateTime startTime;
/**
*
*/
private Date endTime;
private LocalDateTime endTime;
/**
*
*/

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.gateway.filter.logging;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@ -38,12 +39,13 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMAT;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
/**
* 访
@ -89,8 +91,8 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ?
JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null);
values.put("httpStatus", gatewayLog.getHttpStatus());
values.put("startTime", DateUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMAT));
values.put("endTime", DateUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMAT));
values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER));
values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER));
values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null);
log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values));
}
@ -112,7 +114,7 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
gatewayLog.setRequestUrl(request.getURI().getRawPath());
gatewayLog.setQueryParams(request.getQueryParams());
gatewayLog.setRequestHeaders(request.getHeaders());
gatewayLog.setStartTime(new Date());
gatewayLog.setStartTime(LocalDateTime.now());
gatewayLog.setUserIp(WebFrameworkUtils.getClientIP(exchange));
// 继续 filter 过滤
@ -179,8 +181,9 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
if (body instanceof Flux) {
DataBufferFactory bufferFactory = response.bufferFactory();
// 计算执行时间
gatewayLog.setEndTime(new Date());
gatewayLog.setDuration((int) DateUtils.diff(gatewayLog.getEndTime(), gatewayLog.getStartTime()));
gatewayLog.setEndTime(LocalDateTime.now());
gatewayLog.setDuration((int) (LocalDateTimeUtil.between(gatewayLog.getStartTime(),
gatewayLog.getEndTime()).toMillis()));
// 设置其它字段
gatewayLog.setUserId(SecurityFrameworkUtils.getLoginUserId(exchange));
gatewayLog.setUserType(SecurityFrameworkUtils.getLoginUserType(exchange));

View File

@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("管理后台 - 动态表单 Response VO")
@ -28,6 +28,6 @@ public class BpmFormRespVO extends BpmFormBaseVO {
private List<String> fields;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 用户组分页 Request VO")
@Data
@ -25,6 +25,6 @@ public class BpmUserGroupPageReqVO extends PageParam {
@DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间")
private Date[] createTime;
private LocalDateTime[] createTime;
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
import lombok.*;
import java.time.LocalDateTime;
import java.util.*;
import io.swagger.annotations.*;
@ -14,6 +16,6 @@ public class BpmUserGroupRespVO extends BpmUserGroupBaseVO {
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 流程模型的分页的每一项 Response VO")
@Data
@ -21,7 +21,7 @@ public class BpmModelPageItemRespVO extends BpmModelBaseVO {
private String formName;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
/**
*
@ -39,7 +39,7 @@ public class BpmModelPageItemRespVO extends BpmModelBaseVO {
private Integer version;
@ApiModelProperty(value = "部署时间", required = true)
private Date deploymentTime;
private LocalDateTime deploymentTime;
@ApiModelProperty(value = "中断状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
private Integer suspensionState;

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 流程模型的创建 Request VO")
@Data
@ -21,6 +21,6 @@ public class BpmModelRespVO extends BpmModelBaseVO {
private String bpmnXml;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 流程定义的分页的每一项 Response VO")
@Data
@ -18,6 +18,6 @@ public class BpmProcessDefinitionPageItemRespVO extends BpmProcessDefinitionResp
private String formName;
@ApiModelProperty(value = "部署时间", required = true)
private Date deploymentTime;
private LocalDateTime deploymentTime;
}

View File

@ -1,11 +1,12 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
@ -18,11 +19,11 @@ public class BpmOALeaveBaseVO {
@ApiModelProperty(value = "请假的开始时间", required = true)
@NotNull(message = "开始时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date startTime;
private LocalDateTime startTime;
@ApiModelProperty(value = "请假的结束时间", required = true)
@NotNull(message = "结束时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endTime;
private LocalDateTime endTime;
@ApiModelProperty(value = "请假类型", required = true, example = "1", notes = "参见 bpm_oa_type 枚举")
private Integer type;

View File

@ -15,7 +15,8 @@ public class BpmOALeaveCreateReqVO extends BpmOALeaveBaseVO {
@AssertTrue(message = "结束时间,需要在开始时间之后")
public boolean isEndTimeValid() {
return !getEndTime().before(getStartTime());
return !getEndTime().isBefore(getStartTime());
}
}

View File

@ -5,7 +5,7 @@ import io.swagger.annotations.*;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -24,7 +24,7 @@ public class BpmOALeaveRespVO extends BpmOALeaveBaseVO {
@ApiModelProperty(value = "申请时间", required = true)
@NotNull(message = "申请时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date createTime;
private LocalDateTime createTime;
@ApiModelProperty(value = "流程id")
private String processInstanceId;

View File

@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 流程活动的 Response VO")
@Data
@ -16,9 +16,9 @@ public class BpmActivityRespVO {
private String type;
@ApiModelProperty(value = "流程活动的开始时间", required = true)
private Date startTime;
private LocalDateTime startTime;
@ApiModelProperty(value = "流程活动的结束时间", required = true)
private Date endTime;
private LocalDateTime endTime;
@ApiModelProperty(value = "关联的流程任务的编号", example = "2048", notes = "关联的流程任务,只有 UserTask 等类型才有")
private String taskId;

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -35,6 +35,6 @@ public class BpmProcessInstanceMyPageReqVO extends PageParam {
@ApiModelProperty(value = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date[] createTime;
private LocalDateTime[] createTime;
}

View File

@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("管理后台 - 流程实例的分页 Item Response VO")
@ -30,10 +30,10 @@ public class BpmProcessInstancePageItemRespVO {
private Integer result;
@ApiModelProperty(value = "提交时间", required = true)
private Date createTime;
private LocalDateTime createTime;
@ApiModelProperty(value = "结束时间", required = true)
private Date endTime;
private LocalDateTime endTime;
/**
*

View File

@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@ -28,10 +28,10 @@ public class BpmProcessInstanceRespVO {
private Integer result;
@ApiModelProperty(value = "提交时间", required = true)
private Date createTime;
private LocalDateTime createTime;
@ApiModelProperty(value = "结束时间", required = true)
private Date endTime;
private LocalDateTime endTime;
@ApiModelProperty(value = "提交的表单值", required = true)
private Map<String, Object> formVariables;

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 流程任务的 Done 已完成的分页项 Response VO")
@Data
@ -15,7 +15,7 @@ import java.util.Date;
public class BpmTaskDonePageItemRespVO extends BpmTaskTodoPageItemRespVO {
@ApiModelProperty(value = "结束时间", required = true)
private Date endTime;
private LocalDateTime endTime;
@ApiModelProperty(value = "持续时间", required = true, example = "1000")
private Long durationInMillis;

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -23,10 +23,10 @@ public class BpmTaskDonePageReqVO extends PageParam {
@ApiModelProperty(value = "开始的创建收间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date beginCreateTime;
private LocalDateTime beginCreateTime;
@ApiModelProperty(value = "结束的创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endCreateTime;
private LocalDateTime endCreateTime;
}

View File

@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 流程任务的 Running 进行中的分页项 Response VO")
@Data
@ -17,10 +17,10 @@ public class BpmTaskTodoPageItemRespVO {
private String name;
@ApiModelProperty(value = "接收时间", required = true)
private Date claimTime;
private LocalDateTime claimTime;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
@ApiModelProperty(value = "激活状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
private Integer suspensionState;

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -23,10 +23,10 @@ public class BpmTaskTodoPageReqVO extends PageParam {
@ApiModelProperty(value = "开始的创建收间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date beginCreateTime;
private LocalDateTime beginCreateTime;
@ApiModelProperty(value = "结束的创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endCreateTime;
private LocalDateTime endCreateTime;
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSimpleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
@ -44,7 +45,7 @@ public interface BpmModelConvert {
default BpmModelPageItemRespVO convert(Model model, BpmFormDO form, Deployment deployment, ProcessDefinition processDefinition) {
BpmModelPageItemRespVO modelRespVO = new BpmModelPageItemRespVO();
modelRespVO.setId(model.getId());
modelRespVO.setCreateTime(model.getCreateTime());
modelRespVO.setCreateTime(DateUtils.of(model.getCreateTime()));
// 通用 copy
copyTo(model, modelRespVO);
// Form
@ -57,7 +58,7 @@ public interface BpmModelConvert {
if (modelRespVO.getProcessDefinition() != null) {
modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
modelRespVO.getProcessDefinition().setDeploymentTime(deployment.getDeploymentTime());
modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
}
return modelRespVO;
}
@ -65,7 +66,7 @@ public interface BpmModelConvert {
default BpmModelRespVO convert(Model model) {
BpmModelRespVO modelRespVO = new BpmModelRespVO();
modelRespVO.setId(model.getId());
modelRespVO.setCreateTime(model.getCreateTime());
modelRespVO.setCreateTime(DateUtils.of(model.getCreateTime()));
// 通用 copy
copyTo(model, modelRespVO);
return modelRespVO;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
@ -67,7 +68,7 @@ public interface BpmProcessDefinitionConvert {
BpmProcessDefinitionPageItemRespVO respVO = convert(bean);
respVO.setSuspensionState(bean.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
if (deployment != null) {
respVO.setDeploymentTime(deployment.getDeploymentTime());
respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime()));
}
if (form != null) {
respVO.setFormName(form.getName());

View File

@ -1,17 +1,16 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* Convert
*

View File

@ -17,4 +17,5 @@ public interface BpmMessageConvert {
@Mapping(source = "templateCode", target = "templateCode")
@Mapping(source = "templateParams", target = "templateParams")
SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams);
}

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.bpm.convert.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
@ -151,7 +152,7 @@ public interface BpmTaskConvert {
BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId())
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
.setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
taskExtDO.setCreateTime(task.getCreateTime());
taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime()));
return taskExtDO;
}

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import lombok.*;
import java.time.LocalDateTime;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
@ -46,11 +48,11 @@ public class BpmOALeaveDO extends BaseDO {
/**
*
*/
private Date startTime;
private LocalDateTime startTime;
/**
*
*/
private Date endTime;
private LocalDateTime endTime;
/**
*
*/

View File

@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
@ -80,14 +80,14 @@ public class BpmActivityDO {
*/
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date startTime;
private LocalDateTime startTime;
/**
*
*/
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date endTime;
private LocalDateTime endTime;
private Integer transactionOrder;

View File

@ -11,7 +11,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.Map;
/**
@ -79,7 +79,7 @@ public class BpmProcessInstanceExtDO extends BaseDO {
*
* HistoricProcessInstance endTime
*/
private Date endTime;
private LocalDateTime endTime;
/**
*

View File

@ -8,7 +8,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
/**
* Bpm
@ -67,7 +67,7 @@ public class BpmTaskExtDO extends BaseDO {
*
* HistoricTaskInstance endTime
*/
private Date endTime;
private LocalDateTime endTime;
/**
*

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
@ -46,7 +47,7 @@ public class BpmOALeaveServiceImpl implements BpmOALeaveService {
@Transactional(rollbackFor = Exception.class)
public Long createLeave(Long userId, BpmOALeaveCreateReqVO createReqVO) {
// 插入 OA 请假单
long day = DateUtil.betweenDay(createReqVO.getStartTime(), createReqVO.getEndTime(), false);
long day = LocalDateTimeUtil.between(createReqVO.getStartTime(), createReqVO.getEndTime()).toDays();
BpmOALeaveDO leave = BpmOALeaveConvert.INSTANCE.convert(createReqVO).setUserId(userId).setDay(day)
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
leaveMapper.insert(leave);

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
@ -32,6 +33,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -74,10 +76,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
}
if (pageVO.getBeginCreateTime() != null) {
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getBeginCreateTime()));
}
if (pageVO.getEndCreateTime() != null) {
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getEndCreateTime()));
}
// 执行查询
List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
@ -106,10 +108,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
}
if (pageVO.getBeginCreateTime() != null) {
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getBeginCreateTime()));
}
if (pageVO.getEndCreateTime() != null) {
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getEndCreateTime()));
}
// 执行查询
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
@ -205,7 +207,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 更新任务拓展表为不通过
taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
.setEndTime(new Date()).setReason(reqVO.getReason()));
.setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
}
@Override
@ -249,7 +251,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
public void updateTaskExtComplete(Task task) {
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task)
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) // 不设置也问题不大,因为 Complete 一般是审核通过,已经设置
.setEndTime(new Date());
.setEndTime(LocalDateTime.now());
taskExtMapper.updateByTaskId(taskExtDO);
}
@ -280,7 +282,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 更新任务
taskExtMapper.updateById(new BpmTaskExtDO().setId(taskExt.getId()).setResult(BpmProcessInstanceResultEnum.CANCEL.getResult())
.setEndTime(new Date()).setReason(BpmProcessInstanceDeleteReasonEnum.translateReason(task.getDeleteReason())));
.setEndTime(LocalDateTime.now()).setReason(BpmProcessInstanceDeleteReasonEnum.translateReason(task.getDeleteReason())));
}
});

View File

@ -16,7 +16,7 @@ spring:
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean

View File

@ -17,8 +17,9 @@ import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildLocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
@ -106,7 +107,7 @@ public class BpmUserGroupServiceTest extends BaseDbUnitTest {
BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class, o -> { // 等会查询到
o.setName("芋道源码");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setCreateTime(DateUtils.buildTime(2021, 11, 11));
o.setCreateTime(buildLocalDateTime(2021, 11, 11));
});
userGroupMapper.insert(dbUserGroup);
// 测试 name 不匹配
@ -114,12 +115,13 @@ public class BpmUserGroupServiceTest extends BaseDbUnitTest {
// 测试 status 不匹配
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 createTime 不匹配
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(DateUtils.buildTime(2021, 12, 12))));
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(buildLocalDateTime(2021, 12, 12))));
// 准备参数
BpmUserGroupPageReqVO reqVO = new BpmUserGroupPageReqVO();
reqVO.setName("源码");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setCreateTime((new Date[]{buildTime(2021, 11, 10),buildTime(2021, 11, 12)}));
reqVO.setCreateTime((new LocalDateTime[]{buildLocalDateTime(2021, 11, 10),
buildLocalDateTime(2021, 11, 12)}));
// 调用
PageResult<BpmUserGroupDO> pageResult = userGroupService.getUserGroupPage(reqVO);

View File

@ -5,7 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("RPC 服务 - API 访问日志创建 Request DTO")
@Data
@ -40,10 +40,10 @@ public class ApiAccessLogCreateReqDTO {
@ApiModelProperty(value = "开始时间", required = true)
@NotNull(message = "开始请求时间不能为空")
private Date beginTime;
private LocalDateTime beginTime;
@ApiModelProperty(value = "结束时间", required = true)
@NotNull(message = "结束请求时间不能为空")
private Date endTime;
private LocalDateTime endTime;
@ApiModelProperty(value = "执行时长,单位:毫秒", required = true)
@NotNull(message = "执行时长不能为空")
private Integer duration;

View File

@ -5,7 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("RPC 服务 - API 错误日志创建 Request DTO")
@Data
@ -40,7 +40,7 @@ public class ApiErrorLogCreateReqDTO {
@ApiModelProperty(value = "异常时间", required = true)
@NotNull(message = "异常时间不能为空")
private Date exceptionTime;
private LocalDateTime exceptionTime;
@ApiModelProperty(value = "异常名", required = true)
@NotNull(message = "异常名不能为空")
private String exceptionName;

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 代码生成字段定义 Response VO")
@Data
@ -18,6 +18,6 @@ public class CodegenColumnRespVO extends CodegenColumnBaseVO {
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -26,6 +26,6 @@ public class CodegenTablePageReqVO extends PageParam {
@ApiModelProperty(value = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date[] createTime;
private LocalDateTime[] createTime;
}

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 代码生成表定义 Response VO")
@Data
@ -21,9 +21,9 @@ public class CodegenTableRespVO extends CodegenTableBaseVO {
private Integer dataSourceConfigId;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间", required = true)
private Date updateTime;
private LocalDateTime updateTime;
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.infra.controller.admin.db.vo;
import lombok.*;
import java.time.LocalDateTime;
import java.util.*;
import io.swagger.annotations.*;
@ -14,6 +16,6 @@ public class DataSourceConfigRespVO extends DataSourceConfigBaseVO {
private Integer id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -26,6 +26,6 @@ public class FileConfigPageReqVO extends PageParam {
@ApiModelProperty(value = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date[] createTime;
private LocalDateTime[] createTime;
}

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 文件配置 Response VO")
@Data
@ -31,6 +31,6 @@ public class FileConfigRespVO extends FileConfigBaseVO {
private FileClientConfig config;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -26,6 +26,6 @@ public class FilePageReqVO extends PageParam {
@ApiModelProperty(value = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date[] createTime;
private LocalDateTime[] createTime;
}

View File

@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel(value = "管理后台 - 文件 Response VO", description = "不返回 content 字段,太大")
@Data
@ -26,6 +26,6 @@ public class FileRespVO {
private Integer size;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -5,7 +5,7 @@ import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -54,12 +54,12 @@ public class ApiAccessLogBaseVO {
@ApiModelProperty(value = "开始请求时间", required = true)
@NotNull(message = "开始请求时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date beginTime;
private LocalDateTime beginTime;
@ApiModelProperty(value = "结束请求时间", required = true)
@NotNull(message = "结束请求时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endTime;
private LocalDateTime endTime;
@ApiModelProperty(value = "执行时长", required = true, example = "100")
@NotNull(message = "执行时长不能为空")

View File

@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
/**
* API 访 Excel VO
@ -48,10 +48,10 @@ public class ApiAccessLogExcelVO {
private String userAgent;
@ExcelProperty("开始请求时间")
private Date beginTime;
private LocalDateTime beginTime;
@ExcelProperty("结束请求时间")
private Date endTime;
private LocalDateTime endTime;
@ExcelProperty("执行时长")
private Integer duration;

View File

@ -5,7 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -27,7 +27,7 @@ public class ApiAccessLogExportReqVO {
@ApiModelProperty(value = "开始时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date[] beginTime;
private LocalDateTime[] beginTime;
@ApiModelProperty(value = "执行时长", example = "100", notes = "大于等于,单位:毫秒")
private Integer duration;

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -32,7 +32,7 @@ public class ApiAccessLogPageReqVO extends PageParam {
@ApiModelProperty(value = "开始时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date[] beginTime;
private LocalDateTime[] beginTime;
@ApiModelProperty(value = "执行时长", example = "100", notes = "大于等于,单位:毫秒")
private Integer duration;

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
import java.time.LocalDateTime;
@ApiModel("管理后台 - API 访问日志 Response VO")
@Data
@ -18,6 +18,6 @@ public class ApiAccessLogRespVO extends ApiAccessLogBaseVO {
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
private LocalDateTime createTime;
}

View File

@ -5,7 +5,7 @@ import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -55,7 +55,7 @@ public class ApiErrorLogBaseVO {
@ApiModelProperty(value = "异常发生时间", required = true)
@NotNull(message = "异常发生时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date exceptionTime;
private LocalDateTime exceptionTime;
@ApiModelProperty(value = "异常名", required = true)
@NotNull(message = "异常名不能为空")

View File

@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
/**
* API Excel VO
@ -48,7 +48,7 @@ public class ApiErrorLogExcelVO {
private String userAgent;
@ExcelProperty("异常发生时间")
private Date exceptionTime;
private LocalDateTime exceptionTime;
@ExcelProperty("异常名")
private String exceptionName;
@ -75,14 +75,14 @@ public class ApiErrorLogExcelVO {
private Integer exceptionLineNumber;
@ExcelProperty("创建时间")
private Date createTime;
private LocalDateTime createTime;
@ExcelProperty(value = "处理状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.API_ERROR_LOG_PROCESS_STATUS)
private Integer processStatus;
@ExcelProperty("处理时间")
private Date processTime;
private LocalDateTime processTime;
@ExcelProperty("处理用户编号")
private Integer processUserId;

Some files were not shown because too many files have changed in this diff Show More