月明星稀 - 开发日志
type: channel
link: https://www.ymxx.net/category/%E5%BC%80%E5%8F%91%E6%97%A5%E5%BF%97/
items: Flutter/iOS 后台自动切歌与通知栏手动“下一首”稳定实现方法
items: Android/Flyme 通知栏“暂停后继续播放无响应”技术复盘：根因、关键代码与最终稳定方案
items: 播放页默认封面切换闪烁（技术细节版）：从资源猜测到动画结构稳定性的完整修复
items: 在线播放切歌“声音和信息不一致”：一次从入口补丁到状态链路重构的排查
items: 重装后收藏/喜欢 歌曲不见了：一次「ID 稳定性 + 数据迁移」的修复记录
items: iOS 本地 FLAC 拖动进度条不准：一次「日志正确但耳朵不对」的排查
items: 制作一个激活码自动领取插件
items: SollinPlayer更新计划（长期更新）
items: 抖音视频下载工具 - 可以一键下载无水印的收藏、主页、喜欢的所有视频
items: FlaskHTTPStudio - 在线webHTTP 请求测试工具（开源）

Flutter/iOS 后台自动切歌与通知栏手动“下一首”稳定实现方法
type: item
date: 2026-04-23T06:22:00+00:00
description: Flutter/iOS 后台自动切歌与通知栏“下一首”稳定实现方法前段时间在重构播放器内核时，我顺手把一类最烦的 iOS 播放问题彻底收了一遍：后台自动切歌不稳定、锁屏/通知栏点“下一首”偶发失灵，或者声音切了但元数据还停留在上一首。这个问题最恶心的地方不在于“完全坏掉”，而在于它经常是 80% 正常，20% 抽风：前台切歌基本正常；进后台以后，播完自动下一首偶发不切；通知栏点下一首，有时能切，有时像没点到；更诡异的是，有时声音已经切过去了，但歌名、封面、按钮绑定的歌曲还是旧的。涉及核心文件：lib/core/services/audio_manager.dartios/Runner/AppDelegate.swiftlib/core/services/ios_carplay_service.dart先说最终结论这次修完之后，我对这个问题的总结非常明确：iOS 后台自动切歌和通知栏“下一首”是否稳定，关键不在于你有没有写 skipToNext()，而在于你有没有把“切歌”当成一个完整的状态机来管理。真正稳定的实现，至少要满足这几条：所有切歌入口统一收口：播放器按钮、通知栏、锁屏、CarPlay、自动切歌，最后都走同一条主链路。在线切歌必须串行化：任意时刻只能有一个活跃切歌任务。旧异步任务不能回写新状态：要有 token 防串写。切歌中再次点击不能简单吞掉：要记录“最后一次用户意图”，当前切歌结束后自动 drain。在线播放列表启动流程也要有独立 token：否则它会和手动切歌抢状态。iOS 后台缓存切歌不要傻等 play() Future：它可能很晚才 resolve，但音频其实已经播了。通知栏的 queueIndex 和 mediaItem 不能完全相信播放器内部 index：必须优先使用你自己维护的当前索引。下面按排查过程慢慢讲。需注意，本文仅供技术性学习参考，不代表你的软件完全适用，具体问题具体分析，如果遇到问题，欢迎评论区留言交流。 -by Leguan1. 问题现象：看起来像“偶发失灵”，本质是多条状态链路抢写我最早观察到的现象有四类。1.1 自动切歌偶发不触发歌曲在前台播放完时大多能自动切下一首，但一旦切到后台，尤其是在线歌曲，就会出现：明明还有下一首；进度已经到结尾；但就是停在那里不走。1.2 通知栏/锁屏点下一首偶发没反应这个现象在“当前歌曲刚切完、或者正处于切歌中”时更明显：点一次 next，没反应；再点一次，又突然跳到后一首；有时还会出现“第二次操作覆盖第一次”的错觉。1.3 声音切过去了，但元数据还是旧的这是最迷惑人的一种。用户主观体验是：耳朵听到已经是下一首；但锁屏显示还是上一首；播放页里某些区域也还没更新；有时暂停一下，信息又“自己好了”。这类问题最容易让人误以为是 UI 层刷新 bug。1.4 日志多数正确，但体验依然错更坑的是，很多日志看起来都很健康：targetIndex 是对的；setAudioSource 是成功的；play() 也发起了；SWITCH success 也打出来了。但用户体验仍然不稳定。这通常意味着：你看到的不是“某一步完全失败”，而是多个异步流程竞争状态，最后谁晚回来谁覆盖谁。2. 第一轮误判：以为只是“切歌函数写得不够严谨”最开始我以为这是个很普通的切歌重入问题。于是第一轮修法很朴素：加 _isSwitchingOnlineTrack 锁；切歌中直接忽略新的 next/prev；给 skipToNext() 加节流。看起来很合理，但很快发现两个副作用：用户连续点两下 next，第二下被吞了，体感上就是“按钮不灵”；下一次计算目标索引时，有时还是基于旧的 _currentIndex，结果方向也会错。也就是说，只靠一个“正在切歌就 return”的入口锁，最多只是降低混乱，并没有真正解决竞争。3. 第二轮误判：加了 token，为什么还会被旧状态拉回去？随后我把在线切歌链路加上了 switchToken。这个思路本身是对的：每次切歌时递增 token；在 resolve -> setAudioSource -> play -> commit 的各个阶段检查 token；发现 token 过期就立刻放弃，不允许旧任务再写 _currentIndex / mediaItem / queue。理论上这已经能挡住大部分“旧 Future 晚回来”的问题。但实测仍然有一类异常：手动点 next 之后，音频已经切对了；几百毫秒后，UI 又被“拉回去”；日志里会混入 ONLINE_START success 一类晚到消息。这时候我才意识到：切歌并不是唯一一条会写播放状态的链路。4. 真正根因：不是一个 race，而是四条链路在竞争最后梳理下来，真正互相竞争的其实是四条链路：手动切歌链路sk…

