news 2026/9/10 20:17:27

假期作业三:极简技术栈实现情绪记账、自动备份与实时数据看板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
假期作业三:极简技术栈实现情绪记账、自动备份与实时数据看板

“假期作业三”,这个系列我做了好几期,不少朋友从第一期追到现在。简单说,第三期延续了我之前定下的规矩:假期里不搞宏大叙事,就憋三个拿得出手的小项目,从零到一,做完收工。这次的三样东西分别是一个带情绪分析的极简记账本、一个局域网内自动备份关键文件夹的小工具,还有一块把自己博客访问数据做成实时可视化的电子看板。没有用到任何重型框架,全部是能在一个月内消化完的技术栈。这个系列的核心价值不是秀技术,而是让每个项目都能在三天到一周内完整落地,陪你度过一段不焦虑的、有产出的假期时光。

如果你正在纠结假期该做点什么练手,或者想找几个既能写进简历、又能解决实际小问题的项目,这期的内容应该能给你一些灵感。照例,每个项目我都会把设计思路、核心代码、参数取舍和踩过的坑展开讲清楚,方便你照着做或者改成自己的版本。

1. 三个项目怎么选出来的

1.1 选题逻辑:不是越大越好,而是要能闭环

假期做项目最忌讳的就是开局一张图、后面全靠编。我在定第三期选题时给自己列了几个硬性标准:第一,每个项目的需求必须来自真实生活场景,不能是纯学术的概念演示;第二,单个项目的开发周期控制在三到七天,超过一周就要砍功能;第三,所有项目必须在本地环境能跑通,不依赖花钱的云服务。

根据这个标准,我淘汰了好几个看似炫酷但落地成本很高的想法,比如做一个基于深度学习的图像分类器、做一个多人在线的协同白板。原因是这些项目要么需要大量标注数据,要么需要部署服务器、处理实时通信,短期冲刺容易烂尾。最后留下来的这三个项目,共同点是都能在个人电脑上独立完成闭环:记账本解决月初看账单一脸懵的问题,备份工具解决重要文件误删后找不到的问题,数据看板解决博客访问数据只能看数字、看不出规律的问题。

1.2 技术选型思路:用自己最顺手的东西

这套项目的技术栈整体上是“旧瓶装新酒”:核心语言选了Python和JavaScript,数据库用了SQLite和浏览器自带的localStorage,可视化用了ECharts和轻量的socket服务。没有引入诸如分布式、微服务这类概念,原因很简单:项目一旦引入和核心需求匹配不上的技术,学习成本和排错成本就会同步升高,假期时间根本不够用。

比如记账本项目,为什么要用SQLite而不是MySQL?因为SQLite是文件型数据库,零配置文件,可以随项目一键打包,适合这种单机应用。为什么不用Django或Flask的完整脚手架?因为一个单页应用加上几个API接口就能满足需求,没必要为了一个杯子买一整套茶具。这些取舍让三个项目都保持了很高的完成度,也便于读者用较短的代码量看懂每个功能背后的逻辑。

1.3 项目一:情绪记账本——记账不只是记数字

记账的难点从来不是“记录”本身,而是“坚持记录”。市面上绝大多数记账App功能过重,光是为每笔消费选分类就要点好几次屏幕,时间一长就失去了记录的欲望。我做这个记账本的核心思路是:让“记一笔”的操作在十秒内完成,并且自动给每笔支出打上情绪标签,等月底回看时,你能清楚地看到哪段时间花钱花得最开心、哪段时间是在报复性消费。

技术上,前端就是一个单纯的HTML页面,所有数据保存在浏览器本地,通过localStorage持久化。后端不是重点,重点在于数据结构和交互设计:每一笔记录必须包含金额、分类、日期、备注和情绪(开心、一般、烦躁、焦虑)五个字段,这五个字段构成了后续所有统计分析的原始素材。

1.4 项目二:关键文件夹备份工具——向冷备份说再见

第二件事源于一次真实事故:我有一次误删了一个存满旅行照片的文件夹,等到发现时回收站已经被清空了,还好之前手动拷贝过一次到移动硬盘,不然几年的记忆就全没了。手动备份的问题是,你永远不知道下一次备份该在什么时候,“经常备份”这个美好的愿望执行几次之后就变成了“偶尔备份”,最后变成“再也不备份”。

这次我干脆写了一个自动备份工具:设定好源文件夹和目标文件夹之后,工具启动时会自动对比两边文件的差异,把新增和修改过的文件增量拷贝过去,同时在目标文件夹里保留最近十个历史版本。这样一来,备份动作不再依赖我的记忆,只要记得偶尔运行一次程序,或者干脆把它加到开机启动项里,它就会在后台默默完成所有工作。

1.5 项目三:博客实时访问看板——用数据看见内容的长尾价值

写博客的人大多想知道自己写的内容到底有没有人看、大家是通过什么渠道来的、哪一类内容更受欢迎。第三期收尾项目就是把博客的访问日志做成一个实时刷新的可视化看板,让我在写新文章的时候,瞄一眼屏幕就能感受到旧内容的持续吸引力。

这个项目用到的方法是:Nginx访问日志会产生一行行固定格式的文本,我用Python脚本定期读取新增的行,解析出IP、访问时间、请求路径、User-Agent等字段,写入SQLite数据库,然后通过一个轻量的WebSocket服务把数据变化推送到浏览器端,浏览器端用ECharts渲染出实时曲线、地域分布和热门文章排行。整个过程听起来复杂,但真正实现起来,核心代码大约只有三百行。

2. 每个项目的实现要点和难点

2.1 情绪记账本的字段设计为什么很关键

很多初学者写记账本时,数据库表结构往往只设计金额和时间两个字段,觉得够用了。实际上,没有分类和备注的记账本基本上是一堆没有意义的数据:你能看到哪天花了两百,但完全想不起来花在了哪里。所以我的核心表结构一开始就确定下来,不给自己留返工的空间。

建表语句非常简单,但字段的含义需要仔细斟酌。我把“分类”设计成可自定义的文本字段而非预置的下拉选项,是因为每个人关心的消费类别不一样,有人对餐饮敏感,有人对游戏支出敏感,硬编码的分类列表会逼着用户在“其他”这个垃圾筐里塞进大量真实信息。情绪字段同样重要,它不参与金额计算,却能为月底的复盘提供有趣的视角。

2.2 localstorage 与 SQLite 的取舍

记账本项目的前端存储层用的是localStorage,因为它足够简单,且浏览器会自动管理数据持久化。不过这个方案有一个明显的边界:localStorage的数据容量一般在5MB到10MB之间,如果每天记二十笔账,每一笔按两百字节算,一年也不到2MB,所以完全够用。但如果你打算在这个项目上继续扩展数据库,比如加多人协同,那就必须换到后端数据库了。

我在项目里额外做了一层数据导入导出功能,可以把localStorage里的所有记录导出成一个JSON文件,也支持导入。这个功能听起来不起眼,但在换电脑、清缓存的时候能救你一命,是防止数据丢失的最后一道保险。

2.3 文件夹对比算法与增量备份的实现逻辑

备份工具的核心挑战在于:如何快速判断哪些文件需要备份,而不是每次都把整个文件夹无脑拷贝一遍。最简单粗暴的做法是遍历源文件夹中的所有文件,然后逐个判断目标文件夹里是否存在同名且同大小的文件,如果不存在就拷贝。这个方案在文件数量较少时运行得很快,但当文件夹里有两万张照片时,遍历一次可能要几十秒,体验很差。

我采用的优化思路是引入一个“文件清单快照”文件。这个快照里记录了上一次备份后所有文件的相对路径、大小和修改时间。每次运行备份工具时,先读取快照,然后对比当前文件系统的状态,只在文件列表发生变化的位置执行拷贝操作。对于新增文件直接拷贝,对于修改过的文件覆盖旧版本,对于已经删除的文件则在快照中同步移除。处理完之后更新快照,整个过程记录日志,方便回溯。

2.4 增量备份的版本管理与轮转策略

