大苦茶子
type: channel
link: https://tc.xukucha.cn/
description: 大冬天的非常冷，打工人很辛苦，需要一杯奶茶暖暖肚子。
items: 给自己画饼吧
items: web3学习笔记(三)
items: web3学习笔记(二)
items: Vue中强大的图片预览组件
items: web3学习笔记(一)
items: 手动实现SSR，深入了解SSR是怎么一回事
items: 前端迅速通关TS
items: 前端如何实现GPT的打字（streaming）效果
items: 前端如何使用CryptoJS来做好加密
items: 一切发生皆利于我

给自己画饼吧
type: item
date: 2025-09-12T10:38:46+08:00
description: 今天周五，坐在工位上发呆，刷着掘金突然想起来很久没来自己的小站看看了。看到几个月以前写的web3学习笔记，当时奔着高薪、远程办公学的，还花了钱买棵，现在也已经放弃了（web3感觉岗位少、容易跑路），继续做开发的话不如继续做前端或者学习一下python更加靠谱。现在这份工作并不开心，也对未来充满了迷茫。努力工作买辆摩托车，一路骑到西藏，新疆，云南，还抽空考了摩托车驾照。去这些地方的路很不好骑，天气也十分恶劣，当然我也没去。是时候实现这些目标了，思考了很久，决定今年结束或者明年年初辞职去做一些自己想做的事情。

web3学习笔记(三)
type: item
date: 2025-04-21T01:51:00+08:00
description: 到目前为止都很基础，暂时还没有强度web3学习笔记(三)12.映射（Mapping）和地址（Address）12.1.Addresses （地址）以太坊区块链由account组成，把他理解为银行账户，每个账户都有地址，这是账户的唯一标识符。例如：0x0cE446255506E92DF41614C46F1d6df9Cc96918312.2.Mapping（映射）这个和其他语言的概念是一样的：// 地址 到 uint类型变量的映射,映射名 accountBalance mapping (address => uint) public accountBalance; // 反过来的例子 mapping (uint => string) userIdToName;13.Msg.sender在 Solidity 中，有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender，它指的是当前调用者（或智能合约）的 address。一个合约只会在区块链上什么也不干，除非说有人调用了合约中的某些方法（函数）。所以说msg.sdner总是存在的。mapping (address => uint) favoriteNumber; function setMyNumber(uint _myNumber) public { // 更新favoriteNumber这个映射来将_myNumber储存在msg.sender名下 favoriteNumber[msg.sender] = _myNumber; } function whatIsMyNumber() public view returns (uint) { // 拿到储存在address(地址)下的值 return favoriteNumber[msg.sender]; }这个例子中，任何人都能调用setMyNumber在我们合约的地址下存入一个uint。然后可以调用whatIsMyNumber返回储存的uint。14.Requirerequire使得函数在执行过程中，当不满足某些条件时抛出错误，并停止执行。这个就很类似try catch或者说return了。function sayHi(string _name) public returns (string) { // 在solidity中，是没有办法直接比较字符串的，所以要用之前学到的keccak256转换成一个256位的16进制数字比较 require(keccak256(_name) == keccak256("zs")); // 当_name的值为 "zs"的时候才能返回 "Hi!"，不然就抛出错误停止执行 return "Hi!" }所以说，在函数中使用require做前提判断是非常有必要的。15.继承（Inheritance）在一个合约中，如果写了太多的代码，后期管理维护起来非常困难，所以我们要想办法把他们整理好，分散到多个合约中。contract Father { function sayHi() public returns (string) { return "Hi!"; } } // 由于Son是从Father那里继承的，所以当编译部署了Son后，他可以访问Father中的公共方法（Son也能说Hi） contract Son is Father{ function sayHello() public returns (string) { return "Hello!"; } }16.引入（Import）当有多个文件的时候，就可以使用import把一个文件引入另一个文件，这个就不多说了，太熟悉了。// father.sol contract Father { function sayHi() public returns (string) { return "Hi!"; } }// son.sol import "./father.sol"; contract Son is Father{ function sayHello() public returns (string) { return "Hello!"; } }17.Storage与MemoryStorage指的是永久存在区块链中的变量。Memory变量是临时的，我把它理解为存在内存中的，当外部函数对某合约调用完成的时候，这个变量就移除了。大多数的时候，都用不到这些关键字，默认情况下Solidity会自动处理他们。contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uin…

web3学习笔记(二)
type: item
date: 2025-04-14T00:34:00+08:00
description: 主要是学习solidity基础web3学习笔记(二)1.合约Solidity 的代码都包裹在合约里面， 一份合约就是以太应币应用的基本模块， 所有的变量和函数都属于一份合约。// 表示代码所使用的软件许可(license) // SPDX-License-Identifier: MIT // 表示使用的Solidity版本，这里`^0.8.21`表示的是不小于0.8.21版本 pragma solidity ^0.8.21; // contract创建合约，并命名为`HelloWeb3` contract HelloWeb3{ // 声明了一个字符串，赋值为`Hello Web3!` string public _string = "Hello Web3!"; }2.状态变量和整数上一步，我们做了一个合约的外壳，下面学习怎么使用变量状态变量是被永久地保存在合约中。也就是说它们被写入以太币区块链中，想象成写入一个数据库。contract Example { // 这个无符号整数将会永久的被保存在区块链中 // 定义了a的类型为uint，赋值100 uint a = 100; }2.1.uintuint 无符号数据类型， 指其值不能是负数。对于有符号的整数存在名为 int 的数据类型。uint实际上是unint256的简写，所以我们后面也会用到unint8，unint16等等。3.数学运算在 Solidity 中，数学运算很直观明了，与其它程序设计语言相同:加法: x + y减法: x - y,乘法: x * y除法: x / y取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5，余3)乘方: x**ypragma solidity ^0.4.19; contract HelloWeb3 { uint a = 16; uint b = 10 ** a; }4.结构体如果需要更加复杂的数据类型，就需要使用到结构体，个人感觉类似JS中或者其他语言中的类和对象。struct Person { uint age; string name; }5.数组跟其他语言一样，solidity也有数组，不过分两种：静态数组和动态数组。// 固定长度为2，类型为uint uint[2] array1; // 固定长度为5，类型为string string[5] array2; // 动态数组，可以动态添加元素 uint[] array3; // 甚至还有机构体数组 Person[] array4; 5.1.公共数组可以使用public修饰数组，其他合约可以从这个数组读取数据，但是不能写入数据。struct Person { uint age; string name; } Person[] public people;6.定义函数// 这里变量用了_做开头，但这个不是语法要求，是一种习惯 function foo(string _name, uint _age){ }7.使用结构体和数组struct Person { uint age; string name; } Person[] public peopleList; // 创建一个新的Person: Person person1 = Person(172, "zs"); // 将新创建的person1添加进people数组: peopleList.push(person1);当然也可以把创建和push合为一步peopleList.push(Person(172,"zs"))结合一下上面写的函数，定义一个函数，函数里面创造了一个People，然后在peopleList的尾部加入这个People。function foo(string _name, uint _age){ // 创建的People的参数由函数传入 peopleList.push(Person(_age,_name)) }8.私有/公共函数在合约中定义的函数默认为public的，也就是公共的。这就意味着其他的合约也可以调用这个函数，这样是不安全的。所以要有一个习惯，这个函数确定是公共的才把他设置为public。公共的对立面就是私有，那么如何定义一个私有的函数呢？uint[] numbers; // 在函数名字后面使用private关键字，就能把他设置为私有 // 私有函数的名字用_开头，这是一种习惯 function _addToArray(uint _number) private { numbers.push(_number); }9.函数的更多属性9.1.返回值// 函数如果有返回值，有语法规定 // returns 定义了返回值的类型为 string string str = "hello"; fun…

Vue中强大的图片预览组件
type: item
date: 2025-04-08T16:57:00+08:00
description: 亲测非常好用，Vue2和Vue3都可以使用，本文演示Vue3的用法Vue中强大的图片预览组件，v-viewr,支持缩放、旋转等操作作者网站：传送门1.创建demo# 创建一个vue3项目，取名v-viewer npm create vue@latest # 进入目录 cd .\v-viewer\ # 安装依赖 npm i # 安装v-viewer npm install v-viewer@next viewerjs创建以后，清空一下App.vue中默认的组件以及main.ts中引入的样式，方面我们后面操作。然后创建一个组件用来测试效果// App.vue <TestViewer /> // main.ts import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.use(VueViewer) app.mount('#app')// components/TestViewer.vue 2.全局引入v-viewer引入v-viewer及必需的css样式，并使用app.use()注册插件，之后即可使用。有三种使用方法：1.组件；2.指令；3.api引入以后这三种方式都能够使用。// main.ts import { createApp } from 'vue' import App from './App.vue' // 引入viewer的样式 import 'viewerjs/dist/viewer.css' import VueViewer from 'v-viewer' const app = createApp(App) // 使用viewer app.use(VueViewer) app.mount('#app')3.使用组件形式和指令形式一行，都会展示图片列表3.1.组件形式// components/TestViewer.vue // 直接使用全局注册的组件<viewer> <viewer :images="images"> </viewer> 3.2.指令形式// components/TestViewer.vue // 在一个div上使用v-viewer即可 3.3.api形式这种形式可以不用展示出图片列表，例如下图，可以点击按钮唤起预览。要是用这种形式，使用setup就不方便了，因为要使用this。不使用this也可以，那就不能全局引入了。下面演示两种形式：// components/TestViewer.vue // 全局引入，使用this调用api Click to show 非全局引入，去掉main.ts中的app.use(VueViewer) Click to show 4.配置项以上基本就可以满足绝大部分需求了，如果要更加的定制化的话，建议把产品经理打一顿。更多的配置项要自己去viewerjs慢慢看了。

web3学习笔记(一)
type: item
date: 2025-04-07T00:54:00+08:00
description: 纯理论，我都听睡着了。第一天，通过视频来了解区块链原理，B站视频：传送门1.从比特币到区块链比特币是电子现金系统，不依赖于第三方而实现点对点的线上交易区块链是保障比特币可以运行的底层技术区块链解决的最重要的问题是交易各方之间的信任问题2.区块链如何运行2.1.区块与区块链区块：一段时间的账单区块链：账本2.2.账户体系公钥和私钥私钥：只有用户知道的秘钥，用来签名交易公钥：哈希处理后的用户标识符2.3.多人竞争记账为了决定谁有权记录新区块，许多区块链使用工作量证明(PoW)机制：矿工们竞争解决复杂的数学难题（找到使区块头哈希值小于特定目标的随机数）第一个解决问题的矿工获得奖励2.4.UTXO模型未花费交易输出(UTXO)模型是比特币等区块链使用的账户模型：每笔交易都消耗之前交易的输出并创建新的输出钱包余额是所有未花费输出的总和，而不是直接记录账户余额交易输入必须完全消耗，多余部分作为找零返回这种模型有助于提高隐私性和并行处理能力2.5.”双花“和最长链原则双花是指同一笔资金被花费两次的尝试：区块链通过共识机制防止双花链出现分叉的时候，网络认可最长的链作为权威版本交易通常需要等待多个确认才会视为最终确认，防止短期分叉导致的双花2.6.区块链分类公有链：任何人都可以参与，完全去中心化，如比特币、以太坊联盟链：由预选的组织共同维护，部分去中心化，如Hyperledger Fabric私有链：由单一实体控制，中心化程度高，但提供更好的性能和隐私保护3.区块链的骨骼与灵魂3.1.非对称加密函数RSA原理：基于大数分解的困难性，使用两个大质数的乘积作为密钥的基础ECC原理：基于椭圆曲线上的离散对数问题3.2.哈希函数哈希函数特性：单向性：无法从哈希值反推原始数据确定性：相同输入产生相同输出雪崩效应：输入微小变化导致输出显著不同抗碰撞性：难以找到产生相同哈希值的两个不同输入梅克尔树:结构：二叉哈希树，叶节点是数据块的哈希值，非叶节点是其两个子节点哈希值的哈希工作原理：交易被哈希为叶节点相邻的哈希值对被组合并再次哈希，形成上一层节点此过程重复直到获得单一根哈希（梅克尔根）优势：快速验证：可以仅使用梅克尔根验证区块完整性高效证明：轻节点可以通过梅克尔证明（约log₂(n)个哈希）验证交易包含在区块中节省存储空间：轻节点无需存储完整区块数据区块指针：概念：每个区块通过存储前一个区块的哈希值形成链式结构3.3.拜占庭容错问题拜占庭将军问题：问题描述：多个将军需要协调一致的进攻或撤退决策，但通信信道不可靠且将军中可能有叛徒拜占庭容错（BFT）：定义：系统能够在部分节点出现任意故障（包括恶意行为）的情况下继续正常工作理论限制：容忍f个拜占庭故障需要至少3f+1个节点3.4.主流的共识机制PoW（工作量证明）：原理：矿工通过解决计算难题（寻找特定哈希值）来获得创建新区块的权利PoS（权益证明）：原理：节点根据持有代币的数量和时间获得创建新区块的概率DPoS（委托权益证明）：原理：代币持有者投票选出有限数量的代表（通常21-101个）来验证交易和创建区块PBFT（实用拜占庭容错）：原理：通过多轮投票达成共识，适用于已知验证者身份的系统4.没看下去，太理论了不知道在讲什么，有时间慢慢看

手动实现SSR，深入了解SSR是怎么一回事
type: item
date: 2025-03-20T02:20:00+08:00
description: 面试造轮子，工作用轮子0.前言今天参加了一场面试，面试官问了一个问题：如何实现SSR？给我问的有点懵，因为以往都是借助现有的框架比如：Nuxt或者Next来做SSR，自己确实不知道如何实现SSR。面试的时候这个问题没有答好，心里面虽然知道是怎么一回事，但是没有办法表达出来，归根结底还是没有手动实现过。1.创建项目安装依赖注意，node版本要在18以上，不然有可能会出问题。首先新建一个项目目录react-ssr-videos，初始化项目：npm init -y安装依赖：npm install react react-dom express npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli webpack-node-externals nodemon npm-run-all css-loader style-loader安装了一堆东西，简单的来说一下这些都是干什么的，其实不用太过在意，因为现在也不可能让你手动实现SSR，我们只要了解这整个过程就好。核心的依赖，虽然说是手动实现SSR，但是我也不想从原生JS开始写起，还是要用到React的：react - React库的核心包，提供创建组件和管理组件状态的功能。react-dom - 提供DOM特定的方法，包括renderToString（服务器端渲染）和hydrateRoot（客户端水合）。express - Node.js的web应用框架，用于创建HTTP服务器和API路由。node-fetch - 在服务器端使用与浏览器fetch API兼容的网络请求工具。其余的都是些构建编译的东西，感觉确实很复杂：webpack - JavaScript应用的静态模块打包工具，负责将项目代码转换成浏览器可运行的格式。webpack-cli - Webpack的命令行工具，允许通过命令行运行webpack。webpack-node-externals - Webpack插件，防止将node_modules打包进服务器端代码中。@babel/core - Babel的核心包，负责将现代JavaScript代码转换为向后兼容的版本。@babel/preset-env - Babel预设，根据目标环境自动确定需要的转换。@babel/preset-react - Babel预设，添加对JSX语法的支持。@babel/register - 允许在Node.js环境中直接运行使用ES模块和JSX的代码。babel-loader - Webpack加载器，使Webpack能够使用Babel进行代码转换。nodemon - 开发工具，监视文件变化并自动重启Node.js应用。npm-run-all - 允许并行或顺序运行多个npm脚本。style-loader - 将CSS插入到DOM中。css-loader - 解析CSS文件中的@import和url()。2.创建配置文件首先我们需要两个webpack的配置文件，因为我们实际上要为这个应用构建两个不同的包，一个用于服务端，也就是node，一个用于客户端，也就是浏览器。可能会有这个问题：都已经在服务端处理好了，为什么还要为客户端打一个包呢？试想一下，如果不给客户端打包，也就是客户端只有页面没有JS，那么客户端就没有办法交互：点击按钮没反应，输入字符也没有反应。。。。所以说我们不仅仅要有服务端的包，也需要客户端的包，通俗点来讲就是：也就是打包一份JS给客户端。都说到这了，那就顺便提一下，水合的过程：浏览器加载服务器渲染的HTML（用户立即看到页面内容）浏览器加载并执行客户端JavaScript bundleReact通过hydrate函数"接管"已存在的HTML节点React将事件处理器附加到这些DOM节点React构建内部的组件树并与已渲染的HTML匹配此时，页面看起来没有变化，但已经变成了可交互的应用OK，回归正题，把配置完成：// webpack.client.js - 客户端Webpack配置 const path = require('path'); module.exports = { entry: './src/client/index.js', output: { path: path.resolve(__dirname, 'public'), filename: 'bundle.js', publicPath: '/' }, mode: 'development', module: { rules: [ { test: /\.js$/, exclude: /node_modules…

