From 06f3d931100bd538ce131c54867f12da7809ca0f Mon Sep 17 00:00:00 2001 From: 01Petard <1520394133@qq.com> Date: Fri, 10 Jan 2025 13:01:03 +0800 Subject: [PATCH] =?UTF-8?q?doc:=20=E5=A4=87=E4=BB=BD=E6=96=87=E7=AB=A0?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/config.mts | 3 +- "docs/\345\274\200\345\217\221/index.md" | 25 +- ...02\346\225\260\346\240\241\351\252\214.md" | 4 +- ...02\346\225\260\346\240\241\351\252\214.md" | 23 ++ ...45\345\277\227\350\256\260\345\275\225.md" | 276 ++++++++++++++++++ 5 files changed, 316 insertions(+), 15 deletions(-) rename "docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" => "docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265 - \345\217\202\346\225\260\346\240\241\351\252\214.md" (94%) create mode 100644 "docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\345\217\202\346\225\260\346\240\241\351\252\214.md" create mode 100644 "docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\346\227\245\345\277\227\350\256\260\345\275\225.md" diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9b492c1..fd394a7 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -65,7 +65,8 @@ export default defineConfig({ { text: '开发', items: [ - {text: '日志记录最佳实践', link: '/开发/日志记录最佳实践'}, + {text: '最佳实践-参数校验', link: '/开发/最佳实践-参数校验'}, + {text: '最佳实践-日志记录', link: '/开发/最佳实践-日志记录'}, {text: 'Nginx配置文件说明', link: '/开发/Nginx配置文件说明'}, {text: '搭建K8S集群环境', link: '/开发/搭建K8S集群环境'}, {text: '抖音评论区设计', link: '/开发/抖音评论区设计'}, diff --git "a/docs/\345\274\200\345\217\221/index.md" "b/docs/\345\274\200\345\217\221/index.md" index e27d2c5..9d1c66c 100644 --- "a/docs/\345\274\200\345\217\221/index.md" +++ "b/docs/\345\274\200\345\217\221/index.md" @@ -4,15 +4,16 @@ ## 目录 -1. [日志记录最佳实践](./日志记录最佳实践.md) -2. [Nginx配置文件说明](./Nginx配置文件说明.md) -3. [搭建K8S集群环境](./搭建K8S集群环境.md) -4. [抖音评论区设计](./抖音评论区设计.md) -5. [MYDB操作手册](./MYDB操作手册.md) -6. [批量导出zip压缩包和Excel表格](./批量导出zip压缩包和Excel表格.md) -7. [阿里云OSS和内容安全Java实现参考代码](./阿里云OSS和内容安全Java实现参考代码.md) -8. [SSO单点登录的实现原理](./SSO单点登录的实现原理.md) -9. [ThreadLocal原理和使用](./ThreadLocal原理和使用.md) -10. [对Java中多态的理解](./对Java中多态的理解.md) -11. [JDBC和JAVA类型对比](./JDBC和JAVA类型对比.md) -12. [C++的STL库常见函数](./C++的STL库常见函数.md) \ No newline at end of file +1. [最佳实践-参数校验](./最佳实践-参数校验.md) +2. [最佳实践-日志记录](./最佳实践-日志记录.md) +3. [Nginx配置文件说明](./Nginx配置文件说明.md) +4. [搭建K8S集群环境](./搭建K8S集群环境.md) +5. [抖音评论区设计](./抖音评论区设计.md) +6. [MYDB操作手册](./MYDB操作手册.md) +7. [批量导出zip压缩包和Excel表格](./批量导出zip压缩包和Excel表格.md) +8. [阿里云OSS和内容安全Java实现参考代码](./阿里云OSS和内容安全Java实现参考代码.md) +9. [SSO单点登录的实现原理](./SSO单点登录的实现原理.md) +10. [ThreadLocal原理和使用](./ThreadLocal原理和使用.md) +11. [对Java中多态的理解](./对Java中多态的理解.md) +12. [JDBC和JAVA类型对比](./JDBC和JAVA类型对比.md) +13. [C++的STL库常见函数](./C++的STL库常见函数.md) \ No newline at end of file diff --git "a/docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265 - \345\217\202\346\225\260\346\240\241\351\252\214.md" similarity index 94% rename from "docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" rename to "docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265 - \345\217\202\346\225\260\346\240\241\351\252\214.md" index 256b60b..b317499 100644 --- "a/docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" +++ "b/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265 - \345\217\202\346\225\260\346\240\241\351\252\214.md" @@ -1,6 +1,6 @@ -# 日志记录最佳实践 +# 最佳实践 - 参数校验 -image-20250109163531835 +image-20250110124552934 ## 基本原则 diff --git "a/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\345\217\202\346\225\260\346\240\241\351\252\214.md" "b/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\345\217\202\346\225\260\346\240\241\351\252\214.md" new file mode 100644 index 0000000..ba1bbd6 --- /dev/null +++ "b/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\345\217\202\346\225\260\346\240\241\351\252\214.md" @@ -0,0 +1,23 @@ +# 最佳实践 - 参数校验 + +image-20250110125946771 + +## 基本参数管理与校验 + +- 🔒 同一参数字段,在不同接口中应保持统一的最大长度限制,并且确保与数据库中的最大长度相匹配。 +- ✅ 对所有请求参数使用注解的方式进行全面校验,包括但不限于非空、长度及取值范围,。 +- 💼 若输入参数过多,则考虑将它们封装进单一对象内来简化管理。 + +## 安全性校验 + +- 🔍 查询接口除了基本校验外,还需验证用户状态等信息。 + +## 测试与质量保证 + +- 👷‍♂️ 编码完成后,自行测试参数校验逻辑,以发现潜在问题。 + +## 文档与维护 + +- 📝 为每一个参数提供注释,以便他人理解维护。 + +- 📄 接口变更需及时更新至接口文档,保证文档的一致性和完整性。 diff --git "a/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\346\227\245\345\277\227\350\256\260\345\275\225.md" "b/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\346\227\245\345\277\227\350\256\260\345\275\225.md" new file mode 100644 index 0000000..5b7ad93 --- /dev/null +++ "b/docs/\345\274\200\345\217\221/\346\234\200\344\275\263\345\256\236\350\267\265-\346\227\245\345\277\227\350\256\260\345\275\225.md" @@ -0,0 +1,276 @@ +# 最佳实践 - 日志记录 + +image-20250110124335414 + +## 基本原则 + +- ❌ 避免记录重复、无意义的日志,确保每一条日志都有助于调试 +- 🔄 避免在循环中使用日志,以免产生大量冗余数据 +- 📘 只记录必要的参数,而不是完整的对象结构 + +## 风险防范 + +- 🌐 将英文作为日志语言,以预防编码问题 +- 🔨 不滥用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 + ... + ```