Skip to content

Common-Base 模块集成指南

1. 概述

本文档旨在帮助开发者将 common-base 模块集成到 Spring Boot 3.x 项目中。common-base 提供了统一的基础设施,包括响应模型、异常体系、工具类和基础依赖,可以显著提高开发效率和代码质量。

2. 环境要求

2.1 系统要求

  • Java: JDK 17 或更高版本
  • Spring Boot: 3.x 版本
  • Maven: 3.6+ 或 Gradle 7+
  • 数据库: 可选,支持 MySQL/PostgreSQL/Oracle

2.2 可选依赖要求

根据项目需求,可能需要以下可选依赖:

  • Redis: 如需使用 RedisUtil
  • MongoDB: 如需使用 MongoUtil
  • MyBatis Flex: 如需使用 FlexBaseEntity
  • Sa-Token: 如需使用 TokenUtils

3. 快速开始

3.1 Maven 项目集成

3.1.1 添加依赖

在项目的 pom.xml 中添加以下依赖:

xml
<dependency>
    <groupId>com.beyondsoft</groupId>
    <artifactId>common-base</artifactId>
    <version>${common-base.version}</version>
</dependency>

3.1.2 配置可选依赖

根据项目需要,添加可选依赖:

xml
<!-- Redis 支持(如需使用 RedisUtil) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- MongoDB 支持(如需使用 MongoUtil) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

<!-- MyBatis Flex 支持(如需使用 FlexBaseEntity) -->
<dependency>
    <groupId>com.mybatis-flex</groupId>
    <artifactId>mybatis-flex-spring-boot3-starter</artifactId>
</dependency>

<!-- Sa-Token 支持(如需使用 TokenUtils) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>

3.2 Gradle 项目集成

3.2.1 添加依赖

build.gradle 中添加:

groovy
dependencies {
    implementation 'com.beyondsoft:common-base:${common-base.version}'
    
    // 可选依赖
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    implementation 'com.mybatis-flex:mybatis-flex-spring-boot3-starter'
    implementation 'cn.dev33:sa-token-spring-boot3-starter'
}

4. 基础配置

4.1 应用配置

4.1.1 application.yml 配置

yaml
spring:
  application:
    name: your-application-name
  
  # Redis 配置(如需使用)
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
  
  # MongoDB 配置(如需使用)
  data:
    mongodb:
      uri: mongodb://localhost:27017/test
  
  # 邮件配置(如需使用邮件功能)
  mail:
    host: smtp.example.com
    port: 587
    username: your-email@example.com
    password: your-password
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true

# 日志配置
logging:
  level:
    com.beyondsoft: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log

4.1.2 IP 数据库配置

确保 ip2region.xdb 文件位于 classpath 的 db 目录下:

src/main/resources/db/ip2region.xdb

如果文件不存在,可以从 ip2region 项目下载最新版本。

4.2 异常处理配置

4.2.1 全局异常处理器

创建全局异常处理器,统一处理 common-base 中的异常:

