Java全栈项目-校园物业服务平台

news/2025/1/10 10:39:53 标签: java, 开发语言

项目简介

本项目是一个基于Spring Boot + Vue.js的校园物业服务管理平台,旨在提供便捷的校园物业服务预约和管理功能。

技术栈

后端

  • Spring Boot 2.7.x
  • Spring Security
  • MyBatis-Plus
  • MySQL 8.0
  • Redis
  • JWT

前端

  • Vue.js 3
  • Element Plus
  • Axios
  • Vite
  • Pinia

核心功能

  1. 用户管理

    • 学生/教职工注册登录
    • 角色权限控制
    • 个人信息管理
  2. 报修服务

    • 在线报修申请
    • 维修进度追踪
    • 维修评价反馈
  3. 物业公告

    • 通知发布
    • 公告管理
    • 消息推送
  4. 设施预约

    • 会议室预约
    • 体育场地预约
    • 预约记录查询

项目亮点

  1. 前后端分离架构
java:src/main/java/com/example/config/CorsConfig.java">@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        return new CorsFilter(source);
    }
}
  1. JWT认证
java:src/main/java/com/example/util/JwtUtil.java">@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;
    
    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
}
  1. Redis缓存
java:src/main/java/com/example/config/RedisConfig.java">@Configuration
@EnableCaching
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

项目部署

  • 采用Docker容器化部署
  • Nginx反向代理
  • Jenkins自动化部署

项目成果

  1. 显著提升校园物业服务效率
  2. 优化资源调配
  3. 提高用户满意度
  4. 实现数据可视化管理

技术难点解决

  1. 使用Redis解决高并发场景
  2. 采用分布式锁处理资源预约冲突
  3. 实现WebSocket推送实时消息
  4. 优化大数据量查询性能

总结与展望

本项目实现了校园物业服务的信息化、智能化管理,未来计划:

  1. 引入AI智能客服
  2. 开发移动端应用
  3. 接入物联网设备
  4. 扩展更多便民服务功能

校园物业服务平台功能详解

一、用户管理模块

1. 用户注册与登录

数据库设计
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `real_name` varchar(50) COMMENT '真实姓名',
  `user_type` tinyint NOT NULL COMMENT '用户类型:1学生 2教职工 3维修人员 4管理员',
  `student_id` varchar(20) COMMENT '学号/工号',
  `phone` varchar(11) COMMENT '手机号',
  `email` varchar(50) COMMENT '邮箱',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注册接口
java:src/main/java/com/example/controller/UserController.java">@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @PostMapping("/register")
    public Result register(@RequestBody UserRegisterDTO dto) {
        // 参数校验
        if (!validateUserInput(dto)) {
            return Result.error("参数不合法");
        }
        
        // 密码加密
        String encodedPassword = passwordEncoder.encode(dto.getPassword());
        
        // 保存用户信息
        User user = new User();
        BeanUtils.copyProperties(dto, user);
        user.setPassword(encodedPassword);
        
        userService.save(user);
        return Result.success();
    }
}
登录实现
java:src/main/java/com/example/service/impl/UserServiceImpl.java">@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Override
    public LoginVO login(LoginDTO loginDTO) {
        // 验证用户名密码
        User user = userMapper.selectByUsername(loginDTO.getUsername());
        if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
            throw new BusinessException("用户名或密码错误");
        }
        
        // 生成token
        String token = jwtUtil.generateToken(user.getUsername());
        
        // 存入Redis
        redisTemplate.opsForValue().set(
            "token:" + token,
            user,
            24,
            TimeUnit.HOURS
        );
        
        return new LoginVO(token, user);
    }
}

2. 角色权限控制

角色表设计
CREATE TABLE `role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_code` varchar(50) NOT NULL COMMENT '角色编码',
  PRIMARY KEY (`id`)
);

CREATE TABLE `user_role` (
  `user_id` bigint NOT NULL,
  `role_id` bigint NOT NULL,
  PRIMARY KEY (`user_id`, `role_id`)
);
权限拦截器
java:src/main/java/com/example/interceptor/AuthInterceptor.java">@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取token
        String token = request.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            throw new UnauthorizedException("未登录");
        }
        
        // 验证token
        String username = jwtUtil.validateToken(token);
        
        // 获取用户权限
        User user = (User) redisTemplate.opsForValue().get("token:" + token);
        List<String> permissions = roleService.getUserPermissions(user.getId());
        
        // 判断是否有权限
        RequiresPermissions annotation = ((HandlerMethod) handler).getMethodAnnotation(RequiresPermissions.class);
        if (annotation != null && !permissions.contains(annotation.value())) {
            throw new ForbiddenException("无权限访问");
        }
        
        return true;
    }
}

