阅读视图

发现新文章,点击刷新页面。
🔲 ☆

macOS App 开发 iCloud 不同步问题解决

背景

时光鸡 - 纪念日·轮播提醒 我又做了 iOS 的第一个版本,想通过 CloudKit 实现两端的数据同步。但是同样的代码 iOS 正常,数据可以正常同步到 iCloud,macOS 的始终不行。

我在这个问题上调试了很久才解决掉,记录一下解决过程。

现象

  1. 同样的代码 iOS 开发环境和商店版本都正常可以同步 iCloud 数据。
  2. 同样的代码 macOS 版本开发环境可以正常同步数据,但是线上版本不行。

如果你也遇到了 macOS 开发环境 iCloud 能正常同步数据,但是 TestFlight 和线上版本却不行,可以看看我下面的处理过程。

我 iOS 版本数据同步是正常的,就排除了类似配置错误或者这个 CloudKit Database 不可用等可能性。

解决办法

参考这个案例 SwiftUI macOS app not syncing with iCloud

Apple 开发者论坛也有同样的问题备份 SwiftUI macOS应用程序无法与iCloud同步

Well, the answer was simple and yet hard to find. It seems when you convert a Core Data app to work with CloudKit you need to add CloudKit.framework to the Frameworks, Libraries, and Embedded Content section. without it, the app will work when running in debug mode (via Xcode) but once it’s in production, signed and notarized, it won’t. Even if you have the right entitlements, all pointing to production, etc.

翻译一下:

嗯,答案很简单,但很难找到。似乎当您将 Core Data 应用程序转换为与 CloudKit 一起使用时,您需要添加 CloudKit.frameworkFrameworks, Libraries, and Embedded Content 部分。如果没有它,应用程序将在调试模式下运行时(通过 Xcode)工作,但一旦它投入生产、签名和公证,它就不会工作。即使您拥有正确的权利,所有权利都指向生产等。

选择你的 Targets,第一个 General Tab 里面有个 Frameworks, Libraries, and Embedded Content,点击 ➕ 添加 CloudKit.framework,Embed 选择 Do Not Embed

报错的日志

error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _performSetupRequest:]_block_invoke(1242): <NSCloudKitMirroringDelegate: 0x60000377c000>: Failed to set up CloudKit integration for store: <NSSQLCore: 0x13dc04080> (URL: file:///Users/admin/Library/Containers/xxx)
<CKError 0x60000064adf0: "Service Unavailable" (6/NSCocoaErrorDomain:4099); "Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error."; Retry after 5.0 seconds>

error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate recoverFromError:](2312): <NSCloudKitMirroringDelegate: 0x60000377c000> - Attempting recovery from error: <CKError 0x60000064adf0: "Service Unavailable" (6/NSCocoaErrorDomain:4099); "Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error."; Retry after 5.0 seconds>

Error retrieving daemon to get network transfer endpoint: Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.cloudd was invalidated: failed at lookup with error 159 - Sandbox restriction." UserInfo={NSDebugDescription=The connection to service named com.apple.cloudd was invalidated: failed at lookup with error 159 - Sandbox restriction.}

Error getting network transfer endpoint: <CKError 0x600000600990: "Service Unavailable" (6/NSCocoaErrorDomain:4099); "Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error."; Retry after 5.0 seconds>

error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](612): <NSCloudKitMirroringDelegate: 0x60000377c000> - resetting internal state after error: <CKError 0x60000064adf0: "Service Unavailable" (6/NSCocoaErrorDomain:4099); "Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error."; Retry after 5.0 seconds>

我的报错日志和 Apple 开发者论坛上提到的这个案例完全一致 SwiftData iCloud sync breaks after disabling and re-enabling iCloud

不过他的解决办法不是上面提到的 CloudKit.framework 而是 com.apple.developer.icloud-container-environment 这个属性写的 Development 导致上线后出现了问题,改成 Production 就好了。我感觉这个配置还挺好用的,我就直接固定成 Production 了,这样开发环境也直接连接线上数据库,反正都是连接我个人的 iCloud 不影响其他用户的数据。

该属性的 Apple 文档地址

🔲 ☆

我和 Apple 开发的缘分

Apple 开发者会员 🍎

今年 1 月 1 日的时候我又开通了 Apple 开发一年的会员,本来想 1 月 1 日注册,这个日子很好记,同时也代表一个新的开始,没想到因为时差问题,Apple 是从 12 月 31 开始计算的。

