0%

模块话,模块化说了那么多次,以为了解个大概就行了,然而在一次面试经历中被问到AMD和ES Module,所以特此来一篇,汇总一下吸收的模块方面的内容。

又是一篇搬运文章,谁让我放荡不羁爱打野呢。作者博客地址Preethi Kasireddy

为什么模块很重要?

如果没有模块,你能想象在复杂场景下你得js代码是个什么鬼模样吗?模块解决了名称空间和可维护性等变得越来越难以处理的问题。

好的模块是高度独立的,具有独特的功能,可以根据需要对它们进行改组,删除或添加,而不会破坏整个系统。

优势:

1)可维护性:根据定义,模块是独立的。精心设计的模块旨在尽可能减少对代码库各部分的依赖,从而使其能够独立增长和改进。当模块与其他代码解耦时,更新单个模块要容易得多。

2)命名空间:在JavaScript中,顶级函数范围之外的变量是全局变量(意味着每个人都可以访问它们)。因此,普遍存在“命名空间污染”,其中完全不相关的代码共享全局变量。

在不相关的代码之间共享全局变量在开发中是一个很大的禁忌。模块允许我们通过为变量创建私有空间来避免名称空间污染。

3)可重用性:抽取通用部分,哪里需要就拿去,不用重复写,当然也对应的第一点,当有修改时只需要该一份。

早期的时候为了达到“模块模式”,也有很多方式,不过我看了下基本上都是基于匿名闭包的基础上而来的。

这些方式有一个共同点:使用单个全局变量将其代码包装在函数中,从而使用闭包作用域为其自身创建私有名称空间,同时自定义公开哪些方法、变量

大家可以看下jQuery的源码,就是这样的实现方式,如下面的代码。

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
var myGradesCalculate = (function () {

// Keep this variable private inside this closure scope
var myGrades = [93, 95, 88, 0, 55, 91];

var average = function() {
var total = myGrades.reduce(function(accumulator, item) {
return accumulator + item;
}, 0);

return'Your average grade is ' + total / myGrades.length + '.';
};

var failing = function() {
var failingGrades = myGrades.filter(function(item) {
return item < 70;
});

return 'You failed ' + failingGrades.length + ' times.';
};

// Explicitly reveal public pointers to the private functions
// that we want to reveal publicly

return {
average: average,
failing: failing
}
})();

myGradesCalculate.failing(); // 'You failed 2 times.'
myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'

如您所见,这种方法使我们可以决定将哪些变量/方法设为私有(例如*myGrades*),以及通过将它们放在return语句中(例如*average**failing***)来公开哪些变量/方法。

所以大家可以看出,早期关于模块的写法,尽管每种方法都以其自己的方式有效,但它们也有缺点:

  1. 大家各自发挥的,这样有个最大的问题就是到最后就是乱的,就像为什么需要TC39一样,让大家有一套标准,才能从而写出更友好通用的模块。
  2. 依赖管理是个问题,需要我们开发人员自己管理依赖,记得刚开始用jQuery这种第三方库的时候,如果没注意到顺序就会报错,所以想象一下如果引用较多时,管理依赖关系并正确解决这些问题会让人头疼。
  3. 全局作用域被污染,上述的方式创建的变量(比如myGradesCalculate)都在全局范围内,所以该全局范围内的代码的每个部分都可以更改该变量。恶意代码可以有意更改该变量,以使您的代码执行您不希望这样做的事情,或者非恶意代码可能会无意间破坏了您的变量。

所以基于上述原因,这个时候我们就需要一套规范了来解决这些个问题:我们能否设计一种无需遍历全局范围即可请求模块接口的方法

规范

现在比较通行得规范有两种:CommonJSAMD

CommonJS

CommonJS是一个旨在定义一系列规范以帮助开发服务器端JavaScript应用程序的项目。CommonJS团队尝试解决的领域之一就是模块,负责设计和实现用于声明模块的JavaScript API。我听说CommonJS,最早是在15年写Node应用的时候接触的,Node.js最开始就是遵循这套规范弄得模块化,但是据说后来不用该规范了。

一个CommonJS的模块本质上是一种可重复使用的一段JavaScript代码其中出口特定对象,使它们可用于其他模块需要在他们的计划。

使用CommonJS,每个JavaScript文件都将模块存储在其自己的唯一模块上下文中(就像将其包装在闭包中一样)。在此范围内,我们使用module.exports对象公开模块,并要求将其导入。

当您定义CommonJS模块时,它可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
function myModule() {
this.hello = function() {
return 'hello!';
}

this.goodbye = function() {
return 'goodbye!';
}
}

module.exports = myModule;

我们使用特殊对象模块,并将我们函数的引用放入module.exports中。这使CommonJS模块系统知道我们要公开的内容,以便其他文件可以使用它。

然后,当某人想要使用myModule时,他们可以在其文件中要求它,如下所示:

1
2
3
4
5
var myModule = require('myModule');

var myModuleInstance = new myModule();
myModuleInstance.hello(); // 'hello!'
myModuleInstance.goodbye(); // 'goodbye!'

与我们之前讨论的模块模式相比,这种方法有两个明显的好处:

  1. 避免全局命名空间污染
  2. 明确我们的依赖关系

要注意的另一件事是,CommonJS采用服务器优先的方法并同步加载模块。这很重要,因为如果我们有我们需要的其他三个模块需要,它就会加载它们一个接一个。

现在,它可以在服务器上很好地工作,但是不幸的是,这使得为浏览器编写JavaScript时更难使用,因为服务器端通常是从磁盘读取,而浏览器需要网络请求,所以只要加载模块的脚本一直在运行(JavaScript线程将停止直到代码被加载),它就会阻止浏览器运行其他任何东西,直到加载完成。

AMD

从上面我们知道CommonJS是同步的,所以很显然不适用浏览器端,那我们就需要异步模块定义的规范,即AMD

使用AMD加载模块如下所示:

1
2
3
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
console.log(myModule.hello());
});

这里发生的是,define函数将每个模块依赖项的数组作为第一个参数。这些依赖项在后台加载(以非阻塞方式),并且一旦加载了define,便调用回调函数。

接下来,回调函数将加载的依赖项作为参数(在本例中为myModule*myOtherModule),以允许函数使用这些依赖项。最后,还必须使用**define***关键字定义依赖项本身。

例如,***myModule***可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
define([], function() {

return {
hello: function() {
console.log('hello');
},
goodbye: function() {
console.log('goodbye');
}
};
});

与CommonJS不同,AMD采用了浏览器优先的方法以及异步行为来完成工作。

除了异步之外,AMD的另一个好处是您的模块可以是对象,函数,构造函数,字符串,JSON和许多其他类型,而CommonJS仅支持将对象作为模块。

AMD与CommonJS相比,其提供的io,文件系统和其他面向服务器的功能不兼容。

UMD

对于需要同时支持AMD和CommonJS功能的项目,还有另一种格式:通用模块定义(UMD)。

UMD本质上创建了一种使用这两种方法之一的方式,同时还支持全局变量定义。结果,UMD模块能够在客户端和服务器上工作。

以下是UMD如何开展业务的快速体验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['myModule', 'myOtherModule'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('myModule'), require('myOtherModule'));
} else {
// Browser globals (Note: root is window)
root.returnExports = factory(root.myModule, root.myOtherModule);
}
}(this, function (myModule, myOtherModule) {
// Methods
function notHelloOrGoodbye(){}; // A private method
function hello(){}; // A public method because it's returned (see below)
function goodbye(){}; // A public method because it's returned (see below)

// Exposed public methods
return {
hello: hello,
goodbye: goodbye
}
}));

ES Module

是我做前端开始听的最多的了,当然也因为无时无刻都在用它。

上面咱们所说的,都不是JavaScript固有的。不过幸运的是,TC39(定义ECMAScript语法和语义的标准机构)引入了ECMAScript 6(ES6)内置模块。

ES6提供了多种导入和导出模块的可能性,其他人则做了很好的解释-以下是其中的一些资源:

与CommonJS或AMD相比,ES6模块最大的优点是它能够提供两全其美的优势:紧凑和声明性语法以及异步加载,以及诸如更好地支持依赖项等附加优点。

ES6模块最让人兴奋的应该是导入是导出的实时只读视图,即是只读引用,不过却可以改写属性。所以你猜到了当你模块里的值属性发生变化时,导入的地方获取的值是一样的。ES Module具体的后面还有一篇文档单讲,不然这文章就太长了。

最后我们对比一下两种方式吧

ES Module与CommonJS:

  • CommonJS规范通常适用于Node这类服务器端的
  • CommonJS模块是对象,是运行时加载,运行时才把模块挂载在exports之上(加载整个模块的所有),加载模块其实就是查找对象属性。
  • ES Module不是对象,是使用export显示指定输出(函数、对象、变量等),再通过import导入。为编译时加载,编译时遇到import就会生成一个只读引用。等到运行时就会根据此引用去被加载的模块取值。所以不会加载模块所有方法,仅取所需。
  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

说明:

全局范围(作用域)

  1. 写在script标签中的JS代码,都在全局作用域 。
  2. 全局作用域在页面打开时创建,在页面关闭时销毁。
  3. 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用 。
  4. 全局作用域中,创建变量都会作为window对象的属性保存
  5. 创建的函数都会作为window对象的方法保存
  6. 全局作用域中的变量都是全局变量,在页面的任何部分都可以访问的到并可以修改它

来源:

话说从接触Linux那一天起,就有一种独领风骚的傲气,命令走天下的这种霸气,那时候感觉精通Linux的连女朋友都带光环。

我最开始接触Linux是从Ubuntu开始,当时笔记本装了双系统,我就鼓捣了一个Ubuntu,现在用的较多的CentOS,首先入门都是先从了解root开始,我其实一直对root很好奇,刚好今天看到twitter上ruanyf大佬推了一篇文章Root User in Ubuntu: Important Things You Should Know讲基于Ubuntu的root的文章,基础好用,我们现在做的产品的安装包刚好也是基于Ubuntu做的,所以更有必要在此做个中文版记录。

开始

文章主要讲了以下四块:

  1. 为什么在Ubuntu中禁用root用户
  2. 以root身份使用命令
  3. 切换到root用户
  4. 解锁root用户

什么是root用户?为什么将其锁定在Ubuntu中?

根用户Ubuntu

稍微了解Linux的都知道,在Linux中,有一个称为root的超级用户。这是超级管理员帐户,可以使用系统执行任何操作。它可以访问任何文件并在Linux系统上运行任何命令。

拥有权利的同时也被赋予了重大的责任。超级用户可以为您提供对系统的完全控制权,因此也格外谨慎。超级用户可以访问系统文件并运行命令来更改系统配置。因此,错误的命令可能会造成无法挽回的损失,比如网上老说的一个梗:当rm -rf 之后我跑路了。所以一般情况下都不会给root权限,管理员会分配部分权限建对应的用户供对应的人使用。

这也说明了为什么Ubuntu默认情况下锁定了root用户,就是为了避免意外灾难。

您无需具有root特权即可执行日常任务,例如将文件移动到主目录中,从Internet下载文件,创建文档等。

*以此类比更好地理解它。如果必须切水果,可以使用菜刀。如果必须砍伐树木,则必须使用锯。现在,您可以使用锯切水果,但这不明智,是吗?*

这是否意味着您不能成为Ubuntu的root用户或无法使用具有root用户特权的系统?不,您仍然可以在“ sudo”的帮助下获得root用户访问权限(在下一节中说明)。

重点:

用户功能强大,无法用于常规任务。这就是为什么不建议始终使用root的原因。您仍然可以使用root运行特定命令。

如何在Ubuntu中以root用户身份运行命令?

须藤三明治xkcd图片来源:xkcd

当你需要某些系统特定任务的root特权。例如,如果要通过命令行更新Ubuntu,则不能以常规用户身份运行该命令。会有以下类似的错误。

1
2
3
4
5
6
apt update
Reading package lists... Done
E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)
E: Unable to lock directory /var/lib/apt/lists/
W: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)
W: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)

那么,这个时候怎么做呢?简单的答案是在需要以root身份运行的命令之前添加sudo。

1
sudo apt update

Ubuntu和许多其他Linux发行版使用一种称为sudo的特殊机制。Sudo是一个程序,用于以root(或其他用户)身份控制对运行命令的访问。

Sudo实际上是一个多功能的工具。可以将其配置为允许用户以root用户身份运行所有命令。您可以配置它仅以root身份运行选定的几个命令。您也可以配置为不带密码运行sudo

在安装Ubuntu时,必须创建一个用户帐户。该用户帐户在您的系统上以管理员身份运行,并且按照Ubuntu中的默认sudo策略,它可以使用root特权在系统上运行任何命令。

运行sudo不需要root密码,但需要用户自己的password

这就是为什么当使用sudo运行命令时,总会一开始就询问密码:

1
2
gamehu@nuc:~$ sudo apt update
[sudo] password for gamehu:

如您在上面的示例中看到的,用户gamehu试图使用sudo运行’apt update’命令,系统要求输入gamehu的密码。

要注意当您开始在终端中输入密码时,屏幕上什么都没有发生不会有任何显示,也没有所谓的删除键、退格键…这里会记录你按下的所有键。因为作为默认安全功能,屏幕上不显示任何内容。甚至没有星号(*)。您输入密码,然后按Enter。*

划重点:

要在Ubuntu中以root身份运行命令,请在命令前添加sudo。
当要求输入密码时,输入您的帐户密码。
在屏幕上键入密码时,看不到任何内容。只需继续正确的输入密码,然后按Enter。

如何在Ubuntu中成为root用户?

你可以使用sudo以root身份运行命令。但是,在某些情况下,您必须以root用户身份运行多个命令,则可以临时切换为root用户。

sudo命令允许您使用以下命令模拟root登录shell:

1
2
3
4
5
6
sudo -i
gamehu@nuc:~$ sudo -i
[sudo] password for gamehu:
root@nuc:~# whoami
root
root@nuc:~#

您会注意到,切换到root用户时,shell命令提示符将从$(美元键符号)更改为#(磅键符号)。

*尽管已向您展示了如何成为root用户,但我必须警告你,应避免将系统用作root用户。毕竟出于某种原因,我们不建议这样做。*

临时切换到root用户的另一种方法是使用su命令:

1
sudo su

如果您尝试在不使用sudo的情况下使用su命令,则会遇到“ su身份验证失败”错误。

您可以使用exit命令恢复为普通用户。

1
exit

如何在Ubuntu中启用root用户?

到目前为止,您已经知道默认情况下,root用户在基于Ubuntu的发行版中被锁定。

Linux使您可以自由地对系统进行任何操作。解锁root用户是这些自由之一。

如果出于某种原因决定启用root用户,则可以通过为它设置密码来启用它:

1
sudo passwd root

同样,不建议这样做,我也不鼓励您在桌面上执行此操作。如果忘记了密码,将无法再次在Ubuntu中更改root密码

您可以通过删除密码来再次锁定root用户:

1
sudo passwd -dl root

记录近两年各种机会下与事业部头部三剑客交流收获的一些真知灼见。

关于合作

目标一致

看上去的完美搭档其实是一个很磨人的过程,一开始恨不得一见面就呼巴掌作为打招呼的方式,最终能达成默契或者说合作,目标一致最重要的前提。

不管你怎么看不惯一个人,三观有多么不合,但是当你发现大家目标都是一致的时候,静下来的时候大家总会想一想,是不是我有问题,慢慢的就变得能够互相包容,所以要做成事首要条件不是考虑要找多要好的人多默契的人,关键在于你们的目标是否一致,只要目标一致你会发现你们很难分割彼此。

所以有目标是一件重要紧急的事。

关于做事

干一行,爱一行,不然干不了大事。

最深刻的一句话,我记得当时是我谈起对我现在做的事好像也没到喜欢的程度的时候,领导对我说了这句话,听后我记得当时醍醐灌顶,然后手脚冰凉,内心很奔溃,至此以后我逐渐爱上了我现在做的事,虽然还是有很厌烦的时候,不过总能爱回去。

勇于释放自己,学会利用资源,学会影响上级。

这句话是我最亲近的老大对我说的,当我在抱怨做事的一些困惑时。这是最有挑战的不过收获也是最大的,这句话不是教我怎么在领导面前表现,而是告诉我领导在意的是你的结果,当你结果好的时候,才会在意过程,从而改变对你的一些印象。当你做出结果时,他们会关注到的。

关于自信

当你进入了公司就不要再因为自己的出身、学历等感到自卑,因为能进来表示公司已经认可了你,接下来你要想的是,怎么做到比别人牛逼。

否定自己是很耗能量的一件事,积极一点。

OK,正式说明了

SSO的说明网上有很多我就不在这儿丢人了。找了张小图SSO的作用一目了然。

以下主要记录一下我在产品中SSO的实践案例。

案例1

案例1 是比较标准的基于OpenID方式的SSO,用Node.js写的。

案例1没什么说的,网上样例很多,如果有兴趣可以看下我之前写的,不过比较老了,也是第一次写nodejs。

案例2

则是非标的SSO,用Java+javascript写的。

案例2虽然不是非标的,不过整体流程是具备的,比较适用特定编码场景(Spring Security+OpenID),可能有需要的同学,反正我是没在网上找到这类案例。

