ModelMapper的常见用法 ,号称是beanUtils.copyProp....的升级版??,代码复制粘贴即可复现效果,so convenient

官网案例

以下将官网案例做一个解释

1)快速入门

递归遍历源对象的属性拷贝给目标对象

拷贝对象下对象的属性值

@Data
class Order {
    private Customer customer;
    private Address billingAddress;
}

@Data
class Customer {
    private Name name;
}

@Data
class Name {
    private String firstName;
    private String lastName;
}

@Data
class Address {
    private String street;
    private String city;
}

@Data
class OrderDTO {
    private String customerFirstName;
    private String customerLastName;
    private String billingStreet;
    private String billingCity;
}

public class Test {

    public static void main(String[] args) {
        Order order = new Order();
        Customer zww = new Customer();
        Name name = new Name();
        Address address = new Address();
        // OrderDTO orderDTO = new OrderDTO();

        name.setFirstName("ww");
        name.setLastName("Z");
        zww.setName(name);

        address.setCity("汕头");
        address.setStreet("明茵路");
        order.setCustomer(zww);
        order.setBillingAddress(address);
        System.out.println(order);
        ModelMapper modelMapper = new ModelMapper();
        OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
        System.out.println(orderDTO);
    }
}

image-20241011121206910

通过上述例子,我们可以将order中的customer对象中的name中的firstname拷贝到orderDto的firstname

匹配规则是根据属性名拼接,例如customerFirstname就是找customer属性对象,然后下一层的firstname,但是没有,找name的下一层,有firstname,根据属性名的映射递归一个对象的子对象,直至找到属性如果属性名

image-20241011121446292

匹配属性名从第一个字符开始匹配,如果匹配成功跳出,以至于为什么firstNameXXXdfsf能命中,但是如果是下面的情况就匹配不上

image-20241011121552370

1.1)解释

image-20241011123048411

比较源和目标类型, 根据匹配策略(后续自定义匹配器) 如果没定义策略, 会尽可能的匹配, 例如上述提到的后面加XXX和前面加XXX产生属性名不一致的匹配情况

image-20241011133308065

我们也可以设置匹配策略为松散的getConfiguration。setMatchingStrategy(常量LOOSE),这样属性名前几个字符就算不一致 也能匹配上,street。billingStreet

或者是设置一个验证, 或者添加映射规则 addmappings

2)属性映射

1.传入源值和目标值,返回一个typeMap,刚才是直接map映射且返回对象,此时我们不急着map,自定义一些规则

2.我们这里新增一个目标属性来进行测试

image-20241011141709654

image-20241011142423143

3.这里我们使用了一个addMapping添加映射规则,参数1提取源对象的属性,参数2设置属性映射给其他对象,

如果层级只有一层,可以使用方法引用,XXX::getXXX不过如果你要映射的属性在对象里的一个对象属性里面。那就不能XXX:getXXX::getAAA链式方法引用了。

参数2你也可以使用箭头函数(lambda表达式 x->x.getxxx 隐式返回x对象,类似于js)传入目标元素dest,和从第一个参数获取到的value值(默认为Object类型,且如果是字符串不能toString而是强转为String -> (String)value)。然后进行setXXX((String)value)

最后我们仅需要,调用map方法开始映射。返回值就是映射也可以说是转换后的新对象。效果如上, 官网说明如下,代码结尾再声明先展示效果,后续需要CV自己运行查看即可

但是如果你是使用的 lambda表达式,转换类型只需要

image-20241011153048954
前面统一规定即可

image-20241011143139989

4.除了这种addMapping还可以使用lambda添加多个规则/ 或者映射属性到另一个不同类型的属性上

image-20241011151154935

image-20241011151531549

5.深度映射

说白了就是第一个参数和第二个参数用lambda一个一个get进去,或者get到里面再set而已

image-20241011151615215

6.跳过映射,排除一些属性不拷贝

image-20241011151703416

规则器上调用方法传输 目标的方法引用

