Java毕业设计实战:Spring Boot+微信小程序实现温室监控系统全流程搭建
发布时间:2026/9/8 7:17:14来源:尧图网络
最近被问得最多的 Java 毕业设计题目里温室监控系统绝对排得上号。乍一听很“传统”无非是温度湿度、传感器数据、曲线图怎么做到有工程感怎么在答辩时讲出东西来才是很多同学真正发愁的地方。做这类项目难点从来不是 Java 语法也不是小程序页面怎么写而是你能否把一条完整的数据链路跑通传感器数据 → 后端接口 → MySQL 存储 → 小程序展示 → 控制指令回流。这个链路一旦打通毕设的工作量和答辩说服力都会明显不一样。这篇文章会用一套“Spring Boot 微信小程序 MySQL”的温室监控系统为例把架构设计、数据库表设计、统一返回格式、登录流程、小程序请求封装、前后端联调、常见坑和答辩扩展方向完整拆开。如果你正在选毕设题目或者已经选了但还没理清楚代码结构这篇建议收藏后照着做。1. 这个课题真正锻炼的能力先给一个明确判断温室监控小程序这种题目最大的价值不是“智能农业”这个方向本身而是它完整覆盖了 Java 后端开发最常被考察的全流程能力。你可以从中练习到Spring Boot 项目的分层架构Controller、Service、Mapper、Entity 各司其职RESTful API 的设计与统一响应格式MySQL 表结构设计以及 MyBatis/MyBatis-Plus 的增删改查、分页、聚合查询HTTP 接口与微信小程序wx.request的联调过程用户登录场景中 openid 换取、Token 维护、请求鉴权数据库实时数据与历史数据的展示方案。对很多企业级开发岗位来说这些恰好是日常工作最常接触的部分。毕设做完你不仅交了一个系统还能在简历上写清楚“完成了基于 Spring Boot 的物联网数据采集与展示平台”面试时也能讲出接口设计和技术选型的理由。这套系统的典型使用场景是这样的温室里部署若干传感器节点采集空气温度、空气湿度、土壤湿度、光照强度等数据定时上报到后端服务后端接收后写入 MySQL同时提供查询最新数据和历史趋势的接口管理员或种植人员打开微信小程序即可看到温室内各项环境指标的实时数值也可以对卷帘、风机、灌溉设备下发控制指令还可以查看系统自动产生的告警记录。如果你的毕业设计不需要接真实硬件也完全可以用模拟器或定时任务生成模拟数据效果一样能演示清楚。这一点后面会展开讲。2. 系统整体设计与技术选型2.1 功能模块划分一个能用来答辩的温室监控系统至少要包含以下几个模块。这里不是让你堆功能而是每个模块都要能讲清楚“为什么需要”以及“数据怎么流转”。模块核心功能数据来源对应页面用户登录微信授权登录、Session/Token 维护微信登录凭证登录页、个人中心实时监控展示最新温度、湿度、光照等数据传感器上报接口监控首页历史数据按时间段查询环境数据变化sensor_record 表历史曲线页设备控制下发通风、灌溉、补光指令用户操作设备控制页告警管理阈值触发告警、查看告警记录后端定时判断告警列表页设备管理维护温室设备的基础信息后台管理设备列表页实际做的时候优先把实时监控、历史数据、设备控制三个模块做透这三个足以支撑论文里的系统设计、数据库建模和功能测试章节。2.2 技术栈与选型理由层次技术选型理由前端微信小程序原生框架无需安装 App扫码即可体验演示非常方便生态成熟、资料多后端Spring Boot快速搭建、自动配置、社区活跃毕业设计首选ORMMyBatis-Plus单表 CRUD 不用写 SQL能把代码量压得很低适合周期短的毕设数据库MySQL 5.7 / 8.0最常用、面试常问、安装调试资料最多权限方案JWT 或自定义 Token适合小程序登录态维护简单可控Spring Boot 版本选择上要特别注意如果你使用 Spring Boot 2.x一般搭配 JDK 8如果使用 Spring Boot 3.x则需要 JDK 17 及以上并且包名从javax变成了jakarta。关于版本这里不写死因为各学校实验室环境不同请以你实际能稳定运行的一套为准。毕设项目最忌讳的是“用最新版本”和“跟风升级”稳定跑通比版本新更重要。2.3 系统架构项目不做过度拆分采用经典前后端分离结构小程序端负责页面渲染、用户交互、数据展示后端提供 RESTful API 接口数据库存储用户、设备、传感器数据、告警记录可选中间件如果数据量大可以引入 Redis 缓存最新数据但毕设不强制。数据会经历这样的完整链路传感器上传数据 → 后端/api/sensor/report接口接收 → 校验数据合法性 → 写入sensor_record表 → 小程序端定时调用/api/sensor/latest查询最新值 → 渲染到页面 → 用户点击控制按钮 → 后端生成控制指令记录 → 模拟设备执行并回写状态。这条链路能说清楚整个项目的核心你已经掌握了。3. 数据库设计不要一上来就建表数据库设计是很多同学容易忽视、但答辩老师一定会问的部分。注意一个原则不要一上来就写CREATE TABLE而是先通过实体关系梳理表结构再落成 SQL。3.1 核心数据表这套系统最核心的表可以设计如下。用户表 user存储微信小程序用户的基本信息。字段类型说明idbigint主键自增openidvarchar(64)微信用户的唯一标识nicknamevarchar(64)用户昵称avatarvarchar(255)头像地址create_timedatetime创建时间设备表 device维护温室中可控或监测中的设备信息。字段类型说明idbigint主键device_namevarchar(64)设备名称device_typevarchar(32)设备类型风机、卷帘、灌溉等statustinyint当前状态0 关闭1 开启greenhouse_idint所属温室编号传感器数据表 sensor_record这是全系统最重要的表实时监控和历史曲线都依赖它。字段类型说明idbigint主键device_idint所属设备或传感器编号temperaturedecimal(5,2)空气温度humiditydecimal(5,2)空气湿度soil_moisturedecimal(5,2)土壤湿度light_intensityint光照强度create_timedatetime采集时间告警记录表 alert_record当某项指标超过阈值时由后端自动生成一条告警记录。字段类型说明idbigint主键device_idint告警设备alert_typevarchar(32)告警类型温度过高、湿度过低等alert_valuedecimal(10,2)触发告警时的数值create_timedatetime告警时间statustinyint处理状态0 未处理1 已处理控制指令表 control_command用户在小程序上点击操作后生成的下发指令记录。字段类型说明idbigint主键device_idint控制设备commandvarchar(32)指令内容open、closecreate_timedatetime指令时间operatorvarchar(64)操作人3.2 核心表 SQL 示例-- 文件路径sql/schema.sql CREATE DATABASE IF NOT EXISTS greenhouse DEFAULT CHARACTER SET utf8mb4; USE greenhouse; CREATE TABLE sensor_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_id INT NOT NULL COMMENT 传感器/设备ID, temperature DECIMAL(5,2) COMMENT 空气温度, humidity DECIMAL(5,2) COMMENT 空气湿度, soil_moisture DECIMAL(5,2) COMMENT 土壤湿度, light_intensity INT COMMENT 光照强度, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 采集时间, KEY idx_device_time (device_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT传感器数据表;注意这里给device_id和create_time建了联合索引。原因很简单历史趋势页最常执行的 SQL 就是“按设备 ID 和时间范围查询”没有索引时数据量上来后查询会明显变慢。这个设计点写进论文里能给数据库设计章节加分。4. 环境准备先把项目骨架跑起来动手写代码前先把环境理顺。很多同学卡在第一天不是因为代码难而是环境问题没解决。4.1 后端环境JDK按 Spring Boot 版本选择2.x 用 JDK 83.x 用 JDK 17IDEIntelliJ IDEA社区版即可MySQL5.7 或 8.0注意字符集设置为utf8mb4构建工具Maven建议配置阿里云镜像否则依赖下载会慢数据库连接工具Navicat 或 DataGrip 任选。4.2 小程序端环境微信开发者工具使用前需要注册一个小程序测试号在开发者工具中勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”否则本地开发时无法请求http://localhost或局域网 IP 接口。这里多说一句request合法域名只影响真机预览和发布体验版本地开发调试可以直接关闭校验但上线前必须把后端域名配置为 HTTPS并且在小程序后台添加到 request 合法域名列表。毕设演示一般用开发者工具或真机调试即可。4.3 Spring Boot 项目初始化推荐直接使用 Spring Initializr 生成项目骨架。核心依赖只需要Spring WebMySQL DriverMyBatis-Plus Framework如果 Spring Initializr 没有可在pom.xml中手动添加这是一个最小化的pom.xml关键依赖片段dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency如果你的项目整体面积不大直接用 MyBatis-Plus 的BaseMapper就能完成绝大多数单表操作不需要每张表都写 XML。这也是把精力集中在业务逻辑上而不是重复 CRUD 的关键。4.4 基础配置# 文件路径src/main/resources/application.yml 或 application.properties spring.datasource.urljdbc:mysql://localhost:3306/greenhouse?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.password你的数据库密码 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # MyBatis-Plus 相关配置 mybatis-plus.mapper-locationsclasspath*:mapper/**/*.xml mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl注意最后一个配置项会在控制台打印 SQL 日志开发阶段强烈建议开启。接口报错时先看 SQL 有没有执行、执行的参数是什么排查速度会快很多。上线前再把它去掉。5. 后端核心实现从 Entity 到 Controller5.1 统一返回结果类前后端联调最容易出现的问题是各接口返回格式不统一。有的返回Map有的返回String小程序端处理起来非常痛苦。我的建议是写一个统一的返回结果类后文所有接口都用它包装。// 文件路径src/main/java/com/example/greenhouse/common/Result.java Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }这里用了 Lombok 的Data注解需要引入lombok依赖。如果不熟悉 Lombok也可以手动写 getter/setter效果一样。统一返回格式后小程序端的wx.request只需要做一层拦截判断code是否为 200是则正常取数据否则弹出错误提示。这个模式也符合大多数公司后端接口的实际写法写到简历上不心虚。5.2 实体类以传感器记录为例// 文件路径src/main/java/com/example/greenhouse/entity/SensorRecord.java Data TableName(sensor_record) public class SensorRecord { TableId(type IdType.AUTO) private Long id; private Integer deviceId; private BigDecimal temperature; private BigDecimal humidity; private BigDecimal soilMoisture; private Integer lightIntensity; private Date createTime; }TableName用来指定实体对应的数据库表名TableId用来指定主键。字段的驼峰命名和数据库下划线命名之间MyBatis-Plus 默认会做自动映射比如deviceId自动映射到device_id。5.3 Controller 实现Controller 层尽量保持薄只做参数接收和结果返回业务判断放在 Service 层。// 文件路径src/main/java/com/example/greenhouse/controller/SensorController.java RestController RequestMapping(/api/sensor) public class SensorController { Autowired private SensorRecordService sensorRecordService; /** * 查询某个设备最新的传感器数据 */ GetMapping(/latest) public ResultSensorRecord latest(RequestParam Integer deviceId) { return Result.success(sensorRecordService.getLatestRecord(deviceId)); } /** * 分页查询历史数据 */ GetMapping(/history) public ResultIPageSensorRecord history( RequestParam Integer deviceId, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 20) Integer size) { return Result.success(sensorRecordService.pageHistory(deviceId, page, size)); } }这里的IPage是 MyBatis-Plus 的分页对象。如果不想依赖它也可以返回PageResult自定义分页结构核心是前端拿到total和records两个字段。5.4 Service 实现// 文件路径src/main/java/com/example/greenhouse/service/impl/SensorRecordServiceImpl.java Service public class SensorRecordServiceImpl extends ServiceImplSensorRecordMapper, SensorRecord implements SensorRecordService { Override public SensorRecord getLatestRecord(Integer deviceId) { return lambdaQuery() .eq(SensorRecord::getDeviceId, deviceId) .orderByDesc(SensorRecord::getCreateTime) .last(limit 1) .one(); } Override public IPageSensorRecord pageHistory(Integer deviceId, long page, long size) { return lambdaQuery() .eq(SensorRecord::getDeviceId, deviceId) .orderByDesc(SensorRecord::getCreateTime) .page(new Page(page, size)); } }如果项目里没有引入 MyBatis-Plus也可以写传统Mapper加 XML 的方式SQL 逻辑类似SELECT * FROM sensor_record WHERE device_id #{deviceId} ORDER BY create_time DESC LIMIT 1;两种方式选一种即可。用 MyBatis-Plus 代码更少答辩时可以解释“为什么选它”——因为单表 CRUD 和分页非常常见用它可以把开发重心放在业务上。这个理由技术面试官是认可的。5.5 跨域配置如果你在小程序端请求时遇到跨域问题不要慌。wx.request本身是客户端到服务端的请求不严格受浏览器同源策略影响但后端有时候仍然需要处理 CORS。这里给出一个常规配置类// 文件路径src/main/java/com/example/greenhouse/config/CorsConfig.java Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }需要说明的是毕设阶段这个配置够用生产环境请按实际域名收紧allowedOriginPatterns不要使用通配符。5.6 微信登录接口登录是毕业设计里必讲的一个点。微信小程序登录流程可以简化为三步小程序端调用wx.login()获取临时登录凭证code小程序把code通过wx.request发送给后端后端调用微信的jscode2session接口用code换取用户的openid并把该openid作为用户唯一标识保存。后端登录接口示例// 文件路径src/main/java/com/example/greenhouse/controller/UserController.java RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; Autowired private WxProperties wxProperties; PostMapping(/login) public ResultString login(RequestBody LoginRequest loginRequest) { String openid userService.getOpenidByCode(loginRequest.getCode()); User user userService.findOrCreate(openid); String token userService.createToken(user.getId()); return Result.success(token); } }这里需要特别提醒AppSecret绝对不允许出现在小程序前端代码里只能放在后端。原因很简单AppSecret一旦泄露任何人都可以伪造 session 换取用户信息。答辩时如果能主动说出这一点会是一个很明显的加分项。6. 小程序端核心实现从请求封装到页面渲染6.1 请求封装把wx.request封装成一个 Promise 方法页面里就不需要重复写success和fail了。// 文件路径utils/request.js const BASE_URL http://localhost:8080/api; function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常请检查后端服务, icon: none }); reject(err); } }); }); } module.exports { request };这个封装有两个关键点请求头里自动带上Authorization登录接口拿到 Token 后存入wx.setStorageSync后续请求就能携带用户态。统一判断res.data.code是否为 200避免每个页面重复做错误处理。6.2 监控首页数据展示在首页onLoad生命周期中调用后端接口拿数据显示到页面。// 文件路径pages/monitor/monitor.js const { request } require(../../utils/request); Page({ data: { temperature: --, humidity: --, soilMoisture: --, lightIntensity: --, updateTime: }, onLoad() { this.fetchLatestData(); }, onPullDownRefresh() { this.fetchLatestData().finally(() wx.stopPullDownRefresh()); }, fetchLatestData() { return request(/sensor/latest?deviceId1) .then(data { this.setData({ temperature: data.temperature, humidity: data.humidity, soilMoisture: data.soilMoisture, lightIntensity: data.lightIntensity, updateTime: data.createTime }); }) .catch(err { console.error(获取环境数据失败, err); }); } });WXML 部分只需要按自己审美展示数值即可。核心逻辑是setData更新页面数据onPullDownRefresh做下拉刷新。6.3 登录按钮实现// 文件路径pages/login/login.js const { request } require(../../utils/request); Page({ handleLogin() { wx.login({ success(res) { if (res.code) { request(/user/login, POST, { code: res.code }) .then(token { wx.setStorageSync(token, token); wx.navigateBack(); }) .catch(err { console.error(登录失败, err); }); } else { console.error(登录失败, res.errMsg); } } }); } });很多同学会误以为小程序登录必须做“微信授权弹窗”其实基础登录只需要拿到code。头像、昵称等用户信息属于更后面的扩展功能不必在第一步纠缠。6.4 历史数据与图表历史数据页可以采用wx-charts或 canvas 手绘折线图。具体方式看个人熟悉程度这里只强调一个易踩的坑后端返回的时间字段如果带时区差异小程序端展示时可能相差 8 小时。解决方案是在 JDBC 连接串中配置serverTimezoneAsia/Shanghai并且统一在接口 DTO 中格式化时间字符串不要让前端自己去猜时区。7. 联调与运行验证7.1 启动后端mvn spring-boot:run或者先在 IDEA 中直接运行GreenhouseApplication.java的主方法。启动成功后控制台会打印 Spring Boot 的 Banner 和端口号默认是 8080。如果没有看到启动成功日志第一步应该检查数据库连接配置具体排查方式见第 8 节。7.2 使用 curl 快速验证接口后端启动后先不要急着打开小程序先用 curl 验证接口是否可访问curl http://localhost:8080/api/sensor/latest?deviceId1如果数据库中还没有数据接口会返回 null 或空对象但不应该报 500 错误。正常返回示例{ code: 200, message: success, data: { id: 1, deviceId: 1, temperature: 26.50, humidity: 60.20, soilMoisture: 35.10, lightIntensity: 1200, createTime: 2025-01-06 10:30:00 } }7.3 小程序端验证在微信开发者工具中导入小程序项目填入自己的小程序 AppID 或用测试号在详情中勾选“不校验合法域名”。然后点击编译页面能展示温度、湿度数据即表示前后端链路已打通。这里有一个非常常见的问题开发者工具里能请求但真机预览不行。原因是手机无法访问localhost需要把BASE_URL改为电脑的局域网 IP例如http://192.168.1.100:8080/api而且手机和电脑需要在同一个局域网内。8. 常见问题与排查方法下面是这套系统从开发到演示过程中出现频率最高的问题整理成表格方便你照着排查。问题现象可能原因排查方式解决方案后端启动报连接数据库失败MySQL 未启动、用户名密码错误、数据库不存在查看控制台最后几行错误日志先用 Navicat 或命令行连接数据库确认连接参数正确Maven 依赖下载慢或失败默认中央仓库网络不稳定查看 Maven 日志中的下载地址配置阿里云镜像仓库接口返回 404Controller 路径或请求方法不对查看 IDEA 控制台是否打印 HandlerMapping检查RequestMapping路径确认 GET/POST 方法一致小程序请求报errno或request:failBASE_URL配置不对、未勾选不校验合法域名在 console 查看完整错误信息本地调试时改为局域网 IP并在开发者工具中勾选不校验域名MyBatis-Plus 实体映射失败数据库字段和实体属性对不上开启 SQL 日志查看查询语句中的列名检查驼峰映射配置或使用TableField指定小程序页面空白数据没拿到或setData字段名不对console 打印res和data对象确认接口返回字段和 JS 中属性名一致前端中文乱码数据库连接串缺少编码参数查看页面返回的 JSON在 JDBC URL 中增加characterEncodingutf8真机上图片加载失败图片域名未在小程序后台配置查看 console 中的域名报错本地演示可关闭合法域名校验上线需配置合法域名每个问题都要养成先看日志的习惯。后端接口出问题看 IDEA 控制台小程序出问题看开发者工具的 Console 和 Network 面板绝大多数问题都能在这两层日志里找到线索。9. 毕业设计答辩的亮点与扩展方向到这里一个完整的温室监控系统已经能跑起来了。但如果你想让它在答辩时更出彩我建议从下面几个方向挑一两个做深而不是继续堆页面。9.1 模拟传感器数据既然毕设大概率没有真实硬件那就要把“数据从哪来”这个逻辑讲圆。最稳妥的方案是启动一个定时任务每隔几秒钟向/api/sensor/report接口写入一条模拟数据。// 文件路径src/main/java/com/example/greenhouse/task/SensorDataTask.java Component public class SensorDataTask { Autowired private SensorRecordMapper sensorRecordMapper; Scheduled(fixedDelay 5000) public void generateSensorData() { SensorRecord record new SensorRecord(); record.setDeviceId(1); record.setTemperature(BigDecimal.valueOf(25 Math.random() * 5)); record.setHumidity(BigDecimal.valueOf(50 Math.random() * 10)); record.setSoilMoisture(BigDecimal.valueOf(30 Math.random() * 15)); record.setLightIntensity((int) (800 Math.random() * 800)); sensorRecordMapper.insert(record); } }在启动类上记得加上EnableScheduling。这样演示时不需要打开任何硬件设备页面上的数据也会一直刷新观感很接近真实系统。注意真实项目中上报数据的接口一定要做参数校验和重复数据判断防止脏数据刷入。毕设演示环境可以用简单实现但论文里要能说出这些风险。9.2 告警规则与推送告警模块可以做成两步后端定时检查最新数据是否超过阈值如果超过则写入alert_record表小程序端在首页轮询或下拉刷新时展示未处理告警数量。这个模块非常适合用来回答“你的系统除了增删改查还做了什么”这个问题。因为它是典型的“数据驱动业务判断”不是简单的页面堆叠。9.3 接口安全性如果想把项目提到“工程级”可以给非登录接口加一个简单的拦截器验证请求头中的 Token 是否存在。// 文件路径src/main/java/com/example/greenhouse/config/AuthInterceptor.java public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } // 这里补充 Token 校验逻辑 return true; } }这里的 Token 校验逻辑需要自己在 Redis 或数据库中维护毕设阶段可以直接校验数据库中的 user 表或者用 JWT 解析出用户 ID 并查询确认存在。关键是让答辩老师看到你有安全意识而不是真的要你实现一套完整的认证中心。9.4 可以继续深入的方向如果你的时间比较充裕还有几个不错的扩展点使用 Redis 缓存最新传感器数据降低数据库查询压力小程序端增加 ECharts 或 canvas 折线图用图表展示历史趋势增加告警阈值配置页面让用户自己设置各指标的上下限接入 WebSocket 或小程序订阅消息实现告警实时推送后端增加单元测试覆盖核心 Service 方法。这些方向里任何一个做扎实了都能支撑起开题报告中的“研究内容”和论文中的“系统实现”章节。题外话一句如果这种前后端分离项目的开发环境遇到 IDEA 创建项目超时、Maven 依赖拉取慢这些都属于常见环境问题一般通过修改 Maven 镜像仓库、关闭不必要的 IDE 插件就能解决不要因为这些问题浪费时间换项目。最后想说的是毕业设计最大的收获不是你“做完了”一个系统而是你能把“为什么这么设计”讲清楚。温室监控这个题目的上限很高但即使你只做了实时监控、历史数据和告警管理只要链路完整、代码结构清晰、数据库设计合理它就是一份合格的、能过答辩的毕设。建议先把这套链路跑通再逐步扩展祝你顺利。建议收藏备用也欢迎在评论区分享你做到哪一步卡住了一起排查。
网站建设高端定制企业官网