第10篇:测试与实战 - 构建完整的示例应用

项目地址:github.com/nemoob/atla...

已打包,可直接接入项目使用

前言

经过前面9章的学习,我们已经构建了一个功能完整的企业级日志框架。本章将通过构建一个完整的示例应用,展示框架的实际应用效果,并进行全面的测试验证。

示例应用架构

业务场景设计

我们将构建一个电商系统的核心模块:

graph TB A[电商示例应用] --> B[用户模块] A --> C[商品模块] A --> D[订单模块] A --> E[支付模块] B --> F[用户注册/登录] C --> G[商品管理] D --> H[订单处理] E --> I[支付/退款]

测试要点:

  • 🎯 注解应用:在不同场景下使用@LogMethod和@LogClass
  • 🔄 链路追踪:验证分布式追踪的完整性
  • 🛡️ 敏感信息脱敏:测试密码、支付信息的脱敏
  • 📊 性能监控:验证高并发下的框架表现

核心业务模块实现

1. 用户服务

java 复制代码
package com.simpleflow.log.samples.service;

import com.simpleflow.log.annotation.LogClass;
import com.simpleflow.log.annotation.LogIgnore;
import com.simpleflow.log.annotation.LogLevel;
import com.simpleflow.log.annotation.LogMethod;
import com.simpleflow.log.samples.model.User;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 用户服务 - 演示类级日志配置
 */
@Service
@LogClass(
    level = LogLevel.INFO,
    prefix = "用户服务",
    logArgs = true,
    logResult = false, // 默认不记录返回值,避免用户信息泄露
    includeMethods = {"find.*", "create.*", "update.*"},
    sensitiveFields = {"password", "phone", "email"}
)
public class UserService {
    
    private final ConcurrentHashMap<Long, User> userStorage = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);
    
    /**
     * 用户注册 - 重写类级配置,启用返回值记录
     */
    @LogMethod(
        level = LogLevel.INFO,
        logResult = true,
        startMessage = "开始用户注册,用户名:{}",
        successMessage = "用户注册成功,用户ID:{},耗时:{}ms",
        sensitiveFields = {"password", "phone", "email", "idCard"}
    )
    public User register(User user) {
        // 模拟业务处理时间
        simulateProcessing(100);
        
        if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        
        // 检查用户名是否已存在
        boolean exists = userStorage.values().stream()
            .anyMatch(u -> u.getUsername().equals(user.getUsername()));
        
        if (exists) {
            throw new IllegalArgumentException("用户名已存在:" + user.getUsername());
        }
        
        user.setId(idGenerator.getAndIncrement());
        user.setPassword(encryptPassword(user.getPassword()));
        userStorage.put(user.getId(), user);
        
        return user;
    }
    
    /**
     * 用户登录 - 高度敏感操作
     */
    @LogMethod(
        level = LogLevel.WARN,
        startMessage = "用户登录尝试,用户名:{}",
        successMessage = "用户登录成功,用户名:{},耗时:{}ms",
        errorMessage = "用户登录失败,用户名:{},原因:{}",
        sensitiveFields = {"password"}
    )
    public User login(String username, String password) {
        simulateProcessing(200);
        
        User user = userStorage.values().stream()
            .filter(u -> u.getUsername().equals(username))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("用户不存在"));
        
        if (!verifyPassword(password, user.getPassword())) {
            throw new IllegalArgumentException("密码错误");
        }
        
        return user;
    }
    
    /**
     * 查找用户 - 继承类级配置
     */
    public User findById(Long id) {
        simulateProcessing(50);
        
        User user = userStorage.get(id);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在,ID:" + id);
        }
        
        return user;
    }
    
    @LogIgnore(reason = "内部加密方法,敏感操作")
    private String encryptPassword(String password) {
        return "encrypted:" + password.hashCode();
    }
    
    @LogIgnore(reason = "内部验证方法,敏感操作")
    private boolean verifyPassword(String input, String encrypted) {
        return encrypted.equals("encrypted:" + input.hashCode());
    }
    
    @LogIgnore(reason = "测试工具方法")
    private void simulateProcessing(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2. 订单服务

java 复制代码
package com.simpleflow.log.samples.service;

import com.simpleflow.log.annotation.LogClass;
import com.simpleflow.log.annotation.LogLevel;
import com.simpleflow.log.annotation.LogMethod;
import com.simpleflow.log.samples.model.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 订单服务 - 演示服务间调用的链路追踪
 */
@Service
@LogClass(
    level = LogLevel.INFO,
    prefix = "订单服务",
    logExecutionTime = true,
    includeRequestId = true
)
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private ProductService productService;
    
    @Autowired
    private PaymentService paymentService;
    
    private final ConcurrentHashMap<Long, Order> orderStorage = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);
    
    /**
     * 创建订单 - 复杂业务流程,涉及多个服务调用
     */
    @LogMethod(
        level = LogLevel.INFO,
        startMessage = "开始创建订单,用户ID:{},商品列表:{}",
        successMessage = "订单创建成功,订单ID:{},总金额:{},耗时:{}ms",
        errorMessage = "订单创建失败,用户ID:{},错误:{}"
    )
    public Order createOrder(Long userId, List<Long> productIds) {
        // 1. 验证用户
        User user = userService.findById(userId);
        
        // 2. 验证商品并计算总价
        List<Product> products = productIds.stream()
            .map(productService::findById)
            .toList();
        
        BigDecimal totalAmount = products.stream()
            .map(Product::getPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        // 3. 创建订单
        Order order = new Order();
        order.setId(idGenerator.getAndIncrement());
        order.setUserId(userId);
        order.setProductIds(productIds);
        order.setTotalAmount(totalAmount);
        order.setStatus("CREATED");
        order.setCreateTime(LocalDateTime.now());
        
        orderStorage.put(order.getId(), order);
        
        return order;
    }
    
    /**
     * 处理订单 - 涉及支付服务调用
     */
    @LogMethod(
        startMessage = "开始处理订单,订单ID:{}",
        successMessage = "订单处理完成,订单ID:{},支付ID:{}"
    )
    public Order processOrder(Long orderId, String paymentMethod) {
        Order order = findById(orderId);
        
        if (!"CREATED".equals(order.getStatus())) {
            throw new IllegalStateException("订单状态不正确,当前状态:" + order.getStatus());
        }
        
        try {
            // 调用支付服务
            String paymentId = paymentService.processPayment(
                order.getId(), 
                order.getTotalAmount(), 
                paymentMethod
            );
            
            // 更新订单状态
            order.setStatus("PAID");
            order.setPaymentId(paymentId);
            order.setUpdateTime(LocalDateTime.now());
            
            orderStorage.put(orderId, order);
            
            return order;
            
        } catch (Exception e) {
            order.setStatus("PAYMENT_FAILED");
            orderStorage.put(orderId, order);
            throw new RuntimeException("订单支付失败:" + e.getMessage(), e);
        }
    }
    
    public Order findById(Long id) {
        Order order = orderStorage.get(id);
        if (order == null) {
            throw new IllegalArgumentException("订单不存在,ID:" + id);
        }
        return order;
    }
}

3. 支付服务

java 复制代码
package com.simpleflow.log.samples.service;

import com.simpleflow.log.annotation.LogClass;
import com.simpleflow.log.annotation.LogLevel;
import com.simpleflow.log.annotation.LogMethod;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 支付服务 - 演示敏感信息处理和异常场景
 */
@Service
@LogClass(
    level = LogLevel.WARN, // 支付相关操作使用WARN级别
    prefix = "支付服务",
    logArgs = true,
    logResult = false, // 支付结果包含敏感信息,默认不记录
    sensitiveFields = {"cardNumber", "cvv", "accountNumber", "paymentToken"}
)
public class PaymentService {
    
    /**
     * 处理支付
     */
    @LogMethod(
        level = LogLevel.ERROR, // 支付处理使用ERROR级别便于监控
        startMessage = "开始处理支付,订单ID:{},金额:{},支付方式:{}",
        successMessage = "支付处理成功,支付ID:{},订单ID:{},金额:{}",
        errorMessage = "支付处理失败,订单ID:{},金额:{},错误:{}"
    )
    public String processPayment(Long orderId, BigDecimal amount, String paymentMethod) {
        // 模拟支付处理时间
        simulateProcessing(500);
        
        // 参数验证
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("支付金额必须大于0");
        }
        
        if (amount.compareTo(new BigDecimal("10000")) > 0) {
            throw new IllegalArgumentException("单笔支付金额不能超过10000元");
        }
        
        // 模拟支付失败的情况(10%概率)
        if (ThreadLocalRandom.current().nextInt(100) < 10) {
            throw new RuntimeException("支付网关异常,请稍后重试");
        }
        
        // 生成支付ID
        String paymentId = "PAY_" + UUID.randomUUID().toString().replace("-", "");
        
        return paymentId;
    }
    
    /**
     * 退款处理
     */
    @LogMethod(
        level = LogLevel.WARN,
        startMessage = "开始处理退款,支付ID:{},退款金额:{}",
        successMessage = "退款处理成功,退款ID:{},原支付ID:{}",
        sensitiveFields = {"paymentId", "refundId"}
    )
    public String refund(String paymentId, BigDecimal refundAmount) {
        simulateProcessing(300);
        
        // 模拟退款失败(5%概率)
        if (ThreadLocalRandom.current().nextInt(100) < 5) {
            throw new RuntimeException("退款处理失败,银行系统异常");
        }
        
        String refundId = "REFUND_" + UUID.randomUUID().toString().replace("-", "");
        return refundId;
    }
    
    private void simulateProcessing(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

配置文件

application.yml

yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: ecommerce-demo

# SimpleFlow日志框架配置
simpleflow:
  log:
    enabled: true
    default-level: INFO
    web-enabled: true
    actuator-enabled: true
    global-sensitive-fields:
      - password
      - cardNumber
      - cvv
      - paymentToken
      - idCard
      - phone
      - email
    
    request-log:
      enabled: true
      log-parameters: true
      exclude-patterns:
        - /actuator/**
        - /favicon.ico
    
    performance:
      async-enabled: false
      cache-size: 500
      max-log-length: 10000

# Actuator配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,simpleflow-log
  endpoint:
    health:
      show-details: always

# 日志配置
logging:
  level:
    com.simpleflow.log.samples: INFO
    com.simpleflow.log: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId:-}] %logger{36} - %msg%n"

综合测试

1. 集成测试

java 复制代码
package com.simpleflow.log.samples.integration;

import com.simpleflow.log.context.ThreadLocalTraceHolder;
import com.simpleflow.log.samples.model.Order;
import com.simpleflow.log.samples.model.User;
import com.simpleflow.log.samples.service.OrderService;
import com.simpleflow.log.samples.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.math.BigDecimal;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class EcommerceIntegrationTest {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private OrderService orderService;
    
    @BeforeEach
    void setUp() {
        ThreadLocalTraceHolder.clearCurrentTrace();
    }
    
    @Test
    void testCompleteOrderFlow() {
        // 初始化追踪上下文
        ThreadLocalTraceHolder.initTrace();
        ThreadLocalTraceHolder.setCurrentUserId("test-user");
        
        // 1. 注册用户
        User user = new User();
        user.setUsername("testuser");
        user.setPassword("123456");
        user.setEmail("test@example.com");
        user.setPhone("13800138000");
        
        User registeredUser = userService.register(user);
        assertNotNull(registeredUser.getId());
        assertEquals("testuser", registeredUser.getUsername());
        
        // 2. 创建订单
        List<Long> productIds = List.of(1L, 2L);
        Order order = orderService.createOrder(registeredUser.getId(), productIds);
        
        assertNotNull(order.getId());
        assertEquals("CREATED", order.getStatus());
        
        // 3. 处理订单支付
        Order paidOrder = orderService.processOrder(order.getId(), "CREDIT_CARD");
        assertEquals("PAID", paidOrder.getStatus());
        assertNotNull(paidOrder.getPaymentId());
        
        // 验证追踪上下文在整个流程中保持一致
        assertNotNull(ThreadLocalTraceHolder.getCurrentRequestId());
        assertEquals("test-user", ThreadLocalTraceHolder.getCurrentUserId());
        
        System.out.println("完整订单流程测试成功!");
        System.out.println("追踪ID: " + ThreadLocalTraceHolder.getCurrentTraceId());
        System.out.println("请求ID: " + ThreadLocalTraceHolder.getCurrentRequestId());
    }
    
    @Test
    void testSensitiveDataMasking() {
        ThreadLocalTraceHolder.initTrace();
        
        // 测试敏感信息脱敏
        User user = new User();
        user.setUsername("sensitive-test");
        user.setPassword("super-secret-password");
        user.setEmail("sensitive@example.com");
        user.setPhone("13812345678");
        
        User registeredUser = userService.register(user);
        assertNotNull(registeredUser);
        
        // 登录测试敏感信息脱敏
        User loginUser = userService.login("sensitive-test", "super-secret-password");
        assertNotNull(loginUser);
        
        System.out.println("敏感信息脱敏测试通过!");
    }
}

2. 性能测试

java 复制代码
package com.simpleflow.log.samples.performance;

import com.simpleflow.log.context.ThreadLocalTraceHolder;
import com.simpleflow.log.samples.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootTest
class PerformanceTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    void testHighConcurrency() throws InterruptedException {
        int threadCount = 100;
        int operationsPerThread = 10;
        CountDownLatch latch = new CountDownLatch(threadCount);
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < threadCount; i++) {
            final int threadIndex = i;
            executor.submit(() -> {
                try {
                    // 每个线程初始化自己的追踪上下文
                    ThreadLocalTraceHolder.initTrace();
                    ThreadLocalTraceHolder.setCurrentUserId("perf-test-" + threadIndex);
                    
                    for (int j = 0; j < operationsPerThread; j++) {
                        try {
                            userService.findById(1L);
                        } catch (Exception e) {
                            // 忽略业务异常,专注性能测试
                        }
                    }
                    
                } finally {
                    latch.countDown();
                    ThreadLocalTraceHolder.clearCurrentTrace();
                }
            });
        }
        
        latch.await();
        executor.shutdown();
        
        long endTime = System.currentTimeMillis();
        long totalOperations = threadCount * operationsPerThread;
        double opsPerSecond = (double) totalOperations / (endTime - startTime) * 1000;
        
        System.out.printf("性能测试结果:\n");
        System.out.printf("总操作数:%d\n", totalOperations);
        System.out.printf("总耗时:%dms\n", endTime - startTime);
        System.out.printf("吞吐量:%.2f ops/sec\n", opsPerSecond);
        
        // 验证性能在可接受范围内
        assertTrue(opsPerSecond > 1000, "吞吐量应该大于1000 ops/sec");
    }
}

运行效果展示

1. 启动应用

bash 复制代码
mvn spring-boot:run

2. 查看日志输出

yaml 复制代码
2024-08-23 10:30:15.123 INFO  [http-nio-8080-exec-1] [REQ_20240823103015123_host_001] - HTTP请求开始 - RequestID: REQ_20240823103015123_host_001, Method: POST, URI: /api/users/register
2024-08-23 10:30:15.125 INFO  [http-nio-8080-exec-1] [REQ_20240823103015123_host_001] - 用户服务 - 开始用户注册,用户名:testuser
2024-08-23 10:30:15.225 INFO  [http-nio-8080-exec-1] [REQ_20240823103015123_host_001] - 用户服务 - 用户注册成功,用户ID:1,耗时:100ms
2024-08-23 10:30:15.226 INFO  [http-nio-8080-exec-1] [REQ_20240823103015123_host_001] - HTTP请求完成 - RequestID: REQ_20240823103015123_host_001, Method: POST, URI: /api/users/register, Status: 200, Duration: 103ms

3. 访问监控端点

bash 复制代码
# 查看框架状态
curl http://localhost:8080/actuator/simpleflow-log

# 查看健康检查
curl http://localhost:8080/actuator/health

# 查看指标
curl http://localhost:8080/actuator/metrics/simpleflow.log.method.calls

本章小结

✅ 完成的任务

  1. 完整示例应用:构建了电商系统的核心模块
  2. 注解应用演示:展示了@LogMethod和@LogClass的实际使用
  3. 链路追踪验证:验证了分布式环境下的上下文传递
  4. 敏感信息脱敏:测试了密码、支付信息等敏感数据的处理
  5. 性能测试:验证了高并发场景下的框架表现

🎯 学习要点

  • 实际应用场景中的注解配置策略
  • 敏感信息处理的最佳实践
  • 链路追踪在微服务中的价值
  • 性能监控的重要性
  • 测试策略的完整性

💡 思考题

  1. 如何在更复杂的微服务架构中应用这个框架?
  2. 生产环境中还需要考虑哪些优化点?
  3. 如何与现有的日志系统进行集成?

🎉 系列总结

经过10章的学习,我们从零开始构建了一个完整的企业级日志框架:

技术栈掌握

  • Java注解:设计优雅的API接口
  • Spring AOP:实现无侵入的切面编程
  • 配置管理:灵活的配置体系设计
  • 链路追踪:分布式环境下的请求跟踪
  • Web集成:HTTP请求的全链路追踪
  • Spring Boot Starter:自动配置的最佳实践
  • 监控运维:Actuator集成和健康检查

框架特性

  • 🎯 注解驱动:声明式的日志配置
  • 🔄 链路追踪:完整的请求生命周期跟踪
  • 🛡️ 安全处理:敏感信息自动脱敏
  • 高性能:低开销的运行时性能
  • 🏥 可观测性:完善的监控和运维支持

生产级特性

  • 📦 开箱即用:Spring Boot Starter自动配置
  • 🔧 灵活配置:支持多层级配置合并
  • 🚀 易于扩展:模块化的架构设计
  • 🔍 调试友好:详细的日志信息和监控指标

这个框架不仅展示了如何从零开始构建一个完整的技术框架,更重要的是体现了企业级软件开发的思维方式和最佳实践。希望通过这个系列,能够帮助你在技术成长的道路上更进一步!


💡 最后的话: 技术框架的价值不在于功能的复杂度,而在于能否真正解决实际问题,提升开发效率。愿这个框架能在你的项目中发挥价值!

相关推荐
Funcy7 分钟前
XxlJob源码分析01:环境准备
java
the beard37 分钟前
Feign整合Sentinel实现服务降级与Feign拦截器实战指南
java·spring·sentinel
THMAIL1 小时前
攻克 Java 分布式难题:并发模型优化与分布式事务处理实战指南
java·开发语言·分布式
小沈同学呀1 小时前
使用Java操作微软 Azure Blob Storage:上传和下载文件
java·microsoft·azure
CYRUS_STUDIO2 小时前
一步步带你移植 FART 到 Android 10,实现自动化脱壳
android·java·逆向
练习时长一年3 小时前
Spring代理的特点
java·前端·spring
CYRUS_STUDIO3 小时前
FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器
android·java·逆向
MisterZhang6663 小时前
Java使用apache.commons.math3的DBSCAN实现自动聚类
java·人工智能·机器学习·自然语言处理·nlp·聚类
Swift社区4 小时前
Java 常见异常系列:ClassNotFoundException 类找不到
java·开发语言
一只叫煤球的猫5 小时前
怎么这么多StringUtils——Apache、Spring、Hutool全面对比
java·后端·性能优化