哈哈哈,生活真是处处充满惊喜和意外。不过这也是个很好记的日子,寓意是辞旧迎新?🧨

上面说了「又」。是的,这不是我第一次缴费了,我之前有一年也买了一年开发者会员想开发 App,但是一年什么都没干,白交了一年的学费,属实冲动消费了。

其实没有开发者会员也是能开发 App 的,只是一些功能比如 iCloud 同步,自己开发的 App 用不了,也没法上架。

⚠️ 所以我建议大家先把自己的 App 开发出来,等要上架了,再开通发者会员也来得及,不要冲动消费。

产出

第一年的会员没有产出,那今年那?

可能是 12 月 31 日的寓意起作用了,挥手告别过去,我今年开发过 4 款 App 📱。

两款上架了,上架的都盈利了。一款待上架阶段,一款算是调研阶段。

其中一款,收益还可以,每天能卖出去 1-3 份,让我对独立开发这个方向看到一点希望 💰。

缘分从何谈起?💗

回到主题,聊一聊缘分,大四快毕业的时候,我们学校和校外的 IT 培训班合作,让我们去培训班免费体验 7 天,能选的培训班都在一线城市,还管吃管住,这不就是免费的 7 天旅行吗?大家都相当开心 😆。

我选的北京的一个培训班,当时培训班有两个方向,一个 iOS 开发,一个前端开发,我当时前端比较熟悉,不假思索的选前端,但是选前端的人太多了,名额满了,我就只能去 iOS 班了,刚进班里,老师说又有了一个前端开发的名额,谁想去,我第一个举手了,但是还有一位女生也想去,老师说女士优先,就让这位女同学去了前端班。

我就这样留在了 iOS 班。iOS 班开发 App 是没有手机真机的,只有一台很卡入门级别配置还很老的 Mac mini,外接的显示器也很糊,就这我也是第一次接触 macOS 呀。

老师讲完课让我们根据讲的内容自由发挥,最后他选出做的最好的前三名的小组,前三名是有奖品的。

老师讲的内容就是怎么做一个 iOS 的登录页面,我很快就做出来了,然后又加了背景图、按钮颜色等自由发挥的内容,老师看到我作业后说,这个是我钦点的第一名了。因为我做的是最快、最完整的,还有自由发挥的额外内容。

很意外,就这么拿了一个一等奖 🏆。

可能因为是体验课,大部分同学都没认真听,也没把这个奖品当回事,我做了,就这样拿了一个一等奖。

奖品是一个程序员风格的双肩包 🎒,大约 200 元左右。质量很好,我背了挺长时间。

我们是三个人一组,一等奖是我们小组三个人都有这个包。我们组有一位同学,一直在睡觉,他很惊讶,说自己睡了一觉起来我们组得了一等奖,白得一个包。哈哈哈哈。

这就是我和 Apple 开发最早的缘分 💗。

那次培训班之旅,洗脑还是挺成功的,回学校的火车上,还是有不少人对培训老师讲的培训前景,以及这个行业的高薪心动了,甚至有人都不想考研了,打算直接去培训。

不过回到学校后不少人也冷静下来了,该考研考研,该实习实习。

毕业后我并没有做 iOS 开发,因为没有这个基础,我没有 iOS 开发的设备,对一个普通学生来说,这些设备很昂贵。其次周围也没有这样的氛围,我周围的人都在做前端或者后端,我继续从事了我已经很熟悉的前端开发。

实习的时候我分期买了第一部 iPhone,后买又买了 iPad,感觉苹果的设备很精致,系统设计交互很特别。

再后来自己有收入了,也买了不少的 Apple 设备,当我同时拥有 Mac 和 iPhone 的时候我有过为什么不试试 Apple 开发的想法? 💡

最好的时刻

至今我从事前端开发 7 年多了,兜兜转转,我又开始接触 Apple 生态的开发了。想法落地,我开始尝试 iOS 和 macOS 开发,可能一切都是最好的安排。今年有了 AI 的加持,写个 App 比以前更容易了。

奇妙 🍀

兜兜转转,我又回到这条道路上。又开始了 Apple 生态的软件开发,我觉的很奇妙。

🔲 ☆

