2026年2月

点赞 + 关注 + 收藏 = 学会了

整理了一个NAS小专栏,有兴趣的工友可以关注一下 👉 《NAS邪修》

给 NAS 设置静态 IP,核心是解决默认 DHCP 自动分配导致 IP “乱跑” 的问题,相当于给 NAS 在局域网内固定专属 “门牌号”。

尤其是用 Docker 部署了一堆应用,要是 NAS 的 IP 一变,在 Docker 部署的应用也要跟着变了。

我手上有群晖、绿联、飞牛这3品牌的 NAS,我分别说说这几个品牌的 NAS 在哪可以设置静态 IP。

群晖

打开「控制面板」,选择「网络」。

切换到「网络界面」面板,选择当前连接的「局域网」,可以看到现在使用的是 DHCP。

点击「编辑」按钮,将“IPv4”这项改为“手动设置网络配置”,输入一个 IP 即可。

保存好之后,回到「网络界面」面板就能看到当前已经将「局域网 1」设置为静态 IP。

绿联

打开「控制面板」,选择「网络设置」。

切换到「网络连接」面板,选择已连接的“LAN”,点击编辑。

将「IPv4」改为“手动设置网络配置”,然后填入一个“IPv4地址”。

设置完,回到「网络连接」面板就能看到刚刚设置的这项多了一个“静态”标识。

飞牛

打开「系统设置」,切换到「网络设置」面板,点击当前正在使用的「网口」的右侧的3个点,然后点击“编辑”。

在编辑面板,将「IPv4」这边改为“手动设置”,然后输入一个「IPv4」地址即可。

回到「网络设置」面板就能看到“IP 获取方式”这项从原本的“DHCP”变成“手动”了。


以上就是本文的全部内容啦,有疑问可以在评论区讨论~

想了解更多NAS玩法可以关注《NAS邪修》👏

点赞 + 关注 + 收藏 = 学会了

大家好,我是良许。

最近几年,随着物联网设备的爆发式增长,嵌入式系统的安全问题越来越受到重视。

我记得几年前做汽车电子项目的时候,客户对安全的要求还比较宽松,但现在不一样了,几乎每个项目都会被问到"你们的加密方案是什么?

""如何防止固件被破解?"这些问题。

今天就和大家聊聊嵌入式安全和加密技术这个话题。

1. 为什么嵌入式系统需要安全保护

1.1 嵌入式系统面临的安全威胁

嵌入式设备不像服务器那样有专人维护,它们往往部署在各种环境中,面临的威胁也更加多样化。

我在做汽车电子的时候就遇到过这样的案例:有黑客通过 CAN 总线注入恶意指令,导致车辆的某些功能异常。

这还只是冰山一角,实际上嵌入式系统面临的威胁包括:

物理攻击:攻击者可以直接接触到设备,通过 JTAG、UART 等调试接口读取内存数据,甚至可以用显微镜观察芯片内部结构进行侧信道攻击。

我见过有人用热风枪把 Flash 芯片拆下来,直接用编程器读取固件。

网络攻击:现在的嵌入式设备大多联网,黑客可以通过网络漏洞入侵设备。

比如很多智能家居设备使用弱密码或者默认密码,很容易被攻破。

固件逆向:攻击者获取固件后,可以通过反汇编、反编译等手段分析代码逻辑,找出加密算法和密钥,甚至可以修改固件植入后门。

供应链攻击:在生产、运输、维护等环节,设备可能被植入恶意代码或硬件木马。

1.2 安全防护的必要性

对于我们做嵌入式开发的来说,安全不再是可选项,而是必选项。

特别是在汽车、医疗、工控等关键领域,一旦出现安全问题,后果不堪设想。

我之前在外企做项目的时候,有一次因为安全测试没通过,整个项目延期了三个月,损失了上百万。

所以现在我做项目,都会在设计阶段就把安全考虑进去,而不是等到最后才想起来打补丁。

2. 嵌入式加密技术基础

2.1 对称加密算法

对称加密是最常用的加密方式,加密和解密使用同一个密钥。

在嵌入式系统中,AES(高级加密标准)是最流行的对称加密算法。

AES 加密原理:AES 支持 128 位、192 位、256 位三种密钥长度,加密过程包括字节替换、行移位、列混淆、轮密钥加等步骤。

对于嵌入式系统来说,AES-128 通常就足够了,因为它在安全性和性能之间取得了很好的平衡。

下面是一个使用 STM32 的硬件 AES 加密的示例代码:

#include "stm32f4xx_hal.h"
​
CRYP_HandleTypeDef hcryp;
​
// 初始化AES硬件加密模块
void AES_Init(void)
{
    __HAL_RCC_CRYP_CLK_ENABLE();
    
    hcryp.Instance = CRYP;
    hcryp.Init.DataType = CRYP_DATATYPE_8B;
    hcryp.Init.KeySize = CRYP_KEYSIZE_128B;
    hcryp.Init.Algorithm = CRYP_AES_ECB;
    
    if (HAL_CRYP_Init(&hcryp) != HAL_OK)
    {
        Error_Handler();
    }
}
​
// AES加密函数
HAL_StatusTypeDef AES_Encrypt(uint8_t *plaintext, uint8_t *key, 
                               uint8_t *ciphertext, uint16_t length)
{
    HAL_StatusTypeDef status;
    
    // 设置密钥
    hcryp.Init.pKey = (uint32_t *)key;
    
    if (HAL_CRYP_Init(&hcryp) != HAL_OK)
    {
        return HAL_ERROR;
    }
    
    // 执行加密
    status = HAL_CRYP_Encrypt(&hcryp, (uint32_t *)plaintext, 
                              length/4, (uint32_t *)ciphertext, 1000);
    
    return status;
}
​
// AES解密函数
HAL_StatusTypeDef AES_Decrypt(uint8_t *ciphertext, uint8_t *key, 
                               uint8_t *plaintext, uint16_t length)
{
    HAL_StatusTypeDef status;
    
    hcryp.Init.pKey = (uint32_t *)key;
    
    if (HAL_CRYP_Init(&hcryp) != HAL_OK)
    {
        return HAL_ERROR;
    }
    
    status = HAL_CRYP_Decrypt(&hcryp, (uint32_t *)ciphertext, 
                              length/4, (uint32_t *)plaintext, 1000);
    
    return status;
}

这段代码展示了如何使用 STM32 的硬件加密模块进行 AES 加密和解密。

使用硬件加密的好处是速度快、功耗低,而且密钥不会暴露在软件中,安全性更高。

我在实际项目中,只要芯片支持硬件加密,都会优先使用。

2.2 非对称加密算法

非对称加密使用一对密钥:公钥和私钥。

公钥用于加密,私钥用于解密。

最常用的非对称加密算法是 RSA 和 ECC(椭圆曲线加密)。

RSA vs ECC:在嵌入式系统中,我更倾向于使用 ECC 而不是 RSA,原因很简单:ECC 在相同安全强度下,密钥长度更短,计算速度更快,占用的存储空间也更小。

比如 256 位的 ECC 密钥,安全强度相当于 3072 位的 RSA 密钥。

下面是一个使用 mbedTLS 库进行 ECC 签名验证的示例:

#include "mbedtls/ecdsa.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
​
// ECC签名验证函数
int verify_firmware_signature(uint8_t *firmware, size_t fw_len, 
                               uint8_t *signature, size_t sig_len)
{
    int ret;
    mbedtls_ecdsa_context ctx;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    uint8_t hash[32];
    
    // 初始化
    mbedtls_ecdsa_init(&ctx);
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    
    // 计算固件的SHA256哈希
    mbedtls_sha256(firmware, fw_len, hash, 0);
    
    // 加载公钥(这里假设公钥已经存储在设备中)
    // 实际项目中,公钥通常烧录在OTP区域或者安全存储区
    ret = load_public_key(&ctx);
    if (ret != 0)
    {
        goto cleanup;
    }
    
    // 验证签名
    ret = mbedtls_ecdsa_read_signature(&ctx, hash, sizeof(hash),
                                       signature, sig_len);
    
cleanup:
    mbedtls_ecdsa_free(&ctx);
    mbedtls_entropy_free(&entropy);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    
    return ret;
}

这个函数用于验证固件的数字签名,确保固件没有被篡改。

在实际项目中,我会在 Bootloader 阶段就进行签名验证,只有验证通过的固件才允许运行。

2.3 哈希算法

哈希算法用于生成数据的"指纹",常用的有 SHA-256、SHA-3 等。

哈希算法有两个重要特性:单向性(不可逆)和抗碰撞性(很难找到两个不同的输入产生相同的输出)。

在嵌入式系统中,哈希算法主要用于:

  • 固件完整性校验
  • 密码存储(存储密码的哈希值而不是明文)
  • 数字签名(先对数据进行哈希,再对哈希值签名)
#include "mbedtls/sha256.h"
​
// 计算固件的SHA256哈希值
void calculate_firmware_hash(uint8_t *firmware, size_t length, 
                              uint8_t *hash_output)
{
    mbedtls_sha256_context ctx;
    
    mbedtls_sha256_init(&ctx);
    mbedtls_sha256_starts(&ctx, 0); // 0表示SHA-256
    mbedtls_sha256_update(&ctx, firmware, length);
    mbedtls_sha256_finish(&ctx, hash_output);
    mbedtls_sha256_free(&ctx);
}
​
// 验证固件完整性
int verify_firmware_integrity(uint8_t *firmware, size_t length, 
                               uint8_t *expected_hash)
{
    uint8_t calculated_hash[32];
    
    calculate_firmware_hash(firmware, length, calculated_hash);
    
    // 比较计算出的哈希值和预期的哈希值
    if (memcmp(calculated_hash, expected_hash, 32) == 0)
    {
        return 0; // 验证通过
    }
    else
    {
        return -1; // 验证失败
    }
}

3. 密钥管理

3.1 密钥存储

密钥的安全存储是整个加密系统的基础。

如果密钥泄露,再强的加密算法也没用。

在嵌入式系统中,密钥存储有几种方案:

硬编码在代码中:这是最不安全的方式,但我见过很多项目都这么做。

攻击者只要反编译固件,就能轻松找到密钥。

千万不要这样做!

存储在 Flash 中:比硬编码稍好一点,但 Flash 的内容可以被读取出来。

如果一定要存在 Flash 中,至少要对密钥进行加密存储,使用一个主密钥来加密其他密钥。

存储在 OTP 区域:OTP(One-Time Programmable)区域只能写入一次,写入后无法修改,而且通常有读保护功能。

这是比较安全的方式,我在项目中经常使用。

使用安全芯片:最安全的方式是使用专门的安全芯片(如 TPM、SE 等)来存储密钥。

这些芯片有防篡改机制,即使攻击者物理接触到芯片,也很难提取出密钥。

3.2 密钥派生

在实际应用中,我们通常不会直接使用原始密钥,而是通过密钥派生函数(KDF)从主密钥派生出多个子密钥。

这样做的好处是:

  • 不同的功能使用不同的密钥,即使一个密钥泄露,其他功能仍然安全
  • 可以定期更换子密钥,而不需要更换主密钥
#include "mbedtls/hkdf.h"
​
// 使用HKDF派生密钥
int derive_key(uint8_t *master_key, size_t master_key_len,
               uint8_t *info, size_t info_len,
               uint8_t *derived_key, size_t derived_key_len)
{
    const mbedtls_md_info_t *md_info;
    
    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    
    return mbedtls_hkdf(md_info, NULL, 0, 
                        master_key, master_key_len,
                        info, info_len,
                        derived_key, derived_key_len);
}
​
// 示例:派生不同用途的密钥
void generate_session_keys(uint8_t *master_key)
{
    uint8_t encryption_key[16];
    uint8_t authentication_key[32];
    
    // 派生加密密钥
    derive_key(master_key, 32, 
               (uint8_t *)"encryption", 10,
               encryption_key, 16);
    
    // 派生认证密钥
    derive_key(master_key, 32,
               (uint8_t *)"authentication", 14,
               authentication_key, 32);
}

4. 安全启动(Secure Boot)

4.1 安全启动的原理

安全启动是嵌入式系统安全的第一道防线。

它的核心思想是:在系统启动的每个阶段,都验证下一阶段代码的完整性和真实性,形成一条信任链。

信任链的建立

  1. 芯片厂商在芯片出厂时,会在 ROM 中烧录一段不可修改的启动代码(Boot ROM),这是信任的根(Root of Trust)
  2. Boot ROM 验证 Bootloader 的签名,确保 Bootloader 没有被篡改
  3. Bootloader 验证应用程序的签名,确保应用程序是可信的
  4. 只有验证通过,系统才会继续启动,否则进入安全模式或者拒绝启动

4.2 实现安全启动

下面是一个简化的安全启动流程示例:

#define APP_START_ADDR    0x08010000
#define APP_SIZE          0x00070000
#define SIGNATURE_ADDR    0x08080000
​
typedef void (*app_func_t)(void);
​
// 安全启动主函数
void secure_boot(void)
{
    uint8_t *app_code = (uint8_t *)APP_START_ADDR;
    uint8_t *signature = (uint8_t *)SIGNATURE_ADDR;
    int ret;
    
    // 1. 验证应用程序签名
    ret = verify_firmware_signature(app_code, APP_SIZE, 
                                     signature, 64);
    
    if (ret != 0)
    {
        // 签名验证失败,进入安全模式
        enter_safe_mode();
        return;
    }
    
    // 2. 验证应用程序哈希值
    uint8_t expected_hash[32];
    uint8_t calculated_hash[32];
    
    // 从安全存储区读取预期的哈希值
    read_expected_hash(expected_hash);
    calculate_firmware_hash(app_code, APP_SIZE, calculated_hash);
    
    if (memcmp(expected_hash, calculated_hash, 32) != 0)
    {
        // 哈希验证失败
        enter_safe_mode();
        return;
    }
    
    // 3. 验证通过,跳转到应用程序
    app_func_t app = (app_func_t)(*(uint32_t *)(APP_START_ADDR + 4));
    
    // 设置应用程序的栈指针
    __set_MSP(*(uint32_t *)APP_START_ADDR);
    
    // 跳转到应用程序
    app();
}
​
// 安全模式处理
void enter_safe_mode(void)
{
    // 记录错误日志
    log_security_error();
    
    // 可以尝试恢复出厂固件
    // 或者等待通过安全通道更新固件
    
    while(1)
    {
        // 闪烁LED指示错误
        HAL_GPIO_TogglePin(ERROR_LED_GPIO_Port, ERROR_LED_Pin);
        HAL_Delay(500);
    }
}

5. 通信安全

5.1 TLS/DTLS 协议

对于需要网络通信的嵌入式设备,使用 TLS(传输层安全)或 DTLS(数据报传输层安全)协议是必须的。

TLS 用于 TCP 连接,DTLS 用于 UDP 连接。

在嵌入式 Linux 系统中,我通常使用 mbedTLS 库来实现 TLS 通信。

下面是一个简单的 TLS 客户端示例:

#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
​
int tls_client_connect(const char *server_addr, const char *server_port)
{
    int ret;
    mbedtls_net_context server_fd;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    
    // 初始化
    mbedtls_net_init(&server_fd);
    mbedtls_ssl_init(&ssl);
    mbedtls_ssl_config_init(&conf);
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    
    // 初始化随机数生成器
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, 
                                 &entropy, NULL, 0);
    if (ret != 0)
    {
        return -1;
    }
    
    // 连接到服务器
    ret = mbedtls_net_connect(&server_fd, server_addr, 
                              server_port, MBEDTLS_NET_PROTO_TCP);
    if (ret != 0)
    {
        return -1;
    }
    
    // 设置SSL配置
    mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
                                MBEDTLS_SSL_TRANSPORT_STREAM,
                                MBEDTLS_SSL_PRESET_DEFAULT);
    
    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
    
    // 设置SSL上下文
    mbedtls_ssl_setup(&ssl, &conf);
    mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send,
                        mbedtls_net_recv, NULL);
    
    // 执行SSL握手
    while ((ret = mbedtls_ssl_handshake(&ssl)) != 0)
    {
        if (ret != MBEDTLS_ERR_SSL_WANT_READ && 
            ret != MBEDTLS_ERR_SSL_WANT_WRITE)
        {
            return -1;
        }
    }
    
    // 握手成功,可以开始安全通信
    return 0;
}

5.2 消息认证码(MAC)

对于一些资源受限的设备,可能无法使用完整的 TLS 协议。

这时候可以使用消息认证码(MAC)来保证消息的完整性和真实性。

常用的 MAC 算法有 HMAC(基于哈希的消息认证码)。

#include "mbedtls/md.h"
​
// 计算HMAC
int calculate_hmac(uint8_t *key, size_t key_len,
                   uint8_t *message, size_t msg_len,
                   uint8_t *mac_output)
{
    const mbedtls_md_info_t *md_info;
    
    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    
    return mbedtls_md_hmac(md_info, key, key_len, 
                          message, msg_len, mac_output);
}
​
// 验证消息的HMAC
int verify_message_hmac(uint8_t *key, size_t key_len,
                        uint8_t *message, size_t msg_len,
                        uint8_t *received_mac)
{
    uint8_t calculated_mac[32];
    
    calculate_hmac(key, key_len, message, msg_len, calculated_mac);
    
    if (memcmp(calculated_mac, received_mac, 32) == 0)
    {
        return 0; // 验证通过
    }
    else
    {
        return -1; // 验证失败
    }
}

6. 防护措施和最佳实践

6.1 代码混淆和加固

即使使用了加密技术,如果攻击者能够轻松逆向你的代码,找出加密算法的实现细节,安全性还是会大打折扣。

因此,代码混淆和加固也很重要。

代码混淆技术

  • 控制流平坦化:把代码的控制流打乱,让逆向分析变得困难
  • 虚假控制流:插入永远不会执行的代码分支,干扰静态分析
  • 字符串加密:把代码中的字符串常量加密,运行时再解密

防调试技术

  • 检测调试器:通过检测调试寄存器、时间差异等方式判断是否在调试状态
  • 反 JTAG:禁用或加密 JTAG 接口
  • 读保护:启用芯片的读保护功能,防止通过调试接口读取 Flash 内容

6.2 安全更新机制

再安全的系统也可能存在漏洞,因此需要有安全的固件更新机制。

我在项目中实现 OTA(Over-The-Air)更新时,会遵循以下原则:

双区更新:Flash 分为两个区域,一个运行区,一个备份区。

更新时先把新固件写入备份区,验证通过后再切换。

这样即使更新失败,也能回退到旧版本。

增量更新:对于大型固件,可以只传输变化的部分,减少传输时间和流量消耗。

安全通道:固件传输必须使用加密通道,防止中间人攻击。

签名验证:新固件必须经过签名验证,确保来自可信的源。

// OTA更新流程
int ota_update(uint8_t *new_firmware, size_t fw_size, 
               uint8_t *signature)
{
    // 1. 验证签名
    if (verify_firmware_signature(new_firmware, fw_size, 
                                   signature, 64) != 0)
    {
        return -1; // 签名验证失败
    }
    
    // 2. 写入备份区
    if (write_to_backup_area(new_firmware, fw_size) != 0)
    {
        return -2; // 写入失败
    }
    
    // 3. 验证写入的数据
    if (verify_backup_area(new_firmware, fw_size) != 0)
    {
        return -3; // 验证失败
    }
    
    // 4. 设置启动标志,下次启动时切换到新固件
    set_boot_flag(BOOT_FROM_BACKUP);
    
    // 5. 重启系统
    NVIC_SystemReset();
    
    return 0;
}

6.3 日志和审计

安全事件的记录和审计也很重要。我在项目中会记录以下事件:

  • 启动失败(签名验证失败、哈希验证失败等)
  • 非法访问尝试
  • 固件更新记录
  • 密钥更换记录

这些日志可以帮助我们及时发现安全问题,分析攻击手段。

当然,日志本身也需要保护,防止被篡改。

7. 总结

嵌入式安全是一个系统工程,需要从硬件、软件、通信等多个层面来考虑。

我这些年做项目的经验告诉我,安全不是事后补救,而应该在设计阶段就融入到系统中。

虽然增加安全机制会带来一些额外的开发工作量和成本,但相比于出现安全问题后的损失,这些投入是完全值得的。

最后给大家几点建议:第一,不要自己发明加密算法,使用经过验证的标准算法。

第二,密钥管理是重中之重,一定要妥善保护。

第三,保持学习,安全技术在不断发展,攻击手段也在不断进化,我们需要持续关注最新的安全动态。

希望这篇文章能够帮助大家更好地理解嵌入式安全和加密技术,在实际项目中应用这些知识,开发出更安全可靠的嵌入式系统。

更多编程学习资源

你们用淘宝闪购卡吗?用的 17pm ,经常中午用闪购点外卖,选地址和付款的时候都卡得不行,特别特别卡有的时候付款直接卡到失败。不是饿了么套壳么,以前饿了么也没这么卡,是哪里用得有问题?

新春临近,先住各位 V 友新年快乐~

从场景切入

传统的 ExecutorServiceFutureCompletableFuture 非常强大,但写起来比较麻烦:

  • 线程池要手动创建和关闭
  • 超时逻辑每个任务都要写一遍
  • 失败了要不要取消其他任务?得自己判断
  • 异常怎么传播?要么吞掉,要么手动包装
  • 想知道任务跑了多久?自己打日志

我一直在思考怎么能让 Javaer 用多线程的时候能简单点,少点弯弯绕绕,于是诞生了 ThreadForge 。

ThreadForge:把复杂度收敛到一个可推理的模型里

ThreadForge 的设计哲学很简单:先降低认知成本,再追求性能。

可以把它理解成一个结构化并发框架——让你用写同步代码的思维写并发代码,同时自动处理那些容易遗漏的边界情况。

也可以把它理解成对于 Java 内置并发工具的二次包装,目标是让 Java 并发更简单、更清晰。

什么是结构化?

看一个最简单的例子:

try (ThreadScope scope = ThreadScope.open()) {
    Task<String> user = scope.submit("load-user", () -> fetchUser());
    Task<Integer> orders = scope.submit("load-orders", () -> fetchOrders());
    
    scope.await(user, orders);
    
    // 到这里,两个任务肯定都结束了(成功、失败或超时)
    String result = user.await() + ":" + orders.await();
}
// scope 关闭时,所有任务自动取消、资源自动清理

这段代码有几个关键点:

  1. 所有任务都绑定在 ThreadScope,生命周期有边界,不会泄漏
  2. 默认就是安全的:默认超时、默认失败传播、自动取消
  3. 代码结构就是任务关系:读代码的人一眼就能看出两个任务是并发的,且必须都完成才能继续

对比传统写法,你需要:

  • 创建线程池,配置核心线程数、队列大小
  • 提交任务,手动处理 Future
  • 写 try-finally 确保 shutdown
  • 手动处理超时和异常传播

这里其实就能看出来 ThreadForge 的设计初衷和目标了,就是努力让我们省掉这些重复劳动,专注业务逻辑。

五个让你省脑力的设计

1. 默认行为就是正确的

// 默认:FAIL_FAST + 30 秒超时 + 自动取消其他任务
try (ThreadScope scope = ThreadScope.open()) {
    Task<Integer> a = scope.submit(() -> riskyRpc());
    Task<Integer> b = scope.submit(() -> anotherRpc());
    scope.await(a, b);
} catch (ScopeTimeoutException timeout) {
    // 超时了,所有任务已被自动取消
    fallback();
} catch (FailurePropagationException failed) {
    // 某个任务失败了,其他任务已被自动取消
    handleError(failed);
}

不需要配置,不需要思考,开箱即用。

2. 失败策略明确且统一

不同场景对失败的容忍度不同,ThreadForge 提供了 5 种明确的策略:

  • FAIL_FAST:快速失败,立即取消其他任务(默认)
  • COLLECT_ALL:等所有任务结束,汇总所有失败
  • SUPERVISOR:不自动取消,失败信息收集到 Outcome
  • CANCEL_OTHERS:失败后取消其余任务,但不抛异常
  • IGNORE_ALL:忽略失败,只返回成功的结果
// 场景:批量导入,即使部分失败也要知道哪些成功了
try (ThreadScope scope = ThreadScope.open()
        .withFailurePolicy(FailurePolicy.SUPERVISOR)) {
    
    List<Task<Void>> tasks = ids.stream()
        .map(id -> scope.submit(() -> importData(id)))
        .collect(toList());
    
    Outcome outcome = scope.await(tasks);
    
    // 明确知道哪些成功、哪些失败
    log.info("成功: {}, 失败: {}", 
        outcome.successCount(), outcome.failureCount());
}

3. 并发度控制不再需要手动管理队列

// 场景:调用外部 API,最多同时 50 个请求
try (ThreadScope scope = ThreadScope.open()
        .withConcurrencyLimit(50)) {
    
    List<Task<Result>> tasks = hugeIdList.stream()
        .map(id -> scope.submit(() -> externalApi.call(id)))
        .collect(toList());
    
    List<Result> results = scope.awaitAll(tasks);
}
// 自动限流,不会把外部服务打爆

不需要自己写信号量,也不需要手动分批,框架自动处理。

4. 生命周期观测统一收口

ThreadScope scope = ThreadScope.open()
    .withHook(new ThreadHook() {
        @Override
        public void onStart(TaskInfo info) {
            metrics.taskStarted(info.name());
        }
        
        @Override
        public void onSuccess(TaskInfo info, Duration duration) {
            metrics.taskSuccess(info.name(), duration.toMillis());
        }
        
        @Override
        public void onFailure(TaskInfo info, Throwable error, Duration duration) {
            log.error("Task {} failed after {}", info.name(), duration, error);
            metrics.taskFailed(info.name());
        }
    });

不需要在每个任务里重复写日志和监控代码,同时新的 1.0.2 版本中内置了 ScopeMetricsSnapshot 作为观测点,直接 .toString() 就能看到完整的调用耗时等情况 。

5. 跨 JDK 版本的一致体验

// 同一套 API
try (ThreadScope scope = ThreadScope.open()) {
    // JDK 21+: 自动使用虚拟线程
    // JDK 8-20: 自动降级到线程池
    Task<String> task = scope.submit(() -> longRunningTask());
    return task.await();
}

不需要分叉代码,不需要写 if-else,框架自动适配。

适用场景

ThreadForge 特别适合这些场景:

并发 RPC 聚合

try (ThreadScope scope = ThreadScope.open()) {
    Task<User> user = scope.submit(() -> userService.get(uid));
    Task<List<Order>> orders = scope.submit(() -> orderService.list(uid));
    Task<Profile> profile = scope.submit(() -> profileService.get(uid));
    
    scope.await(user, orders, profile);
    
    return buildResponse(user.await(), orders.await(), profile.await());
}

批量数据处理

try (ThreadScope scope = ThreadScope.open()
        .withConcurrencyLimit(100)
        .withDeadline(Duration.ofMinutes(5))) {
    
    List<Task<Void>> tasks = records.stream()
        .map(r -> scope.submit(() -> process(r)))
        .collect(toList());
    
    scope.awaitAll(tasks);
}

生产者-消费者模式

try (ThreadScope scope = ThreadScope.open()) {
    Channel<Data> channel = Channel.bounded(1000);
    
    scope.submit(() -> {
        for (Data d : datasource) {
            channel.send(d);
        }
        channel.close();
        return null;
    });
    
    List<Task<Void>> consumers = IntStream.range(0, 4)
        .mapToObj(i -> scope.submit(() -> {
            for (Data d : channel) {
                process(d);
            }
            return null;
        }))
        .collect(toList());
    
    scope.awaitAll(consumers);
}

开始使用

Maven:

<dependency>
    <groupId>pub.lighting</groupId>
    <artifactId>threadforge-core</artifactId>
    <version>1.0.2</version>
</dependency>

Gradle:

implementation("pub.lighting:threadforge-core:1.0.2")

最小示例:

try (ThreadScope scope = ThreadScope.open()) {
    Task<String> task = scope.submit(() -> "Hello, ThreadForge");
    System.out.println(task.await());
}

📦 GitHub: github.com/wuuJiawei/ThreadForge
📖 文档: 见项目 docs/api/README.md
📄 License: MIT

最后

感谢所有看到这里的朋友。

JDK21 之后,官方团队也跟进了结构化并发类,可以称这个项目是又一个轮子,也可以称它是在工程化里面的一次探讨和另一种解决方案,毕竟给低版本的 JDK 也提供了可能性。

欢迎点赞、评论,如果有任何问题,也欢迎提出您的宝贵意见。

jimeng-2026-02-10-3757-「 ThreadForge 」 这是我新开发的开源项目,帮我做一个 logo 。Java....png

这是让即梦画的 logo ,看起来有点意思,像是个老派的项目。

这一年

你写了多少行代码?
修复了多少个 Bug ?
熬了多少次夜,
还在群里 @产品经理:“这个确定不改了吗?😇”

年终奖没到预期,没事,反正都没年终奖。
升职加薪没有你,没事,不被裁就还好。
那个心心念念的女孩最后还是黄了——没事,你早就习惯了。

那些没写完的需求,没调通的接口,没表白的喜欢,没回家过的年……

没关系。

需求会变更,接口会过期,喜欢的人会龟你,但
春天会来的,女孩还会嫁人的。

今年不祝你升职加薪。

祝你:

写出的每一行代码,别炸得太突然;
熬过的每一个夜,第二天不用早起;
哪怕今年两手空空,推开门的那一刻,有人跟你说:
“回来啦,今年别做龟男了~”

看《大家还想润吗》里面的图片,假如就按照 [$750,000 INfiNiteedGe Inc. SOFTWARE ENGINEER ] 来算,扣完税到手能有多少,再算上对应圈层的生活成本,再扣一层,一年下来,到手实际能有多少,it 行业普遍情况是个啥样

[职位描述]

  1. 根据业务需求和技术需求,完成需求方案设计和功能实现;
  2. 参与后台系统方案评估以及前端 H5 页面功能重构和性能优化;
  3. 前端 H5 功能持续迭代升级,并控制上线风险;
    [工作地]
    北京、深圳
    [岗位要求]
    1.211 硕士研究生及以上学历,有 3 年以上工作经验
    2.具有扎实的编程功底,良好的设计能力和编程习惯;
    3.熟悉 HTML5/CSS3/Javascript 前端开发技术;熟悉 H5 页面常用布局、ajax 底层技术、浏览器工作原理;熟悉手机端 H5 开发方法,具备解决浏览器兼容性问题的经验;
    4.熟悉前端模块化方案,熟悉组件化开发方式,能够实现高质量组件;熟悉前端 VUE ,vant ,elementUi 等敏捷开发框架;
    5.熟练使用相关工具分析调试前端页面,进行性能分析,解决页面浏览交互性能问题;
    6.熟悉设计模式、数据结构、常用算法,并能在实际项目中运用,解决相关问题;
    7.学习能力强,有耐心,踏实肯干,工作积极主动,有较好的合作精神和服务意识,能承受较大的压力;
    8.有百万级日活 APP 设计、开发经验者优先;
    9.有移动端原生开发经验、服务器开发经验者优先
    有意者简历请发送到 [email protected]

最近半年陆陆续续花了不少时间做了一个实验性项目,叫「像素工位」( PixelDesk )。

简单说就是一个 2D 像素风格的虚拟办公室,你可以控制一个小人在里面走动,查看其它工位的人在干什么,
绑定一个属于自己的工位,设置当前状态(工作中/摸鱼/开会...),
还能发帖、和 AI NPC 聊天、交换明信片之类的。

技术栈是 Next.js + Phaser 3 + PostgreSQL ,纯 vibe coding 。

最初的想法很简单:现在远程办公越来越多,但线上社交太"扁平"了,
想试试把社交行为放进一个有空间感的像素世界里,看看会不会产生不一样的互动。

目前功能做了不少( vibe coding ,完全不是产品导向的乱加功能),但是做完后后面完全不知道它有没有意义了,哈哈哈~~😭

项目地址:看一下反馈哈,我也不知道一个完全 vibe coding 的玩意有没有必要开源,大家给个意见。

在线体验: https://pixel.infyniclick.com/
开源地址: https://github.com/infynilong/pixeldesk.git

做完叫 AI 帮我批判性的审核了一下,发现有个商业化的类似项目:Gather.town , 人家做得就好多了,不亏是商业化项目。

发在这里,
1 、是想看看大家的反馈,完全没意义的东西,我就不再继续搞下去了,纯纯自嗨
2 、如果还有点意义,如果有朋友有意思,咱们也可以一起搞来玩玩。

【情报名称】Django 多个安全漏洞通告
【情报等级】高危
【漏洞描述】
近日,Django 发布安全公告,修复了多个存在于 Django 中的安全漏洞,漏洞编号为:CVE-2025-13473、CVE-2025-14550、CVE-2026-1207、CVE-2026-1285、CVE-2026-1287、CVE-2026-1312。

Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架,它鼓励快速开发和简洁、实用的设计。
CVE-2025-13473 用户名枚举漏洞
近日,Django 发布安全公告,修复了多个存在于 Django 中的安全漏洞,漏洞编号为:CVE-2025-13473、CVE-2025-14550、CVE-2026-1207、CVE-2026-1285、CVE-2026-1287、CVE-2026-1312。

Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架,它鼓励快速开发和简洁、实用的设计。

CVE-2025-13473 用户名枚举漏洞

漏洞类型:其它

漏洞等级:低危

漏洞详情:通过 mod_wsgi 认证 django.contrib.auth.handlers.modwsgi.check_password() 功能使远程攻击者能够通过时序攻击枚举用户。

远程利用:是

POC 状态:未公开

在野利用状态:未发现

漏洞细节:未公开

利用复杂度:未知

CVE-2025-14550 拒绝服务漏洞

漏洞类型:拒绝服务

漏洞等级:中危

漏洞详情:当接收到单个头部的重复时, ASGIRequest 远程攻击者可以通过多个重复头部的专门请求,引发潜在的拒绝服务。该漏洞源于在重复字符串串接时,重复组合头部,产生超线性计算,导致服务降级或中断。

远程利用:是

POC 状态:未公开

在野利用状态:未发现

漏洞细节:未公开

利用复杂度:未知

CVE-2026-1207 SQL 注入

漏洞类型:SQL 注入

漏洞等级:高危

漏洞详情:如果使用不可信数据作为带状索引,GIS 字段的栅格查询(仅在 PostGIS 上实现)会被 SQL 注入。

远程利用:是

POC 状态:未公开

在野利用状态:未发现

漏洞细节:未公开

利用复杂度:未知

CVE-2026-1285 拒绝服务漏洞

漏洞类型:拒绝服务

漏洞等级:中危

漏洞详情:django.utils.text.Truncator.chars() Truncator.words() 而方法(带 html=True 和 和 truncatechars_html truncatewords_html 模板过滤器)则可能因某些输入中大量不匹配的 HTML 端标签而遭受拒绝服务攻击,可能导致 HTML 解析过程中时间复杂度达到二次方。

远程利用:是

POC 状态:未公开

在野利用状态:未发现

漏洞细节:未公开

利用复杂度:未知

CVE-2026-1287 SQL 注入

漏洞类型:SQL 注入

漏洞等级:高危

漏洞详情:FilteredRelation 在列别名中通过控制字符存在 SQL 注入风险,使用一个精心设计的字典,并利用字典扩展,当 **kwargs 传递给 QuerySet 方法的 annotate() 、 aggregate() 、 extra() 、 values() 、 values_list() 和 alias() 时。

远程利用:是

POC 状态:未公开

在野利用状态:未发现

漏洞细节:未公开

利用复杂度:未知

CVE-2026-1312 SQL 注入

漏洞类型:SQL 注入

漏洞等级:高危

漏洞详情:QuerySet.order_by() 在 FilteredRelation 中使用时,如果列别名包含点号,且使用了一个经过精心设计的字典,并进行了字典扩展,则存在 SQL 注入漏洞。

远程利用:是

POC 状态:未公开

在野利用状态:未发现

漏洞细节:未公开

利用复杂度:未知

受影响产品
影响版本:

  • Django 主分支
  • Django 6.0 分支
  • Django 5.2 分支
  • Django 4.2 分支

非影响版本:

  • Django 主分支已在代码中修复
  • Django 6.0 分支已在代码中修复
  • Django 5.2 分支已在代码中修复
  • Django 4.2 分支已在代码中修复
    解决方案与修复建议
    目前官方已发布安全版本,受影响用户可下载:

https://www.djangoproject.com/download/

【情报名称】:Apache Shiro 多个安全漏洞通告
【漏洞编号】:CVE-2026-23903,CVE-2026-23901
【情报等级】:中危
【漏洞描述】:近日,Apache 发布了安全公告,修复了存在的安全漏洞,漏洞编号:CVE-2026-23903、CVE-2026-23901。
Apache Shiro 是一个 Java 安全框架,提供了身份验证,授权,加密和会话管理等安全功能。
CVE-2026-23903 Apache Shiro 大小写不敏感文件系统静态文件访问绕过漏洞
漏洞类型:访问控制绕过
漏洞等级:中危
漏洞详情:当 Apache Shiro 在大小写不敏感的文件系统(如默认的 macOS 文件系统)上部署时,攻击者可以通过修改请求中文件名的大小写来绕过 Shiro 的安全过滤器,直接访问受保护的静态文件。
远程利用:是
POC 状态:未公开
在野利用状态:未发现
漏洞细节:未公开
利用复杂度:未知
CVE-2026-23901 Apache Shiro 基于时间的用户枚举攻击漏洞
漏洞类型:访问控制绕过
漏洞等级:低危
漏洞详情:由于不存在的用户与现有用户的认证处理代码路径存在差异,攻击者可以通过精确测量请求响应时间来判断目标用户是否存在,从而实现用户枚举攻击。
远程利用:否
POC 状态:未公开
在野利用状态:未发现
漏洞细节:未公开
利用复杂度:未知
【受影响产品】:影响版本:

  • Apache Shiro < 2.1.0
    非影响版本:
  • Apache Shiro ≥ 2.1.0
    【解决方案与修复建议】:1、目前官方已发布相关安全版本,受影响用户可及时下载更新。
    https://github.com/apache/shiro/tags

01 AI 开发者的基建

过去很多团队把主要精力放在算法本身;在大模型生态成熟后,工程侧更常见的阻塞变成两类:一是环境与依赖的可复现,二是数据导入、检索与存储的落地成本。AI 项目往往带着较重的依赖组合(如 PyTorch、Transformers、各类 RAG 框架),如果每次协作、换机器、进 CI 都要重新处理 Python 版本、虚拟环境、锁文件与依赖冲突,成本会被放大。

这篇文章介绍两个工具,目标是把环境成本和检索数据落地成本更低:

  • uv:由 Astral 团队推出的 Rust 编写的 Python 包管理器,以速度与一致性优化 Python 工作流。
  • pyseekdb:面向 seekdb 与 OceanBase AI search 的 Python SDK,支持嵌入式与远程两种部署方式,并覆盖向量、全文与混合检索能力。

02 什么是 uv

在 Python 生态里,装包本身并不难,难在团队协作下的一致性:不同人用不同工具(pip+venv/poetry),再叠加不同 OS、代理、CPU 架构,常见结果是代码没问题,但别人跑不起来。

uv 的项目模式围绕 pyproject.toml 管依赖、用 uv.lock 锁定解析结果,并通过 uv sync/uv run让环境与锁文件保持一致。它的定位很明确:用一个命令行把项目、依赖、锁版本、环境同步与运行命令这一整套工作流连起来,并强调性能与工程化一致性。

03 pyseekdb 简介

RAG 场景里,开发者通常需要把文本切分、向量化、入库、检索、过滤、排序这一套链路跑通。pyseekdb 提供的是偏应用侧的 SDK:以 collection 为中心组织数据与检索逻辑,覆盖向量、全文与混合检索,并同时支持嵌入式与远程模式。

3.1 两种连接形态

pyseekdb 支持:

  • 嵌入式:在 Python 进程内使用本地路径持久化数据,适合本地实验、测试或轻量应用。
  • 远程:连接到远程 seekdb 服务或 OceanBase 集群。

3.2 混合检索(Hybrid Search)

在 pyseekdb 中,可以通过 query 调用执行向量检索或混合检索(由后端能力与配置决定),返回包含相似度与文档片段的结果集。与直接操控底层索引相比,这种方式更适合应用侧快速落地。

04 为什么 pyseekdb 需要 uv

pyseekdb 本身不一定重,但它经常会和 LangChain、LlamaIndex、Dify 等组合使用。一旦依赖开始变重,环境初始化与复现就更容易拖慢协作效率。
uv 在这里的价值主要是两点:

  • uv.lock 明确锁定解析结果,并用 uv sync / uv run 把安装/同步/运行收敛到更少的步骤。
  • 在共享 demo 时,用 uv syncuv run 尽量复现同一套环境。

pyseekdb 的嵌入式特性配合 uv 的轻量环境,让开发者在普通笔记本上就能完成从数据导入、索引构建到 RAG 问答的全流程开发。

05 手把手教你轻松构建

下面用 pyseekdb GitHub 官方 demo/rag 跑通一条完整链路,目标是让你在 5 分钟内从“环境准备”到“可查询的知识库界面”。

前置条件

  • Python 3.11+
  • 已安装 uv
  • 已准备 LLM API Key(用于生成回答)
  • pyseekdb

步骤 1:准备环境

git clone https://github.com/oceanbase/pyseekdb.git
cd demo/rag
uv sync

如果需要本地模型(sentence-transformers

uv sync --extra local

步骤 2:配置 .env

cp .env.example .env

推荐先用默认 embedding(无需额外 API Key):

EMBEDDING_FUNCTION_TYPE=default
OPENAI_API_KEY=sk-your-key
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
OPENAI_MODEL_NAME=qwen-plus
SEEKDB_DIR=./data/seekdb_rag
SEEKDB_NAME=test
COLLECTION_NAME=embeddings

说明

  • default 会自动下载内置 ONNX 模型,适合先验证流程。
  • 若改为 api,请补齐EMBEDDING_*相关配置。
  • 若改为 local,请配置SENTENCE_TRANSFORMERS_*并确保已安装--extra local依赖。

步骤 3:导入数据

uv run python seekdb_insert.py ../../README.md

也可以导入目录:

uv run python seekdb_insert.py path/to/your_dir

你会看到脚本输出导入的分块数量与进度,成功后数据会落在SEEKDB_DIR指定目录中。

步骤 4:启动界面

uv run streamlit run seekdb_app.py

启动后打开浏览器,在输入框里提问即可看到:

  • 检索到的相关片段
  • LLM 的生成回答(依赖你在.env里配置的 LLM)

效果:

  • 文档被切分、向量化并写入 seekdb
  • 查询时执行向量/混合检索
  • UI 中展示检索结果与 LLM 生成答案

06 回归开发的本质

uv 解决的是项目环境可复现与流程收敛,pyseekdb 解决的是RAG 场景下的存储与检索落地成本和易用性。把两者放在一起,是把 demo 交付与协作时的摩擦做小:项目结构、依赖、运行方式更统一;本地 embedded 能快速开始,之后再按需要切到远程服务。

Adobe Acrobat Pro DC 2022 是一款专门用于PDF阅读以及编辑的办公辅助软件。它将全球最佳的PDF解决方案提升到新的高度,配有直观触控式界面,通过开发强大的新功能,为用户工作带来许多的便利。

一、准备工作

  1. 获取安装包安装包下载:https://pan.quark.cn/s/0cca2634fe5c

二、安装步骤

  1. 解压安装包

    找到下载的安装包文件,右键点击它,选择【解压到当前文件夹】(建议解压至非系统盘,如D盘,避免占用C盘空间)。

  2. 进入解压目录

    解压完成后,双击打开生成的文件夹(通常命名为“Acrobat2022”或类似),找到安装程序文件(一般为 setup.exe或 Acrobat2022_Setup.exe,图标多为蓝色/红色Adobe标识),双击运行。

  3. 选择安装模式

    弹出安装向导后,选择【自定义安装】(部分版本显示为“自定义”,可灵活调整安装路径和功能,比“快速安装”更可控),点击【下一步】。

  4. 修改安装路径(关键步骤):

    • 点击【更改】按钮(或“浏览”),自定义安装位置(避免默认C盘,建议手动选择路径);
    • 示例:打开D盘,新建文件夹并命名为“Acrobat2022”(路径建议简洁,如 D:\Program Files\Adobe\Acrobat2022或直接 D:\Acrobat2022,避免中文/特殊符号);
    • 选中新建的文件夹后,点击【确定】返回安装界面。
  5. 开始安装

    确认安装路径无误后,点击【安装】按钮,等待进度条完成(期间请勿关闭安装窗口,耗时约3-10分钟,取决于电脑性能)。

  6. 完成基础安装

    进度条满格后,点击【完成】(此时软件主体已安装至指定路径,但可能需进一步激活或启动)。

  7. 启动软件

    若安装界面提示“立即启动”,点击该按钮直接打开Acrobat 2022;若未提示,可通过桌面快捷方式(安装时若勾选“创建桌面快捷方式”会自动生成)或开始菜单找到软件图标,双击启动。

三、安装完成

至此,Acrobat 2022已成功安装并可正常使用。首次启动可能需要登录Adobe账号或完成简单初始化设置(根据版本不同略有差异),按提示操作即可。

在 AI 飞速发展的当下,行业讨论往往聚焦于追求规模更大、结构更复杂的大语言模型。然而,在那些基础配套设施与海量数据并非唾手可得的领域,正涌现出另一种截然不同的发展思路。

这一叙事由 Lelapa AI 首席技术官兼联合创始人 Jade Abbott 等人提出,强调资源限制反而会成为自然语言模型研发中的创新催化剂。这些限制非但没有成为阻碍,在严苛条件下运行的必要性反而催生出一种务实的研发思路,这或许会重新定义我们在全球范围内构建与推广 AI 的方式。

AI 开发的传统思路往往依赖庞大的计算资源、完善的云基础设施与海量数据集,而这些条件大多只存在于语言生态成熟的环境中。这种模式虽在特定场景下有效,却忽视了非洲大陆等地区所独有的挑战与机遇。在这些地区,电力供应不稳定、高速互联网未普及、数百种语言缺乏数字化语料,这些现实都要求我们对 AI 开发方式进行根本性的重新思考。

Abbott 的工作提供了一个极具参考价值的案例,展示了如何在不照搬西方模式的前提下应对这些复杂问题,开辟出全新路径,将效率、可及性与文化适配性放在优先位置,尤其是在基础配套设施和模型训练数据都十分匮乏的情况下。

他们“分而治之”的理念、创新的合成数据生成方法、战略性的模型选择,以及在资源受限环境中稳健评估与持续改进的核心思路,共同确保了模型能够贴合实际需求、解决真实问题,不受地域与资源条件的限制。

该公司采用务实、以问题为中心的方法,与通用人工智能(AGI)抽象、通用特性形成鲜明对比,包括细致定义待解决的具体问题,再针对现有约束条件优化工程解决方案。这种“分而治之”的思路并非只是理论概念,而是已深入落地的技术实践,影响着其大语言模型开发的方方面面。

以下段落详细阐述了这种思维方式在实践中的体现:在电力与网络不稳定的环境中运行模型,在数据缺失时主动创造数据,并依据反馈持续迭代系统。这一实践充分证明,明确的约束、扎实的工程能力与局部洞察相结合,便能打造出在现实世界中真正行之有效的 AI。

通过提升工程效率应对基础设施匮乏

非洲大陆面临一系列独特的基础设施难题,亟需创新的工程解决方案。与电力、互联网普及且稳定的地区不同,这里许多区域存在供电不稳、网络受限的问题。这一现实直接影响了大型依赖云计算的大语言模型的部署与运行可行性。

对务实的技术实践者而言,这意味着需要高度优化、低功耗的模型,使其能够在边缘设备上运行,或尽可能减少对持续云服务的依赖。研究问题也从“我们的模型能有多强大?”转变为“在现有能源与网络限制下,我们的模型如何创造价值?”这通常会涉及以下技术:

  • 模型量化:降低模型中数值表示的精度(例如从 32 位浮点数转换为 8 位整数),可以显著减少内存占用与计算开销,使模型能够在性能较弱的硬件上运行。

  • 模型蒸馏:通过训练较小的“学生”模型,模仿更大、更复杂的“教师”模型的行为,可将高性能但资源消耗大的模型知识,迁移到更高效、更适合在资源受限环境中部署的模型中。

  • 边缘部署策略:设计可直接在移动设备或本地服务器上运行的大语言模型,尽量减少与远程数据中心的持续通信。这需要对模型架构、推理优化进行细致考量,同时针对文本转语音、基础翻译等特定任务实现离线运行能力。

  • 异步数据同步:对于确实需要一定网络连接的模型,采用可靠的异步数据同步机制,确保在网络可用时高效交换更新与新数据,而非依赖持续在线。

这些技术并非只是理论探讨,而是让 AI 能够在每瓦特功耗、每字节传输都至关重要的场景下真正落地部署的基础工程实践。其核心是在真实的运行条件下实现实用价值,而非不计成本地追求理论性能极限。

应对数据稀缺:合成数据生成的技术与应用

为非洲语言开发大语言模型最主要的技术障碍之一是数字化语言数据极度稀缺。历史上,许多土著语言很少被文字记录,而殖民影响进一步压制了其书面形式的发展。这导致 AI 开发者缺乏支撑英语等主流大语言模型训练所需的大规模文本语料库。

Abbott 应对这一挑战的解决方案是有针对性地生成高质量合成数据。这并非随机生成文本,而是一套经过精心工程化的流程,所生成的数据既贴合特定应用场景,又能代表相应的人口统计特征。这种方法不仅适用于稀有语言,也适用于受隐私问题或法规保护的敏感数据。

这个看似“枯燥”却很实用的案例是为约翰内斯堡开发呼叫中心转录模型。传统方案需要采集并转录大量真实的呼叫中心音频,但受隐私法规限制,加之人工转录成本极高,往往难以落地。这种场景需要一套包含以下内容的解决方案:

  1. 问题定义:明确界定问题的范围——例如,转录特定语言或方言的呼叫中心对话,聚焦特定类型的咨询,并限定来电者的年龄区间。

  2. 人机回环数据创建:该公司并非完全依赖算法生成数据,而是雇佣团队(通常为前呼叫中心坐席)来模拟呼叫中心的互动场景。这些工作人员会依据脚本和指导方针,分别扮演坐席与来电者,生成高度贴近真实对话的音频数据。这一方式能确保数据捕捉到自然的语音模式、口音及特定领域的专业术语。

  3. 受控环境模拟:搭建可模拟呼叫中心环境的系统,实现音频数据的可控生成。该系统可调整背景噪音、通话质量与说话人特征,从而构建出稳健且多样化的数据集。

  4. 迭代改进:在模型部署并收集反馈后开展错误分析。若模型在特定语言细节或嘈杂环境下表现不佳,就优化数据生成流程,补充更多能解决这些问题的样本。这种迭代反馈循环可确保合成数据在质量和相关性上持续提升。

  5. 用于数据生成的特征提取:当真实客户数据因隐私问题受到严格保护时,可从中提取关键特征与属性,无需直接访问敏感内容。这些特征将为新合成数据的生成参数与指导规则提供依据,确保生成数据能够体现受保护真实数据的统计特征与语言模式。

这种实操性的数据生成方法虽在人力资本上投入较大,但能生成高度针对性、且符合伦理规范的数据集,而这些数据原本难以获取。它体现了从数据收集到数据创建的根本性转变,这也是任何在数据稀缺环境下工作的技术人员所必备的关键能力。

战略模型选择与持续改进

基础模型的选择是一项关键决策,需要基于对现有约束条件的务实判断。一味追求选择体量最大、宣传最广的模型,往往会适得其反,尤其在数据与计算资源有限的情况下。

对于技术专家来说,模型选择过程涉及:

  1. 定义操作约束:在评估模型之前,先明确定义运行环境:延迟要求是多少?可用硬件有哪些(CPU、GPU、内存)?功耗限制是多少?这些约束条件决定了模型可行的大小与复杂度范围。

  2. 对较小模型进行基准测试:不要从最大的模型入手,而是先评估 Hugging Face 等平台上现有的更小、更高效的模型。这类模型通常能提供不错的基线效果,且微调所需的资源要少得多。

  3. 性能与资源的权衡:要理解模型性能、大小与计算需求之间存在持续的权衡关系。在资源受限的环境中,一个精度稍低但更快、更小的模型,往往比精度略高却体积过大、难以部署的模型更具实用价值。

  4. 特定领域预训练:这个案例研究表明,针对特定领域或特定语言进行预训练能够显著提升模型在上下文敏感型应用中的表现。以非洲语言(如斯瓦希里语)为核心预训练的小模型,在面向相关非洲语言任务微调后效果往往优于规模更大、但以英语为中心的模型。这一结果凸显了基础训练数据在语言与文化上保持对齐的重要性。

  5. 迭代实验和错误分析:模型选择过程很少是一次性的决策。它涉及:

  • 候选选择:筛选出若干个满足初始约束条件、具备应用潜力的模型。

  • 快速原型设计和微调:在生成的合成数据上对这些候选模型进行微调。

  • 定性错误分析:除了定量指标外,还应对模型错误进行定性分析:它主要出现哪类错误?这些错误能否通过补充数据、更换微调方法或调整模型架构来解决?

  • 战略杠杆:根据错误分析结果,确定需要采取哪些优化手段:生成更具针对性的数据,应用模型优化技术(量化、蒸馏),或放弃当前模型,改用其他架构。

这种迭代式、数据驱动的方法,能够确保为当前问题找到最优的可用模型。

“AI 缺陷”的演变定义与持续集成

AI 中“缺陷”的概念与传统软件工程有着本质区别。在传统软件中,缺陷通常是二元的:要么已修复,要么未修复。而在 AI 领域,性能是按梯度衡量的,“错误”往往只是特定场景下 1% 的准确率下降,而非彻底的系统故障。这种精细化的理解,对于将 AI 集成到持续改进流程中至关重要。因此,管理 AI “缺陷”所采用的方法主要包括:

  1. 将用户反馈封装为测试集:当用户报告问题(例如“模型在 X、Y 场景下表现不佳”)时,这类反馈不会被当作孤立事件处理。相反,它会被转化为用于定位该问题的小型代表性测试集,并成为评估套件中的永久组成部分。

  2. 梯度式进度追踪:这些“缺陷”测试集并不采用二元的“已修复 / 未修复”状态,而是基于百分比进行评估。一个模型在某类缺陷上可能呈现出 70% 的改进效果,这表明即便问题尚未完全解决,也已取得明显进展。这为模型迭代提供了更贴合实际、更具可操作性的视角。

  3. 构建"缺陷数据库":随着时间的推移,这些小型测试集将逐步积累成一个庞大的数据库。该数据库可充当全面的安全保障,确保新模型在部署时,能够持续针对各类已知问题和边缘场景进行评估。

  4. 集成到 CI/CD 中:每个候选模型在部署前都会基于这个完整的“缺陷数据库”进行测试。这为 AI 建立了持续集成机制,让开发团队乃至业务相关方都能清晰理解模型改动在各个问题领域所带来的影响。

  5. 战略性资源分配:缺陷数据库的结果可为战略决策提供依据。如果某类缺陷反复出现或仅取得有限改进,可据此决定为该场景投入更多数据生成资源、探索不同模型架构,或采用更激进的优化技术。

这种将软件缺陷理念适配到机器学习领域并将其融入持续反馈循环的做法是构建可靠、负责任的 AI 系统的关键一步。它跳出了抽象的性能指标,转向具体、与业务相关的评估,为管理 AI 开发中固有的不确定性提供了实用框架。

在多维度场景中衡量影响

对于任何科技公司,尤其是向消费者提供各类产品(包括开源产品与商业产品)的企业而言,衡量自身影响力至关重要。其有效性唯有通过多维度方法进行评估,超越简单指标,才能真正体现工作所带来的更广泛价值。

从实践角度来看,这涉及:

  • 用户参与度指标:对于商业服务而言,追踪增值对话量、模型使用频率、用户留存率等指标,能够直观反映大语言模型的实用性与应用情况。

  • 开源采用情况:对于开源发布的模型与框架,GitHub、Hugging Face 等平台上的下载量、分叉数和贡献数等指标,能够反映其社区参与度与更广泛的技术影响力。

  • 研究与出版物:通过学术论文与出版物传播知识,有助于推动学术交流、树立思想领导力。引用量、阅读量等指标可作为衡量这种学术影响力的依据。

  • 叙事转变与倡导:除直接技术成果外,该公司还积极致力于改变非洲人工智能领域的发展叙事。这包括公开演讲、政策参与,以及倡导更具包容性与伦理规范的人工智能实践。尽管这类“叙事影响力”较难量化,但它对培育健康的支持性生态系统至关重要。

这种多维度的影响力评估方法能够体现整体工作如何与技术进步、社会效益和应用创新相互融合。

联邦学习:理想的前沿

展望未来,人们正积极探索将联邦学习作为一种持续优化模型的机制,尤其适用于部署在网络连接不稳定的移动设备上的模型。联邦学习可让多个持有本地数据样本的去中心化设备协同训练模型,且无需交换原始数据,仅将模型更新(如参数权重变化)上传至中央服务器,从而有效保护用户隐私。

尽管对于 NLP 领域的真实应用场景而言,这在很大程度上仍偏理想化,但其技术影响十分重大:

  • 隐私保护更新:用户数据保留在设备本地,解决了关键的隐私问题,在数据保护法规不断完善的地区尤为重要。

  • 持续的设备端改进:模型可以直接从设备端的真实使用模式中学习并自适应,长期来看能够实现更个性化、更精准的效果。

  • 克服连接障碍:模型更新可进行批量处理,并在网络连接恢复时传输,让系统在间歇性网络环境下仍能稳定运行。

  • 去中心化智能:这种方法推动了更去中心化的 AI 生态系统,减少对集中式云基础设施的依赖,并为本地社区提供更贴合需求、响应更及时的 AI 工具。

将联邦学习成功应用于 AI 模型将是一次重大的技术飞跃,尤其在资源受限的环境中,可让模型持续迭代,适配多样的语言与语境差异,同时无需牺牲用户隐私,也不依赖持续的网络连接。

结论

本案例研究概述了在现实约束条件下开发 AI 系统的实用框架,阐述了有限的基础设施、数据稀缺以及效率要求等挑战,如何推动形成更具针对性的设计方案与迭代式工程实践。综合来看,这些案例表明:AI 的进步往往更少依赖规模,而更多取决于目标明确性、严谨的实验以及贴合场景的问题解决能力。

尽管非洲大陆严苛的技术条件看似与西方环境截然不同,但深入观察后可以发现,Lelapa AI 所采用的方法,同样适用于发达经济体中监管严格的场景,尤其是已实施隐私相关立法的地区。

他们通过务实解决各类问题,在尽可能多的场景中为用户创造价值,证明了即便在传统资源稀缺的条件下,依然能够构建并推广具有影响力的 AI 系统。

本案例研究得出的经验教训并不局限于特定地理背景,而是适用于所有希望构建可靠、合规且有价值的 AI 解决方案的技术人员与组织。将约束视为创新的催化剂、精准定义问题、为效率而设计、通过严谨评估推动持续学习,我们就能跳出单纯追求规模的误区,打造出真正满足人类多元需求的人工智能。AI 的未来不在于打造更大的模型,而在于为所有人开发更智能、更具适应性、更易获取的智能。

原文链接:

https://www.infoq.com/articles/building-llms-resource-constrained-environments/

如下是一次线上 paas 平台内的 java 包邮件发送异常,就是一个典型案例。实际发生问题,程序员言之凿凿自己代码没问题,然后大家一起过代码排查结果,问题如下

如何把研发标准从制定标准落实到代码管理规范中?逐渐形成一种思维模式和惯性,而不是发现问题解决问题,比如:超实,数据获取方式,异步处理,这些参数的使用和定义能否从项目开始就顺手写出更健壮的代码?发家如何看待这些问题?请多指教

问题 1 (致命):SMTP 超时设置为 1 秒

prop.setProperty("mail.smtp.timeout", "1000");

风险点,高峰期频繁出现

SocketTimeoutException: Read timed out ,被误判为数据库或邮件系统问题。

问题 2:未设置连接和写入超时

mail.smtp.connectiontimeout

mail.smtp.writetimeout

风险点:

网络波动时连接长时间阻塞,大附件发送过程中线程被占用,在高并发下容易形成线程堆积

问题 3:同步 HTTP 线程直接发送邮件

javaMailSender.send(mimeMessage); 运行在进程:XNIO-1 task-*

风险点:SMTP 属于慢 IO 操作,高并发多附件容易卡住,高并发情况下可能导致接口响应变慢甚至线程耗尽

问题 4:附件加载方式不稳定

mimeMessageHelper.addAttachment(

attachment.getFileName(),

getInputStreamSourceFromUrl(attachment.getUrl())

);

风险:

网络耗时叠加,超时概率显著增加,整体发送时长不可控

问题 5:异常处理过于笼统,未做错误判断处理

catch (Exception e) {

log.error("邮件发送失败,错误信息:", e);

}

风险点:

无法区分认证、连接、超时等问题,定位困难,缺乏可观测性。

在企业的日常运营中,流程管理是确保工作规范、提升效率的关键环节。每一个流程实例都需要一个唯一的标识符,这就是流程编号。它如同流程的“身份证”,贯穿流程的发起、审批、执行与归档全过程,确保流程可追溯、易管理。
流程编号并不是一成不变,在JVS低代码流程引擎中,有一个流程编码自定义配置的能力,允许企业根据自身管理需求,设计出贴合业务、便于查询跟踪的编号规则。
流程编号是一种用于标识和跟踪特定流程或业务流程实例的标识符。在企业或组织中,流程编号通常用于确保流程的准确性和可追溯性,帮助管理和优化业务流程。
通过自定义编号规则,你可以将业务类型、日期、部门等信息融入编号中。例如,一个“采购申请”流程的编号可以定义为 P-20250213-00001(P代表采购,20250213代表日期,00001为当日序号),这让流程管理一目了然,极大地提升了查询与统计效率。
示例演示
图片
接下来我说一说具体的配置方式。
操作步骤
进入流程引擎设计,点击【高级设置】页面
图片
流程编号默认按系统规则生成,即阿拉伯数字顺序计数,从1开始依次递增。
选择按自定义规则生成流程编号点击编辑图标,编号格式设置同我们流水号组件设置如下
图片
①:前缀,加在编号前面的,自定义标识,如“P”代表流程,“M”代表管理,“S”代表支持等
②:后缀,拼接在最后面的
③:时间标识,拼接在前缀后
不设置,默认设置

年月
年月日
年月日时
年月日时分
年月日时分秒
④:序号位数,默认是5,可设置1-9位数字
⑤:重置规则
不重置
按年重置
按月重置
按天重置
按小时重置
按业务需要设置好编号格式点击【确定】最后点击【保存】并【发布】流程,重新发起流程查看效果。
流程编号展示位置
流程办理页面
图片
流程进度页面
图片
工作台
图片
在线demo:https://app.bctools.cn
基础框架开源地址:https://gitee.com/software-minister/jvs

大家好,我是小富~

前几天我不是分享了如何零成本搭建 next-ai-draw-io,教大家用 AI 生成 draw.io 风格的架构图。后台反响还不错,看来大家对手绘架构图真的是苦之久矣。

但在日常写文章时,我发现很多读者更偏爱那种手绘感十足的 Excalidraw 风格,就是下面这种,逼格高、视觉美,能让文章瞬间显得高级起来:

我原本在琢磨,能不能用 Gemini Pro 给自己搓一个 AI 绘图整合平台,把 draw.io 和 Excalidraw 全揉进去。

结果去 GitHub 一搜,好家伙,已经有大佬把我想做的给做了!这个开源项目简直是为我这种懒癌博主量身定制的:Mermaid、draw.io、Excalidraw 三大王牌风格全部支持。

回头再看看以前为了画个原理图熬夜的样子,真的感觉是在浪费生命啊!

AI Draw Nexus AI 绘图全家桶

GitHub 地址https://github.com/hkxiaoyao/ai-draw-nexus

在线体验https://ai-draw-nexus.aizhi.site/

我挨个试了一遍,大家感受下这输出质量:

1. Excalidraw 风格

输入: HTTP 长轮询原理图,生成的逻辑线条清晰,手绘质感爆棚。

2. Draw.io 风格

我之前很多的系统架构、流程图都是这个风格,现在 AI 加持真的太方便了。

3. Mermaid 风格

写 Markdown 就更简单了一秒出图。

这不是功能阉割版,而是全量版!可以在 AI 生成的基础上,直接手动微调。

快速上手

如果你想本地运行,这个基于 Next.js 的前端项目安装起来也非常简单:

# 1. 克隆项目
git clone https://github.com/hkxiaoyao/ai-draw-nexus
cd ai-draw-nexus

# 2. 安装依赖 (推荐用 pnpm)
pnpm install

# 3. 开启生产力大门
pnpm dev

不过,目前的在线版本每天有 10 次免费配额,这也很正常毕竟 API 线上的费用确实贵(上次我那个免费工具被大家两天就用欠费了,哈哈 😂)。

现在仅支持 OpenAIAnthropic 两大模型。如果你有自己的 Key,建议本地搭一个,那才是真正的绘图自由!

好了,下期见~