天津移动巡查系统 + 湖北天地图数据采集更新系统的后端工程。

# 架构

后端工程是基于 Spring Boot 的单体应用，主要分为三层，

- 接口层：为客户端程序提供 API
- 业务层：实现具体的业务逻辑
- 数据层：缓存和持久化存储

## 接口层

目前只实现了 HTTP API，在代码中表现为各种各样的 `@RestContoller` 类。

## 业务层

因为早期开发时没有固定的规范，代码组织不够合理，业务逻辑的实现代码在 `@RestController` 类和 `@Service` 类中都有出现。

对一些比较重要的业务逻辑的介绍请见后文。

## 数据层

目前通过 PostgreSQL 进行数据化的持久化存储，并基于 Redis 实现了一个简单的缓存中间件。 

# 业务流程

从整体上看，后端工程实现的业务逻辑比较复杂，这里只简单介绍主要业务流程所涉及的具体逻辑。目前只有湖北地图院将本系统投入到实际生产中，他们的业务流程包括以下部分：

1. 创建任务：管理员创建新任务，进行采集工具、底图、数据库等方面的配置；
2. 创建数据库：管理员创建该任务对应的数据库，并导入初始数据；
3. 分配任务：管理员为该任务指定外业人员，并根据作业区域、初始数据分布情况等条件进行任务分配；
4. 开展外业：外业人员进行核查、采集等工作，并上传数据；
5. 监管任务：管理员检查任务完成情况。

下面介绍各个环节的具体业务逻辑。

## 创建任务

目前实现了两种创建任务的方式：

- 管理员通过网页端进行各项配置，前端应用将配置项（组织成 JSON）发送给后端，后端据此创建配置文件和任务；
- 管理员通过网页端上传配置文件，后端解析配置文件读取配置项，创建任务。

第一种方式的实现主要在 [`cn.edu.whu.boot.xml.controller.TaskManagementController#createTask`](src/main/java/cn/edu/whu/boot/xml/controller/TaskManagementController.java) 和 [`cn.edu.whu.boot.xml.service.impl.TaskServiceImpl#insertTaskAndXmlEnity`](src/main/java/cn/edu/whu/boot/xml/service/impl/TaskServiceImpl.java) 这两个方法中，第二种方式的实现主要在 [`cn.edu.whu.boot.xml.controller.TaskManagementController#createTaskByXmlFile`](src/main/java/cn/edu/whu/boot/xml/controller/TaskManagementController.java) 和 [`cn.edu.whu.boot.xml.service.impl.TaskServiceImpl#insertTaskAndXmlEnity`](src/main/java/cn/edu/whu/boot/xml/service/impl/TaskServiceImpl.java) 这两个方法中。

这两种实现都涉及到**配置文件**的读/写操作，具体代码在 [`cn.edu.whu.boot.xml.xmlreader.XmlTemplateReader`](src/main/java/cn/edu/whu/boot/xml/xmlreader/XmlTemplateReader.java) 类中。

## 创建数据库

管理员创建任务之后，还需要进行一次手动确认才能创建数据库。创建数据库的步骤为：

1. 在 PostgreSQL 中新建一个 schema，该任务相关的所有表都在该 schema 下；
2. 在上一步新建的 schema 下创建表。

这一流程中执行的 SQL 语句都是**根据配置项动态生成的**，相关的代码主要在 [`cn.edu.whu.boot.xml.controller.DataBaseTableController#createTable`](src/main/java/cn/edu/whu/boot/xml/controller/DataBaseTableController.java)、[`cn.edu.whu.boot.xml.controller.DataBaseTableController#buildAllSqlString`](src/main/java/cn/edu/whu/boot/xml/controller/DataBaseTableController.java)、[`cn.edu.whu.boot.xml.service.impl.TableServiceImpl#createTableAndUpdateTask`](src/main/java/cn/edu/whu/boot/xml/service/impl/TableServiceImpl.java) 这些方法中。

管理员可以向表中导入 Shapefile 数据进行初始化，相关代码在 [`cn.edu.whu.boot.collection.controller.ImportController.shpDataMigration`](src/main/java/cn/edu/whu/boot/collection/controller/ImportController.java) 和 [`cn.edu.whu.boot.collection.service.impl.ImportServiceImpl.shpDataInsert`](src/main/java/cn/edu/whu/boot/collection/service/impl/ImportServiceImpl.java) 这两个方法中。

## 分配任务

管理员指定外业人员和上传工作区域 Shapefile 后，系统可自动进行任务分配，即将上一步中导入的数据按照一定的规则分配给外业人员，相关代码主要在 [`cn.edu.whu.boot.allocation.controllers.TaskController#assignCollectingTasks`](src/main/java/cn/edu/whu/boot/allocation/controllers/TaskController.java) 这个方法中。

任务自动分配的业务逻辑比较复杂，更具体的介绍可在[这一文档](docs/任务自动分配.md)中找到。

## 开展外业

外业人员核查/采集数据后会通过移动端程序上传数据，后端根据情况进行插入（INSERT）或更新（UPDATE）操作，相关代码在 [`cn.edu.whu.boot.collection.controller.BaseController#insertAndRecheckData`](src/main/java/cn/edu/whu/boot/collection/controller/BaseController.java)、[`cn.edu.whu.boot.collection.service.impl.BaseServiceImpl.insertData`](src/main/java/cn/edu/whu/boot/collection/service/impl/BaseServiceImpl.java)、[`cn.edu.whu.boot.collection.service.impl.BaseServiceImpl.updateRecheckData`](src/main/java/cn/edu/whu/boot/collection/service/impl/BaseServiceImpl.java) 这几个方法中。

## 监管任务

管理员可在网页端实时查看任务的进行情况，主要涉及到两类信息的查询：

- 任务进度，包括任务总量、已完成量、未完成量和每个外业人员的进度等，代码在 [`cn.edu.whu.boot.statistics.service.ReportService#countWorkloadByUser`](src/main/java/cn/edu/whu/boot/statistics/service/ReportService.java) 这个方法中。
- 外业人员已上传的数据，相关的代码在 [`cn.edu.whu.boot.collection.controller.GetController#getDataByPageNum`](src/main/java/cn/edu/whu/boot/collection/controller/GetController.java) 和 [`cn.edu.whu.boot.collection.service.impl.SearchPageServiceImpl#getPageDataByWorkerAndStatus`](src/main/java/cn/edu/whu/boot/collection/service/impl/SearchPageServiceImpl.java) 这两个方法中；这个接口返回的数据中包含一个状态码字段（`status`），关于该字段含义的解释请见[这一文档](docs/数据状态码字段.md)。

# 部署

如果条件允许的话可以用 [Docker Compose](docker-compose.yaml) 进行部署，否则按顺序执行以下操作：

1. 安装所需的各种软件，包括 JDK 1.8、PostgreSQL （版本至少为 9.6）、 [Redis](https://github.com/microsoftarchive/redis/releases) 和 [NGINX](http://nginx.org/en/download.html)；
2. 创建数据库，名称为 `tj_project`，通过 `psql` 或其他工具执行 [`scheme.sql`](postgres/schema.sql) 和 [`tables.sql`](postgres/tables.sql) 这两个 SQL 文件，对数据库进行初始化设置；
   - 创建的数据库的名称如果不是 `tj_project`，需要相应地修改 [`scheme.sql`](postgres/schema.sql) 和 [`tables.sql`](postgres/tables.sql) 文件开头 `\c` 命令的参数 
3. 运行 `redis-server.exe`，启动 Redis 服务；
4. 修改 [`application.properties`](src/main/resources/application.properties) 中数据库用户名和密码、静态资源存储路径等参数，将后端工程打包成 JAR 文件后启动即可；也可先打包，启动时再通过命令行传入相关参数；
   - 最方便的方法是写一个启动脚本，把所需的参数写在脚本内，然后直接通过脚本来启动程序，下面是一个 Windows 脚本的示例：

   ```
   java -jar <jar 文件路径> --server.port=9001
   ```
   - 如果编写了启动脚本，还可以将脚本的快捷方式放在 Startup 目录下（执行 `shell:common startup` 命令可打开该目录），这样系统重启时后端工程也会跟着重启
5. 将[这个文件](nginx/conf/nginx.conf)作为模板配置 Nginx，然后运行 `nginx.exe` 启动 NGINX 服务器。


## 将 NGINX 和 Redis 注册为 Windows 服务

1. 关闭正在运行的 NGINX（通过 `nginx.exe -s stop` 命令关闭） 和 Redis 实例；
2. 下载并解压 [NSSM](http://nssm.cc/download)，以管理员身份在相应的解压目录下启动一个 Powershell 窗口，执行命令 `nssm install`，将 `nginx.exe` 和 `redis-server.exe` 注册为 Windows 服务;
3. 通过 [`Start-Service`](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-service?view=powershell-7.2) 命令启动服务：

```powershell
> Start-Service "nginx"
> Start-Service "redis"
```