leniu-error-handler

leniu-yunshitang-core 异常处理指南

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "leniu-error-handler" with this command: npx skills add xu-cell/ai-engineering-init/xu-cell-ai-engineering-init-leniu-error-handler

leniu-yunshitang-core 异常处理指南

本文档专注于 leniu-tengyun-core 项目的 Java 后端异常处理规范。

快速索引

场景 推荐方式

业务异常 throw new LeException("msg")

带参数异常 throw new LeException("用户 {} 不存在", userId)

参数校验 @Validated(InsertGroup.class)

日志记录 log.error("msg: {}", e.getMessage(), e)

国际化消息 throw new LeException(I18n.getMessage("key"))

  1. 业务异常 - LeException

基本用法

import com.pig4cloud.pigx.common.core.exception.LeException;

// ✅ 基本用法:抛出业务异常 throw new LeException("用户不存在");

// ✅ 带占位符(支持 {} 占位符) throw new LeException("用户 {} 不存在", userId); throw new LeException("订单 {} 状态 {} 无法支付", orderId, status);

// ✅ 条件抛出(手动检查) if (ObjectUtil.isNull(user)) { throw new LeException("用户不存在"); }

// ✅ 参数校验 if (StrUtil.isBlank(bo.getName())) { throw new LeException("名称不能为空"); }

// ✅ 集合判空 if (CollUtil.isEmpty(list)) { throw new LeException("列表不能为空"); }

  1. 国际化 - I18n

国际化消息使用

import net.xnzn.core.common.i18n.I18n;

// 获取国际化消息 String message = I18n.getMessage("user.not.exists");

// 带参数的国际化消息 String message = I18n.getMessage("user.password.retry.limit.exceed", maxRetryCount);

// 在异常中使用 throw new LeException(I18n.getMessage("user.not.exists")); throw new LeException(I18n.getMessage("order.status.invalid", orderStatus));

// 带占位符的国际化 throw new LeException(I18n.getMessage("user.not.found", userId));

  1. 参数校验

使用 @Validated 自动校验

import jakarta.validation.Valid; import jakarta.validation.constraints.*;

// Controller 层校验 @PostMapping("/add") public Long add(@Validated(InsertGroup.class) @RequestBody LeRequest<XxxDTO> request) { // 参数校验失败会自动抛出异常 return xxxService.add(request.getContent()); }

@PostMapping("/update") public void update(@Validated(UpdateGroup.class) @RequestBody LeRequest<XxxDTO> request) { xxxService.update(request.getContent()); }

DTO 类校验注解

@Data @ApiModel("XXX DTO") public class XxxDTO implements Serializable {

@ApiModelProperty(value = "主键ID")
@NotNull(message = "主键ID不能为空", groups = {UpdateGroup.class})
private Long id;

@ApiModelProperty(value = "名称", required = true)
@NotBlank(message = "名称不能为空", groups = {InsertGroup.class, UpdateGroup.class})
@Size(max = 100, message = "名称长度不能超过100个字符")
private String name;

@ApiModelProperty(value = "邮箱")
@Email(message = "邮箱格式不正确")
private String email;

@ApiModelProperty(value = "手机号")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;

@ApiModelProperty(value = "数量")
@Min(value = 0, message = "数量不能小于0")
@Max(value = 9999, message = "数量不能大于9999")
private Integer count;

@ApiModelProperty(value = "开始时间", required = true)
@NotNull(message = "开始时间不能为空", groups = {InsertGroup.class, UpdateGroup.class})
private Date startTime;

}

分组校验定义

// InsertGroup.java public interface InsertGroup {}

// UpdateGroup.java public interface UpdateGroup {}

  1. 日志规范

日志级别

级别 使用场景 示例

ERROR 系统错误、业务异常 数据库连接失败、第三方接口超时

WARN 警告信息、潜在问题 缓存未命中、重试操作

INFO 重要业务流程、操作记录 用户登录、订单创建

DEBUG 开发调试信息 方法入参、中间变量

日志最佳实践

import lombok.extern.slf4j.Slf4j; import cn.hutool.core.util.ObjectUtil;

