0%

背景

由于某些客户现场以及我们公司自身的要求,产品上线之前都会有一轮的安全扫描,用AppScan等这类工具扫一扫,最近就处理了几个安全扫描的问题,虽说处理的比较原始但是还是需要记录一下,你晓得哇,因为比较折磨人。

工作原理

什么交XSS、SQL注入网上一大堆我就不出丑了哈,要解决这块问题那就先得搞清楚AppScan这类软件的工作原理。

AppScan 工作原理小结如下:

  • 通过爬行配置的整个 Web 应用,了解其结构找到接口等信息
  • 根据分析的结果,发送修改后的Request 进行攻击尝试(扫描规则库模拟XSS、SQL注入等操作)
  • 最后分析 Respone ,验证是否符合预期是否存在安全漏洞

所以由此看出处理XSS、SQL注入等问题只能在第二个和第三个环节出手。

处理姿势

处理这类问题我所知道的通常就3种姿势:

  1. 拦截

    触发了这类校验直接拦截掉并提示不让其做任何操作,简单粗暴但是不人性化。经常让人很懵逼。

  2. 替换

    把可触发这类校验的关键字全部替换为其它字符或者转换为字符串等。这种容易破坏原有的表达。

  3. 加密

    这是种欺骗扫描软件的方式,直接前后端约定加密方式,对所有的输入进行统一加密,后端再统一解密,这样扫描软件识别不了任何关键字。我同事就这样干过,虽然说性能上会有一点问题但是好在不用动任何代码。嗯,这种只能说骚操作。

实操

最后和架构师定的方式是通过filter+正则这种最原始最简单的方式来做,我们这是toB的运维系统所以让大家失望了没上高大上的安全策略。

输入流

需要先搞清楚为什么需要处理输入流,因为 reqeust.getInputStream 方法只能读取一次。我们可以大概捋一下是咋回事。

我们需要输入流所以需要调用reqest.getInputStream(),getInputStream返回值为ServletInputStream,所以我们先看看ServletInputStream。

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
public abstract class ServletInputStream extends InputStream {
protected ServletInputStream() {
}

public int readLine(byte[] b, int off, int len) throws IOException {
if (len <= 0) {
return 0;
} else {
int count = 0;

int c;
while((c = this.read()) != -1) {
b[off++] = (byte)c;
++count;
if (c == 10 || count == len) {
break;
}
}

return count > 0 ? count : -1;
}
}

public abstract boolean isFinished();

public abstract boolean isReady();

public abstract void setReadListener(ReadListener var1);
}

然后知道是继承自InputStream,所以我们先看InputStream,注意read和reset方法。

read方法告诉我们会从输入流一直读取下一个字节、如果以达到末尾侧返回-1。

reset告诉我们可以重置读取的位置。

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
public abstract class InputStream implements Closeable {

// MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
// use when skipping.
private static final int MAX_SKIP_BUFFER_SIZE = 2048;

/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;

/**
* Repositions this stream to the position at the time the
* <code>mark</code> method was last called on this input stream.
*
* <p> The general contract of <code>reset</code> is:
*
* <ul>
* <li> If the method <code>markSupported</code> returns
* <code>true</code>, then:
*
* <ul><li> If the method <code>mark</code> has not been called since
* the stream was created, or the number of bytes read from the stream
* since <code>mark</code> was last called is larger than the argument
* to <code>mark</code> at that last call, then an
* <code>IOException</code> might be thrown.
*
* <li> If such an <code>IOException</code> is not thrown, then the
* stream is reset to a state such that all the bytes read since the
* most recent call to <code>mark</code> (or since the start of the
* file, if <code>mark</code> has not been called) will be resupplied
* to subsequent callers of the <code>read</code> method, followed by
* any bytes that otherwise would have been the next input data as of
* the time of the call to <code>reset</code>. </ul>
*
* <li> If the method <code>markSupported</code> returns
* <code>false</code>, then:
*
* <ul><li> The call to <code>reset</code> may throw an
* <code>IOException</code>.
*
* <li> If an <code>IOException</code> is not thrown, then the stream
* is reset to a fixed state that depends on the particular type of the
* input stream and how it was created. The bytes that will be supplied
* to subsequent callers of the <code>read</code> method depend on the
* particular type of the input stream. </ul></ul>
*
* <p>The method <code>reset</code> for class <code>InputStream</code>
* does nothing except throw an <code>IOException</code>.
*
* @exception IOException if this stream has not been marked or if the
* mark has been invalidated.
* @see java.io.InputStream#mark(int)
* @see java.io.IOException
*/
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}

所以从上面可以得知ServletInputStream是没有重写r关键的reset方法,所以行为是与InputStream保持一致的即输入流读取一遍之后就没了后续就一直返回-1。

所以解决办法就是找到重写reset的方法的类,即就找到我们常用的ByteArrayInputStream也是继承自InputStream,但是其重写了reset等方法。

我们看下源码:

注意read里面的pos变量,它是标识现在读取的流的位置,所以如果我们想多次读取输入流,需要调用上面说的reset方法重置pos为0。

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
public
class ByteArrayInputStream extends InputStream {
/**
* The currently marked position in the stream.
* ByteArrayInputStream objects are marked at position zero by
* default when constructed. They may be marked at another
* position within the buffer by the <code>mark()</code> method.
* The current buffer position is set to this point by the
* <code>reset()</code> method.
* <p>
* If no mark has been set, then the value of mark is the offset
* passed to the constructor (or 0 if the offset was not supplied).
*
* @since JDK1.1
*/
protected int mark = 0;

/**
* The index of the next character to read from the input stream buffer.
* This value should always be nonnegative
* and not larger than the value of <code>count</code>.
* The next byte to be read from the input stream buffer
* will be <code>buf[pos]</code>.
*/
protected int pos;
/**
* Reads the next byte of data from this input stream. The value
* byte is returned as an <code>int</code> in the range
* <code>0</code> to <code>255</code>. If no byte is available
* because the end of the stream has been reached, the value
* <code>-1</code> is returned.
* <p>
* This <code>read</code> method
* cannot block.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream has been reached.
*/
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}

/**
* Resets the buffer to the marked position. The marked position
* is 0 unless another position was marked or an offset was specified
* in the constructor.
*/
public synchronized void reset() {
pos = mark;
}

这些搞清楚之后就是看怎么能在到咱们的filter的时候得到的request是可以读取多次同时又不影响其它地方的读取(比如controller),刚好severlet.api提供了一个叫HttpServletRequestWrapper的东西,刚好提供一种包装(专业名词:装饰器模式)的手法让我们可以包装request请求对象使其可扩展其它能力。包装高富帅哪里都吃得开。

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
/**
* @author Gamehu
* @date 2019/5/9 18:32
* @description sql注入问题,前置处理输入流,避免输入流获取一次以后失效导致系统异常
*/
public class xxxHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(SqlInjectHttpServletRequestWrapper.class);
/**
* 存储请求输入流
*/
private byte[] body;


public SqlInjectHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
try {
body = inputStreamToByte(request.getInputStream());
} catch (IOException e) {
throw RiilExceptionUtils.bizException(e, BaseWebErrorCode.SecurityConstant.ERROR, BaseWebErrorMsg.SecurityConstant.ERROR_MSG);
}
}

/**
* 流转 字节数组
*
* @param is
* @return
* @throws IOException
*/
private byte[] inputStreamToByte(InputStream is) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int ch;
while ((ch = is.read(buffer)) != -1) {
byteStream.write(buffer, 0, ch);
}
byte[] data = byteStream.toByteArray();
byteStream.close();
return data;
}

@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

/**
* 重写方法用于多次获取流,防止读取用于校验过后,后面服务无法获取参数的情况
*
* @return
*/
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {
LOGGER.info("setReadListener");
}

@Override
public int read() {
return bais.read();
}
};


}

OK,到这儿输入流总算搞定了,nice,然后开始Filter上场了。

Filter

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

/**
* @author Gamehu
* @date 2018/12/20 15:18
* @description XSS、SQL注入校验
*/
@WebFilter(urlPatterns = "/*", filterName = "xxFilter")
public class xxFilter implements Filter {


@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
...
//安全扫描问题(sql注入、xss)处理,对参数进行校验
securityScanParamsValidate(filterChain, response, request);
}


/**
* 安全扫描问题(sql注入、xss)处理,对参数进行校验
*
* @param filterChain
* @param response
* @param request
* @throws IOException
* @throws ServletException
*/
private void securityScanParamsValidate(final FilterChain filterChain, final HttpServletResponse response, final HttpServletRequest request) throws IOException, ServletException {
final String paramsAndValues = SecurityScanUtil.extractPostRequestBody(request);
if (StringUtils.isEmpty(paramsAndValues)) {
filterChain.doFilter(request, response);
} else {
//根据参数是否能转换为json,执行不同的校验
parseParamAndValidate(filterChain, response, request, paramsAndValues);
}

}

private void parseParamAndValidate(final FilterChain filterChain, final HttpServletResponse response, final HttpServletRequest request, final String paramsAndValues) throws IOException, ServletException {
try {
final JSONObject paramsObj = JSONObject.parseObject(paramsAndValues);
if (paramsObj.size() == 0) {
filterChain.doFilter(request, response);
} else {
//对参数进行拆分校验,只校验每个参数值
jsonParamValidate(filterChain, response, request, paramsAndValues);
}

} catch (JSONException ex) {
LOGGER.error("isJSONValid,不是有效的JSON字符串,{}", ex.getMessage());
//参数不是合法地json格式则进行整句校验(不进行任何拆分)
notJsonParamValidate(filterChain, response, request, paramsAndValues);
}
}

/**
* 非json格式参数安全问题校验
*
* @param filterChain
* @param response
* @param request
* @param paramsAndValues
* @throws IOException
* @throws ServletException
*/
private void notJsonParamValidate(final FilterChain filterChain, final HttpServletResponse response, final HttpServletRequest request, final String paramsAndValues) throws IOException, ServletException {
final String uri = request.getRequestURI();
// sql注入校验
if (SecurityScanUtil.execSqlInjectValidate(uri, NO_KEY, paramsAndValues)) {
response.sendRedirect(SQL_INJECT_ERROR);
} else if (XssUtil.xssMatcher(uri, NO_KEY, paramsAndValues)) {
//xss校验
response.sendRedirect(XSS_ERROR);
} else {
filterChain.doFilter(request, response);
}
}

/**
* json格式参数安全问题校验(sql注入、xss)
*
* @param filterChain
* @param response
* @param request
* @param paramsAndValues
* @throws IOException
* @throws ServletException
*/
private void jsonParamValidate(final FilterChain filterChain, final HttpServletResponse response, final HttpServletRequest request, final String paramsAndValues) throws IOException, ServletException {
//防sql注入校验
if (SecurityScanUtil.sqlInjectValidate(request.getRequestURI(), paramsAndValues)) {
response.sendRedirect(SQL_INJECT_ERROR);
} else if (SecurityScanUtil.xssValidate(request.getRequestURI(), paramsAndValues)) {
//xss校验
response.sendRedirect(XSS_ERROR);
} else {
filterChain.doFilter(request, response);
}
}

@Override
public void destroy() {
LOGGER.info("destroy");
}


}
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
*
* @author Gamehu
* @description Xss和Sql注入检查 工具类
* @date 2019/5/10 9:54
*/
public class SecurityScanUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityScanUtil.class);
private static final String GRAPHQL = "graphql";

private SecurityScanUtil() {
}

/**
* 单个参数的sql注入校验
*
* @param paramName
* @param value
* @return
*/
public static String sqlInjectionValidate(final HttpServletRequest request, final String paramName, final String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
/*
* 防sql注入,如果存在抛出校验异常
*/
if (SqlInjectUtil.haveSqlInject(request.getRequestURI(), paramName, value)) {
return "sqlError";
}

return value;
}


/**
* 读取输入流中参数
*
* @param request
* @return
*/

public static String extractPostRequestBody(final HttpServletRequest request) {

final Scanner s;
try {
s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
} catch (IOException e) {
throw RiilExceptionUtils.bizException(e, BaseWebErrorCode.SecurityConstant.INPUTSTREAM_ERROR, BaseWebErrorMsg.SecurityConstant.INPUTSTREAM_ERROR_MSG);
}
return s.hasNext() ? s.next() : "";

}


/**
* 效验sql注入问题
* <h3>SQL注入的几种方式:</h3>
* <pre>
* 1) 使用 ' or 语句,将查询条件扩大,实现破坏性查询(操作)
* 2) 使用 ; 将SQL分成两部分,在后面一部分实现破坏性操作
* 3) 使用注释,将后面的条件取消掉,将查询条件扩大,注意MySQL有三种注释的方法,都需要处理
*
* 为了简化处理,这里只考虑字符串类型参数注入情况(整型等其它类型在应用内部类型转换会失败,所以基本可以忽略)
* </pre>
*
* @return
* @throws IOException
*/
public static boolean sqlInjectValidate(final String uri, final String paramsAndValues) {
//获取参数列表以及参数值
final JSONObject paramsObj = JSONObject.parseObject(paramsAndValues);
final Set<String> keys = paramsObj.keySet();
return keys.stream().anyMatch(key -> haveSqlInjectCondition(uri, paramsObj, key));
}

/**
* 判断是否存在sql注入的表达式
*
* @param uri
* @param paramsObj
* @param key
* @return
*/
private static Boolean haveSqlInjectCondition(final String uri, final JSONObject paramsObj, final String key) {
//graphql 不进行校验
if (key.equalsIgnoreCase(GRAPHQL)) {
return false;
}
final String value = convertParamToString(paramsObj, key);
return SqlInjectUtil.haveSqlInject(uri, key, value);
}

/**
* 效验XSS问题
*
* @param uri
* @param paramsAndValues
* @return
*/
public static boolean xssValidate(final String uri, final String paramsAndValues) {
//获取参数列表以及参数值
final JSONObject paramsObj = JSONObject.parseObject(paramsAndValues);
final Set<String> keys = paramsObj.keySet();
return keys.stream().anyMatch(key -> haveXssCondition(uri, paramsObj, key));
}

/**
* 判断是否存在xss的表达式
*
* @param uri
* @param paramsObj
* @param key
* @return
*/
private static boolean haveXssCondition(final String uri, final JSONObject paramsObj, final String key) {
final String value = convertParamToString(paramsObj, key);
//判断是否存在xss攻击问题
return XssUtil.haveXss(value, key, uri);
}

/**
* 处理参数为字符串
*
* @param paramsObj
* @param key
* @return
*/
private static String convertParamToString(final JSONObject paramsObj, final String key) {
final Object obj = paramsObj.get(key);

String value = null;

if (obj instanceof JSONObject) {
value = ((JSONObject) obj).toJSONString();
}

if (obj instanceof JSONArray) {
value = ((JSONArray) obj).toJSONString();
}

if (obj instanceof String) {
value = (String) obj;
}
return value;
}

}
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

/**
* @author Gamehu
* @date 2019/9/26 12:12
* @description XSS校验的工具类
*/
public class XssUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(XssUtil.class);
private static final String GRAPHQL = "graphql";
private static Pattern[] patterns = new Pattern[]{
// Script fragments
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
// src='...'
Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
// lonely script tags
Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
// eval(...)
Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
// expression(...)
Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
// javascript:...
Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
// vbscript:...
Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
// onload(...)=...
Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
};


/**
* 判断是否存在xss攻击
*
* @param value
* @param key
* @param uri
* @return true表示存在,false表示不存在
*/
public static Boolean haveXss(final String value, final String key, final String uri) {
if (key.equalsIgnoreCase(GRAPHQL)) {
return false;
}
return xssMatcher(value, key, uri);
}

public static boolean xssMatcher(final String value, final String key, final String uri) {
if (StringUtils.isNotBlank(value)) {
Matcher matcher;
for (Pattern pattern : patterns) {
matcher = pattern.matcher(value);
// 匹配
if (matcher.find()) {
LOGGER.error("存在xss风险,URI:{},参数:{},参数值:{}", uri, key, value);
return true;
}
}

}
return false;
}

}
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

/**
* @author Gamehu
* @date 2019/9/26 12:12
* @description SQL注入校验的工具类
*/
public class SqlInjectUtil {
/**
* SQL语法检查正则:只检查一个关键字可能存在误判情况,这里要求必须符合两个关键字(有先后顺序)才算匹配
* 第一组关键字
*/
final static String sqlInjectGroup = "select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|drop|execute";

/**
* 构造SQL语法检查正则
* (?i)忽略字母的大小写,\s.*空白+字符
*/
final static Pattern sqlSyntaxPattern = Pattern.compile("(?i)(.*)\\b(" + sqlInjectGroup + " )\\b\\s.*", Pattern.CASE_INSENSITIVE);


/**
* 读取输入流中参数
*
* @param request
* @return
*/

public static String extractPostRequestBody(final HttpServletRequest request) {

final Scanner s;
try {
s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
} catch (IOException e) {
throw RiilExceptionUtils.bizException(e, BaseWebErrorCode.SecurityConstant.INPUTSTREAM_ERROR, BaseWebErrorMsg.SecurityConstant.INPUTSTREAM_ERROR_MSG);
}
return s.hasNext() ? s.next() : "";

}

/**
* 执行SQL注入校验
*
* @param uri
* @param key
* @param oldValue
* @return
*/
public static boolean haveSqlInject(final String uri, final String key, final String oldValue) {
if (StringUtils.isNotBlank(oldValue)) {
//统一转为小写
final String newValue = oldValue.toLowerCase();
final String logStr = "存在sql注入风险,URL:{},参数:{},参数值:{}";
// 检查是否包含SQL注入敏感字符
if (sqlSyntaxPattern.matcher(newValue).find()) {
LOGGER.error(logStr, uri, key, newValue);
return true;
}
}
return false;
}

}

因为我们用了graphql,有些地方还用了dsl,所以正则是魔鬼,我写崩溃了差点,当然如果有更好的方法请告诉我,万分感谢。

长出一口气…..

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

高难度沟通发生的三个场景

矛盾、争取、推进

近段时间刚好,做的分享和听的分享较多。刚好缘分就是那么奇妙,在一个网课平台刚好看到了这个网课《如何提高Presentation的展现能力》,在此做个记录。

图1所阐述的观点,是我个人觉得做得最契合的。确实我一直认为,Presentation别弄太多文字类或者花哨的东西,PPT应该是辅助我们说明白一个事情,所以主次需要分清。

图2所说几个点

  • 底稿我觉得如果是很正式或者很重要的Presentation是个很好的减少风险的点。

  • 另外关于互动,这点是很重要的,不管是眼神还是肢体还是语言,他能让讲解人和听众建立联系,如果没有互动,那就感觉像是下命令了,你们只管听我只管讲,不能有异议这样的场景特别糟糕。

  • 时间管理特别重要,我特别厌烦那种拖沓的,慢条斯理的Presentation,让氛围很疲惫,这种效果很差,这点我也需要改进,虽然我很注意时间控制,但是也没有做到掐表的程度,但是我认为有些情况是可以掐表,比如做分享。

图3是我最没有底气的点,Presentation事后的修正和反思做得比较少,特别是修正,要想整个Presentation比较有质量,确实这块是不可或缺的,尽量不因此留下遗憾。

Presentation:我觉得可解释为:具有分享、议题、同步等性质的会议。

图片摘自:

嗯,跟前端暧昧的时间还是挺长了,是时候展现真正的技术了。

常在写代码哪有不bug的道理,那对于前端朋友来讲chrome的工具栏基本上能满足大多数场景下的bug排查,对于奉行假如你暂时不够牛逼,那就善用工具的我来说,我心荡漾啊。必须记录一下,当然chrome提供的工具太多了,今天暂且主要聊聊Sources,也是我使用频率Top 2,结合一些网上的一些说明加自身的实践来说一说。

不用再于数字不好看这些细节,我们走的是放荡不羁的路线,咱们挨个说啊。

数字 表头
1 点击该箭头,移动鼠标到页面上定位到页面元素,跳转到Elements 工具栏。
2 用于模拟移动设备上的效果。
3 必用的模块,已加载的全部资源,以域名划分文件夹,调试时从这儿下手找源码,当然我通常直接快捷键Ctrl+P定位文件。
4、5 Filesystem & Overrides 可以加载本地文件夹,把Chrome当成IDE用。
6 Content scripts 扩展工具的脚本,比如百度翻译插件等
7 Snippets 代码片段,不会因为刷新丢失,使用:添加=>保存(ctrl+s)=>运行(Run)=>不用则移除(Remove)
8 源码面板,在此处进行打断点、修改端点等操作
9 调试的快捷键面板
10 变量监察:添加个变量后会一直监察这个变量的值,当前作用域无值时显示< not availble >
11 Call Stack 函数调用栈,会列出断点的调用堆栈列表。
12 Scope 断点所在作用域列表,级别划分如下:
  • Local 当前作用域 展示作用域下的变量
  • Closure (x) 闭包作用域,x是函数名称
  • Script 标签作用域
  • Global 全局作用域Window
13 Breakpoints 源码的断点列表。
14 XHR/fetch Breakpoints 请求断点:ajax和fetch请求都可以在这里打断点并在Call Stack显示调用栈,很方便追踪。
15 DOM Breakpoints 这里列出html的断点。
16 Global Listeners 全局监听器:指的是绑定在 window 对象上的事件。
17 Event Listeners Breakpoints 所有事件的断点:勾选指定类型,比如Mouse/click,则所有的click事件都会被断住。

markdown搞出这个表格太费劲,anyway看着舒服就好,下面聊聊对应上面表格中的一些具体的使用场景。

  • Filesystem Chrome式的IDE,可以编辑各种文件并且在当前页就可以实时刷新看到效果,如果写单个模块时蛮有用的,比较快。修改后ctrl+s保存,修改的是本地文件,刷新可看到效果。
  • Overrides 覆盖网络请求的资源,即html、js、css、图片等资源,注意一定是同域名同路径同名的文件才能产生覆盖效果。勾选,Enable Local Overrides,修改文件后ctrl+s保存,修改的是Save as Overrides到本地的文件,刷新可看到效果。

  • Snippets,也是我使用比较多的功能,以前不知道的时候都是找网上现场的js编辑器,但是受网络和维护的影响,有些时候不好用,而且很多还要区分ES5、ES6,自从有了Snippets,随便写各种代码片段超级好用。 但是要注意变量的作用域问题,所以最好用IIFE方式写代码,避免出现错误。

源码面板

  • 这也是调试的时候使用频率超级高的区域,ctrl+p定位到文件,行号处右键出现对断点操作的一些选项,分新增和编辑两类。
  • Add conditional breakpoint/edit breakpoint ,添加/修改带条件的断点,比如写a===”a”,则表示当a等于”a”时才触发断点,如下图。当条件表达式为真时,触发断点(条件性行断点的颜色为橙色)
  • Blackbox Script,黑盒脚本,很多时候是要引用第三方库或框架的,当我们调试时,调试的对象应该是我们自己写的代码,但很多时候,我们经常在焦灼地进行下一步下一步时,突然代码跳到了第三方库或框架的源码上去,多数情况下我们不会关注这些地方的内容,但是它就要跳过去,这种是比较蛋疼的。黑盒脚本就是用来解决这个问题的,它能够把一个脚本文件标记为 “Blackbox Script”,那么我们就永远不可能进入这个文件内部,这个文件对我们来讲就是一个黑盒子。为什么要强调“永远”呢?因为不仅普通的断点不能访问这个被标记了的脚本,其他的,比如说 DOM 断点、事件断点等等都无法访问那个脚本文件内部。

  • 调试面板:Pause on exceptions,在发生异常的地方停顿

Watch面板

  • 用于监视变量的值。

点击1处,将打开一个内联输入框,您可以在输入框中输入要监视的变量名称。输入完毕,按Enter键,即可将其添加到列表中。监视器将显示添加时变量的当前值。如果变量未设置或找不到,值将显示为

点击2手动刷新变量。注:监视列表不是变量的实时视图,除非逐步执行。当使用断点逐步执行时,监视列表中的值将自动更新

点击3,删除变量。

Call stack

代码暂停时,可以在Call Stack窗口查看当前的调用栈。展示了代码到暂停处的完整执行路径,这让我们能够深入代码去找出导致错误的原因。最后调用函数在最顶上,所以最好别用匿名函数,不利于调用栈查看。

Scope

作用域:显示断点所在的作用域,级别划分如下:

Local 当前作用域 展开作用域下的变量
Closure (x) 闭包作用域,x是函数名称
Script 标签作用域
Global 全局作用域Window

DOM Breakpoints

当改变一个节点或者其子元素时,可以设置一个DOM断点:

  1. 点击Elements面板

  2. 找到想要设置断点的元素

  3. 在此元素上右键

  4. Break on –> Subtree modifications / Attribute modifications / Node removal

    Subtree modifications(子树修改):当前选中的元素,删除、增加其子代或者改变其子代的内容。修改子元素的属性或者当前选中元素有任何改变都不会触发此类型断点
    Attributes modifications(属性修改):当前选中的元素,增加、删除其属性,或者修改某个属性值
    Node Removal(节点移除):移除当前选中的元素

XHR/Fetch Breakpoints

当XHR的请求URL包含某一特定的字符串时,可以暂停调试代码。DevTools会在XHR调用send()那行代码的地方暂停。

  1. 点击Sources面板
  2. 展开XHR Breakpoints小窗口
  3. 点击“增加断点(Add breakpoint)”
  4. 输入一个字符串,只要在某个XHR的请求URL中包含此字符串, 会进入断点(暂停),如下图。

Event Listeners Breakpoints

这个断点类型也算是比较常用的一个了,特别是当我们调试别人的代码时,触发某个事件,想找到对应的代码。事件类型可以是很具体的,比如click事件,也可以是某一类别的事件,比如“鼠标事件”。

  1. 点击Sources面板
  2. 展开Event Listener Breakpoints小窗口
  3. 勾选某一类别的事件或者是某一具体的事件

PS:

感谢:

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

今天看到一篇文章,是以我媳妇儿的男神马云老师为引得文章。

所有为了能更完美的适配我媳妇儿的诉求,当然我就毫不犹豫的要点进去的看看。

我心甚慰啊,是这两天引发我思考最多的文章,特此记录一下。

文章指出底层系统定义:那些驱动你的核心动力。

体现在两个方面:大局观和是非观。

是非观决定你能走多久。什么事能做什么事不能做,自己心里衡量。有界限。

大局观决定你能走多远。你想进步你的格局就要大于你的现状。

所以前者指引方向,后者决定边界。

所以当你想做什么的时候,先自省一下,自己的底层系统,看有没有bug。

其中大局观是我感慨最多也是我认为自己最需要严肃对待的。

虽然我这两年都在有意识的往这方面努力,但是还远远不够。

记录下我粗糙的认识

我认为的大局观,首先需要对抗的就是,老守着自己的一亩三分地,事不关己高高挂起,满足于现状的状态(这也是我在正在经历的阶段),需要跳脱出来,换角度,纵向、横向的看自己看周围看世界。

说起来有点虚,我一开始看别人动不动用跳脱、升华、灵魂这类词语我就特别不屑,总觉得的是吹牛x。但是亲测真的有效,很多时候做某件事的时候我就强迫自己,重新审视一下要做的事。

那怎么才能重新审视呢,那就涉及到要需要换角色换角度了,那怎么才能换角色换角度呢,这个时候就会突然在头脑中会出现,自己扮演产品经理、研发总监(公司副总)、总经理的角色,另外还会出现一个专门发出质疑的“自己”。这时候你就会萌发很多很多想法,这时候你会发现你考虑事项的方式以及处理事项的方式往往会和你一开始你想的有出入(当然这里肯定有一个边界,那就是你得从你的现状出发),这种出入不见得一定优于之前的想法,但是它肯定会带来附加效果,这种附加效果有可能会来的快也可能来得晚,有可能好有可能坏,但是正因为这样我们才能不断修正自己,我想这种修正应该会让我向大局观进一步。

最近在看一本书《系统之美》,看起来有些艰涩,但是真的很受用,我觉得其中有句话就很符合我对大局观的理解

重塑系统,发现更大的世界

对于怎么才叫有大局观,我其实也没摸到精确的定义,但是这不妨碍我想得到她。人神奇的地方不就是能不断的折腾吗。能捏碎也能重塑。

最后

我觉得作为程序员,别顽固(坚守哪个语言第一、接受不了改变…)、别看谁都不顺眼(你看那说话的SB、你看那写代码的SB、你看那群里发图的SB….)、别老想着对抗(产品、ucd、测试、运维….,感觉他么的一个公司全是你的敌人),我们应该多想想如果我保持现状到底跟自己的职业生涯、跟同事、跟领导、跟公司能带来什么。

我们是不是得抽时间找找自己?

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

当你不够牛逼时,那就善用工具

我简单整理了一下我最近比较常用的工具,也会摘一些做一些分析,多偏向于前端,因为最近前端写得多。

chrome devtools

由于尽量不想让自己在编码路上留下的足迹会让后人觉得像屎一样…年初到现在断断续续的都在想办法提升个人的工程能力

关于Lint最早是前两年写了一段时间的nodejs,然后知道了一个叫ESLint的玩意…这东西好用啊,让代码可以不太像屎一样了所以最近就捡起来了,当然工具是辅助最重要的还是基础。

伴随着格言 do not BB,show me code。刚好同事让我帮忙跟他弄一下ESLint,那我想着干脆整个文档,那不就简单了,大家都能用。

以下就是跟我们项目贴合度比较高的ESLint简单的配置教程出来(为博客凑个数,话说确实太懒了文章写的少),然后有时间再分析其原理的东西。

ESLint 配置

安装ESLint相关库

npm install -g eslint 
// 用了 React 需要再安一个babel-eslint  
npm install -D eslint babel-eslint
//如果要用大厂的需安装对应的库,比如用airbnb的
npm install -D eslint-config-airbnb
// 因为要校验 Reac 语法,所以这里需要下载一个 React 语法规则的包  
npm install -D eslint-plugin-react

可能不全,但是总之一句话,别想太多提示差什么包就装什么包就对了。

生成配置文件

node_modules/.bin/eslint --init
//如果全局安装了 可以直接
eslint --init  

会出现一个界面,根据模板创建ESLint的配置文件,最终填完之后大概就是下面这样

演示一把:

选一种方式生成配置文件,可根据需求选择,建议第一个问题选第二种Use a popular style guide(使用大厂的),然后选一个,通常用airbnb的. 跟着界面依次填写下面的内容,最终会在你根目录下生成一个文件名为.eslintrc.js的配置文件 。

根据模板创建配置文件,可以选个模板,也可以自己回答问题

PS E:\eslint> eslint --init
? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? (Use arrow keys)
> Airbnb (https://github.com/airbnb/javascript)
  Standard (https://github.com/standard/standard)
  Google (https://github.com/google/eslint-config-google)

比如选airbnb

module.exports = {
"extends": "airbnb"
};

也可以自己回答问题来创建

> eslint --init

? How would you like to configure ESLint? Answer questions about your style
// 是否校验 Es6 语法
? Are you using ECMAScript 6 features? Yes  
// 是否校验 Es6 模块语法
? Are you using ES6 modules? Yes   
// 代码运行环境,Browser 指浏览器 
? Where will your code run? Browser   
// 是否校验 CommonJs 语法  
? Do you use CommonJS? Yes  
// 是否校验 JSX 语法
? Do you use JSX? Yes   
// 是否校验 React 语法
? Do you use React? Yes 
// 首行空白选择 Tab 键还是 Space
? What style of indentation do you use? Tabs
// 字符串使用单引号 'string' 还是双引号 "string"
? What quotes do you use for strings? Double
// 操作系统
? What line endings do you use? Windows 
// 每行代码结尾是否校验加分号 ;
? Do you require semicolons? Yes
// 以 .js 格式生成配置文件
? What format do you want your config file to be in? JavaScript   

PS:也可以直接在根目录下手动建一个文件.eslintrc.js(后缀也可以是JSON、YAML等),然后手动写配置

贴一个我的(手动写的)

如果只用于JS语法类扫描,可参考但不建议直接使用,因为配置的东西有点多

module.exports = {
"extends": ["airbnb", "prettier","prettier/react"],
"parser": "babel-eslint",
//如果我们想对一些非标准 JS 语法添加 Lint 怎么办呢?有办法,ESLint 还支持我们自定义 parser。 parser是为了非标准语法能生的,plugin是针对符合js语法的规则集合的扩展。
"plugins": [
    "prettier",
    "react",
    "jsx-a11y",
    "import"],
//自定义规则,可以覆盖掉extends的配置。
"rules": {
    "jsx-a11y/click-events-have-key-events":0,
    "jsx-a11y/interactive-supports-focus":0,
    "jsx-a11y/no-static-element-interactions ":0,
    // 'one-var':2,//"error" 或 2 开启规则,使用错误级别的错误
    // 强制驼峰命名规则
    "camelcase": [0, {
        "properties": "never"
    }],
    "prettier/prettier": ["error"],
    "react/prop-types": ["warn"],
    "react/jsx-uses-react": "error",
    "react/jsx-uses-vars": "error",
    "no-unused-vars": 1,//"warn" 或 1 - 开启规则,使用警告级别的错误
    "global-require": 0,//"off" 或 0 - 关闭规则
    "prefer-destructuring": 0,
    "class-methods-use-this": 0,
    "react/no-unused-state": 1,
    "jsx-a11y/no-static-element-interactions":0,
    "import/extensions":0
},
"env":{
    //定义env会带进来一些全局变量,browser会添加所有的浏览器变量比如Windows
    "browser": true,
    "es6": true
},
//当我们将默认的解析器从Espree改为babel-eslint的时候,我们需要指定parseOptions,这个是必须的。parserOptions ESLint 允许你指定你想要支持的 JavaScript 语言选项。默认情况下,ESLint 支持 ECMAScript 5 语法。你可以覆盖该设置,以启用对 ECMAScript 其它版本和 JSX 的支持。
"parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
        "jsx": true// 启用 JSX
    },
    "ecmaVersion": 6
},

};`

配置IDE

VSCode配置需自行上网搜索

启用ESLint插件

配置触发ESLint自动修复的快捷键

ESLint的使用方法

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

冰山模型

这玩意能比较通透的了解自己有几斤几两。

  1. 认知迷茫
    • 危机感的缺失
      • 物质需求基本满足,过于安逸
    • 经验传承的断层
      • 父辈那一代的经验和技能不能很好的借鉴
    • 失败的自我认知教育
    • 文化信仰的缺失

最终的表现就是,同样的起点,同样的终点,迷茫的人会走很多很多弯路。

如何做职业规划

  1. 找到人生目标

    • 马斯洛需求理论
    • 从欲望的角度
  2. 拆解人生目标

    • 逻辑树
  3. 选择职业目标

    • 工具
      • 逻辑树,拆解自己的选择标准
      • 决策矩阵
        • 简化的矩阵,选择维度=2时
    • 标准
      • 感性因素(随心所欲)、理性因素(扬长避短、顺势而为)
  4. 梳理职业路径

    • 职业路径图,搞清楚所在职业整个的路径是什么。
    • 对标管理,每个小目标都可以选择一个最佳实践
      • 对标的目标的评估标准(CQ、RQ、IQ),找到自己与对标对象之间的差距,补足
  5. 制定发展计划

    • 甘特图,小目标转化为计划
  6. 总结:工具包

  7. 延伸问题

    • 如果第一步就卡住了怎么办
      不着急,但是无论无何要把它搞出来。
    • 为什么我的目标不够坚定
      有红利期,就有人扑上去,那我扑不扑。

可能的解法

感谢:

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

最近研发老大牵头组织了异常关于根因分析的讨论,目的在于让大家学会做根因分析,因为往往大家都是直接下结论(给答案),既容易发生矛盾也同时也没有解决根本问题。

现状

1.当有问题出现时,往往根据现象再加上以往的经验或者直接拍出一个结论,这种情况在短期内的效果是可以的,但是从长期来看会掩盖掉很多底层的问题。结果就是问题该出还出,不仅没有提升团队的效率反而降低了团队的效率。

2.当问题出现时就代表甩锅以及互怼的开始,没有经过根因分析,上来先下个结论,开始界定责任,然后开始扯皮。

根因分析

问题描述

(什么时间*-什么地点-什么产品-什么人物-发生什么故障现象-*造成什么影响)

示例

1
2
3
4
5
6
7
时间:2019-09-02
地点:XX现场
产品:XX产品/XX版本
发现人:XX现场交付人员
故障现象描述:客服在正常登陆系统提单的时候报错,并且提交不了工单
【帮助】如有多个故障现象,应分别描述;
结果影响:系统XX时间不可用,导致客户非常不满,造成XXX损失
过程还原

直接叙述工作过程,有问题的环节或阶段,什么人,做了什么事,当时是怎么考虑的,在这个动作后结果是什么。

问题定位

描述最终定位到的直接原因是什么。举个例子,比如某段代码编写存在XXX错误

技术根因分析

引入环节:

  1. 产品设计是否有问题?
  2. 需求分析是否有问题?
  3. 设计环节是否有问题?
  4. 代码编写是否有问题?
  5. 其他

流出环节:

  1. 各评审环节是否有遗漏?
  2. 是否进行研发自测?
  3. 测试场景、测试用例是否覆盖全?
  4. 是否进行了系统测试?
  5. 其他?

确定关键根因是什么

如果有多个根因在逻辑层次上相同,则取关键的原因,根因应该是具体的、客观的、在目前组织能力下可被改进的。

管理根因分析
  • 流程/制度原因:
  • 组织因素:
  • 执行原因:

【帮助】流程/制度方面:考虑组织管理上是否有合适的流程、指导书、管理Checklist;

组织因素方面:考虑人员分配、个人技能、培训、组织环境等原因;

执行方面:考虑计划、监控、沟通方面的原因。

纠正、预防措施
根本原因 措施类型 措施内容 责任人 预定完成日期
技术根因: 例如,XX特性,在大规格、灵活配置等方面需求设计不充分 纠正措施 例如:对XX特性组织进行重新设计,刷新XX方案 2018/11/1
预防措施 例如:更新××技术规范、工具、checklist等等
管理根因:组织管理、流程方面的原因,比如xx,没有按照流程,但是最终还是交付了。 纠正措施
预防措施

小结

上诉的内容,关键还是在于给一个框架,让问题发生人,根据框架的引导,能比较深刻的挖掘出问题的根因,按此框架填写后,往往会伴随着评审,最终判断分析的彻底性以及合理性。

当然这只是一种方式,一段时间实践下来,其实是有助于减少问题发生率以及增加个人问题的处理成本从而倒逼相关人员注意到质量的重要性。

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