1. 项目背景与核心需求
城市垃圾分类宣传网站是一个典型的"互联网+环保"应用场景。随着国内垃圾分类政策的全面推进,各地政府都在积极探索数字化宣传手段。这个项目需要解决三个核心问题:
- 信息传递效率:传统线下宣传单页、社区讲座等方式覆盖面有限,难以形成持续影响力
- 互动体验缺失:静态宣传材料无法让居民实际体验分类过程
- 数据统计困难:管理部门缺乏直观的数据看板来评估宣传效果
采用SpringBoot+Vue的技术组合,可以实现:
- 后端快速构建RESTful API(SpringBoot优势)
- 前端实现丰富的交互效果(Vue优势)
- 前后端完全分离的现代化架构
提示:选择这个技术栈时,建议考虑团队技术储备。如果成员更熟悉React或Angular,也可以相应调整前端框架。
2. 技术架构设计
2.1 系统分层架构
code复制┌───────────────────────────────────────┐
│ 前端层 │
│ (Vue + Element UI + ECharts) │
└───────────────┬───────────────────────┘
│ HTTP/HTTPS
┌───────────────▼───────────────────────┐
│ 网关层 │
│ (Nginx反向代理 + 负载均衡) │
└───────────────┬───────────────────────┘
│ RESTful API
┌───────────────▼───────────────────────┐
│ 应用服务层 │
│ (SpringBoot + MyBatis Plus + Redis) │
└───────────────┬───────────────────────┘
│ JDBC
┌───────────────▼───────────────────────┐
│ 数据层 │
│ (MySQL + MongoDB) │
└───────────────────────────────────────┘
2.2 关键技术选型理由
后端技术栈:
- SpringBoot 2.7.x:快速搭建RESTful服务
- MyBatis Plus 3.5.x:简化数据库操作
- Redis 6.x:缓存热点数据(如分类知识)
- MongoDB 5.x:存储用户行为日志
前端技术栈:
- Vue 3.x:组合式API开发模式
- Element Plus:UI组件库
- ECharts 5.x:数据可视化
- Axios:HTTP请求处理
注意:MongoDB在这里主要用于存储非结构化日志数据,如果项目规模较小,可以先用MySQL统一存储。
3. 核心功能模块实现
3.1 垃圾分类知识库模块
后端实现要点:
java复制// 实体类设计
@Data
@TableName("garbage_category")
public class GarbageCategory {
@TableId(type = IdType.AUTO)
private Long id;
private String name; // 垃圾名称
private Integer type; // 1可回收 2有害 3厨余 4其他
private String description; // 处理建议
private String imageUrl; // 示例图片
}
// 分页查询接口
@GetMapping("/categories")
public R<Page<GarbageCategory>> listCategories(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
LambdaQueryWrapper<GarbageCategory> wrapper = Wrappers.lambdaQuery();
if (StringUtils.isNotBlank(keyword)) {
wrapper.like(GarbageCategory::getName, keyword);
}
return R.success(categoryService.page(new Page<>(pageNum, pageSize), wrapper));
}
前端实现技巧:
- 使用Virtual Scroll优化长列表渲染性能
- 实现本地缓存减少API调用
- 添加模糊搜索功能
3.2 分类小游戏模块
核心交互流程:
- 系统随机生成垃圾物品
- 用户拖拽到对应垃圾桶
- 实时反馈分类结果
- 累计积分奖励
Vue关键代码:
vue复制<template>
<div class="game-container">
<div
v-for="item in garbageItems"
:key="item.id"
draggable="true"
@dragstart="dragStart(item)"
class="garbage-item">
<img :src="item.image" :alt="item.name">
</div>
<div
v-for="bin in bins"
:key="bin.type"
@drop="dropHandler(bin.type)"
@dragover.prevent
class="bin">
{{ bin.label }}
</div>
</div>
</template>
<script setup>
// 拖拽逻辑实现
const dragStart = (item) => {
currentItem.value = item;
};
const dropHandler = (binType) => {
if (currentItem.value.type === binType) {
score.value += 10;
showFeedback('分类正确!');
} else {
showFeedback('分类错误,正确答案是:' + getTypeName(currentItem.value.type));
}
nextItem();
};
</script>
4. 数据可视化实现
4.1 用户行为分析看板
使用ECharts实现的管理员视图:
javascript复制// 用户访问热力图
const initHeatMap = () => {
const chart = echarts.init(document.getElementById('heatmap'));
const option = {
tooltip: {},
visualMap: {
min: 0,
max: 1000,
calculable: true,
inRange: {
color: ['#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695']
}
},
calendar: {
range: '2023',
cellSize: ['auto', 20]
},
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data: getHeatMapData()
}
};
chart.setOption(option);
};
4.2 垃圾分类统计报表
SpringBoot后端数据处理:
java复制@GetMapping("/stats/category")
public R<CategoryStatsVO> getCategoryStats(
@RequestParam(required = false) String region,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
// 构建查询条件
QueryWrapper<GarbageRecord> wrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(region)) {
wrapper.eq("region_code", region);
}
if (startDate != null && endDate != null) {
wrapper.between("create_time", startDate, endDate);
}
// 按分类类型分组统计
List<Map<String, Object>> stats = recordMapper.selectMaps(
wrapper.select("category_type", "COUNT(*) as count")
.groupBy("category_type")
);
// 转换为VO对象
CategoryStatsVO vo = new CategoryStatsVO();
stats.forEach(item -> {
Integer type = (Integer) item.get("category_type");
Long count = (Long) item.get("count");
switch (type) {
case 1: vo.setRecyclable(count); break;
case 2: vo.setHazardous(count); break;
case 3: vo.setKitchen(count); break;
case 4: vo.setOther(count); break;
}
});
return R.success(vo);
}
5. 项目部署与优化
5.1 前端部署方案
推荐使用Docker容器化部署:
dockerfile复制# 前端Dockerfile
FROM node:16 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
配套的Nginx配置优化:
nginx复制server {
listen 80;
server_name localhost;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
}
}
5.2 后端性能优化
- 缓存策略:
java复制@Cacheable(value = "garbageCategory", key = "#id")
public GarbageCategory getById(Long id) {
return getById(id);
}
@CacheEvict(value = "garbageCategory", key = "#entity.id")
public boolean updateById(GarbageCategory entity) {
return updateById(entity);
}
- 异步日志处理:
java复制@Async
@EventListener
public void handleUserBehaviorEvent(UserBehaviorEvent event) {
// 写入MongoDB
mongoTemplate.insert(event);
}
- 接口限流配置:
java复制@Configuration
public class RateLimitConfig {
@Bean
public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
FilterRegistrationBean<RateLimitFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RateLimitFilter());
registration.addUrlPatterns("/api/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
6. 项目扩展方向
6.1 微信小程序集成
通过uni-app实现多端兼容:
javascript复制// 小程序端适配代码
export default {
onShareAppMessage() {
return {
title: '垃圾分类知识科普',
path: '/pages/index/index'
}
},
methods: {
scanGarbage() {
uni.scanCode({
success: (res) => {
this.searchKeyword = res.result;
this.search();
}
});
}
}
}
6.2 AI图像识别扩展
集成TensorFlow.js实现浏览器端识别:
javascript复制async function loadModel() {
model = await tf.loadGraphModel('/models/garbage/model.json');
}
async function classify(imageElement) {
const tensor = tf.browser.fromPixels(imageElement)
.resizeNearestNeighbor([224, 224])
.toFloat()
.expandDims();
const predictions = await model.predict(tensor).data();
const top5 = Array.from(predictions)
.map((p, i) => ({ probability: p, className: CLASSES[i] }))
.sort((a, b) => b.probability - a.probability)
.slice(0, 5);
return top5;
}
6.3 社区积分系统设计
java复制// 积分服务实现
@Service
public class PointServiceImpl implements PointService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
@Transactional
public void addPoints(Long userId, int points, String source) {
// 更新总积分
redisTemplate.opsForValue().increment(
"user:points:" + userId,
points
);
// 记录积分明细
PointRecord record = new PointRecord();
record.setUserId(userId);
record.setPoints(points);
record.setSource(source);
record.setCreateTime(LocalDateTime.now());
pointRecordMapper.insert(record);
}
}
在实际开发中,我们遇到的最棘手问题是移动端拖拽交互的性能问题。通过以下优化显著提升了体验:
- 使用CSS transform代替top/left定位
- 对静态元素开启GPU加速
- 实现节流的事件处理
- 使用Vue的keep-alive缓存游戏组件状态