@Slf4j @Service public class XxxServiceImpl implements XxxService {

@Resource
private XxxMapper xxxMapper;

// ✅ 好的:使用占位符(性能更好)
@Override
public Long add(XxxDTO dto) {
    log.info("开始新增XXX,名称: {}", dto.getName());

    // 业务逻辑
    XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
    xxxMapper.insert(entity);

    log.info("新增XXX成功,ID: {}", entity.getId());
    return entity.getId();
}

// ❌ 不好:字符串拼接(每次都会拼接,即使日志级别不输出)
@Override
public Long addBad(XxxDTO dto) {
    log.info("开始新增XXX,名称: " + dto.getName());  // ❌
    // ...
}

// ✅ 好的:异常日志带堆栈(第三个参数传异常对象)
@Override
public XxxVO getById(Long id) {
    try {
        XxxEntity entity = xxxMapper.selectById(id);
        if (ObjectUtil.isNull(entity)) {
            throw new LeException("记录不存在");
        }
        return BeanUtil.copyProperties(entity, XxxVO.class);
    } catch (Exception e) {
        log.error("查询失败: {}", e.getMessage(), e);  // ✅
        throw new LeException("查询失败");
    }
}

// ❌ 不好:只记录消息,丢失堆栈
@Override
public void bad() {
    try {
        // ...
    } catch (Exception e) {
        log.error("处理失败: {}", e.getMessage());  // ❌
        throw new LeException("处理失败");
    }
}

// ✅ 好的:判断日志级别(避免不必要的序列化开销)
@Override
public void debugMethod(XxxDTO dto) {
    if (log.isDebugEnabled()) {
        log.debug("详细数据: {}", JacksonUtil.writeValueAsString(dto));
    }
    // ...
}

}

  1. Service 层完整示例

import com.pig4cloud.pigx.common.core.exception.LeException; import com.pig4cloud.pigx.common.core.util.LeRequest; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.collection.CollUtil; import lombok.extern.slf4j.Slf4j; import net.xnzn.core.common.i18n.I18n; import net.xnzn.core.xxx.dto.XxxDTO; import net.xnzn.core.xxx.mapper.XxxMapper; import net.xnzn.core.xxx.model.XxxEntity; import net.xnzn.core.xxx.vo.XxxVO;

import javax.annotation.Resource; import java.util.List;

/**

  • XXX 服务实现 */ @Slf4j @Service public class XxxServiceImpl implements XxxService {

    @Resource private XxxMapper xxxMapper;

    @Override public Long add(XxxDTO dto) { log.info("开始新增XXX,类型: {}", dto.getXXXType());

     // 参数校验
     if (StrUtil.isBlank(dto.getName())) {
         throw new LeException(I18n.getMessage("name.required"));
     }
    
     // 业务校验
     if (dto.getStartTime().after(dto.getEndTime())) {
         throw new LeException(I18n.getMessage("time.invalid"));
     }
    
     // 转换并保存
     XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
     entity.setDelFlag(2); // 正常状态
    
     xxxMapper.insert(entity);
    
     log.info("新增XXX成功,ID: {}", entity.getId());
     return entity.getId();
    

    }

    @Override public void update(XxxDTO dto) { if (ObjectUtil.isNull(dto.getId())) { throw new LeException(I18n.getMessage("id.required")); }

     XxxEntity exist = xxxMapper.selectById(dto.getId());
     if (ObjectUtil.isNull(exist)) {
         throw new LeException(I18n.getMessage("record.not.exists"));
     }
    
     XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
     xxxMapper.updateById(entity);
    
     log.info("更新XXX成功,ID: {}", dto.getId());
    

    }

    @Override public void delete(Long id) { if (ObjectUtil.isNull(id)) { throw new LeException(I18n.getMessage("id.required")); }

     int rows = xxxMapper.deleteById(id);
     if (rows == 0) {
         throw new LeException(I18n.getMessage("delete.failed"));
     }
    
     log.info("删除XXX成功,ID: {}", id);
    

    } }

  1. 用户友好提示

错误提示规范

// ✅ 好的:用户友好提示 throw new LeException("订单已发货,无法取消"); throw new LeException("库存不足,请减少购买数量"); throw new LeException("验证码已过期,请重新获取"); throw new LeException("该用户名已被注册,请换一个试试");

// ❌ 不好:技术术语 throw new LeException("order.status.invalid"); throw new LeException("NullPointerException at line 123"); throw new LeException("数据库连接失败"); throw new LeException("Duplicate entry for key 'uk_username'");

  1. 常见错误对比

