1. 项目背景与核心价值
最近在帮某物业公司做数字化升级时,发现市面上现成的物业管理系统要么功能冗余要么价格虚高。于是决定用SpringBoot+Vue这套黄金组合,开发一个轻量级的综合小区管理系统。这个系统最核心的价值在于:
- 实现了业主、物业、商户三端数据实时互通
- 将传统纸质流程全部线上化(报修、缴费、投诉等)
- 通过数据驾驶舱直观展示小区运营指标
整套系统采用前后端分离架构,后端用SpringBoot提供RESTful API,前端用Vue3+Element Plus构建管理界面。数据库选用MySQL 8.0,ORM层采用MyBatis-Plus提高开发效率。下面我会从技术选型到具体实现,完整复盘这个项目的开发过程。
2. 技术架构设计
2.1 整体架构设计
系统采用经典的三层架构:
code复制[前端] Vue3 + Element Plus + Axios
↑
[REST API] SpringBoot 2.7 + Spring Security
↑
[持久层] MyBatis-Plus + MySQL 8.0
选择这套技术栈主要基于以下考量:
- SpringBoot的自动配置和起步依赖能快速搭建后端服务
- Vue3的Composition API更适合复杂前端状态管理
- MyBatis-Plus的ActiveRecord模式简化了90%的CRUD操作
- MySQL作为关系型数据库能很好满足物业数据的关联查询需求
2.2 数据库设计要点
物业系统的核心在于数据关系的设计,主要包含以下几张关键表:
sql复制-- 业主表
CREATE TABLE `owner` (
`id` bigint NOT NULL AUTO_INCREMENT,
`building_id` varchar(20) COMMENT '楼栋编号',
`room_id` varchar(20) COMMENT '房号',
`name` varchar(50) NOT NULL,
`mobile` varchar(11) NOT NULL,
`id_card` varchar(18) COMMENT '身份证号',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 物业费表
CREATE TABLE `property_fee` (
`id` bigint NOT NULL AUTO_INCREMENT,
`owner_id` bigint NOT NULL,
`fee_type` tinyint COMMENT '1-物业费 2-车位费 3-水电费',
`amount` decimal(10,2) NOT NULL,
`status` tinyint DEFAULT 0 COMMENT '0-未缴 1-已缴',
`due_date` date NOT NULL COMMENT '缴费截止日',
PRIMARY KEY (`id`),
KEY `idx_owner` (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意的点:
- 业主手机号设为唯一索引,作为系统登录账号
- 金额字段使用decimal(10,2)避免精度丢失
- 建立合理的索引提高查询效率
3. 核心功能实现
3.1 业主认证模块
采用JWT实现无状态认证,关键代码:
java复制// JWT工具类
public class JwtUtil {
private static final String SECRET = "物业系统密钥";
public static String generateToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24小时
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public static Boolean validateToken(String token, UserDetails user) {
final String username = extractUsername(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
}
前端在axios拦截器中添加token:
javascript复制// request拦截器
service.interceptors.request.use(config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
}, error => {
return Promise.reject(error)
})
3.2 物业缴费流程
缴费业务涉及多个服务的调用,采用Spring事务管理:
java复制@Transactional
public PayResult payPropertyFee(Long ownerId, Long feeId, PayMethod method) {
// 1. 校验业主和费用信息
PropertyFee fee = feeMapper.selectById(feeId);
if (fee == null || !fee.getOwnerId().equals(ownerId)) {
throw new BusinessException("缴费信息不匹配");
}
// 2. 调用支付接口
PaymentService payment = PaymentFactory.getService(method);
String tradeNo = payment.pay(fee.getAmount());
// 3. 更新缴费状态
fee.setStatus(PAID);
fee.setPayTime(new Date());
fee.setTradeNo(tradeNo);
feeMapper.updateById(fee);
// 4. 生成电子收据
receiptService.generate(fee);
return new PayResult(tradeNo, fee.getAmount());
}
3.3 报修工单系统
实现带图片上传的工单提交功能:
vue复制<template>
<el-upload
action="/api/upload"
list-type="picture-card"
:on-success="handleUploadSuccess">
<i class="el-icon-plus"></i>
</el-upload>
</template>
<script setup>
const form = reactive({
content: '',
images: []
})
const handleUploadSuccess = (res) => {
form.images.push(res.data.url)
}
</script>
后端用SpringBoot处理文件上传:
java复制@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.fail("请选择文件");
}
try {
String fileName = UUID.randomUUID() + getExtension(file);
Path path = Paths.get(uploadDir, fileName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return Result.success("/uploads/" + fileName);
} catch (IOException e) {
log.error("文件上传失败", e);
return Result.fail("上传失败");
}
}
4. 系统部署方案
4.1 后端部署要点
采用Docker容器化部署,Dockerfile示例:
dockerfile复制FROM openjdk:11-jre
WORKDIR /app
COPY target/community-manager.jar .
EXPOSE 8080
ENTRYPOINT ["java","-jar","community-manager.jar"]
启动参数建议:
bash复制docker run -d -p 8080:8080 \
-e SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/community \
-e SPRING_DATASOURCE_USERNAME=root \
-e SPRING_DATASOURCE_PASSWORD=123456 \
--name community-backend \
community-image
4.2 前端部署优化
使用Nginx部署前端并开启gzip压缩:
nginx复制server {
listen 80;
server_name community.example.com;
gzip on;
gzip_types text/plain application/javascript application/x-javascript text/css;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
}
}
5. 开发中的典型问题
5.1 跨域问题解决方案
开发阶段配置CORS:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3600);
}
}
生产环境建议:
- 使用Nginx反向代理统一域名
- 或配置具体的allowedOrigins
5.2 数据权限控制
不同角色看到不同范围的数据:
java复制@GetMapping("/complaints")
public PageResult<Complaint> listComplaints(
@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@AuthenticationPrincipal User user) {
LambdaQueryWrapper<Complaint> query = new LambdaQueryWrapper<>();
// 业主只能看自己的投诉
if (user.isOwner()) {
query.eq(Complaint::getOwnerId, user.getId());
}
// 物业员工看本小区投诉
else if (user.isStaff()) {
query.eq(Complaint::getCommunityId, user.getCommunityId());
}
return new PageResult<>(complaintService.page(
new Page<>(pageNum, pageSize), query));
}
6. 性能优化实践
6.1 缓存策略
使用Redis缓存热点数据:
java复制@Service
@CacheConfig(cacheNames = "notice")
public class NoticeService {
@Cacheable(key = "#communityId+':'+#type")
public List<Notice> getLatestNotices(Long communityId, Integer type) {
return noticeMapper.selectLatest(communityId, type);
}
@CacheEvict(key = "#notice.communityId+':'+#notice.type")
public void addNotice(Notice notice) {
noticeMapper.insert(notice);
}
}
6.2 SQL优化案例
优化前的慢查询:
sql复制SELECT * FROM property_fee
WHERE owner_id IN (
SELECT id FROM owner WHERE building_id = 'A栋'
)
优化后使用JOIN:
sql复制SELECT f.* FROM property_fee f
JOIN owner o ON f.owner_id = o.id
WHERE o.building_id = 'A栋'
建立联合索引:
sql复制ALTER TABLE owner ADD INDEX `idx_building_room` (`building_id`, `room_id`);
这套系统最终上线后,将物业公司的日常工作效率提升了60%以上。特别是移动端缴费功能,让业主满意度大幅提升。整个开发过程中,MyBatis-Plus的Lambda查询和SpringBoot的自动配置帮我们节省了大量重复工作。