当前时间: 2026-07-19 13:19:07
分类:办公文件
评论(0)
源码丢了,但新版的小林筛警更懂你源码丢了,只剩一个 82 MB 的 EXE
凌晨一点,派出所值班室的灯还亮着。我对着电脑屏幕上一个 82 MB 的 exe 文件发呆。这是我写的警情筛选软件,不少人都在用,叫"小林筛警"。名字土,胜在诚实——小林写的,筛警情的。从 v1.0 写到 v6.0,它慢慢成了所里的"编外骨干":每月统计警情,导出的表格比我手写的工整十倍。对,自己写的软件,自己没保住源码。备份意识这种东西,跟减肥计划一样,永远停留在"明天就开始"。硬盘翻了个底朝天,只剩下这个打包好的 exe。双击还能跑,活儿还会干,但里面的逻辑、代码、我当年写的注释——全没了。
一、挖了一半,我决定不挖了
这个 exe 是用 PyInstaller 打的包——相当于把 Python 程序和运行环境焊死在一个罐子里。理论上可以拆开,反汇编字节码,把业务逻辑一点点拼回来。说干就干。解包、拆 PYZ、研究 marshal 格式、写解析器……啃了两个晚上,九个模块挖通了三个,18108 行字节码才看到个头。越挖越觉得不对劲:就算全挖出来又怎样?这段时间攒了一堆新需求:统计要做环比同比、大屏要出图、地址匹配要加别名、分类口径要重新对齐……补丁越摞越多,v6.0 的架子早就撑不住了。挖源码是为了修 bug,但修 bug 的功夫,够我重写一个更好的。鼠标停在反汇编窗口上,我把工具一关:算了,不挖了。
二、3场大病,和一根压垮骆驼的前缀
考古的同时,我把这段时间软件闹的三场病也查了个清楚。尽量说人话。病历一:DataFrame object has no attribute str翻译过来:程序想给"警情类别"那一列做文字处理,一伸手抓到了两列。原因是原表同时有"类别"和"警情类别"两列,软件改名逻辑没写严谨,两列撞了名——程序抱着两张表喊"列呢列呢"。病历二:编号 A1990738**** 的传奇人生软件找列只认列名、不认内容。有一批数据里缺了"报警类型"列,它就按关键词兜底乱点鸳鸯——"接警单编号"里有"接警"俩字,好,就你了。于是整列编号被当成警情内容分析了一遍。A1990738****本该安静当个编号,却被软件从头到尾读了一遍,分析了一遍,统计了一遍。它什么都没报,只是长得有点像。有些 Excel 的第一行不是表头,是合并居中的大标题"警情总览"。软件写死了"第一行就是表头",一读,列名全变成"警情总览""Unnamed: 1""Unnamed: 2"……好比简历第一行印着"个人简历"四个大字,软件就把"个""人""简""历"当成了四位候选人。新一批数据,每条地址前面都被加了一个前缀:"XX分局:"。地址匹配一跑,所有警情都归了管分局大院那片的同事。他一个人背走了全所警情。柱状图上一枝独秀,其他同事的柱子矮成了地平线。我是真怕他看见了问我:"小林,我这个月是捅了马蜂窝吗?"
三、不修了,重写:一个软件三个模块
病查到这份上,补丁已经打不住了。我决定:不缝缝补补了,重写。警情筛选:导入 Excel,自动清洗、分类、认辖区,拿不准的留人工复核。大屏生成:一键导出 PNG 图组 + PPT + 动态网页三件套。技术栈简单交代一下。Tkinter是 Python 自带的桌面界面库,样子朴实但扛造,老电脑不挑;SQLite是嵌入式数据库,一个文件就是一个带锁的小账本,v7.0 的记忆功能全靠它;ECharts是网页图表库,大屏动态网页靠它画。筛选的核心逻辑是关键词打分制:候选类别按命中词强弱打分,分高者上岗,同分按优先级裁决。不是看到词就拍板,是让候选类别"竞选上岗"。电诈:严格口径,必须有电话/网络/转账这类非接触要素;只说"被骗了"不算。涉手机:只管实体侵财——手机被盗、被抢、被扒;手机转账归电诈。盗窃电动车电瓶:只认电动车系,摩托车、自行车去盗窃总类待着。双抢(抢劫+抢夺):抢字当头,优先归它;和盗窃同时命中时,双抢赢。涉黄:窄进。按摩光出现不算,得和涉黄词同框——毕竟辖区不少盲人按摩店。涉赌:宽进。打麻将、炸金花直接命中,误伤(如"打麻将噪音扰民")靠人工剔除。外加一条从宽规则:"不见了""丢了"先算盗窃,但全部进人工复核列表,配一键确认。理念一句话:宁可错杀,不可放过——但必须给民警一键反悔的权力。软件负责勤快,人负责拍板。筛选结果界面,拿不准的条目分Tab列队等候人工确认
四、真正花心思的,是八个基层巧思
技术上没什么高精尖。真正花心思的,全是适配基层干活的小设计。1. 表头自动探测
专治病历三。导入 Excel 先扫前 10 行逐行打分:哪行像表头,哪行上岗;合并居中的"警情总览"大标题自动跳过。2. 列内容校验
专治病历二。列名匹配上了不算完,还要抽查内容:编号列必须像编号,时间列必须能解析,地址列必须有中文;一列只服务一个字段,不许多头认领。发现货不对板就弹窗问人,绝不硬猜。3. 污染前缀:双列清洗 + 自动嗅探
"XX分局:"这种前缀,出现位置不稳定——有时在地址列,有时在内容列,所以两列都查,双列清洗。更麻烦的是市局指挥中心的接警员写作风格不稳定:这批有,下批可能就没了。所以软件会自动嗅探——超过 30% 的行带同一前缀,就弹窗问"这是新污染吗?"确认一次,永久记住。4. 九级地址匹配流水线 + 最长名称优先
认地址有九个台阶,从精确命中、别名命中,到规范化、包含、模糊匹配,逐级下落;都认不出,就老实说认不出,进人工列表。v6 还有个隐蔽毛病:按表顺序先中先得,"绿地世纪城"会半路打劫"绿地世纪城A区"。v7.0 改成最长名称优先:名字长的先匹配,A 区归 A 区,谁也别想截胡。5. 记忆功能四件套
民警的每次纠正都记进小账本(就是前面那本 SQLite):地址别名、分类改判、剔除前缀、手填数字。纠正一次,记一辈子;下次同类的坑,软件自己绕着走。越用越聪明——这才算徒弟带出来了。6. 人工复核三 Tab + 一键确认全部
拿不准的条目分三个 Tab 列队:建议确认、分类存疑、完全未识别。没问题就"一键确认全部",有问题单独改判——软件先干完 95% 的活,人只对付 5% 的刺头。7. 专门造了一份"坑点合集"假数据
测试不能拿真警情,我造了一份 56 行的虚构警情表,把所有坑埋了个遍:合并标题行、A2020293654*** 式怪异编号、空单元格、"打麻将扰民"式误伤句……拿假数据把真坑全踩一遍——妖魔鬼怪见过了,真数据来了才不慌。8. 全链路 300+ 条自动化断言守门
每次改动,全部测试重跑:清洗、分类、匹配、统计、大屏,300+ 条断言挨个过。规矩是"零劣化":新功能可以上,老功能一步不许退。
五、门口那块屏,和分局的七天
基层的你一定都懂:分局要的统计周期很奇怪。有时候整月,有时候随心所欲七天——还不一定是自然周。所以 v7.0 的环比、同比,起止日期完全自定义:你圈哪七天,它就算哪七天。历史数据难免缺斤短两,没数据的格子留空标黄,民警手填一次,软件永久记住——带"人工填报"标记,绝不覆盖真实数据。软件可以记事,但不篡改历史。一键导出两张表:全所汇总表(报分局)+ 各民警表(内部考核)。统计分析界面,周期自定义,缺数据的格子标黄待填所门口有块 LCD 宣传屏,只吃图片和视频,不吃 HTML。想喂它会动的网页?它不稀罕。所以大屏模块一键同出三件套:PNG 图组 + 自动播放 PPT + 动态 HTML——不管你们所门口的大屏幕要什么,总有一个适合你。大屏生成界面,三件套一键导出视觉上和上一版"科幻指挥中心深空蓝"彻底切割,改成浅色暖调亲民风。派出所不是飞船驾驶舱,是群众推门就进的地方。民警的辛苦付出:23:00–06:00 夜间警情单列;24 小时分布图里夜间柱子用墨绿色单独标出。夜里的警,是兄弟们熬着出的,应该被看见。大屏总览页,夜间警情单独标注警区的工作对比:干了多少一目了然,环比同比清清楚楚。警区对比页,工作量与环比变化群众的急难愁盼:破案数、追回电动车数、挽损金额、调解台账。这些数字,是给进进出出的群众看的。为民服务页,展示破案、挽损与调解成果合规红线一厘米不让:只展示聚合数字;黄赌归并显示;滚动表只有日期、警区、类别、状态四列。
最后
不用装环境,双击即用。数据文件夹在软件旁边自动创建;换电脑,整个文件夹拷走,就是搬家。比 v6.0 小了 30 MB——可见当年那罐"奶粉"里,不少是结块的。说实话,这次重写,我有个 AI 搭档。我出需求和基层经验——口径怎么定、坑长什么样、大屏给谁看;它干体力活——解包、写解析器、跑测试、改 bug。18108 行字节码要全靠我下班啃,v7.0 得排到退休。它不知道"分局的七天不是自然周",也不认识所门口那块只吃图片的屏。它不知道"按摩"本身不犯法,更不知道为什么夜间警情要单独用墨绿色标出来。
郑重声明:本文所有截图、数据均为虚构演示数据,不涉及任何真实案件、真实警情与真实个人信息。PS:软件可以说是完全重新写了,所以换个图标也很正常!
基本
文件
流程
错误
SQL
调试
- 请求信息 : 2026-07-23 05:47:05 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/864388.html
- 运行时间 : 0.094536s [ 吞吐率:10.58req/s ] 内存消耗:4,850.13kb 文件加载:145
- 缓存信息 : 0 reads,0 writes
- 会话信息 : SESSION_ID=9a42f21874194a6155cc6a62432b5e10
- CONNECT:[ UseTime:0.000603s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
- SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000819s ]
- SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000311s ]
- SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000269s ]
- SHOW FULL COLUMNS FROM `set` [ RunTime:0.000527s ]
- SELECT * FROM `set` [ RunTime:0.000208s ]
- SHOW FULL COLUMNS FROM `article` [ RunTime:0.000554s ]
- SELECT * FROM `article` WHERE `id` = 864388 LIMIT 1 [ RunTime:0.000472s ]
- UPDATE `article` SET `lasttime` = 1784756825 WHERE `id` = 864388 [ RunTime:0.011282s ]
- SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000257s ]
- SELECT * FROM `article` WHERE `id` < 864388 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000446s ]
- SELECT * FROM `article` WHERE `id` > 864388 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000366s ]
- SELECT * FROM `article` WHERE `id` < 864388 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000611s ]
- SELECT * FROM `article` WHERE `id` < 864388 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000808s ]
- SELECT * FROM `article` WHERE `id` < 864388 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002359s ]
0.096199s