近几年为了解决一些不同项目的业务问题陆续开发了很多工具类。但是每次新建项目都要把这些工具类拽来拽去特别麻烦。 所以搞了一个工具包,提交到了中央仓库。每次新建项目的时候只需要引入依赖即可。
Common Boot Starter
小于 1 分钟
近几年为了解决一些不同项目的业务问题陆续开发了很多工具类。但是每次新建项目都要把这些工具类拽来拽去特别麻烦。 所以搞了一个工具包,提交到了中央仓库。每次新建项目的时候只需要引入依赖即可。
//SpEL 解析器
SpelExpressionParser parser = new SpelExpressionParser();
//上下文数据
User user = new User();
user.setId(1);
user.setName("张三");
user.setCreateTime(LocalDate.now());
user.setAge(18);
user.setBalance(BigDecimal.TEN);
StandardEvaluationContext context = new StandardEvaluationContext(user);
//数值计算
System.out.println(parser.parseExpression("1+1").getValue()); //2
System.out.println(parser.parseExpression("age*(age+2)").getValue(context)); //360
System.out.println(parser.parseExpression("balance*2").getValue(context)); //20
System.out.println(parser.parseExpression("balance/2").getValue(context)); //5
System.out.println(parser.parseExpression("balance-3").getValue(context)); //7
System.out.println(parser.parseExpression("balance^2").getValue(context)); //100
//字符串拼接
System.out.println(parser.parseExpression("'name: '+name").getValue(context)); //name: 张三
//Map
System.out.println(parser.parseExpression("{name: name}").getValue(context)); //{name=张三}
//方法调用
System.out.println(parser.parseExpression("pow(balance, 4)").getValue(context)); //10000
//逻辑运算
System.out.println(parser.parseExpression("balance > 20").getValue(context)); //false
System.out.println(parser.parseExpression("balance == 20 or balance == 10").getValue(context)); //true
System.out.println(parser.parseExpression("balance == 10 and balance < age").getValue(context)); //true
package cn.doblue.assetsana.utils.data;
import java.security.SecureRandom;
/**
* 随机工具类
*/
public class RandomUtil {
public static String ALPHANUMERIC = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm";
private final static SecureRandom random = new SecureRandom();
/**
* 生成随机字母数字
* @param length 长度
* @return 随机字母字符串
*/
public static String randomAlphanumeric(int length){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(ALPHANUMERIC.charAt(random.nextInt(ALPHANUMERIC.length())));
}
return sb.toString();
}
/**
* 随机 0 - size 的整数
*/
public static Integer randomInt(int size) {
return random.nextInt(size);
}
}
最近在写一个开发工具包,实现启动自动创建代理 bean 的方法,看了一下 OpenFeign 的源码。之前写的方法也可以实现之前的文章。 这里介绍 OpenFeign 的实现方法:
package cn.linkot.boot.meow.annocation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Cat {
String value() default "";
}
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
上周客户在导入数据的时候发现导入的表格(含有计算公式)结果的值有些为空,查后台日志发现是导入失败,Exception: IllegalStateException: value changed。
将表格拿到开发环境测试成功复现问题,打断点查看 StackTrace 找到如下代码:
public class EvaluationCache {
//...........
public PlainValueCellCacheEntry getPlainValueEntry(int bookIndex, int sheetIndex,
int rowIndex, int columnIndex, ValueEval value) {
Loc loc = new Loc(bookIndex, sheetIndex, rowIndex, columnIndex);
PlainValueCellCacheEntry result = _plainCellCache.get(loc);
if (result == null) {
result = new PlainValueCellCacheEntry(value);
_plainCellCache.put(loc, result);
if (_evaluationListener != null) {
_evaluationListener.onReadPlainValue(sheetIndex, rowIndex, columnIndex, result);
}
} else {
// TODO - if we are confident that this sanity check is not required, we can remove 'value' from plain value cache entry
if (!areValuesEqual(result.getValue(), value)) {
throw new IllegalStateException("value changed"); //在这里报错!
}
if (_evaluationListener != null) {
_evaluationListener.onCacheHit(sheetIndex, rowIndex, columnIndex, value);
}
}
return result;
}
//................
}
在 Spring Web MVC 项目中通常我们可以直接使用 ThreadLocal 对值进行保存,同一次请求内多次获取都能拿到值。 但是在 Web Flux 中一个请求可能经过不同线程,所以无法使用这种方法。可以使用 Mono 对象的 context 功能来替代。
Spring Boot 版本
2.6.3
使用 Mono 的 contextWrite 方法保存值。
public final Mono<T> contextWrite(Function<Context, Context> contextModifier) {
return onAssembly(new MonoContextWrite(this, contextModifier));
}
使用 GlobalFilter 过滤所有请求,用 ModifyRequestBodyGatewayFilterFactory 生成的 filter 通过 RewriteFunction 修改请求体。
@Component
@Order(10)
public class RequestBodyModifyFilter implements GlobalFilter{
@Resource
private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return reqBodyRewriter().filter(exchange, chain);
}
/**
* 修改请求体的filter
*/
@Bean
private GatewayFilter reqBodyRewriter(){
return modifyRequestBodyGatewayFilterFactory.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(
String.class, String.class, rewriteFunction()
));
}
/**
* 此处是对原请求体的修改方法
*/
private RewriteFunction<String, String> rewriteFunction(){
return (serverWebExchange, s) -> {
//对请求体进行操作、s为原请求体
return Mono.just("修改后的结果");
};
}
}
children(子集) 的 get、set 方法
import java.util.List;
public interface Tree<T> {
List<T> getChildren();
void setChildren(List<T> children);
}
@Slf4j
public class MinioService {
private final MinioConfig config;
private MinioClient client;
private final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
public MinioService(com.doblue.smlt.config.MinioConfig config) {
this.config = config;
try{
client = MinioClient.builder()
.endpoint(config.getHost())
.credentials(config.getAccessKey(), config.getSecretKey())
.build();
}catch (Exception ex){
log.error("Minio 初始化失败!Trace:");
ex.printStackTrace();
}
}
/**
* 文件上传
* @param file 文件
* @param path minio 远程路径
* @return 公网访问 url
*/
public String upload(MultipartFile file, String path){
try{
return upload(file, path, file.getContentType());
}catch (Exception ex){
log.error("文件上传失败!Trace:");
ex.printStackTrace();
throw new UnknownException(ex.getMessage());
}
}
/**
* 文件上传
* @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));
PutObjectArgs args = PutObjectArgs.builder()
.contentType(contentType)
.object(filename)
.bucket(config.getBucket())
.stream(file.getInputStream(), file.getSize(), ObjectWriteArgs.MIN_MULTIPART_SIZE)
.build();
client.putObject(args);
return minioPublicURL(filename);
}
/**
* 根据完整 URL 来移除 minio 服务器中的对象
* @param url 例:http://127.0.0.1:9000/furniture/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png
*/
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.getMessage());
e.printStackTrace();
throw new UnknownException(e.getMessage());
}
}
/**
* 获取对象文件公网访问 url
* @param filename 对象名称
* @return 公网访问 url
*/
private String minioPublicURL(String filename) {
return config.getHost() + '/' + config.getBucket() + "/" + filename;
}
/**
* 生成对象路径
* @param basicPath 根路径
* @param extension 文件扩展名
* @return basicPath/yyyyMMdd/uuid.extension
*/
public String genPath(String basicPath, String extension){
return basicPath + "/" + format.format(new Date()) + "/" + UUID.randomUUID() + extension;
}
/**
* 获取文件的扩展名
* @param file 文件
* @return 例:.jpg
*/
public String getExtension(MultipartFile file){
return getExtension(file.getOriginalFilename());
}
/**
* 获取文件的扩展名
* @param name 文件名称
* @return 例:.jpg
*/
public String getExtension(String name){
return name.substring(name.lastIndexOf('.'));
}
/**
* 递归移除目录下所有文件
* @param folder 目录
*/
public void removeFolder(String folder){
try{
if (!folder.endsWith("/")){
folder+="/";
}
Iterable<Result<Item>> results = client.listObjects(
ListObjectsArgs.builder()
.bucket(config.getBucket())
.prefix(folder)
.build());
for (Result<Item> r : results) {
Item item = r.get();
if (item.isDir()){
removeFolder(item.objectName());
}else {
removeObject(item.objectName());
}
}
}catch (Exception ex){
log.error("文件上传失败:");
ex.printStackTrace();
throw new UnknownException(ex.getMessage());
}
}
/**
* 下载文件,用于受保护的 minio 服务器
* 此方法未测试
*/
public GetObjectResponse download(String object){
GetObjectArgs args = GetObjectArgs.builder()
.bucket(config.getBucket())
.object(object)
.build();
try{
return client.getObject(args);
}catch (Exception ex){
log.error("MinIO 文件下载错误:");
ex.printStackTrace();
throw new UnknownException(ex.getMessage());
}
}
/**
* 根据url下载文件
* 此方法未测试
*/
public GetObjectResponse downloadByUrl(String url){
return download(extractObjectName(url));
}
/**
* 提取 url 中的 minio 对象路径
* @param url 例:http://127.0.0.1:9000/furniture/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png
* @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 e){
log.error("minIO 文件拷贝失败:");
e.printStackTrace();
throw new UnknownException(e.getMessage());
}
return minioPublicURL(targetObject);
}
/**
* 通过 url 拷贝
* @see MinioService#copy(String, String)
*/
public String copyByUrl(String url, String path){
return copy(extractObjectName(url), path);
}
}