备份工具除了增量拷贝之外,还必须解决“误备份”的问题。比如,如果你不小心删除了一个重要文件,然后运行了备份工具,快照就会把这个文件的删除状态记录下来,这会导致旧版本在下次备份时被清理掉。为了避免这种情况,我设计了多版本轮转机制:每次备份之前,先把目标文件夹里的现有内容压缩成一个带时间戳的版本包,保留最近十个版本包后自动删除最旧的。这样即使你在当天误删了文件,只要版本包里还有昨天甚至前天的记录,就能翻出来恢复。

版本轮转的参数需要根据磁盘空间和备份频率来调整。我个人的默认策略是:备份频率设为每六小时一次(配合定时任务),版本保留数设为十个。换算下来,可以回溯大约两天半以内的任何状态,同时磁盘占用一般不会超过源文件夹总大小的两倍。如果你的文件夹里全是视频这类大文件,建议把保留数降到3到5个,否则版本包的体积会非常可观。

2.5 实时看板的数据链路设计

看板项目的整个数据链路可以拆成四个环节:日志采集、数据存储、消息推送和前端渲染。日志采集这步,常规思路是写一个死循环定期读文件的新增行,但这样做有一个问题:如果Nginx由于某种原因很久不写日志,脚本就会一直空转,浪费系统资源。我的改进方案是借助生成器的惰性求值特性,每当有新行可读时才产生数据,没有新行时挂起等待,这样脚本的资源占用几乎可以忽略不计。

数据存储端我仍然选择了SQLite。它的并发读写能力虽然不如真正的数据库,但对于个人博客这种每秒最多几个请求的量级来说绰绰有余。消息推送环节,我用WebSocket替代了传统的轮询,因为轮询每隔几秒请求一次API,不仅浪费带宽,还会给日志数据库带来不必要的压力。前端渲染选择了ECharts的折线图、柱状图和地图,配合WebSocket推送的数据,可以实现秒级刷新的效果。

2.6 ECharts 动态数据更新的一个关键细节

使用ECharts做动态数据展示时,新手最容易踩的坑是不理解setOption的合并机制。如果每次数据更新都调用一次完整的setOption并且传入了全新的series,图表实例会重新创建series,导致颜色闪烁、动画抖动。正确做法是初始化图表之后,把系列数据通过更新对应索引的series.data来替换,并且设置notMerge参数为false,让ECharts只更新数据而不重建系列,这样才能保持视觉上的连贯性。

另外一个细节是时间轴的处理。实时数据的时间字段如果精确到秒,折线图上的点会非常密集,而且由于网络波动,偶尔会出现数据点缺失。我建议在前端展示层做一次聚合:把最近一小时的数据按分钟聚合,最近二十四小时的数据按半小时聚合,这样曲线既平滑又不牺牲细节。

3. 实际操作全过程记录

3.1 记账本项目从页面到逻辑的完整实现

这个项目我最终做成了纯前端单页面,文件结构非常简单:一个HTML文件、一个CSS文件、一个JavaScript文件。所有页面逻辑分成录入、列表、统计三个区域,页面底部有一个导出按钮和一个导入按钮。界面的设计原则是清爽,主色调用莫兰迪绿,字体全部用系统默认字体,不额外加载字体文件,这样离线打开也能正常使用。

录入区的交互流程是这样的:用户输入金额后,点击分类按钮选择一个分类,再点五个情绪图标中的一个,最后可选填备注。每次提交时,程序会生成一个唯一ID,用时间戳加随机数拼接而成,然后写入localStorage的records数组中。为了让用户产生“记录一下很快”的正反馈,我加入了一个微动效:金额数字会快速跳动一下并变绿,提示录入成功。

统计区的逻辑是重头戏。我用一个combine函数把所有记录按日期聚合,生成每日支出数组,然后在Canvas上画出一条趋势线。趋势线能直观地显示支出高峰出现在哪天,情绪标签则用饼图展示不同情绪下花费金额的占比。比较有趣的是,我把“烦躁”这种负面情绪与金额的分布做了关联,当烦躁情绪下的消费占比超过总消费的20%时,页面会出现一个温和的提示语:“这段时间似乎有些情绪化消费,可以试着关注一下自己的状态。”

3.2 备份工具的命令行参数和配置项

备份工具是一个Python命令行程序,调用方式很简单,在终端里执行python backup.py /path/to/source /path/to/target即可。为了让不同场景的用户能灵活调整行为,我提供了几个可选参数:--interval用来设置备份间隔(按小时计),--versions用来设置保留版本数,--dry-run则会在不实际拷贝文件的情况下,打印出这次备份会执行哪些操作,方便先在测试目录里跑一遍验证。

核心的增量对比函数大致逻辑是:先读取目标目录下的snapshot.json文件,把里面的文件列表加载成一个字典;再遍历源目录里的所有文件,计算每个文件的相对路径、大小和修改时间;对比新旧数据后,把差异类型分为新增、修改、删除、无变化四类。对于新增和修改文件,用shutil.copy2拷贝并保留元信息;对于删除文件,只在记录中标记而不真正删除目标文件,因为目标文件可能存在于某个旧版本包中,贸然删除会导致回溯失败。

3.3 用定时任务让备份变成“无需思考”的动作

有人可能觉得,既然工具已经写了,每天手动跑一次也挺好。但我个人的体会是:凡是需要靠人去记住的运维动作,最终一定会被遗忘。所以我建议把备份工具注册到系统的定时任务里。在macOS或Linux上,可以用crontab设置一行:0 */6 * * *,意思是每隔六小时整点运行一次;在Windows上,则可以用“任务计划程序”新建一个触发器,设定触发条件为“每天”,重复间隔设为6小时。

注册完定时任务后,我习惯再做一个验证动作:手动运行一次backup.py --dry-run,检查输出日志是否正常,然后故意改动一个测试文件,等下一个周期触发时去看这个修改是否被正确备份。这一步虽然简单,但能避免“以为在备份、实际没跑通”的乌龙。我的经验是,如果你做完工具后连一次真实的恢复演练都没做过,那这个备份工具本质上跟没做差不多。

3.4 看板项目:日志采集、WebSocket推送和前端渲染

看板项目的实现,我按数据流动方向拆分成了三个独立模块,方便分别调试。第一个模块是log_watcher.py,负责监听Nginx日志文件。它用seek定位到文件末尾,然后进入循环,每次尝试读取新行并解析。解析用的是一个正则表达式,把日志行中的IP、时间、请求方法、请求路径、状态码、响应字节数和User-Agent提取成结构化字段,再插入SQLite的access_log表。

第二个模块是websocket_server.py,启动后同时承担两个职责:一是持续消费数据库中的最新记录,二是把变化推送给订阅了WebSocket连接的前端页面。为了让实时性足够高,我没用“读数据库全表”的方式,而是在access_log表中维护一个自增ID字段,每次推送时只读取ID大于上次推送最大值的记录,这样即使日志量很大,推送的负载也很小。

第三个模块是前端看板页面。页面布局是典型的监控大屏风格:顶部是访客总数、今日浏览量、在线人数三个指标卡片,中间是实时流量曲线,左侧是热门文章Top10,右侧是最近来访地域分布。ECharts初始化时只创建一次实例,后续接收WebSocket消息时通过myChart.setOption更新对应series,保证图表流畅。

3.5 部署时遇到的端口冲突与跨域问题

看板项目在本地调试时一切正常,但部署到服务器上时遇到了两个典型问题。第一个是端口冲突:WebSocket服务默认挂在9000端口,而服务器上已经有一个旧服务占用了这个端口,导致启动直接报错。解决办法很简单,把WebSocket端口改成了9001,并更新前端的连接地址。这里要特别提醒,如果前端页面是HTTPS环境,WebSocket连接必须用wss协议,否则会被浏览器拦截。

第二个问题是跨域。如果看板页面部署在域名A下,但是WebSocket服务在域名B的9001端口上,浏览器会认为这是跨域请求并阻止连接。解决办法是在WebSocket服务端设置允许跨域(CORS)头。Python标准库里的websockets库本身不直接提供CORS配置,我在服务端加了自定义的响应头,把Access-Control-Allow-Origin设为具体的前端域名,避免用*这种全放开的方式,减少安全风险。

4. 常见问题与排查技巧实录

4.1 记账本的数据丢失和浏览器缓存策略

记账本项目最常见的用户反馈是“记录突然没了”。排查这类问题,第一件事不是去翻代码,而是确认浏览器是不是开启了“退出时清除浏览数据”的功能。凡是把数据放在localStorage里的前端应用,都逃不过这个坑:用户正常关闭浏览器再打开,数据还在;但如果用的是某些手机浏览器的“无痕模式”或“自动清理”选项,页面刷新或关闭时数据就会被清理掉。我给出的解决方案是,在页面加载时自动从localStorage读取数据,同时设置一个导出到文件的功能,建议用户每周手动导出一次。

如果你希望数据更稳妥,可以在现有架构上加一个“同步到WebDAV”的按钮,通过WebDAV协议把JSON文件上传到自己的云盘。这个方案能让你在手机和电脑之间共享记账数据,同时不需要自己维护服务器。不过同步冲突的问题需要单独处理:如果两台设备同时修改记录,就会产生两个版本的JSON文件,建议在导入时按最后修改时间合并,而不是简单地覆盖。

4.2 备份工具误报“文件已修改”的原因分析

增量备份在使用一段时间后,偶尔会出现一种让人困惑的情况:明明某个文件没有动过,但备份工具却把它判定为“已修改”。我排查下来,这种情况通常不是文件内容真的变了,而是文件元信息发生了变化。比如,你在手机或相机上处理过照片,再拷回电脑时,文件的修改时间很可能被更新到了拷贝那一刻,即使内容没有变化。

解决这个问题的办法是调整对比策略:不是只比较大小和修改时间,而是对文件内容做分块哈希校验。具体做法是,在快照中记录每个文件的总大小和前几块内容的哈希指纹。每次对比时,先看大小是否一致,如果一致再看首块内容哈希。这样既避免了全文件哈希带来的性能开销,又能过滤掉大部分仅元信息变化的情况。要注意的是,这个方案不能百分百覆盖所有场景,比如文件恰好被修改但头部没变的极端情况,但实际使用中误报率可以从接近10%降到不足1%。

4.3 看板不显示数据时,从哪几个方向逐一排查

看板页面空白是小概率出现但一定会出现的问题。我的排查顺序一般是这样的:先看WebSocket服务端有没有收到日志更新数据,如果服务端没有输出,说明日志监听模块没有正常工作,这时需要检查日志文件路径是否正确、Nginx是否开启了日志写入权限;如果服务端有数据推送,再看浏览器端的控制台有没有报错,常见的报错是连接被拒绝或跨域被阻止。

如果两端看起来都正常但图表依然空白,那大概率是前端数据解析或渲染的问题。我遇到过一种情况是Nginx日志的日期格式跟解析正则不匹配,导致所有记录的时间字段都是空值,而ECharts的时间轴不接受空值,整个图就静默失败了。解决办法是在解析之后加一层数据校验,如果某条记录的时间字段为空就直接丢弃并打出一条警告日志,让异常能在开发阶段暴露出来,而不是默默藏到线上。

4.4 三个项目通用的一条避坑原则:小步快跑

关于避坑,我想分享一条适用于所有项目的通用原则:小步快跑,先让最核心的链路跑通,再逐步叠加周边功能。写记账本的时候,我的第一步是让“录入一笔记录并立刻在列表中显示出来”这个动作跑通,导出功能、统计图表都是后面一两天才加的。备份工具也一样,一开始只写一个最朴素的“整个文件夹拷贝过去”的脚本,确认没有问题之后才着手加增量对比、版本轮转和定时任务。看板项目的第一步是“把日志文件某一行读出来并打印到控制台”,这一步通了,后面的一切才有支撑。