image-20241011152104577

3)变换器

有点类似于插件,会对映射后的值做一个统一处理,例如这里我们要把映射后的值全部变为大写

我们看一下官方文档如何解释的

image-20241011154131810

①首先用一个lambda 实现了函数式接口,什么是函数式接口?也就是一个接口里只能有一个抽象方法,和若干个静态或者默认方法,用lambda实现,函数式接口接收该实现返回的接口后续调用该接口的实现方法

image-20241011154546443

也可以实现抽象方法的基础上再调用默认方法

image-20241011154610432

如果函数式接口有多个 抽象方法,那么该接口就不能作为函数式接口

image-20241011154704162

②using使用该函数式接口

image-20241011154749693

image-20241011155016933

这个converter第一个泛型为string为src源属性值,第二个也是string,目标属性值

我们已经给这个函数式接口实现了convert方法,using即可,然后他会在映射给dest目标元素时将src做一个加工处理,调用函数式接口toUpper里的我们实现的lambda表达式方法covert传入上下文context也就是ctx然后获取源值getSource,加工后返回出来的新的src,最后映射给dest类。效果如下

image-20241011155344165

③lambda定义转换器

image-20241011155956368

官网这里没做空值判断,我们给他加一个,和前面统一一点

image-20241011160208695

看到另一个博主做了一个枚举转换

image-20241011160320615

只要搞清楚一点点原理,就比较好懂了,这里用到了lambda传入上下文,返回一个枚举类, 这个枚举类通过getInstance 传入source字符串的构造函数生成,然后映射到OrderDTO的orderSource属性上,so ez有没有,只要设置好函数式接口的入类型String和出类型 OrderSource,然后获取上下文的source对齐进行加工就可以得到美味蟹黄堡了奥里给

4)Provider设置默认值

image-20241011162723532

当映射属性的源值为null时,可以指定一个默认值,外部定义or lambda表达式

先不设置值,然后调用查看有无默认值

image-20241011163114677

5)条件判断器

image-20241011163200843

同理,外部定义还是lambda,累了不说了

image-20241011163727267

由于是空,不映射

当然也可以不用自己实现,有内置方法的提供

image-20241011165426600

官网还有具体映射的过程,这里就不看了,有兴趣的小伙伴可以自行研究,以上的用法够用了感觉

代码下面的为个人前言,有点长,不想看可以退了

代码

@Data
class Order {
    private Customer customer;
    private Address billingAddress;
}

@Data
class Customer {
    private Name name;
}

@Data
class Name {
    private String xxfirstName;
    private String lastName;
}

@Data
class Address {
    private String street;
    private String city;
}

@Data
class OrderDTO {
    private String customerFirstName;
    private String customerLastName;
    private String billingStreet;
    private String billingCity;
    private String testAddMapping;
}

public class Test {

