组件地址
<dependency>
<groupId>org.opengoofy.index12306</groupId>
<artifactId>index12306-convention-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
组件功能
异常码抽象和公共异常码
异常码接口抽象
package org.opengoofy.index12306.framework.starter.convention.errorcode;
/**
* 平台错误码
*
*/
public interface IErrorCode {
/**
* 错误码
*/
String code();
/**
* 错误信息
*/
String message();
}
封装常用的公共异常码
package org.opengoofy.index12306.framework.starter.convention.errorcode;
/**
* 基础错误码定义
*
* @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
*/
public enum BaseErrorCode implements IErrorCode {
// ========== 一级宏观错误码 客户端错误 ==========
CLIENT_ERROR("A000001", "用户端错误"),
// ========== 二级宏观错误码 用户注册错误 ==========
USER_REGISTER_ERROR("A000100", "用户注册错误"),
USER_NAME_VERIFY_ERROR("A000110", "用户名校验失败"),
USER_NAME_EXIST_ERROR("A000111", "用户名已存在"),
USER_NAME_SENSITIVE_ERROR("A000112", "用户名包含敏感词"),
USER_NAME_SPECIAL_CHARACTER_ERROR("A000113", "用户名包含特殊字符"),
PASSWORD_VERIFY_ERROR("A000120", "密码校验失败"),
PASSWORD_SHORT_ERROR("A000121", "密码长度不够"),
PHONE_VERIFY_ERROR("A000151", "手机格式校验失败"),
// ========== 二级宏观错误码 系统请求缺少幂等Token ==========
IDEMPOTENT_TOKEN_NULL_ERROR("A000200", "幂等Token为空"),
IDEMPOTENT_TOKEN_DELETE_ERROR("A000201", "幂等Token已被使用或失效"),
// ========== 一级宏观错误码 系统执行出错 ==========
SERVICE_ERROR("B000001", "系统执行出错"),
// ========== 二级宏观错误码 系统执行超时 ==========
SERVICE_TIMEOUT_ERROR("B000100", "系统执行超时"),
// ========== 一级宏观错误码 调用第三方服务出错 ==========
REMOTE_ERROR("C000001", "调用第三方服务出错");
private final String code;
private final String message;
BaseErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
@Override
public String code() {
return code;
}
@Override
public String message() {
return message;
}
}
异常分类
既然定义了异常码,就需要知道异常是如何分类的
抽象异常
由于客户端异常、服务端异常、远程调用异常都继承于运行时异常,则三种异常必然会有相似之处,因此统一抽象层AbstractException
package org.opengoofy.congomall.springboot.starter.convention.exception;
import com.google.common.base.Strings;
import lombok.Getter;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.IErrorCode;
import java.util.Optional;
/**
* 抽象项目中三类异常体系,客户端异常、服务端异常以及远程服务调用异常
*
* @see ClientException
* @see ServiceException
* @see RemoteException
*/
@Getter
public abstract class AbstractException extends RuntimeException {
public final String errorCode;
public final String errorMessage;
public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable);
this.errorCode = errorCode.code();
this.errorMessage = Optional.ofNullable(Strings.emptyToNull(message)).orElse(errorCode.message());
}
}
客户端异常
参数校验失败异常、幂等异常.....
package org.opengoofy.congomall.springboot.starter.convention.exception;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.BaseErrorCode;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.IErrorCode;
/**
* 客户端异常
*
*/
public class ClientException extends AbstractException {
public ClientException(IErrorCode errorCode) {
this(null, null, errorCode);
}
public ClientException(String message) {
this(message, null, BaseErrorCode.CLIENT_ERROR);
}
public ClientException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}
public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}
@Override
public String toString() {
return "ClientException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}
服务端异常
消息模版不合规、消息内容包含关键字......
package org.opengoofy.congomall.springboot.starter.convention.exception;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.BaseErrorCode;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.IErrorCode;
/**
* 服务端异常
*
*/
public class ServiceException extends AbstractException {
public ServiceException(String message) {
this(message, null, BaseErrorCode.SERVICE_ERROR);
}
public ServiceException(IErrorCode errorCode) {
this(null, errorCode);
}
public ServiceException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}
public ServiceException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}
@Override
public String toString() {
return "ServiceException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}
远程调用异常
package org.opengoofy.congomall.springboot.starter.convention.exception;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.IErrorCode;
/**
* 远程服务调用异常
*
*/
public class RemoteException extends AbstractException {
public RemoteException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}
public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}
@Override
public String toString() {
return "RemoteException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}
封装分页对象
分页请求参数对象(入参)
package org.opengoofy.index12306.framework.starter.convention.page;
import lombok.Data;
/**
* 分页请求对象
*
* <p> {@link PageRequest}、{@link PageResponse}
* 可以理解是防腐层的一种实现,不论底层 ORM 框架,对外分页参数属性不变
*
*/
@Data
public class PageRequest {
/**
* 当前页
*/
private Long current = 1L;
/**
* 每页显示条数
*/
private Long size = 10L;
}
分页返回参数对象(出参)
package org.opengoofy.index12306.framework.starter.convention.page;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 分页返回对象
*
* <p> {@link PageRequest}、{@link PageResponse}
* 可以理解是防腐层的一种实现,不论底层 ORM 框架,对外分页参数属性不变
*
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 当前页
*/
private Long current;
/**
* 每页显示条数
*/
private Long size = 10L;
/**
* 总数
*/
private Long total;
/**
* 查询数据列表
*/
private List<T> records = Collections.emptyList();
public PageResponse(long current, long size) {
this(current, size, 0);
}
public PageResponse(long current, long size, long total) {
if (current > 1) {
this.current = current;
}
this.size = size;
this.total = total;
}
public PageResponse setRecords(List<T> records) {
this.records = records;
return this;
}
public <R> PageResponse<R> convert(Function<? super T, ? extends R> mapper) {
List<R> collect = this.getRecords().stream().map(mapper).collect(Collectors.toList());
return ((PageResponse<R>) this).setRecords(collect);
}
}
提供 MyBatisPlus 分页和自定义分页对象之间的转换工具类,帮助开发提高开发效率
package org.opengoofy.index12306.framework.starter.database.toolkit;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.opengoofy.index12306.framework.starter.common.toolkit.BeanUtil;
import org.opengoofy.index12306.framework.starter.convention.page.PageRequest;
import org.opengoofy.index12306.framework.starter.convention.page.PageResponse;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 分页工具类
*
*/
public class PageUtil {
/**
* {@link PageRequest} to {@link Page}
*/
public static Page convert(PageRequest pageRequest) {
return convert(pageRequest.getCurrent(), pageRequest.getSize());
}
/**
* {@link PageRequest} to {@link Page}
*/
public static Page convert(long current, long size) {
return new Page(current, size);
}
/**
* {@link IPage} to {@link PageResponse}
*/
public static PageResponse convert(IPage iPage) {
return buildConventionPage(iPage);
}
/**
* {@link IPage} to {@link PageResponse}
*/
public static <TARGET, ORIGINAL> PageResponse<TARGET> convert(IPage<ORIGINAL> iPage, Class<TARGET> targetClass) {
iPage.convert(each -> BeanUtil.convert(each, targetClass));
return buildConventionPage(iPage);
}
/**
* {@link IPage} to {@link PageResponse}
*/
public static <TARGET, ORIGINAL> PageResponse<TARGET> convert(IPage<ORIGINAL> iPage, Function<? super ORIGINAL, ? extends TARGET> mapper) {
List<TARGET> targetDataList = iPage.getRecords().stream()
.map(mapper)
.collect(Collectors.toList());
return PageResponse.<TARGET>builder()
.current(iPage.getCurrent())
.size(iPage.getSize())
.records(targetDataList)
.total(iPage.getTotal())
.build();
}
/**
* {@link IPage} build to {@link PageResponse}
*/
private static PageResponse buildConventionPage(IPage iPage) {
return PageResponse.builder()
.current(iPage.getCurrent())
.size(iPage.getSize())
.records(iPage.getRecords())
.total(iPage.getTotal())
.build();
}
}
封装公共响应对象
定义返回体
package org.opengoofy.congomall.springboot.starter.convention.result;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 全局返回对象
*/
@Data
@Accessors(chain = true)
public class Result<T> implements Serializable {
private static final long serialVersionUID = 5679018624309023727L;
/**
* 正确返回码
*/
public static final String SUCCESS_CODE = "0";
/**
* 返回码
*/
private String code;
/**
* 返回消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 请求ID
*/
private String requestId;
public boolean isSuccess() {
return SUCCESS_CODE.equals(code);
}
}
同时,为了方便 Result 的创建,通过一个 Results 工具类用来创建返回类。
package org.opengoofy.congomall.springboot.starter.web;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.opengoofy.congomall.springboot.starter.convention.errorcode.BaseErrorCode;
import org.opengoofy.congomall.springboot.starter.convention.exception.AbstractException;
import org.opengoofy.congomall.springboot.starter.convention.result.Result;
import java.util.Optional;
/**
* 全局返回对象构造器
*/
public final class Results {
/**
* 构造成功响应
*
* @return
*/
public static Result<Void> success() {
return new Result<Void>()
.setCode(Result.SUCCESS_CODE)
.setRequestId(TraceContext.traceId());
}
/**
* 构造带返回数据的成功响应
*
* @param data
* @param <T>
* @return
*/
public static <T> Result<T> success(T data) {
return new Result<T>()
.setCode(Result.SUCCESS_CODE)
.setRequestId(TraceContext.traceId())
.setData(data);
}
/**
* 构建服务端失败响应
*
* @return
*/
protected static Result<Void> failure() {
return new Result<Void>()
.setCode(BaseErrorCode.SERVICE_ERROR.code())
.setRequestId(TraceContext.traceId())
.setMessage(BaseErrorCode.SERVICE_ERROR.message());
}
/**
* 通过 {@link AbstractException} 构建失败响应
*
* @param abstractException
* @return
*/
protected static Result<Void> failure(AbstractException abstractException) {
String errorCode = Optional.ofNullable(abstractException.getErrorCode())
.orElse(BaseErrorCode.SERVICE_ERROR.code());
String errorMessage = Optional.ofNullable(abstractException.getErrorMessage())
.orElse(BaseErrorCode.SERVICE_ERROR.message());
return new Result<Void>()
.setCode(errorCode)
.setRequestId(TraceContext.traceId())
.setMessage(errorMessage);
}
/**
* 通过 errorCode、errorMessage 构建失败响应
*
* @param errorCode
* @param errorMessage
* @return
*/
protected static Result<Void> failure(String errorCode, String errorMessage) {
return new Result<Void>()
.setCode(errorCode)
.setRequestId(TraceContext.traceId())
.setMessage(errorMessage);
}
}
具体参数
响应示例
{
"code": "0",
"message": null,
"data": {
"productBrand": {
"id": 1477055818267025408,
"name": "小米",
"desc": "小米是一家以手机、智能硬件和IoT平台为核心的互联网公司,以智能手机、智能电视、笔记本等丰富的产品与服务。致力于让全球每个人都能享受科技带来的美好生活",
"pic": null,
"sort": 1
},
"productSpu": {
"id": 1477055850256982016,
"categoryId": 1477055818388660224,
"brandId": 1477055818267025408,
"name": "小米MIX Fold2 轻薄折叠 骁龙8+旗舰处理器 徕卡光学镜头 自研微水滴形态转轴 12GB+512GB 月影黑 5G手机",
"productSn": "100033890205",
"pic": "",
"photoAlbum": null,
"price": 9999,
"promotionPrice": 9988,
"promotionStartTime": "2022-09-14T13:02:26.000+00:00",
"promotionEndTime": "2022-09-14T13:02:26.000+00:00",
"subTitle": "轻薄折叠机,整机轻至262g,展开厚度5.4mm,骁龙8+旗舰处理器!【点击抢购小米11Ultra】",
"sales": 0,
"unit": "件",
"detail": null,
"publishStatus": 0,
"newStatus": 0,
"recommandStatus": 0
},
"productSkus": [
{
"id": 1477056250724933632,
"productId": 1477055850256982016,
"price": 9999,
"stock": 99526,
"lockStock": 474,
"pic": "",
"attribute": "[{\"key\":\"外观\",\"value\":\"月影黑\"},{\"key\":\"版本\",\"value\":\"12GB+256GB\"}]"
},
{
"id": 1477056250741710848,
"productId": 1477055850256982016,
"price": 11999,
"stock": 299,
"lockStock": 5,
"pic": "",
"attribute": "[{\"key\":\"外观\",\"value\":\"月影黑\"},{\"key\":\"版本\",\"value\":\"12GB+512GB\"}]"
},
{
"id": 1477056338104868864,
"productId": 1477055850256982016,
"price": 9999,
"stock": 188,
"lockStock": 2,
"pic": "",
"attribute": "[{\"key\":\"外观\",\"value\":\"星耀金\"},{\"key\":\"版本\",\"value\":\"12GB+256GB\"}]"
},
{
"id": 1477056338335555584,
"productId": 1477055850256982016,
"price": 11999,
"stock": 299,
"lockStock": 4,
"pic": "",
"attribute": "[{\"key\":\"外观\",\"value\":\"星耀金\"},{\"key\":\"版本\",\"value\":\"12GB+512GB\"}]"
}
]
},
"requestId": "4a2c99691f1f4d7dbc944ddbcf60108d.423.16777712688418813",
"success": true
}