Android/Flyme 通知栏“暂停后继续播放无响应”技术复盘：根因、关键代码与最终稳定方案
type: item
date: 2026-03-05T12:42:00+00:00
description: Android/Flyme 通知栏“暂停后继续播放无响应”技术复盘：根因、关键代码与最终稳定方案这篇偏技术细节，重点放在：问题如何被误导、如何用日志证明、最终是怎么改代码稳住的。涉及核心文件：lib/core/services/audio_manager.dart1. 问题现象与复现复现环境：Android：Flyme 12.6.0.0AApp：1.0.5+2010~1.0.5+2014 逐版验证构建：flutter build apk --release --split-per-abi问题路径：通知栏点暂停；再点继续播放；部分机型无反应。附带异常：有些通知样式会出现一个方形按钮（本质是 stop/custom action 显示路径差异）。2. 第一阶段：先清表层问题2.1 去掉 stop 按钮，固定 3 个控制位先把通知栏按钮收敛为三键：prev / play-pause / next。controls: [ MediaControl.skipToPrevious, if (_player.playing) MediaControl.pause else MediaControl.play, MediaControl.skipToNext, ], androidCompactActionIndices: const [0, 1, 2],同时移除 MediaControl.stop，避免 ROM 显示方形动作位导致误触 stop。2.2 click() 不再依赖 playbackState.playingBaseAudioHandler.click() 默认根据 playbackState 判断切换，某些时刻可能滞后。改成看 _player.playing：@override Future<void> click([MediaButton button = MediaButton.media]) async { switch (button) { case MediaButton.media: if (_player.playing) { await pause(); } else { await play(); } break; case MediaButton.next: await skipToNext(); break; case MediaButton.previous: await skipToPrevious(); break; } }2.3 onNotificationDeleted() 不再 stop默认实现会 stop()，在 Flyme 上可能导致暂停状态下通知被系统清理后队列丢失。@override Future<void> onNotificationDeleted() async { if (_player.playing) { await pause(); } else { _savePlaybackState(); } }这一步避免了 idx=-1 / playlist=0 这类“状态被清空”的问题。3. 第二阶段：建立可观测性（先证明再改）为了避免“你测的不是我改的包”，增加双版本锚点：启动版本：STARTUP_VERSION app=...+build构建探针：BUILD_PROBE ...static const String buildProbe = 'AUDIO_MANAGER_BUILD_2026_02_27_NOTIFDBG_V7_PREPARE_B14'; debugPrint('[AudioManager][BUILD_PROBE] $buildProbe');并加 NOTIF_DBG 全链路日志：click() requested ...play() requested ...play() dispatch ...play() post-check ...play() future resolved ...这样就能看清楚“到底有没有收到系统命令”“命令收到后有没有真正进入播放”。4. 真正根因：await _player.play() 语义误用旧代码核心问题是把 await _player.play() 当成“播放立即开始”的同步点。4.1 旧写法（有风险）@override Future<void> play() async { await _ensureAudioSessionConfigured(); await session.setActive(true); await _player.play(); // 这里会被长时间阻塞 // 后续状态提交逻辑被拖延 }4.2 日志证据实际日志里经常出现：play() requested 在 T0play() future …

播放页默认封面切换闪烁（技术细节版）：从资源猜测到动画结构稳定性的完整修复
type: item
date: 2026-02-11T00:00:00+00:00
description: 播放页默认封面切换闪烁（技术细节版）：从资源猜测到动画结构稳定性的完整修复这篇是上一版复盘的技术加强版，重点补上：具体代码点位为什么前几轮“看起来合理”的修复无效最终有效方案背后的渲染机制1. 问题定义（精确到动画阶段）问题不是“默认封面偶发闪”，而是：仅默认封面（无 artworkPath）会闪；真实歌曲封面不闪；闪烁发生在动画临界点：大封面开始缩小时（t 从 0 往上）小封面回大封面结束时（t 回到 0）这类现象在 Flutter 里通常优先怀疑：动画树结构在阈值发生插拔（if (t > 0)）；同一视觉对象走了两条不同渲染链路；资源首帧不可用（次要）。2. 关键代码背景播放器核心动画在：lib/features/player/presentation/screens/player_screen.dart方法：_buildAnimatedLayout(...)驱动：AnimatedBuilder(animation: _layoutAnimation, ...)封面渲染组件在：lib/core/widgets/artwork_widget.dart默认封面资源：assets/images/fengmiantu.png3. 失败方案（为什么失败）方案 A：仅做默认图预缓存做法：await precacheImage(const AssetImage('assets/images/fengmiantu.png'), context);结论：无效（或仅轻微改善）。原因：预缓存只能解决“资源首帧缺失”；解决不了动画临界点的节点替换/重建。方案 B：固定默认图 provider + DecorationImage做法：const AssetImage _kDefaultCoverProvider = AssetImage(_kDefaultCoverAsset);并用 DecorationImage 渲染默认封面。结论：改善但不根治。原因：资源流稳定了，但动画结构仍在临界点变化。方案 C：默认封面单独分支优化（RepaintBoundary 等）做法：给默认封面做独立分支组件，尝试压缩重绘。结论：仍闪。原因：真实封面和默认封面路径差异仍在；动画临界点依旧可能触发分支切换。4. 真正根因（双重）根因 1：动画时存在结构插拔播放器动画里歌词层最初是类似：if (t > 0) Positioned(...)当 t 在 0 附近跳变时，Stack 子节点会插入/移除，容易出现一帧闪动。根因 2：默认封面与真实封面走了不同渲染路径真实封面：ArtworkWidget 正常路径默认封面：专用分支路径只要路径不一致，动画临界点就更容易出现“仅某一类素材闪”的问题。5. 最终有效方案（代码级）5.1 歌词层常驻，禁止阈值插拔把：if (t > 0) Positioned(...)改为：Positioned( ... child: IgnorePointer( ignoring: lyricsOpacity < 0.01, child: Opacity( opacity: lyricsOpacity, child: _buildLyricsSection(context, state), ), ), )效果：Stack 子节点数量在动画全过程保持稳定。5.2 统一封面渲染链路：ArtworkWidget 增加强制默认图开关在 ArtworkWidget 新增参数：final bool forceDefaultArtwork;构造参数默认值：this.forceDefaultArtwork = false,在 _buildArtwork 顶部短路：if (forceDefaultArtwork) { return placeholder ?? _DefaultArtwork(size: size); }然后在播放器动画里，无论有无封面都走 ArtworkWidget，只是无封面时启用：ArtworkWidget( id: song?.id, artworkPath: song?.artworkPath, ... allowQueryArtworkFallback: false, forceDefaultArtwork: song?.artworkPath == null || song!.artworkPath!.isEmpty, )这一步是关键：把“默认封面和真实封面”收敛到一套布局/裁剪/动画容器。5.3 保留资源稳定性措施（作为配套，不是主因）默认图固定 provider：const AssetImage _kDefaultCoverProvider = AssetImage(_kDefaultCoverAsset);播放器 init 后预缓存：…

在线播放切歌“声音和信息不一致”：一次从入口补丁到状态链路重构的排查
type: item
date: 2026-02-09T00:00:00+00:00
description: 在线播放切歌“声音和信息不一致”：一次从入口补丁到状态链路重构的排查这个 Bug 折腾了我一整个下午：在播放器里点上一首/下一首，声音切过去了；但封面、歌名、评论按钮绑定的歌曲有时还是旧的；有时点一下暂停，信息又“突然正常”。最容易复现的路径是：点播放 A（正常）切到 B（正常）再切回 A（声音回去了，但 UI 还显示 B）这不是单点 bug，而是几个并发问题叠在一起。现象：日志看起来“多数正确”，但体验不稳定先看表面，很多日志都很“健康”：targetIndex 对SWITCH success 对currentIndex/mediaItem 也经常对但偶发时会出现两类关键信号：1) iOS cached source set ok 很快出现，但 iOS cached play ok 可能晚几秒甚至十几秒；2) 切歌过程中又出现 Online playlist started with ConcatenatingAudioSource 这种“启动流程晚到”日志。这两类一叠加，就会造成：用户已经继续点了切歌；旧异步流程晚回来后又写状态；UI 和音频出现错位，直到下一次播放状态事件（比如 pause）才被动刷新。第一轮碰壁：只做“入口防重入”不够最开始做的是常规处理：加 _isSwitchingOnlineTrack 锁；正在切歌时忽略新点击；加切歌节流。这能减少乱点，但有两个副作用：用户点击被吞掉，体感“按钮不灵”；索引计算还基于旧 currentIndex，后续点击方向会错。结论：只靠入口锁，治标不治本。第二轮碰壁：只做 token 防串写，仍有漏网随后给 playAtIndex 加了 switchToken，防止旧切歌晚回来覆盖新状态。这一步是对的，但仍有残留：setOnlinePlaylist 自身也是异步链路；用户手动切歌后，旧的“启动在线播放列表”流程可能继续完成并回写状态。于是出现了一个很迷惑的现象：切歌明明成功了，过一会儿 UI 又被“拉回去”或延迟刷新。真正根因：不是一个 race，是三条状态链路在竞争最终确认是三层并发竞争：手动切歌链路（skip -> playAtIndex -> setAudioSource/play -> commit）在线播放启动链路（setOnlinePlaylist -> setAudioSource/play -> commit）UI 数据源链路（一部分组件读 stream state，一部分直接读 audioManager.currentSong）只要这三条不统一，某个链路晚到就可能覆盖另一个链路，导致“声音和信息不同步”。最终方案（分层修复）1）切歌链路：token + 排队，不再简单 ignore在 AudioManager.playAtIndex 的在线分支里：每次切歌递增 switchToken；在 setSource/play/commit 前后都检查 token；过期任务直接 stale ignored，不允许写 _currentIndex/mediaItem/queue。同时增加了“切歌排队”而不是“切歌忽略”：切歌进行中收到新的 next/prev，不丢弃，写入 _queuedOnlineSwitchIndex；当前切歌完成后自动 drain 队列，执行最后一次用户意图。这样既避免了串写，又保留了交互连续性。2）在线播放启动链路：增加 session token，防晚到回写给 setOnlinePlaylist 单独加了 online start session token：启动时记录 sessionToken；setAudioSource 后、play 后都校验是否过期；过期则打印 stale ... ignored 并退出，不再提交状态。再加一条关键策略：用户手动切歌时，主动取消 pending 的 online start session。这一步直接解决了“切歌中又出现 Online playlist started 并污染状态”的问题。3）iOS 缓存切歌：不再阻塞等待 play() Future 完成日志里反复出现：iOS cached source set ok 很快但 await _player.play() 可能很久才返回如果等它返回再提交 UI，信息会明显滞后。所以改成：发起 play() 请求（异步监听成功/失败日志）；状态提交不再被 play() Future 阻塞。这让 UI 不会因为 iOS 的慢返回而“卡住旧歌信息”。4）UI 层收口：统一从 playbackState 读当前歌曲播放器页面之前有双数据源：有些组件读 playbackState.currentSong有些组件直接读 audioManager.currentSong这会导…

