2026年2月

当你的 PHP 网站一天内流量增长 10 倍时,会发生什么?

引言:意料之外的流量激增

想象一下,你是一个小型 PHP 网站的主开发。过去几个月,网站运行一直很平稳——流量不高,负载也可预测。然后某一天,一切突然改变:你的网站流量在一夜之间增长了 10 倍。

接下来会发生什么?你的网站开始变慢,用户感受到明显延迟,甚至更糟,网站直接崩掉。你遇到的是大多数开发者迟早都会碰到的问题:在突发压力下扩展系统。本文会带你看清 PHP 文件加载机制背后到底发生了什么,以及在扩展 Web 应用时,如何避开那些会导致性能下降和 Bug 的常见陷阱。

实际发生了什么?

开发 PHP 网站时,你会频繁使用 includerequireinclude_oncerequire_once 把外部文件引入脚本。这些能力对构建模块化、可维护的 PHP 应用非常关键。但当流量上来后,这些看似普通的操作很快就可能变成瓶颈。

底层主要发生了以下事情:

  • 文件包含:PHP 会在运行时解析这些包含语句,到文件系统中查找目标文件(相对路径或绝对路径),并执行其中代码。
  • 缓存行为:PHP 会把脚本编译为 opcode(字节码)来提升执行速度。如果你使用 include_oncerequire_once,PHP 会先检查该文件在当前请求里是否已加载。这能防止重复包含,但也会带来额外性能成本,在高流量下更明显。
  • 路径解析:PHP 会按执行上下文解析路径。在 Web 服务器中,不同环境下“当前实际路径”可能和你的预期不一致,部署时尤其容易出问题。

为了更直观,可以把 include 想成“去图书馆借书”——每借一次都要走一遍借阅流程。单次开销不大,但并发高了,这些固定动作就会被放大。

下一节我们看开发者在 PHP 文件包含中最常见的错误。

常见错误

没有正确使用 require_once

表现:在应用不同位置反复对同一文件使用 requireinclude

原因:没有意识到重复包含同一文件会导致性能问题,甚至触发函数或类重复声明的致命错误。

影响:可能出现函数重复定义、类重复声明,或因不必要的重复解析导致变慢。

路径解析错误

表现:使用相对路径,如 include 'config.php';,却没有考虑脚本是从哪里执行的。

原因:误以为 PHP 总会在你预期的目录下解析路径。

影响:文件包含失败,或在不同环境行为不一致。

过度使用 include_once / require_once

表现:所有文件都用 include_oncerequire_once,认为这是最稳妥的方案。

原因:想确保文件不被重复包含,但忽略了这两个语句比 include / require 更重,因为 PHP 需要先检查文件是否已加载。

影响:在高流量应用中,过度使用 _once 会拖慢性能,检查机制本身可能成为瓶颈,显著影响响应时间。

不使用绝对路径

表现:使用相对路径(例如 include 'includes/functions.php';),却没有考虑应用上线后目录结构可能变化。

原因:很多开发者只看本地可运行,忽略了不同环境路径解析细节。

影响:生产环境无法包含文件,出现难以定位的错误。

用户输入带来的安全风险

表现:根据用户输入动态包含文件(如 include $_GET['page'] . '.php';)。

原因:为了让页面更动态,直接把用户可控输入用于文件路径。

影响:应用会暴露给本地文件包含(LFI)或远程文件包含(RFI)攻击。

没有缓存包含结果

表现:在一个请求里多次包含重量级文件(如配置文件或库文件)。

原因:没有对包含行为做缓存或优化,尤其是那些很少变更的共享资源。

影响:每次包含都需要再次解析并执行文件,即便文件未变化,也会造成额外负载和性能下降。

如何正确处理

现在我们看正确做法。

在必要时使用 require_once

只有在你确实需要确保“同一请求只包含一次”时,才使用 require_onceinclude_once。其他场景优先用 includerequire 以获得更好性能。

// Bad example
include 'config.php';  // Included multiple times across scripts

// Good example
require_once 'config.php';  // Ensures the file is only loaded once

使用绝对路径

始终从项目根目录定义绝对路径,确保包含的是正确文件。

// Bad example
include 'includes/functions.php';  // Might break in production

// Good example
include $_SERVER['DOCUMENT_ROOT'] . '/includes/functions.php';  // Resolves paths correctly

缓存包含

对于不常变化的文件(如配置文件),使用 OPcache 等 opcode 缓存机制。

动态包含时净化输入

如果必须动态包含文件,务必净化输入,避免安全漏洞。

// Bad example
include $_GET['page'] . '.php';  // Potential security risk

// Good example
$page = basename($_GET['page']);
$allowed_pages = ['home', 'about', 'contact'];
if (in_array($page, $allowed_pages)) {
    include $page . '.php';
} else {
    echo "Page not found";
}

生产环境注意事项

随着网站规模扩大,文件包含在生产环境中的影响会越来越关键。这里有几个生产实践要点:

安全影响

  • 路径穿越与 RFI/LFI:务必净化输入,防止用户包含服务器上的任意文件。
  • PHP 配置:在 PHP 配置中关闭远程文件包含(allow_url_include = Off),避免被恶意利用。

扩展与性能

  • Opcode Cache:使用 OPcache 缓存已编译字节码,加速重复请求。
  • *_once 的性能开销:在关键性能路径避免过度使用 require_onceinclude_once

可观测性

  • 为包含失败实现完善的日志与错误处理。
  • 可用 Sentry 等工具追踪文件包含问题。
  • 使用 Xdebug 等分析工具追踪包含文件的性能开销。

部署差异

要理解应用在不同环境(如 Docker、Serverless)中的运行差异。部署架构不同,文件路径行为也不同,因此包含路径要尽量环境无关。

API 密集型应用

通过模块化降低对大文件手动包含的依赖。现代 PHP 应用通常通过 Composer 加载依赖,这会减少手写 include 的需求。

排障检查清单

如果你的应用因为文件包含开始报错,可按以下清单排查:

  • 检查 PHP 日志:查看 include()require()require_once() 相关错误。
  • 校验路径:确认使用的是正确路径(绝对路径 vs 相对路径)。
  • 净化用户输入:确保动态包含不会被 LFI/RFI 利用。
  • 检查性能:用 profiling 工具定位文件包含耗时。
// Debugging snippet
error_log("File included: " . $file_path);

结论:关键要点

  • 理解文件包含的影响:文件何时、如何被包含,会直接影响性能、安全和可维护性。
  • require_once 要节制:它对防止重复包含很重要,但有性能成本。
  • 安全优先:动态包含必须先净化输入。
  • 优化性能:启用 opcode 缓存,并减少不必要的重复包含。

下一步

如果你最近已经遇到性能问题,建议尽快回顾你的文件包含实践:

  • 改为使用绝对路径。
  • 在高频路径里减少对 *_once 的依赖。
  • 对动态包含建立白名单与日志。

这些调整通常改动不大,但对线上稳定性的提升很直接。

当你的 PHP 网站一天内流量增长 10 倍时,会发生什么?

在企业级应用开发中,数据脱敏是合规性的刚需。然而,开发者往往在“功能完整性”与“系统性能”之间面临权衡:基于 AOP 的方案难以穿透复杂的嵌套对象(如 List<Map>),而基于 Jackson 序列化的方案又难以应用于非 Web 输出场景(如 Excel 导出、日志记录)。

开源项目 easy-desensitize 提供了一个值得关注的解法。该框架通过精简的反射缓存架构,在保证深度递归处理的同时,将损耗压制在了纳秒级别。

核心特性

  • 高性能:基于反射缓存的零拷贝架构,性能损耗极低
  • 全场景支持:兼容 Web API、Excel 导出、日志记录等多种使用场景
  • 智能识别:自动处理 List、Map、数组及多层嵌套对象
  • 灵活扩展:支持自定义脱敏策略和处理器
  • 无缝集成:提供 Spring Boot Starter,一键开启全局脱敏

1. 概述

easy-desensitize-core是一个独立于业务逻辑的Java脱敏引擎。其核心价值在于提供了一套结构自适应的脱敏机制。

该框架不依赖特定的 Web 容器,其底层设计避开了高开销的正则表达式,转而采用基于字符索引的高效切分算法。它最显著的特征是能够自动探测对象图(Object Graph),在处理包含集合、Map、数组以及多层嵌套 Bean 的复杂数据模型时,表现出了极高的鲁棒性。

2. 快速开始

该框架遵循“声明式定义,编程式调用”的设计原则,接入成本极低。

2.1 依赖引入

在 Maven 项目中引入核心库

<dependency>
    <groupId>io.github.zhengyuelaii</groupId>
    <artifactId>easy-desensitize-core</artifactId>
    <version>${latest.version}</version> <!-- 使用变量替换最新版本 -->
</dependency>

2.2 规则标注

定义脱敏处理器

/**
 * 手机号脱敏处理器示例
 */
public class MobileMaskingHandler implements MaskingHandler {
   @Override
   public String getMaskingValue(String value) {
      // 使用内置 Masker 工具类隐藏中间 4 位
      return Masker.hide(value, 3, 7);
   }
}

定义实体类

public class User {
    @MaskingField(typeHandler = KeepFirstAndLastHandler.class)
    private String name;
    @MaskingField(typeHandler = MobileMaskingHandler.class)
    private String mobile;
    @MaskingField(typeHandler = FixedMaskHandler.class)
    private String password;
    // getter、setter 略
}
通过在实体类字段上添加 @MaskingField 注解,开发者可以精确定义脱敏策略。

