最近在辅导学生毕业设计和指导团队项目时,发现很多同学在构建“前后端分离”的微服务项目时,常常陷入技术栈选择、模块拆分、环境联调等环节的泥潭。特别是将 Spring Boot、Vue 和微服务架构结合,用于一个具体的业务场景(如智能设备管理),往往资料零散,难以形成闭环。本文将以一个完整的“智能设备管理系统”为例,系统性地拆解从技术选型、环境搭建、核心模块开发到部署上线的全流程,并提供完整的代码、配置和避坑指南。无论你是正在寻找毕业设计课题的 Java 学习者,还是希望将微服务架构落地的开发者,都能从本文获得可直接复用的实战方案。

1. 项目背景与核心概念

1.1 什么是智能设备管理系统?

智能设备管理系统是一个用于集中监控、管理和维护各类联网设备(如 IoT 传感器、智能网关、工业控制器等)的软件平台。其核心功能通常包括:设备注册与鉴权、实时状态监控、远程指令下发、数据采集与分析、告警处理以及用户权限管理。在物联网和工业互联网快速发展的背景下,这类系统是连接物理世界与数字世界的核心枢纽。

1.2 为什么采用前后端分离与微服务架构?

传统的单体应用将所有功能模块打包在一起,随着业务复杂度的提升,会面临部署困难、技术栈固化、扩展性差等问题。而本系统采用的技术组合旨在解决这些问题:

  • 前后端分离 (Spring Boot + Vue) :后端专注于业务逻辑和 API 提供,使用 Spring Boot 快速构建 RESTful 服务;前端使用 Vue.js 构建动态、响应式的用户界面。两者通过 HTTP/JSON 通信,职责清晰,可以独立开发、测试和部署。
  • 微服务架构 :将庞大的单体应用拆分为一组小型、自治的服务。例如,将设备管理、用户认证、数据采集、告警引擎拆分为独立的服务。每个服务可以使用最适合的技术栈,独立伸缩,提高了系统的灵活性、可维护性和容错能力。

1.3 技术栈选型说明

  • 后端 (Spring Boot 2.x) : 作为微服务的基础框架,提供了自动配置、内嵌服务器等特性,能极大提升开发效率。
  • 前端 (Vue 3 + Element Plus) : Vue 3 的 Composition API 使代码组织更灵活,Element Plus 提供了丰富的 UI 组件,能快速搭建管理后台界面。
  • 微服务核心组件 :
    • Nacos : 作为服务注册与发现中心、配置中心。服务启动后向 Nacos 注册,并能从 Nacos 动态获取配置。
    • Spring Cloud Gateway : 作为 API 网关,统一处理所有入口流量,负责路由、过滤、限流等。
    • Spring Cloud OpenFeign : 声明式的 HTTP 客户端,用于微服务之间的调用,简化了 REST 客户端的编写。
    • Sentinel : 流量控制、熔断降级组件,保障微服务链路的稳定性。
  • 数据层 : MySQL 作为业务数据存储,Redis 用于缓存会话、设备状态等热点数据。
  • 消息队列 : RabbitMQ 或 Kafka,用于处理设备上报数据的异步解耦和削峰填谷。

2. 环境准备与项目初始化

2.1 开发环境清单

在开始编码前,请确保你的开发环境已就绪。以下版本为本文示例环境,请根据实际情况调整。

工具/环境 推荐版本 说明
操作系统 Windows 10/11, macOS, Linux 均可
JDK 1.8 或 11 Spring Boot 2.x 兼容版本
Maven 3.6+ 项目管理与构建工具
Node.js 16.x 或 18.x Vue 运行环境
NPM 8.x+ Node.js 包管理器
IDE IntelliJ IDEA, VS Code 后端推荐 IDEA,前端推荐 VS Code
数据库 MySQL 8.0 也可使用 5.7
缓存 Redis 6.x
服务注册/配置中心 Nacos 2.x 从官网下载并启动

2.2 后端父工程与公共模块创建