重装后收藏/喜欢 歌曲不见了：一次「ID 稳定性 + 数据迁移」的修复记录
type: item
date: 2026-01-16T00:00:00+00:00
description: 重装后收藏/喜欢不见了：一次「ID 稳定性 + 数据迁移」的修复记录最近有个很典型、也很容易被忽略的问题：应用重装之后，收藏的歌曲、喜欢的歌曲看起来“全没了”。更准确地说是：收藏数据其实还在本地存储里，但 UI 匹配不到对应的歌曲，于是喜欢列表/歌单展示为空。现象与日志用户侧的表现很直观：重装 App重新扫描本地歌曲（或重新授权）「我喜欢的音乐」变成空收藏/喜欢按钮状态也全变回未收藏日志非常“打脸”：一边说 favorites 确实加载出来了，另一边却说匹配结果为 0。[Favorites] Loaded 17 favorites: {804604524, 749312547, ...} [Match Debug] First 3 song IDs: [1414957450, 147442901, 1927087886] [Match Debug] First 3 favorites: [804604524, 749312547, 37290334] [Match Debug] Matched 0 songs out of 17 favorites这说明两件事：1) favorites 的持久化没坏（能读到 17 个 id） 2) songs 列表也没坏（扫描出了歌曲 id） 3) 但 favorites 里存的 id，和当前扫描出来的 song.id 已经不是同一套体系排查路径：到底是谁变了？收藏/喜欢的存储很简单：本地只存 Set<int> 的 songId（shared_preferences），UI 展示时用：final favoriteSongs = songs.where((s) => favorites.contains(s.id)).toList();因此只要 s.id 的生成规则发生变化（或同一首歌在重装后拿到的 id 变了），favorites 就会“全部失效”。我把排查重点放到两类常见不稳定来源：1）系统媒体库 id 不稳定Android/iOS 的系统媒体库可能返回一个“看起来像主键”的 id（例如 on_audio_query 的 SongModel.id），但它未必承诺跨重装、跨版本、跨扫描一致。2）用「文件绝对路径」生成 id，会被 iOS 重装打爆iOS 卸载重装后，App 的沙盒容器路径会变（例如 .../Application/<UUID>/Documents/... 里的 <UUID> 变了）。如果 id 是 hash(绝对路径)，那同一个文件在新容器里就会产生完全不同的 id。更糟的是：我之前为了修别的问题，把 id 从一种 hash 改成了另一种（比如 String.hashCode vs 自定义 hash），这也会让旧数据瞬间“断链”。定位到关键点：收藏/歌单依赖「稳定 songId」播放器里有三处会依赖 songId：喜欢/收藏（favorite_songs）本地歌单（歌单里存 songIds）播放状态恢复（缓存 playlist songIds + index）只要 songId 不稳定，这三个功能都会在“升级/重装/换机”时出现类似问题。所以修复目标不是“让匹配代码更聪明”，而是：1) 定义一套跨重装稳定的 songId 规则 2) 对历史版本产生的旧 id 做迁移解决方案一：canonical path + 稳定 hash我新增了一个 SongId 工具（lib/core/services/song_id.dart），做两件事：1) 对文件路径做 canonicalize：如果文件位于 app 的 Documents 下，把“安装相关”的前缀剥离掉把路径变成 "<DOCS>/Music/xxx.flac" 这种相对且稳定的形式2) 对 canonical path 做确定性 hash（djb2，并限定 31-bit 正整数）核心思路：同一首歌只要相对路径不变，重装后 id 也不变。解决方案二：启动时自动迁移旧数据光有新规则还不够：用户重装/升级后，preferences 里存的还是旧 id。因此我在“扫描歌曲完成后”（LocalSongsNotifier.scanSongs()）追加了迁移步骤（lib/core/services/providers.dart）：1) 用当前扫描到的 songs 建一张映射表：key：旧算法可能生成的 legacyId（例如 filePath.hashCode、旧的 hash）value：当前 canonicalId（新规则算出来的 song.id）2) 用这张表批量改写：favorites（FavoritesService.migrateSongIds）playlists（PlaylistService.migrateSongIds，并保持原…