执行脱敏

public class QuickStart {
   public static void main(String[] args) {
      User user = new User();
      user.setName("张小凡");
      user.setMobile("13700001234");
      user.setPassword("123456");
      
      EasyDesensitize.mask(user);
      System.out.println(user);
      // 输出:User [name=张*凡, mobile=137****1234, password=******]
   }
}

3. 复杂对象支持

在实际业务中,数据往往以 Result<T> 或嵌套列表的形式存在。easy-desensitize在处理这类复杂结构时表现出了关键技术优势。

  • List脱敏
public class ListTypeDesensitize {
   
   public static void main(String[] args) {
      User user = new User();
      user.setName("李小鹏");
      user.setMobile("13700001234");
      user.setPassword("123456");
      
      // List脱敏
      List<User> list = Collections.singletonList(user);
      EasyDesensitize.mask(list);
      System.out.println(list);
      // 输出:[User [name=李*鹏, mobile=137****1234, password=******]]
   }
}
  • Map脱敏
public class MapTypeDesensitize {
    
   public static void main(String[] args) {
      User user1 = new User("李小鹏", "13700001234", "123456");
      User user2 = new User("张三", "13888880000", "456789");
      
      // Map脱敏
      Map<String, Object> map = new HashMap<>();
      map.put("code", 200);
      map.put("data", user1);
      map.put("list", Collections.singleton(user2));
      
      EasyDesensitize.mask(map);
      System.out.println(map);
      // 输出:{code=200, data=User [name=李*鹏, mobile=137****1234, password=******], list=[User [name=张*, mobile=138****0000, password=******]]}
   }
}
  • 树形数据脱敏
public class TreeVO {
   private String id;
   @MaskingField(typeHandler = KeepFirstAndLastHandler.class)
   private String name;
   private List<TreeVO> children;
   // ...
}

public class TreeTypeDesensitize {

   public static void main(String[] args) {
      TreeVO root = new TreeVO();
      root.setId("1");
      root.setName("XXX有限公司");
      
      TreeVO t1 = new TreeVO();
      t1.setId("1001");
      t1.setName("行政部");
      
      TreeVO t2 = new TreeVO();
      t2.setId("1001");
      t2.setName("研发部");
      
      root.setChildren(Arrays.asList(t1, t2));
      EasyDesensitize.mask(root);
      System.out.println(root);
      // 输出:TreeVO [id=1, name=X*****司, children=[TreeVO [id=1001, name=行*部, children=null], TreeVO [id=1001, name=研*部, children=null]]]
   }
}
  • 泛型数据脱敏
// 统一包装
public class Result<T> {

    private Integer code;
    private String message;
    private T data;

    // ...
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }
    // ...
}

// 测试脱敏
public class ResultDesensitize {
    public static void main(String[] args) {
        Result<User> result = Result.success(new User("张小凡", "13700001234", "123456"));
        EasyDesensitize.mask(result);
        System.out.println(result);
        // 输出:Result{code=200, message='success', data=User [name=张*凡, mobile=137****1234, password=******]}
    }
}

项目仓库: easy-desensitize-core

4. 与 Spring 集成

通过easy-desensitize-spring-boot-starter,框架实现了自动装配逻辑。它利用 Spring MVC 的 ResponseBodyAdvice 切面,在 HTTP 响应序列化前的最后一步自动触发脱敏。这意味着开发者无需在 Service 层编写任何脱敏代码,即可实现全局的安全加固。

4.1 引入依赖

<dependency>
    <groupId>io.github.zhengyuelaii</groupId>
    <artifactId>easy-desensitize-spring-boot-starter</artifactId>
    <version>${latest.version}</version> <!-- 使用变量替换最新版本 -->
</dependency>

4.2 创建实体类

在字段上使用 @MaskingField 定义脱敏策略。

import io.github.zhengyuelaii.desensitize.core.annotation.MaskingField;
import io.github.zhengyuelaii.desensitize.core.handler.FixedMaskHandler;
import io.github.zhengyuelaii.desensitize.core.handler.KeepFirstAndLastHandler;

public class User {
    @MaskingField(typeHandler = KeepFirstAndLastHandler.class)
    private String name; // 李小龙 -> 李*龙
    @MaskingField(typeHandler = FixedMaskHandler.class)
    private String password; // 123456 -> ******
    private String address;

    // getter/setter
}

4.3 在Controller启用脱敏

方法或类 上添加 @ResponseMasking 注解。

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/get")
    @ResponseMasking
    public User get() {
        return new User( "李小龙", "12345678", "上海");
    }

}

响应结果

{
    "username": "李*龙",
    "password": "******",
    "address": "上海"
}

项目仓库: easy-desensitize-spring-boot-starter

总结

从实测结果看,easy-desensitize 不仅仅是一个简单的注解工具,它更像是一个经过精细调优的性能引擎。对于追求高吞吐量、且需处理复杂对象模型的 Java 应用,它提供了一个高性能且低侵入的标准化方案。

测试代码:easy-desensitize-samples

在物联网(IoT)领域,设备的功耗管理是确保其长期稳定运行的关键。对于依赖电池供电的 IoT 设备,如数据传输单元(DTU),精确评估和优化功耗至关重要。这不仅有助于合理设计电池容量,更能显著延长设备的使用寿命。

本文将深入探讨基于 EdgeBus 平台的 DTU 设备功耗计算方法,涵盖不同运行模式下的功耗组成部分,并提供详细的计算逻辑和优化建议。如需查阅详细的计算公式和示例,请访问我们的官方指南:DTU 功耗计算指南

DTU 功耗构成

DTU 的总功耗主要由以下几个部分组成:

  1. 基础功耗​:设备在静态或休眠状态下的最低功耗。
  2. SW 模式功耗​:当启用 SleepWakeup (SW) 模式时,设备周期性监听前导码所产生的额外功耗。此模式旨在降低功耗,同时满足 LoRaWAN 协议要求。
  3. LoRaWAN 功耗​:LoRaWAN 模块在数据传输过程中产生的功耗。
  4. 有线通信功耗​:与外部设备进行有线通信(如 RS-485、M-Bus、4-20mA、0-10V 接口)时产生的功耗,包括外部设备自身功耗和 DTU 接口功耗。

功耗计算方法概述

1. 基础功耗

所有 ManThink DTU 产品的基本静态功耗通常为 ​3 µA​。这是设备在最低活动状态下的持续功耗。

2. SW 模式功耗

SW 模式通过周期性地监听前导码来降低功耗。其功耗受 ​周期 (Period)​、扩频因子 (SF)带宽 (BW) 三个参数影响。SF 和 BW 共同决定符号持续时间,DTU 会持续监听两个符号持续时间以检测唤醒前导码。

  • 更长的周期​:意味着更低的静态功耗,但响应时间会更长。
  • 更长的符号持续时间​:支持更远的通信距离,但会增加功耗。

详细的 SW 模式功耗计算涉及符号时间的获取和 SW 静态电流的计算。您可以通过 DTU 功耗计算指南 了解具体的计算步骤和示例。

3. LoRaWAN 功耗

LoRaWAN 传输功耗与数据速率和通信距离密切相关。较低的数据速率和较长的距离会导致更长的传输时间,从而增加功耗。LoRaWAN 传输功耗可以通过 Semtech 在线计算器进行估算。

关键参数包括:

  • Tx 功率​:传输功率等级。
  • 区域​:定义不同区域的数据速率 (DR) 参数。
  • 数据速率 (DR)​:LoRaWAN 支持通过 ADR(自适应数据速率)进行调整。为延长电池寿命,应选择合适的 DR 值,或通过增加网关部署密度来提高信号质量,使设备能以最高 DR 运行。
  • Payload 长度​:应用层数据大小。

通过在线工具提交参数后,可以获得​单次传输的能量消耗​。静态电流等效值可通过将平均传输功耗除以传输周期来计算。更多详情和示例请参考 DTU 功耗计算指南

4. 有线通信功耗

有线通信功耗包含两部分:外部设备功耗和 DTU 有线接口功耗。

外部设备功耗

当 DTU 通过升压电路(通常效率为 70%)为外部设备提供 15.1V 电源时,外部设备电流需要转换为电池侧等效电流。这确保了功耗计算的准确性。

外部设备功耗不仅取决于电流,还与通信工作时间有关,包括预热时间、指令传输时间、设备响应时间以及超时重试时间。串行通信时间可以通过传输和接收字节数、每字节位数和波特率计算得出。

对于详细的电流转换公式、通信工作时间计算以及单次读取能量计算,请查阅 DTU 功耗计算指南

DTU 有线接口功耗

例如,RS-485 接口的工作电流通常小于 12 mA。由于 RS-485 不需要升压转换,电池侧等效电流可直接按此值计算。其他接口类型如 4-20mA 和 0-10V 也有其典型的电流消耗。

整体功耗计算与电池寿命评估

1. 总通信功耗

总通信功耗包括在完整有线通信过程中所有组件的功耗,如外部设备电池侧等效电流、RS-485 接口电流和 MCU 工作电流。这些功耗的叠加构成了设备在通信时的瞬时功耗。

2. 日均功耗

日均功耗是评估电池寿命的关键指标,它必须综合考虑设备在不同模式下的运行时间及其相应的功耗。日均功耗是基础功耗、SW 模式功耗、LoRaWAN 功耗和有线通信功耗在 24 小时内的累积。

重要提示​:如果 DTU 无需为外部设备供电,则应忽略外部设备功耗的计算,此时有线通信功耗仅包括 DTU 自身的有线接口功耗和 MCU 工作功耗。