前端迅速通关TS
type: item
date: 2025-03-18T01:43:00+08:00
description: 看完通关TS，不做标题党0.准备安装环境# 全局安装TS npm i typescript -g # 检查TS版本 tsc -v # 安装tsc-node npm i tsc-node -g # 生产TS配置文件 tsc --init1.基础类型字符串类型let a: string = 'asdf'数字类型JS中任意数字类型都没问题。let notANumber: number = NaN;//Nan let num: number = 123;//普通数字 let infinityNumber: number = Infinity;//无穷大 let decimal: number = 6;//十进制 let hex: number = 0xf00d;//十六进制 let binary: number = 0b1010;//二进制 let octal: number = 0o744;//八进制s布尔类型let flag: boolean = true注意一点的是使用new Boolean()会报错，因为new Boolean()结果是一个对象。// 不能将类型“Boolean”分配给类型“boolean”。 // “boolean”是基元，但“Boolean”是包装器对象。如可能首选使用“boolean”。 let flag: boolean = new Boolean(1)空值类型用来表示一个函数没有返回值。function fn(): void { console.log('log') }null和undefined类型undefined表示声明了没有赋值，null表示声明以后赋值了空值。let u: undefined = undefined;//定义undefined let n: null = null;//定义null可以把undefined赋值给void：let u: void = undefined非严格模式下，也可以把null赋值给void，但是严格模式下不行：// 严格模式下会出现报错提示： // 不能将类型“null”分配给类型“void”。 let n: void = null;非严格模式下null和undefined可以赋值给任何类型，但是在严格模式下null只能赋值给null或者any类型；undefined只能赋值undefined，void，或者any类型。let n1: any = null let n2: null = null let n3: undefined = null // 报错 let u1: any = undefined; let u2: null = undefined; // 报错 let u3: undefined = undefined;2.any和unknow所有的类型都可以赋值给any类型，any类型也可以，是完全跳过了TS的检查。let anyValue: any = 10; let str1: string = anyValue; 所有类型也都可以赋值给unkonw类型，但是unknown 类型的值只能赋值给 unknown 或 any 类型。let unknownValue: unknown = "hello"; let str2: string = unknownValue; // 错误：不能将类型 'unknown' 分配给类型 'string'any 可以直接操作，不进行类型检查。console.log(anyValue.length); // 允许（即使可能运行时出错） anyValue.callSomeMethod(); // 允许（即使可能运行时出错）unknown 不能直接操作。console.log(unknownValue.length); // 错误：对象的类型为 'unknown' unknownValue.callSomeMethod(); // 错误：对象的类型为 'unknown'3.接口和对象类型在TS中，我们用来约束对象的类型主要用的就是interface（接口）。//这样写是会报错的 因为我们在person定义了a，b但是对象里面缺少b属性 //使用接口约束的时候不能多一个属性也不能少一个属性 //必须与接口保持一致 interface Person { b: string, a: string } // 报错提示少了b属性 const person: Person = { a: "213" }重名的interface会进行合并：interface Person { a: string, } interface Person { b: number, } // 最终Person接口定义的类型是： // interface …

前端如何实现GPT的打字（streaming）效果
type: item
date: 2025-02-28T01:36:00+08:00
description: 产品经理：你做一个GPT那样的打字效果出来1.获取Api KeyApi Key可以通过https://openrouter.ai获取，自行注册一个就可以免费试用了。这里我选的是Mistral 7B Instruct(free)，为了下面的步骤顺利进行，推荐和我使用一样的。然后就跳转到这个页面，再次点击创建按钮如图输入就行然后就获取到了key，注意自己保存下来2.项目代码搭建项目，我使用的是Next.js，根据个人习惯喜欢用什么框架就用什么框架。传送门：Nextjs# 执行创建Nextjs项目 npx create-next-app@latest我选择了这些配置给出项目代码可以直接食用：app/ ├── components/ │ ├── ApiKeyForm.tsx │ ├── ChatInput.tsx │ └── MessageList.tsx ├── types/ │ └── chat.ts ├── layout.tsx └── page.tsx// app/components/ApiKeyForm.tsx 'use client'; import React, { useState } from 'react'; import { Key } from 'lucide-react'; import type { ApiKeyFormProps } from '../types/chat'; export default function ApiKeyForm({ onSubmit }: ApiKeyFormProps) { const [apiKey, setApiKey] = useState(''); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (apiKey.trim()) { onSubmit(apiKey.trim()); } }; return ( <Key className="w-12 h-12 text-blue-500" /> 输入 OpenRouter API Key 请输入您的API Key以开始对话 setApiKey(e.target.value)} className="w-full p-3 border border-gray-200 rounded-lg focus:outline-none focus:border-blue-500" placeholder="sk-or-v1-..." /> 确认 ); }// app/components/ChatInput.tsx 'use client'; import React, { useState } from 'react'; import { Send, Loader2 } from 'lucide-react'; import type { ChatInputProps } from '../types/chat'; export default function ChatInput({ onSend, isLoading }: ChatInputProps) { const [input, setInput] = useState(''); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!input.trim() || isLoading) return; await onSend(input.trim()); setInput(''); }; return ( setInput(e.target.value)} placeholder="发送消息..." className="w-full p-4 pr-12 rounded-lg border border-gray-200 focus:outline-none focus:border-blue-500" disabled={isLoading} /> {isLoading ? ( <Loader2 className="w-5 h-5 animate-spin" /> ) : ( <Send className="w-5 h-5" /> )} ); } // app/components/MessageList.tsx 'use client'; import React, { useEffect, useRef } from 'react'; import { User, Bot } from 'lucide-react'; import type { Messag…