为什么要写博客

  1. 🎁 分享欲。和大家分享有效的解决方案,分享生活和编程中遇到的事情,也避免别人踩坑。
  2. 🤩 成就感。有自然的浏览量、帮助到了其他人、有人订阅我博客,都很有成就感。
  3. 🗳️ 整理。本地笔记合并整理后,发布到博客上,我会把本地的删除掉,一个笔记整理的过程。
  4. 🔍 搜索。同样的问题下次再遇到,我会优先在自己博客内搜索。
  5. 🧶 练习表达。写博客是一个思考和练习表达的过程。
🔲 ☆

macOS 升级 beta 系统后,如何打开原来版本的 XCode

背景

每年开发者大会之后,我都提前安装 beta 版本的 macOS 系统,体验新特性。

但是升级后,系统内的 Xcode 也会变成 Beta 版本,原来的 Xcode 就会覆盖一个禁用的标志,无法打开了。

Xcode Beta 提交新版本无法上架到 App Store,日常开发还是需要使用原来的 Xcode。

本文记录一下,如何打开原来的 Xcode

方法

open /Applications/Xcode.app/Contents/MacOS/Xcode 

xcode-select -p

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

分享 😆

同时和大家分享一个开源的 Xcode 安装软件 Xcodes,可以下载安装任意版本的 Xcode,很方便。

官网

Xcodes Github

🔲 ☆

如何管理多个 git 账号

如果你有多个 git 账户,有没有遇到过提交代码的用户信息混乱的情况? 如果一劳永逸的解决这个问题那?

分目录配置 git 用户信息

git 可以设置全局的用户信息,然后可以再单独为每个仓库设置用户信息。如果忘记了给项目重置用户信息,可能就会发生上面的问题 — 提交时的邮箱和用户名错了。😓

如果你喜欢用不同的目录区分个人和公司的项目,可以使用下面的方法配置自己的 git 用户信息。😀

比如,你把公司的项目都放在了 Company 目录下,个人的项目都放在了 Personal 目录下下面。

那你可以这么做:

Company 下新建一个 .gitconfig_include 文件,配置你想在该目录下给所有 git 仓库设置的用户信息。

[user]
name = 用户名
email = 邮箱

然后在 ~/.gitconfig 内添加

[includeIf "gitdir:~/Company/"]
path = ~/Company/.gitconfig_include

⚠️ 复制提醒。Company 要替换成你自己的目录

这样,之后 Company 目录下的 git 仓库都会使用对应的 git 用户信息。💪

这是 git 的一个用法,感兴趣的同学可以看这里 conditional includes

如何在一台电脑上配置多个 ssh 密钥

上面解决的问题是只有一个 ssh 密钥,但是对应公司和个人不同的 git 账号信息。

还有一种情况是,一台电脑上有多个人的 ssh 密钥,比如我和小秋都是程序员,有时候一台电脑会同时配置我们俩的 ssh 密钥,分别对应不同的 git 账号。

除了上面要分目录,还需要设置不同的 ssh 密钥和 git 账号的关系。

可以在 ~/.ssh 目录再新建一套密钥或者也可以复制已有的到该目录,起不同的名字,比如下面这样是两套 ssh

id_rsa
id_rsa.pub

id_rsa_other
id_rsa_other.pub

然后在 ~/.ssh/config 配置文件分别指定不同的账号使用不同的 ssh 密钥:

# 默认账户
Host github.com
HostName github.com
User defaultUser // 这里改成自己的用户名
IdentityFile ~/.ssh/id_rsa

# 另一个账户
Host github.com-user1
HostName github.com
User otherUser // 这里改成自己的用户名
IdentityFile ~/.ssh/id_rsa_other

克隆仓库的时候可以改成这种格式:

git clone git@github.com-user1:otherUser/repo_name.git

参考

Set git config values for all child folders

🔲 ☆

写给自己的JavaScript系列之一步步写个 JavaScript 解释器

为了更好的理解 JavaScript 的各种语言特性,更好的掌握 JavaScript 的底层原理 😄。我想动手写一个 JavaScript 版的 JavaScript 解释器。简单来说,就是能够运行 JavaScript(以下简称 js ) 代码。我们平时写的 js 代码一般由浏览器或者 Nodejs 的解释器来解释并执行,本文的目标就是写一个能够解释 js 代码的解释器 🚀。

写一个解释器的工程,想一想就感觉好大。咱们可以拆解一下,一步步来。每实现一个小目标都会很有成就感,不至于望而却步 🐶。

第一阶段的目标

✅ 1、首先实现打印值

const x = 'Hello World!';
console.log(x);

2、然后实现加减乘除四则运算

const x = 1, y = 2;
console.log(x + y);

3、然后我们实现函数

function add(x, y) {
return x + y;
}
console.log(add(1,2));

4、趁热打铁实现闭包。

实现闭包其实我想写一个 js 解释器的初始动机,我本来在写一篇解释什么是 js 闭包的文章,但是我写着写着感觉有点无聊,没什么意思,就想实现个 js 解释器。 从另一个角度理解这个问题。

6、实现 if 条件表达式

7、实现 for 循环

8、打包 js 可以在其他文件内使用

将执行结果输出到外部

上面是我的玩具计划。

😌 ———————— 这是一条分割线 ———————- 😌

下面是我的开发过程。

参考资料

我找到的一些用 js 实现 js 解释器的文章:

和上面文章相关的 Github 上的 JavaScript 解释器源码

前三个是 TypeScript 项目,最后一个是 JavaScript 项目

热身

JavaScript 代码转化成抽象语法树(AST)这一步,直接用现成的库实现。这里我们使用 acorn 库。这是一个 js 的解析器,可以将 js 代码解析成 AST,Babel 最开始也使用了 acorn

如果你想了解整个编译器的开发(包括 AST 的生成),可以看这个项目微型编译器

抽象语法树(AST)

大体解释一下。🐵

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有两个分支的节点来表示。 – 维基百科

AST 的作用:

  • 代码风格检测(eslint等)
  • 代码的格式化,自动补全
  • 代码高亮
  • 代码错误检查
  • 代码的混淆压缩
  • 转换代码的工具。如 webpack,rollup。各种代码规范之间的转换,TypeScript JSX 等转换为原生 js

实际项目中对 AST 的应用:

我在网上找到的三个案例:🌏

分析统计微信小程序代码使用的 api

支付宝和微信小程序的代码转换

美团的模块依赖关系检测工具

正文

1、使用 acorn 将 js 转换成 ast 抽象语法树

const { Parser } = require('acorn');

class Runjs {
constructor(code = '') {
this.ast = Parser.parse(code);
}
run() {
console.log(this.ast);
}
}

module.exports = Runjs;

new Runjs(`
let x = 'Hello World!';
console.log(x);
`).run();

我们可以使用这个工具看一下,解析出来的抽象语法树。😉

Esprima 在线工具

ast

2、解析出来的 AST 每个节点都有个 type 属性,要做一个节点遍历器,处理每一个节点。

有哪些节点那?可以从这里查看

let x = 'Hello World!';
console.log(x);

⬇️ 上面的代码生成的 AST 如下:

{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "x"
},
"init": {
"type": "Literal",
"value": "Hello World!"
}
}
],
"kind": "const"
},
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "Identifier",
"name": "console"
},
"property": {
"type": "Identifier",
"name": "log"
}
},
"arguments": [
{
"type": "Identifier",
"name": "x"
}
]
}
}
]
}

如何实现节点遍历器?

先定义一个节点控制器 nodeHandler,分别处理每一个节点

下面是上面用到的节点类型:

const nodeHandler = {
Program(nodeIterator) {
/*处理 Program 类型的代码 */
},
VariableDeclaration(nodeIterator) {
/*处理 VariableDeclaration 类型的代码 */
},
Identifier(nodeIterator) {},
Literal(nodeIterator) {},
ExpressionStatement(nodeIterator) {},
CallExpression(nodeIterator) {},
MemberExpression(nodeIterator) {},
};

然后实现一个节点遍历器 🆗

class NodeIterator {
constructor(node) {
this.node = node;
this.nodeHandler = nodeHandler;
}

traverse(node) {
const nodeIterator = new NodeIterator(node);
const parse = this.nodeHandler[node.type];
if (!parse) {
throw new Error(`canjs: Unknown node type "${node.type}".`);
}
return parse(nodeIterator);
}
}

(未完待续~)🎈

🔲 ☆

JavaScript 文件的异步加载 async 和 defer

背景

在 webpack 等工具的照料下,我们忘记了很多的东西,比如说 JavaScript 文件的异步加载,异步执行等等内容。唯一能记起是关于 script 的优化可能是把 script 标签放到页面底部。