    public static void main(String[] args) {
        Order order = new Order();
        Customer zww = new Customer();
        Name name = new Name();
        Address address = new Address();
        // OrderDTO orderDTO = new OrderDTO();

        // name.setXxfirstName("ww");
        name.setLastName("Z");
        zww.setName(name);

        address.setCity("汕头");
        address.setStreet("明茵路");
        order.setCustomer(zww);
        order.setBillingAddress(address);
        System.out.println("映射前:"+order);
        ModelMapper modelMapper = new ModelMapper();
        // 创建 TypeMap
        TypeMap<Order, OrderDTO> typeMap = modelMapper.typeMap(Order.class, OrderDTO.class);
        // // 1.单个mapping规则
        // typeMap.addMapping(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping((String) value));
        // typeMap.addMapping(src->src.getCustomer().getName().getXxfirstName(),OrderDTO::setTestAddMapping);
        // // 2.lambda定义多个规则
        // typeMap.addMappings(mapper->
        //         mapper.<String>map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping(value)));
        // typeMap.addMappings(mapper->
        // {
        //     mapper.map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping((String) value));
        //     mapper.map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping((String) value));
        //     mapper.map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping((String) value));
        // });

        // // 3.跳过映射
        // typeMap.addMappings(mapper->mapper.skip(OrderDTO::setCustomerLastName));
        // 4.变换器
        // Converter<String, String> toUppercase =
        //         ctx -> ctx.getSource() == null ? null : ctx.getSource().toUpperCase();
        // typeMap.addMappings(mapper->{
        //     mapper.using(toUppercase).<String>map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping(value));
        // });
        // // 4.1 lambda定义变换器
        // typeMap.addMappings(mapper->{
        //     mapper.using(ctx ->(ctx.getSource()==null?null:(String)(ctx.getSource())).toUpperCase())
        //             .<String>map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping(value));
        // });
        // // 5.provider默认值
        // typeMap.addMappings(mappper->{
        //     mappper.with((Provider<String> )p->{return "哈哈哈";}).<String>map(src->src.getCustomer().getName().getXxfirstName(),(dest,value)->dest.setTestAddMapping(value));
        // });
        // 6.条件构造器
        typeMap.addMappings(mapper->{
            mapper.when((Condition<String,String>)c->c.getSource()!=null&&!c.getSource().isEmpty()).<String>map(src->src.getCustomer().getName().getXxfirstName(),(dest, value)->dest.setTestAddMapping(value));
        });
        // 执行映射
        OrderDTO map = typeMap.map(order);
        System.out.println("映射后:"+map);



    }
}

前言

在一个开源项目中看到的手法,貌似是beanutils的升级版

感觉很牛逼的一个东西,

场景举例

前端传来的对象M里有一个List和共同的name属性 根据前端的坑挖的属性名

而后端不需要这个List而是需要将A变成B,需要将List转为List

image-20241010215403120

第一个简称bm,第二个ob(object)

如何将ListVM里的 List变成List<List

自己的做法

AtomicInteger index = new AtomicInteger(1);
List<PaperQuestionTypeDto> paperQuestionTypeDto = paperDto.getPaperQuestionTypeDto();
// 这一步将前端的题型类转为后端的题型类,也就是其里面的题目类改造为order和id的
List<PaperQuestionTypeVM> paperQuestionTypeVMList = paperQuestionTypeDto.stream().map(i -> {
    // 后端题型类
    PaperQuestionTypeVM paperQuestionTypeVM = new PaperQuestionTypeVM();
    //改造题目类
    List<PaperQuestionVM> collect = i.getQuestionDtos().stream().map(p -> {
        PaperQuestionVM paperQuestionVM = new PaperQuestionVM();
        paperQuestionVM.setId(p.getId());
        paperQuestionVM.setItemOrder(index.getAndIncrement());
        return paperQuestionVM;
    }).collect(Collectors.toList());
    // 初始化name值
    paperQuestionTypeVM.setName(i.getName());
    paperQuestionTypeVM.setPaperQuestionVMS(collect);
    return paperQuestionTypeVM;
}).collect(Collectors.toList());

参照他人做法,先引入个依赖

<dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.3.3</version>
        </dependency>

配置类

package com.mindskip.xzs.utility;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

public class ModelMapperSingle {
    /**
     * The constant modelMapper.
     */
    protected final static ModelMapper modelMapper = new ModelMapper();
    private final static ModelMapperSingle modelMapperSingle = new ModelMapperSingle();

    static {
        modelMapper.getConfiguration().setFullTypeMatchingRequired(true);
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    }

    /**
     * Instance model mapper.
     *
     * @return the model mapper
     */
    public static ModelMapper Instance() {
        return modelMapperSingle.modelMapper;
    }
}

静态生成单例,加到静态方法工厂后续调用获取对象

引入并书写方法

