初始话
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