{"record":{"id":"dc48bb1d10b4ff78","repo":"paascloud/paascloud-master","slug":"uac10013008","errorCode":"UAC10013008","errorMessage":"删除菜单失败, menuId=%s","messagePattern":"删除菜单失败, menuId=(.+?)","errorType":"error_code","errorClass":"UacBizException","httpStatus":null,"severity":"error","filePath":"paascloud-provider/paascloud-provider-uac/src/main/java/com/paascloud/provider/service/impl/UacMenuServiceImpl.java","lineNumber":187,"sourceCode":"\t\t// 获取当前菜单信息\n\t\tUacMenu uacMenuQuery = new UacMenu();\n\t\tuacMenuQuery.setId(id);\n\t\tuacMenuQuery = mapper.selectOne(uacMenuQuery);\n\t\tif (PublicUtil.isEmpty(uacMenuQuery)) {\n\t\t\tthrow new UacBizException(ErrorCodeEnum.UAC10013003, id);\n\t\t}\n\n\t\t// 删除菜单与角色的关联关系\n\t\tUacRoleMenu uacRoleMenu = new UacRoleMenu();\n\t\tuacRoleMenu.setMenuId(id);\n\t\tuacRoleMenuService.delete(uacRoleMenu);\n\n\n\t\t// 删除菜单\n\t\tresult = uacMenuMapper.deleteByPrimaryKey(id);\n\t\tif (result < 1) {\n\t\t\tlogger.error(\"删除菜单失败 menuId={}\", id);\n\t\t\tthrow new UacBizException(ErrorCodeEnum.UAC10013008, id);\n\t\t}\n\n\t\t// 删除权限\n\t\t// TODO 应该先查询再删除\n\t\tuacActionService.deleteByMenuId(id);\n\n\t\t// 修改当前删除菜单的父菜单是否是叶子节点\n\t\tUacMenu updateParentUacMenu = new UacMenu();\n\t\tupdateParentUacMenu.setId(uacMenuQuery.getPid());\n\t\tupdateParentUacMenu.setLeaf(MenuConstant.MENU_LEAF_YES);\n\t\t// 是二三级\n\t\tif (Objects.equals(MenuConstant.MENU_LEVEL_TWO, uacMenuQuery.getLevel()) || Objects.equals(MenuConstant.MENU_LEVEL_THREE, uacMenuQuery.getLevel())) {\n\t\t\t// 查询是否是叶子节点\n\t\t\tint count = uacMenuMapper.selectMenuChildCountByPid(uacMenuQuery.getPid());\n\t\t\tif (count == 0) {\n\t\t\t\tuacMenuMapper.updateByPrimaryKeySelective(updateParentUacMenu);\n\t\t\t}\n\t\t}","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/paascloud/paascloud-master/blob/781281a9503332ed3cef44ea618349d14230a127/paascloud-provider/paascloud-provider-uac/src/main/java/com/paascloud/provider/service/impl/UacMenuServiceImpl.java#L169-L205","documentation":"UacBizException UAC10013008 thrown when deleteUacMenuById's final delete (uacMenuMapper.deleteByPrimaryKey(id)) affects fewer than 1 row, meaning the menu row disappeared between the existence check and the delete, or the delete statement failed to match. An error log '删除菜单失败 menuId=...' is written first.","triggerScenarios":"Calling deleteUacMenuById(id) where the row passed the initial existence check but was deleted concurrently by another transaction before deleteByPrimaryKey ran, or where the primary-key delete matched 0 rows for any reason.","commonSituations":"Two admins deleting the same menu simultaneously; duplicate delete requests from a retried HTTP call racing each other; read-replica/primary lag making the existence check pass on stale data.","solutions":["Verify the menu no longer exists — if it is already gone, treat the delete as idempotent success and skip the error.","Deduplicate delete requests on the client (disable button after first click) to avoid racing deletes.","Confirm the deleteByPrimaryKey SQL matches rows by the correct primary key column in the mapper XML.","Inspect DB logs for row-lock or transaction issues around the delete."],"exampleFix":"// before: fail hard on 0 rows\nint result = uacMenuMapper.deleteByPrimaryKey(id);\nif (result < 1) { throw new UacBizException(ErrorCodeEnum.UAC10013008, id); }\n// after: idempotent delete\nint result = uacMenuMapper.deleteByPrimaryKey(id);\nif (result < 1 && uacMenuMapper.selectByPrimaryKey(id) != null) {\n  throw new UacBizException(ErrorCodeEnum.UAC10013008, id);\n}","handlingStrategy":"try-catch","validationCode":"if (uacMenuMapper.selectByPrimaryKey(id) == null) { return; /* already gone */ }","typeGuard":"boolean menuStillExists(Long id) { return uacMenuMapper.selectByPrimaryKey(id) != null; }","tryCatchPattern":"try {\n  uacMenuService.deleteUacMenuById(id);\n} catch (UacBizException e) {\n  if (\"UAC10013008\".equals(e.getCode()) && uacMenuMapper.selectByPrimaryKey(id) == null) {\n    return; // raced with another delete; treat as success\n  }\n  throw e;\n}","preventionTips":["Serialize deletions of the same menu (e.g. optimistic locking or unique UI action).","Make deletes idempotent: 0 affected rows on an already-deleted row is acceptable.","Debounce/deduplicate retry requests from the client."],"tags":["database","menu-management","uac","delete","race-condition"],"backgroundTag":"database-write-failed","analyzedSha":"781281a9503332ed3cef44ea618349d14230a127","analyzedAt":"2026-09-10T10:59:02.070Z","contentChangedAt":"2026-09-10T10:59:02.070Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}