From d9da26d0bcea499a7d83ac3c707a810725f8f228 Mon Sep 17 00:00:00 2001 From: lijie67 Date: Wed, 20 Apr 2022 14:02:10 +0800 Subject: [PATCH] Site updated: 2022-04-20 14:02:10 --- 2020/03/expect/index.html | 165 +++++++++++++++++++ 2020/07/stickynav/index.html | 198 +++++++++++++++++++++++ 2021/03/permissions/index.html | 277 ++++++++++++++++++++++++++++++++ 2022/03/webgl-shader/index.html | 30 ++-- archives/index.html | 65 +++++++- index.html | 135 +++++++++++++++- 6 files changed, 851 insertions(+), 19 deletions(-) create mode 100644 2020/03/expect/index.html create mode 100644 2020/07/stickynav/index.html create mode 100644 2021/03/permissions/index.html diff --git a/2020/03/expect/index.html b/2020/03/expect/index.html new file mode 100644 index 0000000..d9e0e59 --- /dev/null +++ b/2020/03/expect/index.html @@ -0,0 +1,165 @@ + + + + + + + + + + + 使用Expect快速登录服务器 + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+  hello world +
+
+ + +
+
+ + +

+ 使用Expect快速登录服务器 +

+ + + +
+
+ + + + + + + +
+ + +
+ +
+
+ +
+ +
+

前段时间开发中台项目的时候经常需要频繁登录生产服务器去修改代码,但通常企业为了服务器安全,需要通过跳板机来链接,如下图,每次都需要先登录两台服务器才能链接到正式服务器,非常繁琐,这时候expect就派上用场了

+

+

expect是什么

expect就是一个能帮我们自动化执行交互式脚本的命令行工具

+

安装

mac brew install expect

+

linux yum install expect

+

expect 常用命令

spawn 接收命令 如ssh 192.168.100.100

+

expect 等待接收上面命令执行完的字符串,执行下一步操作

+

send 发送你要输入的内容

+

interact 退出自动执行 返回人工交互

+

开始使用

下面来实现如何自动登录

+

先创建一个文件login_server.sh

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#! /usr/bin/expect

#定义一些变量
set timeout 30 #超时时间,随便写一个吧
set username hello
set password 123456 #密码123456

# 登录跳板机
spawn ssh $username@192.168.000.00
#expect 根据上面命令执行后返回的信息,如果包含password,就证明成功,执行下一步,
#所以需要根据自己返回的信息确定成功后每次都包含哪些字符串
expect "password"
# 发送密码
send "$password\r"
# 返回的信息包含"@"成功
expect "@"

# 下面就基本重复上面的步骤

# 堡垒机
send "ssh 10.123.456.789\r"
expect "*password:"
send "$password\r"

# idc
expect "*IP:"
send "10.123.45.67\r"
expect "*user:"
send "$username\r"
expect "Password:"
send "$password\r"

# 最后退出返回人工操作
interact

+ +

然后命令行 ./login_server.sh 等执行完就ok了

+ +
+
+ + + + + + + + + +
+
+ + + + + + diff --git a/2020/07/stickynav/index.html b/2020/07/stickynav/index.html new file mode 100644 index 0000000..8e568d5 --- /dev/null +++ b/2020/07/stickynav/index.html @@ -0,0 +1,198 @@ + + + + + + + + + + + 移动端吸顶导航组件的实现 + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+  hello world +
+
+ + +
+
+ + +

+ 移动端吸顶导航组件的实现 +

+ + + +
+
+ + + + + + + +
+ + +
+ +
+
+ +
+ +
+

前言

吸顶导航是营销会场类最常用的组件之一, 现在的会场页面是越来越长,如果从第一屏手动滑到最后一屏,还是一个挺累的操作,所以吸顶导航还是很有必要存在的,组件很常见,但是开源的不多,而且大多是PC版,几乎都不能满足业务的需求,所以决定自己写一个。

+

先看下组件效果 demo

+

+

功能拆解

梳理下组件需要实现的功能

+
    +
  • 到达首层吸顶和最后一层取消吸顶
  • +
  • 当前楼层高亮显示
  • +
  • 选中导航居中显示
  • +
  • 默认显示或滑到首层才显示
  • +
  • 滑动过程中控制隐藏显示
  • +
  • 展开显示更多
  • +
+

功能实现

下面我会介绍下其中几个功能的实现方法,全部源码有兴趣的话可以点击这里

+

导航选中居中

1. 如何居中

首先我们可以先考虑怎么居左,我们知道每一项距离左边的宽度是m,那居左就是-m,居中就是再减中线的位置,中线的位置如果是M,那就是M-m

+

2. 处理边界的情况

通过M-m,我们再来处理到达边界的问题,主要两种情况

+

1.当M-m>0的时候,则已经到达最左边

+

2.当M-m >于可滚动的距离(滚动条长度-可视长度),就是到达最右边

+

实现代码:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*以下代码为了方便理解,略有删减*/

/*
* 导航切换
*/
watch(){
translateX(value){
//滚动条位置修改
this.scrollView.scrollLeft = Math.abs(value)
}
},
methods:{
center(index){
//当前选中项
const activeItem = this.$refs.navitem[index]
//选中项距离左边的距离和宽度
const {offsetLeft,offsetWidth} = activeItem
//导航条可见的宽度
const touchWidth = this.stickyNav.offsetWidth
//可滚动宽度 = 整个滚动宽度 - 导航条可见的宽度
const scrollWidth = this.scrollView.scrollWidth - touchWidth
//导航条中点
const half = (touchWidth - offsetWidth) / 2
//需要滚动的长度
let scrollLeft = half - offsetLeft
//到达最左边
scrollLeft > 0 && (scrollLeft = 0);
//到达最右边
scrollLeft < -scrollWidth && (scrollLeft = -scrollWidth)
this.translateX = scrollLeft
}
}
+ +

导航缓动

实现了导航居中后我们再给他加一个缓动的效果,上面已经通过监听滚动的值去修改滚动条scrollLeft改变位置,由于watch可以监听值的变化,我们可以取到初始值和结束值,所以我们只需给数字变化添加一个缓动的过程,这里使用了一个插件tweenjs来实现这个功能。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import tween from '@tweenjs/tween.js"
watch:{
translateX(star, end) {
this.tween(star,end)
}
}
methods:{
tween(start,end){
new TWEEN.Tween({
number: start
})
.to({
number: end
},
100)
.onUpdate(tween => {
//改变滚动位置
this.scrollView.scrollLeft = -tween.number;

})
.start();
function animate(){
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
animate()
}
}

+

滚动过程中的隐藏和显示

实现这个功能我们需要知道用户当前的操作是上划还是下划,同样借助于vue中的watch功能,我们监听当前屏幕滚动的距离scrollTop,可以得到一个当前值和过去值,将两个值对比,当前值大于过去值的时候,则表示用户手指是向上滑(屏幕往下滚动)的,反之向下,代码如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*以下代码为了方便理解,略有删减*/

data(){
//控制导航是否显示隐藏的变量
scrollHide:false,
//需要设置一个定时器,当用户一段时间没操作的时候,显示导航条
scrollTimer:false

},
watch:{

scrollTop(newValue, oldValue){
const delay = 2000
//向下滚动
if(newValue > oldvalue){
//改变属性,控制隐藏显示
this.scrollHide = true
//清除定时器
clearTimeout(this.scrollTimer)
this.scrollTimer = null
//向上
}else{
this.scrollHide = fasle
}
if(!this.scrollTimer){
this.scrollTimer = setTimeout(()=>{
this.scrollHide = fasle
})
}
}
}

+

向下滚动隐藏的功能是实现了,但还有一个问题,就是当点击导航栏的时候页面也是向下滚的,这时候还会触发上面的函数,这个时候体验效果有点奇怪,所以还需要优化下,当用户的操作是点击屏幕的时候不去执行隐藏导航的功能

+

改进版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*以下代码为了方便理解,略有删减*/

data(){
//控制导航是否显示隐藏的变量
scrollHide:false,
//需要设置一个定时器,当用户一段时间没操作的时候,显示导航条
scrollTimer:false,
//是否点击事件
isClickScroll: false
},
methods:{
//点击时触发
change(index) {
this.isClickScroll = true;
}
},
watch:{
scrollTop(newValue, oldValue){
if(this.isClickScroll){
setTimeout(() => {
this.isClickScroll = false
}, 10);
}
if (this.isClickScroll) return;
const delay = 2000
//向下滚动
if(newValue > oldvalue){
//改变属性,控制隐藏显示
this.scrollHide = true;
//清除定时器
clearTimeout(this.scrollTimer);
this.scrollTimer = null;
//向上
}else{
this.scrollHide = fasle;
}
if(!this.scrollTimer){
this.scrollTimer = setTimeout(()=>{
this.scrollHide = fasle;
this.isClickScroll = false;
})
}
}
}

+ + + +

遇到的一些问题

京东APP沉浸式兼容问题

沉浸式效果:

+

+

沉浸式就是去掉了首屏标题栏的一种沉浸式体验,,如果开启了沉浸式,那么首屏标题栏是一个透明的状态,整个页面的高度就会上移,然后当你往下滑动的时候标题栏会出现,这时候导航栏如果吸顶,那么就会被标题栏给挡住看不到了,解决方法就是需要增加导航栏距离顶部的高度,而且是动态修改的,因为在APP中获取标题栏的高度是一个异步的操作,原先组件中并没考虑需要动态修改高度的情况,所以需要点小修改,先看下一开始是怎么初始化组件的:

+
1
2
3
4
5
6
7
8
<StickyNav :options="options"/>

options:{
disabled:false,
stickyTop:0, //距离顶部
zIndex:1000,
...
}
+

我们是通过stickyTop属性来控制导航栏距离顶部的距离,但是如果异步去修改这个对象的值是没有任何变化的,因为vue中是无法检测到对象的修改,

+

1.通过watch的deep属性,设置为true可以监听options对象的修改,再重新复制到新对象

+
1
2
3
4
5
6
7
8
watch{
options:{
handler(value){
assign(this.stickyOptions,value)
},
deep:true
}
}
+

2.或者把stickyTop单独作为一个prop属性传给组件,这样可以实时变化

+

低端机兼容性问题

兼容性问题通常出现在一些很低端的手机上,比如android4.0,ios8、不过如果做到以下3点基本也没什么问题

+

1.ES6兼容

通常我们webpack上已经配置了babel转换,但其实只是对语法的编译,比如你可以使用箭头函数等
如果你使用了Promise、Object.assign、includes等全局方法其实都不能被转换的,最简单的方法可以全局引入polyfill

+
1
2
npm install babel-polyfill --save
import 'babel-polyfill'
+ +

或者你的项目中只是用了一两个方法,引入整个polyfill太浪费,也可以使用一些第三方库,如 lodash/includes

+

2.CSS自动 -webkit- 前缀

还有就是样式不生效的问题,一般我们现在都是在webpack工程中配置autoprefixer去自动加前缀,不过要注意修改下package.json下的browserslist

+
1
2
3
4
"browserslist": [
"Android >= 4.4",
"iOS 8"
]
+ +

3.尽量不要使用flex布局

flex布局有某些很老的机型还是支持不是很好,用inline-block来代替

+

结束

本文到这里就结束啦,组件vue-stivky-nav已经开源到npm上,欢迎使用和提问题,如果您对本文有什么问题也可以在底下留言讨论。

+ +
+
+ + + + + + + + + +
+
+ + + + + + diff --git a/2021/03/permissions/index.html b/2021/03/permissions/index.html new file mode 100644 index 0000000..c107072 --- /dev/null +++ b/2021/03/permissions/index.html @@ -0,0 +1,277 @@ + + + + + + + + + + + [译] 在Javascript中处理用户权限 + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+  hello world +
+
+ + +
+
+ + +

+ [译] 在Javascript中处理用户权限 +

+ + + +
+
+ + + + + + + +
+ + +
+ +
+
+ +
+ +
+

在开发用户界面的时候经常需要处理一些用户权限的逻辑,例如管理员和普通访客展示界面是不一样的等,您如何在前端处理这种逻辑?本文将介绍如何以一种优雅的方式来处理,或许可以给你提供一些思路

+
+

原文地址:https://css-tricks.com/handling-user-permissions-in-javascript/

+
+

所以,您正在开发一款新的web应用程序,比如食谱应用,文档管理器,甚至是您的私有云,您现在已经到了开发用户和权限的地步,以文档管理为例:您不仅需要管理员,还可能需要邀请具有只读访问权限的来宾或可以编辑但不能删除您的文件的人员,您如何在前端处理这种逻辑,而不至于用太多复杂的条件和检查使您的代码混乱?

+

在本文中,我们将介绍一个示例实现,说明如何以优雅而简洁的方式处理此类情况。您的需求可能会有所不同,但我希望能您从中获得一些想法。

+

让我们假设您已经构建了后端,为数据库中的所有用户添加了一个表,并且可能为角色提供了专用的列或属性。实现细节完全取决于您。为了这个演示,让我们使用以下角色:

+
    +
  • Admin: 可以做任何事情,例如创建,删除和编辑自己的或他人的文档。
  • +
  • Editor: 可以创建,查看和编辑文件,但不能删除它们。
  • +
  • Guest: 只可以查看文件。
  • +
+

像大多数现代的Web应用一样,您的应用可能会使用RESTful API与后端进行通信,所以让我们使用这个场景进行演示,即使您采用不同的技术,如GraphQL或服务器端渲染,您仍然可以应用我们即将要演示的模式。

+

关键是在获取一些数据时,返回当前登录用户的角色(或权限)。

+
1
2
3
4
5
6
7
{
id: 1,
title: "My First Document",
authorId: 742,
accessLevel: "ADMIN",
content: {...}
}
+ +

在这里,我们获取一个带有一些属性的文档,其中包括一个叫做用户角色的accessLevel的属性。这样我们才能知道登录的用户允许或不允许做什么。我们接下来的工作是在前端添加一些逻辑,以确保访客不会看到他们不应该看到的东西,反之亦然。

+
+

理想情况下,您不应该只依靠前端来检查权限。一个有Web技术经验的人仍然可以在没有UI的情况下向服务器发送一个请求,目的是操纵数据,因此您的后端也应该进行检查。

+
+

顺便说一下,这种模式与框架无关。无论您使用React,Vue甚至是一些野生的Vanilla JavaScript,都没关系。

+

定义常量

第一步(可选,但强烈建议)是创建一些常量。这些将是简单的对象,包含所有的行为(Actions)、角色(Role)和其它重要功能。我喜欢把它们放到一个专门的文件中,可以命名为constants.js。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const actions = {
MODIFY_FILE: "MODIFY_FILE",
VIEW_FILE: "VIEW_FILE",
DELETE_FILE: "DELETE_FILE",
CREATE_FILE: "CREATE_FILE"
}

const roles = {
ADMIN: "ADMIN",
EDITOR: "EDITOR",
GUEST: "GUEST"
}

export { actions, roles }
+ +

如果您有擅长使用TypeScript,您可以使用emuns枚举来获得一个稍微干净的语法。

+

为您的行为和角色创建一个常量集合有一些优势:

+
    +
  • 一个单一的来源 而不是查看您的整个代码库,您只需打开constants.js就可以看到您的应用内部的功能。这种方法也是非常可扩展的,比如说当您添加或删除行为时。
  • +
  • 没有输入错误 您可以导入对象,而不必每次都手动键入一个角色或行为,因为它容易造成错别字和令人讨厌的调试环节,而是可以导入对象,并且借助您喜欢的编辑器的能力,可以免费获得建议和自动完成功能。如果您仍然输错名称,ESLint或其它工具很可能会对您大喊大叫,直到您修复它为止。
  • +
  • 文档 如果您是在一个团队工作,新的团队成员会喜欢这种简单的方式,因为他们不需要通过大量的文件来了解存在哪些权限或操作,也可以使用JSDoc轻松地将其记录下来。
  • +
+

使用这些常量是非常简单,导入并使用它们是这样的。

+
1
2
3
import { actions } from "./constants.js"

console.log(actions.CREATE_FILE)
+ +

定义权限

到了激动人心的部分:建立一个数据结构模型,将我们的行为映射到角色上。有很多方法可以解决这个问题,但我最喜欢下面这个方法。让我们创建一个新的文件,把它叫做permissions.js,然后在里面放一些代码:

+
1
2
3
4
5
6
7
8
import { actions, roles } from "./constants.js"

const mappings = new Map()

mappings.set(actions.MODIFY_FILE, [roles.ADMIN, roles.EDITOR])
mappings.set(actions.VIEW_FILE, [roles.ADMIN, roles.EDITOR, roles.GUEST])
mappings.set(actions.DELETE_FILE, [roles.ADMIN])
mappings.set(actions.CREATE_FILE, [roles.ADMIN, roles.EDITOR])
+ +

让我们一步一步地了解一下:

+
    +
  • 首先,我们需要导入我们的常量。
  • +
  • 然后我们创建一个新的JavaScript Map,称为mappings。我们也可以使用任何其它的数据结构,比如对象,数组,我喜欢使用Maps,因为它们提供了一些方便的方法,比如.has()、.get()等。
  • +
  • 接下来,我们为我们的应用程序的每个行为添加(或者说设置)一个新的键值对。行为作为键,我们通过它来获取执行所述行为所需的角色。至于值,我们定义一个必要的角色数组。
  • +
+

这种方法可能一开始看起来很奇怪(对我来说确实如此),但随着时间的推移,我学会了欣赏它。其好处是显而易见的,特别是在有大量行为和角色的大型应用中。

+
    +
  • 同样,只有一个来源 您需要知道编辑一个文档需要什么角色吗?没问题,到permissions.js里找找看。

    +
  • +
  • 修改业务逻辑是出奇的简单 假设您的产品经理决定,从明天开始,允许编辑删除文件;只需将他们的角色添加到DELETE_FILE条目中,然后就可以了。添加新的角色也是如此:在映射变量中添加更多的条目,就可以了。

    +
  • +
  • 可测试的 您可以使用snapshot tests来确保这些映射里面没有任何意外的变化在代码评审期间也更清晰。

    +
  • +
+

上面的例子相当简单,可以扩展到更复杂的情况。例如,如果您有具有不同角色访问权限的不同文件类型。在本文的最后,我们将对此进行更多的讨论。

+

在用户界面中检查权限

我们定义了所有的行为和角色,并创建了一个map来解释谁可以做什么。现在我们需要实现一个函数,以便我们在用户界面中使用并检查这些角色。

+

当创建这样的新行为时,我总是喜欢从API的外观表现开始,之后,我会实现该API背后的实际逻辑。

+

假如我们有一个React组件,可以渲染一个下拉菜单。

+
1
2
3
4
5
6
7
8
9
10
11

function Dropdown() {
return (
<ul>
<li><button type="button">刷新</button><li>
<li><button type="button">重命名</button><li>
<li><button type="button">复制</button><li>
<li><button type="button">删除</button><li>
</ul>
)
}
+

显然,我们不希望访客看到,也不希望他们点击 “删除 “或 “重命名 “选项,但我们希望他们看到 “刷新”。另一方面,作者应该看到除了 “删除 “以外的所有内容。我想象一些API是这样的。

+
1
hasPermission(file, actions.DELETE_FILE)
+

第一个参数是文件本身,由我们的REST API获取。它应该包含之前的accessLevel属性,可以是ADMINEDITORGUEST。由于同一个用户在不同的文件中可能有不同的权限,我们总是需要提供这个参数。

+

至于第二个参数,我们传递一个操作,比如删除文件。然后,如果当前登录的用户有该操作的权限,该函数应该返回一个布尔值true,如果没有,则返回false

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import hasPermission from "./permissions.js"
import { actions } from "./constants.js"

function Dropdown() {
return (
<ul>
{hasPermission(file, actions.VIEW_FILE) && (
<li><button type="button">刷新</button></li>
)}
{hasPermission(file, actions.MODIFY_FILE) && (
<li><button type="button">重命名</button></li>
)}
{hasPermission(file, actions.CREATE_FILE) && (
<li><button type="button">复制</button></li>
)}
{hasPermission(file, actions.DELETE_FILE) && (
<li><button type="button">删除</button></li>
)}
</ul>
)
}
+ +

您可能想找一个不太冗长的函数名称,甚至可能想用不同的方式来实现整个逻辑(我想到了柯里化(Currying)),但对我来说,这已经做得相当不错了,即使是在权限超级复杂的应用中。当然,JSX看起来比较杂乱,但这是一个小小的代价。在整个应用中始终如一地使用这种模式,会让权限变得更干净、更直观易懂。

+

如果您还不相信,让我们看看没有hasPermission函数下的情况下会是什么样子:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
return (
<ul>
{['ADMIN', 'EDITOR', 'GUEST'].includes(file.accessLevel) && (
<li><button type="button">Refresh</button></li>
)}
{['ADMIN', 'EDITOR'].includes(file.accessLevel) && (
<li><button type="button">Rename</button></li>
)}
{['ADMIN', 'EDITOR'].includes(file.accessLevel) && (
<li><button type="button">Duplicate</button></li>
)}
{file.accessLevel == "ADMIN" && (
<li><button type="button">Delete</button></li>
)}
</ul>
)
+ +

您可能会说,这看起来还不错,但想想如果添加更多的逻辑,如许可证检查或更细粒度的权限,会发生什么。在我们这个
行业,事情往往会很快失控。

+

您是否在想,既然每个人都可能会看到 “刷新 “按钮,为什么我们需要第一次权限检查?我喜欢把它放在那里,因为您永远不知道将来会发生什么变化。一个新的角色可能会被引入,甚至可能看不到这个按钮。在这种情况下,您只需要更新您的 permissions.js,就可以不用管这个组件了,这样Git提交的时候就会更干净,也会减少出错的机

+

实现权限检查器

最后,是时候实现将这一切粘合在一起的功能了:行为、角色和UI。实现的方法很简单。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import mappings from "./permissions.js"

function hasPermission(file, action) {
if (!file?.accessLevel) {
return false
}

if (mappings.has(action)) {
return mappings.get(action).includes(file.accessLevel)
}

return false
}

export default hasPermission
export { actions, roles }

+ +

您可以把上面的代码放到一个单独的文件中,甚至是permissions.js中。我个人将它们放在一个文件中。

+

让我们来消化一下这里发生的事情:

+
    +
  1. 我们定义一个新的函数,hasPermission,接收一个file参数(来自后端数据)和我们要执行的操作

    +
  2. +
  3. 作为一个失败安全机制,如果由于某些原因,文件为null或不包含访问accessLevel属性,我们返回false。最好格外小心,不要因为代码中的一个小故障或一些错误而将 “秘密 “信息暴露给用户。

    +
  4. +
  5. 来到核心,我们检查mappings是否包含我们正在寻找的行为。如果是,我们可以安全地获取它的值(记住,它是一个角色数组),并检查我们当前登录的用户是否拥有该行为所需的角色。这要么返回true,要么返回false

    +
  6. +
  7. 最后,如果mappings没有包含我们正在寻找的行为(可能是代码中的错误或再次出现小故障),我们返回false以确保安全。

    +
  8. +
  9. 在最后两行,我们不仅要导出hasPermission函数,还要重新导出我们的常量,以方便开发者。这样,我们就可以在一行中导入所有的实用程序。

    +
  10. +
+

更多使用案例

所示代码出于演示目的非常简单。不过,您还是可以把它作为您的应用程序的基础,并据此塑造它。我认为它是任何JavaScript驱动的应用程序实现用户角色和权限的良好起点。

+

通过一点重构,您甚至可以重复使用这个模式来检查一些不同的东西,比如许可证(licenses)。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { actions, licenses } from "./constants.js"

const mappings = new Map()

mappings.set(actions.MODIFY_FILE, [licenses.PAID])
mappings.set(actions.VIEW_FILE, [licenses.FREE, licenses.PAID])
mappings.set(actions.DELETE_FILE, [licenses.FREE, licenses.PAID])
mappings.set(actions.CREATE_FILE, [licenses.PAID])

function hasLicense(user, action) {
if (mappings.has(action)) {
return mappings.get(action).includes(user.license)
}

return false
}
+ +

我们声明用户的license属性,而不是用户的角色:同样的输入,同样的输出,完全不同的背景。

+

在我的团队中,我们需要同时或单独检查用户角色和许可证,当我们选择这个模式时,我们为不同的检查创建了不同的函数,并将两种检查重新组合到一个新的函数中,我们最终使用的是hasAccess util:

+
1
2
3
function hasAccess(file, user, action) {
return hasPermission(file, action) && hasLicense(user, action)
}
+

每次调用hasAccess时传递3个参数有点不理想,您可能会在您的应用中找到一种方法来解决这个问题(比如Curryingglobal state)。在我们的应用中,我们使用了包含用户信息的全局存储,所以我们可以简单地删除第2个参数,然后从存储中获取这些信息来代替。

+

您还可以在权限结构方面更深入。您是否有不同类型的文件(或实体,通俗点说)?您是否想根据用户的许可证启用某些文件类型?让我们以上面的例子为例,让它稍微强大一点:

+
1
2
3
4
5
6
7
8
9
10
11
const mappings = new Map()

mappings.set(
actions.EXPORT_FILE,
new Map([
[types.PDF, [licenses.FREE, licenses.PAID]],
[types.DOCX, [licenses.PAID]],
[types.XLSX, [licenses.PAID]],
[types.PPTX, [licenses.PAID]]
])
)
+

这为我们的权限检查器增加了一个全新的层次。现在,我们可以为一个行为拥有不同类型的实体。让我们假设您想为您的文件提供一个导出功能(EXPORT_FILE),但您希望您的用户为您建立的那个超级漂亮的Microsoft Office转换器付费(我们不直接提供一个数组,而是在行为中嵌套第二个Map,并传递我们想要覆盖的所有文件类型。您会问,为什么要使用Map?和我前面提到的原因一样:它提供了一些友好的方法,比如.has(),不过,您可以随意使用一些不同的方法。

+

随着最近的更改,我们的hasLicense功能已经不能满足需要了,所以是时候稍微更新一下了。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hasLicense(user, file, action) {
if (!user || !file) {
return false
}

if (mappings.has(action)) {
const mapping = mappings.get(action)

if (mapping.has(file.type)) {
return mapping.get(file.type).includes(user.license)
}
}

return false
}
+ +

不知道是不是只是我个人的感觉,即使复杂度增加了,是不是看起来还是超级易读?

+

测试

如果您想确保您的应用能按预期工作,即使在代码重构或引入新功能后,您最好准备好一些测试覆盖率。关于测试用户权限,您可以使用不同的方式:

+
    +
  • 创建用于映射,操作,类型等的快照测试。这可以在Jest或其它测试框架中轻松实现,并确保没有任何东西意外地通过代码审查。不过如果权限一直在变化,更新这些快照可能会很繁琐。

    +
  • +
  • hasLicensehasPermission添加单元测试,并通过对一些实际的测试用例进行硬编码来断言该函数正在按预期工作。单元测试在大多数情况下是一个好主意,因为您想确保返回的是正确的值。

    +
  • +
  • 除了确保内部逻辑工作,您还可以结合您的常量使用额外的快照测试来覆盖每一个场景。我的团队使用了类似的东西。

    +
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
Object.values(actions).forEach((action) => {
describe(action.toLowerCase(), function() {
Object.values(licenses).forEach((license) => {
it(license.toLowerCase(), function() {
expect(hasLicense({ type: 'PDF' }, { license }, action)).toMatchSnapshot()
expect(hasLicense({ type: 'DOCX' }, { license }, action)).toMatchSnapshot()
expect(hasLicense({ type: 'XLSX' }, { license }, action)).toMatchSnapshot()
expect(hasLicense({ type: 'PPTX' }, { license }, action)).toMatchSnapshot()
})
})
})
})

+ +

但同样,个人的喜好和测试方式也有很多不同。

+

总结

就是这样! 希望您能够在下一个项目中获得一些想法或灵感,这种模式可能是您想要的的。总结一下它的一些优点:

+
    +
  • 在您的UI(组件)中不再需要复杂的条件或逻辑 您可以依靠hasPermission函数的返回值,并根据该值轻松地显示和隐藏元素。能够将业务逻辑从您的UI中分离出来,有助于提供一个更干净、更可维护的代码库。

    +
  • +
  • 您的权限的单一真相来源. 与其通过许多文件来了解用户可以或不可以看到什么,不如到权限mappings中去看看。这使得扩展和改变用户权限变得轻而易举,因为您可能甚至不需要接触任何的标记

    +
  • +
  • 很容易测试 不管您是决定快照测试,还是与其它组件的集成测试,还是其它测试,集中化的权限都可以轻松地编写测试。

    +
  • +
  • 文档 您不需要用TypeScript编写您的应用程序,就能从自动补全或代码验证中受益;使用预定义的常量来处理行为、角色、licenses等,可以减少恼人的错别字,让工作更请轻松。此外,其它团队成员可以很容易地发现哪些行为、角色或任何东西是可用的,以及它们在哪里被使用。

    +
  • +
+

假设您想看这个模式的完整演示,可以去这个CodeSandbox,用React玩玩这个想法。它包括了不同的权限检查,甚至还有一些测试范围。

+

对此您有什么看法吗?您有没有类似的方法来处理这种事情,?我一直对其他人想出的办法很感兴趣,欢迎在评论区发表任何反馈意见。谢谢!

+ +
+
+ + + + + + + + + +
+
+ + + + + + diff --git a/2022/03/webgl-shader/index.html b/2022/03/webgl-shader/index.html index 485879a..11b2aa6 100644 --- a/2022/03/webgl-shader/index.html +++ b/2022/03/webgl-shader/index.html @@ -8,12 +8,12 @@ - Webgl基础-着色器 + webGL基础-着色器 - + @@ -59,7 +59,7 @@

- Webgl基础-着色器 + webGL基础-着色器

@@ -88,7 +88,7 @@

不会3D的厨师不是一个好司机,这个时代不卷就只能去开滴滴。 在元宇宙概念的背景下,上级领导也要求做点技术储备,在学习了一段时间3D框架后,也顺便了解下webGL的原理。

-

WebGL

webgl其实是一个很底层的图像引擎,主要提供的能力就是接收输入的图形的顶点坐标,程序在GPU中帮你画出图形(光珊化)和上色后输出到浏览器,在GPU运行的程序则是一种OpenGL着色语言(GLGS),一个webgl程序大概是如下步骤、其中最关键的还是着色器部分。

+

webGL

webGL其实是一个很底层的图像引擎,主要提供的能力就是接收输入的图形的顶点坐标,程序在GPU中帮你画出图形(光珊化)和上色后输出到浏览器,在GPU运行的程序则是一种OpenGL着色语言(GLGS),一个webGL程序大概是如下步骤、其中最关键的还是着色器部分。

下面的一些代码基本都会省略api调用部分

着色器

简单点的说着色器就干两件事:

@@ -96,7 +96,7 @@

根据顶点坐标绘制图像
  • 给图形的每个像素点上色
  • -

    webgl中分别对应两种着色器,顶点着色器(vertext shader)片元着色器(fragment shader) ,两种是成对出现的组成一段着色程序在GPU上运行。

    +

    webGL中分别对应两种着色器,顶点着色器(vertext shader)片元着色器(fragment shader) ,两种是成对出现的组成一段着色程序在GPU上运行。

    缓冲区

    缓冲是发送到GPU的一些二进制数据序列,通常情况下缓冲数据包括位置,法向量,纹理坐标,顶点颜色值等。 也可以存储任何数据。

    如果我们要画一个三角形,那我们在缓冲区写入三个顶点的数据,数据类型是一个Float32Aarray 就是一个一维数组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    //三个2维顶点
    var positions = new Float32Array([
    -0.5, -0.5, //v0
    -0.5, 0.5, //v1
    0.5, -0.5 //v2
    ])

    //创建buffer等方法省略。。。。

    @@ -106,7 +106,7 @@

    这是配置顶点数据的,下面我们继续看下画一个三角形顶点着色器和片元着色器的代码:

    画一个三角形

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    //顶点着色器
    const vsSource = `
    //属性值,从缓冲中获取数据
    attribute vec4 position;
    void main(void) {
    //gl_Position内置的系统变量,记录顶点坐标
    gl_Position = position;
    }`;

    //片元着色器
    const fsSource = `
    void main(void) {
    //gl_FragColor内置的系统变量,记录像素颜色
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); //绿色
    }`

    //画图
    const type = gl.TRIANGLES
    const offset = 0 //从第一个顶点开始
    const vCount = 3 //总顶点数,执行多少次
    gl.drawArrays(type, offset, vCount)

    -

    attributes 属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器,前面我们已经在缓冲区创建了一个顶点数组,两个可以理解为等值,通过webgl api绑定后,在顶点着色器中就可以获取到,再赋值给gl_Position

    +

    attributes 属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器,前面我们已经在缓冲区创建了一个顶点数组,两个可以理解为等值,通过webGL api绑定后,在顶点着色器中就可以获取到,再赋值给gl_Position

    片元着色器 是在顶点着色器运行完后给图形的每个片元(也可以理解为每个像素)上色,上面代码中我们是设置了固定的颜色,gl中颜色值是vec4(r,g,b,a),每个通道的取值范围是0-1,rgb分别代表红绿蓝,如果红色则是vec4(1, 0, 0 ,1)

    最后调用gl.drawArrays(type, offset, vCount) 来执行程序,顶点着色器的运行次数是一个确切的数字,例如这个三角形的例子,我们是设置了三个顶点,所以vCount这个值就是3,每次从属性值中获取2个,这个2个也是通过api指定的,为了方便理解,代码没有写出来,假设我们是一个三维的图形(x,y,z),那么就是指定每次获取3个。

    如果画个矩形,那就是:

    @@ -114,18 +114,18 @@

    给片元着色器传值

    前面片元着色器颜色是固定,但在实际程序中,我们可能会给每个三角形设置不同颜色,或者贴图的时候需要传纹理坐标,那么如何传值呢?同样我们需要创建一个buffer,使用关键字varying将数据通过顶点着色器再传到片元着色器,如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //顶点颜色
    const colors = Float32Array([
    0.1, 0.0 , 0.0, 0.1, //red
    0.1, 1.0 , 0.1, 0.1, //green
    0.1, 0.0 , 0.1, 0.1, //blue
    ])

    //顶点着色器
    const vsSource = `
    //属性值,从缓冲中获取数据
    attribute vec4 position;
    //缓冲区取color数据
    attribute vec4 colors;
    //定义varying数据
    varying vec4 v_color;
    void main(void) {
    gl_Position = position;
    v_color = colors;
    }`;

    //片元着色器
    const fsSource = `
    varying vec4 v_color;
    void main(void) {
    gl_FragColor = v_color;
    }`

    -

    从代码上看我们还是用attribute从缓冲区取数据,然后通过varying关键字定义了v_color,在顶点着色器中赋值,最后片元着色器也是通过varying 取到颜色值。我们给三角形三个顶点设置了不同颜色,每个片元的值是由顶点的线性插值得到,所以看到的是下面的渐变的颜色。

    -

    执行顺序

    根据下图,我们再来梳理下着色器的执行流程,首先在缓冲区中获得数据,顶点着色器根据坐标输出图形(三角形),这一步可以叫做图元装配,确定形状后,我们就知道图形中存在多少片元(像素),每个像素都会调用一次片元着色器进行上色。

    +

    从代码上看我们还是用attribute从缓冲区取数据,然后通过varying关键字定义了v_color,在顶点着色器中赋值,最后片元着色器也是通过varying 取到颜色值。我们给三角形三个顶点设置了不同颜色,每个片元的值是由顶点的线性插值得到,所以看到的是下面的渐变的颜色。

    +

    执行顺序

    根据下图,我们再来梳理下着色器的执行流程,首先在缓冲区中获得数据,顶点着色器根据坐标输出图形(三角形),这一步可以叫做图元装配,确定形状后,我们就知道图形中存在多少片元(像素),每个像素都会调用一次片元着色器进行上色。

    顶点索引

    我们知道再复杂的模型的是由三角形的组成的,前面我们画一个平面是使用了两个三角6个顶点来画的,但其实两个三角中其中其实是有两个顶点是重复的,所以我们可以使用gl.drawElemnt来代替gl.drawArrays,前者支持通过索引来指定顶点,这样可以减少我们顶点传输量,一个平面可能就只减少了两个点,但如果图形复杂就减少很多内存了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    //平面顶点
    const position = [
    -1.0, -1.0, 1.0 //顶点0
    1.0, -1.0, 1.0, //顶点1
    1.0, 1.0, 1.0, //顶点2
    -1.0, 1.0, 1.0, //顶点3
    ]
    //索引指定使用哪个顶点
    const index = [
    0, 1, 2, 0, 2, 3 //下标
    ]
    //api部分
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index);
    gl.drawElements(gl.TRIANGLES, 36);

    -

    总结

    本篇到这里就结束了,其实只是讲了一点点基础概念,实际要发开3d还需要非常多的储备知识、比如还要了解光照原理,摄像机(透视矩阵)原理等等,其实对数学知识的要求还是很高的、当然webgl也就了解下就好,实际开发可以使用three.js,babylonjs等框架。

    -

    参考

    WebGL 理论基础

    -

    mdn Getting started with WebGL

    -

    An Introduction to WebGL

    -

    Raw WebGL 101 — Part 1: Getting Started

    -

    WebGL model view projection

    -

    《WebGL 编程指南》

    +

    总结

    本篇到这里就结束了,其实只是讲了一点点基础概念,实际要发开3d还需要非常多的储备知识、比如还要了解光照原理,摄像机(透视矩阵)原理等等,其实对数学知识的要求还是很高的、当然webGL也就了解下就好,实际开发可以使用three.js,babylonjs等框架。

    +

    参考

    webGL 理论基础

    +

    mdn Getting started with webGL

    +

    An Introduction to webGL

    +

    Raw webGL 101 — Part 1: Getting Started

    +

    webGL model view projection

    +

    《webGL 编程指南》


    diff --git a/archives/index.html b/archives/index.html index 9e15d8a..aacd9db 100644 --- a/archives/index.html +++ b/archives/index.html @@ -84,7 +84,7 @@

    归档

    - 该分类共 7 篇文章 + 该分类共 10 篇文章
    @@ -109,7 +109,7 @@

    2022

    - Webgl基础-着色器 + webGL基础-着色器
    @@ -180,6 +180,22 @@

    2021

    +
    +
    + 03-02 +
    + +
    + + + + + +
    02-05 @@ -198,6 +214,51 @@

    2021

    + + + + + + + + +
    +

    2020

    +
    diff --git a/index.html b/index.html index 8f7a491..9fe8f91 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@

    - Webgl基础-着色器 + webGL基础-着色器

    @@ -95,7 +95,7 @@

    不会3D的厨师不是一个好司机,这个时代不卷就只能去开滴滴。 在元宇宙概念的背景下,上级领导也要求做点技术储备,在学习了一段时间3D框架后,也顺便了解下webGL的原理。 -WebGLwebgl其实是一个很底层的图像引擎,主要提供的能力就是接收输入的图形的顶点坐标,程序在GPU中帮你画出图形(光珊... +webGLwebGL其实是一个很底层的图像引擎,主要提供的能力就是接收输入的图形的顶点坐标,程序在GPU中帮你画出图形(光珊...
    @@ -251,6 +251,49 @@

    +
  • + + +

    + + [译] 在Javascript中处理用户权限 + +

    + + + +
    + + 在开发用户界面的时候经常需要处理一些用户权限的逻辑,例如管理员和普通访客展示界面是不一样的等,您如何在前端处理这种逻辑?本文将介绍如何以一种优雅的方式来处理,或许可以给你提供一些思路 + +原文地址:https://css-tricks.com/handling-user-permissions-... + +
    +
    + + + + + + + +
    + + +
    + +
    +
  • + + + + + + +
  • @@ -293,6 +336,94 @@

    +
  • + + +

    + + 移动端吸顶导航组件的实现 + +

    + + + +
    + + 前言吸顶导航是营销会场类最常用的组件之一, 现在的会场页面是越来越长,如果从第一屏手动滑到最后一屏,还是一个挺累的操作,所以吸顶导航还是很有必要存在的,组件很常见,但是开源的不多,而且大多是PC版,几乎都不能满足业务的需求,所以决定自己写一个。 +先看下组件效果 demo + +功能拆解梳理下组件需... + +
    +
    + + + + + + + +
    + + +
    + +
    +
  • + + + + + + + +
  • + + +

    + + 使用Expect快速登录服务器 + +

    + + + +
    + + 前段时间开发中台项目的时候经常需要频繁登录生产服务器去修改代码,但通常企业为了服务器安全,需要通过跳板机来链接,如下图,每次都需要先登录两台服务器才能链接到正式服务器,非常繁琐,这时候expect就派上用场了 + +expect是什么expect就是一个能帮我们自动化执行交互式脚本的命令行工具 +安... + +
    +
    + + + + + + + +
    + + +
    + +
    +
  • + + + + + + +