diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java index 825ea22..035eb42 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java @@ -77,6 +77,15 @@ public class R implements Serializable return restResult(null,responseEnum.getCode(), responseEnum.getMsg()); } + /** + * 构建重复提交错误消息 + * @param time + * @return + */ + public R buildRepeatRequest(int time){ + return restResult(null,ResponseEnum.REPEAT_REQUEST_ERROR.getCode(),time+"分钟内"+ResponseEnum.REPEAT_REQUEST_ERROR.getMsg()); + } + private static R restResult(T data, int code, String msg) { R apiResult = new R<>(); diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java index e018622..e84e13a 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java @@ -10,27 +10,20 @@ public enum ResponseEnum { //======================系统异常========================, SERVER_ERROR(500, "系统繁忙,请稍候重试!"), NOT_AUTH(500, "未登录!"), + REPEAT_REQUEST_ERROR(500, "请勿重复请求"), PARAM_ERROR(400, "参数异常!"), - NOT_EXIST(110001, "查询为空"), - PAY_CONFIG_ERROR(110002, "支付配置未启用或未配置!"), - - //======================订单异常======================== + //======================业务异常======================== + NOT_EXIST(110001, "查询为空"), + PAY_CONFIG_ERROR(110002, "支付配置未启用或未配置!"), /** * 订单已过期,当前端看到该状态码的时候,提示订单信息已过期,请重新确认后提交,此时用户点击确定,前端刷新页面。 */ - ORDER_EXPIRED(210001, "订单已过期"), - - /** - * 请勿重复提交订单, - * 1.当前端遇到该异常时,说明前端防多次点击没做好 - * 2.提示用户 订单已发生改变,请勿重复下单 - */ - REPEAT_ORDER(210002,"请勿重复提交订单"), - - ORDER_CANCEL(210003, "该订单已取消,请重新下单!"), - ORDER_REPEAT_PAY(210004, "该订单已支付,请勿重复支付!"), + ORDER_EXPIRED(110003, "订单已过期"), + ORDER_CANCEL(110004, "该订单已取消,请重新下单!"), + ORDER_REPEAT_PAY(110005, "该订单已支付,请勿重复支付!"), + REFUND_SING_ERROR(110006, "签名错误!"), diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/utils/MD5Util.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/utils/MD5Util.java new file mode 100644 index 0000000..461bf1f --- /dev/null +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/utils/MD5Util.java @@ -0,0 +1,140 @@ +package com.bnyer.common.core.utils; + +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description : + */ +public class MD5Util { + + /** + * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合 + */ + protected static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + protected static MessageDigest messagedigest = null; + static { + try { + messagedigest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException nsaex) { + System.err.println(MD5Util.class.getName() + + "初始化失败,MessageDigest不支持MD5Util。"); + nsaex.printStackTrace(); + } + } + + /** + * 生成字符串的md5校验值 + * + * @param s + * @return + */ + public static String getMD5String(String s) { + return getMD5String(s.getBytes()); + } + public static String getStaffMD5String(String staffId,String s) { + + return getMD5String((staffId+s).getBytes()); + } + public static String getUserMD5String(String userId,String s) { + return getMD5String(s.getBytes()); + } + + /** + * 判断字符串的md5校验码是否与一个已知的md5码相匹配 + * + * @param password 要校验的字符串 + * @param md5PwdStr 已知的md5校验码 + * @return + */ + public static boolean checkPassword(String password, String md5PwdStr) { + String s = getMD5String(password); + return s.equals(md5PwdStr); + } + + /** + * 生成文件的md5校验值 + * + * @param file + * @return + * @throws IOException + */ + public static String getFileMD5String(File file) throws IOException { + InputStream fis; + fis = new FileInputStream(file); + byte[] buffer = new byte[1024]; + int numRead = 0; + while ((numRead = fis.read(buffer)) > 0) { + messagedigest.update(buffer, 0, numRead); + } + fis.close(); + return bufferToHex(messagedigest.digest()); + } + public static String getFileMD5String(InputStream fis) throws IOException { + byte[] buffer = new byte[1024]; + int numRead = 0; + while ((numRead = fis.read(buffer)) > 0) { + messagedigest.update(buffer, 0, numRead); + } + fis.close(); + return bufferToHex(messagedigest.digest()); + } + + public static String getMD5String(byte[] bytes) { + messagedigest.update(bytes); + return bufferToHex(messagedigest.digest()); + } + + private static String bufferToHex(byte bytes[]) { + return bufferToHex(bytes, 0, bytes.length); + } + + private static String bufferToHex(byte bytes[], int m, int n) { + StringBuffer stringbuffer = new StringBuffer(2 * n); + int k = m + n; + for (int l = m; l < k; l++) { + appendHexPair(bytes[l], stringbuffer); + } + return stringbuffer.toString(); + } + + private static void appendHexPair(byte bt, StringBuffer stringbuffer) { + char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同 + char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换 + stringbuffer.append(c0); + stringbuffer.append(c1); + } + + + /** + * MD5方法 + * + * @param text 明文 + * @param key 密钥 + * @return 密文 + * @throws Exception + */ + public static String md5(String text, String key) throws Exception { + //加密后的字符串 + System.out.println("text + key "+text + key); + String encodeStr= DigestUtils.md5Hex(text + key); + System.out.println("MD5加密后的字符串为:encodeStr="+encodeStr); + return encodeStr; + } + + public static void main(String[] args) throws IOException { + System.out.println(MD5Util.getMD5String("RVWU202305121022211042vip")); + + } + +} diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/controller/UserVipRecordController.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/controller/UserVipRecordController.java new file mode 100644 index 0000000..82d0413 --- /dev/null +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/controller/UserVipRecordController.java @@ -0,0 +1,39 @@ +package com.bnyer.img.controller; + +import com.bnyer.common.core.domain.R; +import com.bnyer.img.query.UserVipQuery; +import com.bnyer.img.query.UserVipRecordQuery; +import com.bnyer.img.service.UserVipRecordService; +import com.bnyer.img.service.UserVipService; +import com.bnyer.img.vo.UserVipRecordVo; +import com.bnyer.img.vo.UserVipVo; +import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author :WXC + * @Date :2023/05/10 + * @description : + */ +@Api(value = "用户会员记录相关接口",tags = "用户会员记录相关接口") +@RestController +@RequestMapping("/img/mini/vipRecord") +@Slf4j +public class UserVipRecordController { + + @Resource + private UserVipRecordService userVipRecordService; + + @Operation(summary="获取用户会员记录",description = "获取用户会员记录") + @GetMapping(value = "/queryUserVipRecord") + public R queryUserVipRecord(UserVipRecordQuery query){ + return R.ok(userVipRecordService.queryUserVipRecord(query)); + } + + +} diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/mapper/UserVipRecordMapper.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/mapper/UserVipRecordMapper.java index 113a89d..db8dd32 100644 --- a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/mapper/UserVipRecordMapper.java +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/mapper/UserVipRecordMapper.java @@ -2,8 +2,11 @@ package com.bnyer.img.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.bnyer.common.core.domain.UserVipRecord; +import com.bnyer.img.query.UserVipRecordQuery; +import com.bnyer.img.vo.UserVipRecordVo; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserVipRecordMapper extends BaseMapper { + UserVipRecordVo queryUserVipRecord(UserVipRecordQuery query); } \ No newline at end of file diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/query/UserVipRecordQuery.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/query/UserVipRecordQuery.java new file mode 100644 index 0000000..3f2c6a6 --- /dev/null +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/query/UserVipRecordQuery.java @@ -0,0 +1,26 @@ +package com.bnyer.img.query; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + * @author :WXC + * @Date :2023/03/31 + * @description : + */ +@Getter +@Setter +@ApiModel("用户会员记录查询请求对象") +public class UserVipRecordQuery { + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "用户id") + private Long userId; + /** + * 用户客户端类型 + */ + private Integer userClientType; +} diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/UserVipRecordService.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/UserVipRecordService.java index d6d4c08..7520259 100644 --- a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/UserVipRecordService.java +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/UserVipRecordService.java @@ -2,6 +2,9 @@ package com.bnyer.img.service; import com.bnyer.common.core.dto.AddUserVipRecordDto; import com.bnyer.common.core.dto.PayUserVipDto; +import com.bnyer.img.query.UserVipRecordQuery; +import com.bnyer.img.vo.UserVipRecordVo; +import com.bnyer.img.vo.UserVipVo; public interface UserVipRecordService { @@ -20,4 +23,10 @@ public interface UserVipRecordService { */ void addUserVipRecord(AddUserVipRecordDto dto); + /** + * 查询用户会员记录 + * @param query + * @return + */ + UserVipRecordVo queryUserVipRecord(UserVipRecordQuery query); } diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/impl/UserVipServiceRecordImpl.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/impl/UserVipServiceRecordImpl.java index 2646c0e..ca45af0 100644 --- a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/impl/UserVipServiceRecordImpl.java +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/service/impl/UserVipServiceRecordImpl.java @@ -6,13 +6,20 @@ import com.bnyer.common.core.domain.UserVipRecord; import com.bnyer.common.core.domain.VipOrder; import com.bnyer.common.core.dto.AddUserVipRecordDto; import com.bnyer.common.core.dto.PayUserVipDto; +import com.bnyer.common.core.enums.ResponseEnum; import com.bnyer.common.core.exception.ServiceException; +import com.bnyer.common.core.utils.StringUtils; import com.bnyer.common.core.utils.bean.EntityConvertUtil; +import com.bnyer.common.core.vo.UserInfoVo; import com.bnyer.common.redis.service.RedissonService; +import com.bnyer.common.security.utils.SecurityUtils; import com.bnyer.img.constants.UserVipOrderStatusConstant; import com.bnyer.img.enums.EnumUserVipRecordStatus; import com.bnyer.img.mapper.UserVipRecordMapper; +import com.bnyer.img.query.UserVipRecordQuery; import com.bnyer.img.service.UserVipRecordService; +import com.bnyer.img.vo.UserVipRecordVo; +import com.bnyer.img.vo.UserVipVo; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; @@ -20,6 +27,7 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Date; +import java.util.Objects; import java.util.concurrent.TimeUnit; @Service @@ -91,4 +99,27 @@ public class UserVipServiceRecordImpl implements UserVipRecordService { userVipRecord.setStatus(EnumUserVipRecordStatus.VALID.getStatus()); userVipRecordMapper.insert(userVipRecord); } + + /** + * 获取用户会员记录 + * @param query + * @return + */ + @Override + public UserVipRecordVo queryUserVipRecord(UserVipRecordQuery query) { + UserInfoVo userInfo = SecurityUtils.getUserInfo(); + if (Objects.isNull(query.getId())){ + query.setUserId(userInfo.getId()); + query.setUserClientType(userInfo.getUserClientType()); + } + UserVipRecordVo userVipRecordVo = userVipRecordMapper.queryUserVipRecord(query); + if (Objects.isNull(userVipRecordVo)){ + userVipRecordVo = new UserVipRecordVo(); + userVipRecordVo.setIsVip("0"); + }else { + // TODO: 2023/05/12 同步会员到期状态 + userVipRecordVo.setIsVip("1"); + } + return userVipRecordVo; + } } diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/vo/UserVipRecordVo.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/vo/UserVipRecordVo.java new file mode 100644 index 0000000..6c03a22 --- /dev/null +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/vo/UserVipRecordVo.java @@ -0,0 +1,56 @@ +package com.bnyer.img.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +@NoArgsConstructor +@ApiModel(value = "用户会员记录查询响应对象") +public class UserVipRecordVo { + + @ApiModelProperty(value="id") + private Long id; + + @ApiModelProperty(value="订单号") + private String orderNo; + + @ApiModelProperty(value="用户id") + private Long userId; + + @ApiModelProperty(value="用户手机号") + private String phone; + + @ApiModelProperty(value="vip表id") + private Long vipId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value="开始时间") + private Date startTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value="到期时间") + private Date endTime; + + @ApiModelProperty(value="状态(0->已到期;1->已生效;)") + private Integer status; + + @ApiModelProperty(value="vip名称") + private String vipName; + + @ApiModelProperty(value = "vip类型名称") + private String vipTypeName; + + @ApiModelProperty(value = "用户客户端类型:10用户-抖音 20用户-快手 30用户-微信 40艺术家-微信") + private Integer userClientType; + + @ApiModelProperty(value = "是否开通会员:0 未开通 1已开通") + private String isVip; + +} diff --git a/bnyer-services/bnyer-img/src/main/resources/com/bnyer/img/mapper/UserVipRecordMapper.xml b/bnyer-services/bnyer-img/src/main/resources/com/bnyer/img/mapper/UserVipRecordMapper.xml index a037825..9b35c02 100644 --- a/bnyer-services/bnyer-img/src/main/resources/com/bnyer/img/mapper/UserVipRecordMapper.xml +++ b/bnyer-services/bnyer-img/src/main/resources/com/bnyer/img/mapper/UserVipRecordMapper.xml @@ -22,7 +22,33 @@ - id,order_no,user_id , phone,vip_name,vip_type_name,user_client_type,status, vip_id, start_time, end_time, is_show, create_time, update_time, - sort + a.id, + a.order_no, + a.user_id , + a.phone,vip_name, + a.vip_type_name, + a.user_client_type, + a.`status`, vip_id, + a.start_time, end_time, + a.is_show, + a.create_time, + a.update_time, + a.sort + diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java new file mode 100644 index 0000000..c27d369 --- /dev/null +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java @@ -0,0 +1,63 @@ +package com.bnyer.order.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description : 防重复提交 + */ +@Target({ METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface LimitRepeatRequest { + + /** + * 限制当前用户同一个api 不能重复提交 + */ + String SELF="self"; + + /** + * 限制所有用户同一个参数不能重复提交,如我新增了用户 张三, 那么其他人不能再并发情况下重复添加张三 + */ + String ALL_USER="all"; + + /** + * 当前时间内 api 只能请求一次,单位秒 + * @return + */ + int time() default 5; + + /** + * 对部分参数做重复请求限制 + * @return + */ + String[] bodyParam() default {}; + + /** + * 是否对全部参数做重复请求限制 + * @return + */ + boolean bodyAllParam() default false; + + /** + * 重复请求限制的用户范围 + * LimitRepeatRequest.SELF:针对当前登录用户 + * LimitRepeatRequest.ALL_USER:针对所有用户 + * + * @return + */ + String userRange() default SELF; + + /** + * 错误提示信息 + * @return + */ + String message() default ""; + +} diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java new file mode 100644 index 0000000..b13f80b --- /dev/null +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java @@ -0,0 +1,150 @@ +package com.bnyer.order.aop; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.bnyer.common.core.domain.R; +import com.bnyer.common.core.utils.MD5Util; +import com.bnyer.common.core.utils.StringUtils; +import com.bnyer.common.core.vo.UserInfoVo; +import com.bnyer.common.redis.service.RedisService; +import com.bnyer.common.security.utils.SecurityUtils; +import com.bnyer.order.annotation.LimitRepeatRequest; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description :重复提交限制aop + */ +@Slf4j +@Aspect +@Component +public class LimitRepeatRequestAspect { + + @Autowired + private RedisService redisService; + + @Around("@annotation(limitRepeatRequest)") + public Object around(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) throws Throwable{ + String key = getKey(joinPoint,limitRepeatRequest); + Object cacheObject = redisService.getCacheObject(key); + Object message; + if (Objects.nonNull(cacheObject)){ + if (StringUtils.isNotBlank(limitRepeatRequest.message())){ + message = limitRepeatRequest.message(); + }else { + message = new R().buildRepeatRequest(limitRepeatRequest.time()); + } + }else { + redisService.setCacheObject(key,"1",(long)limitRepeatRequest.time(), TimeUnit.SECONDS); + message = joinPoint.proceed(); + } + return message; + } + + /** + * 获取rediskey + * + * @param joinPoint + * @param limitRepeatRequest + * @return + */ + private String getKey(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) { + UserInfoVo userInfo = SecurityUtils.getUserInfo(); + Method currentMethod = getCurrentMethod(joinPoint); + //最后拼接好的key + StringBuilder key = new StringBuilder("LimitRepeatRequestAspect#" + currentMethod.getName()); + //限制范围 + String userRange = limitRepeatRequest.userRange(); + if (LimitRepeatRequest.SELF.equals(userRange)){ + key.append("#"); + key.append(userInfo.getUserClientType()); + key.append("#"); + key.append(userInfo.getId()); + } + //获取请求参数 + JSONObject requestParams = getRequestParams(joinPoint); + //部分参数做重复请求限制 + if (Objects.nonNull(requestParams)){ + String[] bodyParam = limitRepeatRequest.bodyParam(); + if (bodyParam != null){ + for (String param : bodyParam) { + key.append("#"); + Object obj = requestParams.get(param); + if (obj instanceof JSONArray){ + JSONArray jsonArray = requestParams.getJSONArray(param); + if (CollUtil.isNotEmpty(jsonArray)){ + for (Object o : jsonArray) { + if (o instanceof String){ + key.append(o); + } + } + } + }else if(obj instanceof String){ + String value = requestParams.containsKey(param)?requestParams.getString(param):""; + key.append(value); + }else if(obj instanceof Integer){ + String value = requestParams.containsKey(param)?requestParams.getString(param):""; + key.append(value); + } + } + } + } + //全部参数做重复请求限制 + if (limitRepeatRequest.bodyAllParam()){ + String jsonStr = JSON.toJSONString(requestParams); + key.append("#"); + key.append(jsonStr); + } + return MD5Util.getMD5String(key.toString()); + } + + /** + * 获取请求参数 + * @param joinPoint + * @return + */ + private JSONObject getRequestParams(ProceedingJoinPoint joinPoint) { + Object[] args = joinPoint.getArgs(); + if (args != null && args.length != 0){ + String jsonString = JSON.toJSONString(args); + JSONArray parseArray = JSON.parseArray(jsonString); + return parseArray.getJSONObject(0); + } + return null; + } + + /** + * 获取当前方法 + * @param joinPoint + * @return + */ + private Method getCurrentMethod(ProceedingJoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + if (!(signature instanceof MethodSignature)){ + throw new IllegalArgumentException("该注解只能作用于方法上面"); + } + MethodSignature methodSignature = (MethodSignature)signature; + Object target = joinPoint.getTarget(); + try { + Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); + return method; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java index 2208272..39611fa 100644 --- a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java @@ -2,6 +2,7 @@ package com.bnyer.order.controller; import com.bnyer.common.core.domain.R; import com.bnyer.common.core.web.controller.BaseController; +import com.bnyer.order.annotation.LimitRepeatRequest; import com.bnyer.order.bean.dto.AddVipOrderDto; import com.bnyer.order.bean.query.VipOrderExtQuery; import com.bnyer.order.bean.query.VipOrderQuery; @@ -38,6 +39,7 @@ public class VipOrderController extends BaseController { /** * 提交订单 */ + @LimitRepeatRequest(bodyAllParam = true, message = "您的订单已提交,请勿频繁操作") @PostMapping("/addVipOrder") @Operation(summary = "生成会员订单,返回订单号" , description = "生成会员订单,返回订单号,通过订单号调用支付接口") public R addVipOrder(@Valid @RequestBody AddVipOrderDto addVipOrderDto) { diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java new file mode 100644 index 0000000..605b466 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java @@ -0,0 +1,63 @@ +package com.bnyer.pay.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description : 防重复提交 + */ +@Target({ METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface LimitRepeatRequest { + + /** + * 限制当前用户同一个api 不能重复提交 + */ + String SELF="self"; + + /** + * 限制所有用户同一个参数不能重复提交,如我新增了用户 张三, 那么其他人不能再并发情况下重复添加张三 + */ + String ALL_USER="all"; + + /** + * 当前时间内 api 只能请求一次,单位秒 + * @return + */ + int time() default 5; + + /** + * 对部分参数做重复请求限制 + * @return + */ + String[] bodyParam() default {}; + + /** + * 是否对全部参数做重复请求限制 + * @return + */ + boolean bodyAllParam() default false; + + /** + * 重复请求限制的用户范围 + * LimitRepeatRequest.SELF:针对当前登录用户 + * LimitRepeatRequest.ALL_USER:针对所有用户 + * + * @return + */ + String userRange() default SELF; + + /** + * 错误提示信息 + * @return + */ + String message() default ""; + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java new file mode 100644 index 0000000..e3116f0 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java @@ -0,0 +1,150 @@ +package com.bnyer.pay.aop; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.bnyer.common.core.domain.R; +import com.bnyer.common.core.utils.MD5Util; +import com.bnyer.common.core.utils.StringUtils; +import com.bnyer.common.core.vo.UserInfoVo; +import com.bnyer.common.redis.service.RedisService; +import com.bnyer.common.security.utils.SecurityUtils; +import com.bnyer.pay.annotation.LimitRepeatRequest; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description :重复提交限制aop + */ +@Slf4j +@Aspect +@Component +public class LimitRepeatRequestAspect { + + @Autowired + private RedisService redisService; + + @Around("@annotation(limitRepeatRequest)") + public Object around(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) throws Throwable{ + String key = getKey(joinPoint,limitRepeatRequest); + Object cacheObject = redisService.getCacheObject(key); + Object message; + if (Objects.nonNull(cacheObject)){ + if (StringUtils.isNotBlank(limitRepeatRequest.message())){ + message = limitRepeatRequest.message(); + }else { + message = new R().buildRepeatRequest(limitRepeatRequest.time()); + } + }else { + redisService.setCacheObject(key,"1",(long)limitRepeatRequest.time(), TimeUnit.SECONDS); + message = joinPoint.proceed(); + } + return message; + } + + /** + * 获取rediskey + * + * @param joinPoint + * @param limitRepeatRequest + * @return + */ + private String getKey(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) { + UserInfoVo userInfo = SecurityUtils.getUserInfo(); + Method currentMethod = getCurrentMethod(joinPoint); + //最后拼接好的key + StringBuilder key = new StringBuilder("LimitRepeatRequestAspect#" + currentMethod.getName()); + //限制范围 + String userRange = limitRepeatRequest.userRange(); + if (LimitRepeatRequest.SELF.equals(userRange)){ + key.append("#"); + key.append(userInfo.getUserClientType()); + key.append("#"); + key.append(userInfo.getId()); + } + //获取请求参数 + JSONObject requestParams = getRequestParams(joinPoint); + //部分参数做重复请求限制 + if (Objects.nonNull(requestParams)){ + String[] bodyParam = limitRepeatRequest.bodyParam(); + if (bodyParam != null){ + for (String param : bodyParam) { + key.append("#"); + Object obj = requestParams.get(param); + if (obj instanceof JSONArray){ + JSONArray jsonArray = requestParams.getJSONArray(param); + if (CollUtil.isNotEmpty(jsonArray)){ + for (Object o : jsonArray) { + if (o instanceof String){ + key.append(o); + } + } + } + }else if(obj instanceof String){ + String value = requestParams.containsKey(param)?requestParams.getString(param):""; + key.append(value); + }else if(obj instanceof Integer){ + String value = requestParams.containsKey(param)?requestParams.getString(param):""; + key.append(value); + } + } + } + } + //全部参数做重复请求限制 + if (limitRepeatRequest.bodyAllParam()){ + String jsonStr = JSON.toJSONString(requestParams); + key.append("#"); + key.append(jsonStr); + } + return MD5Util.getMD5String(key.toString()); + } + + /** + * 获取请求参数 + * @param joinPoint + * @return + */ + private JSONObject getRequestParams(ProceedingJoinPoint joinPoint) { + Object[] args = joinPoint.getArgs(); + if (args != null && args.length != 0){ + String jsonString = JSON.toJSONString(args); + JSONArray parseArray = JSON.parseArray(jsonString); + return parseArray.getJSONObject(0); + } + return null; + } + + /** + * 获取当前方法 + * @param joinPoint + * @return + */ + private Method getCurrentMethod(ProceedingJoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + if (!(signature instanceof MethodSignature)){ + throw new IllegalArgumentException("该注解只能作用于方法上面"); + } + MethodSignature methodSignature = (MethodSignature)signature; + Object target = joinPoint.getTarget(); + try { + Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); + return method; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/RefundDto.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/RefundDto.java index a48386d..ff096ee 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/RefundDto.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/RefundDto.java @@ -1,9 +1,13 @@ package com.bnyer.pay.bean.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import javax.validation.constraints.NotBlank; +import java.math.BigDecimal; + /** * @author :WXC * @Date :2023/05/08 @@ -14,4 +18,16 @@ import lombok.Setter; @NoArgsConstructor public class RefundDto { + @NotBlank(message = "支付订单号不能为空") + @ApiModelProperty(value = "支付订单号") + private String payId; + + @ApiModelProperty(value = " 退款金额(元) ,如果不传则全额退款", example = "0.01") + private BigDecimal refundAmount; + + @NotBlank(message = "签名不能为空") + @ApiModelProperty(value = "签名") + private String sign; + + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/UnifiedOrderVo.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/UnifiedOrderVo.java index 4772706..dac9b91 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/UnifiedOrderVo.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/UnifiedOrderVo.java @@ -13,9 +13,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class UnifiedOrderVo { - @ApiModelProperty(value = "应用id") - private String appId; - @ApiModelProperty(value = "内部系统支付单号/开发者测单号") private String outOrderNo; diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java index c0da0a4..1296451 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java @@ -1,11 +1,17 @@ package com.bnyer.pay.controller; import com.bnyer.common.core.domain.R; +import com.bnyer.common.core.enums.ResponseEnum; +import com.bnyer.common.core.exception.ServiceException; +import com.bnyer.pay.annotation.LimitRepeatRequest; +import com.bnyer.pay.bean.dto.RefundDto; import com.bnyer.pay.bean.dto.UnifiedOrderDto; import com.bnyer.pay.bean.dto.QueryOrderDto; +import com.bnyer.pay.bean.vo.ThirdRefundVo; import com.bnyer.pay.service.UnifiedPayService; import com.bnyer.pay.bean.vo.UnifiedOrderVo; import com.bnyer.pay.bean.vo.QueryOrderVo; +import com.bnyer.pay.utils.PaymentRefundUtil; import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; @@ -32,6 +38,7 @@ public class UnifiedPayController { @Autowired private UnifiedPayService unifiedPayService; + @LimitRepeatRequest(bodyAllParam = true, message = "您的订单已提交,请勿频繁操作") @PostMapping("/unifiedOrder") @Operation(summary = "统一下单,并生成支付订单" , description = "生成支付订单,返回前端支付所需参数") public R unifiedOrder(@Valid @RequestBody UnifiedOrderDto dto, HttpServletRequest request){ @@ -46,4 +53,15 @@ public class UnifiedPayController { return R.ok(queryOrderVo); } + @LimitRepeatRequest(time = 10, message = "请勿频繁操作") + @PostMapping("/refund") + @Operation(summary = "统一退款" , description = "统一退款") + public R refund(@Valid @RequestBody RefundDto dto){ + if (!PaymentRefundUtil.checkSign(dto)){ + throw new ServiceException(ResponseEnum.REFUND_SING_ERROR); + } + unifiedPayService.refund(dto); + return R.ok(); + } + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java index c9afe6c..7bd34cd 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java @@ -27,11 +27,11 @@ public class PayFactory { } public static class SingletonHolder{ - public static PayFactory payStrategy = new PayFactory(); + public static PayFactory payFactory = new PayFactory(); } public static PayFactory getInstance(){ - return SingletonHolder.payStrategy; + return SingletonHolder.payFactory; } public IPayStrategy getConcreteStrategy(String payType){ diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java index f3a936c..71d0557 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java @@ -102,14 +102,18 @@ public class WxPayStrategy extends AbstractPayStrategy { amount.setTotal(BaseWxPayRequest.yuanToFen(bo.getPayAmount())); orderRequest.setAmount(amount); //调用微信支付接口 - WxPayUnifiedOrderV3Result wxPayUnifiedOrderV3Result = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, orderRequest); + WxPayUnifiedOrderV3Result wxPayUnifiedOrderV3Result = wxPayService.unifiedOrderV3(TradeTypeEnum.JSAPI, orderRequest); WxPayUnifiedOrderV3Result.JsapiResult jsapiResult = wxPayUnifiedOrderV3Result.getPayInfo(TradeTypeEnum.JSAPI, wxPayConfig.getAppid(), wxPayConfig.getMchid(), wxPayService.getConfig().getPrivateKey()); - thirdUnifiedOrderVo.setAppId(wxPayConfig.getAppid()); ThirdUnifiedOrderVo.WxThirdInOrderVo wxThirdInOrderVo = new ThirdUnifiedOrderVo.WxThirdInOrderVo(); + //微信返回信息封装 + wxThirdInOrderVo.setAppId(jsapiResult.getAppId()); wxThirdInOrderVo.setPackageValue(jsapiResult.getPackageValue()); wxThirdInOrderVo.setTimeStamp(jsapiResult.getTimeStamp()); wxThirdInOrderVo.setNonceStr(jsapiResult.getNonceStr()); wxThirdInOrderVo.setPaySign(jsapiResult.getPaySign()); + wxThirdInOrderVo.setSignType(jsapiResult.getSignType()); + + thirdUnifiedOrderVo.setAppId(wxPayConfig.getAppid()); thirdUnifiedOrderVo.setOutOrderNo(bo.getPayId()); thirdUnifiedOrderVo.setAppId(jsapiResult.getAppId()); @@ -117,6 +121,8 @@ public class WxPayStrategy extends AbstractPayStrategy { thirdUnifiedOrderVo.setThirdCode("0"); thirdUnifiedOrderVo.setThirdMsg("ok"); thirdUnifiedOrderVo.setThirdNo(wxPayUnifiedOrderV3Result.getPrepayId()); + + thirdUnifiedOrderVo.setWxThirdInOrderVo(wxThirdInOrderVo); return thirdUnifiedOrderVo; } catch (WxPayException e) { log.error("微信支付:统一下单接口调用失败,payId:{},error{}", bo.getPayId(), e.getMessage()); diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumVerificationKey.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumVerificationKey.java new file mode 100644 index 0000000..279434e --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumVerificationKey.java @@ -0,0 +1,20 @@ +package com.bnyer.pay.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description : + */ +@Getter +@NoArgsConstructor +@AllArgsConstructor +public enum EnumVerificationKey { + VIP("vip", "VipInOrder20230512Key"), + ; + private String key; + private String value; +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java index 75f8af8..59ed825 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java @@ -1,7 +1,9 @@ package com.bnyer.pay.service; +import com.bnyer.pay.bean.dto.RefundDto; import com.bnyer.pay.bean.dto.UnifiedOrderDto; import com.bnyer.pay.bean.dto.QueryOrderDto; +import com.bnyer.pay.bean.vo.ThirdRefundVo; import com.bnyer.pay.bean.vo.UnifiedOrderVo; import com.bnyer.pay.bean.vo.QueryOrderVo; @@ -28,4 +30,10 @@ public interface UnifiedPayService { * @return */ QueryOrderVo queryOrder(QueryOrderDto dto); + + /** + * 统一退款 + * @param dto + */ + void refund(RefundDto dto); } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java index 9ca0618..ec9fe52 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java @@ -18,6 +18,7 @@ import com.bnyer.pay.bean.bo.QueryOrderBo; import com.bnyer.pay.bean.bo.UnifiedOrderBo; import com.bnyer.pay.bean.dto.AddPayInfoDto; import com.bnyer.pay.bean.dto.QueryOrderDto; +import com.bnyer.pay.bean.dto.RefundDto; import com.bnyer.pay.bean.dto.UnifiedOrderDto; import com.bnyer.pay.bean.vo.*; import com.bnyer.pay.constant.KSPayConstants; @@ -121,8 +122,8 @@ public class UnifiedPayServiceImpl implements UnifiedPayService { * @param dto */ private void checkData(UnifiedOrderDto dto) { - PayInfo payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getOrderNo,dto.getOrderNo())); - if (Objects.nonNull(payInfo) && EnumPayStatus.SUCCESS.getStatus() == payInfo.getPayStatus()){ + PayInfo payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getOrderNo,dto.getOrderNo()).eq(PayInfo::getPayStatus,EnumPayStatus.SUCCESS.getStatus())); + if (Objects.nonNull(payInfo)){ throw new ServiceException(ResponseEnum.ORDER_REPEAT_PAY); } } @@ -248,4 +249,9 @@ public class UnifiedPayServiceImpl implements UnifiedPayService { } } + @Override + public void refund(RefundDto dto) { + + } + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PaymentRefundUtil.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PaymentRefundUtil.java new file mode 100644 index 0000000..cadee6e --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PaymentRefundUtil.java @@ -0,0 +1,39 @@ +package com.bnyer.pay.utils; + +import com.bnyer.common.core.utils.MD5Util; +import com.bnyer.pay.bean.dto.RefundDto; +import com.bnyer.pay.enums.EnumVerificationKey; + +/** + * @author :WXC + * @Date :2023/05/12 + * @description : 退款工具类 + */ +public class PaymentRefundUtil { + + /** + * 获取签名 + * @param dto + * @return + */ + public static String getSign(RefundDto dto) { + return MD5Util.getMD5String(dto.getPayId() + EnumVerificationKey.VIP.getValue()); + } + + /** + * 校验签名 + * @param dto + * @return + */ + public static boolean checkSign(RefundDto dto) { + + return MD5Util.getMD5String(dto.getPayId() + EnumVerificationKey.VIP.getValue()).equals(dto.getSign()); + } + + public static void main(String[] args) { + String sign = MD5Util.getMD5String("RVWU202305121022211042" + EnumVerificationKey.VIP.getValue()); + System.out.println(sign); + System.out.println(MD5Util.getMD5String("RVWU202305121022211042" + EnumVerificationKey.VIP.getValue()).equals(sign)); + } + +} diff --git a/pom.xml b/pom.xml index 7b9dce0..0a3953b 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 5.8.0.M3 3.16.2 4.23.21.ALL - 4.4.0 + 4.5.0 7.2.18 2.2.2