定义 i18n 资源

src\main\resources\i18n\messages.properties

biz.data.exist = 数据已存在

UserVO.names.error    = 账号输入错误
UserVO.names.NotNull  = 账号不能为空
UserVO.names.Size     = 账号长度必须是{min}-{max}个字符

文件夹里包含三个属性文件: messages.properties,messages_en_US.properties和messages_zh_CN.properties,
其中 messages.properties 是个默认文件,即使为空也要有,否则MessageSource加会变成[Empty MessageSource]
切记切记 所有文件编码必需是 UTF-8 格式

application.yaml配置

spring:
  messages:
    #相对路径 开头请勿添加斜杠, 注意要放一个messages.properties 否则会加载失败,建议直接把这个当成中文来用
    basename: i18n/messages
    encoding: UTF-8

定义错误枚举类

src\main\java\cn\flyrise\pai\demo\exception\BusinessErrors.java

划重点:

  • DUPLICATE_KEY(“1001”, “数据已存在”, “biz.data.exist”)
  • 1001 :错误代码,取值范围 1001-9999 (建议取1001-4999开始,与 InvalidCode、VO校验 区分开)
  • 数据已存在 :默认错误信息,当 biz.data.exist 在 messages.properties 中找不到时使用
  • biz.data.exist :对比应 messages.properties 中定义的Key

import cn.flyrise.starter.util.I18nUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 业务错误代码定义枚举类,错误代码 1001-4999
 * <br />
 * 放于Service层在处理逻辑时根据判断抛出相应异常,ex:
 * <pre>
 *  {@code
 *
 *     public boolean insert(UserPO userPO) {
 *         if (StrUtil.isNotEmpty(userPO.getId()) && userMapper.selectById(userPO.getId()) != null) {
 *             throw new DemoBizException(BusinessErrors.DUPLICATE_KEY);
 *         }
 *
 *         return SqlHelper.retBool(userMapper.insert(userPO));
 *     }
 *  }
 * </pre>
 *
 * @author Joe
 * @date 2020/5/8 星期五 16:30
 */
public enum BusinessErrors {
    //手动指定错误码
    DUPLICATE_KEY("1001", "数据已存在", "biz.data.exist")

    //... 其他错误码的定义
    ;

    /**
     * 错误代码 1001-4999
     */
    private String code;
    /**
     * 错误消息
     */
    private String msg;
    /**
     * 错误消息国际化key
     */
    private String key;

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

    BusinessErrors(String code, String msg, String key) {
        this.code = code;
        this.msg = msg;
        this.key = key;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        String msgStr = I18nUtil.get(key, msg);
        if (key != null && !key.equals(msgStr)) {
            return msgStr;
        }
        return msg;
    }

    /**
     * 获取参数化的msg值
     */
    public String format(Object... params) {
        String msg = this.getMsg();
        if (msg != null && msg.contains(StrUtil.EMPTY_JSON)) {
            return StrUtil.format(msg, params);
        }
        return msg;
    }

}

简化使用方案示例:
由于要编key 对有些人来说太难了,所以采用了一个取巧的方案如下,构造方法可以自行发挥


