5 Commits
8ffb3a20b8
...
b8f32c105f
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
b8f32c105f |
Merge branch 'milestone-20251203-冲刺计划' into sunjiabei/feature-20251202094523-银行接口同步
|
1 month ago |
|
|
8e40c6cdb6 |
12-04 发送精网号方法,储值发送精网号
|
1 month ago |
|
|
72d63cbc16 |
12.3 冲刺计划累计充值逻辑修改
|
1 month ago |
|
|
823c1ed420 |
Merge branch 'refs/heads/milestone-20251203-冲刺计划' into huangqizheng/feature-20251203174217-冲刺计划
|
1 month ago |
|
|
5554d943d1 |
12.3 冲刺计划累计充值
|
1 month ago |
11 changed files with 706 additions and 3 deletions
-
1src/main/java/com/example/demo/DemoApplication.java
-
370src/main/java/com/example/demo/config/GlobalExceptionHandler.java
-
23src/main/java/com/example/demo/config/RedTimeRuleConfig.java
-
39src/main/java/com/example/demo/controller/Temporary/RedController.java
-
32src/main/java/com/example/demo/domain/vo/Red.java
-
25src/main/java/com/example/demo/mapper/Temporary/RedMapper.java
-
2src/main/java/com/example/demo/security/SecurityConfig.java
-
19src/main/java/com/example/demo/service/Temporary/RedService.java
-
149src/main/java/com/example/demo/serviceImpl/Temporary/RedServiceImpl.java
-
25src/main/java/com/example/demo/serviceImpl/coin/AuditServiceImpl.java
-
24src/main/resources/mapper/RedMapper.xml
@ -0,0 +1,370 @@ |
|||
package com.example.demo.config; |
|||
|
|||
import cn.hutool.core.io.resource.NoResourceException; |
|||
|
|||
import com.example.demo.Util.BusinessException; |
|||
import com.example.demo.Util.ExecutionContextUtil; |
|||
import com.example.demo.Util.FeiShuAlertUtil; |
|||
import com.example.demo.domain.vo.coin.ExecutionContext; |
|||
import com.example.demo.domain.vo.coin.Result; |
|||
import com.example.demo.exception.SystemException; |
|||
import com.fasterxml.jackson.core.JsonParseException; |
|||
import com.fasterxml.jackson.databind.exc.InvalidFormatException; |
|||
import io.micrometer.common.util.StringUtils; |
|||
import jakarta.validation.ConstraintViolation; |
|||
import jakarta.validation.ConstraintViolationException; |
|||
import org.apache.commons.lang3.exception.ExceptionUtils; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.http.converter.HttpMessageNotReadableException; |
|||
import org.springframework.validation.BindException; |
|||
import org.springframework.validation.FieldError; |
|||
import org.springframework.web.HttpRequestMethodNotSupportedException; |
|||
import org.springframework.web.bind.MethodArgumentNotValidException; |
|||
import org.springframework.web.bind.MissingServletRequestParameterException; |
|||
import org.springframework.web.bind.annotation.ExceptionHandler; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestControllerAdvice; |
|||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; |
|||
import org.springframework.web.servlet.NoHandlerFoundException; |
|||
import org.springframework.web.servlet.resource.NoResourceFoundException; |
|||
|
|||
import java.time.LocalDate; |
|||
import java.time.LocalDateTime; |
|||
import java.util.Arrays; |
|||
import java.util.Date; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 全局异常处理器 - 优化封装版 |
|||
*/ |
|||
@RestControllerAdvice |
|||
public class GlobalExceptionHandler { |
|||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); |
|||
|
|||
// ==================== 业务异常处理 ==================== |
|||
|
|||
/** |
|||
* 处理业务异常 |
|||
*/ |
|||
@ExceptionHandler({BusinessException.class}) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleBusinessException(BusinessException e) { |
|||
logger.warn("业务异常: {}", e.getMessage()); |
|||
return ResponseEntity.status(HttpStatus.OK).body(Result.error(e)); |
|||
} |
|||
|
|||
/** |
|||
* 处理系统异常 |
|||
*/ |
|||
@ExceptionHandler(SystemException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleSystemException(SystemException e) { |
|||
logger.error("系统异常: ", e); |
|||
|
|||
// 发送飞书报警 |
|||
sendFeishuAlert(e, "系统异常: " + e.getMessage(), getCauseMessage(e)); |
|||
|
|||
// 返回通用的错误信息 |
|||
return ResponseEntity.status(HttpStatus.OK) |
|||
.body(Result.error(new BusinessException("正在为您努力加载中..."))); |
|||
} |
|||
|
|||
// ==================== 资源未找到异常处理 ==================== |
|||
|
|||
/** |
|||
* 处理资源未找到异常 |
|||
*/ |
|||
@ExceptionHandler(NoResourceFoundException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleNoResourceFoundException(NoResourceFoundException e) { |
|||
logger.warn("资源未找到: {} - {}", e.getHttpMethod(), e.getResourcePath()); |
|||
|
|||
String errorMessage = String.format("接口[%s %s]不存在,请检查接口地址", |
|||
e.getHttpMethod(), e.getResourcePath()); |
|||
|
|||
Result result = Result.error(new BusinessException(404, errorMessage)); |
|||
return ResponseEntity.status(HttpStatus.OK).body(result); |
|||
} |
|||
|
|||
// ==================== 参数校验异常处理 ==================== |
|||
|
|||
/** |
|||
* 处理参数校验异常 |
|||
*/ |
|||
@ExceptionHandler(MethodArgumentNotValidException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleMethodArgumentNotValid(MethodArgumentNotValidException e) { |
|||
String errorMessage = e.getBindingResult().getFieldErrors().stream() |
|||
.map(FieldError::getDefaultMessage) |
|||
.collect(Collectors.joining(", ")); |
|||
|
|||
logger.warn("参数校验异常: {}", errorMessage); |
|||
return ResponseEntity.status(HttpStatus.OK) |
|||
.body(Result.error(new BusinessException(errorMessage))); |
|||
} |
|||
|
|||
/** |
|||
* 处理约束违反异常 |
|||
*/ |
|||
@ExceptionHandler(ConstraintViolationException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleConstraintViolation(ConstraintViolationException e) { |
|||
String errorMessage = e.getConstraintViolations().stream() |
|||
.map(ConstraintViolation::getMessage) |
|||
.collect(Collectors.joining(", ")); |
|||
|
|||
logger.warn("约束违反异常: {}", errorMessage); |
|||
return ResponseEntity.status(HttpStatus.OK) |
|||
.body(Result.error(new BusinessException(errorMessage))); |
|||
} |
|||
|
|||
/** |
|||
* 处理请求方法不支持异常 |
|||
*/ |
|||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleHttpRequestMethodNotSupported( |
|||
HttpRequestMethodNotSupportedException e) { |
|||
|
|||
String errorMessage = "请求方法不支持,请使用: " + e.getSupportedHttpMethods(); |
|||
logger.warn("请求方法不支持: {}", e.getMethod()); |
|||
return ResponseEntity.status(HttpStatus.OK) |
|||
.body(Result.error(new BusinessException(errorMessage))); |
|||
} |
|||
|
|||
/** |
|||
* 处理参数类型不匹配异常 |
|||
*/ |
|||
@ExceptionHandler(MethodArgumentTypeMismatchException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleMethodArgumentTypeMismatch( |
|||
MethodArgumentTypeMismatchException e) { |
|||
|
|||
String errorMessage = handleTypeMismatch(e); |
|||
logger.warn("参数类型不匹配: {} - {}", e.getName(), e.getValue()); |
|||
return ResponseEntity.status(HttpStatus.OK) |
|||
.body(Result.error(new BusinessException(errorMessage))); |
|||
} |
|||
|
|||
// /** |
|||
// * 处理表单绑定异常 |
|||
// */ |
|||
// @ExceptionHandler(BindException.class) |
|||
// @ResponseBody |
|||
// public ResponseEntity<Result> handleBindException(BindException e) { |
|||
// String errorMessage = handleBindException(e); |
|||
// logger.warn("表单绑定异常: {}", errorMessage); |
|||
// return ResponseEntity.status(HttpStatus.OK) |
|||
// .body(Result.error(new BusinessException(errorMessage))); |
|||
// } |
|||
|
|||
/** |
|||
* 处理缺失请求参数异常 |
|||
*/ |
|||
@ExceptionHandler(MissingServletRequestParameterException.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleMissingServletRequestParameter( |
|||
MissingServletRequestParameterException e) { |
|||
|
|||
String errorMessage = handleMissingParameter(e); |
|||
logger.warn("缺失请求参数: {}", e.getParameterName()); |
|||
return ResponseEntity.status(HttpStatus.OK) |
|||
.body(Result.error(new BusinessException(errorMessage))); |
|||
} |
|||
|
|||
/** |
|||
* 处理请求体不可读异常 |
|||
*/ |
|||
// @ExceptionHandler(HttpMessageNotReadableException.class) |
|||
// @ResponseBody |
|||
// public ResponseEntity<Result> handleHttpMessageNotReadable( |
|||
// HttpMessageNotReadableException e) { |
|||
// |
|||
// String errorMessage = handleHttpMessageNotReadable(e); |
|||
// logger.warn("请求体不可读: {}", e.getMessage()); |
|||
// return ResponseEntity.status(HttpStatus.BAD_REQUEST) |
|||
// .body(Result.error(new BusinessException(errorMessage))); |
|||
// } |
|||
|
|||
// ==================== 通用异常处理 ==================== |
|||
|
|||
/** |
|||
* 处理未预期的异常 |
|||
*/ |
|||
@ExceptionHandler(Exception.class) |
|||
@ResponseBody |
|||
public ResponseEntity<Result> handleUnexpectedException(Exception e) { |
|||
// 如果是参数校验相关异常,已经被上面的方法处理,这里作为兜底 |
|||
if (isValidationException(e)) { |
|||
String errorMessage = getValidationErrorMessage(e); |
|||
logger.warn("参数校验异常(兜底): {}", errorMessage); |
|||
Result result = Result.error(new BusinessException(errorMessage)); |
|||
return ResponseEntity.status(HttpStatus.OK).body(result); |
|||
} |
|||
|
|||
logger.error("未预期异常: {}", e.getMessage(), e); |
|||
|
|||
// 发送飞书报警 |
|||
sendFeishuAlert(e, "未预期异常: " + e.getClass().getName(), e.getMessage()); |
|||
|
|||
// 返回通用错误信息 |
|||
Result result = Result.error(new BusinessException("正在为您努力加载中...")); |
|||
return ResponseEntity.status(HttpStatus.OK).body(result); |
|||
} |
|||
|
|||
// ==================== 异常处理工具方法 ==================== |
|||
|
|||
/** |
|||
* 发送飞书报警 |
|||
*/ |
|||
private void sendFeishuAlert(Exception e, String title, String detail) { |
|||
ExecutionContext context = ExecutionContextUtil.getExecutionContext(); |
|||
if (context != null) { |
|||
String message = title; |
|||
if (StringUtils.isNotBlank(detail)) { |
|||
message += "\n错误详情: " + detail; |
|||
} |
|||
|
|||
FeiShuAlertUtil.sendAlertMessage( |
|||
context, |
|||
e.getStackTrace()[0].getFileName(), |
|||
e.getStackTrace()[0].getLineNumber(), |
|||
message, |
|||
context.getRequestParams() |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取异常原因信息 |
|||
*/ |
|||
private String getCauseMessage(Exception e) { |
|||
if (e.getCause() != null) { |
|||
return e.getCause().getMessage(); |
|||
} |
|||
return ""; |
|||
} |
|||
|
|||
/** |
|||
* 判断是否是字段校验异常 |
|||
*/ |
|||
private boolean isValidationException(Exception e) { |
|||
return e instanceof MethodArgumentNotValidException || |
|||
e instanceof ConstraintViolationException || |
|||
e instanceof HttpMessageNotReadableException || |
|||
e instanceof HttpRequestMethodNotSupportedException || |
|||
e instanceof MethodArgumentTypeMismatchException || |
|||
e instanceof BindException || |
|||
e instanceof MissingServletRequestParameterException || |
|||
e instanceof NoHandlerFoundException || |
|||
e instanceof NoResourceException; |
|||
} |
|||
|
|||
/** |
|||
* 获取校验异常的错误信息(兜底方法) |
|||
*/ |
|||
private String getValidationErrorMessage(Exception e) { |
|||
if (e instanceof MethodArgumentNotValidException) { |
|||
return ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors().stream() |
|||
.map(FieldError::getDefaultMessage) |
|||
.collect(Collectors.joining(", ")); |
|||
} else if (e instanceof ConstraintViolationException) { |
|||
return ((ConstraintViolationException) e).getConstraintViolations().stream() |
|||
.map(ConstraintViolation::getMessage) |
|||
.collect(Collectors.joining(", ")); |
|||
} else if (e instanceof MethodArgumentTypeMismatchException) { |
|||
return handleTypeMismatch((MethodArgumentTypeMismatchException) e); |
|||
} else if (e instanceof BindException) { |
|||
return handleBindException((BindException) e); |
|||
} else if (e instanceof MissingServletRequestParameterException) { |
|||
return handleMissingParameter((MissingServletRequestParameterException) e); |
|||
} else if (e instanceof HttpMessageNotReadableException) { |
|||
return handleHttpMessageNotReadable((HttpMessageNotReadableException) e); |
|||
} else if (e instanceof HttpRequestMethodNotSupportedException) { |
|||
return "请求方法不支持,请使用: " + ((HttpRequestMethodNotSupportedException) e).getSupportedHttpMethods(); |
|||
} |
|||
return "参数校验失败"; |
|||
} |
|||
|
|||
/** |
|||
* 处理类型不匹配异常 |
|||
*/ |
|||
private String handleTypeMismatch(MethodArgumentTypeMismatchException ex) { |
|||
String parameterName = ex.getName(); |
|||
Class<?> requiredType = ex.getRequiredType(); |
|||
|
|||
String errorMsg = String.format("参数'%s'的值'%s'格式错误", |
|||
parameterName, ex.getValue()); |
|||
|
|||
if (requiredType != null) { |
|||
if (Number.class.isAssignableFrom(requiredType)) { |
|||
errorMsg += ",应为数字类型"; |
|||
} else if (requiredType == Boolean.class || requiredType == boolean.class) { |
|||
errorMsg += ",应为布尔值(true/false)"; |
|||
} else if (requiredType == Date.class || requiredType == LocalDate.class) { |
|||
errorMsg += ",日期格式应为: yyyy-MM-dd"; |
|||
} else if (requiredType == LocalDateTime.class) { |
|||
errorMsg += ",日期时间格式应为: yyyy-MM-dd HH:mm:ss"; |
|||
} else if (requiredType.isEnum()) { |
|||
errorMsg += ",可选值为: " + Arrays.toString(requiredType.getEnumConstants()); |
|||
} |
|||
} |
|||
return errorMsg; |
|||
} |
|||
|
|||
/** |
|||
* 处理表单对象绑定错误 |
|||
*/ |
|||
private String handleBindException(BindException ex) { |
|||
return ex.getBindingResult().getFieldErrors().stream() |
|||
.map(error -> { |
|||
String field = error.getField(); |
|||
String message = error.getDefaultMessage(); |
|||
Object rejectedValue = error.getRejectedValue(); |
|||
|
|||
return rejectedValue == null |
|||
? String.format("字段'%s': %s", field, message) |
|||
: String.format("字段'%s'的值'%s'无效: %s", field, rejectedValue, message); |
|||
}) |
|||
.collect(Collectors.joining("; ")); |
|||
} |
|||
|
|||
/** |
|||
* 处理必填参数缺失异常 |
|||
*/ |
|||
private String handleMissingParameter(MissingServletRequestParameterException ex) { |
|||
return String.format("缺少必填参数: '%s' (类型: %s)", |
|||
ex.getParameterName(), ex.getParameterType()); |
|||
} |
|||
|
|||
/** |
|||
* 处理请求体解析异常 |
|||
*/ |
|||
private String handleHttpMessageNotReadable(HttpMessageNotReadableException ex) { |
|||
Throwable rootCause = ex.getRootCause(); |
|||
|
|||
if (rootCause instanceof JsonParseException) { |
|||
return "JSON格式错误: " + rootCause.getMessage(); |
|||
} else if (rootCause instanceof InvalidFormatException) { |
|||
InvalidFormatException ife = (InvalidFormatException) rootCause; |
|||
return String.format("字段'%s'的值'%s'格式不正确,期望类型: %s", |
|||
ife.getPath().get(ife.getPath().size() - 1).getFieldName(), |
|||
ife.getValue(), |
|||
ife.getTargetType().getSimpleName()); |
|||
} else if (rootCause != null) { |
|||
return "请求体格式错误: " + rootCause.getMessage(); |
|||
} |
|||
return "请求体格式错误或无法解析"; |
|||
} |
|||
|
|||
/** |
|||
* 获取异常根源信息 |
|||
*/ |
|||
private String getRootCauseMessage(Throwable e) { |
|||
Throwable rootCause = ExceptionUtils.getRootCause(e); |
|||
return rootCause != null ? rootCause.getMessage() : e.getMessage(); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package com.example.demo.config; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.time.LocalDateTime; |
|||
import java.time.ZoneId; |
|||
|
|||
// 可放入 application.yml(推荐),或写死在代码(演示用) |
|||
@ConfigurationProperties(prefix = "business.rules.red") |
|||
@Data |
|||
@Component |
|||
public class RedTimeRuleConfig { |
|||
// 充值开放时间:2025-12-04 20:00:00(北京时间) |
|||
private LocalDateTime rechargeStartTime = LocalDateTime.of(2025, 12, 4, 20, 0, 0); |
|||
|
|||
// 消费开放时间:2025-12-06 10:00:00(北京时间) |
|||
private LocalDateTime consumeStartTime = LocalDateTime.of(2025, 12, 6, 10, 0, 0); |
|||
|
|||
// 时区:中国标准时间 |
|||
private ZoneId zoneId = ZoneId.of("Asia/Shanghai"); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
package com.example.demo.controller.Temporary; |
|||
|
|||
import com.example.demo.domain.vo.Red; |
|||
import com.example.demo.domain.vo.coin.Result; |
|||
import com.example.demo.service.Temporary.RedService; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import java.math.BigDecimal; |
|||
|
|||
/** |
|||
* @program: GOLD |
|||
* @ClassName RedController |
|||
* @description: |
|||
* @author: huangqizhen |
|||
* @create: 2025−12-03 16:47 |
|||
* @Version 1.0 |
|||
**/ |
|||
@RestController |
|||
@RequestMapping("/Temporary") |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
@CrossOrigin |
|||
public class RedController { |
|||
@Autowired |
|||
private RedService redService; |
|||
@RequestMapping("/Red") |
|||
public Result selectSum(@RequestBody Red red) { |
|||
try { |
|||
redService.selectSum(red.getJwcode(),red.getType()); |
|||
} |
|||
catch (Exception e) { |
|||
return Result.error(e.getMessage()); |
|||
} |
|||
return Result.success(redService.selectSum(red.getJwcode(),red.getType())); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
package com.example.demo.domain.vo; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.checkerframework.checker.units.qual.N; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* @program: GOLD |
|||
* @ClassName red |
|||
* @description: |
|||
* @author: huangqizhen |
|||
* @create: 2025−12-03 16:31 |
|||
* @Version 1.0 |
|||
**/ |
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class Red { |
|||
private Integer id; |
|||
private Integer jwcode; |
|||
private BigDecimal sum; |
|||
private Integer type; |
|||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") |
|||
private Date czTime; |
|||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") |
|||
private Date updateTime; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
package com.example.demo.mapper.Temporary; |
|||
|
|||
import org.apache.ibatis.annotations.Mapper; |
|||
import org.apache.ibatis.annotations.Param; |
|||
|
|||
import java.math.BigDecimal; |
|||
|
|||
/** |
|||
* @program: GOLD |
|||
* @ClassName RedMapper |
|||
* @description: |
|||
* @author: huangqizhen |
|||
* @create: 2025−12-03 17:03 |
|||
* @Version 1.0 |
|||
**/ |
|||
@Mapper |
|||
public interface RedMapper { |
|||
BigDecimal selectSum(@Param("jwcode") Integer jwcode, @Param("type") Integer type); |
|||
//查询是否有该精网号 |
|||
boolean selectJwcode(@Param("jwcode") Integer jwcode); |
|||
|
|||
int upsertAndAdd(@Param("jwcode") Integer jwcode, |
|||
@Param("type") Integer type, |
|||
@Param("sum") BigDecimal sum); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package com.example.demo.service.Temporary; |
|||
|
|||
import com.example.demo.domain.vo.Red; |
|||
|
|||
import java.math.BigDecimal; |
|||
|
|||
/** |
|||
* @program: GOLD |
|||
* @ClassName RedService |
|||
* @description: |
|||
* @author: huangqizhen |
|||
* @create: 2025−12-03 16:38 |
|||
* @Version 1.0 |
|||
**/ |
|||
public interface RedService { |
|||
BigDecimal selectSum(Integer jwcode, Integer type); |
|||
void addAmount(Integer jwcode,BigDecimal sum, Integer type); |
|||
String sendJwcode(Integer jwcode); |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
package com.example.demo.serviceImpl.Temporary; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.example.demo.Util.BusinessException; |
|||
import com.example.demo.config.RedTimeRuleConfig; |
|||
import com.example.demo.domain.vo.Red; |
|||
import com.example.demo.exception.SystemException; |
|||
import com.example.demo.mapper.Temporary.RedMapper; |
|||
import com.example.demo.service.Temporary.RedService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.io.IOException; |
|||
import java.math.BigDecimal; |
|||
import java.net.URI; |
|||
import java.net.http.HttpClient; |
|||
import java.net.http.HttpRequest; |
|||
import java.net.http.HttpResponse; |
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
|
|||
|
|||
/** |
|||
* @program: GOLD |
|||
* @ClassName RedServiceImpl |
|||
* @description: |
|||
* @author: huangqizhen |
|||
* @create: 2025−12-03 16:37 |
|||
* @Version 1.0 |
|||
**/ |
|||
@Slf4j |
|||
@Service |
|||
public class RedServiceImpl implements RedService { |
|||
private static final String BASE_URLProd = "http://39.101.133.168:8828/scms"; |
|||
private static final String BASE_URLDev = "http://gf977328.natappfree.cc"; |
|||
private static final String PATH = "/api/coupon/IssueRechargeRedPacket"; |
|||
|
|||
private static final HttpClient CLIENT = HttpClient.newHttpClient(); |
|||
|
|||
@Autowired |
|||
private RedMapper redMapper; |
|||
@Autowired |
|||
private RedTimeRuleConfig timeRuleConfig; |
|||
@Override |
|||
public BigDecimal selectSum(Integer jwcode,Integer type) { |
|||
if (jwcode == null){ |
|||
throw new BusinessException("未接受到精网号"); |
|||
} |
|||
if (redMapper.selectJwcode(jwcode)==false){ |
|||
throw new BusinessException("未找到该精网号"); |
|||
} |
|||
if(redMapper.selectJwcode(jwcode)) |
|||
if (type == null){ |
|||
throw new BusinessException("未接受到类型"); |
|||
} |
|||
if (type !=1 && type != 2){ |
|||
throw new BusinessException("类型错误"); |
|||
} |
|||
return redMapper.selectSum(jwcode,type); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void addAmount(Integer jwcode, BigDecimal sum, Integer type) { |
|||
// 1. 基础参数校验 |
|||
validateParams(jwcode, type, sum); |
|||
|
|||
// 2. ⭐⭐⭐ 时间窗口校验(关键!) |
|||
validateTimeWindow(type, sum); |
|||
|
|||
// 3. 执行原子累加 |
|||
int affected = redMapper.upsertAndAdd(jwcode, type, sum); |
|||
if (affected == 0) { |
|||
throw new BusinessException("操作失败,请重试"); |
|||
} |
|||
} |
|||
|
|||
|
|||
/* |
|||
调用充值红包发放接口 |
|||
|
|||
*/ |
|||
@Override |
|||
public String sendJwcode(Integer jwcode) { |
|||
try { |
|||
String body = JSON.toJSONString(java.util.Map.of("jwcode", jwcode)); |
|||
|
|||
HttpRequest request = HttpRequest.newBuilder() |
|||
.uri(URI.create(BASE_URLDev + PATH)) //URL记得换 |
|||
.header("Content-Type", "application/json") |
|||
.POST(HttpRequest.BodyPublishers.ofString(body)) |
|||
.build(); |
|||
|
|||
HttpResponse<String> resp = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); |
|||
|
|||
if (resp.statusCode() != 200) { |
|||
log.warn("红包接口异常,status:{},body:{}", resp.statusCode(), resp.body()); |
|||
} |
|||
return resp.body(); |
|||
} catch (IOException | InterruptedException e) { |
|||
log.error("调用红包接口失败,jwcode:{}", jwcode, e); |
|||
return "{\"success\":false,\"msg\":\"网络异常\"}"; |
|||
} |
|||
} |
|||
|
|||
|
|||
// --- 校验方法 --- |
|||
private void validateParams(Integer jwcode, Integer type, BigDecimal delta) { |
|||
if (jwcode == null || jwcode <= 0) { |
|||
throw new BusinessException("精网号无效"); |
|||
} |
|||
if (type == null || (type != 1 && type != 2)) { |
|||
throw new BusinessException("类型必须为 1(充值)或 2(消费)"); |
|||
} |
|||
if (delta == null || delta.compareTo(BigDecimal.ZERO) == 0) { |
|||
throw new BusinessException("变动金额不能为0"); |
|||
} |
|||
} |
|||
|
|||
private void validateTimeWindow(Integer type, BigDecimal delta) { |
|||
LocalDateTime now = LocalDateTime.now(timeRuleConfig.getZoneId()); |
|||
|
|||
if (type == 1) { |
|||
// 充值:必须 delta > 0,且当前时间 >= rechargeStartTime |
|||
if (delta.compareTo(BigDecimal.ZERO) < 0) { |
|||
throw new BusinessException("充值金额不能为负数"); |
|||
} |
|||
if (now.isBefore(timeRuleConfig.getRechargeStartTime())) { |
|||
String openTime = timeRuleConfig.getRechargeStartTime() |
|||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); |
|||
throw new BusinessException("红包充值通道暂未开放,请于 " + openTime + " 后操作"); |
|||
} |
|||
} |
|||
else if (type == 2) { |
|||
// 消费:必须 delta < 0(或你设计为正数+方向,此处按负数扣款) |
|||
if (delta.compareTo(BigDecimal.ZERO) > 0) { |
|||
throw new BusinessException("消费金额应为负数(表示扣款)"); |
|||
} |
|||
if (now.isBefore(timeRuleConfig.getConsumeStartTime())) { |
|||
String openTime = timeRuleConfig.getConsumeStartTime() |
|||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); |
|||
throw new BusinessException("红包消费通道暂未开放,请于 " + openTime + " 后操作"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!DOCTYPE mapper |
|||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="com.example.demo.mapper.Temporary.RedMapper"> |
|||
<insert id="upsertAndAdd" parameterType="map"> |
|||
INSERT INTO red_account (jwcode, type, sum) |
|||
VALUES ( |
|||
#{jwcode}, |
|||
#{type}, |
|||
#{sum}, |
|||
) |
|||
ON DUPLICATE KEY UPDATE |
|||
sum = sum + #{sum} |
|||
</insert> |
|||
<select id="selectSum" resultType="java.math.BigDecimal"> |
|||
select sum from red where jwcode=#{jwcode} and type=#{type} |
|||
</select> |
|||
<select id="selectJwcode" resultType="boolean"> |
|||
SELECT EXISTS( |
|||
SELECT 1 FROM red WHERE jwcode = #{jwcode} |
|||
) |
|||
</select> |
|||
</mapper> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue