置入正题
一、项目结构设计
1、三层架构
controller表现层
service业务逻辑层
dao数据访问层
还有写一个贯穿整个项目的common层,用来处理项目中各个区域的内容
二、通用处理
1.错误码
定义错误码,用来处理程序在运行过程中出现了什么错误,方面前后端进行捕捉,从而进行修改代码,可以提高开发效率
本次错误码细分到表现层controller与业务逻辑层service,可以更精确知道错误点
设置错误信息格式,如下代码
@Data
public class ErrorCode {
private final Integer code;
private final String msg;
public ErrorCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
GlobalErrorCodeConstants 类用来出来全局统一的信息
//此类是处理全局有可能发生的错误
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode(200, "成功!");
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常!");
ErrorCode UNKONW = new ErrorCode(999, "未知错误!");
}
ControllerErrorCodeConstants 这个类是进行出来controller层发生的错误信息
public interface ControllerErrorCodeConstants {
//--------- 人员错误码 -------------
//--------- 活动模块错误码 -------------
//--------- 奖品模块错误码 -------------
//--------- 抽奖错误码 -------------
}
ServiceErrorCodeConstants 这个类是进行出来service层发生的错误信息
public interface ServiceErrorCodeConstants {
//--------- 人员错误码 -------------
//--------- 活动模块错误码 -------------
//--------- 奖品模块错误码 -------------
//--------- 抽奖错误码 -------------
}
目前还没有写controller与service内容,暂时先这个样子写,后续补充内容
不要写dao层处理内容,因为数据库会给出许多错误,但是我们可以在其他层来处理dao层给出的错误信息,比如dao层会给出14个不一样的信息,我们在service层就可以同义处理说“数据获取识别”。
2.自定义异常类
此项目设置两个自定义异常,一个是针对表现层controller来进行设计,还有一个是针对业务逻辑层service进行设置
//不写@EqualsAndHashCode(callSuper = true)可以会出现问题
@Data
@EqualsAndHashCode(callSuper = true) // 正确继承 RuntimeException 的 equals 和 hashCode
public class ControllerException extends RuntimeException {
private int code; // 错误码
private String message; // 错误信息
public ControllerException() {
}
public ControllerException(Integer code, String message) {
this.code = code;
this.message = message;
}
public ControllerException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
}
//不写@EqualsAndHashCode(callSuper = true)可以会出现问题
@Data
@EqualsAndHashCode(callSuper = true) // 正确继承 RuntimeException 的 equals 和 hashCode
public class ServiceException extends RuntimeException {
private int code; // 错误码
private String message; // 错误信息
public ServiceException() {}
public ServiceException(int code, String message) {
this.code = code;
this.message = message;
}
public ServiceException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
}
自定义异常类闯入了一个ErrorCode类也是可以进行解析的,这样设置可以在写代码的时候更加灵活,不写也是可以的,建议写上。
3.commonResult<T>统一处理
设置统一处理方面前端进行获取数据,前端只需要根据一个格式就可以获取到后端返回给前端的内容,比如前端接收到Result这个类型的值,前端可以像下面代码这样获取数据。
//获取后端返回的数据 //获取后端返回的时间 //获取真假
Result.data Result.time Result.Boolean
这样前端很舒服,因为前端只需要获取Result这个类就行,如果后端不进行统一处理数据,前端需要就需要分别3此获取到data,time,Boolean这样让前端耗费大量精力。
为什么要封装?
1. 统⼀的返回格式:确保客⼾端收到的响应具有⼀致的结构,⽆论处理的是哪个业务逻辑。
2. 错误码和消息:提供错误码(code)和错误消息(msg),帮助客⼾端快速识别和处理错误。
3. 泛型数据返回:使⽤泛型 <T> 允许返回任何类型的数据,增加了返回对象的灵活性。
4. 静态⽅法:提供了 error() 和 success() 静态⽅法,⽅便快速创建错误或成功的响应对象。
5. 错误码常量集成:通过 ErrorCode 和 GlobalErrorCodeConstants 使⽤预定义的错误码,保持错误码的⼀致性和可维护性。
6. 序列化:实现了 Serializable 接⼝,使得 CommonResult<T> 对象可以被序列化为多种格式,如
JSON或XML,⽅便⽹络传输。
7. 业务逻辑解耦:将业务逻辑与API的响应格式分离,使得后端开发者可以专注于业务逻辑的实现,⽽不必关⼼如何构建HTTP响应。
8. 客⼾端友好:客⼾端开发者可以通过统⼀的接⼝获取数据和错误信息,⽆需针对每个API编写特定的错误处理逻辑。
/**
* 通用返回结果类,用于封装接口返回的数据结构。
* 包含:状态码、提示信息、具体数据。
*
* @param <T> 泛型,用于指定返回数据的具体类型。
*/
@Data
public class CommonResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;
/**
* 成功返回结构的静态方法。
* code 设置为 200(成功状态码),msg 为空,data 为传入的数据。
*
* @param data 实际返回的数据
* @param <T> 数据类型
* @return 封装好的 CommonResult 对象
*/
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.msg = "";
result.data = data;
return result;
}
/**
* 错误返回结构的静态方法。
* code 为传入的错误码,msg 为错误信息。
*
* @param code 错误码
* @param msg 错误提示信息
* @param <T> 数据类型(一般为 null)
* @return 封装好的 CommonResult 对象
*/
public static <T> CommonResult<T> error(Integer code, String msg) {
Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 不是错误的异常");
CommonResult<T> result = new CommonResult<>();
result.code = code;
result.msg = msg;
return result;
}
/**
* 根据统一的错误码对象(ErrorCode)进行错误返回。
* 实际调用的是上面的 error(code, msg) 方法。
*
* @param errorCode 封装好的错误码对象(包含 code 和 msg)
* @param <T> 数据类型
* @return 封装好的 CommonResult 对象
*/
public static <T> CommonResult<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMsg());
}
}
4、序列化与反序列化
序列化是将对象类型的数据转为JSON格式,JSON好处有持久化存储,方便网络传输,方面进程通讯等等好处
序列化使用单例模式,这个模式的好处可以减少系统开销,在获取此类的时候就直接创建了ObjectMapper ,这个类可以进行对象与JSON的转化
此代码的优点有四点:
1.使用单例模式管理 ObjectMapper
,避免重复创建。
2.封装通用异常处理逻辑,提高复用性。
3.支持复杂类型(如集合)的反序列化。
4.接口简洁、易用,适合在项目中全局调用。
public class JacksonUtils {
// 私有构造函数,防止实例化
private JacksonUtils() {}
/**
* 单例 ObjectMapper,用于序列化/反序列化 JSON
*/
private final static ObjectMapper OBJECT_MAPPER;
// 静态初始化 ObjectMapper 实例
static {
OBJECT_MAPPER = new ObjectMapper();
}
// 获取 ObjectMapper 实例
private static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
/**
* 封装执行逻辑(无异常类型传入)
* @param parser 实际执行的逻辑
*/
private static <T> T tryParse(Callable<T> parser) {
return tryParse(parser, JsonParseException.class);
}
/**
* 封装执行逻辑(传入需要拦截的异常类型)
* 如果抛出的异常类型是指定的,就抛 JsonParseException,否则抛 IllegalStateException
*/
private static <T> T tryParse(Callable<T> parser, Class<? extends Exception> check) {
try {
return parser.call();
} catch (Exception ex) {
if (check.isAssignableFrom(ex.getClass())) {
throw new JsonParseException(ex);
}
throw new IllegalStateException(ex);
}
}
/**
* 将对象序列化成 JSON 字符串
*/
public static String writeValueAsString(Object object) {
return JacksonUtils.tryParse(() -> JacksonUtils.getObjectMapper().writeValueAsString(object));
}
/**
* 将 JSON 字符串反序列化为对象
*/
public static <T> T readValue(String content, Class<T> valueType) {
return JacksonUtils.tryParse(() -> JacksonUtils.getObjectMapper().readValue(content, valueType));
}
/**
* 将 JSON 字符串反序列化为 List<T> 类型的集合
*/
public static <T> T readListValue(String content, Class<?> paramClasses) {
JavaType javaType = JacksonUtils.getObjectMapper().getTypeFactory()
.constructParametricType(List.class, paramClasses);
return JacksonUtils.tryParse(() -> JacksonUtils.getObjectMapper().readValue(content, javaType));
}
}
我们将序列化的代码演示一下
@Test
void JacksonUtileTest() {
CommonResult<String> result = CommonResult.success("success");
String str = null;
//序列化
str = JacksonUtils.writeValueAsString(result);
System.out.println(str);
//反序列化
result = JacksonUtils.readValue(str, CommonResult.class);
System.out.println(result.getData());
//序列化List
List<CommonResult<String>> commonResults = Arrays.asList(CommonResult.success("success1")
, CommonResult.success("success2"));
str = JacksonUtils.writeValueAsString(commonResults);
System.out.println(str);
//反序列化List
commonResults = JacksonUtils.readListValue(str, CommonResult.class);
for (CommonResult<String> commonResult : commonResults) {
System.out.println(commonResult.getData());
}
}
待续...