forked from xuxing409/blog-demo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
316 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# 最佳实践 - 参数校验 | ||
|
||
<img src="https://cdn.jsdelivr.net/gh/01Petard/imageURL@main/img/202501101259876.png" alt="image-20250110125946771" style="zoom:60%;" /> | ||
|
||
## 基本参数管理与校验 | ||
|
||
- 🔒 同一参数字段,在不同接口中应保持统一的最大长度限制,并且确保与数据库中的最大长度相匹配。 | ||
- ✅ 对所有请求参数使用注解的方式进行全面校验,包括但不限于非空、长度及取值范围,。 | ||
- 💼 若输入参数过多,则考虑将它们封装进单一对象内来简化管理。 | ||
|
||
## 安全性校验 | ||
|
||
- 🔍 查询接口除了基本校验外,还需验证用户状态等信息。 | ||
|
||
## 测试与质量保证 | ||
|
||
- 👷♂️ 编码完成后,自行测试参数校验逻辑,以发现潜在问题。 | ||
|
||
## 文档与维护 | ||
|
||
- 📝 为每一个参数提供注释,以便他人理解维护。 | ||
|
||
- 📄 接口变更需及时更新至接口文档,保证文档的一致性和完整性。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
# 最佳实践 - 日志记录 | ||
|
||
<img src="https://cdn.jsdelivr.net/gh/01Petard/imageURL@main/img/202501101243595.png" alt="image-20250110124335414" style="zoom:50%;" /> | ||
|
||
## 基本原则 | ||
|
||
- ❌ 避免记录重复、无意义的日志,确保每一条日志都有助于调试 | ||
- 🔄 避免在循环中使用日志,以免产生大量冗余数据 | ||
- 📘 只记录必要的参数,而不是完整的对象结构 | ||
|
||
## 风险防范 | ||
|
||
- 🌐 将英文作为日志语言,以预防编码问题 | ||
- 🔨 不滥用Error级别的日志,减轻紧急响应负担 | ||
|
||
- 🚫 不要在日志记录过程中阻断业务流程 | ||
|
||
###### 错误方式 | ||
|
||
不要用可能会发生空指针异常的对象或方法 | ||
|
||
```java | ||
public void createShop(Shop shop){ | ||
log.info("create and print log: {}", shop.getName().toLowerCase()); | ||
} | ||
``` | ||
|
||
## 性能保障 | ||
|
||
- 💡 使用专用日志库,而非标准输出,来提高性能并便于管理 | ||
|
||
###### 错误方式 | ||
|
||
```java | ||
public void love() { | ||
System.out.println("i love java..."); | ||
// 业务逻辑 | ||
... | ||
} | ||
``` | ||
|
||
###### 原因:println 内部实现是带锁的 | ||
|
||
```java | ||
public void println(boolean x) { | ||
synchronized (this) { | ||
print(x); | ||
newLine(); | ||
} | ||
} | ||
``` | ||
|
||
##### 正确方式 | ||
|
||
```java | ||
public void love() { | ||
log.info("i love java..."); | ||
// 业务逻辑 | ||
... | ||
} | ||
``` | ||
|
||
- 🔒 统一日志框架减少依赖耦合,提升维护便利性 | ||
|
||
禁用`Log4j`或`LogBack`的API输出日志,而是用`Slf4j`,防止代码和日志强耦合 | ||
|
||
```java | ||
// 直接使用 | ||
@Slf4j | ||
public class Test {} | ||
|
||
// 或者直接引入 | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
private static final Logger log = LoggerFactory.getLogger(xxx.class); | ||
``` | ||
|
||
- ⚙️ 根据日志级别控制输出,防止不必要的资源耗费 | ||
|
||
##### 错误方式 | ||
|
||
```java | ||
public void hello(String name) { | ||
log.trace("trace hello" + name); | ||
log.debug("debug hello" + name); | ||
log.info("info hello" + name); | ||
// 业务逻辑 | ||
... | ||
} | ||
``` | ||
|
||
##### 正确方式 | ||
|
||
强烈建议使用占位符来输出内容,防止执行字符串拼接操作,浪费系统资源 | ||
|
||
```java | ||
public void hello() { | ||
if (log.isTraceEnabled()) { | ||
log.trace("trace hello {}", name); | ||
} | ||
if (log.isDebugEnabled()) { | ||
log.debug("debug hello {}", name); | ||
} | ||
if (log.isInfoEnabled()) { | ||
log.info("info hello {}", name); | ||
} | ||
// 业务逻辑 | ||
... | ||
} | ||
``` | ||
|
||
- 👾 错误地使用`e.printStackTrace()`,可能导致严重的资源耗尽 | ||
|
||
###### 错误方式 | ||
|
||
```java | ||
public void hello() { | ||
try { | ||
// 业务逻辑 | ||
... | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
``` | ||
|
||
###### 原因:`e.printStackTrace()`会将产生的字符串存放到字符串常量池,降低可用内存空间 | ||
|
||
```java | ||
public void printStackTrace() { | ||
printStackTrace(System.err); | ||
} | ||
``` | ||
|
||
##### 正确方式 | ||
|
||
```java | ||
public void hello() { | ||
try { | ||
// 业务逻辑 | ||
... | ||
} catch (Exception e) { | ||
log.error("execute failed", e); | ||
} | ||
} | ||
``` | ||
|
||
- 🐛 在日志中禁用复杂对象序列化以防潜在风险 | ||
|
||
###### 错误方式 | ||
|
||
禁止使用JSON序列化工具,这些工具是通过调用对象的get方法将对象进行序列化的,如果对象的get方法被重写则可能会面临抛出异常的风险 | ||
|
||
```java | ||
public void hello(Object data) { | ||
log.info("print log, data={}", JSON.toJSONString(data)); | ||
// 业务逻辑 | ||
... | ||
} | ||
``` | ||
|
||
##### 正确方式 | ||
|
||
推荐自定义toString 方法 | ||
|
||
```java | ||
public void hello() { | ||
log.info("hello and print log, data={}", data); | ||
} | ||
``` | ||
|
||
## 开发简化 | ||
|
||
- 🕵️♂️ 提供详细异常信息有助于问题追踪 | ||
|
||
###### 错误方式 | ||
|
||
```java | ||
public void hello() { | ||
try { | ||
// 业务逻辑 | ||
... | ||
} catch (Exception e) { | ||
// 没有打印异常 e,无法定位出现什么类型的异常 | ||
log.error("execute failed"); | ||
// 没有记录详细的堆栈异常信息,只记录错误基本描述信息,不利于排查问题 | ||
log.error("execute failed", e.getMessage()); | ||
} | ||
} | ||
``` | ||
|
||
##### 正确方式 | ||
|
||
```java | ||
public void hello() { | ||
try { | ||
// 业务逻辑 | ||
... | ||
} catch (Exception e) { | ||
log.error("execute failed", e); | ||
} | ||
} | ||
``` | ||
|
||
- 💼 记录关键函数的输入/输出,增强排错能力 | ||
|
||
```java | ||
public String doSth(String id, String type) { | ||
log.info("start: {}, {}", id, type); // 入口处记录初始值 | ||
String res = process(id, type); | ||
log.info("end: {}, {}, {}", id, type, res); // 出口处记录结果 | ||
} | ||
``` | ||
|
||
- 🤝 利用条件语句前后的日志,简化程序跟踪 | ||
|
||
```java | ||
public void doSth() { | ||
... | ||
if (user.isVip()) { | ||
log.info("vip member, Id: {}, start vip", userId()); | ||
// 会员逻辑 | ||
} else { | ||
log.info("该用户是非会员, Id: {}, 开始处理非会员逻辑", userId()); | ||
// 非会员逻辑 | ||
} | ||
... | ||
} | ||
``` | ||
|
||
- 📍 添加Trace ID用于关联相关事件流,串联一次性的日志链路 | ||
|
||
```java | ||
import javax.servlet.ServletResponse; | ||
import javax.servlet.http.HttpServletRequest; | ||
import java.io.IOException; | ||
import java.util.UUID; | ||
|
||
@Component | ||
public class MDCCFilter implements Filter { | ||
|
||
private static final String TRACE_ID = "traceId"; | ||
|
||
@Override | ||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | ||
throws IOException, javax.servlet.ServletException { | ||
try { | ||
// 为每个请求生成唯一的 Trace ID | ||
String traceId = UUID.randomUUID().toString(); | ||
MDC.put(TRACE_ID, traceId); | ||
|
||
// 如果需要,可以从请求中提取更多上下文信息,例如用户 ID | ||
if (request instanceof HttpServletRequest) { | ||
HttpServletRequest httpRequest = (HttpServletRequest) request; | ||
String userId = httpRequest.getHeader("X-User-Id"); // 假设用户 ID 在请求头中 | ||
|
||
if (userId != null) { | ||
MDC.put("userId", userId); | ||
} | ||
} | ||
|
||
// 执行后续的 Filter 链 | ||
chain.doFilter(request, response); | ||
} finally { | ||
// 清理 MDC,避免线程数据污染 | ||
MDC.clear(); | ||
} | ||
} | ||
} | ||
|
||
// 日志格式 | ||
%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}] | ||
%logger{36} - %msg%n | ||
... | ||
``` |