我们采用 Maven 多模块来组织微服务项目结构。

  1. 创建父工程 (smart-device-parent) : 使用 IDEA 创建 Spring Initializr 项目,类型选择 Maven POM ,只保留 spring-boot-starter spring-boot-starter-test 依赖。删除不必要的文件,最终 pom.xml 核心内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.18</version> <!-- 使用一个稳定的版本 -->
            <relativePath/>
        </parent>
    
        <groupId>com.example</groupId>
        <artifactId>smart-device-parent</artifactId>
        <version>1.0.0</version>
        <packaging>pom</packaging> <!-- 关键:打包方式为 pom -->
    
        <modules>
            <module>smart-device-common</module>
            <module>smart-device-gateway</module>
            <module>smart-device-auth</module>
            <module>smart-device-manager</module>
            <module>smart-device-monitor</module>
        </modules>
    
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>2021.0.8</spring-cloud.version>
            <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
            <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        </properties>
    
        <!-- 依赖管理,统一管理子模块版本 -->
        <dependencyManagement>
            <dependencies>
                <!-- Spring Cloud 依赖 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!-- Spring Cloud Alibaba 依赖 -->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!-- MyBatis-Plus -->
                <dependency>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                    <version>${mybatis-plus.version}</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    </project>
    
  2. 创建公共模块 (smart-device-common) : 在父工程下新建 smart-device-common 模块,用于存放工具类、通用实体、常量、异常定义等。其 pom.xml 引入通用依赖,如 Lombok、Hutool 等。

2.3 前端项目初始化

使用 Vue CLI 或 Vite 快速创建前端项目。

# 使用 Vue CLI (需全局安装 @vue/cli)
npm install -g @vue/cli
vue create smart-device-frontend
# 选择 Vue 3, 手动选择特性(Router, Vuex, CSS Pre-processors等)

# 或使用 Vite (更推荐,速度更快)
npm create vue@latest smart-device-frontend
# 同样选择需要的特性

cd smart-device-frontend
npm install element-plus axios vue-router@4 pinia
# Element Plus 为 UI 库,axios 用于 HTTP 请求,vue-router 路由,pinia 状态管理
npm install sass -D # 安装 CSS 预处理器

3. 微服务基础设施搭建

3.1 Nacos 服务注册与发现

所有微服务都需要注册到 Nacos,并能发现其他服务。

  1. 启动 Nacos Server : 从官网下载并解压,进入 bin 目录,执行 startup.cmd -m standalone (Windows) 或 sh startup.sh -m standalone (Linux/macOS)。访问 http://localhost:8848/nacos ,默认账号密码为 nacos/nacos

  2. 在微服务中集成 Nacos Client : 以 smart-device-auth (认证服务) 为例,在其 pom.xml 中添加依赖:

    <dependencies>
        <!-- Nacos 服务发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- Nacos 配置管理 (可选,如果需要动态配置) -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- Web 模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
  3. 配置 bootstrap.yml (或 application.yml ) : 在 resources 目录下创建 bootstrap.yml ,优先级更高,适合配置中心相关配置。

    spring:
      application:
        name: smart-device-auth # 服务名,用于注册
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # Nacos Server 地址
          config:
            server-addr: localhost:8848
            file-extension: yaml # 配置格式
            group: DEFAULT_GROUP
            namespace: public # 命名空间
    server:
      port: 8081 # 认证服务端口
    
  4. 主启动类添加注解 : 在 SmartDeviceAuthApplication.java 上添加 @EnableDiscoveryClient 注解。

    @SpringBootApplication
    @EnableDiscoveryClient // 启用服务发现客户端
    public class SmartDeviceAuthApplication {
        public static void main(String[] args) {
            SpringApplication.run(SmartDeviceAuthApplication.class, args);
        }
    }
    
  5. 启动服务并验证 : 启动 smart-device-auth 服务,刷新 Nacos 控制台的服务列表,应该能看到名为 smart-device-auth 的服务实例。

3.2 Spring Cloud Gateway API 网关搭建

