-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.json
1 lines (1 loc) · 102 KB
/
search.json
1
[{"title":"3种常用的缓存读写策略","url":"//articles/2021/06/10/1623259256672.html","content":"\n以前只是用过Redis作缓存, 缓存和数据库之间怎么舒服怎么来, 今天才发现原来还有缓存模式一说. 下文将从读和写来介绍3种常用缓存读写模式\n旁路缓存模式 ( Cache Aside Pattern )这个模式应该是比较容易想到的, 同时也是比较常用的.\n读\n若缓存存在数据, 则直接返回缓存数据\n若缓存不存在该数据, 则到数据库进行查询并将结果写入缓存\n\n写\n更新db记录\n删除缓存记录\n\n为什么写操作时要先更新db后删除缓存?我们假设先删除缓存再更新db, 且有一个查询和一个更新操作. 数据库与缓存存在一个记录old.\n假设更新操作已经按照我们的假设删除了缓存old, 正准备更新db为new, 此时查询操作开始, 没命中缓存, 于是从db中查询并更新旧数据old到缓存中, 这个时候更新操作才开始进行更新, 将数据库更新为new.\n整个流程下来, 两个操作都顺利完成了任务, 此时数据库数据为更新后的new, 而缓存中为旧数据old. 于是就产生了脏数据, 甚至如果接下来没有写操作或者没有给缓存设置过期时间的话, 这个脏数据会一直脏下去.\n那么, 先更新db再删除缓存就没有问题吗?\n并不是, 先更新db再删除缓存也存在一定问题.\n还是一样我们有两个操作: 查询与写入. 数据库与缓存内容为old. 接下来分为两种情况\n1 首先假设写入操作已经更新完db为new, 即将删除缓存. 此时查询操作他来了, 拿了缓存中的old就走了, 查询操作走了后写入操作才来得及将缓存删除. 这就导致了此次查询操作查到的是脏数据.\n2 假设查询操作没有命中缓存, 到db中查询结果为old后正打算写入缓存, 这个时候更新操作来了, 光速更新db为new后删除缓存. 更新操作结束后, 查询操作才将查询到的old写入缓存, 于是就出现了缓存为old, 数据库为new的脏数据的情况.\n既然如此也存在问题, 那为什么还要这样设计呢?\n对于上述2种情况 :\n1 首先发生两个操作按照假设的进行的情况概率很低, 而且此时的脏数据只会被读取一次, 是一次性的脏数据, 影响不大.\n2 同样的按照假设来的概率很低, 首先db(磁盘)的IO速度是远远小于缓存(内存)的. 于是在查询操作查询db后到写入到缓存中的这段时间, 基本上是不足以给更新操作完成所有动作的, 因此发生的概率很小.\n引用缓存更新的套路 | 酷 壳 - CoolShell的一句话\n\n在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性能是有冲突的。软件设计从来都是取舍Trade-Off。\n\n为什么写操作只是删除缓存而不是更新缓存?Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend? - Quora\n读写穿透模式 ( Read/Write Through Pattern )读写穿透模式主要是将缓存作为主体, 将持久化操作交给缓存进行操作, 应用不需要编写持久化操作的代码.\n对于应用来说, 缓存以及数据库是透明的, 应用只需要进行查询/写入, 对于不存在缓存中的数据等并不需要进行额外的操作, 例如 : 查询数据时缓存不存在该数据, 由缓存自己从db中查询数据并写入缓存中, 不需要应用自己写入. 这也就减少了应用的代码量, 少了点要操心的事情.\n读\n若命中缓存, 直接返回\n缓存中无该数据, 向db查询, 由缓存将查询结果更新到缓存中.\n\n写\n若缓存中存在该数据, 直接更新缓存数据, 并由缓存自动更新db\n若缓存中无该数据, 直接更新db.\n\n异步缓存模式 ( Write Behind Pattern )这种模式和读写穿透模式是差不多的, 比较大的区别在于**”同步”与”异步”**.\n异步缓存模式采用的是定时将缓存中需要持久化的数据写入到db中.\n这样的设计有一个很明显的问题 : 无法做到强一致性. 因为是定时写入db, 一旦出现缓存更新后, 还没来得及写入db就挂掉了, 此时数据就会出现不一致甚至丢失的情况. 但是带来的好处也是很明显 : 提高了IO速度. 因为每次写入数据时, 不需要立刻写入db, 对于单次数据库操作来说是提高了速度的.\n","tags":["缓存","设计"]},{"title":"Chrome无法同步密码","url":"/2022/03/08/Chrome%E6%97%A0%E6%B3%95%E5%90%8C%E6%AD%A5%E5%AF%86%E7%A0%81/","content":"问题最近重装了一次Manjaro, 然后重新在AUR安装Chrome之后发现Chrome同步的时候不会读取密码,输入密码后点击Save也不会保存密码,在设置中查看Saved passwords也是空空如也,试过重新登录但是并没有用。\n在CLI打开Chrome,在打开一些原本会自动填充密码的网站时会发现CLI中出现了如下日志:\n[223901:223901:0308/201429.790834:ERROR:password_sync_bridge.cc(340)] Passwords datatype error was encountered: Failed to load entries from password store. Encryption service failure.\n按照这个去网上搜索,发现了一个可以完美解决的方法。\n猜想因为问题是出现在重装之后,而我的home目录在重装的时候没有格式化,是另外的硬盘,所以上次登录的数据还存在,可能是存在的数据在校验的时候不匹配,导致无法同步密码。但是具体原因也不清楚,在搜索过程中并没有找到对应的解释。期间我还安装了Dev版本发现并没有这个问题再加上删除登录数据就可以解决这个问题,因此排除了是系统或是机器的问题。\n删除Login Data与Login Data-journal\n退出Chrome\n进入Chrome对应用户资料目录\nMac: ~/Library/Application Support/Google/Chrome\nLinux:~/.config/google-chrome\nWindows %UserProfile%\\AppData\\Local\\Google\\Chrome\\User Data\n\n\n如果你没有添加其他配置文件只有一份默认配置文件的话,那么cd Default; rm Login\\ Data; rm Login\\ Data-journal \n如果有多份配置文件那么就重复对这多份配置文件做下面同样的操作: rm <profile folder name>/Login\\ Data; rm <profile folder name>/Login\\ Data-journal. (替换<>以及其中的内容为配置文件夹名)\n\n参考Fixing a Google Chrome failure to save passwords\n"},{"title":"Flutter 签名生成Apk","url":"/2022/09/15/Flutter-%E7%AD%BE%E5%90%8D%E7%94%9F%E6%88%90Apk/","content":"问题最近在研究一个叫Hacki的Flutter应用,用Debug模式调试应用的时候应用可以在手机上运行没有问题,而当我打算生成Release版本的时候,就出现了报错。\n运行flutter build apk,之后会得到一个错误SigningConfig "release" is missing required property "storeFile".. 而对于我这种没有接触过安卓编程的小白来说,完全搞不清楚为什么会出现这个问题,于是只能求助于搜索引擎。\n问题所在与解决一通搜索之后定位问题到android/build.gradle文件中,搜索storeFile可以找到这一个代码块:\nsigningConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] }}\n\n从命名上来看可以看出来大概应该是签名的问题,而从我对于安卓打包应用的了解,打包为Release版的时候是需要进行签名的。再往下看,storeFile使用了一个三目运算符来赋值,最后得到的结果只有两个:file(keystoreProperties['storeFile'])和null. 推测应该就是条件不满足导致取值为null了,问题应该就是签名的时候缺失了keystoreProperties['storeFile']的配置。\n跳转到keystoreProperties定义的地方可以看到:\ndef keystoreProperties = new Properties()def keystorePropertiesFile = rootProject.file('key.properties')if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile))}\n从这一块可以看到我们需要一个叫做key.properties的文件,同时应该要往里写入一些东西来作为keystoreProperties. 那么思路就有了,直接搜索Flutter android key.properties,来到官网的Build and release an Android app. 可以看到我们需要做以下几步:\n\n创建一个keystore文件\n# -keystore后跟着的是keystore文件的存放位置,可以自定义,只要第2步在项目里指定更改后的位置就可以了# 运行后需要输入密钥(重要, 后面要用),姓名住址之类的信息(不重要,但是如果你要发布给别人用的话还是最好写一下)# Linux&Mac可以使用keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload# Windows可以使用keytool -genkey -v -keystore %userprofile%\\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload\n给我们的项目配置一下keystore的信息(密钥,路径等)在android/下创建一个文件key.properties,填入:\nstorePassword = 第一步的密钥keyPassword = 第一步的密钥keyAlias = upload // 不是很明白这个干啥的storeFile = 第一步生成的文件的路径\n在build.gradle文件中配置好keystoreProperties参考上文\n\n\n到这里问题就完美解决了,不过中间还是有很多安卓相关的没搞明白,留个坑以后填。\n","tags":["Flutter","Android"]},{"title":"Flutter安卓使用外部存储","url":"/2022/09/16/Flutter%E5%AE%89%E5%8D%93%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8%E5%AD%98%E5%82%A8/","content":"环境flutter --version# Flutter 3.0.5 • channel stable • https://github.com/flutter/flutter.git # Framework • revision f1875d570e (9 周前) • 2022-07-13 11:24:16 -0700 # Engine • revision e85ea0e79c # Tools • Dart 2.17.6 • DevTools 2.12.2 # Android 12\n\n问题最近在写Flutter的应用,这个应用会生成一些文件,但我在使用file.saveTo('/storage/emulated/0/tmp.csv')的时候会报错,提示没有权限,即使在手机上赋予应用对文件拥有完全控制权也无法保存。\n搜索之后发现可以使用path_provider库的getExternalStorageDirectory()方法获取路径,再使用获取到的路径进行保存就可以成功了。\n同时需要在android/app/src/main/AndroidManifest.xml文件中加入以下的权限请求。(有一些是不必要的,但是因为不熟悉安卓,搜索到的答案提到的权限都一股脑全加了)然后就大功告成了!\n// AndroidManifest.xml<uses-permission android:name="android.permission.WAKE_LOCK" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE"/><uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.MANAGE_DOCUMENTS" /><uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /><application// ...android:requestLegacyExternalStorage="true"// ...","tags":["Flutter","Android"]},{"title":"Flutter dart_vlc库踩坑","url":"/2022/08/09/Flutter-dart-vlc%E5%BA%93%E8%B8%A9%E5%9D%91/","content":"dart_vlcdart_vlc是Flutter中的一个库,用于播放视频。基本上这个库就是调用vlc来完成视频的播放。但是官方的文档似乎是有点缺失的,所以踩了一些坑。\n环境$ uname -aLinux hasee 5.15.50-1-MANJARO #1 SMP PREEMPT Sun Jun 26 07:06:30 UTC 2022 x86_64 GNU/Linux$ flutter --versionFlutter 3.0.3 • channel stable •https://github.com/flutter/flutter.gitFramework • revision 676cefaaff (7 周前) • 2022-06-22 11:34:49-0700Engine • revision ffe7b86a1eTools • Dart 2.17.5 • DevTools 2.12.2\n\n使用前建议dart_vlc虽然文档可能更新不太及时,但是他提供了一个可运行的example。克隆dart_vlc的代码仓库,运行example/main.dart后可以将例子跑起来,如果跑得起来说明在当前平台dart_vlc是没有问题的。\n坑1:Readme中的版本是旧版本在pub.dev上,最新版本是3.0.0,但是文档中的依赖引入写的却是1.9.0,出问题的时候搜了一下Issue才发现这件事情。\n坑2:vlc 和 libvlc 冲突最开始引入dart_vlc的时候,我电脑上是已经安装了vlc了。但是看文档中说还需要安装libvlc,一安装,好家伙,和vlc冲突了,需要先卸载vlc。于是后面我把vlc卸载了,然后安装了libvlc。然后当我想要重新把vlc安装回来的时候,elisa依赖libvlc了,无奈只能把elisa和libvlc全删了,再安装回vlc。\n坑3:vlc.hpp not found这个坑是最开始没有安装libvlc的时候发现的,但当时我没有在main方法中进行await DartVLC.initialize(useFlutterNativeView: true);. 后面在处理坑2之后发现了这件事所以补上了初始化语句,然后一路顺畅就没报错了。也不清楚是哪一步的问题。\n","tags":["Flutter"]},{"title":"Hexo Github Action配置","url":"/2022/03/04/Hexo-Github-Action%E9%85%8D%E7%BD%AE/","content":"概括 在本文中我们将会实现下列目标:\n\n编写完博客后将博客 push 上 Github,由 Github Action 自动帮我们构建,并且部署到 Github page 上\n大概聊一下整个过程的简单原理\n\n原理Github Action 相当于在我们 push 代码或是执行其他动作的时候自动执行一些操作,Github 给我们开一个机器来执行一些我们编写的操作(类似与 CI ),所以我们可以通过 Github Action 就实现自动部署 Hexo. \n所以我们需要做的事情其实和我们为一台新电脑配置 git 的权限,然后用这台新电脑 git clone npm instal hexo g hexo d一样,只不过我们需要将这些操作写成一个yml 文件,让 Github Action 在我们每次 push 的时候自动帮我们部署到 Github Page 上而已。所以我们需要\n\n配置 git: 生成 ssh 密钥,私钥交给 Github Action 的机器,公钥放在仓库的deploy keys中。\n按照 Github Action 文档来编写 workflow 文件\n拉取代码\n设置 ssh 私钥\n安装node依赖\nnpm install\nhexo g\nhexo d\n\n\n\n这里有一个问题,有的人的博客原文和 hexo 仓库是不同的仓库,有的人是同一个仓库不同分支,这个其实问题不大。因为不管我们最终要将静态页面部署到哪里是 hexo 的 _config.yml 来负责的,我们只需要对存放博客原文的仓库进行 Github Action 的设置就可以了。\nssh密钥生成与配置\n运行ssh-keygen -f github-action-hexo -C hexo\n在ssh-keygen的说明书里可以看到:ssh-keygen -c [-a rounds] [-C comment] [-f keyfile] [-P passphrase]. 即-C参数只是用来作为一个注释,写什么都可以。而-f是输出的文件名,ssh-keygen会在当前目录下生成2个文件,一个是私钥一个是公钥(.pub). \n\n\n我们需要将生成的私钥和公钥放到对应的Github仓库中,其中公钥需要放到仓库的Deploy keys中,而私钥是用来设置在Github Action给我们的机器中的。具体操作:\n添加公钥:首先打开我们的hexo仓库,也就是<username>.github.io,然后打开仓库的Settings,点击左侧的Deploy keys,点击Add deploy key,然后将我们在第一步中生成的github-action-hexo文件中的内容全部复制进来,这里给这个key取什么名字都没什么所谓。注意,一定要勾选Allow write access\n添加私钥:打开hexo仓库的设置,在左侧点击Secrets,再点击Actions。点击New repository secret,将github-action-hexo.pub中的内容全部复制进来,同时给这个secret取名为HEXO_DEPLOY_PRI. \n添加私钥这一步其实相当于是添加了一个私密的全局变量,别人是看不到这个变量的值的,也就保证了仓库的安全。这个变量会在后面初始化git ssh key的时候用到,所以如果你取了别的名字的话就需要在后续的步骤中也换成同样的名字。\n\n\n\n\n\n编写workflow如果懒得查文档的话,按照下面的yml修改一下即可:\nname: Hexo deployment # 这个是在Github Action上显示的名字on: push: branches: - source # 当哪个分支发生 push 动作时执行jobs, 在本文中也就是博客原文所在的分支jobs: build: runs-on: ubuntu-latest # 选择机器的系统和版本 strategy: matrix: node-version: [14.x] # 选择node版本 steps: - uses: actions/checkout@v2 # 相当于拉取源代码 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@main with: node-version: ${{ matrix.node-version }} - name: Configuration environment env: HEXO_DEPLOY_PRI: ${{secrets.HEXO_DEPLOY_PRI}} # 这里的secrets.HEXO_DEPLOY_PRI如果在上文中有修改的话也要记得修改 run: | mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name "<username>" # 你的用户名 git config --global user.email "<email>" # 你的邮箱 - name: Install dependencies run: | npm i -g hexo-cli npm i - name: Deploy hexo run: | hexo clean && hexo generate && hexo deploy\n\n参考hexo配合github action 自动构建(多种形式) - 掘金Github Action 文档\n","tags":["Hexo","Github Action"]},{"title":"Google Java Style Guide 学习","url":"/2021/12/21/Google-Java-Style-Guide-%E5%AD%A6%E4%B9%A0/","content":"好久没有写Java了, 由于要重新维护xWash,打算将项目按照规范来进行重构,所以选择了Google Java Style Guide来进行学习。同时,本文只记录一些我个人认为比较重要或者值得记录的信息。\n2. 文件的基础要求2.2. 文件编码应该为UTF-8\n2.3.3 对于非ASCII的字符处理官方文档原文: \n\nThe choice depends only on which makes the code easier to read and understand, although Unicode escapes outside string literals and comments are strongly discouraged.翻译:在代码编写上,表现非ASCII字符的形式应该尽量让提高代码的可读性,尽管使用\\uxxxx的形式来表现Unicode很可能会让人不太容易理解。\n\n\n\n\n代码\n好坏\n\n\n\nString unitAbbrev = “μs”;\nBest: perfectly clear even without a comment.\n\n\nString unitAbbrev = “\\u03bcs”;\n// “μs” Allowed, but there’s no reason to do this.\n\n\nString unitAbbrev = “\\u03bcs”;\n// Greek letter mu, “s” Allowed, but awkward and prone to mistakes.\n\n\nString unitAbbrev = “\\u03bcs”;\nPoor: the reader has no idea what this is.\n\n\nreturn ‘\\ufeff’ + content;\n// byte order mark Good: use escapes for non-printable characters, and comment if necessary.\n\n\n一句话:除了特殊情况,哪一种编写方式能提高代码可读性就尽量用哪种。\n3. 源文件格式3.1 版权信息和许可证如果文件有版权信息和许可证,那么应该将版权信息和许可证写在源文件中\n3.2 package语句package语句没有长度限制\n3.3 import语句3.3.1 不使用通配符3.3.2 不受长度限制3.3.3 顺序与间隔import语句遵循下面的规则:\n\n所有静态import在一个块中\n非静态import在另一个块中\n\n如果存在同时静态import和非静态import,那么就使用一行空行来分隔开这两个块\n而在每个块中,import的名要按照ASCII的顺序进行排序。(不知道是不是笔者理解有误,笔者觉得这个是没有必要的事情)附上原文:\n\nWithin each block the imported names appear in ASCII sort order. (Note: this is not the same as the import statements being in ASCII sort order, since ‘.’ sorts before ‘;’.)\n\n3.3.4 不使用静态import导入类不要用static import去导入静态内部类,使用普通的import语句进行导入即可。\n3.4 类的声明3.4.1 一个文件只存在一个顶级类3.4.2 类成员的顺序良好的类成员顺序可以提高代码的可读性,但是实际上并没有一个固定的类成员顺序的规则。比较重要的是类成员的顺序要按照一些“逻辑上的顺序”的规则来进行排序,这种顺序能让维护代码的人更好地了解类。\n例如一个新增的方法如果按照”添加的先后顺序”来进行排序,那么这个新增的方案就会被放置在最后或者最前,这不算逻辑上的顺序。\n一句话:以提高代码的可读性为目的对类成员进行排序,让维护者可以更清晰的了解该类。\n3.4.2.1 重载当一个类有多个构造函数或者多个相同名字的方法时,不要将这些相同名字的方法分开存放, 而是将他们连续的存放在类中。\n4 格式\n名称解释: block-like constructs, 指代类、方法或者构造函数的类体或者方法体(即被花括号包围起来的部分),下称块结构。原文:Terminology Note: block-like construct refers to the body of a class, method or constructor. Note that, by Section 4.8.3.1 on array initializers, any array initializer may optionally be treated as if it were a block-like construct.\n\n4.1 花括号4.1.1 可选的花括号在使用if else for do while的时候,就算执行的代码块为空,或者只有一行代码,也要使用花括号。\n4.1.2 K&R style这一节主要是说花括号的格式问题,对于非空的代码块和块结构,有如下规则:\n\n在{之前不要出现换行符\n在{之后接换行符\n在}之前接换行符\n当花括号表示当前的语句,方法,构造函数或者有名的类的结束时,在}后接换行符。例如下面的例子中}后跟着else,说明if语句还没有结束,所以}后面不会接换行符。\n\n例子:\nreturn () -> { while (condition()) { method(); }};return new MyClass() { @Override public void method() { if (condition()) { try { something(); } catch (ProblemException e) { recover(); } } else if (otherCondition()) { somethingElse(); } else { lastThing(); } }};\n\n4.1.4 空块:简单格式一个空块或者块结构可以遵守4.1.2中提到的K&R style。需要注意的是,对于空块来说,可以在{之后紧跟}而不是换行符,除非这个空块属于某一个带有多个块的语句(if/else或者try/catch.finally).\n例子:\n// ✅ 合格的格式void doNothing() {}// ✅ 也是合格的格式void doNothingElse() {}// ❌ 不合格的格式,在带有多个块的语句中,块不能出现{}的简单的格式try { doSomething();} catch (Exception e) {}\n\n4.2 块缩进缩进为2个空格\n4.3 每行一个语句原文:\n\nEach statement is followed by a line break.笔者在这里有个疑问:怎样才算是一个statement?例如:new List<Integer>().stream().forEach(System.out::println)这种应该要分行写还是一行?\n\n4.4 每行字符数量限制:100每行的字符数量应该少于100,这里的字符指的是任何Unicode字符\n以下情况是例外,不需要遵守长度限制:\n\n一些长度无法限制在100字符以内的行,例如:Javadoc中的长链接,长JSNI方法引用\npackage和import语句\n注释中可能被直接复制到终端执行的命令行语句\n\n4.5 分行术语解释:分行,指当代码被分割称几行的行为。\n原文如下:\n\nTerminology Note: When code that might otherwise legally occupy a single line is divided into multiple lines, this activity is called line-wrapping.\n\n4.5.1 换行的位置换行的主要观点是:在更高的语法等级上分行。\n\n在非赋值语句中,应该在符号前换行\n小数点.\n方法作用域冒号::\n类型限制中的&符号\ncatch方法中的管道|符号\n\n\n在赋值语句中,应该在=后换行(在=前换行也可以接受)\n方法或者构造方法中的(需要紧跟在方法后\n逗号,紧跟在逗号之前的token\n关于lambda表达式,原文为:A line is never broken adjacent to the arrow in a lambda, except that a break may come immediately after the arrow if the body of the lambda consists of a single unbraced expression.\n\n4.5.2 持续缩进:至少4个空格当出现分行时,换行之后的代码需要和源句子保持4个空格以上的缩进距离。\n关于缩进问题,这里笔者认为还是保持所有地方缩进一致可能比较好,保持一致在编写代码时就不需要去过分关注缩进问题,不会出现偶尔忘记哪里哪里需要缩进多少,哪里哪里又是缩进多少。\n4.6 空白4.6.1 垂直方向上的空白行空行可以出现在:\n\n在类的字段,构造器,方法,内部类,静态和实例初始化器之间\n在连续的代码中是否要使用空行来分割是可选的,如果要使用空行,那么需要做到将代码块分割成逻辑上的多个部分\n\n\n第三节提到的import语句中,静态和非静态的import需要使用空行分隔。\n\n空行的出现是为了提高代码的可读性,例如将代码从逻辑层面上分开。鼓励在进行初始化的块之前添加空行;鼓励在初始化之后,或者类最后一个成员定义之后添加空行。\n添加连续的空行是允许的,但是不建议这样做。\n4.6.2 水平方向上的空白除了语言本身需要的空格,以及字面量,注释和Javadoc之外,代码中的空格只会出现在下面的几种情况中\n\n分隔带有(的保留字:if for catch\n分隔前面可能带有{的保留字:else catch\n在所有{之前都加空格,除了下面两种情况:\n@SomeAnnotation({a, b}) (no space is used)\nString[][] x = ; (两个{之间不需要加空格,见下面第8项)\n\n\n在任何二元或者三元运算符的左右两边都需要加入空格\n<T extends Foo & Bar>\ncatch (FooException | BarException e)\nforeach语法中的:\nlambda中的箭头->\n以下情况除外:\n::\n.\n\n\n\n\n在,:;之后或者)之后加入空格\n注释行符号//的两边\n声明变量时,类型和变量之间需要空格\n初始化数组时,下面两种格式都是可以的\n`new int[] {5, 6}\n`new int[] { 5, 6 }\n\n\n在类型注解和[]之间需要加入空格\n\n4.6.3 水平对齐:非必需这个不是很重要,举一个例子:\nprivate int x; // this is fineprivate Color color; // this tooprivate int x; // permitted, but future editsprivate Color color; // may leave it unaligned\n\n4.7 分组括号例子: a = (tmp == true) ? 1 : 0\n除了以下2种情况下之外,建议使用括号来提高代码可读性。\n\n不加入括号不会误导阅读代码的人\n加入括号不会提高代码可读性\n\n同时,不能假设每个人都对Java中的计算优先级了如指掌。\n4.8 特殊结构4.8.1 枚举类每个逗号之后都会跟着一个枚举实例,每个枚举实例之间可以有空行。例子:\nprivate enum Answer { YES { @Override public String toString() { return "yes"; } }, NO, MAYBE}\n如果一个枚举类中没有方法或者实例没有文档,那么可以按照有以下格式:private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }\n4.8.2 变量声明4.8.2.1 一个变量一次声明每次声明只能声明一个变量, 所以不能这样声明变量:int a, b;。\n例外:在for循环的头部中,可以一次声明多个变量\n4.8.2.2 需要时再声明局部变量在声明时应该尽量靠近第一次使用的地方。\n4.8.3 数组4.8.3.1 数组初始化:可以用块的形式初始化当初始化数组时,可以按照下面的例子来进行初始化:\nnew int[] { new int[] { 0, 1, 2, 3 0,} 1, 2,new int[] { 3, 0, 1, } 2, 3} new int[] {0, 1, 2, 3}\n4.8.3.2 不使用C语言格式的数组声明[]紧跟在类型后而不是变量后\nString[] args✅\nString args[]❌\n4.8.4 switch语句4.8.4.1 缩进缩进2个空格\n4.8.4.2 fall-through: 加注释fall-through指的是switch语句中某个case不加break等中断语句, 从而进入下一个case执行下一个case的代码的过程。\n允许使用fall-throught, 但是在使用的时候需要使用注释// fall through来注明存在fall-throught\n例子:\nswitch (input) { case 1: case 2: prepareOneOrTwo(); // fall through case 3: handleOneTwoOrThree(); break; default: handleLargeNumber(input);}\n需要注意的是case 1:并没有加注释。因为case 1:紧跟着case 2:, 不会造成误解。\n4.8.4.3 必需包含default每个switch语句必需包含default:, 无论default中是否有代码\n例外:用switch语句用在枚举变量上,在举例出所有可能性之后可以不使用default。\n4.8.5 注解应用在类,方法,构造方法的注解后面应该紧跟着修饰的对象,如果有多个注解那么每个注解占一行,每个注解都是同级的。\n@Override@Nullablepublic String getNameIfPresent() { ... }\n例外:只有一个没有参数的注解可以跟修饰对象出现在同一行:\n@Override public int hashCode() { ... }\n如果注解用于修饰成员变量,即使注解带有参数,多个注解也可以和被成员变量处于同一行。例如:\n@Partial @Mock DataLoader loader;\n对于局部变量,类型和参数而言,没有固定的规则来规定格式。笔者认为,按照整个Google style的风格来说,只要能提高可读性即可。\n4.8.6 注释4.8.6.1 块注释块注释有和代码一样的缩进等级: 2. 在使用/* ... */进行注释时,中间包括的每一行都需要以*开始,并且要和上一个带*的行对齐。例如:\n/* * This is // And so /* Or you can * okay. // is this. * even do this. */ */\n\n4.8.7 修饰符修饰符的顺序如下:public protected private abstract default static final transient volatile synchronized native strictfp\n4.8.8 数字字面量long类型的整数使用大写字母L作为后缀,不要使用小写字母l,防止和数字1搞混。例如:3000000000L而不是3000000000l\n5 命名5.1 变量命名变量只使用ASCII字母以及数字,下划线。所以所有的变量名都可以用正则表达式\\w+来进行匹配。\n在Google Style里,不会使用任何特殊前缀或者后缀,例如下面的几个命名都不属于Google Style: name_ mName s_name kName\n5.2 变量类型规则5.2.1 包名包名只使用小写字母,在连接多个单词时直接连接。例如:com.example.deepspace而不是com.example.deepSpace或者com.example.depp_space\n5.2.2 类命名类使用首字母大写的驼峰式命名。\n类名往往是名称或者名词短语。例如Character ImmutableList. 接口名同样也可以是名词或者名词短语,不过有时候也会是形容词或者形容词性短语(如:Runnable)\n对于注解,没有一个固定的命名规范。\n测试类的命名为:被测试的类名 + Test. 例如:HashTest HashIntegrationTest\n5.2.3 方法名方法名采用小写字母开头的驼峰式写法。\n方法名通常是名词或者名词短语,例如sendMessage stop\n下划线可以出现在Junit测试方法中,用于区分不同的测试用例,一个常用的格式为:<methodUnderTest>_<state>,一个例子:pop_emptyStack. 对于测试方法,没有一个唯一正确的命名规则。\n5.2.4 常量命名常量使用大写字母和下划线进行命名,下划线用于分割不同的单词。\n当一个字段的内容永远不会发生改变以及设置为常量后不会带来副作用时,该字段应该是被static final修饰的常量。例如基本类型变量,字符串,不变量,类型固定的不变集合。\n如果一个实例中能被观测到的内容会发生改变,那么就不应该设置为常量。仅仅预计一个对象是不可变是不够的。例如:\n// Constantsstatic final int NUMBER = 5;static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");static final ImmutableMap<String, Integer> AGES = ImmutableMap.of("Ed", 35, "Ann", 32);static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutablestatic final SomeMutableType[] EMPTY_ARRAY = {};enum SomeEnum { ENUM_CONSTANT }// Not constantsstatic String nonFinal = "non-final";final String nonStatic = "non-static";static final Set<String> mutableCollection = new HashSet<String>();static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);static final ImmutableMap<String, SomeMutableType> mutableValues = ImmutableMap.of("Ed", mutableInstance, "Ann", mutableInstance2);static final Logger logger = Logger.getLogger(MyClass.getName());static final String[] nonEmptyArray = {"these", "can", "change"};\n同时,常量一般为名称或者名词短语\n5.2.5 变量命名变量使用小写开头的驼峰式写法,一般为名词或名词短语。\n5.2.6 参数命名参数使用小写开头的驼峰式写法,不允许在public方法中使用只有一个字母的参数名。\n\n笔者注:public方法是用来暴露给外界查看的,如果使用一个字母的参数名那么很容易让使用该方法的人误解。同时笔者觉得私有方法可能尽量也不用一个字母为好。\n\n5.2.7 局部变量局部变量使用小写开头的驼峰式写法。如果局部变量可以是常量,是不可变的,也不应该将其设置为常量,不应该将其按照常量的格式来命名。\n5.2.8 类型变量命名每个类型变量应该遵循下面的其中一个规则:\n\n一个大写字母,可以在字母后带有一个数字T E X T2\n使用类命名的规范,在后面紧跟一个大写字母T. 例如:RequestT FooBarT\n\n5.3 驼峰命名的定义有时我们需要将英语短语转化为驼峰式,例如一些缩写和一些比较不一样的结构:IPv6 iOS. 为了提高可读性,在命名时我们采用下面的确定性的方案:\n首先从名字的散文格式开始: \n\n笔者不是很理解这里的意思,放上原文:Beginning with the prose form of the name\n\n\n将短语转化为纯ASCII字符,同时去掉'号,例如:Müller's algorithm会被转化为Muellers algorithm\n以空格和其他表达符号为分隔符,将第1步的结果进行分隔。\n推荐:如果一个单词在日常使用时已经含有驼峰式写法了,对其进行拆分。(例如:AdWords -> ad words)需要注意的是,例如iOS并不是一种驼峰式的写法,所以不建议拆分形如iOS的单词。\n\n\n将第2步中所有单词都变为小写,同时将除了第一个单词之外的每个单词的首字母变为大写\n按顺序连接所有第3步中的单词,就得到了一个符合驼峰式的命名\n\n需要注意的是,有一些词是比较容易出错和被忽视的。\n\n原文:Note that the casing of the original words is almost entirely disregarded.\n\n例如:| Prose form | Correct | Incorrect || ———- | ——- | ——– || “XML HTTP request” | XmlHttpRequest | XMLHTTPRequest|| “new customer ID” | newCustomerId | newCustomerID|| “inner stopwatch” | innerStopwatch | innerStopWatch|| “supports IPv6 on iOS?” | supportsIpv6OnIos | supportsIPv6OnIOS|| “YouTube importer” | YouTubeImporter 或 YoutubeImporter | |\n其中YoutubeImporter虽然是可以的,但是不推荐这种格式\n\n注:在英文中有一些含糊不清的连字符单词,例如nonempty和non-empty都是正确的,所以方法名checkNonempty和checkNonEmpty都是正确的\n\n6 编程习惯6.1 @Override: 必须使用必须给重写父类的方法加上@Override注解\n6.2 异常捕获:不要什么都不做一般来说,我们捕获到某个异常之后,至少都会将异常打印出来,或者重新将异常抛出,而不是什么都不做。如果确定在异常处理中要什么都不做的话,那么就加上一句注释来解释例如:\ntry { int i = Integer.parseInt(response); return handleNumericResponse(i);} catch (NumberFormatException ok) { // it's not numeric; that's fine, just continue}return handleTextResponse(response);\n\n在编写测试的时候,如果异常名是expected或者以expected开头,那么就不需要加注释。例如:\ntry { emptyStack.pop(); fail();} catch (NoSuchElementException expected) {}\n6.3 静态成员:使用类调用静态成员当我们使用静态成员时,必须使用类来调用静态成员而不是使用实例来调用静态成员:\nFoo aFoo = ...;Foo.aStaticMethod(); // goodaFoo.aStaticMethod(); // badsomethingThatYieldsAFoo().aStaticMethod(); // very bad\n\n6.4 Finalizes: 不使用一般情况下不要使用Object.finalize\n\nTips: 如果一定要使用finalize方法,那么首先阅读一下Effective Java Item 7的”Avoid Finalizers”,小心使用,不过还是建议别用。原文:Don’t do it. If you absolutely must, first read and understand Effective Java Item 7, “Avoid Finalizers,” very carefully, and then don’t do it.\n\n7 Javadoc7.1 格式7.1.1 一般格式基本的Javadoc块格式如下例子:\n/** * Multiple lines of Javadoc text are written here, * wrapped normally... */public int method(String str) { ... }\n或者也可以是一行:/** An especially short bit of Javadoc. */\n一般来说使用Javadoc块格式都没有错。在Javadoc确实只需要一行就可以完成的情况下才使用一行的Javadoc. 需要注意的是使用一行的Javadoc需要在没有类似于@return的标签的时候才能使用。\n7.1.2 段落使用一行以*开头的空行来隔开不同的段,每个段的开头都要有<p>标签,<p>标签和随后的单词之间没有空格。\n7.1.3 块标签标准的块标签和出现的顺序如下:@param @return @throws @deprecated, 同时这四个标签一定要带有描述。\n7.2 总结每个Javadoc块都会以一个简短的总结开头。这个总结非常重要,当我们使用IDEA等IDE时,鼠标悬停在某个方法或者类上就会显示这个总结来对方法或者类进行描述。同时在生成文档时可以更好地对方法和类进行描述。\n\n此处笔者还没有很清楚,可以查阅原文\n\n7.3 Javadoc使用位置至少要做到将Javadoc应用在所有public类和该类中所有public protected方法上。\n7.3.1 例外:自我解释型方法Javadoc可以不用在某些非常简单直观的方法上,例如getFoo, 因为只看方法名就能知道方法做了什么,而且也没有必要在这么简单的方法上加上”Returns the foo”.\n\n特别注意:在某些带有相关信息的例子中不适用上述自我解释型方法的例外,例如:有一个方法getCanonicalName,这里就不能忽略Javadoc(如果忽略了Javadoc,那么阅读的人根本不知道这里的”canonical name”指代的是什么东西。\n\n7.3.2 例外:重写Javadoc在重写的方法上不是必须的。\n7.3.4 不需要Javadoc的情景原文:Other classes and members have Javadoc as needed or desired.\nWhenever an implementation comment would be used to define the overall purpose or behavior of a class or member, that comment is written as Javadoc instead (using /**).\nNon-required Javadoc is not strictly required to follow the formatting rules of Sections 7.1.2, 7.1.3, and 7.2, though it is of course recommended.\n","tags":["learning-note"]},{"title":"IPv4首部中的Evil bit","url":"//articles/2021/08/15/1629020674611.html","content":"\n今天突然了解到IPv4中有一个“彩蛋”:Evil bit. 觉得很有意思所以在这里记下来。\n首部结构我们先看一下维基百科 IPv4给出的IPv4首部结构:\n\n可以看到其中有一个3位的Flags标志,flags的第一位就是我们的Evil bit, 第二位是DF(不分片),第三位是MF(还有分片)。\nEvil bit来源在维基百科 Evil bit中说到了Evil bit来源于RFC 3514, 是一个愚人节的幽默玩笑,用于指出ip数据包是不是有恶意的,因为有了这个字段,所以接收方只需要检查Evil bit就可以知道数据包是否是恶意数据包。\n\nThe evil bit is a fictional IPv4 packet header field proposed in RFC 3514, a humorous April Fools' Day RFC from 2003 authored by Steve Bellovin. The RFC recommended that the last remaining unused bit, the “Reserved Bit”^[1]^ in the IPv4 packet header, be used to indicate whether a packet had been sent with malicious intent, thus making computer security engineering an easy problem – simply ignore any messages with the evil bit set and trust the rest.\n\n当然了,都说了是愚人节玩笑了,那怎么可能有用嘛,不过倒是真的有这个字段,就是没啥用,一般博客或者文章也不会提及这个字段是干嘛的。\n而在RFC 3514中煞有其事地提到:\n\nCurrently-assigned values are defined as follows:\n0x0 If the bit is set to 0, the packet has no evil intent. Hosts, network elements, etc., SHOULD assume that the packet is harmless, and SHOULD NOT take any defensive measures. (We note that this part of the spec is already implemented by many common desktop operating systems.)\n0x1 If the bit is set to 1, the packet has evil intent. Secure systems SHOULD try to defend themselves against such packets. Insecure systems MAY chose to crash, be penetrated, etc.\n\n后记除了本文所说到的Evil bit彩蛋之外, 其实有很多IT项目都是有彩蛋的。例如redis的LOLWUT(甚至还能加参数),也有让人离职的Antd圣诞彩蛋(bushi). \n","tags":["彩蛋","Internet"]},{"title":"Intellij IDEA因BindException异常导致启动失败","url":"//articles/2021/06/22/1624335267925.html","content":"\n前记最近几天电脑启动Pycharm和IDEA都偶尔会出现异常并且无法打开, 最开始是只出现一两次重启解决了也就没管, 今天电脑刚开机就遇见IDEA无法启动了, 这可就不能忍了.\n注 : 题主电脑是Windows\n解决由于上官网很快就解决了就忘记截图了, 不过栈顶的异常如下 : \njava.net.BindException: Address already in use: bind\n看到这个异常的时候大概推测应该是端口被占用了, 刚好异常提醒很贴心的给出了官网的处理连接Start Failed, Internal error: recovering IDE to the working state after the critical startup error – IDEs Support (IntelliJ Platform) | JetBrains\n在官网中找到了下面这段话\n\nIf you get “java.net.BindException: Address already in use: bind “ exception, please refer to IDEA-238995 for the workaround.\n\n进入上面的超链接就有解决方案了, 解决方案我贴在下面\n官网给出的解决方案应变方法 : 在具有管理员权限的Powershell或者cmd中运行如下命令 :\n( 如果是Win10可以直接右键最下角的开始就可以找到带管理员的powershell )\nnetsh int ipv4 set dynamicport tcp start=49152 num=16383netsh int ipv4 set dynamicport udp start=49152 num=16383\n\n如果上面的命令运行了之后还是打不开IDEA, 那么试一下下面的命令\nnet stop winnatnet start winnat\n\nPROBLEM\nIf all the 50 ports between 6942 and 6991 are reserved, taken by the other apps or firewall doesn’t allow IDE to bind on them, startup fails with the following exception:\njava.util.concurrent.CompletionException: java.net.BindException: Address already in use: bind at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314) at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834)Caused by: java.net.BindException: Address already in use: bind at java.base/sun.nio.ch.Net.bind0(Native Method) at java.base/sun.nio.ch.Net.bind(Net.java:455) at java.base/sun.nio.ch.Net.bind(Net.java:447) at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227) at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:134) at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:550) at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1334) at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:506) at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:491) at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:973) at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:248) at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:356) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164) at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ... 1 more-----JRE 11.0.6+8-b765.25 amd64 by JetBrains s.r.oC:\\Users\\yatwi\\AppData\\Local\\JetBrains\\Toolbox\\apps\\Rider\\ch-0\\201.6668.197\\jbr\n","tags":["IDE"]},{"title":"Linux中find -exec为什么需要\"\\;\"结尾","url":"//articles/2021/05/15/1621049160760.html","content":" \n前记在Linux中可以使用find -exec来查找相关目录/文件并做出cp, rm, mv等操作. 其格式一般为find path [option] -exec mv {} /path/file \\;\n需要以 ; 结尾的原因首先, 我们先不以;结尾来输入指令, 结果发现出现了如下的提示: missing argument to exec. 也就是说Linux没有正确识别到给-exec的参数.\n而通过man find并且找到 -exec command ; 的解释可以发现有这么一句话:\n\nAll following arguments to find are taken to be arguments to the command until an argument consisting of `;’ is encountered.翻译 : 以下所有要查找的参数都将被视为命令的参数,直到包含“;”的参数遇到。\n\n而在Linux中, 分号; 是有特殊含义的, 因此我们需要加上*转移符号* 来进行转义.\n因此当我们输入find /home -name testfile -exec cp {} ~/testfile3 \\;后可以发现cp命令成功执行.\n管道实现除了使用find -exec来实现查找复制之外,我们还可以使用管道+xargs来实现。\n首先,如果我们这样输入:find ./ -name "*.log" | xargs cp ./logs/,那铁定是不行的。这样输入的意思是将./logs/复制到find的结果中。即:cp ./logs/ (find ./ -name "*.log")。\n要想像上述find -exec那样使用{}来代替find结果我们需要给xargs加上参数 -i. 最终结果:find ./ -name "*.log" | xargs cp {} ./logs/.\n至于-i是什么意思,我们只需要查看说明书即可:man xargs查看即可,其实差不多就是占位符的意思。有意思的是里面的-I选项,我们可以使用-I来指定占位符的内容,例如:find ./ -name "*.log" | xargs -I 0.o cp 0.o ./logs/\n","tags":["Linux","find"]},{"title":"MBG自动添加@Mapper注解","url":"/2022/03/17/MBG%E8%87%AA%E5%8A%A8%E6%B7%BB%E5%8A%A0@Mapper%E6%B3%A8%E8%A7%A3/","content":"问题最近开始使用MBG来生成MyBatis的Mapper和Dao, 只需要一点点设置就可以自动数据库自动生成Dao相关的代码,用起来还是挺方便的。但是因为我用的是Mybatis3和Spring,参照Mall项目的MBG配置后发现Mapper没有带上@Mapper,在Service使用的时候发现没有注册成Bean,一番查找后发现可以通过添加MBG插件的方式来解决。\n添加插件在官网Supplied Plugins中可以找到很多插件,但是我们需要的是一个添加@Mapper注解的插件,搜索一下可以找到一个叫做org.mybatis.generator.plugins.MapperAnnotationPlugin的插件,描述如下:\n\nThis plugin has no impact and is not needed when the target runtime in use is based on MyBatis Dynamic SQL.This plugin adds the @Mapper annotation to generated mapper interfaces. This plugin should only be used in MyBatis3 environments.\n\n我们只需要往MBGConfiguration.xml中添加<plugin type="org.mybatis.generator.plugins.MapperAnnotationPlugin"/>即可。\n"},{"title":"Manjaro构建WinPE镜像","url":"/2022/04/11/Manjaro%E6%9E%84%E5%BB%BAWinPE%E9%95%9C%E5%83%8F/","content":"问题最近显卡不知道出什么问题了,Manjaro偶尔开机才能检测到,导致我想玩Overwatch的时候需要重复开机直到显卡出现,最近给我整烦了,于是在排查是不是系统问题过程中,就需要我们的WinPE出场了。\n环境GPU: Nvidia GTX 1050 + Intel集成显卡\n动手根据Windows PE - ArchWiki里面的流程,我们需要:\n\n确保你有类似安装Ventoy之类的USB设备(因为本文主要是构建winPE镜像,因此不过多赘述这一块)\nsudo pacman -S wimlib,因为下面用到的mkwinpeimg命令由这个lib提供\n下载个Windows镜像iso,我们可以去itellyou下载,很方便也很快,如果去官网下载又慢又不好找。\n新建一个文件start.cmd,里面的内容为cmd.exepause\n将下载的iso镜像和第4步的脚本放在同一个文件夹下(比较方便)\n将下载的iso挂载到某目录下,可以直接在当前目录下创建新目录来挂载:sudo mount 下载的镜像名.iso 目录。执行mkwinpeimg --iso --windows-dir=你的目录 --start-script=start.cmd 输出winPE镜像名.iso. (请替换目录和镜像名)。例子:mkwinpeimg --iso --windows-dir=/media/winimg --start-script=start.cmd winpe.iso\n得到的winpe.iso就是我们要的pe镜像了,然后记得取消挂载sudo umount 你的目录\n\n可能出现的问题\n挂载的时候出现mount: /xxx/xxx: failed to setup loop device for /xxx/xxx/windows.iso.,我个人是使用sudo进行挂载就可以了。\n\n参考文档Windows PE - ArchWiki\n","tags":["winpe","linux","manjaro"]},{"title":"Markdown中的空白字符实现段落缩进.md","url":"/2022/08/15/Markdown%E4%B8%AD%E7%9A%84%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6%E5%AE%9E%E7%8E%B0%E6%AE%B5%E8%90%BD%E7%BC%A9%E8%BF%9B/","content":"emsp&emsp;是一个html提供的多种空格实体的一种。\n  利用这个实体我们可以插入空格实现Markdown的文本缩进。或者在这个地方→  ←插入两个空格来隔开\n\n上述markdown的源代码如下:\n\n`&emsp;`是一个html提供的多种空格实体的一种。&emsp;&emsp;利用这个实体我们可以插入空格实现Markdown的文本缩进。或者在这个地方→&emsp;&emsp;←插入两个空格来隔开\n\n其他空格实体\n这部分引用于其他博客:https://www.jianshu.com/p/160e5cb0209c\n\n&nbsp; 它叫不换行空格,全称No-Break Space,它是最常见和我们使用最多的空格,大多数的人可能只接触了&nbsp;,它是按下space键产生的空格。在HTML中,如果你用空格键产生此空格,空格是不会累加的(只算1个)。要使用html实体表示才可累加,该空格占据宽度受字体影响明显而强烈。\n&ensp;它叫”半角空格”,全称是En Space,en是字体排印学的计量单位,为em宽度的一半。根据定义,它等同于字体度的一半(如16px字体中就是8px)。名义上是小写字母n的宽度。此空格传承空格家族一贯的特性:透明的,此空格有个相当稳健的特性,就是其占据的宽度正好是1/2个中文宽度,而且基本上不受字体影响。\n&emsp;它叫“全角空格”,全称是Em Space,em是字体排印学的计量单位,相当于当前指定的点数。例如,1 em在16px的字体中就是16px。此空格也传承空格家族一贯的特性:透明的,此空格也有个相当稳健的特性,就是其占据的宽度正好是1个中文宽度,而且基本上不受字体影响。\n&thinsp;它叫窄空格,全称是Thin Space。我们不妨称之为”瘦弱空格”;,就是该空格长得比较瘦弱,身体单薄,占据的宽度比较小。它是em之六分之一宽。\n&zwnj;它叫零宽不连字,全称是Zero Width Non Joiner,简称”ZWNJ”,是一个不打印字符,放在电子文本的两个字符之间,抑制本来会发生的连字,而是以这两个字符原本的字形来绘制。Unicode中的零宽不连字字符映射为&ldquo;&rdquo;(zero width non-joiner,U+200C),HTML字符值引用为:&zwnj;\n&zwj;它叫零宽连字,全称是Zero Width Joiner,简称“ZWJ”,是一个不打印字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。零宽连字符的Unicode码位是U+200D(HTML: &zwj;)。\n此外,浏览器还会把以下字符当作空白进行解析:空格(&#x0020;)、制表位(&#x0009; )、换行(&#x000A; )和回车( &#x000D;)还有(&#12288;)等等。\n","tags":["Markdown"]},{"title":"MySQL8 内存占用高","url":"/2022/03/11/MySQL8-%E5%86%85%E5%AD%98%E5%8D%A0%E7%94%A8%E9%AB%98/","content":"问题xwash服务部署在一个内存只有1G的服务器上,最近突然发现服务器偶尔无法ssh登录,服务也停止了,重启服务器后发现内存居然占用高达95%,docker stats发现MySQL占用了300+MB的内存。\n猜想这里有3个问题需要找到答案:\n\nxwash调用MySQL的频率并不高,数据量很小,但是为什么MySQL占用这么高?\n内存占用高为什么我的服务不可用了?\n在个人电脑上同样跑docker compose up后,docker stats中MySQL只占用了20+MB而服务器上却占用了300+MB?\n\n猜想:\n\nMySQL是一个磁盘IO数据库,为了加速应该是默认用内存做了一些缓存,导致内存占用高。\n内存占用高导致系统需要将数据从内存和虚拟内存之间频繁置换,导致CPU飙升无法进行服务。\n可能是系统、docker版本、拉取的镜像的原因。\n\n解决在一番搜索后发现只需要修改/etc/mysql/conf.d/docker.cnf中的内容(如果不是用docker那可以修改mysql.cnf效果应该是一样的),添加下面的配置后重启MySQL服务即可\nperformance_schema_max_table_instances=400 table_definition_cache=400 table_open_cache=256performance_schema=off\n我们大概可以从上述配置中看出来,这些配置应该是降低了缓存的空间,禁用了performance这个功能,设置了performance的实例数量之类的操作。缓存比较好理解,毕竟缓存是需要内存空间的,但是这个performance是个什么东西?\n在MySQL :: MySQL 8.0 Reference Manual :: 27.19 Using the Performance Schema to Diagnose Problems中是这样描述的:\n\nThe Performance Schema is a tool to help a DBA do performance tuning by taking real measurements instead of “wild guesses.”\n\n也就是说这个Performance Schema是一个用于帮助提高数据库性能的工具,但是对于我们这种小机器来说,内存不足才是重要的问题,这个工具对我们来说可能是雪上加霜而不是雪中送炭,所以将其禁用掉。\n而performance_schema_max_table_instances大致意思应该是检测的表的最大数量,在官方文档中是这样描述的:\n\nThe maximum number of instrumented table objects\n\n未解\n我们在配置文件中已经将performance关掉了为什么还需要设置performance_schema_max_table_instances为400?\n为什么个人电脑上的MySQL和服务器上的MySQL在使用同一个docker-compose.xml跑相同的服务时内存占用相差300MB?CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS// 服务器docker stats(优化后,优化前占用为300+MB)a41da2fc2d2 xwash-backend-mysql-1 0.10% 160.6MiB / 980.8MiB 16.38% 43.3kB / 483kB 70.2MB / 69.6MB 42// 个人电脑docker stats5e47f78866f4 xwash-backend-mysql-1 0.31% 24.84MiB / 7.646GiB 0.32% 2.93MB / 14.1MB 256MB / 70.7MB 46\n\n参考资料docker 安装 MySQL 8,并减少内存占用 记录MySQL :: MySQL 8.0 Reference Manual :: 27.19 Using the Performance Schema to Diagnose Problems\n"},{"title":"MySQL - binlog & redo log","url":"//articles/2021/07/10/1625919877898.html","content":"\n我们知道,MySQL常用的存储引擎InnoDB使用了两个日志模块:binlog 和 redo log来保证数据库有灾难恢复的能力(crash-safe)。本文主要记录关于两个日志模块相关的一些问题。\n简单介绍一下binlog和redo logbinlog: MySQL自带的日志模块,用于记录数据库发生了什么改变。例如其他可能有一条记录:表user中,给id=4的地方的字段grade加1. \nredo log: InnoDB特有的日志模块,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。\n为什么需要redo log因为数据库有数据变更时,就要向磁盘进行IO操作。最开始没有redo log的时候,每次有一条更新就需要将其更新到数据库中,而这个更新过程需要在庞大的数据库中找到该条数据。\n查找过程如果频繁的话会占用大量IO,此时就出现了redo log,redo log提高了MySQL的效率。\nredo log像是一个缓冲区,每次有数据更新就将记录写入redo log,然后更新内存中的数据,等到磁盘空闲或者MySQL觉得合适的时机才将redo log中的更新全部真正写入到数据库(磁盘)中。\n另外一点,redo log赋予了MySQL crash-safe的能力.\n为什么只用binlog没有crash-safe的能力首先我们要知道,binlog可以用来主从同步和数据恢复。\n如果MySQL只使用binlog日志模块,那我们分两种情况讨论:\n\n先提交事务,后写入binlog\n先写入binlog,后提交事务\n\n如果先提交事务后写入binlog,那么可能会出现事务提交后数据库崩了,或者机器宕机,那么就会出现binlog中没有对应的记录,那么就会出现数据不一致的问题。\n那先写入binlog呢?先写入binlog会出现binlog存在记录,但是事务没提交,一样是数据不一致。\n因此只用binlog无法保证能够crash-safe。\n怎么用两个日志模块保证crash-safeMySQL使用了redo log + binlog,而这两个结合起来就可以保证crash-safe了,其流程如下。\n\n事务准备提交时,向redo log写入一条prepare记录使得事务为prepare状态\n写入binlog\n将redo log中的prepare状态改写为commit\n\n这套流程也叫做两阶段提交。\n同样的,我们假设在每个步骤之间会出现宕机的情况。\n\nredo log为prepare后立即宕机:此时只有redo log中有prepare的记录,重启数据库后可以发现redo log中有一条prepare记录,binlog中无该prepare对应的记录,于是可以继续提交该事务。\n写入binlog后宕机:重启数据库后,binlog中存在记录,而redo log中记录为prepare,此时就可以直接将prepare改为commit进行提交。\n\n因此,无论在哪个步骤出现了异常错误,MySQL都可以通过两阶段提交的机制恢复数据。\n","tags":["待分类"]},{"title":"Rust单测找不到文件","url":"/2022/10/08/Rust%E5%8D%95%E6%B5%8B%E6%89%BE%E4%B8%8D%E5%88%B0%E6%96%87%E4%BB%B6/","content":"初学Rust, 在编写测试的时候看官方文档中说只需要写一个mod如下即可:\n#[cfg(test)]mod tests { use super::*; #[test] fn test_sqrt() -> Result<(), String> { let x = 4.0; assert_eq!(x, 4.0); Ok(()) }}\n\n而我在照做然后运行cargo test的时候却出现了running 0 tests的结果,搜索了一下发现原来是需要在main.rs中声明使用刚才定义的mod,解决方法是正在main.rs中加入mod tests即可。\n附加内容: 引用其他路径下的文件一般来说我们的单测会放在一个文件夹中,这里拿文件夹test举例。这个时候想要引用到文件夹内,或者说不是跟main.rs同一目录的文件时,我们需要给我们原本的mod语句加上一个宏来制定文件的路径,例如:\n#[path ="./test/your_test_file.rs"]mod your_test_mod;\n\nTODOS","tags":["Rust","单元测试"]},{"title":"Spring mail SMTPSenderFailedException: 553 Mail from must equal authorized user 解决","url":"/2022/04/26/Spring-mail-SMTPSenderFailedException-553-Mail-from-must-equal-authorized-user-%E8%A7%A3%E5%86%B3/","content":"版本<artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version>\njdk17\n问题在使用Spring Boot mail的时候,参照网上给出的配置后,出现了com.sun.mail.smtp.SMTPSenderFailedException: 553 Mail from must equal authorized user的错误。错误提示也很明确啊,邮箱的from要跟认证的用户一致。那么请看网上给出的配置:\nmail: host: smtp.yeah.net username: [email protected] password: xxxxxxxxxxxxxxx properties: from: [email protected]\n解决问题就在于这里面确实写了from字段,而且也跟username一致。那么基本上可能说明字段写错了。先说答案,确实字段写错了,正确的写法应该是:\nmail: host: smtp.yeah.net username: [email protected] password: xxxxxxxxxxxxxxx properties: smtp.from: [email protected]\n\n然而这个写法并不是在某个答案里面直接找到的,因为在搜索过程中即使用的是Google,第一页里面10个有8个是采集站,又或者是一些尝试过后无效的方法(可能是版本问题)。而因为找不到可以用的解决方法,而我又基本上确定了就是字段写错的缘由,所以决定将这个字段试出来。我在某一篇文章中发现了这么一个配置项:properties.mail.smtp.ssl.enable: true,于是乎在我键入properties.mail.smtp.from: [email protected]回车之后,Copilot居然给我提示properties.mail.smtp.from: [email protected]。然后我试了一下,嗯,Copilot牛逼!!!\n"},{"title":"Spring 定时任务一段时间后停止","url":"//articles/2021/05/19/1621431407601.html","content":"前记我在项目中需要定时使用http请求数据, 在本地部署到tomcat使用过程中无问题, 在部署到vultr的服务器后一段时间后发现出现了问题 : 定时任务完全不工作了.\n先说结论 : 由于网络不佳导致http请求超时, 在代码中加上timeout即可. (我还以为超时会直接抛出异常,也可能是我哪里姿势不对)\n参数环境定时任务使用spring的@scheduled注解, 每30s执行一次.\ntomcat9\nspring 5.0.2.RELEASE\n工具库Hutool 5.6.3\n思考问题所在首先在本地部署时定时任务连续执行了一天, 正常运行没有出现问题.\n然而一旦部署到vultr就会时不时出现定时任务不运行, 考虑到vultr的网络问题, 猜想应该是http请求失败导致定时任务完全不执行.\n解决首先到tomcat的日志中查看是否存在异常抛出, 在tomcat的logs目录中, 查看所有定时任务停止的时间段的日志. 可惜, 并没有发现错误. 考虑到vultr是外国服务器, 可能请求超时, 于是给所有http请求加上timeout.\nJava中使用Hutool库如下 :\nint timeout = 2000;String body = HttpRequest.post(host).timeout(timeout).header().body().execute().body(); // POST请求String body2 = HttpUtil.get(url,timeout); // GET请求\n\n来加上timeout进行请求. 问题解决了.\n","tags":["http","超时"]},{"title":"Vim 映射Esc位置的方案","url":"//articles/2021/07/06/1625583047644.html","content":"\n由于在用Vim的时候需要频繁去按Esc,为了我们的手指健康以及效率提高,于是就有很多映射Esc的方案出现了。\njj使用jj来代替Esc是配置起来最容易的方案,只需要在~/.vimrc中输入imap jj <Esc>,退出Vim再进入就可以轻敲2次j键来代替Esc了。\n但是这里可能就会有一个疑问了,在Vim的command mode下会不会出现快速按下j被识别为Esc导致不会移动光标呢?\n答案是不会,此处引用一下fandom的说明。\n\nThe :imap command is used to create the mapping so that it only applies while in insert mode\n\n但是会导致一个新问题 : 在insert mode下输入jj会直接Esc\n使用Ctrl [代替Esc此处引用一下fandom的说明。\n\nIf you have an American English keyboard, pressing Ctrl-[ (control plus left square bracket) is equivalent to pressing Esc. This provides an easy way to exit from insert mode.\n\n简单来说,你只需要在Vim中按下两个键就可以实现Esc的功能:Ctrl + [。\nCapsLock大小写锁定键CapsLock这个键占据了一个非常有利的位置,只需要伸出左手小拇指就可以按到,我自己也是用Caps当作我的游戏麦克风开关快捷键的。\n然而这个键说实话基本上没什么用,唯一功能就是切换大小写,然而切换大小写我们可以按住Shift键来实现,只要你习惯了用Shift切换大小写输入,那么这个Caps键就显得有些食之有肉,弃之有味了。\n将Caps这个占了高效位置的键交给其他高频使用的按键来使用,说起来也算是很符合计算机思想了,同时网上也有很多人推荐将CapsLock映射到Esc。\n使用系统的键盘映射工具修改 - 推荐由于下面两个方法xmodmap & setxkbmap都是启动后就会失效,所以我Google了一下,发现了这么一句话:Isn’t there a “keyboard settings” GUI in ubuntu?. 这倒是提醒了我,于是直接在Google搜“deepin 键盘映射”,还真让我找到了这个修改键盘映射 for deepin, 那对于其他系统,我们直接如法炮制按照系统来搜索修改键盘映射的方法即可。\n经测试,重启登录后,我们的修改依然有效,终于是解决了这个问题。\nxmodmap - 不推荐一开始是打算直接修改vimrc来完成键位映射,结果发现vim中似乎无法映射Caps键,那没办法, 只能请出我们的xmodmap来修改用户全局的键盘映射了。\n首先,查看要更换的两个键的keycode。\n在terminal中输入xev, 按下Caps键,可以看到输出了如下信息\nKeyPress event, serial 38, synthetic NO, window 0x8400001, root 0x205, subw 0x0, time 25407643, (895,586), root:(895,616), state 0x12, keycode 66 (keysym 0xff1b, Escape), same_screen YES, XLookupString gives 1 bytes: (1b) " XmbLookupString gives 1 bytes: (1b) " XFilterEvent returns: False\n\n重点信息在于按下的Caps键的keycode,记录下来。同理,记录下Esc的keycode.\n接下来只需要执行xmodmap -e 'clear Lock' -e 'keycode 66 = Escape' -e 'keycode 9 = Caps_Lock'即可。\nps : 可以使用xmodmap -pke来查看所有”keycode = key”的信息。\n\n如果使用xmodmap来改变映射,在看Youtube视频的时候还是需要按下键盘上的Esc键才能退出。\n\nsetxkbmap - 不是很推荐使用xmodmap改变映射会出现一个问题有点繁琐,而且重启就没了.\n而虽然我们也可以用setxkbmap -option caps:swapescape来交换两者(caps & escape),但是一样的,重启就没了。\n在终端中输入setxkbmap -option caps:swapescape即可交换(只能在本次连接内生效),我试过将该命令放在~/.profile,crontab,~/.zshrc中,没有一个生效的,每次重启还是需要手动进行修改。\n我的推荐实际上,我更加推荐使用Caps作为Esc的方法,同时也推荐使用系统的键盘映射工具进行修改,一劳永逸,即使重启修改也依然存在。\n","tags":["Vim","xmodmap","keyboard"]},{"title":"You are in emergency mode ... Cannot open access to console, the root account is locked. 的一种解决方法","url":"//articles/2021/08/12/1628747469166.html","content":"前记前天(2021.08.10)电脑(deepin 20.2)在向某个disk写入数据的时候突然出现无法写入的错误,进入home目录一看,所有目录和文件都变成了只读状态,写入文件得到报错:read only file system。浏览器也无法使用One Tab拓展了,推测应该是写入数据的时候出了点状况导致整个disk变成只读了。\n因为我的根目录是挂载在另外的硬盘上的,所以想着重启也不会出现什么大问题,而且不排除是bug的可能,重启一下试试,结果一重启,电脑干脆就卡在了开机界面不动了。\n在出现问题的时候,因为我自己是很清楚是哪个disk出现问题了,所以估计开机卡在开机界面应该是开机的时候自动挂载了出问题的磁盘的原因。所以我们的思路就是开机不要挂载出问题的磁盘,然后我们把损坏的磁盘进行一个修复,再让系统开机挂载修复好的磁盘就可以了。\n把出问题的disk取消开机挂载由于我们现在连系统都进不去,我自己是直接掏出Live usb进入usb里面的系统(当然了你有其他方法进入tty或者修改grub启动参数进入系统,只要能修改到原本根目录下的文件/etc/fstab就可以)。\n由于我是用Live usb的系统进入,所以此时的根目录并不是我们原本的根目录,这个时候就要手动将原本的根目录进行挂载,然后在终端输入vim /xxxx/etc/fstab,(具体方法可以看文章下面的进入live USB修改原系统root密码)将出问题的磁盘的挂载信息注释掉,例如下图中,我们需要在红色框内,UUID前加上#来将其注释掉。\n\n搞定之后保存,这个时候我们重启一下,就会发现电脑不再卡在开机界面的,但是会得到下面的提示:\n\n这个时候如果你的系统没有root密码,无论你按什么Enter, Ctrl Alt F4都无效,甚至按下电脑的开机键也不会关机,无奈只能将电脑强制断电了。\n但是如果你可以进入emergency mode,那就直接跳转到查看报错吧\n解决方法可以看到上图中关键的信息是:You are in emergency mode 和 Cannot open access to console, the root account is locked。\n我们先搜索第一个关键信息emergency mode,可以找到有很多人都有这种情况发生,但是大部分的人都是按下Enter or Ctrl D就可以跳过并且正常进入系统,和我们的情况不太相同,我们的情况是无论按什么都会重复出现这个提示信息。\n搜索第一个信息无果,那我们搜第二个Cannot open access to console, the root account is locked. 几番查找后发现居然是因为没有root密码导致无法打开console,汗颜。这个时候我们只需要给root加上密码就行。\n进入live USB修改原系统root密码在我们安装deepin的时候做了一个类似于Windows的pe系统的安装工具在我们的U盘中,我们需要进入USB中的系统为我们原先的系统的root加上密码。首先电脑关机后插上U盘,开机后选择系统选项(这一点deepin做的真不错,不需要一直按F12或者其他键),在Boot处直接选择我们的U盘启动(跟重装系统是一样的)。\n当界面到达安装界面的选择语言时,按下Ctrl Alt F4,输入startx后稍等一下就可以进入live usb中。进入系统后,输入sudo fdisk -l查看我们有的disk设备。\n\n我们先将原先系统挂载在/上的disk, 在上图中我可以确定是/dev/nvme0n1p3,将其挂载到某个目录下(可以在~/下新建一个空目录):mount /dev/nvme0n1p3 ~/tmp\n\n这里如果无法确定哪个是我们要挂载的disk的话,deepin其实直接在文件管理器中双击打开分区就可以自动挂载到/media/${user}/xxx下,在文件管理器中也可以进入其中查看是否有/bin目录,如果有,那就是我们要的哪个disk了,直接在文件管理器中右键,打开终端即可。\n\n然后我们需要在终端中切换到原系统中的root账户,将终端的当前目录切换到我们刚才挂载的目录cd ~/tmp中(如果是使用上述deepin的文件管理器右键进入终端的方法的话不需要输入上一条命令)\n输入sudo chroot .\n上面命令有个小数点不要漏掉,成功后我们再输入passwd进行密码修改。当我们修改完密码后其实就可以直接重启了,这个时候就可以正常进入到emergency mode了。\n查看报错当我们从emergency mode登录后,可以看到提示叫我们使用journalctl -xb查看启动日志。\n\n通过查看日志,在最后一页看到一个错误Failed to start File System Check on /dev/disk/by-uuid/a2bfcdf2-ebe5-xxxxxxxxxx-xxxxx-xxxxxxxxx.\n\ntips : 使用f可以翻页, b上一页,G到结尾。\n\n可以看到,这里有个错误,检查某个disk的时候有错误,这个时候我们先拍照记下这个uuid,然后输入cat /etc/fstab查看对应的uuid是哪个。\n\n修复磁盘我自己发生问题的时候对应的是上图红圈处对应的设备/dev/sdb1。我们运行fsck -a /dev/sdb1进行修复,稍微等待一下就可以修复成功了。\n\n参数-a是让fsck发现错误后直接修复而不需要询问\n我自己的磁盘是/dev/sdb1,但是对应到你自己的电脑中就要按照自己的来了。\n\n修复完毕后千万记得,我们前面是修改过/etc/fstab的,所以要将其修改回来,把我们之前加上的注释去掉就可以重启了。\n后记在搜索过程中,发现很多出现这个问题的人都是对分区作出一些修改,修改了/etc/fstab文件导致的问题。但是我自己上一次对fstab文件修改已经是很多次开关机之前的事情了,导致问题的原因可能还是写入数据的后死后出现了某些问题导致整个file system出错。\n同时中间遇到的root没有密码居然无法进入emergency mode也是让人非常汗颜,甚至最开始并不知道是这个原因,以为是磁盘损坏了,扫描了半天并没有发现磁盘损坏。\n在使用deepin的live usb的时候,遇到一个bug,扫描磁盘的时候我出门,习惯性按下win L进行锁屏,然后我使用那个用户也没有密码,所以很悲催,我无法解锁,不输入任何内容直接点解锁也无法解锁,甚至关机也需要密码,按下笔记本的开机键关机也需要密码,无奈只能直接断电(但是我的磁盘还在扫描阿!!!淦)\n","tags":["Linux","solution"]},{"title":"github显示fork与上游的commit差别","url":"/2021/12/21/github%E6%98%BE%E7%A4%BAfork%E4%B8%8E%E4%B8%8A%E6%B8%B8%E7%9A%84commit%E5%B7%AE%E5%88%AB/","content":"今天在用Elasticsearch-head拓展的时候发现在点击文档的时候会出现多层重叠的问题, 所以想看一下有没有修复这个问题, 然而仓库上一次commit的时间是2年前, 所以想看看有没有其他人fork后在继续维护, 但是点开fork列表后发现Github并没有显示哪些fork是领先上游, 哪些是落后的.\n搜索了一下发现以及有人提出过这个问题: How to determine which forks on GitHub are ahead? StackOverflow\n其中有一个老哥写了一段js的代码, 非常好用.\n(async () => { /* while on the forks page, collect all the hrefs and pop off the first one (original repo) */ const aTags = [...document.querySelectorAll('div.repo a:last-of-type')].slice(1); for (const aTag of aTags) { /* fetch the forked repo as html, search for the "This branch is [n commits ahead,] [m commits behind]", print it directly onto the web page */ await fetch(aTag.href) .then(x => x.text()) .then(html => aTag.outerHTML += `${html.match(/This branch is.*/).pop().replace('This branch is', '').replace(/([0-9]+ commits? ahead)/, '<font color="#0c0">$1</font>').replace(/([0-9]+ commits? behind)/, '<font color="red">$1</font>')}`) .catch(console.error); }})();\n\n甚至还有个老哥用go写了个项目, 似乎是在本地CLI上直接跑的(没用上就没细看): forkizard\n, 作者在Readme中展现的效果如下:\n$ ./forkizard k0kubun/pp2019/06/20 16:27:35 44 forks 44 / 44 [===============================================================================================================================] 100.00% 32sdone/wtertius/pp +6 -3/hbbio/pp +5 -3/quanticko/pp +2 -12/federico-bollo/pp +1 -12/juntaki/pp +1 -12/sniperkit/pp +1 -12/yudai/pp +4 -39/wangkechun/pp +2 -57","tags":["杂症"]},{"title":"ext4磁盘的操作记录","url":"/2022/10/04/ext4%E7%A3%81%E7%9B%98%E7%9A%84%E6%93%8D%E4%BD%9C%E8%AE%B0%E5%BD%95/","content":"磁盘信息查看sudo fdisk -l: 查看当前磁盘的信息\n格式化sudo mkfs -t ext4 <device>: 格式化为ext4格式(注:格式化操作会让磁盘内容丢失)\n\n例子:sudo mkfs -t ext4 /dev/sdc1\n\n修改磁盘标识e2label <device> <name>: 修改磁盘标识(磁盘名)\n\n例子:e2label /dev/sdc1 new_disk\n\n挂载/取消挂载sudo mount <device> <directory>: 挂载sudo umount <device>:取消挂载\n注意事项\n格式化后的磁盘可能没有权限读写,需要在挂载之后修改权限。sudo chown -R <directory> <user>:<group>\n例子:sudo chown -R /mnt/new_disk qbug:qbug\n\n\n\n","tags":["Linux","磁盘"]},{"title":"localhost 和 127.0.0.1","url":"//articles/2021/05/15/1621048944039.html","content":"前记环境 : Windows10 + WSL2今天在WSL2中搭建Halo博客的时候发现无法通过127.0.0.1进行访问, 在防火墙开放端口, 将ip更换为WSL2的ip依旧不能访问. 随后搜了一下”WSL2 访问内部服务”后发现原来是需要使用localhost进行访问, 使用localhost后就可以正常访问了.\nlocalhost 和 127.0.0.1 的区别虽然localhost和127.0.0.1都可以用来访问到本地主机, 但这两者在细节之处还是有一些不同的.\n\nlocalhost指”本机服务器”, 不会被解析成ip, 而是直接访问到本机的服务, 同时因为是”本机服务器”的身份, 因此也不会受一些权限的限制.\n127.0.0.1指”本地地址”, 相当于本机向127.0.0.1这个地址发起请求, 对于本机的相对服务来说, 这个请求不知道是谁发来的, 因此可能有权限的限制.\n将hosts中的关于127.0.0.1的解析行注释掉后, 通过Tracert查看两者经过的路由器可以看到, 两者走的路由器并不和baidu.com路由器不一样, 也就是说127.0.0.1和localhost都不会发送出去, 应该是在IP层就被拦截然后发送回应用层了.\n\n\n注: 127开头的ip地址一般用于环回, 也就是说不会发送出去, 只是在本机中传输, 其所在的回环接口一般被理解为虚拟网卡,并不是真正的路由器接口.\n\n验证是否走网卡在hosts文件中有这样的说明:”localhost name resolution is handled within DNS itself.”即localhost直接交由DNS进行解析, 同时hosts中的localhost解析行也被注释了.\n为了验证localhost和127.0.0.1究竟走不走网卡, 我把所有网卡都禁用了(连带WSL2的网卡也禁用了), 然后进行ping后发现localhost和127.0.0.1依然可以直接ping通, 且WSL2中的服务也可以很流畅的访问. 因此localhost和127.0.0.1是不走网卡的.\n相关文章彻底明白ip地址,区分localhost、127.0.0.1和0.0.0.0Windows访问WSL2的网络应用 - 微软官方文档\n","tags":["localhost"]},{"title":"nodejs手动安装过程出现\"/usr/bin/env: \"node\" no such file\"等问题","url":"//articles/2021/08/24/1629811555795.html","content":"今天安装nodejs的时候,开开心心下好包,解压到node文件夹中,在终端跑了一下node/bin/npm -v,结果居然出现了/usr/bin/env: "node" no such file的报错,我擦列?\n其实这个问题是因为node使用的是/usr/bin/node来跑npm,所以我们需要把解压后的node/bin/node建立软连接到usr/bin/node上。只需执行ln -s /your path/node/bin/node /usr/bin/node即可。\n但是这里有个小坑,建立连接的时候不要偷懒写./node,写全路径,不然就会/usr/bin/env: “node”: 符号连接的层数过多的问题。\n","tags":["杂症"]},{"title":"tcp/ip 5层模型下的多重校验以及思考","url":"//articles/2021/07/29/1627489403948.html","content":"\n我们知道,tcp/ip 有5层。从上到下分别为:应用层,传输层,网络层,数据链路层,物理层。今天我们要说到的只有前4层,要聊的是校验。\n什么是校验?在生活中,每逢下雨天,WIFI就有很大概率会变得不稳定,因为信号在传输过程在受到了湿度上升的干扰,导致出现数据有一些bit会被flip. 校验就是为了避免这种情况,检查包是否因为信噪/机器错误/…被篡改.\ntcp/ip模型中的校验在5层模型中,前四层每一层都会进行或强或弱的校验,尽最大可能以确保数据送达时不会出现任何错误。也就是说,一个数据包需要经过至少4层校验才能成功到达应用程序。为什么我们需要这么多层校验呢?我们模拟一下一个包被接收后可能被篡改的过程。\n假设现在有一个包P到达网络层前,包中的IP地址被修改为其他IP : 我们记修改后IP为Y,而此时好巧不巧包中的校验结果也被“非常巧合”地修改为被篡改后的包的校验值,那么这个时候包P就可以非常顺利到达Y并且由于校验值也被修改为正确的值,所以包P通过了网络层的校验。如果这个时候传输层没有多一层校验,那么Y将错误地接收这个本不属于Y的包。\n所以其实多重校验是为了保证数据包99.99999999%不会出问题,要是真有那极小的概率出现了问题,那也不一定会造成重大损失;要是真的造成了重大损失,uh,那就纯属天意了。\nUDP中的伪首部在UDP校验中,需要一个叫做伪首部的数据块加入校验运算。伪首部如下图所示(0 - 96 bit offset,被粉红色高亮的就是伪首部). (图片来自维基百科)\n\n之所以我们需要伪首部,主要原因是UDP在设计的时候IP地址同时也属于UDP这一层(所以计算校验的时候把IP计算上是本分工作),但是IP地址同时在网络层也有存在,如果给UDP首部塞进去个IP地址那就是浪费空间了。于是就有了这一招伪首部。其次就是我们上面所提到的,防止出错,在这里出错有两个可能:IP地址和协议。\n不过可能就有人(包括我自己)会问了:不是IP层校验过了吗?怎么传输层还要校验?\n这是因为防止出错,因为如今的互联网是大数据时代,每分每秒的数据量都是十分庞大的,在这么庞大的数据量下,玩过炉石传说的同学应该知道:大概率会发生小概率事件,因此我们最好将出错概率降到最小。\n一些小细节在数据链路层上,会采用CRC等比较强的校验方式,因为数据链路层是除了物理层最先接触到数据的,所以如果出错了就尽早将该包丢弃,不至于到达应用层才发现出错了。\n而在网络层以及传输层(不包括TSL/SSL)上,只是采用简单的checkSum进行校验。\n而在TSL/SSL,应用层上采用了较强的校验方式,前者是为了安全,后者则是最后一道防线,理应加强校验。\n","tags":["Internet"]},{"title":"vue3导入element-ui 报错Uncaught TypeError: Cannot read property 'prototype' of undefined.","url":"/2022/04/01/vue3%E5%AF%BC%E5%85%A5element-ui-%E6%8A%A5%E9%94%99Uncaught-TypeError-Cannot-read-property-prototype-of-undefined/","content":"环境vue --version@vue/cli 5.0.4vue 3\n问题今天使用vue3的时候按照某教程按照如下方式导入element-ui:\nimport Element from 'element-ui'import "element-ui/lib/theme-chalk/index.css"\n\n结果npm run serve后页面空白,打开Console一看报错了:Uncaught TypeError: Cannot read property 'prototype' of undefined.\n解决搜索了一下发现element-ui是给vue2.x使用的,我们使用vue3需要导入element-plus包,通过需要按照官方文档来进行导入。\n官网贴出来的具体导入代码如下:\n// main.tsimport { createApp } from 'vue'import ElementPlus from 'element-plus'import 'element-plus/dist/index.css'import App from './App.vue'const app = createApp(App)app.use(ElementPlus)app.mount('#app')\n\n"},{"title":"vue项目中使用vuex存储变量","url":"//articles/2021/05/15/1621049877913.html","content":"前记因为项目前端需要访问后端的api, 在写axios的时候发现有写了几个地方都是同一个api连接, 于是就想写入”配置文件”中以便直接读取和解耦. 在vue的官方文档中驰骋了一会, 发现因为版本不一样, 不知道怎么引入项目, 当场裂开. 好在后来还是找到了.(主要怕自己下次忘了所以记一下)\n环境vue3@vue/cli 4.5.12\n简单使用\n创建store文件夹位于project/src/, 文件夹内有一个index.js文件\n在state中写入要存储的变量\n\nstore/index.jsimport { createStore } from "vuex";export default createStore({ state: { food:"鸡扒饭", anotherFood:"盖浇饭" }, mutations: {}, actions: {}, modules: {},});\n\n\n在main.js中使用store\n\n因为版本的差异 , 我项目中使用的是createApp.use(store)\n// main.jsimport { createApp } from "vue";import App from "./App.vue";import router from "./router";import store from "./store";createApp(App).use(store).use(router).use(store).mount("#app");\n\n而网上有一些版本则是new Vue()\n// 其他版本// 省略importnew Vue({ el: '#app', store, ... // 省略其他内容})\n\n\n在component中使用$store.state.var获取变量\n\n<template> <h1>{{ $store.state.food }}</h1> <h1>{{ $store.state.anotherFood }}</h1></template>\n\n在中也能使用, 只需要在前面加上this.即可, 例如 console.log(this.$store.state.food)\n其他使用方法因为官方文档中只是没有说明要如何将vuex引入到项目中, 因此也就不记录其他例如mutations的用法的, 感兴趣的可以自行查看官方文档.\n参考vuex使用demo - 简书vuex官方文档\n","tags":["vue"]},{"title":"wsl2端口映射","url":"/2022/01/05/wsl2%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84/","content":"最近在和同事协调工作的时候需要让同事通过局域网访问我的后端服务, 然而我的服务是放在wsl2里面的, 从局域网内除了本机之外的机器都没办法直接访问到wsl2中的服务. 所以想到做一下端口映射或者转发.\nwsl2端口映射打开带有管理员权限的powershell, 我们需要先获取本机局域网内ip和wsl2的ip. \n# 添加netsh interface portproxy add v4tov4 listenport=${pc-port} listenaddress=0.0.0.0 connectport=${wsl2-port} connectaddress=${wsl2-ip}netsh interface portproxy add v4tov4 listenport=9000 listenaddress=0.0.0.0 connectport=9000 connectaddress=172.20.70.33# 删除netsh interface portproxy delete v4tov4 lietenport=${pc-port}netsh interface portproxy delete v4tov4 listenport=9000# 查看netsh interface portproxy show all# 修改防火墙规则netsh advfirewall firewall add rule name="TCP 9000" protocol=TCP dir=in localport=9000 action=allow\n"},{"title":"开发过程中浏览器允许跨域","url":"//articles/2021/05/15/1621049680340.html","content":" \n前记最近在用vue写页面调用后端的api, 发现使用axios调用api会出现跨域问题.\n由于在后端捣鼓了一阵子也没解决这个问题, 于是乎就从浏览器入手, 直接禁用浏览器的同源策略即可.\nFirefox .ver 88.0.1 (64 位)\n有效方法 : 下载扩展cors-everywhere, 当需要跨域时点击扩展将其变为绿色就可以进行跨域了.\n网上有很多博客文章都说 about:config -> security.fileuri.strict_origin_policy/network.http.referer.XOriginPolicy 设置为false(后者设置为0)即可, 亲身试了一下没有效果 . 猜测可能是Firefox新版本不允许这样禁用同源策略.\n\n若你是在后端添加access-origin:”*“, 则可能同样无法通过Firefox进行跨域, 在Firefox中可能需要直接指明origin而不是使用通配符*.\nEdge .ver 90.0.818.51 (官方内部版本) (64 位)\n简单粗暴方法 : 装扩展 Allow CORS: Access-Control-Allow-Origin, 点开扩展点左边的大图标就可以开始跨域了.\n\nChrome .ver 90.0.4430.93(正式版本) (64 位)\n在浏览器快捷方式中的路径后面加入如下参数--args --disable-web-security --user-data-dir="C:/ChromeDevSession"\n装扩展Allow CORS: Access-Control-Allow-Origin, 同Edge.\n\n","tags":["同源策略","浏览器","跨域"]},{"title":"使用burp suite抓取安卓APP包","url":"//articles/2021/05/15/1621063500676.html","content":"\n前记最近需要抓取某平台的数据, 而因为目标数据只能在APP上请求, 因此就需要我心爱的安卓手机出场了. \n经过一番尝试, 建议使用安卓版本<7的手机进行抓包, 可以少掉很多限制. 我也是用安卓6的小米3抓包成功的, 使用安卓10的小米8出现了一堆问题而且最后还是没办法成功抓包.\n补充(2021.12.20):直接在安卓上装了个HttpCanary,还挺好用的直接用手机就能抓包。随便推荐个工具叫Http Tool Kit也挺不错的。\n环境&设备PC : Windows10\n手机 : 小米3(安卓6 可以抓包)\n手机 : 小米8(安卓10 无法抓取部分包)\n抓包准备流程如果在安装用户证书流程做完后, 已经可以满足你的需求, 则不需要将证书安装到系统中.\n安装用户证书流程首先我们选用burp suite在PC端进行抓包, 为了抓取https包需要2,3两个步骤, 若不需要抓取https即可以跳过.\n\nburp suite -> Proxy -> Options 设置好要拦截的ip以及端口.\n在(1.)的Options中找到Import / export CA certificate, 一路导出即可.\n将(2.)得到的证书传输到手机中进行安装( 各个手机安装方式有所差异, 但是一般都是 设置-> 安装证书 -> 选择证书 即可 )\n手机连接跟PC在同一局域网的Wifi, 进入Wifi设置代理, 代理ip为PC的ip, 端口即在(1.)中设置的端口.\n回到burp, 进入于Options平级的Intercept, 点击按钮使得Intercept is on\n手机访问网站即可抓包.\n\n安装系统证书流程此步骤是为了解决APP不信任用户证书的问题.\n系统证书目录/system/etc/security/cacerts/\n\n如果你是安卓10并且使用Magisk, 安装Magisk模块Move Certificates, 其会将所有用户证书放入系统证书. (安全问题有待考虑)\n手动放入目录 ( 手机需要root, 非root的方法请自行查找 )\n假设你已经有了burp的证书PW.cer, 执行如下Shell (我是在wsl2下执行的, Windows应该也差不多)\nsudo openssl x509 -in PW.cer -inform DER -out PW.pem -outform pem # 转格式\nsudo openssl x509 -inform PEM -subject_hash -in PW.pem|head -1 # !!!复制输出结果,假设结果是7bf17d07\nsudo cat PW.pem > 7bf17d07.0 # 7bf17d07是证书hash, 0是防止存在相同hash的证书的编号\n\n\n将7bf17d07.0传输到手机中, 使用MT管理器之类的管理器将其放入/system/etc/security/cacerts/下 . 或者使用adb进行push.\n\n\n\n完成以上操作, 你应该就可以愉快进行抓包啦~\n可能出现的问题手机设置代理后无法联网我是使用校园网就不会出现这个问题, 但是用手机连PC的热点就会出现只要设置代理就无法联网, 且无法访问到PC的服务. ( 但是以前抓包是用PC的热点却可以联网 )\n这个问题一般是因为找不到代理对象而出现的.\n部分网站/APP无法联网或抓到包原因 :\n\nAPP并没有走我们的抓包工具的代理服务器\nAPP不信任我们安装的证书\n\n解决方法 :\n\nsasdsda修改host将请求都导向代理服务器(没试过, 但是应该可行)\n\n使用低版本安卓(< Android Nougat)\n将证书放入系统证书目录. (需要做一些修改, 见抓包流程)\n\n\n\n安卓10抓包大失败出问题我个人主力机是小米8, 也给心爱的它装上了Magisk并进行Root了. 抓个APP的包不是手到擒来? 信心十足的把小米8和PC都连上校园网, 设置好代理, 手机安装好CA证书, 打开burp suite一气呵成开始抓包. 然后在测试阶段就发现微信公众号的图片加载不出来, 最重要的是 目标APP无法联网. What ?!?\n寻求解决Google了一下才发现是因为Android10中该APP只信任系统证书, 于是又想把证书变成系统证书. 然后又发现GG了, 无论如何都无法挂载/system为读写. adb, twrp, MT管理器都试过了均没有效果.\n其中twrp可以将证书放入系统证书目录, 但是一开机就又恢复原状了. 然而过了几天在更新Clash订阅的时候突然发现出现了报错信息:/system/etc/security/cacerts/7bf17d07.0 permission denied , 居然成功放进去了, 结果发现还是APP内依然无法联网.\n于是只好宣布安卓10抓包大失败!\n山穷水复疑无路, 柳暗花明又一村, 好在在Android10 下我们仍有备选方案 : JustTrustMe(Xposed) 与 Move Certificates(Magisk).\n\nJustTrustMe .v4\n官方版本为 .v2 , 2016年最后一次更新, 试了一下已经没有用了, 于是找到这个老哥写的v4版本, 但是, 试了一下依然没有用.\n\n\nMove Certificates\n留待测试.\n\n\n\n安卓6 抓包大成功在我的小米3 (Android 6)中, 只要按照抓包流程的操作来, 安装完系统证书后就可以进行抓包了, 没有一点问题.(唯一问题就是小米3实在是太卡了)\n","tags":["Android","安卓","抓包"]},{"title":"拓竹A1问题处理","url":"/2025/01/15/%E6%8B%93%E7%AB%B9A1%E9%97%AE%E9%A2%98%E5%A4%84%E7%90%86/","content":"最近使用拓竹A1,发现使用过程中出现一些神奇问题,记录一下。\n问题1 吐料到拨片上不知道怎么回事,拨片似乎在吐料的时候卡在中间,导致料吐在拨片上凝固,然后整个拨片都会卡住无法正常使用。\n解决方法就是拿吹风机热风对着凝固的耗材狂吹2分钟,软化耗材后然后迅速使用尖嘴钳或者比较能出力的钳子,夹住耗材上下拨弄(此时应该可以用钳子使耗材明显形变,如果不能请继续加热),就可以拿下来了。\n问题2 拓竹官方PLA耗材耗尽后纸片被带入,需要拆五通原因现象使用拓竹官方PLA耗材,打印过程中出现纸片被带入,导致五通堵住,需要拆五通清理。原因是耗材的最后一段使用了一个胶带和纸条来站住耗材,最后入料就把胶带纸条也带进机器里了。此时千万不要尝试用较大的器具去将纸条挑出来,只会越捅越深,最后需要拆五通来解决。如下图:\n拆五通解决拆五通的步骤详见拓竹wiki,此处不赘述。拔出来之后的五通如下图,这里还需要将四个黑黑的那一层板拆开,需要一个镊子(非常重要,我使用的是夹眉毛的大镊子,非常难撬开)。用镊子伸进四个黑黑那一层的缝隙(即AMS的S旁边的黑线缝隙),然后狠狠撬开。这里会需要非常大力才能撬开,请尽情用力。\n拆开五通后,零件如下,中间的零件上似乎有润滑油,请小心尽量不要碰到。然后我们就可以用耗材丝从拿着的这个零件上,从小头把把纸片捅出来了。\n","tags":["3d打印"]},{"title":"有了Socket, 为什么还需要WebSocket?","url":"/2022/08/20/%E6%9C%89%E4%BA%86Socket-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%98%E9%9C%80%E8%A6%81WebSocket/","content":"WebSocket和Socket我们知道Socket(套接字),其实就是协议+ip+端口, 是基于TCP协议的。不同于HTTP短暂的请求/响应,Socket可以保持一条TCP长链接几天,同时通信双方可以随时向对方发送消息。\n而WebSocket看上去像是Socket的翻版,同样的可以为通信双方建立一条链接进行通信,双方也可以随时发送消息给对方。而WebSocket解决的问题就是服务器向客户端推送消息的问题,那么问题来了\n\nSocket同样也可以解决服务器向客户端推送消息的问题,为什么会有WebSocket出现而不是直接用Socket呢?\nWebSocket和Socket之间有什么关系吗?\n\n在查阅了众多资料后发现实际上WebSocket和Socket并没有什么很明显或者说很强的关系,查阅资料后得出的一个结论就是WebSocket和Socket没什么关系。\n对以上两个问题的解答:\n\nWebSocket的一个使用场景是浏览器,而浏览器本身存在很多限制例如同源策略,所以浏览器其实不允许直接操作TCP。因此需要一个新的协议来实现这个功能。(实际上我们可以在很多地方使用WebSocket,例如在Java中就可以使用WebSocket客户端, autojs脚本中也可以使用)\nWebSocket可以用Socket来实现。WebSocket与HTTP一样位于应用层,运行在TCP之上;而我们熟知的Java中的Socket相当于是TCP层的,直接操纵TCP,更加灵活。\n\n实际上WebSocket最开始叫做TCPConnection,后来改名为WebSocket. 感觉就像JavaScript与Java之间的关系,不过WebSocket因为是应用层的协议,肯定是需要调用传输层的协议,所以多少还是需要依赖于Socket的。\n参考资料What’s the difference between WebSocket and plain socket communication? - StackOverflow\n","tags":["Socket","WebSocket"]},{"title":"目录跳转工具z安装","url":"/2022/11/20/%E7%9B%AE%E5%BD%95%E8%B7%B3%E8%BD%AC%E5%B7%A5%E5%85%B7z%E5%AE%89%E8%A3%85/","content":"简介z是一个在命令行中非常好用的工具,他的作用也非常简单,就是让你可以用简短的文本快速跳转到你访问过的目录。例如我在目录/home/qbug/proj/work/java/backend/myWork中工作,这个时候我需要切换到/usr/local/share目录下做其他事情,等到我想要跳转回第一个目录时,只需要输入z myWork甚至z my即可。\n安装z的Readme界面如果没有细看的话就很容易遗漏掉安装方法,同时官方提供的安装方法对于新手来说也是太过简单了,因此我在这里提供一个相对比较详细的安装方法。\n\n克隆仓库 git clone https://github.com/rupa/z && cd z\n将克隆下来的z.sh文件放入/usr/local/bin目录下: sudo mv z.sh /usr/local/bin/z(或者也可以放在其他目录中,只要在$PATH中即可)\n将说明书移入说明书目录:sudo mv z.1 /usr/local/share/man/man1\n在你使用的shell配置文件(例如zsh.rc, bash.rc)中添加. /usr/local/bin/z. (如果你在第2步的时候修改了z的位置,那么这里也要相应修改)\n\n大功告成了,现在z会记住你访问过的目录,只要是你访问过的目录都可以用z进行快速跳转了!\n","tags":["CLI"]},{"title":"记录同源策略与跨域","url":"//articles/2021/05/15/1621049584605.html","content":"前记在写项目时因为跨域的事情捣鼓了一段时间, 虽然以前也了解过CSRF攻击, 但是看到跨域时也没有跟跨域联系起来, 因此有必要写一下自己对跨域的理解.\n同源策略同源策略(Same-origin policy,简称 SOP) 指浏览器 的同源策略( 也就是说可以在浏览器层面破坏同源策略 ).\n以下是来自MDN对同源策略要求的解释\n\n如果两个 URL 的 protocol、port (en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。\n\n也就是说只要 协议, 主机, 端口 三者相同时, 则说明两个URL是同源的.协议和端口很好理解, 长得一样就行, 而主机其实也是如此, 主机也就是从协议结束位置开始到端口号开始处结束. 例如有如下URL : https://demo.example.com[:80]/demo1.html , 其中主机即为demo.example.com, 中括号指代可以出现也可以不出现.\n以下是来自MDN的例子[\n同源策略的意义假设我们没有同源策略, 每个网站的的cookies, DOM等重要数据就完全暴露出来, 那么就很容易出现网站被随意篡改攻击.\n举个简单的场景, 假设你登陆某银行网站, 然后没有退出网站就去浏览一些不正规的网站, 当你点开视频看的兴起的时候, 手机突然噔噔收到一条短信:”您的账户成功向xxx转账10000元 “, 你当场吐血三升…\n虽然上面的例子现实中不太可能出现, 但是为什么你明明只是点开了视频就被转账了呢? 归根结底还是因为没有了同源策略的保护, 你所访问的不正规网站可以使用你在银行页面登陆后的cookies来向银行发起转账请求 , 因为这个过程是前端页面, 也就是你的浏览器发起的请求, 在银行看来这个请求就是你本人发起的, 因此同源策略还是非常有必要的.\n来看一下MDN是如何解释同源策略的.\n\n同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。\n\n可以看到其中只是说SOP”减少可能被攻击的媒介”, 也就是说SOP只能减少被攻击的风险, 并不是完全的安全.\nSOP的限制SOP主要有三个限制:\n\nAjax请求不能发送\nDOM无法获取并操作\nCookie, LocalStorage和IndexDB无法获取\n\n第2,3点都是比较容易理解的, 比较如果允许获取DOM以及其他敏感信息, 那么用户的安全几乎等于没有.\n而之所以要禁用Ajax, 主要是因为Ajax请求会带上访问的网站的cookie. 假设你浏览器中还存有某银行的cookie, 如果没有禁止跨域, 那么当你访问某恶意网站时, 该网站发起一个Ajax请求就可以伪装成你进行转账, 修改密码等操作了.\n后记虽然SOP给程序员写代码带来了一些问题, 但是相较于安全性来说, SOP还是相当有必要的, 且在开发中程序员大可以选择一个专门用于调试的浏览器(我自己是用火狐当调试), 设置浏览器允许跨域即可.\n","tags":["同源策略","浏览器","跨域","SOP"]}]