import cn.flyrise.common.core.domain.InvalidCode;
import cn.flyrise.common.core.utils.I18nUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public enum BusinessErrors {
    /**
     * 通用
     */
    SUCCESS("1000", "操作成功", "success"),

    //手动指定错误码
    // error.definition.1001
    ERR_1001("model", "创建模型失败:{}"),
    ERR_1002("model", "模型已存在,不能重复创建"),
    ERR_1003("model", "模型不存在,无法更新"),
    ERR_1004("model", "套件标识不能为空"),
    ERR_1005("model", "保存模型失败:{}"),
    ERR_1006("model", "保存读取Bpmn Xml失败"),
    ERR_1007("model", "查无model"),
    ERR_1008("model", "删除模型失败:{}"),
    ERR_1009("model", "导出model的xml文件失败:modelId={}"),
    ERR_1010("model", "当前套件不存在流程"),
    ERR_1011("model", "租户({})安装或更新套件({})流程失败"),
    ERR_1012("model", "转换BPMN XMl异常:{}"),
    ERR_1013("model", "查无模型数据"),
    ERR_1014("model", "未配置表单关联数据"),
    ERR_1015("model", "已发版模型不允许删除"),
    ERR_1016("model", "模型名称已存在,不能重复创建"),

    // error.definition.2001
    ERR_2001("definition", "查询失败:{}"),
    ERR_2002("definition", "删除失败,存在运行中的流程实例"),
    ERR_2003("definition", "查无流程定义:{}"),
    ERR_2004("definition", "还原失败"),

    // error.process.3001
    ERR_3001("process", "未发布流程定义或已挂起({}/{}/{}),无法发起流程"),
    ERR_3002("process", "流程定义({})已被挂起,无法发起流程"),
    ERR_3003("process", "查无任务无法办理:{}"),
    ERR_3004("process", "任务已挂起无法办理:{}"),
    ERR_3005("process", "办理失败:{}"),
    ERR_3006("process", "查无流程实例:{}"),
    ERR_3007("process", "生成流程图失败:{}"),
    ERR_3008("process", "查无流程定义:({}/{})"),

    //... 其他错误码的定义
    // error.todo.4001
    ERR_4000("todo", "任务不存在"),
    ERR_4001("todo", "当前用户信息异常"),
    ERR_4002("todo", "草稿文件已失效,请刷新"),
    ERR_4003("todo", "获取下一节点信息参数异常"),
    ERR_4004("todo", "当前流程实例已经结束"),
    ERR_4005("todo", "当前任务已经结束(已办理,被终止)"),
    ERR_4006("todo", "当前任务规则异常"),
    ERR_4007("todo", "未安装或已失效"),
    ERR_4008("todo", "条件流参数异常"),
    ERR_4009("todo", "规则中心条件流API异常{}"),

    ERR_5000("node", "会签节点,需等待所有人办理"),

    ERR_6000("comment", "意见不存在,或已被删除"),
    ERR_6001("comment", "同个审批人,自动办理"),

    ERR_7000("fastReply", "快捷回复不能超过{}条"),
    ERR_7001("fastReply", "已存在{}该记录"),
    ERR_7002("fastReply", "快捷回复不能超过{}个字符"),

    ERR_8000("body", "已办理过的任务不允许补充正文"),
    ERR_8001("body", "不是候选人,不能补充正文"),
    ERR_8002("body", "补充正文不存在,或被删除"),
    ERR_8003("body", "已办理过的任务不允许删除补充正文"),
    ERR_8004("body", "无权限删除"),
    ;

    /**
     * 错误代码 1001-4999
     */
    private final String code;
    /**
     * 错误消息
     */
    private final String msg;

    /**
     * 错误消息国际化key
     */
    private final String key;

    /**
     * ERR_nnnn 的命名方式使用
     *
     * @param module 模块代码
     * @param msg    内容
     */
    BusinessErrors(String module, String msg) {
        this.msg = msg;
        String name = this.name();
        if (!name.contains("_")) {
            throw new RuntimeException("命名不符合规范, 如:ERR_1001、 MSG_1001、 VIEW_5001");
        }
        String[] names = name.split("_");
        if (!ReUtil.isMatch("^[a-zA-Z]+$", names[0]) || !NumberUtil.isInteger(names[1])) {
            throw new RuntimeException("命名不符合规范, 如:ERR_1001、 MSG_1001、 VIEW_5001");
        }
        name = names[0];
        this.code = names[1];
        if ("ERR".equals(name)) {
            name = "error";
        } else if ("MSG".equals(name)) {
            name = "message";
        } else {
            name = name.toLowerCase();
        }
        this.key = CharSequenceUtil.format("{}.{}.{}", name, module, this.code);
    }

    /**
     * 自定义完整内容
     *
     * @param code 错误代码
     * @param msg  内容
     * @param key  配置文件Key
     */
    BusinessErrors(String code, String msg, String key) {
        this.code = code;
        this.msg = msg;
        this.key = key;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        String msgStr = I18nUtil.get(key, msg);
        if (key != null && !key.equals(msgStr)) {
            return msgStr;
        }
        return msg;
    }

    public String getKey() {
        return key;
    }

    /**
     * 获取参数化的msg值
     */
    public String format(Object... params) {
        String msg = this.getMsg();
        if (msg != null && msg.contains(StrUtil.EMPTY_JSON)) {
            return CharSequenceUtil.format(msg, params);
        }
        return msg;
    }

    /**
     * 生成properties时打开运行,内容将会输出出到控制台,即可复制使用
     */
    /*public static void main(String[] args) {
        List<String> msgs = new ArrayList<>();
        Optional.ofNullable(ClassUtil.scanPackage("cn.flyrise.pai"))
                .ifPresent(classes -> {
                    for (Class<?> clazz : classes) {
                        Optional.ofNullable(ClassUtil.getDeclaredFields(clazz))
                                .ifPresent(fields -> {
                                    for (Field field : fields) {
                                        Optional.ofNullable(field.getAnnotation(InvalidCode.class))
                                                .ifPresent(invalidCode -> {
                                                    String message = invalidCode.message();
                                                    if (CharSequenceUtil.isBlank(message)) {
                                                        message = "invalid.code.".concat(invalidCode.value());
                                                    } else if(message.startsWith("{") && message.endsWith("}")){
                                                        message = message.substring(1, message.length() - 1);
                                                    }
                                                    msgs.add(message.concat("=").concat(invalidCode.desc()));
                                                });
                                    }
                                });
                    }
                });
        msgs.stream().sorted().collect(Collectors.toCollection(LinkedHashSet::new)).forEach(System.out::println);
        for (BusinessErrors value : BusinessErrors.values()) {
            System.out.println(value.getKey() + "=" + value.getMsg());
        }
    }*/
}

