Что должен содержать базовый проект SpringBoot?

Spring Boot

предисловие

   Создайте новый проект или разделите старый огромный проект на несколько проектов. При создании нового проекта часто необходимо выполнять повторяющуюся работу, например копирование часто используемых классов инструментов, общего кода и т. д. Таким образом, вы можете сделать базовый проект для удобства использования.Когда вы испытываете новый проект, вы можете разработать бизнес-код, просто настроив его непосредственно в базовом проекте.

Что должно быть включено в базовый проект.

  • Документация по онлайн-интерфейсу Swagger.
  • Генератор кода CodeGenerator.
  • единый возврат.
  • Общий объект подкачки.
  • Общий класс инструментов.
  • Глобальный перехват исключений.
  • Перечисление ошибок.
  • Пользовательское исключение.
  • Файлы конфигурации для нескольких сред.
  • Конфигурация Maven с несколькими средами.
  • Конфигурация журнала.
  • ДженкинсФайл.

Можно добавить в комментариях


Swagger

   Написание документации по интерфейсу обычно является головной болью, но swagger используется, чтобы помочь нам решить эту проблему. Документацию по интерфейсу можно создать онлайн и протестировать на странице.

   может четко показать, что на данные запроса был дан ответ. Конечно, все это нужно настроить в коде.

Примечание. Документацию по интерфейсу можно открыть только в среде тестирования/разработки, закройте ее в других средах.

Общие аннотации Swagger

  • @Api用于Controller
  • @ApiOperation用于Controller内的方法。
  • @ApiResponses用于标识接口返回数据的类型。
  • @ApiModel用于标识类的名称
  • @ApiModelProperty用于标识属性的名称

кейс

@RestController
@Api(tags = "用户")
@AllArgsConstructor
@RequestMapping("/user")
public class UserController {

    private IUserService userService;

    /**
     * 获取用户列表
     * @param listUserForm 表单数据
     * @return 用户列表
     */
    @ApiOperation("获取用户列表")
    @GetMapping("/listUser")
    @ApiResponses(
            @ApiResponse(code = 200, message = "操作成功", response = UserVo.class)
    )
    public ResultVo listUser(@Validated ListUserForm listUserForm){
        return ResultVoUtil.success(userService.listUser(listUserForm));
    }

}
@Data
@ApiModel("获取用户列表需要的表单数据")
@EqualsAndHashCode(callSuper = false)
public class ListUserForm extends PageForm<ListUserForm> {

    /**
     * 用户状态
     */
    @ApiModelProperty("用户状态")
    @NotEmpty(message = "用户状态不能为空")
    @Range(min =  -1 , max = 1 , message = "用户状态有误")
    private String status;

}

Соответствующую конфигурацию чванства можно просмотретьбазовый проектвнутриSwaggerConfiguration.java.

Генератор кода CodeGenerator.

   Генератор кода mybatis_plus может помочь нам сгенерироватьentity,service,serviceImpl,mapper,mapper.xml. Избавляет вас от необходимости создавать кучу классов сущностей.

   Поскольку конфигурация слишком длинная, она не будет размещена здесь Конфигурацию соответствующего CodeGenerator можно посмотретьбазовый проектвнутриCodeGenerator.java.


Часто используемые пакеты

Равномерно вернуть ResultVo

Унифицировать формат данных ответа всех интерфейсов.

@Data
@ApiModel("固定返回格式")
public class ResultVo {

    /**
     * 错误码
     */
    @ApiModelProperty("错误码")
    private Integer code;

    /**
     * 提示信息
     */
    @ApiModelProperty("提示信息")
    private String message;

    /**
     * 具体的内容
     */
    @ApiModelProperty("响应数据")
    private Object data;

}

абстрактная форма BaseForm

public abstract class BaseForm<T> {

    /**
     * 获取实例
     * @return 返回实体类
     */
    public abstract T buildEntity();

}

У некоторых друзей могут возникнуть вопросы, какая польза от этого класса. Взгляните на код ниже.

    /**
     * 添加用户
     * @param userForm 表单数据
     * @return true 或者 false
     */
    @Override
    public boolean addUser(AddUserForm userForm) {
        User user = new User();
        user.setNickname(userForm.getNickname());
        user.setBirthday(userForm.getBirthday());
        user.setUsername(userForm.getUsername());
        user.setPassword(userForm.getPassword());
        return save(user);
    }

Рефакторинг, он чувствует себя немного свежее.

/**
 * 添加用户
 * @param userForm 表单数据
 * @return true 或者 false
 */
@Override
public boolean addUser(AddUserForm userForm) {
    User user = new User();
    BeanUtils.copyProperties(this,user);
    return save(user);
}

Рефакторинг с помощью BaseForm AddUserForm наследует BaseForm и переопределяет buildEntity

@Data
@EqualsAndHashCode(callSuper = false)
public class AddUserForm extends BaseForm<User> {

    /**
     * 昵称
     */
    private String nickname;

    /**
     * 生日
     */
    private Date birthday;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 构造实体
     * @return 实体对象
     */
    @Override
    public User buildEntity() {
        User user = new User();
        BeanUtils.copyProperties(this,user);
        return user;
    }
}
/**
 * 添加用户
 * @param userForm 表单数据
 * @return true 或者 false
 */
@Override
public boolean addUser(AddUserForm userForm) {
    return save(userForm.buildEntity());
}

   Приведенный выше код кажется вам знакомым?Во многих случаях полученные параметры преобразуются в соответствующие классы сущностей, а затемспастииливозобновить. Так что для такихformможет быть унаследованbaseformи реализоватьbuildEntity()Это может больше соответствовать объектно-ориентированному,serviceне волнуетformкак превратиться вentity, нужно звонить только при использованииbuildEntity(), особенно вform -> entityКогда это относительно сложно, это может уменьшитьserviceкод внутри. Сделайте логику кода более понятной.


Общий объект подкачки

Когда дело доходит до запроса, подавляющее большинство нужно использовать вкладки, чтобы объект погруженного пакета необходим. Это можно отметить подPageForm.calcCurrent(),PageVo.setCurrentAndSize(),PageVo.setTotal()несколько методов.

PageForm

@Data
@ApiModel(value = "分页数据", description = "分页需要的表单数据")
public class PageForm<T extends PageForm<?>>{

    /**
     * 页码
     */
    @ApiModelProperty(value = "页码 从第一页开始 1")
    @Min(value = 1, message = "页码输入有误")
    private Integer current;

    /**
     * 每页显示的数量
     */
    @ApiModelProperty(value = "每页显示的数量 范围在1~100")
    @Range(min = 1, max = 100, message = "每页显示的数量输入有误")
    private Integer size;

    /**
     * 计算当前页 ,方便mysql 进行分页查询
     * @return 返回 pageForm
     */
    @ApiModelProperty(hidden = true)
    public T calcCurrent(){
        current = (current - 1 ) * size;
        return (T) this;
    }
}

PageVo

@Data
public class PageVo<T> {
    /**
     * 分页数据
     */
    @ApiModelProperty(value = "分页数据")
    private List<T> records;
    /**
     * 总条数
     */
    @ApiModelProperty(value = "总条数")
    private Integer total;

    /**
     * 总页数
     */
    @ApiModelProperty(value = "总页数")
    private Integer pages;

    /**
     * 当前页
     */
    @ApiModelProperty(value = "当前页")
    private Integer current;

    /**
     * 查询数量
     */
    @ApiModelProperty(value = "查询数量")
    private Integer size;

    /**
     * 设置当前页和每页显示的数量
     * @param pageForm 分页表单
     * @return 返回分页信息
     */
    @ApiModelProperty(hidden = true)
    public PageVo<T> setCurrentAndSize(PageForm<?> pageForm){
        BeanUtils.copyProperties(pageForm,this);
        return this;
    }

    /**
     * 设置总记录数
     * @param total 总记录数
     */
    @ApiModelProperty(hidden = true)
    public void setTotal(Integer total) {
        this.total = total;
        this.setPages(this.total % this.size > 0 ? this.total / this.size + 1 : this.total / this.size);
    }
}

кейс

ListUserForm
@Data
@ApiModel("获取用户列表需要的表单数据")
@EqualsAndHashCode(callSuper = false)
public class ListUserForm extends PageForm<ListUserForm> {