前端如何使用CryptoJS来做好加密
type: item
date: 2025-02-18T02:26:00+08:00
description: 明文传一个手机号怎么了，反正都开户了0.前言某次面试了一家银行的前端，被问到了关于前端安全方面的问题，具体就是前端如何做好加密解密，尤其是涉及到了用户信息，比如用户的手机号、身份证号之类的数据怎么处理。这确实是一个很重要的问题，尤其在金融类项目中，对数据安全性要求极高。今天就来详细讲解前端加密解密的相关知识。1.CryptoJS首先需要了解一下CryptoJS：这是一个强大的加密库，提供了多种加密算法，包括 AES、MD5、SHA 等。在前端项目中使用非常广泛。1.1 基础使用// 引入 import CryptoJS from 'crypto-js'; // 加密 function encrypt(data, key) { return CryptoJS.AES.encrypt(data, key).toString(); } // 解密 function decrypt(encryptedData, key) { const bytes = CryptoJS.AES.decrypt(encryptedData, key); return bytes.toString(CryptoJS.enc.Utf8); } // 使用示例 const key = 'secret key 123'; const text = '要加密的内容'; const encrypted = encrypt(text, key); console.log('加密后：', encrypted); const decrypted = decrypt(encrypted, key); console.log('解密后：', decrypted);1.2 进阶使用// 使用CBC模式的AES加密 const encrypted = CryptoJS.AES.encrypt('hello', 'key', { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: CryptoJS.enc.Utf8.parse('1234567890123456') }).toString(); // 对应的解密 const decrypted = CryptoJS.AES.decrypt(encrypted, 'key', { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: CryptoJS.enc.Utf8.parse('1234567890123456') }).toString(CryptoJS.enc.Utf8);1.3 其他功能// MD5哈希 const md5Hash = CryptoJS.MD5('要哈希的内容').toString(); // SHA256哈希 const sha256Hash = CryptoJS.SHA256('要哈希的内容').toString(); // Base64编码 const base64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse('hello'));2.AES对称加密AES是一种对称加密算法，所谓对称加密，就是加密和解密使用相同的密钥。就像一把钥匙开一把锁，这个钥匙既可以用来锁门，也可以用来开门。AES加密有几种不同的工作模式：2.1ECB模式最简单的加密模式，但安全性较低，因为相同的明文会产生相同的密文。const encryptedECB = CryptoJS.AES.encrypt('hello', 'key', { mode: CryptoJS.mode.ECB }).toString();2.2 CBC模式更安全的加密模式，需要提供一个初始向量（IV），相同的明文会产生不同的密文。const encryptedCBC = CryptoJS.AES.encrypt('hello', 'key', { mode: CryptoJS.mode.CBC, iv: CryptoJS.enc.Utf8.parse('1234567890123456') }).toString();对称加密的优点是速度快，适合加密大量数据，但缺点是密钥的传输和保管比较困难。因为如果把秘钥直接写到前端项目中，可能会有泄露的风险。在实际的项目中，比较推荐使用CBC模式，更加的安全可靠。3.RSA非对称加密RSA是一种非对称加密算法，它使用一对密钥：公钥和私钥。公钥可以公开给任何人，用于加密数据；而私钥必须严格保密，用于解密数据。3.1 基本原理使用公钥加密的数据只能用私钥解密，这就保证了数据的安全性。即使黑客获取了公钥，也无法解密数据。3.2 使用场景…

一切发生皆利于我
type: item
date: 2025-02-08T23:46:00+08:00
description: 准备找工作了，希望一切顺顺利利。（顺便展示一下自己）