MVI架构实战:以收藏页为例的状态管理与Flow实现
1. 为什么收藏页是最容易暴露架构问题的功能如果说项目中哪个功能看起来最简单、改起来却最容易翻车“我的收藏”绝对能排进前三。原因很简单一个收藏页同时承担了列表加载、分页、取消收藏、下拉刷新、空态展示、错误恢复还要跟商品详情页或列表页保持数据同步。用传统的回调式写法初期确实爽一旦需求开始叠加状态之间互相拉扯代码就会迅速膨胀到谁都看不懂。这也是我决定在 MVI 架构系列里专门拿它做实战示例的原因。写这个系列的前两篇时我一直在强调 MVI 的核心思想UI 只是状态的投影用户操作变成 IntentReducer 负责把 Intent 翻译成新状态。收藏页正好是验证这一套思路的绝佳场景。它有明确的列表状态、有需要即时反馈的“取消收藏”操作、有分页和刷新带来的并发状态组合。把逻辑理顺之后你会发现 MVI 的单向数据流并不是什么高深理论它只是把一个容易失控的页面用一套纪律约束住了。1.1 一个“看起来很简单”的页面藏着哪些状态先别急着写代码先把收藏页可能出现的所有情况列一遍。很多人翻车就是因为只考虑了“有数据”这一条主路径结果空态、失败态、刷新态、分页加载态一拥而上时代码变成一锅粥。我把它整理成一张映射表你对照着看会非常直观用户操作页面表现数据层动作进入页面显示加载中请求第一页收藏列表点击取消收藏该项立即变为未收藏态调用后端解除收藏接口下拉刷新顶部出现刷新动画重新请求第一页并覆盖旧列表滑到列表底部底部出现加载中请求下一页并追加到末尾请求失败显示错误提示或重试按钮保留旧数据或清空列表所有收藏为空显示空态引导图无网络请求这还只是主流程。真到了线上你还会遇到快速点击导致的重复请求、刷新和分页同时触发的竞态、取消收藏失败后的数据回滚、页面被杀后恢复进度等问题。如果你用isLoading一个布尔值包打天下后面每加一个状态就要在代码里叠一层if那种“改一处崩三处”的体验做过的人都懂。1.2 MVI 在这里解决什么传统写法的问题本质上是状态没有唯一来源。Activity 里维护一个mListAdapter 里维护一个mList缓存里又有一个mList三处数据用回调同步任何一处忘记更新UI 就悄悄错了。MVI 的做法非常强硬全页面只认一个State这个State是不可变的数据对象所有 UI 都从它推导。你想改页面不能“直接改某个列表”而是发一个Intent经过Reducer算出新的State再让 UI 重新渲染。绕是绕了一点但换来了确定性。收藏页尤其适合这种约束因为它的操作类型很少但每个操作都会改变多个维度列表本身、收藏状态、加载标记、错误信息。与其把这些维度分散在多个变量里不如统一打包成一个数据类每次变更都生成新对象。我刚从回调式写法迁移过来时也不适应总觉得多此一举但用了两周之后就再也回不去了——排查问题的速度提升了一个量级。2. 动手前的三件套State、Intent、EffectMVI 落地前先把三个核心概念定义清楚。State是页面的当前快照Intent是用户操作或系统事件的入口Effect是只发生一次、不该被持久化的副作用。这三个东西一旦含糊后面全是坑。2.1 把页面状态画进一个不可变数据类状态设计是整个 MVI 实现里最需要花心思的部分。你要像一个产品经理画原型图一样把页面可能出现的每个视觉细节都映射成字段。对于收藏页我的状态通常长这样data class FavoriteUiState( val isLoading: Boolean false, val isRefreshing: Boolean false, val isLoadingMore: Boolean false, val items: ListFavoriteItem emptyList(), val page: Int 1, val hasMore: Boolean true, val errorMessage: String? null )注意几个细节。第一加载状态我拆成了三个字段首屏加载、下拉刷新、加载更多。很多初学者只用一个isLoading结果刷新时列表也要被清空体验非常割裂。拆开之后每个并发操作的独立状态一目了然。第二items是只读列表。你在 Kotlin 里可以用ListFavoriteItem声明但一定不要用MutableList。只要列表可变就会有人忍不住去add或remove状态唯一来源就失守了。第三errorMessage没必要单独区分“首次加载失败”和“刷新失败”失败后的 UI 表现可以通过items是否为空来判断。列表为空时失败显示整页错误列表非空时失败显示顶部提示条这两种场景用同一个字段配上items的状态就能区分开。2.2 把用户的每个操作收拢成一个 IntentIntent 设计的准则是面向用户意图而不是面向 UI 回调。比如“点击了取消收藏按钮”不是 Intent而“要取消这个商品的收藏”才是。sealed class FavoriteIntent { data object LoadInitial : FavoriteIntent() data object Refresh : FavoriteIntent() data object LoadMore : FavoriteIntent() data class ToggleFavorite(val itemId: String) : FavoriteIntent() }这里有一个我踩过坑的地方你可能会想再定义一些“事件已完成”的 Intent比如LoadSuccess、LoadFailure。在严格的 MVI 定义里这是合理的因为数据加载结果也要通过 Intent 回到状态流中。但在 Android 上用协程实现时数据结果既可以通过Intent回到 Reducer也可以在 ViewModel 内部直接调用update修改状态。我自己的实践是用户在 UI 层发出的操作走 Intent网络请求的结果在 ViewModel 的协程里直接更新 State。这样做的原因是避免 State 被数据回调再次驱动导致状态更新顺序不可控。后续如果你想把 MVI 的所有逻辑都收敛到纯函数 Reducer 里做单元测试再把网络结果也转成 Intent 也不迟架构上并不冲突。2.3 Effect那些“只发生一次”的反馈收藏页除了状态变化还有两类典型副作用Toast 提示和页面跳转。这两种东西都不应该放到 State 里。你想想如果把showToast放进 State旋转屏幕后 State 被保留Toast 会重复弹一次这显然不对。所以单独定义 Effect 流sealed class FavoriteEffect { data class ShowToast(val message: String) : FavoriteEffect() data class NavigateToDetail(val itemId: String) : FavoriteEffect() }Effect 用SharedFlow发射UI 层每次收集都会收到新事件但事件本身不参与状态渲染。很多 MVI 教程会把 Effect 也塞进 State用eventId去重这是可行的但会增加状态层的复杂度。我更喜欢把两者分开职责更清晰。3. 数据层给收藏列表一个稳定的入口MVI 关注的是表现层但数据层接口设计直接决定 ViewModel 里面的代码能不能写得流畅。3.1 仓储接口不关心数据从哪来只关心数据长什么样我建议给 ViewModel 暴露一个FavoriteRepository接口隐藏数据源是网络、数据库还是内存缓存的细节。interface FavoriteRepository { suspend fun getFavoritePage(page: Int, pageSize: Int): PageResultFavoriteItem suspend fun removeFavorite(itemId: String): ResultUnit suspend fun favoriteCount(): FlowInt }分页结果单独封装一个PageResult里面带上hasMore标志。不要只返回List否则 ViewModel 每次都要自己猜是不是最后一页不仅重复还容易判断错。data class PageResultT( val list: ListT, val hasMore: Boolean )3.2 缓存、分页与幂等的取舍收藏页的数据特点是低频、高价值、用户敏感。我一般会让仓储层做“先本地后远端”的缓存策略冷启动时先读本地数据库或 DataStore 缓存立即渲染缓存为空或过期时再请求网络成功后写缓存。这样做的好处是收藏页不会启动时白屏即使用户无网络也能看到上次的收藏内容。但要注意一个问题本地缓存和远程数据会有状态不一致的时刻。比如用户明明已经取消收藏网络接口还没调用成功缓存里已经被我更新了那么新页面清理缓存重建后收藏列表就会短暂把已经取消的商品又列出来。所以缓存更新时机必须和 Repository 的写操作保持一致。取消收藏接口成功后立刻更新本地缓存失败则保持原样。这条规则在仓储层内部实现MVI 里的 ViewModel 不需要知道细节只需要响应成功或失败的结果。4. ViewModel 中的单向数据流实现到这一步才是真正的代码落地。我以一个基于 Kotlin 协程和 Flow 的标准实现为例带你完整走一遍。4.1 建立 StateFlow 与 SharedFlow 两条通道ViewModel 对外暴露两个属性一个StateFlow给 UI 层做渲染一个SharedFlow给 UI 层做一次性事件消费。class FavoriteViewModel( private val repository: FavoriteRepository ) : ViewModel() { private val _state MutableStateFlow(FavoriteUiState()) val state: StateFlowFavoriteUiState _state.asStateFlow() private val _effect MutableSharedFlowFavoriteEffect() val effect: SharedFlowFavoriteEffect _effect.asSharedFlow() fun dispatch(intent: FavoriteIntent) { when (intent) { FavoriteIntent.LoadInitial - loadInitial() FavoriteIntent.Refresh - refresh() FavoriteIntent.LoadMore - loadMore() is FavoriteIntent.ToggleFavorite - toggleFavorite(intent.itemId) } } }MutableStateFlow记住最新值新订阅者立刻拿到当前页面状态ViewModel 存活期间状态天然被保留这正是收藏页需要的特性。MutableSharedFlow默认不重放事件所以每次 Toast、每次跳转都只会被消费一次。4.2 实现初始化加载与下拉刷新先看初始化加载private fun loadInitial() { if (_state.value.isLoading) return viewModelScope.launch { _state.update { it.copy(isLoading true, errorMessage null) } repository.getFavoritePage(1, PAGE_SIZE) .onSuccess { pageData - _state.update { it.copy( isLoading false, items pageData.list, page 1, hasMore pageData.hasMore ) } } .onFailure { e - _state.update { it.copy(isLoading false, errorMessage e.message) } } } }注意我用了update而不是直接_state.value ...。这是一个非常容易忽略但极其重要的细节。MutableStateFlow的更新必须是原子的如果多个协程同时执行_state.value ...后写的会覆盖先写的轻则状态丢失重则 UI 错乱。而update基于 CAS 循环实现并发安全。MVI 里这个坑尤其容易踩因为你的 Intent 会从各种入口发进来。下拉刷新和初始化加载的区别在于刷新时不显示全屏 Loading而是走isRefreshing标记并且刷新成功后直接替换整个列表而不是追加。private fun refresh() { if (_state.value.isRefreshing || _state.value.isLoading) return viewModelScope.launch { _state.update { it.copy(isRefreshing true, errorMessage null) } val result repository.getFavoritePage(1, PAGE_SIZE) result.onSuccess { pageData - _state.update { it.copy( isRefreshing false, items pageData.list, page 1, hasMore pageData.hasMore ) } }.onFailure { e - _state.update { it.copy(isRefreshing false, errorMessage e.message) } } } }4.3 分页加载的细节处理分页的核心是防止重复请求以及正确处理追加逻辑。很多分页 bug 都来自“滑到底部时连续触发两次 LoadMore”。private fun loadMore() { val currentState _state.value if (currentState.isLoading || currentState.isRefreshing || currentState.isLoadingMore || !currentState.hasMore ) return viewModelScope.launch { _state.update { it.copy(isLoadingMore true) } val nextPage currentState.page 1 repository.getFavoritePage(nextPage, PAGE_SIZE) .onSuccess { pageData - _state.update { it.copy( isLoadingMore false, items it.items pageData.list, page nextPage, hasMore pageData.hasMore ) } } .onFailure { e - _state.update { it.copy(isLoadingMore false, errorMessage e.message) } } } }我在这里用了“按钮触发式”的分页写法也就是说由 UI 层在检测到滚动到底部时调用dispatch(FavoriteIntent.LoadMore)。View 层用 addOnScrollListener 计算是否接近底部即可。这种模式写起来直观也方便测试。PAGE_SIZE 的选择值得多考虑一下。很多人喜欢用 10觉得省流量实际上对收藏页来说 10 太小会导致频繁加载更多列表跳动明显而且容易在快速滚动时出现短暂空白。我这边一般用 20能够让大多数手机用户一次滑屏看到足够多的内容又不会让接口返回太慢。5. 取消收藏乐观更新与失败回滚的完整过程取消收藏是收藏页里最有代表性的一个操作也是很多人处理得最粗糙的地方。直接把 item 从列表里删掉等你需求迭代到“取消收藏应该保留位置再点可以重新收藏”时你之前那套写死removeAt的代码就要重写了。5.1 为什么不直接“把 item 从列表里删掉”直接删掉的问题在于你删的只是 UI 层的展示仓库层和后端层不一定同步。如果用户是在弱网环境下操作接口失败后你又得把 item 加回去这个“删除再恢复”的过程不仅视觉上闪一下而且列表顺序也可能因为数据重载而错乱。正确做法是把“取消收藏”当作一个切换操作。item 本身还在列表中只是isFavorite字段变成 false。这样即使用户重复点击也能在“已收藏”和“未收藏”之间正确切换不会出现删掉了却加不回来的尴尬情况。5.2 具体实现步骤与异常处理实现分三步先做乐观更新再发起网络请求最后按结果提交最终状态。private val pendingUpdateIds mutableSetOfString() private fun toggleFavorite(itemId: String) { if (!pendingUpdateIds.add(itemId)) return viewModelScope.launch { // 乐观更新 _state.update { state - state.copy( items state.items.map { item - if (item.id itemId) { item.copy(isFavorite !item.isFavorite) } else { item } } ) } repository.updateFavorite(itemId, isFavorite !_state.value.items.find { it.id itemId }?.isFavorite ?: false) .onSuccess { _effect.emit(FavoriteEffect.ShowToast(操作成功)) } .onFailure { e - // 回滚 _state.update { state - state.copy( items state.items.map { item - if (item.id itemId) { item.copy(isFavorite !item.isFavorite) } else { item } } ) } _effect.emit(FavoriteEffect.ShowToast(操作失败${e.message})) } .also { pendingUpdateIds.remove(itemId) } } }这里有个细节我要单独强调在调用repository.updateFavorite时我传进去的是“取反后的值”但我一开始乐观更新时已经把它改成相反状态了。所以需要在乐观更新后再判断当前 item 的isFavorite值把取反后的值传给仓储。否则你会传成“旧状态”相当于取消收藏时传了一个isFavorite true接口把收藏又加回来了数据就乱了。这种细节真的不到线上遇到不会留意我希望你一步到位避开。另外pendingUpdateIds用来防止用户疯狂点击同一 item 导致并发请求。MVI 虽然给了我们单向数据流但并没有自动解决 UI 防抖问题这类“幂等保护”还是得自己加。6. UI 层绑定与防重复操作ViewModel 写得再漂亮最后还是要跟 RecyclerView 打交道。UI 层这一步处理不好前面所有架构设计都会白费。6.1 从 State 到 RecyclerViewDiffUtil 与 collect在 Fragment 或 Activity 里我通常用repeatOnLifecycle配合collect来订阅 StateFlowviewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.state.collect { state - adapter.submitList(state.items) { // 列表更新完成回调 } // 更新加载 UI } } }adapter使用带AsyncListDiffer的ListAdapter。这里有一个很关键的收益因为 MVI 的State是不可变的每次submitList传入的都是新列表实例。AsyncListDiffer会自动对比新旧列表只刷新有变化的 item收藏页切换收藏状态时不会让整个列表闪一下。如果你之前用notifyDataSetChanged体验差不说滚动位置还会乱跳。Effect 流的收集方式viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.effect.collect { effect - when (effect) { is FavoriteEffect.ShowToast - showToast(effect.message) is FavoriteEffect.NavigateToDetail - navigateToDetail(effect.itemId) } } } }因为 SharedFlow 没有默认缓冲建议在创建时设置extraBufferCapacityprivate val _effect MutableSharedFlowFavoriteEffect( replay 0, extraBufferCapacity 1, onBufferOverflow BufferOverflow.DROP_OLDEST )6.2 防重复点击与状态恢复收藏页的点击目标主要有两种收藏按钮和 item 本身。在 MVI 架构里防重复点击我推荐放在两个层面双管齐下。第一层是 UI 层做防抖。比如在收藏按钮的setOnClickListener里用clickable配合协程做 500ms 间隔限制或者简单点直接设置一个不可点击的窗口期。这个属于常规操作不再展开。第二层是状态层拦截就是我上面写的pendingUpdateIds。这个更可靠因为它冻结的是业务操作本身而不是依赖 UI 层的调用频率。状态恢复这个点容易被忽略。进程被系统回收后ViewModel 实例会丢失用户从“最近任务”切回来收藏页可能变成空白。解决办法是把关键的当前页码和列表总数保存到SavedStateHandleclass FavoriteViewModel( private val repository: FavoriteRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { // 初始化时恢复 page }不过大多数情况下ViewModel 在屏幕旋转和普通的 Activity 重建时都能存活只有进程被杀才需要恢复。所以我的建议是第一版先保证 ViewModel 被持有期间的完整性等出现真实需求再补充SavedStateHandle。过度设计同样是一种坏味道。7. 常见问题排查与 MVI 的边界最后分享一些我在这个页面上实际踩过的坑。这些坑不会写在任何官方文档里但每一个都曾经让我排查到怀疑人生。7.1 SharedFlow 事件丢失只顾着订阅 StateFlow却没有及时订阅 Effect 流或者在 Activity 进入后台时收集暂停此时发出的 Toast 事件就会悄悄丢掉。表现就是“明明调用了取消收藏却没有任何提示”。我解决这个问题的方法是把“操作是否成功”沉淀到 State 层。比如在FavoriteUiState里加一个lastOperationMessage字段UI 层在收集 State 时检测到这个字段变化再决定弹 Toast。StateFlow 有粘性不会丢事件。这套方案牺牲了一点“纯 Effect”的优雅但换来的是更可靠的用户体验。7.2 状态并发更新导致的覆盖在我还在用_state.value ...手动复制的时候遇到过刷新和取消收藏同时进行结果其中一个协程把另一个协程写好的状态覆盖了列表里已经取消收藏的商品又闪回来。排查了很久后来全部改成_state.update { }才彻底解决。记住MutableStateFlow的value赋值不是原子的多个协程并发写入时后执行的并不一定基于最新的状态。MVI 本来就强调状态集中管理如果并发这块不处理好反而比传统写法更容易翻车。7.3 MVI 不是银弹什么时候别硬套写了几个 MVI 页面之后你会产生一种冲动想给所有页面都套上这套模式。我劝你冷静。收藏页之所以适合 MVI是因为它有明显的多状态并发、需要同步多个数据来源、操作类型固定且有限。但如果你面对的是一个纯展示页面比如关于页、隐私政策页数据就拉一次没有任何复杂交互硬套 MVI 只会让代码量翻倍收益却为零。我现在的判断标准很简单这个页面有没有“多个互相关联的状态”有没有“一个操作触发多个模块变化”如果有就值得用 MVI如果没有用一个普通的ViewModel加StateFlow就足够了。架构是服务于可维护性的不是用来证明你技术水平的。我在实际项目中用这套方式重写收藏页之后一个最直观的感受是需求方再跟我说“这里要加一个筛选”“这里要支持长按多选取消”时我不再需要像过去那样在一堆回调里翻来翻去找状态更新的位置只需要加 Intent、扩展 State、补一两个 Reducer 分支。页面行为的每个细节都能被追踪、被测试、被解释。这种确定性才是 MVI 最值得你花时间掌握的地方。

相关新闻

Linux内存性能测试工具STREAM:从四项基准到避坑指南

Linux内存性能测试工具STREAM:从四项基准到避坑指南

简介:内存带宽是服务器性能评估中常被忽视的基础指标,尤其当CPU占用不高而吞吐受限时,瓶颈往往隐藏于内存子系统。STREAM作为Linux生态中最经典的内存性能测试工具,通过Copy、Scale、Add、Triad四项基准循环,以量化内存…

2026/10/10 12:56:46 阅读更多 →
IDA MCP本地AI逆向:语义分析六层架构与零认证设计

IDA MCP本地AI逆向:语义分析六层架构与零认证设计

1. 这不是“AI写代码”,而是“AI读汇编”——一场逆向工程范式的静默迁移你有没有试过打开一个没符号表的Windows驱动,面对满屏mov eax, dword ptr [esi0x14]和跳转如麻的jmp short loc_4012A7,盯着IDA Pro的反汇编窗口发呆两小时&#xff0c…

2026/10/10 12:56:46 阅读更多 →
屏幕亮度调节失效的底层原理与全平台解决方案

屏幕亮度调节失效的底层原理与全平台解决方案

1. 为什么屏幕亮度调节不是“点两下就完事”的小事? 很多人第一次意识到屏幕亮度问题,是在某个午后——眼睛发酸、视线模糊,盯着文档半小时却记不住一句话。这时候才想起去调亮度,结果发现:Windows笔记本的F5/F6键没反…

2026/10/10 12:55:45 阅读更多 →

最新新闻

热电联产机组联合优化调度:Matlab+YALMIP建模风电消纳与储热电锅炉算例

热电联产机组联合优化调度:Matlab+YALMIP建模风电消纳与储热电锅炉算例

1. 冬季供暖季的弃风困局:热电联产机组到底卡在哪每年供暖季一过,风电场的同事就开始盯着调度曲线叹气:白天风光还好,一到后半夜风速上来了,风电场却得压出力,甚至有整场停机的时候。而另一边,热…

2026/10/10 16:01:52 阅读更多 →
用AI高效阅读鸿蒙源码:仓库定位、调用链与实战技巧

用AI高效阅读鸿蒙源码:仓库定位、调用链与实战技巧

简介:面向鸿蒙OS平台的“阅读”应用鸿蒙版仓库源码,特别适合鸿蒙应用开发者、对小说阅读器实现感兴趣的工程师,以及希望复用书源管理方案的技术人员。工程基于ArkTS编写主要页面与业务逻辑,并搭配svg、png等图标与图片资源&#x…

2026/10/10 16:01:52 阅读更多 →
Java IO流深度解析:字节流字符流、缓冲流与序列化实战指南

Java IO流深度解析:字节流字符流、缓冲流与序列化实战指南

1. 别被IO流的类图吓到:先搞懂设计骨架做Java开发几年后回头看,IO流其实是整个Java生态里设计最经典、也最劝退新手的模块之一。所谓“Java进阶--IO流”,不是让你把几十个类的名字背下来,而是先看清这套体系背后的两个核心设计思想…

2026/10/10 16:01:52 阅读更多 →
Vector v0.51.0 版本深度解析:OTLP 编解码、file source 去遗留化与遥测可靠性加固

Vector v0.51.0 版本深度解析:OTLP 编解码、file source 去遗留化与遥测可靠性加固

可观测性数据工程数据集成日志分析 【免费下载链接】vector A high-performance observability data pipeline. 项目地址: https://gitcode.com/GitHub_Trending/vect/vector 点击查看 免费下载 Vector v0.51.0(发布于 2025-11-04)是面向可观…

2026/10/10 16:01:52 阅读更多 →
Spring AI 2.x 深度技术解析:从架构重构到企业级落地,TaoToken 统一 Key 接入实践

Spring AI 2.x 深度技术解析:从架构重构到企业级落地,TaoToken 统一 Key 接入实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 16:01:52 阅读更多 →
探索AI工具——我的Cursor初体验:从Base URL改到TaoToken

探索AI工具——我的Cursor初体验:从Base URL改到TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 16:00:51 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →