Spring Boot项目集成日志系统使用完整指南

Spring Boot项目集成日志系统使用指南

目录

Java日志系统简介

1. 日志系统的作用

日志系统是软件开发中不可或缺的组成部分,主要用于:

  • 问题诊断: 记录程序运行状态和错误信息
  • 性能监控: 记录方法执行时间和资源使用情况
  • 审计追踪: 记录用户操作和系统行为
  • 运维支持: 提供系统运行状态和异常信息

2. Java日志框架发展历程

复制代码
Java 1.4 (2002) → Logging API (内置)
    ↓
Apache Log4j 1.x (2001-2015)
    ↓
SLF4J + Logback (2006-至今) ← 推荐使用
    ↓
Log4j 2.x (2012-至今) ← 高性能选择

3. 主流日志框架对比

框架 优势 劣势 适用场景
Log4j 1.x 成熟稳定,配置灵活 已停止维护,性能一般 老项目维护
Log4j 2.x 性能优秀,功能丰富 配置相对复杂 高性能要求
Logback 性能好,配置简单 功能相对简单 Spring Boot默认
SLF4J 抽象层,统一接口 需要配合具体实现 框架集成

日志类型详解

1. 日志级别 (Log Levels)

1.1 标准日志级别
java 复制代码
// 从低到高的日志级别
TRACE < DEBUG < INFO < WARN < ERROR < FATAL

// 实际使用中的日志级别
DEBUG  // 调试信息,开发环境使用
INFO   // 一般信息,记录程序运行状态
WARN   // 警告信息,潜在问题但不影响运行
ERROR  // 错误信息,程序异常但可恢复
1.2 日志级别使用场景
java 复制代码
@Log4j2
public class UserService {
  
    public User getUserById(Long id) {
        // DEBUG: 详细的调试信息
        log.debug("开始查询用户,ID: {}", id);
      
        try {
            User user = userMapper.selectById(id);
          
            // INFO: 重要的业务信息
            log.info("成功查询用户: {}", user.getUsername());
          
            return user;
        } catch (Exception e) {
            // ERROR: 异常信息
            log.error("查询用户失败,ID: {}, 错误: {}", id, e.getMessage(), e);
            throw new RuntimeException("用户查询失败", e);
        }
    }
}

2. 日志输出目标 (Appenders)

2.1 控制台输出
xml 复制代码
<!-- Logback配置 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>
2.2 文件输出
xml 复制代码
<!-- 滚动文件输出 -->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>./logs/application.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>./logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>
2.3 数据库输出
xml 复制代码
<!-- 数据库输出 -->
<appender name="db" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
        <driverClass>com.mysql.cj.jdbc.Driver</driverClass>
        <url>jdbc:mysql://localhost:3306/logs</url>
        <user>root</user>
        <password>password</password>
    </connectionSource>
</appender>

3. 日志格式 (Patterns)

3.1 常用格式占位符
xml 复制代码
<!-- 标准日志格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>

<!-- 格式说明 -->
%d{yyyy-MM-dd HH:mm:ss.SSS}  <!-- 时间戳 -->
[%thread]                     <!-- 线程名 -->
%-5level                      <!-- 日志级别,左对齐5位 -->
%logger{36}                   <!-- 类名,最大36字符 -->
%msg                          <!-- 日志消息 -->
%n                            <!-- 换行符 -->
3.2 自定义格式示例
xml 复制代码
<!-- 包含MDC信息的格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n</pattern>

<!-- 包含调用位置的格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%line - %msg%n</pattern>

Spring Boot日志配置

1. Spring Boot日志自动配置

1.1 默认配置

Spring Boot默认使用Logback作为日志实现,提供以下自动配置:

yaml 复制代码
# application.yml
logging:
  level:
    root: INFO
    com.zmy.enrollment: DEBUG
    org.springframework.web: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: ./logs/application.log
    max-size: 10MB
    max-history: 30
1.2 依赖配置
xml 复制代码
<!-- Spring Boot Starter Web 自动包含日志依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 显式添加日志依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

2. 项目实际配置分析

2.1 主配置文件 (application.yml)
yaml 复制代码
# Logger Config
logging:
  level:
    com:
      baomidou:
        mybatisplus: ERROR      # MyBatis Plus日志级别
      zmy:
        enrollment: DEBUG       # 项目业务日志级别
    org:
      springframework:
        web:
          client: ERROR         # Spring Web客户端日志级别
    springfox:
      documentation: ERROR      # Swagger文档日志级别
2.2 Logback配置文件 (logback-spring.xml)
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
  
    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] [%X{traceId}] [%X{userId}-%X{loginName}-%X{username}] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
  
    <!-- 应用日志文件 -->
    <appender name="file" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator>
            <Key>systemFlag</Key>
            <DefaultValue>zmbm</DefaultValue>
        </discriminator>
        <sift>
            <appender name="EXTRACTOR_LOG_${systemFlag}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <File>./logs/${systemFlag}/application.log</File>
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>./logs/${systemFlag}/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                        <maxFileSize>512MB</maxFileSize>
                    </timeBasedFileNamingAndTriggeringPolicy>
                    <maxHistory>60</maxHistory>
                </rollingPolicy>
                <encoder>
                    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
                </encoder>
            </appender>
        </sift>
    </appender>
  
    <!-- 根日志配置 -->
    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
  
    <!-- 特定包日志配置 -->
    <logger name="org.springframework.scheduling" level="info" />
    <logger name="com.zmy.enrollment.web.base.filter" level="info" />
    <logger name="com.zmy.enrollment.web.zmbm.task" level="info" />
</configuration>

3. 代码中的日志使用

3.1 Lombok注解使用
java 复制代码
// 使用@Log4j2注解
@Log4j2
@RestController
@RequestMapping("/api/user")
public class UserController {
  
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        log.info("查询用户信息,ID: {}", id);
      
