diff --git a/src/main/java/com/xujie/sys/common/exception/I18nException.java b/src/main/java/com/xujie/sys/common/exception/I18nException.java new file mode 100644 index 0000000..6016ef4 --- /dev/null +++ b/src/main/java/com/xujie/sys/common/exception/I18nException.java @@ -0,0 +1,32 @@ +package com.xujie.sys.common.exception; + +public class I18nException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private final String messageKey; + private final transient Object[] args; + private final int code; + + public I18nException(String messageKey, Object... args) { + this(messageKey, 500, args); + } + + public I18nException(String messageKey, int code, Object... args) { + super(messageKey); + this.messageKey = messageKey; + this.code = code; + this.args = args == null ? new Object[0] : args; + } + + public String getMessageKey() { + return messageKey; + } + + public Object[] getArgs() { + return args; + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/com/xujie/sys/common/exception/XJExceptionHandler.java b/src/main/java/com/xujie/sys/common/exception/XJExceptionHandler.java index 251fb3d..5a7f7c3 100644 --- a/src/main/java/com/xujie/sys/common/exception/XJExceptionHandler.java +++ b/src/main/java/com/xujie/sys/common/exception/XJExceptionHandler.java @@ -7,11 +7,17 @@ import com.xujie.sys.common.utils.R; import org.apache.shiro.authz.AuthorizationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; import org.springframework.dao.DuplicateKeyException; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.NoHandlerFoundException; +import java.util.Locale; + /** * 异常处理器 */ @@ -19,6 +25,15 @@ import org.springframework.web.servlet.NoHandlerFoundException; public class XJExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private MessageSource messageSource; + + @ExceptionHandler(I18nException.class) + public R handleI18nException(I18nException e) { + logger.warn("I18n exception, key={}", e.getMessageKey()); + return R.error(e.getCode(), resolveMessage(e.getMessageKey(), e.getArgs())); + } + /** * 处理自定义异常 */ @@ -26,29 +41,44 @@ public class XJExceptionHandler { public R handleRRException(XJException e) { R r = new R(); r.put("code", e.getCode()); - r.put("msg", e.getMessage()); + r.put("msg", resolveMessage(e.getMessage(), null)); return r; } @ExceptionHandler(NoHandlerFoundException.class) public R handlerNoFoundException(Exception e) { - return R.error(404, "路径不存在,请检查路径是否正确"); + logger.warn("No handler found", e); + return R.error(404, resolveMessage("common.path.not.found", null)); } @ExceptionHandler(DuplicateKeyException.class) public R handleDuplicateKeyException(DuplicateKeyException e) { - e.printStackTrace(); - return R.error("数据库已存在该记录!"); + logger.error("Duplicate key exception", e); + return R.error(resolveMessage("common.duplicate.record", null)); } @ExceptionHandler(AuthorizationException.class) public R handleAuthorizationException(AuthorizationException e) { - return R.error("没有权限,请联系管理员授权"); + logger.warn("Authorization exception", e); + return R.error(resolveMessage("common.no.permission", null)); } @ExceptionHandler(Exception.class) public R handleException(Exception e) { - e.printStackTrace(); - return R.error(e.getMessage()); + logger.error("Unhandled exception", e); + return R.error(resolveMessage(e.getMessage(), null)); + } + + private String resolveMessage(String messageKey, Object[] args) { + if (!StringUtils.hasText(messageKey)) { + messageKey = "common.unknown.error"; + } + Locale locale = LocaleContextHolder.getLocale(); + String localized = messageSource.getMessage(messageKey, args, null, locale); + if (StringUtils.hasText(localized)) { + return localized; + } + localized = messageSource.getMessage(messageKey, args, null, Locale.SIMPLIFIED_CHINESE); + return StringUtils.hasText(localized) ? localized : messageKey; } } diff --git a/src/main/java/com/xujie/sys/config/I18nConfig.java b/src/main/java/com/xujie/sys/config/I18nConfig.java new file mode 100644 index 0000000..49a8421 --- /dev/null +++ b/src/main/java/com/xujie/sys/config/I18nConfig.java @@ -0,0 +1,19 @@ +package com.xujie.sys.config; + +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +@Configuration +public class I18nConfig { + + @Bean + public MessageSource messageSource() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasenames("classpath:i18n/messages"); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setUseCodeAsDefaultMessage(true); + return messageSource; + } +} diff --git a/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java b/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java index 9be7db9..628cc7f 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java @@ -2,6 +2,7 @@ package com.xujie.sys.modules.longchuang.service.impl; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.xujie.sys.common.exception.I18nException; import com.xujie.sys.common.utils.PageUtils; import com.xujie.sys.modules.longchuang.data.ProductionPlanNodeReportData; import com.xujie.sys.modules.longchuang.data.ProductionPlanNodeAssigneeData; @@ -184,7 +185,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { @Transactional(rollbackFor = Exception.class) public void saveNodeRoleAssign(ProductionPlanNodeRoleAssignData data) { if (data == null || !StringUtils.hasText(data.getOrderType()) || !StringUtils.hasText(data.getNodeCode())) { - throw new RuntimeException("节点角色配置参数不完整"); + throw new I18nException("lc.production.node.role.assign.param.incomplete"); } Long userId = getCurrentUserId(); String nodeName = StringUtils.hasText(data.getNodeName()) ? data.getNodeName() : data.getNodeCode(); @@ -248,13 +249,13 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { @Transactional(rollbackFor = Exception.class) public void saveNodeAssignee(ProductionPlanNodeAssigneeSaveData data) { if (data == null || !StringUtils.hasText(data.getOrderNo()) || !StringUtils.hasText(data.getOrderType())) { - throw new RuntimeException("节点负责人分配参数不完整"); + throw new I18nException("lc.production.node.assignee.assign.param.incomplete"); } Long currentUserId = getCurrentUserId(); String normalizedOrderType = normalizeOrderType(data.getOrderType()); ProductionPlanOrderRowData order = productionPlanMapper.queryOrderByOrderNo(data.getOrderNo(), normalizedOrderType); if (order == null) { - throw new RuntimeException("订单不存在,不能分配节点负责人"); + throw new I18nException("lc.production.order.not.exists.cannot.assign"); } productionPlanMapper.deleteNodeAssigneeByOrder(data.getOrderNo(), normalizedOrderType); if (data.getAssigneeList() == null || data.getAssigneeList().isEmpty()) { @@ -270,7 +271,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { for (ProductionPlanNodeAssigneeData item : validList) { String roleType = buildNodeRoleType(normalizedOrderType, item.getNodeCode()); if (!StringUtils.hasText(roleType)) { - throw new RuntimeException("节点角色类型未定义: " + item.getNodeCode()); + throw new I18nException("lc.production.node.role.type.undefined", item.getNodeCode()); } List assigneeUserIdList = item.getAssigneeUserIdList() == null ? Collections.emptyList() : item.getAssigneeUserIdList(); for (Long assigneeUserId : assigneeUserIdList.stream().distinct().collect(Collectors.toList())) { @@ -279,7 +280,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { } int hasRole = productionPlanMapper.countUserRoleTypePermission(assigneeUserId, roleType); if (hasRole <= 0) { - throw new RuntimeException("用户未配置对应角色类型,节点: " + item.getNodeCode() + ", 角色类型: " + roleType); + throw new I18nException("lc.production.user.role.type.not.configured", item.getNodeCode(), roleType); } ProductionPlanNodeAssigneeData assigneeData = new ProductionPlanNodeAssigneeData(); assigneeData.setNodeCode(item.getNodeCode()); @@ -415,7 +416,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { } ProductionPlanOrderRowData existsOrder = productionPlanMapper.queryOrderByOrderNo(data.getOrderNo(), orderType); if (existsOrder == null) { - throw new RuntimeException("订单不存在,无法保存"); + throw new I18nException("lc.production.order.not.exists.cannot.save"); } productionPlanMapper.updateOrder(data); if (data.getNodeList() != null && !data.getNodeList().isEmpty()) { @@ -475,21 +476,21 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private void validateSaveData(ProductionPlanOrderSaveData data, String orderType) { if (data == null) { - throw new RuntimeException("保存参数不能为空"); + throw new I18nException("lc.production.save.param.empty"); } if (ORDER_TYPE_CABLE_COP.equals(orderType)) { if (!StringUtils.hasText(data.getTaskNo())) { - throw new RuntimeException("任务单号不能为空"); + throw new I18nException("lc.production.task.no.required"); } if (!StringUtils.hasText(data.getTaskType())) { - throw new RuntimeException("任务类型不能为空"); + throw new I18nException("lc.production.task.type.required"); } } else { if (!StringUtils.hasText(data.getProjectNo())) { - throw new RuntimeException("项目号不能为空"); + throw new I18nException("lc.production.project.no.required"); } if (!StringUtils.hasText(data.getModelNo())) { - throw new RuntimeException("型号不能为空"); + throw new I18nException("lc.production.model.required"); } } } @@ -568,18 +569,18 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private void reportOrderNode(ProductionPlanNodeReportData data, String orderType) { if (data == null || !StringUtils.hasText(data.getOrderNo()) || !StringUtils.hasText(data.getNodeCode())) { - throw new RuntimeException("报工参数不完整"); + throw new I18nException("lc.production.report.param.incomplete"); } ProductionPlanOrderRowData order = productionPlanMapper.queryOrderByOrderNo(data.getOrderNo(), orderType); if (order == null) { - throw new RuntimeException("订单不存在或已删除"); + throw new I18nException("lc.production.order.not.exists.or.deleted"); } if (STATUS_DONE.equals(order.getStatus())) { - throw new RuntimeException("订单已完成,不能继续报工"); + throw new I18nException("lc.production.order.done.cannot.report"); } ProductionPlanOrderNodeData node = productionPlanMapper.queryOrderNodeByCode(data.getOrderNo(), data.getNodeCode()); if (node == null) { - throw new RuntimeException("报工节点不存在"); + throw new I18nException("lc.production.report.node.not.exists"); } String nodeReportMode = normalizeNodeReportMode(order.getNodeReportMode()); if (NODE_REPORT_MODE_SEQUENTIAL.equals(nodeReportMode)) { @@ -592,11 +593,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { } } if (firstUnDoneNode != null && !firstUnDoneNode.getNodeCode().equals(data.getNodeCode())) { - throw new RuntimeException("当前订单按顺序报工,请先完成节点:" + firstUnDoneNode.getNodeName()); + throw new I18nException("lc.production.report.sequential.need.first.node", firstUnDoneNode.getNodeName()); } } if (NODE_STATUS_DONE.equals(node.getStatus())) { - throw new RuntimeException("节点已完成,无需重复报工"); + throw new I18nException("lc.production.node.already.done"); } checkNodeRolePermission(orderType, data.getNodeCode()); checkNodeAssigneePermission(data.getOrderNo(), orderType, data.getNodeCode()); @@ -606,7 +607,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { if (ORDER_TYPE_CABLE_COP.equals(orderType)) { BigDecimal reportQty = data.getReportQty() == null ? BigDecimal.ZERO : data.getReportQty(); if (reportQty.compareTo(BigDecimal.ZERO) <= 0) { - throw new RuntimeException("报工数量必须大于0"); + throw new I18nException("lc.production.report.qty.gt.zero"); } productionPlanMapper.updateCableCopReportQty( data.getOrderNo(), @@ -634,24 +635,24 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private void checkNodeAssigneePermission(String orderNo, String orderType, String nodeCode) { int assigneeCount = productionPlanMapper.countNodeAssigneeByNode(orderNo, orderType, nodeCode); if (assigneeCount <= 0) { - throw new RuntimeException("当前节点未分配负责人,请先完成节点人员分配"); + throw new I18nException("lc.production.node.assignee.not.assigned"); } Long currentUserId = getCurrentUserId(); int matchedCount = productionPlanMapper.countNodeAssigneeByNodeUser(orderNo, orderType, nodeCode, currentUserId); if (matchedCount <= 0) { - throw new RuntimeException("当前用户不是该节点负责人,不能报工"); + throw new I18nException("lc.production.node.assignee.current.user.invalid"); } } private void checkNodeRolePermission(String orderType, String nodeCode) { String roleType = buildNodeRoleType(orderType, nodeCode); if (!StringUtils.hasText(roleType)) { - throw new RuntimeException("未定义节点角色类型,无法报工"); + throw new I18nException("lc.production.node.role.type.undefined.for.report"); } Long userId = getCurrentUserId(); int allowCount = productionPlanMapper.countUserRoleTypePermission(userId, roleType); if (allowCount <= 0) { - throw new RuntimeException("当前用户不具备该节点报工角色权限,角色类型: " + roleType); + throw new I18nException("lc.production.node.role.no.permission", roleType); } } @@ -706,11 +707,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private void finishOrder(String orderNo, String orderType) { if (!StringUtils.hasText(orderNo)) { - throw new RuntimeException("完工参数不能为空"); + throw new I18nException("lc.production.finish.param.empty"); } ProductionPlanOrderRowData order = productionPlanMapper.queryOrderByOrderNo(orderNo, orderType); if (order == null) { - throw new RuntimeException("订单不存在或已删除"); + throw new I18nException("lc.production.order.not.exists.or.deleted"); } Long userId = getCurrentUserId(); productionPlanMapper.finishAllOrderNode(orderNo, userId); @@ -722,14 +723,14 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private void deleteOrder(String orderNo, String orderType) { if (!StringUtils.hasText(orderNo)) { - throw new RuntimeException("删除参数不能为空"); + throw new I18nException("lc.production.delete.param.empty"); } productionPlanMapper.deleteOrderNode(orderNo); productionPlanMapper.deleteNodeAssigneeByOrder(orderNo, orderType); productionPlanMapper.deleteNodeReportLogByOrder(orderNo, orderType); int effect = productionPlanMapper.deleteOrder(orderNo, orderType, getCurrentUserId()); if (effect <= 0) { - throw new RuntimeException("删除失败,订单不存在"); + throw new I18nException("lc.production.delete.failed.order.not.exists"); } } @@ -764,7 +765,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private Long getCurrentUserId() { SysUserEntity user = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); if (user == null || user.getUserId() == null) { - throw new RuntimeException("未获取到登录用户"); + throw new I18nException("lc.production.current.user.not.found"); } return user.getUserId(); } diff --git a/src/main/resources/i18n/messages_en_US.properties b/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..5b23725 --- /dev/null +++ b/src/main/resources/i18n/messages_en_US.properties @@ -0,0 +1,31 @@ +common.unknown.error=Unknown exception. Please contact administrator. +common.path.not.found=Path not found. Please check the request path. +common.duplicate.record=The record already exists in database. +common.no.permission=No permission. Please contact administrator for authorization. + +lc.production.node.role.assign.param.incomplete=Node role configuration parameters are incomplete. +lc.production.node.assignee.assign.param.incomplete=Node assignee assignment parameters are incomplete. +lc.production.order.not.exists.cannot.assign=Order does not exist, cannot assign node assignees. +lc.production.node.role.type.undefined=Node role type is undefined: {0} +lc.production.user.role.type.not.configured=User has no matching role type configured, node: {0}, role type: {1} +lc.production.order.not.exists.cannot.save=Order does not exist, cannot save. +lc.production.save.param.empty=Save parameters cannot be empty. +lc.production.task.no.required=Task number cannot be empty. +lc.production.task.type.required=Task type cannot be empty. +lc.production.project.no.required=Project number cannot be empty. +lc.production.model.required=Model cannot be empty. +lc.production.report.param.incomplete=Report parameters are incomplete. +lc.production.order.not.exists.or.deleted=Order does not exist or has been deleted. +lc.production.order.done.cannot.report=Order is completed and cannot be reported again. +lc.production.report.node.not.exists=Reporting node does not exist. +lc.production.report.sequential.need.first.node=This order must be reported sequentially. Please complete node first: {0} +lc.production.node.already.done=Node is already completed. No duplicate report is needed. +lc.production.report.qty.gt.zero=Report quantity must be greater than 0. +lc.production.node.assignee.not.assigned=Current node has no assignee. Please assign node personnel first. +lc.production.node.assignee.current.user.invalid=Current user is not the assignee of this node and cannot report. +lc.production.node.role.type.undefined.for.report=Node role type is undefined and cannot be reported. +lc.production.node.role.no.permission=Current user does not have report permission for this role type: {0} +lc.production.finish.param.empty=Finish parameters cannot be empty. +lc.production.delete.param.empty=Delete parameters cannot be empty. +lc.production.delete.failed.order.not.exists=Delete failed. Order does not exist. +lc.production.current.user.not.found=Logged-in user is not available. diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..5c22a94 --- /dev/null +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,32 @@ +common.unknown.error=未知异常,请联系管理员 +common.path.not.found=路径不存在,请检查路径是否正确 +common.duplicate.record=数据库已存在该记录 +common.no.permission=没有权限,请联系管理员授权 + +lc.production.node.role.assign.param.incomplete=节点角色配置参数不完整 +lc.production.node.assignee.assign.param.incomplete=节点负责人分配参数不完整 +lc.production.order.not.exists.cannot.assign=订单不存在,不能分配节点负责人 +lc.production.node.role.type.undefined=节点角色类型未定义: {0} +lc.production.user.role.type.not.configured=用户未配置对应角色类型,节点: {0},角色类型: {1} +lc.production.order.not.exists.cannot.save=订单不存在,无法保存 +lc.production.save.param.empty=保存参数不能为空 +lc.production.task.no.required=任务单号不能为空 +lc.production.task.type.required=任务类型不能为空 +lc.production.project.no.required=项目号不能为空 +lc.production.model.required=型号不能为空 +lc.production.report.param.incomplete=报工参数不完整 +lc.production.order.not.exists.or.deleted=订单不存在或已删除 +lc.production.order.done.cannot.report=订单已完成,不能继续报工 +lc.production.report.node.not.exists=报工节点不存在 +lc.production.report.sequential.need.first.node=当前订单按顺序报工,请先完成节点: {0} +lc.production.node.already.done=节点已完成,无需重复报工 +lc.production.report.qty.gt.zero=报工数量必须大于0 +lc.production.node.assignee.not.assigned=当前节点未分配负责人,请先完成节点人员分配 +lc.production.node.assignee.current.user.invalid=当前用户不是该节点负责人,不能报工 +lc.production.node.role.type.undefined.for.report=未定义节点角色类型,无法报工 +lc.production.node.role.no.permission=当前用户不具备该节点报工角色权限,角色类型: {0} +lc.production.finish.param.empty=完工参数不能为空 +lc.production.delete.param.empty=删除参数不能为空 +lc.production.delete.failed.order.not.exists=删除失败,订单不存在 +lc.production.current.user.not.found=未获取到登录用户 +