二、报修服务模块

1. 报修申请

报修表设计
CREATE TABLE `repair_order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '申请人ID',
  `repair_type` tinyint NOT NULL COMMENT '报修类型:1水电 2家具 3空调 4其他',
  `location` varchar(100) NOT NULL COMMENT '报修地点',
  `description` varchar(500) NOT NULL COMMENT '问题描述',
  `images` varchar(500) COMMENT '图片地址,多个逗号分隔',
  `status` tinyint NOT NULL COMMENT '状态:0待处理 1已派单 2维修中 3已完成 4已评价',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);
报修提交接口
java:src/main/java/com/example/controller/RepairController.java">@RestController
@RequestMapping("/api/repair")
public class RepairController {
    
    @PostMapping("/submit")
    public Result submit(@RequestBody RepairDTO dto) {
        // 上传图片
        List<String> imageUrls = uploadImages(dto.getImageFiles());
        
        // 创建工单
        RepairOrder order = new RepairOrder();
        BeanUtils.copyProperties(dto, order);
        order.setImages(String.join(",", imageUrls));
        order.setStatus(0);
        
        repairService.save(order);
        
        // 发送通知给管理员
        notifyAdmin(order);
        
        return Result.success();
    }
}

2. 维修进度追踪

进度更新接口
java:src/main/java/com/example/service/impl/RepairServiceImpl.java">@Service
public class RepairServiceImpl implements RepairService {
    
    @Override
    @Transactional
    public void updateProgress(RepairProgressDTO dto) {
        // 更新工单状态
        RepairOrder order = getById(dto.getOrderId());
        order.setStatus(dto.getStatus());
        updateById(order);
        
        // 记录处理日志
        RepairLog log = new RepairLog();
        log.setOrderId(dto.getOrderId());
        log.setOperator(dto.getOperator());
        log.setContent(dto.getContent());
        repairLogService.save(log);
        
        // 发送进度通知
        if (dto.getStatus() == 2) {
            // 维修中状态,通知用户预计到达时间
            notifyUser(order.getUserId(), "维修人员正在前往处理,预计" + dto.getEstimatedTime() + "到达");
        } else if (dto.getStatus() == 3) {
            // 完成状态,通知用户评价
            notifyUser(order.getUserId(), "维修已完成,请及时评价");
        }
    }
}

3. 维修评价

评价表设计
CREATE TABLE `repair_rating` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_id` bigint NOT NULL COMMENT '工单ID',
  `user_id` bigint NOT NULL COMMENT '评价人ID',
  `rating` tinyint NOT NULL COMMENT '评分:1-5',
  `comment` varchar(500) COMMENT '评价内容',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_id` (`order_id`)
);
评价提交接口
java:src/main/java/com/example/controller/RepairController.java">@RestController
@RequestMapping("/api/repair")
public class RepairController {
    
    @PostMapping("/rate")
    public Result rate(@RequestBody RatingDTO dto) {
        // 检查工单状态
        RepairOrder order = repairService.getById(dto.getOrderId());
        if (order.getStatus() != 3) {
            return Result.error("工单状态不正确");
        }
        
        // 保存评价
        RepairRating rating = new RepairRating();
        BeanUtils.copyProperties(dto, rating);
        ratingService.save(rating);
        
        // 更新工单状态为已评价
        order.setStatus(4);
        repairService.updateById(order);
        
        // 统计维修人员评分
        updateRepairmanRating(order.getRepairmanId());
        
        return Result.success();
    }
}

校园物业服务平台功能详解(续)

三、物业公告模块

1. 通知公告表设计

CREATE TABLE `notice` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `title` varchar(100) NOT NULL COMMENT '标题',
  `content` text NOT NULL COMMENT '内容',
  `type` tinyint NOT NULL COMMENT '类型:1通知 2公告 3新闻',
  `status` tinyint NOT NULL COMMENT '状态:0草稿 1已发布 2已下架',
  `publisher_id` bigint NOT NULL COMMENT '发布人ID',
  `publish_time` datetime COMMENT '发布时间',
  `top` tinyint DEFAULT 0 COMMENT '是否置顶:0否 1是',
  `view_count` int DEFAULT 0 COMMENT '浏览次数',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 公告管理接口

java:src/main/java/com/example/controller/NoticeController.java">@RestController
@RequestMapping("/api/notice")
@RequiresPermissions("notice")
public class NoticeController {
    
    @PostMapping("/publish")
    public Result publish(@RequestBody NoticeDTO dto) {
        Notice notice = new Notice();
        BeanUtils.copyProperties(dto, notice);
        notice.setStatus(1);
        notice.setPublishTime(new Date());
        notice.setPublisherId(getCurrentUserId());
        
        noticeService.save(notice);
        
        // 发送消息推送
        if (dto.isNeedPush()) {
            pushNoticeToUsers(notice);
        }
        
        return Result.success();
    }
    
    @PostMapping("/top/{id}")
    public Result setTop(@PathVariable Long id, @RequestParam Boolean top) {
        noticeService.update()
            .set("top", top)
            .eq("id", id)
            .update();
        return Result.success();
    }
}

3. WebSocket消息推送

java:src/main/java/com/example/websocket/WebSocketServer.java">@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
    
    private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
    
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        sessionMap.put(userId, session);
    }
    
    @OnClose
    public void onClose(@PathParam("userId") String userId) {
        sessionMap.remove(userId);
    }
    
    public void pushMessage(String userId, String message) {
        Session session = sessionMap.get(userId);
        if (session != null) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("消息推送失败", e);
            }
        }
    }
}

四、设施预约模块

1. 设施和预约表设计

-- 设施表
CREATE TABLE `facility` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '名称',
  `type` tinyint NOT NULL COMMENT '类型:1会议室 2体育场地',
  `location` varchar(100) COMMENT '位置',
  `capacity` int COMMENT '容量',
  `description` varchar(500) COMMENT '描述',
  `status` tinyint NOT NULL COMMENT '状态:0停用 1启用',
  PRIMARY KEY (`id`)
);

-- 预约表
CREATE TABLE `reservation` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `facility_id` bigint NOT NULL COMMENT '设施ID',
  `user_id` bigint NOT NULL COMMENT '预约人ID',
  `date` date NOT NULL COMMENT '预约日期',
  `start_time` time NOT NULL COMMENT '开始时间',
  `end_time` time NOT NULL COMMENT '结束时间',
  `purpose` varchar(200) COMMENT '用途',
  `status` tinyint NOT NULL COMMENT '状态:0待审核 1已通过 2已拒绝 3已取消',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_facility_date` (`facility_id`, `date`)
);

2. 预约服务实现

java:src/main/java/com/example/service/impl/ReservationServiceImpl.java">@Service
public class ReservationServiceImpl implements ReservationService {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Override
    @Transactional
    public Result reserve(ReservationDTO dto) {
        // 检查设施是否可用
        Facility facility = facilityService.getById(dto.getFacilityId());
        if (facility.getStatus() != 1) {
            return Result.error("设施不可用");
        }
        
        // 使用分布式锁防止并发预约
        String lockKey = "reservation:lock:" + dto.getFacilityId() + ":" + dto.getDate();
        boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (!locked) {
            return Result.error("操作太频繁,请稍后重试");
        }
        
        try {
            // 检查时间段是否已被预约
            boolean timeConflict = checkTimeConflict(dto);
            if (timeConflict) {
                return Result.error("该时间段已被预约");
            }
            
            // 创建预约记录
            Reservation reservation = new Reservation();
            BeanUtils.copyProperties(dto, reservation);
            reservation.setUserId(getCurrentUserId());
            reservation.setStatus(0);
            
            save(reservation);
            
            // 发送审核通知
            notifyAdmin(reservation);
            
            return Result.success();
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
    private boolean checkTimeConflict(ReservationDTO dto) {
        return baseMapper.selectCount(new QueryWrapper<Reservation>()
            .eq("facility_id", dto.getFacilityId())
            .eq("date", dto.getDate())
            .eq("status", 1)
            .and(w -> w
                .between("start_time", dto.getStartTime(), dto.getEndTime())
                .or()
                .between("end_time", dto.getStartTime(), dto.getEndTime())
            )) > 0;
    }
}

3. 预约记录查询

java:src/main/java/com/example/controller/ReservationController.java">@RestController
@RequestMapping("/api/reservation")
public class ReservationController {
    
    @GetMapping("/list")
    public Result list(ReservationQueryDTO query) {
        Page<Reservation> page = new Page<>(query.getPage(), query.getSize());
        
        // 构建查询条件
        QueryWrapper<Reservation> wrapper = new QueryWrapper<Reservation>()
            .eq(query.getFacilityId() != null, "facility_id", query.getFacilityId())
            .eq(query.getStatus() != null, "status", query.getStatus())
            .ge(query.getStartDate() != null, "date", query.getStartDate())
            .le(query.getEndDate() != null, "date", query.getEndDate())
            .orderByDesc("create_time");
            
        // 如果不是管理员,只能查看自己的预约
        if (!isAdmin()) {
            wrapper.eq("user_id", getCurrentUserId());
        }
        
        // 分页查询
        Page<ReservationVO> result = reservationService.page(page, wrapper)
            .convert(reservation -> {
                ReservationVO vo = new ReservationVO();
                BeanUtils.copyProperties(reservation, vo);
                // 设置关联信息
                vo.setFacilityName(facilityService.getById(reservation.getFacilityId()).getName());
                vo.setUserName(userService.getById(reservation.getUserId()).getRealName());
                return vo;
            });
            
        return Result.success(result);
    }
    
    @PostMapping("/cancel/{id}")
    public Result cancel(@PathVariable Long id) {
        Reservation reservation = reservationService.getById(id);
        
        // 检查是否可以取消
        if (reservation.getStatus() != 0 && reservation.getStatus() != 1) {
            return Result.error("当前状态不可取消");
        }
        
        // 检查权限
        if (!isAdmin() && !reservation.getUserId().equals(getCurrentUserId())) {
            return Result.error("无权操作");
        }
        
        // 更新状态
        reservation.setStatus(3);
        reservationService.updateById(reservation);
        
        return Result.success();
    }
}

4. 预约统计分析

java:src/main/java/com/example/service/impl/ReservationStatServiceImpl.java">@Service
public class ReservationStatServiceImpl implements ReservationStatService {
    
    @Override
    public Map<String, Object> getStatistics(String startDate, String endDate) {
        Map<String, Object> result = new HashMap<>();
        
        // 按设施类型统计预约次数
        List<Map<String, Object>> typeStats = baseMapper.selectMaps(
            new QueryWrapper<Reservation>()
                .select("f.type", "count(*) as count")
                .leftJoin("facility f", "facility_id = f.id")
                .between("date", startDate, endDate)
                .eq("status", 1)
                .groupBy("f.type")
        );
        result.put("typeStats", typeStats);
        
        // 按时段统计使用率
        List<Map<String, Object>> timeStats = baseMapper.selectMaps(
            new QueryWrapper<Reservation>()
                .select("DATE_FORMAT(start_time, '%H:00') as time_slot", "count(*) as count")
                .between("date", startDate, endDate)
                .eq("status", 1)
                .groupBy("time_slot")
                .orderByAsc("time_slot")
        );
        result.put("timeStats", timeStats);
        
        return result;
    }
}

WebSocket客服对话功能实现

一、数据库设计

1. 会话和消息表

-- 会话表
CREATE TABLE `chat_session` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `customer_service_id` bigint COMMENT '客服ID',
  `status` tinyint NOT NULL COMMENT '状态:0等待中 1进行中 2已结束',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `end_time` datetime COMMENT '结束时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_cs_id` (`customer_service_id`)
);

-- 消息表
CREATE TABLE `chat_message` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `session_id` bigint NOT NULL COMMENT '会话ID',
  `sender_id` bigint NOT NULL COMMENT '发送者ID',
  `sender_type` tinyint NOT NULL COMMENT '发送者类型:1用户 2客服',
  `content` text NOT NULL COMMENT '消息内容',
  `msg_type` tinyint NOT NULL COMMENT '消息类型:1文本 2图片 3文件',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_session_id` (`session_id`)
);

二、WebSocket配置

1. WebSocket配置类

java:src/main/java/com/example/config/WebSocketConfig.java">@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Autowired
    private ChatWebSocketHandler chatHandler;
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatHandler, "/ws/chat")
                .setAllowedOrigins("*")
                .addInterceptors(new ChatHandshakeInterceptor());
    }
}

2. WebSocket拦截器

java:src/main/java/com/example/websocket/ChatHandshakeInterceptor.java">public class ChatHandshakeInterceptor implements HandshakeInterceptor {
    
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
            WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        // 获取token,验证用户身份
        String token = request.getHeaders().getFirst("Authorization");
        String userId = JwtUtil.getUserId(token);
        if (StringUtils.isEmpty(userId)) {
            return false;
        }
        
        // 将用户信息存入attributes
        attributes.put("userId", userId);
        return true;
    }
    
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
            WebSocketHandler wsHandler, Exception exception) {
    }
}

三、WebSocket处理器

1. 消息处理器

java:src/main/java/com/example/websocket/ChatWebSocketHandler.java">@Component
@Slf4j
public class ChatWebSocketHandler extends TextWebSocketHandler {
    
    @Autowired
    private ChatSessionService chatSessionService;
    
    @Autowired
    private ChatMessageService chatMessageService;
    
    // 保存所有客户端连接
    private static final Map<String, WebSocketSession> userSessions = new ConcurrentHashMap<>();
    private static final Map<String, WebSocketSession> customerServiceSessions = new ConcurrentHashMap<>();
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        String userId = getUserId(session);
        String userType = getUserType(session);
        
        if ("USER".equals(userType)) {
            userSessions.put(userId, session);
            // 为用户分配客服
            assignCustomerService(userId);
        } else if ("CUSTOMER_SERVICE".equals(userType)) {
            customerServiceSessions.put(userId, session);
        }
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        try {
            // 解析消息
            ChatMessageDTO messageDTO = JSON.parseObject(message.getPayload(), ChatMessageDTO.class);
            
            // 保存消息到数据库
            ChatMessage chatMessage = saveChatMessage(messageDTO);
            
            // 转发消息给接收方
            forwardMessage(chatMessage);
            
        } catch (Exception e) {
            log.error("处理消息失败", e);
        }
    }
    
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        String userId = getUserId(session);
        String userType = getUserType(session);
        
        if ("USER".equals(userType)) {
            userSessions.remove(userId);
            // 结束会话
            endChatSession(userId);
        } else if ("CUSTOMER_SERVICE".equals(userType)) {
            customerServiceSessions.remove(userId);
            // 重新分配该客服的会话
            reassignSessions(userId);
        }
    }
    
    private void assignCustomerService(String userId) {
        // 查找空闲的客服
        String csId = findAvailableCustomerService();
        if (csId != null) {
            // 创建会话
            ChatSession session = chatSessionService.createSession(userId, csId);
            
            // 通知双方会话建立
            notifySessionCreated(session);
        } else {
            // 没有可用客服,将用户加入等待队列
            addToWaitingQueue(userId);
        }
    }
    
    private void forwardMessage(ChatMessage message) throws IOException {
        String receiverId = message.getSenderType() == 1 ? 
                message.getSession().getCustomerServiceId().toString() :
                message.getSession().getUserId().toString();
                
        WebSocketSession receiverSession = message.getSenderType() == 1 ?
                customerServiceSessions.get(receiverId) :
                userSessions.get(receiverId);
                
        if (receiverSession != null && receiverSession.isOpen()) {
            receiverSession.sendMessage(new TextMessage(JSON.toJSONString(message)));
        }
    }
}

四、客服服务实现

1. 会话服务

java:src/main/java/com/example/service/impl/ChatSessionServiceImpl.java">@Service
public class ChatSessionServiceImpl implements ChatSessionService {
    
    @Override
    @Transactional
    public ChatSession createSession(String userId, String csId) {
        ChatSession session = new ChatSession();
        session.setUserId(Long.valueOf(userId));
        session.setCustomerServiceId(Long.valueOf(csId));
        session.setStatus(1);
        save(session);
        
        // 创建欢迎消息
        ChatMessage welcomeMsg = new ChatMessage();
        welcomeMsg.setSessionId(session.getId());
        welcomeMsg.setSenderId(Long.valueOf(csId));
        welcomeMsg.setSenderType(2);
        welcomeMsg.setContent("您好,很高兴为您服务!");
        welcomeMsg.setMsgType(1);
        chatMessageService.save(welcomeMsg);
        
        return session;
    }
    
    @Override
    public void endSession(Long sessionId) {
        update()
            .set("status", 2)
            .set("end_time", new Date())
            .eq("id", sessionId)
            .update();
    }
    
    @Override
    public List<ChatSessionVO> getCustomerServiceSessions(Long csId) {
        return baseMapper.selectSessionList(new QueryWrapper<ChatSession>()
            .eq("customer_service_id", csId)
            .eq("status", 1)
            .orderByDesc("create_time"));
    }
}

2. 消息服务

java:src/main/java/com/example/service/impl/ChatMessageServiceImpl.java">@Service
public class ChatMessageServiceImpl implements ChatMessageService {
    
    @Override
    public List<ChatMessageVO> getSessionMessages(Long sessionId) {
        return baseMapper.selectList(new QueryWrapper<ChatMessage>()
            .eq("session_id", sessionId)
            .orderByAsc("create_time"))
            .stream()
            .map(this::convertToVO)
            .collect(Collectors.toList());
    }
    
    @Override
    public void saveMessage(ChatMessageDTO dto) {
        ChatMessage message = new ChatMessage();
        BeanUtils.copyProperties(dto, message);
        
        // 处理特殊消息类型
        if (message.getMsgType() == 2) {
            // 图片消息,保存图片
            String imageUrl = uploadImage(dto.getImageData());
            message.setContent(imageUrl);
        } else if (message.getMsgType() == 3) {
            // 文件消息,保存文件
            String fileUrl = uploadFile(dto.getFileData());
            message.setContent(fileUrl);
        }
        
        save(message);
    }
}

五、前端实现

1. WebSocket连接管理

javascript:src/utils/websocket.js">class ChatWebSocket {
    constructor() {
        this.ws = null;
        this.messageCallbacks = [];
    }
    
    connect() {
        const token = localStorage.getItem('token');
        this.ws = new WebSocket(`ws://localhost:8080/ws/chat`);
        
