1. UiTrainplanRequest 公用实现的核心挑战
在前后台分离架构中,前端调用公用组件的设计往往面临几个典型痛点。以UiTrainplanRequest为例,这个组件需要同时服务于管理后台和用户前台,但两者的权限控制、数据过滤逻辑和错误处理机制可能存在显著差异。我曾在一个电商平台项目中遇到过类似场景,后台需要完整订单数据而前台只能查看用户自身订单,公用组件如果设计不当会导致严重的权限漏洞。
公用实现的首要原则是"统一契约,差异处理"。具体到UiTrainplanRequest,我们需要确保:
- 基础请求URL和参数结构保持一致
- 身份认证采用统一机制(如JWT)
- 错误码体系遵循相同规范
但必须为不同端保留定制化空间,比如:
javascript复制// 基础请求类
class BaseTrainplanRequest {
constructor(apiPath) {
this.baseUrl = '/api/v1/trainplan';
this.headers = {
'Authorization': `Bearer ${getToken()}`
};
}
// 公用请求方法
async send(params) {
try {
const response = await axios({
method: 'post',
url: `${this.baseUrl}/${this.apiPath}`,
data: params,
headers: this.headers
});
return this._handleResponse(response);
} catch (error) {
this._handleError(error);
}
}
// 可被子类覆盖的钩子方法
_handleResponse(response) {
return response.data;
}
_handleError(error) {
console.error('Request failed:', error);
throw error;
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 前后台差异化的实现策略
2.1 权限控制的分层设计
后台系统通常需要管理员级权限,而前台可能只需要基础用户权限。建议采用装饰器模式实现权限控制:
javascript复制// 权限装饰器
function withAdminPermission(target) {
const originalSend = target.prototype.send;
target.prototype.send = async function(params) {
if (!checkAdminRole()) {
throw new Error('Insufficient permissions');
}
return originalSend.call(this, params);
};
return target;
}
// 后台专用类
@withAdminPermission
class AdminTrainplanRequest extends BaseTrainplanRequest {
constructor() {
super('admin');
}
}
2.2 数据过滤的管道模式
前台通常需要过滤敏感字段,可以通过管道模式实现:
javascript复制class FrontendTrainplanRequest extends BaseTrainplanRequest {
constructor() {
super('frontend');
this.filters = [
this._filterSensitiveData,
this._formatDisplayData
];
}
_handleResponse(response) {
return this.filters.reduce(
(data, filter) => filter(data),
super._handleResponse(response)
);
}
_filterSensitiveData(data) {
delete data.internalNotes;
delete data.costDetails;
return data;
}
_formatDisplayData(data) {
return {
...data,
schedule: formatDate(data.schedule)
};
}
}
3. 工程化最佳实践
3.1 配置化请求策略
建议将差异点抽象为配置项,通过webpack环境变量区分:
javascript复制// config/request.js
export const getRequestConfig = () => ({
baseURL: process.env.API_BASE,
timeout: process.env.NODE_ENV === 'production' ? 5000 : 10000,
withCredentials: true
});
// 请求工厂
export function createTrainplanRequest() {
return process.env.BUILD_TARGET === 'admin'
? new AdminTrainplanRequest()
: new FrontendTrainplanRequest();
}
3.2 类型安全的TypeScript实现
如果使用TypeScript,可以定义泛型接口确保类型安全:
typescript复制interface ITrainplanResponse<T> {
code: number;
data: T;
message?: string;
}
class TypedTrainplanRequest<T> extends BaseTrainplanRequest {
async sendTyped(params: any): Promise<ITrainplanResponse<T>> {
const response = await super.send(params);
return response as ITrainplanResponse<T>;
}
}
// 前台使用示例
interface FrontendTrainplan {
id: string;
title: string;
schedule: string;
}
const request = new TypedTrainplanRequest<FrontendTrainplan>();
const result = await request.sendTyped({ id: '123' });
4. 部署与调优要点
4.1 Nginx反向代理配置
公用API接口在部署时需要特别注意路径转发规则:
nginx复制location /api/v1/trainplan {
proxy_pass http://backend-service;
# 前后台区分头
if ($http_x_client_type = "admin") {
proxy_set_header X-Required-Role admin;
}
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache off;
}
4.2 性能监控与降级策略
建议在公用请求类中加入性能埋点:
javascript复制class MonitoredTrainplanRequest extends BaseTrainplanRequest {
async send(params) {
const start = performance.now();
try {
const result = await super.send(params);
trackSuccess(performance.now() - start);
return result;
} catch (error) {
trackError(error, performance.now() - start);
// 前台降级策略
if (this.isFrontend) {
return getCachedData();
}
throw error;
}
}
}
5. 常见问题解决方案
5.1 CSRF防护的兼容处理
前后台可能使用不同的CSRF防护策略,推荐采用双重token机制:
javascript复制// 请求拦截器示例
axios.interceptors.request.use(config => {
if (config.url.includes('/trainplan')) {
config.headers['X-CSRF-TOKEN'] = getCSRFToken();
config.headers['X-Client-Type'] = process.env.BUILD_TARGET;
}
return config;
});
5.2 接口版本兼容方案
当接口需要升级时,建议通过header控制版本:
javascript复制class VersionedTrainplanRequest extends BaseTrainplanRequest {
constructor(version = 'v1') {
super();
this.headers['Accept-Version'] = version;
}
// 支持版本回退
async fallbackRequest(params) {
try {
return await this.send(params);
} catch (error) {
if (error.response?.status === 406) {
this.headers['Accept-Version'] = 'v0';
return this.send(params);
}
throw error;
}
}
}
在大型项目中,我曾遇到一个典型案例:前台需要显示精简版培训计划而后台需要完整数据。通过上述模式,我们实现了核心请求逻辑复用率85%的同时,保持了足够的灵活性。关键是要建立清晰的扩展点文档,说明哪些方法可以覆盖、哪些应该保持稳定