protected final static ModelMapper modelMapper = ModelMapperSingle.Instance();

 AtomicInteger index = new AtomicInteger(1);
        // 这一步将前端的题型类转为后端的题型类,也就是其里面的题目类改造为order和id的
        // 方法2,使用modelMapper映射对象数据不使用set 和new直接比对相同属性名,拷贝属性和值
        List<PaperQuestionTypeVM> paperQuestionTypeVMList = paperQuestionTypeDto.stream().map(i -> {
            // 这一步将内部列表映射到另一个list,只映射属性名一致的值和属性,同步了name,省去new,setName
            PaperQuestionTypeVM questionTypeVM = modelMapper.map(i, PaperQuestionTypeVM.class);
            List<PaperQuestionVM> questionVMS = i.getQuestionDtos().stream().map(q -> {
                // 将题目列表转为别的类,仅包含order和id,这里id属性一致,映射,order自己set
                PaperQuestionVM questionVM = modelMapper.map(q, PaperQuestionVM.class);
                questionVM.setItemOrder(index.getAndIncrement());
                return questionVM;
            }).collect(Collectors.toList());
            // 将遍历的出来新的的题目列表赋值给题型.name不用给了,映射好了
            questionTypeVM.setPaperQuestionVMS(questionVMS);
            return questionTypeVM;
        }).collect(Collectors.toList());

先map List将其每一个A转为B,然后再对里面的ListC遍历将C转为只有order和id的D类,返回为ListD,将ListD设置给ListB,完成了List(ListC) 为List (List)

image-20241011103125086

少了一个setId和setName,因为属性名一致,直接映射,有点类似于beanutils的copy。不过这个还可以自定义映射的属性,source的dest目的属性,做一个匹配

ModelMapper modelMapper = new ModelMapper();

// 自定义映射规则
modelMapper.typeMap(UserDTO.class, User.class).addMappings(mapper -> {
    mapper.map(UserDTO::getUsername, User::setName); // 映射用户名
    mapper.map(src -> src.getAddress().getStreet(), (dest, value) -> dest.getUserAddress().setStreetName((String) value)); // 嵌套映射
    mapper.map(src -> src.getAddress().getCity(), (dest, value) -> dest.getUserAddress().setCityName((String) value)); // 嵌套映射
});

// 执行映射
User user = modelMapper.map(userDTO, User.class);

从一个类的属性映射到另一个类的属性上

试试看用beanutils

  private static List<PaperQuestionTypeVM> getPaperQuestionTypeVMS(List<PaperQuestionTypeDto> paperQuestionTypeDto) {
        AtomicInteger index = new AtomicInteger(1);
        // 这一步将前端的题型类转为后端的题型类,也就是其里面的题目类改造为order和id的
        // 方法2,使用modelMapper映射对象数据不使用set 和new直接比对相同属性名,拷贝属性和值
        List<PaperQuestionTypeVM> paperQuestionTypeVMList = paperQuestionTypeDto.stream().map(i -> {
            // 这一步将内部列表映射到另一个list,只映射属性名一致的值和属性,同步了name,省去new,setName
            // PaperQuestionTypeVM questionTypeVM = modelMapper.map(i, PaperQuestionTypeVM.class);
            PaperQuestionTypeVM paperQuestionTypeVM = new PaperQuestionTypeVM();
            BeanUtils.copyProperties(i,paperQuestionTypeVM);
            List<PaperQuestionVM> questionVMS = i.getQuestionDtos().stream().map(q -> {
                // 将题目列表转为别的类,仅包含order和id,这里id属性一致,映射,order自己set
                // PaperQuestionVM questionVM = modelMapper.map(q, PaperQuestionVM.class);
                PaperQuestionVM paperQuestionVM = new PaperQuestionVM();
                BeanUtils.copyProperties(q,paperQuestionVM);
                paperQuestionVM.setItemOrder(index.getAndIncrement());
                return paperQuestionVM;
            }).collect(Collectors.toList());
            // 将遍历的出来新的的题目列表赋值给题型.name不用给了,映射好了
            paperQuestionTypeVM.setPaperQuestionVMS(questionVMS);
            return paperQuestionTypeVM;
        }).collect(Collectors.toList());
        return paperQuestionTypeVMList;
    }

