跳到主要内容

我对区块链学习过程的记录

· 阅读需 5 分钟

从抽象到具体,从为什么创立区块链,到技术层面。一个掺杂我个人思考的记录。

自由世界人民对自由更进一步的追求,用技术天然的可靠性替代对大机构的信任。

一开始,区块链第一个实现形式是比特币,创始人希望它成为一种货币,交易货币产生的账单,在区块链中存储,谁能获得记账权,谁能获得比特币奖励。区块链的数据存在于每个节点的电脑中(节点可以是你我他这样的普通人)。挖矿就是对记账权的争夺,谁算力强解出难题,谁能获得奖励。整个世界历史上产生的所有数据都存在一台普通的家用电脑也许不可思议,但是区块链只存储数据的摘要,比特币区块链至今总大小只有几百 G。另外,对于个人用户来说,如果不从事挖矿,也不需要下载全部数据。个人用户可以进行加密货币交易,或者使用智能合约,而挖矿的任务交给算力强的节点。而个人只需要付给矿工少量加密货币即可。这并不违背去中心化,因为每个人参与区块链建设的机会是均等的,数据也是透明可查的。

区块链各节点之间每隔一小段时间会进行相互通信,看看是否有人篡改数据,并对异常节点做出惩罚,所以,想篡改区块链中的数据是不可能的。所以,区块链不会跑路,不会毁约。

比特币是区块链在货币上的伟大实践,后来人们发现,不只是货币,其他类型数据也可以上链。互联网经过多年发展,也变得和现实一样,形成了寡头统治的局面。国内的比如美团,滴滴等,收取高额提成。于是,以太坊出现了。以太坊相当于把世界上加入区块链的电脑组合成一台超级计算机,而以太坊就是操作系统。以太坊上可以部署智能合约,取代现在的合同(租房,打车,外卖等线上线下的合约)。以太坊的资金一直在快速增长,它是未来。

技术细节

hash 算法在区块链中的体现形式

(算法是演示网站上的,和实际的以太坊算法不一样,但总体原理类似)

现有一种哈希算法,表示为 hashvalue = f(block, nonce, data).其中 hashvalue 是哈希值,block 指区块,nonce 是随机数,data 是要存储的数据。mine这个动作就是要求解出一个随机数,使生成的哈希值开头四位数为 0.

分布式区块链

区块链是一种类似链表的数据结构。相比于区块,区块链中的区块增加了 prev 数据段,记录了上一个区块的 hashvalue,(第一个区块的 prev 为 000...000).每修改其中一个区块的数据,那么在链上的后续区块都要重新求解,这保证了数据篡改成本极高。同时,分布式使每个用户都保存了区块,使得数据修改成为不可能。

数字签名技术

有算法负责根据私钥(密码)生成公钥。然后,私钥可以对数据进行签名,生成的签名发布到公共空间,别人可以用公钥(公开)来判断数据是否来自于你。

招聘系统面试状态爬虫

· 阅读需 6 分钟

这几天学python爬虫,又买了腾讯云服务器,找个项目练练手。找到了一个需求:对招聘进度做一个定时爬取,如果有变化,微信通知我。任务可以分为两大块,一块是代码实现,一块是项目部署。

代码实现

之前手头上有几个moka的api:

https://app.mokahr.com/personal-center/editApplication/xxxxxx?orgId=zte

后来觉得官网上的会更直观:

https://app.mokahr.com/personal-center/applications?orgId=zte

带上cookie,发起get请求,对返回的json数据肉眼解析,找到面试状态字段的位置。如果状态不是‘面试’,或者脚本请求不到数据,通知我。脚本可能会因为cookie过期失效。所以在外层套try/catch进行异常处理。最后,用一个免费的消息推送网站http://wx.xtuis.cn/提供的接口,将消息推送到手机。

# -*- coding: UTF-8 -*- 
import requests
import json

## 中文乱码着实麻烦。将编码方式改为utf-8.
import sys
defaultencoding = 'utf-8'
if sys.getdefaultencoding() != defaultencoding:
reload(sys)
sys.setdefaultencoding(defaultencoding)

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
'Cookie': ''
}

url = 'https://app.mokahr.com/personal-center/applications?orgId=zte'


def sendMessage(token, mydata):
baseurl = "http://wx.xtuis.cn/"
url = baseurl + token + ".send"

data = {
"text": 'zte notification:'+mydata,
"desp": ''
}
requests.post(url, data=data)


if __name__ == '__main__':
try:
res = requests.get(url=url, headers=headers)
# fp = open('zte.json', 'w', encoding='utf-8')
print(res.json())
# json.dump(res.json(), fp, ensure_ascii=False)
app_list = res.json()[0]['candidateApps'][0]['projectApps'][0]['apps']
app = app_list[0]
status = app['stage']
print(status)
except:
status = 'error!'
print('error!')
if status != '面试':
sendMessage('', status)

项目部署

webshell可以直接拖拽上传本地编写好的py文件,很方便。上传后,设置任务调度。核心命令就是linux的crond。参数:前面五个*设置执行时间间隔,后面跟命令。依次输入:

crontab -e ##进入vim编辑
60 * * * * python ../zte.py ##注意要从根目录开始,写绝对路径。一小时执行一次。
crontab -l ##查看任务是否创建

如果py程序中有print语句用于调试等目的,可以打开mail。任务执行的打印语句会出现在mail中。

mail的翻页命令:z

b系统

b系统的招聘系统是自研的。需要登录验证。由于cookie的有效时间很短,用requests麻烦,就用selenium工具,可以和脚本a做出差距。

代码

selenium模仿人操纵浏览器的行为。所以,主要就是找到想要点击的标签。其中,在登录后,要休眠一定时间,使浏览器能加载网页。

# -*- coding:utf-8 -*-

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
import sys
import requests
defaultencoding = 'utf-8'
if sys.getdefaultencoding() != defaultencoding:
reload(sys)
sys.setdefaultencoding(defaultencoding)

def sendMessage(token, mydata):
baseurl = "http://wx.xtuis.cn/"
url = baseurl + token + ".send"

data = {
"text": '华为招聘:'+mydata,
"desp": ''
}
requests.post(url, data=data)


options = webdriver.ChromeOptions()
options.add_argument('--headless') # 确保无头
options.add_argument('--disable-gpu') # 无需要gpu加速
options.add_argument('--no-sandbox') # 无沙箱
driver = webdriver.Chrome(executable_path="./chromedriver", chrome_options=options) # 添加软链接后是不需要写路径的

if __name__ == '__main__':
try:
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 确保无头
options.add_argument('--disable-gpu') # 无需要gpu加速
options.add_argument('--no-sandbox') # 无沙箱
driver = webdriver.Chrome(executable_path="./chromedriver", chrome_options=options)
driver.get("https://uniportal.huawei.com/uniportal/?redirect=https%3A%2F%2Fcareer.huawei.com%2Freccampportal%2Flogin_index.html%3Fredirect%3Dhttps%3A%2F%2Fcareer.huawei.com%2Freccampportal%2Fportal5%2Fcampus-recruitment.html%3Fi%3D46238")


driver.find_element(By.ID, 'uid').send_keys('')
driver.find_element(By.ID, 'password').send_keys('')
driver.find_element(By.CLASS_NAME, 'login_submit_pwd_v2').click()

sleep(5)

driver.get('https://career.huawei.com/reccampportal/services/portal/portaluser/pro/getResumeLockSatus')
page = driver.page_source
code = driver.find_element(By.TAG_NAME, 'pre').text
if code != '1':
status = '可以撤回'
else:
status = '不变'
except:
status = '请求失败'

print(status)
if status == '可以撤回' or status == '请求失败':
sendMessage('jDn7yKWZELMb1AAKJnGSle8EU', status)

driver.quit()

部署

我没想到部署是更麻烦的一步。首先需要给云服务器安装chrome和chromedriver。

首先安装chrome:

[root@localhost ~]#  cd /ect/yum.repos.d/
[root@localhost yum.repos.d]# vim google-chrome.repo

vim模式编辑文件:

[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
[root@localhost yum.repos.d]# yum -y install google-chrome-stable --nogpgcheck

然后下载chromedriver,并把文件放到脚本所在文件夹:

wget https://npm.taobao.org/mirrors/chromedriver/83.0.4103.14/chromedriver_linux64.zip
unzip chromedriver_linux64.zip

最后一步,运行。因为selenium模块需要python3,centos默认的是python2.想了几种解决办法,包括把默认版本调至python3,用宝塔创建环境。但是最终选择用conda虚拟环境。主要参考的安装教程:链接

切换到conda环境,运行指令python huawei.py,发现lighthouse用户不支持chrome,故sudo python huawei.py. 报错:chromedriver找不到。首先以为是环境和用户的问题,在crontab中添加相关语句也不管用,然后我知道了,代码中的driver路径要用绝对路径。

总结

学爬虫是有强大的正向激励的。之前秋招时经常守着页面刷新查看状态,很焦虑。现在可以放心了。我还要很多想法,比如b站虚拟主播生日会舰长数爬取,并自动制作表格等。

string/array方法总结

· 阅读需 6 分钟

string、array是非常常用的数据类型。对下面的常用方法进行总结。

string

说明:str表示某一个字符串

字符串长度:str.length

查找子字符串位置:str.indexOf(substr).可以接受第二个参数作为起始检索位置。

str.search()功能和indexOf一致,但是search不能接受第二个参数。

slice(), substring(), substr(): slice(切片),三者都接收索引参数,截取子字符串。substr()第二个参数是要截取的字符串长度

replace(): 替换字符串,只替换首个。如果要全局替换,则要用正则表达式。

str.toUpperCase() 转化为大写

concat():concat(连接),和加号一样的功能。可以多加几个concat实现多个字符串拼接。

返回字符的安全方法:str.charAt()。 用 str[0] //h也能返回正确结果,但不安全。

str.split() split(分割) 根据分隔符返回数组

面试题:slice是干嘛的、splice是否会改变原数组

1. slice是来截取的
参数可以写slice(3)、slice(1,3)、slice(-3)
返回的是一个新的数组
2. splice 功能有:插入、删除、替换
返回:删除的元素
该方法会改变原数组

array.splice(index, howmany, item1, ....., itemX)

参数 描述
index 必需。整数,指定在什么位置添加/删除项目,使用负值指定从数组末尾开始的位置。
howmany 可选。要删除的项目数。如果设置为 0,则不会删除任何项目。
item1, ..., itemX 可选。要添加到数组中的新项目。

补充方法

转为字符串

x.toString():转化为字符串 String(x):全局方法,转化为字符串 num.toFixed():数字转化为指定位数的小数字符串

转为数字

parseInt(x): 全局方法,字符串->整数,直接去掉小数部分,类似于Math.floor(x) parseFloat(x): 全局方法,返回浮点数 Number(x): 全局方法,可以将字符串,日期,boolean转化为数字 +'100':一元+运算符,返回数字

其他

x.valueOf():大多数情况下返回变量自身,日期调用本方法会返回毫秒数

array方法总结

arr是某个数组

如何识别数组:因为typeof arr返回Object,所以用Array.isArray(arr)和arr instanceof Array来判断

数组转为字符串:arr.toString() 结果是逗号分隔的字符串。还可以通过arr.join('分隔符')来指定分隔符。

pop,push,shift,unshift:略

想在任意位置删除元素:arr.splice()

合并数组:arr1.concat(arr2)

返回子数组:arr.slice()

数组排序:自带接口:arr.sort() 注意点:1.默认按字符串顺序升序,也就是说数字排序会错。2.为了解决数字排序,需要传入一个参数函数。

查找最大、最小值:数组没有自带的api,需要借助Math对象。

let max = Math.max(...arr)
let min = Math.min(...arr)
注:...arr为扩展运算符

回调函数:其参数为上一步代码获得的结果

数组迭代:为数组的每一个元素执行操作

Array.forEach(callback) (callback为回调函数) forEach不会更改原数组,也不会返回任何东西,它只负责将元素交给回调函数处理。

var txt = "";
var numbers = [45, 4, 9, 16, 25];
numbers.forEach(myFunction);

function myFunction(value, index, array) { // value, index, array来自数组,其中,index, array可省略
txt = txt + value + "<br>";
}

Array.map(callback): 通过对每个数组元素执行函数来创建新数组。不改变原数组,返回新数组

var numbers1 = [45, 4, 9, 16, 25];
var numbers2 = numbers1.map(myFunction);

function myFunction(value, index, array) {
return value * 2;
}

filter() 方法创建一个包含通过测试的数组元素的新数组。

var numbers = [45, 4, 9, 16, 25];
var over18 = numbers.filter(myFunction);

function myFunction(value, index, array) {
return value > 18;
}

arr.indexOf()

其他数据类型(日期等)

创建日期:new Date(str) str可省,则为当前时间

方法:getTime():时间戳,毫秒值

Math类: Math.random() 0-1的随机数,小数点后17位

注:Array/String/Boolean可以用new的方式创建,但是没有意义且可能会带来错误,因为new出来数据类型都是对象

注:typeof的结果:

NaN 的数据类型是数字 数组的数据类型是对象 日期的数据类型是对象 null 的数据类型是 object 未定义变量的数据类型为 undefined * 未赋值的变量的数据类型也是 undefined *

跨域问题描述和解决方案

· 阅读需 3 分钟

什么是跨域

跨域是浏览器对js脚本和ajax请求做出的限制。不同源的js脚本获取对方的cookie,ajax无法发送跨域请求。

解决方案

CORS

在服务器设置字段Access-Control-Allow-Origin。当浏览器收到服务器返回的资源上,没有Access-Control-Allow-Origin字段,就会拒绝加载

jsonp

jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

问题描述:调试阶段,前端项目请求后端接口返回数据,前端项目和后端的端口不一致,产生了跨域问题。部署阶段,前端打包完毕,项目发起请求,同样有跨域问题,两种场景的解决方法不同。

下面解决问题的场景是,打包的前端文件并不在服务器的文件路径中。

调试阶段

后端可以不用设置,只要前端设置代理服务器即可。在vue根目录下的vue.config.js添加下述代码:

module.exports = ({
devServer: {
proxy: 'http://localhost:3000' //3000为后端接口的端口号
},
publicPath: './' //解决打包后的静态文件路径问题
})

跨域只存在浏览器中,服务器之间不存在跨域,所以可以通过服务器代理

部署阶段

vue根目录下建立两个配置文件:.env.development.env.production

//.env.development
VUE_APP_BASE_API='http://localhost:3000'
VUE_APP_ENV='dev'
//.env.production
VUE_APP_BASE_API='http://localhost:3000'
VUE_APP_ENV='pro'

在请求代码处,设置判定条件,若当前调试环境,则url=/home, 若为打包环境,则url为全文

let api = null;
if (process.env.VUE_APP_ENV === 'dev') {
api = '/home'
} else {
api = process.env.VUE_APP_BASE_API + '/home'
}

最后,express要设置cors允许跨域

router.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});

初学vue2踩坑记

· 阅读需 6 分钟

跟着 b 站黑马程序员的视频入门 vue,跟敲代码过程中,踩到一些坑,在这里记录一下:

  • 定义方法时,是methods而不是method
  • {{}}叫做插值表达式

  • 写计数器案例时用到了 flex,发现下面这种换行不是 flex 认可的换行,align-content无效:

<span>{{num}}</br>{{num}}</span>
  • v-bind bind,绑定的意思,就是给元素绑定属性。首先,用简写形式:[name]="content"。然后绑定类名属性class时,最简单的写法是,:class="{[name]:isActive}",也就是切换类。 v-for 有一个性能优化问题。实时绑定的数据,在数据量大的情况下,计算复杂大,所以,vue 在使用了 v-for 的标签里添加了一个 key 属性,并将其用 v-bind 绑定,属性值应该是一个不重复的值,在数据更新时,判断是否时新的 key, 然后渲染。不宜用索引值,因为在数组中间插入时,很多数据的 key 会更新。

  • v-model 双向绑定表单元素的值。那么常见的表单元素及其值有哪些呢?

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。

官网文档上的举得例子挺多,感觉这里是比较容易犯错的。

  • <input type=''>

顺便复习一下 input 表单元素。看到以下类型能想起他们作用和浏览器中的样式即可。 text/password/radio/checkbox/button


下面是做视频中的项目时遇到问题的记录

  • 如何将输入框的内容双向绑定?

原以为input需要声明一个值作为输入内容,实际上 v-model 自己就可以将输入值和data中的变量绑定。所以应该先创建data中的变量,然后绑定。

  • 悬浮在 item 时,显示删除符合的 css

原本想的是先用行内样式将删除模块设为display:none。后来发现不起效。原因是行内样式的优先级最高,<style>标签里的 css 会被覆盖。所以把上述代码放到了<style>里,成功。

  • 如何设置主体高度随条目数量增大而增大,并在超过某一限度时设置滚动

直接上代码:

.items {
max-height: 420px;
overflow: auto;
}

附上overflow中 scroll 和 auto 属性的区别:

二者都会在内容超出范围时显示滚动条,但在元素没有超出时,auto 会隐藏滚动条,scroll 依旧显示滚动条,但是禁用该滚动条。


一起记录下 vuecli 的入门学习过程。

  • babel:将 es 较新版本的规则编译成较低的,来让浏览器运行

  • npm run serve后,生成的两个地址中,有一个网络地址,可以通过其他在同一局域网下的设备访问

  • package.json中,script下包含了项目的启动方式。除了start外,都要写成npm run ...的形式。

  • 开发环境和生产环境:开发环境包含了很多库,还要.vue后缀文件,不能被浏览器解析。所以,执行 webpack(vuecli 自带)打包,npm run build命令后,变成 html\css\js 的生成环境。

  • .gitignore说明了哪些文件在 git 上传中被忽略

  • .vue文件中的<template>标签下。只能用一个 html 根标签。一个组件就包含了 html\js\css.

  • main.js和 vue 组件里的 js 代码什么关系? index.html直接执行的 js 文件只有main.js,而main.js导入了其他组件编写的 js 代码的接口。

  • 组件注册分为全局(很少用)和局部。局部组件三步走:引入+注册+使用。注册完毕后,就可以将整个组件以 html 标签的形式插入到 app.vue 中。

  • style标签里面添加scoped可以令样式只作用于当前组件,是通过为组件标签添加 hash 值属性实现的。

  • slot是什么?用于组件之间的嵌套。在组件 a 标签内部,插入组件 b 标签,并在组件 b 标签中添加 slot 属性及 id。然后在组件 a 内添加<slot>标签,name 属性是 id。即可实现精确的占位。

package.json 里面的 devDependencies 和 dependencies 分别是什么?

配置项命令描述
devDependencies--save-dev 简写 -D开发环境,管理的依赖包仅在开发阶段有效
dependencies--save 简写 -S生产环境,管理的依赖包在项目上线后依然有效

css从0开始

· 阅读需 5 分钟

前言

一直苦于 css 的内容繁多,潜意识回避。但是对于初级前端岗位,重现页面是基本功。所以,决定花几天时间,重学 css。知乎上的回答建议将 css 分为选择器、样式、盒模型、定位四个部分。那么我就按这个路线学一遍,并记录自己容易出错的地方。

选择器

id,类自不必说。

  • 分组/后代/子元素区别
h1,
h2 {
} /*分组*/
h1 h2 {
} /*后代*/
h1 > h2 {
} /*子元素*/
  • 兄弟选择器
h1 + h2 {
} /*兄弟选择器有两种,这种是相邻兄弟选择器,只选择到了h2*/
h1 ~ h2 {
} /*通用兄弟选择器,选择h1所有的兄弟h2*/
  • 属性选择器
a[href] {
} /*模仿了其他语言对象属性的表达方式,选择有href属性的a标签*/
  • 伪类选择器
a:link {
} /*语法特点:单个冒号,后面表示标签的状态。常见的:p:first-child, nth-child(n)*/
  • 伪元素选择器
div::before{} /*指定元素的某一部分,一共5种伪元素选择器*/


选择器 例子 例子描述
::after p::after 在每个 <p> 元素之后插入内容。
::before p::before 在每个 <p> 元素之前插入内容。
::first-letter p::first-letter 选择每个 <p> 元素的首字母。
::first-line p::first-line 选择每个 <p> 元素的首行。
::selection p::selection 选择用户选择的元素部分。
  • 一个大坑
.class1.class2
同时属于两类的元素
.class1
.class2
属于class1的元素的后代里,属于class2的;
  • 总结 没什么,背住!

样式

这部分一般面试不问,使用时查找即可。

  • background 一种是颜色背景,一种是图片背景。后者需要设置一些属性,写法上应该都采取简写:
body {
background: #ffffff url("tree.png") no-repeat right top;
}
  • text 文本对齐
text-align:left\right\justify
vertical-align:top\middle\button
  • line-height 一张图说明

  • 一些坑

要实现文本的水平垂直居中对齐,可以分别设置

text-align: center;
line-height: //box height;;

盒模型

面试重点!应该牢牢掌握。

  • padding 内边距,空白透明,只能设置长度。顺便记录一下非常重要的浏览器长度单位: 可分为绝对单位和相对单位, 绝对:

    • px. 最基础的
    • in\cm\mm 计算机中都会转成 px。

    相对:

    • em: 相对于父元素字体大小的倍数, 百分号也是基于父元素
    • rem: 相对于根节点 html(16px).rem 用的更多,因为它不需要层层换算。
    • vw/vh:基于可视区域,1vw=可视区宽度的 1%
  • border 牵涉到上下左右的问题,出于简便的考虑,应该采用简写形式。

p {
border: 5px solid red; /**如果要设置某一侧边框的样式,应该写border-left: xxxx */
}
  • margin margin 和 padding 书写方式很像,但是操蛋的是 auto。auto 表示自动计算应得的空间。所以可以实现居中操作。

定位

最复杂和让人蛋疼的部分。

  • position 要想让元素移动,除了要设置 position, 还要设置 top\left 等数值

    position: relative/absolute/fixed;
    • relative 相对于其原来位置移动,不脱离文档流

    • absolute 相对于最近的有定位的祖先元素移动,脱离了文档流

    • 子绝父相:本质上是父元素不脱离文档流,而子元素可以在父元素内方便得改变位置

  • float

    float: left/right/none/inherit;

    浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。 由于浮动框不在文档的普通流中,所以文档的普通流中的块框表现得就像浮动框不存在一样。

  • flexbox flex 比 float+position 组合好用多了。

先写到这里吧,flex 另开一个新坑。

nodejs构建服务器

· 阅读需 2 分钟

从nodejs服务器想到的

服务器的定义是,监听请求,处理请求和做出响应。除了nodejs外,其他语言也有对应的web server框架,比如java的tomcat。

用下列代码实现一个简单的服务器:

var http = require('http')
var fs = require('fs')
http.createServer(function (request, response) {
fs.readFile('1.txt', (err, data)=>{
response.setHeader("Access-Control-Allow-Origin", "*")
response.end(data)
})
}).listen(3000, function () {
console.log('running')
})

用node命令执行上述代码后,就可以发送请求了。我们总说请求,可是请求究竟有哪些类型呢?网上将http请求分为get\post等8种类型,我觉得,从另一个角度,可以分为全局型和局部型。全局型是浏览器输入框改变,则会发起请求,并刷新页面。而局部型如ajax,只更新dom树几个节点的内容,不刷新页面。

全局型请求,是通过更改末尾地址实现。而上面的代码对于所有请求的响应都相同。所以,可以通过路由分发来实现不同地址响应。

通俗易懂的Promise解释

· 阅读需 6 分钟

本博客的第一篇文章,讲讲 Javascript 里的 Promise。Promise 要记住的点:

  • 写法
  • 用法

这篇文章,不谈那些状态,而是尽量接触问题的本质,谈谈 Promise 究竟解决了什么问题,在何时用。

Hello World!

· 阅读需 1 分钟

记录一下本网站的建立过程

首先,本网站是一个静态博客,基于 GitHub pages 提供的免费服务器,域名在 Namesilo 上购买,页面管理器为基于 nodejs 的 hexo, 总花费¥ 6.72/year. 建站第一天,感觉访问速度还可以。因为在国外买的域名,还不用备案,很爽,all for freedom.

日常管理上,需要发布新文章时,在 Git Bash 上执行下列流程:

    hexo new "title"
hexo g
hexo s
hexo d

如果后期网站外观固定下来,维护起来还是很省心的,nice。