基于SSH框架的图书租借与推荐分享平台 - 源码深度解析

JavaJavaScriptSSH框架HTMLCSSMySQLJSP+Servlet
2026-02-1036 浏览

文章摘要

本项目是一款基于SSH(Struts2 + Spring + Hibernate)整合框架开发的图书租借与推荐分享平台,旨在解决传统图书流转效率低、社区分享互动性弱的核心痛点。平台通过线上化的租借流程与智能推荐机制,有效盘活闲置图书资源,降低用户阅读成本,同时构建一个以书会友的互动社区,提升图书阅读...

在数字化阅读日益普及的今天,传统图书流转效率低下和社区分享互动性弱的问题日益凸显。一款基于SSH(Struts2 + Spring + Hibernate)整合框架开发的图书共享与推荐平台应运而生,通过线上化的租借流程与智能推荐机制,有效盘活闲置图书资源,构建以书会友的互动社区。

系统架构与技术栈

该平台采用经典的三层架构设计,各层职责分明。表现层使用Struts2框架处理用户请求与页面跳转,通过Action类接收前端表单数据并调用业务逻辑;业务层由Spring框架进行管理,利用IoC容器实现服务组件的依赖注入与事务控制;数据持久层依托Hibernate实现对象关系映射,简化对图书信息、用户订单等数据的CRUD操作。

技术栈配置如下:

<!-- Struts2核心配置 -->
<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-core</artifactId>
    <version>2.5.30</version>
</dependency>

<!-- Spring Web集成 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.18</version>
</dependency>

<!-- Hibernate核心 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.7.Final</version>
</dependency>

数据库设计亮点分析

借阅业务核心表设计

borrow_book表作为租借业务的核心,采用多外键关联设计确保数据完整性:

CREATE TABLE `borrow_book` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `code` varchar(255) DEFAULT NULL COMMENT '借阅编号',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `isDelete` int(11) DEFAULT NULL COMMENT '是否删除',
  `address_id` int(11) DEFAULT NULL COMMENT '地址ID',
  `book_id` int(11) DEFAULT NULL COMMENT '图书ID',
  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
  PRIMARY KEY (`id`),
  KEY `FK_o68m15drmbidjf8nuh8jlddnt` (`address_id`),
  KEY `FK_219lk7xgyf8pjxmio6fqx84ws` (`book_id`),
  KEY `FK_b23nxw5ngbtebsghdi1r0ipmp` (`user_id`),
  CONSTRAINT `FK_219lk7xgyf8pjxmio6fqx84ws` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`),
  CONSTRAINT `FK_b23nxw5ngbtebsghdi1r0ipmp` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
  CONSTRAINT `FK_o68m15drmbidjf8nuh8jlddnt` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='借阅图书表'

设计亮点包括:

  • 软删除机制isDelete字段实现逻辑删除,保留历史数据追溯能力
  • 业务编码唯一性code字段存储借阅编号,支持业务查询和跟踪
  • 多维度索引优化:针对address_idbook_iduser_id建立外键索引,提升关联查询性能
  • 时间戳管理createTime自动记录业务发生时间,便于数据分析和统计

地址信息分级存储设计

address表采用四级行政区划分离存储,支持灵活的地址管理和配送优化:

CREATE TABLE `address` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `address` varchar(255) DEFAULT NULL COMMENT '详细地址',
  `code` varchar(255) DEFAULT NULL COMMENT '邮政编码',
  `isDelete` int(11) DEFAULT NULL COMMENT '是否删除',
  `jiedao` varchar(255) DEFAULT NULL COMMENT '街道',
  `name` varchar(255) DEFAULT NULL COMMENT '收货人姓名',
  `phone` varchar(255) DEFAULT NULL COMMENT '联系电话',
  `sheng` varchar(255) DEFAULT NULL COMMENT '省份',
  `shi` varchar(255) DEFAULT NULL COMMENT '城市',
  `xian` varchar(255) DEFAULT NULL COMMENT '县区',
  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
  PRIMARY KEY (`id`),
  KEY `FK_7rod8a71yep5vxasb0ms3osbg` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='地址表'

分级地址设计支持按地域进行图书配送优化和用户群体分析,为后续的区域化推荐提供数据基础。

图书信息扩展性设计

book表设计充分考虑了平台的可扩展性:

CREATE TABLE `book` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `bookUrl` varchar(255) DEFAULT NULL COMMENT '图书图片URL',
  `isDelete` int(11) DEFAULT NULL COMMENT '是否删除',
  `isJd` int(11) DEFAULT NULL COMMENT '是否京东图书',
  `name` varchar(255) DEFAULT NULL COMMENT '图书名称',
  `oneType` int(11) DEFAULT NULL COMMENT '一级分类',
  `price` varchar(255) DEFAULT NULL COMMENT '原价',
  `sumNum` int(11) DEFAULT NULL COMMENT '库存数量',
  `twoType` int(11) DEFAULT NULL COMMENT '二级分类',
  `ms` varchar(6000) DEFAULT NULL COMMENT '图书描述',
  `salePrice` varchar(255) DEFAULT NULL COMMENT '销售价格',
  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
  `jduser_id` int(11) DEFAULT NULL COMMENT '京东用户ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='图书表'

特殊设计包括:

  • 双用户关联:同时支持普通用户和京东用户图书上架
  • 大文本描述ms字段采用6000字符长度,满足详细图书介绍需求
  • 价格策略分离:原价与销售价格分开存储,支持灵活的价格策略
  • 分类层级管理:两级分类设计支持更精细的图书归类

核心功能实现

智能图书推荐引擎

平台集成了基于用户行为的协同过滤推荐算法,通过Spring服务封装实现个性化推荐:

@Service("bookRecommendService")
@Transactional
public class BookRecommendServiceImpl implements BookRecommendService {
    
    @Autowired
    private UserBehaviorDao userBehaviorDao;
    
    @Autowired
    private BookDao bookDao;
    
    /**
     * 基于用户协同过滤的图书推荐
     */
    @Override
    public List<Book> getPersonalizedRecommendations(Integer userId, int limit) {
        // 1. 获取目标用户的行为数据
        List<UserBehavior> targetUserBehaviors = userBehaviorDao.findByUserId(userId);
        
        // 2. 查找相似用户
        Map<Integer, Double> similarUsers = findSimilarUsers(userId, targetUserBehaviors);
        
        // 3. 生成推荐图书列表
        List<Book> recommendedBooks = generateRecommendations(userId, similarUsers, limit);
        
        return recommendedBooks;
    }
    
    /**
     * 计算用户相似度(余弦相似度算法)
     */
    private Map<Integer, Double> findSimilarUsers(Integer targetUserId, 
                                                 List<UserBehavior> targetBehaviors) {
        Map<Integer, Double> similarityScores = new HashMap<>();
        
        // 构建目标用户向量
        Map<Integer, Double> targetUserVector = buildUserVector(targetBehaviors);
        
        // 获取其他用户数据
        List<Object[]> otherUsers = userBehaviorDao.findOtherUsers(targetUserId);
        
        for (Object[] otherUser : otherUsers) {
            Integer otherUserId = (Integer) otherUser[0];
            List<UserBehavior> otherBehaviors = userBehaviorDao.findByUserId(otherUserId);
            
            Map<Integer, Double> otherUserVector = buildUserVector(otherBehaviors);
            double similarity = calculateCosineSimilarity(targetUserVector, otherUserVector);
            
            if (similarity > 0.2) { // 设置相似度阈值
                similarityScores.put(otherUserId, similarity);
            }
        }
        
        return similarityScores;
    }
    
    /**
     * 余弦相似度计算
     */
    private double calculateCosineSimilarity(Map<Integer, Double> vector1, 
                                           Map<Integer, Double> vector2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        
        // 计算点积
        for (Integer bookId : vector1.keySet()) {
            if (vector2.containsKey(bookId)) {
                dotProduct += vector1.get(bookId) * vector2.get(bookId);
            }
            norm1 += Math.pow(vector1.get(bookId), 2);
        }
        
        for (Double value : vector2.values()) {
            norm2 += Math.pow(value, 2);
        }
        
        if (norm1 == 0 || norm2 == 0) {
            return 0.0;
        }
        
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
}

智能推荐首页

分布式事务管理的租借流程

租借业务涉及多个数据表的更新操作,采用Spring声明式事务确保数据一致性:

@Controller("borrowAction")
@Scope("prototype")
public class BorrowAction extends BaseAction<BorrowBook> {
    
    @Autowired
    private BorrowService borrowService;
    
    @Autowired
    private BookService bookService;
    
    @Autowired
    private UserService userService;
    
    /**
     * 执行图书租借操作
     */
    @Action(value = "borrow_executeBorrow", 
            results = {
                @Result(name = "success", type = "redirect", location = "borrow_list.action"),
                @Result(name = "error", location = "/WEB-INF/pages/error.jsp")
            })
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public String executeBorrow() {
        try {
            // 1. 验证图书库存
            Book book = bookService.getById(model.getBookId());
            if (book.getSumNum() <= 0) {
                addActionError("图书库存不足");
                return ERROR;
            }
            
            // 2. 验证用户积分是否足够
            User user = userService.getById(model.getUserId());
            double requiredPoints = calculateRequiredPoints(book);
            if (user.getPoints() < requiredPoints) {
                addActionError("用户积分不足");
                return ERROR;
            }
            
            // 3. 生成租借记录
            BorrowBook borrowRecord = new BorrowBook();
            borrowRecord.setCode(generateBorrowCode());
            borrowRecord.setCreateTime(new Date());
            borrowRecord.setBookId(model.getBookId());
            borrowRecord.setUserId(model.getUserId());
            borrowRecord.setAddressId(model.getAddressId());
            borrowService.save(borrowRecord);
            
            // 4. 更新图书库存
            book.setSumNum(book.getSumNum() - 1);
            bookService.update(book);
            
            // 5. 扣除用户积分
            user.setPoints(user.getPoints() - requiredPoints);
            userService.update(user);
            
            // 6. 记录用户行为用于推荐算法
            recordUserBehavior(user.getId(), book.getId(), "BORROW");
            
            addActionMessage("图书租借成功!");
            return SUCCESS;
            
        } catch (Exception e) {
            // 事务自动回滚
            addActionError("租借过程发生错误:" + e.getMessage());
            return ERROR;
        }
    }
    
    /**
     * 生成唯一的租借编号
     */
    private String generateBorrowCode() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timestamp = sdf.format(new Date());
        Random random = new Random();
        int randomNum = random.nextInt(9000) + 1000;
        return "BORROW_" + timestamp + "_" + randomNum;
    }
}

租借订单提交

购物车业务逻辑实现

购物车模块支持批量操作和价格实时计算:

@Service("shopCarService")
@Transactional
public class ShopCarServiceImpl implements ShopCarService {
    
    @Autowired
    private ShopCarDao shopCarDao;
    
    @Autowired
    private BookDao bookDao;
    
    @Override
    public void addToCart(Integer userId, Integer bookId, Integer quantity) {
        // 检查是否已存在购物车记录
        ShopCar existingItem = shopCarDao.findByUserAndBook(userId, bookId);
        
        if (existingItem != null) {
            // 更新数量
            existingItem.setNum(existingItem.getNum() + quantity);
            updateItemPrice(existingItem);
            shopCarDao.update(existingItem);
        } else {
            // 新增记录
            ShopCar newItem = new ShopCar();
            newItem.setUserId(userId);
            newItem.setBookId(bookId);
            newItem.setNum(quantity);
            newItem.setIsDelete(0);
            updateItemPrice(newItem);
            shopCarDao.save(newItem);
        }
    }
    
    /**
     * 更新购物车项价格信息
     */
    private void updateItemPrice(ShopCar shopCar) {
        Book book = bookDao.getById(shopCar.getBookId());
        if (book != null) {
            // 使用销售价格计算
            double price = Double.parseDouble(book.getSalePrice());
            shopCar.setPrice(String.valueOf(price));
            shopCar.setTotalPrice(String.valueOf(price * shopCar.getNum()));
        }
    }
    
    @Override
    public List<ShopCarDTO> getCartDetails(Integer userId) {
        List<ShopCar> cartItems = shopCarDao.findByUserId(userId);
        List<ShopCarDTO> result = new ArrayList<>();
        
        for (ShopCar item : cartItems) {
            ShopCarDTO dto = new ShopCarDTO();
            BeanUtils.copyProperties(item, dto);
            
            // 关联图书信息
            Book book = bookDao.getById(item.getBookId());
            dto.setBookName(book.getName());
            dto.setBookImage(book.getBookUrl());
            dto.setAvailableStock(book.getSumNum());
            
            result.add(dto);
        }
        
        return result;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDelete(Integer userId, Integer[] itemIds) {
        for (Integer itemId : itemIds) {
            ShopCar item = shopCarDao.getById(itemId);
            if (item != null && item.getUserId().equals(userId)) {
                item.setIsDelete(1); // 软删除
                shopCarDao.update(item);
            }
        }
    }
}

购物车管理界面

图书信息管理模块

图书管理支持多条件查询和批量操作:

@Controller("bookAction")
@Scope("prototype")
public class BookAction extends BaseAction<Book> {
    
    @Autowired
    private BookService bookService;
    
    private String searchKeyword;
    private Integer searchType;
    private Date startDate;
    private Date endDate;
    
    /**
     * 分页查询图书列表
     */
    @Action(value = "book_list", 
            results = @Result(name = "success", location = "/WEB-INF/pages/book/list.jsp"))
    public String list() {
        try {
            // 构建查询条件
            Map<String, Object> params = new HashMap<>();
            if (StringUtils.isNotBlank(searchKeyword)) {
                params.put("searchKeyword", "%" + searchKeyword + "%");
            }
            if (searchType != null) {
                params.put("type", searchType);
            }
            if (startDate != null && endDate != null) {
                params.put("startDate", startDate);
                params.put("endDate", endDate);
            }
            
            // 分页查询
            pageMap = bookService.findByPage(params, getPage(), getRows());
            return SUCCESS;
            
        } catch (Exception e) {
            addActionError("查询图书列表失败:" + e.getMessage());
            return ERROR;
        }
    }
    
    /**
     * 图书上架处理
     */
    @Action(value = "book_save", 
            results = {
                @Result(name = "success", type = "redirect", location = "book_list.action"),
                @Result(name = "input", location = "/WEB-INF/pages/book/add.jsp")
            })
    public String save() {
        try {
            // 设置默认值
            model.setIsDelete(0);
            model.setCreateTime(new Date());
            model.setSumNum(1); // 租借图书默认库存为1
            
            // 处理图书图片上传
            if (imageFile != null) {
                String imagePath = uploadImage(imageFile, imageFileFileName);
                model.setBookUrl(imagePath);
            }
            
            bookService.save(model);
            addActionMessage("图书上架成功!");
            return SUCCESS;
            
        } catch (Exception e) {
            addActionError("图书上架失败:" + e.getMessage());
            return INPUT;
        }
    }
    
    /**
     * 图片上传处理
     */
    private String uploadImage(File file, String fileName) throws Exception {
        String basePath = ServletActionContext.getServletContext().getRealPath("/upload/images");
        String fileExtension = fileName.substring(fileName.lastIndexOf("."));
        String newFileName = UUID.randomUUID().toString() + fileExtension;
        String fullPath = basePath + File.separator + newFileName;
        
        File destFile = new File(fullPath);
        FileUtils.copyFile(file, destFile);
        
        return "/upload
本文关键词
SSH框架图书租借源码解析数据库设计推荐分享平台

上下篇

上一篇
没有更多文章
下一篇
没有更多文章