乐闻世界logo
搜索文章和话题

面试题手册

如何在 React Native 中集成和使用 Lottie 动画?

在 React Native 中使用 Lottie 动画需要以下步骤:1. 安装依赖npm install lottie-react-native# 或yarn add lottie-react-native对于 iOS,还需要安装 CocoaPods 依赖:cd ios && pod install2. 基本使用import LottieView from 'lottie-react-native';import { useRef, useEffect } from 'react';const MyComponent = () => { const animationRef = useRef(null); useEffect(() => { // 自动播放动画 animationRef.current?.play(); }, []); return ( <LottieView ref={animationRef} source={require('./animation.json')} autoPlay={false} loop={true} style={{ width: 200, height: 200 }} /> );};3. 常用属性source:动画数据源,可以是 require() 或 URLautoPlay:是否自动播放loop:是否循环播放progress:动画进度(0-1)speed:播放速度direction:播放方向(1 正向,-1 反向)colorFilters:颜色过滤器resizeMode:调整模式(cover, contain, center)4. 动画控制方法const animationRef = useRef(null);// 播放动画animationRef.current?.play();// 暂停动画animationRef.current?.pause();// 停止动画并重置animationRef.current?.reset();// 播放到指定进度animationRef.current?.play(30, 60); // 从 30% 播放到 60%// 设置进度animationRef.current?.setProgress(0.5); // 设置到 50%**5. 动态属性修改**jsxconst [animationData, setAnimationData] = useState(null);useEffect(() => { // 动态加载动画 fetch('https://example.com/animation.json') .then(response => response.json()) .then(data => setAnimationData(data));}, []);**6. 事件监听**jsx console.log('Animation finished')} onAnimationLoad={() => console.log('Animation loaded')} onAnimationLoop={() => console.log('Animation looped')}/>**7. 性能优化**- 使用 `useNativeDriver={true}` 启用原生驱动- 对于复杂动画,考虑使用 `LottieView` 的 `cacheComposition` 属性- 在列表中使用时,使用 `FlatList` 的 `removeClippedSubviews` 属性- 避免在不可见的组件中播放动画**8. 常见问题解决**- 动画不显示:检查 JSON 文件路径是否正确- 动画卡顿:检查设备性能,考虑降低帧率- iOS 上不工作:确保已运行 `pod install`- Android 上不工作:检查 Gradle 配置**9. 高级用法**jsx// 使用 LottieComposition 预加载动画import { useLottie } from 'lottie-react-native';const { View } = useLottie({ src: require('./animation.json'), loop: true, autoPlay: true,});// 动态修改颜色```
阅读 0·2月19日 17:55

在 Spring Boot 中如何实现 CSRF 防护?

