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.log4.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: true7.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 19. 监控与日志
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: always10. 常见问题
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数据库找不到
解决方案:
- 确保
ip2region.xdb文件在src/main/resources/db/目录下 - 或者指定自定义路径:
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导入内存溢出
解决方案:
- 使用流式读取
- 分批处理数据
- 增加JVM内存:
-Xmx2g -Xms1g
11. 最佳实践
11.1 代码规范
- 统一异常处理:所有业务异常使用
BusinessException及其子类 - 统一响应格式:Controller返回业务对象,由异常处理器统一包装
- 工具类使用:直接使用静态方法,无需实例化
- 常量使用:使用
CommonConstants中的常量,避免魔法值
11.2 性能优化
- 缓存策略:合理使用Redis和本地缓存
- 数据库优化:使用连接池,合理设置参数
- 批量操作:Excel导入等操作使用批量处理
11.3 安全防护
- SQL注入防护:所有用户输入都要进行SQL注入检查
- 参数校验:使用
@Valid注解进行参数校验 - 异常信息:避免在异常中泄露敏感信息
12. 升级指南
12.1 从旧版本升级
- 备份配置:备份现有的配置文件
- 更新依赖:更新pom.xml中的版本号
- 测试兼容性:运行测试确保兼容性
- 逐步部署:先在测试环境部署,验证无误后再上生产
文档信息
- 版本: 1.0.0
- 更新日期: 2025-12-30
- 维护团队: BeyondSoft 架构组
- 文档状态: 正式发布
相关文档
- 架构设计文档
- [API