网关是所有外部请求的入口,负责路由转发、权限校验、流量控制等。

  1. 创建网关模块 (smart-device-gateway) : 在父工程下新建模块,依赖如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Nacos 服务发现,网关需要知道后端服务地址 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- Sentinel 网关流控 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    </dependencies>
    
  2. 配置网关路由规则 ( application.yml ) :

    spring:
      application:
        name: smart-device-gateway
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
        gateway:
          discovery:
            locator:
              enabled: true # 开启从注册中心动态创建路由
          routes:
            - id: auth-service-route
              uri: lb://smart-device-auth # lb:// 表示负载均衡到服务名
              predicates:
                - Path=/api/auth/** # 匹配路径
              filters:
                - StripPrefix=1 # 去掉第一段路径(/api/auth)
            - id: manager-service-route
              uri: lb://smart-device-manager
              predicates:
                - Path=/api/device/**
              filters:
                - StripPrefix=1
        sentinel:
          transport:
            dashboard: localhost:8080 # Sentinel 控制台地址
    server:
      port: 8888 # 网关端口
    
  3. 启动网关 : 启动网关服务,访问 http://localhost:8888/api/auth/hello 的请求会被转发到 smart-device-auth 服务的 /hello 接口。

3.3 集成 Sentinel 实现熔断降级

在微服务调用链中,某个服务不稳定可能导致雪崩。Sentinel 可以提供服务熔断、降级和限流保护。

  1. 启动 Sentinel Dashboard : 从官网下载 jar 包,运行 java -jar sentinel-dashboard.jar 。默认访问 http://localhost:8080 ,账号密码 sentinel/sentinel

  2. 在业务服务中引入 Sentinel : 在 smart-device-manager (设备管理服务) 的 pom.xml 中添加依赖:

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  3. 配置 Sentinel ( application.yml ) :

    spring:
      cloud:
        sentinel:
          transport:
            dashboard: localhost:8080
          eager: true # 饥饿加载,服务启动即连接 Sentinel
    
  4. 使用注解进行熔断降级 : 在需要保护的方法上使用 @SentinelResource 注解。

    @Service
    public class DeviceService {
        // 模拟获取设备详情,可能会调用其他不稳定服务
        @SentinelResource(value = "getDeviceDetail", fallback = "getDeviceDetailFallback")
        public DeviceDetail getDeviceDetail(String deviceId) {
            // 这里可能是数据库查询或远程调用
            // 如果发生异常或慢调用,会触发降级方法
            return remoteService.getDetail(deviceId);
        }
    
        // 降级方法,返回兜底数据
        public DeviceDetail getDeviceDetailFallback(String deviceId, Throwable ex) {
            log.warn("获取设备详情降级,deviceId: {}, 异常: {}", deviceId, ex.getMessage());
            return new DeviceDetail(deviceId, "设备状态未知", "降级模式");
        }
    }
    

4. 核心业务模块开发实战

4.1 设备管理服务 (smart-device-manager)

此服务负责设备的 CRUD、状态管理、分组等核心业务。

  1. 数据库设计与 MyBatis-Plus 集成

    • 建表语句示例 ( device_info ) :

      CREATE TABLE `device_info` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
        `device_id` varchar(64) NOT NULL COMMENT '设备唯一标识',
        `device_name` varchar(128) DEFAULT NULL COMMENT '设备名称',
        `device_type` varchar(32) DEFAULT NULL COMMENT '设备类型',
        `status` tinyint(4) DEFAULT '0' COMMENT '状态 (0:离线, 1:在线, 2:故障)',
        `last_heartbeat` datetime DEFAULT NULL COMMENT '最后心跳时间',
        `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`),
        UNIQUE KEY `uk_device_id` (`device_id`),
        KEY `idx_status` (`status`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备信息表';
      
    • 实体类 ( DeviceInfo.java ) :

      @Data
      @TableName("device_info") // MyBatis-Plus 表名注解
      public class DeviceInfo {
          @TableId(type = IdType.AUTO)
          private Long id;
          private String deviceId;
          private String deviceName;
          private String deviceType;
          private Integer status;
          private Date lastHeartbeat;
          @TableField(fill = FieldFill.INSERT)
          private Date createTime;
          @TableField(fill = FieldFill.INSERT_UPDATE)
          private Date updateTime;
      }
      
    • Mapper 接口 ( DeviceInfoMapper.java ) :

      @Mapper
      public interface DeviceInfoMapper extends BaseMapper<DeviceInfo> {
          // 继承 BaseMapper 即拥有基本的 CRUD 方法
          // 可在此定义自定义 SQL 方法
          List<DeviceInfo> selectOnlineDevices(@Param("type") String type);
      }
      

      对应的 XML 文件 DeviceInfoMapper.xml 中编写自定义 SQL。

  2. Service 与 Controller 层

    • Service 接口与实现 :

      public interface IDeviceService extends IService<DeviceInfo> {
          Page<DeviceInfo> queryDevicePage(DeviceQueryDTO queryDTO);
          boolean updateDeviceStatus(String deviceId, Integer status);
      }
      
      @Service
      public class DeviceServiceImpl extends ServiceImpl<DeviceInfoMapper, DeviceInfo> implements IDeviceService {
          @Override
          public Page<DeviceInfo> queryDevicePage(DeviceQueryDTO queryDTO) {
              Page<DeviceInfo> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
              LambdaQueryWrapper<DeviceInfo> wrapper = new LambdaQueryWrapper<>();
              wrapper.like(StringUtils.isNotBlank(queryDTO.getDeviceName()),
                          DeviceInfo::getDeviceName, queryDTO.getDeviceName())
                     .eq(queryDTO.getStatus() != null,
                          DeviceInfo::getStatus, queryDTO.getStatus())
                     .orderByDesc(DeviceInfo::getUpdateTime);
              return this.page(page, wrapper);
          }
          @Override
          public boolean updateDeviceStatus(String deviceId, Integer status) {
              LambdaUpdateWrapper<DeviceInfo> wrapper = new LambdaUpdateWrapper<>();
              wrapper.eq(DeviceInfo::getDeviceId, deviceId)
                     .set(DeviceInfo::getStatus, status)
                     .set(DeviceInfo::getUpdateTime, new Date());
              return this.update(wrapper);
          }
      }
      
    • Controller 提供 REST API :

      @RestController
      @RequestMapping("/api/device")
      public class DeviceController {
          @Autowired
          private IDeviceService deviceService;
      
          @GetMapping("/page")
          public R<Page<DeviceInfo>> getDevicePage(DeviceQueryDTO queryDTO) {
              return R.ok(deviceService.queryDevicePage(queryDTO));
          }
      
          @PostMapping
          public R<Boolean> addDevice(@RequestBody @Valid DeviceInfo deviceInfo) {
              // 参数校验 @Valid
              boolean saved = deviceService.save(deviceInfo);
              return saved ? R.ok(true) : R.fail("添加设备失败");
          }
      
          @PutMapping("/{deviceId}/status")
          public R<Boolean> updateStatus(@PathVariable String deviceId, @RequestParam Integer status) {
              return R.ok(deviceService.updateDeviceStatus(deviceId, status));
          }
      }
      

4.2 前端 Vue 3 页面开发

前端使用 Vue 3 + Element Plus + Axios + Pinia 架构。

  1. API 请求封装 ( src/utils/request.js ) : 使用 Axios 创建实例,统一处理请求拦截(添加 Token)、响应拦截(处理错误)等。

    import axios from 'axios';
    import { ElMessage } from 'element-plus';
    import router from '@/router';
    import { useUserStore } from '@/stores/user';
    
    const service = axios.create({
      baseURL: process.env.VUE_APP_BASE_API, // 对应网关地址,如 http://localhost:8888/api
      timeout: 10000,
    });
    
    // 请求拦截器
    service.interceptors.request.use(
      (config) => {
        const userStore = useUserStore();
        if (userStore.token) {
          config.headers['Authorization'] = `Bearer ${userStore.token}`;
        }
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    
    // 响应拦截器
    service.interceptors.response.use(
      (response) => {
        const res = response.data;
        // 假设后端统一返回格式为 { code: 200, data: {}, msg: 'success' }
        if (res.code !== 200) {
          ElMessage.error(res.msg || 'Error');
          // 如果是 401 未授权,跳转到登录页
          if (res.code === 401) {
            router.push('/login');
          }
          return Promise.reject(new Error(res.msg || 'Error'));
        } else {
          return res.data; // 直接返回业务数据
        }
      },
      (error) => {
        ElMessage.error(error.message || '网络错误');
        return Promise.reject(error);
      }
    );
    
    export default service;
    
  2. 设备管理页面 ( src/views/device/DeviceList.vue ) : 使用 Composition API ( <script setup> ) 编写。

    <template>
      <div class="device-container">
        <el-card>
          <template #header>
            <div class="card-header">
              <span>设备管理</span>
              <el-button type="primary" @click="handleAdd">新增设备</el-button>
            </div>
          </template>
          <!-- 搜索表单 -->
          <el-form :inline="true" :model="queryParams">
            <el-form-item label="设备名称">
              <el-input v-model="queryParams.deviceName" placeholder="请输入" clearable />
            </el-form-item>
            <el-form-item label="状态">
              <el-select v-model="queryParams.status" placeholder="请选择" clearable>
                <el-option label="全部" :value="null" />
                <el-option label="在线" :value="1" />
                <el-option label="离线" :value="0" />
                <el-option label="故障" :value="2" />
              </el-select>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="handleQuery">搜索</el-button>
              <el-button @click="resetQuery">重置</el-button>
            </el-form-item>
          </el-form>
          <!-- 设备表格 -->
          <el-table :data="deviceList" v-loading="loading" border>
            <el-table-column prop="deviceId" label="设备ID" width="200" />
            <el-table-column prop="deviceName" label="设备名称" />
            <el-table-column prop="deviceType" label="类型" />
            <el-table-column prop="status" label="状态">
              <template #default="scope">
                <el-tag :type="statusTagType(scope.row.status)">
                  {{ statusText(scope.row.status) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="lastHeartbeat" label="最后心跳" width="180">
              <template #default="scope">
                {{ formatDate(scope.row.lastHeartbeat) }}
              </template>
            </el-table-column>
            <el-table-column label="操作" width="200" fixed="right">
              <template #default="scope">
                <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
                <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
          <!-- 分页 -->
          <div class="pagination-container">
            <el-pagination
              v-model:current-page="queryParams.pageNum"
              v-model:page-size="queryParams.pageSize"
              :total="total"
              :page-sizes="[10, 20, 50]"
              layout="total, sizes, prev, pager, next, jumper"
              @size-change="handleSizeChange"
              @current-change="handleCurrentChange"
            />
          </div>
        </el-card>
        <!-- 新增/编辑对话框 -->
        <DeviceDialog v-model="dialogVisible" :form-data="currentRow" @success="getList" />
      </div>
    </template>
    
    <script setup>
    import { ref, reactive, onMounted } from 'vue';
    import { ElMessage, ElMessageBox } from 'element-plus';
    import DeviceDialog from './components/DeviceDialog.vue';
    import { getDevicePage, deleteDevice } from '@/api/device';
    import { formatDate } from '@/utils/date';
    
    // 状态定义
    const loading = ref(false);
    const deviceList = ref([]);
    const total = ref(0);
    const dialogVisible = ref(false);
    const currentRow = ref({});
    
    // 查询参数
    const queryParams = reactive({
      pageNum: 1,
      pageSize: 10,
      deviceName: '',
      status: null,
    });
    
    // 状态映射
    const statusMap = { 0: '离线', 1: '在线', 2: '故障' };
    const statusTagMap = { 0: 'info', 1: 'success', 2: 'danger' };
    
    const statusText = (status) => statusMap[status] || '未知';
    const statusTagType = (status) => statusTagMap[status] || '';
    
    // 方法
    const getList = async () => {
      loading.value = true;
      try {
        const res = await getDevicePage(queryParams);
        deviceList.value = res.records || [];
        total.value = res.total;
      } catch (error) {
        console.error(error);
      } finally {
        loading.value = false;
      }
    };
    
    const handleQuery = () => {
      queryParams.pageNum = 1;
      getList();
    };
    
    const resetQuery = () => {
      Object.assign(queryParams, {
        pageNum: 1,
        pageSize: 10,
        deviceName: '',
        status: null,
      });
      getList();
    };
    
    const handleAdd = () => {
      currentRow.value = {};
      dialogVisible.value = true;
    };
    
    const handleEdit = (row) => {
      currentRow.value = { ...row };
      dialogVisible.value = true;
    };
    
    const handleDelete = (row) => {
      ElMessageBox.confirm(`确认删除设备 "${row.deviceName}" 吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(async () => {
        await deleteDevice(row.id);
        ElMessage.success('删除成功');
        getList();
      }).catch(() => {});
    };
    
    const handleSizeChange = (val) => {
      queryParams.pageSize = val;
      getList();
    };
    
    const handleCurrentChange = (val) => {
      queryParams.pageNum = val;
      getList();
    };
    
    // 生命周期
    onMounted(() => {
      getList();
    });
    </script>
    
    <style scoped>
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .pagination-container {
      margin-top: 20px;
      display: flex;
      justify-content: flex-end;
    }
    </style>
    
  3. API 接口定义 ( src/api/device.js ) :

    import request from '@/utils/request';
    
    export function getDevicePage(params) {
      return request({
        url: '/device/page',
        method: 'get',
        params,
      });
    }
    
    export function addDevice(data) {
      return request({
        url: '/device',
        method: 'post',
        data,
      });
    }
    
    export function updateDevice(data) {
      return request({
        url: `/device/${data.id}`,
        method: 'put',
        data,
      });
    }
    
    export function deleteDevice(id) {
      return request({
        url: `/device/${id}`,
        method: 'delete',
      });
    }
    

5. 服务间通信与数据一致性

5.1 使用 OpenFeign 进行服务调用

smart-device-monitor (监控服务) 需要从 smart-device-manager 获取设备信息时,使用 OpenFeign。

  1. 在调用方 ( monitor ) 添加依赖 :

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 启用 Feign 客户端 : 在主启动类上添加 @EnableFeignClients

  3. 声明 Feign 客户端接口 :

    // 在 monitor 服务中
    @FeignClient(name = "smart-device-manager", path = "/api/device")
    public interface DeviceClient {
        @GetMapping("/{deviceId}")
        R<DeviceInfo> getDeviceById(@PathVariable("deviceId") String deviceId);
    
        @PostMapping("/batch/status")
        R<Boolean> updateBatchStatus(@RequestBody Map<String, Integer> statusMap);
    }
    
  4. 注入并使用 :

    @Service
    public class MonitorService {
        @Autowired
        private DeviceClient deviceClient;
    
        public void processDeviceAlert(String deviceId) {
            R<DeviceInfo> result = deviceClient.getDeviceById(deviceId);
            if (result.getCode() == 200) {
                DeviceInfo device = result.getData();
                // 处理告警逻辑...
            }
        }
    }
    

5.2 分布式事务考虑 (Seata)

对于跨服务的写操作(如创建订单同时扣减库存),需要考虑分布式事务。可以使用 Seata 的 AT 模式。由于篇幅限制,这里仅给出集成思路:

  1. 部署 Seata Server。
  2. 在每个微服务的数据库中创建 undo_log 表。
  3. 引入 spring-cloud-starter-alibaba-seata 依赖。
  4. 在全局事务发起方法上添加 @GlobalTransactional 注解。

6. 常见问题与排查思路

在开发部署过程中,你可能会遇到以下典型问题:

问题现象 可能原因 排查步骤与解决方案
服务无法注册到 Nacos 1. Nacos Server 未启动或网络不通。
2. spring.cloud.nacos.discovery.server-addr 配置错误。
3. 服务端口冲突。
4. 依赖未正确引入。
1. 检查 Nacos 控制台 ( localhost:8848 ) 是否可访问。
2. 检查 bootstrap.yml 配置,确保地址和端口正确。
3. 查看服务启动日志,是否有注册成功的提示或错误信息。
4. 确认 pom.xml 中已添加 spring-cloud-starter-alibaba-nacos-discovery 依赖。
Gateway 路由转发 404 1. 路由配置的 predicates 路径匹配错误。
2. 目标服务未注册或服务名错误。
3. Gateway 未开启服务发现 ( spring.cloud.gateway.discovery.locator.enabled=true )。
4. 过滤器(如 StripPrefix )配置不当。
1. 使用 curl 或 Postman 直接访问后端服务接口,确认服务正常。
2. 检查 Nacos 中目标服务实例是否存在且健康。
3. 检查 Gateway 的 routes 配置,特别是 uri lb:// 前缀和服务名。
4. 调整或暂时注释掉过滤器,看是否能正常转发。
前端请求跨域 (CORS) 错误 浏览器安全策略阻止了不同源(域名、端口、协议)的请求。 方案一(推荐): 在 Gateway 统一配置 CORS
java<br>@Bean<br>public CorsWebFilter corsFilter() {<br> CorsConfiguration config = new CorsConfiguration();<br> config.addAllowedOriginPattern("*"); // 生产环境应指定具体域名<br> config.addAllowedHeader("*");<br> config.addAllowedMethod("*");<br> config.setAllowCredentials(true);<br> UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();<br> source.registerCorsConfiguration("/**", config);<br> return new CorsWebFilter(source);<br>}<br>
方案二: 在每个后端服务的 @RestController 上添加 @CrossOrigin 注解。
Sentinel 规则不生效 1. Sentinel Dashboard 未启动或连接失败。
2. 服务未正确引入 Sentinel 依赖和配置。
3. 资源名未正确匹配( @SentinelResource value )。
4. 规则未正确配置或未推送。
1. 确认 Sentinel Dashboard 已启动且服务配置的 dashboard 地址正确。
2. 检查服务日志,看是否有连接到 Sentinel 的日志。
3. 访问 Sentinel Dashboard,在“簇点链路”中查看资源是否出现。
4. 在 Dashboard 上为资源配置流控、降级规则。
Vue 前端打包后访问 API 404 1. 生产环境 API 地址配置错误。
2. Nginx 等代理服务器配置错误。
3. 前端路由模式为 history ,但服务器未配置 fallback。
1. 检查 .env.production 文件中的 VUE_APP_BASE_API 变量,确保指向正确的网关或后端地址。
2. 检查 Nginx 配置,确保 /api/ 路径被代理到后端网关。
3. 如果使用 history 模式,在 Nginx 配置中添加 try_files $uri $uri/ /index.html;
MyBatis-Plus 插入/更新时间为空 实体类字段未添加 @TableField(fill = FieldFill.INSERT) 等注解,或未配置元对象处理器。 1. 确保实体类字段有正确的 @TableField 注解。
2. 创建一个配置类,实现 MetaObjectHandler 接口,重写 insertFill updateFill 方法,自动填充时间。

7. 项目部署与生产环境建议

7.1 后端服务打包与运行

  1. 打包 : 在每个微服务模块根目录执行 mvn clean package -DskipTests ,生成可执行的 jar 文件。

  2. 运行 : 使用 java -jar 命令运行,但生产环境更推荐使用 Docker 容器化部署。

  3. Dockerfile 示例 :

    # 使用 OpenJDK 官方镜像
    FROM openjdk:8-jre-slim
    # 维护者信息
    LABEL maintainer="your-email@example.com"
    # 设置时区
    RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
    # 将 jar 包复制到容器中
    COPY target/smart-device-manager-1.0.0.jar /app.jar
    # 暴露端口
    EXPOSE 8082
    # 启动命令
    ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=prod"]
    
  4. 使用 Docker Compose 编排 : 编写 docker-compose.yml 文件,一键启动 Nacos、MySQL、Redis、各个微服务等。

7.2 前端项目打包与部署

  1. 打包 : 在前端项目根目录执行 npm run build ,生成 dist 目录。

  2. 部署 : 将 dist 目录下的文件放置到 Nginx 或 Apache 的静态资源目录下。

  3. Nginx 配置示例 :

    server {
        listen       80;
        server_name  your-domain.com; # 或 localhost
    
        # 前端静态资源
        location / {
            root   /usr/share/nginx/html/dist;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html; # 支持 Vue Router 的 history 模式
        }
    
        # 反向代理到后端网关
        location /api/ {
            proxy_pass http://gateway-service:8888/api/; # 网关服务地址
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
    

7.3 生产环境最佳实践

  1. 配置分离 : 使用 Nacos 配置中心管理所有环境的配置(开发、测试、生产),实现配置的动态刷新。
  2. 日志集中管理 : 集成 ELK (Elasticsearch, Logstash, Kibana) 或 Sentry,集中收集和查看微服务日志,便于问题排查。
  3. 监控与告警 : 集成 Prometheus + Grafana 监控 JVM、微服务指标(如 QPS、延迟、错误率),并设置告警规则。
  4. 服务高可用 : 关键服务(如 Nacos、MySQL、Redis)应部署集群,避免单点故障。微服务本身也应多实例部署,并通过网关实现负载均衡。
  5. 安全加固 :
    • API 安全 : 网关层集成 JWT 鉴权,对敏感接口进行权限控制。
    • 数据库安全 : 使用强密码,限制数据库访问 IP,定期备份。
    • 网络安全 : 使用 HTTPS,配置防火墙规则,关闭不必要的端口。
  6. CI/CD : 搭建 Jenkins 或 GitLab CI 流水线,实现代码提交后自动构建、测试、打包和部署,提升交付效率。

通过以上七个部分的系统讲解,我们完成了一个基于 Spring Boot + Vue 的微服务智能设备管理系统的核心搭建与开发。这个项目不仅涵盖了毕业设计所需的技术广度与深度,也体现了企业级微服务项目的基本架构思想。建议读者在理解整体流程后,动手实践每一个模块,并根据自己的业务需求进行功能扩展,例如加入设备数据可视化大屏、告警推送(集成邮件/短信)、设备固件升级管理等模块,让项目更加丰满和实用。

更多推荐