java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResult> handleBusinessException(BusinessException e) {
        log.error("业务异常: {}", e.getMessage(), e);
        ErrorResult error = ErrorResult.failure(
            ResponseCodeEnums.DEFAULT_ERROR, 
            e, 
            HttpStatus.BAD_REQUEST
        );
        return ResponseEntity.badRequest().body(error);
    }
    
    /**
     * 处理参数无效异常
     */
    @ExceptionHandler(ParameterInvalidException.class)
    public ResponseEntity<ErrorResult> handleParameterInvalidException(ParameterInvalidException e) {
        log.error("参数无效异常: {}", e.getMessage(), e);
        ErrorResult error = ErrorResult.failure(
            ResponseCodeEnums.PARAM_IS_INVALID,
            e,
            HttpStatus.BAD_REQUEST
        );
        return ResponseEntity.badRequest().body(error);
    }
    
    /**
     * 处理资源不存在异常
     */
    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResult> handleNotFoundException(NotFoundException e) {
        log.error("资源不存在异常: {}", e.getMessage(), e);
        ErrorResult error = ErrorResult.failure(
            ResponseCodeEnums.NOT_FOUND,
            e,
            HttpStatus.NOT_FOUND
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    /**
     * 处理其他异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResult> handleException(Exception e) {
        log.error("系统异常: {}", e.getMessage(), e);
        ErrorResult error = ErrorResult.failure(
            e,
            HttpStatus.INTERNAL_SERVER_ERROR
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

5. 核心功能集成

5.1 统一响应模型集成

5.1.1 Controller 层集成

在 Controller 中直接返回业务对象,由异常处理器统一包装:

java
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
    
    @Autowired
    private UserService userService;
    
    /**
     * 获取用户信息
     */
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        log.info("获取用户信息,用户ID: {}", id);
        User user = userService.getUserById(id);
        if (user == null) {
            throw new NotFoundException(ResponseCodeEnums.USER_NOT_EXIST);
        }
        return user;
    }
    
    /**
     * 创建用户
     */
    @PostMapping
    @Transactional
    public User createUser(@RequestBody @Valid UserCreateRequest request) {
        log.info("创建用户,用户名: {}", request.getUsername());
        
        // 检查用户名是否已存在
        if (userService.existsByUsername(request.getUsername())) {
            throw new BusinessException(ResponseCodeEnums.USER_HAS_EXISTED);
        }
        
        // 创建用户
        User user = userService.createUser(request);
        log.info("用户创建成功,用户ID: {}", user.getId());
        
        return user;
    }
    
    /**
     * 分页查询用户
     */
    @GetMapping("/list")
    public Page<User> listUsers(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) String username) {
        
        log.info("分页查询用户,页码: {}, 大小: {}, 用户名: {}", page, size, username);
        
        // 创建分页对象
        Page<User> pageInfo = new Page<>(page, size);
        
        // 构建查询条件
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(username)) {
            queryWrapper.like(User::getUsername, username);
        }
        
        // 执行分页查询
        Page<User> result = userService.page(pageInfo, queryWrapper);
        
        return result;
    }
}

5.1.2 响应格式示例

成功响应:

json
{
  "status": 200,
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 1,
    "username": "admin",
    "email": "admin@example.com"
  },
  "timestamp": 1634567890000,
  "traceId": "1234567890abcdef",
  "reason": null
}

错误响应:

json
{
  "status": 400,
  "error": "Bad Request",
  "msg": "用户已存在",
  "code": 20005,
  "path": "/api/users",
  "exception": "com.beyondsoft.common.exceptions.BusinessException",
  "reasons": "用户已存在",
  "timestamp": "2025-12-30T10:30:00",
  "data": null,
  "traceId": "1234567890abcdef"
}

5.2 工具类集成

5.2.1 IP 工具集成

在需要获取客户端 IP 的地方使用:

java
@RestController
@RequestMapping("/api/system")
@Slf4j
public class SystemController {
    
    @GetMapping("/ip")
    public Map<String, String> getClientIp(HttpServletRequest request) {
        Optional<String> ip = IpUtils.getIpAddress(request);
        Optional<String> location = ip.flatMap(IpUtils::getLocationInfo);
        
        Map<String, String> result = new HashMap<>();
        result.put("ip", ip.orElse(CommonConstants.NO_IP));
        result.put("location", location.orElse(CommonConstants.NO_LOCATION));
        
        log.info("客户端IP: {}, 位置: {}", result.get("ip"), result.get("location"));
        
        return result;
    }
}

5.2.2 Spring 工具集成

在非 Spring 管理的类中获取 Bean:

java
@Component
public class SomeComponent {
    
    public void someMethod() {
        // 获取 RedisTemplate
        RedisTemplate<String, Object> redisTemplate = 
            SpringUtil.getBean("redisTemplate", RedisTemplate.class);
        
        // 获取配置属性
        String appName = SpringUtil.getProperty("spring.application.name");
        
        // 获取当前环境
        String activeProfile = SpringUtil.getActiveProfile();
    }
}

5.2.3 Redis 工具集成

java
@Service
@Slf4j
public class UserService {
    
    private static final String USER_CACHE_KEY = "user:cache:";
    private static final long USER_CACHE_TIMEOUT = CaffeineTimeConstant.ONE_HOUR;
    
    public User getUserById(Long id) {
        // 先从缓存获取
        User cachedUser = RedisUtil.getObject(USER_CACHE_KEY + id, User.class);
        if (cachedUser != null) {
            log.info("从缓存获取用户: {}", id);
            return cachedUser;
        }
        
        // 缓存未命中,从数据库获取
        User user = userMapper.selectById(id);
        if (user != null) {
            // 放入缓存
            RedisUtil.setObject(USER_CACHE_KEY + id, user, USER_CACHE_TIMEOUT);
            log.info("用户数据已缓存: {}", id);
        }
        
        return user;
    }
    
    public void updateUser(User user) {
        // 更新数据库
        userMapper.updateById(user);
        
        // 清除缓存
        RedisUtil.del(USER_CACHE_KEY + user.getId());
        log.info("用户缓存已清除: {}", user.getId());
    }
}

5.3 异常体系集成

5.3.1 Service 层异常使用

java
@Service
@Slf4j
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public User createUser(UserCreateRequest request) {
        // 参数校验
        if (StringUtils.isBlank(request.getUsername())) {
            throw new ParameterInvalidException("用户名不能为空");
        }
        
        if (StringUtils.isBlank(request.getPassword())) {
            throw new ParameterInvalidException("密码不能为空");
        }
        
        if (request.getPassword().length() < 6) {
            throw new ParameterInvalidException("密码长度不能小于6位");
        }
        
        // 检查用户名是否已存在
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, request.getUsername());
        User existingUser = userMapper.selectOne(queryWrapper);
        
        if (existingUser != null) {
            Map<String, Object> data = new HashMap<>();
            data.put("username", request.getUsername());
            data.put("existingUserId", existingUser.getId());
            
            throw new BusinessException(ResponseCodeEnums.USER_HAS_EXISTED, data);
        }
        
        // 创建用户
        User user = new User();
        user.setUsername(request.getUsername());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setEmail(request.getEmail());
        user.setStatus(1);
        
        int result = userMapper.insert(user);
        if (result <= 0) {
            throw new InternalServerException("创建用户失败");
        }
        
        log.info("用户创建成功: {}", user.getId());
        return user;
    }
    
    @Override
    public User getUserById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new NotFoundException(ResponseCodeEnums.USER_NOT_EXIST);
        }
        return user;
    }
    
    @Override
    public void deleteUser(Long id) {
        User user = getUserById(id);
        
        // 检查是否可以删除
        if (CommonConstants.SUPER_ADMIN.equals(user.getUsername())) {
            throw new BusinessException("超级管理员不能删除");
        }
        
        int result = userMapper.deleteById(id);
        if (result <= 0) {
            throw new InternalServerException("删除用户失败");
        }
        
        log.info("用户删除成功: {}", id);
    }
}

5.3.2 自定义异常

如果需要自定义异常,可以继承 RequestException

java
public class CustomBusinessException extends RequestException {
    
    public CustomBusinessException(String message) {
        super(message);
    }
    
    public CustomBusinessException(ResponseCodeEnums code) {
        super(code);
    }
    
    public CustomBusinessException(ResponseCodeEnums code, Object data) {
        super(code, data);
    }
}

5.4 实体模型集成

5.4.1 继承基础实体

java
@Data
@EqualsAndHashCode(callSuper = true)
@Table(name = "sys_user")
@EntityListeners(BaseEntityListener.class)
public class User extends FlexBaseEntity {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    @Column(name = "username")
    private String username;
    
    @Column(name = "password")
    private String password;
    
    @Column(name = "email")
    private String email;
    
    @Column(name = "phone")
    private String phone;
    
    @Column(name = "status")
    private Integer status;
    
    @Column(name = "department_id")
    private Long departmentId;
}

5.4.2 使用实体监听器

实体监听器会自动填充创建时间和更新时间:

java
@Service
public class UserService {
    
    public User createUser(UserCreateRequest request) {
        User user = new User();
        user.setUsername(request.getUsername());
        // 其他字段设置...
        
        // 保存时,BaseEntityListener会自动设置createTime和updateTime
        userMapper.insert(user);
        
        return user;
    }
    
    public void updateUser(User user) {
        // 更新时,BaseEntityListener会自动更新updateTime
        userMapper.updateById(user);
    }
}

6. 高级集成

6.1 注解查询集成

6.1.1 定义查询对象

java
@AndOperator
public class UserQuery {
    
    @Eq("username")
    private String username;
    
    @Eq("status")
    private Integer status;
    
    @Gt("create_time")
    private LocalDateTime createTimeStart;
    
    @Lt("create_time")
    private LocalDateTime createTimeEnd;
    
    @In("department_id")
    private List<Long> departmentIds;
    
    // getters and setters
}

6.1.2 查询解析器

需要实现注解解析器来将注解转换为查询条件:

java
@Component
public class QueryAnnotationParser {
    
    public <T> LambdaQueryWrapper<T> parse(Object queryObject, Class<T> entityClass) {
        LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>();
        
        Class<?> clazz = queryObject.getClass();
        Field[] fields = clazz.getDeclaredFields();
        
        for (Field field : fields) {
            field.setAccessible(true);
            
            try {
                Object value = field.get(queryObject);
                if (value == null) {
                    continue;
                }
                
                // 解析 @Eq 注解
                Eq eq = field.getAnnotation(Eq.class);
                if (eq != null) {
                    String column = StringUtils.isNotBlank(eq.value()) ? eq.value() : field.getName();
                    queryWrapper.eq(getColumn(entityClass, column), value);
                }
                
                // 解析 @Gt 注解
                Gt gt = field.getAnnotation(Gt.class);
                if (gt != null) {
                    String column = StringUtils.isNotBlank(gt.value()) ? gt.value() : field.getName();
                    queryWrapper.gt(getColumn(entityClass, column), value);
                }
                
                // 解析 @Lt 注解
                Lt lt = field.getAnnotation(Lt.class);
                if (lt != null) {
                    String column = StringUtils.isNotBlank(lt.value()) ? lt.value() : field.getName();
                    queryWrapper.lt(getColumn(entityClass, column), value);
                }
                
                // 解析 @In 注解
                In in = field.getAnnotation(In.class);
                if (in != null && value instanceof Collection) {
                    String column = StringUtils.isNotBlank(in.value()) ? in.value() : field.getName();
                    queryWrapper.in(getColumn(entityClass, column), (Collection<?>) value);
                }
                
            } catch (IllegalAccessException e) {
                log.error("解析字段失败: {}", field.getName(), e);
            }
        }
        
        return queryWrapper;
    }
    
    private <T> SFunction<T, ?> getColumn(Class<T> entityClass, String columnName) {
        // 这里需要实现根据列名获取Lambda表达式的方法
        // 可以使用反射或预定义的映射表
        return null;
    }
}

6.1.3 在Controller中使用

java
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private QueryAnnotationParser queryParser;
    
    @GetMapping("/query")
    public List<User> queryUsers(UserQuery query) {
        LambdaQueryWrapper<User> queryWrapper = queryParser.parse(query, User.class);
        return userService.list(queryWrapper);
    }
}

6.2 Excel导入导出集成

6.2.1 导出Excel

java
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
    
    @GetMapping("/export")
    public void exportUsers(HttpServletResponse response) throws IOException {
        // 查询数据
        List<User> userList = userService.getAllUsers();
        
        // 设置响应头
        response.setContentType(CommonConstants.EXCEL_CONTENT);
        response.setCharacterEncoding("UTF-8");
        String fileName = URLEncoder.encode("用户列表.xlsx", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        
        // 导出Excel
        ExcelUtil.exportExcel(
            userList, 
            "用户列表", 
            "用户信息", 
            User.class, 
            "user_list.xlsx", 
            response
        );
        
        log.info("用户列表导出成功,共{}条记录", userList.size());
    }
}

6.2.2 导入Excel

java
@RestController
@RequestMapping("/api/users")
@Slf4l
public class UserController {
    
    @PostMapping("/import")
    @Transactional
    public CommonResult<Integer> importUsers(@RequestParam("file") MultipartFile file) {
        try {
            // 导入Excel
            List<User> userList = ExcelUtil.importExcel(file, 1, User.class);
            
            // 批量保存
            int count = userService.batchInsert(userList);
            
            log.info("用户导入成功,共导入{}条记录", count);
            return CommonResult.success(count);
            
        } catch (IOException e) {
            log.error("导入Excel失败", e);
            throw new ExcelException("导入Excel失败: " + e.getMessage());
        }
    }
    
