ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot+Android智能公寓平台开发实战

SpringBoot+Android智能公寓平台开发实战

1. 项目概述

这个基于SpringBoot+Android的智能化公寓运营平台,是我在去年完成的一个实际商业项目。当时客户需要一套能够同时满足物业管理和租户使用的综合性系统,经过三个月的开发迭代,最终交付的这套系统目前已经稳定运行了9个月,管理着12栋公寓楼共计800多套房间。

系统采用典型的B/S架构设计,后端使用SpringBoot框架,前端Android应用通过RESTful API与后端交互。数据库选用MySQL 8.0,主要考虑到其事务处理能力和对复杂查询的优化。整个系统包含六大核心模块:租户管理、房间管理、合同管理、缴费管理、报修管理和系统管理。

2. 技术架构设计

2.1 后端技术栈选型

选择SpringBoot作为后端框架主要基于以下几个考量:

  1. 快速开发:自动配置和起步依赖大大减少了样板代码
  2. 微服务友好:方便后续扩展为微服务架构
  3. 生态丰富:Spring生态有大量现成解决方案

核心依赖包括:

  • Spring Web MVC:处理HTTP请求
  • Spring Data JPA:数据库操作
  • Spring Security:认证授权
  • Lombok:减少样板代码
  • MapStruct:对象映射

数据库设计遵循第三范式,主要表包括:

CREATE TABLE `apartment` ( `id` bigint NOT NULL AUTO_INCREMENT, `building_no` varchar(20) NOT NULL, `room_no` varchar(20) NOT NULL, `area` decimal(10,2) NOT NULL, `monthly_rent` decimal(10,2) NOT NULL, `status` tinyint NOT NULL COMMENT '0-空闲 1-已租 2-维修中', PRIMARY KEY (`id`), UNIQUE KEY `idx_building_room` (`building_no`,`room_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 移动端技术实现

Android端采用MVVM架构,主要组件包括:

  • Retrofit:网络请求
  • Room:本地缓存
  • ViewModel:UI数据管理
  • LiveData:数据观察
  • Navigation:页面导航

一个典型的网络请求封装:

interface ApiService { @GET("repairs") suspend fun getRepairList(@Query("tenantId") tenantId: Long): Response<List<Repair>> } class RepairRepository { private val api = RetrofitClient.getInstance().create(ApiService::class.java) suspend fun fetchRepairs(tenantId: Long): Result<List<Repair>> { return try { val response = api.getRepairList(tenantId) if (response.isSuccessful) { Result.success(response.body()!!) } else { Result.failure(Exception("Network error")) } } catch (e: Exception) { Result.failure(e) } } }

3. 核心功能实现

3.1 租赁管理模块

租赁流程包含以下步骤:

  1. 房源浏览与筛选
  2. 在线预约看房
  3. 电子合同签订
  4. 押金支付
  5. 钥匙领取

合同签署采用第三方电子签名服务集成:

public class ContractService { @Value("${esign.appId}") private String appId; @Value("${esign.appSecret}") private String appSecret; public String createSignUrl(Long contractId, Long tenantId) { // 调用电子签名平台API EsignClient client = new EsignClient(appId, appSecret); return client.createSignUrl(contractId, tenantId); } }

3.2 报修管理流程

报修流程设计要点:

  1. 多媒介报修:支持文字、语音、图片
  2. 智能分派:根据维修工技能和位置自动分配
  3. 进度推送:实时状态更新
  4. 评价反馈:维修完成后满意度评价

报修状态机实现:

public enum RepairStatus { PENDING(0, "待接单"), ACCEPTED(1, "已接单"), PROCESSING(2, "处理中"), COMPLETED(3, "已完成"), CANCELLED(4, "已取消"); // 状态转换校验逻辑 public static boolean canTransfer(RepairStatus from, RepairStatus to) { // 实现状态转换规则 } }

4. 关键技术难点与解决方案

4.1 并发支付处理

遇到的主要问题:高峰期租金支付时的并发冲突

解决方案:

  1. 乐观锁控制:
@Transactional public PaymentResult payRent(PaymentRequest request) { Lease lease = leaseRepository.findById(request.getLeaseId()) .orElseThrow(() -> new BusinessException("租赁合同不存在")); // 使用版本号控制并发 int updated = leaseRepository.updatePaymentStatus( lease.getId(), lease.getVersion(), PaymentStatus.PROCESSING); if (updated == 0) { throw new OptimisticLockException("支付状态更新冲突"); } // 调用支付网关 // ... }
  1. 引入分布式锁:
public PaymentResult payRentDistributed(PaymentRequest request) { String lockKey = "payment:" + request.getLeaseId(); try { // 尝试获取锁,超时时间3秒 boolean locked = redisLock.tryLock(lockKey, 3, TimeUnit.SECONDS); if (!locked) { throw new BusinessException("系统繁忙,请稍后再试"); } // 处理支付逻辑 // ... } finally { redisLock.unlock(lockKey); } }

4.2 移动端离线处理

Android端离线处理策略:

  1. 本地数据库缓存关键数据
  2. 操作队列存储待同步请求
  3. 网络恢复后自动同步
  4. 冲突解决策略(最后写入获胜)

Room数据库配置示例:

@Database(entities = [Repair::class, Payment::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun repairDao(): RepairDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { INSTANCE ?: Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "apartment.db" ).build().also { INSTANCE = it } } } } }

5. 安全设计与实现

5.1 认证授权方案

采用JWT + Spring Security实现:

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .antMatchers("/api/tenant/**").hasRole("TENANT") .antMatchers("/api/staff/**").hasRole("STAFF") .antMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }

5.2 敏感数据保护

  1. 数据库字段加密:
@Converter public class CryptoConverter implements AttributeConverter<String, String> { @Value("${encryption.key}") private String key; @Override public String convertToDatabaseColumn(String attribute) { // 使用AES加密 return AESUtil.encrypt(attribute, key); } @Override public String convertToEntityAttribute(String dbData) { return AESUtil.decrypt(dbData, key); } }
  1. 接口敏感信息脱敏:
@JsonComponent public class SensitiveDataSerializer extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { if (value == null) { gen.writeNull(); return; } // 手机号脱敏:138****1234 if (value.matches("^1[3-9]\\d{9}$")) { gen.writeString(value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")); } // 身份证脱敏 else if (value.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) { gen.writeString(value.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1**********$2")); } else { gen.writeString(value); } } }

6. 性能优化实践

6.1 数据库优化

  1. 索引优化:
-- 为常用查询字段添加索引 ALTER TABLE payment ADD INDEX idx_tenant_date (tenant_id, payment_date); ALTER TABLE repair ADD INDEX idx_status_created (status, created_at);
  1. 查询优化:
// 避免N+1查询 @Query("SELECT r FROM Repair r JOIN FETCH r.tenant WHERE r.status = :status") List<Repair> findByStatusWithTenant(@Param("status") RepairStatus status);

6.2 接口性能提升

  1. 缓存策略:
@Cacheable(value = "apartmentCache", key = "#buildingNo+'-'+#roomNo") public Apartment getApartmentDetail(String buildingNo, String roomNo) { return apartmentRepository.findByBuildingNoAndRoomNo(buildingNo, roomNo) .orElseThrow(() -> new ResourceNotFoundException("房间不存在")); }
  1. 批量操作优化:
@Transactional public void batchImportApartments(List<ApartmentImportDTO> dtos) { // 使用批量插入 jdbcTemplate.batchUpdate( "INSERT INTO apartment(building_no, room_no, area, monthly_rent, status) VALUES (?,?,?,?,0)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ApartmentImportDTO dto = dtos.get(i); ps.setString(1, dto.getBuildingNo()); ps.setString(2, dto.getRoomNo()); ps.setBigDecimal(3, dto.getArea()); ps.setBigDecimal(4, dto.getMonthlyRent()); } @Override public int getBatchSize() { return dtos.size(); } }); }

7. 项目部署方案

7.1 后端部署

采用Docker容器化部署:

FROM openjdk:11-jre-slim WORKDIR /app COPY target/apartment-management-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"]

使用docker-compose编排:

version: '3' services: app: build: . ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - DB_URL=jdbc:mysql://mysql:3306/apartment depends_on: - mysql mysql: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=rootpass - MYSQL_DATABASE=apartment - MYSQL_USER=appuser - MYSQL_PASSWORD=apppass volumes: - mysql_data:/var/lib/mysql ports: - "3306:3306" volumes: mysql_data:

7.2 移动端发布

Android发布流程:

  1. 生成签名密钥:
keytool -genkey -v -keystore apartment.jks -keyalg RSA -keysize 2048 -validity 10000 -alias apartment
  1. 配置Gradle签名:
android { signingConfigs { release { storeFile file("apartment.jks") storePassword "yourpassword" keyAlias "apartment" keyPassword "yourpassword" } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }

8. 项目演进方向

  1. 物联网集成:接入智能门锁、水电表等设备
  2. 数据分析:租金预测、维修预测等
  3. 微信小程序:扩展访问渠道
  4. 微服务化:按业务拆分服务

智能门锁集成示例:

public class SmartLockService { private final RestTemplate restTemplate; public void unlockDoor(Long apartmentId, String temporaryCode) { String url = "https://api.smartlock.com/v1/devices/{deviceId}/unlock"; Map<String, Object> request = new HashMap<>(); request.put("code", temporaryCode); request.put("duration", 30); // 30秒有效 restTemplate.postForObject( url, request, Void.class, getDeviceId(apartmentId)); } private String getDeviceId(Long apartmentId) { // 查询公寓对应的设备ID } }

9. 开发经验总结

  1. 领域建模要准确:初期对租赁周期状态设计不够完善,导致后期频繁修改

  2. 移动端兼容性:测试要覆盖各种Android版本,特别是权限处理的变化

  3. 接口版本控制:从开始就要考虑API版本兼容问题

  4. 文档自动化:使用Swagger UI自动生成API文档

Swagger配置示例:

@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example.apartment")) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("公寓管理系统API") .description("包含移动端和后管的所有接口") .version("1.0") .build(); } }

10. 常见问题排查

  1. Android端数据不同步问题:
  • 检查网络状态监听是否正常
  • 验证同步队列是否正常工作
  • 查看冲突解决策略是否合理
  1. 支付状态不一致:
  • 检查分布式锁的有效期
  • 验证事务隔离级别
  • 核对支付网关回调处理
  1. 性能瓶颈分析:
@Aspect @Component @Slf4j public class PerformanceMonitor { @Around("execution(* com.example.apartment..*.*(..))") public Object logPerformance(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long elapsed = System.currentTimeMillis() - start; if (elapsed > 500) { // 超过500ms记录警告 log.warn("Performance warning: {}.{} took {} ms", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), elapsed); } return result; } }
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进