某次你看到 ‘‘ 这样的引入语句,请问这 async 到底是什么意义呢?

任务

1、学习 async 的作用,使用限制等。

2、有没有其他类似的属性呢?

技能

性能优化

开始

聊 JavaScript 文件的异步加载之前,我们先来看一段 HTML 文档的加载流程。

<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://a.com/a.js">
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
这里有很多的 HTML 标签和内容
</body>
</html>

对于上面的 HTML 文档,浏览器的解析过程如下:

1、浏览器一边下载 HTML 网页,一边开始解析(并不是下载完之后,才开始开始解析)。

2、解析过程中,浏览器发现 <script> 标签就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。

3、如果 <script> 标签引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。

4、JavaScript 引擎执行完毕,控制权交还渲染引擎,继续解析 HTML 网页。

加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后再继续渲染。

这就是为什么我们一般建议把 <script> 标签放在 </body> 之前,而不是放在 <head></head> 中,因为加载 <script> 中的脚本并执行会阻塞 HTML 页面的渲染,当 JavaScript 文件很大的时候,甚至会长时间出现白屏。

有没有什么办法可以优化 JavaScript 文件的加载执行,提高页面加载的效率?

解读 async 和 defer 属性

翻阅 HTML5 的文档 就会发现,<script> 还有 asyncdefer 两个和脚本加载执行有关的属性。

async - Execute script in parallel //并行执行脚本

defer - Defer script execution // 延迟执行脚本

HTML4.1是这么解释 defer 的:

When set, this boolean attribute provides a hint to the user agent that the script is not going to generate any document content (e.g., no “document.write” in javascript) and thus, the user agent can continue parsing and rendering.

就是说浏览器在遇到设置了 defer 的 <script> 时,加载了 JavaScript 文件后,不会立即执行,不会阻塞浏览器解析 HTML,而且如果设置了 defer 不要在 JavaScript 脚本里写 document.write

HTML5.1这么解释 defer 和 async 的:

此处省略将近 1500 字的英文引用,感兴趣点击的 HTML5.1 文档。

对省略的原文总结如下:

1、defer 和 async 只对外部加载的脚本有效果,<script> 包含的 JavaScript 代码块无效。

2、必须设置了 src 属性,不然 defer 和 async 也无效。

3、defer 和 async 这两个值是布尔类型的。

4、如果设置了 async 属性,会并行加载脚本文件并执行,下载时不会阻塞 HTML 的解析,但是脚本执行的时候会阻塞 HTML 的解析。如果没有设置 async 属性,但是设置了 defer 属性,也会并行加载脚本文件,但是会等到页面完成解析再去执行。如果这两个属性都没有设置,会阻塞页面解析,加载并执行脚本文件。三种方式的对比图如下:

图片来源:www.w3c.org图片来源:www.w3c.org

HTML5.3的文档把第四条补充了一下。。。

1、defer 对 module 脚本是无效的,但是 async 是有效的

2、如果同时设置了 defer 和 async 为 true, 以 defer 为准

小总结

通过给 <script> 标签设置 defer 属性,将脚本文件设置为延迟加载,当浏览器遇到带有 defer 属性的 <script> 标签时,会再开启一个线程去下载 JavaScript 文件,同时继续解析 HTML 文档,等 HTML 全部解析完毕 DOM 加载完成之后(也就是DOMContentLoaded 事件之后 onload 事件之前),再去执行加载好的 JavaScript 文件。多个js文件的执行顺序就是它们在页面中出现的顺序。

async 属性和 defer 属性类似,也是会开启一个线程去下载 JavaScript 文件,但和defer 不同的是,它会在下载完成后立刻执行,而不是会等到 DOM 加载完成之后再执行,所以还是有可能会造成阻塞。对多个带有 async 的 JavaScript 文件,它不能像defer 那样保证按顺序执行,它是哪个 JavaScript 文件先下载完就先执行哪个。

使用

什么时候用 defer,什么时候用 async 呢?

一般来说,两者之间的选择则是看脚本之间是否有依赖关系,有依赖的话应当要保证执行顺序,应当使用 defer 没有依赖的话使用 async。要注意的是两者都不应该使用 document.write,这个导致整个页面被清除。

模块化的代码、测试代码或者监听代码使用 async。

这两个值是布尔类型的,在 HTML 里面只写属性名即可。

<script src="https://a.com/a.js" async>

如果不考虑兼容 IE 浏览器,完全可以将 <script> 放在 <head> 里,同时守设置 defer 属性,这样 HTML 解析的时候,可以并行下载 JavaScript 脚本,等 HTML 页面解析完,JavaScript 脚本在开始执行,充分利用资源。比如查看 gitlab 页面源码。

兼容性

defer 是 HTML4 就有的属性,而 async 是 HTML5 新加入的属性。defer 出现的早,IE6 及以上的浏览器基本都支持,可以放心用。 async IE10 就开始支持了,移动端的支持比较好。不考虑 IE 的话,两个属性都可以放心使用。

兼容性可以点击这里查看caniuse

测试

如果同时设置了 defer 和 async 为 true,浏览器会怎么执行?

我们做个小实验。

新建一个文件夹,文件结构如下:

.
├── 1.js
├── 2.js
└── index.html

1.js 如下:

alert(1);

2.js 如下

alert(2);

index.html 的内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script defer async src="./1.js"></script>
<script async defer src="./2.js"></script>
<title>测试 async 和 defer </title>
</head>
<body>

<script>
alert(3);
</script>
</body>
</html>

设置两个 js 是为了排除 async 和 defer 先后顺序的影响。

FireFox、Chrome、Safari、三个浏览器的执行结构都是 3、1、2,说明同时设置了 defer 和 async 为 true, 以 defer 为准。

扩展阅读

async vs defer attributes

参考

网页性能优化之异步加载js文件 - 掘金

HTML4.1

HTML5.1

HTML5.3

🔲 ☆

我的技术书单

🙉 是左耳朵耗子(陈皓) 推荐的书籍
📖 是在读的书籍
📕 是已读的书籍

专业基础

算法

算法图解 🙉 📖

算法 🙉

算法导论 🙉

编程珠玑 🙉

数据结构与算法分析 🙉

基础

数据库系统概念 🙉

现代操作系统 🙉

计算机网络 🙉

计算机程序的构造和解释 🙉

编译原理 🙉

深入理解计算机系统 🙉

Wireshark 数据包分析实战 🙉

Unix 高级环境编程 🙉

Unix 网络编程 第 1 卷 套接口 API 、第 2 卷 进程间通信 🙉

TCP/IP 详解 卷 I 协议 🙉

如果上面的三本看不懂,可以先看是下面的三本

Linux C 编程一站式学习 🙉

TCP/IP 网络编程 🙉

图解 TCP/IP 🙉

HTTP 权威指南 🙉 📕

The TCP/IP Guide 🙉

程序员修养

重构 改善既有代码的设计 🙉 📕

修改代码的艺术 🙉

代码整洁之道 🙉 📕

程序员的职业素养 🙉

程序员的思维修炼 📕

前端开发

CSS 类

CSS世界 📖

CSS实战手册(第四版)8.4 📕

CSS权威指南(第三版)8.5

精通CSS(第2版)8.7

JavaScript 类

JavaScript DOM 编程艺术(第二版)

JavsScript高级程序设计

编写可维护的JavaScript

高性能网站建设指南

深入浅出 React 和 Redux 📖

你不知道的JavaScript 📖

ECMAScript 6入门(全彩)

JavaScript语言精粹

JavaScript权威指南

高性能JavaScript

JavaScript设计模式

Effective JavaScript:编写高质量JavaScript代码的68个有
效方法

JavaScript框架设计

UI交互

写给大家看的设计书 📖

30天学会绘画

文字设计的原理

编程语言

七周七语言

Github 上的一份书单:https://github.com/guanpengchn/awesome-books

🔲 ☆

新手到专家的五个阶段

《程序员的思维修炼:开发认知潜能的九堂课 (图灵程序设计丛书)》读书笔记

德雷福斯模型的5个阶段

有效的使用德雷福斯模型

  • 需要一个明确定义的任务。 ⏱
  • 任务需要有适当难度——有挑战性但可行。⏰
  • 任务环境可以提供大量反馈,以便于你采取行动。🛠
  • 提供重复犯错和纠正错误的机会。稳步做这种实践十年,你就会达到目标。💯
🔲 ☆

关于转载

最近,发现自己写的博文,被别人转载了。有商业性质的网站、有个人博主,也有各种爬虫自动采集收录。

