RuoYi.Net后端返回雪花ID前端精度丢失问题

遇到问题

RuoYi.Net 使用 Oracle库,SYS_MENU 菜单表的 MENU_ID 创建为 NUMBER(19) 类型,Vue 前端新增菜单保存到数据库的 MENU_ID ,以及后端返回给前端的 MENU_ID 没问题(ORM 使用的是SqlSugar,SqlSugar 新增入库时如果主键为 long 类型则自动生成雪花ID),但在前端显示时发现精度丢失,如下:

数据库中 MENUID1978349139955683328
前端显示 MENUID1978349139955683300

解决办法

参见 SqlSugar 官网: https://www.donet5.com/Doc/8nuge/2561

或者在 RuoYi.Admin\Startup.cs 中,修改如下代码:

 .AddNewtonsoftJson(options =>
 {
     // 忽略循环引用
     options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
     options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";

     // 忽略所有 null 属性
     //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
     // long 类型序列化时转 string, 防止 JavaScript 出现精度溢出问题
     options.SerializerSettings.Converters.AddLongTypeConverters(); // 这一行扩展方法
 })

RuoYi.Framework\JsonSerialization\Extensions\NewtonsoftJsonExtensions.cs,如下:

using RuoYi.Framework.JsonSerialization;

namespace Newtonsoft.Json;

/// <summary>
/// Newtonsoft.Json 拓展
/// </summary>
[SuppressSniffer]
public static class NewtonsoftJsonExtensions
{
    /// <summary>
    /// 添加 DateTime/DateTime?/DateTimeOffset/DateTimeOffset? 类型序列化处理
    /// </summary>
    /// <param name="converters"></param>
    /// <param name="outputFormat"></param>
    /// <param name="localized">自动转换 DateTimeOffset 为当地时间</param>
    /// <returns></returns>
    public static IList<JsonConverter> AddDateTimeTypeConverters(this IList<JsonConverter> converters, string outputFormat = "yyyy-MM-dd HH:mm:ss", bool localized = false)
    {
        converters.Add(new NewtonsoftJsonDateTimeJsonConverter(outputFormat));
        converters.Add(new NewtonsoftNullableJsonDateTimeJsonConverter(outputFormat));

        converters.Add(new NewtonsoftJsonDateTimeOffsetJsonConverter(outputFormat, localized));
        converters.Add(new NewtonsoftJsonNullableDateTimeOffsetJsonConverter(outputFormat, localized));

        return converters;
    }

    /// <summary>
    /// 添加 long/long? 类型序列化处理
    /// </summary>
    /// <param name="converters"></param>
    /// <param name="overMaxLengthOf17">是否超过最大长度 17 再处理</param>
    /// <remarks></remarks>
    public static IList<JsonConverter> AddLongTypeConverters(this IList<JsonConverter> converters, bool overMaxLengthOf17 = false)
    {
        converters.Add(new NewtonsoftJsonLongToStringJsonConverter(overMaxLengthOf17));
        converters.Add(new NewtonsoftJsonNullableLongToStringJsonConverter(overMaxLengthOf17));

        return converters;
    }

    /// <summary>
    /// 添加 DateOnly/DateOnly? 类型序列化处理
    /// </summary>
    /// <param name="converters"></param>
    /// <returns></returns>
    public static IList<JsonConverter> AddDateOnlyConverters(this IList<JsonConverter> converters)
    {
#if !NET5_0
        converters.Add(new NewtonsoftJsonDateOnlyJsonConverter());
        converters.Add(new NewtonsoftJsonNullableDateOnlyJsonConverter());
#endif
        return converters;
    }

    /// <summary>
    /// 添加 TimeOnly/TimeOnly? 类型序列化处理
    /// </summary>
    /// <param name="converters"></param>
    /// <returns></returns>
    public static IList<JsonConverter> AddTimeOnlyConverters(this IList<JsonConverter> converters)
    {
#if !NET5_0
        converters.Add(new NewtonsoftJsonTimeOnlyJsonConverter());
        converters.Add(new NewtonsoftJsonNullableTimeOnlyJsonConverter());
#endif
        return converters;
    }
}