在 Spring Boot 中实现 CSRF 防护有多种方式,Spring Security 提供了内置的 CSRF 保护机制。Spring Security CSRF 保护概述Spring Security 默认启用 CSRF 保护,它通过以下方式工作:生成 CSRF Token将 Token 存储在服务器会话中在表单中自动添加 Token验证请求中的 Token配置方式1. 使用默认配置(推荐)Spring Security 默认启用 CSRF 保护,无需额外配置。@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll(); }}2. 自定义 CSRF Token 存储默认使用 HttpSessionCsrfTokenRepository,可以自定义存储方式。使用 Cookie 存储@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() .anyRequest().authenticated(); }}自定义 Token Repositorypublic class CustomCsrfTokenRepository implements CsrfTokenRepository { @Override public CsrfToken generateToken(HttpServletRequest request) { String token = UUID.randomUUID().toString(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token); } @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { // 自定义存储逻辑 request.getSession().setAttribute("_csrf", token); } @Override public CsrfToken loadToken(HttpServletRequest request) { // 自定义加载逻辑 return (CsrfToken) request.getSession().getAttribute("_csrf"); }}@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(new CustomCsrfTokenRepository()) .and() .authorizeRequests() .anyRequest().authenticated(); }}3. 禁用 CSRF 保护(不推荐)某些情况下可能需要禁用 CSRF 保护(如 REST API)。@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .and() .authorizeRequests() .anyRequest().authenticated(); }}4. 部分禁用 CSRF 保护只为特定路径禁用 CSRF 保护。@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .ignoringAntMatchers("/api/**", "/public/**") .and() .authorizeRequests() .antMatchers("/api/**").permitAll() .anyRequest().authenticated(); }}前端集成1. Thymeleaf 模板Spring Security 自动在 Thymeleaf 模板中添加 CSRF Token。<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>Login</title></head><body> <form th:action="@{/login}" method="post"> <!-- CSRF Token 自动添加 --> <input type="text" name="username" placeholder="Username"> <input type="password" name="password" placeholder="Password"> <button type="submit">Login</button> </form></body></html>2. JSP 模板<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %><form action="/login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <input type="text" name="username" placeholder="Username"> <input type="password" name="password" placeholder="Password"> <button type="submit">Login</button></form>3. AJAX 请求// 获取 CSRF Tokenfunction getCsrfToken() { const metaTag = document.querySelector('meta[name="_csrf"]'); const headerName = document.querySelector('meta[name="_csrf_header"]'); return { token: metaTag ? metaTag.getAttribute('content') : '', headerName: headerName ? headerName.getAttribute('content') : 'X-CSRF-TOKEN' };}// 发送 AJAX 请求function sendAjaxRequest(url, data) { const { token, headerName } = getCsrfToken(); return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', [headerName]: token }, body: JSON.stringify(data) });}// 使用示例sendAjaxRequest('/api/data', { name: 'John' }) .then(response => response.json()) .then(data => console.log(data));4. 在 HTML 中添加 Meta 标签<!DOCTYPE html><html><head> <meta name="_csrf" th:content="${_csrf.token}"/> <meta name="_csrf_header" th:content="${_csrf.headerName}"/> <title>My App</title></head><body> <!-- 页面内容 --></body></html>高级配置1. 自定义 CSRF Token 生成器@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(csrfTokenRepository()) .and() .authorizeRequests() .anyRequest().authenticated(); } @Bean public CsrfTokenRepository csrfTokenRepository() { HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); repository.setHeaderName("X-CSRF-TOKEN"); repository.setParameterName("_csrf"); return repository; }}2. 自定义 CSRF Token 验证器public class CustomCsrfTokenValidator implements CsrfTokenValidator { @Override public boolean validateToken(HttpServletRequest request, CsrfToken token) { // 自定义验证逻辑 String requestToken = request.getHeader(token.getHeaderName()); return token.getToken().equals(requestToken); }}@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenValidator(new CustomCsrfTokenValidator()) .and() .authorizeRequests() .anyRequest().authenticated(); }}3. 配置 SameSite Cookie@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("SESSION"); serializer.setCookiePath("/"); serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); serializer.setSameSite("Lax"); serializer.setUseHttpOnlyCookie(true); serializer.setUseSecureCookie(true); return serializer; }}测试 CSRF 保护1. 单元测试@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class CsrfProtectionTest { @Autowired private MockMvc mockMvc; @Test public void testCsrfProtection() throws Exception { mockMvc.perform(post("/transfer") .contentType(MediaType.APPLICATION_JSON) .content("{\"amount\":100}")) .andExpect(status().isForbidden()); } @Test public void testWithValidCsrfToken() throws Exception { MvcResult result = mockMvc.perform(get("/csrf")) .andReturn(); String csrfToken = result.getResponse().getContentAsString(); mockMvc.perform(post("/transfer") .contentType(MediaType.APPLICATION_JSON) .header("X-CSRF-TOKEN", csrfToken) .content("{\"amount\":100}")) .andExpect(status().isOk()); }}2. 集成测试@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@AutoConfigureMockMvcpublic class CsrfIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test public void testCsrfWithRestTemplate() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // 没有 CSRF Token HttpEntity<String> request = new HttpEntity<>("{\"amount\":100}", headers); ResponseEntity<String> response = restTemplate.postForEntity("/transfer", request, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); }}常见问题1. AJAX 请求 403 错误原因:缺少 CSRF Token解决:在请求头中添加 CSRF Tokenfetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, body: JSON.stringify(data)});2. 多标签页 Token 失效原因:会话过期或 Token 不匹配解决:确保所有标签页使用同一个会话3. 文件上传失败原因:文件上传无法使用表单 Token解决:使用请求头或预签名 URL最佳实践使用默认配置:Spring Security 默认配置已经足够安全使用 Cookie 存储 Token:便于前端获取为 AJAX 请求添加 Token:确保所有请求都包含 Token定期更新 Token:降低 Token 泄露风险配合其他防护措施:如 SameSite Cookie、Origin 验证测试 CSRF 保护:确保防护机制正常工作总结Spring Boot 通过 Spring Security 提供了完善的 CSRF 保护机制。默认配置已经足够安全,可以根据需要自定义 Token 存储方式和验证逻辑。前端需要确保所有请求都包含有效的 CSRF Token,特别是 AJAX 请求。配合其他防护措施可以构建更强大的安全防护体系。
阅读 0·2月19日 17:52

如何在 Tauri 中集成 React 或其他前端框架

在 Tauri 应用中集成前端框架(React、Vue、Svelte 等)需要以下步骤:1. 创建 Tauri 项目npm create tauri-app@latest# 或cargo tauri init2. 安装前端框架以 React 为例:npm install react react-domnpm install --save-dev @vitejs/plugin-react3. 配置构建工具修改 vite.config.ts:import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';export default defineConfig({ plugins: [react()], clearScreen: false, server: { port: 1420, strictPort: true, watch: { ignored: ["**/src-tauri/**"], }, },});4. 配置 Tauri 前端入口修改 src-tauri/tauri.conf.json:{ "build": { "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build", "devPath": "http://localhost:1420", "distDir": "../dist" }}5. 创建前端应用src/main.tsx:import React from 'react';import ReactDOM from 'react-dom/client';import App from './App';ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode>);6. 使用 Tauri APIimport { invoke } from '@tauri-apps/api/tauri';import { open } from '@tauri-apps/api/dialog';const App = () => { const handleClick = async () => { const selected = await open(); console.log(selected); }; return <button onClick={handleClick}>Open File</button>;};7. 开发和构建# 开发模式npm run tauri dev# 构建生产版本npm run tauri build不同框架的注意事项React:确保正确配置 JSX 转换Vue:配置 Vue 插件和模板编译器Svelte:使用 Svelte 插件处理 .svelte 文件Next.js:需要禁用服务端渲染,配置静态导出
阅读 0·2月19日 17:51

如何在 Web 项目中集成和使用 Lottie 动画?

在 Web 项目中使用 Lottie 动画有多种方式,以下是详细的实现方法:1. 使用 lottie-web 库安装:npm install lottie-web# 或yarn add lottie-web基本使用:import lottie from 'lottie-web';// 方式1:从 URL 加载const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer: 'svg', // 'svg', 'canvas', 'html' loop: true, autoplay: true, path: 'https://example.com/animation.json'});// 方式2:从本地文件加载import animationData from './animation.json';const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer: 'svg', loop: true, autoplay: true, animationData: animationData});2. 使用 LottieFiles Player(推荐)HTML 方式:<script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script><lottie-player src="https://example.com/animation.json" background="transparent" speed="1" style="width: 300px; height: 300px" loop autoplay></lottie-player>React 组件方式:npm install @lottiefiles/react-lottie-player# 或yarn add @lottiefiles/react-lottie-playerimport { Player } from '@lottiefiles/react-lottie-player';function MyComponent() { return ( <Player autoplay loop src="https://example.com/animation.json" style={{ height: '300px', width: '300px' }} /> );}3. 使用 react-lottie安装:npm install react-lottie# 或yarn add react-lottieimport Lottie from 'react-lottie';import animationData from './animation.json';const defaultOptions = { loop: true, autoplay: true, animationData: animationData, rendererSettings: { preserveAspectRatio: 'xMidYMid slice' }};function MyComponent() { return <Lottie options={defaultOptions} height={400} width={400} />;}4. 动画控制播放控制:const animation = lottie.loadAnimation({...});// 播放animation.play();// 暂停animation.pause();// 停止animation.stop();// 设置播放速度animation.setSpeed(1.5);// 设置播放方向animation.setDirection(-1); // -1 反向,1 正向// 跳转到指定帧animation.goToAndStop(30, true); // true 表示帧数,false 表示时间// 设置进度animation.goToAndPlay(0.5, true); // 0.5 表示 50% 进度事件监听:animation.addEventListener('complete', () => { console.log('Animation completed');});animation.addEventListener('loopComplete', () => { console.log('Animation loop completed');});animation.addEventListener('enterFrame', () => { console.log('Animation entered frame');});animation.addEventListener('config_ready', () => { console.log('Configuration ready');});animation.addEventListener('data_ready', () => { console.log('Data ready');});animation.addEventListener('DOMLoaded', () => { console.log('DOM loaded');});animation.addEventListener('destroy', () => { console.log('Animation destroyed');});5. 动态属性修改修改颜色:// 使用 colorFiltersanimation.setColorFilter([ { keypath: 'layer1', color: '#FF0000' }]);// 或者直接修改 JSON 数据animationData.layers[0].shapes[0].c.k = [1, 0, 0, 1];animation.destroy();const newAnimation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), animationData: animationData, renderer: 'svg', loop: true, autoplay: true});修改文本:// 对于文本图层animationData.layers.forEach(layer => { if (layer.ty === 1) { // 文本图层 layer.t.d.k[0].s.t = 'New Text'; }});6. 性能优化使用 Canvas 渲染:const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer: 'canvas', // Canvas 比 SVG 性能更好 loop: true, autoplay: true, path: 'animation.json', rendererSettings: { preserveAspectRatio: 'xMidYMid slice', clearCanvas: false, progressiveLoad: true, hideOnTransparent: true }});懒加载:// 使用 Intersection Observerconst observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const animation = lottie.loadAnimation({ container: entry.target, renderer: 'svg', loop: true, autoplay: true, path: 'animation.json' }); observer.unobserve(entry.target); } });});document.querySelectorAll('.lottie-lazy').forEach(el => { observer.observe(el);});7. 响应式处理// 监听窗口大小变化const animation = lottie.loadAnimation({...});window.addEventListener('resize', () => { animation.resize();});// 或者使用 CSS 媒体查询#lottie-container { width: 100%; max-width: 500px; height: auto;}@media (max-width: 768px) { #lottie-container { max-width: 300px; }}8. 与框架集成Vue.js:<template> <div ref="lottieContainer"></div></template><script>import lottie from 'lottie-web';import animationData from './animation.json';export default { mounted() { this.animation = lottie.loadAnimation({ container: this.$refs.lottieContainer, renderer: 'svg', loop: true, autoplay: true, animationData: animationData }); }, beforeDestroy() { this.animation.destroy(); }};</script>Angular:import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core';import lottie from 'lottie-web';import * as animationData from './animation.json';@Component({ selector: 'app-lottie', template: '<div #lottieContainer></div>'})export class LottieComponent implements AfterViewInit { @ViewChild('lottieContainer', { static: true }) lottieContainer: ElementRef; ngAfterViewInit() { lottie.loadAnimation({ container: this.lottieContainer.nativeElement, renderer: 'svg', loop: true, autoplay: true, animationData: animationData }); }}9. 错误处理try { const animation = lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer: 'svg', loop: true, autoplay: true, path: 'animation.json' }); animation.addEventListener('data_failed', (error) => { console.error('Animation data failed to load:', error); // 显示降级内容 document.getElementById('lottie-container').innerHTML = '<img src="fallback.png" alt="Animation fallback">'; });} catch (error) { console.error('Failed to load animation:', error);}10. 最佳实践使用 CDN 加速动画文件加载为动画提供合适的占位符实现降级方案(静态图片或 CSS 动画)在组件卸载时清理动画实例使用 Intersection Observer 实现懒加载根据设备性能选择合适的渲染器压缩和优化 JSON 文件大小使用 Service Worker 缓存动画文件
阅读 0·2月19日 17:50

如何通过验证 Referer 头来防护 CSRF 攻击,有哪些局限性?

验证 Referer 头是防护 CSRF 攻击的一种传统方法,通过检查 HTTP 请求的 Referer 头来验证请求来源的合法性。Referer 头的基本原理Referer 头是 HTTP 请求头的一部分,它包含了发起请求的页面 URL。服务器可以通过检查 Referer 头来判断请求是否来自受信任的来源。验证 Referer 头的实现基本验证逻辑function validateReferer(req, trustedDomains) { const referer = req.headers.referer; if (!referer) { return false; // 拒绝没有 Referer 的请求 } try { const refererUrl = new URL(referer); const refererDomain = refererUrl.hostname; return trustedDomains.some(domain => refererDomain === domain || refererDomain.endsWith('.' + domain) ); } catch (error) { return false; // 无效的 URL }}// 使用示例app.post('/api/transfer', (req, res) => { const trustedDomains = ['example.com', 'www.example.com']; if (!validateReferer(req, trustedDomains)) { return res.status(403).send('Invalid referer'); } // 处理转账请求});验证策略1. 严格匹配Referer 必须完全匹配受信任的域名提供最强的安全性可能影响某些合法请求2. 域名匹配允许子域名更灵活的验证需要确保所有子域名都是受信任的3. 允许空 Referer某些情况下 Referer 可能为空(如直接输入 URL)需要结合其他验证方式降低安全性Referer 头的局限性1. 隐私设置某些浏览器或隐私插件可能阻止发送 Referer用户可以禁用 Referer 头导致合法请求被拒绝2. 可伪造性Referer 头可以被攻击者伪造不是绝对安全的防护方式需要配合其他防护措施3. HTTPS 降级从 HTTPS 页面发起 HTTP 请求时,Referer 可能被移除混合内容场景下的处理复杂4. 浏览器兼容性不同浏览器对 Referer 的处理可能不同某些移动浏览器的行为不一致最佳实践1. 多层防护function csrfProtection(req, res, next) { // 验证 Referer if (!validateReferer(req, trustedDomains)) { return res.status(403).send('Invalid referer'); } // 验证 CSRF Token if (req.body._csrf !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next();}2. 配置 Referer-Policy<meta name="referrer" content="strict-origin-when-cross-origin">3. 白名单管理维护受信任的域名列表定期更新和审查支持动态配置4. 日志记录if (!validateReferer(req, trustedDomains)) { logger.warn('Invalid referer attempt', { referer: req.headers.referer, ip: req.ip, path: req.path }); return res.status(403).send('Invalid referer');}适用场景适合使用 Referer 验证的场景内部管理系统API 接口保护作为辅助防护措施不适合单独使用的场景公共网站对安全性要求极高的系统需要支持多种访问方式的场景现代替代方案随着 SameSite Cookie 和 CSRF Token 的普及,Referer 验证通常作为辅助防护手段使用,而不是主要的防护方式。验证 Referer 头虽然有一定的局限性,但在正确的使用场景下,配合其他防护措施,仍然可以提供有效的 CSRF 防护。
阅读 0·2月19日 17:49

如何在大型应用中使用 MobX 进行状态管理?

在大型应用中,使用 MobX 进行状态管理需要考虑架构设计、模块化和可维护性。以下是构建大型 MobX 应用的最佳实践:1. Store 架构设计单一 Store vs 多个 Store单一 Storeclass RootStore { @observable user = null; @observable products = []; @observable cart = []; @observable orders = []; constructor() { makeAutoObservable(this); }}优点:简单直接易于调试状态集中管理缺点:文件可能过大难以模块化团队协作困难多个 Storeclass UserStore { @observable user = null; @observable isAuthenticated = false; constructor(rootStore) { this.rootStore = rootStore; makeAutoObservable(this); } @action login = async (credentials) => { const user = await api.login(credentials); this.user = user; this.isAuthenticated = true; };}class ProductStore { @observable products = []; @observable loading = false; constructor(rootStore) { this.rootStore = rootStore; makeAutoObservable(this); } @computed get featuredProducts() { return this.products.filter(p => p.featured); }}class CartStore { @observable items = []; constructor(rootStore) { this.rootStore = rootStore; makeAutoObservable(this); } @computed get total() { return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0); } @action addItem = (product) => { this.items.push({ ...product, quantity: 1 }); };}优点:模块化清晰易于团队协作代码组织更好缺点:需要处理 store 间的依赖调试可能更复杂2. Store 间通信通过 RootStore 共享引用class RootStore { constructor() { this.userStore = new UserStore(this); this.productStore = new ProductStore(this); this.cartStore = new CartStore(this); makeAutoObservable(this); }}// 在 CartStore 中访问 UserStoreclass CartStore { @action checkout = async () => { const user = this.rootStore.userStore.user; if (!user) { throw new Error('User not logged in'); } await api.createOrder(this.items, user.id); };}使用依赖注入class CartStore { constructor(userStore) { this.userStore = userStore; makeAutoObservable(this); } @action checkout = async () => { const user = this.userStore.user; // ... };}// 创建 storeconst userStore = new UserStore();const cartStore = new CartStore(userStore);使用事件总线class EventBus { @observable events = []; emit(event, data) { this.events.push({ event, data, timestamp: Date.now() }); } on(event, callback) { return reaction( () => this.events.filter(e => e.event === event), (events) => { if (events.length > 0) { callback(events[events.length - 1].data); } } ); }}3. 状态持久化使用 localStorageclass StorageStore { @observable state = {}; constructor(key, initialState = {}) { this.key = key; this.state = this.loadState() || initialState; makeAutoObservable(this); // 自动保存 autorun(() => { this.saveState(toJS(this.state)); }); } loadState() { try { const saved = localStorage.getItem(this.key); return saved ? JSON.parse(saved) : null; } catch (error) { console.error('Failed to load state:', error); return null; } } saveState(state) { try { localStorage.setItem(this.key, JSON.stringify(state)); } catch (error) { console.error('Failed to save state:', error); } }}// 使用const appStore = new StorageStore('appState', { user: null, theme: 'light', language: 'en'});使用 sessionStorageclass SessionStorageStore extends StorageStore { loadState() { try { const saved = sessionStorage.getItem(this.key); return saved ? JSON.parse(saved) : null; } catch (error) { return null; } } saveState(state) { try { sessionStorage.setItem(this.key, JSON.stringify(state)); } catch (error) { console.error('Failed to save state:', error); } }}使用 IndexedDBclass IndexedDBStore { @observable data = null; constructor(dbName, storeName) { this.dbName = dbName; this.storeName = storeName; makeAutoObservable(this); this.initDB(); } async initDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onerror = () => reject(request.error); request.onsuccess = () => { this.db = request.result; this.loadData(); resolve(); }; request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName); } }; }); } async loadData() { const transaction = this.db.transaction([this.storeName], 'readonly'); const store = transaction.objectStore(this.storeName); const request = store.get('data'); request.onsuccess = () => { runInAction(() => { this.data = request.result; }); }; } async saveData(data) { const transaction = this.db.transaction([this.storeName], 'readwrite'); const store = transaction.objectStore(this.storeName); store.put(data, 'data'); }}4. 代码组织按功能模块组织src/ stores/ user/ index.js actions.js getters.js product/ index.js actions.js getters.js cart/ index.js actions.js getters.js index.js按类型组织src/ stores/ observables/ user.js products.js cart.js actions/ userActions.js productActions.js cartActions.js computed/ userComputed.js productComputed.js cartComputed.js index.js5. 类型安全(TypeScript)定义 Store 接口interface IUserStore { user: User | null; isAuthenticated: boolean; login: (credentials: Credentials) => Promise<void>; logout: () => void;}class UserStore implements IUserStore { @observable user: User | null = null; @observable isAuthenticated: boolean = false; constructor(private rootStore: RootStore) { makeAutoObservable(this); } @action login = async (credentials: Credentials): Promise<void> => { const user = await api.login(credentials); this.user = user; this.isAuthenticated = true; }; @action logout = (): void => { this.user = null; this.isAuthenticated = false; };}定义 RootStoreinterface IRootStore { userStore: IUserStore; productStore: IProductStore; cartStore: ICartStore;}class RootStore implements IRootStore { userStore: IUserStore; productStore: IProductStore; cartStore: ICartStore; constructor() { this.userStore = new UserStore(this); this.productStore = new ProductStore(this); this.cartStore = new CartStore(this); makeAutoObservable(this); }}6. 测试测试 Storeimport { UserStore } from './UserStore';describe('UserStore', () => { let store; beforeEach(() => { store = new UserStore(); }); it('should initialize with default values', () => { expect(store.user).toBeNull(); expect(store.isAuthenticated).toBe(false); }); it('should login user', async () => { await store.login({ username: 'test', password: 'test' }); expect(store.user).not.toBeNull(); expect(store.isAuthenticated).toBe(true); }); it('should logout user', () => { store.user = { id: 1, name: 'Test' }; store.isAuthenticated = true; store.logout(); expect(store.user).toBeNull(); expect(store.isAuthenticated).toBe(false); });});测试组件import { render, screen } from '@testing-library/react';import { observer } from 'mobx-react-lite';import { UserStore } from './UserStore';const TestComponent = observer(({ store }) => ( <div> {store.isAuthenticated ? ( <div>Welcome, {store.user?.name}</div> ) : ( <div>Please login</div> )} </div>));describe('TestComponent', () => { it('should show login message when not authenticated', () => { const store = new UserStore(); render(<TestComponent store={store} />); expect(screen.getByText('Please login')).toBeInTheDocument(); }); it('should show welcome message when authenticated', () => { const store = new UserStore(); store.user = { id: 1, name: 'Test' }; store.isAuthenticated = true; render(<TestComponent store={store} />); expect(screen.getByText('Welcome, Test')).toBeInTheDocument(); });});7. 性能优化使用 computed 缓存class ProductStore { @observable products = []; @observable filters = { category: null, priceRange: null, search: '' }; @computed get filteredProducts() { let result = this.products; if (this.filters.category) { result = result.filter(p => p.category === this.filters.category); } if (this.filters.priceRange) { result = result.filter(p => p.price >= this.filters.priceRange.min && p.price <= this.filters.priceRange.max ); } if (this.filters.search) { const search = this.filters.search.toLowerCase(); result = result.filter(p => p.name.toLowerCase().includes(search) ); } return result; }}使用 reaction 延迟执行class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { makeAutoObservable(this); reaction( () => this.query, (query) => { this.performSearch(query); }, { delay: 300 } ); } @action performSearch = async (query) => { if (!query) { this.results = []; return; } this.loading = true; try { this.results = await api.search(query); } finally { this.loading = false; } };}8. 错误处理全局错误处理class ErrorStore { @observable errors = []; @action addError = (error) => { this.errors.push({ message: error.message, stack: error.stack, timestamp: Date.now() }); }; @action clearErrors = () => { this.errors = []; };}// 在 RootStore 中集成class RootStore { constructor() { this.errorStore = new ErrorStore(); this.userStore = new UserStore(this); this.productStore = new ProductStore(this); // 全局错误捕获 window.addEventListener('error', (event) => { this.errorStore.addError(event.error); }); window.addEventListener('unhandledrejection', (event) => { this.errorStore.addError(event.reason); }); }}总结构建大型 MobX 应用的关键点:合理设计 Store 架构(单一 vs 多个)处理 Store 间通信实现状态持久化良好的代码组织使用 TypeScript 提高类型安全编写全面的测试优化性能完善的错误处理遵循这些最佳实践,可以构建可维护、可扩展的大型 MobX 应用。
阅读 0·2月19日 17:49

如何防御 CSRF 攻击?

防御 CSRF 攻击需要多层防护策略,以下是几种有效的防御方法:1. CSRF Token(推荐)工作原理服务器生成随机 Token,存储在会话中Token 添加到表单隐藏字段或请求头中服务器验证请求中的 Token 是否与会话中的匹配实现示例// 前端<form action="/transfer" method="POST"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="text" name="amount"> <button type="submit">Transfer</button></form>// 或使用 AJAXfetch('/transfer', { method: 'POST', headers: { 'X-CSRF-Token': csrf_token, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 100 })});注意事项Token 必须足够随机(至少 128 位)Token 应该有时效性每个 Token 只能使用一次(可选)敏感操作必须验证 Token2. SameSite Cookie 属性工作原理SameSite 属性控制 Cookie 在跨站请求中的发送行为。属性值// Strict:只在同站请求中发送 CookieSet-Cookie: sessionid=abc123; SameSite=Strict// Lax:允许某些跨站请求(如导航)Set-Cookie: sessionid=abc123; SameSite=Lax// None:允许跨站请求(需要 Secure 属性)Set-Cookie: sessionid=abc123; SameSite=None; Secure推荐配置敏感操作使用 SameSite=Strict一般应用使用 SameSite=Lax避免 SameSite=None 除非必要3. 验证 Referer/Origin 头工作原理检查请求的来源是否合法。实现示例// 服务器端验证const allowedOrigins = ['https://example.com', 'https://www.example.com'];function validateOrigin(req) { const origin = req.headers.origin || req.headers.referer; if (!origin) return false; const originUrl = new URL(origin); return allowedOrigins.includes(originUrl.origin);}注意事项Referer 可能被浏览器禁用或修改Origin 只在跨站请求中存在不能作为唯一防御手段4. 双重提交 Cookie工作原理Token 同时存储在 Cookie 和请求参数中服务器验证两者是否匹配实现示例// 设置 Cookieres.cookie('csrf_token', token, { httpOnly: true, sameSite: 'strict' });// 表单中包含 Token<input type="hidden" name="csrf_token" value="{{ csrf_token }}">// 服务器验证if (req.cookies.csrf_token !== req.body.csrf_token) { throw new Error('CSRF token mismatch');}注意事项需要配合其他防护措施不适用于所有场景5. 自定义请求头工作原理使用自定义 Header(如 X-Requested-With)简单跨站请求无法添加自定义 Header实现示例fetch('/api/data', { headers: { 'X-Requested-With': 'XMLHttpRequest' }});注意事项只适用于 AJAX 请求需要服务器验证 Header6. 验证码工作原理敏感操作要求用户输入验证码验证码需要用户主动输入适用场景金额较大的转账删除重要数据修改关键设置7. 重新认证工作原理敏感操作要求重新输入密码确保用户意图实现示例// 执行敏感操作前function performSensitiveAction(callback) { showPasswordPrompt((password) => { verifyPassword(password).then(valid => { if (valid) callback(); }); });}综合防御策略最佳实践使用 CSRF Token:主要防御手段设置 SameSite=Lax:增强防护验证 Origin/Referer:额外验证敏感操作双重确认:重要操作定期更新 Token:降低风险监控异常请求:及时发现问题框架支持Spring Security:自动生成和验证 CSRF TokenDjango:内置 CSRF 保护中间件Express:使用 csurf 中间件Laravel:自动添加 CSRF Token总结没有单一的防御方法可以完全防止 CSRF 攻击。建议使用多层防护策略,以 CSRF Token 为主,配合 SameSite Cookie、Origin 验证等措施,为敏感操作添加额外的确认机制。
阅读 0·2月19日 17:48

Module Federation 的部署策略有哪些?如何进行生产环境部署?

Module Federation 的部署策略对于生产环境的稳定性和性能至关重要。以下是详细的部署方案:1. 部署架构设计独立部署模式:┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Host App │ │ Remote App 1│ │ Remote App 2││ :3000 │ │ :3001 │ │ :3002 │└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ └───────────────────┼───────────────────┘ │ ┌──────▼──────┐ │ Load Balancer│ └──────┬──────┘ │ ┌──────▼──────┐ │ CDN / Nginx│ └─────────────┘2. 环境配置管理开发环境配置:// webpack.config.jsconst isDevelopment = process.env.NODE_ENV === 'development'module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'app1', filename: 'remoteEntry.js', remotes: { app2: isDevelopment ? 'app2@http://localhost:3002/remoteEntry.js' : 'app2@https://app2.example.com/remoteEntry.js' }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true } } }) ]}生产环境配置:// .env.productionREMOTE_APP_URL=https://cdn.example.comREMOTE_APP_VERSION=1.2.3// webpack.config.jsconst remoteUrl = process.env.REMOTE_APP_URLconst remoteVersion = process.env.REMOTE_APP_VERSIONmodule.exports = { plugins: [ new ModuleFederationPlugin({ name: 'app1', remotes: { app2: `app2@${remoteUrl}/app2/${remoteVersion}/remoteEntry.js` } }) ]}3. CDN 部署策略静态资源上传:# 使用 AWS S3 + CloudFrontaws s3 sync ./dist s3://my-bucket/app1/1.0.0/ --deleteaws cloudfront create-invalidation --distribution-id E1234567890 --paths "/*"# 使用阿里云 OSSossutil cp -rf ./dist oss://my-bucket/app1/1.0.0/# 使用腾讯云 COScoscli cp -r ./dist cos://my-bucket/app1/1.0.0/Nginx 配置:server { listen 80; server_name app1.example.com; # 主应用 location / { root /var/www/app1; try_files $uri $uri/ /index.html; } # 远程模块入口 location /remoteEntry.js { root /var/www/app1; add_header Cache-Control "public, max-age=31536000, immutable"; } # 模块文件 location /assets/ { root /var/www/app1; add_header Cache-Control "public, max-age=31536000, immutable"; } # 启用 gzip 压缩 gzip on; gzip_types text/javascript application/javascript;}4. 版本管理策略语义化版本控制:{ "version": "1.2.3", "scripts": { "version:patch": "npm version patch && npm run deploy", "version:minor": "npm version minor && npm run deploy", "version:major": "npm version major && npm run deploy" }}版本回滚机制:# 部署脚本 deploy.sh#!/bin/bashVERSION=$1BACKUP_DIR="/var/backups/app1"CURRENT_DIR="/var/www/app1"# 备份当前版本if [ -d "$CURRENT_DIR" ]; then cp -r "$CURRENT_DIR" "$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)"fi# 部署新版本cp -r "./dist" "$CURRENT_DIR"# 健康检查if ! curl -f http://localhost:3000/health; then echo "Health check failed, rolling back..." cp -r "$BACKUP_DIR/latest" "$CURRENT_DIR" exit 1fiecho "Deployment successful"5. CI/CD 集成GitHub Actions 配置:name: Deploy Module Federationon: push: branches: [main]jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '16' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Run tests run: npm test - name: Deploy to S3 uses: jakejarvis/s3-sync-action@master with: args: --delete env: AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'us-east-1' SOURCE_DIR: 'dist' - name: Invalidate CloudFront uses: chetan/invalidate-cloudfront-action@master env: DISTRIBUTION: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} PATHS: '/*' AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'us-east-1'6. 监控和告警健康检查端点:// health.jsapp.get('/health', (req, res) => { const health = { status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), remoteModules: { app2: { loaded: true, version: '1.2.3', lastUpdated: new Date().toISOString() } } } res.json(health)})监控指标:// metrics.jsconst Prometheus = require('prom-client')const httpRequestDuration = new Prometheus.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'code']})const moduleLoadDuration = new Prometheus.Histogram({ name: 'module_load_duration_seconds', help: 'Duration of module loading in seconds', labelNames: ['module', 'status']})// 使用示例const start = Date.now()await import('remoteApp/Module')moduleLoadDuration.observe( { module: 'remoteApp/Module', status: 'success' }, (Date.now() - start) / 1000)7. 灾难恢复备份策略:# 定期备份脚本#!/bin/bashBACKUP_DIR="/backups/$(date +%Y%m%d)"mkdir -p "$BACKUP_DIR"# 备份所有应用for app in app1 app2 app3; do cp -r "/var/www/$app" "$BACKUP_DIR/"done# 上传到 S3aws s3 sync "$BACKUP_DIR" s3://my-backups/故障转移配置:upstream app1_cluster { server app1-primary.example.com weight=3; server app1-backup.example.com weight=1; server app1-dr.example.com backup;}server { location / { proxy_pass http://app1_cluster; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; proxy_next_upstream_tries 2; }}最佳实践总结:环境隔离:开发、测试、生产环境完全隔离版本管理:使用语义化版本,支持快速回滚CDN 加速:使用 CDN 分发静态资源,提升加载速度自动化部署:集成 CI/CD,实现自动化部署流程监控告警:实时监控应用状态,及时发现和解决问题备份恢复:定期备份,制定灾难恢复计划灰度发布:支持灰度发布,降低风险文档完善:维护详细的部署文档和操作手册通过以上部署策略,可以确保 Module Federation 应用在生产环境中的稳定性和可靠性。
阅读 0·2月19日 17:46

Module Federation 如何实现动态加载?有哪些优势?

Module Federation 的动态加载是指在运行时根据需要动态导入远程模块,而不是在构建时静态确定所有依赖。以下是详细说明:动态导入语法:// 基本动态导入const RemoteButton = React.lazy(() => import('remoteApp/Button'))// 带错误处理的动态导入const loadRemoteModule = async () => { try { const module = await import('remoteApp/Button') return module.default } catch (error) { console.error('Failed to load remote module:', error) return FallbackComponent }}动态加载的优势:按需加载:只在需要时才加载远程模块,减少初始加载时间灵活性:可以根据用户权限、环境等条件动态决定加载哪些模块性能优化:避免加载用户不需要的功能,提升整体性能独立部署:远程模块可以独立更新,无需重新部署主应用实现原理:Module Federation 使用 Webpack 的动态导入(dynamic import)机制,结合容器插件实现:入口文件加载:首先加载远程应用的 remoteEntry.js模块解析:通过 remoteEntry.js 解析模块映射关系异步加载:使用 import() 语法异步加载目标模块依赖注入:自动注入共享依赖,确保模块正常运行实际应用场景:// 场景1:根据路由动态加载const routes = [ { path: '/dashboard', component: React.lazy(() => import('dashboardApp/Dashboard')) }, { path: '/settings', component: React.lazy(() => import('settingsApp/Settings')) }]// 场景2:根据用户权限动态加载const loadAdminPanel = async (isAdmin) => { if (isAdmin) { const AdminPanel = await import('adminApp/AdminPanel') return AdminPanel.default } return null}// 场景3:懒加载组件function App() { const [RemoteComponent, setRemoteComponent] = useState(null) useEffect(() => { import('remoteApp/Feature') .then(module => setRemoteComponent(() => module.default)) .catch(error => console.error(error)) }, []) return ( <Suspense fallback={<Loading />}> {RemoteComponent && <RemoteComponent />} </Suspense> )}错误处理和降级策略:// 完整的错误处理示例const RemoteModule = React.lazy(() => import('remoteApp/Module') .catch(error => { console.error('Remote module load failed:', error) // 降级到本地模块 return import('./LocalFallback') }))性能优化技巧:预加载:在空闲时预加载可能需要的远程模块缓存策略:合理设置 remoteEntry.js 的缓存策略代码分割:远程模块内部也使用代码分割,进一步优化加载CDN 加速:将 remoteEntry.js 和模块文件部署到 CDN注意事项:动态加载是异步的,需要配合 Suspense 或 async/await 使用确保远程应用的入口文件可访问处理网络错误和加载失败的情况考虑添加加载状态和错误边界
阅读 0·2月19日 17:46

Module Federation 如何进行调试和问题排查?有哪些调试工具?

Module Federation 的调试和问题排查是开发过程中的重要环节,以下是详细的调试方案:1. 开发环境调试启用 Source Maps:// webpack.config.jsmodule.exports = { devtool: 'source-map', output: { devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]?[loaders]', devtoolFallbackModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]?[loaders]' }}配置开发服务器:// webpack.config.jsconst HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { devServer: { port: 3000, hot: true, historyApiFallback: true, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization' }, client: { overlay: { errors: true, warnings: false } } }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ]}2. 模块加载调试模块加载追踪:// module-loader-debug.jsclass ModuleLoaderDebugger { constructor() { this.loadTimes = new Map() this.loadErrors = new Map() this.interceptImports() } interceptImports() { const originalImport = window.__webpack_require__ window.__webpack_require__ = (moduleId) => { const startTime = performance.now() try { const module = originalImport(moduleId) const loadTime = performance.now() - startTime this.loadTimes.set(moduleId, { loadTime, timestamp: Date.now() }) console.log(`✅ Module loaded: ${moduleId} (${loadTime.toFixed(2)}ms)`) return module } catch (error) { this.loadErrors.set(moduleId, { error, timestamp: Date.now() }) console.error(`❌ Module load failed: ${moduleId}`, error) throw error } } } getLoadStats() { return { totalModules: this.loadTimes.size, totalErrors: this.loadErrors.size, averageLoadTime: Array.from(this.loadTimes.values()) .reduce((sum, stat) => sum + stat.loadTime, 0) / this.loadTimes.size, slowModules: Array.from(this.loadTimes.entries()) .filter(([_, stat]) => stat.loadTime > 1000) .map(([id, stat]) => ({ id, loadTime: stat.loadTime })) } }}// 初始化调试器if (process.env.NODE_ENV === 'development') { new ModuleLoaderDebugger()}3. 远程模块调试远程模块检查工具:// remote-module-debugger.jsclass RemoteModuleDebugger { constructor() { this.remoteModules = new Map() this.checkRemoteModules() } async checkRemoteModules() { const remotes = this.getRemoteConfig() for (const [name, config] of Object.entries(remotes)) { try { await this.checkRemoteModule(name, config) } catch (error) { console.error(`Remote module check failed: ${name}`, error) } } } getRemoteConfig() { // 从 webpack 配置中获取远程模块配置 return { remoteApp1: { url: 'http://localhost:3001/remoteEntry.js', scope: 'remoteApp1' }, remoteApp2: { url: 'http://localhost:3002/remoteEntry.js', scope: 'remoteApp2' } } } async checkRemoteModule(name, config) { console.log(`Checking remote module: ${name}`) // 检查远程入口文件是否可访问 const response = await fetch(config.url) if (!response.ok) { throw new Error(`Failed to fetch remote entry: ${response.status}`) } // 检查入口文件内容 const content = await response.text() if (!content.includes(config.scope)) { throw new Error(`Remote entry does not contain scope: ${config.scope}`) } // 尝试加载远程模块 try { const module = await import(`${name}/Module`) this.remoteModules.set(name, { status: 'loaded', module, timestamp: Date.now() }) console.log(`✅ Remote module loaded: ${name}`) } catch (error) { this.remoteModules.set(name, { status: 'error', error, timestamp: Date.now() }) console.error(`❌ Failed to load remote module: ${name}`, error) } } getModuleStatus(name) { return this.remoteModules.get(name) } getAllModuleStatus() { return Object.fromEntries(this.remoteModules) }}export const remoteModuleDebugger = new RemoteModuleDebugger()4. 共享依赖调试共享依赖检查工具:// shared-dependency-debugger.jsclass SharedDependencyDebugger { constructor() { this.sharedDependencies = new Map() this.checkSharedDependencies() } checkSharedDependencies() { const shared = this.getSharedConfig() for (const [name, config] of Object.entries(shared)) { this.checkSharedDependency(name, config) } } getSharedConfig() { // 从 webpack 配置中获取共享依赖配置 return { react: { singleton: true, requiredVersion: '^17.0.0', strictVersion: false }, 'react-dom': { singleton: true, requiredVersion: '^17.0.0', strictVersion: false } } } checkSharedDependency(name, config) { try { const dependency = require(name) const version = dependency.version const isSingleton = config.singleton const requiredVersion = config.requiredVersion const strictVersion = config.strictVersion const isCompatible = this.checkVersionCompatibility( version, requiredVersion, strictVersion ) this.sharedDependencies.set(name, { version, isSingleton, requiredVersion, isCompatible, status: isCompatible ? 'ok' : 'incompatible' }) if (isCompatible) { console.log(`✅ Shared dependency: ${name}@${version}`) } else { console.warn(`⚠️ Incompatible shared dependency: ${name}@${version} (required: ${requiredVersion})`) } } catch (error) { this.sharedDependencies.set(name, { status: 'error', error }) console.error(`❌ Failed to load shared dependency: ${name}`, error) } } checkVersionCompatibility(currentVersion, requiredVersion, strictVersion) { if (strictVersion) { return currentVersion === requiredVersion } // 使用 semver 检查版本兼容性 const semver = require('semver') return semver.satisfies(currentVersion, requiredVersion) } getDependencyStatus(name) { return this.sharedDependencies.get(name) } getAllDependencyStatus() { return Object.fromEntries(this.sharedDependencies) }}export const sharedDependencyDebugger = new SharedDependencyDebugger()5. 性能分析工具模块加载性能分析:// performance-analyzer.jsclass PerformanceAnalyzer { constructor() { this.metrics = new Map() this.setupPerformanceObserver() } setupPerformanceObserver() { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { if (entry.entryType === 'resource') { this.recordResourceLoad(entry) } }) }) observer.observe({ entryTypes: ['resource', 'measure'] }) } } recordResourceLoad(entry) { const { name, duration, transferSize } = entry if (name.includes('remoteEntry') || name.includes('node_modules')) { this.metrics.set(name, { duration, transferSize, timestamp: entry.startTime }) } } analyzeModuleLoad(moduleName) { const startTime = performance.now() return { start: () => startTime, end: () => { const endTime = performance.now() const duration = endTime - startTime this.metrics.set(moduleName, { duration, timestamp: startTime }) console.log(`📊 Module load time: ${moduleName} (${duration.toFixed(2)}ms)`) return duration } } } getPerformanceReport() { const metrics = Array.from(this.metrics.values()) return { totalLoadTime: metrics.reduce((sum, m) => sum + (m.duration || 0), 0), totalTransferSize: metrics.reduce((sum, m) => sum + (m.transferSize || 0), 0), averageLoadTime: metrics.reduce((sum, m) => sum + (m.duration || 0), 0) / metrics.length, slowResources: metrics.filter(m => m.duration > 1000), largeResources: metrics.filter(m => m.transferSize > 100000) } }}export const performanceAnalyzer = new PerformanceAnalyzer()6. 错误处理和日志全局错误处理:// error-handler.jsclass ErrorHandler { constructor() { this.errors = [] this.setupErrorHandlers() } setupErrorHandlers() { // 捕获模块加载错误 window.addEventListener('error', (event) => { if (event.filename && event.filename.includes('remoteEntry')) { this.handleModuleLoadError(event) } }) // 捕获未处理的 Promise 拒绝 window.addEventListener('unhandledrejection', (event) => { this.handleUnhandledRejection(event) }) } handleModuleLoadError(event) { const error = { type: 'module_load_error', message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack, timestamp: Date.now() } this.errors.push(error) console.error('Module load error:', error) // 发送到错误追踪服务 this.sendToErrorTracking(error) } handleUnhandledRejection(event) { const error = { type: 'unhandled_rejection', message: event.reason?.message || String(event.reason), stack: event.reason?.stack, timestamp: Date.now() } this.errors.push(error) console.error('Unhandled rejection:', error) // 发送到错误追踪服务 this.sendToErrorTracking(error) } sendToErrorTracking(error) { // 发送到 Sentry 或其他错误追踪服务 if (window.Sentry) { window.Sentry.captureException(error) } // 或发送到自定义错误追踪端点 fetch('/api/error-tracking', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(error) }).catch(console.error) } getErrors() { return this.errors } clearErrors() { this.errors = [] }}export const errorHandler = new ErrorHandler()7. 调试工具集成React DevTools 集成:// debug-tools.jsimport React from 'react'import { createRoot } from 'react-dom/client'// 开发环境调试面板if (process.env.NODE_ENV === 'development') { const DebugPanel = () => { const [stats, setStats] = React.useState({}) React.useEffect(() => { const interval = setInterval(() => { setStats({ modules: window.__webpack_require__.c, loadTimes: window.moduleLoadTimes, remoteModules: window.remoteModules }) }, 1000) return () => clearInterval(interval) }, []) return React.createElement('div', { style: { position: 'fixed', bottom: 0, right: 0, background: 'rgba(0,0,0,0.8)', color: 'white', padding: '10px', fontSize: '12px', zIndex: 9999 } }, React.createElement('pre', null, JSON.stringify(stats, null, 2))) } const root = createRoot(document.createElement('div')) root.render(React.createElement(DebugPanel))}通过以上调试工具和方法,可以有效地排查和解决 Module Federation 开发过程中的各种问题。
阅读 0·2月19日 17:46