作为一名有多年开发经验的Java工程师,最近我完成了一个基于Spring Boot 3的网文阅读系统开发。这个项目采用前后端分离架构,后端使用Spring Boot 3 + MyBatis Plus,前端使用微信小程序,数据库选用MySQL 8.0。系统主要实现了小说展示、购买阅读、书架管理等功能模块。
在开发过程中,我发现Spring Boot 3的新特性如GraalVM原生镜像支持、JDK 17的Record类等都能很好地提升系统性能。同时,微信小程序的轻量级特性也让用户能够随时随地享受阅读乐趣。
后端技术栈:
前端技术栈:
数据库:
系统采用经典的三层架构:
code复制客户端层(微信小程序)
↓
API网关层(Spring Cloud Gateway)
↓
业务逻辑层(Spring Boot微服务)
↓
数据访问层(MyBatis Plus + Redis)
↓
数据存储层(MySQL)
这种分层架构的优势在于:
采用JWT实现无状态认证,关键代码如下:
java复制// JWT工具类
public class JwtUtil {
private static final String SECRET = "your-secret-key";
private static final long EXPIRATION = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public static String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
实现小说CRUD操作,使用MyBatis Plus简化开发:
java复制@Service
public class NovelServiceImpl extends ServiceImpl<NovelMapper, Novel> implements NovelService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public Page<Novel> getNovelList(int pageNum, int pageSize) {
String cacheKey = "novel:list:" + pageNum + ":" + pageSize;
Page<Novel> page = (Page<Novel>) redisTemplate.opsForValue().get(cacheKey);
if (page == null) {
page = page(new Page<>(pageNum, pageSize));
redisTemplate.opsForValue().set(cacheKey, page, 1, TimeUnit.HOURS);
}
return page;
}
@Override
@Transactional
public boolean addNovel(Novel novel) {
boolean result = save(novel);
if (result) {
// 清除相关缓存
redisTemplate.delete("novel:list:*");
}
return result;
}
}
集成微信支付API实现小说购买功能:
java复制@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@Autowired
private WxPayService wxPayService;
@PostMapping("/create")
public Result createOrder(@RequestBody PaymentRequest request) {
try {
// 1. 创建微信支付订单
WxPayUnifiedOrderRequest wxRequest = new WxPayUnifiedOrderRequest();
wxRequest.setBody("小说购买-" + request.getNovelName());
wxRequest.setOutTradeNo(generateOrderNo());
wxRequest.setTotalFee(request.getAmount());
wxRequest.setSpbillCreateIp(request.getClientIp());
wxRequest.setNotifyUrl("https://yourdomain.com/api/payment/notify");
wxRequest.setTradeType("JSAPI");
wxRequest.setOpenid(request.getOpenid());
// 2. 调用微信支付接口
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(wxRequest);
// 3. 返回前端需要的支付参数
Map<String, String> payParams = new HashMap<>();
payParams.put("appId", result.getAppid());
payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
payParams.put("nonceStr", result.getNonceStr());
payParams.put("package", "prepay_id=" + result.getPrepayId());
payParams.put("signType", "MD5");
// 4. 生成签名
String sign = WxPayUtil.generateSignature(payParams, wxPayService.getConfig().getMchKey());
payParams.put("paySign", sign);
return Result.success(payParams);
} catch (Exception e) {
log.error("创建支付订单失败", e);
return Result.fail("创建支付订单失败");
}
}
}
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
sql复制CREATE TABLE `novel` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '小说标题',
`author` varchar(50) NOT NULL COMMENT '作者',
`cover` varchar(255) DEFAULT NULL COMMENT '封面图',
`description` text COMMENT '简介',
`category_id` bigint DEFAULT NULL COMMENT '分类ID',
`price` decimal(10,2) DEFAULT '0.00' COMMENT '价格',
`word_count` int DEFAULT '0' COMMENT '字数',
`status` tinyint DEFAULT '0' COMMENT '状态:0-连载中,1-已完结',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_author` (`author`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小说表';
sql复制CREATE TABLE `chapter` (
`id` bigint NOT NULL AUTO_INCREMENT,
`novel_id` bigint NOT NULL COMMENT '小说ID',
`title` varchar(100) NOT NULL COMMENT '章节标题',
`content` longtext COMMENT '章节内容',
`is_free` tinyint DEFAULT '0' COMMENT '是否免费:0-收费,1-免费',
`price` decimal(10,2) DEFAULT '0.00' COMMENT '价格',
`word_count` int DEFAULT '0' COMMENT '字数',
`sort` int DEFAULT '0' COMMENT '排序',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_novel` (`novel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='章节表';
为了提高查询性能,我们在关键字段上建立了索引:
对于高频查询但更新不频繁的数据,如小说分类、热门小说列表等,我们使用Redis缓存:
java复制// 获取热门小说列表
public List<Novel> getHotNovels() {
String cacheKey = "novel:hot";
List<Novel> novels = (List<Novel>) redisTemplate.opsForValue().get(cacheKey);
if (novels == null) {
novels = lambdaQuery()
.orderByDesc(Novel::getClickCount)
.last("limit 10")
.list();
redisTemplate.opsForValue().set(cacheKey, novels, 1, TimeUnit.HOURS);
}
return novels;
}
多级缓存架构:
缓存更新策略:
读写分离:
SQL优化:
java复制// 批量插入章节
@Transactional
public void batchInsertChapters(List<Chapter> chapters) {
if (CollectionUtils.isEmpty(chapters)) {
return;
}
String sql = "INSERT INTO chapter (novel_id, title, content, is_free, price, word_count, sort) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Chapter chapter = chapters.get(i);
ps.setLong(1, chapter.getNovelId());
ps.setString(2, chapter.getTitle());
ps.setString(3, chapter.getContent());
ps.setInt(4, chapter.getIsFree());
ps.setBigDecimal(5, chapter.getPrice());
ps.setInt(6, chapter.getWordCount());
ps.setInt(7, chapter.getSort());
}
@Override
public int getBatchSize() {
return chapters.size();
}
});
}
html复制<image
lazy-load
src="{{novel.cover}}"
mode="aspectFill"
></image>
javascript复制Page({
data: {
novels: [],
page: 1,
loading: false,
noMore: false
},
onReachBottom() {
if (this.data.loading || this.data.noMore) return;
this.setData({ loading: true });
this.loadMoreNovels();
},
loadMoreNovels() {
const { page } = this.data;
wx.request({
url: 'https://yourdomain.com/api/novels',
data: { page },
success: (res) => {
const newNovels = res.data.list;
this.setData({
novels: [...this.data.novels, ...newNovels],
page: page + 1,
noMore: newNovels.length === 0
});
},
complete: () => {
this.setData({ loading: false });
}
});
}
})
XSS防护:
CSRF防护:
SQL注入防护:
java复制// 密码加密
public String encryptPassword(String password) {
return new BCryptPasswordEncoder().encode(password);
}
// 密码验证
public boolean checkPassword(String rawPassword, String encodedPassword) {
return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);
}
java复制// 手机号脱敏
public static String desensitizePhone(String phone) {
if (StringUtils.isBlank(phone) || phone.length() != 11) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
java复制@RestController
@RequestMapping("/api/novel")
public class NovelController {
@RateLimiter(value = 100, key = "novel:detail:#{novelId}")
@GetMapping("/detail/{novelId}")
public Result getNovelDetail(@PathVariable Long novelId) {
// 获取小说详情逻辑
}
}
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: novel-mysql
environment:
MYSQL_ROOT_PASSWORD: yourpassword
MYSQL_DATABASE: novel_db
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
restart: always
redis:
image: redis:7.0
container_name: novel-redis
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
restart: always
novel-api:
build: .
container_name: novel-api
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
- redis
restart: always
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
enabled: true
Prometheus + Grafana监控:
ELK日志收集:
微信支付回调问题:
分页缓存问题:
事务失效问题:
对于小说内容这类大文本字段:
搜索功能优化:
静态资源处理:
社交功能:
推荐系统:
多端适配:
这个项目从零开始搭建,过程中遇到了各种技术挑战,但通过不断学习和实践都得到了解决。Spring Boot 3的稳定性和开发效率给我留下了深刻印象,微信小程序的生态也越来越完善。希望我的这些经验能对正在开发类似项目的同行有所帮助。