Redux 实践与中间件应用
Redux 异步处理的挑战
Redux 核心设计是同步的、单向数据流,但现代应用中异步操作无处不在。Redux 中间件填补了这一缺口,专门解决异步流程管理、副作用隔离等复杂场景。
中间件架构原理
中间件位于 action 被发起之后、到达 reducer 之前,提供了拦截和处理 action 的机会。
// 中间件基本结构
const middleware = store => next => action => {// 前置处理console.log('dispatching', action);// 调用下一个中间件或reducerlet result = next(action);// 后置处理console.log('next state', store.getState());return result;
}
Redux-Thunk: 函数式异步处理
核心原理
Redux-Thunk 允许 action creator 返回函数而非普通对象,函数接收 dispatch
和 getState
参数。
// redux-thunk 核心实现(仅20行代码)
function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
实战应用示例
// 用户列表加载功能完整实现
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';// Slice定义
const userSlice = createSlice({name: 'users',initialState: {data: [],loading: false,error: null},reducers: {fetchStart: (state) => {state.loading = true;state.error = null;},fetchSuccess: (state, action) => {state.data = action.payload;state.loading = false;},fetchFailure: (state, action) => {state.loading = false;state.error = action.payload;}}
});// 导出actions
export const { fetchStart, fetchSuccess, fetchFailure } = userSlice.actions;// Thunk action creator
export const fetchUsers = () => async (dispatch, getState) => {try {dispatch(fetchStart());// 可以访问当前stateconst { users } = getState();if (users.data.length > 0 && !users.loading) {return; // 避免重复加载}const response = await axios.get('https://api.example.com/users');dispatch(fetchSuccess(response.data));} catch (error) {dispatch(fetchFailure(error.message));}
};// 带参数的thunk
export const fetchUserById = (userId) => async (dispatch) => {try {dispatch(fetchStart());const response = await axios.get(`https://api.example.com/users/${userId}`);dispatch(fetchSuccess([response.data]));} catch (error) {dispatch(fetchFailure(error.message));}
};export default userSlice.reducer;
React组件集成
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from './userSlice';const UserList = () => {const dispatch = useDispatch();const { data, loading, error } = useSelector((state) => state.users);useEffect(() => {dispatch(fetchUsers());}, [dispatch]);if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error}</div>;return (<div><h2>用户列表</h2><ul>{data.map(user => (<li key={user.id}>{user.name}</li>))}</ul></div>);
};export default UserList;
Redux-Saga: 声明式异步处理
核心原理
Redux-Saga 使用生成器函数管理副作用,提供丰富的组合操作符,适合复杂异步流程。
Ran tool
实战应用示例
// users模块完整实现
import { createSlice } from '@reduxjs/toolkit';
import { call, put, takeLatest, select } from 'redux-saga/effects';
import axios from 'axios';// Slice定义
const userSlice = createSlice({name: 'users',initialState: {data: [],loading: false,error: null},reducers: {fetchUsersRequest: (state) => {state.loading = true;state.error = null;},fetchUsersSuccess: (state, action) => {state.data = action.payload;state.loading = false;},fetchUsersFailure: (state, action) => {state.loading = false;state.error = action.payload;}}
});export const { fetchUsersRequest, fetchUsersSuccess, fetchUsersFailure
} = userSlice.actions;// Saga函数
export function* fetchUsersSaga() {try {// 可以通过select Effect获取Redux状态const { users } = yield select();if (users.data.length > 0 && !users.loading) {return; // 避免重复加载}// call Effect执行异步调用const response = yield call(axios.get, 'https://api.example.com/users');// put Effect分发新actionyield put(fetchUsersSuccess(response.data));} catch (error) {yield put(fetchUsersFailure(error.message));}
}// 带参数的Saga
export function* fetchUserByIdSaga(action) {try {const { userId } = action.payload;const response = yield call(axios.get, `https://api.example.com/users/${userId}`);yield put(fetchUsersSuccess([response.data]));} catch (error) {yield put(fetchUsersFailure(error.message));}
}// Root Saga
export function* usersSaga() {// 监听对应action类型,触发saga处理函数yield takeLatest('users/fetchUsersRequest', fetchUsersSaga);yield takeLatest('users/fetchUserById', fetchUserByIdSaga);
}export default userSlice.reducer;
复杂流程处理
import { call, put, takeLatest, all, race, delay } from 'redux-saga/effects';// 并发请求
function* fetchDashboardData() {try {yield put(dashboardLoadingStart());// 并行执行多个请求const [users, posts, comments] = yield all([call(axios.get, '/api/users'),call(axios.get, '/api/posts'),call(axios.get, '/api/comments')]);yield put(dashboardLoadSuccess({ users: users.data, posts: posts.data, comments: comments.data }));} catch (error) {yield put(dashboardLoadFailure(error.message));}
}// 请求竞态处理(请求超时处理)
function* fetchUserWithTimeout(action) {try {const { userId } = action.payload;// 竞争条件:请求成功 vs 超时const { response, timeout } = yield race({response: call(axios.get, `https://api.example.com/users/${userId}`),timeout: delay(5000) // 5秒超时});if (response) {yield put(fetchUserSuccess(response.data));} else if (timeout) {yield put(fetchUserFailure('请求超时'));}} catch (error) {yield put(fetchUserFailure(error.message));}
}
Redux-Observable: 响应式异步处理
核心原理
Redux-Observable 基于 RxJS,以 Epic 形式处理 action 流,擅长复杂事件流处理和响应式编程。
// Epic基本结构
const pingEpic = (action$, state$) => action$.pipe(ofType('PING'),delay(1000),map(() => ({ type: 'PONG' })));
实战应用示例
// 用户模块完整实现
import { createSlice } from '@reduxjs/toolkit';
import { ofType } from 'redux-observable';
import { ajax } from 'rxjs/ajax';
import { mergeMap, map, catchError, filter, withLatestFrom } from 'rxjs/operators';
import { of } from 'rxjs';// Slice定义
const userSlice = createSlice({name: 'users',initialState: {data: [],loading: false,error: null},reducers: {fetchUsersRequest: (state) => {state.loading = true;state.error = null;},fetchUsersSuccess: (state, action) => {state.data = action.payload;state.loading = false;},fetchUsersFailure: (state, action) => {state.loading = false;state.error = action.payload;},fetchUserById: (state, action) => {state.loading = true;state.error = null;}}
});export const { fetchUsersRequest, fetchUsersSuccess, fetchUsersFailure,fetchUserById
} = userSlice.actions;// Epic定义
export const fetchUsersEpic = (action$, state$) => action$.pipe(ofType('users/fetchUsersRequest'),withLatestFrom(state$),filter(([action, state]) => {// 避免重复加载return state.users.data.length === 0 || state.users.loading === false;}),mergeMap(() => ajax.getJSON('https://api.example.com/users').pipe(map(response => fetchUsersSuccess(response)),catchError(error => of(fetchUsersFailure(error.message))))));export const fetchUserByIdEpic = (action$) => action$.pipe(ofType('users/fetchUserById'),mergeMap(action => ajax.getJSON(`https://api.example.com/users/${action.payload}`).pipe(map(response => fetchUsersSuccess([response])),catchError(error => of(fetchUsersFailure(error.message))))));export default userSlice.reducer;
复杂操作符应用
import { combineEpics } from 'redux-observable';
import { interval, of, EMPTY } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged, takeUntil, retry, timeout
} from 'rxjs/operators';// 自动补全搜索Epic
const autocompleteEpic = (action$) => action$.pipe(ofType('SEARCH_INPUT_CHANGE'),debounceTime(300), // 防抖distinctUntilChanged(), // 防止重复请求相同搜索词switchMap(action => {// switchMap取消前一次未完成的请求const searchTerm = action.payload;return ajax.getJSON(`https://api.example.com/search?q=${searchTerm}`).pipe(map(results => ({ type: 'SEARCH_RESULTS', payload: results })),takeUntil(action$.pipe(ofType('CANCEL_SEARCH'))),timeout(5000),retry(2),catchError(err => of({ type: 'SEARCH_ERROR', payload: err.message })));})
);// 长轮询Epic
const pollingEpic = (action$) => action$.pipe(ofType('START_POLLING'),switchMap(action => {return interval(10000).pipe(switchMap(() => ajax.getJSON('https://api.example.com/updates').pipe(map(data => ({ type: 'RECEIVE_UPDATES', payload: data })),catchError(err => of({ type: 'POLLING_ERROR', payload: err.message })))),takeUntil(action$.pipe(ofType('STOP_POLLING'))));})
);
中间件方案对比分析
特性 | Redux-Thunk | Redux-Saga | Redux-Observable |
---|---|---|---|
学习曲线 | 低,函数式编程 | 中高,Generator语法 | 高,需RxJS基础 |
代码复杂度 | 低(简单场景) 高(复杂场景) | 中等 | 初始高,后期可降低 |
测试难度 | 中等,需模拟异步 | 低,纯函数易测试 | 中等,需RxJS测试工具 |
异步流程控制 | 基础,手动控制 | 丰富,声明式 | 极其强大,响应式流 |
取消操作 | 困难,需手动实现 | 简单,内置支持 | 简单,内置支持 |
竞态处理 | 困难,需手动实现 | 简单,内置race | 简单,多种操作符 |
并发控制 | 需手动实现 | 内置all/fork | 内置多种操作符 |
调试便利性 | 一般 | 优秀,支持时间旅行 | 一般,需RxJS工具 |
适用场景 | 简单异步操作 | 复杂业务流程 | 事件流处理、响应式UI |
中间件选型决策指南
项目规模考量
- 小型项目: Redux-Thunk 足够应付,学习成本最低
- 中型项目: Redux-Saga 平衡了复杂性和功能性
- 大型项目: Redux-Observable 提供长期可扩展性,尤其处理事件流、实时应用
团队因素
- 团队熟悉度:已有RxJS经验优先考虑Redux-Observable
- 学习资源:Redux-Saga社区资源更丰富
- 新手友好度:Redux-Thunk > Redux-Saga > Redux-Observable
业务复杂度
- 简单CRUD:Redux-Thunk
- 多步骤流程、状态机:Redux-Saga
- 复杂事件流、高响应UI:Redux-Observable
中间件整合
Redux Toolkit整合
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import { createEpicMiddleware, combineEpics } from 'redux-observable';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import { rootSaga } from './sagas';
import { rootEpic } from './epics';// 创建中间件实例
const sagaMiddleware = createSagaMiddleware();
const epicMiddleware = createEpicMiddleware();const store = configureStore({reducer: rootReducer,middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(thunk) // 简单异步处理.concat(sagaMiddleware) // 复杂业务逻辑.concat(epicMiddleware) // 特殊事件流处理
});// 运行根saga和根epic
sagaMiddleware.run(rootSaga);
epicMiddleware.run(rootEpic);export default store;
模块化组织
src/
├── features/
│ ├── users/
│ │ ├── usersSlice.js # 定义state和reducers
│ │ ├── usersThunks.js # 简单异步操作
│ │ ├── usersSagas.js # 复杂业务流程
│ │ └── usersEpics.js # 响应式事件流
│ └── ...
├── store/
│ ├── rootReducer.js # 组合所有reducers
│ ├── rootSaga.js # 组合所有sagas
│ ├── rootEpic.js # 组合所有epics
│ └── index.js # store配置
案例:购物车
需求场景
实现购物车功能,包括添加商品、调整数量、获取实时库存和优惠信息、下单流程。
混合中间件实现
// cartSlice.js
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';const cartSlice = createSlice({name: 'cart',initialState: {items: [],loading: false,error: null,checkoutStatus: 'idle' // 'idle' | 'processing' | 'success' | 'failed'},reducers: {// 基础state变更addToCart: (state, action) => {const { product, quantity = 1 } = action.payload;const existingItem = state.items.find(item => item.id === product.id);if (existingItem) {existingItem.quantity += quantity;} else {state.items.push({...product, quantity});}},updateQuantity: (state, action) => {const { productId, quantity } = action.payload;const item = state.items.find(item => item.id === productId);if (item) {item.quantity = quantity;}},removeFromCart: (state, action) => {const productId = action.payload;state.items = state.items.filter(item => item.id !== productId);},// 异步action状态管理checkoutStart: (state) => {state.checkoutStatus = 'processing';state.error = null;},checkoutSuccess: (state) => {state.checkoutStatus = 'success';state.items = [];},checkoutFailure: (state, action) => {state.checkoutStatus = 'failed';state.error = action.payload;},// 库存检查inventoryCheckStart: (state) => {state.loading = true;},inventoryCheckSuccess: (state, action) => {state.loading = false;// 更新商品库存信息action.payload.forEach(stockInfo => {const item = state.items.find(item => item.id === stockInfo.productId);if (item) {item.inStock = stockInfo.inStock;item.maxAvailable = stockInfo.quantity;}});},inventoryCheckFailure: (state, action) => {state.loading = false;state.error = action.payload;}}
});export const {addToCart,updateQuantity,removeFromCart,checkoutStart,checkoutSuccess,checkoutFailure,inventoryCheckStart,inventoryCheckSuccess,inventoryCheckFailure
} = cartSlice.actions;export default cartSlice.reducer;
使用Thunk处理简单操作
// cartThunks.js
import axios from 'axios';
import {inventoryCheckStart,inventoryCheckSuccess,inventoryCheckFailure
} from './cartSlice';// 简单库存检查 - Thunk适合
export const checkInventory = () => async (dispatch, getState) => {const { cart } = getState();if (cart.items.length === 0) return;try {dispatch(inventoryCheckStart());// 获取购物车内所有商品IDconst productIds = cart.items.map(item => item.id);// 检查库存const response = await axios.post('/api/inventory/check', { productIds });dispatch(inventoryCheckSuccess(response.data));} catch (error) {dispatch(inventoryCheckFailure(error.message));}
};
使用Saga处理复杂结账流程
// cartSagas.js
import { takeLatest, put, call, select, all } from 'redux-saga/effects';
import axios from 'axios';
import {checkoutStart,checkoutSuccess,checkoutFailure
} from './cartSlice';// 复杂结账流程 - Saga适合多步骤流程
function* checkoutSaga() {try {// 1. 获取当前购物车const { cart } = yield select();if (cart.items.length === 0) {yield put(checkoutFailure('购物车为空'));return;}// 2. 最终库存确认const inventoryResponse = yield call(axios.post, '/api/inventory/check', {productIds: cart.items.map(item => item.id)});// 3. 检查库存不足情况const outOfStockItems = [];inventoryResponse.data.forEach(stockInfo => {const cartItem = cart.items.find(item => item.id === stockInfo.productId);if (cartItem && cartItem.quantity > stockInfo.quantity) {outOfStockItems.push({...cartItem,availableQuantity: stockInfo.quantity});}});if (outOfStockItems.length > 0) {yield put(checkoutFailure({message: '部分商品库存不足',outOfStockItems}));return;}// 4. 创建订单const orderData = {items: cart.items,totalAmount: cart.items.reduce((total, item) => total + item.price * item.quantity, 0)};const orderResponse = yield call(axios.post, '/api/orders', orderData);// 5. 处理支付const paymentResponse = yield call(axios.post, '/api/payments', {orderId: orderResponse.data.id,amount: orderData.totalAmount});// 6. 完成结账yield put(checkoutSuccess());// 7. 可选:发送确认邮件等后续操作yield call(axios.post, '/api/notifications/order-confirmation', {orderId: orderResponse.data.id});} catch (error) {yield put(checkoutFailure(error.message));}
}export function* cartSagas() {yield all([takeLatest('cart/checkoutStart', checkoutSaga)]);
}
使用Observable处理实时价格
// cartEpics.js
import { ofType } from 'redux-observable';
import { ajax } from 'rxjs/ajax';
import { mergeMap, map, catchError, switchMap, debounceTime, takeUntil, withLatestFrom
} from 'rxjs/operators';
import { of, timer } from 'rxjs';// 处理实时价格更新 - Observable适合事件流
export const priceUpdateEpic = (action$, state$) => action$.pipe(ofType('cart/addToCart', 'cart/updateQuantity', 'cart/removeFromCart'),debounceTime(500), // 防抖,避免频繁请求withLatestFrom(state$),switchMap(([action, state]) => {const { cart } = state;// 如果购物车为空,不请求if (cart.items.length === 0) {return of({ type: 'cart/priceUpdateSkipped' });}// 准备请求数据const requestData = {items: cart.items.map(item => ({productId: item.id,quantity: item.quantity}))};// 发起价格计算请求return ajax.post('/api/cart/calculate', requestData).pipe(map(response => ({type: 'cart/priceUpdateSuccess',payload: response.response})),takeUntil(action$.pipe(ofType('cart/addToCart', 'cart/updateQuantity', 'cart/removeFromCart'))),catchError(error => of({type: 'cart/priceUpdateFailure',payload: error.message})));})
);// 实时库存轮询 - Observable适合循环事件
export const inventoryPollingEpic = (action$, state$) => action$.pipe(ofType('INVENTORY_POLLING_START'),switchMap(() => {// 每30秒查询一次库存return timer(0, 30000).pipe(withLatestFrom(state$),mergeMap(([_, state]) => {const { cart } = state;if (cart.items.length === 0) {return of({ type: 'INVENTORY_POLLING_SKIP' });}const productIds = cart.items.map(item => item.id);return ajax.post('/api/inventory/check', { productIds }).pipe(map(response => ({type: 'cart/inventoryCheckSuccess',payload: response.response})),catchError(error => of({type: 'cart/inventoryCheckFailure',payload: error.message})));}),takeUntil(action$.pipe(ofType('INVENTORY_POLLING_STOP'))));})
);
总结与思考
-
混合策略:根据需求复杂度选择合适的中间件
- 简单异步操作:Redux-Thunk
- 复杂业务流程:Redux-Saga
- 事件流处理:Redux-Observable
-
测试优先:中间件测试方法各不相同
- Thunk:模拟store和API调用
- Saga:单独测试每个generator函数
- Observable:使用RxJS专用测试工具如TestScheduler
-
状态设计:保持状态扁平化,规范异步状态表示
- 包含loading/error/data三要素
- 使用请求状态枚举(idle/loading/success/failed)
- 规范化复杂数据结构
-
代码组织:按功能模块化
- 每个模块包含reducer、action、不同中间件实现
- 按业务领域而非技术层次拆分文件
- 使用barrel文件导出公共API
-
性能保障:
- 避免冗余请求:使用缓存状态和条件判断
- 处理竞态条件:取消过时操作
- 调试体验:使用Redux DevTools监控action和状态变化
Redux中间件生态是前端异步状态管理的强大解决方案,选择合适的中间件组合才能提升开发效率和代码可维护性。
参考资源
官方文档
- Redux 官方文档 - 包含中间件概念和API详解
- Redux Toolkit 官方文档 - 现代Redux最佳实践工具集
- Redux-Thunk GitHub - 官方仓库和文档
- Redux-Saga 官方文档 - 完整API参考和教程
- Redux-Observable 文档 - 详细介绍Epic和操作符
教程与深度解析
- Dan Abramov: Redux中间件详解 - Redux创建者对中间件的深度解释
- LogRocket: Redux中间件对比 - 不同中间件优缺点分析
- RxJS官方文档 - Redux-Observable的核心依赖
工具与插件
- Redux DevTools Extension - 调试Redux应用的必备工具
- redux-logger - 日志中间件,展示action流
- redux-persist - Redux状态持久化方案
视频教程
- Redux Middleware深入浅出 - Dan Abramov的系列课程
- Redux-Saga入门到精通 - 完整视频教程
- RxJS + Redux实战 - Redux-Observable使用指南
社区讨论
- Redux中间件选择指南 - Stack Overflow上的详细对比
- Redux FAQ - 中间件相关问题 - 官方常见问题解答
高级模式与最佳实践
- 可取消的异步操作模式 - Redux-Saga中的任务取消
- Redux性能优化指南 - 官方性能提升建议
- React Query与Redux协作 - 现代数据获取方案与Redux集成
趋势与未来
- Redux Toolkit Query - Redux生态的最新数据获取解决方案
- Reselect - Redux选择器库,提升性能
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
相关文章:
Redux 实践与中间件应用
Redux 异步处理的挑战 Redux 核心设计是同步的、单向数据流,但现代应用中异步操作无处不在。Redux 中间件填补了这一缺口,专门解决异步流程管理、副作用隔离等复杂场景。 中间件架构原理 中间件位于 action 被发起之后、到达 reducer 之前,…...

ModBus总线协议
一、知识点 1. 什么是Modbus协议? Modbus 是一种工业通信协议,最早由 Modicon 公司在1979年提出,目的是用于 PLC(可编程逻辑控制器)之间的数据通信。它是主从式通信,即一个主机(主设备…...

【计算机网络】非阻塞IO——poll实现多路转接
🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:计算机网络 🌹往期回顾🌹:【计算机网络】非阻塞IO——select实现多路转接 🔖流水不争,争的是滔滔不息 一、…...
在.NET Core控制器中获取AJAX传递的Body参数
.Net Core是支持前后端不分离式的开发的,如果在原始系统中采用不分离式开发,后面需要在原系统中增加功能,并且新的服务采用其他语言开发,且系统原来功能保持原样,这样前端系统可以单独调用新开发的接口。 但是&#x…...
snprintf函数用法及注意事项详解
当 format 后没有可变参数(即 ... 为空)时,va_start 的行为和后续操作如下: 1. va_start 的行为 va_start 的核心任务是根据最后一个固定参数(format)的地址,计算可变参数列表的起始位置。即使…...
vue-20(Vuex 状态管理的最佳实践)
Vuex 状态管理的最佳实践 Vuex 是管理大型 Vue.js 应用状态的一个强大工具,但其有效性取决于其组织和维护的质量。管理不善的 Vuex 存储可能会变得难以控制、难以调试,并成为性能瓶颈。本课程深入探讨构建 Vuex 存储的最佳实践,重点关注可维…...

DAX权威指南8:DAX引擎与存储优化
文章目录 十七、DAX引擎17.1 DAX 引擎的体系结构17.1.1 表格模型的双引擎架构17.1.2 存储引擎的三种模式17.1.2.1 VertiPaq引擎17.1.2.2 DirectQuery 引擎17.1.2.3 对比与最佳实践 17.1.3 数据刷新 17.2 理解 VertiPaq 存储引擎17.2.1 列式数据库17.2.2 VertiPaq 压缩17.2.2.1 …...

智慧货运飞船多维度可视化管控系统
图扑搭建智慧货运飞船可视化系统,借数字孪生技术,高精度复刻货运飞船外观、结构与运行场景。整合多维度数据,实时呈现飞行状态、设备参数等信息,助力直观洞察货运飞船运行逻辑,为航天运维、任务推演及决策提供数字化支…...

电脑开不了机,主板显示67码解决过程
文章目录 现象分析内存条问题BIOS设置问题其它问题 解决清理内存条金手指所需工具操作步骤注意事项 电脑在运行过程中,显示内存不足,重启电脑却无法启动。 现象 System Initialization 主板风扇是转的,也有灯光显示,插上屏幕&am…...
Spring Boot 类加载机制深度解析
Spring Boot 类加载机制深度解析 前言 在 Java 应用开发中,类加载机制是一个重要且复杂的话题。Spring Boot 作为现代 Java 开发的主流框架,其类加载机制更是值得深入了解。本文将从基础概念到实际应用,全面解析 Spring Boot 的类加载机制。…...
Python 训练营打卡 Day 45
TensorBoard 简单来说,TensorBoard 是 TensorFlow 自带的一个「可视化工具」,就像给机器学习模型训练过程装了一个「监控屏幕」。你可以用它直观看到训练过程中的数据变化(比如损失值、准确率)、模型结构、数据分布等,…...

自托管图书搜索引擎Bookologia
简介 什么是 Bookologia ? Bookologia 是一个专门的书籍搜索引擎,可以在几秒钟内找到任何书籍。它是开源的,可以轻松自托管在 Docker 上,为用户提供一个简单而高效的书籍查找体验。 主要特点 简洁的用户界面:界面设计…...

前端flex、grid布局
flex布局 弹性布局是指通过调整其内元素的宽高,从而在任何的显示设备上实现对可用显示空间最佳填充的能力。弹性容器扩展其内元素来填充可用空间,或将其收缩来避免溢出 简单来说,弹性盒子模型,是为了你的网页可以在不同分辨率设…...

Maven相关问题:jna版本与ES冲突 + aop失效
文章目录 1、背景2、解决3、一点思考4、环境升级导致AOP失效5、okhttp Bean找不到6、总结 记录一些Maven依赖相关的思考 1、背景 做一个监控指标收集,用一下jna依赖: <dependency><groupId>net.java.dev.jna</groupId><artifact…...

Tomcat全方位监控实施方案指南
#作者:程宏斌 文章目录 一.二进制部署1、安装包信息2、新建配置文件2.1 配置config.yaml文件2.2 上传jar包 3、修改配置3.1 备份3.2 修改bin目录下的startup.sh文件 4、重启tomcat5、访问测试 二.docker部署1、临时方案1.1、重新启动容器1.2…...
开源PHP在线客服系统源码搭建教程
在当今数字化时代,在线客服系统已成为企业与客户沟通的重要桥梁。开源PHP客服系统因其灵活性、低成本和高可定制性而受到众多企业的青睐。本文将介绍几款优秀的开源PHP客服系统,并提供详细的搭建教程。 演示网站:gofly.v1kf.com 1.1 主流开源…...
centos7升级glibic-2.28
centos7升级glibic-2.28 最近使用trae连接服务器的时候,提示远程系统不兼容: Trae CN需要glibc 2.28或更高版本。检测到的版本: 2.17。下面是升级步骤。centos7默认的glibc不支持node v18及以上。 1、进入/home/download目录(没有download,则新建一个)…...
在Docker里面运行Docker
Docker 凭借其轻量级和可移植的容器,无疑改变了软件开发和部署的世界。但如果我告诉你 Docker 本身可以在另一个 Docker 容器中运行,你会怎么想?没错!这个概念通常被称为“Docker Inside Docker”或“DinD”,它为开发人员和系统管理员开辟了一个全新的可能性领域。在这篇博…...
设计模式复习小结
1.容易忘得设计原则 接口隔离:指接口中的功能太杂则可以拆分一下。防止实现类实现了接口后自动依赖了一些不需要的功能。不同功能拆分成不同的接口。 里氏代换:强调父类能出现的地方,子类一定能正常跑。 迪米特法则:又称最少知…...

To be or Not to be, That‘s a Token——论文阅读笔记——Beyond the 80/20 Rule和R2R
本周又在同一方向上刷到两篇文章,可以说,……同学们确实卷啊,要不卷卷开放场域的推理呢? 这两篇都在讲:如何巧妙的利用带有分支能力的token来提高推理性能或效率的。 第一篇叫 Beyond the 80/20 Rule: High-Entropy Mi…...
【基础】每天掌握一个Linux命令 - awk
目录 【基础】每天掌握一个Linux命令 - awk一、工具概述二、安装方式Ubuntu/Debian系统:CentOS/RHEL系统:macOS系统: 三、核心功能四、基础用法基本语法常用选项内置变量基本操作示例1. 打印文件所有内容2. 打印每行的第一个字段3. 指定分隔符…...

《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》
本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P38 变量复制(Variable Replication)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author)…...

AWS API Gateway配置日志
问题 访问API Gateway接口出现了403问题,具体报错如下: {"message":"Missing Authentication Token"}需要配置AWS API Gateway日志,看请求过程是什么样子的。 API Gateway 先找到API Gateway的的日志角色,…...

Towards Open World Object Detection概述(论文)
论文:https://arxiv.org/abs/2103.02603 代码:https://github.com/JosephKJ/OWOD Towards Open World Object Detection 迈向开放世界目标检测 Abstract 摘要 Humans have a natural instinct to identify unknown object instances in their environ…...

轻松备份和恢复 Android 系统 | 4 种解决方案
我们通常会在 Android 手机上存储大量重要的个人数据,包括照片、视频、联系人、信息等等。如果您不想丢失宝贵的数据,可以备份 Android 数据。当您需要访问和使用这些数据时,可以将其恢复到 Android 设备。如果您想了解 Android 备份和恢复&a…...

具备强大的数据处理和分析能力的智慧地产开源了
智慧地产视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。 AI是新形势下数…...
RK3588和FPGA桥片之间IO电平信号概率性不能通信原因
1.GPIO管脚配置问题 RK3588对IO进行配置的时候,如果配置为多功能复用,没有明确IO功能,可能引起信号接收不稳定, 需要在驱动中设备树中配置管脚为GPIO功能,确保没有功能复用的干扰。 2.上下拉电阻阻值设置不当 GPIO引脚…...

【iSAQB软件架构】软件架构中构建块的视图:黑箱、灰箱和白箱及其交互机制
在软件架构描述中,黑箱视图(Black-box)、灰箱视图(Gray-box)和白箱视图(White-box) 是不同抽象层级的构建模块表示方式,用于满足不同受众和设计阶段的需求。以下是基于ISAQB标准的清…...
.net jwt实现
.NET 中实现 JWT 认证:详细指南 在现代的 Web 应用开发中,安全认证是至关重要的一环。JSON Web Token(JWT)作为一种广泛使用的认证机制,为 API 提供了安全、便捷的身份验证方式。本文将详细介绍如何在 ASP.NET Core 项…...
LangChain【7】之工具创建和错误处理策略
文章目录 一 LangChain 自定义工具概述二创建自定义工具的三种方法2.1 方法一:tool 装饰器2.1.1 同步方法案例2.1.2 工具描述方式1:传参2.1.3 工具描述方式2:文档字符串 2.2 方法二:StructuredTool类2.2.1 StructuredTool创建自定…...