3. 电池寿命估算

在评估电池寿命时,建议考虑工程裕量,如正常运行、重试场景和安全裕量系数。电池寿命估算公式将电池容量与日均功耗及安全裕量相结合,以提供一个实际可行的设备寿命预测。

所有这些计算的详细公式和具体示例,都可以在 DTU 功耗计算指南 中找到。

工程建议

为准确评估和优化 DTU 设备功耗,以下建议可供实际应用参考:

  1. 优化 LoRaWAN 参数​:根据实际通信需求和网络覆盖条件,合理设置 LoRaWAN DR 值和传输间隔,以平衡功耗和通信性能。
  2. 简化外部设备​:选择低功耗外部设备,并优化其工作模式,以减少外部供电的时间和电流。
  3. 考虑异常场景​:在功耗评估中充分考虑异常超时重试等场景,确保设备在最坏条件下也能满足寿命要求。
  4. 定期监测​:设备部署后,定期监测实际功耗数据并与理论计算进行对比。根据对比结果及时调整优化策略。

参考资料:

[1] [DTU Power Consumption Calculation Guide] https://think-link.net/tkl-docs/en/AN/AN-26022301%20DTU%20Power%20Consumption%20Calculation%20Guide

TKL+EB 组合方案重新定义了 LoRaWAN 在存量市场改造中的角色:它不仅解决了传感器无线化接入的难题,更通过 EB 的灵活性与远程可控性,实现了硬件先行、软件后置、快速迭代的新型部署范式。

无论您是传感器厂商、系统集成商,还是智慧城市解决方案提供商,只要您有 ​将传统设备接入 LoRaWAN 网络的需求,我们都诚挚邀请您与门思科技合作。

  • 免费提供技术支持与对接服务;
  • 无需支付任何费用;
  • 只需提供设备样品、说明书及相关接入需求,即可启动对接流程。

ThinkLink 体验地址 :https://thinklink.manthink.cn

ThinkLink 官网 :https://think-link.net

要放客厅不能太大,机器多了也不好管理,成本也高

所以采用了 ITX 机箱,到处找结果咸鱼找到了霜秋凛然的 3D 打印机箱
显卡外置,4 盘位,可以满足所有需求

正面
侧面

模块化、可复用、开源的 HarmonyOS 快速开发框架

为什么需要 HCompass?

鸿蒙生态正在高速发展,越来越多的开发者投入到 HarmonyOS 应用开发中。但在实际项目中,我们常常面临这些痛点:

  • 每个新项目都要从零搭建基础架构,重复造轮子
  • 网络请求、路由导航、状态管理等通用能力缺乏统一封装
  • 多人协作时代码风格不一致,模块边界模糊
  • 业务功能难以跨项目复用,积累的经验无法沉淀

HCompass 正是为解决这些问题而生。它不是一个 UI 组件库,而是一套完整的应用开发框架 -- 帮你把地基打好,让你专注于业务本身。


核心理念:功能包即积木

HCompass 的核心思想很简单:把应用拆成一个个独立的"功能包",像搭积木一样组合它们。

每个功能包都是一个自包含的业务模块,拥有自己的页面、ViewModel、服务和数据模型。需要登录功能?放入 auth 功能包。需要用户中心?放入 user 功能包。功能包之间通过契约层解耦,互不依赖,随时可以拆卸和替换。

你的应用 = Entry(入口) + 若干功能包(积木)

四层架构,职责分明

HCompass 采用清晰的四层架构设计:

层级职责关键特征
Entry应用入口初始化框架、注册功能包、配置路由
Packages业务功能包独立开发、独立测试、可跨项目复用
Shared共享契约定义服务接口和类型,功能包之间的"协议"
Core框架核心与业务无关的通用能力,可直接迁移到任何项目

这种分层设计带来的好处是:Core 层可以直接复用到你的下一个项目,Packages 层的功能包可以按需组合,Shared 层确保模块之间的通信有据可依。


开箱即用的核心能力

依赖注入(DI)

轻量级 DI 容器,让功能包之间彻底解耦:

// 在 Shared 层定义契约
interface IUserRepository {
  getUserInfo(id: string): Promise<User>;
}

// 在功能包中实现并注册
register<IUserRepository>(ServiceKeys.USER_REPO, new UserRepositoryImpl());

// 在任何地方解析使用
const userRepo = resolve<IUserRepository>(ServiceKeys.USER_REPO);

不再需要硬编码依赖关系,功能包只依赖契约,不依赖具体实现。

导航系统

统一的路由管理,支持路由守卫:

// 路由跳转
NavigationService.push(UserRoutes.PROFILE, { userId: "123" });

// 路由守卫 -- 未登录自动跳转登录页
GuardManager.addGuard(new LoginGuard());

支持登录守卫、权限守卫、条件守卫,灵活组合,按优先级执行。

网络请求

基于 Axios 封装,告别重复的请求模板代码:

// 继承 BaseNetWorkViewModel,自动管理 loading/error/success 状态
@ObservedV2
class UserProfileViewModel extends BaseNetWorkViewModel<User> {
  async fetchData(): Promise<void> {
    await this.request(() => this.userRepo.getUserInfo(this.userId));
  }
}

内置拦截器链、统一错误处理、分页逻辑,你只需关注业务数据。

设计系统

统一的设计令牌,确保 UI 一致性:

// 百分比常量,告别魔法数字
Row() { ... }.width(P100).height(P50)

// 间距组件,统一视觉节奏
Column() {
  Text("标题")
  SpaceVerticalMedium()  // 12vp 间距
  Text("内容")
}

基础父类

三个 ViewModel 基类覆盖 90% 的页面场景:

基类适用场景
BaseViewModel通用页面,提供生命周期管理
BaseNetWorkViewModel网络请求页面,自动处理 loading/error/success
BaseNetWorkListViewModel分页列表页面,内置下拉刷新和上拉加载

实战案例:聚合登录功能包

光说架构不够直观,我们直接看官方实现的 login 功能包 -- 一个支持华为账号一键登录、微信、支付宝、短信验证码的聚合登录模块,完整展示了功能包从契约定义到页面渲染的全流程。

功能包目录结构

packages/login/
├── LoginModule.ets                    # 模块生命周期(DI注册 + 路由 + 守卫)
├── view/
│   ├── LoginPage.ets                  # 主登录页(华为一键登录 + 三方登录)
│   └── SmsLoginPage.ets               # 短信验证码登录页
├── viewmodel/
│   ├── LoginViewModel.ets             # 登录业务逻辑
│   └── SmsLoginViewModel.ets          # 短信登录逻辑
├── components/
│   ├── AnimatedAuthPage.ets           # 认证页面基础布局
│   ├── PhoneInputField.ets            # 手机号输入组件
│   ├── VerificationCodeField.ets      # 验证码输入组件
│   ├── UserAgreement.ets              # 用户协议组件
│   └── ...
├── navigation/
│   ├── LoginNav.ets                   # 登录页导航构建器
│   └── SmsLoginNav.ets                # 短信登录页导航构建器
├── services/
│   └── AuthNavSvcImpl.ets             # 导航服务实现
└── models/
    └── Constant.ets                   # 三方 APP_ID 配置

View / ViewModel / Service / Navigation 各司其职,结构一目了然。

第一步:在 Shared 层定义契约

功能包对外暴露的能力,全部通过 Shared 层的接口契约来声明:

// shared/contracts -- 导航服务契约
export const AUTH_NAV_SVC_KEY: string = "authNavService";

export interface IAuthNavSvc {
  toLogin(): void;       // 跳转登录页
  toSmsLogin(): void;    // 跳转短信登录页
}

// shared/contracts -- 路由常量
export class AuthRoutes {
  static Login = "auth/login";
  static SmsLogin = "auth/sms-login";
}

其他功能包只需要依赖这些接口,不需要知道登录页长什么样、用了哪个 SDK。

第二步:注册模块 -- 一个类搞定 DI、路由、守卫

LoginModule 实现 FeatureModule 接口,框架会在启动时自动调用:

export class LoginModule implements FeatureModule {
  readonly moduleId: string = 'auth';
  readonly moduleName: string = '认证模块';
  readonly version: string = '1.0.0';
  readonly dependencies: string[] = [];

  // 注册服务到 DI 容器
  registerServices(container: Container): void {
    container.register<IAuthNavSvc>(AUTH_NAV_SVC_KEY, () => new AuthNavSvcImpl());
  }

  // 注册页面路由
  registerRoutes(registry: RouteRegistry): void {
    registry.register(AuthRoutes.Login, loginNavBuilderWrapper);
    registry.register(AuthRoutes.SmsLogin, smsLoginNavBuilderWrapper);
  }

  // 注册路由守卫 -- 未登录自动拦截
  registerGuards(navigationService: NavigationService): void {
    navigationService.registerGuard(new AuthGuard());
  }
}

三个方法,把服务注入、路由注册、登录守卫全部声明完毕。当用户访问受保护的页面时,AuthGuard 会自动检查登录状态,未登录则跳转到登录页:

class AuthGuard implements RouteGuard {
  readonly name: string = 'AuthGuard';
  readonly priority: number = 100;  // 优先级最高

  canActivate(context: RouteContext): boolean {
    return getUserState().isLoggedIn();  // 已登录放行,未登录拦截
  }

  onReject(context: RouteContext): void {
    // 拦截后自动跳转登录页
    navigation?.navigateTo(AuthRoutes.Login, context.params);
  }
}

第三步:ViewModel 封装业务逻辑

LoginViewModel 继承 BaseViewModel,集中处理多种登录方式:

@ObservedV2
export default class LoginViewModel extends BaseViewModel {
  @Trace anonymousPhone: string = "";  // 华为账号匿名手机号

  // 华为账号一键登录控制器
  huaweiLoginController: LoginWithHuaweiIDButtonController =
    new LoginWithHuaweiIDButtonController()
      .setAgreementStatus(AgreementStatus.ACCEPTED)
      .onClickLoginWithHuaweiIDButton((error, response) => {
        this.handleLoginWithHuaweiIDButton(error, response);
      });

  // 微信 OAuth 登录
  async onWechatLoginClick(): Promise<void> {
    const share: ShareWxSdk = new ShareWxSdk(APP_ID_WX);
    const res = await share.wechatOAuth("snsapi_userinfo", state, () => {});
    if (res instanceof SendAuthResp) {
      const userInfo = await share.getUserInfo(APP_SECRET_WX, res);
      // 处理登录结果...
    }
  }

  // 支付宝授权登录
  onAlipayLoginClick(): void {
    AFServiceCenter.call(AFService.AFServiceAuth, params);
  }
}

View 层不包含任何业务逻辑,只负责绑定 ViewModel 的状态和方法。

第四步:View 层 -- 声明式 UI 渲染

@ComponentV2
export struct LoginPage {
  @Local private vm: LoginViewModel = new LoginViewModel();

  build(): void {
    AppNavDestination({ viewModel: this.vm }) {
      // Logo
      LogoIcon()

      // 匿名手机号展示
      Text(this.vm.anonymousPhone)

      // 华为账号一键登录按钮
      LoginWithHuaweiIDButton({
        params: { style: Style.BUTTON_CUSTOM, loginType: LoginType.QUICK_LOGIN },
        controller: this.vm.huaweiLoginController,
      })

      // 短信验证码登录
      IBestButton({
        text: $r("app.string.sms_login"),
        onBtnClick: () => {
          getContainer().tryResolve<IAuthNavSvc>(AUTH_NAV_SVC_KEY)?.toSmsLogin();
        }
      })

      // 第三方登录(微信 / 支付宝 / QQ)
      this.buildThirdPartyLogin()

      // 用户协议
      UserAgreement()
    }
  }
}

注意跳转短信登录页时,通过 DI 容器解析 IAuthNavSvc 服务来导航,而不是直接引用目标页面 -- 这就是契约解耦的威力。

第五步:可复用组件沉淀

login 功能包还沉淀了一组可复用的认证 UI 组件:

// AnimatedAuthPage -- 认证页面通用布局,接收自定义内容
@ComponentV2
export struct AnimatedAuthPage {
  @Param title: ResourceStr = "";
  @BuilderParam content: CustomBuilder;

  build(): void {
    LargePaddingVerticalScroll({ fillMaxSize: true }) {
      Text(this.title).fontSize(28).fontWeight(FontWeight.Medium)
      SpaceVerticalXXLarge()
      if (this.content) { this.content(); }
    }
  }
}

// 短信登录页直接复用这个布局
AnimatedAuthPage({ title: $r("app.string.welcome_login") }) {
  PhoneInputField({ ... })
  VerificationCodeField({ ... })
  UserAgreement()
  IBestButton({ text: $r("app.string.login"), ... })
}

PhoneInputFieldVerificationCodeFieldUserAgreement 这些组件,在你开发注册页、找回密码页时可以直接复用。

小结:一个功能包的完整生命周期

Shared 层定义契约(接口 + 路由 + 类型)
        |
LoginModule 注册服务、路由、守卫
        |
ViewModel 封装业务逻辑(华为/微信/支付宝/短信)
        |
View 层声明式渲染(绑定 ViewModel,零业务代码)
        |
可复用组件沉淀(AnimatedAuthPage / PhoneInputField / ...)

这就是 HCompass 功能包的开发范式。每个功能包都遵循同样的模式,新成员看一个包就能上手所有包。


技术栈

技术用途
HarmonyOS NEXT开发平台
ArkTS / ArkUI开发语言与 UI 框架
@ohos/axiosHTTP 请求
@ibestservices/ibest-ui-v2UI 组件库
@ibestservices/ibest-orm数据库 ORM

谁适合使用 HCompass?

  • 独立开发者:快速搭建应用骨架,把精力放在业务创新上
  • 创业团队:统一技术栈和代码规范,降低协作成本
  • 企业开发团队:沉淀业务功能包,跨项目复用,提升交付效率
  • 鸿蒙生态贡献者:以功能包为单位贡献开源模块,共建生态

完善的文档体系

HCompass 提供了基于 VitePress 构建的完整文档站点,涵盖:

  • 快速开始 -- 从零搭建你的第一个 HCompass 应用
  • 架构设计 -- 深入理解四层架构和 MVVM 模式
  • 核心模块 -- DI、导航、网络、组件等每个模块的详细 API 文档
  • 功能包开发 -- 手把手教你创建和发布功能包
  • 最佳实践 -- 代码规范、性能优化、安全指南、状态管理

文档地址:https://hcompass.codelably.com


快速开始

# 克隆项目
git clone https://github.com/codelably/HCompass.git

# 使用 DevEco Studio 打开项目
# 等待依赖同步完成
# 运行 entry 模块即可体验

环境要求:

  • DevEco Studio 5.0+
  • HarmonyOS SDK 最新稳定版
  • Node.js 18.0+

开源协议

HCompass 基于 MIT 协议开源,你可以自由使用、修改和分发。

欢迎通过 Issue 和 Pull Request 参与项目建设,一起让鸿蒙开发更简单。


这不只是教程,是一份完整实录。(实在不会,丢给CC让他帮你安装)

从【以为很简单】到【真的跑通】,每一个报错、每一个坑、每一次修正,我都记下来了。

最终结果:从素材到公众号草稿箱,全链路自动化可用。


🤖 这个系统在做什么?

一句话:给素材 → 自动写文 → 自动排版 → 自动进公众号草稿箱。

系统流程图

我只做三件事:

  1. 写选题与要求(task.md)
  2. 放原始素材(materials.md)
  3. 审稿后点击发布

其余流程由系统自动完成。

这套系统的核心价值在于:把重复劳动交给机器,把创意决策留给人。


🧩 系统组成(最终可用架构)

整个系统由 5 个核心组件构成:

1. Claude Code CLI — 写作引擎

根据 task.md 和 materials.md 生成符合要求的 article.md。这是整个系统的大脑,负责理解需求、提炼素材、生成文章。

2. AGENTS.md — 写作规范

这是稳定输出的关键。AGENTS.md 是一个开放标准,用于指导 AI 编码代理的行为规范。

在内容创作场景中,它定义了写作风格、禁用词汇、排版规则等约束条件,确保 AI 生成的内容始终符合你的品牌调性。

3. KIE API — 图像生成

用于生成文章封面图。KIE.ai 提供了多种 AI 图像生成模型的统一接口,包括 GPT-Image-1、Midjourney、Flux 等,价格比官方更实惠,响应速度快。

4. ImageMagick — 图片处理

负责封面图的裁剪和尺寸处理。微信公众号封面图有特定尺寸要求:大图 900×383px (2.35:1),小图 383×383px (1:1)。

ImageMagick 可以通过命令行快速完成批量裁剪。

5. @wenyan-md/cli — 发布工具

将 Markdown 转换为微信公众号格式并推送到草稿箱。这个工具支持自动上传图片、应用主题样式、处理代码高亮和公式渲染。

技术架构图

完整流程:

素材(task.md + materials.md)

Claude Code CLI 读取 AGENTS.md 规范

生成 article.md

(可选)KIE API 生成封面图

ImageMagick 裁剪为 900×383px

wenyan publish 推送

公众号草稿箱 ✅


🛠 从零到跑通:完整过程

1)先盘点依赖

当时已完成:

  • ✅ Claude Code CLI
  • ✅ Node.js / Python3
  • ✅ ImageMagick

待完成:

  • ❌ 安装 wenyan 发布工具
  • ❌ 配置公众号 AppID/AppSecret
  • ❌ 配置微信 API IP 白名单
  • ❌ 配置 KIE API Key(可选)
  • ❌ 端到端发布测试

2)安装 wenyan(第一个坑)

一开始装错包名:

npm install -g wenyan-cli

报错 404(包不存在)。

正确包名是:

npm install -g @wenyan-md/cli

安装成功(1.0.11)。

**💡 经验:**包名容易搞混,遇到 404 先去 npm 官网确认正确的 package name。


3)公众号配置(第二个坑:命令用错)

一开始以为可以这样配:

wenyan config set appId xxx

wenyan config set appSecret xxx

结果报错:too many arguments

原因:这个版本的 wenyan 不支持这套 config 子命令。

正确方式:用环境变量(示例):

echo 'export WECHAT\_APP\_ID="你的AppID"' >> ~/.zshrc

echo 'export WECHAT\_APP\_SECRET="你的AppSecret"' >> ~/.zshrc

source ~/.zshrc

KIE 也是同理:

echo 'export KIE\_API\_KEY="你的KIE\_API\_KEY"' >> ~/.zshrc

source ~/.zshrc

**⚠️ 安全提醒:**AppSecret 只展示一次,若泄露请立即重置并更新本地环境变量。


4)IP 白名单(最大的坑)

这是我踩得最深的一个坑。

报错:

40164: invalid ip xxx.xxx.xxx.xxx, not in whitelist

**现象:**每次请求显示的出口 IP 都可能变化。

**根因:**VPN/代理自动切换节点导致出口 IP 不固定。

img_v3_02v7_7e0c24d1-eba0-4f9a-93bc-b6480cd4407g.jpg

我最终的解决方案:

① 查询当前出口 IP(通过本地代理):

curl -s --proxy http://127.0.0.1:7890 https://api.ipify.org

② 把返回 IPv4 加到公众号后台「API IP 白名单」

图片

③ 发布时固定走同一代理出口:

HTTPS\_PROXY=http://127.0.0.1:7890 HTTP\_PROXY=http://127.0.0.1:7890 \

wenyan publish -f "./article.md"

然后成功拿到 media_id,文章进入草稿箱。

**💡 经验:**如果你在国内服务器运行,直接用 curl -s https://ipinfo.io/ip 查询出口 IP 即可,不需要代理。


5)验收命令(替代 doctor)

wenyan doctor 在该版本不可用。

可以用渲染命令做最小验收:

echo "# 测试" | wenyan render | head -20

能正常输出 HTML 结构说明本地渲染链路没问题。

再用一次 wenyan publish 验证微信接口链路:

wenyan publish -f "./test-article.md"

image.png

✅ 最终可复用 SOP(精简版)

经过多次踩坑,我总结出这套可直接复用的标准流程:

Step 1:安装工具

npm i -g @wenyan-md/cli

brew install imagemagick  # macOS

Step 2:配置环境变量

export WECHAT\_APP\_ID="你的AppID"

export WECHAT\_APP\_SECRET="你的AppSecret"

export KIE\_API\_KEY="你的KIE\_API\_KEY"  # 可选

Step 3:公众号后台配置

  • 启用 AppSecret
  • 添加 API IP 白名单(填实际出口 IPv4)

Step 4:本地渲染测试

echo "# 测试" | wenyan render | head -20

Step 5:发布测试(必要时带代理)

HTTPS\_PROXY=http://127.0.0.1:7890 \

wenyan publish -f "./article.md"

Step 6:到草稿箱确认文章与封面

image.png

🕳️ 坑点总表(实战版)

现象

根因

解决方案

包名错误

npm 404

包名写错

用 @wenyan-md/cli

配置命令错误

too many arguments

版本不支持 config set

改用环境变量

IP 白名单反复失败

40164 invalid ip

代理出口 IP 漂移

固定节点+固定代理端口

封面报错

找不到 cover

frontmatter 缺字段/路径无效

补 cover 且路径有效

doctor 命令不存在

not valid command

该版本无此子命令

用 render + publish 验证

图片尺寸不对

封面显示不全

未按 2.35:1 裁剪

用 ImageMagick 裁剪为 900×383px

踩坑示意图


🎯 关键技术深度解析

AGENTS.md:稳定输出的灵魂

很多人以为 AI 写作就是“丢个 prompt 就行”。

错了。

真正决定质量的,是你的 AGENTS.md。

AGENTS.md 是一个由 Linux 基金会下属的 Agentic AI Foundation 管理的开放标准。它最初是为编码代理设计的,但现在被广泛应用于各种 AI 自动化场景。

它的作用是什么?

把你的部落知识(tribal knowledge)写成机器可读的规范。

就像资深工程师脑子里的那些“潜规则”:

  • 这个项目用什么技术栈
  • 代码风格是什么样的
  • 哪些文件绝对不能碰
  • 测试怎么跑

在内容创作场景中,AGENTS.md 定义的是:

  • 写作风格:口语化程度、段落长度、是否用 emoji
  • 禁用词汇:哪些 AI 味的词绝对不能出现
  • 排版规则:标题层级、引用格式、代码块样式
  • 质量标准:字数范围、数据引用要求、可读性基准

一个好的 AGENTS.md 应该包含:

  1. 可执行命令(Commands first): AI 能直接跑的指令
  2. 具体代码示例(Code examples over explanations):展示什么是好的输出
  3. 明确边界(Clear boundaries):告诉 AI 什么绝对不能做
  4. 技术栈细节(Project knowledge):版本号、文件位置、依赖关系

💡 最佳实践:

  • 控制在 ≤150 行,太长会稀释信号
  • 用反引号包裹命令,让 AI 能直接复制粘贴
  • 把 AGENTS.md 当代码一样做 code review
  • 当 AI 犯错时,迭代更新规范

我的 AGENTS.md 里写了什么?

  • 禁用“首先、其次、综上所述”等 AI 套话
  • 要求 70% 段落必须是单句(<50 字)
  • 数据必须加粗,引用必须用 > 格式
  • 标题不超过 35 字,必须包含数据+情绪反差

规范写得越细,AI 输出越稳定、越像你自己。

image.png


wenyan-md:从 Markdown 到公众号的最后一公里

wenyan 是一个开源工具,专门解决“Markdown → 微信公众号”这个痛点。

它做了这些事:

  1. 自动转换格式: Markdown 语法 → 微信富文本
  2. 图片自动上传:扫描文章中的图片路径,自动上传到微信素材库
  3. 主题样式应用:支持多种预设主题,也可以自定义 CSS
  4. 代码高亮:自动处理代码块的语法高亮
  5. 公式渲染:支持 LaTeX 数学公式
  6. 推送草稿箱:调用微信 API,直接创建草稿

关键 API 调用链路:

1. 获取 access\_token

POST https://api.weixin.qq.com/cgi-bin/token

2. 上传图片素材

POST https://api.weixin.qq.com/cgi-bin/material/add\_material

3. 创建草稿

POST https://api.weixin.qq.com/cgi-bin/draft/add

citation

wenyan 把这些复杂的 API 调用封装成一个简单的命令:

wenyan publish -f "./article.md"

这就是工程化的力量。


KIE API:多模型图像生成的统一入口

之前生成封面图,我要在不同平台之间切换:

  • Midjourney 要去 Discord
  • DALL-E 要去 OpenAI 后台
  • Stable Diffusion 要自己部署

现在有了 KIE API,一个接口调用所有主流模型。

image.png

支持的模型包括:

  • GPT-Image-1 / 1.5: OpenAI 最新图像模型,文字渲染准确
  • Flux.1 Kontext:开源模型,风格化能力强
  • Midjourney:商业插画首选
  • Nano Banana Pro: YouMind 自研,速度快

价格优势:

KIE 的定价比官方便宜 20-30%,而且按需付费,不需要订阅。

集成方式:

\# 设置 API Key

export KIE\_API\_KEY="your\_api\_key"

\# 生成图片(伪代码示例)

curl -X POST https://api.kie.ai/v1/images/generate \

-H "Authorization: Bearer $KIE\_API\_KEY" \

-d '{"prompt": "...", "model": "gpt-image-1"}'

生成的图片可以直接用 ImageMagick 裁剪:

magick input.jpg -crop 900x383+0+0 cover.jpg


ImageMagick:命令行图片处理的瑞士军刀

ImageMagick 是一个开源的图像处理工具,支持超过 100 种图片格式。

核心命令:

1. 裁剪图片

\# 从左上角裁剪 900×383 的区域

magick input.jpg -crop 900x383+0+0 output.jpg

\# 居中裁剪

magick input.jpg -gravity center -crop 900x383+0+0 output.jpg

2. 调整尺寸

\# 缩放到 900×383(可能变形)

magick input.jpg -resize 900x383 output.jpg

\# 按比例缩放,宽度固定 900px

magick input.jpg -resize 900x output.jpg

3. 批量处理

\# 批量裁剪当前目录所有 jpg

for img in *.jpg; do

magick "$img" -crop 900x383+0+0 "cropped\_$img"

done

💡 实战技巧:

微信公众号封面有大图(2.35:1)和小图(1:1)两种显示形式。如果想同时兼顾,可以做一张 1283×383px 的图:左边 383×383 是小图区域,右边 900×383 是完整大图。


🔄 完整自动化工作流实战

现在把所有组件串起来,看看一篇文章是怎么从 0 到发布的。

场景:我要写一篇“独立开发者搞钱案例”

Step 1:准备素材

创建 task.md:

\# 任务

将 YouTube 视频转换成公众号文章

\# 要求

- 字数 1500-2000

- 风格:信息猎手+卡兹克

- 必须包含数据和 SOP

创建 materials.md:

\# 素材

- YouTube 视频链接:https://youtube.com/watch?v=xxx

- 视频文字稿:(粘贴完整转录文本)

Step 2: Claude 自动写作

claude code --agents ./AGENTS.md \

"读取 task.md 和 materials.md,生成 article.md"

Claude 会:

  1. 读取 AGENTS.md 了解写作规范
  2. 分析 materials.md 提取核心信息
  3. 按照 task.md 的要求生成文章
  4. 输出 article.md
图片

Step 3:生成封面图(可选)

\# 调用 KIE API 生成封面

\# (这里可以用脚本或手动)

\# 裁剪为公众号尺寸

magick cover-raw.jpg -crop 900x383+0+0 cover.jpg

Step 4:发布到草稿箱

wenyan publish -f "./article.md"

Step 5:人工审核

登录公众号后台,在草稿箱中:

  • 检查排版是否正常
  • 确认图片是否都上传成功
  • 微调细节(如果需要)
  • 点击发布

整个流程,除了审核,全部自动化。

从素材到草稿箱,不超过 5 分钟


💡 进阶优化方向

系统跑通后,我又做了一些优化:

1)多平台发布

wenyan 不仅支持微信,还支持知乎、掘金、CSDN 等平台。

可以写一个脚本,一次生成,多平台分发:

\# 发布到微信

wenyan publish -f "./article.md" --platform wechat

\# 发布到知乎

wenyan publish -f "./article.md" --platform zhihu

2)定时发布

结合 cron 或 GitHub Actions,可以实现定时自动发布:

\# .github/workflows/publish.yml

name: Auto Publish

on:

schedule:

- cron: '0 9 * * *'  # 每天早上9点

jobs:

publish:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v2

- run: npm i -g @wenyan-md/cli

- run: wenyan publish -f "./article.md"

env:

WECHAT\_APP\_ID: ${{ secrets.WECHAT\_APP\_ID }}

WECHAT\_APP\_SECRET: ${{ secrets.WECHAT\_APP\_SECRET }}

3)内容质量监控

可以加一个 AI 审核层,在发布前自动检查:

  • 是否有敏感词
  • 字数是否达标
  • 是否有明显的 AI 痕迹
  • 数据引用是否准确

如果不通过,自动打回重写。

4)数据分析闭环

发布后,可以用 n8n 或 Zapier 监控文章数据:

  • 阅读量
  • 点赞数
  • 分享数
  • 用户评论

然后自动生成数据报告,反馈到下一次的 task.md 中,形成闭环优化。


🤔 这套系统值不值得搭?

如果你是:

  • 偶尔写一篇:不必折腾全自动,手动发布更快
  • 每周稳定更新:值得投入 1-2 天搭建
  • 批量产出内容:必须搞,能节省 80% 时间
  • 团队协作:非常值得,统一规范+自动化审核

投入产出比:

  • 搭建时间:1-2 天(包括踩坑)
  • 单篇文章节省时间:30-60 分钟
  • 回本周期:发布 5-10 篇文章后

但工具只是放大器。

真正决定内容质量的,永远是你的 AGENTS.md。

规范写得越细,AI 输出越稳定、越像你自己。

如果你的 AGENTS.md 只有 3 行,那 AI 生成的内容就是 3 行的质量。

如果你的 AGENTS.md 有 150 行细节约束,那 AI 就能生成 150 行质量的内容。

这是一个“垃圾进,垃圾出”(Garbage In, Garbage Out)的系统。

你投入多少,就能收获多少。


🎬 写在最后

这套系统从想法到跑通,我花了整整半天。

踩了很多坑。

但当我第一次看到文章自动出现在草稿箱时,那种成就感是无法替代的。

这不仅仅是一个自动化工具。

它改变了我对内容生产的理解:

从手工作坊 → 流水线工厂。

以前写一篇文章,我要:

  • 找素材(30 分钟)
  • 写初稿(2 小时)
  • 排版(30 分钟)
  • 配图(30 分钟)
  • 上传发布(10 分钟)

总计:3 小时 40 分钟。

现在:

  • 准备素材(10 分钟)
  • 运行脚本(2 分钟)
  • 审稿微调(20 分钟)

总计:32 分钟。

效率提升了 7 倍。

效率对比

更重要的是,我的精力被解放了。

我不再纠结【这个词用得对不对】、【这个段落要不要换行】。

我只需要关注:这个选题值不值得写?这个观点够不够深刻?

工具处理执行,人类负责决策。

这才是 AI 时代内容创作的正确姿势。

如果你也想搭建这样一套系统,我的建议是:

别怕踩坑。

每一个报错,都是你对系统理解更深一层的机会。

每一次调试,都是你把“隐性知识”变成“显性规范”的过程。

当你把所有坑都踩完,你就拥有了一套真正属于自己的内容生产流水线。

而这套流水线,会成为你最大的竞争壁垒。


P. S. 如果你在搭建过程中遇到问题,欢迎留言交流。我会持续更新这套系统的优化方案。

此即未来。

图片

最近经常逛公园,对遇到的植物感兴趣但是浏览器自带的拍照识物不好用,V 友有推荐的工具吗?最好是可以接入 API 的,自己也可以做一些小玩意~

给定一个 host.html

<!doctype html>
<meta charset="utf-8" />
<title>host</title>

<iframe
  id="outer"
  sandbox="allow-scripts"
  src="https://another-site/sandbox.html"
></iframe>

其中 sandbox.html

<!doctype html>
<meta charset="utf-8" />
<title>sandbox</title>

<script>
  const inner = document.createElement('iframe');
  inner.src = 'https://another-another-site/inner.html';
  document.body.appendChild(inner);
</script>

其中 inner.html

<!doctype html>
<meta charset="utf-8" />
<title>inner</title>

<script>
    const v = localStorage.getItem('no_such_key');
    console.log('value:', v);
</script>

问:试分析localStorage.getItem('no_such_key')的行为


我的回答:这大概率会抛出一个 SecurityError ,我的映像里因为 sandbox 的缘故 iframe 里面的网页浏览器是不会指定 origin 的。没有 origin 的话大概率 localstorage, session storage, index db 这一类都不可用

Follow up

  1. iframe src 和 iframe origin 的关系
  2. sandbox 里哪个属性控制这个行为?
  3. 在 inner html 里类似 fetch(..., { credentials: "include" })请求在哪些情况会带上 cookie

在软件分发领域,代码签名证书是保障代码完整性、验证开发者身份的核心工具。随着用户安全意识提升和操作系统对未签名软件的拦截机制强化,开发者需在EV(扩展验证)与OV(组织验证)两类证书中做出选择。本文从技术原理、验证流程、应用场景及成本效益四大维度展开分析,为开发者提供决策参考。
代码签名证书申请入口,填写注册码230959兑换

一、技术原理:信任链构建的底层逻辑

1. EV代码签名证书:基于硬件加密的强信任体系

EV证书采用FIPS 140-2认证的USB Key存储私钥,通过物理隔离防止私钥泄露。其技术核心在于:

  • 双重验证机制:结合企业身份验证与硬件令牌签名,确保代码签名过程不可篡改;
  • 时间戳服务:证书附带时间戳,即使证书过期,已签名的代码仍可验证有效性;
  • 内核驱动签名支持:唯一支持Windows内核模式驱动签名的证书类型,满足微软WHQL认证要求。

2. OV标准证书:平衡安全与效率的解决方案

OV证书通过企业组织信息验证构建信任链,技术特点包括:

  • 单因素身份验证:仅需验证企业营业执照、注册地址等基础信息;
  • 多平台兼容性:支持.exe、.dll、.msi等32/64位文件格式,覆盖99.9%主流操作系统;
  • 动态信任积累:通过持续签名行为建立软件信誉,逐步消除SmartScreen警告。

二、验证流程:从申请到签发的全链路对比

1. EV证书:5-7个工作日的深度审核

EV证书的验证流程包含以下关键环节:

  • 企业资质审查:验证营业执照、税务登记证等法律文件的有效性;
  • 物理地址核验:通过邓白氏编码、工商公示系统确认企业实际运营地址;
  • 电话回访确认:CA机构致电企业注册电话,核实申请人身份及授权信息;
  • 硬件令牌发放:审核通过后邮寄USB Key,开发者需在专用设备上完成签名。

案例:某金融科技公司申请EV证书时,因注册地址与办公地址不一致,需补充提供租赁合同及水电费账单,导致审核周期延长至9个工作日。

2. OV证书:1-3个工作日的标准化流程

OV证书的验证流程相对简化:

  • 基础信息提交:上传营业执照、法人身份证等扫描件;
  • 公开信息交叉验证:通过国家企业信用信息公示系统核验企业状态;
  • 电子化签发:审核通过后直接下载证书文件,无需硬件设备。

数据:根据JoySSL统计,OV证书平均签发时间为1.8个工作日,92%的申请可在24小时内完成。

三、应用场景:安全需求与成本敏感度的博弈

1. EV证书的强制适用场景

  • Windows驱动开发:微软要求所有内核模式驱动必须通过EV签名,否则无法加载;
  • 高风险软件分发:涉及用户隐私数据(如医疗、金融类应用)需满足等保2.0三级要求;
  • 品牌信任建设:新品牌软件通过EV证书的“绿色地址栏”标识快速建立市场认知。

案例:某安全软件厂商使用EV证书后,软件安装转化率提升37%,用户投诉率下降62%。

2. OV证书的性价比优势场景

  • 企业级应用更新:已有用户基础的软件通过OV证书维持信任,避免EV证书的高成本;
  • 开源项目维护:社区驱动型项目需低成本验证代码完整性,OV证书可满足基本需求;
  • 内网系统部署:如使用JoySSL OV内网IP证书,可为局域网服务提供企业级加密与身份验证。

数据:OV证书年均成本约为EV证书的65%,但可覆盖80%的常规软件分发场景。

四、成本效益分析:长期投资回报率计算

1. 直接成本对比

表格

证书类型年均费用(元)硬件成本审核周期续费成本
EV证书5,300含USB Key5-7天与首年同
OV证书3,8001-3天降价5%

:JoySSL等厂商提供OV证书首年优惠,实际成本可低至3,000元/年。

2. 隐性成本考量

  • EV证书

    • 优势:减少用户流失带来的潜在收入损失(据统计,未签名软件安装放弃率达41%);
    • 风险:硬件令牌丢失需支付2,000元补办费用。
  • OV证书

    • 优势:无需承担硬件管理成本,适合DevOps自动化流程;
    • 风险:新软件首次安装仍可能触发SmartScreen警告,需通过持续签名积累信誉。

3. 长期ROI模型

以年分发量10万次的软件为例:

  • EV证书

    • 成本:4,800元/年
    • 收益:安装转化率提升25% → 新增用户2.5万 → 假设单用户ARPU值50元 → 年增收125万元
  • OV证书

    • 成本:3,800元/年
    • 收益:安装转化率提升15% → 新增用户1.5万 → 年增收75万元