        this.ws.onopen = () => {
            console.log('WebSocket连接成功');
            // 发送认证信息
            this.ws.send(JSON.stringify({
                type: 'AUTH',
                token: token
            }));
        };
        
        this.ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            this.messageCallbacks.forEach(callback => callback(message));
        };
        
        this.ws.onclose = () => {
            console.log('WebSocket连接关闭');
            // 尝试重连
            setTimeout(() => this.connect(), 3000);
        };
    }
    
    sendMessage(message) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(message));
        }
    }
    
    onMessage(callback) {
        this.messageCallbacks.push(callback);
    }
}

export default new ChatWebSocket();

2. 聊天组件

<template>
  <div class="chat-window">
    <!-- 消息列表 -->
    <div class="message-list" ref="messageList">
      <div v-for="msg in messages" :key="msg.id" 
           :class="['message', msg.senderType === 1 ? 'message-right' : 'message-left']">
        <div class="avatar">
          <img :src="msg.senderType === 1 ? userAvatar : csAvatar">
        </div>
        <div class="content">
          <template v-if="msg.msgType === 1">
            {{ msg.content }}
          </template>
          <template v-else-if="msg.msgType === 2">
            <img :src="msg.content" class="message-image">
          </template>
          <template v-else-if="msg.msgType === 3">
            <div class="file-message" @click="downloadFile(msg.content)">
              <i class="el-icon-document"></i>
              <span>{{ getFileName(msg.content) }}</span>
            </div>
          </template>
        </div>
      </div>
    </div>
    
    <!-- 输入区域 -->
    <div class="input-area">
      <div class="toolbar">
        <el-upload
          action="/api/chat/upload"
          :show-file-list="false"
          :before-upload="beforeImageUpload">
          <i class="el-icon-picture"></i>
        </el-upload>
        <el-upload
          action="/api/chat/upload"
          :show-file-list="false"
          :before-upload="beforeFileUpload">
          <i class="el-icon-folder"></i>
        </el-upload>
      </div>
      <el-input
        v-model="inputContent"
        type="textarea"
        :rows="3"
        placeholder="请输入消息"
        @keyup.enter.native="sendMessage">
      </el-input>
      <el-button type="primary" @click="sendMessage">发送</el-button>
    </div>
  </div>
</template>

<script>
import chatWebSocket from '@/utils/websocket';

export default {
  data() {
    return {
      messages: [],
      inputContent: '',
      sessionId: null
    };
  },
  
  created() {
    chatWebSocket.connect();
    chatWebSocket.onMessage(this.handleMessage);
  },
  
  methods: {
    handleMessage(message) {
      this.messages.push(message);
      this.$nextTick(() => {
        this.scrollToBottom();
      });
    },
    
    sendMessage() {
      if (!this.inputContent.trim()) return;
      
      const message = {
        sessionId: this.sessionId,
        content: this.inputContent,
        msgType: 1,
        senderType: 1
      };
      
      chatWebSocket.sendMessage(message);
      this.inputContent = '';
    },
    
    beforeImageUpload(file) {
      const isImage = file.type.startsWith('image/');
      if (!isImage) {
        this.$message.error('只能上传图片文件!');
        return false;
      }
      
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => {
        const message = {
          sessionId: this.sessionId,
          msgType: 2,
          senderType: 1,
          imageData: reader.result
        };
        chatWebSocket.sendMessage(message);
      };
      return false;
    },
    
    scrollToBottom() {
      const messageList = this.$refs.messageList;
      messageList.scrollTop = messageList.scrollHeight;
    }
  }
};
</script>

