卢浮宫博物馆
逛展的一天,非常有意义。
卢浮宫展出内容非常丰富,从史前文明到现代文明,时间跨度非常广。
非常值得花几个小时看一看。
逛展的一天,非常有意义。
卢浮宫展出内容非常丰富,从史前文明到现代文明,时间跨度非常广。
非常值得花几个小时看一看。
今天本来计划要游泳的,但是早上临出门时却找不到门禁卡,没有门禁卡就不能去游泳,所以只能遗憾的改变运动计划。
这次出国幸好还带了跑步的裤子和鞋子,于是换了衣服装备就出门跑步了。
距离上次跑步已经超过两个月了,今年总的跑步次数相信10个手指头数的过来,所以今天的跑量定在了5公里,先让身体适应一下。
跑到4公里的时候已经口渴到不行了,所以只能停止跑步走回来,一路在想自己真是个勇士,这么热的天,没有做防嗮,也有没有带水就出门了,手臂眼见的逐渐变红了。
晚上突发奇想,迪拜应该有马拉松比赛吧?如果能坚持跑几个月,练到以前的巅峰状态,说不定可以在迪拜跑个马,于是就上网查了迪拜马拉松。
迪拜下一次马拉松在2024年的1月7日星期日,大约还有5个月的时间,报名费150美金,现在就可以开始注册报名了。
考虑到今天才刚开始恢复跑步,所以就暂时没报名,计划先练两个月,看看练得结果如何在做打算,只要在12月10日前都可以注册报名。
到迪拜的第二天
午饭后,确定了今天要去的地方——未来博物馆
于是开始在淘宝订票,然后就出发了
终于做出了决定,要去旅游了
但是老板不批假期,只能继续搬砖
等下个月
每天在同样的时间出门
到达同一个站台
搭上8:32的地铁
在下一站,一个化着精致妆容的老妇人会上车
每个人都在看手机,听音乐,沉浸在自己的世界里
每天在同样的时间拐弯
走这条必经之路
顺便买一份早餐
回过头看,那个撑着伞的熟悉的陌生人又出现了
每个人都在赶路,匆匆的,为了美好的生活奋斗
最近在做一个功能给客户端发包。
客户端上传包到后台,选择发布的环境后,进行发布,实现版本热更。
发包功能关键的一部分是发布到不同的环境服务器, OpsEnv 结构体定义了环境服务器的信息,
其中 Addr,Port,Passport,Password,PubKey,AuthType 六个字段和发包相关。
1 | package sftp |
SFTP是用SSH封装过的FTP协议,相当于经过加密的FTP协议,功能与FTP一样,只是传输数据经过加密。
所以首先需要获得ssh连接。
newSshClient 方法提供了两种连接方式——密码和密钥。
getSftpClient 方法在获得ssh连接的基础上,获得sftp连接。
1 | package sftp |
取得sftp连接后,接下来就能使用该sftp连接执行文件上传和下载的操作。
Upload 方法提供上传文件功能,上传文件分几步实现:
下载文件与上传文件的操作类似,唯一的区别是把远程文件的内容写到本地文件。
1 | package sftp |
文件经过打包压缩,才上传到远程服务器上,因此还需要对远程文件进行解压缩。
Unzip 方法接收一个解压缩的命令参数,然后调用 runCommand 方法远程执行命令,最后返回命令的执行结果。
1 | package sftp |
1 | package main |
JavaScript对象是键值对的集合。它是一种可以包含各种数据类型的非原生的数据类型。
1 | const userDetails = { |
在 JavaScript 中使用对象时,有时你可能想要更改值或向对象添加新属性。
在某些情况下,在更新或添加新属性之前,你会希望创建一个新对象并复制或克隆原始对象的值。
例如,如果你想复制 userDetails 对象的值,然后修改 name 属性的值。此时,你会使用赋值 (=) 运算符。
1 | const newUser = userDetails; |
一切看起来都正常,但是当我们修改一下新对象,看看会发生什么?
1 | const newUser = userDetails; |
结果就是原始对象 userDetails 的值也受到了影响,因为对象是引用类型。
这意味着存储在新对象或原始对象中的任何值都指向同一个对象。
这不是你想要的。你希望将一个对象的值存储在一个新对象中,并在不影响原始对象的情况下操作新对象中的值。
在本文中,你将学习三种可用于执行此操作的方法。你还将了解深克隆和浅克隆的含义以及它们的工作原理。
如果你赶时间,以下有三种方法和它们的例子。
1 | // Spread Method |
如果你不赶时间,让我们开始吧。
扩展运算符是在 ES6 中引入的,可以将值扩展到三个点前面的对象中。
1 | // Declaring Object |
这不再被引用,这意味着更改新对象的值不会影响原始对象。
1 | // Cloning the Object with Spread Operator |
当你检查原始对象的 name 属性或整个对象中的值时,你会注意到它没有受到影响。
注意:当遇到深对象时,扩展运算符只能对对象进行浅拷贝,当你读完本文你就会明白了。
扩展运算符的替代方法是 Object.assign() 方法。你可以使用此方法将值和属性从一个或多个源对象复制到目标对象。
1 | // Declaring Object |
这不再被引用,这意味着更改新对象的值不会影响原始对象。
1 | // Cloning the Object with Object.assign() Method |
当你检查原始对象的 name 属性或整个对象中的值时,你会注意到它没有受到影响。
注意:当遇到深对象时,Object.assign() 方法只能对对象进行浅拷贝,当你读完本文你就会明白了。
最后一个方法是 JSON.parse()。你将结合JSON.stringify() 一起使用。
你可以使用它来深度克隆,但它有一些缺点。
首先,让我们看看它是如何工作的。
1 | // Declaring Object |
就像前面的方法一样,新的对象不再引用它。这意味着你可以在不影响原始对象的情况下更改新对象中的值。
1 | // Cloning the Object with JSON.parse() Method |
当你检查原始对象的 name 属性或整个对象中的值时,你会注意到它没有受到影响。
注意:此方法可用于深度克隆,但不是最佳选择,因为它不适用于function或symbol属性。
现在让我们探讨浅克隆和深度克隆,以及如何使用该JSON.parse()方法执行深度克隆。你还将了解为什么它不是最佳的选择。
到目前为止,本文使用的示例是一个只有一层的基础对象。
这意味着我们只执行了浅克隆。
但是当一个对象有多于一层时,你就需要进行深度克隆。
1 | // Shallow object 浅对象 |
注意,深对象不止一层,因为 userDetails 对象中还有另外一个对象。
一个深对象可以有任意多的层次。
注意:当你使用扩展操作符或Object.assign()方法克隆一个深对象时,更深的对象将引用。
1 | const userDetails = { |
你会注意到原始对象和新对象都会受到影响,因为当你使用扩展运算符或Object.assign()方法克隆深对象时,将引用更深的对象。
你可以使用JSON.parse()方法,一切都会正常进行。
1 | const userDetails = { |
但是这种方法有一个问题 —— 就是你可能会丢失数据。
正如以上的例子显示的那样,JSON.stringify()与数字、字符串或布尔值等原生的数据类型配合得很好。
相对应的,当遇到非原生的数据类型时,JSON.stringify()方法出现意想不到的结果。
例如,当属性的值为:Nan、Infinityto、null、undefined、Symbol、object等类型是,JSON.stringify()方法会返回一个空的键值对并跳过它。
1 | const userDetails = { |
JSON.stringify() 将不会返回值为 Symbol 和 undefined 的键值对。
1 | console.log(cloneUser); |
这意味着你需要小心。实施深克隆的最佳选择是使用 Lodash。
这样你就可以确定你的任何数据都不会丢失。
1 | const userDetails = { |
本文介绍了如何使用三种主要方法在 JavaScript 中克隆对象。
你已经了解了这些方法的工作原理,以及何时使用每种方法。
你还了解了深度克隆。
你可以阅读本文以了解为什么JSON.parse(JSON.stringify())在 JavaScript 中克隆对象是一种不好的做法。
上传图片一个非常常用的功能,使用 elementUI 的 el-upload 组件,可以方便的实现上传图片文件等,用法参照例子 用户头像上传。
如例子所示,每次使用该组件,都要写如下代码:
demo.vue
1 | <el-upload |
如果在多个地方都需要用到上传图片的功能,那么就要多次写同样的代码,显然没必要。
稍微封装一下 demo.vue 的代码,使它变成一个独立的组件,通过简单的调用,就可以实现上传图片的功能。
该组件接收一个图片地址参数,当有图片地址的时候,显示图片;
还有一个回调函数,当图片地址发生改变时,可以通知父组件。
upImg.vue
1 | <template> |
upImg.vue 组件定义 imgUrl 变量作为图片的地址,
当父组件调用 upImg.vue 组件时,传入一个图片地址参数 imageUrl ,
子组件接收并监听该参数,当参数值发生变化时,赋值给 imgUrl 变量。
当成功上传图片后,imgUrl 取得图片地址, 并且触发父组件上的 getImgUrl 事件,抛出图片地址。
父组件只要监听 getImgUrl 事件,即可访问图片地址。
如下代码即是父组件的调用过程。
index.vue
1 | <template> |
以上,通过对 el-upload 组件的封装,实现上传功能,简化了调用过程。
migrate 是一个用go写的数据库迁移工具,提供CLI和导入库两种方式,支持的数据库有MySQL,PostgreSQL,SQLite,MongoDB等。
使用migrate数据库迁移工具可以在更新的时候避免一些因手动操作导致的意外事故,提高更新流程的集成度。
MacOS或者Windows可以通过软件包下载
MacOS
1 | brew install golang-migrate |
Windows
1 | scoop install migrate |
Linux从这里 Release Downloads 下载工具包,解压即可使用。
1 | migrate create -ext sql -dir ./sit106/sql -seq create_test_table |
create命令会创建两个空sql文件,一个名字带up,表示升级文件,一个名字带down,表示回滚文件,文件名带有顺序的版本号和指定的文件名。
1 | migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' up [N] |
1 | ## 第一次执行up命令后,会自动创建 schema_migrations 表,记录迁移版本号和状态。 |
每一次执行迁移命令,会首先从 schema_migrations 表获取版本号,再从指定目录获取比当前版本号要高的迁移的文件,再执行迁移。
注意: 不加参数 N ,则更新到最新,默认不加。
1 | migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' down [N] |
注意: 不加参数 N ,则回滚到版本0。
1 | migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' force N |
当升级或者回滚的sql脚本出现错误,当前版本会被标记为脏状态,无法继续更新或者回滚。
在修正sql脚本的错误后,需要使用次命令,清除脏状态,方可以继续升级或者回滚。
1 | migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' version |
1 | migrate -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' goto N |
按顺序升级或者回滚到指定版本。
1 | migrate |
以上命令的 path 参数的写法-path ./sit106/sql
是一个简写的方式,原型写法为
1 | 仅当从文件系统加载配置的时候,可以使用简写方式。 |
1 | package migration |
从以上代码可以看出,当up命令发生错误时,需要执行down命令,但是具体down几个版本,则需要自己控制参数。
可新增一个字段pre_version,记录上一个版本号。
1 | CREATE TABLE `schema_migrations` ( |
当执行up命令前,记录当前的版本号version,执行up命令后,得到new_version,最后更新schema_migrations表。
1 | set pre_version=version,version=new_version |
当执行down命令时,首先取pre_version的值,回滚版本后,更新后执行。
1 | set version=pre_version |
这样至少可以回滚到上一个版本。
在一个多人开发的项目中,难免会遇到冲突,所以在代码审查的时候要尤其注意。
尽量让你的迁移时幂等的——连续两次运行相同的sql语句得到相同的结果,这会让迁移代码更加健壮。例如使用CREATE TABLE IF NOT EXISTS
代替CREATE TABLE
如果一次迁移中有多个命令/查询,最好把它们包在一个事务中,这样,当其中一个命令失败,我们的数据库将保持不变。
双重认证顾名思义即是在验证用户账号密码后,再进行一次验证码认证,提高登录流程的安全性。
https://www.authenticatorapi.com
参数名 | 格式 | 必需 | 描述 |
---|---|---|---|
AppName | string | 是 | AppName |
AppInfo | string | 是 | AppInfo |
SecretCode | string | 是 | SecretCode |
1 | <a title='Manually pair with GEZDGNBVGY3TQQSYLFKA' href='https://www.authenticatorapi.com'><img src='https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl=otpauth%3A%2F%2Ftotp%2FJohn%3Fsecret%3DGEZDGNBVGY3TQQSYLFKA%26issuer%3DMyApp' border=0></a> |
提取img标签里src属性的值,保存起来,不用重复请求接口
参数名 | 格式 | 必需 | 描述 |
---|---|---|---|
Pin | string | 是 | Pin |
SecretCode | string | 是 | SecretCode |
1 | False|True |
经过测试发现: