社区商铺管理系统是一个基于Java技术栈开发的B/S架构应用,旨在解决传统社区商铺管理中存在的信息孤岛、服务响应慢、管理效率低下等问题。作为一名有多年Java开发经验的工程师,我在实际项目中发现很多社区还在使用纸质档案和Excel表格管理商铺信息,这种模式不仅容易出错,而且难以实现多方协同。这个系统正是为了解决这些痛点而设计的。
系统采用Spring Boot作为后端框架,Vue.js作为前端框架,MySQL作为数据库,实现了商铺信息管理、租赁合同管理、维修服务、投诉处理等核心功能。特别值得一提的是,我们还加入了社区论坛和商城模块,增强了系统的社交属性。下面我将从技术选型、系统设计到具体实现,详细分享这个项目的开发经验。
选择合适的技术栈是项目成功的关键。经过多方比较,我们最终确定了以下技术方案:
后端技术:
前端技术:
数据库:
开发工具:
技术选型心得:Spring Boot相比传统SSM框架,省去了大量XML配置,内置Tomcat服务器让部署更加简单。Vue.js的双向数据绑定特性让前端开发效率大幅提升。
系统采用经典的三层架构,分为表示层、业务逻辑层和数据访问层:
code复制表示层(Web)
├── 用户界面(Vue.js)
├── API网关(Spring MVC)
│
业务逻辑层(Service)
├── 商铺管理服务
├── 租赁合同服务
├── 维修服务
├── 投诉处理服务
│
数据访问层(DAO)
├── MyBatis-Plus
├── Redis缓存
│
基础设施
├── MySQL数据库
├── 文件存储
这种分层架构的优势在于:
商铺信息管理是系统的核心功能之一,主要包括商铺信息的CRUD操作、分类管理和状态变更。
数据库表设计:
sql复制CREATE TABLE `shop_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`shop_name` varchar(100) NOT NULL COMMENT '商铺名称',
`shop_type` varchar(50) NOT NULL COMMENT '商铺类型',
`location` varchar(200) NOT NULL COMMENT '商铺位置',
`area` decimal(10,2) NOT NULL COMMENT '面积(平方米)',
`rent` decimal(10,2) NOT NULL COMMENT '月租金',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-空闲 1-已租 2-装修中',
`landlord_id` bigint NOT NULL COMMENT '房东ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_landlord` (`landlord_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商铺信息表';
关键实现代码:
商铺服务接口:
java复制public interface ShopService {
// 添加商铺
Result addShop(ShopDTO shopDTO);
// 更新商铺信息
Result updateShop(Long id, ShopDTO shopDTO);
// 获取商铺详情
Result<ShopVO> getShopDetail(Long id);
// 分页查询商铺列表
Result<PageInfo<ShopVO>> queryShopList(ShopQuery query, Integer pageNum, Integer pageSize);
// 变更商铺状态
Result changeShopStatus(Long id, Integer status);
}
商铺服务实现类中的分页查询方法:
java复制@Override
public Result<PageInfo<ShopVO>> queryShopList(ShopQuery query, Integer pageNum, Integer pageSize) {
// 构建查询条件
LambdaQueryWrapper<ShopInfo> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(query.getShopName())) {
wrapper.like(ShopInfo::getShopName, query.getShopName());
}
if (query.getShopType() != null) {
wrapper.eq(ShopInfo::getShopType, query.getShopType());
}
if (query.getStatus() != null) {
wrapper.eq(ShopInfo::getStatus, query.getStatus());
}
// 执行分页查询
Page<ShopInfo> page = new Page<>(pageNum, pageSize);
IPage<ShopInfo> iPage = shopInfoMapper.selectPage(page, wrapper);
// 转换为VO对象
List<ShopVO> voList = iPage.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList());
// 构建返回结果
PageInfo<ShopVO> pageInfo = new PageInfo<>();
pageInfo.setList(voList);
pageInfo.setTotal(iPage.getTotal());
pageInfo.setPageNum(pageNum);
pageInfo.setPageSize(pageSize);
return Result.success(pageInfo);
}
租赁合同管理模块实现了合同创建、签署、变更和终止的全生命周期管理。
核心业务流程:
数据库表设计:
sql复制CREATE TABLE `lease_contract` (
`id` bigint NOT NULL AUTO_INCREMENT,
`contract_no` varchar(50) NOT NULL COMMENT '合同编号',
`shop_id` bigint NOT NULL COMMENT '商铺ID',
`landlord_id` bigint NOT NULL COMMENT '房东ID',
`tenant_id` bigint NOT NULL COMMENT '租客ID',
`start_date` date NOT NULL COMMENT '起始日期',
`end_date` date NOT NULL COMMENT '结束日期',
`rent_amount` decimal(10,2) NOT NULL COMMENT '月租金',
`deposit` decimal(10,2) NOT NULL COMMENT '押金',
`payment_cycle` tinyint NOT NULL COMMENT '付款周期:1-月付 3-季付 6-半年付 12-年付',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-草稿 1-待签署 2-已生效 3-已到期 4-已终止',
`contract_file` varchar(255) DEFAULT NULL COMMENT '合同文件路径',
`sign_time` datetime DEFAULT NULL COMMENT '签署时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_contract_no` (`contract_no`),
KEY `idx_shop` (`shop_id`),
KEY `idx_landlord` (`landlord_id`),
KEY `idx_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租赁合同表';
电子签名实现:
java复制public class ContractService {
@Autowired
private DigitalSignService digitalSignService;
public Result signContract(Long contractId, Long userId, String signImage) {
// 验证合同状态
LeaseContract contract = contractMapper.selectById(contractId);
if (contract == null) {
return Result.error("合同不存在");
}
if (contract.getStatus() != 1) {
return Result.error("合同当前状态不可签署");
}
// 验证用户身份
if (!userId.equals(contract.getLandlordId()) &&
!userId.equals(contract.getTenantId())) {
return Result.error("无权签署该合同");
}
// 保存签名图片
String signPath = fileService.uploadSignImage(signImage);
// 生成合同PDF并添加签名
String signedPdfPath = digitalSignService.addSignToContract(
contract.getContractFile(),
signPath,
userId.equals(contract.getLandlordId()) ? "房东" : "租客"
);
// 更新合同状态
if (contract.getSignTime() == null) {
// 第一个签署人
contract.setSignTime(new Date());
contract.setStatus(1); // 待另一方签署
} else {
// 第二个签署人,合同生效
contract.setStatus(2);
contract.setEffectiveDate(new Date());
// 更新商铺状态为已租
shopService.changeShopStatus(contract.getShopId(), 1);
}
contract.setContractFile(signedPdfPath);
contractMapper.updateById(contract);
return Result.success("签署成功");
}
}
系统实现了多种自动提醒功能,大幅提升了管理效率:
实现原理:
使用Spring的@Scheduled注解实现定时任务,结合消息队列实现异步通知。
java复制@Component
public class ReminderTask {
@Autowired
private LeaseContractMapper contractMapper;
@Autowired
private MessageService messageService;
// 每天凌晨1点执行
@Scheduled(cron = "0 0 1 * * ?")
public void checkContractExpiration() {
// 查询30天后到期的合同
LocalDate date = LocalDate.now().plusDays(30);
List<LeaseContract> contracts = contractMapper.selectList(
new LambdaQueryWrapper<LeaseContract>()
.eq(LeaseContract::getStatus, 2)
.apply("DATE(end_date) = DATE('{0}')", date)
);
// 发送提醒
contracts.forEach(contract -> {
String content = String.format(
"您的合同%s将于30天后到期,请及时处理续约或退租事宜",
contract.getContractNo()
);
messageService.sendMessage(
contract.getTenantId(),
"合同到期提醒",
content
);
messageService.sendMessage(
contract.getLandlordId(),
"合同到期提醒",
content
);
});
}
}
系统提供了丰富的数据统计功能,帮助管理者了解商铺运营情况:
实现示例:
java复制public class StatisticsService {
public RentalStatisticsVO getRentalStatistics(LocalDate startDate, LocalDate endDate) {
RentalStatisticsVO vo = new RentalStatisticsVO();
// 查询出租率
BigDecimal rentalRate = contractMapper.selectRentalRate();
vo.setRentalRate(rentalRate);
// 查询租金收入趋势
List<RentalIncomeDTO> incomeList = contractMapper.selectRentalIncomeTrend(
startDate, endDate);
vo.setIncomeTrend(incomeList);
// 查询各类型商铺占比
List<ShopTypeRatioDTO> typeRatio = shopMapper.selectShopTypeRatio();
vo.setShopTypeRatio(typeRatio);
return vo;
}
}
系统采用Docker容器化部署,部署架构如下:
code复制Nginx (负载均衡)
├── 前端容器1
├── 前端容器2
│
Spring Boot应用集群
├── 应用容器1
├── 应用容器2
├── 应用容器3
│
MySQL主从集群
├── 主库
├── 从库1
├── 从库2
│
Redis哨兵集群
├── Redis节点1
├── Redis节点2
├── Redis节点3
Docker Compose配置示例:
yaml复制version: '3'
services:
frontend:
image: nginx:1.19
ports:
- "80:80"
volumes:
- ./frontend/dist:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- backend
backend:
image: openjdk:8-jdk
ports:
- "8080:8080"
volumes:
- ./backend.jar:/app.jar
environment:
- SPRING_PROFILES_ACTIVE=prod
command: java -jar /app.jar
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=community_shop
volumes:
- ./mysql/data:/var/lib/mysql
redis:
image: redis:6.0
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
在实际运行中,我们针对以下方面进行了优化:
数据库优化:
缓存策略:
接口优化:
缓存使用示例:
java复制@Service
public class ShopServiceImpl implements ShopService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String SHOP_CACHE_PREFIX = "shop:";
private static final long CACHE_EXPIRE = 30; // 分钟
@Override
@Cacheable(value = "shop", key = "#id")
public ShopVO getShopDetail(Long id) {
ShopInfo shop = shopInfoMapper.selectById(id);
if (shop == null) {
return null;
}
return convertToVO(shop);
}
@Override
@CacheEvict(value = "shop", key = "#id")
public Result updateShop(Long id, ShopDTO shopDTO) {
// 更新逻辑
}
// 手动缓存热门商铺列表
public List<ShopVO> getHotShops() {
String cacheKey = "hot_shops";
List<ShopVO> cached = (List<ShopVO>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 查询数据库
List<ShopInfo> shops = shopInfoMapper.selectHotShops();
List<ShopVO> result = shops.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
// 设置缓存
redisTemplate.opsForValue().set(
cacheKey,
result,
CACHE_EXPIRE,
TimeUnit.MINUTES
);
return result;
}
}
java复制public Result applyForLease(Long shopId, Long userId) {
// 获取分布式锁
String lockKey = "shop_lease:" + shopId;
boolean locked = redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS);
if (!locked) {
return Result.error("系统繁忙,请稍后再试");
}
try {
// 查询商铺状态
ShopInfo shop = shopInfoMapper.selectById(shopId);
if (shop.getStatus() != 0) {
return Result.error("商铺已被租赁");
}
// 更新商铺状态
shop.setStatus(1); // 预定中
int updated = shopInfoMapper.updateById(shop);
if (updated == 0) {
// 乐观锁更新失败
return Result.error("商铺状态已变更");
}
// 创建租赁申请记录
LeaseApply apply = new LeaseApply();
apply.setShopId(shopId);
apply.setUserId(userId);
apply.setStatus(0); // 申请中
leaseApplyMapper.insert(apply);
return Result.success("申请成功");
} finally {
// 释放锁
redisLock.unlock(lockKey);
}
}
java复制public String uploadContractFile(MultipartFile file) {
// 校验文件类型
String originalName = file.getOriginalFilename();
String extension = originalName.substring(originalName.lastIndexOf(".") + 1);
if (!Arrays.asList("pdf", "doc", "docx").contains(extension.toLowerCase())) {
throw new BusinessException("不支持的文件类型");
}
// 校验文件内容
if (!fileScanner.scan(file)) {
throw new BusinessException("文件安全检查未通过");
}
// 生成存储路径
String path = "contracts/" + UUID.randomUUID() + "." + extension;
// 存储文件
try {
fileStorageService.store(file.getInputStream(), path);
} catch (IOException e) {
throw new BusinessException("文件上传失败");
}
return path;
}
接口设计建议:
java复制// 统一响应格式示例
public class Result<T> {
private boolean success;
private String code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setSuccess(true);
result.setCode("200");
result.setMessage("成功");
result.setData(data);
return result;
}
public static Result<?> error(String code, String message) {
Result<?> result = new Result<>();
result.setSuccess(false);
result.setCode(code);
result.setMessage(message);
return result;
}
}
基于现有系统,还可以进一步扩展以下功能:
技术预研方向:
这个社区商铺管理系统从实际需求出发,采用成熟稳定的技术栈,经过精心设计和开发,已经能够满足大多数社区商铺管理的需求。在开发过程中,我们遇到了各种挑战,但通过团队协作和技术攻关,最终都得到了很好的解决。希望这个项目的经验分享对其他开发者有所帮助。