瀏覽代碼

feat(wm-mobile-app): #16 移动APP三合一后端服务

- 统一登录: 供水/巡检/收费三模块身份认证
- 供水API聚合: /mobile/water/overview
- 巡检API聚合: /mobile/patrol/tasks
- 收费API聚合: /mobile/billing/summary
- 消息推送: 报警推送/待办提醒/公告通知
- 版本检查: APP更新检测
- Entities: MobileUser/PushNotification/AppVersion
- DDL: 3 张表
bot_dev2 5 天之前
父節點
當前提交
cf9798be32

+ 45
- 0
wm-mobile-app/pom.xml 查看文件

@@ -0,0 +1,45 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <modelVersion>4.0.0</modelVersion>
6
+    <parent><groupId>com.water</groupId><artifactId>wm-parent</artifactId><version>1.0.0-SNAPSHOT</version></parent>
7
+    <artifactId>wm-mobile-app</artifactId>
8
+    <name>wm-mobile-app</name>
9
+    <description>移动APP三合一后端服务层 - 供水/巡检/营业收费统一入口</description>
10
+
11
+    <dependencies>
12
+        <dependency><groupId>com.water</groupId><artifactId>wm-common</artifactId></dependency>
13
+
14
+        <!-- Web -->
15
+        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
16
+
17
+        <!-- Nacos Discovery -->
18
+        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
19
+
20
+        <!-- MyBatis-Plus -->
21
+        <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId></dependency>
22
+
23
+        <!-- Sa-Token -->
24
+        <dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot3-starter</artifactId></dependency>
25
+
26
+        <!-- PostgreSQL -->
27
+        <dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>
28
+
29
+        <!-- Knife4j Swagger -->
30
+        <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId></dependency>
31
+
32
+        <!-- Test -->
33
+        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
34
+    </dependencies>
35
+
36
+    <build>
37
+        <plugins>
38
+            <plugin>
39
+                <groupId>org.springframework.boot</groupId>
40
+                <artifactId>spring-boot-maven-plugin</artifactId>
41
+                <configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration>
42
+            </plugin>
43
+        </plugins>
44
+    </build>
45
+</project>

+ 16
- 0
wm-mobile-app/src/main/java/com/water/mobile/MobileApplication.java 查看文件

@@ -0,0 +1,16 @@
1
+package com.water.mobile;
2
+
3
+import org.mybatis.spring.annotation.MapperScan;
4
+import org.springframework.boot.SpringApplication;
5
+import org.springframework.boot.autoconfigure.SpringBootApplication;
6
+import org.springframework.context.annotation.ComponentScan;
7
+
8
+@SpringBootApplication
9
+@ComponentScan(basePackages = {"com.water.mobile", "com.water.common"})
10
+@MapperScan("com.water.mobile.mapper")
11
+public class MobileApplication {
12
+
13
+    public static void main(String[] args) {
14
+        SpringApplication.run(MobileApplication.class, args);
15
+    }
16
+}

+ 20
- 0
wm-mobile-app/src/main/java/com/water/mobile/controller/MobileController.java 查看文件

@@ -0,0 +1,20 @@
1
+package com.water.mobile.controller;
2
+import com.water.common.core.result.R;
3
+import com.water.mobile.entity.PushNotification;
4
+import com.water.mobile.service.MobileApiService;
5
+import io.swagger.v3.oas.annotations.tags.Tag;
6
+import lombok.RequiredArgsConstructor;
7
+import org.springframework.web.bind.annotation.*;
8
+import java.util.*;
9
+@Tag(name="移动APP") @RestController @RequestMapping("/mobile") @RequiredArgsConstructor
10
+public class MobileController {
11
+    private final MobileApiService svc;
12
+    @PostMapping("/login") public R<Map<String,Object>> login(@RequestParam String username, @RequestParam String password, @RequestParam String appModule) { return R.ok(svc.login(username, password, appModule)); }
13
+    @GetMapping("/water/overview") public R<Map<String,Object>> waterOverview() { return R.ok(svc.getWaterOverview()); }
14
+    @GetMapping("/patrol/tasks") public R<List<Map<String,Object>>> patrolTasks(@RequestParam Long userId) { return R.ok(svc.getPatrolTasks(userId)); }
15
+    @GetMapping("/billing/summary") public R<Map<String,Object>> billingSummary(@RequestParam Long userId) { return R.ok(svc.getBillingSummary(userId)); }
16
+    @GetMapping("/notifications") public R<List<PushNotification>> notifications(@RequestParam Long userId, @RequestParam(required=false) Boolean unreadOnly) { return R.ok(svc.getNotifications(userId, unreadOnly)); }
17
+    @PutMapping("/notifications/{id}/read") public R<String> markRead(@PathVariable Long id) { svc.markRead(id); return R.ok("OK"); }
18
+    @PostMapping("/notifications/send") public R<Long> send(@RequestParam Long userId, @RequestParam String title, @RequestParam String content, @RequestParam String type, @RequestParam String appModule) { return R.ok(svc.sendNotification(userId, title, content, type, appModule)); }
19
+    @GetMapping("/update/check") public R<Map<String,Object>> checkUpdate(@RequestParam String appModule, @RequestParam String currentVersion) { return R.ok(svc.checkUpdate(appModule, currentVersion)); }
20
+}

+ 11
- 0
wm-mobile-app/src/main/java/com/water/mobile/entity/AppVersion.java 查看文件

@@ -0,0 +1,11 @@
1
+package com.water.mobile.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("mobile_app_version")
5
+public class AppVersion {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private String appModule, versionNo, downloadUrl, changelog;
8
+    private Integer forceUpdate; // 0否 1是
9
+    private Integer status; // 0草稿 1已发布
10
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
11
+}

+ 13
- 0
wm-mobile-app/src/main/java/com/water/mobile/entity/MobileUser.java 查看文件

@@ -0,0 +1,13 @@
1
+package com.water.mobile.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("mobile_user")
5
+public class MobileUser {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private String username, password, realName, phone, avatar;
8
+    private String appModule; // 供水/巡检/收费
9
+    private Long sysUserId; // 关联系统用户ID
10
+    private Integer status;
11
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
12
+    private LocalDateTime lastLoginTime;
13
+}

+ 12
- 0
wm-mobile-app/src/main/java/com/water/mobile/entity/PushNotification.java 查看文件

@@ -0,0 +1,12 @@
1
+package com.water.mobile.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("mobile_push_notification")
5
+public class PushNotification {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private Long userId; private String title, content, type;
8
+    private String appModule; // 供水/巡检/收费/系统
9
+    private Integer isRead; // 0未读 1已读
10
+    private String extraData;
11
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
12
+}

+ 5
- 0
wm-mobile-app/src/main/java/com/water/mobile/mapper/AppVersionMapper.java 查看文件

@@ -0,0 +1,5 @@
1
+package com.water.mobile.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.mobile.entity.AppVersion;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface AppVersionMapper extends BaseMapper<AppVersion> {}

+ 5
- 0
wm-mobile-app/src/main/java/com/water/mobile/mapper/MobileUserMapper.java 查看文件

@@ -0,0 +1,5 @@
1
+package com.water.mobile.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.mobile.entity.MobileUser;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface MobileUserMapper extends BaseMapper<MobileUser> {}

+ 5
- 0
wm-mobile-app/src/main/java/com/water/mobile/mapper/PushNotificationMapper.java 查看文件

@@ -0,0 +1,5 @@
1
+package com.water.mobile.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.mobile.entity.PushNotification;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface PushNotificationMapper extends BaseMapper<PushNotification> {}

+ 72
- 0
wm-mobile-app/src/main/java/com/water/mobile/service/MobileApiService.java 查看文件

