feat: 1. 完善全局路由的访问权限的验证。 2. 完善接口返回的树形路由结构 3.优化评查点列表的查询,改用表连接的方式,废弃使用数据库的rpc函数,同时进行地区隔离和权限隔离。

4. 删除冗余的评查文件列表。      5.完善上传文档 页面初始化查询数据的时候 查询文件类型(改成动态指定)  6. 添加获取入口模块的查询接口。    7.完善服务端中判断token的有效性,失效则跳转到登录页。
8. 重构layout和sidebar的页面,改成由动态权限路由来渲染对应的菜单栏。       9.重构入口页面,通过动态查询根据不同地区的人返回不同的入口。
This commit is contained in:
2025-11-20 01:35:30 +08:00
parent adfb84a31d
commit 2edde8a8ab
23 changed files with 1201 additions and 2154 deletions
+80 -50
View File
@@ -29,7 +29,7 @@ import { OAUTH_CONFIG, API_BASE_URL } from "~/config/api-config";
* @property {'common'} common - 普通用户,有基本的系统访问权限
* @property {'developer'} developer - 开发者/管理员,有完整的系统管理权限
*/
export type UserRole = 'common' | 'admin' | 'deptLeader' | 'groupLeader' | string;
export type UserRole = string;
/**
* 用户信息接口,对应 sso_users 表结构
@@ -144,22 +144,22 @@ export async function getSession(request: Request) {
* @param expiresIn OAuth token过期时间(秒)
* @returns JWT字符串
*/
async function generateFrontendJWT(userInfo: UserInfo, savedUserData: SsoUser, userRole: UserRole, expiresIn: number): Promise<string> {
const jwtUserInfo: UserInfoForJWT = {
sub: userInfo.sub,
user_id: savedUserData.id!,
username: savedUserData.username,
nick_name: savedUserData.nick_name,
email: savedUserData.email,
phone_number: savedUserData.phone_number,
ou_id: savedUserData.ou_id,
ou_name: savedUserData.ou_name,
is_leader: savedUserData.is_leader,
user_role: userRole
};
// async function generateFrontendJWT(userInfo: UserInfo, savedUserData: SsoUser, userRole: UserRole, expiresIn: number): Promise<string> {
// const jwtUserInfo: UserInfoForJWT = {
// sub: userInfo.sub,
// user_id: savedUserData.id!,
// username: savedUserData.username,
// nick_name: savedUserData.nick_name,
// email: savedUserData.email,
// phone_number: savedUserData.phone_number,
// ou_id: savedUserData.ou_id,
// ou_name: savedUserData.ou_name,
// is_leader: savedUserData.is_leader,
// user_role: userRole
// };
return JWTUtils.generateJWT(jwtUserInfo, expiresIn);
}
// return JWTUtils.generateJWT(jwtUserInfo, expiresIn);
// }
/**
* 创建包含JWT的用户信息对象
@@ -169,38 +169,44 @@ async function generateFrontendJWT(userInfo: UserInfo, savedUserData: SsoUser, u
* @param frontendJWT 前端JWT
* @returns 完整的用户信息对象
*/
function createUserInfoWithJWT(userInfo: UserInfo, savedUserData: SsoUser, userRole: UserRole, frontendJWT: string) {
return {
// 保持与callback.tsx中enhancedUserInfo相同的数据结构
sub: userInfo.sub,
username: savedUserData.username,
nick_name: savedUserData.nick_name,
phone_number: savedUserData.phone_number,
email: savedUserData.email,
ou_id: savedUserData.ou_id,
ou_name: savedUserData.ou_name,
status: savedUserData.status,
is_leader: savedUserData.is_leader,
// 增强字段,与OAuth登录保持一致
user_id: savedUserData.id,
user_role: userRole,
frontend_jwt: frontendJWT
};
}
// function createUserInfoWithJWT(userInfo: UserInfo, savedUserData: SsoUser, userRole: UserRole, frontendJWT: string) {
// return {
// // 保持与callback.tsx中enhancedUserInfo相同的数据结构
// sub: userInfo.sub,
// username: savedUserData.username,
// nick_name: savedUserData.nick_name,
// phone_number: savedUserData.phone_number,
// email: savedUserData.email,
// ou_id: savedUserData.ou_id,
// ou_name: savedUserData.ou_name,
// status: savedUserData.status,
// is_leader: savedUserData.is_leader,
// // 增强字段,与OAuth登录保持一致
// user_id: savedUserData.id,
// user_role: userRole,
// frontend_jwt: frontendJWT
// };
// }
export async function getUserSession(request: Request) {
const session = await getSession(request);
const isAuthenticated = session.get("isAuthenticated") === true;
const userRole = session.get("userRole") as UserRole;
let accessToken = session.get("accessToken");
const accessToken = session.get("accessToken");
const refreshToken = session.get("refreshToken");
let tokenIssuedAt = session.get("tokenIssuedAt");
const tokenIssuedAt = session.get("tokenIssuedAt");
let tokenExpiresIn = session.get("tokenExpiresIn");
const userInfo = session.get("userInfo");
let frontendJWT = session.get("frontendJWT");
const frontendJWT = session.get("frontendJWT");
// 🔑 检查是否是公共路径(不需要认证的路径)
const url = new URL(request.url);
const pathname = url.pathname;
const publicPaths = ['/login', '/favicon.ico', '/callback'];
const isPublicPath = publicPaths.some(path => pathname.startsWith(path));
let refreshedSession = null;
let shouldRegenerateJWT = false;
// let shouldRegenerateJWT = false;
// 🔑 新的统一过期检查逻辑
// 不区分 admin 和 OAuth 用户,所有用户都使用同样的过期检查
@@ -251,20 +257,44 @@ export async function getUserSession(request: Request) {
}
}
// 🚨 如果 session 无效(包括 token 过期),自动重定向到登录页
if (!finalIsAuthenticated && isAuthenticated) {
console.error("❌ [getUserSession] Session 已失效,清除 session 并重定向到登录页");
// 🚨 统一的认证检查和重定向逻辑
if (!finalIsAuthenticated) {
// 如果是公共路径,不重定向,直接返回未认证状态
if (isPublicPath) {
// console.log("️ [getUserSession] 公共路径,允许未认证访问:", pathname);
return {
isAuthenticated: false,
userRole,
accessToken,
refreshToken,
userInfo,
refreshedSession,
frontendJWT
};
}
// 销毁服务端 session
const { redirect } = await import("@remix-run/node");
const destroyedSession = await sessionStorage.destroySession(session);
// 非公共路径且未认证,重定向到登录页
if (isAuthenticated) {
// Session 存在但已失效(token 过期或数据不完整)
console.error("❌ [getUserSession] Session 已失效,清除 session 并重定向到登录页");
const { redirect } = await import("@remix-run/node");
const destroyedSession = await sessionStorage.destroySession(session);
// 重定向到登录页,添加 expired=true 参数标识是因为过期重定向
throw redirect("/login?expired=true", {
headers: {
"Set-Cookie": destroyedSession
}
});
// 重定向到登录页,添加 expired=true 参数
throw redirect("/login?expired=true", {
headers: {
"Set-Cookie": destroyedSession
}
});
} else {
// Session 不存在(首次访问或已登出)
console.warn("⚠️ [getUserSession] 未登录,重定向到登录页");
const { redirect } = await import("@remix-run/node");
// 保存当前路径,登录后可以跳转回来
const redirectTo = pathname !== '/login' ? pathname : '/';
throw redirect(`/login?redirect=${encodeURIComponent(redirectTo)}`);
}
}
return {