    @PostMapping("/import-with-listener")
    @Transactional
    public CommonResult<Integer> importUsersWithListener(@RequestParam("file") MultipartFile file) {
        try {
            // 创建监听器
            UserExcelListener listener = new UserExcelListener();
            
            // 导入Excel(使用监听器)
            ExcelUtil.importExcel(file, 1, User.class, listener);
            
            // 获取解析的数据
            List<User> userList = listener.getUserList();
            
            // 批量保存
            int count = userService.batchInsert(userList);
            
            log.info("用户导入成功,共导入{}条记录", count);
            return CommonResult.success(count);
            
        } catch (IOException e) {
            log.error("导入Excel失败", e);
            throw new ExcelException("导入Excel失败: " + e.getMessage());
        }
    }
}

// 自定义Excel监听器
public class UserExcelListener extends ExcelListener<User> {
    
    private List<User> userList = new ArrayList<>();
    
    @Override
    public void invoke(User user, AnalysisContext context) {
        // 数据校验
        if (StringUtils.isBlank(user.getUsername())) {
            throw new ExcelException("第" + context.readRowHolder().getRowIndex() + "行:用户名不能为空");
        }
        
        // 数据处理
        user.setStatus(1);
        user.setCreateTime(LocalDateTime.now());
        
        userList.add(user);
    }
    
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("Excel解析完成,共解析{}条数据", userList.size());
    }
    
    public List<User> getUserList() {
        return userList;
    }
}

6.3 SQL注入防护集成

6.3.1 在过滤器中集成

java
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class SqlInjectFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                   HttpServletResponse response, 
                                   FilterChain filterChain) 
            throws ServletException, IOException {
        
        // 检查GET参数
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            String paramValue = request.getParameter(paramName);
            
            if (SqlInjectUtils.checkSqlInject(paramValue)) {
                log.warn("检测到SQL注入攻击,参数名: {}, 参数值: {}", paramName, paramValue);
                throw new RequestSqlInjectException("请求参数存在SQL注入攻击");
            }
        }
        
        // 检查POST参数(需要包装请求)
        if (CommonConstants.POST.equals(request.getMethod()) || 
            CommonConstants.PUT.equals(request.getMethod()) ||
            CommonConstants.PATCH.equals(request.getMethod())) {
            
            MultiReadHttpServletRequestWrapper wrappedRequest = 
                new MultiReadHttpServletRequestWrapper(request);
            
            // 读取请求体
            String requestBody = wrappedRequest.getBodyAsString();
            if (StringUtils.isNotBlank(requestBody)) {
                if (SqlInjectUtils.checkSqlInject(requestBody)) {
                    log.warn("检测到SQL注入攻击,请求体: {}", requestBody);
                    throw new RequestSqlInjectException("请求体存在SQL注入攻击");
                }
            }
            
            // 继续过滤器链
            filterChain.doFilter(wrappedRequest, response);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

6.3.2 在Service中集成

java
@Service
@Slf4j
public class UserService {
    
    public List<User> searchUsers(String keyword) {
        // 检查关键字是否包含SQL注入
        if (SqlInjectUtils.checkSqlInject(keyword)) {
            throw new RequestSqlInjectException("搜索关键字存在SQL注入攻击");
        }
        
        // 安全查询
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(User::getUsername, keyword);
        
        return userMapper.selectList(queryWrapper);
    }
}

7. 测试集成

7.1 单元测试配置

7.1.1 测试依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

7.1.2 测试配置

yaml
# src/test/resources/application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  
  redis:
    host: localhost
    port: 6379
    database: 1
  
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true

7.2 工具类测试

7.2.1 IpUtils 测试

java
@SpringBootTest
class IpUtilsTest {
    
    @Test
    void testGetIpAddress() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("x-forwarded-for", "192.168.1.1");
        
        Optional<String> ip = IpUtils.getIpAddress(request);
        assertTrue(ip.isPresent());
        assertEquals("192.168.1.1", ip.get());
    }
    
    @Test
    void testGetLocationInfo() {
        Optional<String> location = IpUtils.getLocationInfo("192.168.1.1");
        assertTrue(location.isPresent());
        assertTrue(location.get().contains("中国"));
    }
}

7.2.2 SpringUtil 测试

java
@SpringBootTest
class SpringUtilTest {
    
    @Test
    void testGetBean() {
        // 测试获取Bean
        RedisTemplate<?, ?> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
        assertNotNull(redisTemplate);
        
        // 测试获取ApplicationContext
        ApplicationContext context = SpringUtil.getApplicationContext();
        assertNotNull(context);
    }
}

7.3 异常测试

7.3.1 业务异常测试

java
@SpringBootTest
class BusinessExceptionTest {
    
    @Test
    void testBusinessException() {
        // 测试基本异常
        BusinessException ex1 = new BusinessException("测试异常");
        assertEquals("测试异常", ex1.getMessage());
        
        // 测试带响应码的异常
        BusinessException ex2 = new BusinessException(ResponseCodeEnums.USER_NOT_EXIST);
        assertEquals(ResponseCodeEnums.USER_NOT_EXIST.code(), ex2.getCode());
        assertEquals(ResponseCodeEnums.USER_NOT_EXIST.message(), ex2.getMessage());
        
        // 测试带数据的异常
        Map<String, Object> data = new HashMap<>();
        data.put("userId", 1L);
        BusinessException ex3 = new BusinessException(ResponseCodeEnums.USER_NOT_EXIST, data);
        assertEquals(data, ex3.getData());
    }
}

8. 性能优化

8.1 缓存优化

8.1.1 Redis 缓存配置

java
@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson序列化
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            mapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        serializer.setObjectMapper(mapper);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1)) // 默认缓存1小时
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
}

8.1.2 本地缓存配置

java
@Configuration
public class CaffeineConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        
        // 配置缓存策略
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .recordStats());
        
        return cacheManager;
    }
}

8.2 数据库优化

8.2.1 连接池配置

yaml
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      connection-test-query: SELECT 1

9. 监控与日志

9.1 日志配置

9.1.1 Logback 配置

xml
<!-- src/main/resources/logback-spring.xml -->
<configuration>
    <property name="LOG_PATH" value="logs"/>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    
    <logger name="com.beyondsoft" level="DEBUG"/>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

9.2 监控端点

9.2.1 Actuator 配置

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true
  endpoint:
    health:
      show-details: always

10. 常见问题

10.1 依赖冲突问题

问题:版本冲突

解决方案

xml
<dependency>
    <groupId>com.beyondsoft</groupId>
    <artifactId>common-base</artifactId>
    <version>${common-base.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </exclusion>
    </exclusions>
</dependency>

10.2 配置问题

问题:IP数据库找不到

解决方案

  1. 确保 ip2region.xdb 文件在 src/main/resources/db/ 目录下
  2. 或者指定自定义路径:
java
@Configuration
public class IpConfig {
    
    @Bean
    public Searcher ipSearcher() throws IOException {
        String dbPath = "/path/to/your/ip2region.xdb";
        return Searcher.newWithFileOnly(dbPath);
    }
}

10.3 性能问题

问题:Excel导入内存溢出

解决方案

  1. 使用流式读取
  2. 分批处理数据
  3. 增加JVM内存:-Xmx2g -Xms1g

11. 最佳实践

11.1 代码规范

  1. 统一异常处理:所有业务异常使用 BusinessException 及其子类
  2. 统一响应格式:Controller返回业务对象,由异常处理器统一包装
  3. 工具类使用:直接使用静态方法,无需实例化
  4. 常量使用:使用 CommonConstants 中的常量,避免魔法值

11.2 性能优化

  1. 缓存策略:合理使用Redis和本地缓存
  2. 数据库优化:使用连接池,合理设置参数
  3. 批量操作:Excel导入等操作使用批量处理

11.3 安全防护

  1. SQL注入防护:所有用户输入都要进行SQL注入检查
  2. 参数校验:使用 @Valid 注解进行参数校验
  3. 异常信息:避免在异常中泄露敏感信息

12. 升级指南

12.1 从旧版本升级

  1. 备份配置:备份现有的配置文件
  2. 更新依赖:更新pom.xml中的版本号
  3. 测试兼容性:运行测试确保兼容性
  4. 逐步部署:先在测试环境部署,验证无误后再上生产

文档信息

  • 版本: 1.0.0
  • 更新日期: 2025-12-30
  • 维护团队: BeyondSoft 架构组
  • 文档状态: 正式发布

相关文档

Copyright © 2025-present | 网站备案号:豫ICP备19038229号-1