阅读视图

发现新文章,点击刷新页面。

Web 3D地球实时统计访问来源

前言

这篇文章介绍一个Web 3D地球实时统计访问来源的开源项目,效果如下,当服务器有http流量进来时,web通过3D地球+飞线实时绘制客户端的来源

Peek 2025-11-29 21-03.gif

项目地址:github.com/houxinlin/l…

实现原理

要在服务器上捕获http流量,也就是抓包,抓包有两种方案,一是使用类似pcap的库从内核捕获网络数据包,另一种比较复杂是使用ebpf拦截系统的函数点,以检测流量,本项目是使用pcap实现。

要通过代码把流量抓下来,其实逻辑比较简单,大概如下

  1. 打开设备 (Open Device)
    首先得告诉程序我们要监听哪个网卡(比如 eth0 还是 wlan0)。这里通常需要开启混杂模式 ,否则网卡默认只会接收发给它自己的包,而忽略广播或其他流量。
  2. 设置过滤器 (Set BPF Filter)
    这一步很重要,服务器上的流量太杂了,SSH、数据库、等数据包。我们只想看 HTTP 流量,所以需要设置一个过滤规则,比如 tcp port 80 or tcp port 443。这用的就是 BPF (Berkeley Packet Filter) 语法,效率极高,直接在内核层就把不相关的包扔掉了。
  3. 循环抓包 (Loop)
    前两步配置好后,就是在一个死循环里不断地从句柄中读取数据包(Packet)。

伪代码大概长这样:

// 1. 打开网卡 eth0,65535是最大捕获长度,1是开启混杂模式
handle = pcap_open_live("eth0", 65535, 1, 1000, errbuf);

// 2. 编译并设置过滤规则
pcap_compile(handle, &fp, "tcp port 80", 0, net);
pcap_setfilter(handle, &fp);

// 3. 每抓到一个包就回调 process_packet 函数
pcap_loop(handle, -1, process_packet, NULL);

pcap_loop 捕获到数据时,拿到手的是一堆原始的二进制字节,截获到的数据包,需要解析出以太网帧头,以太头由 14 字节固定长度构成,用于指明目标与源 MAC 地址,以及数据使用的上层协议类型,如下。

image.png

其中数据类型表示以太网帧中载荷(Payload)是什么协议的数据。

协议类型 十六进制 说明
IPv4 0x0800 表示数据部分是 IPv4 数据包
ARP 0x0806 地址解析协议
IPv6 0x86DD IPv6 数据包

在项目中,解析时直接跳过14字节去解析ip报文即可,因为用不到以太数据,解析到ip头后就可以获取客户端的地址了,ip头格式如下。

image.png

获取ip包信息代码如下

void process(u_char *d, struct pcap_pkthdr *h, u_char *p) {
    struct ip *ip4_pkt = (struct ip *) (p + link_offset);
    uint32_t ip_hl = ip4_pkt->ip_hl * 4;
    uint8_t ip_proto = ip4_pkt->ip_p;
    char ip_src[INET_ADDRSTRLEN];
    char ip_dst[INET_ADDRSTRLEN];
    unsigned char *data;
    uint32_t len = h->caplen;

    inet_ntop(AF_INET, (const void *) &ip4_pkt->ip_src, ip_src, sizeof(ip_src));
    inet_ntop(AF_INET, (const void *) &ip4_pkt->ip_dst, ip_dst, sizeof(ip_dst));
 }

接下来拿到客户端的ip后,解析出ip的经纬度通过websocket发送到前端即可,经纬度有两种办法可以获取。

  1. 使用maxminddb

    他是一种离线的经纬度查询系统,但是测试后不太准确,优点是速度极快。

  2. 在线服务

    寻找大量的在线ip转经纬度服务,找到他的api接口,在程序中使用负载均衡(因为免费服务都要分钟内次数限制)

但是,目前广泛的做法是nginx做web监听,使用https 443端口,虽然抓包获取ip来源没问题,但是有时候我们想通过http请求头中的字段获取ip,比如真实的用户IP往往藏在HTTP请求头的 X-Forwarded-For 或者 X-Real-IP 类似的字段里,这就需要解析tcp数据包,从tcp的Payload中解析出http请求头信息。

GET /api/v1/stats HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0...
X-Forwarded-For: 203.0.113.195
...

项目直接调用github.com/nodejs/http… 这个库去解析http头,他是被广泛验证的高性能 C 解析库(这也是 Node.js 早期底层使用的解析器)。

日本股票市场渲染 KlineCharts K 线图

下面是针对日本股票市场的完整对接方案,包含从获取股票列表渲染 KlineCharts K 线图的详细步骤和代码。

核心流程

  1. 获取日本股票列表:使用 countryId=35 查询日本市场的股票,获取目标股票的 id (即 PID)。
  2. 获取 K 线数据:使用该 pid 请求历史 K 线数据。
  3. 绘制图表:将数据转换为 KlineCharts 格式并渲染。

第一步:获取日本股票 PID (API 调试)

在写代码前,您需要先通过 API 拿到您想展示的日本股票(例如丰田、索尼等)的 id

请求方式:

  • 接口 URL: https://api.stocktv.top/stock/stocks
  • 参数:
    • countryId: 35 (日本)
    • pageSize: 10
    • key: 您的Key

请求示例 (GET):

https://api.stocktv.top/stock/stocks?countryId=35&pageSize=10&page=1&key=您的Key

返回示例 (假设): 您会在返回的 data.records 列表中找到股票信息。

{
  "id": 99999,  <-- 这个是 PID,记下这个数字用于下一步
  "name": "Toyota Motor Corp",
  "symbol": "7203",
  "countryId": 35,
  ...
}

第二步:完整实现代码 (HTML + KlineCharts)

将以下代码保存为 .html 文件。请替换代码顶部的 YOUR_API_KEY 和您在上一步获取到的 JAPAN_STOCK_PID

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>日本股票 K线图 (KlineCharts)</title>
    <script src="https://cdn.jsdelivr.net/npm/klinecharts/dist/klinecharts.min.js"></script>
    <style>
        body { margin: 0; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
        h2 { margin-bottom: 10px; }
        .config-box { 
            background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px; 
            display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
        }
        input, select, button { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
        button { background-color: #007bff; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #0056b3; }
        #chart-container { width: 100%; height: 600px; border: 1px solid #e0e0e0; border-radius: 4px; }
    </style>
</head>
<body>

    <h2>StockTV 日本股票 K线演示 (CountryID=35)</h2>

    <div class="config-box">
        <label>股票PID: <input type="number" id="pidInput" value="953373" placeholder="例如: 953373"></label>
        
        <label>周期: 
            <select id="intervalSelect">
                <option value="P1D">日线 (1 Day)</option>
                <option value="PT1H">1小时 (1 Hour)</option>
                <option value="PT15M">15分钟 (15 Min)</option>
                <option value="PT5M">5分钟 (5 Min)</option>
            </select>
        </label>

        <button onclick="loadChartData()">生成图表</button>
    </div>

    <div id="chart-container"></div>

    <script>
        // 配置您的 API Key
        const API_KEY = '联系我们获取key'; // TODO: 请在此处填入您的真实 Key
        const BASE_URL = 'https://api.stocktv.top';

        // 初始化 KlineCharts
        let chart = klinecharts.init('chart-container');
        
        // 设置一些基础样式
        chart.setStyleOptions({
            candle: {
                tooltip: {
                    labels: ['时间', '开', '收', '高', '低', '成交量']
                }
            }
        });

        chart.createIndicator('VOL'); // 创建成交量指标

        async function loadChartData() {
            const pid = document.getElementById('pidInput').value;
            const interval = document.getElementById('intervalSelect').value;

            if (!pid) {
                alert("请输入股票 PID");
                return;
            }

            console.log(`正在请求日本股票数据: PID=${pid}, Interval=${interval}`);

            try {
                // 构造 StockTV API 请求
                // 文档接口: /stock/kline
                const url = `${BASE_URL}/stock/kline?pid=${pid}&interval=${interval}&key=${API_KEY}`;
                
                const response = await fetch(url);
                const resJson = await response.json();

                if (resJson.code === 200) {
                    const stockData = resJson.data;

                    if (!stockData || stockData.length === 0) {
                        alert("该股票在此周期下无数据");
                        return;
                    }

                    // 数据格式转换
                    // StockTV: { time: 1719818400000, open: 239.42, ... }
                    // KlineCharts: { timestamp: 1719818400000, open: 239.42, ... }
                    const klineData = stockData.map(item => {
                        return {
                            timestamp: item.time, // 直接使用 API 返回的时间戳
                            open: Number(item.open),
                            high: Number(item.high),
                            low: Number(item.low),
                            close: Number(item.close),
                            volume: Number(item.volume)
                        };
                    });

                    // 确保按时间升序排序
                    klineData.sort((a, b) => a.timestamp - b.timestamp);

                    // 渲染数据
                    chart.applyNewData(klineData);
                    console.log("图表渲染成功,数据条数:", klineData.length);
                } else {
                    console.error("API 错误:", resJson);
                    alert("接口报错: " + resJson.message);
                }

            } catch (err) {
                console.error("请求失败:", err);
                alert("网络请求失败,请检查控制台 (F12)");
            }
        }

        // 窗口大小调整时自动调整图表
        window.addEventListener('resize', () => {
            chart.resize();
        });
        
        // 页面加载时自动尝试加载一次(方便测试)
        // 如果您有确定的日本股票PID,可以在 input 的 value 中预设
        // loadChartData(); 
    </script>
</body>
</html>

关键点说明

  1. CountryId=35 的使用

    • countryId=35 主要用于查询列表 (/stock/stocks) 阶段,用于筛选出日本市场的股票及其对应的 PID。
    • 一旦拿到 PID,在请求 K 线数据 (/stock/kline) 时,只需要 PID,不需要再传 countryId。
  2. 数据映射 (Mapping)

    • StockTV 返回的字段是 time, open, high, low, close, volume
    • KlineCharts 要求的字段是 timestamp, open, high, low, close, volume
    • 代码中 timestamp: item.time 这一行完成了关键的转换。
  3. 周期格式

    • 请确保传给 API 的 interval 参数是 P1D (日), PT1H (时) 等 ISO8601 格式,否则 API 可能会报错或返回空数据。

大部分人都错了!这才是chrome插件多脚本通信的正确姿势 | 掘金一周 11.27

本文字数1500+ ,阅读时间大约需要 5分钟。

【掘金一周】本期亮点:

「上榜规则」:文章发布时间在本期「掘金一周」发布时间的前一周内;且符合各个栏目的内容定位和要求。 如发现文章有抄袭、洗稿等违反社区规则的行为,将取消当期及后续上榜资格。

一周“金”选

掘金一周 文章头图 1303x734.jpg

内容评审们会在过去的一周内对社区深度技术好文进行挖掘和筛选,优质的技术文章有机会出现在下方榜单中,排名不分先后。

前端

大部分人都错了!这才是chrome插件多脚本通信的正确姿势 @不一样的少年_

Chrome 浏览器其实就是把各种工作分开来做,谁负责啥都很清楚。主进程管大局,渲染进程负责把网页内容展示出来,网络进程专门搞数据传输,GPU进程让动画和视频更流畅,插件进程则让你装的各种扩展各自独立运行。

别再滥用 Base64 了——Blob 才是前端减负的正确姿势 @404星球的猫

Blob 最大的特点是纯客户端、零网络:数据一旦进入 Blob,就活在内存里,无需上传服务器即可预览、下载或进一步加工。

转转UI自动化走查方案探索 @转转技术团队

整个方案的核心其实就做了一件事:把两个看起来完全不同的东西(设计稿的JSON和HTML的DOM树),通过一系列归一化处理,变成可以直接比对的同构数据。这个过程中最大的感受是,前端开发和UI设计之间的gap,本质上是两套不同的渲染规则在互相较劲。

npm scripts的高级玩法:pre、post和--,你真的会用吗? @ErpanOmer

npm scripts,它不是一个简单的脚本快捷方式。它是一个工作流(Workflow)的定义 。prepost,定义了你工作流的执行顺序依赖,保证了代码检查等功能,而--是确保你工作流中的脚本参数

Vue高阶组件已过时?这3种新方案让你的代码更优雅 @良山有风来

HOC到Composition API,不仅仅是API的变化,更是开发思维的升级。 HOC代表的组件包装模式已经成为过去,而基于函数的组合模式正是未来。这种转变让我们的代码更加清晰、可测试、可维护。

后端

Spring 项目别再乱注入 Service 了!用 Lambda 封装个统一调用组件,爽到飞起 @只会写代码

其实这组件就干了 3 件事:1. 你传个 Lambda(比如UserService::queryUser),它帮你找到对应的 Service 实例;2. 把找到的实例和方法缓存起来,下次调用更快;3. 统一执行方法,顺便把日志、异常处理都包了。

Golang HTTP请求超时与重试:构建高可靠网络请求|得物技术 @得物技术

HTTP请求看似简单,但它连接着整个系统的"血管"。忽视超时和重试,就像在血管上留了个缺口——平时没事,压力一来就大出血。构建高可靠的网络请求需要在超时控制、重试策略、幂等性保证和性能优化之间取得平衡。

Android

回顾 Flutter Flight Plans ,关于 Flutter 的现状和官方热门问题解答 @恋猫de小郭

在 Flutter 官方刚举行的 Flutter Flight Plans 直播里,除了发布 Flutter 3.38Dart 3.10 之外,其实还有不少值得一聊的内容,例如企业级的 Flutter 案例展示,Flutter + AI 的场景,重点还有针对大量热门问题的 Q&A(多窗口、GenUI、PC\Web 插件) 等

Android系统BUG:修改线程名目标错乱问题探究 @卓修武K

此次的问题发生原因是 三方地图SDK 重写了start()函数,又多次调用了start函数,导致滴滴的booster插件添加的setName逻辑也被多次触发,而此时调用setName的线程刚好是主线程,因此最终影响了 主进程名称

人工智能

Doubao-Seed-Code深度测评:一张设计稿生成完整网站,视觉理解编程模型全流程实战 @Nturmoils

即使有些模型通过MCP工具调用实现了"看图",但本质上是先把图片转成文字描述,再交给模型理解。这个过程中信息折损非常大,效果远不及原生VLM能力。Doubao-Seed-Code的视觉理解是模型训练阶段就内置的能力,可以直接"看懂"图片,识别UI布局、配色方案、设计细节,然后生成对应的代码。

如何实现 Remote MCP-Server @袋鼠云数栈UED团队

对于公司内部的MCP-Server, 由于隐私性问题不能发布为npm包,那么就没法以npx或者uvx等形式快速的共享使用。所以基本会以STDIO类型的MCP-Server进行开发,在内部进行共享时只能将对应源文件拉取本地使用。

社区活动日历

掘金官方 文章头图 1303x734.jpg

活动日历

活动名称 活动时间
🚀TRAE SOLO 实战赛 2025年11月13日-2025年12月16日

📖 投稿专区

大家可以在评论区推荐认为不错的文章,并附上链接和推荐理由,有机会呈现在下一期。文章创建日期必须在下期掘金一周发布前一周以内;可以推荐自己的文章、也可以推荐他人的文章。

一键去水印|5 款免费小红书解析工具推荐

一、前言

刷短视频时,看到喜欢的内容想存到手机里,结果点"保存"却提示"不允许"?别慌,不是手机坏了,而是作者或平台把下载开关关掉了。下面教你几招,照样能把视频"救"回来——适用于抖音、小红书、快手等主流 App,全程大白话,一看就会。

二、直接保存到相册

对于未限制下载权限的视频,操作步骤如下:

  1. 点击分享按钮。
  2. 在弹出的选项中,选择保存到相册
  3. 视频会自动保存到手机相册中。

三、无法直接保存的视频

有些视频因创作者设置了"禁止下载"功能,保存到相册按钮会显示为灰色,无法直接点击。这种情况下,可以尝试以下方法:

1. 屏幕录制

  • 利用手机自带的屏幕录制功能,手动录制视频内容。
  • 缺点:可能需要后续裁剪多余的内容。

2. 借助第三方工具

下面几个在线工具亲测可用,支持抖音 + 小红书。请按需选择,用完即走:

工具名称 & 入口 支持平台 速度/稳定性 特色小功能
去水印下载鸭 nologo.code24.top 抖音、小红书、快手等 快(CDN 国内双线) 浏览器插件+小程序;自动识别剪贴板
小红书专用下载 www.xhs-download.online 仅小红书 极快(节点在香港,晚高峰也稳) 专注小红书高清去水印下载
下载狗 www.xiazaitool.com/xhs 抖音、小红书、快手等 中等(教育网可用) 轻量级小红书去水印神器,图片、视频一键在线去水印;无广告
小红刷 www.xiaohongshua.com 仅小红书 界面极简,三步搞定
RedNote 视频下载器 www.rednote-downloader.com/zh 仅小红书 界面极简

使用流程(大同小异):

  1. 在 App 里点"分享 → 复制链接"
  2. 把链接粘贴到对应网页的输入框
  3. 等待 3-5 秒出现下载按钮 → 右键/长按保存即可
❌