AI摘要
本文详细介绍了如何使用Spring Boot 3.3.5、MyBatis-Flex和SQLite构建零工平台后端系统。项目支持工作发布、申请匹配、用户管理、支付结算等业务场景,强调高性能、高可用和易扩展。文章涵盖了架构设计、技术选型、核心功能实现和生产部署,分享了JWT认证体系、统一结果封装、工作管理核心业务、文件上传服务等关键功能的实现细节,以及数据库设计、部署实战、监控与运维、性能优化等方面的经验总结。
本文将详细分享我使用Spring Boot 3.3.5 + MyBatis-Flex + SQLite构建零工平台后端系统的完整经验,包含架构设计、技术选型、核心功能实现和生产部署。
项目背景
随着零工经济的快速发展,我决定开发一个现代化的零工服务平台。后端系统需要支持工作发布、申请匹配、用户管理、支付结算等复杂业务场景,同时要保证高性能、高可用和易扩展。
项目概览
- 项目名称: 智慧零工平台后端系统
- 技术栈: Spring Boot 3.3.5 + Java 21 + MyBatis-Flex + SQLite
- 项目地址: GitHub - Gigplatform_backend
- API文档: 24个业务模块,100+接口
- 部署方式: 宝塔面板 + PM2 + Nginx
技术选型分析
为什么选择Spring Boot 3?
- Java 21支持: 享受最新的语言特性和性能优化
- 原生镜像: 支持GraalVM原生编译,启动更快
- 响应式编程: 内置WebFlux支持
- 可观测性: 增强的监控和链路追踪
- 安全性: 升级的Spring Security
MyBatis-Flex vs MyBatis-Plus
经过对比,我选择了MyBatis-Flex:
// MyBatis-Flex 优势示例
// 1. 类型安全的查询
List<Job> jobs = queryWrapper
.select(JOB.ALL_COLUMNS)
.from(JOB)
.where(JOB.STATUS.eq("ACTIVE"))
.and(JOB.SALARY.ge(5000))
.orderBy(JOB.CREATE_TIME.desc())
.limit(10)
.list();
// 2. 智能字段映射
@Table("t_job")
public class Job {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("job_title")
private String title;
// 自动处理驼峰命名
private String companyName; // -> company_name
}
SQLite的选择
对于中小型项目,SQLite具有独特优势:
- 零配置: 无需独立数据库服务器
- 高性能: 本地文件访问,减少网络开销
- 事务支持: 完整的ACID特性
- 跨平台: 易于开发和部署
- 成本低: 降低运维复杂度
架构设计
整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端应用 │ │ API网关 │ │ 负载均衡 │
│ (uni-app) │◄───│ (Nginx) │◄───│ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────────────┐
│ Spring Boot │
│ (业务逻辑层) │
└─────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 认证授权 │ │ 业务服务 │ │ 数据访问 │
│ (JWT) │ │ (Service) │ │ (MyBatis) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────────┐
│ SQLite 数据库 │
└─────────────────┘
项目模块结构
com.example.backend/
├── controller/ # 控制器层
│ ├── AuthController # 认证授权
│ ├── JobsController # 工作管理
│ ├── UsersController # 用户管理
│ └── ... # 其他业务控制器
├── service/ # 业务逻辑层
│ ├── impl/ # 实现类
│ └── interfaces/ # 接口定义
├── mapper/ # 数据访问层
├── entity/ # 实体类
├── config/ # 配置类
├── common/ # 公共组件
│ ├── Result.java # 统一返回结果
│ ├── JwtUtil.java # JWT工具
│ └── JwtFilter.java # JWT过滤器
└── BackendApplication.java # 启动类
核心功能实现
1. JWT认证体系
@Component
public class JwtUtil {
@Value("${jwt.secret:default-secret}")
private String secret;
@Value("${jwt.expiration:86400}")
private Long expiration;
public String generateToken(String username) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimsFromToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
return null;
}
}
public boolean validateToken(String token) {
try {
Claims claims = getClaimsFromToken(token);
return claims != null && !claims.getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
// JWT过滤器
@Component
public class JwtFilter implements Filter {
@Autowired
private JwtUtil jwtUtil;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 跳过认证的路径
String path = httpRequest.getRequestURI();
if (isPublicPath(path)) {
chain.doFilter(request, response);
return;
}
String token = extractToken(httpRequest);
if (token == null || !jwtUtil.validateToken(token)) {
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpResponse.getWriter().write(
JSON.toJSONString(Result.error(102, "未登录,请先登录"))
);
return;
}
// 设置用户信息到上下文
Claims claims = jwtUtil.getClaimsFromToken(token);
LoginUser loginUser = new LoginUser();
loginUser.setUsername(claims.getSubject());
UserContext.setCurrentUser(loginUser);
chain.doFilter(request, response);
}
}
2. 统一结果封装
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success() {
return new Result<>(0, "调用成功", null);
}
public static <T> Result<T> success(T data) {
return new Result<>(0, "调用成功", data);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> error(String message) {
return new Result<>(200, message, null);
}
}
// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后重试");
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(220, message);
}
@ExceptionHandler(IllegalArgumentException.class)
public Result<String> handleIllegalArgumentException(IllegalArgumentException e) {
return Result.error(220, e.getMessage());
}
}
3. 工作管理核心业务
@RestController
@RequestMapping("/api/jobs")
@Slf4j
public class JobsController {
@Autowired
private JobsService jobsService;
// 获取工作列表(支持多条件筛选)
@GetMapping("/latest/list")
public Result<Map<String, Object>> getJobList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) String location,
@RequestParam(required = false) String salaryRange) {
try {
Page<Job> jobPage = jobsService.getJobList(
page, size, keyword, categoryId, location, salaryRange
);
Map<String, Object> result = new HashMap<>();
result.put("list", jobPage.getRecords());
result.put("total", jobPage.getTotal());
result.put("pages", jobPage.getPages());
result.put("current", jobPage.getCurrent());
return Result.success(result);
} catch (Exception e) {
log.error("获取工作列表失败", e);
return Result.error("获取工作列表失败");
}
}
// 申请工作
@PostMapping("/apply/{jobId}")
public Result<String> applyForJob(@PathVariable Long jobId) {
try {
LoginUser currentUser = UserContext.getCurrentUser();
// 获取工人资料
WorkerProfile profile = workerProfilesService.getByUserId(currentUser.getUserId());
if (profile == null) {
return Result.error(220, "请先完善工人资料");
}
// 检查是否已申请
boolean hasApplied = jobApplicationsService.hasApplied(jobId, profile.getId());
if (hasApplied) {
return Result.error(220, "您已申请过该工作");
}
// 创建申请记录
JobApplication application = new JobApplication();
application.setJobId(jobId);
application.setWorkerId(profile.getId());
application.setStatus("PENDING");
application.setApplyTime(new Date());
jobApplicationsService.save(application);
// 更新工作申请数量
jobsService.incrementApplyCount(jobId);
return Result.success("申请成功");
} catch (Exception e) {
log.error("申请工作失败, jobId: {}", jobId, e);
return Result.error("申请失败,请稍后重试");
}
}
}
// 业务服务实现
@Service
@Transactional
public class JobsServiceImpl implements JobsService {
@Autowired
private JobsMapper jobsMapper;
@Override
public Page<Job> getJobList(int page, int size, String keyword,
Long categoryId, String location, String salaryRange) {
QueryWrapper query = QueryWrapper.create()
.select(JOB.ALL_COLUMNS)
.from(JOB)
.where(JOB.STATUS.eq("ACTIVE"));
// 关键词搜索
if (StringUtils.hasText(keyword)) {
query.and(JOB.TITLE.like(keyword)
.or(JOB.DESCRIPTION.like(keyword))
.or(JOB.COMPANY_NAME.like(keyword)));
}
// 分类筛选
if (categoryId != null) {
query.and(JOB.CATEGORY_ID.eq(categoryId));
}
// 地区筛选
if (StringUtils.hasText(location)) {
query.and(JOB.LOCATION.like(location));
}
// 薪资范围筛选
if (StringUtils.hasText(salaryRange)) {
applySalaryFilter(query, salaryRange);
}
query.orderBy(JOB.CREATE_TIME.desc());
return jobsMapper.paginate(page, size, query);
}
private void applySalaryFilter(QueryWrapper query, String salaryRange) {
switch (salaryRange) {
case "0-3000":
query.and(JOB.SALARY_MIN.le(3000));
break;
case "3000-5000":
query.and(JOB.SALARY_MIN.ge(3000).and(JOB.SALARY_MAX.le(5000)));
break;
case "5000-8000":
query.and(JOB.SALARY_MIN.ge(5000).and(JOB.SALARY_MAX.le(8000)));
break;
case "8000+":
query.and(JOB.SALARY_MIN.ge(8000));
break;
}
}
}
4. 文件上传服务
@RestController
@RequestMapping("/api/upload")
@Slf4j
public class UploadController {
@Value("${file.upload.dir:uploads}")
private String uploadDir;
@Value("${file.upload.max-size:10485760}") // 10MB
private long maxFileSize;
@PostMapping("/image")
public Result<Map<String, String>> uploadImage(@RequestParam("file") MultipartFile file) {
try {
// 验证文件
if (file.isEmpty()) {
return Result.error(220, "文件不能为空");
}
if (file.getSize() > maxFileSize) {
return Result.error(220, "文件大小不能超过10MB");
}
// 验证文件类型
String contentType = file.getContentType();
if (!isValidImageType(contentType)) {
return Result.error(220, "只支持 jpg、png、gif 格式的图片");
}
// 生成文件名
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String filename = generateFileName() + "." + extension;
// 创建目录
Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 保存文件
Path filePath = uploadPath.resolve(filename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// 返回访问URL
String fileUrl = "/uploads/" + filename;
Map<String, String> result = new HashMap<>();
result.put("url", fileUrl);
result.put("filename", filename);
return Result.success(result);
} catch (Exception e) {
log.error("文件上传失败", e);
return Result.error("文件上传失败");
}
}
private boolean isValidImageType(String contentType) {
return contentType != null && (
contentType.equals("image/jpeg") ||
contentType.equals("image/png") ||
contentType.equals("image/gif")
);
}
private String generateFileName() {
return System.currentTimeMillis() + "_" +
ThreadLocalRandom.current().nextInt(1000, 9999);
}
}
数据库设计
核心表结构
-- 用户表
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20) NOT NULL,
user_type VARCHAR(20) NOT NULL, -- 'employer' 或 'employee'
avatar VARCHAR(255),
status VARCHAR(20) DEFAULT 'ACTIVE',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 工作表
CREATE TABLE jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(200) NOT NULL,
description TEXT,
category_id INTEGER,
employer_id INTEGER NOT NULL,
company_name VARCHAR(100),
location VARCHAR(100),
salary_min DECIMAL(10,2),
salary_max DECIMAL(10,2),
salary_type VARCHAR(20), -- 'hourly', 'daily', 'monthly'
requirements TEXT,
contact_info VARCHAR(255),
status VARCHAR(20) DEFAULT 'ACTIVE',
apply_count INTEGER DEFAULT 0,
view_count INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (employer_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
-- 工作申请表
CREATE TABLE job_applications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_id INTEGER NOT NULL,
worker_id INTEGER NOT NULL,
status VARCHAR(20) DEFAULT 'PENDING', -- PENDING, APPROVED, REJECTED
apply_time DATETIME DEFAULT CURRENT_TIMESTAMP,
response_time DATETIME,
employer_comment TEXT,
FOREIGN KEY (job_id) REFERENCES jobs(id),
FOREIGN KEY (worker_id) REFERENCES worker_profiles(id),
UNIQUE(job_id, worker_id)
);
-- 工人资料表
CREATE TABLE worker_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL UNIQUE,
real_name VARCHAR(50),
age INTEGER,
gender VARCHAR(10),
education VARCHAR(50),
experience_years INTEGER,
skills TEXT, -- JSON格式存储技能列表
resume TEXT,
certification VARCHAR(255),
location VARCHAR(100),
available_time VARCHAR(255),
hourly_rate DECIMAL(8,2),
rating DECIMAL(3,2) DEFAULT 0,
completed_jobs INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
索引优化
-- 性能优化索引
CREATE INDEX idx_jobs_status_created ON jobs(status, created_at);
CREATE INDEX idx_jobs_category_location ON jobs(category_id, location);
CREATE INDEX idx_jobs_employer ON jobs(employer_id);
CREATE INDEX idx_applications_job_worker ON job_applications(job_id, worker_id);
CREATE INDEX idx_applications_status ON job_applications(status);
CREATE INDEX idx_worker_profiles_user ON worker_profiles(user_id);
CREATE INDEX idx_worker_profiles_location ON worker_profiles(location);
部署实战
1. 宝塔面板部署
创建部署脚本:
#!/bin/bash
# deploy.sh - 自动化部署脚本
echo "=== 智慧零工平台后端部署脚本 ==="
# 1. 环境检查
check_environment() {
echo "检查部署环境..."
# 检查Java版本
if ! java -version 2>&1 | grep "21\." > /dev/null; then
echo "错误: 需要Java 21"
exit 1
fi
# 检查PM2
if ! command -v pm2 &> /dev/null; then
echo "安装PM2..."
npm install -g pm2
fi
echo "环境检查完成"
}
# 2. 备份数据
backup_data() {
echo "备份数据库..."
if [ -f "/www/wwwroot/smartgigplatform.db" ]; then
cp /www/wwwroot/smartgigplatform.db \
/www/wwwroot/backup/smartgigplatform_$(date +%Y%m%d_%H%M%S).db
echo "数据库备份完成"
fi
}
# 3. 部署应用
deploy_app() {
echo "部署应用..."
# 停止旧版本
pm2 stop smart-gig-platform 2>/dev/null || true
# 部署新版本
cp smart-gig-platformbackend-0.0.1-SNAPSHOT.jar /www/wwwroot/
cp application-prod.properties /www/wwwroot/
cp ecosystem.config.js /www/wwwroot/
# 设置权限
chown www:www /www/wwwroot/smart-gig-platformbackend-0.0.1-SNAPSHOT.jar
chmod 755 /www/wwwroot/smart-gig-platformbackend-0.0.1-SNAPSHOT.jar
# 启动应用
cd /www/wwwroot
pm2 start ecosystem.config.js
echo "应用部署完成"
}
# 4. 健康检查
health_check() {
echo "进行健康检查..."
sleep 10
# 检查进程状态
if pm2 show smart-gig-platform | grep "online" > /dev/null; then
echo "✅ 应用启动成功"
else
echo "❌ 应用启动失败"
pm2 logs smart-gig-platform --lines 20
exit 1
fi
# 检查API接口
if curl -s http://localhost:8081/actuator/health | grep "UP" > /dev/null; then
echo "✅ API健康检查通过"
else
echo "❌ API健康检查失败"
exit 1
fi
}
# 执行部署
main() {
check_environment
backup_data
deploy_app
health_check
echo "=== 部署完成 ==="
echo "应用状态: http://localhost:8081/actuator/health"
echo "日志查看: pm2 logs smart-gig-platform"
}
main "$@"
2. PM2进程管理
// ecosystem.config.js
module.exports = {
apps: [{
name: 'smart-gig-platform',
script: '/www/server/java/jdk-21.0.2/bin/java',
args: [
'-server',
'-Xms512m',
'-Xmx1024m',
'-Dspring.profiles.active=prod',
'-Dfile.encoding=UTF-8',
'-jar',
'smart-gig-platformbackend-0.0.1-SNAPSHOT.jar'
],
cwd: '/www/wwwroot',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production'
},
log_file: '/www/wwwroot/logs/app.log',
out_file: '/www/wwwroot/logs/app-out.log',
error_file: '/www/wwwroot/logs/app-error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true
}]
}
3. Nginx配置
# lgpt.ybyq.wang.conf
server {
listen 80;
server_name lgpt.ybyq.wang;
root /www/wwwroot/lgpt.ybyq.wang/h5;
index index.html;
# API代理
location /api/ {
proxy_pass http://127.0.0.1:8081/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 8k;
}
# 文件上传代理
location /uploads/ {
alias /www/wwwroot/uploads/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA路由支持
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types
application/json
application/javascript
text/css
text/javascript
text/plain;
# 访问日志
access_log /www/wwwlogs/lgpt.ybyq.wang.log;
error_log /www/wwwlogs/lgpt.ybyq.wang.error.log;
}
监控与运维
1. 应用监控
// 自定义健康检查
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try {
// 检查数据库连接
try (Connection connection = dataSource.getConnection()) {
boolean isValid = connection.isValid(5);
if (isValid) {
return Health.up()
.withDetail("database", "可用")
.withDetail("connection_pool", getConnectionPoolInfo())
.build();
}
}
} catch (Exception e) {
return Health.down()
.withDetail("database", "不可用")
.withDetail("error", e.getMessage())
.build();
}
return Health.down().build();
}
private Map<String, Object> getConnectionPoolInfo() {
Map<String, Object> info = new HashMap<>();
info.put("active", "查询活跃连接数");
info.put("idle", "查询空闲连接数");
return info;
}
}
// 自定义指标
@Component
public class BusinessMetrics {
private final MeterRegistry meterRegistry;
private final Counter jobCreatedCounter;
private final Counter jobAppliedCounter;
public BusinessMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.jobCreatedCounter = Counter.builder("jobs.created")
.description("创建的工作数量")
.register(meterRegistry);
this.jobAppliedCounter = Counter.builder("jobs.applied")
.description("申请的工作数量")
.register(meterRegistry);
}
public void incrementJobCreated() {
jobCreatedCounter.increment();
}
public void incrementJobApplied() {
jobAppliedCounter.increment();
}
}
2. 日志管理
# application-prod.properties
logging:
level:
com.example.backend: INFO
org.springframework.web: WARN
org.springframework.security: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file:
name: /www/wwwroot/logs/application.log
max-size: 100MB
max-history: 30
3. 备份策略
#!/bin/bash
# backup.sh - 数据备份脚本
BACKUP_DIR="/www/wwwroot/backup"
DB_FILE="/www/wwwroot/smartgigplatform.db"
UPLOAD_DIR="/www/wwwroot/uploads"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 数据库备份
backup_database() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/smartgigplatform_$timestamp.db"
echo "备份数据库..."
cp $DB_FILE $backup_file
gzip $backup_file
echo "数据库备份完成: $backup_file.gz"
}
# 文件备份
backup_uploads() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/uploads_$timestamp.tar.gz"
echo "备份上传文件..."
tar -czf $backup_file -C /www/wwwroot uploads/
echo "文件备份完成: $backup_file"
}
# 清理旧备份(保留7天)
cleanup_old_backups() {
echo "清理旧备份文件..."
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete
echo "清理完成"
}
# 执行备份
main() {
echo "=== 开始备份 $(date) ==="
backup_database
backup_uploads
cleanup_old_backups
echo "=== 备份完成 $(date) ==="
}
main
性能优化
1. 数据库查询优化
// 使用MyBatis-Flex的批量操作
@Service
public class JobsBatchService {
@Autowired
private JobsMapper jobsMapper;
// 批量插入工作
@Transactional
public void batchCreateJobs(List<Job> jobs) {
// 使用MyBatis-Flex的批量插入
jobsMapper.insertBatch(jobs);
}
// 批量更新状态
@Transactional
public void batchUpdateStatus(List<Long> jobIds, String status) {
UpdateWrapper updateWrapper = UpdateWrapper.create()
.set(JOB.STATUS, status)
.set(JOB.UPDATED_AT, new Date())
.where(JOB.ID.in(jobIds));
jobsMapper.update(null, updateWrapper);
}
// 分页查询优化
public Page<JobVO> getJobsWithDetails(int page, int size) {
// 使用关联查询减少N+1问题
QueryWrapper query = QueryWrapper.create()
.select(
JOB.ALL_COLUMNS,
CATEGORY.NAME.as("categoryName"),
USER.USERNAME.as("employerName")
)
.from(JOB)
.leftJoin(CATEGORY).on(JOB.CATEGORY_ID.eq(CATEGORY.ID))
.leftJoin(USER).on(JOB.EMPLOYER_ID.eq(USER.ID))
.where(JOB.STATUS.eq("ACTIVE"))
.orderBy(JOB.CREATE_TIME.desc());
return jobsMapper.paginate(page, size, query);
}
}
2. 缓存策略
// Redis缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
RedisCacheManager.Builder builder = RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory())
.cacheDefaults(cacheConfiguration());
return builder.build();
}
private RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
// 使用缓存
@Service
public class JobsCacheService {
@Cacheable(value = "jobs", key = "#jobId")
public Job getJobById(Long jobId) {
return jobsMapper.selectOneById(jobId);
}
@Cacheable(value = "job-list", key = "#page + '_' + #size + '_' + #keyword")
public Page<Job> getJobList(int page, int size, String keyword) {
// 查询逻辑
return jobsMapper.paginate(page, size, query);
}
@CacheEvict(value = "jobs", key = "#job.id")
public void updateJob(Job job) {
jobsMapper.update(job);
}
@CacheEvict(value = {"jobs", "job-list"}, allEntries = true)
public void clearAllCache() {
// 清空所有缓存
}
}
开发经验总结
最佳实践
- 分层架构: 严格按照Controller-Service-Mapper分层
- 异常处理: 统一异常处理和错误码管理
- 参数校验: 使用@Valid注解进行参数验证
- 事务管理: 合理使用@Transactional注解
- 日志记录: 关键操作添加日志记录
- API文档: 使用Swagger生成API文档
踩过的坑
- SQLite并发问题: 使用连接池解决并发访问
- JWT安全性: 定期更换密钥,设置合理过期时间
- 文件上传: 限制文件大小和类型,防止恶意上传
- 数据库备份: 定期备份,避免数据丢失
- 性能优化: 合理使用索引,避免N+1查询问题
结语
通过这次Spring Boot 3 + MyBatis-Flex的实践,我深入体验了现代Java开发的便利性。项目从零到生产部署,积累了丰富的实战经验。
关键收获包括:
- 现代化技术栈能显著提升开发效率
- 合理的架构设计是项目成功的基石
- 性能优化需要在开发过程中持续关注
- 监控运维对生产环境稳定性至关重要
希望这篇文章能为Java后端开发者提供一些参考和启发。
项目链接:
- 💻 GitHub仓库: https://github.com/BXCQ/Gigplatform_backend
- 🌐 在线API: https://lgpt.ybyq.wang/api/
- 📚 技术博客: https://blog.ybyq.wang/
欢迎交流讨论!邮箱: 676567473@qq.com