定义 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));
}
}