RuoYi.Framework\JsonSerialization\Converters\NewtonsoftJson\NewtonsoftJsonLongToStringJsonConverter.cs,如下:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace RuoYi.Framework.JsonSerialization;

/// <summary>
/// 解决 long 精度问题
/// </summary>
[SuppressSniffer]
public class NewtonsoftJsonLongToStringJsonConverter : JsonConverter<long>
{
    /// <summary>
    /// 构造函数
    /// </summary>
    public NewtonsoftJsonLongToStringJsonConverter()
    {
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="overMaxLengthOf17"></param>
    public NewtonsoftJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false)
    {
        OverMaxLengthOf17 = overMaxLengthOf17;
    }

    /// <summary>
    /// 是否超过最大长度 17 再处理
    /// </summary>
    public bool OverMaxLengthOf17 { get; set; }

    /// <summary>
    /// 反序列化
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="objectType"></param>
    /// <param name="existingValue"></param>
    /// <param name="hasExistingValue"></param>
    /// <param name="serializer"></param>
    /// <returns></returns>
    public override long ReadJson(JsonReader reader, Type objectType, long existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var jt = JValue.ReadFrom(reader);
        return jt.Value<long>();
    }

    /// <summary>
    /// 序列化
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="value"></param>
    /// <param name="serializer"></param>
    public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer)
    {
        if (OverMaxLengthOf17)
        {
            if (value.ToString().Length <= 17) writer.WriteValue(value);
            else writer.WriteValue(value.ToString());
        }
        else writer.WriteValue(value.ToString());
    }
}

/// <summary>
/// 解决 long? 精度问题
/// </summary>
[SuppressSniffer]
public class NewtonsoftJsonNullableLongToStringJsonConverter : JsonConverter<long?>
{
    /// <summary>
    /// 构造函数
    /// </summary>
    public NewtonsoftJsonNullableLongToStringJsonConverter()
    {
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="overMaxLengthOf17"></param>
    public NewtonsoftJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false)
    {
        OverMaxLengthOf17 = overMaxLengthOf17;
    }

    /// <summary>
    /// 是否超过最大长度 17 再处理
    /// </summary>
    public bool OverMaxLengthOf17 { get; set; }

    /// <summary>
    /// 反序列化
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="objectType"></param>
    /// <param name="existingValue"></param>
    /// <param name="hasExistingValue"></param>
    /// <param name="serializer"></param>
    /// <returns></returns>
    public override long? ReadJson(JsonReader reader, Type objectType, long? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var jt = JValue.ReadFrom(reader);
        return jt.Value<long?>();
    }

