fix(core): 统一错误处理机制并增强租户管理员角色权限控制

- 移除页面中手动 message.error 调用,统一由拦截器处理
- 在 http 拦截器中实现后端错误消息自动展示
- 添加对业务错误码(code != 0)的处理逻辑
- 完善 HTTP 状态码错误(4xx, 5xx)的处理
- 增强 RoleController 中租户管理员角色的越权保护
- 在角色权限绑定页面添加平台管理员模式检查
- 限制非平台管理员修改 TENANT_ADMIN 角色权限的能力
- 清理调试用的 console.log 输出
master
chenhao 2026-02-26 17:09:14 +08:00
parent 0530605839
commit bf7439b200
14 changed files with 68 additions and 48 deletions

View File

@ -101,6 +101,18 @@ public class RoleController {
// 权限越权校验 // 权限越权校验
Long currentTenantId = getCurrentTenantId(); 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)) { if (!Long.valueOf(0).equals(currentTenantId)) {
List<com.imeeting.entity.SysPermission> myPerms = sysPermissionService.listByUserId(getCurrentUserId(), currentTenantId); List<com.imeeting.entity.SysPermission> myPerms = sysPermissionService.listByUserId(getCurrentUserId(), currentTenantId);

View File

@ -1,4 +1,5 @@
import axios from "axios"; import axios from "axios";
import { message } from "antd";
const http = axios.create({ const http = axios.create({
baseURL: "", baseURL: "",
@ -17,8 +18,11 @@ http.interceptors.request.use((config) => {
http.interceptors.response.use( http.interceptors.response.use(
(resp) => { (resp) => {
const body = resp.data; const body = resp.data;
// 如果返回的 code 不是 0表示业务错误
if (body && body.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).code = body.code;
(err as any).msg = body.msg; (err as any).msg = body.msg;
return Promise.reject(err); return Promise.reject(err);
@ -26,17 +30,21 @@ http.interceptors.response.use(
return resp; return resp;
}, },
(error) => { (error) => {
// 处理 HTTP 状态码错误 (4xx, 5xx)
if (error.response && (error.response.status === 401 || error.response.status === 403)) { if (error.response && (error.response.status === 401 || error.response.status === 403)) {
// Clear session/local storage
localStorage.removeItem("accessToken"); localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken"); localStorage.removeItem("refreshToken");
sessionStorage.removeItem("userProfile"); sessionStorage.removeItem("userProfile");
// Force redirect to login with timeout flag
window.location.href = "/login?timeout=1"; 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 body = error.response?.data;
const errorMsg = body?.msg || error.message || "网络异常";
// 防止重复弹出相同的提示(可选逻辑,根据需要调整)
message.error(errorMsg);
if (body && body.msg) { if (body && body.msg) {
const err = new Error(body.msg); const err = new Error(body.msg);
(err as any).code = body.code; (err as any).code = body.code;

View File

@ -74,7 +74,6 @@ export function usePermission() {
if (isAdmin) return true; if (isAdmin) return true;
if (!codes || codes.length === 0) { if (!codes || codes.length === 0) {
console.log(`Permission check for [${perm}]: codes is empty, returning true`);
return true; return true;
} }
@ -87,8 +86,6 @@ export function usePermission() {
} else { } else {
result = !hasButtonCodes || codes.includes(perm); result = !hasButtonCodes || codes.includes(perm);
} }
console.log(`Permission check for [${perm}]: result=[${result}], hasMenuCodes=[${hasMenuCodes}], hasButtonCodes=[${hasButtonCodes}]`);
return result; return result;
}; };

View File

@ -52,7 +52,7 @@ export default function Devices() {
setData(deviceList || []); setData(deviceList || []);
setUsers(usersList || []); setUsers(usersList || []);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -113,7 +113,7 @@ export default function Devices() {
setOpen(false); setOpen(false);
loadData(); loadData();
} catch (e) { } catch (e) {
if (e instanceof Error && e.message) message.error(e.message); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -125,7 +125,7 @@ export default function Devices() {
message.success(t('common.success')); message.success(t('common.success'));
loadData(); loadData();
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };

View File

@ -25,9 +25,9 @@ export default function Login() {
const data = await fetchCaptcha(); const data = await fetchCaptcha();
setCaptcha(data); setCaptcha(data);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}, [captchaEnabled, t]); }, [captchaEnabled]);
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
@ -91,7 +91,6 @@ export default function Login() {
message.success(t('common.success')); message.success(t('common.success'));
window.location.href = "/"; window.location.href = "/";
} catch (e: any) { } catch (e: any) {
message.error(e.message || t('common.error'));
if (captchaEnabled) { if (captchaEnabled) {
loadCaptcha(); loadCaptcha();
} }

View File

@ -93,7 +93,7 @@ export default function Orgs() {
setSelectedTenantId(list[0].id); setSelectedTenantId(list[0].id);
} }
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -104,7 +104,7 @@ export default function Orgs() {
const list = await listOrgs(selectedTenantId); const list = await listOrgs(selectedTenantId);
setData(list || []); setData(list || []);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -148,7 +148,7 @@ export default function Orgs() {
message.success(t('common.success')); message.success(t('common.success'));
loadOrgs(); loadOrgs();
} catch (e: any) { } catch (e: any) {
message.error(e.message || t('common.error')); // Handled by interceptor
} }
}; };
@ -166,7 +166,7 @@ export default function Orgs() {
setDrawerOpen(false); setDrawerOpen(false);
loadOrgs(); loadOrgs();
} catch (e) { } catch (e) {
if (e instanceof Error && e.message) message.error(e.message); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }

View File

@ -161,7 +161,7 @@ export default function Permissions() {
setOpen(false); setOpen(false);
load(); load();
} catch (e) { } catch (e) {
if (e instanceof Error && e.message) message.error(e.message); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -173,7 +173,7 @@ export default function Permissions() {
message.success(t('common.success')); message.success(t('common.success'));
load(); load();
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };

View File

@ -37,7 +37,7 @@ export default function PlatformSettings() {
const data = await getAdminPlatformConfig(); const data = await getAdminPlatformConfig();
form.setFieldsValue(data); form.setFieldsValue(data);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -53,7 +53,7 @@ export default function PlatformSettings() {
form.setFieldValue(fieldName, url); form.setFieldValue(fieldName, url);
message.success(t('common.success')); message.success(t('common.success'));
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
return false; // 阻止自动上传 return false; // 阻止自动上传
}; };
@ -64,7 +64,7 @@ export default function PlatformSettings() {
await updatePlatformConfig(values); await updatePlatformConfig(values);
message.success(t('common.success')); message.success(t('common.success'));
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }

View File

@ -74,6 +74,16 @@ export default function RolePermissionBinding() {
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [selectedRoleId, setSelectedRoleId] = useState<number | null>(null); 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 // Selection states
const [checkedPermIds, setCheckedPermIds] = useState<number[]>([]); const [checkedPermIds, setCheckedPermIds] = useState<number[]>([]);
const [halfCheckedIds, setHalfCheckedIds] = useState<number[]>([]); const [halfCheckedIds, setHalfCheckedIds] = useState<number[]>([]);
@ -102,7 +112,7 @@ export default function RolePermissionBinding() {
const list = await listPermissions(); const list = await listPermissions();
setPermissions(list || []); setPermissions(list || []);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setLoadingPerms(false); setLoadingPerms(false);
} }
@ -121,7 +131,6 @@ export default function RolePermissionBinding() {
setHalfCheckedIds([]); setHalfCheckedIds([]);
} catch (e) { } catch (e) {
setCheckedPermIds([]); setCheckedPermIds([]);
message.error(t('common.error'));
} }
}; };
@ -161,7 +170,7 @@ export default function RolePermissionBinding() {
await saveRolePermissions(selectedRoleId, allPermIds); await saveRolePermissions(selectedRoleId, allPermIds);
message.success(t('common.success')); message.success(t('common.success'));
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -179,7 +188,7 @@ export default function RolePermissionBinding() {
icon={<SaveOutlined aria-hidden="true" />} icon={<SaveOutlined aria-hidden="true" />}
onClick={handleSave} onClick={handleSave}
loading={saving} loading={saving}
disabled={!selectedRoleId} disabled={!selectedRoleId || (selectedRole?.roleCode === 'TENANT_ADMIN' && !isPlatformMode)}
> >
{saving ? t('common.loading') : t('rolePerm.savePolicy')} {saving ? t('common.loading') : t('rolePerm.savePolicy')}
</Button> </Button>
@ -261,7 +270,7 @@ export default function RolePermissionBinding() {
onCheck={(keys, info) => { onCheck={(keys, info) => {
const checked = Array.isArray(keys) ? keys : keys.checked; const checked = Array.isArray(keys) ? keys : keys.checked;
const halfChecked = info.halfCheckedKeys || []; const halfChecked = info.halfCheckedKeys || [];
setSelectedPermIds(checked.map(k => Number(k))); setCheckedPermIds(checked.map(k => Number(k)));
setHalfCheckedIds(halfChecked.map(k => Number(k))); setHalfCheckedIds(halfChecked.map(k => Number(k)));
}} }}
defaultExpandAll defaultExpandAll

View File

@ -182,7 +182,7 @@ export default function Roles() {
setUserModalOpen(false); setUserModalOpen(false);
selectRole(selectedRole); selectRole(selectedRole);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -193,7 +193,7 @@ export default function Roles() {
message.success(t('common.success')); message.success(t('common.success'));
selectRole(selectedRole); selectRole(selectedRole);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -259,7 +259,7 @@ export default function Roles() {
const users = await fetchUsersByRoleId(role.roleId); const users = await fetchUsersByRoleId(role.roleId);
setRoleUsers(users || []); setRoleUsers(users || []);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setLoadingUsers(false); setLoadingUsers(false);
} }
@ -315,7 +315,7 @@ export default function Roles() {
if (selectedRole?.roleId === id) setSelectedRole(null); if (selectedRole?.roleId === id) setSelectedRole(null);
loadRoles(); loadRoles();
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -342,7 +342,7 @@ export default function Roles() {
setDrawerOpen(false); setDrawerOpen(false);
loadRoles(); loadRoles();
} catch (e) { } catch (e) {
if (e instanceof Error && e.message) message.error(e.message); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -356,7 +356,7 @@ export default function Roles() {
await saveRolePermissions(selectedRole.roleId, allPermIds); await saveRolePermissions(selectedRole.roleId, allPermIds);
message.success(t('common.success')); message.success(t('common.success'));
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -473,7 +473,7 @@ export default function Roles() {
icon={<SaveOutlined aria-hidden="true" />} icon={<SaveOutlined aria-hidden="true" />}
loading={saving} loading={saving}
onClick={savePermissions} onClick={savePermissions}
disabled={!can("sys:role:permission:save")} disabled={!can("sys:role:permission:save") || (selectedRole.roleCode === 'TENANT_ADMIN' && !isPlatformMode)}
> >
{t('roles.savePerms')} {t('roles.savePerms')}
</Button> </Button>

View File

@ -64,12 +64,10 @@ export default function SysParams() {
const res = await pageParams(query); const res = await pageParams(query);
setData(res.records || []); setData(res.records || []);
setTotal(res.total || 0); setTotal(res.total || 0);
} catch (e) {
message.error(t('common.error'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [queryParams, t]); }, [queryParams]);
useEffect(() => { useEffect(() => {
loadData(); loadData();
@ -109,7 +107,7 @@ export default function SysParams() {
message.success(t('common.success')); message.success(t('common.success'));
loadData(); loadData();
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by global interceptor
} }
}; };

View File

@ -59,8 +59,6 @@ export default function Tenants() {
const result = await listTenants(currentParams); const result = await listTenants(currentParams);
setData(result.records || []); setData(result.records || []);
setTotal(result.total || 0); setTotal(result.total || 0);
} catch (e) {
message.error(t('common.error'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -108,7 +106,7 @@ export default function Tenants() {
message.success(t('common.success')); message.success(t('common.success'));
loadData(); loadData();
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -131,7 +129,7 @@ export default function Tenants() {
setDrawerOpen(false); setDrawerOpen(false);
loadData(); loadData();
} catch (e) { } catch (e) {
if (e instanceof Error && e.message) message.error(e.message); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }

View File

@ -64,7 +64,6 @@ export default function UserRoleBinding() {
setCheckedRoleIds(list || []); setCheckedRoleIds(list || []);
} catch (e) { } catch (e) {
setCheckedRoleIds([]); setCheckedRoleIds([]);
message.error(t('common.error'));
} }
}; };
@ -100,7 +99,7 @@ export default function UserRoleBinding() {
await saveUserRoles(selectedUserId, checkedRoleIds); await saveUserRoles(selectedUserId, checkedRoleIds);
message.success(t('common.success')); message.success(t('common.success'));
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by global interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }

View File

@ -145,7 +145,7 @@ export default function Users() {
setTenants(tenantsResp.records || []); setTenants(tenantsResp.records || []);
} }
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -230,7 +230,7 @@ export default function Users() {
}); });
setDrawerOpen(true); setDrawerOpen(true);
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -240,7 +240,7 @@ export default function Users() {
message.success(t('common.success')); message.success(t('common.success'));
loadUsersData(); loadUsersData();
} catch (e) { } catch (e) {
message.error(t('common.error')); // Handled by interceptor
} }
}; };
@ -287,7 +287,7 @@ export default function Users() {
setDrawerOpen(false); setDrawerOpen(false);
loadUsersData(); loadUsersData();
} catch (e) { } catch (e) {
if (e instanceof Error && e.message) message.error(e.message); // Handled by interceptor
} finally { } finally {
setSaving(false); setSaving(false);
} }