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 [email protected]
 * @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 [email protected]
 * @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 [email protected]
 * @date 2019-09-09 10:20
 */
public interface Converter {

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

    /**
     * 对象批量转换
     *
     * @param fromList
     * @param clazz
     * @return
     */
    List convert(List fromList, Class 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 [email protected]
 * @date 2019-09-09 10:26
 */
@Slf4j
public abstract class BaseConverter implements Converter {

    @Override
    public VO convert(DO from, Class 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 convert(List fromList, Class clazz) throws ConverterException {
        if (CollectionUtils.isEmpty(fromList)) {
            return null;
        }
        List 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 [email protected]
 * @date 2019-10-29 11:12
 */
@Component
public class StudentConverter extends BaseConverter {

   /*
    @Autowired
    private TeacherService teacherService;
    */

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

    @Override
    public List convert(List fromList, Class clazz) throws ConverterException {
        List 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 listStudent(String studName) {
    QueryWrapper queryWrapper = new QueryWrapper<>();
    queryWrapper.like("stud_name", studName);
    List 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的属性拷贝方式。

结束语

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

本系列文章

  • springboot2.x Jackson自定义序列化,优雅实现String、List、Object返回""、[]、{}
  • springboot2.x 集成 Mybatis plus(多数据源),提升20%的开发效率
  • springboot2.x 全局异常处理正确方式
  • springboot2.x 如何优雅的实现API输出?
  • 如何优雅的实现数据置顶、置尾、交换、拖动排序?

你可能感兴趣的:(springboot2.x 如何优雅的实现 API 输出?)