不过这样还要多一步new出来然后再copy的操作,那个直接一步到位,copy并且new

怀着好奇心打开了官网,学习一下具体其他用法

其他用法

作用:PO(persistence object持久层也就是对应数据库列的类) 与BO(Bussiness service之间的类) 与VO(value Object 后端给前端的那玩意)和DTO(data transfer object 前端表单的字段集合那个formData) 之间的相互转换,属性拷贝

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/890885.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在三维可视化项目中,B/S和C/S架构该如何选择?

一、什么是B/S和C/S 在3D数据可视化中&#xff0c;有两种常见的架构模式&#xff1a;BS&#xff08;Browser/Server&#xff09;和CS&#xff08;Client/Server&#xff09; B/S模式 B/S模式是指将3D数据可视化的逻辑和处理放在服务器端&#xff0c;而在客户端使用浏览器进行…

智能汽车智能网联

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c;多看一眼都是你的不…

rom定制系列------小米6x_MIUI14_安卓13刷机包修改写入以及功能定制 界面预览

在接待一些定制化系统中。有很多工作室或者一些特殊行业的友友需要在已有固件基础上简略修改其中的功能。方便使用。例如usb调试默认开启。usb安装设置以及usb安装与内置删减一些app的定制服务。今天给友友预览其中小米6X此款机型定制相关的一些界面与功能演示。 定制机型以及…

公司新来的00后测试开发,让我对“跨界”二字有了全新认识

最近&#xff0c;我们部门迎来了一位新面孔——一个00后的年轻人&#xff0c;阿沅。初见他时&#xff0c;我以为他只是众多新入职员工中的普通一员&#xff0c;毕竟他的专业背景与我们的IT行业似乎相去甚远——广告学。然而&#xff0c;他的到来&#xff0c;却如同一阵清风&…

IDEA中的Postfix Completion与Live Templates功能详解

目录 前言1. Postfix Completion&#xff08;后缀补全&#xff09;1.1 什么是Postfix Completion1.2 使用Postfix Completion的步骤1.3 Postfix Completion的具体应用1.4 自定义Postfix Completion 2. Live Templates&#xff08;实时模板&#xff09;2.1 什么是Live Templates…

聊聊 Facebook Audience Network 绑定收款账号的问题

大家好&#xff0c;我是牢鹅&#xff01;本篇是Facebook开发者系列的第五篇&#xff0c;最近看见好多群友在群里问这个&#xff0c;说Facebook的变现账户在绑定国内的银行账户时&#xff08;有些用户反馈就算不是国内的卡也会出现该问题&#xff09;&#xff0c;显示“无法绑定…

【WRF工具】QGis插件GIS4WRF:根据嵌套网格生成namelist.wps文件

【WRF工具】QGis插件GIS4WRF:根据嵌套网格生成namelist.wps文件 准备:WRF嵌套网格QGis根据嵌套网格生成namelist.wps文件检查:根据namelist.wps绘制模拟区域参考GIS4WRF 是一个免费且开源的 QGIS 插件,旨在帮助研究人员和从业者进行高级研究天气研究与预报(WRF)模型的建模…

利用可解释性技术增强制造质量预测模型

概述 论文地址&#xff1a;https://arxiv.org/abs/2403.18731 本研究提出了一种利用可解释性技术提高机器学习&#xff08;ML&#xff09;模型性能的方法。该方法已用于铣削质量预测&#xff0c;这一过程首先训练 ML 模型&#xff0c;然后使用可解释性技术识别不需要的特征并去…

Lucene 倒排索引

倒排索引是什么&#xff1f; 【定义】倒排索引&#xff08;Inverted Index&#xff09;是一种用于信息检索的数据结构&#xff0c;尤其适用于文本搜索。它与传统索引的主要区别在于&#xff0c;传统索引是根据文档来查找词语的位置&#xff0c;而倒排索引则是根据词语来查找文…

