博客

  • Tomcat 吉祥物:一只橙色猫咪背后的设计哲学与技术隐喻

    提到 Apache Tomcat,每个 Java 开发者都对它的核心功能了如指掌 —— 作为轻量级 Servlet 容器,它支撑着无数 Web 应用的运行。但你是否留意过它的吉祥物:一只戴着红色领结的橙色卡通猫?这只看似简单的猫咪形象,其实藏着 Tomcat 的品牌基因与技术理念。今天,我们就来聊聊这只 “程序猫” 的设计故事。

    一、从 “名字” 到 “形象”:为什么是一只猫?

    Tomcat 的吉祥物形象并非凭空而来,而是与项目名称深度绑定的 “命中注定”。

    1999 年,Sun Microsystems 的工程师团队计划开发一款轻量级 Servlet 容器,作为当时 Apache JServ 的替代方案。在命名时,他们希望用一个能体现 “灵活、独立、适应性强” 特质的词汇,最终选择了 “Tomcat”—— 这个词在英语中本指 “公猫”,尤其常用来形容无主却生命力顽强的流浪猫。这种 “不依赖环境、自己就能跑起来” 的意象,恰好契合了这款轻量级容器的定位:无需复杂配置,就能快速部署运行。

    随着项目被捐献给 Apache 基金会,“Tomcat” 的名字被保留下来,而吉祥物形象也顺理成章地以 “猫” 为核心。早期 O’Reilly 出版的 Tomcat 技术书籍封面,就率先采用了公猫形象,进一步强化了 “猫” 与 Tomcat 的关联,最终成为官方吉祥物的设计起点。

    二、形象细节:每一处设计都藏着技术隐喻

    Tomcat 吉祥物的经典形象看似简洁,却处处呼应着产品的技术特性:

    • 橙色为主色调:温暖明亮的橙色传递出 “友好、活力” 的气质,打破了技术工具的冰冷感。这与 Tomcat 作为开发者友好型工具的定位一致 —— 它无需复杂门槛,新手也能快速上手。
    • 红色领结 / 领带:作为点睛之笔,领结让猫咪形象更显 “正式感”,暗示 Tomcat 虽然轻量,却能承担企业级应用的重任,兼具灵活性与可靠性。
    • 动态姿态:无论是早期插画中奔跑的姿势,还是现代 Logo 中微微前倾的动态,都传递出 “敏捷、高效” 的感觉,对应 Tomcat 启动快、资源占用低的技术优势。
    • 与 Java 咖啡杯的联动:在许多官方物料中,猫咪会与 Java 标志性的咖啡杯同框,直观体现 Tomcat 与 Java 生态的深度绑定(毕竟它是 Java Web 开发的核心工具之一)。

    三、设计理念:用 “猫性” 诠释技术价值观

    Tomcat 吉祥物的核心设计理念,是用 “猫的特质” 隐喻产品的技术价值观,让抽象的技术特性变得可感知:

    1. 灵活适配,如猫适应环境

    猫能在各种场景中生存,无论是客厅沙发还是街头角落;Tomcat 也能跨平台运行,兼容 Windows、Linux、macOS 等系统,支持从个人开发环境到企业服务器的全场景部署。这种 “适应性” 正是吉祥物想要传递的核心优势。

    2. 轻量高效,如猫轻盈敏捷

    猫体型小巧却动作迅速,Tomcat 也以 “轻量级” 著称 —— 相比重型应用服务器(如 WebLogic),它无需庞大的资源开销,却能高效处理 Servlet、JSP 等请求。吉祥物的轻盈形象,恰是这种 “轻而强” 特质的视觉化表达。

    3. 可靠稳定,如猫沉稳可靠

    尽管猫给人 “随性” 的印象,但关键时刻却异常可靠(比如精准捕猎)。Tomcat 同样如此:它经过 20 多年迭代,早已成为工业级的稳定工具,支撑着全球无数网站和应用的运行,吉祥物的 “沉稳感” 也暗合这一特质。

    4. 开发者亲和力,拉近技术与用户的距离

    开源项目的吉祥物往往承担着 “情感连接” 的角色。Tomcat 的猫咪形象弱化了技术工具的 “机械感”,让开发者在调试 Bug、部署应用时,能对这个 “伙伴式” 的工具产生亲切感。这种情感链接,也是 Tomcat 能在开发者社区中长盛不衰的原因之一。

    结语:一只猫咪的 “技术人格”

    Tomcat 的吉祥物从来不止是一个 Logo,而是产品技术理念的 “人格化代言”。它用猫的灵活、敏捷、可靠,将抽象的技术特性转化为具象的情感认知,让开发者在使用这个工具时,不仅依赖它的功能,更能感受到背后的设计温度。

    下次启动 Tomcat 看到那只橙色猫咪时,或许你会对它多一份亲切感 —— 毕竟,它不仅是 Java Web 开发的 “老伙计”,更是用设计语言诠释技术价值观的生动载体。

  • #C++ Day58 Basic Data Structure Chapter6  Linked List-Actual questions Coding  February 18 2026

     #include <iostream>

    #include <stdexcept>

    #define eleType double

    using namespace std;

    struct ListNode {

    eleType data;

    ListNode* next;

    ListNode(eleType x):data(x),next(nullptr){}

    };

    class LinkedList {

    private:

    ListNode* head;

    int size;

    public:

    LinkedList():head(nullptr),size(0){}

    ~LinkedList();

    void insert(int index, eleType value);

    void remove(int index);

    ListNode* find(eleType value);

    ListNode* get(int index);

    void update(int index, eleType value);

    void print();

    };

    LinkedList::~LinkedList() {

    ListNode* curr = head;

    while (curr) {

    ListNode* temp = curr;

    curr = curr->next;

    delete temp;

    }

    }

    void LinkedList::insert(int index, eleType value) {

    if (index<0 || index>size) {

    throw std::out_of_range(“invalid position”);

    }

    ListNode* newNode = new ListNode(value);

    if (index == 0) {

    newNode->next = head;

    head = newNode;

    }

    else {

    ListNode* curr = head;

    for (int j = 0; j < index – 1; j++) {

    curr = curr->next;

    }

    newNode->next=curr->next;

    curr->next=newNode;

    }

    size++;

    }

    void LinkedList::remove(int index) {

    if (index < 0 || index >= size) {

    throw std::out_of_range(“invalid position”);

    }

    if (index == 0) {

    ListNode* temp = head;

    head=temp->next;

    delete temp;

    }

    else {

    ListNode* curr = head;

    for (int j = 0; j < index-1; j++) {

    curr = curr->next;

    }

    ListNode* temp=curr->next;

    curr->next = temp->next;

    delete temp;

    }

    size–;

    }

    ListNode* LinkedList::find(eleType value) {

    ListNode* curr = head;

    while (curr != nullptr && curr->data != value) {

    curr = curr->next;

    }

    return curr;

    }

    ListNode* LinkedList::get(int index) {

    if(index<0||index>=size){

    std::out_of_range(“invalid position”);

    }

    ListNode* curr = head;

    for (int j = 0; j < index; j++) {

    curr=curr->next;

    }

    return curr;

    }

    void LinkedList::update(int index, eleType value) {

    if (index < 0 || index >= size) {

    throw std::out_of_range(“invalid position”);

    }

    get(index)->data = value;

    }

    void LinkedList::print() {

    ListNode* curr = head;

    while (curr) {

    cout << curr->data << ” “;

    curr = curr->next;

    }

    cout << endl;

    }

    int main() {

    LinkedList mylist;

    for (int i = 0; i < 10; i++) {

    mylist.insert(i, i * 100);

    }

    mylist.print();

    cout<< mylist.find(500)->data<<endl;

    cout<< mylist.get(2)->data<<endl;

    mylist.insert(0, 666);

    mylist.print();

    mylist.update(1, 555);

    mylist.print();

    mylist.remove(0);

    mylist.print();

    mylist.remove(1);

    mylist.print();

    return 0;

    }

    1.A+B for Input-Output Practice 

    A+B for Input-Output Practice (IV) Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 207939    Accepted Submission(s): 107841

    Problem Description Your task is to Calculate the sum of some integers.  
    Input Input contains multiple test cases. Each test case contains a integer N, and then N integers follow in the same line. A test case starting with 0 terminates the input and this test case is not to be processed.  
    Output For each group of input integers you should output their sum in one line, and with one line of output for each line in input.   
    Sample Input 4 1 2 3 4 5 1 2 3 4 5 0   
    Sample Output 10 15  
    Author lcy

    #include <iostream>

    #include <stdexcept>

    #define eleType double

    using namespace std;

    struct ListNode {

    eleType data;

    ListNode* next;

    ListNode(eleType x) :data(x), next(nullptr) {}

    };

    class LinkedList {

    private:

    ListNode* head;

    int size;

    public:

    LinkedList() :head(nullptr), size(0) {}

    ~LinkedList();

    void insert(int index, eleType value);

    void remove(int index);

    ListNode* find(eleType value);

    ListNode* get(int index);

    void update(int index, eleType value);

    void print();

    };

    LinkedList::~LinkedList() {

    ListNode* curr = head;

    while (curr) {

    ListNode* temp = curr;

    curr = curr->next;

    delete temp;

    }

    }

    void LinkedList::insert(int index, eleType value) {

    if (index<0 || index>size) {

    throw std::out_of_range(“invalid position”);

    }

    ListNode* newNode = new ListNode(value);

    if (index == 0) {

    newNode->next = head;

    head = newNode;

    }

    else {

    ListNode* curr = head;

    for (int j = 0; j < index – 1; j++) {

    curr = curr->next;

    }

    newNode->next = curr->next;

    curr->next = newNode;

    }

    size++;

    }

    void LinkedList::remove(int index) {

    if (index < 0 || index >= size) {

    throw std::out_of_range(“invalid position”);

    }

    if (index == 0) {

    ListNode* temp = head;

    head = temp->next;

    delete temp;

    }

    else {

    ListNode* curr = head;

    for (int j = 0; j < index – 1; j++) {

    curr = curr->next;

    }

    ListNode* temp = curr->next;

    curr->next = temp->next;

    delete temp;

    }

    size–;

    }

    ListNode* LinkedList::find(eleType value) {

    ListNode* curr = head;

    while (curr != nullptr && curr->data != value) {

    curr = curr->next;

    }

    return curr;

    }

    ListNode* LinkedList::get(int index) {

    if (index < 0 || index >= size) {

    std::out_of_range(“invalid position”);

    }

    ListNode* curr = head;

    for (int j = 0; j < index; j++) {

    curr = curr->next;

    }

    return curr;

    }

    void LinkedList::update(int index, eleType value) {

    if (index < 0 || index >= size) {

    throw std::out_of_range(“invalid position”);

    }

    get(index)->data = value;

    }

    void LinkedList::print() {

    ListNode* curr = head;

    while (curr) {

    cout << curr->data << ” “;

    curr = curr->next;

    }

    cout << endl;

    }

    int main() {

    LinkedList mylist;

    eleType x;

    while (cin >> x && x!=0) {

    eleType sum = 0;

    for (int j = 0; j < x; j++) {

    eleType num;

    cin >> num;

    sum+= num ;

    }

    cout << sum<<endl;

    }

    return 0;

    }

    //英雄哥版

    #include <iostream>

    #include <stdexcept>

    #define eleType double

    using namespace std;

    struct ListNode {

    eleType data;

    ListNode* next;

    ListNode(eleType x) :data(x), next(nullptr) {}

    };

    class LinkedList {

    private:

    ListNode* head;

    int size;

    public:

    LinkedList() :head(nullptr), size(0) {}

    ~LinkedList();

    void insert(int index, eleType value);

    void remove(int index);

    ListNode* find(eleType value);

    ListNode* get(int index);

    void update(int index, eleType value);

    void print();

    eleType sum();

    };

    LinkedList::~LinkedList() {

    ListNode* curr = head;

    while (curr) {

    ListNode* temp = curr;

    curr = curr->next;

    delete temp;

    }

    }

    void LinkedList::insert(int index, eleType value) {

    if (index<0 || index>size) {

    throw std::out_of_range(“invalid position”);

    }

    ListNode* newNode = new ListNode(value);

    if (index == 0) {

    newNode->next = head;

    head = newNode;

    }

    else {

    ListNode* curr = head;

    for (int j = 0; j < index – 1; j++) {

    curr = curr->next;

    }

    newNode->next = curr->next;

    curr->next = newNode;

    }

    size++;

    }

    void LinkedList::remove(int index) {

    if (index < 0 || index >= size) {

    throw std::out_of_range(“invalid position”);

    }

    if (index == 0) {

    ListNode* temp = head;

    head = temp->next;

    delete temp;

    }

    else {

    ListNode* curr = head;

    for (int j = 0; j < index – 1; j++) {

    curr = curr->next;

    }

    ListNode* temp = curr->next;

    curr->next = temp->next;

    delete temp;

    }

    size–;

    }

    ListNode* LinkedList::find(eleType value) {

    ListNode* curr = head;

    while (curr != nullptr && curr->data != value) {

    curr = curr->next;

    }

    return curr;

    }

    ListNode* LinkedList::get(int index) {

    if (index < 0 || index >= size) {

    std::out_of_range(“invalid position”);

    }

    ListNode* curr = head;

    for (int j = 0; j < index; j++) {

    curr = curr->next;

    }

    return curr;

    }

    void LinkedList::update(int index, eleType value) {

    if (index < 0 || index >= size) {

    throw std::out_of_range(“invalid position”);

    }

    get(index)->data = value;

    }

    void LinkedList::print() {

    ListNode* curr = head;

    while (curr) {

    cout << curr->data << ” “;

    curr = curr->next;

    }

    cout << endl;

    }

    eleType LinkedList::sum() {

    eleType ret = 0;

    ListNode* curr = head;

    while (curr != NULL) {

    ret += curr->data;

    curr = curr->next;

    }

    return ret;

    }

    int main() {

    eleType n;

    while (cin >> n && n) {//modify the x in only one word

    LinkedList mylist;

    for (int j = 0; j < n; j++) {

    eleType num;

    cin >> num;

    mylist.insert(j, num);

    }

    cout << mylist.sum()<<endl;

    }

    return 0;

    }

  • Java Day 2-3 复习笔记 (26.2.2)

    1. 核心实战:房贷计算器进阶

    输入与校验逻辑 (Scanner & Validation)

    使用 Scanner 接收用户输入,并配合 if 语句进行有效性判断(卫语句风格)。

    • 输入工具:JavaScanner scanner = new Scanner(System.in); // 技巧:记不住类型可以用 var scanner = new Scanner(System.in);
    • 校验逻辑 (Guard Clauses):
      • 本金校验:if(p <= 0) $\rightarrow$ 提示 “贷款金额必须 > 0″。
        • 逻辑: 先比较再取反(推荐直接写 p <= 0 而不是 !(p > 0))。
      • 利率校验: if(yr < 1.0 || yr > 36.0) $\rightarrow$ 提示 “年利率必须是 1~36″。
      • 月份校验: if(m < 1 || m > 360) $\rightarrow$ 提示 “贷款月数必须是 1~360″。

    数学计算 (Math Logic)

    • 月利率: double mr = yr / 12.0 / 100;
    • 幂运算: double pow = Math.pow(1 + mr, m);
    • 月供公式: double payment = p * mr * pow / (pow - 1);

    循环拆解 (The Loop)

    计算每月的本金与利息详情,关键在于更新剩余本金

    Java

    for (int i = 0; i < m; i++) {
        double payInterest = p * mr;                 // 1. 偿还利息
        double payPrincipal = payment - payInterest; // 2. 偿还本金
        p -= payPrincipal;                           // 3. 更新剩余本金 (核心)
    
        // 打印每月详情 (i+1 表示月份)
        // ...使用 NumberFormat 格式化输出
    }
    

    格式化输出 (Formatting)

    使用核心类库 NumberFormat 处理货币显示(自动添加  和千分位)。

    • 代码: NumberFormat.getCurrencyInstance(Locale.CHINA).format(变量)
    • 应用: 用于本金、利息、总还款额的显示。

    2. 方法入门 (Methods)

    定义与概念

    把方法想象成**“被折叠的代码”**,调用时才会展开。

    • 语法:Javastatic int add(int a, int b) { // "int a, int b" 是形参 int c = a + b; return c; }

    调用方式

    • 本类调用: int d = add(100, 200); (不用写类名)
    • 跨类调用: TestMethod.add(100, 200);
    • 实参: 调用时传入的实际数值 (如 100200)。

    3. IDE 效率快捷键 (IntelliJ IDEA)

    根据代码注释整理的高频快捷键:

    • 代码补全: Ctrl + Shift + Enter (补全当前语句,如分号、括号)。
    • 类搜索: Ctrl + N (查找核心类库,如 Scanner)。
    • 自动导包/修复: Alt + Enter (引入局部变量、生成变量定义、取反逻辑)。
    • 复制行: Ctrl + D (快速复制当前行代码)。
  • AI Day2~3-2 10分钟生成1000条爆款笔记!手把手教你搭建“抖音转小红书”AI智能体

    前言

    你是否想过,如何将抖音上优质的视频内容,批量转化为小红书的爆款图文笔记?手动一条条改写太慢了!今天我们来复盘一个 AI Agent 实战案例:利用 Coze(扣子)+ 阿里百炼插件 + 飞书多维表格,实现全自动化的“视频转文案”工作流。

    我们的目标是:扔给 AI 一堆链接,它自动帮我们跑完所有流程,最后直接在飞书表格里收货。

    🚀 核心工作流拆解

    整个智能体的工作流可以分为四个关键阶段:

    1. 链接提取:把用户输入的一坨文字变成标准列表。
    2. 批量文案提取:利用插件获取视频口播稿。
    3. 自动化基建:自动创建飞书 Base 和 Table。
    4. 循环改写与归档:利用 LLM 按照“爆款公式”改写并存入表格。

    🛠️ 第一阶段:链接提取与预处理

    用户输入的内容往往是混乱的,包含各种介绍语和多个链接。我们需要做的第一步就是“清洗数据”。

    • 节点选择:大模型节点(LLM)
    • Prompt 设定
      “提取出用户消息中的所有 URL 链接,去除无关文本,整理并输出为标准的数组格式 (Array)。”
    • 目的:为后续的“数组循环”做准备,只有标准的 Array 格式才能被 Loop 节点识别。

    🧩 第二阶段:批量提取视频文案

    拿到了链接列表,下一步是获取视频里的内容。

    • 插件选择:这里推荐使用 阿里百炼 的相关插件(或者扣子市场里的 提取视频文案 类插件)。
    • 配置重点
      • API Key:一定要在插件配置页填入你的 API Key(例如阿里百炼的 Key),否则无法调用。
      • 输入参数:直接将上一步提取的 URL 数组传给插件,实现批量提取。

    🏗️ 第三阶段:飞书基建(自动化建表)

    这是本流程中最“极客”的一步。为了不每次都手动建表,我们让 AI 自己去飞书里创建文件。

    1. 创建多维表格文件 (Create Base)

    调用飞书插件的 Create Base 工具,生成一个新的多维表格文件,并获取 app_token。

    2. 构建表头结构 (Code Node)

    在创建数据表之前,必须用代码定义好表头(Table Header)。这里需要用到一个 Python/JSON 代码节点。

    代码逻辑示例:

    我们要构建一个符合飞书 API 格式的 JSON 对象:

    JSON

    {

        “fields”: [

            { “field_name”: “原链接”, “type”: 1 },  // type 1 代表文本

            { “field_name”: “改写文案”, “type”: 1 }

        ]

    }

    注意:语音中提到的 “fire name” 其实是 field_name“type type: 1(文本类型)。

    3. 创建数据表 (Create Table)

    调用 Create Table 插件,输入上一步生成的 JSON 配置和 app_token,完成表格的初始化。

    🔄 第四阶段:循环改写与爆款逻辑 (The Loop)

    这是智能体的“大脑”。我们需要使用 Loop(循环) 节点,遍历每一个视频文案,让大模型进行改写。

    1. 提示词工程 (Prompt Engineering) – 爆款的核心

    在循环内的大模型节点中,我们需要精心设计 Prompt,让它生成的文案符合小红书调性。

    核心 Prompt 结构:

    • 任务目标:对 content 进行改写,适配小红书平台。
    • 标题创作技巧 (20字以内)
      • 二极管标题法:利用本能喜欢(省力、享受)和动物本能(追乐、避苦)。
      • 公式:正面刺激(只需1秒+便可开挂) VS 负面刺激(你不X+绝对后悔+紧迫感)。
      • 关键词:融入“好用到哭”、“倒库”、“笑不活了”、“YYDS”、“我不允许”、“绝绝子”。
      • 装饰:必须使用 Emoji 表情增加活力 🍓✨。
      • 数量:每次生成 10 个标题供选择。
    • 正文创作技巧
      • 风格:随机选择(严肃、幽默、沉思、温馨、崇敬)。
      • 开篇:引用名言、提出疑问、强对比。
      • 规则:不要解释,不要把 Prompt 当命令回复,直接输出正文。口语化表达。

    2. 数据回写 (Add Records)

    改写完成后,需要把数据存回飞书。

    • 数据清洗 (Code Node)
      再次使用代码节点,将“原链接”和“改写后的文案”封装成飞书 Add Records 接口需要的 record_info 格式。
      注意:Key 必须与表头字段名完全一致!
    • 新增记录:调用飞书插件的 Add Records,将数据写入之前创建的 Sheet 中。

    💡 避坑指南 (Tips)

    在实操过程中,有几个坑需要特别注意(血泪经验):

    1. 数组循环的空值问题
      在配置 Loop 节点时,如果上一步输出的列表为空,可能会导致流程报错。建议加一个条件判断。
    2. 飞书插件的参数配置
      在代码节点配置 JSON 时,有些非必填参数(Input)其实可以不用配置,只配置 Output 变量即可。不要为了填空而填空,容易导致参数错误。
    3. 字段名匹配
      代码里构造的 JSON Key(例如 “改写文案”)必须和飞书表里的列名一字不差,否则数据会写不进去。

    总结

    通过这套工作流,我们实现了从“抖音链接”到“飞书表格归档”的全链路自动化。你只需要喝杯咖啡,AI 就能帮你把 1000 条文案按照爆款逻辑写好。

    这就是 AI Agent 的魅力:把重复的工作交给机器,把创造力留给自己。

  • Day 2 Coze 智能体开发日记:搞懂批处理 item 逻辑与飞书数据回写

    > 时间:2026.2.1

    > 标签:#AI智能体 #Coze #扣子 #低代码开发 #Python

    > 摘要:这两天在折腾 Coze(扣子)工作流时踩了两个大坑:一是批处理模式下 item 的迭代逻辑,二是飞书多维表格的数据回写格式。这篇笔记作为复盘,希望能帮大家少走弯路。

    🛑 踩坑一:Coze 批处理模式的“逻辑陷阱”

    在处理批量任务(比如批量解析视频链接)时,很多程序员(包括我)的第一反应是找“数组下标”,想用类似 arr[i] 的方式去控制循环。但 Coze 的设计逻辑完全不同。

    1. item 到底是什么?

    在 Coze 的批处理节点中,item 实际上是迭代变量,而不是数组下标。

     * 误区:试图寻找 Item 0、Item 1 这样的索引访问方式。

     * 正解:当你把一个数组(例如 url_list)绑定给批处理节点后,系统会自动运行一个 For Each 循环。

    流程拆解:

    url_list (输入数组) → 循环遍历 → 每次取出一个元素赋值给 item → 传入节点执行。

    举个简单的例子,如果你的 url_list 是 [“url_A”, “url_B”, “url_C”]:

     * 第 1 轮循环:item = “url_A”

     * 第 2 轮循环:item = “url_B”

     * 第 3 轮循环:item = “url_C”

    你只需要在输入参数里填入 item,系统就会自动“喂”进当前轮次的数据。

    2. 为什么要这么设计?

    这种“去下标化”的设计其实是为了低代码的健壮性:

     * 防越界:不需要手动管理 i < length,避免数组越界报错。

     * 自适应:无论数组里有 3 个还是 100 个元素,配置都不用改,item 自动适配。

     * 直观:对非技术背景的开发者更友好,专注“处理当前这个”,而不是“处理第几个”。

    🛠️ 实战二:用 Python 代码将对话存入飞书多维表格

    意图识别后的对话数据(用户问题、AI 回复),如果想通过 API 写入飞书多维表格,直接连接通常会报错。最稳妥的方式是加一个 Python 代码节点 进行数据清洗和格式化。

    1. 飞书侧准备

    首先在飞书多维表格中建好表,表头名称要记住(后面代码里要用):

     * 用户问题 (文本)

     * AI知识库回答 (文本)

     * AI大模型回答 (文本)

    (此处建议插入:飞书多维表格的表头截图)

    2. Coze 节点配置

    在“开始”节点和“飞书-新增记录”节点之间,插入一个 Python 代码节点。

    输入变量 (Input):

     * question ← 引用自 USER_INPUT

     * answer ← 引用自 知识库回复

     * ai_answer ← 引用自 大模型回复

    输出变量 (Output):

     * 变量名:record_info

     * 关键点:变量类型必须选择 Array<Object>(数组包含对象)。因为飞书的 Add Records 接口默认接受列表格式,哪怕你只存一条数据。

    (此处建议插入:Coze 代码节点输入/输出配置的截图)

    3. 核心代码 (Copy 即可用)

    这是由于语音输入容易出错(比如把 params 听成 PA i m s),特意整理后的标准代码。

    > 注意:代码中的 Key(如 “用户问题”)必须与你飞书表格的列名一字不差!

    async def main(args: Args) -> Output:

        # 1. 获取输入参数

        params = args.params

        # 2. 提取字段 (对应 Input 面板的变量名)

        question = params[‘question’]

        answer = params[‘answer’]

        ai_answer = params[‘ai_answer’]

        # 3. 构建输出结构

        # 结构必须符合飞书要求:Array [ Object { fields: { … } } ]

        ret: Output = {

            “record_info”: [

                {

                    “fields”: {

                        # 左边是飞书表格列名,右边是变量

                        “用户问题”: question,

                        “AI知识库回答”: answer,

                        “AI大模型回答”: ai_answer

                    }

                }

            ]

        }

        # 4. 返回

        return ret

    ⚠️ 避坑检查清单 (Checklist)

    在点击“试运行”之前,请对照检查这 4 点,能节省 90% 的 debug 时间:

     * 列名对齐:代码 fields 里的 Key 和飞书表头是否完全一致?(包括空格!)

     * 数据层级:输出的 record_info 必须包裹在 [] (List) 里,List 里面才是 {} (Dict)。直接返回 Dict 会报错。

     * 类型声明:在 IDE 右侧的输出栏,record_info 的类型选对了吗?必须是 Array,子项是 Object。

     * 空值兼容:如果 AI大模型回答 可能为空,确保飞书表格对应列允许空值,或者在代码里做个 if 判断处理。

    > 写在最后:

    > AI Agent 开发很多时候不是难在算法,而是难在这些中间件的数据对齐上。把这两个点打通,数据流转就顺畅多了。希望这篇笔记对你有用!

  • JAVA  Day2~3 26.2.1

    import java.text.NumberFormat;

    import java.util.Locale;

    import java.util.Scanner;

    public class Calculator {

        //代码补全 补全当前语句 Ctrl+Shift+Enter

        public static void main(String[] args) {

            //定义变量:类型 变量名 =

    //        Scanner scanner =new Scanner(System.in);

            //记不住这么多类型也可以在这个例子中用var

    //var scanner =new Scanner(System.in);

            Scanner scanner = new Scanner(System.in);

            //alt + Enter 引入局部变量 快速生成前面的变量定义

            //nextLine nextInt nextDouble

            System.out.println(“请输入本金”);

            double p = scanner.nextDouble();

            System.out.println(“请输入年利率”);

            double yr = scanner.nextDouble();

            double mr = yr / 12.0 / 100;

            System.out.println(“请输入还款月数”);

            int m = scanner.nextInt();

            double pow = Math.pow(1 + mr, m);

            double payment = p * mr * pow / (pow – 1);

            System.out.println(NumberFormat.getCurrencyInstance(Locale.CHINA).format(payment));

        }

    }

    import java.text.NumberFormat;

    import java.util.Locale;

    import java.util.Scanner;

    public class Calculator2 {

        //代码补全 补全当前语句 Ctrl+Shift+Enter

        public static void main(String[] args) {

            //定义变量:类型 变量名 =

    //        Scanner scanner =new Scanner(System.in);

            //记不住这么多类型也可以在这个例子中用var

    //var scanner =new Scanner(System.in);

            //这些都是核心类库实现的 不用重复造轮子

            //Ctrl + N 就可以

            Scanner scanner = new Scanner(System.in);

            //alt + Enter 引入局部变量 快速生成前面的变量定义

            //nextLine nextInt nextDouble

            System.out.println(“请输入本金”);

            double p = scanner.nextDouble();

            if(p<=0) { // 取反的运算符优先级比较高 但是我们应该先比较再取反 所以得用括号

                //这里改回 p<=0 不用 !(p>0) 可以使用 Alt+Enter 取反

                System.out.println(“贷款金额必须 > 0”);

                return;

            }

            System.out.println(“请输入年利率”);

            double yr = scanner.nextDouble();

            if(yr < 1.0 || yr > 36.0) { //这里也不要!(yr >= 1.0 && yr <= 36.0)

                System.out.println(“年利率必须是 1~36”);

                return;

            }

            double mr = yr / 12.0 / 100;

            System.out.println(“请输入贷款月数”);

            int m = scanner.nextInt();

            if(m < 1 || m > 360){ //改一下!(m>=1 && m<=360)

                System.out.println(“贷款月数必须是1~360”);

                return;

            }

            double pow = Math.pow(1 + mr, m);

            double payment = p * mr * pow / (pow – 1);

            System.out.println(NumberFormat.getCurrencyInstance(Locale.CHINA).format(payment));

        }

    }

    public class TestLoop {

        public static void main(String[] args){

            for(int i = 0;i<=3;i++){

                System.out.println(“Hello”);

    //            i=4;

            }

        }

    }

    public class TestSum {

        public static void main(String[] args) {

            int sum = 0;

            for(int i = 1;i<=100;i++){

    //            System.out.println(“sum:” + sum + ” i:” + i);

    //            sum = sum + i;

                sum += i;

            }

            System.out.println(sum);

        }

    }

    public class TestSum {

        public static void main(String[] args) {

            int sum = 0; //Alt + Shift + 上下箭头可以移动 变量的作用范围只在花括号内有效

            for(int i = 1;i<=100;i++){

    //            System.out.println(“sum:” + sum + ” i:” + i);

    //            sum = sum + i;

                sum += i;

            }

            System.out.println(sum);

        }

    }

    这是一份经过深度整理、逻辑连贯的 Java 学习全能笔记

    我将你之前提供的零散片段(环境配置、数学运算、逻辑判断、代码重构)整合成了一个完整的知识体系。这份笔记不仅涵盖了“怎么写代码”,还包含了“怎么写好代码”的职业技巧。

    Java 学习全能笔记:从入门到逻辑进阶

    1. 环境与工具 (Environment & Tools)

    命令行基础 (CMD)

    • 编译 (Compile): javac 文件名.java
      • 注意:必须带 .java 后缀。
    • 运行 (Run): java 类名
      • 注意:不要带后缀,这里使用的是 class 的名字。
    • 乱码解决: Windows 命令行默认 GBK,而代码通常是 UTF-8。
      • 命令:javac -encoding gb18030 xxx.java

    IDE 效率神器 (IntelliJ IDEA)

    • 常用简写:
      • psvm $\rightarrow$ 生成 public static void main…
      • sout $\rightarrow$ 生成 System.out.println…
    • 万能键: Alt + Enter (自动修复错误、导包、逻辑反转)。
    • 文档查看: Ctrl + Q (查看当前类或方法的官方文档)。

    2. 基础语法与类型 (Syntax & Types)

    变量定义

    • 标准格式: 类型 变量名 = 值;
    • 示例:
      • int x = 10;
      • double y = 9.9;
      • char c = ‘A’;
      • String s = “Hello”;

    类型严格性 (Strict Typing)

    警示: Java 是强类型语言。

    • 错误示例: int a = 1000.0;
    • 原因: 不能直接将小数 (double) 塞进整数 (int) 容器中,会报错 incompatible types: possible lossy conversion。

    3. 数学运算与陷阱 (Math Operations)

    除法陷阱 (The Division Trap)

    这是新手最容易踩的坑,请务必区分:

    运算类型代码示例结果说明
    整数除法5 / 31直接截断小数部分,不四舍五入。
    小数除法5.0 / 31.666…只要有一个操作数是小数,结果就是小数。
    整数除零5 / 0报错抛出 ArithmeticException,程序崩溃。
    小数除零5.0 / 0Infinity结果为“无穷大”,程序不会崩溃。

    数学工具类 (Math Class)

    • 幂运算: Java 没有 ^ 运算符。
      • 用法: Math.pow(底数, 指数)
      • 场景: 房贷计算等额本息公式。
    • 其他: Math.max(a, b) 取最大值。

    其他运算符

    • 取模 (%): 取余数,如 5 % 3 = 2。
    • 自增: a++ 等同于 a = a + 1。

    4. 逻辑判断与布尔代数 (Logic & Boolean)

    比较运算符

    结果永远只有 true 或 false。

    • == (等于), != (不等于)
    • >, <, >=, <=

    逻辑运算符 (三大金刚)

    • && (与/AND): 两个条件成立,结果才为真。
    • || (或/OR): 只要有一个条件成立,结果就为真。
    • ! (非/NOT): 黑白颠倒,真变假,假变真。

    易错点:区间判断

    • 数学写法:$1 \le x \le 10$
    • Java 写法:x >= 1 && x <= 10
    • 注意:Java 不支持连续比较,必须拆开写!

    5. 逻辑架构与代码重构 (Structure & Refactoring)

    基础结构: If-Else

    Java

    if (条件) {

        // 条件为 true 时执行

    } else {

        // 条件为 false 时执行

    }

    进阶技巧:卫语句 (Guard Clauses)

    拒绝“嵌套地狱” (Nested Hell): 不要把正确的逻辑一层层包在 if 里。

    • 优化前 (金字塔式):
      Java




      if (User != null) {
    •     if (Age > 18) {
    •         // 执行业务…
    •     }
    • }




    • 优化后 (卫语句 – 推荐):
      • 核心思想:先处理异常/非法情况,直接 return
    • Java




      if (User == null) return; // 拦截1
    • if (Age <= 18) return;    // 拦截2
    • // 执行业务… (代码层级平坦,阅读更清晰)




    IDE 黑科技:逻辑反转 (Invert If)

    • 操作: 光标放在 if 上 $\rightarrow$ Alt + Enter $\rightarrow$ 选择 Invert ‘if’ condition。
    • 作用: 自动将 > 变为 <=, == 变为 !=,并交换代码块。这是练习逻辑思维和重构代码的神器。

    下一步建议:

    目前的笔记已经构建了完美的逻辑基础。接下来的自然步骤是处理“重复性工作”。

    Would you like me to create a concise intro guide for “Loops” (For/While loops) to complete your control flow knowledge?

  • 📝智能体开发实战笔记 (Day 1-2)

    核心主题: RAG 原理、Prompt 工程、工作流编排

    1. 🧠 智能体的底层逻辑:RAG (检索增强生成)

    智能体的对话流本质就是三步走:输入 (Input) -> 执行 (Execution) -> 输出 (Output)。 为了让 AI 懂私有知识(比如路飞课程、公司文档),我们需要 RAG

    ⚙️ 数据处理流程 (Pipeline)

    1. 分块 (Chunking): 把知识库的长文本切成小块。
    2. 嵌入 (Embedding): 扔进 Embedding Model,把文字变成数学向量。
    3. 入库: 存入 向量数据库 (Vector DB)

    🔄 检索与生成 (Retrieval & Generation)

    当用户提问时:

    1. 检索: 把用户的问题也变成向量,去向量库里“搜”最相似的内容。
    2. 注入: 把搜到的内容(Context)填入 Prompt。
    3. 生成: Prompt + 检索到的知识 + 用户问题 -> 扔给大模型 -> 输出答案。

    💡 核心价值: 解决大模型“胡说八道”的问题,让它基于事实回答。

    2. 🗣️ Prompt 工程 (提示词编写)

    提示词是智能体的“大脑皮层”,必须用 Markdown 格式 写才规范。

    📐 结构规范

    • 标题层级: 用 # 表示一级标题,## 表示二级标题(让 AI 读懂结构)。
    • 变量注入: 用双大括号 {{ }} 包裹变量。
      • {{user_input}}: 用户输入的内容。
      • {{context}}: 知识库检索到的内容。

    🛡️ 系统提示词 (System Prompt) 模板

    Markdown

    # Role

    你是一个专业的课程顾问。

    # Constraints

    1. 你必须基于 {{context}} 回复用户,不能编造事实。

    2. 如果知识库中没有答案,请直接回答“不知道”。

    # Task

    回答用户的问题:{{user_input}}

    3. 🖼️ 图片检索与多模态

    智能体不仅能回文字,还能回图片(比如返回课程海报、架构图)。

    • 数据源: 本地图片、在线 URL。
    • 难点: 图片多了检索不准。
    • 解决方案: 人工标注 (Annotation)。给图片写一段准确的文字描述,检索时其实是在检索这段描述。

    📤 图片输出的 Prompt 技巧

    如果需要模型返回图片,要在 Prompt 里规定 Markdown 图片格式:

    Markdown

    # Task

    你必须整理相关图片并返回。

    # Output Format

    请严格按照以下格式输出图片:

    ![图片描述](URL)

    (注意:这里必须告诉模型用标准的 Markdown 图片语法,即![alt](url))

    4. 🔀 高级工作流:让 AI 更像人 (Router & Optimization)

    为了不让 AI 变成只会答题的机器人,我们需要在检索之前加两个“脑子”:

    🚦 第一关:意图识别 (Intent Recognition / Router)

    • 为什么要做? 区分用户是在“闲聊”还是“办正事”。
    • 逻辑分流:
      • 👉 情况 A(打招呼/无关话题): 走 通用大模型(回复“你好”、“天气不错”)。
      • 👉 情况 B(问课程/业务问题): 走 知识库检索流程(RAG)。

    🔧 第二关:问题优化 (Query Rewriting)

    • 为什么要做? 用户问得可能很烂、很模糊(比如只问“多少钱?”)。
    • 怎么做? 在检索之前,先用一个模型把用户的问题改写成机器能听懂的完整句子。
      • 用户问: “贵吗?”
      • 改写后: “请问课程价格是多少?” -> 再去检索知识库,准确率翻倍!

  • JAVA Day1-2 2026.1.31

    根据你提供的截图,我已经帮你整理好了复习笔记,涵盖了 Java 编译运行的易错点 以及 字符编码(ANSI/GBK/UTF-8) 的核心知识点。

    笔记已创建成功,内容如下:

    Java 基础与编码问题复习笔记

    1. Java 编译与运行命令

    • 编译 (Compile):
      • 指令:javac 文件名.java
      • 注意: 必须带文件后缀名 .java。
      • 错误示例: javac v2 (截图中的报错:Class names… only accepted if annotation processing…)
      • 正确示例: javac v2.java
    • 运行 (Run):
      • 指令:java 类名
      • 注意: 只需要写 class 的名字,不要带 .class 或 .java 后缀。
      • 示例: 虽然文件名是 v2.java,但如果代码里定义的是 class C,运行时应输入 java C。

    2. 字符编码核心区别 (ANSI vs GBK)

    • ANSI:
      • 它不是一种具体的编码,而是系统默认本地编码的代称。
      • 中文简体 Windows 系统中,ANSI 等同于 GBK
      • 在英文系统中,ANSI 等同于 ASCII。
    • GBK:
      • 具体的中文编码标准,兼容 ASCII,是中文 Windows 的默认编码。
    • 关键点: 在 Windows 记事本中保存为 “ANSI” 格式,实际上就是保存成了 GBK 编码。

    3. 常见乱码/编译错误

    • 现象: error: unmappable character for encoding UTF-8 (截图中的报错)。
    • 原因:
      • 源代码文件包含中文(如 “你好”)。
      • 文件保存的编码(通常是 Windows 记事本默认的 GBK/ANSI)与编译器 javac 读取时使用的默认编码(通常是 UTF-8)不一致。
    • 解决方案:
      • 推荐: 使用编辑器(如 Notepad++)将文件格式转换为 UTF-8 (无 BOM) 保存。
      • 临时: 编译时强制指定编码格式,例如:javac -encoding GBK v3.java。

    这是一份基于你上传的截图整理的详细复习笔记,涵盖了从命令行编译到 IDE 配置的全过程。笔记已保存,你随时可以回顾。

    Java 学习笔记:编译、编码与工具配置

    1. 命令行编译与运行 (CMD)

    • 编译 (javac):
      • 核心规则: 必须带上文件后缀名 .java。
      • 易错点: 输入 javac v2 会报错,必须输入 javac v2.java。
    • 运行 (java):
      • 核心规则: 只写类名 (Class Name),不要带 .class 或 .java 后缀。
      • 区分文件名与类名: 如果你的文件名是 v2.java,但代码里定义的是 class C,那么编译后生成的是 C.class。运行时必须输入 java C,而不是 java v2。

    2. 中文乱码与编码问题 (Encoding)

    • ANSI 与 GBK 的关系:
      • ANSI: 是 Windows 系统“默认本地编码”的代称。在简体中文 Windows 下,ANSI 实际上就是 GBK
      • GBK: 是具体的中文编码标准。
    • 乱码报错:
      • 现象: error: unmappable character for encoding UTF-8。
      • 原因: 你的代码文件是用 GBK (Windows 默认) 保存的,但编译器默认按 UTF-8 读取,导致中文无法识别。
    • 解决方案:
      • 指定编码编译: 使用命令 javac -encoding gb18030 v3.java。
      • 避坑: 不要使用 -encoding ansi,Java 编译器不识别 “ANSI” 这个名字,必须用 “GBK” 或 “GB18030″。

    3. 开发工具 (IDE) 设置

    • Eclipse:
      • 快捷键设置: 想要像老手一样用 Alt + / 呼出代码提示,需要去菜单栏 Window -> Preferences -> Keys,搜索 “Content Assist”,把 Binding 改成 Alt + /。
      • 实战: 设置好后,输入 main 再按 Alt + / 就能自动生成主方法。
    • IntelliJ IDEA:
      • 直接输入 psvm 或者 main 即可生成 public static void main 方法,无需额外配置。

    4. JShell 交互式练习

    • 变量命名: 变量名不能以数字开头,例如 var 123 是错误的语法。
    • Scanner: 在 JShell 里也可以用 new Scanner(System.in) 来练习读取输入。

    希望这份笔记能帮你巩固这些基础细节!如果有新的报错截图,随时发给我,我帮你加进笔记里。

    这是一份基于你上传的截图整理的详细复习笔记,涵盖了从命令行编译到 IDE 配置的全过程。笔记已保存,你随时可以回顾。

    Java 学习笔记:编译、编码与工具配置

    1. 命令行编译与运行 (CMD)

    • 编译 (javac):
      • 核心规则: 必须带上文件后缀名 .java。
      • 易错点: 输入 javac v2 会报错,必须输入 javac v2.java。
    • 运行 (java):
      • 核心规则: 只写类名 (Class Name),不要带 .class 或 .java 后缀。
      • 区分文件名与类名: 如果你的文件名是 v2.java,但代码里定义的是 class C,那么编译后生成的是 C.class。运行时必须输入 java C,而不是 java v2。

    2. 中文乱码与编码问题 (Encoding)

    • ANSI 与 GBK 的关系:
      • ANSI: 是 Windows 系统“默认本地编码”的代称。在简体中文 Windows 下,ANSI 实际上就是 GBK
      • GBK: 是具体的中文编码标准。
    • 乱码报错:
      • 现象: error: unmappable character for encoding UTF-8。
      • 原因: 你的代码文件是用 GBK (Windows 默认) 保存的,但编译器默认按 UTF-8 读取,导致中文无法识别。
    • 解决方案:
      • 指定编码编译: 使用命令 javac -encoding gb18030 v3.java。
      • 避坑: 不要使用 -encoding ansi,Java 编译器不识别 “ANSI” 这个名字,必须用 “GBK” 或 “GB18030″。

    3. 开发工具 (IDE) 设置

    • Eclipse:
      • 快捷键设置: 想要像老手一样用 Alt + / 呼出代码提示,需要去菜单栏 Window -> Preferences -> Keys,搜索 “Content Assist”,把 Binding 改成 Alt + /。
      • 实战: 设置好后,输入 main 再按 Alt + / 就能自动生成主方法。
    • IntelliJ IDEA:
      • 直接输入 psvm 或者 main 即可生成 public static void main 方法,无需额外配置。

    4. JShell 交互式练习

    • 变量命名: 变量名不能以数字开头,例如 var 123 是错误的语法。
    • Scanner: 在 JShell 里也可以用 new Scanner(System.in) 来练习读取输入。

    希望这份笔记能帮你巩固这些基础细节!如果有新的报错截图,随时发给我,我帮你加进笔记里。

    这是为你整理的完整 Java 复习笔记,结合了你上传的所有截图(包括编译错误、编码设置、IDE技巧以及 JShell 的语法细节)。

    笔记已为你创建,以下是详细内容供你参考:

    Java 复习笔记:基础、编码与 JShell

    1. 命令行编译与运行 (CMD)

    • 编译 (javac):
      • 规则: 必须带文件后缀名 .java。
      • ❌ 错误: javac v2
      • ✅ 正确: javac v2.java
    • 运行 (java):
      • 规则: 只能写类名,不能带后缀 .class。
      • 文件名 vs 类名: 如果文件名为 v2.java 但代码中定义的是 class C,运行时必须输入 java C。

    2. 编码与乱码问题 (Encoding)

    • 原理: Windows 记事本默认保存为 ANSI (在简体中文系统下即 GBK),而 Java 编译器默认按 UTF-8 读取,遇到中文会报错 unmappable character。
    • 解决方案:
      • 编译时指定编码: javac -encoding gb18030 v3.java (GB18030 兼容性最好)。
      • ❌ 错误尝试: -encoding ansi 是无效的,Java 不识别 “ANSI” 这个名称。

    3. IDE 快捷键与技巧

    • Eclipse:
      • 修改提示快捷键: 去 Window -> Preferences -> Keys -> 搜索 “Content Assist”,将 Binding 改为 Alt + /。
      • 实战: 输入 main 后按 Alt+/ 自动生成主方法。
    • IntelliJ IDEA:
      • psvm -> 自动生成 public static void main…。
      • sout -> 自动生成 System.out.println…。

    4. JShell 交互式练习与语法细节

    • Scanner 输入:
      • 初始化: new Scanner(System.in)。
      • 用法: next() 读取单词,nextLine() 读取整行。
    • 字符 (char) vs 字符串 (String):
      • ‘A’ (单引号) 是字符。”A” (双引号) 是字符串。
      • ❌ 错误: ‘bbb’ (报错 unclosed character literal,字符只能存一个字母)。
      • ❌ 错误: ” (报错 empty character literal,字符不能为空)。
    • 文本块 (Text Block):
      • “”” (三个双引号) 启动后必须换行,如果直接跟内容会报错 illegal text block… missing line terminator。
    • 变量命名:
      • ❌ 错误: var 123 (报错,变量名不能以数字开头)。

    这份笔记已经结合了你最新上传的关于 JShell、转义字符和文本块的截图,内容更加完整了。

    笔记已更新,重点补充了以下 JShell 和语法细节:

    Java 学习复习笔记:基础、编码与 JShell 进阶

    1. 命令行编译与运行 (CMD)

    • 编译: javac 文件名.java (必须带后缀 .java)。
    • 运行: java 类名 (不要带后缀)。
      • 易错点: 文件名和类名不一致时,运行时要写类名 (例如 java C)。

    2. 编码与乱码 (Encoding)

    • 原因: Windows 记事本默认 GBK,javac 默认 UTF-8。
    • 解决: 使用 javac -encoding gb18030 xxx.java。
    • 注意: -encoding ansi 是无效指令。

    3. IDE 效率技巧

    • Eclipse: 将代码提示 (Content Assist) 改为 Alt + /。
    • IntelliJ IDEA: 使用 psvm 生成 main 方法,sout 生成打印语句。

    4. JShell 与基础语法 (重点新增)

    • 输入读取:
      • System.in.read(): 读取单个字符的 ASCII 码 (例如输入 ‘a’ 返回 97,’A’ 返回 65)。
      • Scanner: 使用 new Scanner(System.in)。next() 读取单词,nextLine() 读取整行。
    • 字符 (char) vs 字符串 (String):
      • 单引号 ‘A’ 是字符;双引号 “A” 是字符串。
      • ❌ 错误 ‘bbb’: 字符只能包含一个字母。
      • ❌ 错误 ”: 字符不能为空。
    • 转义字符 (Escape Characters):
      • \n: 换行 (Newline)。
      • \t: 制表符 (Tab),用于对齐。
      • \b: 退格 (Backspace),会“吃掉”前一个字符 (例如 “123\b4” 输出 124)。
      • \\: 输出一个反斜杠。
    • 文本块 (Text Blocks):
      • 使用三个双引号 “””。
      • 关键规则: 开始的 “”” 后面必须换行,不能直接跟文字,否则报错 missing line terminator。


  • 在Kali Linux中基于Docker容器化部署Gemma大模型踩坑实录

    1. 背景 (The Why)

    作为一名网络安全从业者,我深知 AI 在未来攻防对抗中的重要性。为了验证“安全+AI”的融合场景,我决定在 Kali Linux 环境下,利用 Docker 容器化技术,私有化部署 Google 最新的 Gemma 模型,并搭建漏洞靶场进行联动测试。

    2. 环境准备 (The Setup)

     * 宿主机系统: Kali Linux 2024.4 (VMware)

     * 核心工具: Docker, Ollama

     * 目标模型: gemma3:1b (Google DeepMind 轻量级模型)

    3. 实战过程与踩坑 (The Action)

    Step 1: Docker 环境确认

    首先检查 Docker 服务状态,确保容器环境正常。

    docker ps -a

    可以看到我本地已经运行了 medicean/vulapps 漏洞靶场,为后续 AI 安全测试做好了准备。

    Step 2: 拉取 Ollama 镜像

    使用 Docker 拉取 Ollama 官方镜像。

    docker pull ollama/ollama

    (这里曾遇到网络波动导致下载慢,通过配置镜像加速解决)

    Step 3: 运行 Ollama 容器

    启动容器并映射端口:

    docker run -d -v ollama:/root/.ollama -p 11434:11434 –name ollama ollama/ollama

    Step 4: 模型拉取与排错 (Troubleshooting)

    在尝试拉取模型时,我遇到了 manifest not found 错误:

     * ❌ 错误尝试:docker exec -it ollama ollama run gamma3:1b (拼写错误)

     * ❌ 错误尝试:docker exec -it ollama ollama run llama3 (版本不对)

     * ✅ 解决方案: 查阅官方文档,确认模型准确名称为 gemma3:1b。

     * 成功命令:

       docker exec -it ollama ollama run gemma3:1b

    4. 最终效果 (The Result)

    成功进入交互式对话界面!

    > User: “who are you?”

    > Gemma: “Hi there! I’m Gemma, a large language model created by the Gemma team at Google DeepMind…”

    5. 总结与思考

    这次实战不仅跑通了 AI 私有化部署,更重要的是验证了 Docker 在 Linux 环境下的灵活性。下一步计划:

     * 使用 Python 编写 API 接口调用该模型。

     * 测试用 AI 生成 SQL 注入 Payload 并打向本地的 VulApps 靶场。

  • [Day 5] MySQL 核心复盘:从增删改查到 SQL 注入的底层思考


    很多人觉得 SQL 只是简单的“增删改查”,但在安全工程师眼里,每一条 SQL 语句都是潜在的攻击面

    一、 数据库的“CRUD”与安全映射

    在开发视角下,SQL 的四大金刚是 INSERT, DELETE, UPDATE, SELECT。
    但在我的安全视角下,它们代表着不同的风险:

    1. 查询 (SELECT) —— 信息泄露的源头
    这是 Web 业务中最常用的语句,也是 SQL 注入 (SQLi) 的重灾区。

    • 基础语法: SELECT * FROM users WHERE id = 1;
    • 安全思考: 如果开发者没有过滤 id 的输入,攻击者通过闭合单引号 ‘,配合 UNION SELECT,就能直接把数据库“拖库”。
      • 关键点: 理解 ORDER BY 猜解字段数,理解 UNION 联合查询的逻辑。

    2. 新增 (INSERT) —— 存储型攻击的温床

    • 基础语法: INSERT INTO messages (content) VALUES (‘Hello’);
    • 安全思考: 如果在这里插入了一段恶意的 XSS 代码(如 <script>alert(1)</script>),那么所有查看这条留言的用户都会中招。这就是存储型 XSS

    3. 修改 (UPDATE) & 删除 (DELETE) —— 破坏性攻击

    • 基础语法: UPDATE users SET password=’123′ WHERE user=’admin’;
    • 安全思考: 这类语句如果没有加上 WHERE 限制,或者存在越权漏洞 (IDOR),可能导致全站数据被篡改或清空。

    二、 实战演练:命令行下的数据掌控

    拒绝图形化工具的“傻瓜式操作”,回归命令行(CLI)才是理解数据库的最佳方式。
    今日实战记录:

    环境: 本地 MySQL 5.7 / 8.0 环境。建库建表:
    CREATE DATABASE security_test;

    USE security_test;

    CREATE TABLE users (

        id INT AUTO_INCREMENT PRIMARY KEY,

        username VARCHAR(50),

        password VARCHAR(50)

    );

    1. 数据操纵:
      • 插入一条模拟管理员数据:INSERT INTO users (username, password) VALUES (‘admin’, ‘123456’);
      • 精准查询:SELECT * FROM users WHERE username = ‘admin’;

    Keep Coding, Keep Hacking. 💻🛡️

  • Day 4:前端不只是页面,是漏洞的入口

    一、HTML:寻找“数据入口” (The Gate)

    对于开发来说,HTML 是骨架。
    对于安全来说,HTML 是 攻击载荷(Payload)的发射台

    在浏览了大量的教程后,我发现 90% 的标签(如 div, span, p)对于初级渗透来说只是噪音。我将精力死死锁定在了一个标签上:<form> 表单

    1. 为什么是 Form?

    因为这是用户与服务器交互的核心通道。SQL 注入、文件上传、逻辑绕过,几乎都是从这里开始的。

    2. 攻击者眼里的 Form 结构

    我看代码时,只关注三个属性:

    • action“靶心在哪里?” 数据被送到了哪个后端文件(如 login.php)?这决定了我要攻击的目标。
    • method“子弹怎么飞?”
      • GET:参数在 URL 里裸奔,容易被日志记录,但也容易修改。
      • POST:参数藏在 HTTP Body 里,这也是我在 Burp Suite 里重点抓包的地方。
    • input 的 name 属性“参数名是什么?” 这是后端接收变量的依据(如 $_POST[‘username’]),也是我构造 SQL 注入语句的拼接点。

    实战感悟:
    以后看到一个输入框,我脑子里出现的不再是“请输入密码”,而是:
    select * from users where username = ‘ [我的Payload] ‘

    //Register.html

    <!DOCTYPE html>

    <html lang=”en”>

    <head>

        <meta charset=”UTF-8″>

        <title>Vito Register Page</title>

        <style type=”text/css”>

          div{

            width: 310px;

            background: #ccc;

            padding: 20px;

            margin-top: 50px;

            margin-right: auto;

            margin-bottom: 0px;

            margin-left: auto;

          }

          h1{

            text-align: center;

          }

          input{

            width: 302px;

            height: 30px;

            border: 1px solid #333;

          }

          .submit{

            width: 308px;

            height: 30px;

            border: 1px solid #333;

            margin-top: 20px;

            border-radius: 5px;

            background: green;

            color: #fff;

          }

          a{

            text-decoration: none;

            color: #222;

          }

        </style>

    </head>

    <body>

      <div>

        <h1>Vito Register Page</h1><hr>

        <form action=”” method=”post” name=”form”>

          Username:<br>

          <input type=”text” name=”username”><br>

           <!–为了使用户体验更好 加一个span标签–>

          <span></span><br>

            Password:<br>

          <input type=”password” name=”password”><br>

            <span></span><br>

            Confirm Password:<br>

          <input type=”password” name=”repass”><br><br>

            <span></span><br>

            <input type=”submit” name=”submit” class=”submit” value=”注册”>

        </form>

        <a href=”login.html”>已注册,去登录</a>

      </div>

    </body>

    </html>

    <script>

        //获取元素

        var form = document.forms[‘form’];

        var username = form.elements[‘username’];

        var password = form.elements[‘password’];

        var repass = form.elements[‘repass’];

        var span=form.getElementsByTagName(‘span’);

        //表单验证

        //1.用户体验

        //获取焦点事件

        username.onfocus = function (){

            span[0].innerHTML=”;

        }

        //失去焦点时进行判断

        username.onblur = function (){

            var len = this.value.length;

            if(len == 0){

                span[0].innerHTML = (‘请输入用户名’);

                span[0].style.color=’red’;

            }

            else if(len < 4 || len>18){

                span[0].innerHTML = ‘帐号长度为4-18位!’;

                span[0].style.color=’blue’;

            }

            else{

                span[0].innerHTML = ‘✅’;

                span[0].style.color=’green’;

            }

        }

        //获取焦点事件

        password.onfocus = function (){

            span[1].innerHTML = ”;

        }

        password.onblur = function (){

            var len = this.value.length;

            if(len == 0){

                span[1].innerHTML = (‘请输入密码’);

                span[1].style.color=’red’;

            }

            else if(len < 4 || len>18){

                span[1].innerHTML = ‘密码长度为4-18位!’;

                span[1].style.color=’blue’;

            }

            else{

                span[1].innerHTML = ‘✅’;

                span[1].style.color=’green’;

            }

            repass.onfocus = function (){

                span[2].innerHTML = ”;

            }

            repass.onblur = function (){

                if(password.value != this.value){

                    span[2].innerHTML=’密码不一致’;

                    span[2].style.color = ‘red’;

                }

                else{

                    span[2].innerHTML = ‘✅’;

                    span[2].style.color = ‘green’;

                }

            }

        }

        //2.数据安全

        form.onsubmit = function (){

            if(

                username.value.length ==0||

                username.value.length < 4||

                username.value.length >18||

                password.value.length ==0||

                password.value.length < 4||

                password.value.length >18||

                password.value != repass.value

            ){

                console.log(‘false’);

                return false;

            }

        }

    </script>

    二、JavaScript:操纵“傀儡” (The Puppet Master)

    如果说 HTML 是静态的房子,那 JS 就是房子里的管家。
    只要控制了 JS,我就能控制受害者的浏览器。这就是 XSS(跨站脚本攻击) 的本质。

    今天的 JS 学习,我跳过了所有的算法逻辑,只死磕一个概念:DOM (文档对象模型)

    1. DOM 的本质

    DOM 就是 JS 操作 HTML 的接口。

    • 开发用它来做特效。
    • 黑客用它来窃取数据

    2. 两个危险的 DOM 操作

    在学习过程中,我重点复现了两个场景:

    • 读取敏感信息: document.cookie。只要能让受害者的浏览器执行这行代码,Session ID 就到手了,这一步就是“杀人诛心”。
    • 篡改页面内容: document.write() 或 innerHTML。这是 DOM 型 XSS 的高发区。如果后端没有过滤,我输入的 <script> 标签就会被当成代码执行。

    实战感悟:
    XSS 不仅仅是弹一个窗 alert(1) 那么简单,它的本质是 “JS 代码注入”。只要能运行 JS,浏览器里的秘密就都不是秘密了。

    三、今日总结:构建“攻击面”思维

    今天的学习让我明白,安全工程师看代码的视角是反直觉的:

    • 正常思维: 这个功能怎么实现?
    • 黑客思维: 这个输入框如果不按套路出牌会怎样?这个参数如果被我改了会怎样?

    Day 4 的任务圆满结束。
    HTML 表单是我的发射器,JS DOM 是我的遥控器

    (本文为个人学习笔记,仅供技术交流)