    /**
     * 用户状态
     */
    @ApiModelProperty("用户状态")
    @NotEmpty(message = "用户状态不能为空")
    @Range(min =  -1 , max = 1 , message = "用户状态有误")
    private String status;

}
UserServiceImpl
/**
 * 获取用户列表
 * @param listUserForm 表单数据
 * @return 用户列表
 */
@Override
public PageVo<UserVo> listUser(ListUserForm listUserForm) {
    PageVo<UserVo> pageVo = new PageVo<UserVo>().setCurrentAndSize(listUserForm);
    pageVo.setTotal(countUser(listUserForm.getStatus()));
    pageVo.setRecords(userMapper.listUser(listUserForm.calcCurrent()));
    return pageVo;
}

/**
 * 获取用户数量
 * @param status 状态
 * @return 用户数量
 */
private Integer countUser(String status){
    return count(new QueryWrapper<User>().eq("status",status));
}
UserController
/**
 * 获取用户列表
 * @param listUserForm 表单数据
 * @return 用户列表
 */
@ApiOperation("获取用户列表")
@GetMapping("/listUser")
@ApiResponses(
        @ApiResponse(code = 200, message = "操作成功", response = UserVo.class)
)
public ResultVo listUser(@Validated ListUserForm listUserForm){
    return ResultVoUtil.success(userService.listUser(listUserForm));
}
Примечания
  • PageVo должен быть установлен при создании экземпляратекущая страницаиСумма, отображаемая на страницеможно назватьsetCurrentAndSize()Заканчивать.
  • При выполнении запроса на пейджинг необходимо вычислить смещение.listUserForm.calcCurrent()

Зачем вычислять смещение?

  • Если на странице запроса 1 отображается 10 записей на странице, параметры, передаваемые внешним интерфейсом,current=1&amp;&amp;size=10,В настоящее времяlimit 1,10нет проблем.
  • Если на странице запроса 2 отображается 10 записей на странице, параметры, переданные внешним интерфейсом,current=2&amp;&amp;size=10,В настоящее времяlimit 2,10Есть проблема, она должна бытьlimit 10,10.calcCurrent()的作用就是如此.

Почему бы не использовать плагин пейджинга, который поставляется с MybatisPlus?

Встроенный запрос на подкачку будет иметь проблемы с производительностью при большом объеме данных.

Общие инструменты

Общие классы инструментов могут быть введены в соответствии с вашими собственными привычками разработки.


Обработка исключений

Общий поток обработки исключений выглядит следующим образом.

  • Выброшена информация об исключении ->ControllerAdviceЗахват форматированного вывода
  • бросать вручнуюCustomExceptionи пройти вReulstEnum——> Захват сообщения об ошибке и вывод сообщения об ошибке.

пользовательское исключение

@Data
@EqualsAndHashCode(callSuper = false)
public class CustomException extends RuntimeException {

    /**
     * 状态码
     */
    private final Integer code;

    /**
     * 方法名称
     */
    private final String method;


    /**
     * 自定义异常
     *
     * @param resultEnum 返回枚举对象
     * @param method     方法
     */
    public CustomException(ResultEnum resultEnum, String method) {
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
        this.method = method;
    }

    /**
     * @param code    状态码
     * @param message 错误信息
     * @param method  方法
     */
    public CustomException(Integer code, String message, String method) {
        super(message);
        this.code = code;
        this.method = method;
    }

}

Перечисление сообщений об ошибках

Добавляйте по бизнесу.

@Getter
public enum ResultEnum {

    /**
     * 未知异常
     */
    UNKNOWN_EXCEPTION(100, "未知异常"),

    /**
     * 添加失败
     */
    ADD_ERROR(103, "添加失败"),

    /**
     * 更新失败
     */
    UPDATE_ERROR(104, "更新失败"),

    /**
     * 删除失败
     */
    DELETE_ERROR(105, "删除失败"),

    /**
     * 查找失败
     */
    GET_ERROR(106, "查找失败"),

    ;

    private Integer code;

    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 通过状态码获取枚举对象
     * @param code 状态码
     * @return 枚举对象
     */
    public static ResultEnum getByCode(int code){
        for (ResultEnum resultEnum : ResultEnum.values()) {
            if(code == resultEnum.getCode()){
                return resultEnum;
            }
        }
        return null;
    }

}

