基于Spring Boot + Vue 3的社区养老系统设计与实现
源码获取:https://mbd.pub/o/bread/YZWXl5puZQ==
🎯 前言
随着我国人口老龄化进程的加速,智慧养老已经成为社会发展的重要课题。传统的养老模式面临着资源分配不均、服务效率低下、信息不对称等诸多挑战。为了解决这些问题,我们设计并开发了一套基于现代Web技术的社区养老系统,旨在通过技术手段提升养老服务的质量和效率。
本文将详细介绍该系统的技术架构、功能模块、实现细节以及部署方案,为相关领域的开发者提供参考和借鉴。
📋 目录
1 项目背景与需求分析
1.1 老龄化社会挑战
根据国家统计局数据,截至2023年底,我国60岁及以上人口达到2.8亿,占总人口的19.8%,其中65岁及以上人口2.1亿,占总人口的14.9%。人口老龄化呈现出规模大、速度快、差异显著等特点,对养老服务提出了更高的要求。
1.2 传统养老模式痛点
- 信息不对称:老年人难以获取准确的养老机构信息
- 服务效率低:人工管理方式效率低下,易出错
- 资源分配不均:优质养老资源集中在特定区域
- 监管难度大:政府部门难以有效监管养老服务质量
1.3 系统需求分析
基于以上痛点,我们确定了系统的主要需求:
功能性需求
- 养老机构信息展示与管理
- 社区活动发布与报名
- 用户权限分级管理
- 数据统计与可视化
- 文件上传与媒体管理
非功能性需求
- 高并发处理能力
- 数据安全与隐私保护
- 响应式界面设计
- 系统可扩展性
- 运维监控能力
2 技术选型与架构设计
2.1 技术栈选型
后端技术栈
<!-- Spring Boot 3.1.4 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
</parent>
<!-- 数据持久层 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- API文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
前端技术栈
{
"dependencies": {
"vue": "^3.4.21",
"element-plus": "^2.7.3",
"pinia": "^2.1.7",
"axios": "^1.5.1",
"echarts": "^5.4.3"
},
"devDependencies": {
"vite": "^5.3.1",
"typescript": "~5.4.0"
}
}
2.2 系统架构设计
整体架构
客户端层(Web/移动端)
↑
网关层(Nginx反向代理)
↑
应用层(Spring Boot微服务)
↑
数据访问层(MyBatis Plus)
↑
数据存储层(MySQL + Redis)
微服务架构优势
- 服务解耦:各功能模块独立开发部署
- 技术异构:不同服务可采用不同技术栈
- 弹性伸缩:根据业务压力动态扩缩容
- 故障隔离:单个服务故障不影响整体系统
2.3 前后端分离架构
采用前后端分离架构,带来以下好处:
- 职责清晰:前端专注UI交互,后端专注业务逻辑
- 并行开发:前后端可同时开发,提高效率
- 技术栈灵活:前后端可独立选择合适的技术
- 性能优化:静态资源CDN加速,API接口缓存
3 数据库设计与优化
3.1 核心表结构设计
用户表 (eld_user)
CREATE TABLE `eld_user` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
`role` int DEFAULT '0' COMMENT '角色(0用户 1管理员)',
`status` int DEFAULT '1' COMMENT '状态(1正常 0禁用)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_phone` (`phone`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
养老院表 (eld_elder)
CREATE TABLE `eld_elder` (
`elder_id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`elder_name` varchar(32) DEFAULT NULL COMMENT '名称',
`elder_content` text COMMENT '内容',
`elder_tag` varchar(32) DEFAULT NULL COMMENT '标签',
`elder_price` varchar(16) DEFAULT NULL COMMENT '价格',
`elder_address` varchar(32) DEFAULT NULL COMMENT '地址',
`elder_date` varchar(32) DEFAULT NULL COMMENT '成立日期',
`elder_cover` varchar(256) DEFAULT NULL COMMENT '封面',
`elder_bed` int DEFAULT NULL COMMENT '房间数',
`elder_area` varchar(32) DEFAULT NULL COMMENT '面积',
`elder_phone` varchar(32) DEFAULT NULL COMMENT '电话',
`elder_jd` varchar(32) DEFAULT NULL COMMENT '经度',
`elder_wd` varchar(32) DEFAULT NULL COMMENT '纬度',
`status` int DEFAULT '1' COMMENT '状态',
PRIMARY KEY (`elder_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 数据库优化策略
索引优化
-- 复合索引示例
CREATE INDEX idx_elder_search ON eld_elder(elder_name, elder_address, status);
-- 覆盖索引优化
CREATE INDEX idx_user_profile ON eld_user(username, phone, email);
查询优化
- **避免SELECT ***:只查询需要的字段
- 合理使用JOIN:避免过多的表连接
- 分页优化:使用游标分页代替LIMIT OFFSET
- 批量操作:使用批量插入和更新
事务管理
@Transactional(rollbackFor = Exception.class)
public void createElder(Elder elder) {
// 业务逻辑
elderMapper.insert(elder);
// 记录操作日志
logService.addLog("创建养老院");
}
4 后端实现细节
4.1 Spring Boot配置
主配置文件
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_elder
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
4.2 业务逻辑实现
用户服务层
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
@Override
public UserDTO login(LoginVO loginVO) {
User user = userMapper.selectByUsername(loginVO.getUsername());
if (user == null || !passwordEncoder.matches(loginVO.getPassword(), user.getPassword())) {
throw new BusinessException("用户名或密码错误");
}
if (user.getStatus() == 0) {
throw new BusinessException("账号已被禁用");
}
return convertToDTO(user);
}
private UserDTO convertToDTO(User user) {
UserDTO dto = new UserDTO();
BeanUtils.copyProperties(user, dto);
return dto;
}
}
活动管理服务
@Service
@Transactional
public class ActiveService {
@Autowired
private ActiveMapper activeMapper;
@Autowired
private ActiveShMapper activeShMapper;
public Page<ActiveVO> getActivePage(ActiveQuery query) {
Page<Active> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<Active> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(query.getActiveName())) {
wrapper.like(Active::getActiveName, query.getActiveName());
}
if (query.getStatus() != null) {
wrapper.eq(Active::getStatus, query.getStatus());
}
wrapper.orderByDesc(Active::getActiveId);
Page<Active> activePage = activeMapper.selectPage(page, wrapper);
return activePage.convert(this::convertToVO);
}
}
4.3 API接口设计
RESTful接口规范
@RestController
@RequestMapping("/api/active")
@Api(tags = "活动管理")
public class ActiveController {
@Autowired
private ActiveService activeService;
@GetMapping("/page")
@ApiOperation("分页查询活动")
public R<Page<ActiveVO>> getActivePage(ActiveQuery query) {
return R.success(activeService.getActivePage(query));
}
@PostMapping
@ApiOperation("创建活动")
public R<Void> createActive(@RequestBody @Valid Active active) {
activeService.createActive(active);
return R.success();
}
@PutMapping("/{id}")
@ApiOperation("更新活动")
public R<Void> updateActive(@PathVariable Long id, @RequestBody Active active) {
active.setActiveId(id);
activeService.updateActive(active);
return R.success();
}
}
统一响应格式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class R<T> implements Serializable {
private Integer code;
private String message;
private T data;
public static <T> R<T> success() {
return new R<>(200, "成功", null);
}
public static <T> R<T> success(T data) {
return new R<>(200, "成功", data);
}
public static <T> R<T> error(String message) {
return new R<>(500, message, null);
}
}
4.4 安全认证实现
JWT认证配置
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWT工具类
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire}")
private Long expire;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
claims.put("role", userDetails.getAuthorities());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expire))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}
5 前端开发实践
5.1 Vue 3组合式API
组件开发示例
<template>
<div class="active-list">
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="activeName" label="活动名称" />
<el-table-column prop="activeStart" label="开始时间" />
<el-table-column prop="activeEnd" label="结束时间" />
<el-table-column label="操作">
<template #default="scope">
<el-button @click="handleView(scope.row)">查看</el-button>
<el-button @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getActivePage } from '@/api/active'
interface Active {
activeId: number
activeName: string
activeStart: string
activeEnd: string
}
const tableData = ref<Active[]>([])
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const loadData = async () => {
try {
const res = await getActivePage({
pageNum: currentPage.value,
pageSize: pageSize.value
})
tableData.value = res.data.records
total.value = res.data.total
} catch (error) {
console.error('加载数据失败:', error)
}
}
const handleView = (row: Active) => {
// 查看详情逻辑
}
const handleEdit = (row: Active) => {
// 编辑逻辑
}
const handlePageChange = (page: number) => {
currentPage.value = page
loadData()
}
onMounted(() => {
loadData()
})
</script>
5.2 状态管理
Pinia状态管理
import { defineStore } from 'pinia'
import { User } from '@/types/user'
export const useUserStore = defineStore('user', {
state: () => ({
user: null as User | null,
token: localStorage.getItem('token') || '',
permissions: [] as string[]
}),
getters: {
isLoggedIn: (state) => !!state.token,
userInfo: (state) => state.user
},
actions: {
setToken(token: string) {
this.token = token
localStorage.setItem('token', token)
},
setUser(user: User) {
this.user = user
},
async login(credentials: LoginData) {
try {
const res = await authApi.login(credentials)
this.setToken(res.data.token)
await this.getUserInfo()
} catch (error) {
throw error
}
},
async getUserInfo() {
try {
const res = await authApi.getUserInfo()
this.setUser(res.data)
} catch (error) {
this.logout()
throw error
}
},
logout() {
this.token = ''
this.user = null
localStorage.removeItem('token')
}
}
})
5.3 路由配置
路由守卫
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
const routes = [
{
path: '/',
component: () => import('@/layouts/MainLayout.vue'),
children: [
{
path: '',
name: 'Home',
component: () => import('@/views/HomeView.vue'),
meta: { requiresAuth: true }
},
{
path: 'active',
name: 'Active',
component: () => import('@/views/ActiveView.vue'),
meta: { requiresAuth: true }
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/LoginView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.isLoggedIn) {
next('/login')
} else {
next()
}
})
export default router
5.4 数据可视化
ECharts集成
<template>
<div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref<HTMLElement>()
let chart: echarts.ECharts | null = null
const initChart = () => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value)
const option = {
title: {
text: '用户活跃度统计',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}]
}
chart.setOption(option)
}
const resizeChart = () => {
chart?.resize()
}
onMounted(() => {
initChart()
window.addEventListener('resize', resizeChart)
})
onUnmounted(() => {
chart?.dispose()
window.removeEventListener('resize', resizeChart)
})
</script>
6 系统安全与性能优化
6.1 安全防护措施
SQL注入防护
// MyBatis Plus条件构造器
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username)
.eq(User::getStatus, 1);
// 使用参数化查询
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(@Param("username") String username);
XSS攻击防护
// 全局XSS过滤器
@Component
@WebFilter(urlPatterns = "/*")
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
XssHttpServletRequestWrapper wrappedRequest =
new XssHttpServletRequestWrapper(httpRequest);
chain.doFilter(wrappedRequest, response);
}
}
6.2 性能优化策略
数据库查询优化
// 使用MyBatis Plus分页插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
Redis缓存优化
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用Jackson序列化
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}
}
业务层缓存
@Service
@CacheConfig(cacheNames = "user")
public class UserService {
@Cacheable(key = "#id")
public User getById(Long id) {
return userMapper.selectById(id);
}
@CacheEvict(key = "#user.userId")
public void updateUser(User user) {
userMapper.updateById(user);
}
}
6.3 并发处理
线程池配置
# 异步线程池配置
task:
pool:
core-size: 8
max-size: 20
queue-capacity: 1000
keep-alive: 60s
异步处理
@Async("taskExecutor")
public void asyncProcessData(List<Data> dataList) {
// 异步处理耗时操作
dataList.forEach(this::processSingleData);
}
@EventListener
public void handleUserRegisterEvent(UserRegisterEvent event) {
// 异步处理用户注册事件
sendWelcomeEmail(event.getUser());
initUserData(event.getUser());
}
7 部署与运维方案
7.1 Docker容器化部署
Docker Compose配置
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: db_elder
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- elder-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- elder-network
backend:
build: ./elder-api
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/db_elder
- SPRING_REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- elder-network
frontend:
build: ./elder-front
ports:
- "80:80"
depends_on:
- backend
networks:
- elder-network
volumes:
mysql_data:
redis_data:
networks:
elder-network:
driver: bridge
7.2 监控与告警
Spring Boot Actuator
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
Prometheus监控
# Prometheus配置
scrape_configs:
- job_name: 'elder-backend'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['backend:8080']
7.3 日志管理
ELK日志收集
# Logstash配置
input {
tcp {
port => 5000
codec => json
}
}
filter {
# 日志处理规则
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "elder-logs-%{+YYYY.MM.dd}"
}
}
8 总结与展望
8.1 项目成果总结
通过本项目的开发实践,我们成功构建了一套完整的社区养老系统,取得了以下成果:
- 技术架构先进:采用Spring Boot + Vue 3前后端分离架构,具备良好的扩展性和维护性
- 功能模块完善:实现了用户管理、养老院管理、活动管理、数据统计等核心功能
- 性能表现优异:通过缓存优化、数据库索引、异步处理等手段提升系统性能
- 安全防护全面:集成JWT认证、XSS防护、SQL注入防护等多层安全机制
- 用户体验良好:响应式设计支持多端访问,界面美观操作便捷
8.2 技术挑战与解决方案
在开发过程中,我们遇到了以下技术挑战并提供了相应的解决方案:
挑战一:高并发访问
问题:系统需要支持大量用户同时访问,特别是在活动报名高峰期
解决方案:
- 使用Redis缓存热点数据,减少数据库压力
- 采用消息队列异步处理耗时操作
- 数据库读写分离,主从复制架构
- Nginx负载均衡,多实例部署
挑战二:数据一致性
问题:分布式环境下数据一致性问题
解决方案:
- 使用Spring事务管理保证本地事务
- 分布式事务采用最终一致性方案
- 重要操作添加重试机制和补偿逻辑
挑战三:移动端适配
问题:需要支持各种移动设备访问
解决方案:
- 采用Element Plus响应式组件
- CSS媒体查询适配不同屏幕尺寸
- 移动端专属交互优化
🎯 结语
社区养老系统的开发是一个复杂而有意义的工程,它不仅是技术的体现,更是对社会责任的担当。通过本项目的实践,我们深刻认识到技术在解决社会问题中的重要作用。
未来,我们将继续完善系统功能,提升技术水平,为智慧养老事业贡献更多力量。同时也希望更多的开发者能够关注养老领域,用技术为老年人创造更好的生活体验。