结论:高风险、高价值软件优先选择EV证书;标准化企业应用OV证书更具经济性。

五、JoySSL的差异化解决方案

作为国内领先的数字证书服务商,JoySSL提供以下创新服务:

  1. 双算法支持:OV/EV证书均支持RSA 2048/4096与国密SM2算法,满足等保合规要求;
  2. 内网IP证书:针对无域名场景推出OV级内网IP证书,价格仅为公网IP证书的80%;
  3. 自动化工具链:集成SignTool、jsign等签名工具,支持CI/CD流水线无缝对接;
  4. 7×24小时技术支持:提供证书吊销、私钥恢复等应急服务,降低运维风险。

案例:某制造业企业通过JoySSL OV内网IP证书,在3天内完成200台工业设备的HTTPS改造,年节省加密设备采购成本超50万元。

六、决策框架:三步选择法

  1. 评估安全等级

    • 涉及用户隐私/核心资产 → EV证书
    • 内部工具/开源项目 → OV证书
  2. 计算成本阈值

    • 预期增收>EV证书成本3倍 → 投资EV
    • 预算有限且需快速部署 → 选择OV
  3. 测试兼容性

    • 使用JoySSL免费试用证书验证目标平台支持情况

在软件供应链安全日益严峻的今天,代码签名证书已从“可选配置”升级为“必选项”。开发者需根据自身安全需求、成本预算及业务发展阶段,在EV与OV证书间做出理性选择。对于大多数企业而言, “OV证书打基础+EV证书保核心” 的混合策略,或许是平衡安全与成本的最优解。

DOM 和 SVG 为什么第一个被淘汰?

很多人会问:为什么不用 DOM?为什么不用 SVG?

答案其实很残酷。

浏览器为了渲染 DOM 和 SVG 节点,底层维护了一套极其庞大的对象模型和布局引擎。每一次你在触控板上轻轻一划,哪怕只是让相机的世界坐标移动几个像素,都可能被迫引发大量节点的重排(Reflow)与重绘(Repaint)

当节点树突破临界点,主线程被压垮,原本丝滑的缩放和平移就会掉进帧率泥潭。

无限画布的核心诉求只有一个:

无论同屏多少元素,都要稳定 60fps。

想达成这一点,你必须脱离浏览器的排版流,进入纯粹的"像素缓冲区"世界。

在当前的 Web 技术栈中,能承载这个目标的只有两条路:Canvas 2DWebGL

它们不是同一个问题的两种解法,而是两种截然不同的工程解法。


Canvas 2D:浏览器替你扛住复杂度

Canvas 2D 是绝大多数画布项目的起点——在线白板、流程图工具、轻量设计工具。

原因很简单:它太好用了。

画一个带边框的矩形:

ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.strokeStyle = "#000000";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(10, 10, 100, 100, [5]);
ctx.fill();
ctx.stroke();

想画曲线就调用 quadraticCurveTo,想写字就 fillText,想加阴影就配置 shadowBlur

Canvas 2D 给你的不是 GPU 访问权,而是一套图形语义 API——你在表达"我要画什么",而不是"我要如何让 GPU 算出这些像素"。底层可能是 Skia、CoreGraphics 或 Direct2D,你不需要知道。

这就是它的价值。

但这背后有个隐藏代价:你把控制权交给了浏览器。

Canvas 2D 的指令调用是单向且串行的,绝大多数场景依赖 CPU 处理绘图指令。当你同屏几万个图形,每一帧都要重新执行所有 draw 指令。哪怕只是平移一个像素,也要把全部图形循环重绘一遍。

你没法告诉浏览器:

"把这批图形放到显存里,下次只改变换矩阵就行。"

Canvas 不给你这个能力。

所以当大量使用图层合成、全局阴影时,每帧的绘制时间很容易突破 16.6ms 的底线。

对于中等规模应用,Canvas 2D 性价比极高。但当你开始追求"Figma 级别"的体验时,它会成为瓶颈。


WebGL:你需要接管整个物理世界

跨入 WebGL 的那一刻,你会遇到一个残酷现实:

这个世界里没有矩形。只有三角形。

我们来做一个直观对比。

在 Canvas 2D 里画一个红色矩形:

ctx.fillStyle = "red";
ctx.fillRect(10, 10, 100, 100);

2 行代码。

在 WebGL 里画同一个矩形:

// 1. 定义矩形四个顶点的坐标(拆分成两个三角形)
const vertices = new Float32Array([
  -0.5,
  0.5, // 左上
  -0.5,
  -0.5, // 左下
  0.5,
  0.5, // 右上
  0.5,
  -0.5, // 右下
]);

// 2. 将数据送入显存 Buffer 中
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// 3. 编写 Vertex Shader 算坐标,编写 Fragment Shader 涂成红色
// ...(此处省略几十行 Shader 编译与变量绑定代码)...

// 4. 通知 GPU 绘制这两个三角形(Triangle Strip)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

如果你想画一个"带圆角的 2 像素红框矩形"呢?

在 WebGL 中,连 roundRect 都没有。你需要用数学算法算出圆弧上的离散点,将边框转换成一个个细小的三角形,然后还要自己在着色器里处理抗锯齿(Anti-Aliasing)。

甚至连文字排版都是个难题。WebGL 本身不懂文字——你要么把文字先用 Canvas 渲染成图片,再贴到 WebGL 里作纹理(Texture),要么引入复杂的 SDF(有向距离场)算法让 GPU 渲染出不失真的文字轮廓。

复杂度是指数级增长的。

选择 WebGL,你不只是在换一个 API。你是在决定成为一个"引擎开发者"。


真正的问题不是性能

我后来想明白了一件事:

大多数人不是死在 Canvas 性能不够,而是死在过早复杂化。

如果你的同屏元素不超过 5000,Canvas 2D 够用。如果你连空间索引、脏矩形优化都没做,谈 WebGL 就是在逃避真正的问题。

但如果你的目标是做重型设计工具级产品——WebGL 是绕不过的坎。

区别在于:你是否愿意为"控制权"买单。


我们的选择:现实、克制

关于 Canvas 还是 WebGL,没有统一的正确答案。取决于你的产品对同屏元素量级的刚需,以及团队对底层图形基建的掌控力。

本系列不会从零手写 WebGL 引擎。

我们选择基于 Canvas 2D,但不会裸写绘制指令。

我们会引入 Konva.js 作为底层支撑。Konva 是一个基于 Canvas 2D 的场景图引擎,自带图元管理、分层渲染和像素级事件系统。我们将在此基础上,剥离前端框架(React/Vue)的边界,用纯 TypeScript 构建一个接近 Excalidraw 架构的轻量级无限画布。

这不是性能最极限的方案。

但它是当前阶段最诚实的解法。


真正的深水区:交互

无论 Canvas 还是 WebGL,它们本质上都只是一块"死板"的像素画布。

当你脱离 DOM,你就失去了节点树。

浏览器的 click 事件只能告诉你:用户点在了 { x: 500, y: 400 }

但不会告诉你:这是第 7823 个图元。

悬停、拖拽、框选、事件冒泡——全部失效。

当画布变成一个没有结构的像素世界,点击本质上变成了一道几何题

下一篇,我们用真实代码来解这道题:在没有 DOM 的"野生像素图"上,从零实现空间检测与事件分发系统。

1.项目成功上线
老板:AI 果然强大,可以把人裁了,换些实习生来,节约成本
2.项目不符合预期
老板:这些个员工 AI 都用不明白,不思进取,裁了,换一批

在如今的数字化时代,软件分发面临着严峻的安全挑战。当用户下载未签名的软件时,系统往往会弹出“未知发布者”的安全警告,这极大地影响了软件的口碑与下载转化率。代码签名证书正是解决这一信任难题的基础设施。

一、什么是代码签名证书?

代码签名证书是一种基于公钥基础设施(PKI)技术的数字证书,专门用于软件开发者对其开发的代码(如.exe、.dll、.msi、.zip等文件)进行数字签名。

它的核心功能就像软件的“数字身份证+防伪封条”:

  1. 身份验证:当用户下载软件时,系统会显示发布者的真实名称(如“XX公司”),证明该软件确实出自合法的开发者之手,而非山寨或仿冒品。
  2. 完整性保护:签名技术确保代码自签名后未被恶意篡改或植入病毒。一旦被修改,签名就会破裂,系统会警告用户文件已损坏或不安全。

二、为什么要部署代码签名证书?

在主流操作系统(Windows、macOS)和安全软件严格拦截未知名软件的今天,部署代码签名证书已成为刚需:

  • 消除安装屏障:避免“Windows Defender SmartScreen”或浏览器拦截,提升软件安装成功率。
  • 建立品牌信誉:特别是EV(扩展验证)证书,能让用户第一时间看到绿色标识的企业信息,极大增强信任感。
  • 提升下载转化率:据统计,经过签名的软件下载转化率可提升42%以上。

三、怎么申请代码签名证书?(以JoySSL为例)

申请代码签名证书通常需要经历选择类型、提交验证、获取证书三个步骤。目前,国产自主数字证书品牌JoySSL因其支持国内验证流程、兼容国际标准且提供自动化管理工具,成为众多开发者的新选择。

以下是具体的申请与部署流程:代码签名证书申请入口

1. 选择证书类型