глобальный перехват исключений

Глобальный перехват исключений заключается в использовании@ControllerAdviceДля реализации можно просмотреть часто используемую конфигурацию перехвата исключений.GlobalExceptionHandling.

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandling {

    /**
     * 自定义异常
     */
    @ExceptionHandler(value = CustomException.class)
    public ResultVo processException(CustomException e) {
        log.error("位置:{} -> 错误信息:{}", e.getMethod() ,e.getLocalizedMessage());
        return ResultVoUtil.error(Objects.requireNonNull(ResultEnum.getByCode(e.getCode())));
    }

    /**
     * 通用异常
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public ResultVo exception(Exception e) {
        e.printStackTrace();
        return ResultVoUtil.error(ResultEnum.UNKNOWN_EXCEPTION);
    }
}

кейс

Controller
/**
 * 删除用户
 * @param id 用户编号
 * @return 成功或者失败
 */
@ApiOperation("删除用户")
@DeleteMapping("/deleteUser/{id}")
public ResultVo deleteUser(@PathVariable("id") String id){
    userService.deleteUser(id);
    return ResultVoUtil.success();
}
Service
/**
 * 删除用户
 * @param id id
 */
@Override
public void deleteUser(String id) {
    // 如果删除失败抛出异常。 -- 演示而已不推荐这样干
    if(!removeById(id)){
        throw new CustomException(ResultEnum.DELETE_ERROR, MethodUtil.getLineInfo());
    }
}
результат

Распечатайте количество строк в файле, где находится код ошибки. Легко проверить.

Примечания

   Все сообщения об ошибках, созданные вручную, должны быть перечислены в списке сообщений об ошибках.ResultEnumВыполняйте единое техническое обслуживание. Разные сервисы используют разные коды ошибок. Удобно различать, когда сообщается об ошибке. Быстро локализуйте проблему.


Конфигурация с несколькими средами

Конфигурация нескольких сред SpringBoot

   Для проекта есть в основном 4 средыdev,test,pre,prod, достаточно создать еще несколько конфигурационных файлов для проекта SpringBoot. Затем вы можете настроить его при запускеspring.profiles.activeдля выбора среды запуска.

java -jar BasicProject.jar --spring.profiles.active=prod  

Конфигурация Maven с несколькими средами

   Если вы хотите динамически указывать среду при упаковке, для этого вам нужно использовать Maven xml.

XML-файл конфигурации
<!--  配置环境  -->
<profiles>
    <profile>
        <!-- 开发 -->
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <activatedProperties>dev</activatedProperties>
        </properties>
    </profile>
    <profile>
        <!-- 测试 -->
        <id>test</id>
        <properties>
            <activatedProperties>test</activatedProperties>
        </properties>
    </profile>
    <profile>
        <!-- 准生产 -->
        <id>pre</id>
        <properties>
            <activatedProperties>pre</activatedProperties>
        </properties>
    </profile>
    <profile>
        <!-- 生产 -->
        <id>prod</id>
        <properties>
            <activatedProperties>prod</activatedProperties>
        </properties>
    </profile>
</profiles>
изменить application.yml
spring:
  profiles:
    # 选择环境
    active: @activatedProperties@
Случаи применения
mvn clean package -P prod
mvn clean package -P pre
mvn clean package -P test

   После упаковки вы можете разархивировать его для просмотраapplication.ymlнайдуspring.profiles.active=@activatedProperties@изменилось.


конфигурация журнала

используя журналконфигурация журнала

JenkinsFile

  JenkinsFile определенно для Дженкинса, как следует из названия. Главное — настроить проект в соответствии с тем, как собирать и публиковать в разных средах. Вам нужно понять синтаксис конвейера и как настроить jenkins.JenkinsFileDemo


кодовый адрес

https://gitee.com/huangxunhui/basic_project.git

конец

  Если вы считаете, что это полезно для вас, вы можете прокомментировать и поставить лайк, или вы можете перейти на мою домашнюю страницу, чтобы увидеть, может быть, есть статья, которая вам нравится, вы также можете просто подписаться на нее, спасибо.