这个原则听上去像废话,但执行起来很容易跑偏。特别是当你开项目前刷了很多技术文章,看到别人用各种高级框架和架构时,就会忍不住想一步到位。我吃过这个亏,第三个项目一开始想用消息队列来解耦日志采集和WebSocket推送,后来冷静想了想,个人博客的日志量根本到不了需要消息队列的级别,引入它只会给自己增加部署和排查的负担,于是果断砍掉,保留了最直接的那条路径。

5. 假期做项目的一些额外心得

5.1 代码能力之外,记录习惯同样重要

这三期“假期作业”做下来,我最大的收获不全是技术上的。以前假期做项目,总会有完成一个功能就心满意足、停下整理文档的情况,结果到了假期结束回头看,代码虽然写了,但很多设计决策和调试过程都忘得差不多了。后来我养成一个习惯:每天结束前,不管项目进展到哪一步,都在项目根目录下新建一个日志文件,用几句话记录当天做了什么、遇到了什么问题、明天打算做什么。

这套记录习惯在收尾写总结时特别有价值。比如这篇“假期作业三”的分享,很多细节我并不是靠记忆写出来的,而是翻看当时记录的building_log.md文件。这种文件的格式完全不需要讲究,甚至可以是一堆零散的短语和命令记录,只要自己一个月后能看懂就达到了目的。我建议每个假期做项目的人都能把“记录”这件事当成项目的一部分,它的长期回报远大于多写几十行代码。

5.2 如何让项目做到“可暂停”而不是“烂尾”

假期项目最大的风险不是不会做,而是中途可能因为旅行、聚会、临时加班等原因中断三五天。一旦中断,再回来时大脑需要重新加载上下文,如果加载成本太高,人就容易放弃。解决这个问题的方法叫“可暂停性”:让项目在任何时刻都能被安全地暂停一段时间,然后再无缝恢复。

三个项目的设计里我都刻意考虑了这一点。记账本完全依赖浏览器本地数据,哪怕半个月不打开,再打开时数据还在,继续记录就行;备份工具由定时任务驱动,就算我十天不上手动运行,它也会按计划自动执行;看板项目更是全自动的数据管道,日志采集和推送服务会持续运行,新页面打开就能看到最新数据。反过来说,如果当时选了那种需要持续互动的在线协同步伐,中断几天很可能就真的回不去了。

5.3 假期作业三之后,还能往哪些方向扩展

这三个项目虽然是完整闭环,但都保留了扩展空间。记账本可以再加一个“月度预算告警”功能,如果当月支出超过预算的80%就给用户提醒,这个逻辑可以在统计区直接实现。备份工具可以增加一个跨设备同步的后端,比如把版本包上传到对象存储服务,这样即使本机硬盘坏了,备份数据也不会丢失。看板项目可以把Nginx日志换成自己写的API服务请求日志,从而把整个后端服务的运行状态也纳入监控范围,变成一个轻量级的可观测性工具。

我个人计划在下一期假期作业里把这三个项目整合成一个简单的“个人效率工具箱”:一个统一的首页入口,三个模块共享一套认证和配置信息,这样既能减少日常使用的重复操作,也为后续增加第四、第五个小模块打好了基础。如果你也打算做类似的系列项目,不妨提前想好不同项目之间的共同点,在后期合并时能省下不少重构的时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 20:16:54

固定污染源温室气体多组分监测标准技术要点解读

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

作者头像 李华
网站建设 2026/9/10 20:13:31

宠物寄养小程序开发:物联网与WebRTC技术实践

1. 项目背景与核心价值去年夏天帮朋友临时照看金毛犬时,发现传统宠物寄养存在三大痛点:主人无法实时查看宠物状态、寄养环境信息不透明、紧急情况沟通滞后。这款小程序正是为解决这些行业顽疾而生,通过数字化手段重构宠物寄养服务流程。市场上…

作者头像 李华
网站建设 2026/9/10 20:12:13

多无人机部署优化:基于BCD+GA的吞吐量与飞行时间平衡方案

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

作者头像 李华
网站建设 2026/9/10 20:11:35

交换机品牌怎么选?十大品牌深度对比与选型实战指南

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

作者头像 李华