iOS 本地 FLAC 拖动进度条不准：一次「日志正确但耳朵不对」的排查
type: item
date: 2026-01-15T00:00:00+00:00
description: iOS 本地 FLAC 拖动进度条不准：一次「日志正确但耳朵不对」的排查最近在重写一个 Flutter 本地音乐播放器，播放器内核用的是 just_audio + audio_service。本来一切都挺顺的，直到我开始认真测试「拖动播放界面的进度条」：UI 上的进度跳过去了，日志也说 seek 成功了，但耳朵听到的位置明显不对。更离谱的是：哪怕你把 slider 直接拉到 100%，实际也没有播放到结尾，而是停在一个“看起来差不多但就是不对”的位置。这个问题只在 iOS 的本地 FLAC 上稳定复现（文件放在应用沙盒里，不走系统媒体库）。现象与复现条件平台：iOS（应用沙盒文件）音频格式：FLAC表现：seek 日志显示 position 已到目标值实际听感偏前，拖到结尾仍未到结尾歌曲时长无异常日志示例（从 UI slider -> seek -> UI 认为“对齐”）：[SLIDER] onChangeEnd: sliderValue=1.0, duration=247153ms, targetPosition=247153ms, actualPosition=5674ms [SEEK] Request: target=247153ms, current=5869ms, duration=247153ms [EFFECTIVE_POS] Aligned! actual=247153ms, pending=247153ms, delta=0ms [SEEK] After seek(): position=247153ms你看日志，完全没毛病：目标=247153ms、seek 后 position=247153ms、甚至 UI 的 “effectivePosition” 还打印了 Aligned!。但声音就是没到那儿。排查路径（以及为什么这些路走不通）我一开始的直觉是“时长算错了 / 元数据不准”。毕竟拖到 100% 还不到尾部，很像 duration 出问题。但很快就排掉了：同一首歌，无论用 on_audio_query 拿的 duration，还是解析 metadata 得到的 duration，显示都正常，且播放从头到尾的总时长也没问题。接下来我开始怀疑是“UI 算法”或“状态更新延迟”，于是做了两件事：1）把 slider 侧的 targetPosition 打印清楚进度条松手时我会算目标毫秒数并调用 seek（lib/features/player/presentation/screens/player_screen.dart），类似这样：onChangeEnd: (value) async { final targetPosition = Duration( milliseconds: (value * duration.inMilliseconds).toInt(), ); debugPrint('[SLIDER] onChangeEnd: sliderValue=$value, ' 'duration=${duration.inMilliseconds}ms, ' 'targetPosition=${targetPosition.inMilliseconds}ms, ' 'actualPosition=${state.position.inMilliseconds}ms'); await audioManager?.seek(targetPosition); }这一步确认：targetPosition 绝对算对了，而且和 UI 上显示的时间一致。2）把 AudioManager.seek 的前后状态也打出来我在 lib/core/services/audio_manager.dart 的 seek 里加了日志，并且等 ProcessingState.ready：@override Future<void> seek(Duration position) async { debugPrint('[SEEK] Request: target=${position.inMilliseconds}ms, ' 'current=${_player.position.inMilliseconds}ms, ' 'duration=${_player.duration?.inMilliseconds}ms'); await _player.seek(position); debugPrint('[SEEK] After seek(): position=${_player.position.inMilliseconds}ms'); await _player.processingStateStream .firstWhere(…

制作一个激活码自动领取插件
type: item
date: 2026-01-08T00:00:00+00:00
description: 本文分享如何为你的 App 实现一个自动化激活码领取系统，支持 WordPress、Typecho 和静态博客（Astro/Hugo）。一、后端 API（Flask）后端负责：生成激活码、验证请求签名、防止重复领取。1.1 激活码模型 from flask_sqlalchemy import SQLAlchemy import random db = SQLAlchemy() class ActivationCode(db.Model): id = db.Column(db.Integer, primary_key=True) code = db.Column(db.String(20), unique=True, nullable=False) device_id = db.Column(db.String(64)) # 绑定的设备 note = db.Column(db.String(200)) # 领取信息 is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) def generate_activation_code(): """生成格式化激活码：XXXX-XXXX-XXXX-XXXX""" chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789' segments = [''.join(random.choices(chars, k=4)) for _ in range(4)] return '-'.join(segments) 1.2 领取接口 @app.route('/api/get-code', methods=['POST']) def claim_code(): data = request.get_json() query = data.get('query', '').strip() # 用户唯一标识 secret = data.get('secret', '') # 密钥 timestamp = data.get('timestamp') # 时间戳 signature = data.get('signature', '') # HMAC 签名 # 1. 验证密钥 if secret != app.config['CLAIM_SECRET']: return jsonify({'success': False, 'message': '密钥错误'}), 401 # 2. 验证时间戳（5分钟内有效，防止重放攻击） if abs(time.time() - int(timestamp)) > 300: return jsonify({'success': False, 'message': '请求已过期'}), 400 # 3. 验证签名 expected_sig = hmac.new( secret.encode(), f"{query}|{timestamp}|{secret}".encode(), hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected_sig): return jsonify({'success': False, 'message': '签名验证失败'}), 401 # 4. 检查是否已领取 existing = ActivationCode.query.filter( ActivationCode.note.like(f"Claimed by: {query} |%") ).first() if existing: return jsonify({'success': True, 'code': existing.code}) # 5. 生成新激活码 code = generate_activation_code() new_code = ActivationCode( code=code, note=f"Claimed by: {query} | {data.get('username')} | {data.get('email')}" ) db.session.add(new_code) db.session.commit() return jsonify({'success': True, 'code': code}) 1.3 安全措施威胁防御措施接口被滥用HMAC-SHA256 签名验证重放攻击时间戳 5 分钟有效期重复领取数据库 note 字段记录用户标识二、WordPress 插件W…

SollinPlayer更新计划（长期更新）
type: item
date: 2026-01-07T00:00:00+00:00
description: 本文章为长期更新，列出后期的更新计划。各位有什么意见反馈也可以在本页面留言。有想对我说的话可以在留言板留言。安卓端：[ ] 添加在线播放音质选择[ ] 同步桌面端备份数据[ ] 添加错误码具体信息[ ] 添加找回密码功能iOS端:[ ] 修复navidrome歌曲只能显示200首[ ] 添加在线功能的开关[x] 无法支持完整CarPlay (没有开发者账号，仅靠自签名无法实现CarPlay，需要 Apple的授权，目前可以通过 CarPlay 的系统"正在播放"应用控制你的音乐)[ ] 同步桌面端备份数据[ ] 导入歌单桌面端：[ ] 修复在播放界面时下一首了歌词不及时跳转到第一句的问题[ ] 添加快捷键，支持快速播放暂停下一曲

抖音视频下载工具 - 可以一键下载无水印的收藏、主页、喜欢的所有视频
type: item
date: 2026-01-05T00:00:00+00:00
description: 起因这两天在整理项目，突然发现去年上半年开源的一个抖音视频下载工具居然还能继续解析使用，那就贴上仓库地址吧。其实这个项目是给我家里人用的，因为他们喜欢把收藏的一些视频下载下来，放到车机上面去，我一个一个下载太复杂了，就诞生了该项目。 ::aru:shutup:: 声明：本项目所有接口均来源于网络分享，仅可开发学习使用，请下载后24H内删除相关所有代码，勿用于违规违法等场景！如有侵权、联系删除！仓库地址：抖音视频下载工具Github链接演示图下面是Readme.md。抖音视频下载工具一个基于PyQt5开发的桌面应用程序，支持下载抖音收藏、用户主页和喜欢的视频。功能特点🎯 支持多种下载模式：下载收藏夹中的视频下载用户主页发布的视频下载用户喜欢的视频📥 批量下载视频，支持断点续传🎨 现代化深色主题界面📊 实时显示下载进度🔄 支持暂停/继续/取消下载📁 可自定义下载目录🎮 全局下载任务控制（全部开始/暂停/取消）💾 支持导出视频链接列表安装说明环境要求Python 3.7+PyQt5PyQtWebEngine依赖安装pip install PyQt5 PyQtWebEngine requests运行程序python app.py使用说明启动程序后，选择要使用的功能：下载收藏视频：输入收藏页面URL下载用户主页视频：输入用户ID或主页URL下载喜欢视频：输入用户ID或喜欢页面URL登录您的抖音账号（如果需要）点击"提取数据到下载中心"按钮开始抓取视频信息在下载中心可以：选择下载目录控制单个视频的下载（开始/暂停/取消）使用全局控制按钮管理所有下载任务查看下载进度和状态打开下载目录查看已下载的视频注意事项请确保您有足够的磁盘空间存储视频下载速度可能受网络条件和抖音服务器限制影响建议在稳定的网络环境下使用请遵守抖音的使用条款和版权规定对于需要用户ID的功能，您可以：直接输入用户ID输入用户主页URL，程序会自动提取用户ID技术特点使用PyQt5构建现代化GUI界面实现多线程下载，避免界面卡顿采用深色主题，提供舒适的视觉体验支持视频信息的本地保存和导出实现断点续传功能，支持下载任务的暂停和继续智能URL解析，自动提取用户ID更新日志v1.1.0新增用户主页视频下载功能新增用户喜欢视频下载功能优化用户界面，添加功能选择按钮支持用户ID和URL两种输入方式v1.0.0实现基本的视频抓取和下载功能添加下载管理功能实现深色主题界面添加批量任务控制功能支持自定义下载目录许可证本项目基于MIT许可证开源。贡献欢迎提交Issue和Pull Request来帮助改进这个项目。

FlaskHTTPStudio - 在线webHTTP 请求测试工具（开源）
type: item
date: 2026-01-04T00:00:00+00:00
description: FlaskHTTPStudio 介绍一个功能强大的 HTTP 请求测试工具，支持 cURL 解析、请求编辑、预设规则、历史记录等功能。基于 Flask + 原生 JavaScript 构建，提供现代化的 Web 界面和完善的安全防护。开源地址FlaskHTTPStudio-Github地址欢迎各位Star、Fork。功能演示cURL 解析从浏览器开发者工具直接粘贴 cURL 命令，自动解析为可编辑的请求表单。请求编辑器支持多种 HTTP 方法、Query 参数、Headers 和 Body 类型的可视化编辑。预设规则系统配置智能预设规则，支持 KV 改值和 JSON Path 深层修改，可按域名和路径过滤作用域。历史记录与收藏自动保存请求历史和响应数据，支持收藏、搜索和一键回放。核心特性1. cURL 解析支持从浏览器开发者工具直接粘贴 cURL 命令（Copy as cURL bash）自动解析 URL、请求方法、Headers、Query 参数、Body 等支持多种 cURL 选项：-X / --request: HTTP 方法-H / --header: 请求头-d / --data / --data-raw / --data-binary: 请求体--data-urlencode: URL 编码数据--json: JSON 数据（自动设置 Content-Type）-F / --form: multipart/form-data-b / --cookie: Cookies-u / --user: Basic 认证-x / --proxy: 代理设置-k / --insecure: 跳过 SSL 验证-L / --location: 跟随重定向-m / --max-time: 超时设置-G / --get: 强制使用 GET 方法-I / --head: HEAD 请求--compressed: 压缩传输2. 请求编辑器基础配置HTTP 方法: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONSURL 编辑: 支持完整 URL 输入Query 参数:可视化 KV 编辑器（实时同步）文本模式编辑（key=value 格式）双向同步功能Headers 管理:可视化 KV 编辑器（实时同步）文本模式编辑（Key: Value 格式）双向同步功能Body 类型支持none: 无请求体json: JSON 格式（支持 JSON Path 预设修改）form-urlencoded: 表单 URL 编码（key=value 每行一个）multipart: 文件上传（支持 key=@file 语法）raw: 原始数据（支持文本或文件上传）3. 预设规则系统强大的参数预设和自动化修改系统，支持：规则类型KV 改值: 修改 Query 参数、Headers 或 Body 中的键值对JSON Path 改值: 使用路径语法修改 JSON Body 中的深层字段支持点号路径：data.realtime支持数组索引：items[0].id规则配置规则名: 唯一标识符目标: Query / Headers / Body(kv)匹配 key/path: 要修改的字段名或 JSON 路径默认替换值: 预设的默认值弹窗输入: 应用时是否弹窗让用户输入作用域过滤:域名匹配（支持包含匹配）路径匹配（支持包含匹配）启用/禁用: 控制规则是否生效自动应用: cURL 解析后自动应用该规则规则管理保存/更新规则编辑现有规则启用/禁用切换单独应用规则批量应用（命中作用域的所有规则）导出/导入 JSON（规则迁移和备份）清空全部规则4. 历史记录与收藏自动保存: 每次发送请求自动保存到历史记录（保留响应数据）收藏功能: 标记重要请求为收藏搜索过滤: 按 URL、Method、时间搜索完整快照: 保存请求的所有参数和配置回放功能:加载历史请求到编辑器直接回放并发送查看保存的响应数据记录管理:删除单条记录清空全部历史最多保存 80 条历史记录5. 高级选项Timeout: 请求超时时间（1-120 秒）Redirects: 是否跟随重定向Verify SSL: SSL 证书验证开关Proxy: HTTP/HTTPS 代理设置Basic Auth: HTTP Basic 认证（user:pass 格式）Cookies: Cookie 字符串（a=1; b=2 格式）6. 响应查看器响应抽屉（移动端友好）状态信息: HTTP 状态码、原因短语、最终 URL性能指标: 请求耗时（毫秒）、响应大小（字节）Headers 展示: 完整响应头Body 展示:源码模式（纯文本/JSON 格式化）HTML 预览模式（仅 text/html 响应）模式…