客户现场的系统A需要登入到我们提供的系统B,没有单独用户中心即也不存在用户同步,客户要求的是能无缝登入,所以解决办法有用户则直接登入无用户则创建后再登入,登录效果与从登录页面发起的登录一样,所以token解析后用Security的方式执行登录。

前端

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
46
47
48
49
50
51
52
53
/**
* sso出现在路径末尾 react router方式 目前采用这种方式 http://.../frame/#/module/xxx?sso=xxx 避免sso一直保留
* @param key 需要获取url参数key
* @returns {string|null}
*/
export function getSsoString(key) {
const str = location.hash;
if (str == null || str.length < 2) {
return null;
}
const arr = str.split('?');
if (arr != null && arr.length === 2) {
const query = arr[1];
if (query != null && query.length > 0) {
const words = query.split('&');
// 将每一个数组元素以=分隔并赋给obj对象
for (let i = 0; i < words.length; i++) {
const tmp_arr = words[i].split('=');
const k = decodeURIComponent(tmp_arr[0]);
const v = decodeURIComponent(tmp_arr[1]);
if (k === key) {
return v;
}
}
}
}
return null;
}

/**
* 单点登录逻辑 在页面token发送到后端进行验证
* @param callback
*/
export function sso(callback) {
const token = getSsoString('sso');
if (token != null) {
req(BASE_WEB_API.SSO, { token }, null, { validateError: true })
.then(response => {
// do something....
if (callback != null) {
callback();
}
})
.catch(e => {
console.error('failed sso --> ', e);
if (callback != null) {
callback();
}
});
} else if (callback != null) {
callback();
}
}

后端

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* 跳转到猎豹系统
*
* @param response
* @throws Exception
*/
@PostMapping(value = "/cheetah", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String cheetah(@RequestBody SSOVO ssovo,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
// 验证license
if (!licenseService.isValid()) {
LOGGER.error("license is invalid");
return validateTokenError(request, LICENSE_ERROR_MSG);
}
//解析token
Context.Token userToken = Context.getUserInfoFromToken(ssovo.getToken());
if (isNullOrEmpty(userToken.getUserName()) || isNullOrEmpty(userToken.getPassword())) {
LOGGER.warn("token is invalid:{}", ssovo.getToken());
return validateTokenError(request);
}
LOGGER.info("当前单点登录的用户信息为:{}", JSON.toJSONString(userToken));
//验证内置用户是否存在,不存在则创建
SSOUserVO user = ssoService.checkUser(userToken.getUserName(), Context.getCmsContext());
if (user != null) {
// 执行登录
user.setPassword(userToken.getPassword());
return ssoLogin(request, response, user);
}
//异常时跳转到登录页
return validateTokenError(request);
} catch (Exception e) {
LOGGER.error("sso登录失败:{}", e.getMessage());
return validateTokenError(request);
}
}

private String validateTokenError(HttpServletRequest request) {
return validateError(request, SSO_VERIFICATION_ERROR_MSG);
}

private String validateTokenError(HttpServletRequest request, String msg) {
return validateError(request, msg);
}

private String validateError(HttpServletRequest request, String msg) {
HttpSession session = request.getSession();
if (session != null) {
//使session失效
session.invalidate();
}
SSOErrorVO errorVo = new SSOErrorVO(SSO_VERIFICATION_ERROR, msg);
return JSON.toJSONString(errorVo);
}
/**
* 执行登录
*
* @param request
* @param response
* @param userToken
* @return
* @throws IOException
* @throws ServletException
*/
private String ssoLogin(HttpServletRequest request, HttpServletResponse response, SSOUserVO userToken) throws IOException, ServletException {
try {
//登录
UsernamePasswordAuthenticationToken authReq
= new UsernamePasswordAuthenticationToken(userToken.getUserName(), userToken.getPassword());
authReq.setDetails(new WebAuthenticationDetails(request));
Authentication auth = authenticationManagerBean.authenticate(authReq);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession(true);
// 永不超时
session.setMaxInactiveInterval(-1);
//TODO 静态导入
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
baymaxLoginSuccessHandler.onAuthenticationSuccess(request, response, auth);
} catch (AuthenticationException failed) {
LOGGER.warn(
"sso: InternalAuthenticationServiceException occurred while trying to authenticate the user.",
failed);
SecurityContextHolder.clearContext();
baymaxAuthenticationFailureHandler.onAuthenticationFailure(request, response, failed);
validateTokenError(request);
}

return null;
}

/**
* 根据用户名,获取用户的token
*
* @param userName
* @param response
* @return
*/
@RequestMapping(value = "/getToken/{userName}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String getToken(@PathVariable(value = "userName", required = false) String userName, HttpServletResponse response) {

try {
return Context.createToken(userName, PasswordUtil.getPlaintextPwd());
} catch (Exception e) {
LOGGER.error("获取token失败:{}", e.getMessage());
formatErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
return null;
}
}

private void formatErrorResponse(HttpServletResponse response, int httpCode, String errorMsg) {
response.setStatus(httpCode);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try (PrintWriter out = response.getWriter();) {
String errorMsgVo = JSON.toJSONString(ImmutableMap.of("code", SSO_GET_TOKEN_ERROR, "message", errorMsg));
out.write(errorMsgVo);
out.flush();
} catch (IOException ex) {
LOGGER.warn("get token :{}", ex.getMessage());
}
}

处理400异常避免出现白页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author Gamehu
* @description 接管400异常,个性化错误提示
* @date 2019/12/19
*/
@RestControllerAdvice(assignableTypes = SSOController.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
@Component
public class SSO400ExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Object defaultErrorHandler(Exception e) {
log.warn("---SSO验证异常--- ERROR: {}", e.getMessage());
return ImmutableMap.of("code", SSO_VERIFICATION_ERROR, "message", e.getMessage());
}
}

引伸阅读:

最近产品提了一个需求,要做菜单优化。

菜单优化:

  1. 重写菜单数据初始化sql脚本,数据结构两层变为三层
  2. 权限数据调整
  3. 兼容原有菜单权限数据

这里面第三点是最麻烦的,兼容原有的菜单数据,我给需求说了两个方案。

PlanA:从时间方面(因为是个小迭代,整个迭代的功能只有一周时间开发)考虑管理员菜单权限保留,其它普通角色菜单权限一律置空,需求也接受(因为客户现场大多数情况都是用管理员账号)。

PlanB:保留原有数据,但是需要1-2天预研一下升级方案是否可行,我提出的方案是直接用sql脚本做,需求也认可,PlanA为兜底方案。

我就提个解决方案,结果这事最后让我支援一下给做了…,所以在这简单记录一下过程产物。

PLV8

简单地说就算PostgreSQL里加个扩展,这个扩展就是V8引擎,是的,你理解的没错,就是Google开源的JavaScript引擎,有了这个扩展那就能在sql里写js代码了,这对于在脚本里写逻辑那可是爽歪歪了。

安装过程我就不多说了,网上有很多,大体流程就是

  1. 从GitHub wget 下来
  2. make install
1
2
3
4
5
#添加扩展
CREATE EXTENSION plv8;

#验证plv8的版本,出来版本号就证明装上了
SELECT plv8_version();

然后你就可以写JS代码了,ES6、coffeeScript等都可以,只要最终是v8能解析的就成。

好现在开始写了,一开始不太了解,打算用存储过程做,但是后来老前辈提醒我,我这个是升级脚本,只需要执行一遍就成,所以没必要做存储过程,最后还得删掉,因为留着没意义。

然后建议我用DO $$的写法,此方式执行完不会留下其它痕迹就跟执行一条长sql一样,贴个代码:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
DO
$$
plv8.elog(INFO, '------------------------------------update user menus start------------------------------------');
buildTree = function(list) {
try {
let temp = {};
let tree = {};
for (let menu of list) {
temp[menu.id] = menu;
}
for (let i in temp) {
if (temp[i].parent) {
if (!temp[temp[i].parent].children) {
temp[temp[i].parent].children = {};
}
temp[temp[i].parent].children[temp[i].id] = temp[i];
} else {

tree[temp[i].id] = temp[i];
}
}
return tree;

} catch (error) {
plv8.elog(ERROR, 'buildTree ' + error);
}
};

getTreeOfMenus = function() {
let cmdb = require('xxx');
let result = cmdb.service.query('default', '{xxx{id name}}', {});
return buildTree(result);
};
let allMenus = getTreeOfMenus();

delete allMenus['xxx'];

let ids = [];
idsOfflatten = function(data) {
try {
for (let id in data) {
ids.push(id);
if (data[id].children) {
idsOfflatten(data[id].children);
}
}
return ids;
} catch (error) {
plv8.elog(ERROR, 'idsOfflatten ' + error);
}
};
let flatIds = idsOfflatten(allMenus);
plv8.elog(INFO, 'new menu ids:' + flatIds);
getNormalMenus = function(menus) {
try {
plv8.elog(INFO, 'old menus : ' + JSON.stringify(menus));
let old_keys = Object.keys(menus);
old_keys.forEach(id => {
if (!flatIds.includes(id)) {
plv8.elog(INFO, 'delete menu id: ' + id);
delete menus[id];
}
});
return menus;
} catch (error) {
plv8.elog(ERROR, 'getNormalMenus ' + error);
}
};

updateMenus = function() {
try {
let ROLE_ADMIN = 'admin',
ROLE_DOMAIN = 'domain';
let query = 'SELECT name, menus, role_type FROM xxx';
let updateAdmin =
'UPDATE xxx SET menus=NULL, last_modified=CURRENT_TIMESTAMP where name = $1';
let updateNormal = 'UPDATE xxx SET menus=$1, last_modified=CURRENT_TIMESTAMP where name = $2';
let execCount=0;
plv8.execute(query).forEach(row => {
let roleType = row.role_type;
if (roleType === ROLE_DOMAIN || roleType === ROLE_ADMIN) {
let adminCount = plv8.execute(updateAdmin, [row.name]);
plv8.elog(INFO, 'update admin user menus is null ,count: '+adminCount );
adminCount>0 ? execCount+=1:null;
return;
}

let newMenus = getNormalMenus(row.menus);
let normalCount = plv8.execute(updateNormal, [newMenus, row.name]);
plv8.elog(INFO, 'update normal user menus , ' + JSON.stringify(newMenus)+',count:'+normalCount);
normalCount>0 ? execCount+=1:null;
});

return execCount;
} catch (error) {
plv8.elog(ERROR, 'updateMenus ' + error);
}
};

let updateCount=updateMenus();
plv8.elog(INFO,'total of successes :'+updateCount);
plv8.elog(INFO,'------------------------------------update user menus end------------------------------------');
$$ LANGUAGE plv8;

小结:

PostgreSQL很强大,这是我初试水,后续有机会会再写写工作中的一些PostgreSQL的实践例子。

其实PostgreSQL几乎可扩展主流的所有编程语言比如C++、Java、nodejs等。

参考文档:

背景

由于我们产品是基于docker做的部署,所以不管在开发过程中还是在处理客户现场问题时,多多少少都要用到一些docker命令,此篇做个简单的记录,把我用到的命令记录下来。

先看图

说命令之前先看图了解下便于更有代入感。

架构图(不包含Dokcer Engine等细节)

命令

各种查看

docker COMMAND --help

查看docker相关命令的信息,里面有每个命令的说明。

docker ps、docker ps -a

docker ps 这是最常用的,查看容器的运行状态,查问题时不ps一下心里都没底,该命令会列出所有正在运行的容器,当然 等同于docker container ls。

另外docker ps -a,可用于显示所有正在运行和退出的容器。

docker info、docker version

docker info 该命令用于获取当前安装的docker版本以及有关操作系统的几条信息。

docker version 列出有关Docker客户端和服务器版本的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Client:
Version: 18.09.7
API version: 1.39
Go version: go1.10.1
Git commit: 2d0083d
Built: Fri Aug 16 14:20:06 2019
OS/Arch: linux/amd64
Experimental: false

Server:
Engine:
Version: 18.09.7
API version: 1.39 (minimum version 1.12)
Go version: go1.10.1
Git commit: 2d0083d
Built: Wed Aug 14 19:41:23 2019
OS/Arch: linux/amd64
Experimental: false
docker search xxx

该命令只有在我自己玩得时候用过(不想重复造轮子),搜索registry上得镜像。

docker images

列出所有的镜像,通常只需要关注REPOSITORY、TAG两列就行。

1
2
3
4
5
6
7
8
9
10
11
root@feature1_dev:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
172.17.162.141:5000/baymax-nginx 1.17.6 231d40e811cd 6 months ago 126MB
172.17.162.141:5000/pg10-cmdb 1.3.0 284de991364f 10 months ago 370MB
172.17.162.141:5000/myflink 1.8.1 96c4d2af10fc 10 months ago 449MB
172.17.162.141:5000/yandex/clickhouse-server 19 58006c9044b7 13 months ago 514MB
172.17.162.141:5000/zookeeper latest f336949ce7a1 19 months ago 148MB
172.17.162.141:5000/redis latest 1babb1dde7e1 20 months ago 94.9MB
172.17.162.141:5000/kafka latest 568143d73a6b 20 months ago 339MB
172.17.162.141:5000/dubbo-admin latest 954bf5f29e96 2 years ago 492MB

docker logs -f container_name

查看容器的日志,我用的也较少。

docker commit -a "gamehu" -m "what f" container_id IMAGE_REPOSITORY:TAG`

通过容器id创建一个新的镜像,

Container

docker start 、stop、restart、rm、kill

高频使用了,后接 container_id/container_name,依次分别为:启动(已存在)容器、停止容器(会进行正常时间等待其停止)、重启重启、删除(已停止)容器、立即停止容器

docker exec -it container_id

使用的较多,通常是为了测试而替换容器内的内容,命令用于访问正在运行的容器,并启用交互模式,可用一些基本的命令。

1
2
3
4
root@feature1_dev:~# docker exec -it 58f5d79c10a3 /bin/bash
root@58f5d79c10a3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@58f5d79c10a3:/#
docker inspect container_id

查看容器的相关信息,用的也较少。

Image

docker run、create

基于镜像创建一个新的容器,run是创建并启动,create是创建但不启动。

示例:docker run -i -t -p 1000:8000 image_name/image_id:TAG,使用镜像,以后台模式启动一个容器,将容器的 8000 端口映射到主机的 1000 端口

docker build <path to docker file>

此命令用于从指定的dockerfile构建镜像。

docker push IMAGE_NAME:TAG

做完镜像推送到镜像仓库。

docker rmi image_id/image_name

删除镜像,通常是处理现场问题,要替换镜像的时候用一用。

docker inspect image_id

查看镜像相关信息,我制作镜像的时候会用一用,用的很少。

docker save image_id> xx.tar

导出镜像,通常是修复现场问题时做该操作,导出已修复的镜像。

docker load < xx.tar

导入镜像,通常是修复现场问题时做该操作,载入已修复后的镜像。

docker tag image_id tag_name

修改镜像的TAG,通常是修复现场问题时做该操作。

感谢

本文引用的内容,如有侵权请联系我删除,给您带来的不便我很抱歉。


最近看了李笑来先生写的《区块链小白书》对其中的理解一个新事物正确的姿势在此做一个备忘。

1. 不要滥用类比

你将要尝试着学习并理解的,是一个前所未有的重大创新,其中有很多你一下子难以理解透彻的概念,很少有人能一下子全都弄明白……

在这样的时候,人们会不由自主地抄近路走捷径 —— 滥用类比:

“哦! 我明白了,这就好像是……”

比如,你经常会看到人们挣扎着理解了半天,突然冒出一句:“哦,我懂了,比特币就是电子黄金!” 类比是约等号(≈),而“这就是”是等号(=),约等号和等号之间的差别有时甚至超过十万八千里。 比特币与电子黄金之间的关系甚至干脆就谈不上是约等于…… 全然不是一个东西。

在理解全新事物的时候,滥用类比的危害非常大,因为你压根就找不到什么过往已经存在的东西真的和这个创新竟然一模一样 —— 否则,它也不可能被称为创新了,是不是?

这种不恰当的类比被滥用多次之后,就再也没办法形成正确的理解了 —— 因为理解一个创新需要理解多个前所未有的概念,每个都做了不恰当的类比之后,多次非常不恰当的约等于拼接起来之后,无论如何都没办法达到一个与正确理解相近的效果。

请务必注意,每次你的脑子里不由自主地冒出 “这就好像……” 这个念头的时候,你都要把它强压回去。

2. 重复重复再重复

遇到暂时无法理解的概念,不要担心、不要纠结、不要停顿,你要做的事情很简单:

  • 继续读下去;
  • 读完之后再重复读很多次……

这是学习任何新知识或者在任何新领域探索的 “必杀技”。这背后有一个重要的原理:

绝大多数难以理解的知识,是因为它内部有很多 “前置引用”。

所谓的前置引用,就是一个在后面才能深入理解的概念竟然在此之前已经被引用了,导致的结果是学习者总是处于懵懂的状态。学校里的知识却不是这样的,学校里的知识总是线性层层递进的,理解了前面,就能理解后面…… 关于 “前置引用”。

“硬着头皮读完,而后重复读很多次” 这个策略,就是可以轻松突破 “前置引用” 所设置的障碍。这个技巧,事实上可以用在任何领域。

3.借助群智的力量

快速掌握新知识,快速适应新领域,还有个重要的技巧,就是借助群智的力量。事实上,在学校里,你早就应该发现这个技巧了 —— 如果你能跟那些学霸经常聊天,经常玩耍,你就会发现总是在不经意之间,很多重点难点就那样轻而易举地被解决掉了……

这首先因为人是社交动物,然而更为重要的是,交流这个东西,随意的交流总是比刻意的交流更为有效 —— 因为随意的交流总是刻意解决那些连你自己都没意识到的问题…… 可偏偏,这些你自己意识不到的问题恰恰是最重要甚至最关键的问题。如果不借助这种群智的力量,很难想象还有什么办法可以解决这种隐秘的关键问题。

本文引用的内容,如有侵权请联系我删除,给您带来的不便我很抱歉。

背景

过去一年多的时间,整个产品是从0到1的,整个前端团队也几乎是从0到1(大多都是后端转的前端),被产品迭代的车路推动,滚滚向前,处于野蛮生长的阶段。最近产品大体进入了一个思考过去未来的阶段,相对较平缓。可以有喘息的时间干点想干的事情了。

你知道的,干一行爱一行,既然在前端了就想着咋个把它做的更好,因为以前做后端,后端的工程化的工具箱(CI、CD、监控…有一大堆实践)是很成熟的也较容易搭建起来,不过前端相对就很陌生了,据说也没那么容易。

记录

刚好关注了【前端早早聊】,发现里面有这部分的内容,盗了两张图,让我对前端工程化有了很宽的认识,做个记录,后续慢慢实践。

本文引用的图片,如有侵权请联系我删除。

最近看《圆桌派》聊的一个话题斜杠:理想还是欲望?一专还是多能?。想在这儿随便说说。

这个词其实前两年说的比较多,当时另一个说法是一个人的标签很多。为什么出现的频率高,也流行,更多的原因是在于那其实是大大多数的梦想。

里面窦先生说了一下,欲望和理想的区别:一定要结果的就是欲望,热爱过程的是理想。所以对于追求斜杠,你是出于欲望还是理想?从我个人来说,我对斜杠的追求我归为欲望,因为很显然我是需要结果的,不管是物质上的还是精神上的我都是有所求的。

我一直认为,斜杠青年:其实是一个很奢侈的一个愿望。

我比较俗啊,我对斜杠的定义是,每个斜杠都应该是一份事业且他们是相互独立(跨界)的,就像《圆桌派》里说的一样你虽然干了很多份事业但是其实他们都是有关联性或者有递进性的这其实不算是斜杠,因为你始终还是在那个圈里。

既然是事业那就肯定会有付出和回报当然咱们谈的不仅限于钱,最起码你要花时间和精力去做,且会收到反馈。人的时间和精力都是有限的,你把有限的时间和精力花在了多个不同的行业,且都得到反馈,这才算是斜杠。

比如你上午卖炒河粉,下午coding,晚上酒吧驻唱。这我觉得可以说是:餐饮/互联网/演艺,三个斜杠。

所以为什么说斜杠是很奢侈的,首先你的精力要跟上,其次你的时间能自己做主,然后你还得技术过硬。当然跟现实生活中一样奢侈品再贵,也总有些人是能消费的。所以不是说斜杠就没有,只是那是很小的一部分。

大家别把斜杠玩坏了,也别一味的飘飘然的追求斜杠,很多时候你自己要先静一静想一想,别听风就是雨的,不仅飞不高还摔得疼。

就算你有这个欲望,不得现有实力和条件吗,先让自己有这些条件然后再想吧。

延宕其实直白的说就是拖延的意思,有人就感觉了,装逼非要搞个不常见的,就是因为不常见所以你才会好奇啊,才会去查一下这个词时代表什么意思。良苦用心在于为了加深印象撒。

正文

此篇内容源起最近要准备进行一次分享,筛选分享主题时,想到了拖延,首先我自身就有这个问题,然后我敞开怀抱感受了一下,身边很多人都或多或少存在拖延的问题。

目录

  • 拖延原因分析
  • 拖延的特征
  • 造成的危害
  • 怎么破

首先这篇不是一个从心理学角度写的东西,当然咱也没这个段位。这是一个不严谨但具有严重个人色彩的分享,只在于刨析自己从而能给朋友们提供一些思考和建议。

拖延原因分析

白话点说可能有下面四个原因:

  1. 对成功所需的能力缺乏自信
  2. 对要去完成某个任务有反感心理:认定做事的过程中会遭遇很多困难,结局也会很惨
  3. 目标和回报太遥远,感受不到对我有什么意义
  4. 无法自我约束,例如容易冲动和分心

加粗部分是我认为对我影响最突出的部分。

首先咱们得说导致拖延的因素有很多,具体你是哪一类,你可以买本《拖延心理学》对号入座。我暂时认为自己时拖延早期患者,所以列出的主要是自己审视有的。

目标和回报太遥远,感受不到对我有什么意义

先分析一下,目标和回报太遥远,感受不到对我有什么意义,这个点其实很有意思,你回过头看一下自己列的目标也好还是flag之类的…,大多数都会有一个比较共通的点,那就是列的太大太虚,然后也没有反馈的环节。

上图所示就是以前我列的目标,注意看打勾的标识完成的,红圈的表示未达成的。我来自我检讨一下啊,首先打圈的一看就不符合smart原则,当然也没法用PDCA那套。第二个原因是这几个都没有进行反馈设定,这就让你总感觉这个目标好像老离我挺远,慢慢的就觉得没意思了,也没用积极性了。

但是相反打勾的我都完成了,主要有两方面的原因。

  1. 这几个都是有明显的反馈环节的,比如团队分享,首先意义对我和对同事而言都是正向的。分享完之后也能收到同事给与的一些讨论和评价,这也是一个反馈的环节。这就让这件事变得有结束有结果有意义了,当然就有积极性。
  2. 这件事很具体,也比较小,跨越的周期不长,比较符合smart原则,事情就变得可达。
无法自我约束,例如容易冲动和分心

先根据 Tim Urban在TED的演讲,刨析一下拖延时的大脑活动情况,我觉得真是讲到我心里去了,具体大家可以搜一下演讲的名字叫<你有拖延症吗>。

如上图在有拖延症的大脑里会有一只Monkey(分心/诱惑),它经常会跳出来,告诉你其实你的时间还有很多,要不我们逛逛YouTobe、BiliBIli…,它会不断的骚扰你,最终你会沦陷让其掌握大脑。其实就是抵御不了诱惑,容易分心。那什么时候我们会突然警醒呢,就是有一个东西出现的时候,如下图,这个东西叫恐慌。它一来Monkey就被吓跑了,但是往往这个时候,已经来不及了,被动了。

拖延特征

我们说说怎么辨别,你是否存在拖延这个问题。

  1. 看它是不是让你烦恼不已

    内在结果:必须承受某些内在情绪的折磨,从恼怒、后悔到强烈的自我谴责和绝望

    外在结果:影响家庭、工作、社交….

  2. 在一开始,你往往信心满满

    在完成目标的一开始,往往信心满满 ,但是最后其实都不能很好的的完成或者说完成不了,往往整个过程如下图。

  3. 自我

    自我价值感 =能力(具有独立性,反对受控制) =表现(通过拖延,“我”说了算),不愿意遵守那些不是我们自己所制定的规则并顺应别人的需要 。

  4. 追求完美

    咋一看还挺牛x,原来拖延还有这么美丽的特征,但是注意我们这儿说的是适应不良型的完美主义,“完美主义”者往往对自己期待过高,不够现实,当无法实现这样的要求是,就不知所措。失望之余,通过拖延让自己从中退却。适应不良型的完美主义,对自己的要求跟对自己的表现的期待之间存在一种矛盾。

拖延的伤害

怎么破

说了那么多,最终咱们得找到解法,当然前提是你有拖延困扰并且你愿意付诸行动改变这个毛病。

以下是我亲身实践且认为效果良好的方子,现在我也还在这么做,这是个持久战别想着10天半个月就有多大的改变,也别相信什么21天就能养成xx习惯,普通人就踏踏实实干就行了。

破法1:你得接受
  • ​ 接受自己能力有限这个事实得人不太会为此过于烦恼 。
  • ​ 接受遵守那些不是我们自己所制定的规则并顺应别人的需要。
破法2:和原因对上号
  • 看看这方面的书以及专业的一些心理视频或者找心理方面的人士咨询,找到自身造成的拖延的原因,才能对症下药,人是个体,个体就有差异千万别总去套别人那套方法论,要找到适合自己的。
破法3:明确的目标与可行性的计划

找到一个目标,然后将它分解成几个小的步骤,跨出小小的第一步,学习怎样记录时间,以及如何优化周围环境使之向有利于成功的方向推进。

  • 目标smart且够小,可达。既非常小,又可以给你带来进步感和成就感。

  • 执行过程中引入监督、奖惩机制

  • 整个执行目标的过程按照PDCA不断循环

  • 训练强大的执行力

    执行能力的基本要素:注意力控制、 认知弹性、 目标设定、 信息处理

    1.启动任务(起始,产生行动的想法)。

    2.维持注意力(持续跟进,专注于一个事项)。

    3.抑制冲动(在行动前有所思考,而不是马上反应)。

    4.转换注意力(从一个事项到另一个事项,转变关注的焦点,有弹性地加以回应)。

    5.流程记忆(记得计划、指令和以前学到的知识,在学习新知识和应对新环境的时候可以记起和运用到旧的知识)。

    6.情绪控制(调整和管理情绪)。

    7.组织材料(获取所需的材料,并依序编排)。

    8.自我监测(具备自我评价的相应语言能力,在必要的时候能够通过自我交谈闯过难关)。

    9.时间管理(时间意识,以及对待时间的务实态度)。

    10.计划(按优先次序考虑问题,找出达成目标的各个步骤,提前为以后的需要和相关进程做好准备)

破法4:学会怎样判断时间**

时间四象限

学会利用零碎时间

预防意外干扰,抵制诱惑

不要太分散精力

找出你的最佳时间

享受你的“自由”时间

  • 主观时间和客观时间,做到平滑过渡。很多人就是忽略客观时间所以才老是导致拖延,时间不是跟着你的意志走的。
  • 列出“非计划”时间,除开必须做的事情,剩下多少时间可以用于实现你的目标,给他排出来。
  • 积极的时间约束(番茄工作法…),主要是提高专注力,人不可能24小时都是专注的,就算你说你可以那请你看看猝死的例子…
破法5:积极一点

记住一点:不断地否定自己或者消极对待是要消耗很多能量地。

领导告诉了我一句话:如果你今年没觉得去年的自己是个SB那你就没有在成长,所以别动不动就沮丧、消极。

所以又对应上前面说的,要从一个个小成就不断地激励自己。

破法6:利用你的身体减轻拖延

锻炼,很多人一提到锻炼就会马上接一个词“身体”,其实锻炼不仅仅是锻炼身体其实还是一种脑力的、心理的活动,锻炼是一种释放,通常锻炼你是不会受其他东西影响的,比如你不能玩手机不能看电视等等…这就迫使你专注起来,并且大脑会开始想事情,有点类似冥想的意思。

你的大脑是比较放空的,你可以进行整理、归纳、思考等一系列活动,同时锻炼完事之后又特别爽,释放了身体和心理上的一些无用的东西。