initial commit
This commit is contained in:
18
public-common/pom.xml
Normal file
18
public-common/pom.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.yelink.example</groupId>
|
||||
<artifactId>rainyhon-xzt</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>public-common</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>yelink-redis-starter</module>
|
||||
<module>yelink-security-starter</module>
|
||||
<module>yelink-minio-starter</module>
|
||||
<module>yelink-doc-starter</module>
|
||||
</modules>
|
||||
</project>
|
||||
39
public-common/yelink-doc-starter/pom.xml
Normal file
39
public-common/yelink-doc-starter/pom.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<groupId>com.yelink.example</groupId>
|
||||
<artifactId>public-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>yelink-doc-starter</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.yelink.doc.configure;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.yelink.doc.properties.DocProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DocAutoconfigure.
|
||||
* @author cwp
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@EnableConfigurationProperties(DocProperties.class)
|
||||
@ConditionalOnProperty(value = "doc.enable", havingValue = "true", matchIfMissing = true)
|
||||
public class DocAutoconfigure {
|
||||
|
||||
private final DocProperties properties;
|
||||
|
||||
public DocAutoconfigure(DocProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(-1)
|
||||
public Docket groupRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(groupApiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
|
||||
.paths(PathSelectors.any())
|
||||
.build().securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists.<SecurityScheme>newArrayList(apiKey()));
|
||||
}
|
||||
|
||||
private ApiInfo groupApiInfo() {
|
||||
String description = String.format("<div style='font-size:%spx;color:%s;'>%s</div>",
|
||||
properties.getDescriptionFontSize(), properties.getDescriptionColor(), properties.getDescription());
|
||||
|
||||
Contact contact = new Contact(properties.getName(), properties.getUrl(), properties.getEmail());
|
||||
|
||||
return new ApiInfoBuilder()
|
||||
.title(properties.getTitle())
|
||||
.description(description)
|
||||
.termsOfServiceUrl(properties.getTermsOfServiceUrl())
|
||||
.contact(contact)
|
||||
.license(properties.getLicense())
|
||||
.licenseUrl(properties.getLicenseUrl())
|
||||
.version(properties.getVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiKey apiKey() {
|
||||
return new ApiKey("BearerToken", "Authorization", "header");
|
||||
}
|
||||
|
||||
private SecurityContext securityContext() {
|
||||
return SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.forPaths(PathSelectors.regex("/.*"))
|
||||
.build();
|
||||
}
|
||||
|
||||
List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||
authorizationScopes[0] = authorizationScope;
|
||||
return Lists.newArrayList(new SecurityReference("BearerToken", authorizationScopes));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.yelink.doc.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* DocProperties.
|
||||
*
|
||||
* @author cwp
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "doc")
|
||||
@Data
|
||||
public class DocProperties {
|
||||
|
||||
/**
|
||||
* 是否开启doc功能.
|
||||
*/
|
||||
private Boolean enable = true;
|
||||
|
||||
/**
|
||||
* 接口扫描路径,如Controller路径.
|
||||
*/
|
||||
private String basePackage;
|
||||
|
||||
/**
|
||||
* 文档标题.
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 文档描述.
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 文档描述颜色.
|
||||
*/
|
||||
private String descriptionColor = "#42b983";
|
||||
|
||||
/**
|
||||
* 文档描述字体大小.
|
||||
*/
|
||||
private String descriptionFontSize = "14";
|
||||
|
||||
/**
|
||||
* 服务url.
|
||||
*/
|
||||
private String termsOfServiceUrl;
|
||||
|
||||
/**
|
||||
* 联系方式:姓名.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系方式:个人网站url.
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 联系方式:邮箱.
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 协议.
|
||||
*/
|
||||
private String license;
|
||||
|
||||
/**
|
||||
* 协议地址.
|
||||
*/
|
||||
private String licenseUrl;
|
||||
|
||||
/**
|
||||
* 版本.
|
||||
*/
|
||||
private String version;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.yelink.doc.configure.DocAutoconfigure
|
||||
45
public-common/yelink-minio-starter/pom.xml
Normal file
45
public-common/yelink-minio-starter/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.yelink.example</groupId>
|
||||
<artifactId>public-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>yelink-minio-starter</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.yelink.minio.configure;
|
||||
|
||||
import com.yelink.minio.properties.MinioConfig;
|
||||
import com.yelink.minio.service.MinioService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MinioAutoconfigure.
|
||||
*
|
||||
* @author cwp
|
||||
* @date 2024-05-16 17:45
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({MinioConfig.class})
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MinioAutoconfigure {
|
||||
|
||||
@Bean
|
||||
public MinioService minioService(MinioConfig minioConfig) {
|
||||
return new MinioService(minioConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yelink.minio.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* MinioConfig.
|
||||
* @author cwp
|
||||
* @date 2024-05-16 17:19
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "minio")
|
||||
@Data
|
||||
public class MinioConfig {
|
||||
|
||||
private String endpoint;
|
||||
|
||||
private String accessKey;
|
||||
|
||||
private String secretKey;
|
||||
|
||||
private String bucket;
|
||||
|
||||
private String applicationName;
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package com.yelink.minio.service;
|
||||
|
||||
import com.yelink.minio.properties.MinioConfig;
|
||||
import io.minio.CopyObjectArgs;
|
||||
import io.minio.CopySource;
|
||||
import io.minio.GetObjectArgs;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import io.minio.RemoveObjectArgs;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* MinioService.
|
||||
*
|
||||
* @author cwp
|
||||
* @date 2024-05-16 17:33
|
||||
*/
|
||||
@Slf4j
|
||||
public class MinioService {
|
||||
|
||||
private static final Map<String, String> MIME_TYPES = new HashMap<>();
|
||||
|
||||
static {
|
||||
MIME_TYPES.put("txt", "text/plain");
|
||||
MIME_TYPES.put("html", "text/html");
|
||||
MIME_TYPES.put("css", "text/css");
|
||||
MIME_TYPES.put("js", "application/javascript");
|
||||
MIME_TYPES.put("json", "application/json");
|
||||
MIME_TYPES.put("xml", "application/xml");
|
||||
MIME_TYPES.put("jpg", "image/jpeg");
|
||||
MIME_TYPES.put("jpeg", "image/jpeg");
|
||||
MIME_TYPES.put("png", "image/png");
|
||||
MIME_TYPES.put("gif", "image/gif");
|
||||
MIME_TYPES.put("bmp", "image/bmp");
|
||||
MIME_TYPES.put("pdf", "application/pdf");
|
||||
// 添加更多扩展名和 Content-Type 对应关系
|
||||
}
|
||||
|
||||
private final MinioConfig config;
|
||||
|
||||
private final SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
||||
|
||||
private MinioClient client;
|
||||
|
||||
public MinioService(MinioConfig config) {
|
||||
this.config = config;
|
||||
try {
|
||||
client = MinioClient.builder().endpoint(config.getEndpoint())
|
||||
.credentials(config.getAccessKey(), config.getSecretKey()).build();
|
||||
} catch (Exception ex) {
|
||||
log.error("Minio 初始化失败!Trace:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件扩展名获取 Content-Type 不使用MultipartFile.getContentType(),是因为它基于客户端传输的content_type,不一定准确.
|
||||
*
|
||||
* @param fileName 文件名,包含后缀
|
||||
* @return MIME 类型
|
||||
*/
|
||||
private String getContentType(String fileName) {
|
||||
String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
|
||||
return MIME_TYPES.getOrDefault(extension, "application/octet-stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传.
|
||||
*
|
||||
* @param file 文件
|
||||
* @param path minio 远程路径
|
||||
* @return 公网访问 url
|
||||
*/
|
||||
public String upload(MultipartFile file, String path) throws Exception {
|
||||
String contentType;
|
||||
if (ObjectUtils.isEmpty(file.getOriginalFilename())) {
|
||||
contentType = file.getContentType();
|
||||
} else {
|
||||
contentType = getContentType(file.getOriginalFilename());
|
||||
}
|
||||
return upload(file, path, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传.
|
||||
*
|
||||
* @param file 文件
|
||||
* @param path minio 远程路径
|
||||
* @param contentType 文件类型
|
||||
* @return 公网访问 url
|
||||
*/
|
||||
public String upload(MultipartFile file, String path, String contentType) throws Exception {
|
||||
String filename = genPath(path, getExtension(file));
|
||||
return upload(file.getInputStream(), filename, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传.
|
||||
*
|
||||
* @param content 文件内容
|
||||
* @param fileExtension 后缀名
|
||||
* @return 内网访问 url
|
||||
*/
|
||||
public String upload(String content, String fileExtension) throws Exception {
|
||||
// 转换字符串为输入流
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
|
||||
// 构建对象名称
|
||||
String objectName = config.getApplicationName() + "/" + format.format(new Date()) + "." + fileExtension;
|
||||
return upload(inputStream, objectName, "text/plain");
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传.
|
||||
*
|
||||
* @param inputStream inputStream
|
||||
* @param objectName 文件名
|
||||
* @param contentType 文件类型
|
||||
* @return 内网访问 url
|
||||
* @throws Exception e
|
||||
*/
|
||||
public String upload(InputStream inputStream, String objectName, String contentType) throws Exception {
|
||||
try {
|
||||
PutObjectArgs args = PutObjectArgs.builder().contentType(contentType).object(objectName)
|
||||
.bucket(config.getBucket()).stream(inputStream, inputStream.available(), -1).build();
|
||||
client.putObject(args);
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
return config.getBucket() + "/" + objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据完整 URL 来移除 minio 服务器中的对象.
|
||||
*
|
||||
* @param url 例:<a href="http://127.0.0.1:9000/furniture/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png">...</a>
|
||||
*/
|
||||
public void removeObjectByUrl(String url) {
|
||||
removeObject(extractObjectName(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据对象名移除 minio 服务器中的对象.
|
||||
*
|
||||
* @param name 例:materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png
|
||||
*/
|
||||
public void removeObject(String name) {
|
||||
try {
|
||||
RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(config.getBucket()).object(name).build();
|
||||
client.removeObject(args);
|
||||
} catch (Exception e) {
|
||||
log.error("MinIO 文件删除错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象文件公网访问 url.
|
||||
*
|
||||
* @param filename 对象名称
|
||||
* @return 公网访问 url
|
||||
*/
|
||||
public String minioPublicUrl(String filename) {
|
||||
return config.getBucket() + "/" + filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成对象路径.
|
||||
*
|
||||
* @param basicPath 根路径
|
||||
* @param extension 文件扩展名
|
||||
* @return basicPath/yyyyMMdd/uuid.extension
|
||||
*/
|
||||
public String genPath(String basicPath, String extension) {
|
||||
return "iot/" + basicPath + extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的扩展名.
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 例:.jpg
|
||||
*/
|
||||
public String getExtension(MultipartFile file) {
|
||||
return getExtension(Objects.requireNonNull(file.getOriginalFilename()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的扩展名.
|
||||
*
|
||||
* @param name 文件名称
|
||||
* @return 例:.jpg
|
||||
*/
|
||||
public String getExtension(String name) {
|
||||
return name.substring(name.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件,用于受保护的 minio 服务器 此方法未测试.
|
||||
*/
|
||||
public String download(String object) {
|
||||
GetObjectArgs args = GetObjectArgs.builder().bucket(config.getBucket()).object(object).build();
|
||||
try {
|
||||
// 读取对象内容
|
||||
try (InputStream inputStream = client.getObject(args)) {
|
||||
// Read the input stream and print to the console till EOF.
|
||||
byte[] buf = new byte[16384];
|
||||
int bytesRead;
|
||||
StringBuilder content = new StringBuilder();
|
||||
while ((bytesRead = inputStream.read(buf, 0, buf.length)) >= 0) {
|
||||
String readContent = new String(buf, 0, bytesRead, StandardCharsets.UTF_8);
|
||||
content.append(readContent);
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("MinIO 文件下载错误:", ex);
|
||||
throw new RuntimeException(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据url下载文件 此方法未测试.
|
||||
*/
|
||||
public String downloadByUrl(String url) {
|
||||
return download(extractObjectName(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取 url 中的 minio 对象路径.
|
||||
*
|
||||
* @param url 例:<a href="http://127.0.0.1:9000/furniture/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png">...</a>
|
||||
* @return 例:/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png
|
||||
*/
|
||||
public String extractObjectName(String url) {
|
||||
return url.substring(minioPublicUrl("").length());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个对象拷贝到指定路径.
|
||||
*
|
||||
* @param objectName minio 中需要拷贝的对象
|
||||
* @param path minio 要拷贝到的路径
|
||||
* @return 拷贝后路径的公网访问 url
|
||||
*/
|
||||
public String copy(String objectName, String path) {
|
||||
String extension = getExtension(objectName);
|
||||
String targetObject = genPath(path, extension);
|
||||
CopyObjectArgs args = CopyObjectArgs.builder().bucket(config.getBucket()).object(targetObject)
|
||||
.source(CopySource.builder().bucket(config.getBucket()).object(objectName).build()).build();
|
||||
try {
|
||||
client.copyObject(args);
|
||||
} catch (Exception ex) {
|
||||
log.error("minIO 文件拷贝失败:", ex);
|
||||
throw new RuntimeException(ex.getMessage());
|
||||
}
|
||||
return minioPublicUrl(targetObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 url 拷贝.
|
||||
*
|
||||
* @see MinioService#copy(String, String)
|
||||
*/
|
||||
public String copyByUrl(String url, String path) {
|
||||
return copy(extractObjectName(url), path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.yelink.minio.configure.MinioAutoconfigure
|
||||
59
public-common/yelink-redis-starter/pom.xml
Normal file
59
public-common/yelink-redis-starter/pom.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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>com.yelink.example</groupId>
|
||||
<artifactId>public-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>yelink-redis-starter</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yelink.common.redis.annotation;
|
||||
|
||||
import com.yelink.common.redis.configure.RedisAutoConfigure;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* EnableYtLettuceRedis.
|
||||
* @author cwp
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(RedisAutoConfigure.class)
|
||||
public @interface EnableYtLettuceRedis {
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.yelink.common.redis.configure;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.yelink.common.redis.service.RedisService;
|
||||
import io.micrometer.core.instrument.util.StringUtils;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Lettuce Redis配置.
|
||||
*
|
||||
* @author cwp
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisAutoConfigure {
|
||||
|
||||
@Bean(name = "redisTemplate")
|
||||
@ConditionalOnClass(RedisOperations.class)
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
jackson2JsonRedisSerializer.setObjectMapper(mapper);
|
||||
|
||||
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
|
||||
template.setKeySerializer(stringRedisSerializer);
|
||||
template.setHashKeySerializer(stringRedisSerializer);
|
||||
template.setValueSerializer(jackson2JsonRedisSerializer);
|
||||
template.setHashValueSerializer(jackson2JsonRedisSerializer);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(name = "redisTemplate")
|
||||
public RedisService redisService(RedisTemplate<String, Object> redisTemplate) {
|
||||
return new RedisService(redisTemplate);
|
||||
}
|
||||
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public RedissonClient redissonSingle(RedisProperties redisProperties) {
|
||||
Config config = new Config();
|
||||
SingleServerConfig singleServerConfig = config.useSingleServer()
|
||||
.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
|
||||
.setDatabase(redisProperties.getDatabase())
|
||||
.setConnectionMinimumIdleSize(10)
|
||||
.setConnectionPoolSize(10);
|
||||
if (StringUtils.isNotEmpty(redisProperties.getPassword())) {
|
||||
singleServerConfig.setPassword(redisProperties.getPassword());
|
||||
}
|
||||
return Redisson.create(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package com.yelink.common.redis.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 定义常用的 Redis操作.
|
||||
*
|
||||
* @author cwp
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RedisService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 指定缓存失效时间.
|
||||
*
|
||||
* @param key 键
|
||||
* @param time 时间(秒)
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean expire(String key, Long time) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key获取过期时间.
|
||||
*
|
||||
* @param key 键 不能为 null
|
||||
* @return 时间(秒) 返回 0代表为永久有效
|
||||
*/
|
||||
public Long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 key是否存在.
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public Boolean hasKey(String key) {
|
||||
try {
|
||||
return redisTemplate.hasKey(key);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存.
|
||||
*
|
||||
* @param key 可以传一个值 或多个
|
||||
*/
|
||||
public void del(String... key) {
|
||||
if (key != null && key.length > 0) {
|
||||
if (key.length == 1) {
|
||||
redisTemplate.delete(key[0]);
|
||||
} else {
|
||||
redisTemplate.delete(Arrays.asList(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存获取.
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return key == null ? null : redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true成功 false失败
|
||||
*/
|
||||
public Boolean set(String key, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入并设置时间.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
|
||||
* @return true成功 false 失败
|
||||
*/
|
||||
public Boolean set(String key, Object value, Long time) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
|
||||
} else {
|
||||
set(key, value);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增.
|
||||
*
|
||||
* @param key 键
|
||||
* @param delta 要增加几(大于0)
|
||||
* @return Long
|
||||
*/
|
||||
public Long incr(String key, Long delta) {
|
||||
if (delta < 0) {
|
||||
throw new RuntimeException("递增因子必须大于0");
|
||||
}
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递减.
|
||||
*
|
||||
* @param key 键
|
||||
* @param delta 要减少几
|
||||
* @return Long
|
||||
*/
|
||||
public Long decr(String key, Long delta) {
|
||||
if (delta < 0) {
|
||||
throw new RuntimeException("递减因子必须大于0");
|
||||
}
|
||||
return redisTemplate.opsForValue().increment(key, -delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* HashGet.
|
||||
*
|
||||
* @param key 键 不能为 null
|
||||
* @param item 项 不能为 null
|
||||
* @return 值
|
||||
*/
|
||||
public Object hget(String key, String item) {
|
||||
return redisTemplate.opsForHash().get(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 hashKey对应的所有键值.
|
||||
*
|
||||
* @param key 键
|
||||
* @return 对应的多个键值
|
||||
*/
|
||||
public Map<Object, Object> hmget(String key) {
|
||||
return redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* HashSet.
|
||||
*
|
||||
* @param key 键
|
||||
* @param map 对应多个键值
|
||||
* @return true 成功 false 失败
|
||||
*/
|
||||
public Boolean hmset(String key, Map<String, Object> map) {
|
||||
try {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HashSet 并设置时间.
|
||||
*
|
||||
* @param key 键
|
||||
* @param map 对应多个键值
|
||||
* @param time 时间(秒)
|
||||
* @return true成功 false失败
|
||||
*/
|
||||
public Boolean hmset(String key, Map<String, Object> map, Long time) {
|
||||
try {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向一张hash表中放入数据,如果不存在将创建.
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param value 值
|
||||
* @return true 成功 false失败
|
||||
*/
|
||||
public Boolean hset(String key, String item, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForHash().put(key, item, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向一张hash表中放入数据,如果不存在将创建.
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param value 值
|
||||
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
|
||||
* @return true 成功 false失败
|
||||
*/
|
||||
public Boolean hset(String key, String item, Object value, Long time) {
|
||||
try {
|
||||
redisTemplate.opsForHash().put(key, item, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除hash表中的值.
|
||||
*
|
||||
* @param key 键 不能为 null
|
||||
* @param item 项 可以使多个不能为 null
|
||||
*/
|
||||
public void hdel(String key, Object... item) {
|
||||
redisTemplate.opsForHash().delete(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断hash表中是否有该项的值.
|
||||
*
|
||||
* @param key 键 不能为 null
|
||||
* @param item 项 不能为 null
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public Boolean hHasKey(String key, String item) {
|
||||
return redisTemplate.opsForHash().hasKey(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* hash递增 如果不存在,就会创建一个 并把新增后的值返回.
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param by 要增加几(大于0)
|
||||
* @return Double
|
||||
*/
|
||||
public Double hincr(String key, String item, Double by) {
|
||||
return redisTemplate.opsForHash().increment(key, item, by);
|
||||
}
|
||||
|
||||
/**
|
||||
* hash递减.
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param by 要减少记(小于0)
|
||||
* @return Double
|
||||
*/
|
||||
public Double hdecr(String key, String item, Double by) {
|
||||
return redisTemplate.opsForHash().increment(key, item, -by);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 key获取 Set中的所有值.
|
||||
*
|
||||
* @param key 键
|
||||
* @return Set
|
||||
*/
|
||||
public Set<Object> sGet(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据value从一个set中查询,是否存在.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public Boolean sHasKey(String key, Object value) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().isMember(key, value);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据放入set缓存.
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值 可以是多个
|
||||
* @return 成功个数
|
||||
*/
|
||||
public Long sSet(String key, Object... values) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().add(key, values);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将set数据放入缓存..
|
||||
*
|
||||
* @param key 键
|
||||
* @param time 时间(秒)
|
||||
* @param values 值 可以是多个
|
||||
* @return 成功个数
|
||||
*/
|
||||
public Long sSetAndTime(String key, Long time, Object... values) {
|
||||
try {
|
||||
Long count = redisTemplate.opsForSet().add(key, values);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return count;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取set缓存的长度.
|
||||
*
|
||||
* @param key 键
|
||||
* @return Long
|
||||
*/
|
||||
public Long sGetSetSize(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().size(key);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除值为value的.
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值 可以是多个
|
||||
* @return 移除的个数
|
||||
*/
|
||||
public Long setRemove(String key, Object... values) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().remove(key, values);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取list缓存的内容.
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始
|
||||
* @param end 结束 0 到 -1代表所有值
|
||||
* @return List
|
||||
*/
|
||||
public List<Object> lGet(String key, Long start, Long end) {
|
||||
try {
|
||||
return redisTemplate.opsForList().range(key, start, end);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取list缓存的长度.
|
||||
*
|
||||
* @param key 键
|
||||
* @return Long
|
||||
*/
|
||||
public Long lGetListSize(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForList().size(key);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过索引 获取list中的值.
|
||||
*
|
||||
* @param key 键
|
||||
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推; index<0时,-1,表尾,-2倒数第二个元素,依次类推
|
||||
* @return Object
|
||||
*/
|
||||
public Object lGetIndex(String key, Long index) {
|
||||
try {
|
||||
return redisTemplate.opsForList().index(key, index);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean lSet(String key, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒)
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean lSet(String key, Object value, Long time) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean lSet(String key, List<Object> value) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPushAll(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存.
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒)
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean lSet(String key, List<Object> value, Long time) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPushAll(key, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据索引修改list中的某条数据.
|
||||
*
|
||||
* @param key 键
|
||||
* @param index 索引
|
||||
* @param value 值
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean lUpdateIndex(String key, Long index, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForList().set(key, index, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除N个值为value.
|
||||
*
|
||||
* @param key 键
|
||||
* @param count 移除多少个
|
||||
* @param value 值
|
||||
* @return 移除的个数
|
||||
*/
|
||||
public Long lRemove(String key, Long count, Object value) {
|
||||
try {
|
||||
return redisTemplate.opsForList().remove(key, count, value);
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.yelink.common.redis.configure.RedisAutoConfigure
|
||||
63
public-common/yelink-security-starter/pom.xml
Normal file
63
public-common/yelink-security-starter/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<groupId>com.yelink.example</groupId>
|
||||
<artifactId>public-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>yelink-security-starter</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-oauth2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yelink.security.annotation;
|
||||
|
||||
import com.yelink.security.configure.AppResourceServerConfigure;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* 启用该模块,需要再启动类增加@AppEnableResourceServer.
|
||||
* @author cwp
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(AppResourceServerConfigure.class)
|
||||
public @interface AppEnableResourceServer {
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.yelink.security.configure;
|
||||
|
||||
import com.yelink.security.expression.WebSecurityExpressionHandler;
|
||||
import com.yelink.security.handler.AccessDeniedHandler;
|
||||
import com.yelink.security.handler.AuthExceptionEntryPoint;
|
||||
import com.yelink.security.properties.EdgeSecurityProperties;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
|
||||
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
*/
|
||||
@KeycloakConfiguration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
|
||||
@EnableConfigurationProperties({EdgeSecurityProperties.class})
|
||||
@EnableAutoConfiguration(exclude = UserDetailsServiceAutoConfiguration.class)
|
||||
public class AppResourceServerConfigure extends KeycloakWebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private EdgeSecurityProperties properties;
|
||||
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) {
|
||||
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
|
||||
SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
|
||||
simpleAuthorityMapper.setPrefix("");
|
||||
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(simpleAuthorityMapper);
|
||||
auth.authenticationProvider(keycloakAuthenticationProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
|
||||
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected SessionRegistry buildSessionRegistry() {
|
||||
return new SessionRegistryImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KeycloakConfigResolver KeycloakConfigResolver() {
|
||||
return new KeycloakSpringBootConfigResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
super.configure(http);
|
||||
|
||||
// 若enable为false,则所有请求都允许访问
|
||||
if (!properties.getEnable()) {
|
||||
http.cors().and()
|
||||
.csrf().disable()
|
||||
.authorizeRequests()
|
||||
.anyRequest().permitAll();
|
||||
return;
|
||||
}
|
||||
|
||||
String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(properties.getAnonUris(), ",");
|
||||
if (ArrayUtils.isEmpty(anonUrls)) {
|
||||
anonUrls = new String[]{};
|
||||
}
|
||||
http
|
||||
.cors().and()
|
||||
.csrf().disable()
|
||||
.antMatcher("/**").authorizeRequests()
|
||||
.antMatchers(anonUrls).permitAll()
|
||||
.antMatchers("/**").access("hasAkAuth()")
|
||||
.anyRequest().authenticated();
|
||||
|
||||
http.authorizeRequests().expressionHandler(new WebSecurityExpressionHandler());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "accessDeniedHandler")
|
||||
public AccessDeniedHandler accessDeniedHandler() {
|
||||
return new AccessDeniedHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "authenticationEntryPoint")
|
||||
public AuthExceptionEntryPoint authenticationEntryPoint() {
|
||||
return new AuthExceptionEntryPoint();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yelink.security.expression;
|
||||
|
||||
import org.springframework.security.access.expression.SecurityExpressionOperations;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
|
||||
public class WebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {
|
||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
private String defaultRolePrefix = "ROLE_";
|
||||
|
||||
@Override
|
||||
protected SecurityExpressionOperations createSecurityExpressionRoot(
|
||||
Authentication authentication, FilterInvocation fi) {
|
||||
WebSecurityHeaderExpression root = new WebSecurityHeaderExpression(authentication, fi);
|
||||
root.setPermissionEvaluator(getPermissionEvaluator());
|
||||
root.setTrustResolver(trustResolver);
|
||||
root.setRoleHierarchy(getRoleHierarchy());
|
||||
root.setDefaultRolePrefix(this.defaultRolePrefix);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.yelink.security.expression;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.expression.WebSecurityExpressionRoot;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
*/
|
||||
public class WebSecurityHeaderExpression extends WebSecurityExpressionRoot {
|
||||
private Authentication a;
|
||||
|
||||
private static final String HEADER_ACCESS_KEY_ID = "AccessKeyId";
|
||||
private static final String HEADER_GATEWAY_TOKEN = "GatewayToken";
|
||||
private static final String GATEWAY_TOKEN_VALUE = "yt:gateway:123456";
|
||||
|
||||
public WebSecurityHeaderExpression(Authentication a, FilterInvocation fi) {
|
||||
super(a, fi);
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public boolean hasAkAuth() {
|
||||
boolean hasAk = request.getHeader(HEADER_ACCESS_KEY_ID) != null ||
|
||||
null != request.getHeader(HEADER_ACCESS_KEY_ID.toLowerCase());
|
||||
|
||||
boolean hasAccessGateway = false;
|
||||
String headerGatewayToken = request.getHeader(HEADER_GATEWAY_TOKEN);
|
||||
if (StringUtils.isNotEmpty(headerGatewayToken)) {
|
||||
String gatewayToken = new String(Base64Utils.encode(GATEWAY_TOKEN_VALUE.getBytes()));
|
||||
hasAccessGateway = StringUtils.equals(gatewayToken, headerGatewayToken);
|
||||
}
|
||||
|
||||
return hasAuth() || (hasAk && hasAccessGateway);
|
||||
}
|
||||
|
||||
private boolean hasAuth() {
|
||||
return !"anonymousUser".equals(a.getPrincipal()) && a.isAuthenticated();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.yelink.security.handler;
|
||||
|
||||
|
||||
import com.yelink.security.utils.ResponseUtil;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
*/
|
||||
public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
|
||||
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("code", 10407);
|
||||
error.put("message", "没有权限访问该资源");
|
||||
|
||||
ResponseUtil.makeResponse(response, MediaType.APPLICATION_JSON_VALUE,
|
||||
HttpServletResponse.SC_FORBIDDEN, error);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.yelink.security.handler;
|
||||
|
||||
|
||||
import com.yelink.security.utils.ResponseUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
int status = HttpServletResponse.SC_UNAUTHORIZED;
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("code", 10007);
|
||||
error.put("message", "无效的token");
|
||||
ResponseUtil.makeResponse(response, MediaType.APPLICATION_JSON_VALUE, status, error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.yelink.security.properties;
|
||||
|
||||
import com.yelink.security.utils.EndpointConstant;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "app.security")
|
||||
@Data
|
||||
public class EdgeSecurityProperties {
|
||||
|
||||
/**
|
||||
* 是否开启安全配置
|
||||
*/
|
||||
private Boolean enable = true;
|
||||
/**
|
||||
* 配置需要认证的uri,默认为所有/**
|
||||
*/
|
||||
private String authUri = EndpointConstant.ALL;
|
||||
/**
|
||||
* 免认证资源路径,支持通配符
|
||||
* 多个值时使用逗号分隔
|
||||
*/
|
||||
private String anonUris;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.yelink.security.utils;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
*/
|
||||
public class EndpointConstant {
|
||||
public static final String ALL = "/**";
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.yelink.security.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author cwp
|
||||
* @date 2024-08-21 17:59
|
||||
*/
|
||||
public class ResponseUtil {
|
||||
/**
|
||||
* 设置响应
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param contentType content-type
|
||||
* @param status http状态码
|
||||
* @param value 响应内容
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public static void makeResponse(HttpServletResponse response, String contentType,
|
||||
int status, Object value) throws IOException {
|
||||
response.setContentType(contentType);
|
||||
response.setStatus(status);
|
||||
response.getOutputStream().write(JSON.toJSONString(value).getBytes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.yelink.security.configure.AppResourceServerConfigure
|
||||
Reference in New Issue
Block a user