Skip to content

common-base 开发文档

1. 项目概述

common-base 是一个基于 Spring Boot 3.x 的公共基础模块,提供了一系列通用功能和工具类,用于支撑其他业务模块的开发。该模块包含了常量定义、异常体系、数据模型、工具类等核心组件,旨在提高开发效率和代码复用性。

2. 核心功能模块

2.1 常量定义

提供了项目中常用的常量定义,包括 HTTP 方法、操作类型、文件类型、IP 地址相关等常量。

主要常量类

  • CommonConstants:通用常量(HTTP方法、操作类型、文件类型等)
  • HeaderConstants:请求头常量
  • RequestConstants:请求相关常量
  • CaffeineTimeConstant:缓存时间常量

2.2 异常体系

定义了完整的异常体系,包括业务异常、参数异常、系统异常等,便于统一处理和管理系统中的异常情况。

主要异常类型

  • BusinessException:业务异常
  • ParameterInvalidException:参数无效异常
  • NotFoundException:资源不存在异常
  • DataConflictException:数据冲突异常
  • InternalServerException:内部服务器异常
  • RequestException:请求异常基类
  • RequestSqlInjectException:SQL 注入异常
  • RequestEncryptException:请求加密异常
  • RemoteAccessException:远程访问异常
  • ExcelException:Excel操作异常
  • FileNotExistException:文件不存在异常

异常枚举

  • ResponseCodeEnums:响应码枚举(200-成功,10001-参数错误,20001-用户错误等)
  • RequestExceptionEnums:请求异常枚举(映射异常类到HTTP状态码和响应码)

2.3 数据模型

提供了通用的数据模型类,用于统一数据传输和响应格式。

主要模型类

  • CommonResult:统一响应结果(包含status、code、msg、data、timestamp、traceId、reason)
  • ErrorResult:错误结果
  • Page:分页模型(支持当前页、每页大小、总记录数、数据列表)
  • FlexBaseEntity:基础实体类(包含createTime、updateTime、createBy、updateBy等字段)
  • ThreadShareDataDTO:线程共享数据
  • BeyondSoftThirdAppKey:第三方应用密钥

2.4 工具类

提供了丰富的工具类,涵盖了 IP 处理、Excel 操作、Redis 操作、Spring 工具等多个方面。

主要工具类

  • IpUtils:IP 地址处理工具(基于ip2region库)
  • ExcelUtil:Excel 操作工具(基于FastExcel库)
  • RedisUtil:Redis 操作工具(需要注入StringRedisTemplate)
  • MongoUtil:MongoDB 操作工具
  • SpringUtil:Spring 工具类(获取Bean、应用上下文等)
  • TokenUtils:Token 处理工具
  • SqlInjectUtils:SQL 注入检测工具
  • ClassUtils:类操作工具
  • MimeTypeUtils:MIME 类型工具
  • PdfUtil:PDF 操作工具(基于PDFBox)

2.5 注解

提供了一系列注解,用于简化开发和提高代码可读性。

主要注解

  • 查询条件注解:@Eq(等于)、@Gt(大于)、@Lt(小于)、@Gte(大于等于)、@Lte(小于等于)、@Ne(不等于)、@In(包含)、@Regex(正则匹配)
  • 操作符注解:@AndOperator(与操作)、@OrOperator(或操作)、@NorOperator(非或操作)
  • 其他注解:@ConditionsAnnotation(条件注解标记)、@No(否操作)

2.6 监听器

提供了实体监听器和 Excel 监听器,用于处理实体生命周期事件和 Excel 导入导出事件。

主要监听器

  • BaseEntityListener:基础实体监听器(自动填充createBy、updateBy、createTime、updateTime字段)
  • ExcelListener:Excel 监听器(用于Excel导入时的数据处理)

2.7 方言扩展

提供了数据库方言扩展,支持动态排序功能。

主要方言类

  • DynamicOrderByDialect:动态排序方言(自动为查询添加order by id desc)

2.8 请求包装

提供了请求包装类,用于支持多次读取请求体。

主要包装类

  • MultiReadHttpServletRequestWrapper:多读取 HTTP 请求包装器(缓存请求体,支持重复读取)

2.9 日志服务

提供了统一的日志服务,支持PostgreSQL自动分区管理。

主要服务类

  • BeyondSoftLogService:日志服务接口
  • BeyondSoftLogServiceImpl:日志服务实现(支持PostgreSQL自动分区、Caffeine缓存、异步批量写入)

3. 主要API和使用方法

3.1 统一响应结果

3.1.1 成功响应

