新增BladeView视图序列化机制,支持按角色动态控制JSON输出字段

This commit is contained in:
smallchill 2026-04-18 01:19:07 +08:00
parent 80a2844c4a
commit 6fc8c48677
13 changed files with 653 additions and 58 deletions

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.config;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.jackson.BladeRoleSupplier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
/**
* Jackson Views 角色提供者自动装配
* <p>
* 默认通过 {@link SecureUtil#getUserRole()} 获取当前用户角色名
* 用户可自定义 {@link BladeRoleSupplier} Bean 来覆盖
* </p>
*
* @author Chill
*/
@AutoConfiguration
public class BladeViewRoleConfiguration {
/**
* 默认角色名称提供者
* <p>使用 {@link ConditionalOnMissingBean} 允许用户自定义覆盖</p>
*/
@Bean
@ConditionalOnMissingBean
public BladeRoleSupplier roleNameSupplier() {
return SecureUtil::getUserRole;
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.config;
import org.springblade.core.tool.jackson.*;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
/**
* BladeView 视图序列化自动装配
* <p>
* 框架内置常驻加载无需手动开启
* Controller 方法未标注 {@link BladeView} 时不产生任何额外开销
* 所有 Bean 均支持 {@link ConditionalOnMissingBean}可由用户自定义覆盖
* </p>
*
* @author Chill
*/
@AutoConfiguration(after = JacksonConfiguration.class)
public class BladeViewAutoConfiguration {
/**
* 视图解析器
*/
@Bean
@ConditionalOnMissingBean
public BladeViewResolver bladeViewResolver(BladeJacksonProperties properties, ObjectProvider<BladeViewCustomizer> viewCustomizer) {
BladeViewResolver resolver = new BladeViewResolver(properties.getView());
viewCustomizer.orderedStream().forEach(customizer -> customizer.customize(resolver));
return resolver;
}
/**
* 响应拦截器
*/
@Bean
@ConditionalOnMissingBean
public BladeViewResponseAdvice bladeViewResponseAdvice(BladeViewResolver viewResolver, ObjectProvider<BladeRoleSupplier> roleNameSupplier) {
return new BladeViewResponseAdvice(viewResolver, roleNameSupplier.getIfAvailable());
}
}

View File

@ -15,70 +15,35 @@
*/
package org.springblade.core.tool.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.jackson.BladeJacksonProperties;
import org.springblade.core.tool.jackson.BladeJavaTimeModule;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
/**
* Jackson配置类
*
* @author Chill
*/
@AutoConfiguration
@AllArgsConstructor
@AutoConfiguration(before = JacksonAutoConfiguration.class)
@ConditionalOnClass(ObjectMapper.class)
@AutoConfigureBefore(JacksonAutoConfiguration.class)
@EnableConfigurationProperties(BladeJacksonProperties.class)
public class JacksonConfiguration {
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
builder.simpleDateFormat(DateUtil.PATTERN_DATETIME);
//创建ObjectMapper
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
//设置地点为中国
objectMapper.setLocale(Locale.CHINA);
//去掉默认的时间戳格式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置为中国上海时区
objectMapper.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
//序列化时日期的统一格式
objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
//序列化处理
objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
objectMapper.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
objectMapper.findAndRegisterModules();
//失败处理
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//单引号处理
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//反序列化时属性不存在的兼容处理
objectMapper.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//日期格式化
objectMapper.registerModule(new BladeJavaTimeModule());
public ObjectMapper objectMapper() {
//创建默认的ObjectMapper
ObjectMapper objectMapper = JsonUtil.getInstance();
//允许空字符串序列化为null对象
objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
//自定义拓展配置
objectMapper.findAndRegisterModules();
return objectMapper;
}

View File

@ -19,6 +19,9 @@ import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* jackson 配置
*
@ -43,4 +46,28 @@ public class BladeJacksonProperties {
*/
private Boolean supportTextPlain = Boolean.FALSE;
/**
* 视图配置
*/
private View view = new View();
/**
* Jackson Views 视图配置
*
* @author Chill
*/
@Getter
@Setter
public static class View {
/**
* 动态模式的默认视图角色获取不到时降级
* <p>可选: summary / detail / admin / administrator</p>
*/
private String defaultView = "summary";
/**
* 角色 视图映射
*/
private Map<String, String> roleMapping = new LinkedHashMap<>();
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
import java.util.function.Supplier;
/**
* BladeView 角色名提供者
* <p>
* 用于 {@link BladeViewResponseAdvice} 动态模式下按角色解析视图
* 自定义时请实现本接口而非通用的 {@code Supplier<String>}
* 以避免与业务工程中其它 {@code Supplier<String>} Bean 产生歧义
* </p>
*
* <h3>示例</h3>
* <pre>
* &#64;Bean
* public BladeRoleSupplier myRoleSupplier() {
* return () -&gt; currentUserHolder.getRole();
* }
* </pre>
*
* @author Chill
*/
@FunctionalInterface
public interface BladeRoleSupplier extends Supplier<String> {
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
import java.lang.annotation.*;
/**
* BladeX 视图控制注解全场景统一
* <p>
* 用在字段上标记该字段的可见视图层级替代 {@code @JsonView}
* 用在方法/类上控制接口的视图过滤策略
* </p>
*
* <h3>字段标注</h3>
* <pre>
* &#64;BladeView(Views.Detail.class)
* private String tenantName;
* </pre>
*
* <h3>Controller 标注</h3>
* <pre>
* // 静态明确指定视图
* &#64;BladeView(Views.Summary.class)
* public R&lt;List&lt;UserVO&gt;&gt; list() { ... }
*
* // 动态根据用户角色自动解析
* &#64;BladeView
* public R&lt;UserVO&gt; detail() { ... }
*
* // 类级别默认方法级可覆盖
* &#64;BladeView(Views.Admin.class)
* public class AdminController { ... }
* </pre>
*
* @author Chill
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BladeView {
/**
* 视图类型
* <p>
* 字段上指定 Views.Summary / Detail / Admin / Administrator
* 方法上指定具体视图 = 静态模式默认 Auto.class = 动态模式
* </p>
*/
Class<?> value() default Auto.class;
/**
* 动态解析标记仅用于 Controller 方法/
*/
interface Auto {
}
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import java.io.Serial;
/**
* 自定义 Jackson 注解内省器
* <p>
* Jackson 序列化引擎识别字段上的 {@link BladeView} 注解
* 将其等同于标准的 {@code @JsonView} 处理
* </p>
* <p>
* 继承 {@link JacksonAnnotationIntrospector}所有标准 Jackson 注解
* {@code @JsonIgnore}{@code @JsonSerialize}{@code @JsonFormat}
* 通过 super 正常工作
* </p>
*
* @author Chill
*/
public class BladeViewAnnotationIntrospector extends JacksonAnnotationIntrospector {
@Serial
private static final long serialVersionUID = 1L;
@Override
public Class<?>[] findViews(Annotated a) {
// 优先识别 @BladeView仅处理非 Auto 的静态视图标注
BladeView bladeView = _findAnnotation(a, BladeView.class);
if (bladeView != null && bladeView.value() != BladeView.Auto.class) {
return new Class<?>[]{bladeView.value()};
}
// 兼容仍然识别标准 @JsonView
return super.findViews(a);
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
/**
* 视图自定义扩展接口
* <p>
* 用户可通过实现此接口来注册自定义视图层级
* 注册为 Spring Bean 即可生效
* </p>
*
* <h3>示例新增超管以上的超级视图</h3>
* <pre>
* // 1. 定义自定义视图接口
* public interface SuperView extends Views.Administrator {}
*
* // 2. 注册到解析器
* &#64;Bean
* public BladeViewCustomizer myViewCustomizer() {
* return resolver -> resolver.registerView("super", SuperView.class, 4);
* }
*
* // 3. YAML 中映射角色
* // blade.jackson.view.role-mapping.superadmin: super
* </pre>
*
* @author Chill
*/
@FunctionalInterface
public interface BladeViewCustomizer {
/**
* 自定义视图注册
*
* @param resolver 视图解析器
*/
void customize(BladeViewResolver resolver);
}

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 视图解析器 视图名/角色名 视图 Class
* <p>
* 内置四级视图summary(0) detail(1) admin(2) administrator(3)
* 可通过 {@link BladeViewCustomizer} 注册自定义视图层级
* </p>
*
* @author Chill
*/
public class BladeViewResolver {
private final BladeJacksonProperties.View viewProperties;
/**
* 视图名称 视图 Class 映射线程安全
*/
private final Map<String, Class<?>> viewMapping = new ConcurrentHashMap<>();
/**
* 视图名称 优先级映射数字越大优先级越高线程安全
*/
private final Map<String, Integer> priorityMapping = new ConcurrentHashMap<>();
public BladeViewResolver(BladeJacksonProperties.View viewProperties) {
this.viewProperties = viewProperties;
// 注册内置四级视图
registerView("summary", Views.Summary.class, 0);
registerView("list", Views.Summary.class, 0);
registerView("detail", Views.Detail.class, 1);
registerView("admin", Views.Admin.class, 2);
registerView("administrator", Views.Administrator.class, 3);
}
/**
* 注册自定义视图层级
*
* @param name 视图名称不区分大小写
* @param viewClass 视图 Class建议继承 {@link Views} 中的接口
* @param priority 优先级数字越大权限越高内置summary=0, detail=1, admin=2, administrator=3
*/
public void registerView(String name, Class<?> viewClass, int priority) {
viewMapping.put(name.toLowerCase(), viewClass);
priorityMapping.put(name.toLowerCase(), priority);
}
/**
* 视图名称 Class未知名称降级到 Summary 最小权限原则
*
* @param viewName 视图名称
* @return 视图 Class
*/
public Class<?> resolve(String viewName) {
return viewMapping.getOrDefault(viewName.toLowerCase(), Views.Summary.class);
}
/**
* 根据角色名解析视图支持逗号分隔多角色取最高权限
*
* @param roleName 角色名可逗号分隔
* @return 视图 Class
*/
public Class<?> resolveByRole(String roleName) {
if (roleName == null || roleName.isEmpty()) {
return resolve(viewProperties.getDefaultView());
}
Map<String, String> mapping = viewProperties.getRoleMapping();
String bestView = null;
int bestPriority = -1;
for (String role : roleName.split(",")) {
String viewName = mapping.get(role.trim().toLowerCase());
if (viewName != null) {
int priority = priorityMapping.getOrDefault(viewName.toLowerCase(), -1);
if (priority > bestPriority) {
bestPriority = priority;
bestView = viewName;
}
}
}
return resolve(bestView != null ? bestView : viewProperties.getDefaultView());
}
/**
* 获取默认视图
*
* @return 默认视图 Class
*/
public Class<?> getDefaultView() {
return resolve(viewProperties.getDefaultView());
}
}

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
import lombok.RequiredArgsConstructor;
import net.dreamlu.mica.auto.annotation.AutoIgnore;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* {@link BladeView} 响应拦截器
* <p>
* 拦截 Controller 方法/类上的 {@link BladeView} 注解
* 解析视图 Class 后包装为 {@link MappingJacksonValue} 交给 Jackson 处理
* </p>
*
* @author Chill
*/
@AutoIgnore
@ControllerAdvice
@RequiredArgsConstructor
public class BladeViewResponseAdvice implements ResponseBodyAdvice<Object> {
private final BladeViewResolver viewResolver;
@Nullable
private final BladeRoleSupplier roleNameSupplier;
@Override
public boolean supports(@NonNull MethodParameter returnType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(BladeView.class)
|| returnType.getDeclaringClass().isAnnotationPresent(BladeView.class);
}
@Override
public Object beforeBodyWrite(@Nullable Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType selectedContentType,
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response) {
if (body == null) {
return null;
}
// 方法级优先于类级
BladeView annotation = returnType.getMethodAnnotation(BladeView.class);
if (annotation == null) {
annotation = returnType.getDeclaringClass().getAnnotation(BladeView.class);
}
if (annotation == null) {
return body;
}
Class<?> viewClass = resolveViewClass(annotation);
MappingJacksonValue container;
if (body instanceof MappingJacksonValue) {
container = (MappingJacksonValue) body;
} else {
container = new MappingJacksonValue(body);
}
container.setSerializationView(viewClass);
return container;
}
private Class<?> resolveViewClass(BladeView annotation) {
if (annotation.value() != BladeView.Auto.class) {
return annotation.value();
}
if (roleNameSupplier != null) {
return viewResolver.resolveByRole(roleNameSupplier.get());
}
return viewResolver.getDefaultView();
}
}

View File

@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
@ -32,6 +33,7 @@ import org.springframework.lang.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Serial;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.util.*;
@ -949,41 +951,45 @@ public class JsonUtil {
}
public static class JacksonObjectMapper extends ObjectMapper {
@Serial
private static final long serialVersionUID = 4288193147502386170L;
private static final Locale CHINA = Locale.CHINA;
public JacksonObjectMapper(ObjectMapper src) {
super(src);
}
public JacksonObjectMapper() {
super();
// 通过 Builder 设置 Feature
super(JsonMapper.builder()
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION) //默认视图包含
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS) //允许未转义的控制字符
.enable(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER) //允许反斜杠转义任意字符
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES) //允许单引号
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) //允许空字符串反序列化为null对象
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) //日期不序列化为时间戳
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) //序列化空对象不报错
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) //反序列化遇到未知属性不报错
.build());
//设置地点为中国
super.setLocale(CHINA);
//去掉默认的时间戳格式
super.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置为中国上海时区
super.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
//序列化时日期的统一格式
super.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
//序列化处理
super.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
super.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
super.findAndRegisterModules();
//失败处理
super.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
super.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//单引号处理
super.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//反序列化时属性不存在的兼容处理s
super.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//日期格式化
super.registerModule(new BladeJavaTimeModule());
//允许空字符串序列化为null对象
super.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
//视图注解内省器 Jackson 识别 @BladeView
super.setAnnotationIntrospector(new BladeViewAnnotationIntrospector());
//注册模块
super.findAndRegisterModules();
}
@Override
public ObjectMapper copy() {
return super.copy();
return new JacksonObjectMapper(this);
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.tool.jackson;
/**
* BladeX 全局视图层级定义
* <p>
* 四级视图Summary Detail Admin Administrator
* 继承即包含高级视图自动包含所有低级视图的字段
* 未标注 {@link BladeView} 的字段在任何视图下始终输出
* </p>
*
* @author Chill
*/
public final class Views {
private Views() {
}
/**
* 摘要视图 列表下拉搜索
*/
public interface Summary {
}
/**
* 详情视图 详情页个人中心
*/
public interface Detail extends Summary {
}
/**
* 管理视图 普通管理员(admin)
*/
public interface Admin extends Detail {
}
/**
* 超管视图 超级管理员(administrator)
*/
public interface Administrator extends Admin {
}
}

View File

@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.jackson.BladeView;
import org.springblade.core.tool.jackson.Views;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
@ -49,6 +51,7 @@ public class BaseEntity implements Serializable {
/**
* 创建人
*/
@BladeView(Views.Admin.class)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人", hidden = true)
private Long createUser;
@ -56,6 +59,7 @@ public class BaseEntity implements Serializable {
/**
* 创建部门
*/
@BladeView(Views.Admin.class)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建部门", hidden = true)
private Long createDept;
@ -63,6 +67,7 @@ public class BaseEntity implements Serializable {
/**
* 创建时间
*/
@BladeView(Views.Detail.class)
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间", hidden = true)
@ -71,6 +76,7 @@ public class BaseEntity implements Serializable {
/**
* 更新人
*/
@BladeView(Views.Admin.class)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人", hidden = true)
private Long updateUser;
@ -78,6 +84,7 @@ public class BaseEntity implements Serializable {
/**
* 更新时间
*/
@BladeView(Views.Detail.class)
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间", hidden = true)
@ -86,12 +93,14 @@ public class BaseEntity implements Serializable {
/**
* 状态[1:正常]
*/
@BladeView(Views.Detail.class)
@Schema(description = "业务状态", hidden = true)
private Integer status;
/**
* 状态[0:未删除,1:删除]
*/
@BladeView(Views.Admin.class)
@TableLogic
@Schema(description = "是否已删除", hidden = true)
private Integer isDeleted;