🌍 环境模块
环境模块负责管理 SCP 系统的核心配置,包括用户认证和接口地址设置。
📋 概述
环境模块提供两个核心环境变量的管理:
token: 用户认证令牌scpEndpoint: 后端 API 接口地址
🚀 快速开始
基础配置
ts
// 设置用户认证token(建议在用户登录成功后设置)
scp.env.token.set('your_access_token');
// 配置后端API接口地址(根据部署环境调整)
scp.env.scpEndpoint.set('http://bcapi-dev1.e-tudou.com');🔐 Token 配置
配置时机
用户认证 token 需要在用户登录成功后立即设置,确保后续 API 调用的身份验证。
登录集成示例
以下是完整的登录处理函数,展示了如何在登录成功后正确设置 token:
ts
/**
* 用户登录处理函数
*
* 参考文献:
* 1. 后端 sa-token: https://sa-token.cc/v/v1.34.0/doc.html#/fun/token-info
* 2. 前端 js-cookie: https://www.npmjs.com/package/js-cookie
*/
function onLogin() {
if (activeKey.value === "account") {
// 账号密码登录
formAccountRef.value?.validate().then(async () => {
loading.value = true;
const formData = toRaw(formAccount);
try {
// 调用登录接口
const { username, password } = formData;
const apiParams = {
name: username,
client: CLIENT_ID,
pwd: shuffleBase64Encode(md5(password))
};
const { msg, data } = await loginApi(apiParams);
if (!data) {
return message.error(msg);
}
// 构建用户信息
let userInfo = {
tenantId: data.principalInfo.tenantId,
userId: data.principalInfo.userId,
userName: data.principalInfo.userName
};
// 存储到本地存储
storage.set({
expires_time: data.tokenInfo.tokenTimeout / (60 * 60 * 24),
token: data.tokenInfo.tokenValue,
access_token: data.tokenInfo.tokenValue, // 兼容旧的 SAAS 应用
userInfo: JSON.stringify(userInfo)
});
// 🔑 关键步骤:设置 SCP 环境变量中的 token
window.scp.env.token.set(data.tokenInfo.tokenValue);
// 页面跳转
pageJump();
} finally {
loading.value = false;
}
});
} else if (activeKey.value === "captcha") {
// 短信验证码登录
formCaptchaRef.value?.validate().then(async () => {
loading.value = true;
const formData = toRaw(formCaptcha);
try {
// 调用短信登录接口
const { mobile, captcha } = formData;
const apiParams = {
name: mobile,
validCode: captcha
};
const { msg, data } = await loginBySmsApi(apiParams);
if (!data) {
return message.error(msg);
}
message.success(msg);
// 构建用户信息
let userInfo = {
tenantId: data.principalInfo.tenantId,
userId: data.principalInfo.userId,
userName: data.principalInfo.userName
};
// 存储到本地存储
storage.set({
expires_time: data.tokenInfo.tokenTimeout / (60 * 60 * 24),
token: data.tokenInfo.tokenValue,
access_token: data.tokenInfo.tokenValue, // 兼容旧的 SAAS 应用
userInfo: JSON.stringify(userInfo)
});
// 🔑 关键步骤:设置 SCP 环境变量中的 token
window.scp.env.token.set(data.tokenInfo.tokenValue);
// 页面跳转
pageJump();
} finally {
loading.value = false;
}
});
}
}🌐 接口地址配置
配置时机
scpEndpoint 需要在路由拦截器中设置,确保每次页面访问时都有正确的接口地址。
Vue 项目路由拦截器集成
ts
/**
* 路由拦截器 - beforeEach
* 在每次路由跳转前设置环境变量
*/
router.beforeEach(async (to, from, next) => {
const cookie_token = storage.get("token");
const cookie_userInfoStr = storage.get("userInfo");
const authStore = AuthStore();
// 🔑 关键步骤:从本地存储恢复 token
window.scp.env.token.set(cookie_token);
// 🌐 关键步骤:设置 API 接口地址
window.scp.env.scpEndpoint.set(import.meta.env.VITE_API_BASE_DOMAIN);
// 1. NProgress 开始
ConditionalNProgress.start();
// 2. 动态设置标题
const title = import.meta.env.VITE_GLOB_APP_TITLE;
document.title = to.meta.title
? `${tt(generatePinyin(to.meta.title as string, undefined), to.meta.title as string)} - ${tt(
generatePinyin(title, undefined),
title
)}`
: tt(generatePinyin(title, undefined), title);
// 3. 判断是访问登录页,有 Token 就在当前页面,没有 Token 重置路由并放行到登录页
if (to.path.toLocaleLowerCase() === LOGIN_URL) {
if (cookie_token && cookie_userInfoStr) return next(from.fullPath);
storage.clear();
resetRouter();
return next();
}
// 4. 白名单可以直接访问
if (ROUTER_WHITE_LIST.includes(to.path)) {
return next();
}
// 5. 判断是否有 Token,没有重定向到 login
if (!cookie_token || !cookie_userInfoStr) return next({ path: LOGIN_URL, replace: true });
// 6. 如果没有菜单列表,就重新请求菜单列表并添加动态路由
authStore.setRouteName(to.name as string);
if (!authStore.authMenuListGet.length) {
await initDynamicRouter();
return next({ ...to, replace: true });
}
// 7. 正常访问页面
next();
});React 项目环境变量配置
在 React 项目中,可以使用 React Router 和 useEffect 钩子来实现类似的功能:
文件名: src/router/AuthGuard.tsx
ts
import { useEffect, useState } from "react"; // 新增useState
import { useLocation } from "react-router";
import { storage } from "@pt/utils/modules/storage";
import { getLoginUrl, ROUTER_WHITE_LIST } from "@/config";
interface AuthGuardProps {
children: React.ReactNode;
}
async function initConfig() {
// @ts-ignore
await scp.env.scpEndpoint.set(import.meta.env.VITE_API_BASE_DOMAIN);
// @ts-ignore
await scp.env.token.set(storage.get("token"));
}
/**
* 路由守卫组件
* 用于验证用户登录状态和权限
*/
export default function AuthGuard({ children }: AuthGuardProps) {
const location = useLocation();
// 新增加载状态:true=还在初始化/校验,false=已完成
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let isCancelled = false;
const checkAuth = async () => {
try {
const currentPath = location.pathname;
const cookie_token = storage.get("token");
const cookie_userInfoStr = storage.get("userInfo");
console.log('[AuthGuard] 路由守卫检查:', {
currentPath,
hasToken: !!cookie_token,
hasUserInfo: !!cookie_userInfoStr,
token: cookie_token,
userInfo: cookie_userInfoStr,
});
// 先执行初始化,确保scp.env配置完成
await initConfig();
if (isCancelled) return;
// 白名单路由直接放行
if (ROUTER_WHITE_LIST.includes(currentPath)) {
console.log('[AuthGuard] 白名单路由,直接放行');
setIsLoading(false); // 加载完成,允许渲染
return;
}
// 无认证信息则跳转登录
if (!cookie_token || !cookie_userInfoStr) {
console.error('[AuthGuard] 缺少认证信息,准备跳转登录页');
storage.clear();
window.location.href = getLoginUrl();
return;
}
// 认证通过,加载完成
setIsLoading(false);
} catch (error) {
console.error('[AuthGuard] 认证检查/初始化失败:', error);
if (!isCancelled) {
storage.clear();
window.location.href = getLoginUrl();
}
}
};
checkAuth();
return () => {
isCancelled = true;
setIsLoading(true); // 卸载时重置加载状态
console.log('[AuthGuard] 路由变化/组件卸载,终止后续操作');
};
}, [location.pathname]);
// 关键:加载中不渲染子组件,避免提前读取未初始化的scp.env
if (isLoading) {
// 可替换为项目的加载组件,比如 <Spin />
return <div>加载中...</div>;
}
return <>{children}</>;
}📚 API 参考
Token 管理
scp.env.token.set(token: string)
设置用户认证令牌。
参数:
token(string): 用户认证
示例:
ts
// 设置 token
scp.env.token.set('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// 获取当前 token
const currentToken = scp.env.token.get();scp.env.token.get()
获取当前设置的用户认证令牌。
返回值:
string | null: 当前的认证令牌,如果未设置则返回 null
接口地址管理
scp.env.scpEndpoint.set(endpoint: string)
设置后端 API 接口地址。
参数:
endpoint(string): 后端 API 的基础地址
环境配置说明
根据不同的部署环境,需要配置对应的接口地址:
开发环境 (dev1):
bash
# .env.development
VITE_API_BASE_DOMAIN = "http://bcapi-dev1.e-tudou.com"测试环境 (beta1):
bash
# .env.beta
VITE_API_BASE_DOMAIN = "https://bcapi-beta1.e-tudou.com"生产环境 (prod):
bash
# .env.production
VITE_API_BASE_DOMAIN = "https://bcapi.i-tudou.com"私有化环境:
bash
# .env.private
VITE_API_BASE_DOMAIN = "https://bcapi.tudoucloud.com"⚠️ 注意: 其他环境服务地址请参考部署文档设置
代码示例:
ts
// 根据环境变量自动设置接口地址
const API_ENDPOINT = import.meta.env.VITE_API_BASE_DOMAIN;
scp.env.scpEndpoint.set(API_ENDPOINT);
// 手动设置不同环境
// 开发环境
scp.env.scpEndpoint.set('http://bcapi-dev1.e-tudou.com');
// 测试环境
scp.env.scpEndpoint.set('https://bcapi-beta1.e-tudou.com');
// 生产环境
scp.env.scpEndpoint.set('https://bcapi.i-tudou.com');
// 私有化环境
scp.env.scpEndpoint.set('https://bcapi.tudoucloud.com');
// 获取当前接口地址
const currentEndpoint = scp.env.scpEndpoint.get();scp.env.scpEndpoint.get()
获取当前设置的接口地址。
返回值:
string | null: 当前的接口地址,如果未设置则返回 null
🔧 最佳实践
1. 环境变量管理
ts
// 推荐:使用环境变量管理不同环境的配置
const API_BASE_URL = import.meta.env.VITE_API_BASE_DOMAIN || 'http://localhost:3000';
scp.env.scpEndpoint.set(API_BASE_URL);2. Token 生命周期管理
ts
// 登录时设置
function handleLogin(tokenData) {
// 存储到本地
storage.set('token', tokenData.tokenValue);
// 设置到环境变量
scp.env.token.set(tokenData.tokenValue);
}
// 登出时清理
function handleLogout() {
// 清理本地存储
storage.remove('token');
// 清理环境变量
scp.env.token.set(null);
}3. 应用初始化
ts
// 应用启动时恢复环境变量
function initializeApp() {
const savedToken = storage.get('token');
const apiEndpoint = import.meta.env.VITE_API_BASE_DOMAIN;
if (savedToken) {
scp.env.token.set(savedToken);
}
if (apiEndpoint) {
scp.env.scpEndpoint.set(apiEndpoint);
}
}⚠️ 注意事项
安全性
- 不要在客户端代码中硬编码敏感信息
- Token 应该通过安全的登录流程获取
- 使用 HTTPS 传输敏感数据
错误处理
ts
try {
scp.env.token.set(tokenValue);
} catch (error) {
console.error('设置 token 失败:', error);
// 处理错误逻辑
}类型安全
ts
// 推荐:添加类型检查
function setToken(token: string | null) {
if (typeof token === 'string' && token.length > 0) {
scp.env.token.set(token);
} else {
console.warn('无效的 token 值');
}
}🐛 常见问题
Q: Token 设置后 API 调用仍然返回 401 错误?
A: 检查以下几点:
- 确认 token 格式正确
- 验证 token 是否已过期
- 检查接口地址是否正确设置
- 确认后端接口是否正常工作
Q: 页面刷新后 token 丢失?
A: 确保在路由拦截器中正确恢复 token:
ts
router.beforeEach((to, from, next) => {
const savedToken = storage.get('token');
if (savedToken) {
scp.env.token.set(savedToken);
}
next();
});Q: 如何在多个环境间切换?
A: 使用环境变量配置,根据不同环境创建对应的配置文件:
bash
# .env.development (开发环境)
VITE_API_BASE_DOMAIN="http://bcapi-dev1.e-tudou.com"
# .env.beta (测试环境)
VITE_API_BASE_DOMAIN="https://bcapi-beta1.e-tudou.com"
# .env.production (生产环境)
VITE_API_BASE_DOMAIN="https://bcapi.i-tudou.com"
# .env.private (私有化环境)
VITE_API_BASE_DOMAIN="https://bcapi.tudoucloud.com"构建时 Vite 会根据 --mode 参数自动加载对应的环境配置:
bash
# 开发环境
npm run dev
# 测试环境构建
npm run build --mode beta
# 生产环境构建
npm run build --mode production
# 私有化环境构建
npm run build --mode private