java
// 基本成功响应
CommonResult result1 = CommonResult.success();
// 输出: {"status":200,"code":200,"msg":"操作成功","data":null,"timestamp":1634567890000,"traceId":null,"reason":null}

// 带数据的成功响应
User user = new User();
user.setId(1L);
user.setUsername("admin");
CommonResult result2 = CommonResult.success(user);
// 输出: {"status":200,"code":200,"msg":"操作成功","data":{"id":1,"username":"admin"},"timestamp":1634567890000,"traceId":"1234567890abcdef","reason":null}

3.1.2 失败响应

java
// 基本失败响应
CommonResult result3 = CommonResult.failure(ResponseCodeEnums.PARAM_IS_INVALID);
// 输出: {"status":null,"code":10001,"msg":"参数校验不通过","data":null,"timestamp":1634567890000,"traceId":null,"reason":null}

// 带数据的失败响应
Map<String, String> errors = new HashMap<>();
errors.put("username", "用户名不能为空");
errors.put("password", "密码长度不能小于6位");
CommonResult result4 = CommonResult.failure(ResponseCodeEnums.PARAM_IS_INVALID, errors);
// 输出: {"status":null,"code":10001,"msg":"参数校验不通过","data":{"username":"用户名不能为空","password":"密码长度不能小于6位"},"timestamp":1634567890000,"traceId":null,"reason":null}

3.1.3 在Controller中使用

java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        // 业务逻辑
        User user = userService.getUserById(id);
        if (user == null) {
            throw new BusinessException(ResponseCodeEnums.USER_NOT_EXIST);
        }
        return user;
    }
    
    @PostMapping
    public User createUser(@RequestBody @Valid UserCreateRequest request) {
        // 业务逻辑
        User user = userService.createUser(request);
        return user;
    }
    
    @GetMapping("/list")
    public List<User> listUsers() {
        // 业务逻辑
        return userService.getAllUsers();
    }
}

// 注意:实际返回会在web模块中经过统一处理,格式化为CommonResult

3.2 异常体系

3.2.1 基本异常使用

java
// 抛出业务异常
throw new BusinessException("业务异常信息");

// 使用响应码枚举抛出异常
throw new BusinessException(ResponseCodeEnums.USER_NOT_EXIST);

// 带数据的异常
Map<String, Object> data = new HashMap<>();
data.put("userId", 1L);
throw new BusinessException(ResponseCodeEnums.USER_NOT_EXIST, data);

// 参数无效异常
throw new ParameterInvalidException("参数无效");

// 资源不存在异常
throw new NotFoundException("资源不存在");

// 数据冲突异常
throw new DataConflictException(ResponseCodeEnums.DATA_CONFLICT);

// 内部服务器异常
throw new InternalServerException();

3.2.2 在Service中使用

java
@Service
public class UserServiceImpl implements UserService {
    
    @Override
    public User createUser(UserCreateRequest request) {
        // 检查用户名是否已存在
        User existingUser = userMapper.findByUsername(request.getUsername());
        if (existingUser != null) {
            throw new BusinessException(ResponseCodeEnums.USER_HAS_EXISTED);
        }
        
        // 检查参数有效性
        if (request.getPassword().length() < 6) {
            throw new ParameterInvalidException("密码长度不能小于6位");
        }
        
        // 创建用户
        User user = new User();
        user.setUsername(request.getUsername());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        userMapper.insert(user);
        
        return user;
    }
    
    @Override
    public User getUserById(Long id) {
        User user = userMapper.findById(id);
        if (user == null) {
            throw new NotFoundException(ResponseCodeEnums.USER_NOT_EXIST);
        }
        return user;
    }
}

3.3 工具类

3.3.1 IP地址处理工具

java
// 在Controller中获取请求IP地址
@GetMapping("/ip")
public CommonResult<String> getIp(HttpServletRequest request) {
    Optional<String> ip = IpUtils.getIpAddress(request);
    return CommonResult.success(ip.orElse(CommonConstants.NO_IP));
}

// 获取IP地址对应的位置信息
String ip = "192.168.1.1";
Optional<String> location = IpUtils.getLocationInfo(ip);
System.out.println("IP地址: " + ip + ",位置: " + location.orElse(CommonConstants.NO_LOCATION));
// 输出示例: IP地址: 192.168.1.1,位置: 中国|0|广东省|深圳市|电信

3.3.2 Spring工具类

java
// 获取Spring Bean
UserService userService = SpringUtil.getBean(UserService.class);

// 通过name获取Bean
Object someBean = SpringUtil.getBean("beanName");

// 通过name和class获取Bean
UserService userService = SpringUtil.getBean("userService", UserService.class);

// 获取应用上下文
ApplicationContext applicationContext = SpringUtil.getApplicationContext();

3.3.3 Redis工具类

注意:RedisUtil不是静态工具类,需要注入使用

java
@Service
public class UserService {
    
    @Autowired
    private RedisUtil redisUtil;
    
    public void cacheUser(User user) {
        // 设置字符串值
        redisUtil.set("user:" + user.getId(), user.getUsername(), Duration.ofHours(1));
        
        // 设置对象(自动序列化为JSON)
        redisUtil.set("user:object:" + user.getId(), user, Duration.ofHours(1));
        
        // 获取字符串值
        String username = redisUtil.get("user:" + user.getId());
        
        // 获取对象(自动反序列化)
        User cachedUser = redisUtil.get("user:object:" + user.getId(), User.class);
        
        // 删除键
        redisUtil.delete("user:" + user.getId());
        
        // 判断键是否存在
        boolean exists = redisUtil.hasKey("user:" + user.getId());
        
        // 设置过期时间
        redisUtil.expire("user:" + user.getId(), 3600, TimeUnit.SECONDS);
    }
}

3.3.4 Excel工具类

java
// 导出Excel
@GetMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {
    List<User> userList = userService.getAllUsers();
    ExcelUtil.exportExcel(userList, response, "用户列表", "用户信息", User.class);
}

// 导入Excel
@PostMapping("/import")
public CommonResult<Integer> importExcel(@RequestParam("file") MultipartFile file) throws IOException {
    List<User> userList = ExcelUtil.importExcel(file, User.class);
    int count = userService.batchInsert(userList);
    return CommonResult.success(count);
}

3.3.5 Sql注入检测工具

java
// 检测SQL注入
String sql = "SELECT * FROM user WHERE username = 'admin' OR '1'='1'";
boolean hasSqlInject = SqlInjectUtils.check(sql);
if (hasSqlInject) {
    throw new RequestSqlInjectException("请求参数存在SQL注入攻击");
}

// 检测请求参数中的SQL注入
public void checkRequestParams(HttpServletRequest request) {
    Enumeration<String> parameterNames = request.getParameterNames();
    while (parameterNames.hasMoreElements()) {
        String paramName = parameterNames.nextElement();
        String paramValue = request.getParameter(paramName);
        if (SqlInjectUtils.check(paramValue)) {
            throw new RequestSqlInjectException("请求参数" + paramName + "存在SQL注入攻击");
        }
    }
}

// 删除转义字符
String safeText = SqlInjectUtils.removeEscapeCharacter("user' OR '1'='1");
// 结果: user OR 11

3.4 基础实体类

3.4.1 继承基础实体类

java
import com.beyondsoft.common.model.FlexBaseEntity;
import lombok.Data;

@Data
public class User extends FlexBaseEntity {
    private Long id;
    private String username;
    private String password;
    private String email;
    private String phone;
    private Integer status;
    private Long departmentId;
}

3.4.2 使用BaseEntityListener

java
import com.beyondsoft.common.listener.BaseEntityListener;
import com.mybatisflex.annotation.Table;
import lombok.Data;

@Data
@Table(value = "user")
@InsertListener(BaseEntityListener.class)
@UpdateListener(BaseEntityListener.class)
public class User extends FlexBaseEntity {
    // 字段定义
}

3.5 监听器

3.5.1 使用ExcelListener

java
import com.beyondsoft.common.listener.ExcelListener;
import cn.idev.excel.context.AnalysisContext;

public class UserExcelListener extends ExcelListener<User> {
    
    private List<User> userList = new ArrayList<>();
    
    @Override
    public void invoke(User user, AnalysisContext context) {
        // 处理每一行数据
        userList.add(user);
    }
    
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 所有数据解析完成后处理
        System.out.println("共解析了" + userList.size() + "条数据");
    }
    
    public List<User> getUserList() {
        return userList;
    }
}

// 使用监听器导入Excel
@PostMapping("/import-with-listener")
public CommonResult<Integer> importExcelWithListener(@RequestParam("file") MultipartFile file) throws IOException {
    UserExcelListener listener = new UserExcelListener();
    InputStream inputStream = new BufferedInputStream(file.getInputStream());
    FastExcelFactory.read(inputStream, listener)
            .head(User.class)
            .doReadAll();
    List<User> userList = listener.getUserList();
    int count = userService.batchInsert(userList);
    return CommonResult.success(count);
}

3.6 请求包装器

3.6.1 在过滤器中使用MultiReadHttpServletRequestWrapper

java
@Component
public class RequestLogFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 包装请求,支持多次读取请求体
        MultiReadHttpServletRequestWrapper wrappedRequest = new MultiReadHttpServletRequestWrapper(request);
        
        // 读取请求体
        String requestBody = wrappedRequest.getMultiBody();
        
        // 记录请求日志
        log.info("Request: {} {}", wrappedRequest.getMethod(), wrappedRequest.getRequestURI());
        log.info("Request Body: {}", requestBody);
        
        // 继续过滤器链,使用包装后的请求
        filterChain.doFilter(wrappedRequest, response);
    }
}

3.7 常量使用

java
// HTTP方法常量
if (request.getMethod().equals(CommonConstants.GET)) {
    // 处理GET请求
} else if (request.getMethod().equals(CommonConstants.POST)) {
    // 处理POST请求
}

// 操作类型常量
log.info("用户执行了{}操作", CommonConstants.MODIFY_INSERT);
log.info("用户执行了{}操作", CommonConstants.MODIFY_UPDATE);
log.info("用户执行了{}操作", CommonConstants.MODIFY_DELETE);

// 文件类型常量
if (file.getContentType().equals(CommonConstants.EXCEL_CONTENT)) {
    // 处理Excel文件
}

// 超级管理员常量
if (user.getUsername().equals(CommonConstants.SUPER_ADMIN)) {
    // 超级管理员特殊处理
}

// 分隔符常量
String[] parts = "a,b,c".split(CommonConstants.SPLIT_COMMA);
String fileName = "file" + CommonConstants.SPLIT_LINE + "name";
String keyValue = "key" + CommonConstants.SPLIT_EQUAL + "value";

3.8 分页模型

3.8.1 创建分页对象

java
// 创建分页对象
Page<User> page = new Page<>(1, 10); // 当前页码1,每页10条

// 设置总记录数
page.setTotal(100L);

// 设置数据
page.setData(userList);

// 获取分页信息
int currentPage = page.getPageNum(); // 当前页码
int pageSize = page.getPageSize(); // 每页大小
long total = page.getTotal(); // 总记录数
List<User> records = page.getData(); // 当前页数据
long skip = page.getSkip(); // 跳过的记录数(用于分页查询)

// 检查当前页是否有数据
boolean hasData = page.currentIsHaveData();

3.8.2 在Controller中使用

java
@GetMapping("/list")
public CommonResult<Page<User>> listUsers(
        @RequestParam(defaultValue = "1") Integer current,
        @RequestParam(defaultValue = "10") Integer size,
        @RequestParam(required = false) String username) {
    
    Page<User> page = new Page<>(current, size);
    
    // 构建查询条件
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    if (StringUtils.isNotBlank(username)) {
        queryWrapper.like(User::getUsername, username);
    }
    
    // 执行分页查询
    long total = userMapper.selectCount(queryWrapper);
    page.setTotal(total);
    
    if (page.currentIsHaveData()) {
        List<User> userList = userMapper.selectListByPage(page.getSkip(), page.getPageSize(), queryWrapper);
        page.setData(userList);
    } else {
        page.setData(Collections.emptyList());
    }
    
    return CommonResult.success(page);
}

3.9 日志服务

3.9.1 使用日志服务

java
@Service
public class AuditLogService {
    
    @Autowired
    private BeyondSoftLogService logService;
    
    public void saveLoginLog(BeyondSoftLoginLog loginLog) {
        // 保存登录日志(异步)
        logService.save(loginLog, false);
    }
    
    public void saveAuditLog(BeyondSoftDataAuditLog auditLog) {
        // 保存审计日志,需要默认对比
        logService.save(auditLog, true);
    }
    
    public void batchSaveRequestLogs(List<BeyondSoftRequestLog> requestLogs) {
        // 批量保存请求日志
        logService.saveBatch(requestLogs, false);
    }
    
    public Page<BeyondSoftLoginLog> queryLoginLogs(Map<String, String> condition, Integer pageSize, Integer pageNum) {
        // 查询登录日志
        return logService.queryMongoLogs(BeyondSoftLoginLog.class, condition, pageSize, pageNum);
    }
}

3.9.2 日志服务特性

  1. 自动分区管理:基于PostgreSQL的按天自动分区
  2. Caffeine缓存:缓存分区状态,24小时过期
  3. 异步写入:使用@Async注解实现异步批量写入
  4. 多数据源支持:支持MyBatis-Flex多数据源切换
  5. 审计日志对比:自动对比新旧对象,生成变更记录
  6. 不可序列化字段处理:自动处理MultipartFile等不可序列化字段

4. 依赖说明

依赖项版本用途备注
fastexcel-Excel 操作高性能Excel读写库
commons-compress-压缩处理文件压缩解压
lombok-简化 Java 代码自动生成getter/setter等
ip2region-IP 地址解析IP地址定位库
spring-boot-starter-data-redis-Redis 操作Redis客户端
hutool-json-JSON 处理JSON序列化/反序列化
hutool-db-数据库操作数据库工具
spring-data-commons-Spring 数据处理Spring数据通用组件
mybatis-flex-spring-boot3-starter-MyBatis Flex 支持ORM框架
sa-token-spring-boot3-starter-认证授权权限认证框架
spring-boot-starter-data-mongodb-MongoDB 支持MongoDB客户端
spring-boot-starter-mail-邮件发送邮件服务
spring-boot-starter-thymeleaf-模板引擎模板渲染
spring-boot-starter-web-Web 支持Web MVC
commons-lang3-语言工具Apache Commons工具
logback-core-日志处理日志框架核心
logback-classic-日志处理日志框架实现
caffeine-缓存本地缓存库
spring-jdbc-JDBC 支持JDBC操作
pdfbox-PDF 处理PDF文档操作

注意:实际版本由父pom统一管理,具体版本请查看pom.xml文件。

5. 配置说明

5.1 日志服务配置

yaml
# application.yml
beyond-soft:
  log:
    schema: audit  # 日志表所在的schema,默认public
    datasource-key: audit  # MyBatis-Flex多数据源key,单数据源可不配置

5.2 Redis配置

yaml
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

5.3 IP地址数据库配置

确保 db/ip2region.xdb 文件存在于classpath下,用于IP地址定位。

6. 注意事项

  1. RedisUtil使用:RedisUtil不是静态工具类,需要通过Spring注入使用。它依赖于StringRedisTemplate。

  2. IP地址解析:使用 ip2region 进行 IP 地址解析,需确保 ip2region.xdb 文件存在于 classpath 的 db/ 目录下。

  3. 日志服务初始化:BeyondSoftLogServiceImpl需要DataSource或JdbcTemplate进行初始化,支持单数据源和多数据源场景。

  4. PostgreSQL分区:日志服务使用PostgreSQL的按天分区功能,会自动创建未来7天的分区。需要确保主表已存在。

  5. Excel操作:ExcelUtil基于FastExcel库,支持.xlsx格式,性能优于Apache POI。

  6. SQL注入检测:SqlInjectUtils使用正则表达式检测SQL注入,但可能存在误报或漏报,建议结合其他安全措施。

  7. 实体监听器:BaseEntityListener依赖于SaToken获取当前用户ID,如果未使用SaToken,会尝试从ThreadLocal获取。

  8. 异常处理:所有异常都继承自RequestException,可以通过RequestExceptionEnums映射到HTTP状态码和响应码。

  9. 工具类线程安全:大部分工具类都是线程安全的,但RedisUtil等需要确保正确配置连接池。

  10. 版本兼容性:该模块基于Spring Boot 3.x开发,与Spring Boot 2.x不兼容。

7. 常见问题

7.1 RedisUtil无法注入

问题:启动时报错,找不到RedisUtil的Bean。

解决方案

  1. 确保配置了StringRedisTemplate的Bean
  2. 在配置类中创建RedisUtil的Bean:
java
@Configuration
public class RedisConfig {
    
    @Bean
    public RedisUtil redisUtil(StringRedisTemplate redisTemplate) {
        return new RedisUtil(redisTemplate);
    }
}

7.2 IP地址解析失败

问题:IpUtils.getLocationInfo()返回空。

解决方案

  1. 检查classpath下是否有db/ip2region.xdb文件
  2. 确保文件路径正确:src/main/resources/db/ip2region.xdb
  3. 下载最新的ip2region数据库文件

7.3 日志服务分区创建失败

问题:日志保存时报分区不存在错误。

解决方案

  1. 确保PostgreSQL版本支持分区功能(10.0+)
  2. 检查主表是否已创建
  3. 检查数据库用户是否有创建表的权限
  4. 查看日志服务的schema配置是否正确

7.4 BaseEntityListener无法获取用户ID

问题:保存实体时,createBy和updateBy字段为null。

解决方案

  1. 确保已集成SaToken并正确配置
  2. 或者通过ThreadShareDataDTO设置用户信息
  3. 检查用户是否已登录

8. 版本历史

  • v1.0.0 (2025-12-30): 初始版本
    • 提供基础功能和工具类
    • 统一的异常体系
    • 完整的日志服务(PostgreSQL自动分区)
    • 常用工具类(IP、Excel、Redis等)

9. 联系方式

如有问题或建议,请联系开发团队。


文档更新时间:2025-12-30
文档版本:1.0.0
模块版本:${revision} //

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