mirror of
https://gitee.com/smallc/blade-tool.git
synced 2026-07-12 19:29:45 +08:00
✨ 新增BladeView视图序列化机制,支持按角色动态控制JSON输出字段
This commit is contained in:
parent
80a2844c4a
commit
6fc8c48677
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
* @Bean
|
||||
* public BladeRoleSupplier myRoleSupplier() {
|
||||
* return () -> currentUserHolder.getRole();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BladeRoleSupplier extends Supplier<String> {
|
||||
}
|
||||
|
|
@ -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>
|
||||
* @BladeView(Views.Detail.class)
|
||||
* private String tenantName;
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Controller 标注</h3>
|
||||
* <pre>
|
||||
* // 静态:明确指定视图
|
||||
* @BladeView(Views.Summary.class)
|
||||
* public R<List<UserVO>> list() { ... }
|
||||
*
|
||||
* // 动态:根据用户角色自动解析
|
||||
* @BladeView
|
||||
* public R<UserVO> detail() { ... }
|
||||
*
|
||||
* // 类级别默认(方法级可覆盖)
|
||||
* @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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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. 注册到解析器
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user