pull/1121/head
parent
7baf13de1d
commit
397f161dc1
|
|
@ -1,9 +1,12 @@
|
|||
package com.ruoyi.web.controller.common;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.aliyun.oss.common.auth.Credentials;
|
||||
import com.aliyun.oss.common.utils.BinaryUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.common.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
|
|
@ -13,17 +16,25 @@ import com.ruoyi.framework.ocr.BusinessLicense;
|
|||
import com.ruoyi.framework.ocr.IdCard;
|
||||
import com.ruoyi.framework.ocr.OcrClientWrapper;
|
||||
import com.ruoyi.framework.oss.OSSClientWrapper;
|
||||
import com.ruoyi.web.controller.common.vo.BusinessLicenseVO;
|
||||
import com.ruoyi.web.controller.common.vo.IdCardVO;
|
||||
import com.ruoyi.web.controller.common.vo.STSCredentialsVO;
|
||||
import com.ruoyi.web.controller.common.vo.UrlVO;
|
||||
import com.ruoyi.web.controller.common.vo.*;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
|
@ -36,6 +47,11 @@ import java.util.concurrent.TimeUnit;
|
|||
@RestController
|
||||
@RequestMapping("/rest/v1/common")
|
||||
public class CommonController {
|
||||
|
||||
@Value("${oss.fileExpireTime:3600}")
|
||||
private Long fileExpireTime;//指定过期时间,单位为秒。
|
||||
@Value("${oss.callbackUrl:http://121.40.117.244:9310/rest/v1/oss-callback/upload}")
|
||||
private String callbackUrl;//上传回调URL
|
||||
@Autowired
|
||||
private OSSClientWrapper ossClient;
|
||||
@Autowired
|
||||
|
|
@ -74,6 +90,104 @@ public class CommonController {
|
|||
return R.ok(vo);
|
||||
}
|
||||
|
||||
@ApiOperation("获取OSS上传签名")
|
||||
@PostMapping("/oss/signature4upload")
|
||||
public R<OSSUploadSignRespVO> signature4upload(@Validated @RequestBody OSSUploadSignReqVO vo) {
|
||||
String accessKeyId = vo.getAccessKeyId();
|
||||
String accessKeySecret = vo.getAccessKeySecret();
|
||||
String securityToken = vo.getSecurityToken();
|
||||
String region = ossProperties.getRegionId();
|
||||
String bucket = vo.getBucket();
|
||||
String uploadDir = StrUtil.emptyIfNull(vo.getUploadDir());
|
||||
try {
|
||||
//获取x-oss-credential里的date,当前日期,格式为yyyyMMdd
|
||||
ZonedDateTime today = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
String date = today.format(formatter);
|
||||
|
||||
//获取x-oss-date
|
||||
ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
|
||||
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
|
||||
String x_oss_date = now.format(formatter2);
|
||||
|
||||
// 步骤1:创建policy。
|
||||
String x_oss_credential = accessKeyId + "/" + date + "/" + region + "/oss/aliyun_v4_request";
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
Map<String, Object> policy = new HashMap<>(2);
|
||||
policy.put("expiration", generateExpiration(fileExpireTime));
|
||||
|
||||
List<Object> conditions = new ArrayList<>(8);
|
||||
|
||||
Map<String, String> bucketCondition = new HashMap<>(1);
|
||||
bucketCondition.put("bucket", bucket);
|
||||
conditions.add(bucketCondition);
|
||||
|
||||
Map<String, String> securityTokenCondition = new HashMap<>(1);
|
||||
securityTokenCondition.put("x-oss-security-token", securityToken);
|
||||
conditions.add(securityTokenCondition);
|
||||
|
||||
Map<String, String> signatureVersionCondition = new HashMap<>(1);
|
||||
signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
|
||||
conditions.add(signatureVersionCondition);
|
||||
|
||||
Map<String, String> credentialCondition = new HashMap<>(1);
|
||||
credentialCondition.put("x-oss-credential", x_oss_credential); // 替换为实际的 access key id
|
||||
conditions.add(credentialCondition);
|
||||
|
||||
Map<String, String> dateCondition = new HashMap<>(1);
|
||||
dateCondition.put("x-oss-date", x_oss_date);
|
||||
conditions.add(dateCondition);
|
||||
|
||||
conditions.add(Arrays.asList("content-length-range", 1, 10240000));
|
||||
conditions.add(Arrays.asList("eq", "$success_action_status", "200"));
|
||||
conditions.add(Arrays.asList("starts-with", "$key", uploadDir));
|
||||
|
||||
policy.put("conditions", conditions);
|
||||
|
||||
String jsonPolicy = mapper.writeValueAsString(policy);
|
||||
|
||||
// 步骤2:构造待签名字符串(StringToSign)。
|
||||
String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes()));
|
||||
|
||||
// 步骤3:计算SigningKey。
|
||||
byte[] dateKey = hmacsha256(("aliyun_v4" + accessKeySecret).getBytes(), date);
|
||||
byte[] dateRegionKey = hmacsha256(dateKey, region);
|
||||
byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
|
||||
byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");
|
||||
|
||||
// 步骤4:计算Signature。
|
||||
byte[] result = hmacsha256(signingKey, stringToSign);
|
||||
String signature = BinaryUtil.toHex(result);
|
||||
|
||||
// 步骤5:设置回调。
|
||||
JSONObject jasonCallback = new JSONObject();
|
||||
jasonCallback.put("callbackUrl", callbackUrl);
|
||||
jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
|
||||
jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
|
||||
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
|
||||
|
||||
OSSUploadSignRespVO response = new OSSUploadSignRespVO();
|
||||
// 将数据添加到 map 中
|
||||
response.setVersion("OSS4-HMAC-SHA256");
|
||||
// 这里是易错点,不能直接传policy,需要做一下Base64编码
|
||||
response.setPolicy(stringToSign);
|
||||
response.setX_oss_credential(x_oss_credential);
|
||||
response.setX_oss_date(x_oss_date);
|
||||
response.setSignature(signature);
|
||||
response.setSecurity_token(securityToken);
|
||||
response.setDir(uploadDir);
|
||||
response.setHost(generateHost(bucket, ossProperties.isHttps()));
|
||||
response.setCallback(base64CallbackBody);
|
||||
// 返回带有状态码 200 (OK) 的 ResponseEntity,返回给Web端,进行PostObject操作
|
||||
return R.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("生成OSS签名异常", e);
|
||||
}
|
||||
return R.fail();
|
||||
}
|
||||
|
||||
@ApiOperation("身份证OCR")
|
||||
@PostMapping("/ocr/idCard")
|
||||
public R<IdCardVO> recognizeIdCard(@Validated @RequestBody UrlVO vo) {
|
||||
|
|
@ -104,4 +218,49 @@ public class CommonController {
|
|||
return R.ok(BeanUtil.toBean(dto, BusinessLicenseVO.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过指定有效的时长(秒)生成过期时间。
|
||||
*
|
||||
* @param seconds 有效时长(秒)。
|
||||
* @return ISO8601 时间字符串,如:"2014-12-01T12:00:00.000Z"。
|
||||
*/
|
||||
private String generateExpiration(long seconds) {
|
||||
// 获取当前时间戳(以秒为单位)
|
||||
long now = Instant.now().getEpochSecond();
|
||||
// 计算过期时间的时间戳
|
||||
long expirationTime = now + seconds;
|
||||
// 将时间戳转换为Instant对象,并格式化为ISO8601格式
|
||||
Instant instant = Instant.ofEpochSecond(expirationTime);
|
||||
// 定义时区为UTC
|
||||
ZoneId zone = ZoneOffset.UTC;
|
||||
// 将 Instant 转换为 ZonedDateTime
|
||||
ZonedDateTime zonedDateTime = instant.atZone(zone);
|
||||
// 定义日期时间格式,例如2023-12-03T13:00:00.000Z
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
|
||||
// 格式化日期时间
|
||||
String formattedDate = zonedDateTime.format(formatter);
|
||||
// 输出结果
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
private byte[] hmacsha256(byte[] key, String data) {
|
||||
try {
|
||||
// 初始化HMAC密钥规格,指定算法为HMAC-SHA256并使用提供的密钥。
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
|
||||
// 获取Mac实例,并通过getInstance方法指定使用HMAC-SHA256算法。
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
// 使用密钥初始化Mac对象。
|
||||
mac.init(secretKeySpec);
|
||||
// 执行HMAC计算,通过doFinal方法接收需要计算的数据并返回计算结果的数组。
|
||||
return mac.doFinal(data.getBytes());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateHost(String bucket, Boolean isHttps) {
|
||||
String url = "http{0}://{1}.{2}";
|
||||
return StrUtil.indexedFormat(url, BooleanUtil.isTrue(isHttps) ? "s" : "", bucket, ossProperties.getEndPoint());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.ruoyi.web.controller.common.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* @author liangyq
|
||||
* @date 2025-07-29
|
||||
*/
|
||||
@ApiModel
|
||||
@Data
|
||||
public class OSSUploadSignReqVO {
|
||||
|
||||
@NotEmpty(message = "accessKeyId不能为空")
|
||||
@ApiModelProperty(value = "STS.accessKeyId")
|
||||
private String accessKeyId;
|
||||
|
||||
@NotEmpty(message = "accessKeySecret不能为空")
|
||||
@ApiModelProperty(value = "STS.accessKeySecret")
|
||||
private String accessKeySecret;
|
||||
|
||||
@NotEmpty(message = "securityToken不能为空")
|
||||
@ApiModelProperty(value = "STS.securityToken")
|
||||
private String securityToken;
|
||||
|
||||
@NotEmpty(message = "桶不能为空")
|
||||
@ApiModelProperty(value = "bucket")
|
||||
private String bucket;
|
||||
|
||||
@ApiModelProperty(value = "上传到指定文件夹")
|
||||
private String uploadDir;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.ruoyi.web.controller.common.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author liangyq
|
||||
* @date 2025-07-29
|
||||
*/
|
||||
@ApiModel
|
||||
@Data
|
||||
public class OSSUploadSignRespVO {
|
||||
|
||||
@ApiModelProperty(value = "version")
|
||||
private String version;
|
||||
|
||||
@ApiModelProperty(value = "policy")
|
||||
private String policy;
|
||||
|
||||
@ApiModelProperty(value = "x_oss_credential")
|
||||
private String x_oss_credential;
|
||||
|
||||
@ApiModelProperty(value = "x_oss_date")
|
||||
private String x_oss_date;
|
||||
|
||||
@ApiModelProperty(value = "signature")
|
||||
private String signature;
|
||||
|
||||
@ApiModelProperty(value = "security_token")
|
||||
private String security_token;
|
||||
|
||||
@ApiModelProperty(value = "dir")
|
||||
private String dir;
|
||||
|
||||
@ApiModelProperty(value = "host")
|
||||
private String host;
|
||||
|
||||
@ApiModelProperty(value = "callback")
|
||||
private String callback;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.ruoyi.web.controller.xkt;
|
||||
|
||||
import com.ruoyi.common.core.controller.XktBaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author liangyq
|
||||
* @date 2025-07-29
|
||||
*/
|
||||
@Api(tags = "OSS回调接口")
|
||||
@RestController
|
||||
@RequestMapping("/rest/v1/oss-callback")
|
||||
public class OSSCallbackController extends XktBaseController {
|
||||
|
||||
@RequestMapping("/upload")
|
||||
public R callback(HttpServletRequest servletRequest) {
|
||||
String filename = servletRequest.getParameter("filename");
|
||||
String size = servletRequest.getParameter("size");
|
||||
String mimeType = servletRequest.getParameter("mimeType");
|
||||
String height = servletRequest.getParameter("imageInfo.height");
|
||||
String width = servletRequest.getParameter("imageInfo.width");
|
||||
logger.info("OSS回调: {}, {}, {}, {}, {}", filename, size, mimeType, height, width);
|
||||
return success();
|
||||
}
|
||||
}
|
||||
|
|
@ -43,4 +43,29 @@ public class AlipayReqDTO {
|
|||
*/
|
||||
@JsonProperty("time_expire")
|
||||
private String timeExpire;
|
||||
/**
|
||||
* https://opendocs.alipay.com/open/common/wifww7
|
||||
*
|
||||
* 余额 balance
|
||||
* 余额宝 moneyFund
|
||||
* 网银 bankPay
|
||||
* 借记卡快捷 debitCardExpress
|
||||
* 信用卡快捷 creditCardExpress
|
||||
* 信用卡卡通 creditCardCartoon
|
||||
* 信用卡 creditCard
|
||||
* 卡通 cartoon
|
||||
* 花呗 pcredit
|
||||
* 花呗分期 pcreditpayInstallment
|
||||
* 信用支付类型(包含 信用卡卡通,信用卡快捷,花呗,花呗分期) credit_group
|
||||
* 红包 coupon
|
||||
* 积分 point
|
||||
* 优惠(包含实时优惠+商户优惠) promotion
|
||||
* 营销券 voucher
|
||||
* 商户优惠 mdiscount
|
||||
* 亲密付 honeyPay
|
||||
* 商户预存卡 mcard
|
||||
* 个人预存卡 pcard
|
||||
*/
|
||||
@JsonProperty("enable_pay_channels")
|
||||
private String enablePayChannels;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ public class AliPaymentMangerImpl implements PaymentManager, InitializingBean {
|
|||
reqDTO.setOutTradeNo(tradeNo);
|
||||
reqDTO.setTotalAmount(amount.toPlainString());
|
||||
reqDTO.setSubject(subject);
|
||||
reqDTO.setEnablePayChannels("balance,moneyFund,bankPay,debitCardExpress");
|
||||
reqDTO.setTimeExpire(DateUtil.formatDateTime(expireTime));
|
||||
|
||||
switch (payPage) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue