1
0

4 Ревизии fe87179679 ... 45de359957

Автор SHA1 Съобщение Дата
  liuchuanwei 45de359957 feat(js):新增js文档 преди 4 месеца
  liuchuanwei f0b95e4322 refactor(spring):重命名 преди 4 месеца
  liuchuanwei 6e1e8bbada fix:修改错误数据和格式 преди 4 месеца
  liuchuanwei 8f7b6fc430 feat(java):合并日志文章 преди 4 месеца

+ 546 - 0
java/怎么打印日志?.md

@@ -0,0 +1,546 @@
+参考:
+* [工作3年啦,你竟然连Java日志体系都没闹懂?说不过去~ ](https://mp.weixin.qq.com/s/8gTsdstUqHdyjLvRkv7nqw)
+* https://www.cnblogs.com/cb0327/p/5759441.html
+
+## logger 使用步骤
+
+1. 引入依赖
+```xml
+<project>
+    <properties>
+        <slf4j.version>1.7.25</slf4j.version>
+        <logback.version>1.1.11</logback.version>
+	</properties>
+    <dependencies>
+        <!-- slf4j -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+    
+        <!-- logback -->
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>${logback.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>${logback.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-access</artifactId>
+            <version>${logback.version}</version>
+        </dependency>
+    </dependencies>
+</project>
+```
+
+2. 配置 logback.xml
+> 实际项目中,可修改下面模板使用
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+    <!-- region 自定义属性 -->
+    
+	<!-- 定义日志文件的存储地址 -->
+	<property name="LOG_DIR" value="/data/logs/logGuide/"/>
+	<!-- 定义日志最大的历史 30天 -->
+	<property name="maxHistory" value="30"/>
+	<!-- 定义日志文件大小 -->
+	<property name="maxFileSize" value="20MB"/>
+
+	<!--
+		%p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
+		%r:输出自应用启动到输出该日志讯息所耗费的毫秒数
+		%t:输出产生该日志事件的线程名
+		%f:输出日志讯息所属的类别的类别名
+		%c:输出日志讯息所属的类的全名
+		%d:输出日志时间点的日期或时间,指定格式的方式: %d{yyyy-MM-dd HH:mm:ss}
+		%l:输出日志事件的发生位置,即输出日志讯息的语句在他所在类别的第几行。
+		%m:输出代码中指定的讯息,如log(message)中的message
+		%n:输出一个换行符号
+	-->
+	<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
+	<property name="pattern" value="%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level  %msg%n"/>
+    
+    <!--endregion-->
+
+	<!--
+		Appender: 设置日志信息的去向,常用的有以下几个
+            ch.qos.logback.core.ConsoleAppender (控制台)
+            ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
+            ch.qos.logback.core.FileAppender (文件)
+	-->
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<!-- 字符串System.out(默认)或者System.err -->
+		<target>System.out</target>
+		<!-- 对记录事件进行格式化 -->
+		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+			<pattern>${pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->
+		<file>${LOG_DIR}/info.log</file>
+		<!-- 当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<!-- 必要节点,包含文件名及"%d"转换符,"%d"可以包含一个java.text.SimpleDateFormat指定的时间格式,默认格式是 yyyy-MM-dd -->
+			<fileNamePattern>${LOG_DIR}/info_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>${maxFileSize}</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,如果是6,则只保存最近6个月的文件,删除之前的旧文件 -->
+			<maxHistory>${maxHistory}</maxHistory>
+		</rollingPolicy>
+		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+			<pattern>${pattern}</pattern>
+		</encoder>
+		<!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>INFO</level>
+			<!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
+			<onMatch>ACCEPT</onMatch>
+			<!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${LOG_DIR}/error.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${LOG_DIR}/error_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>${maxFileSize}</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<maxHistory>${maxHistory}</maxHistory>
+		</rollingPolicy>
+		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+			<pattern>${pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>ERROR</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${LOG_DIR}/debug.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<FileNamePattern>${LOG_DIR}/debug.%d{yyyy-MM-dd}.log
+			</FileNamePattern>
+		</rollingPolicy>
+		<encoder>
+			<Pattern>${pattern}</Pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>DEBUG</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<!--
+		用来设置某一个包或者具体的某一个类的日志打印级别并指定appender
+		<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
+		name:
+			用来指定受此logger约束的某一个包或者具体的某一个类。
+		level:
+			用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+			如果未设置此属性,那么当前logger将会继承上级的级别。
+		additivity:
+			是否向上级logger传递打印信息。默认是true。
+		<logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
+	-->
+
+	<!--
+	<logger name="liu.log.guide.util" additivity="false">
+		<level value="info"/>
+		<appender-ref ref="STDOUT"/>
+		<appender-ref ref="DEBUG"/>
+		<appender-ref ref="INFO"/>
+		<appender-ref ref="ERROR"/>
+	</logger>
+
+	<logger name="liu.log.guide.util.file" additivity="false">
+		<level value="info"/>
+		<appender-ref ref="STDOUT"/>
+	</logger>
+	-->
+	<logger name="liu.log.guide">
+		<appender-ref ref="ERROR"/>
+	</logger>
+
+	<!--
+		也是<logger>元素,但是它是根logger。默认debug
+		level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+		<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
+	-->
+	<root level="INFO">
+		<appender-ref ref="STDOUT"/>
+		<appender-ref ref="DEBUG"/>
+		<appender-ref ref="INFO"/>
+		<appender-ref ref="ERROR"/>
+	</root>
+
+
+</configuration>
+
+```
+
+3. 打印日志
+```java
+public class LogGuideApplication {
+
+    private static Logger logger = LoggerFactory.getLogger(LogGuideApplication.class);
+
+    public static void main(String[] args) {
+        logger.trace("trace ...");
+        logger.debug("debug ...");
+        logger.info("info ...");
+        logger.warn("warn ...");
+        logger.error("error ...");
+    }
+}
+```
+
+
+
+## logger 配置
+logback.xml 的结构:
+```
+configuration
+    |- property
+    |- appender
+    |- logger
+    |- root
+```
+
+**第1种情况:只配置root**
+```xml
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
+		</encoder>
+	</appender>
+	<!-- 将 >= INFO 级别的日志信息,交给 STDOUT 处理-->
+	<root level="INFO">
+		<appender-ref ref="STDOUT"/>
+	</root>
+</configuration>
+```
+结果:控制台只会打印大于等于 INFO 级别的日志信息
+
+**第2种情况:配置一个logger**
+
+```xml
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
+		</encoder>
+	</appender>
+    
+    <logger name="liu.log.guide">
+		<appender-ref ref="STDOUT"/>
+	</logger>
+    
+	<!-- 将 >= INFO 级别的日志信息,交给 STDOUT 处理-->
+	<root level="INFO">
+		<appender-ref ref="STDOUT"/>
+	</root>
+</configuration>
+```
+
+logger 有3个属性:
+* name 日志范围,包名或者类名。root没有name属性,默认根目录。日志范围会优先匹配最精确的包名。
+比如说上面这个例子,在没有name="liu.log.guide"的logger之前,是由root记录所有类包日志的。有了logger之后,包"liu.log.guide"里的日志会交给最精确匹配的logger来记录。
+* level 日志级别,默认继承上级的日志级别。用来筛选不同日志级别的日志信息。
+* additivity 是否将日志信息传递给上级,默认为true
+
+**上面的情况会发生如下过程:**
+1. logger 默认继承了上级root的level,所以会记录所有日志级别level大于等于INFO的日志信息。
+2. logger 将筛选后的日志信息交给appender处理。除此之外,additivity属性默认true,所以还会将日志信息传递给上级logger,即root
+3. root 收到下级logger传递来的日志信息后,将其全部交给自己的appender处理
+4. appender 收到日志信息后,如果设置了levelFilter日志级别过滤器,就会只将过滤后的日志信息输出到目的地。
+> logger上下级关系使有name表示的包或类决定的。比如 name="liu.log" 就是 name="liu.log.guide" 的上级
+
+所以最明显的结果就是:日志级别大于等于INFO的日志都会被记录2次,这2次分别来自于logger和 root
+
+**扩展**
+如果将上面的logback.xml改为如下这样,会发生什么呢?
+```xml
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
+		</encoder>
+	</appender>
+    
+    <logger name="liu.log.guide" level="INFO">
+		<appender-ref ref="STDOUT"/>
+	</logger>
+	<root level="WARN">
+		<appender-ref ref="STDOUT"/>
+	</root>
+</configuration>
+```
+
+## appender 
+参考 https://www.cnblogs.com/cb0327/archive/2004/01/13/5770794.html
+
+### 自定义 appender
+
+> 本身logback提供了AppenderBase和UnsynchronizedAppenderBase两个抽象类(同步和异步),所以我们自定义时,只需要看实际业务继承其中的一个即可。
+
+1. 自定义appender类
+```java
+public class MyDbAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
+
+    Layout<ILoggingEvent> layout;
+
+    //自定义配置
+    String printString;
+
+    @Override
+    public void start(){
+        //这里可以做些初始化判断 比如layout不能为null ,
+        if(layout == null) {
+            addWarn("Layout was not defined");
+        }
+        //或者写入数据库 或者redis时 初始化连接等等
+        super.start();
+    }
+    @Override
+    public void stop() {
+        //释放相关资源,如数据库连接,redis线程池等等
+        System.out.println("logback-stop方法被调用");
+        if(!isStarted()) {
+            return;
+        }
+        super.stop();
+    }
+    @Override
+    public void append(ILoggingEvent event) {
+        if (event == null || !isStarted()){
+            return;
+        }
+        // 此处自定义实现输出
+        // 获取输出值:event.getFormattedMessage()
+        // System.out.print(event.getFormattedMessage());
+        // 格式化输出
+        System.out.print(printString + ":" + layout.doLayout(event));
+
+    }
+    // 省略 setter 和 getter 
+}
+```
+2. logback配置
+```xml
+<configuration>
+	<!-- 自定义appender -->
+	<appender name="MyDbAppender" class="liu.log.guide.appender.MyLogAppender">
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<!-- 日志收集最低日志级别 -->
+			<level>INFO</level>
+		</filter>
+		<layout class="ch.qos.logback.classic.PatternLayout">
+			<pattern>${pattern}</pattern>
+		</layout>
+		<!-- 自定义参数 -->
+		<printString>刘传伟(logback)</printString>
+	</appender>
+
+    <logger name="liu.log.guide">
+		<appender-ref ref="MyDbAppender"/>
+	</logger>
+    <root>
+        <appender-ref ref="MyDbAppender"/>
+    </root>
+</configuration>
+```
+
+> 在 springboot-log-guide 中会用spring 继承logback将日志写入到数据库中
+
+参考:https://www.cnblogs.com/zimublog/p/12923786.html
+
+```java
+@Component
+public class MyLogDbAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
+
+    @Autowired
+    private BusinessLogService businessLogService;
+
+    @PostConstruct
+    public void init() {
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        ThresholdFilter filter = new ThresholdFilter();
+        filter.setLevel("ERROR");
+        filter.setContext(context);
+        filter.start();
+        this.addFilter(filter);
+        this.setContext(context);
+
+        // 定义 logger (和在 logback.xml 中的配置是对应的)
+        Logger logger = context.getLogger("liu.log.guide");
+        logger.setLevel(Level.DEBUG);
+        logger.setAdditive(false);
+        logger.addAppender(MyLogDbAppender.this);
+        // 定义要有下面这句
+        super.start();
+    }
+
+    @Override
+    public void append(ILoggingEvent event) {
+        if (event == null || !isStarted()) {
+            return;
+        }
+        // TODO 根据实际情况替换
+        BusinessLog businessLog = new BusinessLog();
+        businessLog.setLevel(event.getLevel().levelStr);
+        businessLog.setClassPath(event.getLoggerName());
+        businessLog.setMessage(event.getMessage());
+        businessLog.setCreateBy(1L);
+        businessLog.setCreateDate(new Date());
+        try {
+            businessLogService.add(businessLog);
+        } catch (Exception e) {
+            this.addError("上报错误日志失败:" + e.getMessage());
+        }
+    }
+}
+```
+## 对比 log4j.properties
+参考:https://blog.csdn.net/drift_away/article/details/7403658
+
+```properties
+log4j.rootLogger=DEBUG,CONSOLE,A1,im 
+#DEBUG,CONSOLE,FILE,ROLLING_FILE,MAIL,DATABASE
+################### 
+# Console Appender 
+################### 
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 
+log4j.appender.Threshold=DEBUG 
+log4j.appender.CONSOLE.Target=System.out 
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 
+log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 
+#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n
+##################### 
+# File Appender 
+##################### 
+log4j.appender.FILE=org.apache.log4j.FileAppender 
+log4j.appender.FILE.File=file.log 
+log4j.appender.FILE.Append=false 
+log4j.appender.FILE.layout=org.apache.log4j.PatternLayout 
+log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 
+# Use this layout for LogFactor 5 analysis
+
+
+```
+
+
+## 打印日志的最佳实践
+
+参考:[别再乱打日志了,这份 Java 日志规范,应有尽有,建议收藏!!](https://mp.weixin.qq.com/s/UqvMC6t39YTPDa_FqrkbaA)
+
+## 占位符
+slf4j logger 占位符非常简单,并没有类型的区别
+```java
+String name = "中国";
+logger.info("我的名字叫{}", name);
+```
+
+## isDebugEnabled
+
+参考:[Java日志框架中需要判断log.isDebugEnabled()吗?](https://www.cnblogs.com/leiwei/p/14674143.html)
+
+很多代码中都用到了 isDebugEnabled ,如下:
+```java
+if (logger.isTraceEnabled()) {  
+   logger.trace("Detected " + this.multipartResolver);  
+}  
+else if (logger.isDebugEnabled()) {  
+   logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());  
+}
+```
+
+看源码可知,isDebugEnabled() 用来判断当前日志级别是否低于debug,比如info级别高于debug,则不会打印日志。
+```java
+public boolean isDebugEnabled(Marker marker) {
+	final FilterReply decision = callTurboFilters(marker, Level.DEBUG);
+	if (decision == FilterReply.NEUTRAL) {
+		return effectiveLevelInt <= Level.DEBUG_INT;
+	} else {
+		throw new IllegalStateException("Unknown FilterReply value: " + decision);
+	}
+}
+```
+
+**为什么要加if判断?**
+如果不加if判断,即使最终不需要debug日志输出,编译时也会自动执行字符串拼接,多少会消耗一部分性能。
+而加上if判断之后,就会跳过if条件内的代码
+```java
+//代码1
+logger.debug("这是一条debug日志:" + msg);
+//代码2
+if(logger.isDebugEnabled()) {
+	logger.debug("这是一条debug日志:" + msg);
+}
+```
+
+网上有部分人说,不需要加if判断,使用占位符即可。
+```java
+logger.debug("这是一条debug日志:{}", msg);
+```
+
+但在某些情况下,占位符依然不能避免性能消耗。
+```java
+logger.debug("这是一条debug日志:{}", obj.toString());
+```
+如上面这段代码,最终不会打印debug日志,但是会执行 obj.toString()
+
+### 最佳实践
+
+
+==**原则一:如果打印字符串常量,不需要`isDebugEnabled`**==
+
+比较下面两段代码:
+
+```java
+//代码1
+logger.debug("hello, world");
+//代码2
+if(logger.isDebugEnabled()){
+    logger.debug("hello, world");
+}
+```
+
+因为打印的日志是字面常量,没有计算逻辑。两段代码的性能是几乎一样的。添加`isDebugEnabled`反而会导致额外的代码。
+
+==**原则二:如果有参数,且参数只是字符串常量或计算简单,使用占位符**==
+
+考虑如下代码,`debug`方法包含了参数`user.getName()`。虽然执行`debug`方法时,会计算`user.getName()`,但只是一个简单的**get**方法,没有复杂计算,这时候,也可以不添加`isDebugEnabled`。
+
+```java
+logger.debug("hello, {}", user.getName());
+```
+
+==**原则三:如果有参数,且参数计算复杂,添加`isDebugEnabled`**==
+
+```java
+logger.debug("order price: {}", calculatePrice());
+```
+
+假设`calculatePrice`方法需要经过复杂计算。那么就应该添加`isDebugEnabled`判断,使用如下的代码:
+```java
+if(logger.isDebugEnabled()){
+    logger.debug("order price: {}", calculatePrice());
+}
+```

+ 0 - 5
java/日志/slf4j logger 占位符.md

@@ -1,5 +0,0 @@
-slf4j logger 占位符非常简单,并没有类型的区别
-```java
-String name = "中国";
-logger.info("我的名字叫{}", name);
-```

+ 4 - 2
linux/Linux chkconfig命令详解.md

@@ -32,7 +32,7 @@ chkconfig在没有参数运行时,显示用法。如果加上服务名,那
 # higher quality random number generation.
 ```
 
-
+```shell
 使用范例:
 chkconfig --list        #列出所有的系统服务
 chkconfig --add httpd        #增加httpd服务
@@ -48,4 +48,6 @@ chkconfig mysqld on        #设定mysqld在各等级为on,“各等级”包
 2.chkconfig --add servicename
     在chkconfig工具服务列表中增加此服务,此时服务会被在/etc/rc.d/rcN.d中赋予K/S入口了;
 3.chkconfig --level 35 mysqld on
-    修改服务的默认启动等级。
+    修改服务的默认启动等级。
+
+```

+ 26 - 5
nodejs/nodejs学习总结.md

@@ -6,7 +6,7 @@
 
 NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:
 - 允许用户从NPM服务器**下载别人编写的第三方包**到本地使用。
-* 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
+* 允许用户从NPM服务器下载并安装别人编写的==**命令行程序**==到本地使用。
 - 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
 新版的nodejs已经集成了npm,所以安装了nodejs也就一同安装了npm。可以通过输入 **"npm -v"** 来测试是否成功安装,出现版本提示表示安装成功。
 
@@ -23,8 +23,7 @@ npm install npm@6.14.13 -g
 由于国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。
 
 npm 官方原始镜像网址是:https://registry.npmjs.org/
-淘宝 NPM 镜像:https://registry.npm.taobao.org
-阿里云 NPM 镜像:https://npm.aliyun.com
+淘宝 NPM 镜像::https://npm.aliyun.com
 腾讯云 NPM 镜像:https://mirrors.cloud.tencent.com/npm/
 华为云 NPM 镜像:https://mirrors.huaweicloud.com/repository/npm/
 网易 NPM 镜像:https://mirrors.163.com/npm/
@@ -35,7 +34,7 @@ npm 官方原始镜像网址是:https://registry.npmjs.org/
 # 查看当前使用的镜像
 npm config get registry
 # 配置镜像
-npm config set registry https://registry.npm.taobao.org
+npm config set registry https://registry.npmmirror.com
 ```
 
 淘宝 NPM 镜像是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10分钟 一次以保证尽量与官方服务同步。
@@ -70,7 +69,7 @@ npm install express -g
 全局安装的包一般可提供直接执行的命令。我们通过对一些工具类的包采用这种方式安装,如:gulp, nodemon, live-server, nrm等。
 本地安装的包是与具体的项目有关的, 我们需要在开发过程中使用这些具体的功能。
 
-一个经验法则:要用到该包的命令执行任务的就需要全局安装;要通过require引入使用的就需要本地安装-项目包。
+==**一个经验法则:要用到该包的命令执行任务的就需要全局安装;要通过require引入使用的就需要本地安装-项目包。**==
 
 **注:在项目中不能引用全局安装的包**
 
@@ -87,4 +86,26 @@ npm search 模块名
 
 
 ```
+### pnpm
 
+pnpm 远比 npm 、yarn 快。
+```shell
+# 安装pnpm
+npm install -g pnpm
+```
+
+### 问题
+
+#### certificate has expired
+
+方法1:取消SSL验证
+```shell
+npm config set strict-ssl false
+```
+
+方法2:更换npm镜像源
+```shell
+npm config set registry http://registry.cnpmjs.org
+# 该 registry.npm.taobao.org 地址已更换为 https://registry.npmmirror.com
+npm config set registry registry.npm.taobao.org
+```