首先,访问JoySSL官网,根据你的身份和需求选择证书:

  • 个人开发者:可选择个人证书或OV证书。
  • 企业用户:推荐OV(组织验证)或EV(扩展验证)证书。EV证书能即时通过微软SmartScreen信誉认证,适合商业化软件。
    注:注册JoySSL账户时,通常可填写推荐码 230970 以获取技术指导或优惠套餐
2. 提交CSR与验证材料
  • 生成CSR:在本地生成密钥对并创建证书签名请求(CSR)。
  • 提交信息:企业用户需提供营业执照、法人身份证等信息。JoySSL支持AI自动识别营业执照,将OV证书签发时间缩短至约2小时。
  • 验证流程:CA机构会通过电话或邮件回访,核实申请意愿(部分EV证书可能涉及视频面签)。
3. 下载证书并签名

审核通过后,下载证书文件(通常为PFX格式,包含私钥)。使用签名工具(如Windows SDK的signtool.exe)对软件进行签名并添加时间戳。

目前自己已有的选项

预想电脑准备买 Mac Studio
可以跑线上,这个还好
本地可以跑模型 16b 或者 32b 数据的

大概这么多,各位还有什么其他看法,建议买哪个的.


就是自己做股票一些资料收集,通知我,简单的分析下

本地模型只要能支持这个 16b 的模型就行,大部分时间模型还是跑线上的

先给结论:

核心结论: 不是没用 Redis Scheduler,而是 dont_filter=True 跳过了去重器,所以 Redis 里不会创建 dupefilter 这个 Key。

问题背景

在 scrapy-redis 爬虫中,如果不重写 start_requests(),而是使用父类默认方法,会发现 Redis 里没有 xxx:dupefilter 这个 Set。


调度流程

Spider → Engine → Scheduler → DupeFilter(去重器)

是否触发去重,取决于 request.dont_filter 的值。


父类 start_requests() 做了什么?

scrapy_redis.spiders.RedisSpider 默认实现大致如下:

yield Request(url, dont_filter=True)

关键就在 dont_filter=True


调度器内部逻辑

Scheduler.enqueue_request() 中有如下判断:

if not request.dont_filter:
    if self.df.request_seen(request):
        return False

逻辑链路:

  1. dont_filter=True → 跳过去重逻辑
  2. 不调用 request_seen()
  3. 不执行 SADD top250:dupefilter fingerprint
  4. Redis 里不创建 dupefilter Key

对比验证

场景方式Redis dupefilter
使用父类默认 start_requests()dont_filter=True❌ 不创建
自己重写,使用默认 Requestdont_filter=False(默认)✅ 创建

自己重写示例:

def start_requests(self):
    yield scrapy.Request(url)  # dont_filter 默认为 False,会触发去重

dont_filter=True 的影响范围

dont_filter=True 只影响

  • 是否调用去重器(DupeFilter)

不影响

  • 是否使用 Redis Scheduler
  • 是否使用 Redis 队列

为什么 scrapy-redis 这样设计?

RedisSpider 作为分布式爬虫入口,URL 是从 Redis 队列 lpush 进来的,设计假设是:

  • 入口 URL 由外部控制,不需要在 start_requests 阶段去重
  • 若默认开启去重,多个节点并发启动时,start URL 可能被直接拦截

所以默认 dont_filter=True,跳过去重。


总结

问题答案
是否使用 Redis Scheduler?
为什么没有 dupefilter?因为 dont_filter=True,跳过了去重
是不是没用 Redis?不是
去重什么时候发生?dont_filter=False

本质理解:dont_filter 控制的是单个 Request 是否走去重流程,与使用哪个 Scheduler 无关。

TL;DR (太长不看版):
这不是一个套壳大模型的骗钱项目,也不是一个纯 CRUD 的软件外包。
我们正在打造一台分布式、AI 驱动的黑水虻幼虫( BSFL )生物转化设备。目标是将这台包含了边缘视觉防呆、微气候控制和活体养殖的“魔法黑盒”铺设到全国的 B 端(食品生产、加工、消费时产生资源浪费的环节),把有机食品废弃物就地转化为高价值的昆虫蛋白和碳资产。输入是毫无价值甚至恶心的食物垃圾,系统产出的是真实的蛋白质饲料换来的金钱及 ESG 资产。

目前商业模式已闭环,已获得顶级资助人的充足 MVP 阶段研发资金。
我是带着 Z 企资源、场景和商业 Ground Truth 来的,现在缺一个能把 Jetson 、ESP32 、电机和传感器缝合在一起的物理世界破壁人。



🌍 背景与我的“不公平优势” (Why Me?)
我不是技术出身,我是做传统 500 强企业战略与投资一把手出身的,拥有 GIS (地理信息系统)和环境设计硕博背景。

你可能觉得这履历跟硬科技不沾边,但在这个赛道,这是降维打击的武器:

我有底层的商业与数据 Ground Truth: 我曾主导投资过深圳盐田区日吞吐量 15 吨的黑水虻幼虫工厂。我知道投入产出真实工艺参数。我们不需要在实验室里瞎猜转化率。

我搞得定“机器外面的世界”: 环保赛道的本质是“跟城市抢垃圾,跟大企业拿碳汇,跟政府拿补贴”。怎么选址布点、怎么搞定餐厨的入场权、怎么把减碳量做成大企业的 ESG 报告,这都是我的强项。

我们有不焦虑的研发弹药: 我们有充足的资金支持。在接下来的 4 个月里,我们不需要去给 VC 画大饼,我们有足够的预算去买工业级的硬件,专心致志地把 MVP 在真实食堂里跑通。

简单来说:机器外面的脏活累活和找钱,我全包了;机器里面的代码和电路,是你的领地。



🛠️ 我们要造什么?(The Engineering Challenge)
我们要在 4 个月内,造出一台能在真实食堂连续 30 天无专业人员干预、稳定运行的 MVP 原型机。
你需要解决两个极其硬核的物理世界难题:

1. 视觉防呆与异物拦截 (The AI Bouncer): 食堂大妈可能会把塑料袋或易拉罐扔进去,这会弄死所有虫子。你需要用 Jetson Orin 跑通本地的轻量级目标检测模型(如 YOLO ),配合摄像头和机械推杆,精准识别并把致命异物踢出去。

2. 恶劣环境下的自适应微气候 (The Survival Engine): 黑水虻代谢会产生高温和高浓度氨气。你需要用 ESP32 (或其他下位机)串联温湿度、气体传感器和继电器,写出极其鲁棒的控制逻辑,动态控制风扇和加热,保证生物群落不崩溃。

🫵 我在寻找怎样的你?(Who Are You?)
我不在乎你有没有大厂的光环,不在乎你的穿衣打扮,甚至不在乎你是不是计算机科班出身。我看重的是你解决真实物理世界 Bug 的直觉。

必须具备的硬技能:

全栈 IoT 直觉: 能熟练使用 Python 和 C/C++。能让高层算力板( Jetson/树莓派)和底层控制板( ESP32/STM32 )通过串口稳定通信。

边缘 AI 部署: 搞得定摄像头接入,懂一点 OpenCV ,能在本地边缘设备上流畅部署轻量级视觉模型。

动手能力拉满: 会画简单的 PCB ,会选型步进电机和推杆,拿得起电烙铁,能把一堆散件组装成一个能动的物理系统。

极其重要的隐形特质:

“闻得惯臭味”: 我们的测试环境是真实的厨余垃圾和蠕动的昆虫。如果你只喜欢在 24℃ 的恒温写字楼里敲键盘,千万别来;如果你觉得看着一堆烂菜叶在传感器和代码的指挥下变成金灿灿的蛋白是一件极度浪漫的事,那你就是我们要找的人。

极端务实: 知道在 MVP 阶段,工业级的稳定比花哨的炫技更重要。

🎁 你将获得什么?
联合创始人级别的 Equity (股权)。

理性条件下充足的硬件采购预算。 不要委屈自己买劣质电子垃圾,我们用最好的工业级元件来堆冗余。

一段绝对纯粹的硬科技创业经历:没有官僚主义,没有无意义的周报,只有真实物理世界的挑战和立刻能看到的环保社会价值。

☕ Next Step:
别发我那种写满“精通协同办公”的标准格式简历。
如果你感兴趣,带上你业余时间自己动手做过的最酷软硬件项目( GitHub 链接、一段视频、几张电路板照片都行),或者告诉我你曾经排查过的一个最恶心的硬件 Bug 。

地理坐标:中国深圳。我们喝杯咖啡,我把这套商业逻辑的全盘托出给你看。

联系方式: [email protected]

新春快乐,祝 V2 的老哥、靓女们,新春大吉,身体健康,马年鸿运当头,万事顺遂!
开工大吉,宅家上网、打游戏、炒股、交易赚钱、亲戚串门连 Wi-Fi ,没个稳如老狗的网络怎么行?
趁着新春佳节,继续分享现阶段我老婆最常受理的广州电信套餐,给各位准备升级宽带、或者给家里长辈拉网的老哥参考。
如有需要在广州报装宽带或相关咨询,可联系贱内 Base64: MTgwMTE4ODA2Njk=

🧨 第一种,费用 149/月(新春强烈推荐)
适合个人、小家庭、多人合租、公寓、商铺等使用。这个套餐最香的一点是: 如果家庭成员有其它运营商的,可以不换号直接绑定到此套餐,这样就直接把家人其他卡的月租给免了!全家共享,绝对的每月话费节省利器。

🧨 第二种,费用 199/月
适合对网络要求高的大家庭或商务办公使用。过年亲朋好友来串门,十几台设备同时连 Wi-Fi 抢红包、打王者、看 4K 电影都不带卡顿的,越用越省心。