定义业务异常类

要求每个业务工程有一个独立的业务异常类,继承于BizException

代码除了类名直接照抄

import cn.flyrise.common.core.domain.BizException;

/**
 * 演示模块异常类
 *
 * @author Joe
 * @date 2020/5/8 星期五 16:30
 */
public class DemoBizException extends BizException {

    public DemoBizException(BusinessErrors businessErrors, Object... params) {
        super(businessErrors.getCode(), businessErrors.format(params));
    }
}

数据对象(VO)中使用

src\main\java\cn\flyrise\pai\demo\model\vo\UserVO.java

划重点:
通过注解 @InvalidCode 实现错误消息使用

  • value: 错误编码,取值范围 1001-9999 (建议取5001-9999开始,与 枚举类 区分开)
  • message: 错误消息表达式,用 {} 包起来,Key为 messages.properties 定义,

    更新说明:
    2021/01/04 为了简化编Key的过程,message可默认为空,即默认值为 invalid.code.xxxx(xxxx为数值型错误代码), properties中对应的配置为 invalid.code.xxxx=内容

  • desc: 错误消息的描述,当没有在messages.properties中定义时,会作为默认消息

mode层

@ApiModel
public class UserVO implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键")
    private String id;

    @ApiModelProperty("账号")
    @Mapping("name")
    @NotBlank
    @InvalidCode(value = "3001", message = "{view.user.3001}", desc = "账号")
    public String name;

    @ApiModelProperty("密码")
    @NotNull
    @Size(min = 6, max = 16)
    @InvalidCode(value = "3002", desc = "密码")
    protected String password;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

解释一下
@InvalidCode(value = “3001”, message = “{view.user.3001}”, desc = “账号”)
@InvalidCode(value = “3002”, desc = “密码”)

对应的分别是:
view.user.3001=账号
invalid.code.3002=密码

意思就是:开发者定义了message属性就以定义的为准,没有则默认为 invalid.code.编号

控制层(Controller)

   @ApiOperation("修改用户信息")
   @PostMapping("/user/info/update")
   public Reply<?> updateUserInfo(@RequestBody @Valid UserVO userVO)

业务逻辑中使用

src\main\java\cn\flyrise\pai\demo\service\impl\UserServiceImpl.java

@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserPO, UserVO> implements IUserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public boolean insert(UserPO userPO) {
        if (StrUtil.isNotEmpty(userPO.getId()) && userMapper.selectById(userPO.getId()) != null) {
            throw new DemoBizException(BusinessErrors.DUPLICATE_KEY);
        }

        return SqlHelper.retBool(userMapper.insert(userPO));
    }
}
文档更新时间: 2023-12-15 18:26   作者:姚连洲