1. 项目概述与技术栈选型
这个植物健康监测系统采用前后端分离架构,前端使用Vue3+TypeScript+Element Plus,后端基于SpringBoot 2.7.x+MyBatis-Plus 3.5.x,数据库选用MySQL 8.0。系统主要功能包括植物生长环境监测、病虫害识别、养护建议推送等模块。
为什么选择这个技术组合?从2023年StackOverflow开发者调查来看,这个技术栈有几个显著优势:
- SpringBoot的自动配置特性让后端服务快速启动(实测单个微服务启动时间<3s)
- Vue3的Composition API相比Options API在复杂组件中代码组织更清晰(我们的详情页组件代码量减少40%)
- MyBatis-Plus的ActiveRecord模式简化了90%的单表CRUD操作
- MySQL 8.0的JSON字段支持完美适配植物监测中的非结构化传感器数据
实际开发中发现:MyBatis-Plus的LambdaQueryWrapper在复杂联表查询时性能较差,需要手动编写XML映射文件。这是我们在技术选型时未充分考虑的点。
2. 环境搭建与工程初始化
2.1 后端工程配置
使用Spring Initializr生成项目时特别注意:
bash复制# 必须包含的依赖
spring-boot-starter-web
spring-boot-starter-actuator
mybatis-plus-boot-starter
mysql-connector-java
lombok
数据库连接池配置示例(application.yml):
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/plant_health?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
hikari:
maximum-pool-size: 20
connection-timeout: 30000
2.2 前端工程配置
Vue3项目创建时推荐选择:
- TypeScript
- Pinia(替代Vuex)
- Element Plus(UI组件库)
- ESLint + Prettier
实测Element Plus的ElTable组件在渲染1000条数据时会出现明显卡顿,解决方案:
typescript复制// 在表格外层添加虚拟滚动容器
<el-table-v2
:columns="columns"
:data="data"
:width="1000"
:height="500"
/>
3. 核心功能实现细节
3.1 植物数据采集模块
数据库表设计关键字段:
sql复制CREATE TABLE `plant_data` (
`id` bigint NOT NULL AUTO_INCREMENT,
`plant_id` varchar(32) NOT NULL COMMENT '植物唯一标识',
`temperature` decimal(5,2) DEFAULT NULL COMMENT '环境温度',
`humidity` decimal(5,2) DEFAULT NULL COMMENT '环境湿度',
`soil_moisture` decimal(5,2) DEFAULT NULL COMMENT '土壤湿度',
`light_intensity` int DEFAULT NULL COMMENT '光照强度',
`image_url` varchar(255) DEFAULT NULL COMMENT '植物照片URL',
`health_status` tinyint DEFAULT '0' COMMENT '健康状态0-正常 1-预警 2-危险',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_plant_id` (`plant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
MyBatis-Plus实体类配置技巧:
java复制@Data
@TableName(value = "plant_data", autoResultMap = true)
public class PlantData {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("plant_id")
private String plantId;
// 使用TypeHandler处理传感器数据
@TableField(typeHandler = BigDecimalTypeHandler.class)
private BigDecimal temperature;
// 非数据库字段需要明确标记
@TableField(exist = false)
private String plantName;
}
3.2 前后端数据交互规范
我们采用RESTful风格API设计,响应体统一格式:
json复制{
"code": 200,
"message": "success",
"data": {
// 业务数据
},
"timestamp": 1689321600000
}
Axios拦截器配置示例:
typescript复制// request拦截器
axios.interceptors.request.use(config => {
config.headers['Authorization'] = getToken()
return config
})
// response拦截器
axios.interceptors.response.use(
response => {
if (response.data.code !== 200) {
ElMessage.error(response.data.message)
return Promise.reject(response.data)
}
return response.data.data
},
error => {
ElMessage.error(error.message)
return Promise.reject(error)
}
)
4. 安全防护与性能优化
4.1 SQL注入防护方案
针对MyBatis使用${}导致的SQL注入风险(奇安信扫描报漏洞),我们采取以下措施:
- 全局禁用${},强制使用#{}预处理
xml复制<!-- 错误示例 -->
<select id="getByCondition" resultType="PlantData">
SELECT * FROM plant_data WHERE ${column} = #{value}
</select>
<!-- 正确写法 -->
<select id="getByCondition" resultType="PlantData">
SELECT * FROM plant_data
<where>
<if test="column == 'temperature'">temperature = #{value}</if>
<if test="column == 'humidity'">humidity = #{value}</if>
</where>
</select>
- 使用MyBatis-Plus的QueryWrapper时避免字符串拼接
java复制// 错误写法
wrapper.apply("date_format(create_time,'%Y-%m-%d') = " + date);
// 正确写法
wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", date);
4.2 前端性能优化实践
- 路由懒加载大幅提升首屏速度:
typescript复制const routes = [
{
path: '/detail/:id',
component: () => import('@/views/Detail.vue') // 动态导入
}
]
- 使用keep-alive缓存列表页状态:
vue复制<router-view v-slot="{ Component }">
<keep-alive include="PlantList">
<component :is="Component" />
</keep-alive>
</router-view>
5. 典型问题排查实录
5.1 MyBatis动态SQL字段冲突
当实体类字段名是MySQL关键字时(如order、desc等),解决方案:
- 全局配置下划线转驼峰:
yaml复制mybatis-plus:
configuration:
map-underscore-to-camel-case: true
- 字段级别使用反引号转义:
java复制@TableField("`order`")
private Integer order;
5.2 Vue3详情页返回列表状态保持
实现步骤:
- 使用Pinia存储列表查询参数
typescript复制// stores/list.ts
export const useListStore = defineStore('list', {
state: () => ({
queryParams: {
page: 1,
size: 10,
plantType: ''
}
})
})
- 路由跳转时保存滚动位置
typescript复制// 列表页组件
onBeforeRouteLeave((to, from, next) => {
const listStore = useListStore()
listStore.scrollTop = document.documentElement.scrollTop
next()
})
// 返回时恢复
onMounted(() => {
const listStore = useListStore()
nextTick(() => {
window.scrollTo(0, listStore.scrollTop)
})
})
6. 部署与监控方案
6.1 生产环境部署
推荐使用Docker Compose编排:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./frontend
ports:
- "80:80"
6.2 监控指标采集
SpringBoot Actuator配置示例:
yaml复制management:
endpoints:
web:
exposure:
include: "*"
metrics:
tags:
application: plant-health-system
前端监控使用Sentry:
typescript复制import * as Sentry from "@sentry/vue"
Sentry.init({
dsn: "your-dsn",
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router)
})
],
tracesSampleRate: 0.2
})
在项目上线后,我们发现两个关键性能指标:
- 列表页接口平均响应时间从1200ms优化到300ms(通过添加复合索引)
- 前端Bundle大小从8MB减少到2MB(使用代码分割+CDN加载Element Plus)
