普通视图

发现新文章,点击刷新页面。
今天 — 2025年10月15日首页

在你的Rust类型里生成TypeScript的bindings!

作者 Rust菜鸡
2025年10月15日 11:03

你是否经常为前后端接口不一致而苦恼?改了文档改后端,改了后端改前端。为什么不直接从后端接口类型里生成前端接口呢?

当当当当!如果你在用 Rust 开发后端,用 TypeScript 开发前端,那你就有福了!今天给大家介绍一款 Rust 工具——gents。(generate ts, 优雅得像 gentleman )。

这个工具可以在你的 Rust 结构加入一点简单的宏:

#[derive(TS)]
#[ts(file_name = "person.ts", rename_all = "camelCase")]
pub struct Person {
    pub age: u16,
    pub en_name: String,
}

然后写一个 binary 或者测试函数就像这样:

#[test]
fn gents() {
    use gents::FileGroup;
    let mut group = FileGroup::new();
    // Add your root types; dependencies will be included automatically
    group.add::<Person>();
    // The second argument controls whether to generate index.ts
    group.gen_files("outdir", false);
}

运行一下你就能得到一个person.ts文件!还支持 enum 类型哦!

如果仅仅这样也太简单了!如果这个 Rust 类型使用到了别的 TS 类型,可以自动帮你搜集依赖,同时生成相应的 TypeScript 接口!这样,你就可以使用 JSON 格式在前后端通信了!是不是比 Swagger 或者 ProtoBuf 更加方便?超适合用在 monorepo 里,或者一个人包揽前后端。如果你在开发 WebAssembly 应用,那更加好了,因为你可以参考这个项目的用法!

感兴趣的朋友甚至可以研究一下这个 Rust 库的工作原理,个人感觉十分 Rustic!虽然 proc_macro 的代码真的很难读😂。如果有人对他的实现感兴趣,有机会可以再开一期!反正我看完这个代码,真的赞叹作者脑洞清奇!


我是 Rust 菜鸡,关注我,我让大家教我写代码!

昨天 — 2025年10月14日首页

可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了😱 Node.js System Crashed Because of Logging

2025年10月14日 01:35

本文为中英文双语,需要英文博客可以滑动到下面查看哦 | This is a bilingual article. Scroll down for the English version.

小伙伴们!今天我在本地调试项目的过程中,想记录一下错误信息,结果程序就"啪"地一下报出 "Maximum call stack size exceeded" 错误,然后项目直接就crash了。但是我看我用的这个开源项目,官方的代码里好多地方就是这么用的呀?我很纳闷,这是为什么呢?

Snipaste_2025-10-10_00-28-45.png

报错信息


[LOGGER PARSING ERROR] Maximum call stack size exceeded
2025-10-13T17:06:59.643Z debug: Error code: 400 - {'error': {'message': 'Budget has been exceeded! Current cost: 28.097367900000002, Max budget: 0.0', 'type': 'budget_exceeded', 'par... [truncated]
{
  unknown: [object Object],
}
2025-10-13T17:06:59.643Z debug: [api/server/middleware/abortMiddleware.js] respondWithError called
2025-10-13T17:06:59.644Z error: There was an uncaught error: Cannot read properties of undefined (reading 'emit')
2025-10-13T17:06:59.645Z debug: [indexSync] Clearing sync timeouts before exiting...
[nodemon] app crashed - waiting for file changes before starting...

报错截图

image

错误分析

晚上下班以后,晚上躺在床上,我翻来覆去睡不着,干脆打开电脑一番探究,想要知道 ,这个错误到底为何触发,实质原因是什么,以及如何解决它。让我们一起把这个小调皮鬼揪出来看看它到底在搞什么鬼吧!👻

场景复现

想象一下这个场景,你正在开心地写着代码:

app.get('/api/data', async (req, res) => {
  try {
    // 一些可能会出小差错的业务逻辑
    const data = await fetchDataFromAPI();
    res.json(data);
  } catch (error) {
    // 记录错误信息
    logger.debug('获取数据时出错啦~', error); // 哎呀!这一行可能会让我们的程序崩溃哦!
    res.status(500).json({ error: '内部服务器出错啦~' });
  }
});

看起来是不是很正常呢?但是当你运行这段代码的时候,突然就出现了这样的错误:

[LOGGER PARSING ERROR] Maximum call stack size exceeded

更神奇的是,如果你把代码改成这样:

console.log(error); // 这一行却不会让程序崩溃哦,但是上prod的系统,不要这么用哦

它就能正常工作啦!这是为什么呢?🤔

小秘密大揭秘!🔍

console.log虽好,但请勿用它来记录PROD错误!

console.log 是 Node.js 原生提供的函数,它就像一个经验超级丰富的大叔,知道怎么处理各种"调皮"的对象。当 console.log 遇到包含循环引用的对象时,它会聪明地检测这些循环引用,并用 [Circular] 标记来代替实际的循环部分,这样就不会无限递归啦!

简单来说,Node.js 的 console.log 就像一个超级厉害的武林高手,知道如何闪转腾挪,避开各种陷阱!🥋

日志库的"小烦恼"

但是我们自己封装的日志系统(比如项目中使用的 Winston)就不一样啦!为了实现各种炫酷的功能(比如格式化、过滤敏感信息等),日志库通常会使用一些第三方库来处理传入的对象。

在我们的案例中,日志系统使用了 [traverse] 库来遍历对象。这个库在大多数情况下工作得都很好,但当它遇到某些复杂的 Error 对象时,就可能会迷路啦!

Error 对象可不是普通对象那么简单哦!它们可能包含各种隐藏的属性、getter 方法,甚至在某些情况下会动态生成属性。当 [traverse] 库尝试遍历这些复杂结构时,就可能陷入无限递归的迷宫,最终导致调用栈溢出。

什么是循环引用?🌀

在深入了解这个问题之前,我们先来了解一下什么是循环引用。循环引用指的是对象之间相互引用,形成一个闭环。比如说:

const objA = { name: '小A' };
const objB = { name: '小B' };

objA.ref = objB;
objB.ref = objA; // 哎呀!形成循环引用啦!

当尝试序列化这样的对象时(比如用 JSON.stringify),就会出现问题,因为序列化过程会无限递归下去,就像两只小仓鼠在滚轮里永远跑不完一样!🐹

Error 对象虽然看起来简单,但内部结构可能非常复杂,特别是在一些框架或库中创建的 Error 对象,它们可能包含对 request、response 等对象的引用,而这些对象又可能包含对 Error 对象的引用,从而形成复杂的循环引用网络,就像一张大蜘蛛网一样!🕷️

怎样才能让我们的日志系统乖乖听话呢?✨

1. 只记录我们需要的信息

最简单直接的方法就是不要把整个 Error 对象传递给日志函数,而是只传递我们需要的具体属性:

// ❌ 不推荐的做法 - 会让日志系统"生气"
logger.debug('获取数据时出错啦~', error);

// ✅ 推荐的做法 - 让日志系统开心地工作
logger.debug('获取数据时出错啦~', {
  message: error.message,
  stack: error.stack,
  code: error.code
});

2. 使用专门的错误序列化函数

你可以创建一个专门用于序列化 Error 对象的函数,就像给 Error 对象穿上一件"安全外套":

function serializeError(error) {
  return {
    name: error.name,
    message: error.message,
    stack: error.stack,
    code: error.code,
    // 添加其他你需要的属性
  };
}

// 使用方式
logger.debug('获取数据时出错啦~', serializeError(error));

3. 使用成熟的错误处理库

有些库专门为处理这类问题而设计,比如 serialize-error,它们就像专业的保姆一样,会把 Error 对象照顾得好好的:

const { serializeError } = require('serialize-error');

logger.debug('获取数据时出错啦~', serializeError(error));

4. 配置日志库的防护机制

如果你使用的是 Winston,可以配置一些防护机制,给它穿上"防弹衣":

const winston = require('winston');

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  // ... 其他配置
});

最佳实践小贴士 🌟

  1. 永远不要直接记录原始的 Error 对象:它们可能包含复杂的循环引用结构,就像一个调皮的小恶魔。

  2. 提取关键信息:只记录我们需要的错误信息,比如 message、stack 等,就像挑选糖果一样只拿最喜欢的。

  3. 使用安全的序列化方法:确保我们的日志系统能够处理各种边界情况,做一个贴心的小棉袄。

  4. 添加防护措施:在日志处理逻辑中添加 try-catch 块,防止日志系统本身成为故障点,就像给程序戴上安全帽。

  5. 测试边界情况:在测试中模拟各种错误场景,确保日志系统在极端情况下也能正常工作,做一个负责任的好孩子。

image

Terrifying! My Node.js System Crashed Because of Logging an Error Object 😱

Fellow developers! Today, while debugging a project locally, I wanted to log some error information, but suddenly the program threw a "Maximum call stack size exceeded" error and crashed the entire project. But when I look at the open-source project I'm using, I see that the official code does this in many places. I was puzzled, why is this happening?

Error Message


[LOGGER PARSING ERROR] Maximum call stack size exceeded
2025-10-13T17:06:59.643Z debug: Error code: 400 - {'error': {'message': 'Budget has been exceeded! Current cost: 28.097367900000002, Max budget: 0.0', 'type': 'budget_exceeded', 'par... [truncated]
{
  unknown: [object Object],
}
2025-10-13T17:06:59.643Z debug: [api/server/middleware/abortMiddleware.js] respondWithError called
2025-10-13T17:06:59.644Z error: There was an uncaught error: Cannot read properties of undefined (reading 'emit')
2025-10-13T17:06:59.645Z debug: [indexSync] Clearing sync timeouts before exiting...
[nodemon] app crashed - waiting for file changes before starting...

Error Screenshot

image

Error Analysis

After work, I couldn't resist investigating why this error was triggered, what the root cause was, and how to solve it. Let's together catch this little troublemaker and see what it's up to! 👻

Reproducing the Scenario

Imagine this scenario, you're happily coding:

app.get('/api/data', async (req, res) => {
  try {
    // Some business logic that might go wrong
    const data = await fetchDataFromAPI();
    res.json(data);
  } catch (error) {
    // Log the error
    logger.debug('Error fetching data~', error); // Oops! This line might crash our program!
    res.status(500).json({ error: 'Internal server error~' });
  }
});

Doesn't this look normal? But when you run this code, suddenly this error appears:

[LOGGER PARSING ERROR] Maximum call stack size exceeded

What's even more神奇 is, if you change the code to this:

console.log(error); // This line won't crash the program, but don't use this in production systems

It works fine! Why is that? 🤔

The Big Reveal of Little Secrets! 🔍

console.log is Good, But Don't Use It to Log PROD Errors!

console.log is a native Node.js function. It's like an extremely experienced uncle who knows how to handle all kinds of "naughty" objects. When console.log encounters objects with circular references, it cleverly detects these circular references and replaces the actual circular parts with [Circular] markers, so it won't recurse infinitely!

Simply put, Node.js's console.log is like a super skilled martial arts master who knows how to dodge and avoid all kinds of traps! 🥋

The "Little Troubles" of Logging Libraries

But our custom logging systems (like Winston used in the project) are different! To implement various cool features (like formatting, filtering sensitive information, etc.), logging libraries often use third-party libraries to process incoming objects.

In our case, the logging system uses the [traverse] library to traverse objects. This library works well in most cases, but when it encounters certain complex Error objects, it might get lost!

Error objects are not as simple as ordinary objects! They may contain various hidden properties, getter methods, and in some cases, dynamically generated properties. When the [traverse] library tries to traverse these complex structures, it may fall into an infinite recursion maze, ultimately causing a stack overflow.

What Are Circular References? 🌀

Before diving deeper into this issue, let's first understand what circular references are. Circular references refer to objects that reference each other, forming a closed loop. For example:

const objA = { name: 'A' };
const objB = { name: 'B' };

objA.ref = objB;
objB.ref = objA; // Oops! Circular reference formed!

When trying to serialize such objects (like with JSON.stringify), problems arise because the serialization process will recurse infinitely, like two hamsters running forever in a wheel! 🐹

Although Error objects look simple, their internal structure can be very complex, especially Error objects created in some frameworks or libraries. They may contain references to request, response, and other objects, and these objects may in turn contain references to the Error object, forming a complex circular reference network, like a giant spider web! 🕷️

How to Make Our Logging System Behave? ✨

1. Only Log the Information We Need

The simplest and most direct method is not to pass the entire Error object to the logging function, but to pass only the specific properties we need:

// ❌ Not recommended - will make the logging system "angry"
logger.debug('Error fetching data~', error);

// ✅ Recommended - makes the logging system work happily
logger.debug('Error fetching data~', {
  message: error.message,
  stack: error.stack,
  code: error.code
});

2. Use a Dedicated Error Serialization Function

You can create a dedicated function for serializing Error objects, like putting a "safety coat" on the Error object:

function serializeError(error) {
  return {
    name: error.name,
    message: error.message,
    stack: error.stack,
    code: error.code,
    // Add other properties you need
  };
}

// Usage
logger.debug('Error fetching data~', serializeError(error));

3. Use Mature Error Handling Libraries

Some libraries are specifically designed to handle these kinds of issues, such as serialize-error. They're like professional nannies who will take good care of Error objects:

const { serializeError } = require('serialize-error');

logger.debug('Error fetching data~', serializeError(error));

4. Configure Protective Mechanisms for Logging Libraries

If you're using Winston, you can configure some protective mechanisms to give it "bulletproof armor":

const winston = require('winston');

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  // ... other configurations
});

Best Practice Tips 🌟

  1. Never log raw Error objects directly: They may contain complex circular reference structures, like a mischievous little devil.

  2. Extract key information: Only log the error information we need, such as message, stack, etc., like picking candy - only take your favorites.

  3. Use safe serialization methods: Ensure our logging system can handle various edge cases, be a thoughtful companion.

  4. Add protective measures: Add try-catch blocks in the logging logic to prevent the logging system itself from becoming a failure point, like giving the program a safety helmet.

  5. Test edge cases: Simulate various error scenarios in testing to ensure the logging system works properly under extreme conditions, be a responsible good child.

Conclusion | 结语

  • That's all for today~ - | 今天就写到这里啦~

  • Guys, ( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ See you tomorrow~ | 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~

  • Everyone, be happy every day! 大家要天天开心哦

  • Welcome everyone to point out any mistakes in the article~ | 欢迎大家指出文章需要改正之处~

  • Learning has no end; win-win cooperation | 学无止境,合作共赢

  • Welcome all the passers-by, boys and girls, to offer better suggestions! ~ | 欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

第一个成功在APP store 上架的APP

作者 Pluto538
2025年10月12日 23:18

XunDoc开发之旅:当AI医生遇上家庭健康管家

当我在生活中目睹家人为管理复杂的健康数据、用药提醒而手忙脚乱时,一个想法冒了出来:我能否打造一个App,像一位贴心的家庭健康管家,把全家人的健康都管起来?它不仅要能记录数据,还要够聪明,能解答健康疑惑,能主动提醒。这就是 XunDoc App。

1. 搭建家庭的健康数据中枢

起初,我转向AI助手寻求架构指导。我的构想很明确:一个以家庭为单位,能管理成员信息、记录多种健康指标(血压、血糖等)的系统。AI很快给出了基于SwiftUI和MVVM模式的代码框架,并建议用UserDefaults来存储数据。

但对于一个完整的应用而言,我马上遇到了第一个问题:数据如何在不同视图间高效、准确地共享? 一开始我简单地使用@State,但随着功能增多,数据流变得一团糟,经常出现视图数据不同步的情况。

接着在Claude解决不了的时候我去询问Deepseek,它一针见血地指出:“你的数据管理太分散了,应该使用EnvironmentObject配合单例模式,建立一个统一的数据源。” 这个建议成了项目的转折点。我创建了FamilyShareManagerHealthDataManager这两个核心管家。当我把家庭成员的增删改查、健康数据的录入与读取都交给它们统一调度后,整个应用的数据就像被接通了任督二脉,立刻流畅稳定了起来。

2. 请来AI医生:集成Moonshot API

基础框架搭好,接下来就是实现核心的“智能”部分了。我想让用户能通过文字和图片,向AI咨询健康问题。我再次找到AI助手,描述了皮肤分析、报告解读等四种咨询场景,它很快帮我写出了调用Moonshot多模态API的代码。

然而,每件事都不能事事如意的。文字咨询很顺利,但一到图片上传就频繁失败。AI给出的代码在处理稍大一点的图片时就会崩溃,日志里满是编码错误。我一度怀疑是网络问题,但反复排查后,我询问Deepseek,他告诉我:“多模态API对图片的Base64编码和大小有严格限制,你需要在前端进行压缩和校验。”

我把他给我的建议给到了Claude。claude帮我编写了一个“图片预处理”函数,自动将图片压缩到4MB以内并确保编码格式正确。当这个“关卡”被设立后,之前桀骜不驯的图片上传功能终于变得温顺听话。看着App里拍张照就能得到专业的皮肤分析建议,那种将前沿AI技术握在手中的感觉,实在令人兴奋。

3. 打造永不遗忘的智能提醒系统

健康管理,贵在坚持,难在记忆。我决心打造一个强大的医疗提醒模块。我的想法是:它不能是普通的闹钟,而要像一位专业的护士,能区分用药、复查、预约等不同类型,并能灵活设置重复。

AI助手根据我的描述,生成了利用UserNotifications框架的初始代码。但很快,我发现了一个新问题:对于“每周一次”的重复提醒,当用户点击“完成”后,系统并不会自动创建下一周的通知。这完全违背了“提醒”的初衷。

“这需要你自己实现一个智能调度的逻辑,在用户完成一个提醒时,计算出下一次触发的时间,并重新提交一个本地通知。” 这是deepseek告诉我的,我把这个需求告诉给了Claude。于是,在MedicalNotificationManager中, claude加入了一个“重新调度”的函数。当您标记一个每周的用药提醒为“已完成”时,App会悄无声息地为您安排好下一周的同一时刻的提醒。这个功能的实现,让XunDoc从一个被动的记录工具,真正蜕变为一个主动的健康守护者。

4. 临门一脚:App Store上架“渡劫”指南

当XunDoc终于在模拟器和我的测试机上稳定运行后,我感觉胜利在望。但很快我就意识到,从“本地能跑”到“商店能下”,中间隔着一道巨大的鸿沟——苹果的审核。证书、描述文件、权限声明、截图尺寸……这些繁琐的流程让我一头雾水。

这次,我直接找到了DeepSeek:“我的App开发完了,现在需要上传到App Store,请给我一个最详细、针对新手的小白教程。”

DeepSeek给出的回复堪称保姆级,它把整个过程拆解成了“配置App ID和证书”、“在App Store Connect中创建应用”、“在Xcode中进行归档打包”三大步。我就像拿着攻略打游戏,一步步跟着操作:

  • 创建App ID:在苹果开发者后台,我按照说明创建了唯一的App ID com.[我的ID].XunDoc
  • 搞定证书:最让我头疼的证书环节,DeepSeek指导我分别创建了“Development”和“Distribution”证书,并耐心解释了二者的区别。
  • 设置权限:因为App需要用到相机(拍照诊断)、相册(上传图片)和通知(医疗提醒),我根据指南,在Info.plist文件中一一添加了对应的权限描述,确保审核员能清楚知道我们为什么需要这些权限。

一切准备就绪,我在Xcode中点击了“Product” -> “Archive”。看着进度条缓缓填满,我的心也提到了嗓子眼。打包成功!随后通过“Distribute App”流程,我将我这两天的汗水上传到了App Store Connect。当然不是一次就通过上传的。

image.png

5. 从“能用”到“好用”:三次UI大迭代的觉醒

应用上架最初的兴奋感过去后,我陆续收到了一些早期用户的反馈:“功能很多,但不知道从哪里开始用”、“界面有点拥挤,找东西费劲”。这让我意识到,我的产品在工程师思维里是“功能完备”,但在用户眼里可能却是“复杂难用”。

我决定重新设计UI。第一站,我找到了国产的Mastergo。我将XunDoc的核心界面截图喂给它,并提示:“请为这款家庭健康管理应用生成几套更现代、更友好的UI设计方案。”

Mastergo给出的方案让我大开眼界。它弱化了我之前强调的“卡片”边界,采用了更大的留白和更清晰的视觉层级。它建议将底部的标签栏导航做得更精致,并引入了一个全局的“+”浮动按钮,用于快速记录健康数据。这是我第一套迭代方案的灵感来源:从“功能堆砌”转向“简洁现代”

image.png 然而,Mastergo的方案虽然美观,但有些交互逻辑不太符合iOS的规范。于是,第二站,我请来了Stitch。我将完整的产品介绍、所有功能模块的说明,以及第一版的设计图都给了它,并下达指令:“请基于这些材料,完全重现XunDoc的完整UI,但要遵循iOS Human Interface Guidelines,并确保信息架构清晰,新用户能快速上手。”等到他设计好了后 我将我的设计图UI截图给Claude,让他尽可能的帮我生成。

image.png (以上是我的Stitch构建出来的页面) Claude展现出了惊人的理解力。它不仅仅是在画界面,而是在重构产品的信息架构。它建议将“AI咨询”的四种模式(皮肤、症状、报告、用药)从并列排列,改为一个主导航入口,进去后再通过图标和简短说明让用户选择。同时,它将“首页”重新定义为真正的“健康概览”,只显示最关键的数据和今日提醒,其他所有功能都规整地收纳入标签栏。这形成了我的第二套迭代方案从“简洁现代”深化为“结构清晰”

image.png

拿着Claude的输出,我结合Mastergo和Stitch的视觉灵感,再让Cluade一步一步的微调。我意识到,颜色不仅是美观,更是传达情绪和功能的重要工具。我将原本统一的蓝色系,根据功能模块进行了区分:健康数据用沉稳的蓝色,AI咨询用代表智慧的紫色,医疗提醒用醒目的橙色。图标也设计得更加线性轻量,减少了视觉负担。(其实这是Deepseek给我的建议)这就是最终的第三套迭代方案在清晰的结构上,注入温暖与亲和力

image.png 这次从Stitch到Claude的UI重塑之旅,让我深刻意识到,一个成功的产品不仅仅是代码的堆砌。它是一次与用户的对话,而设计,就是这门对话的语言。通过让不同的AI助手在我的引导下“协同创作”,我成功地让XunDoc从一個工程师的作品,蜕变成一个真正为用户着想的产品。

现在这款app已经成功上架到了我的App store上 大家可以直接搜索下来进行使用和体验,我希望大家可以在未来可以一起解决问题!

昨天以前首页

金仓数据库KingbaseES与MyBatis-Plus整合实践:电商系统开发实战

2025年10月11日 16:16

金仓数据库KingbaseES与MyBatis-Plus整合实践:电商系统开发实战

前言:国产数据库的时代机遇

随着数字中国建设的深入推进,国产数据库在关键业务系统中扮演着越来越重要的角色。作为国产数据库的领军者,人大金仓KingbaseES在性能、安全性和稳定性方面表现出色。结合MyBatis-Plus这一强大的ORM框架,我们能够在企业级应用开发中实现高效、可靠的数据库操作。本文将通过一个电商系统的实战案例,深入探讨两者的整合之道。

1. 技术选型背后的思考

1.1 为什么选择KingbaseES?

在当前的技术环境下,数据库选型不仅仅是技术决策,更是战略决策。KingbaseES作为国产数据库的佼佼者,具有以下核心优势:

高兼容性:KingbaseES高度兼容PostgreSQL和Oracle,降低了迁移成本。在我们的电商项目中,从MySQL迁移到KingbaseES仅用了两周时间,这得益于其良好的兼容性。

卓越的性能表现:在某大型促销活动中,我们的系统需要处理每秒上万次的数据库操作。KingbaseES通过其优化的查询计划和并发控制机制,成功支撑了业务高峰。

完善的安全特性:对于电商系统而言,数据安全至关重要。KingbaseES提供了三权分立、透明加密等安全特性,为业务数据提供了坚实保障。

1.2 MyBatis-Plus的价值主张

与传统的MyBatis相比,MyBatis-Plus在以下方面展现出明显优势:

开发效率提升:根据我们的项目统计,使用MyBatis-Plus后,简单的CRUD操作代码量减少了约70%,这主要得益于其强大的通用Mapper功能。

代码可维护性:统一的代码风格和内置的最佳实践,使得团队新成员能够快速上手,降低了项目的维护成本。 在这里插入图片描述

2. 电商系统核心模块设计

2.1 数据库架构设计

在我们的电商系统中,核心表结构设计如下:

-- 商品表
CREATE TABLE tb_product (
    id BIGSERIAL PRIMARY KEY,
    product_code VARCHAR(64) UNIQUE NOT NULL,
    product_name VARCHAR(200) NOT NULL,
    category_id BIGINT NOT NULL,
    price NUMERIC(10,2) NOT NULL,
    stock_quantity INTEGER DEFAULT 0,
    status SMALLINT DEFAULT 1,
    description TEXT,
    specifications JSONB,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 订单表
CREATE TABLE tb_order (
    id BIGSERIAL PRIMARY KEY,
    order_no VARCHAR(32) UNIQUE NOT NULL,
    user_id BIGINT NOT NULL,
    total_amount NUMERIC(10,2) NOT NULL,
    discount_amount NUMERIC(10,2) DEFAULT 0,
    pay_amount NUMERIC(10,2) NOT NULL,
    order_status SMALLINT NOT NULL,
    payment_status SMALLINT NOT NULL,
    payment_time TIMESTAMP,
    delivery_time TIMESTAMP,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 订单明细表
CREATE TABLE tb_order_item (
    id BIGSERIAL PRIMARY KEY,
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    product_name VARCHAR(200) NOT NULL,
    unit_price NUMERIC(10,2) NOT NULL,
    quantity INTEGER NOT NULL,
    subtotal NUMERIC(10,2) NOT NULL,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2.2 核心实体类设计

@TableName(value = "tb_product")
public class Product {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String productCode;
    private String productName;
    private Long categoryId;
    private BigDecimal price;
    private Integer stockQuantity;
    private Integer status;
    private String description;
    
    @TableField(typeHandler = JsonTypeHandler.class)
    private Map<String, Object> specifications;
    
    private Date createdTime;
    private Date updatedTime;
    
    // 省略getter/setter
}

@TableName(value = "tb_order")
public class Order {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String orderNo;
    private Long userId;
    private BigDecimal totalAmount;
    private BigDecimal discountAmount;
    private BigDecimal payAmount;
    private Integer orderStatus;
    private Integer paymentStatus;
    private Date paymentTime;
    private Date deliveryTime;
    private Date createdTime;
    private Date updatedTime;
    
    // 业务方法
    public boolean canBeCanceled() {
        return this.orderStatus == 1; // 待支付状态可取消
    }
}

3. 核心业务逻辑实现

3.1 商品库存管理

在电商系统中,库存管理是最关键也是最复杂的业务之一。我们使用MyBatis-Plus结合KingbaseES实现了高效的库存管理:

@Service
public class ProductServiceImpl extends ServiceImpl<ProductDao, Product> 
    implements ProductService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean reduceStock(Long productId, Integer quantity) {
        // 使用悲观锁确保数据一致性
        Product product = baseMapper.selectByIdForUpdate(productId);
        if (product == null) {
            throw new BusinessException("商品不存在");
        }
        if (product.getStockQuantity() < quantity) {
            throw new BusinessException("库存不足");
        }
        
        // 更新库存
        Product updateProduct = new Product();
        updateProduct.setId(productId);
        updateProduct.setStockQuantity(product.getStockQuantity() - quantity);
        updateProduct.setUpdatedTime(new Date());
        
        return updateById(updateProduct);
    }
    
    @Override
    public IPage<ProductVo> searchProducts(Page<ProductVo> page, ProductQuery query) {
        return baseMapper.selectProductList(page, query);
    }
}

对应的Mapper接口:

public interface ProductDao extends BaseMapper<Product> {
    
    @Select("SELECT * FROM tb_product WHERE id = #{id} FOR UPDATE")
    Product selectByIdForUpdate(Long id);
    
    IPage<ProductVo> selectProductList(IPage<ProductVo> page, 
            @Param("query") ProductQuery query);
}

3.2 订单业务流程

订单处理是电商系统的核心,我们通过MyBatis-Plus实现了完整的订单生命周期管理:

@Service
public class OrderServiceImpl extends ServiceImpl<OrderDao, Order> 
    implements OrderService {
    
    @Autowired
    private ProductService productService;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(OrderCreateRequest request) {
        // 1. 验证商品和库存
        List<OrderItem> orderItems = validateProducts(request.getItems());
        
        // 2. 计算订单金额
        BigDecimal totalAmount = calculateTotalAmount(orderItems);
        BigDecimal payAmount = totalAmount.subtract(request.getDiscountAmount());
        
        // 3. 创建订单
        Order order = buildOrder(request, totalAmount, payAmount);
        baseMapper.insert(order);
        
        // 4. 创建订单明细
        orderItems.forEach(item -> {
            item.setOrderId(order.getId());
            orderItemDao.insert(item);
        });
        
        // 5. 扣减库存
        reduceProductsStock(orderItems);
        
        return order;
    }
    
    @Override
    public IPage<OrderVo> queryUserOrders(Page<OrderVo> page, Long userId, 
            OrderQuery query) {
        return baseMapper.selectUserOrders(page, userId, query);
    }
}

3.3 复杂查询与分页优化

电商系统经常需要处理复杂的查询场景,我们利用MyBatis-Plus的条件构造器实现了高效的查询:

@Service
public class ProductServiceImpl implements ProductService {
    
    public List<Product> findHotProducts(int limit) {
        QueryWrapper<Product> wrapper = new QueryWrapper<>();
        wrapper.select("id", "product_name", "price", "sales_volume")
               .eq("status", 1)
               .gt("stock_quantity", 0)
               .orderByDesc("sales_volume")
               .last("LIMIT " + limit);
        
        return baseMapper.selectList(wrapper);
    }
    
    public IPage<Product> searchProductsByKeywords(Page<Product> page, 
            String keywords, Long categoryId) {
        QueryWrapper<Product> wrapper = new QueryWrapper<>();
        
        if (StringUtils.isNotBlank(keywords)) {
            wrapper.and(w -> w.like("product_name", keywords)
                             .or().like("description", keywords));
        }
        
        if (categoryId != null) {
            wrapper.eq("category_id", categoryId);
        }
        
        wrapper.eq("status", 1)
               .orderByDesc("created_time");
        
        return baseMapper.selectPage(page, wrapper);
    }
}

4. 性能优化实战

4.1 数据库连接池优化

在电商大促期间,数据库连接成为关键资源。我们通过优化Druid连接池配置来提升性能:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:kingbase8://localhost:54321/ecommerce
      username: app_user
      password: your_password
      initial-size: 5
      min-idle: 5
      max-active: 50
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false

4.2 查询性能优化

针对KingbaseES的特性,我们实施了以下优化措施:

索引策略优化:

-- 为常用查询字段创建索引
CREATE INDEX idx_product_category ON tb_product(category_id, status);
CREATE INDEX idx_product_search ON tb_product USING gin(to_tsvector('simple', product_name));
CREATE INDEX idx_order_user_time ON tb_order(user_id, created_time DESC);

查询优化实践:

@Repository
public class OrderDao extends BaseMapper<Order> {
    
    public IPage<OrderVo> selectComplexOrders(IPage<OrderVo> page, 
            @Param("query") OrderComplexQuery query) {
        return page.setRecords(baseMapper.selectComplexOrders(page, query));
    }
}

5. 实战中的经验总结

5.1 事务管理的坑与解决方案

在分布式环境下,事务管理变得复杂。我们遇到的典型问题及解决方案:

问题1:长事务导致连接池耗尽

// 错误的做法:在方法中处理大量数据
@Transactional
public void batchProcessOrders(List<Long> orderIds) {
    for (Long orderId : orderIds) {
        processSingleOrder(orderId); // 处理单个订单
    }
}

// 正确的做法:分批次处理
public void batchProcessOrders(List<Long> orderIds) {
    List<List<Long>> partitions = Lists.partition(orderIds, 100);
    for (List<Long> partition : partitions) {
        processOrderPartition(partition);
    }
}

@Transactional
void processOrderPartition(List<Long> orderIds) {
    for (Long orderId : orderIds) {
        processSingleOrder(orderId);
    }
}

5.2 并发场景下的数据一致性

在秒杀场景中,我们通过多种技术保证数据一致性:

@Service
public class SecKillService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Transactional(rollbackFor = Exception.class)
    public boolean seckillProduct(Long productId, Long userId) {
        String lockKey = "seckill:lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 获取分布式锁
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                // 检查库存
                Product product = productDao.selectByIdForUpdate(productId);
                if (product.getStockQuantity() <= 0) {
                    return false;
                }
                
                // 扣减库存
                productDao.reduceStock(productId);
                
                // 创建订单
                createSeckillOrder(productId, userId);
                return true;
            }
        } finally {
            lock.unlock();
        }
        return false;
    }
}

6. 监控与故障排查

6.1 SQL性能监控

通过配置MyBatis-Plus的SQL日志输出,结合KingbaseES的慢查询日志,我们能够及时发现性能问题:

<configuration>
    <settings>
        <setting name="logImpl" value="SLF4J" />
    </settings>
    <plugins>
        <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
            <property name="sqlLog" value="true"/>
        </plugin>
    </plugins>
</configuration>

6.2 业务指标监控

我们建立了关键业务指标的监控体系:

  • 订单创建成功率
  • 库存扣减准确率
  • 平均查询响应时间
  • 数据库连接池使用率

7. 总结与展望

通过将KingbaseES与MyBatis-Plus整合应用于电商系统,我们获得了以下宝贵经验:

技术价值:

  1. KingbaseES在复杂查询和高并发场景下表现稳定
  2. MyBatis-Plus显著提升了开发效率,降低了维护成本
  3. 两者的结合为国产化替代提供了可行方案

业务价值:

  1. 系统在多次大促活动中保持稳定运行
  2. 数据处理准确率达到99.99%
  3. 平均响应时间控制在200ms以内

未来,我们将继续探索KingbaseES在分布式事务、数据分片等高级特性方面的应用,为更大规模的电商业务提供支撑。同时,随着国产数据库生态的不断完善,相信KingbaseES将在更多关键业务场景中发挥重要作用。

国产数据库的发展不是选择题,而是必答题。作为技术人员,我们应该积极拥抱变化,在技术自主可控的道路上不断探索和实践,为构建安全可靠的数字基础设施贡献自己的力量。

Wireshark常用过滤规则

2025年10月11日 15:52

以下是一些实用的Wireshark数据包过滤规则,按协议和场景分类,适用于分析网络流量。

1. TLS/SSL协议过滤

  • 所有TLS流量
tls
  • 所有TLS握手数据包
tls.handshake
  • Client Hello (类型1)
tls.handshake.type == 1
  • Server Hello (类型2)
tls.handshake.type == 2
  • 特定SNI匹配
tls.handshake.extensions_server_name == "example.com"
tls.handshake.extensions_server_name contains "google"
  • TLS版本过滤
tls.record.version == 0x0303  # TLS 1.2
tls.record.version == 0x0304  # TLS 1.3
  • 加密套件
tls.handshake.ciphersuite

2. HTTP/HTTPS过滤

  • 所有HTTP流量
http
  • HTTP请求
http.request
  • HTTP响应
http.response
  • 特定HTTP方法
http.request.method == "GET"
http.request.method == "POST"
  • 特定URI路径
http.request.uri contains "/api"
http contains "login"
  • HTTP状态码
http.response.code == 200
http.response.code == 404
  • User-Agent过滤
http.user_agent contains "Chrome"
http contains "Mozilla"
  • HTTPS流量(需解密或查看Client Hello)
ssl.handshake.extensions_server_name contains "api.example.com"

3. DNS协议过滤

  • 所有DNS查询
dns
  • DNS查询包
dns.flags.response == 0
  • DNS响应包
dns.flags.response == 1
  • 特定域名查询
dns.qry.name contains "google.com"
dns.qry.name == "www.example.com"
  • DNS类型过滤
dns.qry.type == 1  # A记录
dns.qry.type == 28 # AAAA记录

4. IP和网络层过滤

  • 特定IP地址
ip.src == 192.168.1.100
ip.dst == 8.8.8.8
  • IP地址范围
ip.addr == 192.168.1.0/24
ip.src >= 192.168.1.1 && ip.src <= 192.168.1.255
  • 特定端口
tcp.port == 80 || tcp.port == 443
udp.port == 53  # DNS
  • 特定协议
ip.proto == 6  # TCP
ip.proto == 17 # UDP
ip.proto == 1  # ICMP
  • MAC地址
eth.src == aa:bb:cc:dd:ee:ff

5. TCP协议过滤

  • TCP流量
tcp
  • TCP SYN包(连接建立)
tcp.flags.syn == 1 && tcp.flags.ack == 0
  • TCP SYN-ACK包
