feat(framework): 添加Redis序列化白名单配置功能

This commit is contained in:
2026-01-24 09:48:18 +08:00
parent a4258a3851
commit 9b61ab35b6
23 changed files with 130 additions and 597 deletions
@@ -1,48 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties;
import cn.odboy.framework.properties.model.CaptchaModel;
import cn.odboy.framework.properties.model.ContentRsaEncodeSettingModel;
import cn.odboy.framework.properties.model.JwtAuthSettingModel;
import cn.odboy.framework.properties.model.StorageOSSModel;
import cn.odboy.framework.properties.model.ThreadPoolSettingModel;
import cn.odboy.framework.properties.model.UserLoginSettingModel;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 应用配置
*
* @author odboy
* @date 2025-04-13
*/
@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private ContentRsaEncodeSettingModel rsa;
private JwtAuthSettingModel jwt;
private UserLoginSettingModel login;
private ThreadPoolSettingModel asyncTaskPool;
private CaptchaModel captcha;
private StorageOSSModel oss;
}
@@ -1,31 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.odboy.base.KitObject;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CaptchaModel extends KitObject {
/**
* 验证码有效时间, 单位: 秒
*/
private Long expireTime;
}
@@ -1,31 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.odboy.base.KitObject;
import lombok.Getter;
import lombok.Setter;
/**
* 密码加密传输, 前端公钥加密,后端私钥解密
*/
@Getter
@Setter
public class ContentRsaEncodeSettingModel extends KitObject {
private String privateKey;
}
@@ -1,49 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.odboy.base.KitObject;
import lombok.Getter;
import lombok.Setter;
/**
* Jwt参数配置
*/
@Getter
@Setter
public class JwtAuthSettingModel extends KitObject {
/**
* 必须使用最少88位的Base64对该令牌进行编码
*/
private String base64Secret;
/**
* 令牌过期时间 此处单位/毫秒
*/
private Long tokenValidityInSeconds;
/**
* token 续期检查
*/
private Long detect;
/**
* 续期时间
*/
private Long renew;
}
@@ -1,43 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.odboy.base.KitObject;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OSSConfigModel extends KitObject {
/**
* 服务地址
*/
private String endpoint;
/**
* 存储桶名称
*/
private String bucketName;
/**
* Access Key
*/
private String accessKey;
/**
* Secret Key
*/
private String secretKey;
}
@@ -1,31 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class StorageOSSModel {
/**
* 上传大小, 单位: M
*/
private Long maxSize;
private OSSConfigModel minio;
}
@@ -1,49 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.odboy.base.KitObject;
import lombok.Getter;
import lombok.Setter;
/**
* 线程池 配置
*
* @author odboy
* @date 2025-04-13
*/
@Getter
@Setter
public class ThreadPoolSettingModel extends KitObject {
/**
* 核心线程池大小
*/
private int corePoolSize;
/**
* 最大线程数
*/
private int maxPoolSize;
/**
* 活跃时间
*/
private int keepAliveSeconds;
/**
* 队列容量
*/
private int queueCapacity;
}
@@ -1,135 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.hutool.core.util.StrUtil;
import cn.odboy.base.KitObject;
import cn.odboy.constant.CaptchaCodeEnum;
import cn.odboy.framework.exception.BadRequestException;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.ChineseCaptcha;
import com.wf.captcha.ChineseGifCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import java.awt.Font;
import lombok.Getter;
import lombok.Setter;
/**
* 验证码配置
*
* @author odboy
*/
@Getter
@Setter
public class UserLoginCaptchaSettingModel extends KitObject {
private CaptchaCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration = 5L;
/**
* 验证码内容长度
*/
private int length = 4;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height = 36;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize = 25;
/**
* 依据配置信息生产验证码
*
* @return /
*/
public Captcha getCaptcha() {
Captcha captcha;
switch (codeType) {
case ARITHMETIC -> {
// 算术类型 https://gitee.com/whvse/EasyCaptcha
captcha = new FixedArithmeticCaptcha(width, height);
// 几位数运算, 默认是两位
captcha.setLen(length);
}
case CHINESE -> {
captcha = new ChineseCaptcha(width, height);
captcha.setLen(length);
}
case CHINESE_GIF -> {
captcha = new ChineseGifCaptcha(width, height);
captcha.setLen(length);
}
case GIF -> {
captcha = new GifCaptcha(width, height);
captcha.setLen(length);
}
case SPEC -> {
captcha = new SpecCaptcha(width, height);
captcha.setLen(length);
}
default -> throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
}
if (StrUtil.isNotBlank(fontName)) {
captcha.setFont(new Font(fontName, Font.PLAIN, fontSize));
}
return captcha;
}
static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
public FixedArithmeticCaptcha(int width, int height) {
super(width, height);
}
@Override
protected char[] alphas() {
// 生成随机数字和运算符
int n1 = num(1, 10), n2 = num(1, 10);
int opt = num(3);
// 计算结果
int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
// 转换为字符运算符
char optChar = "+-x".charAt(opt);
this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
this.chars = String.valueOf(res);
return chars.toCharArray();
}
}
}
@@ -1,37 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.framework.properties.model;
import cn.odboy.base.KitObject;
import lombok.Getter;
import lombok.Setter;
/**
* 登录配置
*
* @author odboy
*/
@Getter
@Setter
public class UserLoginSettingModel extends KitObject {
/**
* 账号单用户 登录
*/
private boolean single = false;
private UserLoginCaptchaSettingModel captchaSetting;
}
@@ -20,15 +20,6 @@ import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnection;
@@ -40,6 +31,15 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Component @Component
public class KitRedisHelper { public class KitRedisHelper {
@@ -280,7 +280,8 @@ public class KitRedisHelper {
if (value == null) { if (value == null) {
return null; return null;
} }
if (value instanceof List<?> list) { if (value instanceof List<?>) {
List<?> list = (List<?>) value;
// 检查每个元素是否为指定类型 // 检查每个元素是否为指定类型
if (list.stream().allMatch(clazz::isInstance)) { if (list.stream().allMatch(clazz::isInstance)) {
return list.stream().map(clazz::cast).collect(Collectors.toList()); return list.stream().map(clazz::cast).collect(Collectors.toList());
@@ -13,22 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.odboy.framework.redis; package cn.odboy.framework.redis;
import cn.odboy.framework.properties.AppProperties;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONFactory; import com.alibaba.fastjson2.JSONFactory;
import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.JSONWriter;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.MurmurHash3; import org.apache.commons.codec.digest.MurmurHash3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.KeyGenerator;
@@ -43,19 +41,19 @@ import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Slf4j @Slf4j
@Configuration @Configuration
@EnableCaching @EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class) @AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfiguration { public class RedisConfiguration extends CachingConfigurerSupport {
/** @Autowired
* 自动识别json对象白名单配置(仅允许解析的包名, 范围越小越安全)<br/> 未配置可能导致, 登录失败, 反复登录等问题 private AppProperties properties;
*/
private static final String[] WHITELIST_STR =
{"org.springframework", "cn.odboy.system.dal.dataobject", "cn.odboy.system.dal.model",
"cn.odboy.task.dal.dataobject", "cn.odboy.task.dal.model",};
/** /**
* 设置 redis 数据默认过期时间,默认2小时 设置@cacheable 序列化方式 * 设置 redis 数据默认过期时间,默认2小时 设置@cacheable 序列化方式
@@ -64,8 +62,7 @@ public class RedisConfiguration {
public RedisCacheConfiguration redisCacheConfiguration() { public RedisCacheConfiguration redisCacheConfiguration() {
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith( configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
.entryTtl(Duration.ofHours(2)); .entryTtl(Duration.ofHours(2));
return configuration; return configuration;
} }
@@ -79,8 +76,9 @@ public class RedisConfiguration {
template.setValueSerializer(fastJsonRedisSerializer); template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer);
// 设置fastJson的序列化白名单 // 设置fastJson的序列化白名单
for (String pack : WHITELIST_STR) { for (String pack : properties.getSerialWhiteList()) {
JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack); JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack);
log.warn("设置fastJson的序列化白名单:{}", pack);
} }
// key的序列化采用StringRedisSerializer // key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer()); template.setKeySerializer(new StringRedisSerializer());
@@ -104,8 +102,8 @@ public class RedisConfiguration {
/** /**
* 自定义缓存key生成策略 * 自定义缓存key生成策略
*/ */
@Bean @Bean
@Override
public KeyGenerator keyGenerator() { public KeyGenerator keyGenerator() {
return (target, method, params) -> { return (target, method, params) -> {
Map<String, Object> container = new HashMap<>(8); Map<String, Object> container = new HashMap<>(8);
@@ -137,7 +135,7 @@ public class RedisConfiguration {
} }
@Override @Override
public void handleCachePutError(@NonNull RuntimeException exception, @NonNull Cache cache, @NonNull Object key, @NonNull Object value) { public void handleCachePutError(@NonNull RuntimeException exception, @NonNull Cache cache, @NonNull Object key, Object value) {
// 处理缓存写入错误 // 处理缓存写入错误
log.error("Cache Put Error: {}", exception.getMessage()); log.error("Cache Put Error: {}", exception.getMessage());
} }
@@ -44,9 +44,9 @@ spring:
data: data:
redis: redis:
database: ${REDIS_DB:1} database: ${REDIS_DB:1}
host: ${REDIS_HOST:127.0.0.1} host: ${REDIS_HOST:124.220.107.161}
port: ${REDIS_PORT:26379} port: ${REDIS_PORT:6379}
password: ${REDIS_PWD:kd123456} password: ${REDIS_PWD:?uWxt,CX2>+#*Ij@-xa+}
# 连接超时时间 # 连接超时时间
timeout: 5000 timeout: 5000
repositories: repositories:
@@ -57,9 +57,9 @@ spring:
db-type: com.alibaba.druid.pool.DruidDataSource db-type: com.alibaba.druid.pool.DruidDataSource
# sql监控 # sql监控
driverClassName: com.p6spy.engine.spy.P6SpyDriver driverClassName: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true url: jdbc:p6spy:mysql://127.0.0.1:3306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
# driverClassName: com.mysql.cj.jdbc.Driver # driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://127.0.0.1:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true # url: jdbc:mysql://127.0.0.1:3306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root username: root
password: kd123456 password: kd123456
# 初始连接数,建议设置为与最小空闲连接数相同 # 初始连接数,建议设置为与最小空闲连接数相同
@@ -197,6 +197,13 @@ app:
secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj
captcha: captcha:
expireTime: 300 expireTime: 300
# 序列化白名单
serial-white-list:
- "org.springframework"
- "cn.odboy.system.dal.dataobject"
- "cn.odboy.system.dal.model"
- "cn.odboy.task.dal.dataobject"
- "cn.odboy.task.dal.model"
# debug # debug
#debug: true #debug: true
# 日志配置 # 日志配置
@@ -206,6 +206,13 @@ app:
secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj
captcha: captcha:
expireTime: 300 expireTime: 300
# 序列化白名单
serial-white-list:
- "org.springframework"
- "cn.odboy.system.dal.dataobject"
- "cn.odboy.system.dal.model"
- "cn.odboy.task.dal.dataobject"
- "cn.odboy.task.dal.model"
# debug # debug
#debug: true #debug: true
# 日志配置 # 日志配置
@@ -2,7 +2,7 @@
<configuration scan="true" scanPeriod="30 seconds" debug="false"> <configuration scan="true" scanPeriod="30 seconds" debug="false">
<contextName>CuteJava</contextName> <contextName>CuteJava</contextName>
<property name="log.name" value="cutejava"/> <property name="log.name" value="cutejava"/>
<property name="log.home" value="/Users/tianjun/Downloads/logs"/> <property name="log.home" value="/var/logs"/>
<property name="log.charset" value="utf-8"/> <property name="log.charset" value="utf-8"/>
<property name="log.pattern" <property name="log.pattern"
value="%red(%contextName) - %red(%d{yyyy-MM-dd HH:mm:ss.SSS}) - %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %msg%n"/> value="%red(%contextName) - %red(%d{yyyy-MM-dd HH:mm:ss.SSS}) - %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %msg%n"/>
@@ -16,19 +16,17 @@
package cn.odboy.util; package cn.odboy.util;
import static cn.odboy.util.KitStringUtil.getWeekDay; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import static cn.odboy.util.KitStringUtil.toCamelCase; import static cn.odboy.util.KitStringUtil.toCamelCase;
import static cn.odboy.util.KitStringUtil.toCapitalizeCamelCase; import static cn.odboy.util.KitStringUtil.toCapitalizeCamelCase;
import static cn.odboy.util.KitStringUtil.toUnderScoreCase; import static cn.odboy.util.KitStringUtil.toUnderScoreCase;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
public class KitStringUtilTest { public class KitStringUtilTest {
@Test @Test
@@ -53,7 +51,7 @@ public class KitStringUtilTest {
@Test @Test
public void testGetWeekDay() { public void testGetWeekDay() {
SimpleDateFormat simpleDateformat = new SimpleDateFormat("E"); SimpleDateFormat simpleDateformat = new SimpleDateFormat("E");
assertEquals(simpleDateformat.format(new Date()), getWeekDay()); assertEquals(simpleDateformat.format(new Date()), KitDateUtil.getWeekDay());
} }
@Test @Test
@@ -1,31 +0,0 @@
/*
* Copyright 2021-2026 Odboy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.util;
/**
* 钉钉 Teambition 实际工时数计算
*/
public class WorkTimeTests {
public static void main(String[] args) {
// 总工作时长(时)
double totalHour = 11;
// 实际工作时长(天),一天8小时工作制
double actualHour = totalHour / 8.0;
System.err.println("actualHour=" + actualHour + " 天");
}
}
@@ -22,6 +22,7 @@ import cn.odboy.framework.properties.model.StorageOSSModel;
import cn.odboy.framework.properties.model.SwaggerApiDocSettingModel; import cn.odboy.framework.properties.model.SwaggerApiDocSettingModel;
import cn.odboy.framework.properties.model.ThreadPoolSettingModel; import cn.odboy.framework.properties.model.ThreadPoolSettingModel;
import cn.odboy.framework.properties.model.UserLoginSettingModel; import cn.odboy.framework.properties.model.UserLoginSettingModel;
import java.util.List;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -44,4 +45,8 @@ public class AppProperties {
private ThreadPoolSettingModel asyncTaskPool; private ThreadPoolSettingModel asyncTaskPool;
private CaptchaModel captcha; private CaptchaModel captcha;
private StorageOSSModel oss; private StorageOSSModel oss;
/**
* 自动识别json对象白名单配置(仅允许解析的包名, 范围越小越安全)<br/> 未配置可能导致, 登录失败, 反复登录等问题
*/
private List<String> serialWhiteList;
} }
@@ -15,6 +15,7 @@
*/ */
package cn.odboy.framework.redis; package cn.odboy.framework.redis;
import cn.odboy.framework.properties.AppProperties;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONFactory; import com.alibaba.fastjson2.JSONFactory;
import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.JSONWriter;
@@ -25,6 +26,7 @@ import java.util.Map;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.MurmurHash3; import org.apache.commons.codec.digest.MurmurHash3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
@@ -50,17 +52,8 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
@AutoConfigureBefore(RedisAutoConfiguration.class) @AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfiguration extends CachingConfigurerSupport { public class RedisConfiguration extends CachingConfigurerSupport {
/** @Autowired
* 自动识别json对象白名单配置(仅允许解析的包名, 范围越小越安全)<br/> 未配置可能导致, 登录失败, 反复登录等问题 private AppProperties properties;
*/
private static final String[] WHITELIST_STR =
{
"org.springframework",
"cn.odboy.system.dal.dataobject",
"cn.odboy.system.dal.model",
"cn.odboy.task.dal.dataobject",
"cn.odboy.task.dal.model",
};
/** /**
* 设置 redis 数据默认过期时间,默认2小时 设置@cacheable 序列化方式 * 设置 redis 数据默认过期时间,默认2小时 设置@cacheable 序列化方式
@@ -83,8 +76,9 @@ public class RedisConfiguration extends CachingConfigurerSupport {
template.setValueSerializer(fastJsonRedisSerializer); template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer);
// 设置fastJson的序列化白名单 // 设置fastJson的序列化白名单
for (String pack : WHITELIST_STR) { for (String pack : properties.getSerialWhiteList()) {
JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack); JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack);
log.warn("设置fastJson的序列化白名单:{}", pack);
} }
// key的序列化采用StringRedisSerializer // key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer()); template.setKeySerializer(new StringRedisSerializer());
+3 -1
View File
@@ -9,13 +9,15 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>cutejava-starter</artifactId> <artifactId>cutejava-starter</artifactId>
<name>启动模块</name> <name>启动模块</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>cn.odboy</groupId> <groupId>cn.odboy</groupId>
<artifactId>cutejava-module-task-v1</artifactId> <artifactId>cutejava-module-system</artifactId>
<version>1.5.2</version> <version>1.5.2</version>
</dependency> </dependency>
</dependencies> </dependencies>
<!-- 打包 --> <!-- 打包 -->
<build> <build>
<!-- 和应用名一致 --> <!-- 和应用名一致 -->
@@ -51,7 +51,7 @@ spring:
db-type: com.alibaba.druid.pool.DruidDataSource db-type: com.alibaba.druid.pool.DruidDataSource
# sql监控 # sql监控
driverClassName: com.p6spy.engine.spy.P6SpyDriver driverClassName: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true url: jdbc:p6spy:mysql://127.0.0.1:3306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
username: root username: root
password: kd123456 password: kd123456
# 初始连接数,建议设置为与最小空闲连接数相同 # 初始连接数,建议设置为与最小空闲连接数相同
@@ -105,9 +105,9 @@ spring:
multi-statement-allow: true multi-statement-allow: true
aop-patterns: "cn.odboy.mapper.*,cn.odboy.service.*,cn.odboy.*.mapper.*,cn.odboy.*.service.*" aop-patterns: "cn.odboy.mapper.*,cn.odboy.service.*,cn.odboy.*.mapper.*,cn.odboy.*.service.*"
redis: redis:
database: ${REDIS_DB:1} database: ${REDIS_DB:10}
host: ${REDIS_HOST:127.0.0.1} host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:26379} port: ${REDIS_PORT:6379}
password: ${REDIS_PWD:kd123456} password: ${REDIS_PWD:kd123456}
# 连接超时时间 # 连接超时时间
timeout: 5000 timeout: 5000
@@ -211,6 +211,13 @@ app:
secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj
captcha: captcha:
expireTime: 300 expireTime: 300
# 序列化白名单
serial-white-list:
- "org.springframework"
- "cn.odboy.system.dal.dataobject"
- "cn.odboy.system.dal.model"
- "cn.odboy.task.dal.dataobject"
- "cn.odboy.task.dal.model"
# debug # debug
#debug: true #debug: true
# 日志配置 # 日志配置
@@ -50,7 +50,7 @@ spring:
druid: druid:
db-type: com.alibaba.druid.pool.DruidDataSource db-type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.100.128:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true url: jdbc:mysql://127.0.0.1:3306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
username: root username: root
password: kd123456 password: kd123456
# 初始连接数,建议设置为与最小空闲连接数相同 # 初始连接数,建议设置为与最小空闲连接数相同
@@ -105,8 +105,8 @@ spring:
aop-patterns: "cn.odboy.mapper.*,cn.odboy.service.*,cn.odboy.*.mapper.*,cn.odboy.*.service.*" aop-patterns: "cn.odboy.mapper.*,cn.odboy.service.*,cn.odboy.*.mapper.*,cn.odboy.*.service.*"
redis: redis:
database: ${REDIS_DB:1} database: ${REDIS_DB:1}
host: ${REDIS_HOST:192.168.100.128} host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:26379} port: ${REDIS_PORT:6379}
password: ${REDIS_PWD:kd123456} password: ${REDIS_PWD:kd123456}
# 连接超时时间 # 连接超时时间
timeout: 5000 timeout: 5000
@@ -210,6 +210,13 @@ app:
secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj
captcha: captcha:
expireTime: 300 expireTime: 300
# 序列化白名单
serial-white-list:
- "org.springframework"
- "cn.odboy.system.dal.dataobject"
- "cn.odboy.system.dal.model"
- "cn.odboy.task.dal.dataobject"
- "cn.odboy.task.dal.model"
# debug # debug
#debug: true #debug: true
# 日志配置 # 日志配置
@@ -18,8 +18,6 @@ package cn.odboy.util;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
@@ -45,12 +43,6 @@ public class KitStringUtilTest {
assertEquals("\u0000_a", KitStringUtil.toUnderScoreCase("\u0000A")); assertEquals("\u0000_a", KitStringUtil.toUnderScoreCase("\u0000A"));
} }
@Test
public void testGetWeekDay() {
SimpleDateFormat simpleDateformat = new SimpleDateFormat("E");
assertEquals(simpleDateformat.format(new Date()), KitStringUtil.getWeekDay());
}
@Test @Test
public void testGetIP() { public void testGetIP() {
Assertions.assertEquals("127.0.0.1", KitBrowserUtil.getIp(new MockHttpServletRequest())); Assertions.assertEquals("127.0.0.1", KitBrowserUtil.getIp(new MockHttpServletRequest()));