髋驱自由泳

髋部驱动适合长距离, 相对肩部驱动更省力。 抱水 肩部放松, 整个手臂的阴面(内侧,太阳照不到的那一侧)形成推水的阻力面。 推水阻力面包括大臂内侧,小臂内侧,手掌内侧。 肘部适当弯曲差不多100度。 肩部和腋下放松,但核心(腹部)收紧,身体保持直线。 手臂姿势类似爬墙, 肘部可稍高一些,体力好的时候肘部抱到耳朵位置,体力不好时候抱低一点。 动力传递 髋部滚动和打腿来联合驱动。 髋部滚动带动打腿, 打腿进一步给滚动提供支撑(通过整个大腿,小腿和脚面压水来支撑同侧髋部上抬)。 髋部上抬,进一步通过躯干传导到上肢, 带动抱水的胳膊向后甩。 动力传递的整体感觉类似上图。 在动力传递过程中: 腹部(腹直肌,腹外斜肌)保持适度紧张很关键, 否则滚动的力量就被身体的扭曲吸收了。 发力一侧的关节(抱水侧的肩关节,打腿侧的髋关节)要放松,放松才能甩出去。陆操时候,重心要完全放到不发力的一侧。 在游进中的感觉是: 髋部滚动, 同时两腿上下交换下位置完成打腿,打腿过程中腿部基本保持笔直,两腿大腿内侧轻贴以减阻。 向上滚动的髋部通过拉动背阔肌,把抱水的胳膊向后甩, 一直甩出水面后再悠到前面,再次形成入水姿势。向后甩胳膊时候,也有身体滚动导致向内侧拉抱水侧肘部的感觉。

2022年3月12日 · 1 分钟

Docker

1. Docker client通过运行在主机上的docker daemon操作image和container,registry提供image的发布和下载(类似npm和pip)。 Docker 镜像是一个特殊的只读文件系统,提供容器运行时所需的程序、库、资源、配置等文件,还包含一些为运行时准备的配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 Container是image的运行实例,是运行在宿主机上的一种特殊的进程。一个image可以有多个container。 Container是在Image基础上叠加了一个读写层,在container内部做一些写操作后,可以commit读写层生成新的image 上图省略掉了存储配置文件信息的init层。init层位于上图image和container两层之间。存放运行操作系统时候需要写的一些目录/文件。例如/etc 目录。init层commit时候不提交。 因为没必要,每次只读层运行时候都会自动生成init层内容。 1.1. commands # d d d # d d d d d d d d d d o o o o o o o o o o o o o I c c c C c c c c c c c c c c m k k k o k k k k k k k k k k a e e e n e e e e e e e e e e g r r r t r r r r r r r r r r e a p i r i p r r s r r p c a e u m m n s u u t m m o p t x l a i e n n a r t e l g r - r < $ t l a c e a - - t C ( o c h s < i d O d < c h - e I t < N o C a i l M f c T c O l < t l A c l o A k N _ c o G e a n I e T f o - E n s t N r A i n d w t k a E I l t i o I o - i R c N e a r r D s h n o E _ i e l > e e I n R n n c d l r D t a e t l _ > a I m r u o n i D e s - a n > n - 从 查 删 查 交 w m e a s r 看 除 看 互 o e r 4 m q e 本 某 有 式 r > 删 查 9 e l g 地 个 哪 运 l 除 l 看 f > i i 有 i 些 行 d c s 端 7 t s 哪 m c o 口 9 e t 些 a o 重 n - 映 6 对 r i g n 后 启 t a 射 0 于 / y m e t 台 a q 信 e d b 下 a , a 运 i ) 息 b o i 载 g 可 i 行 n 7 c n h e 以 n e e k / e 只 e r 4 e s l 写 r 删 : r h l I 除 / o D 所 d r - 的 有 o u 在 w 前 c c n 名 o 几 o k / 字 r 位 n e s 是 l t r t d a _ a d i f r i i n i t r m e l e a r e 启 c g _ 动 t e n 的 u a 容 s m 器 - e , s 可 q 以 l 拷 用 i 贝 这 t 文 个 e 件 来 的 到 a 容 d t 器 o t 内 c a 执 k c 行 e h s r 到 h 里 控 面 制 台 上 看 输 出 。 1.2. Dockerfile 是用来build image的配置文件,从一个基础image开始,运行一些列配置命令,得到另外一个image ...