❌ 错误写法

// 错误 1: 使用 RuoYi 的 ServiceException throw new ServiceException("用户不存在"); // ❌ 应该用 LeException

// 错误 2: 使用 RuoYi 的 MessageUtils throw new ServiceException(MessageUtils.message("user.not.exists")); // ❌ 应该用 I18n.getMessage()

// 错误 3: 使用 javax.validation(JDK 21 应用 jakarta.validation) import javax.validation.Valid; // ❌ 错误

// 错误 4: 使用 RuoYi 的 AddGroup/EditGroup @Validated(AddGroup.class) // ❌ 应该用 InsertGroup.class

// 错误 5: 使用 RuoYi 的 StringUtils StringUtils.isBlank(str); // ❌ 应该用 StrUtil.isBlank()

// 错误 6: 使用 RuoYi 的 CollUtil CollUtil.isEmpty(list); // ❌ 应该用 CollUtil.isEmpty()(Hutool)

// 错误 7: 日志不带堆栈 log.error("处理失败: {}", e.getMessage()); // ❌

// 错误 8: 字符串拼接日志 log.info("处理用户: " + userId); // ❌

✅ 正确写法

// 正确 1: 使用 leniu 的 LeException throw new LeException("用户不存在"); // ✅

// 正确 2: 使用 leniu 的 I18n throw new LeException(I18n.getMessage("user.not.exists")); // ✅

// 正确 3: 使用 Jakarta Validation import jakarta.validation.Valid; // ✅

// 正确 4: 使用 leniu 的 InsertGroup/UpdateGroup @Validated(InsertGroup.class) // ✅

// 正确 5: 使用 Hutool 的 StrUtil StrUtil.isBlank(str); // ✅

// 正确 6: 使用 Hutool 的 CollUtil CollUtil.isEmpty(list); // ✅

// 正确 7: 日志带堆栈 log.error("处理失败: {}", e.getMessage(), e); // ✅

// 正确 8: 日志使用占位符 log.info("处理用户: {}", userId); // ✅

  1. 工具类选择

场景 RuoYi-Vue-Plus leniu-tengyun-core

异常类 ServiceException

LeException

国际化 MessageUtils.message(key)

I18n.getMessage(key)

静态工厂 无(必须 new ) 无(必须 new )

对象判空 ObjectUtil.isNull()

ObjectUtil.isNull()

集合判空 CollUtil.isEmpty()

CollUtil.isEmpty()

字符串判空 StringUtils.isBlank()

StrUtil.isBlank()

日志框架 @Slf4j

@Slf4j

  1. 检查清单

使用异常处理前必须检查:

  • 业务异常使用 new LeException() ?

  • 条件判断使用 if + ObjectUtil.isNull() ?

  • 参数校验使用 @Validated(InsertGroup.class) ?

  • 异常消息是否使用国际化(I18n.getMessage() )?

  • 日志记录异常堆栈(log.error("msg: {}", e.getMessage(), e) )?

  • 日志使用占位符 {} ,不使用字符串拼接?

  • 重要操作记录 INFO 日志?

  • 错误提示使用用户友好语言?

  • 是否使用 Jakarta Validation?

  • 分组是否正确(InsertGroup/UpdateGroup)?

  • 是否使用正确的工具类(StrUtil/CollUtil)?

  1. 快速对照表

❌ 错误写法 ✅ 正确写法

throw new ServiceException("msg")

throw new LeException("msg")

MessageUtils.message("key")

I18n.getMessage("key")

import javax.validation.Valid

import jakarta.validation.Valid

@Validated(AddGroup.class)

@Validated(InsertGroup.class)

StringUtils.isBlank(str)

StrUtil.isBlank(str)

log.error("失败: " + e.getMessage())

log.error("失败: {}", e.getMessage(), e)

log.info("用户: " + userId)

log.info("用户: {}", userId)

throw new LeException("DB error")

throw new LeException("数据保存失败,请重试")

参考代码位置

类型 路径

Service 示例 core-attendance/.../service/impl/AttendanceLeaveInfoServiceImpl.java

项目路径:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

loki-log-query

No summary provided by upstream source.

Repository SourceNeeds Review
General

scheduled-jobs

No summary provided by upstream source.

Repository SourceNeeds Review
General

openspec-new-change

No summary provided by upstream source.

Repository SourceNeeds Review