You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Node.js+MariaDB会话过期重登录认证回退及数据库离线兼容问题

社交应用数据库离线回退逻辑实现及问题排查

现有代码片段

前端Login.jsx登录请求代码

fetch(getApiUrl('/auth/login'), {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(values),
  credentials: 'include',
  })
  .then(res => {
    const jsn = res.json();
    if (res.ok) {
      handleAuthLogin(navigate, revalidate)(jsn);
    } else if (jsn.message) {
     toast.error(<div className='text-lg'>{jsn.message}</div>);
    }
  })
  .catch((err) => {
    console.error(err);
    if (err.message === 'Failed to fetch') {
      navigate('/service-unavailable');
    } else if (err.message === 'Invalid token' || err.message === 'Unauthorized') {
      navigate('/login');
    } else {
      toast.error(<div className='text-lg'>Failed to connect to the server. Please check your connection.</div>);
    }
  });

后端index.js代码片段

/**
 * Middleware for handling authentication routes.
 * All routes starting with '/auth' will be handled by the authRouter.
 */
app.use(checkToken);
app.use('/auth', authRouter);
app.use(userRouter);

// Set up the /users endpoint
app.get('/users', async (req, res) => {
    const { name } = req.query.name;

    // Query the database using Prisma
    const users = await prisma.user.findMany({
        where: {
            name: {
                contains: name,
            },
        },
    });

    // Send the result back to the client
    res.json(users);
});

checkToken中间件代码

const checkToken = (req, res, next) => {
    const token = req.headers['authorization'];
    console.log('token: ', token);

    if (!token) {
        return res.status(403).send({ message: 'No token provided.' });
    }

    try {
        const decoded = decodeJwt(token);
        req.userId = decoded.id;
        next();
    } catch (error) {
        return res.status(401).send({ message: 'Unauthorized! Session expired.' });
    }
};

表单验证代码

const formVerification = (req, res, action) => {
  // Extract the data from the request body
  const data = req.body;
  console.log(data);

  // Validate the data against the schema
  schema
    .validate(data)
    .catch(err => {
      // If validation fails, send a 422 status code and the validation error messages
      res.status(422).json({ status: 422, message: err.errors });
    })
    .then(valid => {
      if (valid) {
        const cred = {
          usr: data.username,
          pwd: data.password
        };

        if (action === "login") {
          // Login handle
          handleLogin(cred, req, res);
        } else if (action === "signup") {
          // Signup handle
          handleSignup(cred, req, res);
        } else {
          throw new Error("Invalid action");
        }
      } else {
        res.status(400).json({ status: 400, message: "Invalid request" });
      }
    });
}

handleLogin及JWT相关代码

const hashPwd = (pwd) => {
    return createHash("md5").update(pwd).digest("hex");
};

const encodeJwt = ({ id, name }) => {
    return sign({ id, name }, secret, { expiresIn: "1h" });
};

const decodeJwt = (jwt) => {
    try {
        return verify(jwt, secret);
    } catch (e) {
        throw new Error('Failed to decode JWT: ' + e.message);
    }
};
const handleLogin = ({ usr, pwd }, req, res) => {
    console.log('usr: ', usr, 'pwd: ', pwd);
    getUser(usr, (err, user) => {
        if (err) {
            console.error(err);
            return res.status(500).json({ status: 500, message: "Database is offline" });
        }
        console.log('user: ', user);
        if (!user) {
            return res.status(404).json({ status: 404, message: "User not found" });
        } else if (user.credentials.hash !== hashPwd(pwd)) {
            return res.status(401).json({ status: 401, message: "Invalid password" });
        } else {
            return res.status(200).json({ status: 200, jwt: encodeJwt(user), user: { id: user.id, name: user.name } });
        }
    }, true);
};

遇到的问题

  1. 应用挂载时触发user.js:29 GET http://localhost:5000/user/@me 401 (Unauthorized)错误,对应代码:
// No token stored in local store
const NO_TOKEN_ERR = "no_token";

const UserContext = createContext(null);
const UserProvider = ({ children }) => {
    const [user, setUser] = useState(null);
    const [fetching, setFetching] = useState(false);
    const [error, setError] = useState(null);
    
    // Obtain new data
    const revalidate = () => {
        // Obtain local JWT val
        const jwt = localStorage.getItem("jwt");
        if (!jwt) {
            setError(NO_TOKEN_ERR);
            return;
        }
        const init = {
            method: "GET",
            headers: {
                'Authorization': `Bearer ${jwt}`
            }
        }
        // Fetch that thing
        fetch(getApiUrl("/user/@me"), init) // line 29
            .then(res => res.json())
            .then(({ user }) => setUser(user))
            .catch((e) => setError(e.message))
            .finally(() => setFetching(false))
    }
    // rest of the code
}
  1. 提交登录表单时触发POST http://localhost:5000/auth/login 403 (Forbidden)错误,提示token未定义,但应用挂载时本地存储存在哈希后的token。

需求

  • 数据库离线时自动跳转至/service-unavailable页面
  • 数据库正常时,完成表单验证、登录等常规社交应用功能

解决方案

问题1:401 Unauthorized 错误修复

  • 调整后端中间件顺序:checkToken中间件不应拦截登录/注册路由,修改后代码:
// 修改后
app.use('/auth', authRouter); // 登录/注册路由无需token校验
app.use(checkToken); // 其他需要校验的路由放在中间件之后
app.use(userRouter);
  • 前端revalidate函数补充响应状态校验:
fetch(getApiUrl("/user/@me"), init)
    .then(res => {
        if (!res.ok) {
            if (res.status === 401) {
                localStorage.removeItem("jwt");
                setError("Unauthorized");
                // 可在此添加跳转登录页逻辑
            }
            throw new Error(`HTTP error! status: ${res.status}`);
        }
        return res.json();
    })
    .then(({ user }) => setUser(user))
    .catch((e) => setError(e.message))
    .finally(() => setFetching(false))

问题2:登录请求403 Forbidden 错误修复

按照上述中间件顺序调整后,/auth路由不再被checkToken拦截,即可解决该问题。

数据库离线回退逻辑实现

  1. 前端处理后端返回的数据库离线标识:修复Login.jsx中res.json()的Promise解析问题,补充500状态处理:
.then(res => {
    res.json().then(data => {
        if (res.ok) {
            handleAuthLogin(navigate, revalidate)(data);
        } else {
            if (data.status === 500 && data.message === "Database is offline") {
                navigate('/service-unavailable');
            } else if (data.message) {
                toast.error(<div className='text-lg'>{data.message}</div>);
            }
        }
    });
})
  1. 全局封装fetch工具函数:统一处理网络错误和数据库离线情况:
const fetchWithFallback = (url, options = {}) => {
    return fetch(url, options)
        .then(res => {
            if (res.status === 500) {
                return res.json().then(data => {
                    if (data.message === "Database is offline") {
                        throw new Error("Database offline");
                    }
                    return res;
                });
            }
            return res;
        })
        .catch(err => {
            if (err.message === "Failed to fetch" || err.message === "Database offline") {
                window.location.href = '/service-unavailable';
            }
            throw err;
        });
}

所有请求替换为该工具函数,实现统一跳转。
3. 配置服务不可用页面:确保前端路由存在/service-unavailable页面,展示数据库离线提示信息。


内容的提问来源于stack exchange,提问作者Patrik Bajzík

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:20:43