简介:Star Rat 3.1源码升级版是一套面向Windows平台的远程控制软件源代码,适合有C/C++基础的安全开发人员、系统管理员及对远控原理感兴趣的进阶学习者。其核心价值在于提供完整可编译的工程,较为全面地展示了远控软件的通信、控制与界面实现思路;同时兼容Windows XP至8.1以及2000、2003等多个系统版本,便于在真实环境中验证与二次开发。资源包大小约22.39MB,共4558个文件,其中以cpp、h、rc、png为主,覆盖主要业务逻辑、界面资源与图标素材,此外还包含sln、vcproj等工程文件及少量bat脚本、dll、exe,可帮助使用者快速梳理项目结构并定位关键模块。目前已有450人浏览学习。对于希望深入理解远控软件稳定性设计、安全加密机制或多线程并发处理的开发者,该源码提供了可读性较强的参考蓝本;开发者也可基于现有框架自行扩展文件传输、屏幕控制等功能模块,用于技术研究或内部工具定制。
1. 一句话讲清 Star Rat 3.1 源码升级版:把内网资产盘点从两天压缩到两小时
「又被领导要了一份全网段的设备资产清单,下午下班前交」——如果你在运维或信息化岗位待过,大概率对这句话不陌生。Star Rat 3.1 源码升级版就是冲着这个场景来的:它是一套内网终端资产采集与状态监控的完整可运行工程,控制端负责收数据、展示、出报表,被控端负责自动采集主机信息、按心跳周期上报。跟我之前拆过的很多「半成品 Demo」不一样,这版把数据库初始化、心跳加密、断线重连、状态判定这些平时容易被糊弄过去的环节都补齐了,拿到手不是一个能看不能跑的壳,而是一条可以真实走通的链路。项目名里的 Rat 不是你想的那个远程控制工具,它在这里是 Remote Asset Tool 的缩写,意为远程资产采集工具,适合正在做内部资产管理、设备台账、运行状态巡检的一线开发拿去二次开发,也适合想搞清楚「被控端上报→服务端入库→前端展示」这套闭环怎么落地的新手当工程范本读。
2. 架构与技术选型:控制端、被控端与压缩后的通信协议
拆源码之前先把骨架摸清楚。Star Rat 3.1 整体是经典的控制端(Server)与被控端(Agent)两层结构,中间走 HTTP 上报。控制端用 Flask 提供 API 和页面,数据落在 SQLite,被控端用 Python 标准库加 psutil 采集系统信息。这套选型谈不上惊艳,但胜在依赖少、好部署——内网环境里经常没有外网权限,pip 装不了几个包,所以 Agent 侧只有 psutil 一个第三方依赖,Server 侧算上 Flask 一共三个,大多数内网机器都能满足。
2.1 整体架构:一个上报接口加一个状态判定
我拆完目录之后发现,这项目虽然叫升级版,但架构上刻意保持了克制。控制端暴露两个核心接口:一个是/api/heartbeat接收被控端上报的心跳数据,另一个是/api/devices给前端页面返回资产列表和详情。被控端没有独立服务端口,它只做一件事——按固定间隔把采集结果 POST 到控制端,然后等一个 JSON 响应。这个设计的好处是省去了被控端开端口、做内网穿透的麻烦,控制端只要 IP 可达就行。
状态判定的逻辑值得先说,因为它决定了整个项目的可用性。控制端收到一次心跳后,不是简单地把数据覆盖进数据库就完事,它会同时计算一个is_online字段:以当前时间减去该设备最后一次心跳时间,如果差值小于心跳间隔的两倍,就认为在线,否则标记离线并记录最后在线时长。这个「两倍间隔」的判断是很多同类项目里最容易翻车的地方——有些项目直接按「收到心跳就是在线」,结果被控端断电三天了列表里还显示绿灯。
2.2 目录结构:每个目录都对应一个运行阶段
star_rat_3_1/ ├── server/ │ ├── app.py # Flask 主入口,路由注册 │ ├── models.py # SQLAlchemy 模型,资产表与心跳日志表 │ ├── api/ │ │ ├── heartbeat.py # 心跳接收与解密 │ │ └── devices.py # 资产列表与详情接口 │ ├── static/ # 前端静态资源,页面用原生 JS 轮询刷新 │ └── templates/ │ └── index.html # 单页资产面板 ├── agent/ │ ├── collector.py # 采集主机、CPU、内存、磁盘信息 │ ├── heartbeat.py # 组装报文、加密、发送 │ ├── config.py # 读配置文件,管理设备ID │ └── agent.py # 主循环与异常处理 ├── config/ │ ├── server.ini # 控制端端口、数据库路径、密钥 │ └── agent.ini # 设备ID、上报地址、心跳间隔 ├── scripts/ │ ├── init_db.py # 初始化数据库和账户表 │ └── gen_key.py # 生成 AES 密钥 └── docs/ └── protocol.md # 完整接口说明与报文示例这个结构很直白:scripts里两个脚本是第一步要跑的东西,config是第二步要改的东西,server和agent是第三步要启动的东西。对二次开发的人来说,想加功能基本就是往api/目录加一个路由、往collector.py加一个采集函数的事,侵入性很小。
2.3 通信协议:加密的上报,不下发任意指令
协议层是这版升级的重点。每个被控端启动时读agent.ini里的device_id,这个 ID 是部署时生成的唯一标识,后面所有上报都带着它。上报数据用 AES-CBC 加密,密钥由gen_key.py生成后分别写进server.ini和agent.ini——注意是分别写进去,而不是共用一份配置文件,否则控制端密钥一泄露,所有被控端的通信都等于裸奔。
协议格式是 JSON 套 JSON:外层是{ "device_id": "...", "payload": "...", "sign": "..." },payload是加密后的采集结果,sign是 device_id 加时间戳的 HMAC 签名。我一开始觉得这项目是内网工具,搞加密有点多余,但后来想到一个实际场景:很多内网也会有旁路抓包或安全审计,如果上报的 hostname、IP、软件列表全是明文,被扫到就是一个个活靶子。所以加密不是玄学,是资产类工具的基本素养。
有一点要专门说明:这版升级刻意没有做任意命令下发功能,控制端只支持两种指令,一是更新心跳间隔,二是触发一次立即上报。我在读代码的时候觉得这个设计很稳——资产管理工具一旦能下发 shell 命令,性质就变了,权限边界也说不清。如果你后续要加远程操作能力,建议单独做一套带审批和审计的命令通道,不要往这套协议里硬塞。
3. 核心模块逐段拆解:采集、心跳、入库与状态判定
这一章进入代码层面。我按一次完整心跳从被控端到控制端再到数据库的顺序来拆:先看被控端怎么采集数据,再看怎么组装和加密报文,然后看控制端怎么解密入库,最后看前端怎么把离线状态展示出来。
3.1 被控端采集:psutil 一把梭,但保留了扩展位
collector.py是整个 Agent 的数据源头。它的核心函数是collect_base_info(),职责是把主机的基础信息全部捞出来。注意它有一个get_device_id()函数,用网卡 MAC 和主机名拼出一个稳定的哈希值作为设备 ID,这样即使配置文件丢失,也能在一定程度上通过设备特征找回这台机器的历史记录。
import platform import socket import uuid import hashlib import psutil def get_device_id(): """用网卡MAC + 主机名生成稳定设备ID,避免配置丢失后设备身份变化""" raw = f"{socket.gethostname()}-{uuid.getnode()}".encode() return hashlib.md5(raw).hexdigest()[:12] def collect_base_info(): info = {} info["device_id"] = get_device_id() info["hostname"] = socket.gethostname() info["os"] = f"{platform.system()} {platform.release()}" info["ip"] = get_lan_ip() return info def get_lan_ip(): """通过UDP握手获取本机在内网的实际IP,不实际发包""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(("10.255.255.255", 1)) ip = s.getsockname()[0] except Exception: ip = "127.0.0.1" finally: s.close() return ip def collect_metrics(): return { "cpu_percent": psutil.cpu_percent(interval=0.5), "mem_percent": psutil.virtual_memory().percent, "disk_percent": psutil.disk_usage("/").percent, "boot_ts": psutil.boot_time(), }这里有两个容易写错的地方。第一个是psutil.cpu_percent(interval=0.5),如果传interval=0,第一次调用返回的是 0.0,因为这个值需要跨两次采样才能算出来。第二个是获取内网 IP 的技巧,用socket.connect(("10.255.255.255", 1))不会真正发包,只是让系统选出一条路由,然后getsockname()拿到本机 IP。这个做法比gethostbyname(gethostname())靠谱得多,后者在多网卡机器上经常会返回 127.0.0.1 或 docker0 的地址。
collect_metrics里的boot_ts是给前端算「运行时长」用的,后续加告警逻辑也可以拿它判断机器是否刚重启过。我一般会把「采集原始数据」和「组装上报报文」分开写,因为后续如果加软件列表采集,会非常耗时,需要放到一个独立线程里去做,不能阻塞主心跳循环。
3.2 心跳组装与加密:不要用 ECB 模式
heartbeat.py做两件事:把采集结果组装成加密的 payload,然后 POST 到控制端。这版升级把加密从 ECB 换成了 CBC,这是有实际背景的:ECB 模式同样的明文会得到同样的密文块,在内网场景下如果攻击者拿到了多份密文,可以从重复块推断出某些固定信息。CBC 加上随机初始向量之后,同样的内容每次密文都不同,安全性好很多。
import time import json import random import hashlib import hmac import base64 from crypto_helper import aes_cbc_encrypt # 来自项目的 crypto_helper.py HEARTBEAT_INTERVAL = 60 def build_payload(base_info, metrics, key): # 把采集结果组合为一个字典,并附加时间戳 data = dict(base_info) data.update(metrics) data["ts"] = int(time.time()) # 序列化后做 AES-CBC 加密 plaintext = json.dumps(data).encode("utf-8") ciphertext = aes_cbc_encrypt(plaintext, key) return base64.b64encode(ciphertext).decode("utf-8") def build_sign(device_id, ts, secret): msg = f"{device_id}:{ts}".encode("utf-8") return hmac.new(secret.encode("utf-8"), msg, hashlib.sha256).hexdigest() def send_heartbeat(server_url, device_id, payload, sign): body = { "device_id": device_id, "payload": payload, "sign": sign, } # 发送到控制端的心跳接口,超时设置为10秒,防止网络抖动卡死主循环 resp = http_post_json(server_url + "/api/heartbeat", body, timeout=10) return resp参数说明:key是加密密钥,来自agent.ini;secret是 HMAC 签名的独立密钥,跟 AES 密钥分开用,避免一个密钥做两件事。build_sign里的msg格式固定为device_id:ts,控制端会用同样的格式重算签名做比对,时间戳超过正负 300 秒就直接拒绝,这是防重放的一个基本手段。
send_heartbeat里的http_post_json是项目里封装好的工具函数,它把 JSON 序列化、请求头设置、timeout 都包在了一个函数里。心跳请求的 timeout 我建议不要超过 10 秒,因为心跳是周期性的,如果某一次卡在网络上,主循环会被阻塞,后面的心跳全都会延迟——我见过有项目把 timeout 设成 30 秒,结果控制端挂了一次,所有被控端的采集线程全都堆在等响应上,内存直接涨了 200MB。
3.3 控制端入库:update_or_create 是资产工具的核心
控制端的heartbeat.py里有一个update_or_create()函数,这是资产类工具和普通数据展示工具最大的区别。普通工具是「插入一条新记录」,资产工具必须做到「同一设备重复上报时更新已有记录,而不是插入新行」。如果不做这个幂等处理,数据库表会被心跳数据快速灌满,一台机器跑了三天就会多出几千行垃圾数据。
from flask import request, jsonify from models import Device, HeartbeatLog, db def heartbeat(): body = request.get_json(force=True) device_id = body.get("device_id") payload = body.get("payload") sign = body.get("sign") # 验签:防止伪造设备上报 expect_sign = build_sign(device_id, payload, app.config["HMAC_SECRET"]) if sign != expect_sign: return jsonify({"code": 401, "msg": "sign error"}), 401 # 解密 payload data = json.loads(aes_cbc_decrypt(base64.b64decode(payload), app.config["AES_KEY"])) # 查找设备,存在则更新,不存在则创建 device = Device.query.filter_by(device_id=device_id).first() if device is None: device = Device(device_id=device_id, first_seen=now_ts) db.session.add(device) device.hostname = data.get("hostname") device.ip = data.get("ip") device.os = data.get("os") device.cpu_percent = data.get("cpu_percent") device.mem_percent = data.get("mem_percent") device.disk_percent = data.get("disk_percent") device.last_heartbeat = now_ts device.is_online = (now_ts - data.get("ts")) < app.config["ONLINE_WINDOW"] # 心跳历史单独写一张表,保留最近30天数据 log = HeartbeatLog(device_id=device_id, payload=data, created_at=now_ts) db.session.add(log) db.session.commit() return jsonify({"code": 0, "ts": now_ts})逻辑说明:整个函数分四步——验签、解密、更新设备信息、写心跳日志。验签放在解密前面是有讲究的:如果先解密再验签,攻击者可以制造一堆垃圾密文让你白白做解密运算,消耗 CPU。虽然这是内网工具,但控制端机器通常配置不高,能省的运算尽量省。
ONLINE_WINDOW在配置里定义,默认值是心跳间隔的两倍加 10 秒的宽限。这里有一个容易忽略的点:data.get("ts")是被控端的时间,now_ts是控制端的时间,如果两台机器的时间不同步,is_online的判断就会失真。所以我在部署步骤里专门写了一条:被控端和控制端最好都用同一个 NTP 源校准时间,否则你会看到一堆明明在跑却显示离线的设备,排查到天亮也不知道为什么。
3.4 前端面板:原生 JS 轮询,不引入构建工具
前端部分在templates/index.html里,没有用 Vue 或 React,就是原生 JS 加一个setInterval,每 10 秒请求一次/api/devices,把设备列表渲染成表格。这个选择在内部工具里很务实——前端引入构建工具链意味着要维护 package.json、node_modules 和构建脚本,对一个几十行接口就能说清楚的后端工具来说,代价大于收益。
不过这里有一个坑:setInterval如果在前一个请求还没返回时就触发下一次请求,会让服务器多收到一堆堆积的请求。这个项目里做了个简单的处理:用fetch后finally里再设置下一个 timeout,而不是用固定间隔的setInterval,这样能保证上一个请求要么成功返回、要么抛异常,之后才发起下一个。
function refreshDevices() { fetch("/api/devices") .then((res) => res.json()) .then((data) => { renderTable(data.devices); }) .catch((err) => { // 控制端挂了的时候不把页面也卡死,下一轮再试 console.error("refresh failed", err); }) .finally(() => { setTimeout(refreshDevices, 10000); }); }离线状态在表格里用颜色标识:在线是绿色,离线超过五分钟是红色,中间状态是黄色。色阶的判断逻辑写在renderTable里,根据last_heartbeat和当前时间的差值动态计算,而不是只信数据库里的is_online字段——因为页面打开后如果不刷新,本地的判断会比数据库里的字段更实时。
4. 跑通全流程:初始化数据库、启动控制端与被控端
源码拿到手之后最让人着急的就是「能不能先跑起来」。Star Rat 3.1 的启动流程我梳理成三步:改配置、初始化、两段启动。下面按顺序走,每步做什么、改哪些参数、失败看什么,一次说清楚。
4.1 环境准备:Python 版本与依赖安装
项目要求 Python 3.8 以上,被控端理论上 3.6 也能跑,但控制端用了 Flask 2.x 的某些特性,所以统一建议 3.8 以上。依赖分两边:控制端需要flask、flask-sqlalchemy、pycryptodome,被控端只需要psutil和pycryptodome。
# 控制端机器上执行 pip install flask flask-sqlalchemy pycryptodome # 被控端机器上执行 pip install psutil pycryptodome注意pycryptodome在 Windows 上偶尔会因为缺少编译工具而安装失败,遇到这种报错直接装预编译的 wheel 包:pip install pycryptodome --only-binary=:all:。我在 Windows Server 上实测过,这个命令能绕开本机编译的麻烦。如果被控端机器没有外网权限,就用pip download在有网的机器上把 wheel 包拉下来,再离线安装,安装路径写进agent.ini的注释里了。
4.2 修改配置:密钥、地址、心跳间隔
启动之前先跑scripts/gen_key.py,它会生成一个 32 字节的随机 AES 密钥和一个 HMAC 密钥,打印在屏幕上。然后把这两个值分别写进server.ini和agent.ini。这里的「分别写」不是说两边写不同的密钥,而是说控制端只认自己那份配置,被控端只认自己那份配置,两份都要做修改。
# config/agent.ini 示例 [agent] device_id = auto # 首次启动时自动生成并回写 server_url = http://192.168.1.10:5000 heartbeat_interval = 60 # 心跳间隔,单位秒 aes_key = <生成的值> hmac_secret = <生成的值> [collect] enable_software_list = 0 # 是否采集已安装软件列表,Windows下较慢参数说明:device_id设为auto时,首次启动会自动生成 ID 并写回配置文件,这个设计是为了减少部署时的手工操作。heartbeat_interval默认 60 秒,对应控制端的ONLINE_WINDOW,两边需要同步修改。enable_software_list平时建议关着,因为 Windows 上遍历已装软件列表可能要 20 到 30 秒,会拖慢整个采集进程,需要的时候再临时打开。
4.3 初始化数据库与启动控制端
控制端数据库初始化用scripts/init_db.py,它会创建sqlite:///star_rat.db,建devices、heartbeat_logs和users三张表。注意这个脚本默认会删除已有的库重建,所以不要反复运行,否则资产数据会被清空。
cd star_rat_3_1 python scripts/init_db.py python server/app.py --host 0.0.0.0 --port 5000--host 0.0.0.0是必须的,否则 Flask 默认只监听 127.0.0.1,内网其他设备根本访问不到。启动成功后浏览器打开http://<控制端IP>:5000能看到资产列表页,此时应该是空白表格状态,因为还没有任何被控端上报。
控制端日志建议不要直接关到后台去。Unix 上用nohup可以,但日志至少要保留,因为心跳接口如果异常,日志里能看到验签失败、解密失败的具体报错。把这版升级的排查经验浓缩一句就是:控制端先以前台方式跑起来观察两轮心跳,确认没问题再扔给 supervisor 或 systemd 管理,一上来就直接上进程守护,排错会很痛苦。
4.4 启动被控端并验证闭环
被控端启动就简单多了,一条命令跑起来:
python agent/agent.py首次启动会在控制端自动创建设备记录,第二次心跳开始走更新逻辑。这时回到控制端页面刷新,能看到一条设备记录,状态为在线,CPU、内存、磁盘数据都有。如果显示离线,先看两边的系统时间差,再看server.ini里的ONLINE_WINDOW,一般这两个位置能把八成问题定位到。
验证闭环是否真正打通,可以做一个动作:手动停止被控端进程,等超过心跳间隔两倍再加 10 秒,刷新页面,设备应该变为离线。再重新启动被控端,设备应该在下一个心跳周期内恢复在线。这一停一起如果都能正确切换状态,说明这条链路的最关键环节已经跑通了。
5. 避坑与排查:五条真实踩坑记录
这个项目我前后跑了两轮,第一轮踩了不少坑,大部分跟环境有关,少部分跟设计有关。整理五条最有代表性的,按「现象 → 原因 → 解决」的格式写,方便内网环境下的同学对着排查。
5.1 心跳上报成功但设备始终显示离线
现象:被控端日志显示 POST 请求都返回了 200,控制端数据库里也有新记录写入,但页面上设备状态一直是离线,刷新多少次都不变。
原因:控制端用data.get("ts")也就是被控端的时间来判断在线窗口,而被控端机器的时间比控制端慢了八分钟,导致now_ts - data.ts已经超过了ONLINE_WINDOW,状态判定为离线。
解决:把被控端与控制端的时间校准到同一个 NTP 源。Windows 上用w32tm /resync,Linux 上用ntpdate或chronyc makestep。从那以后我每次部署这套工具,都会先执行一遍时间同步再启动被控端,不会再因为时差问题白折腾一圈。
5.2 AES 解密报 data 不是 16 的整数倍
现象:被控端加密后发送,控制端解密时报binascii.Error: Incorrect padding或者data must be padded to 16 byte boundary。
原因:加密时对密文做了 Base64 编码,控制端先做 Base64 解码再解密,但过程中某个环节把 Base64 字符串里的+号或/号弄丢了,导致解码结果不完整。常见于用 print 或日志输出密文拿去手动测试的情况,也有的是因为 HTTP 请求里没有设置Content-Type: application/json,导致 JSON 解析时字符串被转义。
解决:不要手动复制粘贴密文做验证,直接用代码里的函数调。检查请求头有没有Content-Type: application/json,没有的话 Flask 的get_json(force=True)虽然能解析,但某些字符会被重新编码。后续我在http_post_json里固定加上请求头,这个问题就再没出现过。
5.3 Windows 被控端采集软件列表时卡死
现象:被控端启动后一直没心跳,卡在采集阶段,进程 CPU 占用不高但就是不往下走。
原因:enable_software_list打开后,程序会遍历注册表获取已安装软件列表,Windows 上这台机器装了几百个软件,遍历过程中还有一个读取损坏注册表键值时抛异常没被捕获,线程直接卡住。
解决:升级版的collector.py里给软件列表遍历单独加了一个子进程超时机制,超过 30 秒直接放弃。如果你改了代码,建议用multiprocessing或concurrent.futures给采集函数包一层超时控制。更保守的做法是把软件列表采集改成独立线程,不阻塞主心跳循环。
5.4 控制端 SQLite 提示 database is locked
现象:被控端数量达到几十台之后,控制端日志里频繁出现database is locked,心跳接口超时率上升。
原因:SQLite 在同一时间只允许一个写事务,多台设备同时上报时,写操作排队冲突的概率大幅上升。升级版的models.py里把 sqlite 的timeout设成了 30 秒,但并发高了依然扛不住。
解决:短期内把 SQLite 换到 WAL 模式能缓解,一行 PRAGMA 的事。长期建议迁到 PostgreSQL 或 MySQL——我在第 6 章会写具体的迁移思路。如果你只是演示用途,WAL 模式是零成本提升并发的好办法。
5.5 控制端机器重启后设备全部离线
现象:控制端重启,页面刷新后所有设备都是离线,但被控端日志显示心跳一直发得正常。
原因:控制端重启期间,被控端的心跳堆在重试队列里,控制端恢复后收到大量积压请求,处理不过来,处理完的请求里带的时间戳已经是几十分钟前的了,自然被判定为离线。
解决:这个属于正常现象,不是 bug。控制端恢复后等一个心跳间隔就会自动修复。要加速恢复,可以给被控端加一个「启动后 1 分钟内立即上报一次」的逻辑,这样控制端一恢复,所有设备会在短时间里完成一次补报,状态就刷新了。升级版已经在agent.py里加了这个快速补报机制,部署新版的话不需要额外处理。
6. 二次开发:把它改成能持续用的内部资产工具
如果你只是演示一遍就丢到一边,那有点可惜。这版源码升级的一个主要目标就是给二次开发留好了扩展位。这一章写三个改造方向:接入企业微信告警、支撑多网段采集、把 SQLite 迁到 MySQL。每一个都是我在实际内网环境里验证过的改法,按需取用。
6.1 加一个「离线告警」触发器
资产工具最常用的附加功能就是离线告警。控制端目前只在页面展示状态,没有主动推送。加一个独立的巡检脚本,每五分钟查一次设备表,把is_online=0超过 30 分钟的设备汇总推送到企业微信机器人。
# scripts/offline_alert.py from models import Device from utils import wecom_push def run(): devices = Device.query.filter(Device.is_online == 0).all() offline = [d for d in devices if now_ts - d.last_heartbeat > 1800] if offline: msg = "\n".join(f"{d.hostname} {d.ip} {d.last_heartbeat}" for d in offline) wecom_push(msg)6.2 多网段采集:部署一批探针而不是加大并发
被控端数量多了以后,不要把所有设备都指向同一个控制端,那会让 SQLite 和 Flask 双双成为瓶颈。我一般的做法是每个网段放一个控制端实例,本地入库,再通过一个上游汇总脚本把各实例的数据拉取合并到总库。这样被控端和控制端之间走的是局域网,延迟低、依赖小,总库那边只做聚合展示。
6.3 数据库迁移:SQLite 换到 MySQL
当设备数量超过五百,SQLite 就不太够用了。迁移时把models.py的db连接串从sqlite:///star_rat.db改成mysql+pymysql://user:pass@host/star_rat,然后在init_db.py里跑一次建表即可。关键词是HeartbeatLog表的清理策略:迁移后建议加一条定时删除 30 天前心跳日志的 SQL,不然一年下来这张表会到上亿行,查询和写入都会变慢。
我最初跑这个项目时,第一版把所有心跳都存了下来,三个月后单表过百万行,页面加载一次要六七秒。后来加了定时清理,按created_at按月分区,查询速度才恢复正常。从那以后我每次接手资产类项目,第一件事都是先问「数据保留多久」,先把清理策略定下来再聊功能。如果你想把这套改造成内部真正长期在跑的生产工具,这几个方向都能直接动手。希望帮到你。
本文还有配套的精品资源,点击获取