first commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Ss-Component 模块
|
||||
|
||||
主要实现各种第三方Spring组件服务。
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.zxdmy.excite</groupId>
|
||||
<artifactId>shensong</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ss-component</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<name>ss-component</name>
|
||||
<description>ss-component</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 加载Common依赖,一些常用的工具包 -->
|
||||
<dependency>
|
||||
<groupId>com.zxdmy.excite</groupId>
|
||||
<artifactId>ss-common</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 七牛云依赖 -->
|
||||
<!-- https://mvnrepository.com/artifact/com.qiniu/qiniu-java-sdk -->
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>7.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云短信服务依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dysmsapi20170525</artifactId>
|
||||
<version>2.0.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云OSS服务依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>3.13.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 第三方登录依赖 -->
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId>
|
||||
<version>1.16.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 支付宝支付依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-easysdk</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 微信支付依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
<version>4.2.2.B</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,362 @@
|
||||
package com.zxdmy.excite.component.alipay;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alipay.easysdk.factory.Factory;
|
||||
import com.alipay.easysdk.kernel.Config;
|
||||
import com.alipay.easysdk.kernel.util.ResponseChecker;
|
||||
import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse;
|
||||
import com.alipay.easysdk.payment.common.models.AlipayDataDataserviceBillDownloadurlQueryResponse;
|
||||
import com.alipay.easysdk.payment.common.models.AlipayTradeFastpayRefundQueryResponse;
|
||||
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
|
||||
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
|
||||
import com.alipay.easysdk.payment.facetoface.models.AlipayTradePrecreateResponse;
|
||||
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
|
||||
import com.alipay.easysdk.payment.wap.models.AlipayTradeWapPayResponse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import com.zxdmy.excite.common.service.IGlobalConfigService;
|
||||
import com.zxdmy.excite.component.bo.AlipayBO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 支付宝支付服务实现类
|
||||
* https://github.com/alipay/alipay-easysdk/tree/master/java
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022-01-02 0002 23:31
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class AlipayService {
|
||||
|
||||
private IGlobalConfigService configService;
|
||||
|
||||
private static final String DEFAULT_SERVICE = "alipay";
|
||||
|
||||
private static final String DEFAULT_KEY = "alipay";
|
||||
|
||||
/**
|
||||
* 保存信息至数据库
|
||||
*
|
||||
* @param alipayBO 支付宝支付培配置信息
|
||||
* @return 保存结果:T|F
|
||||
*/
|
||||
public boolean saveConfig(AlipayBO alipayBO) {
|
||||
// 必填信息不能为空
|
||||
if (null == alipayBO.getAppId() || null == alipayBO.getMerchantPrivateKey()) {
|
||||
throw new ServiceException("APPID和应用私钥不能为空,请检查!");
|
||||
}
|
||||
// 如果支付宝公钥为空,则三个证书不能为空
|
||||
if (null == alipayBO.getAlipayPublicKey())
|
||||
if (null == alipayBO.getMerchantCertPath() || null == alipayBO.getAlipayCertPath() || null == alipayBO.getAlipayRootCertPath())
|
||||
throw new ServiceException("证书模式下,[应用公钥证书][支付宝公钥证书][支付宝根证书]不能为空,请检查!");
|
||||
// 保存信息至数据库
|
||||
try {
|
||||
return configService.save(DEFAULT_SERVICE, alipayBO.getKey() != null ? alipayBO.getKey() : DEFAULT_KEY, alipayBO, true);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取配置信息(指定KEY)
|
||||
*
|
||||
* @param confKey 本条支付信息的KEY,如果为null,读取默认KEY
|
||||
* @return 支付宝支付配置信息
|
||||
*/
|
||||
public AlipayBO getConfig(String confKey) {
|
||||
if (null == confKey) {
|
||||
return this.getConfig();
|
||||
}
|
||||
return (AlipayBO) configService.get(DEFAULT_SERVICE, confKey, new AlipayBO());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取配置信息(默认KEY)
|
||||
*
|
||||
* @return 支付宝支付配置信息
|
||||
*/
|
||||
public AlipayBO getConfig() {
|
||||
return this.getConfig(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一支付服务接口(当面付、电脑网站支付、手机网站支付、APP支付)
|
||||
*
|
||||
* @param confKey 支付宝配置信息的KEY。如果加载默认的KEY,输入null
|
||||
* @param payType 支付类型:当面付(faceToFace),电脑网站支付(page),手机网站支付(wap),APP支付(app)
|
||||
* @param subject 商品名称
|
||||
* @param outTradeNo 商户订单号:商户内唯一
|
||||
* @param totalAmount 总金额(单位:元),实例:12.34
|
||||
* @param returnUrl 支付成功后跳转页面(只针对网站支付有效)
|
||||
* @param quitUrl 支付取消跳转页面(只针对手机网站支付有效)
|
||||
* @return 返回结果:当面付为二维码链接,其他均为网站Body代码
|
||||
*/
|
||||
public String pay(String confKey, String payType, String subject, String outTradeNo, String totalAmount, String returnUrl, String quitUrl) {
|
||||
// 必填信息不能为空
|
||||
if (null == subject || null == outTradeNo || null == totalAmount) {
|
||||
throw new ServiceException("商品名称、商户订单号、商品价格不能为空!");
|
||||
}
|
||||
// 1. 设置参数
|
||||
Factory.setOptions(this.getOptions(confKey));
|
||||
try {
|
||||
// 2. 调用API发起创建支付
|
||||
switch (payType.toLowerCase()) {
|
||||
// 当面付
|
||||
case "facetoface":
|
||||
// 创建订单
|
||||
AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace().preCreate(subject, outTradeNo, totalAmount);
|
||||
// 成功?
|
||||
if (ResponseChecker.success(response))
|
||||
return response.getQrCode();
|
||||
break;
|
||||
// 电脑网站支付
|
||||
case "page":
|
||||
if (null == returnUrl) {
|
||||
throw new ServiceException("电脑网站支付的跳转地址(returnUrl)不能为空!");
|
||||
}
|
||||
// 调用接口
|
||||
AlipayTradePagePayResponse response1 = Factory.Payment.Page().pay(subject, outTradeNo, totalAmount, returnUrl);
|
||||
// 成功?
|
||||
if (ResponseChecker.success(response1))
|
||||
return response1.getBody();
|
||||
break;
|
||||
// 手机网站支付
|
||||
case "wap":
|
||||
if (null == returnUrl || null == quitUrl) {
|
||||
throw new ServiceException("手机网站支付的失败退出地址(quitUrl)、成功跳转地址(returnUrl)不能为空!");
|
||||
}
|
||||
AlipayTradeWapPayResponse response2 = Factory.Payment.Wap().pay(subject, outTradeNo, totalAmount, quitUrl, returnUrl);
|
||||
// 成功
|
||||
if (ResponseChecker.success(response2))
|
||||
return response2.getBody();
|
||||
break;
|
||||
// APP支付
|
||||
case "app":
|
||||
AlipayTradeAppPayResponse response3 = Factory.Payment.App().pay(subject, outTradeNo, totalAmount);
|
||||
// 成功
|
||||
if (ResponseChecker.success(response3))
|
||||
return response3.getBody();
|
||||
break;
|
||||
// 其他类型:错误
|
||||
default:
|
||||
throw new ServiceException("输入的支付类型[" + payType + "]不在许可支付类型范围内。许可类型:当面付(faceToFace),电脑网站支付(page),手机网站支付(wap),APP支付(app)");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用遭遇异常,原因:" + e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询支付订单接口
|
||||
*
|
||||
* @param confKey 可选:支付宝配置信息的KEY。如果加载默认的KEY,输入null
|
||||
* @param tradeNo 特殊可选:支付宝交易号(订单号)
|
||||
* @param outTradeNo 特殊可选:商家订单号
|
||||
* @return 查询成功:{ 0:Y,1:支付宝交易号,2:商家订单号,3:交易状态,4:订单金额,5:买家ID,6:买家支付宝账号 } <br> 查询失败:{ E,错误代码,错误描述 }
|
||||
* @apiNote tradeNo 和 outTradeNo 不能同时为空。同时存在优先取 tradeNo。
|
||||
*/
|
||||
public String[] queryPay(String confKey, String tradeNo, String outTradeNo) {
|
||||
// 判断
|
||||
if (null == tradeNo && null == outTradeNo) {
|
||||
throw new ServiceException("tradeNo 和 outTradeNo 不能同时为空!");
|
||||
}
|
||||
// 设置参数
|
||||
Factory.setOptions(this.getOptions(confKey));
|
||||
try {
|
||||
// 执行查询
|
||||
AlipayTradeQueryResponse response = Factory.Payment.Common().optional("trade_no", tradeNo).query(outTradeNo);
|
||||
// 请求成功(即返回信息中没有sub_code)
|
||||
if (ResponseChecker.success(response)) {
|
||||
return new String[]{
|
||||
"Y",
|
||||
response.tradeNo,
|
||||
response.outTradeNo,
|
||||
response.tradeStatus,
|
||||
response.totalAmount,
|
||||
response.buyerUserId,
|
||||
response.buyerLogonId
|
||||
};
|
||||
} else {
|
||||
return new String[]{"E", response.subCode, response.subMsg};
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用遭遇异常,原因:" + e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款接口(支持部分退款)
|
||||
*
|
||||
* @param confKey 可选:支付宝配置信息的KEY。如果加载默认的KEY,输入null
|
||||
* @param tradeNo 特殊可选:商户订单号
|
||||
* @param outTradeNo 特殊可选:商户订单号
|
||||
* @param refundAmount 必填:退款金额
|
||||
* @param reason 可选:退款原因
|
||||
* @return 本次请求退款成功:{ 0:Y,1:支付宝交易号,2:商家订单号,3:退款请求号,4:总退款金额 } <br>
|
||||
* 历史请求退款成功:{ 0:N,1:支付宝交易号,2:商家订单号,3:退款请求号,4:退款金额 } <br>
|
||||
* 退款发生错误:{ 0:E,1:错误代码,2:错误描述 }
|
||||
* @apiNote tradeNo 和 outTradeNo 不能同时为空。同时存在优先取 tradeNo。
|
||||
*/
|
||||
public String[] refund(String confKey, String tradeNo, String outTradeNo, String refundAmount, String reason) {
|
||||
// 判断
|
||||
if (null == tradeNo && null == outTradeNo) {
|
||||
throw new ServiceException("tradeNo 和 outTradeNo 不能同时为空!");
|
||||
}
|
||||
// 设置参数
|
||||
Factory.setOptions(this.getOptions(confKey));
|
||||
try {
|
||||
// 生成唯一的款请求号
|
||||
String outRequestNo = IdUtil.simpleUUID();
|
||||
// 发起请求
|
||||
AlipayTradeRefundResponse response = Factory.Payment.Common()
|
||||
// 支付宝交易号
|
||||
.optional("trade_no", tradeNo)
|
||||
// 退款原因
|
||||
.optional("refund_reason", reason)
|
||||
// 退款请求号
|
||||
.optional("out_request_no", outRequestNo)
|
||||
// 执行退款
|
||||
.refund(outTradeNo, refundAmount);
|
||||
// 如果请求成功(即返回信息中没有sub_code)
|
||||
if (ResponseChecker.success(response)) {
|
||||
return new String[]{
|
||||
// 本次请求退款状态(即资金有改变,详情:https://opensupport.alipay.com/support/knowledge/27585/201602348776 )
|
||||
response.fundChange,
|
||||
response.tradeNo,
|
||||
response.outTradeNo,
|
||||
outRequestNo,
|
||||
response.refundFee
|
||||
};
|
||||
} else {
|
||||
return new String[]{"E", response.subCode, response.subMsg};
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用遭遇异常,原因:" + e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询退款
|
||||
*
|
||||
* @param confKey 可选:支付宝配置信息的KEY。如果加载默认的KEY,输入null
|
||||
* @param outTradeNo 必填:商户订单号
|
||||
* @param outRequestNo 必填:退款请求号
|
||||
* @return 已退款:{ Y,支付宝交易号,商家订单号,退款请求号,订单金额,退款金额,退款原因 } <br> 未退款:{ N,描述 } <br> 发生错误:{ E,错误代码,错误描述 }
|
||||
*/
|
||||
public String[] queryRefund(String confKey, String outTradeNo, String outRequestNo) {
|
||||
// 设置参数
|
||||
Factory.setOptions(this.getOptions(confKey));
|
||||
// 如果请求号为空,则表示全额退款,设置请求号位商家订单号
|
||||
if (null == outRequestNo) {
|
||||
outRequestNo = outTradeNo;
|
||||
}
|
||||
try {
|
||||
// 发起请求
|
||||
AlipayTradeFastpayRefundQueryResponse response = Factory.Payment.Common().queryRefund(outTradeNo, outRequestNo);
|
||||
// 如果请求成功(即返回信息中没有sub_code)
|
||||
if (ResponseChecker.success(response)) {
|
||||
// 如果该接口返回了查询数据,则代表退款成功(详情:https://opensupport.alipay.com/support/knowledge/27585/201602348776 )
|
||||
if (null != response.refundAmount) {
|
||||
return new String[]{
|
||||
"Y",
|
||||
response.tradeNo,
|
||||
response.outTradeNo,
|
||||
response.outRequestNo,
|
||||
response.totalAmount,
|
||||
response.refundAmount,
|
||||
response.refundReason
|
||||
};
|
||||
} else {
|
||||
return new String[]{
|
||||
"N",
|
||||
"该订单未退款或输入的退款请求号有误,请检查!"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return new String[]{"E", response.subCode, response.subMsg};
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用遭遇异常,原因:" + e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载对账单(不能查询当天或当月的)
|
||||
* https://developer.aliyun.com/article/710922
|
||||
*
|
||||
* @param confKey 可选:支付宝配置信息的KEY。如果加载默认的KEY,输入null
|
||||
* @param date 必填:交易的具体日期(如2022-01-01)或月份(2021-12)
|
||||
* @return 获取成功:{Y,下载URL} <br> 发生错误:{ E,错误代码,错误描述 }
|
||||
*/
|
||||
public String[] downloadBill(String confKey, String date) {
|
||||
// 设置参数
|
||||
Factory.setOptions(this.getOptions(confKey));
|
||||
try {
|
||||
// 发送请求
|
||||
AlipayDataDataserviceBillDownloadurlQueryResponse response = Factory.Payment.Common().downloadBill("trade", date);
|
||||
|
||||
// 如果请求成功(即返回信息中没有sub_code)
|
||||
if (ResponseChecker.success(response)) {
|
||||
return new String[]{
|
||||
"Y",
|
||||
response.billDownloadUrl
|
||||
};
|
||||
} else {
|
||||
return new String[]{"E", response.subCode, response.subMsg};
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用遭遇异常,原因:" + e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取配置信息
|
||||
*
|
||||
* @param confKey 支付宝配置信息的KEY。如果加载默认的KEY,输入null
|
||||
* @return 配置信息
|
||||
*/
|
||||
private Config getOptions(String confKey) {
|
||||
// 从数据库中读取配置信息
|
||||
AlipayBO alipayBO = this.getConfig(confKey);
|
||||
// 将配置信息写入支付宝支付配置类中
|
||||
Config config = new Config();
|
||||
config.protocol = alipayBO.getProtocol();
|
||||
config.gatewayHost = alipayBO.getGatewayHost();
|
||||
config.signType = alipayBO.getSignType();
|
||||
|
||||
config.appId = alipayBO.getAppId();
|
||||
|
||||
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
|
||||
config.merchantPrivateKey = alipayBO.getMerchantPrivateKey();
|
||||
// 如果三个证书不为空,则优先设置三个证书
|
||||
if (null != alipayBO.getMerchantCertPath() && null != alipayBO.getAlipayCertPath() && null != alipayBO.getAlipayRootCertPath()) {
|
||||
//注:证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径,优先从文件系统中加载,加载失败后会继续尝试从CLASS_PATH中加载
|
||||
config.merchantCertPath = alipayBO.getMerchantCertPath();
|
||||
config.alipayCertPath = alipayBO.getAlipayCertPath();
|
||||
config.alipayRootCertPath = alipayBO.getAlipayRootCertPath();
|
||||
} else if (null != alipayBO.getAlipayPublicKey()) {
|
||||
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
|
||||
config.alipayPublicKey = alipayBO.getAlipayPublicKey();
|
||||
} else {
|
||||
throw new ServiceException("配置信息有误:证书模式配置不完整或未配置公钥模式,请检查!");
|
||||
}
|
||||
|
||||
//可设置异步通知接收服务地址(可选)
|
||||
config.notifyUrl = alipayBO.getNotifyUrl();
|
||||
|
||||
//可设置AES密钥,调用AES加解密相关接口时需要(可选)
|
||||
config.encryptKey = alipayBO.getEncryptKey();
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.zxdmy.excite.component.aliyun;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.PolicyConditions;
|
||||
import com.aliyun.oss.model.PutObjectResult;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import com.zxdmy.excite.common.service.IGlobalConfigService;
|
||||
import com.zxdmy.excite.component.bo.AliyunOssBO;
|
||||
import com.zxdmy.excite.component.bo.AliyunSmsBO;
|
||||
import com.zxdmy.excite.component.vo.AliyunOssPolicyVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 阿里云对象存储服务
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/5 20:37
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class AliyunOssService {
|
||||
|
||||
IGlobalConfigService configService;
|
||||
|
||||
private static final String DEFAULT_SERVICE = "aliyunOss";
|
||||
|
||||
private static final String DEFAULT_KEY = "aliyunOss";
|
||||
|
||||
public boolean saveConfig(AliyunOssBO aliyunOssBO) throws JsonProcessingException {
|
||||
if (null == aliyunOssBO.getBucketName() || null == aliyunOssBO.getBucketDomain() || null == aliyunOssBO.getEndpoint() || null == aliyunOssBO.getAccessKeyId() || null == aliyunOssBO.getAccessKeySecret()) {
|
||||
throw new ServiceException("部分参数为空,请检查!");
|
||||
}
|
||||
if (null == aliyunOssBO.getKey()) {
|
||||
return configService.save(DEFAULT_SERVICE, DEFAULT_KEY, aliyunOssBO, true);
|
||||
} else {
|
||||
return configService.save(DEFAULT_SERVICE, aliyunOssBO.getKey(), aliyunOssBO, true);
|
||||
}
|
||||
}
|
||||
|
||||
public AliyunOssBO getConfig(String confKey) {
|
||||
AliyunOssBO aliyunOssBO = new AliyunOssBO();
|
||||
return (AliyunOssBO) configService.get(DEFAULT_SERVICE, confKey, aliyunOssBO);
|
||||
}
|
||||
|
||||
public AliyunOssBO getConfig() {
|
||||
return this.getConfig(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 前端直传用的方法
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public AliyunOssPolicyVO createUploadToken(String confKey, String fileName, Long expireTime) {
|
||||
// 读取秘钥等信息
|
||||
AliyunOssBO aliyunOssBO = this.getConfig(confKey);
|
||||
// 创建OSSClient实例。
|
||||
OSS oss = new OSSClientBuilder().build(aliyunOssBO.getEndpoint(), aliyunOssBO.getAccessKeyId(), aliyunOssBO.getAccessKeySecret());
|
||||
|
||||
PolicyConditions policyConditions = new PolicyConditions();
|
||||
// 策略:最大上传文件1G
|
||||
policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
|
||||
|
||||
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
|
||||
Date expireEndDate = new Date(expireEndTime);
|
||||
try {
|
||||
// 生成上传策略
|
||||
String postPolicy = oss.generatePostPolicy(expireEndDate, policyConditions);
|
||||
// 生成签名
|
||||
String postSignature = oss.calculatePostSignature(postPolicy);
|
||||
// 策略转码
|
||||
String base64Policy = Base64.encode(postPolicy);
|
||||
|
||||
// 构造返回数据
|
||||
return new AliyunOssPolicyVO()
|
||||
.setAccessKeyId(aliyunOssBO.getAccessKeyId())
|
||||
.setPolicy(base64Policy)
|
||||
.setSignature(postSignature)
|
||||
.setHost(aliyunOssBO.getBucketDomain())
|
||||
.setCallback("")
|
||||
.setKey(fileName);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
} finally {
|
||||
oss.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端直传用的方法
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public AliyunOssPolicyVO createUploadToken(String fileName, Long expireTime) {
|
||||
return this.createUploadToken(DEFAULT_KEY, fileName, expireTime);
|
||||
}
|
||||
|
||||
|
||||
public boolean uploadFile(String confKey, File file) {
|
||||
// 读取配置
|
||||
AliyunOssBO aliyunOssBO = this.getConfig(confKey);
|
||||
// 创建OSSClient实例。
|
||||
OSS oss = new OSSClientBuilder().build(aliyunOssBO.getEndpoint(), aliyunOssBO.getAccessKeyId(), aliyunOssBO.getAccessKeySecret());
|
||||
try {
|
||||
PutObjectResult putObjectRequest = oss.putObject(aliyunOssBO.getBucketName(), file.getName(), file);
|
||||
return putObjectRequest.getResponse().isSuccessful();
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
oss.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean uploadFile(File file) {
|
||||
return this.uploadFile(DEFAULT_KEY, file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package com.zxdmy.excite.component.aliyun;
|
||||
|
||||
import com.aliyun.dysmsapi20170525.Client;
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import com.zxdmy.excite.common.service.IGlobalConfigService;
|
||||
import com.zxdmy.excite.component.bo.AliyunSmsBO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 阿里云短信服务(Short Message Service)实现
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022-01-02 0002 23:33
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class AliyunSmsService {
|
||||
|
||||
IGlobalConfigService configService;
|
||||
|
||||
private static final String DEFAULT_SERVICE = "aliyunSms";
|
||||
|
||||
/**
|
||||
* 保存阿里云短信服务的开发者信息、消息模板相关信息
|
||||
* 注意:同一个系统的消息模板可能有多个,所以
|
||||
*
|
||||
* @param aliyunSmsBO 实体信息,其中KEY必填
|
||||
* @return 保存结果
|
||||
* @throws JsonProcessingException 异常
|
||||
*/
|
||||
public boolean saveAliyunSmsConfig(AliyunSmsBO aliyunSmsBO) throws JsonProcessingException {
|
||||
// 如果必填项为空,则返回错误信息
|
||||
if (null == aliyunSmsBO.getKey() || null == aliyunSmsBO.getAccessKeyId() || null == aliyunSmsBO.getAccessKeySecret() || null == aliyunSmsBO.getSignName() || null == aliyunSmsBO.getTemplateCode()) {
|
||||
throw new ServiceException("部分必填信息为空,请检查!");
|
||||
}
|
||||
// 保存
|
||||
return configService.save(DEFAULT_SERVICE, aliyunSmsBO.getKey(), aliyunSmsBO, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过配置KEY获取短信配置信息
|
||||
*
|
||||
* @param confKey 阿里云短信服务的配置key
|
||||
* @return 阿里云短信服务实体
|
||||
*/
|
||||
public AliyunSmsBO getAliyunSmsConfig(String confKey) {
|
||||
AliyunSmsBO aliyunSmsBO = new AliyunSmsBO();
|
||||
return (AliyunSmsBO) configService.get(DEFAULT_SERVICE, confKey, aliyunSmsBO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信服务
|
||||
*
|
||||
* @param confKey confKey,当前短信模板配置的key
|
||||
* @param phone 手机号数组
|
||||
* @param templateValue 消息模板中的参数值
|
||||
* @return 结果
|
||||
*/
|
||||
public boolean sendSmsOne(String confKey, String phone, String[] templateValue) {
|
||||
// 从数据库读取配置信息
|
||||
AliyunSmsBO aliyunSmsBO = this.getAliyunSmsConfig(confKey);
|
||||
if (null == aliyunSmsBO) {
|
||||
throw new ServiceException("阿里云短信服务配置信息有误/不存在,请核实");
|
||||
}
|
||||
// 如果输入的参数个数和配置信息里的参数个数不相等,则返回错误信息
|
||||
if (templateValue.length != aliyunSmsBO.getTemplateParam().length) {
|
||||
throw new ServiceException("templateValue个数有误,请核实");
|
||||
}
|
||||
// 发起短信并获取发送结果
|
||||
SendSmsResponse response = this.sendSms(aliyunSmsBO, phone, templateValue, null);
|
||||
// 结果不为空,进一步判断
|
||||
if (response != null) {
|
||||
// 打印结果
|
||||
// System.out.println(response.body.code); // 发送成功为OK
|
||||
// System.out.println(response.body.message); // 发送成功为OK
|
||||
// System.out.println(response.body.requestId); // 请求ID
|
||||
// System.out.println(response.body.bizId); // 请求ID
|
||||
return "OK".equals(response.body.code);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量发送短信
|
||||
* 其中官网中有批量发送短信的接口SendBatchSms。
|
||||
* 但我们这里还是逐个发送
|
||||
*
|
||||
* @param confKey 短信模板的key
|
||||
* @param smsMap 短信map,其中key为手机号,value为String[]数组,其大小与消息模板参数个数一致,并依次对应
|
||||
* @return 结果map
|
||||
*/
|
||||
public Map<String, Boolean> sendSmsBatch(String confKey, Map<String, String[]> smsMap) {
|
||||
// 从数据库读取配置信息
|
||||
AliyunSmsBO aliyunSmsBO = this.getAliyunSmsConfig(confKey);
|
||||
if (null == aliyunSmsBO) {
|
||||
throw new ServiceException("阿里云短信服务配置信息有误,请核实");
|
||||
}
|
||||
int templateLength = aliyunSmsBO.getTemplateParam().length;
|
||||
// 发起短信并获取发送结果
|
||||
SendSmsResponse response;
|
||||
// 记录结果的map
|
||||
Map<String, Boolean> result = new HashMap<>();
|
||||
// 遍历map,逐个构造并发送短信
|
||||
for (String phone : smsMap.keySet()) {
|
||||
// 参数值对等,则可以发送
|
||||
if (smsMap.get(phone).length == templateLength) {
|
||||
response = this.sendSms(aliyunSmsBO, phone, smsMap.get(phone), null);
|
||||
// 发送成功
|
||||
if (response != null && "OK".equals(response.body.code)) {
|
||||
result.put(phone, true);
|
||||
}
|
||||
// 发送失败
|
||||
else {
|
||||
result.put(phone, false);
|
||||
}
|
||||
}
|
||||
// 参数值不对等,直接标记失败
|
||||
else {
|
||||
result.put(phone, false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有方法:配置并发送短信实现类
|
||||
*
|
||||
* @param aliyunSmsBO 配置信息
|
||||
* @param phone 接收手机号
|
||||
* @param templateValue 模板JSON格式的参数
|
||||
* @param outId 流水号
|
||||
* @return 发送结果
|
||||
*/
|
||||
private SendSmsResponse sendSms(AliyunSmsBO aliyunSmsBO, String phone, String[] templateValue, String outId) {
|
||||
// 阿里云短信服务信息配置
|
||||
Config config = new Config()
|
||||
// 必填,开发者的 AccessKey ID
|
||||
.setAccessKeyId(aliyunSmsBO.getAccessKeyId())
|
||||
// 必填,开发者的 AccessKey Secret
|
||||
.setAccessKeySecret(aliyunSmsBO.getAccessKeySecret())
|
||||
// 默认,暂时不支持多Region,默认即可
|
||||
.setRegionId("cn-hangzhou")
|
||||
// 默认,产品域名,开发者无需替换
|
||||
.setEndpoint("dysmsapi.aliyuncs.com")
|
||||
// 可选,启用https协议
|
||||
.setProtocol("https");
|
||||
// 构造请求参数
|
||||
StringBuilder templateParam = new StringBuilder("{");
|
||||
for (int i = 0; i < aliyunSmsBO.getTemplateParam().length; i++) {
|
||||
templateParam.append("\"").append(aliyunSmsBO.getTemplateParam()[i]).append("\":\"").append(templateValue[i]).append("\",");
|
||||
}
|
||||
templateParam.append("}");
|
||||
// 短信请求信息配置
|
||||
SendSmsRequest sendSmsRequest = new SendSmsRequest()
|
||||
// 必填,接收短信的手机号
|
||||
.setPhoneNumbers(phone)
|
||||
// 必填,设置签名名称,应严格按"签名名称"填写,格式如:阿里云
|
||||
.setSignName(aliyunSmsBO.getSignName())
|
||||
// 必填,设置模板CODE,应严格按"模板CODE"填写,格式:SMS_88888888
|
||||
.setTemplateCode(aliyunSmsBO.getTemplateCode())
|
||||
// 可选,设置模板参数, 假如模板中存在变量需要替换则为必填项
|
||||
.setTemplateParam(templateParam.toString())
|
||||
// 可选,设置流水号
|
||||
//.setOutId("10086")
|
||||
;
|
||||
// 发起请求
|
||||
SendSmsResponse response;
|
||||
try {
|
||||
response = new Client(config).sendSms(sendSmsRequest);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.zxdmy.excite.component.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付宝支付配置信息
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/10 22:13
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AlipayBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 本条支付宝支付信息的KEY
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 必填:应用AppId,例如:2019091767145019
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 必填:应用名称
|
||||
*/
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* 必填:应用私钥(Java语言),例如:MIIEvQIBADANB ... ...
|
||||
*/
|
||||
private String merchantPrivateKey;
|
||||
|
||||
// 接口加签方式分:证书模式(收、退款)和公钥模式(只能收款)
|
||||
// 证书模式文档:https://opendocs.alipay.com/common/02kipl
|
||||
// 优先级:证书模式 > 非证书模式
|
||||
|
||||
/**
|
||||
* 证书模式:【应用公钥证书】文件路径,路径优先级:文件系统 > CLASS_PATH
|
||||
*/
|
||||
private String merchantCertPath;
|
||||
|
||||
/**
|
||||
* 证书模式:【支付宝公钥证书】文件路径,路径优先级:文件系统 > CLASS_PATH
|
||||
*/
|
||||
private String alipayCertPath;
|
||||
|
||||
/**
|
||||
* 证书模式:【支付宝根证书】文件路径,路径优先级:文件系统 > CLASS_PATH
|
||||
*/
|
||||
private String alipayRootCertPath;
|
||||
|
||||
/**
|
||||
* 公钥模式:只需要填写此【支付宝公钥】即可,无需赋值上面的三个证书路径
|
||||
*/
|
||||
private String alipayPublicKey;
|
||||
|
||||
/**
|
||||
* 可选:异步通知接收服务地址,例如:https://www.test.com/callback
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 可选:AES密钥(接口内容加密方式),调用AES加解密相关接口时需要
|
||||
*/
|
||||
private String encryptKey;
|
||||
|
||||
/**
|
||||
* 必填:协议类型:http | https 二选一
|
||||
*/
|
||||
private String protocol = "https";
|
||||
|
||||
/**
|
||||
* 必填:gatewayHost,默认:openapi.alipay.com
|
||||
*/
|
||||
private String gatewayHost = "openapi.alipay.com";
|
||||
|
||||
/**
|
||||
* 必填:签名类型,默认RSA2
|
||||
*/
|
||||
private String signType = "RSA2";
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.zxdmy.excite.component.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 阿里云OSS配置信息
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/5 20:18
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AliyunOssBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 本条OSS配置信息的key
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* OSS上的命名空间
|
||||
*/
|
||||
private String bucketName;
|
||||
|
||||
/**
|
||||
* Bucket的域名,由阿里云官方生成,该域名用来访问空间中的文件。
|
||||
*/
|
||||
private String bucketDomain;
|
||||
|
||||
/**
|
||||
* 自己通过CNAME域名解析绑定的域名
|
||||
*/
|
||||
private String bindDomain;
|
||||
|
||||
/**
|
||||
* 访问域名(地域节点),上传使用。
|
||||
* 详情请参考:https://help.aliyun.com/document_detail/31837.html
|
||||
*/
|
||||
private String endpoint;
|
||||
|
||||
/**
|
||||
* 阿里云账号(建议使用子账号)的AccessKey
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* 阿里云账号(建议使用子账号)的AccessKey的秘钥
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.zxdmy.excite.component.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 阿里云短信服务配置信息
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/3 20:28
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AliyunSmsBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 本条短信配置信息的key
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 必填。开发者的 AccessKey ID
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* 必填。开发者的 AccessKey Secret
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* 必填。短信模板的签名,严格按照"签名名称"填写,格式如:阿里云
|
||||
*/
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* 必填。模板CODE,应严格按"模板CODE"填写,格式:SMS_88888888
|
||||
*/
|
||||
private String templateCode;
|
||||
|
||||
/**
|
||||
* 选填。如果消息模板中有参数,则必填。设置模板参数,如:[code,data]
|
||||
*/
|
||||
private String[] templateParam;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.zxdmy.excite.component.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 第三方登录的配置信息实体
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/6 17:30
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class JustAuthBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 本条登录类型的KEY,如:qq、weibo等
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 客户端id:对应各平台的appKey
|
||||
*/
|
||||
private String appID;
|
||||
|
||||
/**
|
||||
* 客户端Secret:对应各平台的appSecret
|
||||
*/
|
||||
private String appSecret;
|
||||
|
||||
/**
|
||||
* 登录成功后的回调地址
|
||||
*/
|
||||
private String redirectUri;
|
||||
|
||||
/**
|
||||
* 只针对qq登录生效:是否需要申请unionid
|
||||
*/
|
||||
private Boolean unionId;
|
||||
|
||||
/**
|
||||
* 企业微信第三方授权用户类型:member|admin
|
||||
*/
|
||||
private String usertype;
|
||||
|
||||
/**
|
||||
* 企业微信登录:授权方的网页应用ID
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 支付宝的公钥,只针对支付宝、阿里云登陆有效
|
||||
*/
|
||||
private String publicKey;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.zxdmy.excite.component.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 描述
|
||||
* </p>
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/3 16:07
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class QiniuOssBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 本条七牛云配置信息的key(主键)
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 七牛云账号的秘钥管理之:AccessKey
|
||||
*/
|
||||
@NotBlank(message = "AccessKey不能为空")
|
||||
private String accessKey;
|
||||
|
||||
/**
|
||||
* 七牛云账号的秘钥管理之:SecretKey
|
||||
*/
|
||||
@NotBlank(message = "SecretKey不能为空")
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* 七牛云空间名称
|
||||
*/
|
||||
@NotBlank(message = "空间名称不能为空")
|
||||
private String bucket;
|
||||
|
||||
/**
|
||||
* 七牛云该空间(bucket)绑定的CDN加速域名
|
||||
*/
|
||||
@NotBlank(message = "CDN加速域名不能为空")
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 地域选择:https://developer.qiniu.com/kodo/1671/region-endpoint-fq
|
||||
* 华东:z0; 华北:z1; 华南:z2; 北美:na0; 东南亚:as0; 华东-浙江2:cn-east-2
|
||||
* 可以为空,填写其他字符,由系统自己选
|
||||
*/
|
||||
// @NotBlank(message = "地域不能为空")
|
||||
private String region;
|
||||
|
||||
/**
|
||||
* 七牛云该空间(bucket)绑定的CDN加速域名的协议 http | https
|
||||
*/
|
||||
@NotBlank(message = "CDN加速域名协议不能为空")
|
||||
private String protocol;
|
||||
|
||||
/*
|
||||
* 上传Token
|
||||
*/
|
||||
private String uploadToken;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.zxdmy.excite.component.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 描述
|
||||
* </p>
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/9 20:27
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class WeChatPayBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
/**
|
||||
* 本条微信支付配置信息的key(主键)
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 设置微信公众号或者小程序等的appid
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 微信支付商户号
|
||||
*/
|
||||
private String mchId;
|
||||
|
||||
/**
|
||||
* 设置APIv2密钥(微信支付商户密钥
|
||||
*/
|
||||
private String mchKey;
|
||||
|
||||
/**
|
||||
* APIv3密钥(微信支付商户密钥
|
||||
*/
|
||||
private String apiV3Key;
|
||||
/**
|
||||
* 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
|
||||
*/
|
||||
private String subAppId;
|
||||
|
||||
/**
|
||||
* 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
|
||||
*/
|
||||
private String subMchId;
|
||||
|
||||
/**
|
||||
* V2:apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
|
||||
*/
|
||||
private String keyPath;
|
||||
|
||||
/**
|
||||
* V3:key.pem文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
|
||||
*/
|
||||
private String privateKeyPath;
|
||||
|
||||
/**
|
||||
* V3:cert.pem文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
|
||||
*/
|
||||
private String privateCertPath;
|
||||
|
||||
/**
|
||||
* 通知地址.通知url必须为外网可访问的url,不能携带参数。公网域名必须为https。
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package com.zxdmy.excite.component.justauth;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import com.zxdmy.excite.common.service.IGlobalConfigService;
|
||||
import com.zxdmy.excite.component.bo.JustAuthBO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.zhyd.oauth.config.AuthConfig;
|
||||
import me.zhyd.oauth.enums.scope.AuthGiteeScope;
|
||||
import me.zhyd.oauth.model.AuthCallback;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.request.*;
|
||||
import me.zhyd.oauth.utils.AuthScopeUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 第三方登录服务接口
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/6 12:02
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class JustAuthService {
|
||||
|
||||
IGlobalConfigService configService;
|
||||
|
||||
private static final String DEFAULT_SERVICE = "justAuth";
|
||||
|
||||
private static final Set<String> CONF_KEY_SET = new HashSet<String>() {{
|
||||
add("qq");
|
||||
add("weibo");
|
||||
add("wechat_open");
|
||||
add("wechat_mp");
|
||||
add("wechat_enterprise");
|
||||
add("baidu");
|
||||
add("dingtalk");
|
||||
add("aliyun");
|
||||
add("alipay");
|
||||
add("huawei");
|
||||
add("feishu");
|
||||
add("gitee");
|
||||
add("github");
|
||||
}};
|
||||
|
||||
|
||||
/**
|
||||
* 保存配置信息
|
||||
*
|
||||
* @param authBO 配置信息类
|
||||
* @return 保存结果
|
||||
*/
|
||||
public boolean saveConfig(JustAuthBO authBO) throws JsonProcessingException {
|
||||
// 配置信息的key必须存在于当前的库中
|
||||
if (null != authBO.getKey() && CONF_KEY_SET.contains(authBO.getKey())) {
|
||||
// 三大必填信息不能为空
|
||||
if (null == authBO.getAppID() || null == authBO.getAppSecret() || null == authBO.getRedirectUri()) {
|
||||
throw new ServiceException("APPID、APPSecret和RedirectUri不能为空!请检查!");
|
||||
} else {
|
||||
return configService.save(DEFAULT_SERVICE, authBO.getKey(), authBO, true);
|
||||
}
|
||||
} else {
|
||||
throw new ServiceException("当前输入的配置KEY[" + authBO.getKey() + "]不在许可范围内,请检查!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置的KEY获取一条配置信息
|
||||
*
|
||||
* @param confKey 配置的KEY
|
||||
* @return 指定KEY的配置信息
|
||||
*/
|
||||
public JustAuthBO getConfig(String confKey) {
|
||||
// 配置信息的key必须存在于当前的库中
|
||||
if (!CONF_KEY_SET.contains(confKey)) {
|
||||
throw new ServiceException("当前输入的配置KEY[" + confKey + "]不在许可范围内,请检查!");
|
||||
}
|
||||
JustAuthBO justAuthBO = new JustAuthBO();
|
||||
return (JustAuthBO) configService.get(DEFAULT_SERVICE, confKey, justAuthBO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的登录配置信息
|
||||
*
|
||||
* @param desensitized 是否脱敏
|
||||
* @return 配置列表
|
||||
*/
|
||||
public List<JustAuthBO> getConfigList(Boolean desensitized) {
|
||||
JustAuthBO justAuthBO = new JustAuthBO();
|
||||
List<Object> objectList = configService.getList(DEFAULT_SERVICE, justAuthBO);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
List<JustAuthBO> justAuthBOList = new ArrayList<>();
|
||||
for (Object o : objectList) {
|
||||
justAuthBO = objectMapper.convertValue(o, justAuthBO.getClass());
|
||||
if (desensitized) {
|
||||
justAuthBO.setAppSecret(DesensitizedUtil.idCardNum(justAuthBO.getAppSecret(), 2, 2));
|
||||
justAuthBO.setPublicKey(DesensitizedUtil.idCardNum(justAuthBO.getPublicKey(), 2, 2));
|
||||
}
|
||||
justAuthBOList.add(justAuthBO);
|
||||
}
|
||||
return justAuthBOList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成登录授权的URL
|
||||
*
|
||||
* @param confKey 配置的KEY,也即授权来源(如qq、weibo等)
|
||||
* @param state 状态码(字符串)
|
||||
* @return 登录的链接
|
||||
*/
|
||||
public String createAuthorizeUrl(String confKey, String state) {
|
||||
if (!CONF_KEY_SET.contains(confKey)) {
|
||||
return null;
|
||||
}
|
||||
return this.createAuthRequest(confKey).authorize(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据回调信息获取登录用户的信息
|
||||
*
|
||||
* @param confKey 配置的KEY,也即授权来源(如qq、weibo等)
|
||||
* @param callback 接口回调信息
|
||||
* @return 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同
|
||||
*/
|
||||
public AuthUser getAuthUser(String confKey, AuthCallback callback) {
|
||||
if (!CONF_KEY_SET.contains(confKey)) {
|
||||
return null;
|
||||
}
|
||||
AuthRequest authRequest = this.createAuthRequest(confKey);
|
||||
// 根据返回的参数,执行登录请求(获取用户信息)
|
||||
AuthResponse<AuthUser> response = authRequest.login(callback);
|
||||
// 判断结果
|
||||
if (response.ok()) {
|
||||
return response.getData();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据具体地授权来源,获取授权请求工具类
|
||||
*
|
||||
* @param confKey 配置的KEY,也即授权来源(如qq、weibo等)
|
||||
* @return 授权工具类
|
||||
*/
|
||||
public AuthRequest createAuthRequest(String confKey) {
|
||||
if (!CONF_KEY_SET.contains(confKey)) {
|
||||
throw new ServiceException("当前输入的配置KEY[" + confKey + "]不在许可范围内,请检查!");
|
||||
}
|
||||
JustAuthBO authBO = this.getConfig(confKey);
|
||||
AuthRequest authRequest;
|
||||
switch (confKey) {
|
||||
// QQ登录
|
||||
case "qq":
|
||||
authRequest = new AuthQqRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.unionId(authBO.getUnionId())
|
||||
.build());
|
||||
break;
|
||||
// 微博登录
|
||||
case "weibo":
|
||||
authRequest = new AuthWeiboRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// 微信开放平台登录
|
||||
case "wechat_open":
|
||||
authRequest = new AuthWeChatOpenRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// 微信公众号登录
|
||||
case "wechat_mp":
|
||||
authRequest = new AuthWeChatMpRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// 微信企业号登录
|
||||
case "wechat_enterprise":
|
||||
authRequest = new AuthWeChatEnterpriseQrcodeRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.agentId(authBO.getAgentId())
|
||||
.build());
|
||||
break;
|
||||
// 百度登录
|
||||
case "baidu":
|
||||
authRequest = new AuthBaiduRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// 钉钉登录
|
||||
case "dingtalk":
|
||||
authRequest = new AuthDingTalkRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// 阿里云登录
|
||||
case "aliyun":
|
||||
authRequest = new AuthAliyunRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.alipayPublicKey(authBO.getPublicKey())
|
||||
.build());
|
||||
break;
|
||||
// 支付宝登录
|
||||
case "alipay":
|
||||
authRequest = new AuthAlipayRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.alipayPublicKey(authBO.getPublicKey())
|
||||
.build());
|
||||
break;
|
||||
// 华为登录
|
||||
case "huawei":
|
||||
authRequest = new AuthHuaweiRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// 飞书登录
|
||||
case "feishu":
|
||||
authRequest = new AuthFeishuRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
// Gitee登录
|
||||
case "gitee":
|
||||
authRequest = new AuthGiteeRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.scopes(AuthScopeUtils.getScopes(AuthGiteeScope.values()))
|
||||
.build());
|
||||
break;
|
||||
// GitHub登录
|
||||
case "github":
|
||||
authRequest = new AuthGithubRequest(AuthConfig.builder()
|
||||
.clientId(authBO.getAppID())
|
||||
.clientSecret(authBO.getAppSecret())
|
||||
.redirectUri(authBO.getRedirectUri())
|
||||
.build());
|
||||
break;
|
||||
default:
|
||||
throw new ServiceException("配置的KEY不存在,请检查!");
|
||||
}
|
||||
return authRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取当前auth服务支持的KEY的集合
|
||||
*
|
||||
* @return 支持的KEY的集合
|
||||
*/
|
||||
public Set<String> getConfKeySet() {
|
||||
return CONF_KEY_SET;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zxdmy.excite.component.oss;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "aliyun.oss")
|
||||
public class AliOSSProperties {
|
||||
private String endpoint;
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String bucketName;
|
||||
private String region;
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package com.zxdmy.excite.component.oss;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aliyun.oss.*;
|
||||
import com.aliyun.oss.model.*;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 阿里云 OSS 工具类
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class AliOSSUtils{
|
||||
@Autowired//将OSS的配置注入进来,后面需要用到
|
||||
private AliOSSProperties aliOSSProperties;
|
||||
@Resource
|
||||
protected HttpServletRequest request;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* oss文件下载
|
||||
* @param path 文件下载本地地址
|
||||
* @param pathOss oss地址
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public Boolean download(String path,String pathOss) throws IOException {
|
||||
|
||||
//获取阿里云OSS参数
|
||||
String endpoint = aliOSSProperties.getEndpoint();
|
||||
String accessKeyId = aliOSSProperties.getAccessKeyId();
|
||||
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
|
||||
String bucketName = aliOSSProperties.getBucketName();
|
||||
|
||||
// 创建连接
|
||||
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
|
||||
// content-disposition
|
||||
try {
|
||||
// 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
|
||||
// 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。
|
||||
ossClient.getObject(new GetObjectRequest(bucketName, pathOss), new File(path));
|
||||
} catch (OSSException oe) {
|
||||
System.out.println("Caught an OSSException, which means your request made it to OSS, "
|
||||
+ "but was rejected with an error response for some reason.");
|
||||
System.out.println("Error Message:" + oe.getErrorMessage());
|
||||
System.out.println("Error Code:" + oe.getErrorCode());
|
||||
System.out.println("Request ID:" + oe.getRequestId());
|
||||
System.out.println("Host ID:" + oe.getHostId());
|
||||
} catch (ClientException ce) {
|
||||
System.out.println("Caught an ClientException, which means the client encountered "
|
||||
+ "a serious internal problem while trying to communicate with OSS, "
|
||||
+ "such as not being able to access the network.");
|
||||
System.out.println("Error Message:" + ce.getMessage());
|
||||
} finally {
|
||||
if (ossClient != null) {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除oss文件
|
||||
* @param path 文件地址
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public Boolean delete(String path) throws IOException {
|
||||
|
||||
//获取阿里云OSS参数
|
||||
String endpoint = aliOSSProperties.getEndpoint();
|
||||
String accessKeyId = aliOSSProperties.getAccessKeyId();
|
||||
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
|
||||
String bucketName = aliOSSProperties.getBucketName();
|
||||
|
||||
// 创建连接
|
||||
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
|
||||
|
||||
try {
|
||||
// 删除文件。
|
||||
// 文件完整路径中不能包含Bucket名称。
|
||||
List<String> keys = new ArrayList<String>();
|
||||
keys.add(path);
|
||||
|
||||
DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(keys).withEncodingType("url"));
|
||||
List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
|
||||
try {
|
||||
for(String obj : deletedObjects) {
|
||||
String deleteObj = URLDecoder.decode(obj, "UTF-8");
|
||||
System.out.println(deleteObj);
|
||||
return !(ossClient.doesObjectExist(bucketName,path));
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (OSSException oe) {
|
||||
System.out.println("Caught an OSSException, which means your request made it to OSS, "
|
||||
+ "but was rejected with an error response for some reason.");
|
||||
System.out.println("Error Message:" + oe.getErrorMessage());
|
||||
System.out.println("Error Code:" + oe.getErrorCode());
|
||||
System.out.println("Request ID:" + oe.getRequestId());
|
||||
System.out.println("Host ID:" + oe.getHostId());
|
||||
} catch (ClientException ce) {
|
||||
System.out.println("Caught an ClientException, which means the client encountered "
|
||||
+ "a serious internal problem while trying to communicate with OSS, "
|
||||
+ "such as not being able to access the network.");
|
||||
System.out.println("Error Message:" + ce.getMessage());
|
||||
} finally {
|
||||
if (ossClient != null) {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找oss文件
|
||||
* @param path 文件地址
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public Boolean query(String path) throws IOException {
|
||||
|
||||
//获取阿里云OSS参数
|
||||
String endpoint = aliOSSProperties.getEndpoint();
|
||||
String accessKeyId = aliOSSProperties.getAccessKeyId();
|
||||
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
|
||||
String bucketName = aliOSSProperties.getBucketName();
|
||||
|
||||
// 创建连接
|
||||
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
|
||||
|
||||
try {
|
||||
// 判断文件是否存在。如果返回值为true,则文件存在,否则存储空间或者文件不存在。
|
||||
// 设置是否进行重定向或者镜像回源。默认值为true,表示忽略302重定向和镜像回源;如果设置isINoss为false,则进行302重定向或者镜像回源。
|
||||
//boolean isINoss = true;
|
||||
boolean found = ossClient.doesObjectExist(bucketName, path);
|
||||
//boolean found = ossClient.doesObjectExist(bucketName, objectName, isINoss);
|
||||
return found;
|
||||
} catch (OSSException oe) {
|
||||
System.out.println("Caught an OSSException, which means your request made it to OSS, "
|
||||
+ "but was rejected with an error response for some reason.");
|
||||
System.out.println("Error Message:" + oe.getErrorMessage());
|
||||
System.out.println("Error Code:" + oe.getErrorCode());
|
||||
System.out.println("Request ID:" + oe.getRequestId());
|
||||
System.out.println("Host ID:" + oe.getHostId());
|
||||
} catch (ClientException ce) {
|
||||
System.out.println("Caught an ClientException, which means the client encountered "
|
||||
+ "a serious internal problem while trying to communicate with OSS, "
|
||||
+ "such as not being able to access the network.");
|
||||
System.out.println("Error Message:" + ce.getMessage());
|
||||
} finally {
|
||||
if (ossClient != null) {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝oss地址文件位置至其他oss地址位置(源Bucket相同)
|
||||
* @param path
|
||||
* @param newPath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public Boolean copy(String path,String newPath) throws IOException {
|
||||
|
||||
//获取阿里云OSS参数
|
||||
String endpoint = aliOSSProperties.getEndpoint();
|
||||
String accessKeyId = aliOSSProperties.getAccessKeyId();
|
||||
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
|
||||
String bucketName = aliOSSProperties.getBucketName();
|
||||
String newBucketName = aliOSSProperties.getBucketName();
|
||||
|
||||
// 创建连接
|
||||
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
|
||||
|
||||
try {
|
||||
|
||||
ObjectMetadata objectMetadata = ossClient.getObjectMetadata(bucketName, path);
|
||||
// 获取被拷贝文件的大小。
|
||||
long contentLength = objectMetadata.getContentLength();
|
||||
long gb = 1024 * 1024 * 1024;
|
||||
//小文件上传
|
||||
if(contentLength < gb) {
|
||||
// 拷贝文件。
|
||||
CopyObjectResult result = ossClient.copyObject(bucketName, path, newBucketName, newPath);
|
||||
System.out.println("ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
|
||||
// 判断文件是否存在。如果返回值为true,则文件存在,否则存储空间或者文件不存在。
|
||||
// 设置是否进行重定向或者镜像回源。默认值为true,表示忽略302重定向和镜像回源;如果设置isINoss为false,则进行302重定向或者镜像回源。
|
||||
|
||||
}
|
||||
//大文件上传(分片上传[大于1GB])
|
||||
else {
|
||||
// 设置分片大小为10 MB。单位为字节。
|
||||
long partSize = 1024 * 1024 * 10;
|
||||
|
||||
// 计算分片总数。
|
||||
int partCount = (int) (contentLength / partSize);
|
||||
if (contentLength % partSize != 0) {
|
||||
partCount++;
|
||||
}
|
||||
System.out.println("total part count:" + partCount);
|
||||
|
||||
// 初始化拷贝任务。可以通过InitiateMultipartUploadRequest指定目标文件元信息。
|
||||
InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(newBucketName, newPath);
|
||||
// 拷贝源文件ContentType和UserMetadata,分片拷贝默认不拷贝源文件的ContentType和UserMetadata。
|
||||
ObjectMetadata metadata = new ObjectMetadata();
|
||||
metadata.setContentType(objectMetadata.getContentType());
|
||||
metadata.setUserMetadata(objectMetadata.getUserMetadata());
|
||||
initiateMultipartUploadRequest.setObjectMetadata(metadata);
|
||||
InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
|
||||
String uploadId = initiateMultipartUploadResult.getUploadId();
|
||||
|
||||
// 分片拷贝。
|
||||
List<PartETag> partETags = new ArrayList<PartETag>();
|
||||
for (int i = 0; i < partCount; i++) {
|
||||
// 计算每个分片的大小。
|
||||
long skipBytes = partSize * i;
|
||||
long size = Math.min(partSize, contentLength - skipBytes);
|
||||
|
||||
// 创建UploadPartCopyRequest。可以通过UploadPartCopyRequest指定限定条件。
|
||||
UploadPartCopyRequest uploadPartCopyRequest =
|
||||
new UploadPartCopyRequest(bucketName, path, newBucketName, newPath);
|
||||
uploadPartCopyRequest.setUploadId(uploadId);
|
||||
uploadPartCopyRequest.setPartSize(size);
|
||||
uploadPartCopyRequest.setBeginIndex(skipBytes);
|
||||
uploadPartCopyRequest.setPartNumber(i + 1);
|
||||
//Map headers = new HashMap();
|
||||
// 指定拷贝的源地址。
|
||||
// headers.put(OSSHeaders.COPY_OBJECT_SOURCE, "/examplebucket/desexampleobject.txt");
|
||||
// 指定源Object的拷贝范围。例如设置bytes=0~1023,表示拷贝1~1024字节的内容。
|
||||
// headers.put(OSSHeaders.COPY_SOURCE_RANGE, "bytes=0~1023");
|
||||
// 如果源Object的ETag值和您提供的ETag相等,则执行拷贝操作,并返回200 OK。
|
||||
// headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
|
||||
// 如果源Object的ETag值和您提供的ETag不相等,则执行拷贝操作,并返回200 OK。
|
||||
// headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_NONE_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
|
||||
// 如果指定的时间等于或者晚于文件实际修改时间,则正常拷贝文件,并返回200 OK。
|
||||
// headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_UNMODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
|
||||
// 如果源Object在用户指定的时间以后被修改过,则执行拷贝操作。
|
||||
// headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_MODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
|
||||
// uploadPartCopyRequest.setHeaders(headers);
|
||||
UploadPartCopyResult uploadPartCopyResult = ossClient.uploadPartCopy(uploadPartCopyRequest);
|
||||
|
||||
// 将返回的分片ETag保存到partETags中。
|
||||
partETags.add(uploadPartCopyResult.getPartETag());
|
||||
}
|
||||
|
||||
// 提交分片拷贝任务。
|
||||
CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
|
||||
newBucketName, newPath, uploadId, partETags);
|
||||
ossClient.completeMultipartUpload(completeMultipartUploadRequest);
|
||||
}
|
||||
|
||||
boolean found = ossClient.doesObjectExist(newBucketName, newPath);
|
||||
return found;
|
||||
} catch (OSSException oe) {
|
||||
System.out.println("Caught an OSSException, which means your request made it to OSS, "
|
||||
+ "but was rejected with an error response for some reason.");
|
||||
System.out.println("Error Message:" + oe.getErrorMessage());
|
||||
System.out.println("Error Code:" + oe.getErrorCode());
|
||||
System.out.println("Request ID:" + oe.getRequestId());
|
||||
System.out.println("Host ID:" + oe.getHostId());
|
||||
} catch (ClientException ce) {
|
||||
System.out.println("Caught an ClientException, which means the client encountered "
|
||||
+ "a serious internal problem while trying to communicate with OSS, "
|
||||
+ "such as not being able to access the network.");
|
||||
System.out.println("Error Message:" + ce.getMessage());
|
||||
} finally {
|
||||
if (ossClient != null) {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.zxdmy.excite.component.oss;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class OSSVO {
|
||||
|
||||
private String region;
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String bucketName;
|
||||
private String endpoint;
|
||||
|
||||
private String fileName;
|
||||
private String prefix;
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
package com.zxdmy.excite.component.qiniu;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qiniu.common.QiniuException;
|
||||
import com.qiniu.http.Response;
|
||||
import com.qiniu.storage.*;
|
||||
import com.qiniu.storage.model.FetchRet;
|
||||
import com.qiniu.storage.model.FileInfo;
|
||||
import com.qiniu.util.Auth;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import com.zxdmy.excite.common.service.IGlobalConfigService;
|
||||
import com.zxdmy.excite.component.vo.QiniuFileVO;
|
||||
import com.zxdmy.excite.component.bo.QiniuOssBO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 七牛云对象存储服务实现类
|
||||
* 更多接口,可以根据 https://developer.qiniu.com/kodo/1239/java 实现。
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022-01-02 0002 23:33
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class QiniuOssService {
|
||||
|
||||
IGlobalConfigService configService;
|
||||
|
||||
private static final String DEFAULT_SERVICE = "qiniu";
|
||||
private static final String DEFAULT_KEY = "qiniuOss";
|
||||
|
||||
/**
|
||||
* 将七牛云的信息保存至数据库
|
||||
*
|
||||
* @param qiniuOssBO 七牛云信息实体。如果指定key,则存储的配置信息的key即为所指定
|
||||
* @return 结果
|
||||
*/
|
||||
public boolean saveQiniuConfig(QiniuOssBO qiniuOssBO) {
|
||||
// 如果必填信息为空,则返回错误
|
||||
if (null == qiniuOssBO.getSecretKey() || null == qiniuOssBO.getAccessKey() || null == qiniuOssBO.getBucket() || null == qiniuOssBO.getDomain()) {
|
||||
throw new ServiceException("AK、SK、空间名称或者域名为空,请核实!");
|
||||
}
|
||||
// 核实无误,填充数据
|
||||
if (null == qiniuOssBO.getProtocol()) {
|
||||
qiniuOssBO.setProtocol("http");
|
||||
}
|
||||
try {
|
||||
if (null == qiniuOssBO.getKey()) {
|
||||
return configService.save(DEFAULT_SERVICE, DEFAULT_KEY, qiniuOssBO, true);
|
||||
}
|
||||
return configService.save(DEFAULT_SERVICE, qiniuOssBO.getKey(), qiniuOssBO, true);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取七牛云配置信息(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @return 七牛云配置信息实体
|
||||
*/
|
||||
public QiniuOssBO getQiniuConfig(String confKey) {
|
||||
QiniuOssBO qiniuOssBO = new QiniuOssBO();
|
||||
qiniuOssBO = (QiniuOssBO) configService.get(DEFAULT_SERVICE, confKey, qiniuOssBO);
|
||||
return qiniuOssBO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取七牛云配置信息(默认key)
|
||||
*
|
||||
* @return 七牛云配置信息实体
|
||||
*/
|
||||
public QiniuOssBO getQiniuConfig() {
|
||||
return this.getQiniuConfig(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取七牛云配置信息,并脱敏(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @return 七牛云配置信息实体
|
||||
*/
|
||||
public QiniuOssBO getQiniuConfigHide(String confKey) {
|
||||
QiniuOssBO qiniuOssBO = new QiniuOssBO();
|
||||
qiniuOssBO = (QiniuOssBO) configService.get(DEFAULT_SERVICE, confKey, qiniuOssBO);
|
||||
qiniuOssBO.setAccessKey(DesensitizedUtil.idCardNum(qiniuOssBO.getAccessKey(), 2, 2));
|
||||
qiniuOssBO.setSecretKey(DesensitizedUtil.idCardNum(qiniuOssBO.getSecretKey(), 2, 2));
|
||||
return qiniuOssBO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库读取七牛云配置信息,并脱敏(默认key)
|
||||
*
|
||||
* @return 七牛云配置信息实体
|
||||
*/
|
||||
public QiniuOssBO getQiniuConfigHide() {
|
||||
return this.getQiniuConfigHide(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部的七牛云配置信息
|
||||
*
|
||||
* @param desensitized 是否脱敏:true:脱敏, false:不脱敏
|
||||
* @return 读取全部配置信息
|
||||
*/
|
||||
public List<QiniuOssBO> getQiniuConfigList(Boolean desensitized) {
|
||||
QiniuOssBO qiniuOssBO = new QiniuOssBO();
|
||||
List<Object> objectList = configService.getList(DEFAULT_SERVICE, qiniuOssBO);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
List<QiniuOssBO> qiniuOssBOList = new ArrayList<>();
|
||||
for (Object o : objectList) {
|
||||
qiniuOssBO = objectMapper.convertValue(o, qiniuOssBO.getClass());
|
||||
if (desensitized) {
|
||||
qiniuOssBO.setAccessKey(DesensitizedUtil.idCardNum(qiniuOssBO.getAccessKey(), 2, 2));
|
||||
qiniuOssBO.setSecretKey(DesensitizedUtil.idCardNum(qiniuOssBO.getSecretKey(), 2, 2));
|
||||
}
|
||||
qiniuOssBOList.add(qiniuOssBO);
|
||||
}
|
||||
return qiniuOssBOList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成上传的Token(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @param fileKey 文件的key,又称文件名,可以带路径
|
||||
* @param expireSeconds 过期时间
|
||||
* @return 结果
|
||||
*/
|
||||
public QiniuOssBO createUploadToken(String confKey, String fileKey, Long expireSeconds) {
|
||||
// 如果必填信息为空,则返回
|
||||
if (null == fileKey || "".equals(fileKey)) {
|
||||
throw new ServiceException("文件的key为空,请核实!");
|
||||
}
|
||||
// 读取配置信息
|
||||
QiniuOssBO qiniuOssBO = new QiniuOssBO();
|
||||
qiniuOssBO = (QiniuOssBO) configService.get(DEFAULT_SERVICE, confKey, qiniuOssBO);
|
||||
// 生成Token
|
||||
Auth auth = Auth.create(qiniuOssBO.getAccessKey(), qiniuOssBO.getSecretKey());
|
||||
String upToken = auth.uploadToken(qiniuOssBO.getBucket(), fileKey, expireSeconds, null);
|
||||
// 写入Token,并返回
|
||||
qiniuOssBO.setUploadToken(upToken);
|
||||
return qiniuOssBO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成上传的Token(默认key)
|
||||
*
|
||||
* @param fileKey 文件的key,又称文件名
|
||||
* @param expireSeconds 过期时间
|
||||
* @return 结果
|
||||
*/
|
||||
public QiniuOssBO createUploadToken(String fileKey, Long expireSeconds) {
|
||||
return this.createUploadToken(DEFAULT_KEY, fileKey, expireSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后端上传文件(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @param file 文件
|
||||
* @return 结果
|
||||
*/
|
||||
public QiniuFileVO uploadFile(String confKey, File file) {
|
||||
if (null == file) {
|
||||
throw new ServiceException("上传的文件为空,请检查!");
|
||||
}
|
||||
// 生成Token
|
||||
QiniuOssBO qiniuOssBO = this.createUploadToken(confKey, file.getName(), 3600L);
|
||||
// 配置信息
|
||||
Configuration configuration = new Configuration(this.createRegion(qiniuOssBO.getRegion()));
|
||||
// 构造上传管理类
|
||||
UploadManager uploadManager = new UploadManager(configuration);
|
||||
// 进行上传
|
||||
try {
|
||||
Response response = uploadManager.put(file, file.getName(), qiniuOssBO.getUploadToken());
|
||||
if (response.isOK()) {
|
||||
return new QiniuFileVO()
|
||||
.setProtocol(qiniuOssBO.getProtocol())
|
||||
.setDomain(qiniuOssBO.getDomain())
|
||||
.setKey(qiniuOssBO.getKey());
|
||||
}
|
||||
return null;
|
||||
} catch (QiniuException ex) {
|
||||
throw new ServiceException(ex.response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后端上传文件(默认key)
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 七牛云配置信息实体
|
||||
*/
|
||||
public QiniuFileVO uploadFile(File file) {
|
||||
return this.uploadFile(DEFAULT_KEY, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抓取网络资源到空间(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @param fileKey 文件名
|
||||
* @param remoteSrcUrl 远程链接
|
||||
* @return 文件信息
|
||||
*/
|
||||
public QiniuFileVO uploadFileFromUrl(String confKey, String fileKey, String remoteSrcUrl) {
|
||||
QiniuOssBO qiniuOssBO = this.getQiniuConfig(confKey);
|
||||
if (qiniuOssBO == null) {
|
||||
throw new ServiceException("key为[" + confKey + "]的配置信息为空,请检查!");
|
||||
}
|
||||
BucketManager bucketManager = this.createBucketManager(qiniuOssBO);
|
||||
//抓取网络资源到空间
|
||||
try {
|
||||
FetchRet fetchRet = bucketManager.fetch(remoteSrcUrl, qiniuOssBO.getBucket(), fileKey);
|
||||
return new QiniuFileVO()
|
||||
.setProtocol(qiniuOssBO.getProtocol())
|
||||
.setDomain(qiniuOssBO.getDomain())
|
||||
.setKey(fetchRet.key)
|
||||
.setHash(fetchRet.hash)
|
||||
.setMimeType(fetchRet.mimeType)
|
||||
.setSize(fetchRet.fsize);
|
||||
} catch (QiniuException ex) {
|
||||
throw new ServiceException(ex.response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抓取网络资源到空间(默认KEY)
|
||||
*
|
||||
* @param fileKey 文件名
|
||||
* @param remoteSrcUrl 远程链接
|
||||
* @return 文件信息
|
||||
*/
|
||||
public QiniuFileVO uploadFileFromUrl(String fileKey, String remoteSrcUrl) {
|
||||
return this.uploadFileFromUrl(DEFAULT_KEY, fileKey, remoteSrcUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛云空间的全部文件(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @param prefix 文件前缀
|
||||
* @param delimiter 文件分隔符
|
||||
* @return 文件信息列表
|
||||
*/
|
||||
public List<FileInfo> getQiniuFileListAll(String confKey, String prefix, String delimiter) {
|
||||
QiniuOssBO qiniuOssBO = this.getQiniuConfig(confKey);
|
||||
if (qiniuOssBO == null) {
|
||||
throw new ServiceException("key为[" + confKey + "]的配置信息为空,请检查!");
|
||||
}
|
||||
BucketManager bucketManager = this.createBucketManager(qiniuOssBO);
|
||||
BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(qiniuOssBO.getBucket(), prefix, 1000, delimiter);
|
||||
// 记录返回值
|
||||
List<FileInfo> fileInfoList = new ArrayList<>();
|
||||
while (fileListIterator.hasNext()) {
|
||||
fileInfoList.addAll(Arrays.asList(fileListIterator.next()));
|
||||
}
|
||||
return fileInfoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛云空间的全部文件(默认key)
|
||||
*
|
||||
* @param prefix 文件前缀
|
||||
* @param delimiter 文件分隔符
|
||||
* @return 文件信息列表
|
||||
*/
|
||||
public List<FileInfo> getQiniuFileListAll(String prefix, String delimiter) {
|
||||
return this.getQiniuFileListAll(DEFAULT_KEY, prefix, delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛云空间的分页文件列表(分页),如果页数超限,则返回第一页(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @param page 页号
|
||||
* @param limit 每页条数
|
||||
* @param prefix 前缀
|
||||
* @param delimiter 分隔符
|
||||
* @return 页面类Page
|
||||
*/
|
||||
public Page<FileInfo> getQiniuFileListPage(String confKey, Integer page, Integer limit, String prefix, String delimiter) {
|
||||
// 定义返回Page实体
|
||||
Page<FileInfo> fileInfoPage = new Page<>();
|
||||
// 获取配置
|
||||
QiniuOssBO qiniuOssBO = this.getQiniuConfig(confKey);
|
||||
if (qiniuOssBO == null) {
|
||||
throw new ServiceException("key为[" + confKey + "]的配置信息为空,请检查!");
|
||||
}
|
||||
// 生成管理器
|
||||
BucketManager bucketManager = this.createBucketManager(qiniuOssBO);
|
||||
// 获取列表迭代器
|
||||
BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(qiniuOssBO.getBucket(), prefix, limit, delimiter);
|
||||
// 遍历直至找到指定页数
|
||||
long total = 0;
|
||||
while (fileListIterator.hasNext()) {
|
||||
page--;
|
||||
if (page == 0) {
|
||||
FileInfo[] items = fileListIterator.next();
|
||||
fileInfoPage.setRecords(Arrays.asList(items));
|
||||
total += items.length;
|
||||
} else {
|
||||
total += fileListIterator.next().length;
|
||||
}
|
||||
}
|
||||
if (fileInfoPage.getRecords().size() == 0) {
|
||||
fileInfoPage.setRecords(Arrays.asList(fileListIterator.next()));
|
||||
}
|
||||
fileInfoPage.setCurrent(page).setSize(limit).setTotal(total);
|
||||
return fileInfoPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛云空间的分页文件列表(分页),如果页数超限,则返回第一页(默认key)
|
||||
*
|
||||
* @param page 页号
|
||||
* @param limit 每页条数
|
||||
* @param prefix 前缀
|
||||
* @param delimiter 分隔符
|
||||
* @return 页面类Page
|
||||
*/
|
||||
public Page<FileInfo> getQiniuFileListPage(Integer page, Integer limit, String prefix, String delimiter) {
|
||||
return this.getQiniuFileListPage(DEFAULT_KEY, page, limit, prefix, delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载链接(一般针对私有资源使用)(指定key)
|
||||
*
|
||||
* @param confKey 配置文件的key,不同的key对应不同的存储空间
|
||||
* @param fileKey 下载资源在七牛云存储的key
|
||||
* @param attName 下载文件的名字。null:与存储的文件名相同
|
||||
* @param expireSeconds 有效期(秒),一般取3600(即1小时)
|
||||
* @return 下载链接
|
||||
*/
|
||||
public String getDownloadUrl(String confKey, String fileKey, String attName, Long expireSeconds) {
|
||||
// 获取配置
|
||||
QiniuOssBO qiniuOssBO = this.getQiniuConfig(confKey);
|
||||
if (qiniuOssBO == null) {
|
||||
throw new ServiceException("key为[" + confKey + "]的配置信息为空,请检查!");
|
||||
}
|
||||
DownloadUrl url = new DownloadUrl(qiniuOssBO.getDomain(), "https".equals(qiniuOssBO.getProtocol()), fileKey);
|
||||
if (null != attName) {
|
||||
url.setAttname(attName);
|
||||
}
|
||||
Auth auth = Auth.create(qiniuOssBO.getAccessKey(), qiniuOssBO.getSecretKey());
|
||||
long deadline = expireSeconds + new Date().getTime();
|
||||
String downloadUrl = "";
|
||||
try {
|
||||
downloadUrl = url.buildURL(auth, deadline);
|
||||
} catch (QiniuException e) {
|
||||
throw new ServiceException(e.response.toString());
|
||||
}
|
||||
return downloadUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载链接(一般针对私有资源使用)(默认key)
|
||||
*
|
||||
* @param fileKey 下载资源在七牛云存储的key
|
||||
* @param attName 下载文件的名字。null:与存储的文件名相同
|
||||
* @param expireSeconds 有效期(秒),一般取3600(即1小时)
|
||||
* @return 下载链接
|
||||
*/
|
||||
public String getDownloadUrl(String fileKey, String attName, Long expireSeconds) {
|
||||
return this.getDownloadUrl(DEFAULT_KEY, fileKey, attName, expireSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置信息生成空间管理器
|
||||
*
|
||||
* @param qiniuOssBO 配置信息
|
||||
* @return 空间管理器
|
||||
*/
|
||||
private BucketManager createBucketManager(QiniuOssBO qiniuOssBO) {
|
||||
// 生成Auth授权类
|
||||
Auth auth = Auth.create(qiniuOssBO.getAccessKey(), qiniuOssBO.getSecretKey());
|
||||
// 配置信息,
|
||||
Configuration configuration = new Configuration(this.createRegion(qiniuOssBO.getRegion()));
|
||||
// 返回
|
||||
return new BucketManager(auth, configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据空间地址设置Region
|
||||
* 详情:https://developer.qiniu.com/kodo/1671/region-endpoint-fq
|
||||
*
|
||||
* @param re 空间地域:华东:z0; 华北:z1; 华南:z2; 北美:na0; 东南亚:as0; 华东-浙江2:cn-east-2
|
||||
* @return 地域类
|
||||
*/
|
||||
private Region createRegion(String re) {
|
||||
// 设置区域:如果不为空
|
||||
Region region;
|
||||
switch (re) {
|
||||
case "z0":
|
||||
region = Region.huadong();
|
||||
break;
|
||||
case "z1":
|
||||
region = Region.huabei();
|
||||
break;
|
||||
case "z2":
|
||||
region = Region.huanan();
|
||||
break;
|
||||
case "na0":
|
||||
region = Region.beimei();
|
||||
break;
|
||||
case "as0":
|
||||
region = Region.xinjiapo();
|
||||
break;
|
||||
case "cn-east-2":
|
||||
region = Region.huadongZheJiang2();
|
||||
break;
|
||||
default:
|
||||
region = new Region.Builder().autoRegion("https://uc.qbox.me");
|
||||
}
|
||||
return region;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.zxdmy.excite.component.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 此[表现层对象]用来返回前端上传时,需要用到的各种参数。
|
||||
* 应用场景:后端生成Policy,前端直传阿里云OSS。上传最大值:5G。
|
||||
* 参考资料:
|
||||
* https://help.aliyun.com/document_detail/31927.htm
|
||||
* https://help.aliyun.com/document_detail/31926.htm
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/5 21:06
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AliyunOssPolicyVO implements Serializable {
|
||||
|
||||
/***
|
||||
* Bucket域名,即Endpoint访问域名。格式为:https://{bucketName}.{endpoint},如:https://bucket-name.oss-cn-hangzhou.aliyuncs.com
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* 文件完整名(如果有前缀则需要携带),如:hello.zip,user/10001/hello.rar
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 用户表单上传的策略(Policy),Policy为经过Base64编码过的字符串。详情请参见 https://help.aliyun.com/document_detail/31988.htm?spm=a2c4g.11186623.0.0.69de5458ikCoa2#section-d5z-1ww-wdb
|
||||
*/
|
||||
private String policy;
|
||||
|
||||
/**
|
||||
* 用户请求的AccessKey ID。
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* 设置阿里云OSS服务端返回状态码为200,不设置则默认返回状态码204。
|
||||
*/
|
||||
private final String success_action_status = "200";
|
||||
|
||||
/**
|
||||
* 上传后的阿里云OSS服务器回调功能。即上传完成后,阿里云向此地址发送一个回调。如果不涉及上传回调,请不要填充值。
|
||||
*/
|
||||
private String callback;
|
||||
|
||||
/**
|
||||
* 对Policy签名后的字符串。详情请参见:https://help.aliyun.com/document_detail/31988.htm?spm=a2c4g.11186623.0.0.69de5458ikCoa2#section-wny-mww-wdb
|
||||
*/
|
||||
private String signature;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.zxdmy.excite.component.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 描述
|
||||
* </p>
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022/1/4 11:51
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class QiniuFileVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
public String key;
|
||||
|
||||
/**
|
||||
* 文件hash值
|
||||
*/
|
||||
public String hash;
|
||||
|
||||
/**
|
||||
* 文件的md5值
|
||||
*/
|
||||
public String md5;
|
||||
|
||||
/**
|
||||
* 文件大小,单位:字节
|
||||
*/
|
||||
public long size;
|
||||
|
||||
/**
|
||||
* 文件上传时间,单位为:100纳秒
|
||||
*/
|
||||
public long putTime;
|
||||
|
||||
/**
|
||||
* 文件的mimeType
|
||||
*/
|
||||
public String mimeType;
|
||||
|
||||
/**
|
||||
* 文件的状态,0表示启用,1表示禁用
|
||||
*/
|
||||
public int status;
|
||||
|
||||
/**
|
||||
* 七牛云该空间(bucket)绑定的CDN加速域名
|
||||
*/
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 七牛云该空间(bucket)绑定的CDN加速域名的协议 http | https
|
||||
*/
|
||||
private String protocol;
|
||||
|
||||
/*
|
||||
* 上传Token
|
||||
*/
|
||||
private String uploadToken;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.zxdmy.excite.component.wechat;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||
import com.github.binarywang.wxpay.bean.result.*;
|
||||
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import com.zxdmy.excite.common.exception.ServiceException;
|
||||
import com.zxdmy.excite.common.service.IGlobalConfigService;
|
||||
import com.zxdmy.excite.common.utils.HttpServletRequestUtil;
|
||||
import com.zxdmy.excite.component.bo.WeChatPayBO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 微信支付服务实现类
|
||||
* 只实现V3版本的接口(文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml )
|
||||
*
|
||||
* @author 沈松
|
||||
* @since 2022-01-02 0002 23:34
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class WeChatPayService {
|
||||
|
||||
private IGlobalConfigService configService;
|
||||
|
||||
private static final String DEFAULT_SERVICE = "wechatPay";
|
||||
|
||||
private static final String DEFAULT_KEY = "wechatPay";
|
||||
|
||||
// 千万不要使用这个垃圾的微信支付沙盒环境.....
|
||||
private static final Boolean SAND_BOX_ENV = false;
|
||||
|
||||
/**
|
||||
* 保存微信支付配置信息至数据库
|
||||
*
|
||||
* @param weChatPayBO 微信支付实体
|
||||
* @return 结果:T|F
|
||||
* @throws JsonProcessingException 异常
|
||||
*/
|
||||
public boolean saveConfig(WeChatPayBO weChatPayBO) throws JsonProcessingException {
|
||||
// 必填信息不能为空
|
||||
if (null == weChatPayBO.getAppId() || null == weChatPayBO.getMchId() || null == weChatPayBO.getMchKey()) {
|
||||
throw new ServiceException("APPID、商户号、商户秘钥不能为空!");
|
||||
}
|
||||
// 保存并返回结果
|
||||
return configService.save(DEFAULT_SERVICE, null != weChatPayBO.getKey() ? weChatPayBO.getKey() : DEFAULT_KEY, weChatPayBO, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库中读取配置信息
|
||||
*
|
||||
* @param confKey 微信支付的配置KEY
|
||||
* @return 微信支付配置信息
|
||||
*/
|
||||
public WeChatPayBO getConfig(String confKey) {
|
||||
if (null == confKey) {
|
||||
return this.getConfig();
|
||||
}
|
||||
return (WeChatPayBO) configService.get(DEFAULT_SERVICE, confKey, new WeChatPayBO());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库中读取配置信息(默认KEY)
|
||||
*
|
||||
* @return 微信支付配置信息
|
||||
*/
|
||||
public WeChatPayBO getConfig() {
|
||||
return this.getConfig(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下单接口(只设置了必填信息)(V3版本)
|
||||
*
|
||||
* @param confKey 必填:微信支付配置信息的KEY。如果使用默认KEY,输入null
|
||||
* @param tradeType 必填:交易类型:jsapi(含小程序)、app、h5、native
|
||||
* @param description 必填:商品描述(商品标题)
|
||||
* @param outTradeNo 必填:商家订单号
|
||||
* @param total 必填:商品金额(单位:分)
|
||||
* @param openId 特殊必填:支付用户的OpenId,JSAPI支付时必填。
|
||||
* @return 支付返回结果:<br>APP支付、JSAPI支付为[预支付交易会话标识] <br>Native支付为[二维码链接] <br>H5支付为[支付跳转链接]
|
||||
*/
|
||||
public String pay(String confKey, String tradeType, String description, String outTradeNo, Integer total, String openId) {
|
||||
// 构建统一下单请求参数对象
|
||||
WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request = new WxPayUnifiedOrderV3Request();
|
||||
// 对象中写入数据
|
||||
wxPayUnifiedOrderV3Request
|
||||
// 【1】必填信息
|
||||
// 应用ID:必填,可不填,通过配置信息读取
|
||||
// .setAppid(weChatPayBO.getAppId())
|
||||
// 直连商户号:必填,可不填,通过配置信息读取
|
||||
// .setMchid(weChatPayBO.getMchId())
|
||||
// 商品描述:必填
|
||||
.setDescription(description)
|
||||
// 商户订单号:必填,同一个商户号下唯一
|
||||
.setOutTradeNo(outTradeNo)
|
||||
// 通知地址:必填,公网域名必须为https,外网可访问。
|
||||
// 可不填,通过配置信息读取
|
||||
.setNotifyUrl(this.getConfig(confKey).getNotifyUrl())
|
||||
// 订单金额:单位(分)
|
||||
.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(total))
|
||||
// 【2】选填信息
|
||||
// 附加信息
|
||||
.setAttach("附加信息")
|
||||
// 订单优惠标记
|
||||
.setGoodsTag("ABCD")
|
||||
// ...
|
||||
;
|
||||
|
||||
try {
|
||||
// 根据请求类型,返回指定类型,其中包含:【3】条件必填信息
|
||||
switch (tradeType.toLowerCase()) {
|
||||
// Native支付
|
||||
case "native":
|
||||
return this.getWxPayService(confKey).unifiedOrderV3(TradeTypeEnum.NATIVE, wxPayUnifiedOrderV3Request).getCodeUrl();
|
||||
// JSAPI支付
|
||||
case "jsapi":
|
||||
// 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
|
||||
wxPayUnifiedOrderV3Request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId));
|
||||
return this.getWxPayService(confKey).unifiedOrderV3(TradeTypeEnum.JSAPI, wxPayUnifiedOrderV3Request).getPrepayId();
|
||||
// H5支付
|
||||
case "h5":
|
||||
wxPayUnifiedOrderV3Request.setSceneInfo(
|
||||
new WxPayUnifiedOrderV3Request.SceneInfo()
|
||||
// 用户终端IP
|
||||
.setPayerClientIp(HttpServletRequestUtil.getRemoteIP())
|
||||
.setH5Info(
|
||||
new WxPayUnifiedOrderV3Request.H5Info()
|
||||
// 场景类型
|
||||
.setType("wechat")
|
||||
)
|
||||
);
|
||||
return this.getWxPayService(confKey).unifiedOrderV3(TradeTypeEnum.H5, wxPayUnifiedOrderV3Request).getH5Url();
|
||||
// APP支付
|
||||
case "app":
|
||||
return this.getWxPayService(confKey).unifiedOrderV3(TradeTypeEnum.APP, wxPayUnifiedOrderV3Request).getPrepayId();
|
||||
default:
|
||||
throw new ServiceException("输入的[" + tradeType + "]不合法,只能为native、jsapi、h5、app其一,请核实!");
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单查询接口(新版V3)
|
||||
*
|
||||
* @param confKey 必填:微信支付配置信息的KEY。如果使用默认KEY,输入null
|
||||
* @param transactionId 微信订单号
|
||||
* @param outTradeNo 商户系统内部的订单号,当没提供transactionId时需要传这个。
|
||||
* @return 查询订单 返回结果对象
|
||||
*/
|
||||
public WxPayOrderQueryV3Result query(String confKey, String transactionId, String outTradeNo) {
|
||||
try {
|
||||
return this.getWxPayService(confKey).queryOrderV3(transactionId, outTradeNo);
|
||||
} catch (WxPayException e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款接口
|
||||
*
|
||||
* @param confKey 必填:微信支付配置信息的KEY。如果使用默认KEY,输入null
|
||||
* @param outTradeNo 商户订单号
|
||||
* @param outRefundNo 商户退款单号
|
||||
* @param total 订单总金额(单位:分)
|
||||
* @param refund 退款金额(单位:分)
|
||||
*/
|
||||
public WxPayRefundV3Result refund(String confKey, String outTradeNo, String outRefundNo, int total, int refund) {
|
||||
WxPayRefundV3Request wxPayRefundV3Request = new WxPayRefundV3Request();
|
||||
wxPayRefundV3Request
|
||||
.setOutTradeNo(outTradeNo)
|
||||
.setOutRefundNo(outRefundNo)
|
||||
.setAmount(new WxPayRefundV3Request.Amount()
|
||||
.setTotal(total)
|
||||
.setRefund(refund)
|
||||
.setCurrency("CNY")
|
||||
);
|
||||
try {
|
||||
return this.getWxPayService(confKey).refundV3(wxPayRefundV3Request);
|
||||
} catch (WxPayException e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付相关接口的服务
|
||||
* (后续的几个服务方法,实现了基本的实例)
|
||||
* (此接口也可以直接在controller中使用)
|
||||
*
|
||||
* @return 微信支付服务接口
|
||||
*/
|
||||
public WxPayService getWxPayService(String confKey) {
|
||||
if (null == confKey) {
|
||||
return this.getWxPayService();
|
||||
}
|
||||
// 读取配置信息
|
||||
WeChatPayBO weChatPayBO = this.getConfig(confKey);
|
||||
// 生成配置
|
||||
WxPayConfig payConfig = new WxPayConfig();
|
||||
// 填充基本配置信息
|
||||
payConfig.setAppId(StringUtils.trimToNull(weChatPayBO.getAppId()));
|
||||
payConfig.setMchId(StringUtils.trimToNull(weChatPayBO.getMchId()));
|
||||
payConfig.setMchKey(StringUtils.trimToNull(weChatPayBO.getMchKey()));
|
||||
payConfig.setApiV3Key(StringUtils.trimToNull(weChatPayBO.getApiV3Key()));
|
||||
payConfig.setSubAppId(StringUtils.trimToNull(weChatPayBO.getSubAppId()));
|
||||
payConfig.setSubMchId(StringUtils.trimToNull(weChatPayBO.getSubMchId()));
|
||||
payConfig.setKeyPath(StringUtils.trimToNull(weChatPayBO.getKeyPath()));
|
||||
payConfig.setPrivateCertPath(StringUtils.trimToNull(weChatPayBO.getPrivateCertPath()));
|
||||
payConfig.setPrivateKeyPath(StringUtils.trimToNull(weChatPayBO.getPrivateKeyPath()));
|
||||
payConfig.setNotifyUrl(StringUtils.trimToNull(weChatPayBO.getNotifyUrl()));
|
||||
// 创建配置服务
|
||||
WxPayService wxPayService = new WxPayServiceImpl();
|
||||
wxPayService.setConfig(payConfig);
|
||||
// 可以指定是否使用沙箱环境
|
||||
payConfig.setUseSandboxEnv(SAND_BOX_ENV);
|
||||
if (SAND_BOX_ENV) {
|
||||
try {
|
||||
payConfig.setMchKey(wxPayService.getSandboxSignKey());
|
||||
wxPayService.setConfig(payConfig);
|
||||
} catch (WxPayException e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
// 返回结果
|
||||
return wxPayService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付相关接口的服务(默认KEY)
|
||||
*
|
||||
* @return 微信支付服务接口
|
||||
*/
|
||||
public WxPayService getWxPayService() {
|
||||
return this.getWxPayService(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user