    /// <summary>
    /// 序列化
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="value"></param>
    /// <param name="serializer"></param>
    public override void WriteJson(JsonWriter writer, long? value, JsonSerializer serializer)
    {
        if (value == null) writer.WriteNull();
        else
        {
            var newValue = value.Value;
            if (OverMaxLengthOf17)
            {
                if (newValue.ToString().Length <= 17) writer.WriteValue(newValue);
                else writer.WriteValue(newValue.ToString());
            }
            else writer.WriteValue(newValue.ToString());
        }
    }
}
<think>我们正在处理一个关于Ruoyi Plus项目的问题:如何在后端传输Map到前端,并在前端将Map转换为数组时添加额外字段。 根据用户的问题,我们需要考虑以下步骤: 1. 后端:准备一个Map类型的数据结构,其中可能包含一些键值对。 2. 后端:在传输给前端之前,可能需要将这个Map转换为一个适合传输的形式(例如,在Ruoyi框架中,通常我们会使用R对象来封装返回数据)。 3. 前端:接收到数据后,将Map转换为数组(数组的元素可能是对象,每个对象代表原Map的一个键值对,通常包含key和value属性)。 4. 前端:在转换过程中,给每个对象添加额外的字段。 参考引用[1]中提到了一些类数组转数组的方法,但这里我们处理的是Map(对象)转数组,所以方法类似。 步骤详解: 后端部分(Java): 假设我们有一个Map<String, Object>,我们想将它传输到前端。在Ruoyi Plus中,我们可以这样返回: R r = R.ok(); r.put("data", map); // 将map放入返回数据的"data"字段 但是,如果我们想在后端直接添加额外字段,那么我们需要在传输之前对Map进行处理。然而,用户的问题是“后端传输Map到前端并转换为数组时,如何添加额外字段”,所以添加字段的操作可能更适合在前端进行(因为前端需要展示的数据结构可能包含一些与视图相关的字段,而后端主要处理业务数据)。 如果确实需要后端添加字段,那么我们可以构建一个包含额外信息的List。例如: Map<String, Object> map = ...; // 原始数据 List<Map<String, Object>> list = new ArrayList<>(); for (Map.Entry<String, Object> entry : map.entrySet()) { Map<String, Object> newItem = new HashMap<>(); newItem.put("key", entry.getKey()); newItem.put("value", entry.getValue()); newItem.put("extraField", "额外字段的值"); // 添加额外字段 list.add(newItem); } 然后返回这个list。 但是,这样返回的就不再是Map,而是一个List了。所以,如果用户要求返回的是Map,但前端需要数组形式并且每个元素有额外字段,那么建议在前端进行转换并添加字段。 前端部分(Vue): 假设我们通过API获取到的数据是一个Map(在JavaScript中,Map通常对应一个普通对象,但这里我们使用对象字面量): // 假设返回的数据结构:{ data: { key1: value1, key2: value2, ... } } this.$axios.get(...).then(response => { const mapData = response.data; // 假设返回的整个数据就是我们的map // 或者如果返回的是R对象,则可能是 response.data.data // 将Map对象转换为数组,并添加额外字段 let array = []; for (let key in mapData) { if (mapData.hasOwnProperty(key)) { array.push({ key: key, value: mapData[key], extraField: '额外值' // 这里可以设置额外字段的值,可能根据key或value计算 }); } } // 现在array就是转换后的数组,每个元素都有key, value和extraField }); 或者使用更现代的方式(使用Object.entries): let array = Object.entries(mapData).map(([key, value]) => ({ key, value, extraField: `额外的${key}` })); 这样,我们就得到了一个数组,每个元素都是包含key、value和extraField的对象。 根据用户的需求,他们可能希望在前端完成这个转换和添加字段的操作,因为这样不会改变后端的数据结构,且前端可以根据需要灵活添加字段。 总结: 有两种方式: 1. 后端将Map转换为一个包含额外字段的List,然后返回List(这样前端直接使用数组,但每个元素已经包含额外字段)。 2. 后端返回原始的Map,前端在接收到数据后,将Map转换为数组并添加额外字段。 考虑到Ruoyi Plus框架的前后端分离特点,通常建议保持后端返回原始数据,由前端根据视图需求进行转换和添加字段。这样后端服务更通用,前端更灵活。 因此,我们推荐前端转换的方式。 示例代码(前端Vue): 假设API返回的数据结构为: { "code": 200, "msg": "成功", "data": { "name": "张三", "age": 20, "gender": "男" } } 我们想要将data这个对象转换为数组,并添加一个字段(例如,添加一个字段“label”用于显示中文标签): this.$http.get('/api/example').then(res => { if (res.data.code === 200) { let mapData = res.data.data; // 这里是一个对象 let array = []; // 定义每个key对应的中文标签(这里假设我们有一个映射关系) const labelMap = { name: '姓名', age: '年龄', gender: '性别' }; for (let key in mapData) { if (mapData.hasOwnProperty(key)) { array.push({ key: key, value: mapData[key], label: labelMap[key] || key // 如果映射存在则使用,否则用key }); } } // 现在array就是转换后的数组,包含key, value, label this.list = array; // 绑定到前端展示 } }); 或者使用map方法: let array = Object.keys(mapData).map(key => ({ key, value: mapData[key], label: labelMap[key] || key })); 这样,我们就完成了在前端将Map(对象)转换为数组并添加额外字段的操作。 如果用户希望后端处理,那么可以这样(Java): Map<String, Object> map = ...; // 原始数据 List<Map<String, Object>> list = new ArrayList<>(); // 假设我们有一个映射关系,将字段名映射为中文标签 Map<String, String> labelMap = new HashMap<>(); labelMap.put("name", "姓名"); labelMap.put("age", "年龄"); labelMap.put("gender", "性别"); for (Map.Entry<String, Object> entry : map.entrySet()) { Map<String, Object> item = new HashMap<>(); item.put("key", entry.getKey()); item.put("value", entry.getValue()); item.put("label", labelMap.getOrDefault(entry.getKey(), entry.getKey())); list.add(item); } return R.ok().put("data", list); // 返回数组 然后前端直接使用response.data.data即可,它已经是一个数组了。 用户可以根据实际需求选择前后端处理的方式。 注意:在Ruoyi Plus中,前后端交互通常使用R对象,所以返回数据时,我们使用R.ok().put("data", ...)来封装数据。 因此,回答用户的问题: 建议在前端进行转换和添加字段,这样不会影响后端的数据结构,并且前端可以灵活处理。但如果这个数组结构是多个地方共用的,也可以考虑在后端处理。 下面按照要求生成相关问题。</think>在Ruoyi Plus项目中,将后端传输的`Map`转换为前端数组并添加额外字段,可通过以下步骤实现: ### 后端处理(Java) 1. **构建包含额外字段的Map** 在Service层将原始Map转换为包含额外字段的新Map,使用Java 8的`Stream`: ```java public Map<String, Object> getEnrichedMap() { Map<String, String> rawMap = ... // 原始数据 return rawMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> { Map<String, Object> item = new HashMap<>(); item.put("value", entry.getValue()); item.put("extraField", "额外值"); // 添加字段 return item; } )); } ``` 2. **Controller返回数据** 通过Ruoyi的`R`对象返回Map: ```java @GetMapping("/data") public R getData() { Map<String, Object> enrichedMap = getEnrichedMap(); return R.ok().put("data", enrichedMap); } ``` ### 前端处理(Vue) 1. **API请求** 使用Axios获取数据: ```javascript import { getData } from "@/api/your-module"; export default { data() { return { list: [] }; }, methods: { async fetchData() { const res = await getData(); this.processMap(res.data); // 处理返回的Map } } }; ``` 2. **转换Map为数组并添加字段** 使用`Object.entries()`将Map转换为数组,并扩展字段: ```javascript processMap(mapData) { this.list = Object.entries(mapData).map(([key, value]) => ({ key, ...value, // 包含原始值(value)和已添加的extraField newField: "动态值" // 前端新增字段 })); } ``` ### 结果示例 原始Map: ```json { "item1": "值1", "item2": "值2" } ``` 转换后的数组: ```json [ { "key": "item1", "value": "值1", "extraField": "额外值", // 后端添加 "newField": "动态值" // 前端添加 }, { "key": "item2", "value": "值2", "extraField": "额外值", "newField": "动态值" } ] ``` ### 关键点说明 1. **后端职责** - 结构化原始数据 - 添加**业务相关**的固定字段(如`extraField`)[^1] 2. **前端职责** - 将Map转换为数组便于渲染 - 添加**视图相关**的动态字段(如`newField`)[^2] 3. **数据流** ```mermaid graph LR A[后端Map] --> B[添加业务字段] --> C[前端接收] --> D[转换为数组] --> E[添加视图字段] ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值