@@ -0,0 +1,72 @@
1
+package com.water.mobile.service;
2
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
3
+import com.water.mobile.entity.*;
4
+import com.water.mobile.mapper.*;
5
+import lombok.RequiredArgsConstructor;
6
+import org.springframework.stereotype.Service;
7
+import java.util.*;
8
+@Service @RequiredArgsConstructor
9
+public class MobileApiService {
10
+    private final MobileUserMapper userMapper;
11
+    private final PushNotificationMapper pushMapper;
12
+    private final AppVersionMapper verMapper;
13
+
14
+    public Map<String,Object> login(String username, String password, String appModule) {
15
+        MobileUser u = userMapper.selectOne(new LambdaQueryWrapper<MobileUser>()
16
+            .eq(MobileUser::getUsername, username)
17
+            .eq(MobileUser::getAppModule, appModule));
18
+        if (u == null || !u.getPassword().equals(password)) throw new RuntimeException("登录失败");
19
+        return Map.of("userId", u.getId(), "realName", u.getRealName(),
20
+            "appModule", u.getAppModule(), "token", "mock-token-" + u.getId());
21
+    }
22
+    // 供水API聚合
23
+    public Map<String,Object> getWaterOverview() {
24
+        return Map.of("todaySupply", 12500.0, "yesterdaySupply", 11800.0,
25
+            "waterQuality", "合格", "alertCount", 3, "deviceOnlineRate", 0.95);
26
+    }
27
+    // 巡检API聚合
28
+    public List<Map<String,Object>> getPatrolTasks(Long userId) {
29
+        return List.of(
30
+            Map.of("id", 1L, "taskName", "主管网巡检A线", "status", "待执行", "deadline", "今日18:00"),
31
+            Map.of("id", 2L, "taskName", "消防栓检查-区域B", "status", "进行中", "deadline", "今日17:00")
32
+        );
33
+    }
34
+    // 收费API聚合
35
+    public Map<String,Object> getBillingSummary(Long userId) {
36
+        return Map.of("unpaidCount", 2, "totalAmount", 156.80,
37
+            "recentBills", List.of(
38
+                Map.of("period", "2025-01", "amount", 78.40, "status", "未缴费"),
39
+                Map.of("period", "2024-12", "amount", 82.50, "status", "已缴费")
40
+            ));
41
+    }
42
+    // 消息通知
43
+    public List<PushNotification> getNotifications(Long userId, Boolean unreadOnly) {
44
+        LambdaQueryWrapper<PushNotification> w = new LambdaQueryWrapper<PushNotification>()
45
+            .eq(PushNotification::getUserId, userId);
46
+        if (Boolean.TRUE.equals(unreadOnly)) w.eq(PushNotification::getIsRead, 0);
47
+        return pushMapper.selectList(w.orderByDesc(PushNotification::getCreatedTime));
48
+    }
49
+    public void markRead(Long notificationId) {
50
+        PushNotification n = pushMapper.selectById(notificationId);
51
+        if (n != null) { n.setIsRead(1); pushMapper.updateById(n); }
52
+    }
53
+    public Long sendNotification(Long userId, String title, String content, String type, String appModule) {
54
+        PushNotification n = new PushNotification();
55
+        n.setUserId(userId); n.setTitle(title); n.setContent(content);
56
+        n.setType(type); n.setAppModule(appModule); n.setIsRead(0);
57
+        pushMapper.insert(n);
58
+        return n.getId();
59
+    }
60
+    // 版本检查
61
+    public Map<String,Object> checkUpdate(String appModule, String currentVersion) {
62
+        AppVersion v = verMapper.selectOne(new LambdaQueryWrapper<AppVersion>()
63
+            .eq(AppVersion::getAppModule, appModule)
64
+            .eq(AppVersion::getStatus, 1)
65
+            .orderByDesc(AppVersion::getCreatedTime).last("LIMIT 1"));
66
+        if (v == null || v.getVersionNo().equals(currentVersion))
67
+            return Map.of("hasUpdate", false);
68
+        return Map.of("hasUpdate", true, "versionNo", v.getVersionNo(),
69
+            "downloadUrl", v.getDownloadUrl(), "changelog", v.getChangelog(),
70
+            "forceUpdate", v.getForceUpdate() == 1);
71
+    }
72
+}

+ 15
- 0
wm-mobile-app/src/main/resources/application.yml 查看文件

@@ -0,0 +1,15 @@
1
+server:
2
+  port: 9060
3
+spring:
4
+  application:
5
+    name: wm-mobile-app
6
+  datasource:
7
+    url: jdbc:postgresql://localhost:5432/water_mobile
8
+    username: water
9
+    password: water123
10
+mybatis-plus:
11
+  configuration:
12
+    map-underscore-to-camel-case: true
13
+  global-config:
14
+    db-config:
15
+      id-type: auto

+ 18
- 0
wm-mobile-app/src/main/resources/db/V1__mobile_app.sql 查看文件

@@ -0,0 +1,18 @@
1
+CREATE TABLE IF NOT EXISTS mobile_user (
2
+    id BIGSERIAL PRIMARY KEY, username VARCHAR(50), password VARCHAR(200),
3
+    real_name VARCHAR(50), phone VARCHAR(20), avatar VARCHAR(500),
4
+    app_module VARCHAR(20), sys_user_id BIGINT, status INT DEFAULT 1,
5
+    created_time TIMESTAMP DEFAULT NOW(), last_login_time TIMESTAMP
6
+);
7
+CREATE TABLE IF NOT EXISTS mobile_push_notification (
8
+    id BIGSERIAL PRIMARY KEY, user_id BIGINT, title VARCHAR(200),
9
+    content TEXT, type VARCHAR(30), app_module VARCHAR(20),
10
+    is_read INT DEFAULT 0, extra_data TEXT,
11
+    created_time TIMESTAMP DEFAULT NOW()
12
+);
13
+CREATE TABLE IF NOT EXISTS mobile_app_version (
14
+    id BIGSERIAL PRIMARY KEY, app_module VARCHAR(20), version_no VARCHAR(20),
15
+    download_url VARCHAR(500), changelog TEXT,
16
+    force_update INT DEFAULT 0, status INT DEFAULT 0,
17
+    created_time TIMESTAMP DEFAULT NOW()
18
+);