博客

  • 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 开发的 “老伙计”,更是用设计语言诠释技术价值观的生动载体。

  • 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 是我的遥控器

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

  • #C++ Day56 Basic Data Structure Chapter6  Linked List-Actual questions Coding  January 22 2026

    #include<iostream>

    using namespace std;

    #define eleType int

    struct SequentialList {

    eleType* elements;

    int capacity;

    int size;

    };

    void initializeList(SequentialList* list, int capacity) {

    list->elements = new eleType[capacity];

    list->capacity = capacity;

    list->size = 0;

    }

    bool isEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int sizeOfList(SequentialList* list) {

    return list->size;

    }

    void destroyList(SequentialList* list) {

    delete[] list->elements;

    list->capacity = 0;

    list->size = 0;

    }

    void insertElement(SequentialList* list, int index,eleType value) {

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

    throw std::invalid_argument(“invalid index”);

    }

    if (list->capacity == list->size) {

    int newCapacity = list->capacity * 2;

    eleType* newList = new eleType[newCapacity];

    for (int i = 0; i < list->size; i++) {

    newList[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = newList;

    list->capacity = newCapacity;

    }

    for (int i = list->size; i > index; i–) {

    list->elements[i] = list->elements[i-1];

    }

    list->elements[index] = value;

    list->size++;

    }

    void removeElement(SequentialList* list, int index) {

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

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    int findElement(SequentialList* list, eleType value) {

    for (int i = 0; i < list->size; i++) {

    if (list->elements[i] == value) {

    return i;

    }

    }

    return -1;

    }

    eleType getElement(SequentialList* list,int index){

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

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    void updateElement(SequentialList* list, int index, eleType value) {

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

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = value;

    }

    int main(){

    SequentialList mylist;

    initializeList(&mylist, 10);

    cout << “isEmpty:” << isEmpty(&mylist) << endl;

    cout << “Size:” << sizeOfList(&mylist) << endl;

    for (int i = 0; i < mylist.capacity; i++) {

    insertElement(&mylist, i, i * 10);

    }

    for (int i = 0; i < sizeOfList(&mylist); i++) {

    cout << getElement(&mylist,i) << ‘ ‘;

    }cout << endl;

    cout << “isEmpty:” << isEmpty(&mylist) << endl;

    cout << “Size:” << sizeOfList(&mylist) << endl;

    int idx = findElement(&mylist, 50);

    //cout << index << endl;

    updateElement(&mylist, idx, 888);

    for (int i = 0; i < sizeOfList(&mylist); i++) {

    cout << getElement(&mylist, i) << ‘ ‘;

    }cout << endl;

    removeElement(&mylist, idx+1);

    for (int i = 0; i < sizeOfList(&mylist); i++) {

    cout << getElement(&mylist, i) << ‘ ‘;

    }cout << endl;

    destroyList(&mylist);

    for (int i = 0; i < sizeOfList(&mylist); i++) {

    cout << getElement(&mylist, i) << ‘ ‘;

    }cout << endl;

    cout << “isEmpty:” << isEmpty(&mylist) << endl;

    cout << “Size:” << sizeOfList(&mylist) << endl;

    return 0;

    }