        try {
            User user = userService.getUserById(id);
            log.debug("用户查询成功: {}", user);
            return ResponseEntity.ok(user);
        } catch (Exception e) {
            log.error("查询用户失败,ID: {}, 错误: {}", id, e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

// 使用@Slf4j注解
@Slf4j
@Service
public class UserService {
  
    public void processUser(User user) {
        log.info("开始处理用户: {}", user.getUsername());
      
        if (log.isDebugEnabled()) {
            log.debug("用户详细信息: {}", user);
        }
      
        // 业务逻辑处理
        log.info("用户处理完成: {}", user.getUsername());
    }
}
3.2 手动创建Logger
java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomService {
    private static final Logger logger = LoggerFactory.getLogger(CustomService.class);
  
    public void doSomething() {
        logger.info("开始执行任务");
      
        try {
            // 业务逻辑
            logger.debug("任务执行中...");
        } catch (Exception e) {
            logger.error("任务执行失败", e);
        }
    }
}

项目实际配置分析

1. 多环境日志配置

1.1 开发环境配置
xml 复制代码
<!-- logback-spring.xml -->
<springProfile name="dev">
    <root level="debug">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</springProfile>
1.2 生产环境配置
xml 复制代码
<!-- logback-spring.xml -->
<springProfile name="prod">
    <root level="warn">
        <appender-ref ref="file" />
        <appender-ref ref="traceFile" />
    </root>
</springProfile>

2. 特殊日志配置

2.1 追踪日志 (Trace Log)
xml 复制代码
<!-- 追踪日志,用于性能监控 -->
<appender name="traceFile" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator>
        <Key>systemFlag</Key>
        <DefaultValue>zmbm</DefaultValue>
    </discriminator>
    <sift>
        <appender name="EXTRACTOR_LOG_${systemFlag}" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <File>/data/logstash/${systemFlag}/trace.log</File>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>/data/logstash/${systemFlag}/%d{yyyy-MM-dd}/trace-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>512MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>60</maxHistory>
            </rollingPolicy>
            <!-- JSON格式输出,便于ELK分析 -->
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <pattern>
                        <pattern>
                        {
                            "timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
                            "traceId": "%X{traceId}",
                            "userId": "%X{userId}",
                            "level": "%level",
                            "message": "%message"
                        }
                        </pattern>
                    </pattern>
                </providers>
            </encoder>
        </appender>
    </sift>
</appender>
2.2 第三方接口日志
xml 复制代码
<!-- 第三方接口调用日志 -->
<appender name="thirdFile" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator>
        <Key>systemFlag</Key>
        <DefaultValue>zmbm</DefaultValue>
    </discriminator>
    <sift>
        <appender name="EXTRACTOR_LOG_${systemFlag}" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <File>./logs/${systemFlag}/thrid.log</File>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>./logs/${systemFlag}/%d{yyyy-MM-dd}/thrid-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>512MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>60</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    </sift>
</appender>

3. 日志分类管理

3.1 按业务模块分类
xml 复制代码
<!-- 不同业务模块使用不同的日志文件 -->
<logger name="com.zmy.enrollment.web.zmbm.activity" level="info" additivity="false">
    <appender-ref ref="activityFile" />
</logger>

<logger name="com.zmy.enrollment.web.zmbm.registration" level="info" additivity="false">
    <appender-ref ref="registrationFile" />
</logger>

<logger name="com.zmy.enrollment.web.zmbm.lottery" level="info" additivity="false">
    <appender-ref ref="lotteryFile" />
</logger>
3.2 按日志级别分类
xml 复制代码
<!-- 错误日志单独输出 -->
<appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>./logs/error.log</file>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>ERROR</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>./logs/error.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>90</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

重难点分析

1. 日志性能优化

难点描述
  • 日志输出对应用性能的影响
  • 大量日志导致的磁盘I/O问题
  • 日志级别判断的性能开销
解决方案
java 复制代码
// 使用条件日志,避免不必要的字符串拼接
@Log4j2
public class PerformanceService {
  
    public void processData(List<Data> dataList) {
        // 避免在非DEBUG级别时执行复杂操作
        if (log.isDebugEnabled()) {
            log.debug("处理数据列表,数量: {}, 详情: {}", dataList.size(), dataList);
        }
      
        // 使用占位符,避免字符串拼接
        log.info("开始处理数据,数量: {}", dataList.size());
      
        // 异步日志处理
        log.info("数据处理完成,耗时: {}ms", System.currentTimeMillis() - startTime);
    }
}
xml 复制代码
<!-- 异步日志配置 -->
<appender name="asyncFile" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="file" />
    <queueSize>512</queueSize>
    <discardingThreshold>0</discardingThreshold>
</appender>

2. 日志文件管理

难点描述
  • 日志文件大小控制
  • 日志文件轮转策略
  • 日志文件清理策略
解决方案
xml 复制代码
<!-- 时间和大小双重控制的滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    <!-- 按日期滚动 -->
    <fileNamePattern>./logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <!-- 单个文件最大大小 -->
    <maxFileSize>100MB</maxFileSize>
    <!-- 保留天数 -->
    <maxHistory>30</maxHistory>
    <!-- 总大小限制 -->
    <totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>

3. 分布式日志追踪

难点描述
  • 分布式系统中的请求追踪
  • 日志关联和聚合
  • 跨服务调用链追踪
解决方案
java 复制代码
// 使用MDC存储追踪信息
@Component
public class LogTraceFilter implements Filter {
  
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        try {
            // 生成追踪ID
            String traceId = UUID.randomUUID().toString();
            MDC.put("traceId", traceId);
          
            // 设置用户信息
            String userId = getUserId(request);
            MDC.put("userId", userId);
          
            chain.doFilter(request, response);
        } finally {
            // 清理MDC
            MDC.clear();
        }
    }
}
xml 复制代码
<!-- 日志格式包含MDC信息 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n</pattern>

4. 日志安全控制

难点描述
  • 敏感信息泄露
  • 日志文件权限控制
  • 日志内容脱敏处理
解决方案
java 复制代码
// 敏感信息脱敏工具类
@Component
public class LogMaskUtil {
  
    public static String maskPhone(String phone) {
        if (StringUtils.isEmpty(phone) || phone.length() < 7) {
            return phone;
        }
        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
    }
  
    public static String maskIdCard(String idCard) {
        if (StringUtils.isEmpty(idCard) || idCard.length() < 10) {
            return idCard;
        }
        return idCard.substring(0, 6) + "********" + idCard.substring(idCard.length() - 4);
    }
}

// 使用脱敏工具
@Log4j2
public class UserService {
  
    public void createUser(User user) {
        // 脱敏后记录日志
        log.info("创建用户: 姓名={}, 手机={}, 身份证={}", 
            user.getName(), 
            LogMaskUtil.maskPhone(user.getPhone()),
            LogMaskUtil.maskIdCard(user.getIdCard()));
    }
}

最佳实践建议

1. 日志级别使用规范

java 复制代码
// 推荐的日志级别使用
@Log4j2
public class BestPracticeService {
  
    public void processBusinessLogic() {
        // TRACE: 详细的执行步骤
        log.trace("进入方法 processBusinessLogic");
      
        // DEBUG: 调试信息,开发环境使用
        log.debug("处理业务逻辑,参数: {}", businessParam);
      
        // INFO: 重要的业务节点
        log.info("开始处理业务,业务ID: {}", businessId);
      
        try {
            // 业务逻辑处理
            if (log.isDebugEnabled()) {
                log.debug("业务处理中,当前状态: {}", currentStatus);
            }
          
            // 业务完成
            log.info("业务处理完成,结果: {}", result);
          
        } catch (BusinessException e) {
            // WARN: 业务异常,可恢复
            log.warn("业务处理警告,业务ID: {}, 原因: {}", businessId, e.getMessage());
            throw e;
          
        } catch (Exception e) {
            // ERROR: 系统异常,需要关注
            log.error("业务处理异常,业务ID: {}, 错误: {}", businessId, e.getMessage(), e);
            throw new RuntimeException("系统异常", e);
        }
    }
}

2. 日志配置优化

xml 复制代码
<!-- 生产环境推荐配置 -->
<configuration>
    <!-- 异步日志处理 -->
    <appender name="asyncFile" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="file" />
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
        <includeCallerData>false</includeCallerData>
    </appender>
  
    <!-- 错误日志单独处理 -->
    <appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>./logs/error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>./logs/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
  
    <!-- 根日志配置 -->
    <root level="info">
        <appender-ref ref="asyncFile" />
        <appender-ref ref="errorFile" />
    </root>
</configuration>

3. 日志监控和告警

yaml 复制代码
# 日志监控配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,loggers
  endpoint:
    loggers:
      enabled: true
    health:
      show-details: always

# 日志级别动态调整
logging:
  level:
    root: INFO
    com.zmy.enrollment: INFO
    # 可以通过Actuator动态调整
    # POST /actuator/loggers/com.zmy.enrollment
    # {"configuredLevel": "DEBUG"}

4. 日志分析工具集成

xml 复制代码
<!-- ELK集成配置 -->
<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <destination>localhost:5000</destination>
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
        <customFields>{"app_name":"enrollment-web","env":"prod"}</customFields>
    </encoder>
</appender>

<!-- 结构化日志输出 -->
<appender name="jsonFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>./logs/application.json</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>./logs/application.%d{yyyy-MM-dd}.json</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
        <providers>
            <timestamp/>
            <logLevel/>
            <loggerName/>
            <message/>
            <mdc/>
            <stackTrace/>
        </providers>
    </encoder>
</appender>

总结

本项目采用了完善的日志系统配置,具有以下特点:

1. 多层次日志配置

  • 控制台输出:开发调试使用
  • 文件输出:生产环境持久化
  • 追踪日志:性能监控和问题排查
  • 第三方日志:接口调用记录

2. 灵活的日志管理

  • 按业务模块分类
  • 按日志级别分类
  • 支持动态配置调整
  • 自动文件轮转和清理

3. 性能优化考虑

  • 异步日志处理
  • 条件日志输出
  • 合理的文件大小控制
  • 日志级别过滤

4. 运维友好设计

  • JSON格式输出,便于ELK分析
  • 包含追踪ID,支持分布式追踪
  • 结构化日志,便于监控告警
  • 多环境配置支持

这种日志配置方案既满足了开发调试的需求,又为生产环境的运维监控提供了强有力的支持,是一个值得参考的企业级日志配置实践。

相关推荐
武子康2 小时前
Java-109 深入浅出 MySQL MHA主从故障切换机制详解 高可用终极方案
java·数据库·后端·mysql·性能优化·架构·系统架构
秋难降2 小时前
代码界的 “建筑师”:建造者模式,让复杂对象构建井然有序
java·后端·设计模式
孤雪心殇2 小时前
如何安全,高效,优雅的提升linux的glibc版本
linux·后端·golang·glibc
BillKu3 小时前
Spring Boot 多环境配置
java·spring boot·后端
哈基米喜欢哈哈哈4 小时前
Kafka复制机制
笔记·分布式·后端·kafka
君不见,青丝成雪4 小时前
SpringBoot项目占用内存优化
java·spring boot·后端
追逐时光者5 小时前
一个 .NET 开源、功能强大的在线文档编辑器,类似于 Microsoft Word,支持信创!
后端·.net
3Cloudream5 小时前
互联网大厂Java面试深度解析:从基础到微服务云原生的全场景模拟
java·spring boot·redis·elasticsearch·微服务·kafka·电商架构