+ 3 - 0
spring/SpringMVC 业务流程.md → spring/SpringMvc业务流程.md

@@ -1,6 +1,9 @@
 
 Spring MVC 涉及众多的注解和配置,很难完全记住,最好的办法就是熟悉其中运行流程,将注解和相关配置串联起来,这样掌握的牢固。
 
+参考:
+* [深入理解spring mvc启动过程与原理](https://mp.weixin.qq.com/s/C217uHsmmZZVFOMmRUY4-Q)
+
 ## <Context:annotation-config /> 和 <context:component-scan >
 
 参考:[Spring开启Annotation<context:annotation-config>和<context:component-scan>诠释及区别](https://www.cnblogs.com/leiOOlei/p/3713989.html)

+ 1 - 0
前端/JavaScript运行机制:事件驱动变成详解.md

@@ -0,0 +1 @@
+[JavaScript运行机制:事件驱动编程详解 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/30894022)

+ 98 - 0
前端/js基础语法.md

@@ -1,3 +1,101 @@
+[《前端科普系列-Web:一路前行一路忘川》](https://zhuanlan.zhihu.com/p/91842778)
+
+[《前端科普系列-Node.js:换个角度看世界》](https://zhuanlan.zhihu.com/p/91844181)
+
+[《前端科普系列-CommonJS:不是前端却革命了前端》](https://zhuanlan.zhihu.com/p/113009496)
+
+[《前端科普系列-Babel:把 ES6 送上天的通天塔》](https://zhuanlan.zhihu.com/p/129089156)
+
+[《前端科普系列-ESlint:守住优雅的护城河》](https://zhuanlan.zhihu.com/p/184951182)
+
+## 六种数据类型
+
+JavaScript 有六种数据类型:
+* 数值
+* 字符串
+* 布尔值
+* null
+* undefined
+* 对象
+其中,数值、字符串、布尔值是原始类型,不能再细分了。对象则是合成类型,由多个原始类型的值合成,可以看作是存放各种值的容器。null 和 undefined 一般是看作两个特殊值。
+
+通过以下三种方式可以确定值的类型:
+* typeof
+* instanceof
+* Object.prototype.toString 方法
+
+```js
+// 数值
+var a = 123;
+console.log(typeof a); //number
+// 字符串
+var b = '123';
+console.log(typeof b); //string
+//布尔
+var c = true;
+console.log(typeof c); //boolean
+//undefined
+var d;
+console.log(typeof d); //undefined
+//函数
+var f = function() {console.log('f');}
+console.log(typeof f); //function
+
+```
+
+
+
+## 字符串转为数值
+
+JavaScript内部所有的数值都是以64位浮点数形式存储的,即使整数也是如此。所以 1 和 1.0 是相同的,是同一个数。
+```js
+console.log(1 === 1.0); // true
+```
+
+由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心!
+
+```js
+var a = 0.1, b = 0.2, c = 0.3;
+console.log(a+b); // 0.30000000000000004
+console.log(a+b === c); // false
+
+console.log(0.3/0.1); // 2.9999999999999996
+console.log(0.3-0.2); // 0.09999999999999998
+console.log(0.2-0.1); // 0.1
+console.log(0.3-0.2 == 0.2-0.1); // false
+```
+```
+
+## 转为布尔值
+
+```js
+var a = '';
+if (a) {console.log('a:true');};
+var b = ' ';
+if (b) {console.log('b:true');};
+var c = 0;
+if (c) {console.log('c:true');};
+var d = 'true';
+if (d) {console.log('d:true');};
+var e = 'false';
+if (e) {console.log('e:true');};
+var f = {};
+if (f) {console.log('f:true');};
+var g = null;
+if (g) {console.log('g:true');};
+var h;
+if (h) {console.log('h:true');};
+```
+
+如果JavaScript认为某个位置应该是布尔值,会将该位置的值自动转为布尔值。转换规则是:除了以下6个值会被转成false,其他值都视为true。
+* undefined
+* null
+* false
+* 0
+* NaN
+* '' 或 "" (空字符串)
+
+
 ## 数组 Array
 ### 定义
 ```js