这个图书管理系统采用了Java+SSM+Flask的混合架构模式,实现了从图书信息录入、分类管理到借阅记录跟踪的全流程数字化管理。作为图书馆日常运营的核心系统,它需要同时满足管理员的高效管理和读者的便捷查询双重需求。
在实际开发中,我们选择了SSM(Spring+SpringMVC+MyBatis)作为后端主力框架,主要处理核心业务逻辑和数据持久化;而Flask则负责构建轻量级的API服务和部分前端展示层。这种架构组合既保证了系统稳定性,又提供了足够的灵活性。系统包含8个核心模块:用户认证、图书录入、分类管理、借阅处理、归还管理、信息查询、统计报表和系统设置。
提示:混合架构的关键在于合理划分服务边界,我们将高频交易型操作(如借还书)放在Java端,而查询统计类功能则通过Flask实现,这样能充分发挥各自技术栈的优势。
SSM框架组合采用了Spring 5.2.8作为控制反转容器,SpringMVC处理Web请求路由,MyBatis 3.5.6作为ORM层。数据库选用MySQL 8.0,主要考虑到其事务处理能力和图书馆业务中频繁的读写操作需求。以下是核心数据表设计要点:
java复制// 典型的图书查询Service层实现
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookMapper bookMapper;
public PageInfo<Book> queryByCondition(BookQueryCondition condition,
Integer pageNum,
Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Book> books = bookMapper.selectByCondition(condition);
return new PageInfo<>(books);
}
}
Flask服务主要承担三方面职责:
我们使用Flask-SQLAlchemy扩展连接MySQL,与SSM共享同一数据库。关键配置如下:
python复制# Flask应用工厂模式配置
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:pass@localhost/library'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
from .api import book_bp
app.register_blueprint(book_bp, url_prefix='/api/v1')
return app
图书录入采用批量导入和单条添加两种方式。对于大批量图书入库,我们开发了Excel模板导入功能,后台使用Apache POI处理文件上传:
java复制// Excel导入控制器
@PostMapping("/import")
public Result importBooks(@RequestParam("file") MultipartFile file) {
try {
InputStream inputStream = file.getInputStream();
List<Book> books = ExcelUtil.importExcel(inputStream, Book.class);
bookService.batchInsert(books);
return Result.success(books.size());
} catch (Exception e) {
logger.error("导入失败", e);
return Result.error("导入失败:" + e.getMessage());
}
}
注意事项:实际处理中需要验证ISBN有效性,我们采用正则表达式校验格式:^[0-9]{13}$,同时通过数据库唯一约束防止重复录入。
借阅业务涉及多个状态变更和校验规则,我们采用事务保证数据一致性:
java复制@Transactional
public Result borrowBook(Integer userId, String isbn) {
// 1. 校验用户
User user = userMapper.selectById(userId);
if (user.getBorrowedCount() >= user.getMaxLimit()) {
return Result.error("借阅数量已达上限");
}
// 2. 校验图书
Book book = bookMapper.selectByIsbn(isbn);
if (book.getStatus() != BookStatus.AVAILABLE) {
return Result.error("图书不可借");
}
// 3. 创建记录
BorrowLog log = new BorrowLog(userId, book.getId());
borrowLogMapper.insert(log);
// 4. 更新状态
book.setStatus(BookStatus.BORROWED);
bookMapper.updateById(book);
return Result.success("借阅成功");
}
除了基础的条件查询,系统实现了基于Elasticsearch的全文检索功能,支持书名、作者、出版社等多字段模糊匹配。我们在Java端集成TransportClient实现索引维护:
java复制// 构建图书索引
public void buildBookIndex(List<Book> books) {
BulkRequestBuilder bulkRequest = client.prepareBulk();
for (Book book : books) {
IndexRequest request = client.prepareIndex("books", "_doc", book.getId().toString())
.setSource(XContentFactory.jsonBuilder()
.startObject()
.field("title", book.getTitle())
.field("author", book.getAuthor())
.field("publisher", book.getPublisher())
.field("summary", book.getSummary())
.endObject());
bulkRequest.add(request);
}
BulkResponse response = bulkRequest.get();
if (response.hasFailures()) {
// 处理失败情况
}
}
Flask服务利用Matplotlib生成可视化报表,关键实现如下:
python复制@app.route('/api/stat/borrow-trend')
def borrow_trend():
days = request.args.get('days', default=30, type=int)
data = db.session.execute(f"""
SELECT DATE(borrow_time) as day, COUNT(*) as count
FROM borrow_log
WHERE borrow_time >= DATE_SUB(CURDATE(), INTERVAL {days} DAY)
GROUP BY day
ORDER BY day
""").fetchall()
dates = [row[0].strftime('%m-%d') for row in data]
counts = [row[1] for row in data]
plt.figure(figsize=(10, 5))
plt.plot(dates, counts, marker='o')
plt.title('近{}天借阅趋势'.format(days))
plt.xlabel('日期')
plt.ylabel('借阅量')
img = BytesIO()
plt.savefig(img, format='png')
img.seek(0)
return send_file(img, mimetype='image/png')
系统采用Docker容器化部署,Java服务与Flask服务分别打包:
dockerfile复制# Java服务Dockerfile示例
FROM openjdk:8-jdk-alpine
COPY target/library-system.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# Flask服务Dockerfile示例
FROM python:3.8-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "-b :5000", "wsgi:app"]
部署时需要注意:
针对图书馆高峰期并发访问特点,我们实施了以下优化措施:
数据库层面:
缓存策略:
java复制// 二级缓存配置示例
@Cacheable(cacheNames = "books", key = "#isbn")
public Book getByIsbn(String isbn) {
return bookMapper.selectByIsbn(isbn);
}
@CacheEvict(cacheNames = "books", key = "#book.isbn")
public void updateBook(Book book) {
bookMapper.updateById(book);
}
初期测试发现当多个用户同时借阅同一本书时,会出现超借现象。解决方案是采用乐观锁机制:
java复制@Transactional
public Result borrowBookWithLock(Integer userId, String isbn) {
// 先查询图书并获取version
Book book = bookMapper.selectByIsbn(isbn);
// 业务校验...
// 更新时带version条件
int affected = bookMapper.updateStatusWithVersion(
book.getId(),
BookStatus.BORROWED,
book.getVersion());
if (affected == 0) {
throw new OptimisticLockingFailureException("图书状态已变更");
}
// 创建借阅记录...
return Result.success("借阅成功");
}
对应的Mapper XML配置:
xml复制<update id="updateStatusWithVersion">
UPDATE book_info
SET status = #{status},
version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
Java服务和Flask服务间的数据一致性通过以下方案保证:
java复制// Java端发送消息示例
public void sendBookUpdateMessage(Book book) {
rabbitTemplate.convertAndSend(
"book.update.queue",
new BookUpdateMessage(book.getId(), book.getStatus())
);
}
Flask端消费消息:
python复制def callback(ch, method, properties, body):
msg = json.loads(body)
# 更新本地缓存或执行其他操作
print(f"Received book update: {msg}")
channel.basic_consume(
queue='book.update.queue',
on_message_callback=callback,
auto_ack=True)
Flask端的JWT验证装饰器示例:
python复制def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'message': 'Token is missing'}), 403
try:
data = jwt.decode(token.split()[1],
current_app.config['SECRET_KEY'],
algorithms=["HS256"])
current_user = User.query.get(data['user_id'])
except:
return jsonify({'message': 'Token is invalid'}), 403
return f(current_user, *args, **kwargs)
return decorated
java复制// 操作日志切面示例
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogMapper logMapper;
@Pointcut("@annotation(com.example.annotation.OperateLog)")
public void operateLogPointCut() {}
@AfterReturning(pointcut = "operateLogPointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperateLog annotation = method.getAnnotation(OperateLog.class);
OperationLog log = new OperationLog();
log.setOperation(annotation.value());
log.setParams(JsonUtil.toJson(joinPoint.getArgs()));
log.setResult(JsonUtil.toJson(result));
// 设置其他字段...
logMapper.insert(log);
}
}
系统预留了微信小程序接口,主要功能包括:
接口采用RESTful设计,返回数据格式示例:
json复制{
"code": 200,
"data": {
"books": [
{
"isbn": "9787115474582",
"title": "Java编程思想",
"author": "Bruce Eckel",
"cover": "/covers/9787115474582.jpg",
"status": "available"
}
],
"total": 1
}
}
基于用户借阅历史实现协同过滤推荐:
python复制def recommend_books(user_id, top_n=5):
# 获取用户借阅历史
history = get_borrow_history(user_id)
# 计算相似用户
similar_users = find_similar_users(user_id)
# 生成推荐列表
recommendations = []
for user in similar_users:
for book in user['books']:
if book not in history:
recommendations.append(book)
# 按热度排序返回
return sorted(recommendations,
key=lambda x: x['popularity'],
reverse=True)[:top_n]
实际开发中,我们使用了Surprise库实现更精确的推荐算法,并将结果缓存以提高响应速度。