比如这篇 短网址(short URL)系统的原理及其实现, 我发现这篇文章在谷歌和百度搜索的时候,前5、6页搜索结果都找不到我自己的原文地址😭,只有必应第一条搜索结果是我自己的博客 😂

我不希望文章被复制粘贴转载 ❌

  • 我的博客是经常更新的。 🚀 我的博文是按照更新时间排序的,在编程技术领域以及社会这个大家庭中,我还很年轻,写的东西难免会有错误,或者认知上的不成熟,一旦我发现有错误或者内容补充,我会不定期的更新文章内容。但是复制粘贴转载的文章,并不会随着我更新而更新,错误的内容会一直保留,给之后的阅读者留下隐患。
  • 我不能更好的收到反馈。 我发现很多转载的文章下面,有对我文章内容的疑问和勘误,转载文章的人,并不会去解释这些疑问,而我作为文章的原创作者,也收不到别人指正的错误。对文章的阅读者和作者的我,都没有很好的反馈。
  • 阅读效果不佳。 最让我受不了的是,我在写作的时候使用了 Markdown 的语法画的流程图很多网站不支持显示,显示的全是代码。还有我用的表情 🐻,他们也显示不出来。。。。

我希望的转载方式 ✅

分享链接转载,指向我的博文。 我觉的这是最好的转载方式,互赢。

如果, 非要复制粘贴的转载,请联系我 获得授权,未经许可禁止转载。

我会在文章的开头注明授权给了谁。我文章更新的时候,也通知转载文章的地方更新。

邮箱地址:hufy78#gmail.com hufy3651#foxmail.com # 换成 @

关于原创自己总结的方法

1、有自己的文章风格。这个好像很难。。。

2、有代码实例,指向自己的网站、Github

3、图片截图包含自己的博客域名目录等

4、截图带上自己的水印

5、每篇文章后面带上转载声明

6、文章内容示例含有自己的标识

使用别人的原创

换位思考,以后我使用别人的图片和摘选的内容时,我会带上来源地址和作者署名,尊重原创。💪

🔲 ☆

linux 服务器初始化配置流程

819542712-5a291b0bb9615_articlex819542712-5a291b0bb9615_articlex

开发 web 应用的时候,经常需要配置服务器。我在阮一峰老师的 Linux服务器的初步配置流程 的基础上,整理了这篇笔记。节约以后配置服务器的时间。

修改 root 密码

如果服务器的默认账号是 root

root 账户默认没有密码 安全起见 先初始化一个

passwd

腾讯云服务器

默认账户是 ubuntu,初始密码是自己设置的。忘记的话可以重置密码。

修改 root 密码

sudo passwd root

切换用户

su ubuntu

创建 Linux 管理员账户

🐧 使用 Linux 服务器的时候,尽量不要使用 root 账号,处理日常操作,我们新建一个管理员账号。

首先,添加一个用户组(这里我自定义的 admin)。

addgroup admin

然后,添加一个新用户(假定为 www)。

useradd -d /home/www -s /bin/bash -m www

上面命令中,参数 d 指定用户的主目录,参数 s 指定用户的 shell,参数 m 表示如果该目录不存在,则创建该目录。

接着,设置新用户的密码。

passwd www

将新用户(www)添加到用户组(admin)。

usermod -a -G admin www

接着,为新用户设定sudo权限。

sudo vi /etc/sudoers

找到下面这一行。

root    ALL=(ALL:ALL) ALL

在这一行的下面,再添加一行。

root    ALL=(ALL:ALL) ALL
www ALL=(ALL:ALL) ALL

最后,先退出 root 用户登录,再用新用户的身份登录。

配置 SSH 服务

把自己电脑的 ssh 公钥,保存到服务器的 ~/.ssh/authorized_keys 文件中

直接使用下面的命令

// 默认端口 22
ssh-copy-id -i ~/.ssh/id_rsa.pub root@123.456.78

// 带端口 25000
ssh-copy-id -i ~/.ssh/id_rsa.pub -p 25000 root@123.456.78

本地电脑配置 ssh config

修改 ~/.ssh/config 文件

// Host 字段后面的名称自定义
Host day-root
hostname 123.456.78
port 25000
user root

Host day
hostname 123.456.78
port 25000
user www

之后 ssh day 无须输入密码,就可以登入服务器了。

然后,进入服务器,编辑SSH配置文件/etc/ssh/sshd_config。

sudo cp /etc/ssh/sshd_config ~     (备份,复原时使用)
sudo vi /etc/ssh/sshd_config

在配置文件中,将 SSH 的默认端口 22 改掉。假设使用 25000

Port 25000

如果修改了端口,记的也修改:

  • 本地的 ssh 配置 ~/.ssh/config 内的端口
  • 云服务器的防火墙 ssh 的端口设置

然后,检查几个设置是否设成下面这样,确保去除前面的#号。

选项含义
Protocol 2ssh 协议使用新版的
PermitRootLogin no不允许 root 登录
PermitEmptyPasswords no不允许空密码登录
PasswordAuthentication no使用密码授权登录
GSSAPIAuthentication no加快连接
PubkeyAuthentication yes允许公钥认证
UseDNS no禁用DNS反向解析 会加快速度
SyslogFacility AUTHPRIV记录用户登录信息

上面主要是禁止 root 用户登录,以及禁止用密码方式登录。

保存后,退出文件编辑。

接着,改变authorized_keys文件的权限。

sudo chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh/

重启 SSHD

sudo service ssh restart

或者

sudo /etc/init.d/ssh restart

如果重启失败,报错如下:

root@VM-24-5-ubuntu:/home/lighthouse# sudo service ssh restart
Job for ssh.service failed because the control process exited with error code.
See "systemctl status ssh.service" and "journalctl -xeu ssh.service" for details.
root@VM-24-5-ubuntu:/home/lighthouse# systemctl status ssh.service
× ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Sun 2022-11-13 15:44:49 CST; 5s ago
Docs: man:sshd(8)
man:sshd_config(5)
Process: 9511 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=255/EXCEPTION)
CPU: 6ms

Nov 13 15:44:49 VM-24-5-ubuntu systemd[1]: ssh.service: Scheduled restart job, restart counter is at 5.
Nov 13 15:44:49 VM-24-5-ubuntu systemd[1]: Stopped OpenBSD Secure Shell server.
Nov 13 15:44:49 VM-24-5-ubuntu systemd[1]: ssh.service: Start request repeated too quickly.
Nov 13 15:44:49 VM-24-5-ubuntu systemd[1]: ssh.service: Failed with result 'exit-code'.
Nov 13 15:44:49 VM-24-5-ubuntu systemd[1]: Failed to start OpenBSD Secure Shell server.

大概率是配置文件修改错了,可以使用以下命令检查配置错误。

/usr/sbin/sshd -T

根据提示进行修改。

运行环境配置

检查服务器的区域设置。

locale

如果结果不是 en_US.UTF-8,建议都设成它。

sudo locale-gen en_US en_US.UTF-8 en_CA.UTF-8
sudo dpkg-reconfigure locales

然后,更新软件

sudo apt-get update
sudo apt-get upgrade

最后,再根据需要,做一些安全设置,比如搭建防火墙,关闭 HTTPHTTPsSSH 以外的端口,详细可参考这篇 《Securing a Linux Server》

设置时区

使用 tzselect 命令选择需要的时区。

tzselect

设置完后,命令行会提示我们将时区的配置文件添加到 .profile

TZ='Asia/Hong_Kong'; export TZ

执行完后,重新登录系统或者刷新 ~/.bashrc 文件使其生效

source ~/.bashrc

更改 Linux 整个系统范围的时区可以使用如下命令:

ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

现在使用 date 命令查看一下时间。

特别番

阿里云服务器

阿里云服务器可以在控制台设置安全组规则。

什么是安全组

简单点,给大家举个栗子🌰,我部署了一个 MongoDB 的数据库,我怕别人黑我数据库,我就可以在安全组的规则里设置 公网入方向 拒绝访问 MongoDB 使用的端口 27017。只允许本地 locahost 访问,禁止公网访问。

下面是教科书版具体解释

阿里云产品介绍 请看这里 安全组

安全组是一种虚拟防火墙,具备状态检测包过滤功能。安全组用于设置单台或多台云服务器的网络访问控制,它是重要的网络安全隔离手段,用于在云端划分安全域。

安全组是一个逻辑上的分组,这个分组是由同一个地域(Region)内具有相同安全保护需求并相互信任的实例组成。每个实例至少属于一个安全组,在创建的时候就需要指定。同一安全组内的实例之间网络互通,不同安全组的实例之间默认内网不通。可以授权两个安全组之间互访。

参考

Linux服务器的初步配置流程

❌