2022年3月8日 · 37 分钟

单元测试,测试驱动开发

1. 简介 下图出自 Learning Curves (for different programming languages) 虽然文章调侃为主, 但也看出来作者对单元测试的态度。 对Python程序员来讲,随着经验的增长, 在掌握了单元测试之后, 个人的生产力得到一个突变(至于掌握装饰器,会自我感觉膨胀,但效率提升不明显)。 很多年前刚走出校门,还是C++年代。 有个前北电的Java大牛布道,给开发团队推荐cpp-unit,说单元测试 “可以显著提高单兵作战能力”。斗转星移,南征北战,北电的Eddie已失去联系,但他的布道显然成功了。我在不同的项目中实践过单元测试: cpp unit junit luaunit python unittest jest 虽然语言不同,但unittest思想都一样,都有setup,teardown,testcase,assert/expect,mock这些概念。 对单元测试“提高单兵作战能力”的说法,我“不能认同更多”。 2. 单元测试的好处 unittest的好处主要在两个方面。 2.1. 鼓励先设计接口 要测试驱动,就要先想怎么测,从而促进在很早期就从接口定义的角度考虑问题。也促进了模块的低耦合高内聚。 另外,还带来一个额外的好处,TDD也会沉淀出类似文档的测试用例。若干年后回顾一个软件时候,看看用例,基本也了解当时的思路了。 2.2. 跑一遍测试的成本低 测试用例的写法规范,测试框架支持方便的执行和反馈结果,这样跑一遍测试没有时间和精力的负担,随时跑。 经常跑测试,持续的集成,有问题也能早暴露。 接口稳定后,有测试用例做质量保障,做重构也方便。 3. 举个例子 假如在开发一个API服务器。 做单元测试从粗到细可以有几个不同的粒度: 从API层面,用http client模拟请求,然后assert返回的结果是否符合预期 从Handler层面,模拟http请求的header,params,body等,然后喂给Handler,看返回的结构是否符合预期 模块层面,如果Handler之下还有其它业务逻辑模块,可以针对模块接口做单元测试 以上 1, 已经可以看作系统测试(端到端测试)了。 2相对于1有一些额外的好处:一般情况下,对应一个请求的处理,除了Hanlder之外,还有一些中间件来做预处理。 对于1来说,必须把中间件的功能和Handler本身作为一个整体测试。 不够灵活。 2相对于1又有一些缺点,需要Mock。 根据所用framework不同,需要mock输入输出的数据结构。 好在一般情况下,mock都比较简单,有很多framework也有第三方做好的mock库。 例如,下面对JS express 的Handler测试: import { posthandler } from '../src/handlers/post'; import { getMockReq, getMockRes } from '@jest-mock/express'; // generate a mocked response and next function, with provided values const { res, next } = getMockRes({ }) test('check post handler returns token in JSON body', async () => { // generate a mock request with params const req = getMockReq({ params: { id: 'abc-def' }, headers:{authorization:'this is my token'} }) // provide the mock req, res, and next to assert await posthandler(req, res) expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ authorization: 'this is my token', }), ) }) 引入的第三方jest-mock/express,可以帮助来生成输入数据,检查输出数据。see? easy. ...

2022年3月6日 · 1 分钟

一个简单的API Proxy

1. 起因 在配置Directus使用钉钉扫码登录时候,发现钉钉的免密登录(OAuth 2)和RFC规范不一致。 需要做协议转换后才能和Directus正常通信。 需求比较小众,没有现成的软件,只好自己动手了。 2. 主要功能 能作为API通信的中间人, 转发客户端和API服务器之间的通信, 记录LOG,方便分析协议; 作为中间人,能修改请求的内容, 修改响应的内容;可以做协议适配,转换。 APIPROXY is a RESTFUL API proxy, monitor and adaptor. Forward RESTFUL API to another host. It’s man in the middle who can monitor and modify the header and body of the API Request & Response. Good for protocol study and adaptation. Features API proxy: forward any incoming API to remote sever and return the response back to client. API monitor: you can get detailed log of the API req and res in the log file. API adpator: modify the request and response on the fly while forwarding, including parameters, body, http headers etc. API mock server: you can add your own API for testing purpose easily. 3. 目前状态 初步实现了对钉钉OAuth2协议的RFC6749兼容封装。 目前Directus已经可以通过它的翻译支持钉钉免密登录。 ...

2022年3月5日 · 1 分钟

Typescript

Typescript学习笔记。From zero to hero。 1. Array,Tuple,Union,Enum // Basic Types let id: number = 5 //变量后面加类型,用冒号隔开 let company: string = 'Traversy Media' let isPublished: boolean = true let x: any = 'Hello' //any类型变量,可以放任何类型数据 let ids: number[] = [1, 2, 3, 4, 5] //不定长数组 区别传统静态语言的int a[4]; let arr: any[] = [1, true, 'Hello'] //any数组,可以混合各种值 // Tuple let person: [number, string, boolean] = [1, 'Brad', true] //元组:已知元素数量和类型的数组,各元素的类型不必相同。 // Tuple Array let employees: [number, string][] //每个元素是元组的数组 employee = [ [1, 'Brad'], [2, 'John'], [3, 'Jill'], ] // Union let pid: string | number //联合 /* 以下是C语言的联合,都是一个变量可以存几种不同类型数据 union data{ int n; char ch; double f; }; */ pid = '22' // Enum enum Direction1 { Up = 1, Down, Left, Right, } enum Direction2 { Up = 'Up', Down = 'Down', Left = 'Left', Right = 'Right', } 2. Map //定义 type MapType = { [id: string]: string; } //实例化 const map: MapType = {}; map['a'] = 'b'; map['c'] = 'd'; //删除 delete map['c']; //枚举 for (let i in map) { console.log(map[i]); } //得到包含所有key的数组 console.log(Object.keys(map)); //另外一种用Record的实现方式 const map: Record<string, string> = {}; map['a'] = 'b'; map['c'] = 'd'; 3. Object // Objects type User = { id: number name: string } const user: User = { id: 1, name: 'John', } // Type Assertion 类型的选择 let cid: any = 1 // let customerId = <number>cid 从any到 number 的选择 let customerId = cid as number 4. Function // Functions function addNum(x: number, y: number): number { return x + y } // Void function log(message: string | number): void { console.log(message) } 5. Interface, Class // Interfaces interface UserInterface { readonly id: number name: string age?: number } const user1: UserInterface = { id: 1, name: 'John', } interface MathFunc { (x: number, y: number): number } const add: MathFunc = (x: number, y: number): number => x + y const sub: MathFunc = (x: number, y: number): number => x - y interface PersonInterface { id: number name: string register(): string } // Classes class Person implements PersonInterface { id: number name: string constructor(id: number, name: string) { this.id = id this.name = name } register() { return `${this.name} is now registered` } } const brad = new Person(1, 'Brad Traversy') const mike = new Person(2, 'Mike Jordan') // Subclasses class Employee extends Person { position: string constructor(id: number, name: string, position: string) { super(id, name) this.position = position } } const emp = new Employee(3, 'Shawn', 'Developer') 6. Generics // Generics => C++的泛型 function getArray<T>(items: T[]): T[] { return new Array().concat(items) } let numArray = getArray<number>([1, 2, 3, 4]) let strArray = getArray<string>(['brad', 'John', 'Jill']) strArray.push(1) // Throws error 7. FAQ 7.1. type alias or interface https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces ...

2022年3月4日 · 3 分钟

Typescript中的模块

和python不同, Typescript/Javascript模块导入导出方式五花八门。 目前因为ES6已经标准化,推荐用import/export。 历史遗留的require/exports由于存在数目巨大的npm库,也还将长期共存。 1. ES6模块导入导出 1.1. 语法 三种export方式,两种import方式 export import export var; import {var} from module export {var}; import {var} from module export default var import var from module 另外可以用 import * as module_alias from module 导入模块所有export的变量到一个对象 module_alias 中。 1.2. 例子 1.2.1. testEs6Export.ts 'use strict' //导出变量 export const a = '100'; //导出方法 export const dogSay = function(){ console.log('wang wang'); } //导出方法第二种 function catSay(){ console.log('miao miao'); } export { catSay }; //export default导出 const m = 100; export default m; //export defult const m = 100;// 这里不能写这种格式。 1.2.2. index.ts import { dogSay, catSay } from './testEs6Export'; //导出了 export 方法 import m from './testEs6Export'; //导出了 export default import * as testModule from './testEs6Export'; //as 集合成对象导出 2. CommonJS 模块导入导出 2.1. 语法 模块中有module.export对象,里面存放的就是export的变量。 ...

2022年3月4日 · 1 分钟

基点 bps 是什么

bps是什么? bp(或者bps)是Basis Point的简称。 中文翻译成基点。 是一个比例, 万分之一的意思。 也就是1%的百分之一。 为什么要有这个概念 为什么不直接说万分之一,而引入一个新术语? Basis points are convenient and steady. Basis points are less ambiguous than percentages as they represent an absolute, set figure instead of a ratio. For example, a 1 percent increase on a 5 percent interest rate could be interpreted as either 5.05 percent or 6 percent. Conversely, if the rate increases by 100 basis points, the result is constant. The rate updates to 6 percent. ...

2022年3月3日 · 1 分钟

设计高性能可扩展的API服务

Directus本身是NodeJS实现的API服务器。 其能支持多大的TPS取决于数据库系统,API查询设计等多个因素。本文提供一些架构上的考虑,目的是使得Directus应用能随着业务的增长,通过增加硬件等方式,同步提高系统处理能力,在性能上具有好的扩展性。 1. 如何规划可扩展的Directus应用 Directus 对于高负载情况的处理有以下两个关注点: 横向扩展应用服务器(directus实例多部署几个) 数据库服务器采用高性能方案,例如Amazon Aurora或者CockroachDB Rijk van Zanten: That being said, I do highly recommend horizontally scaling your Directus instance if you’re planning on running it at scale. Make sure you use Redis for caches / rate limiter, and S3 or another shared file storage for the file storage. At that point, the bottleneck will become the amount of allowed connections and the overall server performance of the database. That being said, there’s a lot of database services nowadays that scale virtually endless, like Amazon Aurora or CockroachDB. ...

2022年3月2日 · 1 分钟

OAuth2 应用实践:Directus集成钉钉登录的尝试

1. 项目简介 这个小项目预期结果是让 Directus 支持使用钉钉账号来登录。 在了解OAuth2协议后(参见上一篇blog,参考资料1),已经有足够知识储备来实施。 Directus 原生支持使用GitHub登录, 所以,解决思路是先从GitHub入手。按下面步骤进行: 配置Directus使用GitHub账号登录,熟悉Directus对OAuth的标准支持功能 配置Directus使用钉钉账号登录,由于钉钉的协议实现和RFC6749/GitHub有不同,这里有可能需要见招拆招 上线Directus到服务器环境,在钉钉的PC版和手机版验证 2. 环境配置 在本地用ngrok暴漏出一个服务,来接受OAuth服务器的redirect。 ngrok http 8055 得到 https://445a-240e-47c-30b0-3b10-600e-ea25-cde5-2334.ngrok.io/ 作为外网域名来访问本机8055端口的directus。 3. Directus 使用 GitHub账号登录 按参考资料2中配置参数。以下配置中,对每一个新的GitHub授权用户,Directus在登录过程中会使用用户email自动创建一个Directus用户,并且将其角色赋值为AUTH_GITHUB_DEFAULT_ROLE_ID。 A A A A A A A A A A # # U U U U U U U U U U T T T T T T T T T T A A H H H H H H H H H H U U _ _ _ _ _ _ _ _ _ _ T T P G G G G G G G G G H H R I I I I I I I I I _ _ O T T T T T T T T T G G V H H H H H H H H H I I I U U U U U U U U U T T D B B B B B B B B B H H E _ _ _ _ _ _ _ _ _ U U R D C C A A P A D I B B S R L L U C R L E C _ _ = I I I T C O L F O E I " V E E H E F O A N M D g E N N O S I W U = A E i R T T R S L _ L " I N t = _ _ I _ E P T g L T h " I S Z U _ U _ i _ I u o D E E R U B R t K F b a = C _ L R L O h E I " u " R U = L I L u Y E t 7 E R " = C E b = R h e T L h " _ _ " " _ 2 . = = t h R I e K " . " " t t E D m E . d h p t G = a Y . 5 t s p I " i = a . t : s S 0 l " e . p / : T f " e " . s / / R 5 m . : g / A f a . i a T 1 i . t p I b l . g h i O 5 " d i u . N a 9 t b g = - " h . i " 1 u c t t 0 b o h r 6 . m u u f c b e - l . " 4 m c e g o c l i m 7 o n - g u a i o s 8 n a e b / u r 8 o t " - a h 6 u / f t a 1 h c 1 / c 4 a e 8 u s 2 t s a h _ 0 o t 6 r o 0 i k f z e " e n " " 重启Directus让配置生效后,可以看到登录界面的GitHub选项。 ...

2022年2月27日 · 8 分钟

OAuth2 协议解析:以GitHub和钉钉为例

1. 原理 假设有一个APP,要我使用GitHub授权登录。 在这个登录场景中: 我作为数据的所有者告诉系统(GitHub),同意授权第三方应用(App)进入系统,获取某些数据(我的ID,头像等)。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用(APP)访问数据使用。 token是短期有效的,我可以随时通过GitHub把这个Token注销,从而使得APP不再能访问我的ID/头像等信息。 这里面有四个角色:用户,应用,系统,资源。 用户是资源所有者,应用是资源的使用者,系统是资源管理者。 现实生活中, 应用和系统各自有一个实例, 用户有多个实例。 应用和系统之间通过OAuth2协议通信。 用户在通信过程中参与(授权)。 把上面时序图对应到一个生活中的场景,业主授权快递公司出入小区送快递: Client Req Auth: 顺丰快递员打电话给业主,有你的快递,得送进去,给办个出入证吧 Resource Owner Grant Auth: 业主联系小区物业, 我是业主,这是我的证明,我允许顺丰最近两天可以进小区物业给我送快递 小区物业告诉业主,OK,你让顺丰联系我拿临时出入证,就说3号楼201房间,授权码:核酸检测利国利民 Client Sends Auth Grant 快递公司联系小区物业说,我是顺丰,这是我的证明,需要给3号楼201房间送快递,业主的授权码是核酸检测利国利民 Auth Server Sends Access Token 小区物业说好,授权码没问题,这个是临时出入证,两天有效。 Clients Sends Access Token 快递员用临时出入证开小区大门 Protected Resoure sends resource 快递员进入小区 注意: 第二步,业主联系物业时候, 要证明自己的确是业主(用小区app登录) 第三步,顺丰联系物业时候,需要证明自己的确是顺丰(报小区物业预先给顺丰分配的client_secret让物业核对). 物业还要检查3号楼201业主的确允许了(通过查授权码)。 如果快递员直接到小区门卫那里说 “核酸检测利国利民”,是没有作用的。“核酸检测利国利民”承载的信息是3号楼201业主允许顺丰在2天内进小区。 快递员给门卫报这个授权码没用,门卫只认出入证。再说,门卫也不知道快递员是不是顺丰的啊。这个授权码只有在快递公司把它换成临时出入证后才可以进小区。 换临时出入证需要验证顺丰的身份。“核酸检测利国利民”承载的信息是可以公开的,其它人听到了也不会有安全风险,因为其他人没有顺丰在物业处注册得到的client_secret, 没办法用授权码换临时出入证。 临时出入证需要妥善保管,任何人拿着都能进小区了。 OAuth2的过程是当有快递时候,业主授权小区物业给快递公司分配临时出入证,在一定的时间内可以出入小区。在同一时间,会存在很多个不同业主授权的有效临时出入证,但一个业主对一家快递公司,在同一时间只有一个有效临时出入证。 角色间的类比关系如下。 中文术语 时序图中的概念 GitHub授权登录APP场景 业主授权顺丰进小区场景 用户 Resource Owner GitHub用户 业主 应用 Client GitHub OAuth APP 顺丰 系统 Authorization Server GitHub 小区物业 资源 Protected Resource GitHub用户名称,头像 小区内部道路 授权 Authorization Grant code 核酸检测利国利民 令牌 Access Token token 临时出入证 下图出自RFC6750, 实际实现时候,Client是APP, Auth Server/Resource Server在同一个域名后面(GitHub,钉钉,Facebook…), Resource Owner也是在这个域名下完成授权(Auth Grant)。 ...

2022年2月26日 · 8 分钟