解决SpringBoot服务返回数据存在$ref $.data等相关问题

1、场景

​ 在日常的开发中,我们数据接口返回数据使用了FastJson序列化数据,当返回一个数据list时候出现" r e f " " ref" " ref"".data" 等类似乱码一样的数据,当时我比较匪夷所思,我写的代码这么完美,为什么会返回非正常数据数据呢?经过我多方查证,原来是FastJson框架自身的问题。

2、问题原因

​ 使用FastJson的JSONArray类型作为返回数据,当像JSONArray对象中添加JSONObject对象,而JSONObject对象中包含相同的节点数据时,FastJson会防止返回数据栈溢出的问题,自动将JSONArray中相同的节点数据使用引用方式代替,即:{" r e f " : ref": ref":…[0]}

3、解决方案

强大的 FastJson 为我们提供了相关的配置参数来禁用循环引用

方法一:使用配置文件

**
 * FastJson配置
 *
 * @author charles.yao
 * @date 2023/8/8
 **/
@Configuration
public class FastJsonConfiguration {

    @Bean
    public HttpMessageConverters getFastJSONHttpMessageConvert() {
        // 定义一个转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 添加fastjson的配置信息 比如 :是否要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();

        // 修改配置返回内容的过滤
        fastJsonConfig.setSerializerFeatures(
                // 格式化输出
                SerializerFeature.PrettyFormat,
                // 消除循环引用
                SerializerFeature.DisableCircularReferenceDetect,
                // 返回结果保留null值
                SerializerFeature.WriteMapNullValue,
                // 将返回值为null的字符串转变成"",在这里可以自己设置
                SerializerFeature.WriteNullStringAsEmpty,
                // List字段如果为null,输出为[],而非null
                SerializerFeature.WriteNullListAsEmpty
        );

        // 解决 SerializerFeature.WriteNullStringAsEmpty 不生效问题
        ValueFilter valueFilter = (object, name, value) -> {
            if (null == value){
                value = "";
            }
            return value;
        };

        // 设置全局日期格式
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");

        // 注入过滤器
        fastJsonConfig.setSerializeFilters(valueFilter);

        // Long、BigDecimal 序列化时转 String
        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
        serializeConfig.put(Long.class, ToStringSerializer.instance);
        serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
        serializeConfig.put(BigDecimal.class, ToStringSerializer.instance);
        // 在转换器中添加配置信息
        fastJsonConfig.setSerializeConfig(serializeConfig);
        fastConverter.setDefaultCharset(StandardCharsets.UTF_8);
        fastConverter.setFastJsonConfig(fastJsonConfig);

        // 解决中文乱码问题,相当于在Controller上的@RequestMapping中加了个属性produces = "application/json"
        List<MediaType> mediaTypeList = new ArrayList<>();
        mediaTypeList.add(MediaType.APPLICATION_JSON);
        fastConverter.setSupportedMediaTypes(mediaTypeList);

        return new HttpMessageConverters(fastConverter);
    }
}

所有可选配置属性:

  • SerializerFeature.PrettyFormat:格式化输出
  • SerializerFeature.WriteMapNullValue:是否输出值为null的字段,默认为false
  • SerializerFeature.DisableCircularReferenceDetect:消除循环引用
  • SerializerFeature.WriteNullStringAsEmpty:将为null的字段值显示为""
  • WriteNullListAsEmpty:List字段如果为null,输出为[],而非null
  • WriteNullNumberAsZero:数值字段如果为null,输出为0,而非null
  • WriteNullBooleanAsFalse:Boolean字段如果为null,输出为false,而非null
  • SkipTransientField:如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true
  • SortField:按字段名称排序后输出。默认为false
  • WriteDateUseDateFormat:全局修改日期格式,默认为false。JSON.DEFFAULT_DATE_FORMAT = “yyyy-MM-dd”;JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
  • BeanToArray:将对象转为array输出
  • QuoteFieldNames:输出key时是否使用双引号,默认为true
  • UseSingleQuotes:输出key时使用单引号而不是双引号,默认为false(经测试,这里的key是指所有的输出结果,而非key/value的key,而是key,和value都使用单引号或双引号输出)

方法二(直接在返回数据禁止循环引用)

JSONArray jsonArrayUserNum = JSONArray.parseArray(JSON.toJSONString(resultJsonArr, SerializerFeature.DisableCircularReferenceDetect));

### 登录模块的设计与实现 在基于 Spring Boot 和 Vue 的项目中,登录模块通常涉及前端表单提交、后端身份验证以及用户会话管理等功能。以下是具体方法和示例代码。 #### 后端:Spring Boot 配置用户认证 通过 `Shiro` 或者 `Spring Security` 可以轻松完成用户的认证与授权逻辑。这里以 Shiro 为例: 1. **配置 Shiro 过滤器链** 在 `shiroFilterChainDefinition` 中定义 URL 权限规则[^1]。 ```java @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // 放行静态资源文件 chainDefinition.addPathDefinition("/css/**", "anon"); chainDefinition.addPathDefinition("/js/**", "anon"); // 登录接口放行 chainDefinition.addPathDefinition("/login", "anon"); // 所有其他请求都需要认证 chainDefinition.addPathDefinition("/**", "authc"); return chainDefinition; } ``` 2. **创建自定义 Realm 类** 自定义 `Realm` 是用于从数据库或其他存储介质获取用户信息的核心类。 ```java public class UserRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 假设有一个服务可以从数据库查询用户的角色和权限 List<String> roles = userService.findRoles(username); List<String> permissions = userService.findPermissions(username); authorizationInfo.setRoles(new HashSet<>(roles)); authorizationInfo.setStringPermissions(new HashSet<>(permissions)); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); // 查询用户是否存在 SysUser user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException(); // 如果用户名不存在抛出异常 } byte[] salt = Encodes.decodeHex(user.getSalt()); return new SimpleAuthenticationInfo( user, user.getPassword(), ByteSource.Util.bytes(salt), getName() ); } } ``` 3. **处理登录请求** 创建一个控制器来接收前端发送的登录请求并返回 JSON 数据。 ```java @RestController @RequestMapping("/api/auth") public class AuthController { @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest request) { Subject currentUser = SecurityUtils.getSubject(); try { UsernamePasswordToken token = new UsernamePasswordToken(request.getUsername(), request.getPassword()); currentUser.login(token); // 尝试登录 Map<String, Object> result = Maps.newHashMap(); result.put("token", currentUser.getSession().getId()); // 返回 session ID return ResponseEntity.ok(result); } catch (UnknownAccountException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("账户不存在!"); } catch (IncorrectCredentialsException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("密码错误!"); } } } ``` --- #### 前端:Vue 表单提交与状态管理 1. **构建登录页面组件** 使用 Element UI 提供的表单控件快速搭建登录界面[^2]。 ```vue <template> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm"> <el-form-item prop="username"> <el-input v-model="ruleForm.username" placeholder="请输入账号"></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" v-model="ruleForm.password" autocomplete="off" placeholder="请输入密码"></el-input> </el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">登 录</el-button> </el-form> </template> <script> export default { data() { return { ruleForm: { username: '', password: '' }, rules: { username: [{ required: true, message: '请输入账号', trigger: 'blur' }], password: [{ required: true, message: '请输入密码', trigger: 'blur' }] } }; }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { const payload = { ...this.ruleForm }; axios.post('/api/auth/login', payload) .then(response => { localStorage.setItem('token', response.data.token); // 存储 Token 到本地 this.$router.push({ path: '/home' }); // 跳转到首页 }) .catch(error => { console.error(error.response.data); alert(error.response.data || '登录失败'); }); } else { console.log('error submit!!'); return false; } }); } } }; </script> ``` 2. **路由守卫保护未授权访问** 设置全局前置守卫,在每次跳转前检查是否有合法的 Token。 ```javascript router.beforeEach((to, from, next) => { const isAuthenticated = !!localStorage.getItem('token'); // 检查 Token 是否存在 if (!isAuthenticated && to.path !== '/login') { next('/login'); // 若无权则重定向至登录页 } else { next(); } }); ``` --- #### 功能扩展建议 - **动态菜单加载** 结合角色权限,动态生成左侧导航栏中的可操作项。 - **功能级访问控制** 对于某些特定按钮或者 API 接口,可以进一步细化到具体的某个动作上进行校验[^7]。 - **JWT 替代 Session** 使用 JWT(JSON Web Tokens)代替传统的服务器端会话机制,减少对内存的压力[^8]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值