fix(core): 统一错误处理机制并增强租户管理员角色权限控制
- 移除页面中手动 message.error 调用,统一由拦截器处理 - 在 http 拦截器中实现后端错误消息自动展示 - 添加对业务错误码(code != 0)的处理逻辑 - 完善 HTTP 状态码错误(4xx, 5xx)的处理 - 增强 RoleController 中租户管理员角色的越权保护 - 在角色权限绑定页面添加平台管理员模式检查 - 限制非平台管理员修改 TENANT_ADMIN 角色权限的能力 - 清理调试用的 console.log 输出master
parent
0530605839
commit
bf7439b200
|
|
@ -101,6 +101,18 @@ public class RoleController {
|
|||
|
||||
// 权限越权校验
|
||||
Long currentTenantId = getCurrentTenantId();
|
||||
SysRole targetRole = sysRoleService.getById(id);
|
||||
if (targetRole == null) {
|
||||
return ApiResponse.error("角色不存在");
|
||||
}
|
||||
|
||||
// 关键校验:只有平台管理员可以修改 TENANT_ADMIN 角色的权限
|
||||
if ("TENANT_ADMIN".equalsIgnoreCase(targetRole.getRoleCode())) {
|
||||
if (!Long.valueOf(0).equals(currentTenantId)) {
|
||||
return ApiResponse.error("租户管理员角色的权限只能由平台管理员修改");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Long.valueOf(0).equals(currentTenantId)) {
|
||||
List<com.imeeting.entity.SysPermission> myPerms = sysPermissionService.listByUserId(getCurrentUserId(), currentTenantId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import axios from "axios";
|
||||
import { message } from "antd";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: "",
|
||||
|
|
@ -17,8 +18,11 @@ http.interceptors.request.use((config) => {
|
|||
http.interceptors.response.use(
|
||||
(resp) => {
|
||||
const body = resp.data;
|
||||
// 如果返回的 code 不是 0,表示业务错误
|
||||
if (body && body.code !== "0") {
|
||||
const err = new Error(body.msg || "请求失败");
|
||||
const errorMsg = body.msg || "请求失败";
|
||||
message.error(errorMsg); // 自动展示后端错误消息
|
||||
const err = new Error(errorMsg);
|
||||
(err as any).code = body.code;
|
||||
(err as any).msg = body.msg;
|
||||
return Promise.reject(err);
|
||||
|
|
@ -26,17 +30,21 @@ http.interceptors.response.use(
|
|||
return resp;
|
||||
},
|
||||
(error) => {
|
||||
// 处理 HTTP 状态码错误 (4xx, 5xx)
|
||||
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
|
||||
// Clear session/local storage
|
||||
localStorage.removeItem("accessToken");
|
||||
localStorage.removeItem("refreshToken");
|
||||
sessionStorage.removeItem("userProfile");
|
||||
// Force redirect to login with timeout flag
|
||||
window.location.href = "/login?timeout=1";
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// Process backend error message if available in response body even for non-200 status
|
||||
const body = error.response?.data;
|
||||
const errorMsg = body?.msg || error.message || "网络异常";
|
||||
|
||||
// 防止重复弹出相同的提示(可选逻辑,根据需要调整)
|
||||
message.error(errorMsg);
|
||||
|
||||
if (body && body.msg) {
|
||||
const err = new Error(body.msg);
|
||||
(err as any).code = body.code;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ export function usePermission() {
|
|||
if (isAdmin) return true;
|
||||
|
||||
if (!codes || codes.length === 0) {
|
||||
console.log(`Permission check for [${perm}]: codes is empty, returning true`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -87,8 +86,6 @@ export function usePermission() {
|
|||
} else {
|
||||
result = !hasButtonCodes || codes.includes(perm);
|
||||
}
|
||||
|
||||
console.log(`Permission check for [${perm}]: result=[${result}], hasMenuCodes=[${hasMenuCodes}], hasButtonCodes=[${hasButtonCodes}]`);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default function Devices() {
|
|||
setData(deviceList || []);
|
||||
setUsers(usersList || []);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ export default function Devices() {
|
|||
setOpen(false);
|
||||
loadData();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ export default function Devices() {
|
|||
message.success(t('common.success'));
|
||||
loadData();
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ export default function Login() {
|
|||
const data = await fetchCaptcha();
|
||||
setCaptcha(data);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
}, [captchaEnabled, t]);
|
||||
}, [captchaEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
|
|
@ -91,7 +91,6 @@ export default function Login() {
|
|||
message.success(t('common.success'));
|
||||
window.location.href = "/";
|
||||
} catch (e: any) {
|
||||
message.error(e.message || t('common.error'));
|
||||
if (captchaEnabled) {
|
||||
loadCaptcha();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default function Orgs() {
|
|||
setSelectedTenantId(list[0].id);
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ export default function Orgs() {
|
|||
const list = await listOrgs(selectedTenantId);
|
||||
setData(list || []);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ export default function Orgs() {
|
|||
message.success(t('common.success'));
|
||||
loadOrgs();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ export default function Orgs() {
|
|||
setDrawerOpen(false);
|
||||
loadOrgs();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export default function Permissions() {
|
|||
setOpen(false);
|
||||
load();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -173,7 +173,7 @@ export default function Permissions() {
|
|||
message.success(t('common.success'));
|
||||
load();
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default function PlatformSettings() {
|
|||
const data = await getAdminPlatformConfig();
|
||||
form.setFieldsValue(data);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ export default function PlatformSettings() {
|
|||
form.setFieldValue(fieldName, url);
|
||||
message.success(t('common.success'));
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
return false; // 阻止自动上传
|
||||
};
|
||||
|
|
@ -64,7 +64,7 @@ export default function PlatformSettings() {
|
|||
await updatePlatformConfig(values);
|
||||
message.success(t('common.success'));
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ export default function RolePermissionBinding() {
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [selectedRoleId, setSelectedRoleId] = useState<number | null>(null);
|
||||
|
||||
// Platform admin check
|
||||
const isPlatformMode = useMemo(() => {
|
||||
const profileStr = sessionStorage.getItem("userProfile");
|
||||
if (profileStr) {
|
||||
const profile = JSON.parse(profileStr);
|
||||
return profile.isPlatformAdmin && localStorage.getItem("activeTenantId") === "0";
|
||||
}
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
// Selection states
|
||||
const [checkedPermIds, setCheckedPermIds] = useState<number[]>([]);
|
||||
const [halfCheckedIds, setHalfCheckedIds] = useState<number[]>([]);
|
||||
|
|
@ -102,7 +112,7 @@ export default function RolePermissionBinding() {
|
|||
const list = await listPermissions();
|
||||
setPermissions(list || []);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setLoadingPerms(false);
|
||||
}
|
||||
|
|
@ -121,7 +131,6 @@ export default function RolePermissionBinding() {
|
|||
setHalfCheckedIds([]);
|
||||
} catch (e) {
|
||||
setCheckedPermIds([]);
|
||||
message.error(t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -161,7 +170,7 @@ export default function RolePermissionBinding() {
|
|||
await saveRolePermissions(selectedRoleId, allPermIds);
|
||||
message.success(t('common.success'));
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -179,7 +188,7 @@ export default function RolePermissionBinding() {
|
|||
icon={<SaveOutlined aria-hidden="true" />}
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={!selectedRoleId}
|
||||
disabled={!selectedRoleId || (selectedRole?.roleCode === 'TENANT_ADMIN' && !isPlatformMode)}
|
||||
>
|
||||
{saving ? t('common.loading') : t('rolePerm.savePolicy')}
|
||||
</Button>
|
||||
|
|
@ -261,7 +270,7 @@ export default function RolePermissionBinding() {
|
|||
onCheck={(keys, info) => {
|
||||
const checked = Array.isArray(keys) ? keys : keys.checked;
|
||||
const halfChecked = info.halfCheckedKeys || [];
|
||||
setSelectedPermIds(checked.map(k => Number(k)));
|
||||
setCheckedPermIds(checked.map(k => Number(k)));
|
||||
setHalfCheckedIds(halfChecked.map(k => Number(k)));
|
||||
}}
|
||||
defaultExpandAll
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ export default function Roles() {
|
|||
setUserModalOpen(false);
|
||||
selectRole(selectedRole);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ export default function Roles() {
|
|||
message.success(t('common.success'));
|
||||
selectRole(selectedRole);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ export default function Roles() {
|
|||
const users = await fetchUsersByRoleId(role.roleId);
|
||||
setRoleUsers(users || []);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setLoadingUsers(false);
|
||||
}
|
||||
|
|
@ -315,7 +315,7 @@ export default function Roles() {
|
|||
if (selectedRole?.roleId === id) setSelectedRole(null);
|
||||
loadRoles();
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -342,7 +342,7 @@ export default function Roles() {
|
|||
setDrawerOpen(false);
|
||||
loadRoles();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -356,7 +356,7 @@ export default function Roles() {
|
|||
await saveRolePermissions(selectedRole.roleId, allPermIds);
|
||||
message.success(t('common.success'));
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -473,7 +473,7 @@ export default function Roles() {
|
|||
icon={<SaveOutlined aria-hidden="true" />}
|
||||
loading={saving}
|
||||
onClick={savePermissions}
|
||||
disabled={!can("sys:role:permission:save")}
|
||||
disabled={!can("sys:role:permission:save") || (selectedRole.roleCode === 'TENANT_ADMIN' && !isPlatformMode)}
|
||||
>
|
||||
{t('roles.savePerms')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -64,12 +64,10 @@ export default function SysParams() {
|
|||
const res = await pageParams(query);
|
||||
setData(res.records || []);
|
||||
setTotal(res.total || 0);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [queryParams, t]);
|
||||
}, [queryParams]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
|
|
@ -109,7 +107,7 @@ export default function SysParams() {
|
|||
message.success(t('common.success'));
|
||||
loadData();
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by global interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@ export default function Tenants() {
|
|||
const result = await listTenants(currentParams);
|
||||
setData(result.records || []);
|
||||
setTotal(result.total || 0);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -108,7 +106,7 @@ export default function Tenants() {
|
|||
message.success(t('common.success'));
|
||||
loadData();
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -131,7 +129,7 @@ export default function Tenants() {
|
|||
setDrawerOpen(false);
|
||||
loadData();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ export default function UserRoleBinding() {
|
|||
setCheckedRoleIds(list || []);
|
||||
} catch (e) {
|
||||
setCheckedRoleIds([]);
|
||||
message.error(t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -100,7 +99,7 @@ export default function UserRoleBinding() {
|
|||
await saveUserRoles(selectedUserId, checkedRoleIds);
|
||||
message.success(t('common.success'));
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by global interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export default function Users() {
|
|||
setTenants(tenantsResp.records || []);
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ export default function Users() {
|
|||
});
|
||||
setDrawerOpen(true);
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ export default function Users() {
|
|||
message.success(t('common.success'));
|
||||
loadUsersData();
|
||||
} catch (e) {
|
||||
message.error(t('common.error'));
|
||||
// Handled by interceptor
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -287,7 +287,7 @@ export default function Users() {
|
|||
setDrawerOpen(false);
|
||||
loadUsersData();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
// Handled by interceptor
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue