
一、 项目背景与意义随着摄影爱好者和专业摄影师的增多以及短视频、直播等行业的蓬勃发展相机及相关摄影器材的需求日益增长。然而高端摄影设备价格昂贵对于个人用户、小型工作室或短期项目而言购买成本过高。相机租赁服务应运而生它为用户提供了灵活、经济的设备使用方案。传统相机租赁管理多依赖手工记录、电话预约或简单的Excel表格存在信息不透明、流程繁琐、易出错、库存管理混乱、订单跟踪困难等问题。因此开发一套高效、智能的相机租赁管理系统实现线上化、自动化管理具有重要的现实意义提升运营效率自动化处理订单、库存、结算减少人工操作和错误。优化用户体验用户可在线浏览设备、实时查看库存、自助下单支付流程透明便捷。精细化库存管理实时跟踪每台设备的状态在库、出租中、维修中避免超租或设备闲置。数据驱动决策通过数据分析了解热门设备、租赁周期、用户偏好为采购和营销提供依据。降低运营成本减少因管理混乱造成的设备丢失、损坏或闲置损失。本系统旨在设计并实现一个功能完善、稳定可靠的B/S架构相机租赁管理平台服务于租赁商家与终端用户。二、 技术栈选型系统采用经典且成熟的后端分离架构确保系统的可维护性、扩展性和高性能。2.1 后端技术栈核心框架Spring Boot 2.7。简化配置快速构建独立运行、生产级的应用。Web框架Spring MVC。处理HTTP请求实现RESTful API。数据持久层ORM框架MyBatis-Plus。增强MyBatis功能提供通用CRUD、分页、条件构造器等极大减少SQL编写。数据库MySQL 8.0。关系型数据库用于存储用户、设备、订单等核心业务数据。连接池HikariCP。高性能的JDBC连接池。安全与权限认证授权Spring Security JWT (JSON Web Token)。实现用户登录认证和接口访问权限控制。密码加密BCryptPasswordEncoder。对用户密码进行不可逆加密存储。缓存Redis。用于缓存热点数据如设备信息、存储用户会话Token、实现分布式锁等提升系统响应速度。消息队列RabbitMQ。用于异步处理订单超时未支付自动取消、库存扣减/回滚、发送系统通知等场景解耦业务提高系统吞吐量。文件存储本地存储或集成阿里云OSS/腾讯云COS。用于存储设备图片、合同文件等。API文档SpringDoc OpenAPI (Swagger UI)。自动生成并可视化API文档便于前后端协作和测试。构建与依赖管理Maven。2.2 前端技术栈可选/建议框架Vue 3 或 React。UI组件库Element Plus (Vue) 或 Ant Design (React)。状态管理Pinia (Vue) 或 Redux (React)。构建工具Vite 或 Webpack。HTTP客户端Axios。2.3 开发与部署版本控制Git。容器化Docker Docker Compose。实现应用、MySQL、Redis、RabbitMQ的一键部署。持续集成/部署Jenkins 或 GitLab CI/CD。三、 系统核心功能模块设计系统主要分为后台管理端和用户端。3.1 后台管理端设备管理相机的增删改查、分类管理、库存数量设置、设备状态可用/出租/维修/报废管理、图片上传。订单管理查看所有订单、审核订单、修改订单状态待支付、已支付、已发货、租赁中、已归还、已完成、已取消、处理续租/退租。用户管理管理注册用户信息、审核用户实名认证资料、设置用户信用等级或黑名单。财务管理租金结算、押金管理、账单生成、收支统计报表。库存与日志库存变动记录、设备维修记录、系统操作日志。数据统计租赁量、营收、热门设备、用户活跃度等数据可视化图表。3.2 用户端用户注册/登录手机号/邮箱注册JWT令牌认证。设备浏览与搜索按品牌、型号、价格、分类筛选设备查看设备详情、库存和评价。租赁下单选择设备、租赁起止时间、计算租金与押金、填写收货地址、提交订单。订单中心查看我的订单、支付订单、申请续租/退租、确认收货/归还、评价。个人中心管理个人信息、实名认证、我的地址、押金记录、优惠券。在线客服集成即时通讯或留言板功能。四、 数据库核心表设计以下是几个核心业务表的结构示例-- 设备表 CREATE TABLE camera_device ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(255) NOT NULL COMMENT 设备名称如索尼A7M4, brand varchar(100) DEFAULT NULL COMMENT 品牌, model varchar(100) DEFAULT NULL COMMENT 型号, category_id bigint DEFAULT NULL COMMENT 分类ID, daily_price decimal(10,2) NOT NULL COMMENT 日租金, deposit decimal(10,2) NOT NULL COMMENT 押金, total_stock int NOT NULL DEFAULT 0 COMMENT 总库存数量, available_stock int NOT NULL DEFAULT 0 COMMENT 可用库存数量, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1-上架 0-下架, description text COMMENT 详细描述, cover_image varchar(500) DEFAULT NULL COMMENT 封面图URL, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT摄影设备表; -- 租赁订单表 CREATE TABLE rental_order ( id varchar(64) NOT NULL COMMENT 订单号业务生成, user_id bigint NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额租金, deposit_amount decimal(10,2) NOT NULL COMMENT 押金金额, order_status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态0-待支付 1-已支付 2-已发货 3-租赁中 4-已归还 5-已完成 6-已取消, pay_status tinyint NOT NULL DEFAULT 0 COMMENT 支付状态0-未支付 1-已支付, start_date date NOT NULL COMMENT 租赁开始日期, end_date date NOT NULL COMMENT 租赁结束日期, actual_return_date date DEFAULT NULL COMMENT 实际归还日期, shipping_address text COMMENT 收货地址, remark varchar(500) DEFAULT NULL COMMENT 用户备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT租赁订单主表; -- 订单明细表一个订单可租多台设备 CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id varchar(64) NOT NULL COMMENT 订单ID, device_id bigint NOT NULL COMMENT 设备ID, device_sn varchar(100) DEFAULT NULL COMMENT 设备序列号实际出租的物理设备, quantity int NOT NULL DEFAULT 1 COMMENT 租赁数量, daily_price decimal(10,2) NOT NULL COMMENT 下单时的日租金, rental_days int NOT NULL COMMENT 租赁天数, item_amount decimal(10,2) NOT NULL COMMENT 明细项金额, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;五、 核心代码实现示例5.1 设备分页查询接口 (Controller Service)// CameraDeviceController.java RestController RequestMapping(/api/device) Api(tags 设备管理) public class CameraDeviceController { Autowired private CameraDeviceService deviceService; GetMapping(/page) ApiOperation(分页查询设备列表) public RPageResultDeviceVO getDevicePage( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword, RequestParam(required false) Long categoryId) { PageQuery query new PageQuery(pageNum, pageSize); DeviceQueryDTO queryDTO new DeviceQueryDTO(); queryDTO.setKeyword(keyword); queryDTO.setCategoryId(categoryId); PageResultDeviceVO pageResult deviceService.queryDevicePage(query, queryDTO); return R.success(pageResult); } } // CameraDeviceServiceImpl.java Service public class CameraDeviceServiceImpl implements CameraDeviceService { Autowired private CameraDeviceMapper deviceMapper; Override public PageResultDeviceVO queryDevicePage(PageQuery query, DeviceQueryDTO queryDTO) { // 构建查询条件 LambdaQueryWrapperCameraDevice wrapper new LambdaQueryWrapper(); wrapper.eq(CameraDevice::getStatus, 1); // 只查询上架设备 if (StringUtils.isNotBlank(queryDTO.getKeyword())) { wrapper.like(CameraDevice::getName, queryDTO.getKeyword()) .or().like(CameraDevice::getBrand, queryDTO.getKeyword()); } if (queryDTO.getCategoryId() ! null) { wrapper.eq(CameraDevice::getCategoryId, queryDTO.getCategoryId()); } wrapper.orderByDesc(CameraDevice::getCreateTime); // 执行分页查询 PageCameraDevice page new Page(query.getPageNum(), query.getPageSize()); PageCameraDevice devicePage deviceMapper.selectPage(page, wrapper); // 转换为VO列表 ListDeviceVO voList devicePage.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList()); return new PageResult(devicePage.getTotal(), voList); } private DeviceVO convertToVO(CameraDevice device) { DeviceVO vo new DeviceVO(); BeanUtils.copyProperties(device, vo); // 可在此处补充其他逻辑如处理图片完整URL return vo; } }5.2 下单与库存扣减 (Service with Transactional)// OrderServiceImpl.java Service Slf4j public class OrderServiceImpl implements OrderService { Autowired private CameraDeviceMapper deviceMapper; Autowired private RentalOrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Autowired private RabbitTemplate rabbitTemplate; Override Transactional(rollbackFor Exception.class) public String createOrder(OrderSubmitDTO submitDTO) { Long userId UserContext.getCurrentUserId(); // 1. 校验设备库存与价格 ListOrderItemDTO itemList submitDTO.getItems(); ListLong deviceIds itemList.stream().map(OrderItemDTO::getDeviceId).collect(Collectors.toList()); ListCameraDevice devices deviceMapper.selectBatchIds(deviceIds); MapLong, CameraDevice deviceMap devices.stream() .collect(Collectors.toMap(CameraDevice::getId, d - d)); BigDecimal totalAmount BigDecimal.ZERO; for (OrderItemDTO item : itemList) { CameraDevice device deviceMap.get(item.getDeviceId()); if (device null) { throw new BusinessException(设备不存在); } if (device.getAvailableStock() item.getQuantity()) { throw new BusinessException(设备【 device.getName() 】库存不足); } // 计算租金 BigDecimal itemRent device.getDailyPrice().multiply(BigDecimal.valueOf(item.getRentalDays())); totalAmount totalAmount.add(itemRent); } // 2. 生成订单号 String orderNo IdGenerator.generateOrderNo(); // 3. 保存订单主表 RentalOrder order new RentalOrder(); order.setId(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setDepositAmount(submitDTO.getDepositAmount()); order.setOrderStatus(OrderStatusEnum.WAIT_PAY.getCode()); order.setPayStatus(PayStatusEnum.UNPAID.getCode()); order.setStartDate(submitDTO.getStartDate()); order.setEndDate(submitDTO.getEndDate()); order.setShippingAddress(submitDTO.getShippingAddress()); orderMapper.insert(order); // 4. 保存订单明细并扣减库存 for (OrderItemDTO item : itemList) { CameraDevice device deviceMap.get(item.getDeviceId()); OrderItem orderItem new OrderItem(); orderItem.setOrderId(orderNo); orderItem.setDeviceId(item.getDeviceId()); orderItem.setQuantity(item.getQuantity()); orderItem.setDailyPrice(device.getDailyPrice()); orderItem.setRentalDays(item.getRentalDays()); orderItem.setItemAmount(device.getDailyPrice().multiply(BigDecimal.valueOf(item.getRentalDays()))); orderItemMapper.insert(orderItem); // 乐观锁更新库存 int updateCount deviceMapper.updateAvailableStock(device.getId(), item.getQuantity()); if (updateCount 0) { throw new BusinessException(设备【 device.getName() 】库存更新失败请重试); } } // 5. 发送延迟消息30分钟后检查订单是否支付未支付则自动取消 rabbitTemplate.convertAndSend(order.delay.exchange, order.cancel, orderNo, message - { message.getMessageProperties().setDelay(30 * 60 * 1000); // 30分钟 return message; }); log.info(订单创建成功订单号{}, orderNo); return orderNo; } }5.3 JWT认证与Spring Security配置// JwtTokenUtil.java 工具类 Component public class JwtTokenUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expiration}) private Long expiration; public String generateToken(String username) { Date now new Date(); Date expiryDate new Date(now.getTime() expiration); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } // ... 其他验证方法 } // JwtAuthenticationFilter.java 过滤器 public class JwtAuthenticationFilter extends OncePerRequestFilter { Autowired private JwtTokenUtil jwtTokenUtil; Autowired private UserDetailsService userDetailsService; Override protected void doFilter