初始话
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
*/target
|
||||||
|
**/.idea
|
||||||
|
.idea/
|
||||||
|
./log
|
||||||
|
./logs
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jiayunet</groupId>
|
||||||
|
<artifactId>back_end</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>client-api</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jiayunet</groupId>
|
||||||
|
<artifactId>manager</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<finalName>client-api</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>2.6.13</version>
|
||||||
|
<configuration>
|
||||||
|
<fork>true</fork>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.jiayunet;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(scanBasePackages = "org.jiayunet")
|
||||||
|
@MapperScan("org.jiayunet.**.mapper")
|
||||||
|
@EnableScheduling
|
||||||
|
@EnableAspectJAutoProxy
|
||||||
|
public class ClientApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ApplicationContext context = SpringApplication.run(ClientApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.jiayunet.server;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction;
|
||||||
|
import com.wechat.pay.java.service.refund.model.RefundNotification;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jiayunet.wxPay.WxPayNotifyMessageAbstract;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Primary
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class WxPayNotifyMessageAbstractImpl implements WxPayNotifyMessageAbstract {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付回调
|
||||||
|
*
|
||||||
|
* @param transaction 支付回调消息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void payMessageHandle(Transaction transaction) {
|
||||||
|
String outTradeNo = transaction.getOutTradeNo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款回调
|
||||||
|
*
|
||||||
|
* @param refundNotification 退款回调消息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void refundMessageHandle(RefundNotification refundNotification) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# tomcat 端口配置
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
# 数据源配置
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://${MYSQL_HOST:8.138.180.255}:${MYSQL_PORT:3306}/${DB_NAME:aiResume}?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
|
||||||
|
username: ${MYSQL_USERNAME:root}
|
||||||
|
password: ${MYSQL_PASSWORD:qwertyuiopasdfghjkl.zxcvbnm}
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:8.138.180.255}
|
||||||
|
password: ${REDIS_PASSWORD:qwertyuiopasdfghjkl.zxcvbnm}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
timeout: 8s
|
||||||
|
|
||||||
|
# 电子邮箱
|
||||||
|
email:
|
||||||
|
status: close
|
||||||
|
account: ${EMAIL_ACCOUNT:sim18502043706@163.com}
|
||||||
|
authorization: ${EMAIL_AUTHORIZATION:CHBCVPYGFZCSUNCP}
|
||||||
|
|
||||||
|
# 微信支付
|
||||||
|
wx_pay:
|
||||||
|
# status close:关闭 open:打开
|
||||||
|
status: close
|
||||||
|
merchant_id: ${MERCHANT_ID:1705334978}
|
||||||
|
privateKey_path: ${PRIVATEKEY_PATH:E:\wxPayCertify\1705334978_20250131_cert\apiclient_key.pem}
|
||||||
|
merchant_serial_number: ${MERCHANT_SERIAL_NUMBER:354F4A0BD7A11CB756ED3BC380A0E41442DE68F7}
|
||||||
|
api_v3_key: ${API_V3_KEY:zhyahjkiysetindhGseuHadjKITshons}
|
||||||
|
public_key_path: ${PUBLIC_KEY_PATH:E:\wxPayCertify\pub_key.pem}
|
||||||
|
public_key_id: ${PUBLIC_KEY_ID:PUB_KEY_ID_0117053349782025013000337100001480}
|
||||||
|
#回调域名地址
|
||||||
|
notify_domain: ${API_NOTIFY_DOMAIN:http://8.138.180.255:8080/api/}
|
||||||
|
|
||||||
|
app:
|
||||||
|
# 加密秘钥配置
|
||||||
|
secret:
|
||||||
|
token: ${SECRET_TOKEN:Aa123123}
|
||||||
|
|
||||||
|
#登陆配置
|
||||||
|
login:
|
||||||
|
# 登陆图片验证相关
|
||||||
|
login_image_verification:
|
||||||
|
enable: false
|
||||||
|
images_code_sources: 123456789ABCDEFGHJKLMNPQRSTUVWXYZ
|
||||||
|
images_w: 111
|
||||||
|
images_h: 36
|
||||||
|
# token配置
|
||||||
|
token:
|
||||||
|
exceed_time: 129600
|
||||||
|
#设备在线数量
|
||||||
|
device_online_quantity: 10
|
||||||
|
|
||||||
|
# 短信
|
||||||
|
sms:
|
||||||
|
service_provider: aliyun
|
||||||
|
aliyun:
|
||||||
|
access_key_id: LTAI5tGRFuJmt6nrxRqZYC7z
|
||||||
|
access_key_secret: xLC3kNSmCvEtsc9j4O8g3rOs70QjZQ
|
||||||
|
|
||||||
|
oss:
|
||||||
|
service_provider: aliyun
|
||||||
|
aliyun:
|
||||||
|
access_key_id: LTAI5tGRFuJmt6nrxRqZYC7z
|
||||||
|
access_key_secret: xLC3kNSmCvEtsc9j4O8g3rOs70QjZQ
|
||||||
|
|
||||||
|
# 防刷配置 20秒 20次
|
||||||
|
prevent_replay:
|
||||||
|
if_open: false
|
||||||
|
interval_time: 20
|
||||||
|
limit_number: 20
|
||||||
|
|
||||||
|
# 微信小程序
|
||||||
|
wx:
|
||||||
|
app_id: wx7d75957cbd35e36b
|
||||||
|
app_secret: d70ef5baa4b9c046f396736d9a42cdc3
|
||||||
|
|
||||||
|
#开放接口
|
||||||
|
ignore:
|
||||||
|
urls: "/public/**"
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# tomcat config
|
||||||
|
server:
|
||||||
|
tomcat:
|
||||||
|
accept-count: 100
|
||||||
|
threads:
|
||||||
|
max: 200
|
||||||
|
min-spare: 10
|
||||||
|
servlet:
|
||||||
|
context-path: /api
|
||||||
|
# spring config
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: client
|
||||||
|
profiles:
|
||||||
|
active: ${PROFILES_ACTIVE:local}
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 4MB
|
||||||
|
max-request-size: 20MB
|
||||||
|
mvc:
|
||||||
|
throw-exception-if-no-handler-found: true
|
||||||
|
web:
|
||||||
|
resources:
|
||||||
|
add-mappings: false
|
||||||
|
cache:
|
||||||
|
redis:
|
||||||
|
cache-null-values: true
|
||||||
|
key-prefix: "${spring.application.name}:"
|
||||||
|
time-to-live: 24h
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
|
property-naming-strategy: LOWER_CAMEL_CASE
|
||||||
|
serialization:
|
||||||
|
fail-on-empty-beans: false
|
||||||
|
write-date-keys-as-timestamps: true
|
||||||
|
write-date-timestamps-as-nanoseconds: false
|
||||||
|
write-dates-as-timestamps: true
|
||||||
|
deserialization:
|
||||||
|
fail-on-unknown-properties: false
|
||||||
|
fail-on-numbers-for-enums: true
|
||||||
|
read-date-timestamps-as-nanoseconds: false
|
||||||
|
# mybatis plus config
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath*:mapper/**/*.xml
|
||||||
|
global-config:
|
||||||
|
db-config:
|
||||||
|
id-type: assign_id
|
||||||
|
insert-strategy: not_null
|
||||||
|
update-strategy: not_null
|
||||||
|
logic-delete-field: isDelete
|
||||||
|
logic-delete-value: '1'
|
||||||
|
logic-not-delete-value: '0'
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
auto-mapping-unknown-column-behavior: warning
|
||||||
|
cache-enabled: false
|
||||||
|
call-setters-on-nulls: true
|
||||||
|
jdbc-type-for-null: 'null'
|
||||||
|
|
||||||
|
#日志
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org:
|
||||||
|
mybatis: info
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jiayunet</groupId>
|
||||||
|
<artifactId>back_end</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpmime</artifactId>
|
||||||
|
<version>4.5.13</version> <!-- 请根据需要选择最新版本 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 添加AspectJ依赖管理 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjrt</artifactId>
|
||||||
|
<version>1.9.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
<version>1.9.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
|
<version>3.17.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.wechatpay-apiv3</groupId>
|
||||||
|
<artifactId>wechatpay-java</artifactId>
|
||||||
|
<version>0.2.15</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>dysmsapi20170525</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.oss</groupId>
|
||||||
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.13</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.jiayunet.aop;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ControllerLogAspect {
|
||||||
|
|
||||||
|
|
||||||
|
// 定义切点:所有Controller包下的方法
|
||||||
|
@Pointcut("execution(* org.jiayunet..controller..*.*(..))")
|
||||||
|
public void controllerPointcut() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法执行前记录请求信息
|
||||||
|
@Before("controllerPointcut()")
|
||||||
|
public void logBefore(JoinPoint joinPoint) {
|
||||||
|
String className = joinPoint.getTarget().getClass().getSimpleName();
|
||||||
|
String methodName = joinPoint.getSignature().getName();
|
||||||
|
|
||||||
|
// 处理参数,过滤掉HttpServletRequest/Response和文件流
|
||||||
|
List<Object> filteredArgs = Arrays.stream(joinPoint.getArgs()).filter(arg -> !(arg instanceof HttpServletRequest || arg instanceof HttpServletResponse || arg instanceof MultipartFile || (arg != null && arg.getClass().isArray() && MultipartFile.class.isAssignableFrom(arg.getClass().getComponentType())))).collect(Collectors.toList());
|
||||||
|
|
||||||
|
log.info("接口参数 方法:{} {}" + "参数: {} ", className, methodName, filteredArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法执行后记录返回结果
|
||||||
|
@AfterReturning(pointcut = "controllerPointcut()", returning = "result")
|
||||||
|
public void logAfterReturning(JoinPoint joinPoint, Object result) {
|
||||||
|
log.info("接口返回:{}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法异常时记录错误信息
|
||||||
|
@AfterThrowing(pointcut = "controllerPointcut()", throwing = "ex")
|
||||||
|
public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
|
||||||
|
String className = joinPoint.getTarget().getClass().getSimpleName();
|
||||||
|
String methodName = joinPoint.getSignature().getName();
|
||||||
|
|
||||||
|
log.error(" 类名: {} 方法: {} 异常: {} 堆栈: {}", className, methodName, ex.getMessage(), getStackTrace(ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStackTrace(Throwable ex) {
|
||||||
|
return Arrays.stream(ex.getStackTrace()).map(StackTraceElement::toString).collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import org.springframework.boot.jackson.JsonComponent;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jackson 全局配置
|
||||||
|
* <p>
|
||||||
|
* 将 Long/long 类型序列化为字符串,避免 JavaScript 精度丢失问题
|
||||||
|
*/
|
||||||
|
@JsonComponent
|
||||||
|
public class JacksonConfig {
|
||||||
|
|
||||||
|
private static final JsonSerializer<Long> LONG_SERIALIZER = new JsonSerializer<Long>() {
|
||||||
|
@Override
|
||||||
|
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
gen.writeString(value.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final JsonDeserializer<Long> LONG_DESERIALIZER = new JsonDeserializer<Long>() {
|
||||||
|
@Override
|
||||||
|
public Long deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
|
||||||
|
String text = p.getText();
|
||||||
|
return (text == null || text.trim().isEmpty()) ? null : Long.parseLong(text.trim());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||||
|
return builder
|
||||||
|
.serializerByType(Long.class, LONG_SERIALIZER)
|
||||||
|
.serializerByType(long.class, LONG_SERIALIZER)
|
||||||
|
.deserializerByType(Long.class, LONG_DESERIALIZER)
|
||||||
|
.deserializerByType(long.class, LONG_DESERIALIZER)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||||
|
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class MybatisConfig {
|
||||||
|
/**
|
||||||
|
* SQL注入器
|
||||||
|
* 在默认的基础上田间InsertBatchSomeColumn
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public DefaultSqlInjector easySqlInjector() {
|
||||||
|
return new DefaultSqlInjector(){
|
||||||
|
@Override
|
||||||
|
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
|
||||||
|
List<AbstractMethod> methodList = super.getMethodList(mapperClass,tableInfo);
|
||||||
|
methodList.add(new InsertBatchSomeColumn());
|
||||||
|
methodList.add(new UpdateBatchMethod("updateBatchMethod"));
|
||||||
|
return methodList;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截器实现得分页插件
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
|
// 添加分页插件
|
||||||
|
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
|
||||||
|
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class OssConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云配置
|
||||||
|
*/
|
||||||
|
@Value("${app.oss.aliyun.access_key_id}")
|
||||||
|
private String accessKeyId;
|
||||||
|
@Value("${app.oss.aliyun.access_key_secret}")
|
||||||
|
private String accessKeySecret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码凭证提供者
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "app.oss.service_provider", havingValue = "aliyun")
|
||||||
|
public DefaultCredentialProvider aliOssCredentialProvider() {
|
||||||
|
Assert.hasText(accessKeyId, "app.oss.aliyun.access_key_id配置缺失");
|
||||||
|
Assert.hasText(accessKeySecret, "app.oss.aliyun.access_key_secret配置缺失");
|
||||||
|
return CredentialsProviderFactory.newDefaultCredentialProvider(accessKeyId,accessKeySecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.redisson.config.Config;
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RedissonConf {
|
||||||
|
/**
|
||||||
|
* redis 配置
|
||||||
|
*/
|
||||||
|
@Value("${spring.redis.host}")
|
||||||
|
private String host;
|
||||||
|
@Value("${spring.redis.password}")
|
||||||
|
private String password;
|
||||||
|
@Value("${spring.redis.port}")
|
||||||
|
private String port;
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Config redissonConfig() {
|
||||||
|
Config config = new Config();
|
||||||
|
config.useSingleServer()
|
||||||
|
.setAddress("redis://"+host+":"+port)
|
||||||
|
.setPassword(password)
|
||||||
|
.setDatabase(1);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.jiayunet.interceptor.JwtAuthenticationTokenFilter;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Value("${app.ignore.urls}")
|
||||||
|
private String ignoreUrls;
|
||||||
|
@Autowired
|
||||||
|
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理跨域问题
|
||||||
|
*/
|
||||||
|
CorsConfigurationSource corsConfigurationSource(){
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.setAllowedHeaders(List.of("*"));
|
||||||
|
config.setAllowedMethods(List.of("*"));
|
||||||
|
config.setAllowedOrigins(List.of("*"));
|
||||||
|
//config.setAllowCredentials(true);
|
||||||
|
config.setMaxAge(3600L);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
http
|
||||||
|
.csrf().disable().httpBasic().disable().formLogin().disable().logout().disable()
|
||||||
|
// 不通过Session获取SecurityContext
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
|
||||||
|
// 对于登录接口 允许匿名访问
|
||||||
|
.antMatchers(ignoreUrls.split(",")).permitAll()
|
||||||
|
// 除上面外的所有请求全部需要鉴权认证
|
||||||
|
.anyRequest().authenticated();
|
||||||
|
|
||||||
|
http.cors().configurationSource(corsConfigurationSource());
|
||||||
|
|
||||||
|
|
||||||
|
// 把token校验过滤器添加到过滤器链中
|
||||||
|
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
|
||||||
|
Map<String, Object> errorResponse = new HashMap<>();
|
||||||
|
errorResponse.put("status", HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
errorResponse.put("message", "Authentication failed");
|
||||||
|
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
|
||||||
|
// 将错误消息写入响应体
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.writeValue(response.getWriter(), errorResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SmsConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云配置
|
||||||
|
*/
|
||||||
|
@Value("${app.sms.aliyun.access_key_id}")
|
||||||
|
private String accessKeyId;
|
||||||
|
@Value("${app.sms.aliyun.access_key_secret}")
|
||||||
|
private String accessKeySecret;
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "app.sms.service_provider", havingValue = "aliyun")
|
||||||
|
public com.aliyun.dysmsapi20170525.Client aliyunSmsClient() throws Exception {
|
||||||
|
Assert.hasText(accessKeyId, "app.sms.aliyun.access_key_id配置缺失");
|
||||||
|
Assert.hasText(accessKeySecret, "app.sms.aliyun.access_key_secret配置缺失");
|
||||||
|
|
||||||
|
com.aliyun.teaopenapi.models.Config config =
|
||||||
|
new com.aliyun.teaopenapi.models.Config().setAccessKeyId(accessKeyId).setAccessKeySecret(accessKeySecret);
|
||||||
|
config.endpoint = "dysmsapi.aliyuncs.com";
|
||||||
|
return new com.aliyun.dysmsapi20170525.Client(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.mapping.SqlSource; /**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class UpdateBatchMethod extends AbstractMethod {
|
||||||
|
protected UpdateBatchMethod(String methodName) {
|
||||||
|
super(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
||||||
|
String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>";
|
||||||
|
String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : "" + tableInfo.getLogicDeleteSql(true, true);
|
||||||
|
String setSql = sqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, false, "item", "item.");
|
||||||
|
String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional);
|
||||||
|
//log.debug("sqlResult----->{}", sqlResult);
|
||||||
|
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
|
||||||
|
// 第三个参数必须和RootMapper的自定义方法名一致
|
||||||
|
return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatchMethod", sqlSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import org.jiayunet.interceptor.BlackListInterceptor;
|
||||||
|
import org.jiayunet.interceptor.PreventReplayInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
@Autowired
|
||||||
|
private PreventReplayInterceptor preventReplayInterceptor;
|
||||||
|
@Autowired
|
||||||
|
private BlackListInterceptor blackListInterceptor;
|
||||||
|
|
||||||
|
|
||||||
|
//配置拦截器
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(blackListInterceptor).addPathPatterns("/**").order(1);
|
||||||
|
registry.addInterceptor(preventReplayInterceptor).addPathPatterns("/**").order(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package org.jiayunet.config;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.core.RSAPublicKeyConfig;
|
||||||
|
import com.wechat.pay.java.core.notification.NotificationParser;
|
||||||
|
import com.wechat.pay.java.core.util.PemUtil;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
|
||||||
|
import com.wechat.pay.java.service.refund.RefundService;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.jiayunet.wxPay.server.TransferServer;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WxPayConfig {
|
||||||
|
/**
|
||||||
|
* 商户号
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.merchant_id}")
|
||||||
|
private String merchantId = "";
|
||||||
|
/**
|
||||||
|
* 商户API私钥路径
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.privateKey_path}")
|
||||||
|
private String privateKeyPath = "";
|
||||||
|
/**
|
||||||
|
* 商户证书序列号
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.merchant_serial_number}")
|
||||||
|
private String merchantSerialNumber = "";
|
||||||
|
/**
|
||||||
|
* 商户APIV3密钥
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.api_v3_key}")
|
||||||
|
private String apiV3Key = "";
|
||||||
|
/**
|
||||||
|
* 平台公钥地址
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.public_key_path}")
|
||||||
|
private String publicKeyPath = "";
|
||||||
|
/**
|
||||||
|
* 平台公钥地址ID
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.public_key_id}")
|
||||||
|
private String publicKeyId = "";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信自动签名验签配置
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public RSAPublicKeyConfig rSAAutoCertificateConfig() {
|
||||||
|
Assert.hasText(merchantId, "微信支付的商户号不能为空");
|
||||||
|
Assert.hasText(privateKeyPath, "商户API私钥路径不能为空");
|
||||||
|
Assert.hasText(merchantSerialNumber, "商户证书序列号不能为空");
|
||||||
|
Assert.hasText(apiV3Key, "商户APIV3密钥不能为空");
|
||||||
|
Assert.hasText(publicKeyPath, "微信支付平台公钥地址不能为空");
|
||||||
|
Assert.hasText(publicKeyId, "微信支付平台公钥Id不能为空");
|
||||||
|
|
||||||
|
return new RSAPublicKeyConfig.Builder()
|
||||||
|
.merchantId(merchantId)
|
||||||
|
.privateKeyFromPath(privateKeyPath)
|
||||||
|
.merchantSerialNumber(merchantSerialNumber)
|
||||||
|
.apiV3Key(apiV3Key)
|
||||||
|
.publicKeyFromPath(publicKeyPath)
|
||||||
|
.publicKeyId(publicKeyId)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信JsApi支付接口能能力server
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public JsapiService jsapiService(RSAPublicKeyConfig rSAPublicKeyConfig) {
|
||||||
|
return new JsapiService.Builder().config(rSAPublicKeyConfig).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信Native支付接口能能力server
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public NativePayService nativePayService(RSAPublicKeyConfig rSAPublicKeyConfig) {
|
||||||
|
return new NativePayService.Builder().config(rSAPublicKeyConfig).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信单家付款接口能能力server
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public TransferServer transferServer(RSAPublicKeyConfig rSAPublicKeyConfig) {
|
||||||
|
return new TransferServer.Builder().config(rSAPublicKeyConfig).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付退款能力server
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public RefundService refundService(RSAPublicKeyConfig rSAPublicKeyConfig) {
|
||||||
|
return new RefundService.Builder().config(rSAPublicKeyConfig).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知解析器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public NotificationParser notificationParser(RSAPublicKeyConfig rSAPublicKeyConfig) {
|
||||||
|
return new NotificationParser(rSAPublicKeyConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户私钥
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
public PrivateKey privateKey(){
|
||||||
|
return PemUtil.loadPrivateKeyFromPath(privateKeyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.jiayunet.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redisKey
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface PreRedisKeyName {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防刷key名
|
||||||
|
*/
|
||||||
|
String PREVENT_REPLAY = "preventReplay:ip:";
|
||||||
|
/**
|
||||||
|
* 黑名单ip
|
||||||
|
*/
|
||||||
|
String BLACK_LIST = "blackList:ip:";
|
||||||
|
/**
|
||||||
|
* 单次执行任务Id
|
||||||
|
*/
|
||||||
|
String EXECUTE_SINGLE_TASK = "execute:single-task-id:";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token Key名 前缀
|
||||||
|
*/
|
||||||
|
String LOGIN_TOKEN = "login:token:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片验证码Key名 前缀
|
||||||
|
*/
|
||||||
|
String LOGIN_IMAGES_CODE_UUID = "login:images:uuid:";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package org.jiayunet.email;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.mail.Authenticator;
|
||||||
|
import javax.mail.Message;
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.PasswordAuthentication;
|
||||||
|
import javax.mail.Session;
|
||||||
|
import javax.mail.Transport;
|
||||||
|
import javax.mail.internet.InternetAddress;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnProperty(name = "email.status", havingValue = "open")
|
||||||
|
@Slf4j
|
||||||
|
public class EmailAbility {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮箱账户
|
||||||
|
*/
|
||||||
|
@Value("${email.account}")
|
||||||
|
private String emailAccount;
|
||||||
|
/**
|
||||||
|
* 发送邮箱授权码
|
||||||
|
*/
|
||||||
|
@Value("${email.authorization}")
|
||||||
|
private String authorization;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接
|
||||||
|
* @return 连接Session
|
||||||
|
*/
|
||||||
|
private Session createSession() {
|
||||||
|
|
||||||
|
Assert.isTrue(StringUtils.hasText(authorization) && StringUtils.hasText(authorization),"邮箱账户授权为配置");
|
||||||
|
|
||||||
|
//连接配置 勿动 163
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.put("mail.smtp.host", "smtp.163.com");
|
||||||
|
props.put("mail.smtp.port", "25");
|
||||||
|
props.put("mail.smtp.auth", "true");
|
||||||
|
props.put("mail.smtp.starttls.enable", "true");
|
||||||
|
Session session = Session.getInstance(props,new Authenticator() {
|
||||||
|
@Override
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(emailAccount,authorization);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("获取邮件发送Session成功");
|
||||||
|
//session.setDebug(true);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件
|
||||||
|
* @param emailAdder 收信人地址
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @param content 邮件类容
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public boolean send(String emailAdder,String subject, String content) {
|
||||||
|
|
||||||
|
//获取session
|
||||||
|
Session session = createSession();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//构建发送消息
|
||||||
|
MimeMessage message=new MimeMessage(session);
|
||||||
|
//设置邮件主题
|
||||||
|
message.setSubject(subject);
|
||||||
|
//设置邮件内容
|
||||||
|
message.setText(content);
|
||||||
|
//设置发件人
|
||||||
|
message.setFrom(new InternetAddress(emailAccount));
|
||||||
|
//设置收件人
|
||||||
|
message.setRecipient(Message.RecipientType.TO, new InternetAddress(emailAdder));
|
||||||
|
|
||||||
|
//发送消息
|
||||||
|
Transport.send(message);
|
||||||
|
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.jiayunet.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final String DELIMITER = " ";
|
||||||
|
private final BusinessExpCodeOperations expCode;
|
||||||
|
private Object data;
|
||||||
|
private String description;
|
||||||
|
private Boolean replace = true;
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg()));
|
||||||
|
this.expCode = expCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode, String description) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg(), description));
|
||||||
|
this.expCode = expCode;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode, String description, boolean replace) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg(), description));
|
||||||
|
this.expCode = expCode;
|
||||||
|
this.description = description;
|
||||||
|
this.replace = replace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode, Throwable throwable) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg()), throwable);
|
||||||
|
this.expCode = expCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode, String description, Throwable throwable) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg(), description), throwable);
|
||||||
|
this.expCode = expCode;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode, String description, Object data) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg(), description));
|
||||||
|
this.expCode = expCode;
|
||||||
|
this.description = description;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(BusinessExpCodeOperations expCode, String description, boolean replace, Object data) {
|
||||||
|
super(String.join(" ", expCode.getCode(), expCode.getMsg(), description));
|
||||||
|
this.expCode = expCode;
|
||||||
|
this.description = description;
|
||||||
|
this.replace = replace;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBusinessCode() {
|
||||||
|
return this.expCode.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBusinessMsg() {
|
||||||
|
return this.expCode.getMsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getReplace() {
|
||||||
|
return this.replace;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.jiayunet.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum BusinessExpCodeEnum implements BusinessExpCodeOperations {
|
||||||
|
|
||||||
|
UNKNOWN_ERROR("系统忙不过来了, 稍后再试试"),
|
||||||
|
PARAMS_INCORRECT("接口参数错误"),
|
||||||
|
TOKEN_EXPIRED("登录已过期, 请重新登录"),
|
||||||
|
TOKEN_INCORRECT("访问令牌异常, 请重新登录"),
|
||||||
|
NOT_LOGGED_IN("未登录"),
|
||||||
|
USER_EXISTED("用户已存在"),
|
||||||
|
USER_NOT_EXIST("用户不存在"),
|
||||||
|
ACCOUNT_EXISTED("账号已存在"),
|
||||||
|
ACCOUNT_NOT_EXIST("账号不存在"),
|
||||||
|
ACCOUNT_DISABLED("账号已停用"),
|
||||||
|
ACCOUNT_UNAUTHORIZED("账号未认证"),
|
||||||
|
PASSWORD_INCORRECT("密码不正确"),
|
||||||
|
ACCOUNT_OR_PASSWORD_INCORRECT("账号或密码不正确"),
|
||||||
|
PHONE_EXISTED("手机号已存在"),
|
||||||
|
PHONE_NOT_EXIST("手机号不存在"),
|
||||||
|
BAD_CAPTCHA("验证码不正确"),
|
||||||
|
BAD_EXPIRED("验证码已失效"),
|
||||||
|
FAILED_SEND_CAPTCHA("验证码发送失败"),
|
||||||
|
REPEAT_SEND_CAPTCHA("验证码已发送过, 注意查收"),
|
||||||
|
TOO_MANY_REQUESTS("请求过多, API限流"),
|
||||||
|
REPEAT_REQUEST("请勿重复请求"),
|
||||||
|
PERMISSION_DENIED("无权访问"),
|
||||||
|
READ_ONLY("不可修改或删除"),
|
||||||
|
DATA_EXISTED("数据已存在"),
|
||||||
|
DATA_NOT_EXIST("数据不存在"),
|
||||||
|
DATA_EXIST_RELATION("数据存在关联"),
|
||||||
|
DATA_DUPLICATED("数据重复");
|
||||||
|
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
BusinessExpCodeEnum(String msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return this.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMsg() {
|
||||||
|
return this.msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.jiayunet.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface BusinessExpCodeOperations {
|
||||||
|
String getCode();
|
||||||
|
|
||||||
|
String getMsg();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.jiayunet.interceptor;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.jiayunet.constant.PreRedisKeyName;
|
||||||
|
import org.jiayunet.tool.HttpIpTool;
|
||||||
|
import org.jiayunet.tool.server.RedisServerTool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁止黑名单
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BlackListInterceptor implements HandlerInterceptor {
|
||||||
|
@Autowired
|
||||||
|
private RedisServerTool redisServerTool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
String key = PreRedisKeyName.BLACK_LIST + HttpIpTool.gteRealIP(request);
|
||||||
|
|
||||||
|
return Boolean.FALSE.equals(redisServerTool.hasKey(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package org.jiayunet.interceptor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import org.jiayunet.constant.PreRedisKeyName;
|
||||||
|
import org.jiayunet.pojo.login.RedisLoginTokenInfo;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jiayunet.tool.server.RedisServerTool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jwt过滤器
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密密钥
|
||||||
|
*/
|
||||||
|
@Value("${app.secret.token:sh.0807.}")
|
||||||
|
private String secret;
|
||||||
|
/**
|
||||||
|
* 在线设备数量
|
||||||
|
*/
|
||||||
|
@Value("${app.login.device_online_quantity:5}")
|
||||||
|
private int deviceOnlineQuantity;
|
||||||
|
/**
|
||||||
|
* token过期时间
|
||||||
|
*/
|
||||||
|
@Value("${app.login.token.exceed_time:43200}")
|
||||||
|
private int tokenExceedTime;
|
||||||
|
/**
|
||||||
|
* 忽略请求路径
|
||||||
|
*/
|
||||||
|
@Value("${app.ignore.urls}")
|
||||||
|
private String ignoreUrls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局配置路径
|
||||||
|
*/
|
||||||
|
@Value("${server.servlet.context-path}")
|
||||||
|
private String contextPath;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisServerTool redisServerTool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
// 忽略接口放行
|
||||||
|
if (ifCurrentUrl(ignoreUrls, request.getRequestURI())) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取token
|
||||||
|
String token = request.getHeader("Token");
|
||||||
|
if (!StringUtils.hasText(token)) {
|
||||||
|
// 放行
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 验证token
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||||
|
DecodedJWT decodedJwt = JWT.require(algorithm).build().verify(token);
|
||||||
|
Long userId = decodedJwt.getClaim("userId").asLong();
|
||||||
|
String uuId = decodedJwt.getClaim("uuId").asString();
|
||||||
|
|
||||||
|
// 获取redis信息
|
||||||
|
String redisKey = PreRedisKeyName.LOGIN_TOKEN + userId;
|
||||||
|
RedisLoginTokenInfo info = null;
|
||||||
|
info = redisServerTool.get(redisKey, RedisLoginTokenInfo.class);
|
||||||
|
Assert.notNull(info, "用户未登录");
|
||||||
|
|
||||||
|
|
||||||
|
// 判断登录有效
|
||||||
|
|
||||||
|
// 获取登录设备信息
|
||||||
|
List<RedisLoginTokenInfo.LoginDevice> devices = info.getLoginDevices();
|
||||||
|
// 过滤过期
|
||||||
|
devices = devices.stream().filter(v -> v.getLastLoginTime().isBefore(new Date(System.currentTimeMillis() + tokenExceedTime * 1000L).toInstant())).collect(Collectors.toList());
|
||||||
|
Map<String, RedisLoginTokenInfo.LoginDevice> map = devices.stream().collect(Collectors.toMap(RedisLoginTokenInfo.LoginDevice::getUuId, v -> v));
|
||||||
|
Assert.isTrue(map.containsKey(uuId), "登录过期");
|
||||||
|
|
||||||
|
// 续期时间
|
||||||
|
map.get(uuId).setLastLoginTime(Instant.now());
|
||||||
|
info.setLoginDevices(devices);
|
||||||
|
redisServerTool.set(redisKey, info, tokenExceedTime, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken =
|
||||||
|
new UsernamePasswordAuthenticationToken(info.getUserId(), info,
|
||||||
|
info.getAuthority().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
|
||||||
|
authenticationToken.setDetails(info);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
|
|
||||||
|
MDC.put("userId", userId.toString());
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为被忽略的路径
|
||||||
|
*
|
||||||
|
* @param ignoreUrls 忽略路径
|
||||||
|
* @param currentUrl 当前路径
|
||||||
|
* @return 是否忽略当前
|
||||||
|
*/
|
||||||
|
private Boolean ifCurrentUrl(String ignoreUrls, String currentUrl) {
|
||||||
|
String charToRemove = "[*/ ]";
|
||||||
|
if (!StringUtils.hasText(ignoreUrls)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理本次请求路径 剔除全局路径
|
||||||
|
if (!StringUtils.hasText(currentUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(contextPath)) {
|
||||||
|
currentUrl = currentUrl.replaceFirst(contextPath, "").replaceAll(charToRemove, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理需要被忽略的字段
|
||||||
|
List<String> urls = Arrays.stream(ignoreUrls.split(",")).map(item -> item.replaceAll(charToRemove, "")).filter(item -> !item.isEmpty()).distinct().collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (String str : urls) {
|
||||||
|
if (currentUrl.startsWith(str)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.jiayunet.interceptor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求日志
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Order(2)
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class LoggingOriginalRequestFilter extends OncePerRequestFilter {
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
String methodName = request.getMethod();
|
||||||
|
log.info(">> Request Url: {} {}", methodName, request.getRequestURI());
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package org.jiayunet.interceptor;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.jiayunet.constant.PreRedisKeyName;
|
||||||
|
import org.jiayunet.pojo.interceptor.RedisPreventReplayInfo;
|
||||||
|
import org.jiayunet.tool.HttpIpTool;
|
||||||
|
import org.jiayunet.tool.server.RedisServerTool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防止高频重复请求
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class PreventReplayInterceptor implements HandlerInterceptor {
|
||||||
|
/**
|
||||||
|
* 是否开启
|
||||||
|
*/
|
||||||
|
@Value("${app.prevent_replay.if_open:true}")
|
||||||
|
private Boolean ifOpen;
|
||||||
|
/**
|
||||||
|
* 间隔时间
|
||||||
|
*/
|
||||||
|
@Value("${app.prevent_replay.interval_time:10}")
|
||||||
|
private Integer intervalTime;
|
||||||
|
/**
|
||||||
|
* 单位时间次数
|
||||||
|
*/
|
||||||
|
@Value("${app.prevent_replay.limit_number:20}")
|
||||||
|
private Integer limitNumber;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisServerTool redisServerTool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
// 关闭时 不执行
|
||||||
|
if (!ifOpen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String redisKeyName = PreRedisKeyName.PREVENT_REPLAY + HttpIpTool.gteRealIP(request);
|
||||||
|
|
||||||
|
// 获取redis中的数据
|
||||||
|
RedisPreventReplayInfo replayInfo = redisServerTool.get(redisKeyName, RedisPreventReplayInfo.class);
|
||||||
|
|
||||||
|
replayInfo = Objects.nonNull(replayInfo) ? replayInfo : new RedisPreventReplayInfo();
|
||||||
|
|
||||||
|
// 计算时间差 并更新数据
|
||||||
|
Duration duration = Duration.between(replayInfo.getLastTiming(), Instant.now());
|
||||||
|
long seconds = duration.getSeconds();
|
||||||
|
replayInfo = seconds <= intervalTime ? replayInfo : new RedisPreventReplayInfo();
|
||||||
|
|
||||||
|
// 增加次数 写回redis
|
||||||
|
replayInfo.setFrequency(replayInfo.getFrequency() + 1);
|
||||||
|
redisServerTool.set(redisKeyName, replayInfo);
|
||||||
|
|
||||||
|
boolean type = replayInfo.getFrequency() <= limitNumber;
|
||||||
|
|
||||||
|
// 加黑
|
||||||
|
if (!type) {
|
||||||
|
redisServerTool.set(PreRedisKeyName.BLACK_LIST + request.getRemoteAddr(), "高频请求", 1, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package org.jiayunet.interceptor;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.mapping.ParameterMapping;
|
||||||
|
import org.apache.ibatis.plugin.*;
|
||||||
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||||
|
import org.apache.ibatis.session.Configuration;
|
||||||
|
import org.apache.ibatis.type.TypeHandlerRegistry;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Intercepts({
|
||||||
|
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, org.apache.ibatis.session.ResultHandler.class}),
|
||||||
|
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
|
||||||
|
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
|
||||||
|
})
|
||||||
|
@Component
|
||||||
|
public class SqlLoggerInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object intercept(Invocation invocation) throws Throwable {
|
||||||
|
|
||||||
|
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
|
||||||
|
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
|
||||||
|
|
||||||
|
// 获取 MappedStatement
|
||||||
|
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||||
|
|
||||||
|
BoundSql boundSql = statementHandler.getBoundSql();
|
||||||
|
|
||||||
|
// 记录执行开始时间
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 执行原始方法
|
||||||
|
return invocation.proceed();
|
||||||
|
} finally {
|
||||||
|
// 计算执行耗时
|
||||||
|
long costTime = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
|
// 获取完整的 SQL(包含参数值)
|
||||||
|
String completeSql = getCompleteSql(mappedStatement.getConfiguration(), boundSql);
|
||||||
|
|
||||||
|
// 输出日志
|
||||||
|
log.info("SQL : {} | Time: {} ms", completeSql, costTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取完整的 SQL 语句(将 ? 替换为实际参数值)
|
||||||
|
*/
|
||||||
|
private String getCompleteSql(Configuration configuration, BoundSql boundSql) {
|
||||||
|
|
||||||
|
String sql = boundSql.getSql().replaceAll("\\s+", " ").trim();
|
||||||
|
Object parameterObject = boundSql.getParameterObject();
|
||||||
|
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
|
||||||
|
|
||||||
|
if (parameterMappings == null || parameterMappings.isEmpty()) {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
|
||||||
|
|
||||||
|
// 替换 SQL 中的 ? 为实际参数值
|
||||||
|
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
|
||||||
|
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
|
||||||
|
} else {
|
||||||
|
MetaObject metaObject = configuration.newMetaObject(parameterObject);
|
||||||
|
for (ParameterMapping parameterMapping : parameterMappings) {
|
||||||
|
String propertyName = parameterMapping.getProperty();
|
||||||
|
Object value;
|
||||||
|
|
||||||
|
if (boundSql.hasAdditionalParameter(propertyName)) {
|
||||||
|
value = boundSql.getAdditionalParameter(propertyName);
|
||||||
|
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
|
||||||
|
value = parameterObject;
|
||||||
|
} else {
|
||||||
|
value = metaObject.getValue(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String paramValueStr = getParameterValue(value);
|
||||||
|
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(paramValueStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将参数值转换为字符串形式
|
||||||
|
*/
|
||||||
|
private String getParameterValue(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return "NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof String) {
|
||||||
|
return "'" + obj + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof Date) {
|
||||||
|
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
return "'" + formatter.format(obj) + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.jiayunet.interceptor;
|
||||||
|
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求链路标识链接器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(1)
|
||||||
|
public class TraceIdFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 判断请求头中是否存在链路id
|
||||||
|
String traceId = request.getHeader("X-Request-ID");
|
||||||
|
|
||||||
|
// 不存在 即时生成
|
||||||
|
if (!StringUtils.hasText(traceId)) {
|
||||||
|
traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存在在日志上下文环境中
|
||||||
|
MDC.put("traceId", traceId);
|
||||||
|
|
||||||
|
|
||||||
|
// 设置到响应头,方便前端追踪
|
||||||
|
response.setHeader("X-Trace-ID", traceId);
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// 请求结束时清除
|
||||||
|
MDC.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package org.jiayunet.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface CommonMapper<T> extends BaseMapper<T> {
|
||||||
|
int DEFAULT_BATCH_SIZE = 5000;
|
||||||
|
|
||||||
|
int insertBatchSomeColumn(Collection<T> entityList);
|
||||||
|
|
||||||
|
int updateBatchMethod(Collection<T> entityList);
|
||||||
|
|
||||||
|
|
||||||
|
default int batchInsert(List<T> entityList) {
|
||||||
|
return this.batchInsert(entityList, DEFAULT_BATCH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
default int batchInsert(List<T> entityList, int batchSize) {
|
||||||
|
if (CollectionUtils.isEmpty(entityList)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
if (batchSize <= 0) {
|
||||||
|
batchSize = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<T>> partition = partition(entityList, batchSize);
|
||||||
|
|
||||||
|
AtomicInteger total = new AtomicInteger();
|
||||||
|
partition.forEach((e) -> {
|
||||||
|
total.addAndGet(this.insertBatchSomeColumn(e));
|
||||||
|
});
|
||||||
|
return total.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default int batchUpdate(List<T> entityList) {
|
||||||
|
return this.batchUpdate(entityList, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
default int batchUpdate(List<T> entityList, int batchSize) {
|
||||||
|
if (CollectionUtils.isEmpty(entityList)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
if (batchSize <= 0) {
|
||||||
|
batchSize = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<T>> partition = this.partition(entityList, batchSize);
|
||||||
|
AtomicInteger total = new AtomicInteger();
|
||||||
|
partition.forEach((e) -> {
|
||||||
|
total.addAndGet(updateBatchMethod(e));
|
||||||
|
});
|
||||||
|
return total.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default List<List<T>> partition(List<T> entityList, Integer batchSize) {
|
||||||
|
List<List<T>> partition = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < entityList.size(); i += batchSize) {
|
||||||
|
int toIndex = Math.min(i + batchSize, entityList.size());
|
||||||
|
partition.add(entityList.subList(i, toIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package org.jiayunet.oss;
|
||||||
|
|
||||||
|
import com.aliyun.oss.HttpMethod;
|
||||||
|
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
|
||||||
|
import com.aliyun.oss.model.OSSObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import com.aliyun.oss.OSS;
|
||||||
|
import com.aliyun.oss.common.auth.*;
|
||||||
|
import com.aliyun.oss.OSSClientBuilder;
|
||||||
|
import com.aliyun.oss.model.PutObjectRequest;
|
||||||
|
import com.aliyun.oss.model.PutObjectResult;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云oss能力
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component("ossAbility")
|
||||||
|
@ConditionalOnProperty(name = "app.oss.service_provider", havingValue = "aliyun")
|
||||||
|
public class AliOssAbility {
|
||||||
|
@Autowired
|
||||||
|
private DefaultCredentialProvider credentialsProvider;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单上传
|
||||||
|
*
|
||||||
|
* @param endpoint 接入口地址 https://oss-cn-guangzhou.aliyuncs.co
|
||||||
|
* @param bucketName Bucket名称
|
||||||
|
* @param objectPathName Object完整路径名称 /user/iii.log
|
||||||
|
*/
|
||||||
|
public void simpleUpload(String endpoint, String bucketName, String objectPathName, InputStream inputStream) throws Exception {
|
||||||
|
// 创建OSSClient实例。
|
||||||
|
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建PutObjectRequest对象。
|
||||||
|
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectPathName, inputStream);
|
||||||
|
|
||||||
|
// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
|
||||||
|
// ObjectMetadata metadata = new ObjectMetadata();
|
||||||
|
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
|
||||||
|
// metadata.setObjectAcl(CannedAccessControlList.Private);
|
||||||
|
// putObjectRequest.setMetadata(metadata);
|
||||||
|
|
||||||
|
PutObjectResult result = ossClient.putObject(putObjectRequest);
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("aliOss 请求封装出现异常 异常信息:{}", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (ossClient != null) {
|
||||||
|
ossClient.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("AliOss上传出现问题");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单下载
|
||||||
|
*
|
||||||
|
* @param endpoint 接入端口地址
|
||||||
|
* @param bucketName Bucket名称
|
||||||
|
* @param objectPathName Object完整路径名称 /user/iii.log
|
||||||
|
* @return 输入流
|
||||||
|
*/
|
||||||
|
public byte[] simpleDownload(String endpoint, String bucketName, String objectPathName) throws Exception {
|
||||||
|
|
||||||
|
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
|
||||||
|
try {
|
||||||
|
// ossObject包含文件所在的存储空间名称、文件名称、文件元数据以及一个输入流。
|
||||||
|
OSSObject ossObject = ossClient.getObject(bucketName, objectPathName);
|
||||||
|
InputStream inputStream = ossObject.getObjectContent();
|
||||||
|
byte[] fileBytes = IOUtils.toByteArray(inputStream);
|
||||||
|
inputStream.close();
|
||||||
|
ossObject.close();
|
||||||
|
return fileBytes;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("aliOss 请求封装出现异常 异常信息:{}", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (ossClient != null) {
|
||||||
|
ossClient.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("AliOss下载出现问题");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代签名的url
|
||||||
|
*
|
||||||
|
* @param endpoint 接入端口地址
|
||||||
|
* @param bucketName Bucket名称
|
||||||
|
* @param objectPathName Object完整路径名称 /user/iii.log
|
||||||
|
* @param httpMethod 方法 仅支持 get:获取 put:修改
|
||||||
|
*/
|
||||||
|
public String signatureUrl(String endpoint, String bucketName, String objectPathName, int second, HttpMethod httpMethod) throws Exception {
|
||||||
|
// 创建OSSClient实例。
|
||||||
|
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 封装生成签名url需要的信息
|
||||||
|
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objectPathName);
|
||||||
|
// 过期时间 毫秒
|
||||||
|
generatePresignedUrlRequest.setExpiration(new Date(new Date().getTime() + second * 1000L));
|
||||||
|
// 设置请求方法
|
||||||
|
generatePresignedUrlRequest.setMethod(httpMethod);
|
||||||
|
|
||||||
|
generatePresignedUrlRequest.setContentType("application/octet-stream");
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);
|
||||||
|
|
||||||
|
return url.toString().replace("http://", "https://");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("aliOss 请求封装出现异常 异常信息:{}", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
ossClient.shutdown();
|
||||||
|
}
|
||||||
|
throw new Exception("AliOss 获取签名Url出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.jiayunet.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口数据同一响应格式
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UnifiedResponse<T> {
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
private String msg;
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public static <K> UnifiedResponse<K> normalResponse(K date) {
|
||||||
|
UnifiedResponse<K> unifiedResponse = new UnifiedResponse<K>();
|
||||||
|
unifiedResponse.setCode("0");
|
||||||
|
unifiedResponse.setMsg("正常响应");
|
||||||
|
unifiedResponse.setData(date);
|
||||||
|
return unifiedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnifiedResponse<?> fail(String code, String msg) {
|
||||||
|
UnifiedResponse<?> unifiedResponse = new UnifiedResponse<Object>();
|
||||||
|
unifiedResponse.setCode(code);
|
||||||
|
unifiedResponse.setMsg(msg);
|
||||||
|
return unifiedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> UnifiedResponse<T> fail(String code, String msg, T data) {
|
||||||
|
UnifiedResponse<T> unifiedResponse = new UnifiedResponse<T>();
|
||||||
|
unifiedResponse.setCode(code);
|
||||||
|
unifiedResponse.setMsg(msg);
|
||||||
|
unifiedResponse.setData(data);
|
||||||
|
return unifiedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.jiayunet.pojo.interceptor;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redis中保存的的防止重复请求的信息
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RedisPreventReplayInfo {
|
||||||
|
/**
|
||||||
|
* 当前时间间隔秒内请求次数
|
||||||
|
*/
|
||||||
|
private Integer frequency = 0;
|
||||||
|
/**
|
||||||
|
* 最后间隔更新时间
|
||||||
|
*/
|
||||||
|
private Instant lastTiming = Instant.now();
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.jiayunet.pojo.login;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redis中保存的用户登录信息
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RedisLoginTokenInfo {
|
||||||
|
private Long userId;
|
||||||
|
/**
|
||||||
|
* 登录设备授权信息
|
||||||
|
*/
|
||||||
|
private List<LoginDevice> loginDevices = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* 拥有权限
|
||||||
|
*/
|
||||||
|
private List<String> authority = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* 角色
|
||||||
|
*/
|
||||||
|
private List<String> role = new ArrayList<>();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class LoginDevice {
|
||||||
|
/**
|
||||||
|
* 最后请求时间
|
||||||
|
*/
|
||||||
|
private Instant lastLoginTime;
|
||||||
|
/**
|
||||||
|
* 单次登陆唯一标识
|
||||||
|
*/
|
||||||
|
private String uuId;
|
||||||
|
/**
|
||||||
|
* 登录ip
|
||||||
|
*/
|
||||||
|
private String loginIp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package org.jiayunet.sms;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.jiayunet.tool.server.RedisServerTool;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Component("smsAbility")
|
||||||
|
@ConditionalOnProperty(name = "app.sms.service_provider", havingValue = "aliyun")
|
||||||
|
@Slf4j
|
||||||
|
public class AliYunSmsAbility implements ISmsAbility {
|
||||||
|
@Autowired
|
||||||
|
private com.aliyun.dysmsapi20170525.Client aliyunSmsClient;
|
||||||
|
@Autowired
|
||||||
|
private RedisServerTool redisServerTool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendVerificationCode(String phone, VerifyCodeAttribute attribute) {
|
||||||
|
String redisKey = attribute.getRedisKeyPre()+phone;
|
||||||
|
|
||||||
|
// 验证是否发送
|
||||||
|
boolean existKey = redisServerTool.hasKey(redisKey);
|
||||||
|
|
||||||
|
if (!attribute.getCover()){
|
||||||
|
Assert.isTrue(!existKey,"验证码已发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成验证码
|
||||||
|
int randomNumber = new Random().nextInt(1000000);
|
||||||
|
String number = String.format("%06d", randomNumber);
|
||||||
|
|
||||||
|
// 发送验证码
|
||||||
|
Assert.isTrue(sendVerificationCode(phone,number,attribute.getSignName(),attribute.getTemplateCode()),"短信发送失败,请稍后重试");
|
||||||
|
|
||||||
|
// 跟新redisK
|
||||||
|
redisServerTool.set(redisKey,number,attribute.getEffectiveTime(), TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVerificationCode(String phone, VerifyCodeAttribute attribute) {
|
||||||
|
return redisServerTool.get(attribute.getRedisKeyPre()+phone,String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delVerificationCode(String phone, VerifyCodeAttribute attribute) {
|
||||||
|
redisServerTool.delete(attribute.getRedisKeyPre()+phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendNotification(String phone, String message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ali 发送验证码
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param validCode 验证码
|
||||||
|
* @param signName 签名
|
||||||
|
* @param templateCode 模板
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
private boolean sendVerificationCode(String phone, String validCode, String signName, String templateCode) {
|
||||||
|
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest =
|
||||||
|
new com.aliyun.dysmsapi20170525.models.SendSmsRequest().setSignName(signName).setTemplateCode(templateCode)
|
||||||
|
.setPhoneNumbers(phone).setTemplateParam("{\"code\":\"" + validCode + "\"}");
|
||||||
|
|
||||||
|
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
|
||||||
|
try {
|
||||||
|
// 复制代码运行请自行打印 API 的返回值
|
||||||
|
SendSmsResponse response = aliyunSmsClient.sendSmsWithOptions(sendSmsRequest, runtime);
|
||||||
|
if (!response.getStatusCode().equals(200)||!response.getBody().getCode().equals("OK")){
|
||||||
|
log.error("短信发送失败: 失败原因:{}",response.getBody());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}catch (Throwable e){
|
||||||
|
log.error("短信发送异常: 异常:{}",e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.jiayunet.sms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象短信发送接口
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface ISmsAbility {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param attribute 配置
|
||||||
|
*/
|
||||||
|
void sendVerificationCode(String phone, VerifyCodeAttribute attribute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param attribute 配置
|
||||||
|
*/
|
||||||
|
String getVerificationCode(String phone, VerifyCodeAttribute attribute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除验证码
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param attribute 配置
|
||||||
|
*/
|
||||||
|
void delVerificationCode(String phone, VerifyCodeAttribute attribute);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送通知短信
|
||||||
|
*
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param message 消息
|
||||||
|
*/
|
||||||
|
void sendNotification(String phone, String message);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package org.jiayunet.sms;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信发送配配置
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class VerificationConfig implements VerifyCodeAttribute {
|
||||||
|
/**
|
||||||
|
* 模板签名
|
||||||
|
*/
|
||||||
|
private final String signName;
|
||||||
|
/**
|
||||||
|
* 模板code
|
||||||
|
*/
|
||||||
|
private final String templateCode;
|
||||||
|
/**
|
||||||
|
* redis 名
|
||||||
|
*/
|
||||||
|
private final String redisKeyPre;
|
||||||
|
/**
|
||||||
|
* 有效时间 单位分钟
|
||||||
|
*/
|
||||||
|
private final Integer effectiveTime;
|
||||||
|
/**
|
||||||
|
* 覆盖已存在的redis值
|
||||||
|
*/
|
||||||
|
private final Boolean cover;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造器
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private String signName;
|
||||||
|
private String templateCode;
|
||||||
|
private String redisKeyPre;
|
||||||
|
private Integer effectiveTime;
|
||||||
|
private Boolean cover;
|
||||||
|
|
||||||
|
public VerificationConfig.Builder config(VerifyCodeAttribute attribute){
|
||||||
|
this.signName = attribute.getSignName();
|
||||||
|
this.templateCode = attribute.getTemplateCode();
|
||||||
|
this.redisKeyPre = attribute.getRedisKeyPre();
|
||||||
|
this.effectiveTime = attribute.getEffectiveTime();
|
||||||
|
this.cover = attribute.getCover();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public VerificationConfig.Builder config(String signName, String templateCode,String redisKeyPre) {
|
||||||
|
this.signName = signName;
|
||||||
|
this.templateCode = templateCode;
|
||||||
|
this.redisKeyPre = redisKeyPre;
|
||||||
|
this.effectiveTime = 5;
|
||||||
|
this.cover = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public VerificationConfig.Builder config(String signName, String templateCode,String redisKeyPre,Integer effectiveTime) {
|
||||||
|
this.signName = signName;
|
||||||
|
this.templateCode = templateCode;
|
||||||
|
this.redisKeyPre = redisKeyPre;
|
||||||
|
this.effectiveTime = effectiveTime;
|
||||||
|
this.cover = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public VerificationConfig.Builder config(String signName, String templateCode,String redisKeyPre,Integer effectiveTime,Boolean cover) {
|
||||||
|
this.signName = signName;
|
||||||
|
this.templateCode = templateCode;
|
||||||
|
this.redisKeyPre = redisKeyPre;
|
||||||
|
this.effectiveTime = effectiveTime;
|
||||||
|
this.cover = cover;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public VerificationConfig.Builder redisKeyPre(String redisKeyPre) {
|
||||||
|
this.redisKeyPre = redisKeyPre;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerificationConfig.Builder templateCode(String templateCode) {
|
||||||
|
this.templateCode = templateCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerificationConfig.Builder signName(String signName) {
|
||||||
|
this.signName = signName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public VerificationConfig.Builder effectiveTime(Integer effectiveTime) {
|
||||||
|
this.effectiveTime = effectiveTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerificationConfig.Builder cover(Boolean cover) {
|
||||||
|
this.cover = cover;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public VerificationConfig build() {
|
||||||
|
Assert.hasText(signName,"signName不能为空");
|
||||||
|
Assert.hasText(templateCode,"templateCode不能为空");
|
||||||
|
Assert.hasText(redisKeyPre,"redisKey不能为空");
|
||||||
|
Assert.notNull(effectiveTime,"effectiveTime不能为空");
|
||||||
|
Assert.notNull(cover,"cover不能为空");
|
||||||
|
return new VerificationConfig(signName, templateCode, redisKeyPre,effectiveTime,cover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私有化构造
|
||||||
|
* @param signName 签名
|
||||||
|
* @param templateCode 短信模板
|
||||||
|
* @param redisKeyPre redis键名
|
||||||
|
* @param effectiveTime 有效时间
|
||||||
|
* @param cover 是否覆盖发送
|
||||||
|
*/
|
||||||
|
private VerificationConfig(String signName, String templateCode, String redisKeyPre, Integer effectiveTime, Boolean cover) {
|
||||||
|
this.signName = signName;
|
||||||
|
this.templateCode = templateCode;
|
||||||
|
this.redisKeyPre = redisKeyPre;
|
||||||
|
this.effectiveTime = effectiveTime;
|
||||||
|
this.cover = cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.jiayunet.sms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface VerifyCodeAttribute {
|
||||||
|
/**
|
||||||
|
* 签名
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
String getSignName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板code
|
||||||
|
*/
|
||||||
|
String getTemplateCode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redisKey 名字
|
||||||
|
*/
|
||||||
|
String getRedisKeyPre();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效时间
|
||||||
|
*/
|
||||||
|
Integer getEffectiveTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否覆盖发送
|
||||||
|
*/
|
||||||
|
Boolean getCover();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package org.jiayunet.tool;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class AuthenticTool {
|
||||||
|
/**
|
||||||
|
* 验证身份证号码
|
||||||
|
*
|
||||||
|
* @param idCard 身份证号
|
||||||
|
*/
|
||||||
|
public static Boolean idCard(String idCard) {
|
||||||
|
// 验证身份证号码
|
||||||
|
// 定义18位和15位身份证号码的正则表达式模式
|
||||||
|
String REGEX_18 = "^[1-9]\\d{5}(?:18|19|20)\\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$";
|
||||||
|
String REGEX_15 = "^[1-9]\\d{5}\\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])\\d{3}$";
|
||||||
|
String REGEX = "^(?:" + REGEX_15 + "|" + REGEX_18 + ")$";
|
||||||
|
Pattern pattern = Pattern.compile(REGEX);
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(idCard)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern.matcher(idCard).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证企业社会代码
|
||||||
|
*
|
||||||
|
* @param companyAccount 企业社会代码
|
||||||
|
*/
|
||||||
|
public static Boolean companyAccount(String companyAccount) {
|
||||||
|
// 校验码对应表
|
||||||
|
final String CHECK_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY"; // 不包含I、O、S、V、Z
|
||||||
|
// 权重数组
|
||||||
|
final int[] WEIGHT_ARRAY = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28};
|
||||||
|
|
||||||
|
if (companyAccount == null || companyAccount.length() != 18) {
|
||||||
|
System.out.println("请输入18位代码");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单验证:只包含数字和大写字母(根据标准,I、O、S、V、Z 不应出现)
|
||||||
|
if (!companyAccount.matches("^[0-9A-HJ-NP-RT-UW-Y]{18}$")) {
|
||||||
|
System.out.println("工商社会代码格式验证错误");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算校验码
|
||||||
|
int sum = 0;
|
||||||
|
for (int i = 0; i < 17; i++) {
|
||||||
|
char c = companyAccount.charAt(i);
|
||||||
|
int num = CHECK_CODE_ARRAY.indexOf(c);
|
||||||
|
if (num == -1) { // 如果字符不在对应表中
|
||||||
|
System.out.println("工商社会代码包含无效字符");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sum += num * WEIGHT_ARRAY[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算校验码位置
|
||||||
|
int mod = sum % 31;
|
||||||
|
char expectedCheckCode;
|
||||||
|
if (mod == 0) {
|
||||||
|
expectedCheckCode = CHECK_CODE_ARRAY.charAt(30); // 31 - 31 = 0
|
||||||
|
} else {
|
||||||
|
expectedCheckCode = CHECK_CODE_ARRAY.charAt(31 - mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取实际的校验码
|
||||||
|
char actualCheckCode = Character.toUpperCase(companyAccount.charAt(17));
|
||||||
|
|
||||||
|
// 比较校验码
|
||||||
|
if (expectedCheckCode != actualCheckCode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证通过
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公司名
|
||||||
|
*
|
||||||
|
* @param companyName 公司名
|
||||||
|
*/
|
||||||
|
public static Boolean companyName(String companyName) {
|
||||||
|
String COMPANY_NAME_REGEX = "^[\u4e00-\u9fa5A-Za-z0-9\\-_]{1,150}$";
|
||||||
|
Pattern COMPANY_NAME_PATTERN = Pattern.compile(COMPANY_NAME_REGEX);
|
||||||
|
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(companyName)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return COMPANY_NAME_PATTERN.matcher(companyName).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean chineseName(String name) {
|
||||||
|
// 简单的2 - 4个汉字人名验证正则表达式
|
||||||
|
String SIMPLE_NAME_REGEX = "^[\u4e00-\u9fa5]{2,6}$";
|
||||||
|
// 包含间隔符“·”的中文人名验证正则表达式
|
||||||
|
String COMPLEX_NAME_REGEX = "^([\u4e00-\u9fa5]{2,6})(·[\u4e00-\u9fa5]{2,4})?$";
|
||||||
|
|
||||||
|
Pattern SIMPLE_NAME_PATTERN = Pattern.compile(SIMPLE_NAME_REGEX);
|
||||||
|
Pattern COMPLEX_NAME_PATTERN = Pattern.compile(COMPLEX_NAME_REGEX);
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(name)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SIMPLE_NAME_PATTERN.matcher(name).matches()||COMPLEX_NAME_PATTERN.matcher(name).matches();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.jiayunet.tool;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class HttpIpTool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求的真实ip
|
||||||
|
*/
|
||||||
|
public static String gteRealIP() {
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||||
|
Assert.notNull(request, "当前非Http请求,无法获取request");
|
||||||
|
|
||||||
|
//存在转发
|
||||||
|
String realIp = request.getHeader("X-Forwarded-For");
|
||||||
|
if (StringUtils.hasText(realIp)) {
|
||||||
|
return realIp.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Nginx 直接链接客户端
|
||||||
|
realIp = request.getHeader("X-Real-IP");
|
||||||
|
if (StringUtils.hasText(realIp)) {
|
||||||
|
return realIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端直链服务
|
||||||
|
return request.getRemoteAddr();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求真实ip
|
||||||
|
* @param request http请求头
|
||||||
|
*/
|
||||||
|
public static String gteRealIP(HttpServletRequest request) {
|
||||||
|
//存在转发
|
||||||
|
String realIp = request.getHeader("X-Forwarded-For");
|
||||||
|
if (StringUtils.hasText(realIp)) {
|
||||||
|
return realIp.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Nginx 直接链接客户端
|
||||||
|
realIp = request.getHeader("X-Real-IP");
|
||||||
|
if (StringUtils.hasText(realIp)) {
|
||||||
|
return realIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端直链服务
|
||||||
|
return request.getRemoteAddr();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package org.jiayunet.tool;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class HttpTool {
|
||||||
|
public static final ObjectMapper objectMapper;
|
||||||
|
static {
|
||||||
|
objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送请求Body为json的请求
|
||||||
|
* @param body 参数对象
|
||||||
|
* @param url 路径
|
||||||
|
* @return 返回Str
|
||||||
|
*/
|
||||||
|
public static String sendJsonPost(@NotNull Object body, @NotNull String url){
|
||||||
|
return sendJsonPost(body,url,new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送post 请求
|
||||||
|
* @param body body
|
||||||
|
* @param url url
|
||||||
|
* @param headerMap 请求头
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String sendJsonPost(@NotNull Object body, @NotNull String url,Map<String,String> headerMap){
|
||||||
|
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||||
|
|
||||||
|
// 创建一个 HttpPost 对象
|
||||||
|
HttpPost httpPost = new HttpPost(url);
|
||||||
|
|
||||||
|
// 创建一个 StringEntity 对象,用于封装 JSON 数据
|
||||||
|
String jsonStr = objectMapper.writeValueAsString(body);
|
||||||
|
StringEntity entity = new StringEntity(jsonStr, StandardCharsets.UTF_8);
|
||||||
|
httpPost.setEntity(entity);
|
||||||
|
|
||||||
|
// 遍历 Map 并设置请求头
|
||||||
|
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
|
||||||
|
httpPost.setHeader(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
httpPost.setHeader("Content-Type","application/json");
|
||||||
|
|
||||||
|
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||||
|
String bodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
return bodyStr;
|
||||||
|
}
|
||||||
|
throw new IOException("Http请求出现错误,响应码:" + response.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送post请求
|
||||||
|
* @param bodyMap body
|
||||||
|
* @param url url
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String sendFormDataPost(@NotNull Map<String,Object> bodyMap, @NotNull String url){
|
||||||
|
return sendFormDataPost(bodyMap,url,new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送post请求
|
||||||
|
* @param bodyMap body
|
||||||
|
* @param url url
|
||||||
|
* @param headerMap 请求头
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String sendFormDataPost(@NotNull Map<String,Object> bodyMap, @NotNull String url,Map<String,String> headerMap){
|
||||||
|
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||||
|
|
||||||
|
// 创建一个 HttpPost 对象
|
||||||
|
HttpPost httpPost = new HttpPost(url);
|
||||||
|
|
||||||
|
// 创建 MultipartEntityBuilder
|
||||||
|
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
||||||
|
// 遍历 Map 并设置请求头
|
||||||
|
for (Map.Entry<String, Object> entry : bodyMap.entrySet()) {
|
||||||
|
builder.addTextBody(entry.getKey(), entry.getValue().toString(), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
httpPost.setEntity(builder.build());
|
||||||
|
|
||||||
|
// 遍历 Map 并设置请求头
|
||||||
|
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
|
||||||
|
httpPost.setHeader(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||||
|
String bodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
return bodyStr;
|
||||||
|
}
|
||||||
|
throw new IOException("Http请求出现错误,响应码:" + response.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* post下载文件
|
||||||
|
* @param body body
|
||||||
|
* @param url url
|
||||||
|
*/
|
||||||
|
public static InputStream downloadPost(@NotNull Object body, @NotNull String url){
|
||||||
|
return downloadPost(body,url, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* post下载文件
|
||||||
|
* @param body body
|
||||||
|
* @param url url
|
||||||
|
* @param headerMap 请求头
|
||||||
|
*/
|
||||||
|
public static InputStream downloadPost(@NotNull Object body, @NotNull String url,Map<String,String> headerMap){
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||||
|
|
||||||
|
// 创建一个 HttpPost 对象
|
||||||
|
HttpPost httpPost = new HttpPost(url);
|
||||||
|
|
||||||
|
// 创建一个 StringEntity 对象,用于封装 JSON 数据
|
||||||
|
String jsonStr = objectMapper.writeValueAsString(body);
|
||||||
|
StringEntity entity = new StringEntity(jsonStr, StandardCharsets.UTF_8);
|
||||||
|
httpPost.setEntity(entity);
|
||||||
|
|
||||||
|
// 遍历 Map 并设置请求头
|
||||||
|
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
|
||||||
|
httpPost.setHeader(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
httpPost.setHeader("Content-Type","application/json");
|
||||||
|
|
||||||
|
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
HttpEntity responseEntity = response.getEntity();
|
||||||
|
byte[] byteArray = EntityUtils.toByteArray(response.getEntity());
|
||||||
|
return new ByteArrayInputStream(byteArray);
|
||||||
|
}
|
||||||
|
throw new IOException("Http请求出现错误,响应码:" + response.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送请求GET请求
|
||||||
|
* @param paramMap 参数Map
|
||||||
|
* @param url 路径
|
||||||
|
* @return 返回Str
|
||||||
|
*/
|
||||||
|
public static String sendGet(@NotNull Map<String,Object> paramMap, @NotNull String url){
|
||||||
|
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||||
|
|
||||||
|
// 创建一个 HttpPost 对象
|
||||||
|
HttpGet httpGet = new HttpGet(url);
|
||||||
|
|
||||||
|
//处理请求参数
|
||||||
|
URIBuilder uriBuilder = new URIBuilder(httpGet.getURI());
|
||||||
|
for (String key : paramMap.keySet()) {
|
||||||
|
Object object = paramMap.get(key);
|
||||||
|
if (Objects.nonNull(object)){
|
||||||
|
uriBuilder.addParameter(key,object.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpGet.setURI(uriBuilder.build());
|
||||||
|
|
||||||
|
//发起请求
|
||||||
|
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
return EntityUtils.toString(response.getEntity(),StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
throw new IOException("Http请求出现异常,响应码:" + response.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException | URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.jiayunet.tool;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象操作工具类
|
||||||
|
*/
|
||||||
|
public class ObjectTool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制非null值
|
||||||
|
* @param source 数据源
|
||||||
|
* @param target 目标对象
|
||||||
|
*/
|
||||||
|
public static void copyNonNullProperties(Object source, Object target) {
|
||||||
|
Assert.notNull(target, "target must not be null");
|
||||||
|
Assert.notNull(source, "source must not be null");
|
||||||
|
|
||||||
|
PropertyDescriptor[] sourceDescriptors = BeanUtils.getPropertyDescriptors(source.getClass());
|
||||||
|
for (PropertyDescriptor descriptor : sourceDescriptors) {
|
||||||
|
try {
|
||||||
|
Object value = descriptor.getReadMethod().invoke(source);
|
||||||
|
if (value != null) { // 只复制非空字段
|
||||||
|
PropertyDescriptor targetDescriptor = BeanUtils.getPropertyDescriptor(target.getClass(), descriptor.getName());
|
||||||
|
if (targetDescriptor != null && targetDescriptor.getWriteMethod() != null) {
|
||||||
|
targetDescriptor.getWriteMethod().invoke(target, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.jiayunet.tool;
|
||||||
|
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class UserSecurityTool {
|
||||||
|
/**
|
||||||
|
* 获取当前用户ID
|
||||||
|
*
|
||||||
|
* @return id
|
||||||
|
*/
|
||||||
|
public static Long getUserId() {
|
||||||
|
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
Assert.notNull(userId, "用户未登录");
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package org.jiayunet.tool;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片验证码工具类
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public class VerifyImageCodeUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
|
||||||
|
*/
|
||||||
|
public static final String VERIFY_CODES = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
private static final Random random = new SecureRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用系统默认字符源生成验证码
|
||||||
|
*
|
||||||
|
* @param verifySize 验证码长度
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String generateVerifyCode(int verifySize) {
|
||||||
|
return generateVerifyCode(verifySize, VERIFY_CODES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用指定源生成验证码
|
||||||
|
*
|
||||||
|
* @param verifySize 验证码长度
|
||||||
|
* @param sources 验证码字符源
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String generateVerifyCode(int verifySize, String sources) {
|
||||||
|
if (sources == null || sources.length() == 0) {
|
||||||
|
sources = VERIFY_CODES;
|
||||||
|
}
|
||||||
|
int codesLen = sources.length();
|
||||||
|
Random rand = new Random(System.currentTimeMillis());
|
||||||
|
StringBuilder verifyCode = new StringBuilder(verifySize);
|
||||||
|
for (int i = 0; i < verifySize; i++) {
|
||||||
|
verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
|
||||||
|
}
|
||||||
|
return verifyCode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出指定验证码图片流
|
||||||
|
*
|
||||||
|
* @param w
|
||||||
|
* @param h
|
||||||
|
* @param os
|
||||||
|
* @param code
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
|
||||||
|
int verifySize = code.length();
|
||||||
|
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Random rand = new Random();
|
||||||
|
Graphics2D g2 = image.createGraphics();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
Color[] colors = new Color[5];
|
||||||
|
Color[] colorSpaces = new Color[] {Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA,
|
||||||
|
Color.ORANGE, Color.PINK, Color.YELLOW};
|
||||||
|
float[] fractions = new float[colors.length];
|
||||||
|
for (int i = 0; i < colors.length; i++) {
|
||||||
|
colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
|
||||||
|
fractions[i] = rand.nextFloat();
|
||||||
|
}
|
||||||
|
Arrays.sort(fractions);
|
||||||
|
|
||||||
|
g2.setColor(Color.GRAY);// 设置边框色
|
||||||
|
g2.fillRect(0, 0, w, h);
|
||||||
|
|
||||||
|
Color c = getRandColor(200, 250);
|
||||||
|
g2.setColor(c);// 设置背景色
|
||||||
|
g2.fillRect(0, 2, w, h - 4);
|
||||||
|
|
||||||
|
// 绘制干扰线
|
||||||
|
Random random = new Random();
|
||||||
|
g2.setColor(getRandColor(160, 200));// 设置线条的颜色
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
int x = random.nextInt(w - 1);
|
||||||
|
int y = random.nextInt(h - 1);
|
||||||
|
int xl = random.nextInt(6) + 1;
|
||||||
|
int yl = random.nextInt(12) + 1;
|
||||||
|
g2.drawLine(x, y, x + xl + 40, y + yl + 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加噪点
|
||||||
|
float yawpRate = 0.05f;// 噪声率
|
||||||
|
int area = (int)(yawpRate * w * h);
|
||||||
|
for (int i = 0; i < area; i++) {
|
||||||
|
int x = random.nextInt(w);
|
||||||
|
int y = random.nextInt(h);
|
||||||
|
int rgb = getRandomIntColor();
|
||||||
|
image.setRGB(x, y, rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
shear(g2, w, h, c);// 使图片扭曲
|
||||||
|
|
||||||
|
g2.setColor(getRandColor(100, 160));
|
||||||
|
int fontSize = h - 4;
|
||||||
|
Font font = new Font("Algerian", Font.ITALIC, fontSize);
|
||||||
|
g2.setFont(font);
|
||||||
|
char[] chars = code.toCharArray();
|
||||||
|
for (int i = 0; i < verifySize; i++) {
|
||||||
|
AffineTransform affine = new AffineTransform();
|
||||||
|
affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1),
|
||||||
|
(w / verifySize) * i + fontSize / 2, h / 2);
|
||||||
|
g2.setTransform(affine);
|
||||||
|
g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
g2.dispose();
|
||||||
|
ImageIO.write(image, "jpg", os);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color getRandColor(int fc, int bc) {
|
||||||
|
if (fc > 255) {
|
||||||
|
fc = 255;
|
||||||
|
}
|
||||||
|
if (bc > 255) {
|
||||||
|
bc = 255;
|
||||||
|
}
|
||||||
|
int r = fc + random.nextInt(bc - fc);
|
||||||
|
int g = fc + random.nextInt(bc - fc);
|
||||||
|
int b = fc + random.nextInt(bc - fc);
|
||||||
|
return new Color(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getRandomIntColor() {
|
||||||
|
int[] rgb = getRandomRgb();
|
||||||
|
int color = 0;
|
||||||
|
for (int c : rgb) {
|
||||||
|
color = color << 8;
|
||||||
|
color = color | c;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] getRandomRgb() {
|
||||||
|
int[] rgb = new int[3];
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
rgb[i] = random.nextInt(255);
|
||||||
|
}
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void shear(Graphics g, int w1, int h1, Color color) {
|
||||||
|
shearX(g, w1, h1, color);
|
||||||
|
shearY(g, w1, h1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void shearX(Graphics g, int w1, int h1, Color color) {
|
||||||
|
|
||||||
|
int period = random.nextInt(2);
|
||||||
|
|
||||||
|
boolean borderGap = true;
|
||||||
|
int frames = 1;
|
||||||
|
int phase = random.nextInt(2);
|
||||||
|
|
||||||
|
for (int i = 0; i < h1; i++) {
|
||||||
|
double d = (double)(period >> 1)
|
||||||
|
* Math.sin((double)i / (double)period + (6.2831853071795862D * (double)phase) / (double)frames);
|
||||||
|
g.copyArea(0, i, w1, 1, (int)d, 0);
|
||||||
|
if (borderGap) {
|
||||||
|
g.setColor(color);
|
||||||
|
g.drawLine((int)d, i, 0, i);
|
||||||
|
g.drawLine((int)d + w1, i, w1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void shearY(Graphics g, int w1, int h1, Color color) {
|
||||||
|
|
||||||
|
int period = random.nextInt(40) + 10; // 50;
|
||||||
|
|
||||||
|
boolean borderGap = true;
|
||||||
|
int frames = 20;
|
||||||
|
int phase = 7;
|
||||||
|
for (int i = 0; i < w1; i++) {
|
||||||
|
double d = (double)(period >> 1)
|
||||||
|
* Math.sin((double)i / (double)period + (6.2831853071795862D * (double)phase) / (double)frames);
|
||||||
|
g.copyArea(i, 0, 1, h1, 0, (int)d);
|
||||||
|
if (borderGap) {
|
||||||
|
g.setColor(color);
|
||||||
|
g.drawLine(i, (int)d, i, 0);
|
||||||
|
g.drawLine(i, (int)d + h1, i, h1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package org.jiayunet.tool.server;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redis 操作工具类
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class RedisServerTool {
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
@Value("${spring.application.name}")
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
public void set(Object key, Object value) {
|
||||||
|
redisTemplate.opsForValue().set(getKeyName(key), toJsonString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Object key, Object value, long timeout, TimeUnit unit) {
|
||||||
|
redisTemplate.opsForValue().set(getKeyName(key), toJsonString(value), timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V> V get(Object key, Class<V> vClass) {
|
||||||
|
String str = redisTemplate.opsForValue().get(getKeyName(key));
|
||||||
|
return toObject(str, vClass);
|
||||||
|
}
|
||||||
|
public <V> V get(Object key, Class<V> vClass,Boolean ifPreKeyName) {
|
||||||
|
String keyName = ifPreKeyName ? getKeyName(key) : key.toString();
|
||||||
|
String str = redisTemplate.opsForValue().get(keyName);
|
||||||
|
return toObject(str, vClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public <V> Collection<V> getArr(Object key, Class<V> vClass) {
|
||||||
|
String str = redisTemplate.opsForValue().get(getKeyName(key));
|
||||||
|
return toArrObject(str, vClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasKey(Object key) {
|
||||||
|
return Boolean.TRUE.equals(redisTemplate.hasKey(getKeyName(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取key的剩余过期时间(秒)
|
||||||
|
* @param key key
|
||||||
|
* @return 剩余秒数,-1表示永不过期,-2表示key不存在
|
||||||
|
*/
|
||||||
|
public Long getExpire(Object key) {
|
||||||
|
return redisTemplate.getExpire(getKeyName(key), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean delete(Object key) {
|
||||||
|
return Boolean.TRUE.equals(redisTemplate.delete(getKeyName(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setNx(Object key, Object value) {
|
||||||
|
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(getKeyName(key), toJsonString(value)));
|
||||||
|
}
|
||||||
|
public boolean setNx(Object key, Object value, long timeout, TimeUnit unit) {
|
||||||
|
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(getKeyName(key), toJsonString(value),timeout,unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toJsonString(Object value) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(value);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <V> V toObject(String str, Class<V> vClass) {
|
||||||
|
if (!StringUtils.hasText(str)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(str, vClass);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <V> Collection<V> toArrObject(String str, Class<V> vClass) {
|
||||||
|
if (!StringUtils.hasText(str)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(str, new TypeReference<Collection<V>>() {
|
||||||
|
});
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将key的键名包装上服务名字
|
||||||
|
* @param key keu
|
||||||
|
*/
|
||||||
|
private String getKeyName(Object key){
|
||||||
|
if (StringUtils.hasText(appName)){
|
||||||
|
return appName+":"+key.toString();
|
||||||
|
}
|
||||||
|
return key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package org.jiayunet.web;
|
||||||
|
|
||||||
|
import javax.validation.ValidationException;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
import org.jiayunet.exception.BusinessException;
|
||||||
|
import org.jiayunet.exception.BusinessExpCodeEnum;
|
||||||
|
import org.jiayunet.pojo.UnifiedResponse;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionAdvice {
|
||||||
|
|
||||||
|
@ExceptionHandler({MethodArgumentTypeMismatchException.class})
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public UnifiedResponse<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return this.unifiedExpResponse(BusinessExpCodeEnum.PARAMS_INCORRECT.getCode(), ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({BusinessException.class})
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public UnifiedResponse<?> businessCheckExceptionHandler(BusinessException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
|
||||||
|
String description = ex.getDescription();
|
||||||
|
String msg = ex.getBusinessMsg();
|
||||||
|
if (StringUtils.hasText(description)) {
|
||||||
|
if (!ex.getReplace()) {
|
||||||
|
msg = msg + "[" + description + "]";
|
||||||
|
} else {
|
||||||
|
msg = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnifiedResponse.fail(ex.getBusinessCode(), msg, ex.getData());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({IllegalArgumentException.class})
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public UnifiedResponse<?> mybatisPlusException(IllegalArgumentException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return this.unifiedExpResponse("断言异常", ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({ValidationException.class})
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public UnifiedResponse<?> validationException(ValidationException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return this.unifiedExpResponse("参数异常拦截", ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({BindException.class})
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public UnifiedResponse<?> bindException(BindException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return this.unifiedExpResponse("参数绑定异常", ex.getMessage());
|
||||||
|
}
|
||||||
|
@ExceptionHandler({Exception.class})
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public UnifiedResponse<?> lastExceptionHandler(Exception ex) {
|
||||||
|
BusinessExpCodeEnum unknownError = BusinessExpCodeEnum.UNKNOWN_ERROR;
|
||||||
|
ex.printStackTrace();
|
||||||
|
return this.unifiedExpResponse(unknownError.getCode(), unknownError.getMsg());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnifiedResponse<?> unifiedExpResponse(String code, String msg) {
|
||||||
|
return UnifiedResponse.fail(code, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnifiedResponse<?> unifiedExpResponse(String errorFrom, String code, String msg) {
|
||||||
|
return UnifiedResponse.fail(code, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.jiayunet.web;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
import org.jiayunet.pojo.UnifiedResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一返回数据格式
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class UnifiedResponseBodyAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
return !ResponseEntity.class.isAssignableFrom(returnType.getParameterType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||||
|
|
||||||
|
// 异常
|
||||||
|
if (body instanceof UnifiedResponse) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnifiedResponse<Object> unifiedResponse = UnifiedResponse.normalResponse(body);
|
||||||
|
return String.class.equals(returnType.getParameterType()) ? objectMapper.writeValueAsString(unifiedResponse) : unifiedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package org.jiayunet.wxPay;
|
||||||
|
|
||||||
|
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByIdRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||||
|
import com.wechat.pay.java.service.refund.RefundService;
|
||||||
|
import com.wechat.pay.java.service.refund.model.CreateRequest;
|
||||||
|
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
|
||||||
|
import com.wechat.pay.java.service.refund.model.Refund;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jiayunet.wxPay.server.model.JsRequestPay;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付能力
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class WxJsPayAbility {
|
||||||
|
@Autowired
|
||||||
|
private JsapiService jsapiService;
|
||||||
|
@Autowired
|
||||||
|
private RefundService refundService;
|
||||||
|
@Autowired
|
||||||
|
private PrivateKey privateKey;
|
||||||
|
/**
|
||||||
|
* 回调域名地址
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.notify_domain}")
|
||||||
|
private String notifyDomain;
|
||||||
|
/**
|
||||||
|
* 商户号
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.merchant_id}")
|
||||||
|
private String merchantId;
|
||||||
|
/**
|
||||||
|
* 小程序id
|
||||||
|
*/
|
||||||
|
@Value("${app.wx.app_id}")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单 已默认回调地址
|
||||||
|
*
|
||||||
|
* @param paramRequest 请求参数
|
||||||
|
* @return 预支付订单号
|
||||||
|
*/
|
||||||
|
public String prepay(PrepayRequest paramRequest) {
|
||||||
|
try {
|
||||||
|
paramRequest.setNotifyUrl(notifyDomain + "/public/wxNotify/pay");
|
||||||
|
log.info("以默认设置支付回调地址为:{}", paramRequest.getNotifyUrl());
|
||||||
|
paramRequest.setAppid(appId);
|
||||||
|
paramRequest.setMchid(merchantId);
|
||||||
|
PrepayResponse prepay = jsapiService.prepay(paramRequest);
|
||||||
|
return prepay.getPrepayId();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSAPI支付下单出现异常,错误信息:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("JSAPI支付下单出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭订单
|
||||||
|
*
|
||||||
|
* @param outTradeNo 外部订单
|
||||||
|
* @param child 商户id
|
||||||
|
*/
|
||||||
|
public void closeOrder(String outTradeNo, String child) {
|
||||||
|
CloseOrderRequest closeRequest = new CloseOrderRequest();
|
||||||
|
closeRequest.setMchid(child);
|
||||||
|
closeRequest.setOutTradeNo(outTradeNo);
|
||||||
|
try {
|
||||||
|
jsapiService.closeOrder(closeRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSAPI支付关闭订单出现异常,outTradeNo:{},mchId:{},错误信息:{}", outTradeNo, child, e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("JSAPI支付关闭订单出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付订单号查询订单
|
||||||
|
*
|
||||||
|
* @param transactionId 交易订单id
|
||||||
|
* @param mchId 商户id
|
||||||
|
* @return 交易信息
|
||||||
|
*/
|
||||||
|
public Transaction queryOrderById(String transactionId, String mchId) {
|
||||||
|
|
||||||
|
QueryOrderByIdRequest request = new QueryOrderByIdRequest();
|
||||||
|
request.setMchid(mchId);
|
||||||
|
request.setTransactionId(transactionId);
|
||||||
|
try {
|
||||||
|
return jsapiService.queryOrderById(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSAPI微信支付订单号查询订单出现异常,transactionId:{},mchId:{},错误信息:{}", transactionId, mchId, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("JSAPI微信支付订单号查询订单出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户订单号查询订单
|
||||||
|
*
|
||||||
|
* @param outTradeNo 外部订单号
|
||||||
|
* @param mchId 商户号
|
||||||
|
*/
|
||||||
|
public Transaction queryOrderByOutTradeNo(String outTradeNo, String mchId) {
|
||||||
|
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
|
||||||
|
request.setMchid(mchId);
|
||||||
|
request.setOutTradeNo(outTradeNo);
|
||||||
|
try {
|
||||||
|
return jsapiService.queryOrderByOutTradeNo(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSAPI支付关闭订单出现异常,outTradeNo:{},mchId:{},错误信息:{}", outTradeNo, mchId, e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("JSAPI支付关闭订单出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款申请
|
||||||
|
*
|
||||||
|
* @param request 退款申请参数
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Refund refund(CreateRequest request) {
|
||||||
|
try {
|
||||||
|
request.setNotifyUrl(notifyDomain + "/public/wxNotify/refund");
|
||||||
|
log.info("以默认设置支付回调地址为:{}", request.getNotifyUrl());
|
||||||
|
return refundService.create(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSAPI创建退款申请出现异常,请求参数:{},错误信息:{}", request, e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("JSAPI创建退款申请出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询单笔退款(通过商户退款单号)
|
||||||
|
*
|
||||||
|
* @param outRefundNo 外部订单号
|
||||||
|
*/
|
||||||
|
public Refund queryByOutRefundNo(String outRefundNo) {
|
||||||
|
try {
|
||||||
|
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
|
||||||
|
request.setOutRefundNo(outRefundNo);
|
||||||
|
return refundService.queryByOutRefundNo(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSAPI查询单笔退款出现异常,outRefundNo:{},错误信息:{}", outRefundNo, e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("JSAPI查询单笔退款出现异常");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名
|
||||||
|
*/
|
||||||
|
public String createSign(String appId, String timeStamp, String nonceStr, String packageStr) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构造签名串
|
||||||
|
String signStr = String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageStr);
|
||||||
|
|
||||||
|
// 加载私钥
|
||||||
|
byte[] keyBytes = privateKey.getEncoded();
|
||||||
|
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
PrivateKey priKey = keyFactory.generatePrivate(keySpec);
|
||||||
|
|
||||||
|
// 使用 SHA256withRSA 签名
|
||||||
|
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||||
|
signature.initSign(priKey);
|
||||||
|
signature.update(signStr.getBytes(StandardCharsets.UTF_8));
|
||||||
|
byte[] signed = signature.sign();
|
||||||
|
|
||||||
|
// 返回 Base64 编码的签名
|
||||||
|
return Base64.getEncoder().encodeToString(signed);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("生成签名失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前端发起js支付需要的参数
|
||||||
|
* @param prepayId 预订单号
|
||||||
|
* @return JsRequestPay
|
||||||
|
*/
|
||||||
|
public JsRequestPay getJsRequestPay(String prepayId) {
|
||||||
|
JsRequestPay jsRequestPay = new JsRequestPay();
|
||||||
|
jsRequestPay.setAppId(appId);
|
||||||
|
jsRequestPay.setNonceStr(UUID.randomUUID().toString().replace("-", "").substring(0,24));
|
||||||
|
jsRequestPay.setSignType("RSA");
|
||||||
|
jsRequestPay.setPackageStr("prepay_id=" + prepayId);
|
||||||
|
jsRequestPay.setTimeStamp(String.valueOf(System.currentTimeMillis()/1000));
|
||||||
|
jsRequestPay.setPaySign(createSign(jsRequestPay.getAppId(),jsRequestPay.getTimeStamp(),jsRequestPay.getNonceStr(),jsRequestPay.getPackageStr()));
|
||||||
|
|
||||||
|
return jsRequestPay;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.jiayunet.wxPay;
|
||||||
|
|
||||||
|
|
||||||
|
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByOutTradeNoRequest;
|
||||||
|
import com.wechat.pay.java.service.refund.RefundService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class WxNativePayAbility {
|
||||||
|
@Autowired
|
||||||
|
private NativePayService nativePayService;
|
||||||
|
/**
|
||||||
|
* 回调域名地址
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.notify_domain}")
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预支付
|
||||||
|
* @param request 请求头
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String prepay(PrepayRequest request) {
|
||||||
|
try {
|
||||||
|
request.setNotifyUrl(notifyDomain+"/public/wxNotify/pay");
|
||||||
|
log.info("以默认设置支付回调地址为:{}",request.getNotifyUrl());
|
||||||
|
PrepayResponse prepay = nativePayService.prepay(request);
|
||||||
|
return prepay.getCodeUrl();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("nativePay支付下单出现异常,错误信息:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("nativePay支付下单出现异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transaction queryOrderByOutTradeNo(String outTradeNo, String mchid) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
|
||||||
|
request.setMchid(mchid);
|
||||||
|
request.setOutTradeNo(outTradeNo);
|
||||||
|
|
||||||
|
return nativePayService.queryOrderByOutTradeNo(request);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("nativePay支付下单出现异常,错误信息:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("nativePay支付下单出现异常");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package org.jiayunet.wxPay;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.core.exception.ValidationException;
|
||||||
|
import com.wechat.pay.java.core.notification.NotificationParser;
|
||||||
|
import com.wechat.pay.java.core.notification.RequestParam;
|
||||||
|
import com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction;
|
||||||
|
import com.wechat.pay.java.service.refund.model.RefundNotification;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferNotification;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
@RequestMapping("/public/wxNotify")
|
||||||
|
@Slf4j
|
||||||
|
public class WxPayNotifyController {
|
||||||
|
@Autowired
|
||||||
|
private NotificationParser parser;
|
||||||
|
@Autowired
|
||||||
|
private WxPayNotifyMessageAbstract wxPayNotifyMessageAbstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付结果回调
|
||||||
|
*/
|
||||||
|
@PostMapping("/pay")
|
||||||
|
public ResponseEntity<String> pay(HttpServletRequest request) {
|
||||||
|
return communalRequestHandle(request, Transaction.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款结果回调
|
||||||
|
*/
|
||||||
|
@PostMapping("/refund")
|
||||||
|
public ResponseEntity<String> refund(HttpServletRequest request) {
|
||||||
|
return communalRequestHandle(request, RefundNotification.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商家转账回调
|
||||||
|
*/
|
||||||
|
|
||||||
|
@PostMapping("/transfer")
|
||||||
|
public ResponseEntity<String> transfer(HttpServletRequest request) {
|
||||||
|
return communalRequestHandle(request, TransferNotification.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public <T> ResponseEntity<String> communalRequestHandle(HttpServletRequest request, Class<T> tClass) {
|
||||||
|
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
|
||||||
|
String wechatpayNonce = request.getHeader("Wechatpay-Nonce");
|
||||||
|
String wechatSignature = request.getHeader("Wechatpay-Signature");
|
||||||
|
String wechatTimestamp = request.getHeader("Wechatpay-Timestamp");
|
||||||
|
|
||||||
|
String requestBody;
|
||||||
|
try {
|
||||||
|
requestBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("微信支付回调获取输入流出错", e);
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("请求体读取失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(requestBody)) {
|
||||||
|
log.error("微信支付回调请求体为空");
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("请求体为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印日志
|
||||||
|
log.info("收到微信支付回调数据, body数据:{}",requestBody);
|
||||||
|
log.info("收到微信支付回调数据, 请求头wechatPaySerial数据:{}",wechatPaySerial);
|
||||||
|
log.info("收到微信支付回调数据, 请求头wechatpayNonce数据:{}",wechatpayNonce);
|
||||||
|
log.info("收到微信支付回调数据, 请求头wechatSignature数据:{}",wechatSignature);
|
||||||
|
log.info("收到微信支付回调数据, 请求头wechatTimestamp数据:{}",wechatTimestamp);
|
||||||
|
|
||||||
|
|
||||||
|
RequestParam requestParam = new RequestParam.Builder()
|
||||||
|
.serialNumber(wechatPaySerial)
|
||||||
|
.nonce(wechatpayNonce)
|
||||||
|
.signature(wechatSignature)
|
||||||
|
.timestamp(wechatTimestamp)
|
||||||
|
.body(requestBody)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
T parse = parser.parse(requestParam, tClass);
|
||||||
|
// 支付回调
|
||||||
|
if (Objects.equals(tClass, Transaction.class)) {
|
||||||
|
wxPayNotifyMessageAbstract.payMessageHandle((Transaction) parse);
|
||||||
|
}
|
||||||
|
// 退款回调
|
||||||
|
if (tClass == RefundNotification.class) {
|
||||||
|
wxPayNotifyMessageAbstract.refundMessageHandle((RefundNotification) parse);
|
||||||
|
}
|
||||||
|
// 商户转账回调
|
||||||
|
if (tClass == TransferNotification.class){
|
||||||
|
wxPayNotifyMessageAbstract.transferMessageHandle((TransferNotification) parse);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
log.error("微信退回调数据验签错误, 请求体: {}, 请求头: {}", requestBody, request.getHeaderNames(), e);
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("验签失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("微信回调业务错误", e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("内部错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok("200");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.jiayunet.wxPay;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction;
|
||||||
|
import com.wechat.pay.java.service.refund.model.RefundNotification;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferNotification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付回调消息处理接口
|
||||||
|
* 使用前需要实集成接口 实现方法
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface WxPayNotifyMessageAbstract {
|
||||||
|
/**
|
||||||
|
* 支付结果回调消息处理
|
||||||
|
* @param transaction 支付回调消息
|
||||||
|
*/
|
||||||
|
default void payMessageHandle(Transaction transaction){
|
||||||
|
System.out.println("微信支付的支付结果回调消息未处理");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款回调消息
|
||||||
|
* @param refundNotification 退款回调
|
||||||
|
*/
|
||||||
|
default void refundMessageHandle(RefundNotification refundNotification){
|
||||||
|
System.out.println("微信支付的退款回调消息未处理");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户支付回调消息
|
||||||
|
* @param refundNotification 退款回调
|
||||||
|
*/
|
||||||
|
default void transferMessageHandle(TransferNotification refundNotification){
|
||||||
|
System.out.println("微信支付的商家转账回调未处理");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.jiayunet.wxPay;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.core.exception.WechatPayException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.jiayunet.wxPay.server.TransferServer;
|
||||||
|
import org.jiayunet.wxPay.server.model.BillNoQueryRequest;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferBillsRequest;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferBillsResponse;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferResultResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(name = "wx_pay.status", havingValue = "open")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class WxTransferPayAbility {
|
||||||
|
@Autowired
|
||||||
|
private TransferServer transferServer;
|
||||||
|
/**
|
||||||
|
* 回调域名地址
|
||||||
|
*/
|
||||||
|
@Value("${wx_pay.notify_domain}")
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起转账
|
||||||
|
* @param request 请求参数
|
||||||
|
*/
|
||||||
|
public TransferBillsResponse transferBills(TransferBillsRequest request) {
|
||||||
|
try {
|
||||||
|
request.setNotifyUrl(notifyDomain+"/public/wxNotify/transfer");
|
||||||
|
log.info("以默认设置回调地址为:{}",request.getNotifyUrl());
|
||||||
|
return transferServer.transferBills(request);
|
||||||
|
|
||||||
|
} catch (WechatPayException e) {
|
||||||
|
log.error("TransferPay发起转账出现异常,错误信息:{}", e.getMessage());
|
||||||
|
throw new IllegalArgumentException(e.getMessage());
|
||||||
|
}catch (Exception e){
|
||||||
|
log.error("TransferPay发起转账出现异常,错误信息:{}", e.getMessage());
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转账订单查询
|
||||||
|
* @param request 请求参数
|
||||||
|
*/
|
||||||
|
public TransferResultResponse billNoQuery(BillNoQueryRequest request) {
|
||||||
|
try {
|
||||||
|
return transferServer.billNoQuery(request);
|
||||||
|
} catch (Exception e){
|
||||||
|
log.error("TransferPay查询账出现异常,错误信息:{}", e.getMessage());
|
||||||
|
throw new RuntimeException("TransferPay查询账出现异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package org.jiayunet.wxPay.server;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.core.Config;
|
||||||
|
import com.wechat.pay.java.core.cipher.PrivacyDecryptor;
|
||||||
|
import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
|
||||||
|
import com.wechat.pay.java.core.http.Constant;
|
||||||
|
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
|
||||||
|
import com.wechat.pay.java.core.http.HostName;
|
||||||
|
import com.wechat.pay.java.core.http.HttpClient;
|
||||||
|
import com.wechat.pay.java.core.http.HttpHeaders;
|
||||||
|
import com.wechat.pay.java.core.http.HttpMethod;
|
||||||
|
import com.wechat.pay.java.core.http.HttpRequest;
|
||||||
|
import com.wechat.pay.java.core.http.HttpResponse;
|
||||||
|
import com.wechat.pay.java.core.http.JsonRequestBody;
|
||||||
|
import com.wechat.pay.java.core.http.MediaType;
|
||||||
|
import com.wechat.pay.java.core.http.RequestBody;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.jiayunet.wxPay.server.model.BillNoQueryRequest;
|
||||||
|
import org.jiayunet.wxPay.server.model.CancelRequest;
|
||||||
|
import org.jiayunet.wxPay.server.model.CancelResponse;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferBillsRequest;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferBillsResponse;
|
||||||
|
import org.jiayunet.wxPay.server.model.TransferResultResponse;
|
||||||
|
|
||||||
|
import static com.wechat.pay.java.core.http.UrlEncoder.urlEncode;
|
||||||
|
import static com.wechat.pay.java.core.util.GsonUtil.toJson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class TransferServer {
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private HostName hostName;
|
||||||
|
private PrivacyEncryptor encryptor;
|
||||||
|
private PrivacyDecryptor decryptor;
|
||||||
|
|
||||||
|
|
||||||
|
/** TransferServer构造器 */
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private HostName hostName;
|
||||||
|
private PrivacyEncryptor encryptor;
|
||||||
|
private PrivacyDecryptor decryptor;
|
||||||
|
|
||||||
|
public TransferServer.Builder config(Config config) {
|
||||||
|
this.httpClient = new DefaultHttpClientBuilder().config(config).build();
|
||||||
|
this.encryptor = config.createEncryptor();
|
||||||
|
this.decryptor = config.createDecryptor();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransferServer.Builder hostName(HostName hostName) {
|
||||||
|
this.hostName = hostName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransferServer.Builder httpClient(HttpClient httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransferServer.Builder encryptor(PrivacyEncryptor encryptor) {
|
||||||
|
this.encryptor = encryptor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransferServer.Builder decryptor(PrivacyDecryptor decryptor) {
|
||||||
|
this.decryptor = decryptor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransferServer build() {
|
||||||
|
return new TransferServer(httpClient, hostName, encryptor, decryptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起转账
|
||||||
|
* @param request 请求参数
|
||||||
|
*/
|
||||||
|
public TransferBillsResponse transferBills(TransferBillsRequest request) {
|
||||||
|
String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
|
||||||
|
if (StringUtils.hasText(request.getUserName())){
|
||||||
|
request.setUserName(encryptor.encrypt(request.getUserName()));
|
||||||
|
if (request.getTransferAmount()<=30){
|
||||||
|
log.info("微信商户转账0.3元一下不支持实名验证,以默认设置为空");
|
||||||
|
request.setUserName(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
|
||||||
|
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
|
||||||
|
headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
|
||||||
|
|
||||||
|
|
||||||
|
HttpRequest httpRequest =
|
||||||
|
new HttpRequest.Builder()
|
||||||
|
.httpMethod(HttpMethod.POST)
|
||||||
|
.url(requestPath)
|
||||||
|
.headers(headers)
|
||||||
|
.body(createRequestBody(request))
|
||||||
|
.build();
|
||||||
|
HttpResponse<TransferBillsResponse> httpResponse = httpClient.execute(httpRequest, TransferBillsResponse.class);
|
||||||
|
return httpResponse.getServiceResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤销转账
|
||||||
|
* @param request 请求参数
|
||||||
|
*/
|
||||||
|
public CancelResponse cancel(CancelRequest request) {
|
||||||
|
String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel";
|
||||||
|
// 添加 path param
|
||||||
|
requestPath = requestPath.replace("{" + "out_bill_no" + "}", urlEncode(request.getOutBillNo()));
|
||||||
|
// 设置请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
|
||||||
|
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
|
||||||
|
HttpRequest httpRequest =
|
||||||
|
new HttpRequest.Builder()
|
||||||
|
.httpMethod(HttpMethod.POST)
|
||||||
|
.url(requestPath)
|
||||||
|
.headers(headers)
|
||||||
|
.body(createRequestBody(request))
|
||||||
|
.build();
|
||||||
|
HttpResponse<CancelResponse> httpResponse = httpClient.execute(httpRequest, CancelResponse.class);
|
||||||
|
return httpResponse.getServiceResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单查询
|
||||||
|
* @param request 参数
|
||||||
|
*/
|
||||||
|
public TransferResultResponse billNoQuery(BillNoQueryRequest request) {
|
||||||
|
String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}";
|
||||||
|
// 添加 path param
|
||||||
|
requestPath = requestPath.replace("{" + "transfer_bill_no" + "}", urlEncode(request.getTransferBillNo()));
|
||||||
|
// 设置请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
|
||||||
|
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
|
||||||
|
HttpRequest httpRequest =
|
||||||
|
new HttpRequest.Builder()
|
||||||
|
.httpMethod(HttpMethod.GET)
|
||||||
|
.url(requestPath)
|
||||||
|
.headers(headers)
|
||||||
|
.build();
|
||||||
|
HttpResponse<TransferResultResponse> httpResponse = httpClient.execute(httpRequest, TransferResultResponse.class);
|
||||||
|
return httpResponse.getServiceResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private RequestBody createRequestBody(Object request) {
|
||||||
|
return new JsonRequestBody.Builder().body(toJson(request)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BillNoQueryRequest {
|
||||||
|
/**
|
||||||
|
* 外部订单号
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_bill_no")
|
||||||
|
private String transferBillNo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CancelRequest {
|
||||||
|
/**
|
||||||
|
* 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
||||||
|
*/
|
||||||
|
@SerializedName("outBillNo")
|
||||||
|
@Expose(serialize = false)
|
||||||
|
private String outBillNo;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CancelResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
||||||
|
**/
|
||||||
|
@SerializedName("out_bill_no")
|
||||||
|
private String outBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【微信转账单号】 商家转账订单的主键,唯一定义此资源的标识
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_bill_no")
|
||||||
|
private String transferBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【单据状态】 CANCELING: 撤销中;CANCELLED:已撤销
|
||||||
|
*/
|
||||||
|
@SerializedName("state")
|
||||||
|
private CancelState state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【最后一次单据状态变更时间】 按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE
|
||||||
|
*/
|
||||||
|
@SerializedName("update_time")
|
||||||
|
private String update_time;
|
||||||
|
|
||||||
|
public enum CancelState{
|
||||||
|
/**
|
||||||
|
* 撤销中
|
||||||
|
*/
|
||||||
|
@SerializedName("CANCELING")
|
||||||
|
CANCELING,
|
||||||
|
/**
|
||||||
|
* 已撤销
|
||||||
|
*/
|
||||||
|
@SerializedName("CANCELLED")
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class JsRequestPay {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填写下单时传入的appid,且必需与当前实际调起支付的公众号appid一致,否则无法调起支付。
|
||||||
|
*/
|
||||||
|
private String appId;
|
||||||
|
/**
|
||||||
|
* Unix 时间戳,是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数。
|
||||||
|
*/
|
||||||
|
private String timeStamp;
|
||||||
|
/**
|
||||||
|
* 随机字符串,不长于32位。该值建议使用随机数算法生成。
|
||||||
|
*/
|
||||||
|
private String nonceStr;
|
||||||
|
/**
|
||||||
|
* 订单详情扩展字符串,JSAPI下单接口返回的prepay_id参数值,提交格式如:prepay_id=***。
|
||||||
|
*/
|
||||||
|
private String packageStr;
|
||||||
|
/**
|
||||||
|
* 签名类型,固定填RSA。
|
||||||
|
*/
|
||||||
|
private String signType;
|
||||||
|
/**
|
||||||
|
* 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 注意:取值RSA格式。
|
||||||
|
*/
|
||||||
|
private String paySign;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class TransferBillsRequest {
|
||||||
|
/** 公众号ID 说明:公众号ID */
|
||||||
|
@SerializedName("appid")
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
/** 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 */
|
||||||
|
@SerializedName("out_bill_no")
|
||||||
|
private String outBillNo;
|
||||||
|
|
||||||
|
/**【转账场景ID】 该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。如:1001-现金营销*/
|
||||||
|
@SerializedName("transfer_scene_id")
|
||||||
|
private String transferSceneId;
|
||||||
|
|
||||||
|
/**【收款用户OpenID】 用户在商户appid下的唯一标识。发起转账前需获取到用户的OpenID,获取方式详见参数说明。**/
|
||||||
|
@SerializedName("openid")
|
||||||
|
private String openid;
|
||||||
|
|
||||||
|
/**【收款用户姓名】 收款方真实姓名。需要加密传入,支持标准RSA算法和国密算法,公钥由微信侧提供。
|
||||||
|
转账金额 >= 2,000元时,该笔明细必须填写
|
||||||
|
若商户传入收款用户姓名,微信支付会校验收款用户与输入姓名是否一致,并提供电子回单**/
|
||||||
|
@SerializedName("user_name")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**【转账金额】 转账金额单位为“分”。*/
|
||||||
|
@SerializedName("transfer_amount")
|
||||||
|
private Integer transferAmount;
|
||||||
|
|
||||||
|
/**【转账备注】 转账备注,用户收款时可见该备注信息,UTF8编码,最多允许32个字符**/
|
||||||
|
@SerializedName("transfer_remark")
|
||||||
|
private String transferRemark;
|
||||||
|
|
||||||
|
/**【通知地址】 异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的URL,必须为HTTPS,不能携带参数**/
|
||||||
|
@SerializedName("notify_url")
|
||||||
|
private String notifyUrl;
|
||||||
|
|
||||||
|
/**【用户收款感知】 用户收款时感知到的收款原因将根据转账场景自动展示默认内容。如有其他展示需求,可在本字段传入。各场景展示的默认内容和支持传入的内容,可查看产品文档了解**/
|
||||||
|
@SerializedName("user_recv_perception")
|
||||||
|
private String userRecvPerception;
|
||||||
|
|
||||||
|
@SerializedName("transfer_scene_report_infos")
|
||||||
|
private List<TransferSceneReportInfo> transferSceneReportInfos;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class TransferSceneReportInfo{
|
||||||
|
/**【信息类型】 不能超过15个字符,商户所属转账场景下的信息类型,此字段内容为固定值,需严格按照转账场景报备信息字段说明传参。**/
|
||||||
|
@SerializedName("info_type")
|
||||||
|
private String infoType;
|
||||||
|
/**【信息内容】 不能超过32个字符,商户所属转账场景下的信息内容,商户可按实际业务场景自定义传参,需严格按照转账场景报备信息字段说明传参。**/
|
||||||
|
@SerializedName("info_content")
|
||||||
|
private String infoContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TransferBillsResponse {
|
||||||
|
/**
|
||||||
|
* 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
||||||
|
**/
|
||||||
|
@SerializedName("out_bill_no")
|
||||||
|
private String outBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识
|
||||||
|
**/
|
||||||
|
@SerializedName("transfer_bill_no")
|
||||||
|
private String transferBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【单据创建时间】 单据受理成功时返回,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE
|
||||||
|
**/
|
||||||
|
@SerializedName("create_time")
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【单据状态】 商家转账订单状态
|
||||||
|
**/
|
||||||
|
@SerializedName("state")
|
||||||
|
private StateEnum state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【失败原因】 订单已失败或者已退资金时,会返回订单失败原因
|
||||||
|
**/
|
||||||
|
@SerializedName("fail_reason")
|
||||||
|
private String failReason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【跳转领取页面的package信息】 跳转微信支付收款页的package信息,APP调起用户确认收款或者JSAPI调起用户确认收款 时需要使用的参数。
|
||||||
|
* 单据创建后,用户24小时内不领取将过期关闭,建议拉起用户确认收款页面前,先查单据状态:如单据状态为待收款用户确认,可用之前的package信息拉起;单据到终态时需更换单号重新发起转账。
|
||||||
|
*/
|
||||||
|
@SerializedName("package_info")
|
||||||
|
private String packageInfo;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tradeState
|
||||||
|
*/
|
||||||
|
public static enum StateEnum {
|
||||||
|
/**
|
||||||
|
* 转账已受理
|
||||||
|
*/
|
||||||
|
@SerializedName("ACCEPTED")
|
||||||
|
ACCEPTED,
|
||||||
|
/**
|
||||||
|
* 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试
|
||||||
|
*/
|
||||||
|
@SerializedName("PROCESSING")
|
||||||
|
PROCESSING,
|
||||||
|
/**
|
||||||
|
* 待收款用户确认,可拉起微信收款确认页面进行收款确认
|
||||||
|
*/
|
||||||
|
@SerializedName("WAIT_USER_CONFIRM")
|
||||||
|
WAIT_USER_CONFIRM,
|
||||||
|
/**
|
||||||
|
* 转账中,可拉起微信收款确认页面再次重试确认收款
|
||||||
|
*/
|
||||||
|
@SerializedName("TRANSFERING")
|
||||||
|
TRANSFERING,
|
||||||
|
/**
|
||||||
|
* 转账成功
|
||||||
|
*/
|
||||||
|
@SerializedName("SUCCESS")
|
||||||
|
SUCCESS,
|
||||||
|
/**
|
||||||
|
* 转账已受理
|
||||||
|
*/
|
||||||
|
@SerializedName("FAIL")
|
||||||
|
FAIL,
|
||||||
|
/**
|
||||||
|
* 转账已受理
|
||||||
|
*/
|
||||||
|
@SerializedName("CANCELLED")
|
||||||
|
CANCELLED,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户支付回调
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TransferNotification {
|
||||||
|
/**
|
||||||
|
* 【商户单号】商户系统内部的商家单号,在商户系统内部唯一
|
||||||
|
*/
|
||||||
|
@SerializedName("out_bill_no")
|
||||||
|
private String outBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【商家转账订单号】微信单号,微信商家转账系统返回的唯一标识
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_bill_no")
|
||||||
|
private String transferBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【单据状态】微信单号,微信商家转账系统返回的唯一标识
|
||||||
|
*/
|
||||||
|
@SerializedName("state")
|
||||||
|
private TransferState state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【商户号】微信支付分配的商户号
|
||||||
|
*/
|
||||||
|
@SerializedName("mch_id")
|
||||||
|
private String mchId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【转账金额】转账总金额,单位为“分”
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_amount")
|
||||||
|
private String transferAmount ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【收款用户OpenID】用户在商户appid下的唯一标识
|
||||||
|
*/
|
||||||
|
@SerializedName("openid")
|
||||||
|
private String openid ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【失败原因】单已失败或者已退资金时,会返回订单失败原因
|
||||||
|
*/
|
||||||
|
@SerializedName("fail_reason")
|
||||||
|
private String failReason ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【单据创建时间】遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
|
||||||
|
*/
|
||||||
|
@SerializedName("create_time")
|
||||||
|
private String createTime ;
|
||||||
|
/**
|
||||||
|
* 【最后一次状态变更时间】遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
|
||||||
|
*/
|
||||||
|
@SerializedName("update_time")
|
||||||
|
private String updateTime;
|
||||||
|
|
||||||
|
public static enum TransferState {
|
||||||
|
@SerializedName("ACCEPTED")
|
||||||
|
ACCEPTED,
|
||||||
|
@SerializedName("PROCESSING")
|
||||||
|
PROCESSING,
|
||||||
|
@SerializedName("WAIT_USER_CONFIRM")
|
||||||
|
WAIT_USER_CONFIRM,
|
||||||
|
@SerializedName("TRANSFERING")
|
||||||
|
TRANSFERING,
|
||||||
|
@SerializedName("SUCCESS")
|
||||||
|
SUCCESS,
|
||||||
|
@SerializedName("FAIL")
|
||||||
|
FAIL,
|
||||||
|
@SerializedName("CANCELING")
|
||||||
|
CANCELING,
|
||||||
|
@SerializedName("CANCELLED")
|
||||||
|
CANCELLED
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package org.jiayunet.wxPay.server.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TransferResultResponse {
|
||||||
|
/**
|
||||||
|
* 微信支付分配的商户号
|
||||||
|
**/
|
||||||
|
@SerializedName("mch_id")
|
||||||
|
private String mchId;
|
||||||
|
/**
|
||||||
|
* 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
||||||
|
*/
|
||||||
|
@SerializedName("out_bill_no")
|
||||||
|
private String outBillNo;
|
||||||
|
/**
|
||||||
|
* 【商家转账订单号】 商家转账订单的主键,唯一定义此资源的标识
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_bill_no")
|
||||||
|
private String transferBillNo;
|
||||||
|
/**
|
||||||
|
* 【商户AppID】 是微信开放平台和微信公众平台为开发者的应用程序(APP、小程序、公众号、企业号corpid即为此AppID)提供的一个唯一标识。此处,可以填写这四种类型中的任意一种APPID,但请确保该appid与商户号有绑定关系。详见:普通商户模式开发必要参数说明。
|
||||||
|
*/
|
||||||
|
@SerializedName("appid")
|
||||||
|
private String appid;
|
||||||
|
/**
|
||||||
|
* 【单据状态】
|
||||||
|
*/
|
||||||
|
@SerializedName("state")
|
||||||
|
private StateEnum state;
|
||||||
|
/**
|
||||||
|
* 【转账金额】 转账金额单位为“分”
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_amount")
|
||||||
|
private Integer transferAmount;
|
||||||
|
/**
|
||||||
|
* 【转账备注】 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
|
||||||
|
*/
|
||||||
|
@SerializedName("transfer_remark")
|
||||||
|
private String transferRemark;
|
||||||
|
/**
|
||||||
|
* 【失败原因】 订单已失败或者已退资金时,会返回订单失败原因
|
||||||
|
*/
|
||||||
|
@SerializedName("fail_reason")
|
||||||
|
private String failReason;
|
||||||
|
/**
|
||||||
|
* 【收款用户OpenID】 用户在商户appid下的唯一标识。发起转账前需获取到用户的OpenID,获取方式详见参数说明。
|
||||||
|
*/
|
||||||
|
@SerializedName("openid")
|
||||||
|
private String openid;
|
||||||
|
/**
|
||||||
|
* 【收款用户姓名】 收款方真实姓名。支持标准RSA算法和国密算法,公钥由微信侧提供转账金额 >= 2,000元时,该笔明细必须填写若商户传入收款用户姓名,微信支付会校验用户OpenID与姓名是否一致,并提供电子回单
|
||||||
|
*/
|
||||||
|
@SerializedName("user_name")
|
||||||
|
private String userName;
|
||||||
|
/**
|
||||||
|
* 【单据创建时间】 单据受理成功时返回,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE
|
||||||
|
*/
|
||||||
|
@SerializedName("create_time")
|
||||||
|
private String createTime;
|
||||||
|
/**
|
||||||
|
* 【最后一次状态变更时间】 单据最后更新时间,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE
|
||||||
|
*/
|
||||||
|
@SerializedName("update_time")
|
||||||
|
private String update_time;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tradeState
|
||||||
|
*/
|
||||||
|
public enum StateEnum {
|
||||||
|
/**
|
||||||
|
* 转账已受理
|
||||||
|
*/
|
||||||
|
@SerializedName("ACCEPTED")
|
||||||
|
ACCEPTED,
|
||||||
|
/**
|
||||||
|
* 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试
|
||||||
|
*/
|
||||||
|
@SerializedName("PROCESSING")
|
||||||
|
PROCESSING,
|
||||||
|
/**
|
||||||
|
* 待收款用户确认,可拉起微信收款确认页面进行收款确认
|
||||||
|
*/
|
||||||
|
@SerializedName("WAIT_USER_CONFIRM")
|
||||||
|
WAIT_USER_CONFIRM,
|
||||||
|
/**
|
||||||
|
* 转账中,可拉起微信收款确认页面再次重试确认收款
|
||||||
|
*/
|
||||||
|
@SerializedName("TRANSFERING")
|
||||||
|
TRANSFERING,
|
||||||
|
/**
|
||||||
|
* 转账成功
|
||||||
|
*/
|
||||||
|
@SerializedName("SUCCESS")
|
||||||
|
SUCCESS,
|
||||||
|
/**
|
||||||
|
* 转账已受理
|
||||||
|
*/
|
||||||
|
@SerializedName("FAIL")
|
||||||
|
FAIL,
|
||||||
|
/**
|
||||||
|
* 转账已受理
|
||||||
|
*/
|
||||||
|
@SerializedName("CANCELLED")
|
||||||
|
CANCELLED,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<!-- 公共属性 -->
|
||||||
|
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%X{userId}] [%thread] %-5level %logger{36} - %msg%n"/>
|
||||||
|
<property name="CONSOLE_LOG_PATTERN" value="%cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) %magenta([%X{traceId}]) %blue([%X{userId}]) %green([%thread]) %highlight(%-5level) %yellow(%logger{36}) - %msg%n"/>
|
||||||
|
<property name="LOG_PATH" value="logs"/>
|
||||||
|
<property name="LOG_FILE" value="${LOG_PATH}/application.log"/>
|
||||||
|
|
||||||
|
<!-- 控制台输出(带颜色) -->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 本地(local)环境,仅使用控制台日志 -->
|
||||||
|
<springProfile name="local">
|
||||||
|
<logger name="SQL_LOGGER" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
</logger>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 非本地环境,输出到文件和控制台 -->
|
||||||
|
<springProfile name="!local">
|
||||||
|
<!-- 文件输出 -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_FILE}</file>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/application-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>14</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- SQL日志单独文件 -->
|
||||||
|
<appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/sql.log</file>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/sql-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>14</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="SQL_LOGGER" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="SQL_FILE"/>
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jiayunet</groupId>
|
||||||
|
<artifactId>back_end</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>manager</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jiayunet</groupId>
|
||||||
|
<artifactId>common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.jiayunet.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oss桶
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum OssBucketEnum {
|
||||||
|
|
||||||
|
|
||||||
|
HuiDongNi("oss-cn-guangzhou.aliyuncs.com", "jiayupet", 1,"https://jiayupet.oss-cn-guangzhou.aliyuncs.com");
|
||||||
|
/**
|
||||||
|
* 服务端口
|
||||||
|
*/
|
||||||
|
private final String endpoint;
|
||||||
|
/**
|
||||||
|
* 桶名
|
||||||
|
*/
|
||||||
|
private final String bucketName;
|
||||||
|
/**
|
||||||
|
* 权限 0 私有 1公共读 2 公共读写
|
||||||
|
*/
|
||||||
|
private final int power;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private final String domain;
|
||||||
|
|
||||||
|
OssBucketEnum(String endpoint, String bucketName, int power, String domain) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.bucketName = bucketName;
|
||||||
|
this.power = power;
|
||||||
|
this.domain = domain;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.jiayunet.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oss 文件路径
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum OssPathEnum {
|
||||||
|
UserImage("image/user", "用户图片"),
|
||||||
|
ResumeImage("image/resume", "简历图片"),
|
||||||
|
SystemImage("image/system", "系统图片"),
|
||||||
|
ResumeFile("file/resume", "简历文件"),
|
||||||
|
|
||||||
|
;
|
||||||
|
/**
|
||||||
|
* 文件存放目录
|
||||||
|
*/
|
||||||
|
private final String catalogue;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private final String comment;
|
||||||
|
|
||||||
|
OssPathEnum(String catalogue, String comment) {
|
||||||
|
this.catalogue = catalogue;
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件路径
|
||||||
|
*
|
||||||
|
* @param fileName 文件全名
|
||||||
|
* @return oss 文件存放路径
|
||||||
|
*/
|
||||||
|
public String getPath(String fileName) {
|
||||||
|
Assert.hasText(fileName, "获取oss文件路径文件名不能为空");
|
||||||
|
String name = UUID.randomUUID().toString().replace("-", "").substring(0, 9);
|
||||||
|
String fileType = fileName.substring(fileName.lastIndexOf("."));
|
||||||
|
return catalogue + "/" + name + fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.jiayunet.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.jiayunet.sms.VerifyCodeAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum SmsVerifyCodeEnum implements VerifyCodeAttribute {
|
||||||
|
/**
|
||||||
|
* 通用验证码
|
||||||
|
*/
|
||||||
|
UNIVERSAL("加鱼宠物", "SMS_480245067", "universal:code:sms:", 5, false);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板签名
|
||||||
|
*/
|
||||||
|
private final String signName;
|
||||||
|
/**
|
||||||
|
* 模板code
|
||||||
|
*/
|
||||||
|
private final String templateCode;
|
||||||
|
/**
|
||||||
|
* redis 名
|
||||||
|
*/
|
||||||
|
private final String redisKeyPre;
|
||||||
|
/**
|
||||||
|
* 有效时间 单位分钟
|
||||||
|
*/
|
||||||
|
private final Integer effectiveTime;
|
||||||
|
/**
|
||||||
|
* 覆盖已存在的redis值
|
||||||
|
*/
|
||||||
|
private final Boolean cover;
|
||||||
|
|
||||||
|
|
||||||
|
public String getRedisKey(String phone) {
|
||||||
|
Assert.hasText(phone, "手机号码不能为空");
|
||||||
|
return this.redisKeyPre + phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.jiayunet.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/public/actuator")
|
||||||
|
public class HealthCheckController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查
|
||||||
|
*/
|
||||||
|
@RequestMapping("/health")
|
||||||
|
public String healthCheck() {
|
||||||
|
return "Service is running";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.jiayunet.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.jiayunet.constant.OssPathEnum;
|
||||||
|
import org.jiayunet.pojo.vo.OssUrlVo;
|
||||||
|
import org.jiayunet.server.OssServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oss控制类
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/oss")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Validated
|
||||||
|
public class OssController {
|
||||||
|
private OssServer ossServer;
|
||||||
|
|
||||||
|
@GetMapping("/requestUploadUrl")
|
||||||
|
public OssUrlVo requestUploadUrl(@RequestParam String fileName, @RequestParam OssPathEnum pathEnum){
|
||||||
|
return ossServer.requestUploadUrl(fileName,pathEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.jiayunet.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.jiayunet.pojo.po.OssFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface OssFileMapper extends CommonMapper<OssFile>{
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.jiayunet.mapper;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.jiayunet.pojo.po.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* userMapper控制类
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper extends CommonMapper<User> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.jiayunet.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oss文件存储
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName("bg_oss_file")
|
||||||
|
public class OssFile {
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 桶名
|
||||||
|
*/
|
||||||
|
private String bucketName;
|
||||||
|
/**
|
||||||
|
* 文件类型
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
/**
|
||||||
|
*文件路径
|
||||||
|
*/
|
||||||
|
private String path;
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
*/
|
||||||
|
private String fileName;
|
||||||
|
/**
|
||||||
|
* 上传用户
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
/**
|
||||||
|
* 状态 0 正常 1删除
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Instant createTime;
|
||||||
|
/**
|
||||||
|
* 删除时间
|
||||||
|
*/
|
||||||
|
private Instant deleteTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.jiayunet.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户基本信息类
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "bg_user")
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
private String mobileNumber;
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
/**
|
||||||
|
* 登陆密码
|
||||||
|
*/
|
||||||
|
private String loginPassword;
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nick;
|
||||||
|
/**
|
||||||
|
* 真实名字
|
||||||
|
*/
|
||||||
|
private String realName;
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String picture;
|
||||||
|
/**
|
||||||
|
* 生日
|
||||||
|
*/
|
||||||
|
private Instant birthday;
|
||||||
|
/**
|
||||||
|
* 性别 1男 2女
|
||||||
|
*/
|
||||||
|
private Integer sex;
|
||||||
|
/**
|
||||||
|
* 微信头像
|
||||||
|
*/
|
||||||
|
private String wechatAvatar;
|
||||||
|
/**
|
||||||
|
* 学员的微信昵称
|
||||||
|
*/
|
||||||
|
private String wechatNick;
|
||||||
|
/**
|
||||||
|
* 微信openid
|
||||||
|
*/
|
||||||
|
private String wechatOpenid;
|
||||||
|
/**
|
||||||
|
* 微信UnionId
|
||||||
|
*/
|
||||||
|
private String wechatUnionId;
|
||||||
|
/**
|
||||||
|
* 状态 0 正常 1 禁用
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加时间
|
||||||
|
*/
|
||||||
|
private Instant createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改时间
|
||||||
|
*/
|
||||||
|
private Instant updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标识 0正常 非0 删除
|
||||||
|
*/
|
||||||
|
@TableLogic(value = "0", delval = "new()")
|
||||||
|
private Long isDelete;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.jiayunet.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oss 文件路径
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OssUrlVo {
|
||||||
|
/**
|
||||||
|
* 上传地址
|
||||||
|
*/
|
||||||
|
private String uploadUrl;
|
||||||
|
/**
|
||||||
|
* 下载地址
|
||||||
|
*/
|
||||||
|
private String downloadUrl;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package org.jiayunet.server;
|
||||||
|
|
||||||
|
import com.aliyun.oss.HttpMethod;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.jiayunet.constant.OssBucketEnum;
|
||||||
|
import org.jiayunet.constant.OssPathEnum;
|
||||||
|
import org.jiayunet.exception.BusinessException;
|
||||||
|
import org.jiayunet.exception.BusinessExpCodeEnum;
|
||||||
|
import org.jiayunet.mapper.OssFileMapper;
|
||||||
|
import org.jiayunet.oss.AliOssAbility;
|
||||||
|
import org.jiayunet.pojo.po.OssFile;
|
||||||
|
import org.jiayunet.pojo.vo.OssUrlVo;
|
||||||
|
import org.jiayunet.tool.UserSecurityTool;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oss 服务类
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class OssServer {
|
||||||
|
|
||||||
|
|
||||||
|
private AliOssAbility aliOssAbility;
|
||||||
|
private OssFileMapper ossFileMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申请上传文件签名授权地址
|
||||||
|
*
|
||||||
|
* @return 地址
|
||||||
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public OssUrlVo requestUploadUrl(String fileName, OssPathEnum pathEnum) {
|
||||||
|
Assert.notNull(pathEnum, " 上传路径不能为空");
|
||||||
|
Assert.hasText(fileName, "文件不能为空");
|
||||||
|
Assert.isTrue(fileName.lastIndexOf(".") >= 0, "文件名必须包含类型");
|
||||||
|
|
||||||
|
OssBucketEnum jiaYuPet = OssBucketEnum.HuiDongNi;
|
||||||
|
Long userId = UserSecurityTool.getUserId();
|
||||||
|
String type = fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||||
|
String path = pathEnum.getPath(fileName);
|
||||||
|
|
||||||
|
|
||||||
|
OssUrlVo ossUrlVo = new OssUrlVo();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//保存上传信息
|
||||||
|
ossFileMapper.insert(new OssFile(null, jiaYuPet.getBucketName(), type, path, fileName, userId, 0, Instant.now(), null));
|
||||||
|
|
||||||
|
//获取上传Url
|
||||||
|
String uploadUrl = aliOssAbility.signatureUrl(jiaYuPet.getEndpoint(), jiaYuPet.getBucketName(), path, 60, HttpMethod.PUT);
|
||||||
|
|
||||||
|
//组装下载地址
|
||||||
|
String downloadUrl = jiaYuPet.getDomain() + "/" + path;
|
||||||
|
|
||||||
|
ossUrlVo.setUploadUrl(uploadUrl);
|
||||||
|
ossUrlVo.setDownloadUrl(downloadUrl);
|
||||||
|
|
||||||
|
return ossUrlVo;
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OssUrlVo simpleUpload(String fileName, OssPathEnum pathEnum, InputStream inputStream) {
|
||||||
|
Assert.notNull(pathEnum, " 上传路径不能为空");
|
||||||
|
Assert.hasText(fileName, "文件不能为空");
|
||||||
|
Assert.isTrue(fileName.lastIndexOf(".") >= 0, "文件名必须包含类型");
|
||||||
|
|
||||||
|
OssBucketEnum jiaYuPet = OssBucketEnum.HuiDongNi;
|
||||||
|
Long userId = UserSecurityTool.getUserId();
|
||||||
|
String type = fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||||
|
String path = pathEnum.getPath(fileName);
|
||||||
|
|
||||||
|
|
||||||
|
OssUrlVo ossUrlVo = new OssUrlVo();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//保存上传信息
|
||||||
|
ossFileMapper.insert(new OssFile(null, jiaYuPet.getBucketName(), type, path, fileName, userId, 0, Instant.now(), null));
|
||||||
|
|
||||||
|
aliOssAbility.simpleUpload(jiaYuPet.getEndpoint(), jiaYuPet.getBucketName(), path, inputStream);
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
|
||||||
|
//组装下载地址
|
||||||
|
String downloadUrl = jiaYuPet.getDomain() + "/" + path;
|
||||||
|
|
||||||
|
ossUrlVo.setUploadUrl(downloadUrl);
|
||||||
|
ossUrlVo.setDownloadUrl(downloadUrl);
|
||||||
|
return ossUrlVo;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<!--项目父pom-->
|
||||||
|
<!--约束版本 提供参数选择-->
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.jiayunet</groupId>
|
||||||
|
<artifactId>back_end</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>common</module>
|
||||||
|
<module>client-api</module>
|
||||||
|
<module>manager</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<spring-boot.version>2.6.13</spring-boot.version>
|
||||||
|
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
||||||
|
<java-jwt.version>3.11.0</java-jwt.version>
|
||||||
|
<aliyun.ssm.version>2.0.24</aliyun.ssm.version>
|
||||||
|
<aliyun.oss.version>3.17.4</aliyun.oss.version>
|
||||||
|
<dubbo.version>2.7.15</dubbo.version>
|
||||||
|
<mail.version>1.6.2</mail.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
|
||||||
|
<!--springboot相关版本限制-->
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<type>pom</type> <!--pom:Maven 项目描述文件,指定了项目的元数据和依赖关系,但不包含实际的代码-->
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
<version>${java-jwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>dysmsapi20170525</artifactId>
|
||||||
|
<version>${aliyun.ssm.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.oss</groupId>
|
||||||
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
|
<version>${aliyun.oss.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
<version>${mail.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||||
|
<!-- 镜像配置 -->
|
||||||
|
<mirrors>
|
||||||
|
<!-- 阿里云镜像 -->
|
||||||
|
<mirror>
|
||||||
|
<id>mirror</id>
|
||||||
|
<mirrorOf>central</mirrorOf>
|
||||||
|
<name>mirror</name>
|
||||||
|
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
|
||||||
|
</mirror>
|
||||||
|
</mirrors>
|
||||||
|
</settings>
|
||||||
Reference in New Issue
Block a user