vmware虚拟机 报错:客户机操作系统已禁用 CPU,请关闭或重置虚拟机 的解决方法

打开cpu虚拟化全部进行勾选 ctrl e 进行关机 勾选上打开就好了 如果没有那个选项 关机>打开虚拟机>管理>更改硬件兼容性> 往小处改改> >更改此虚拟机

MySQL连接查询:联合查询

先看我的表结构 emp表 联合查询的关键字&#xff08;union all, union&#xff09; 联合查询 基本语法 select 字段列表 表A union all select 字段列表 表B 例子&#xff1a;将薪资低于5000的员工&#xff0c; 和 年龄大于50 岁的员工全部查询出来 第一种 select * fr…

x-file-storage:一款强大的文件聚合存储解决方案

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 dromara/x-file-storage 是一个由 Dromara 社区开发和维护的开源项目&#xff0c;旨在提供一个高效、可靠的文件存储解决方案。该项目以其强大的功能和…

正则表达式-“三剑客”(grep、sed、awk)

1.3正则表达式 正则表达式描述了一种字符串匹配的模式&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&#xff0c;在linux中代表自定义的模式模版&#xff0c;linux工具可以用正则表达式过滤文本。Linux…

新版 Notepad++ 下载与安装教程

一、软件准备&#xff1a;麻烦点我 二、双击下载好的 notepad 软件进行安装&#xff0c;选择 “简体中文”。 三、默认 “下一步” 安装。 四、单击 “我接受” 按钮。 五、自定义安装位置&#xff0c;个人建议安装在 D 盘。 六、选择组件&#xff0c;默认 “下一步”。 七、勾…

通过OpenCV实现 Lucas-Kanade 算法

目录 简介 Lucas-Kanade 光流算法 实现步骤 1. 导入所需库 2. 视频捕捉与初始化 3. 设置特征点参数 4. 创建掩模 5. 光流估计循环 6. 释放资源 结论 简介 在计算机视觉领域&#xff0c;光流估计是一种追踪物体运动的技术。它通过比较连续帧之间的像素强度变化来估计图…

C++:list(用法篇+模拟实现)

文章目录 前言一、list 的用法1. list 简介2. 用法代码演示1&#xff09;头/尾 插/删和迭代器遍历2&#xff09;insert与erase3&#xff09;排序sort相关4&#xff09;其他相关 二、list模拟实现1. 结点类模板list_node2. 定义迭代器1&#xff09;为什么要专门封装一个迭代器&a…

使用可白嫖的高配置服务器——DAMODEL进行AI开发教程

DAMODEL&#xff1a;DAMODEL 目前DAmodel注册并实名赠送50大洋的免费额度&#xff0c;搭载4090的服务器费用不到2r/h 教程&#xff1a; 完成注册并实名后 在此点击创建实例 选择实例配置 选择镜像&#xff0c;看你使用哪种dl框架 。 实例自带的磁盘会随实例释放。需要自己…

FineReport 图表切换维度

1、导入数据 可以参考导入Excel数据&#xff0c;直接导入数据也可以在数据库建表&#xff0c;Navicat直接导入数据 以下是数据库建表操作 -- 创建表 create table test11 ( orderTime date NULL, -- 下单时间 quantity int NULL -- 数量 ); 导入数据 2、SQL判断统计维度…

Docker 的数据管理

前置资源 Docker的数据管理资源.zip资源-CSDN文库 一、容器中数据管理 管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&#xff09;。 1&#xff0e;数据卷 数据卷是一个供容…

2024 年 Mac 下这些生产力工具,好用到哭

每段关系最终都会结束 即使不是因为别的原因 也会因为死亡 我只知道 你不对她说出来 她就永远不知道 你的心意 她那天离开的时候 才知道一个道理 有时候 保护一样重要的东西的方式 不是守在她旁边 而是离开她 离得远远的远到看起来谁也 不在乎谁一样 今天呢&#x…