<style scoped>
.chat-window {
  height: 600px;
  display: flex;
  flex-direction: column;
}

.message-list {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}

.message {
  display: flex;
  margin-bottom: 20px;
}

.message-right {
  flex-direction: row-reverse;
}

/* 其他样式省略 */
</style>

http://www.niftyadmin.cn/n/5818530.html

相关文章

编程范式、设计模式和算法之间的关系

编程范式、设计模式和算法是软件开发中的三个重要概念&#xff0c;它们各自关注不同的方面&#xff0c;但又相互关联&#xff0c;共同影响着程序的设计和实现。以下是对三者关系的解析&#xff1a; ### 编程范式&#xff08;Programming Paradigms&#xff09; 编程范式定义了…

后端:Spring(IOC、AOP)

文章目录 1. Spring2. IOC 控制反转2-1. 通过配置文件定义Bean2-1-1. 通过set方法来注入Bean2-1-2. 通过构造方法来注入Bean2-1-3. 自动装配2-1-4. 集合注入2-1-5. 数据源对象管理(第三方Bean)2-1-6. 在xml配置文件中加载properties文件的数据(context命名空间)2-1-7. 加载容器…

Sentinel服务保护 + Seata分布式事务

服务保护 【雪崩问题】微服务调用链路中某个服务&#xff0c;引起整个链路中所有微服务都不可用。 【原因】&#xff1a; 微服务相互调用&#xff0c;服务提供者出现故障。服务调用这没有做好异常处理&#xff0c;导致自身故障。调用链中所有服务级联失败&#xff0c;导致整个…

解锁 JMeter 的 ForEach Controller 高效测试秘籍

各位小伙伴们&#xff0c;今天咱就来唠唠 JMeter 里超厉害的 “宝藏工具”——ForEach Controller&#xff0c;它可是能帮咱们在性能测试的江湖里 “大杀四方” 哦&#xff01; 一、ForEach Controller 是啥 “神器” 想象一下&#xff0c;你手头有一串神秘钥匙&#xff0c;每…

pytorch torch.full_like函数介绍

torch.full_like 是 PyTorch 中用于创建一个具有特定值的新张量&#xff0c;其形状和数据类型与给定张量相同。 函数定义 torch.full_like(input, fill_value, *, dtypeNone, layoutNone, deviceNone, requires_gradFalse, memory_formattorch.preserve_format)参数说明 inpu…

【数据结构】航班查询系统:链表的实际运用

目标&#xff1a;航班查询系统&#xff1a;能够在 Linux 系统 中运行&#xff0c;并用 Makefile 管理项目。 项目文件结构&#xff1a; flight_system/ ├── flight.h # 头文件&#xff0c;声明数据结构与函数 ├── flight.c # 实现文件&#xff0c;定义功能函数 ├──…

【应用篇】09.实现简易的Shell命令行解释器

一、shell和bash的关系 shell是命令解释器&#xff0c;它接收用户的命令并将其传递给内核去执行。bash,即GNU Bourne-Again Shell&#xff0c;是shell的一种实现方式&#xff0c;也是大多数linux系统下默认的shell。 bash的原理 大多数的指令进程&#xff08;除了内建命令&…

【Cocos TypeScript 零基础 6.1】

目录 敌机敌机通用逻辑制作动画制作另外的敌机制作自动生成敌机整理自己实验写的 敌机 创建一个空节点 (绑定敌机逻辑,敌机相关都可以存在此节点下,编程更有逻辑,便于后续维护)制作 prefab制作销毁动画制作第二个敌机敌机0自动生成 敌机通用逻辑 老是创建了2个空节点? 父节…