tcp.flags.syn == 1 && tcp.flags.ack == 1
  • TCP FIN包(连接关闭)
tcp.flags.fin == 1
  • TCP重传包
tcp.analysis.retransmission
  • TCP窗口大小
tcp.window_size > 65535
  • TCP序列号过滤
tcp.seq == 123456
  • TCP会话跟踪
tcp.stream == 0  # 特定TCP流

6. 应用层协议过滤

  • SMTP邮件
smtp
  • FTP
ftp
ftp-data
  • SSH
ssh
  • RDP(远程桌面)
rdp
  • SMB(文件共享)
smb || smb2
  • NTP时间同步
ntp
  • SNMP
snmp

7. 高级过滤和组合

  • 组合条件
ip.src == 192.168.1.100 && tcp.dstport == 80
  • 排除条件
!(ip.src == 192.168.1.1)
  • 逻辑运算
(ip.dst == 8.8.8.8 || ip.dst == 1.1.1.1) && udp.dstport == 53
  • 字符串匹配
frame contains "password"
http contains "secret"
  • 数据包大小
frame.len > 1500

8. 性能和异常检测

  • 大数据包
frame.len > 1400
  • 异常端口
tcp.port > 1024 && tcp.port < 65535
  • 广播/多播
eth.dst[0] & 1  # 以1结尾的MAC地址
  • ARP流量
arp
  • ICMP(ping等)
icmp
  • 丢包检测
tcp.analysis.lost_segment
tcp.analysis.duplicate_ack

9. 流和会话过滤

  • 特定TCP/UDP流
tcp.stream == 1
udp.stream == 0
  • 跟随特定流
    • 右键数据包 > Follow > TCP/UDP/TLS Stream
  • 统计特定流的数据量
statistics.io.graph  # 图形化流量分析

10. 捕获过滤器(Capture Filter)

这些在抓包前使用,减少无关数据:

  • 仅捕获特定主机
host 192.168.1.100
  • 特定端口
port 80 or port 443
  • 特定协议
tcp port 80
udp port 53
  • 网络
net 192.168.1.0/24
  • 排除本地流量
not host 192.168.0.0/16

11. 使用建议

  • 显示过滤器:用于分析已捕获数据包,语法灵活。
  • 捕获过滤器:用于实时抓包,减少数据量。
  • 验证过滤器:过滤栏背景绿色表示语法正确,红色表示错误。
  • 保存过滤器:右键过滤栏 > "Manage Filter Expressions"。
  • 组合使用:用括号和逻辑运算符(如 &&||!)组合条件。

TypeScript的新类型(五):tuple元组

作者 TZOF
2025年10月8日 21:52

定义

元组(tuple)是⼀种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组⽤于精确描述⼀组值的类型,?表示可选元素。

  • 注意: 在ts中tuple 不是关键词,只是⼀种 特殊的数组 形式
  • 可以存储固定数量元素,且元素类型已定义并且可以不同
  • 给元组赋值时元素的个数元素类型都要符合定义时候的声明(除了 ? 可选元素和 ...元素类型[] 任意数量元素情况)
// 第⼀个元素必须是 string 类型,第⼆个元素必须是 number 类型。
let arr1: [string,number]
arr1 = ['hello',123]
// 不可以赋值,arr1声明时是两个元素,赋值的是三个
arr1 = ['hello',123,false]

?可选元素的定义

  • ?加在元素后面,表示该属性为可选元素
// 第⼀个元素必须是 number 类型,第⼆个元素是可选的,如果存在,必须是 boolean 类型。
let arr2: [number,boolean?]
arr2 = [100,false]
arr2 = [200]

...元素类型[],任意数量的元素的定义

  • ...string[]允许元组有任意数量的元素,在...后元素的数据类型
// 第⼀个元素必须是 number 类型,后⾯的元素可以是任意数量的 string 类型
let arr3: [number,...string[]]
arr3 = [100,'hello','world']
arr3 = [100]

MySQL单表为何别超2000万行?揭秘B+树与16KB页的生死博弈|得物技术

作者 得物技术
2025年9月16日 14:17
本文核心介绍,为何业界会有这样的说法?—— “MySQL单表存储数据量最好别超过千万级别” 当然这里是有前提条件的,也是我们最常使用到的: InnoDB存储引擎; 使用的是默认索引数据结构
❌
❌