springboot2.x 如何优雅的实现 API 输出?

前言

很多同学在 API 接口开发过程中肯定遇到过这样的问题:如果 API 输出的内容直接使用 DB 表实体类,可能实际需求还得附带更多的其他业务字段信息。如果新建一个类,包含 DB 表实体类字段信息,并带上其他业务信息,是不是感觉又很累赘,字段属性拷贝更是令人头疼。因此,很有必要考虑用统一的一套方案,来优雅的解决这个困扰。

DO、VO 实体类定义

DO 定义,没什么好说的,跟 DB 表字段一一对应。

package com.yb.demo.pojo.model.db1;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

/**
 * @author daoshenzzg@163.com
 * @date 2019-08-05 17:58
 */
@Data
@TableName("student")
public class Student1DO {
    private Long id;
    @Size(max = 8, message = "studName长度不能超过8")
    private String studName;
    @Min(value = 12, message = "年龄不能低于12岁")
    private Integer studAge;
    private String studSex;
    @TableField(fill = FieldFill.INSERT)
    private Integer createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateTime;
}

VO 就比较有意思了,只需要多定一个 teacherName,表示我们要多输出一个字段信息,然后再继承Student1DO的所有属性。

package com.yb.demo.pojo.response;

import com.yb.demo.pojo.model.db1.Student1DO;
import lombok.Data;

/**
 * @author daoshenzzg@163.com
 * @date 2019-10-29 11:07
 */
@Data
public class StudentVO extends Student1DO {
    private String teacherName;
}

类型转换器定义

二话不说,先定一个类型转换器接口

package com.yb.demo.converter;

import com.yb.demo.common.exception.ConverterException;

import java.util.List;

/**
 * 对象转换器
 *
 * @author daoshenzzg@163.com
 * @date 2019-09-09 10:20
 */
public interface Converter<DO, VO> {

    /**
     * 对象转换
     *
     * @param from
     * @param clazz
     * @return
     */
    VO convert(DO from, Class<VO> clazz) throws ConverterException;

    /**
     * 对象批量转换
     *
     * @param fromList
     * @param clazz
     * @return
     */
    List<VO> convert(List<DO> fromList, Class<VO> clazz) throws ConverterException;
}

然后定一个基础的转换器来实现它。这里的默认实现是使用BeanUtils.copyProperties方式。

package com.yb.demo.converter;

import com.yb.demo.common.exception.ConverterException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 默认基础对象转换器
 *
 * @author daoshenzzg@163.com
 * @date 2019-09-09 10:26
 */
@Slf4j
public abstract class BaseConverter<DO, VO> implements Converter<DO, VO> {

    @Override
    public VO convert(DO from, Class<VO> clazz) throws ConverterException {
        if (from == null) {
            return null;
        }
        try {
            VO to = clazz.newInstance();
            BeanUtils.copyProperties(from, to);
            return to;
        } catch (Exception ex) {
            throw new ConverterException(ex);
        }
    }

    @Override
    public List<VO> convert(List<DO> fromList, Class<VO> clazz) throws ConverterException {
        if (CollectionUtils.isEmpty(fromList)) {
            return null;
        }
        List<VO> toList = new ArrayList<>(fromList.size());
        for (DO from : fromList) {
            toList.add(convert(from, clazz));
        }
        return toList;
    }
}

定义一个 StudentConverter 业务自定义来继承 BaseConverter 。然后在使用super的默认转换器实现拷贝bean属性,再把 teacherName 批量赋值。就达到我们的目的。

package com.yb.demo.converter;

import com.yb.demo.common.exception.ConverterException;
import com.yb.demo.pojo.model.db1.Student1DO;
import com.yb.demo.pojo.response.StudentVO;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 可以自定义转换,也可以直接用BaseConverter的默认实现
 *
 * @author daoshenzzg@163.com
 * @date 2019-10-29 11:12
 */
@Component
public class StudentConverter extends BaseConverter<Student1DO, StudentVO> {

   /*
    @Autowired
    private TeacherService teacherService;
    */

    @Override
    public StudentVO convert(Student1DO from, Class<StudentVO> clazz) throws ConverterException {
        return super.convert(from, clazz);
    }

    @Override
    public List<StudentVO> convert(List<Student1DO> fromList, Class<StudentVO> clazz) throws ConverterException {
        List<StudentVO> voList = super.convert(fromList, clazz);
        // 模拟批量通过学生ID找到对应的老师
        if (!CollectionUtils.isEmpty(voList)) {
            voList.forEach(entry -> entry.setTeacherName("莫言"));
        }
        return voList;
    }
}

StudentConverter 转换器定义完了,然后在业务代码中使用

@Autowired
private StudentConverter studentConverter;

/**
 * 学生列表
 *
 * @return
 */
public List<StudentVO> listStudent(String studName) {
    QueryWrapper<Student1DO> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("stud_name", studName);
    List<Student1DO> students = super.list(queryWrapper);
    return studentConverter.convert(students, StudentVO.class);
}

最终效果如下:

{
    "code": 200,
    "msg": "OK",
    "data": [
        {
            "id": 1,
            "studName": "张三-修改",
            "studAge": 23,
            "studSex": "男",
            "createTime": 1559724842,
            "updateTime": 1559724842,
            "teacherName": "莫言"
        }
    ],
    "ttl": 74
}

建议:如果有同学是做 API 输出,需要追求极致的性能,可以自己在业务converter自己去实现拷贝方式。但,像一些管理后台,也不太追求性能,完全可以直接使用BaseConverter的属性拷贝方式。

结束语

这样去定义,不仅代码耦合度低,你会发现,大伙都会使用这种方式去实现。代码高度统一。

本系列文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容

  • 去年有段时间得空,就把谷歌GAE的API权威指南看了一遍,收获颇丰,特别是在自己几乎独立开发了公司的云数据中心之后...
    骑单车的勋爵阅读 20,471评论 0 41
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,090评论 1 32
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,692评论 0 15
  • 不可或缺的心理营养 父母都希望自己的孩子能够健康成长。可是,我们也许不知道,就像需要身体营养一样,孩子在不同的年龄...
    心创社阅读 2,926评论 3 9
  • 这个周末回家乡武汉看看小宝。算算一月余未见,骨子里都是想念,虽然每天视频也不足以解思念之情。一月未见对一岁多的小宝...
    金艷時光阅读 201评论 0 1