1. 项目概述
这个小区管理系统采用了当前企业级开发中最流行的技术组合:SpringBoot+Vue3+MyBatis。作为一名有多年全栈开发经验的工程师,我认为这种技术栈选择既考虑了开发效率,又保证了系统性能。前后端分离的架构设计让团队可以并行开发,MySQL作为关系型数据库则提供了可靠的数据存储方案。
系统主要面向物业管理人员和小区业主,包含物业费管理、报修处理、停车管理、访客登记等核心功能模块。我在实际开发中发现,这种综合管理系统最难的不是技术实现,而是业务流程的梳理和权限控制的精细化管理。
2. 技术架构解析
2.1 后端技术选型
SpringBoot 2.7.x作为后端框架,这是我经过多个项目验证的稳定版本。相比原生Spring,它最大的优势是自动配置和起步依赖,可以快速搭建项目骨架。我在项目中特别配置了:
java复制spring:
datasource:
url: jdbc:mysql://localhost:3306/community_db?useSSL=false
username: root
password: 你的密码
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: update
MyBatis-Plus 3.5.x作为ORM框架,它的条件构造器和通用Mapper能显著减少样板代码。我特别推荐使用它的分页插件:
java复制@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2.2 前端技术选型
Vue3的组合式API相比Options API更适合复杂业务场景。我推荐使用以下技术栈组合:
- Vite作为构建工具(比Webpack快10倍以上)
- Element Plus作为UI组件库
- Axios处理HTTP请求
- Vue Router实现前端路由
- Pinia替代Vuex进行状态管理
一个典型的API请求封装示例:
javascript复制// src/utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
2.3 数据库设计要点
MySQL表设计遵循第三范式的同时,也需要考虑查询性能。小区管理系统的核心表包括:
| 表名 | 主要字段 | 说明 |
|---|---|---|
| sys_user | id, username, password, phone, role_id | 用户表 |
| property_fee | id, room_id, fee_amount, payment_status | 物业费表 |
| repair_order | id, user_id, description, status | 报修单表 |
| parking_space | id, number, status, owner_id | 停车位表 |
| visitor_record | id, visitor_name, id_card, visit_reason | 访客记录表 |
特别注意:密码字段一定要加密存储,推荐使用BCryptPasswordEncoder
3. 核心功能实现
3.1 权限控制系统
基于RBAC模型实现,包含5张核心表:用户表、角色表、权限表、用户-角色关联表、角色-权限关联表。后端使用Spring Security进行控制:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/property/**").hasAnyRole("ADMIN", "PROPERTY")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.csrf().disable();
}
}
前端实现动态路由的核心逻辑:
javascript复制// 过滤异步路由
function filterAsyncRoutes(routes, roles) {
return routes.filter(route => {
if (hasPermission(roles, route.meta?.roles)) {
if (route.children) {
route.children = filterAsyncRoutes(route.children, roles)
}
return true
}
return false
})
}
3.2 物业费管理模块
包含费用生成、账单查询、在线支付等功能。关键点是费用计算的定时任务:
java复制@Scheduled(cron = "0 0 1 1 * ?") // 每月1日凌晨1点执行
public void generatePropertyFees() {
List<Room> rooms = roomMapper.selectList(null);
rooms.forEach(room -> {
PropertyFee fee = new PropertyFee();
fee.setRoomId(room.getId());
fee.setFeeAmount(calculateFee(room.getArea()));
fee.setPaymentStatus(0);
feeMapper.insert(fee);
});
}
支付接口对接微信支付的示例代码:
java复制@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@PostMapping("/wxpay")
public Result wxPay(@RequestBody PaymentDTO dto) {
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<>();
data.put("body", "物业费缴纳");
data.put("out_trade_no", generateOrderNo());
data.put("total_fee", String.valueOf(dto.getAmount()));
data.put("spbill_create_ip", "123.12.12.123");
data.put("notify_url", "https://yourdomain.com/api/payment/notify");
data.put("trade_type", "JSAPI");
Map<String, String> resp = wxpay.unifiedOrder(data);
return Result.success(resp);
}
}
3.3 报修工单系统
实现报修提交、分配、处理、评价全流程。使用状态机模式管理工单状态流转:
java复制public enum RepairStatus {
PENDING(0, "待处理"),
ASSIGNED(1, "已分配"),
PROCESSING(2, "处理中"),
COMPLETED(3, "已完成"),
EVALUATED(4, "已评价");
// 省略构造函数和getter
}
@Service
public class RepairService {
@Transactional
public void changeStatus(Long orderId, RepairStatus newStatus) {
RepairOrder order = orderMapper.selectById(orderId);
if (!order.getStatus().canTransferTo(newStatus)) {
throw new BusinessException("状态转换不合法");
}
order.setStatus(newStatus);
orderMapper.updateById(order);
// 记录状态变更日志
RepairLog log = new RepairLog(orderId, order.getStatus(), newStatus);
logMapper.insert(log);
}
}
4. 前后端交互设计
4.1 API接口规范
采用RESTful风格设计,返回统一JSON格式:
json复制{
"code": 200,
"message": "success",
"data": {
"list": [...],
"total": 100
},
"timestamp": 1630000000000
}
使用Swagger生成API文档的配置:
java复制@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.community.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("小区管理系统API文档")
.description("前后端接口定义")
.version("1.0")
.build();
}
}
4.2 文件上传处理
使用阿里云OSS存储上传的文件:
java复制@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
String fileName = UUID.randomUUID() + getFileSuffix(file.getOriginalFilename());
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
ossClient.putObject(bucketName, fileName, file.getInputStream());
return Result.success("https://" + bucketName + "." + endpoint + "/" + fileName);
} finally {
ossClient.shutdown();
}
}
前端上传组件实现:
vue复制<template>
<el-upload
action="/api/upload"
:before-upload="beforeUpload"
:on-success="handleSuccess"
>
<el-button type="primary">点击上传</el-button>
</el-upload>
</template>
<script setup>
const beforeUpload = (file) => {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
ElMessage.error('文件大小不能超过10MB!')
return false
}
return true
}
const handleSuccess = (response) => {
if (response.code === 200) {
ElMessage.success('上传成功')
emit('on-success', response.data)
}
}
</script>
5. 部署与运维
5.1 后端部署方案
推荐使用Docker容器化部署:
dockerfile复制FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/community-system.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
Nginx配置示例:
nginx复制server {
listen 80;
server_name yourdomain.com;
location /api/ {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}
5.2 前端部署优化
Vite生产环境构建命令:
bash复制vite build --mode production
配置Gzip压缩提升加载速度:
javascript复制// vite.config.js
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
viteCompression({
algorithm: 'gzip',
ext: '.gz',
})
]
})
5.3 数据库优化建议
- 为常用查询字段建立索引:
sql复制ALTER TABLE repair_order ADD INDEX idx_status (status);
ALTER TABLE property_fee ADD INDEX idx_room_status (room_id, payment_status);
- 配置MySQL连接池参数:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
6. 常见问题与解决方案
6.1 跨域问题处理
SpringBoot后端配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
开发环境Vite代理配置:
javascript复制// vite.config.js
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
6.2 性能优化记录
- 启用MyBatis二级缓存:
xml复制<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 前端组件懒加载:
javascript复制const UserManage = () => import('./views/system/UserManage.vue')
- 接口响应时间监控:
java复制@Bean
public FilterRegistrationBean<StopWatchFilter> stopWatchFilter() {
FilterRegistrationBean<StopWatchFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new StopWatchFilter());
bean.addUrlPatterns("/api/*");
return bean;
}
6.3 安全性加固措施
- SQL注入防护:
java复制@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/list")
public Result list(@RequestParam String name) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(name), "username", name);
return Result.success(userService.list(wrapper));
}
}
- XSS防护:
javascript复制// 前端使用DOMPurify过滤
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(dirtyHtml)
- CSRF防护(虽然JWT方案本身免疫CSRF,但额外防护更安全):
java复制http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
7. 项目扩展方向
7.1 移动端适配方案
基于Uniapp开发微信小程序:
javascript复制// 封装通用请求方法
const request = (url, method, data) => {
return new Promise((resolve, reject) => {
uni.request({
url: baseURL + url,
method,
data,
success: (res) => {
if (res.data.code !== 200) {
uni.showToast({ title: res.data.message, icon: 'none' })
reject(res.data)
} else {
resolve(res.data.data)
}
}
})
})
}
7.2 物联网设备对接
门禁系统对接示例:
java复制@RestController
@RequestMapping("/api/iot")
public class IotController {
@PostMapping("/door/open")
public Result openDoor(@RequestBody DoorOpenDTO dto) {
// 验证用户权限
if (!userService.hasDoorPermission(dto.getUserId(), dto.getDoorId())) {
return Result.error("无开门权限");
}
// 调用物联网平台API
String result = iotPlatform.openDoor(dto.getDoorId());
return Result.success(result);
}
}
7.3 数据分析模块
使用ECharts实现数据可视化:
vue复制<template>
<div ref="chart" style="width: 100%; height: 400px;"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted, ref } from 'vue'
const chart = ref(null)
onMounted(() => {
const myChart = echarts.init(chart.value)
myChart.setOption({
title: { text: '物业费缴纳统计' },
tooltip: {},
xAxis: { data: ['1月', '2月', '3月', '4月', '5月', '6月'] },
yAxis: {},
series: [{ name: '金额', type: 'bar', data: [12000, 15000, 18000, 13500, 21000, 19000] }]
})
})
</script>
8. 开发经验分享
8.1 团队协作建议
-
使用Git分支策略:
- master:生产环境代码
- develop:集成测试分支
- feature/xxx:功能开发分支
- hotfix/xxx:紧急修复分支
-
接口文档维护:
- 使用Swagger + YAPI管理
- 每次接口变更必须更新文档
- 前端先根据Mock数据开发
8.2 代码质量保障
-
代码规范检查:
- 后端:Checkstyle + SpotBugs
- 前端:ESLint + Prettier
-
单元测试覆盖率:
- 关键业务逻辑必须达到80%+
- 使用JaCoCo生成报告
java复制@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testRegister() {
UserDTO dto = new UserDTO("testuser", "Test@123");
Long userId = userService.register(dto);
assertNotNull(userId);
User user = userService.getById(userId);
assertEquals("testuser", user.getUsername());
}
}
8.3 性能调优技巧
-
数据库查询优化:
- 避免SELECT *,只查询需要的字段
- 复杂查询使用EXPLAIN分析
- 批量操作使用MyBatis的foreach
-
前端性能优化:
- 路由懒加载
- 图片懒加载
- 防抖节流应用
javascript复制// 防抖示例
function debounce(fn, delay) {
let timer = null
return function() {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
9. 项目源码解析
9.1 核心目录结构
code复制community-system
├── community-api // 后端项目
│ ├── src/main/java
│ │ ├── com.community
│ │ │ ├── config // 配置类
│ │ │ ├── controller // 控制器
│ │ │ ├── entity // 实体类
│ │ │ ├── mapper // MyBatis Mapper
│ │ │ ├── service // 服务层
│ │ │ └── utils // 工具类
│ │ └── resources
│ │ ├── mapper // XML文件
│ │ └── application.yml
├── community-web // 前端项目
│ ├── public
│ └── src
│ ├── api // 接口定义
│ ├── assets // 静态资源
│ ├── components // 公共组件
│ ├── router // 路由配置
│ ├── stores // Pinia状态
│ ├── utils // 工具函数
│ └── views // 页面组件
└── docs // 文档
9.2 典型业务流程解析
以"业主报修→物业处理→业主评价"流程为例:
- 前端提交报修表单:
vue复制<template>
<el-form :model="form" :rules="rules" @submit.prevent="submit">
<el-form-item label="报修描述" prop="description">
<el-input type="textarea" v-model="form.description" />
</el-form-item>
<el-form-item label="上传图片">
<el-upload :action="uploadUrl" :on-success="handleUploadSuccess">
<el-button type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
</el-form>
</template>
- 后端处理逻辑:
java复制@PostMapping("/repair/submit")
public Result submitRepair(@RequestBody RepairSubmitDTO dto) {
// 验证用户身份
Long userId = SecurityUtils.getUserId();
// 创建工单
RepairOrder order = new RepairOrder();
order.setUserId(userId);
order.setDescription(dto.getDescription());
order.setImages(String.join(",", dto.getImages()));
order.setStatus(RepairStatus.PENDING);
repairService.save(order);
// 发送通知
messageService.sendRepairSubmitMsg(userId, order.getId());
return Result.success(order.getId());
}
- 状态变更处理:
java复制@Transactional
public void processRepair(Long orderId, Long staffId, String remark) {
RepairOrder order = getById(orderId);
if (order.getStatus() != RepairStatus.PENDING) {
throw new BusinessException("工单状态不合法");
}
order.setStaffId(staffId);
order.setStatus(RepairStatus.ASSIGNED);
order.setRemark(remark);
updateById(order);
// 记录操作日志
RepairLog log = new RepairLog(orderId,
RepairStatus.PENDING,
RepairStatus.ASSIGNED,
"分配给员工ID:" + staffId);
repairLogService.save(log);
// 发送通知
messageService.sendRepairAssignMsg(order.getUserId(), staffId);
}
10. 项目部署实战
10.1 生产环境部署清单
-
服务器要求:
- CPU: 4核+
- 内存: 8GB+
- 磁盘: 100GB+ (建议SSD)
- 带宽: 5Mbps+
-
软件依赖:
- JDK 1.8+
- MySQL 5.7+
- Redis 5.0+
- Nginx 1.18+
-
部署步骤:
bash复制# 后端部署
scp target/community-system.jar user@server:/app/
ssh user@server "cd /app && nohup java -jar community-system.jar --spring.profiles.active=prod > log.out 2>&1 &"
# 前端部署
npm run build
scp -r dist/* user@server:/var/www/html/
10.2 性能监控配置
- SpringBoot Actuator健康检查:
yaml复制management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
- Prometheus监控配置:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("application", "community-system");
}
- ELK日志收集方案:
xml复制<!-- logback-spring.xml -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>192.168.1.100:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
10.3 备份与恢复策略
- MySQL自动备份脚本:
bash复制#!/bin/bash
DATE=$(date +%Y%m%d)
BACKUP_DIR="/backup/mysql"
MYSQL_USER="backup"
MYSQL_PASS="yourpassword"
mysqldump -u$MYSQL_USER -p$MYSQL_PASS --all-databases | gzip > $BACKUP_DIR/full_$DATE.sql.gz
find $BACKUP_DIR -mtime +7 -name "*.sql.gz" -exec rm -f {} \;
- 前端静态资源备份:
bash复制rsync -avz /var/www/html/ user@backup-server:/backup/web/
- 应用版本回滚方案:
bash复制# 回滚到上一个版本
ps -ef | grep community-system.jar | grep -v grep | awk '{print $2}' | xargs kill -9
cp /app/community-system.jar.bak /app/community-system.jar
nohup java -jar community-system.jar --spring.profiles.active=prod > log.out 2>&1 &