From accbc9e869c6a7437f45d8bcd093ae5d7555f4a9 Mon Sep 17 00:00:00 2001
From: chenyuepan <3158614516@qq.com>
Date: Thu, 3 Jul 2025 11:32:24 +0800
Subject: [PATCH] =?UTF-8?q?=E7=89=A9=E7=90=86=E8=AE=BE=E5=A4=87=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E5=8F=8A=E9=82=AE=E4=BB=B6=E6=8F=90=E9=86=92=E5=8A=9F?=
=?UTF-8?q?=E8=83=BD=E9=9B=86=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 4 +
.../config/InterceptorConfig.java | 1 +
.../controller/DeviceController.java | 57 ++++
.../controller/InformationController.java | 2 +-
.../controller/ReservationController.java | 24 +-
.../controller/UserController.java | 7 +
.../domain/Device.java | 57 ++++
.../domain/Information.java | 11 +
.../dto/ResetDTO.java | 11 +
.../entity/RecommendVO.java | 15 +
.../mapper/DeviceMapper.java | 31 +++
.../netty/NettyChatServer.java | 3 +-
.../repository/ChatMessageRepository.java | 1 +
.../service/DeviceService.java | 17 ++
.../service/EmailService.java | 37 +++
.../service/ReservationService.java | 2 +
.../service/UserService.java | 2 +
.../service/impl/DeviceServiceImpl.java | 49 ++++
.../service/impl/EmailServiceImpl.java | 80 ++++++
.../service/impl/InformationServiceImpl.java | 46 ++-
.../service/impl/ReservationServiceImpl.java | 33 ++-
.../service/impl/TypeServiceImpl.java | 38 +--
.../service/impl/UserServiceImpl.java | 75 +++--
.../service/impl/VenueHotServiceImpl.java | 17 +-
.../utils/RedisUtil.java | 30 --
.../utils/VenueRecommender.java | 262 ++++++++++++++++++
.../vo/excel/ReservationExcel.java | 17 ++
src/main/resources/application.yml | 25 +-
src/main/resources/mapper/DeviceMapper.xml | 20 ++
...nueReservationServiceApplicationTests.java | 47 +++-
30 files changed, 910 insertions(+), 111 deletions(-)
create mode 100644 src/main/java/com/example/venue_reservation_service/controller/DeviceController.java
create mode 100644 src/main/java/com/example/venue_reservation_service/domain/Device.java
create mode 100644 src/main/java/com/example/venue_reservation_service/dto/ResetDTO.java
create mode 100644 src/main/java/com/example/venue_reservation_service/entity/RecommendVO.java
create mode 100644 src/main/java/com/example/venue_reservation_service/mapper/DeviceMapper.java
create mode 100644 src/main/java/com/example/venue_reservation_service/service/DeviceService.java
create mode 100644 src/main/java/com/example/venue_reservation_service/service/EmailService.java
create mode 100644 src/main/java/com/example/venue_reservation_service/service/impl/DeviceServiceImpl.java
create mode 100644 src/main/java/com/example/venue_reservation_service/service/impl/EmailServiceImpl.java
create mode 100644 src/main/java/com/example/venue_reservation_service/utils/VenueRecommender.java
create mode 100644 src/main/resources/mapper/DeviceMapper.xml
diff --git a/pom.xml b/pom.xml
index ef36511..a3f2635 100644
--- a/pom.xml
+++ b/pom.xml
@@ -229,6 +229,10 @@
org.springframework.boot
spring-boot-starter-data-mongodb
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
diff --git a/src/main/java/com/example/venue_reservation_service/config/InterceptorConfig.java b/src/main/java/com/example/venue_reservation_service/config/InterceptorConfig.java
index c4d75e5..b4c42ed 100644
--- a/src/main/java/com/example/venue_reservation_service/config/InterceptorConfig.java
+++ b/src/main/java/com/example/venue_reservation_service/config/InterceptorConfig.java
@@ -20,6 +20,7 @@ public class InterceptorConfig implements WebMvcConfigurer {
.addPathPatterns("/school/**")
.excludePathPatterns("/user/test")
.excludePathPatterns("/user/admin")
+ .excludePathPatterns("/user/reset")
.excludePathPatterns("/reservation/excel")
.excludePathPatterns("/reservation/payment")
.excludePathPatterns("/swagger-ui/index.html")
diff --git a/src/main/java/com/example/venue_reservation_service/controller/DeviceController.java b/src/main/java/com/example/venue_reservation_service/controller/DeviceController.java
new file mode 100644
index 0000000..b1f4d48
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/controller/DeviceController.java
@@ -0,0 +1,57 @@
+package com.example.venue_reservation_service.controller;
+
+import com.example.venue_reservation_service.domain.Device;
+import com.example.venue_reservation_service.mapper.DeviceMapper;
+import com.example.venue_reservation_service.service.DeviceService;
+import com.example.venue_reservation_service.vo.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import jakarta.annotation.Resource;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/device")
+@Api("设备数据管理")
+@CrossOrigin
+public class DeviceController {
+
+ @Resource
+ private DeviceService deviceService;
+
+ @Resource
+ private DeviceMapper deviceMapper;
+
+ @Resource
+ private SimpMessagingTemplate messagingTemplate;
+
+ // 获取最新设备数据
+ @ApiOperation("获取最新设备数据")
+ @GetMapping("/latest")
+ public Result getLatestDevices() {
+ return Result.ok(deviceService.getLatestDevices()).message("加载成功");
+ }
+//
+ // 定时任务 - 每秒生成一条数据
+// @Scheduled(fixedRate = 1000)
+// public void generateAndSendDeviceData() {
+// // 生成并保存新数据
+// Device newDevice = deviceService.generateDeviceData();
+// deviceService.save(newDevice);
+//
+// // 检查并清理旧数据
+// long count = deviceService.count();
+// if (count > 100) {
+// deviceMapper.deleteOldestRecords(50);
+// }
+//
+// // 推送新数据到前端
+// messagingTemplate.convertAndSend("/topic/devices", newDevice);
+// }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/controller/InformationController.java b/src/main/java/com/example/venue_reservation_service/controller/InformationController.java
index 7a3fa15..87814ff 100644
--- a/src/main/java/com/example/venue_reservation_service/controller/InformationController.java
+++ b/src/main/java/com/example/venue_reservation_service/controller/InformationController.java
@@ -34,7 +34,7 @@ public class InformationController {
@ApiOperation("获取某个场馆内的所有场地数据")
@GetMapping("/detail")
- public Result detail(@RequestParam("type_id")Integer typeId){
+ public Result detail(@RequestParam("type_id") Integer typeId){
return informationService.getDetails(typeId);
}
diff --git a/src/main/java/com/example/venue_reservation_service/controller/ReservationController.java b/src/main/java/com/example/venue_reservation_service/controller/ReservationController.java
index d068d01..4d9a8cc 100644
--- a/src/main/java/com/example/venue_reservation_service/controller/ReservationController.java
+++ b/src/main/java/com/example/venue_reservation_service/controller/ReservationController.java
@@ -1,22 +1,15 @@
package com.example.venue_reservation_service.controller;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.example.venue_reservation_service.domain.Reservation;
import com.example.venue_reservation_service.dto.*;
+import com.example.venue_reservation_service.mapper.ReservationMapper;
import com.example.venue_reservation_service.service.ReservationService;
-import com.example.venue_reservation_service.utils.DownExcel;
import com.example.venue_reservation_service.vo.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
-import org.apache.ibatis.annotations.Param;
import org.springframework.web.bind.annotation.*;
-import javax.ws.rs.Path;
-import java.io.IOException;
-import java.util.List;
-
@RestController
@RequestMapping("/reservation")
@Api("预约操作管理")
@@ -26,6 +19,14 @@ public class ReservationController {
@Resource
private ReservationService reservationService;
+ @Resource
+ private ReservationMapper reservationMapper;
+
+ @GetMapping("/list")
+ public Result userList(@RequestParam("userId") Integer userId){
+ return Result.ok(reservationMapper.selectByUserId(userId)).message("查询成功");
+ }
+
@ApiOperation("添加预约信息")
@PostMapping("/create")
public Result create(@RequestBody RAddDTO dto){
@@ -86,4 +87,11 @@ public class ReservationController {
// 调用服务层导出方法
reservationService.exportPayments(response, yearFilter);
}
+
+ @ApiOperation("智能场地推荐")
+ @GetMapping("/recommend")
+ public Result recommend(@RequestParam("userId") Integer userId){
+ return reservationService.recommend(userId);
+ }
+
}
diff --git a/src/main/java/com/example/venue_reservation_service/controller/UserController.java b/src/main/java/com/example/venue_reservation_service/controller/UserController.java
index 0709696..9da8174 100644
--- a/src/main/java/com/example/venue_reservation_service/controller/UserController.java
+++ b/src/main/java/com/example/venue_reservation_service/controller/UserController.java
@@ -5,6 +5,7 @@ import com.example.venue_reservation_service.dto.*;
import com.example.venue_reservation_service.mq.DelayMessageSender;
import com.example.venue_reservation_service.service.UserService;
import com.example.venue_reservation_service.vo.Result;
+import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -289,4 +290,10 @@ public class UserController {
public Result info(@PathVariable("id") Integer userId) {
return userService.getUserInfo(userId);
}
+
+ @ApiOperation("基于qq邮箱重置密码")
+ @PostMapping("/reset")
+ public Result reset(@RequestBody ResetDTO dto){
+ return userService.passwordReset(dto);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/domain/Device.java b/src/main/java/com/example/venue_reservation_service/domain/Device.java
new file mode 100644
index 0000000..f374da4
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/domain/Device.java
@@ -0,0 +1,57 @@
+package com.example.venue_reservation_service.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ *
+ * @TableName venue_device
+ */
+@TableName(value ="venue_device")
+@Data
+public class Device implements Serializable {
+ /**
+ *
+ */
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ /**
+ *
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime timestamp;
+
+ /**
+ *
+ */
+ private Double temperature;
+
+ /**
+ *
+ */
+ private Double humidity;
+
+ /**
+ *
+ */
+ private Integer fanStatus;
+
+ /**
+ *
+ */
+ private Integer warningStatus;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/domain/Information.java b/src/main/java/com/example/venue_reservation_service/domain/Information.java
index 70e192d..05a748a 100644
--- a/src/main/java/com/example/venue_reservation_service/domain/Information.java
+++ b/src/main/java/com/example/venue_reservation_service/domain/Information.java
@@ -5,7 +5,11 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
/**
*
@@ -50,6 +54,13 @@ public class Information implements Serializable {
*/
private String qrCode;
+ /**
+ * 场地创建时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime updateTime;
+
@TableField(exist = false)
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/com/example/venue_reservation_service/dto/ResetDTO.java b/src/main/java/com/example/venue_reservation_service/dto/ResetDTO.java
new file mode 100644
index 0000000..59ee737
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/dto/ResetDTO.java
@@ -0,0 +1,11 @@
+package com.example.venue_reservation_service.dto;
+
+import lombok.Data;
+
+@Data
+public class ResetDTO {
+
+ private String phone;
+
+ private String email;
+}
diff --git a/src/main/java/com/example/venue_reservation_service/entity/RecommendVO.java b/src/main/java/com/example/venue_reservation_service/entity/RecommendVO.java
new file mode 100644
index 0000000..b6ec52d
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/entity/RecommendVO.java
@@ -0,0 +1,15 @@
+package com.example.venue_reservation_service.entity;
+
+import com.example.venue_reservation_service.domain.Information;
+import com.example.venue_reservation_service.vo.excel.ReservationExcel;
+import lombok.Data;
+
+@Data
+public class RecommendVO {
+
+ private ReservationExcel excel;
+
+ private Information information;
+
+ private Double price;
+}
diff --git a/src/main/java/com/example/venue_reservation_service/mapper/DeviceMapper.java b/src/main/java/com/example/venue_reservation_service/mapper/DeviceMapper.java
new file mode 100644
index 0000000..edb9c56
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/mapper/DeviceMapper.java
@@ -0,0 +1,31 @@
+package com.example.venue_reservation_service.mapper;
+
+import com.example.venue_reservation_service.domain.Device;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+* @author 31586
+* @description 针对表【venue_device】的数据库操作Mapper
+* @createDate 2025-06-29 14:38:46
+* @Entity generator.domain.Device
+*/
+@Mapper
+public interface DeviceMapper extends BaseMapper {
+
+ @Select("SELECT * FROM venue_device ORDER BY timestamp DESC LIMIT #{limit}")
+ List selectLatestDevices(@Param("limit") int limit);
+
+ @Delete("DELETE FROM venue_device ORDER BY timestamp ASC LIMIT #{count}")
+ void deleteOldestRecords(@Param("count") int count);
+
+}
+
+
+
+
diff --git a/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java b/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java
index e70cb68..9418c4b 100644
--- a/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java
+++ b/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java
@@ -15,6 +15,7 @@ import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketSe
import io.netty.handler.stream.ChunkedWriteHandler;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -25,7 +26,7 @@ public class NettyChatServer {
@Value("${server.netty.port}")
private int port;
- @Autowired
+ @Resource
private ChatServerHandler chatServerHandler; // 确保正确注入
private EventLoopGroup bossGroup;
diff --git a/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java b/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java
index 7709b72..2f1815d 100644
--- a/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java
+++ b/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java
@@ -4,6 +4,7 @@ import com.example.venue_reservation_service.entity.ChatMessage;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
+
import java.time.LocalDateTime;
public interface ChatMessageRepository extends MongoRepository {
diff --git a/src/main/java/com/example/venue_reservation_service/service/DeviceService.java b/src/main/java/com/example/venue_reservation_service/service/DeviceService.java
new file mode 100644
index 0000000..f89d442
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/service/DeviceService.java
@@ -0,0 +1,17 @@
+package com.example.venue_reservation_service.service;
+
+import com.example.venue_reservation_service.domain.Device;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+* @author 31586
+* @description 针对表【venue_device】的数据库操作Service
+* @createDate 2025-06-29 14:38:46
+*/
+public interface DeviceService extends IService {
+ Device generateDeviceData();
+
+ List getLatestDevices();
+}
diff --git a/src/main/java/com/example/venue_reservation_service/service/EmailService.java b/src/main/java/com/example/venue_reservation_service/service/EmailService.java
new file mode 100644
index 0000000..c6f16bf
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/service/EmailService.java
@@ -0,0 +1,37 @@
+package com.example.venue_reservation_service.service;
+
+import org.springframework.core.io.InputStreamSource;
+
+public interface EmailService {
+ /**
+ * 发送简单邮件
+ * @param to 收件人
+ * @param subject 主题
+ * @param text 内容
+ */
+ void sendSimpleMessage(String to, String subject, String text);
+
+ /**
+ * 发送HTML格式邮件
+ * @param to 收件人
+ * @param subject 主题
+ * @param htmlContent HTML内容
+ */
+ void sendHtmlMessage(String to, String subject, String htmlContent);
+
+ /**
+ * 发送带附件的邮件
+ * @param to 收件人
+ * @param subject 主题
+ * @param text 内容
+ * @param attachmentFilename 附件文件名
+ * @param attachment 附件
+ */
+ void sendMessageWithAttachment(
+ String to,
+ String subject,
+ String text,
+ String attachmentFilename,
+ InputStreamSource attachment
+ );
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/service/ReservationService.java b/src/main/java/com/example/venue_reservation_service/service/ReservationService.java
index a4873ba..c821fba 100644
--- a/src/main/java/com/example/venue_reservation_service/service/ReservationService.java
+++ b/src/main/java/com/example/venue_reservation_service/service/ReservationService.java
@@ -34,4 +34,6 @@ public interface ReservationService extends IService {
Result removeSingle(Integer userId, Integer id);
void exportPayments(HttpServletResponse response, String yearFilter);
+
+ Result recommend(Integer userId);
}
diff --git a/src/main/java/com/example/venue_reservation_service/service/UserService.java b/src/main/java/com/example/venue_reservation_service/service/UserService.java
index 8bf942a..2a213d3 100644
--- a/src/main/java/com/example/venue_reservation_service/service/UserService.java
+++ b/src/main/java/com/example/venue_reservation_service/service/UserService.java
@@ -40,4 +40,6 @@ public interface UserService extends IService {
Result upload(Integer userId, MultipartFile file);
Result getUserInfo(Integer userId);
+
+ Result passwordReset(ResetDTO dto);
}
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/DeviceServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/DeviceServiceImpl.java
new file mode 100644
index 0000000..5a24819
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/DeviceServiceImpl.java
@@ -0,0 +1,49 @@
+package com.example.venue_reservation_service.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.venue_reservation_service.domain.Device;
+import com.example.venue_reservation_service.mapper.DeviceMapper;
+import com.example.venue_reservation_service.service.DeviceService;
+import jakarta.annotation.Resource;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+* @author 31586
+* @description 针对表【venue_device】的数据库操作Service实现
+* @createDate 2025-06-29 14:38:46
+*/
+@Service
+public class DeviceServiceImpl extends ServiceImpl implements DeviceService {
+
+ @Resource
+ private DeviceMapper deviceMapper;
+
+ // 模拟设备数据
+ @Override
+ public Device generateDeviceData() {
+ Device device = new Device();
+ device.setTimestamp(LocalDateTime.now());
+ device.setTemperature(20 + Math.random() * 15);
+ device.setHumidity(30 + Math.random() * 50);
+ device.setFanStatus(device.getTemperature() > 25 ? 1 : 0);
+ device.setWarningStatus((device.getTemperature() > 30 ||
+ device.getHumidity() > 70 ||
+ device.getHumidity() < 30) ? 1 : 0);
+ return device;
+ }
+
+ // 获取最新设备数据
+ @Override
+ public List getLatestDevices() {
+ return deviceMapper.selectLatestDevices(50);
+ }
+
+}
+
+
+
+
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/EmailServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/EmailServiceImpl.java
new file mode 100644
index 0000000..3b7f794
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/EmailServiceImpl.java
@@ -0,0 +1,80 @@
+package com.example.venue_reservation_service.service.impl;
+
+import com.example.venue_reservation_service.service.EmailService;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeMessage;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.mail.MailException;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class EmailServiceImpl implements EmailService {
+
+ private final JavaMailSender mailSender;
+
+ @Value("${spring.mail.username}")
+ private String from;
+
+ @Override
+ public void sendSimpleMessage(String to, String subject, String text) {
+ try {
+ SimpleMailMessage message = new SimpleMailMessage();
+ message.setFrom(from);
+ message.setTo(to);
+ message.setSubject(subject);
+ message.setText(text);
+
+ mailSender.send(message);
+ } catch (MailException e) {
+ throw new RuntimeException("邮件发送失败", e);
+ }
+ }
+
+ @Override
+ public void sendHtmlMessage(String to, String subject, String htmlContent) {
+ try {
+ MimeMessage message = mailSender.createMimeMessage();
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
+
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ helper.setText(htmlContent, true);
+
+ mailSender.send(message);
+ } catch (MessagingException e) {
+ throw new RuntimeException("HTML邮件发送失败", e);
+ }
+ }
+
+ @Override
+ public void sendMessageWithAttachment(
+ String to,
+ String subject,
+ String text,
+ String attachmentFilename,
+ InputStreamSource attachment
+ ) {
+ try {
+ MimeMessage message = mailSender.createMimeMessage();
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
+
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ helper.setText(text);
+
+ helper.addAttachment(attachmentFilename, attachment);
+
+ mailSender.send(message);
+ } catch (MessagingException e) {
+ throw new RuntimeException("带附件邮件发送失败", e);
+ }
+ }
+}
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/InformationServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/InformationServiceImpl.java
index f94e56f..8d9059e 100644
--- a/src/main/java/com/example/venue_reservation_service/service/impl/InformationServiceImpl.java
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/InformationServiceImpl.java
@@ -18,6 +18,7 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -29,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.time.Duration;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
@@ -78,6 +80,9 @@ public class InformationServiceImpl extends ServiceImpl page = new Page<>(dto.getCurrent(), dto.getSize());
@@ -106,6 +111,11 @@ public class InformationServiceImpl extends ServiceImpl informationList = list(wrapper);
+ informationList.forEach(item -> {
+ if(StringUtils.isNotBlank(item.getImgUrl())){
+ item.setImgUrl(imgPrefix + item.getImgUrl());
+ }
+ });
return Result.ok(informationList).message("场地信息获取成功");
}
@@ -131,7 +141,14 @@ public class InformationServiceImpl extends ServiceImpl vo = new QueryVO();
vo.setTotal(page.getTotal());
- vo.setList(page.getRecords());
+ List records = page.getRecords();
+ records.forEach(item -> {
+ if(StringUtils.isNotBlank(item.getImgUrl())){
+ item.setImgUrl(imgPrefix + item.getImgUrl());
+ }
+ item.setQrCode(imgPrefix + item.getQrCode());
+ });
+ vo.setList(records);
return Result.ok(vo).message("场地信息查询成功");
}
@@ -144,16 +161,17 @@ public class InformationServiceImpl extends ServiceImpl {
- // 处理当前预约
- dealReservation(reservation.getId());
- };
+// Runnable task = () -> {
+// // 处理当前预约
+// dealReservation(reservation.getId());
+// };
// scheduleUtil.scheduleOneTimeTask(task, reservationDate, end);
// // 定时任务解耦设计
@@ -563,6 +567,21 @@ public class ReservationServiceImpl extends ServiceImpllambdaQuery().eq(Reservation::getUserId, userId));
+ VenueRecommender recommender = new VenueRecommender(reservationMapper.selectByUserId(count > 50 ? userId:null), userId);
+ RecommendVO vo = new RecommendVO();
+ ReservationExcel excel = recommender.generateRecommendation();
+ int hours = excel.getEndTime().getHour() - excel.getStartTime().getHour();
+ vo.setExcel(excel);
+ vo.setInformation(informationService.getById(excel.getVenueId()));
+ vo.setPrice(priceService.getPrice(excel.getVenueId()) * hours);
+ return Result.ok(vo).message("推荐结果");
+ }
+
}
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/TypeServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/TypeServiceImpl.java
index 896a954..f304edb 100644
--- a/src/main/java/com/example/venue_reservation_service/service/impl/TypeServiceImpl.java
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/TypeServiceImpl.java
@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.venue_reservation_service.domain.Information;
import com.example.venue_reservation_service.domain.InformationTime;
import com.example.venue_reservation_service.dto.PageDTO;
+import com.example.venue_reservation_service.exception.MinioRemoveException;
import com.example.venue_reservation_service.mapper.InformationMapper;
import com.example.venue_reservation_service.mapper.TypeMapper;
import com.example.venue_reservation_service.domain.Type;
@@ -41,11 +42,23 @@ public class TypeServiceImpl extends ServiceImpl
@Resource
private InformationMapper informationMapper;
+ @Value("${access.url}")
+ private String imgPrefix;
+
@Override
public Result queryTypes(PageDTO dto) {
Page page = new Page<>(dto.getCurrent(), dto.getSize());
page = page(page, null);
- return Result.ok(new QueryVO(page.getRecords(), page.getTotal())).message("场馆信息获取成功");
+ List records = page.getRecords();
+ records.forEach(item -> {
+ if(StringUtils.isNotBlank(item.getImgUrl())){
+ item.setImgUrl(imgPrefix + item.getImgUrl());
+ }
+ });
+ QueryVO vo = new QueryVO<>();
+ vo.setList(records);
+ vo.setTotal(page.getTotal());
+ return Result.ok(vo).message("场馆信息获取成功");
}
private boolean isSame(Type type){
@@ -103,35 +116,28 @@ public class TypeServiceImpl extends ServiceImpl
if (Optional.ofNullable(type).isEmpty()) {
return Result.fail().message("场馆信息不存在");
}
+ if(StringUtils.isNotBlank(type.getImgUrl())){
+ try{
+ minioUtil.removeFile(type.getImgUrl());
+ }catch (Exception e){
+ throw new MinioRemoveException("场馆图片异常,请稍后重试");
+ }
+ }
// 对照片重新命名
- // 获取文件后缀名
String extension = FilenameUtils.getExtension( file.getOriginalFilename());
-
try {
// 重新生成文件名称
String filename = UUID.randomUUID() + "-" + id + "." + extension;
// 调用minio接口上传照片
minioUtil.uploadFile(file, filename, file.getContentType());
- String url = minioUtil.getPresignedObjectUrl(filename);
- // 删除原来的照片
- String imgUrl = type.getImgUrl();
- if (imgUrl!=null && !"".equals(imgUrl)){
- String[] split = imgUrl.split("/");
- // 获取照片名字
- String img = split[split.length - 1];
- minioUtil.removeFile(img);
- }
- System.out.println(url);
- type.setImgUrl(url);
+ type.setImgUrl(filename);
updateById(type);
-
} catch (Exception e) {
e.printStackTrace();
return Result.fail().message("图片上传失败");
}
-
return Result.ok().message("图片上传成功");
}
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java
index e4fece6..38c64e7 100644
--- a/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java
@@ -21,10 +21,14 @@ import com.example.venue_reservation_service.vo.Result;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.annotation.Resource;
import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
+
+import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -65,12 +69,21 @@ public class UserServiceImpl extends ServiceImpl
@Value("${spring.application.name}")
private String name;
- @Value("${access.url}")
- private String imgUrl;
-
@Resource
private MinioUtil minioUtil;
+ @Resource
+ private EmailService emailService;
+
+ @Autowired
+ private RedisTemplate redisTemplate;
+
+ @Value("${access.url}")
+ private String imgPrefix;
+
+ private static final String LOGIN_LOCK_PREFIX = "login_lock:";
+ private static final int MAX_LOGIN_ATTEMPTS = 3;
+ private static final long LOCK_DURATION_MS = 5 * 60 * 1000; // 5 minutes in milliseconds
@Override
public Result getOpenId(String code) {
Map vo = restTemplate.getForObject("https://api.weixin.qq.com/sns/jscode2session?"+
@@ -102,6 +115,7 @@ public class UserServiceImpl extends ServiceImpl
user.setAccessCode(PasswordGenerator.generatePassword());
user.setRegisterTime(LocalDateTime.now());
user.setIsUpload(0);
+ user.setAvatar("");
save(user);
// 添加新的账户余额信息
Balance balance = new Balance(0.0, user.getId());
@@ -113,7 +127,7 @@ public class UserServiceImpl extends ServiceImpl
Double money = balanceService.getOne(Wrappers.lambdaQuery().eq(Balance::getUserId, user.getId()))
.getBalance();
if(user.getIsUpload() == 1){
- user.setAvatar(imgUrl + user.getAvatar());
+ user.setAvatar(imgPrefix + user.getAvatar());
}
user.setBalance(money);
loginVo.setUser(user);
@@ -132,24 +146,24 @@ public class UserServiceImpl extends ServiceImpl
}
String password = dto.getPasswd();
- String redisKey = "login_lock:" + account; // 使用固定前缀
+ String redisKey = LOGIN_LOCK_PREFIX + account; // 使用固定前缀
if (!user.getAccessCode().equals(password)) {
// 处理密码错误的情况
- if (redisUtil.hasKey(redisKey)) {
- String[] parts = String.valueOf(redisUtil.get(redisKey)).split("-");
+ if (redisTemplate.hasKey(redisKey)) {
+ String[] parts = String.valueOf(redisTemplate.opsForValue().get(redisKey)).split("-");
int failCount = Integer.parseInt(parts[0]);
long firstFailTime = Long.parseLong(parts[1]);
// 错误次数已达上限
- if (failCount >= 3) {
- long lockTime = 5 * 60 * 1000; // 5分钟毫秒数
+ if (failCount >= MAX_LOGIN_ATTEMPTS) {
+ long lockTime = LOCK_DURATION_MS; // 5分钟毫秒数
long remainMillis = lockTime - (System.currentTimeMillis() - firstFailTime);
if (remainMillis > 0) {
return Result.fail().message("该账号已被锁定,请在 " + (remainMillis / 1000) + " 秒后重新登录");
} else {
// 锁定已过期,重置计数器
- redisUtil.remove(redisKey);
+ redisTemplate.delete(redisKey);
}
}
}
@@ -157,8 +171,8 @@ public class UserServiceImpl extends ServiceImpl
// 更新错误计数器(包含首次错误情况)
int newCount = 1;
long timestamp = System.currentTimeMillis();
- if (redisUtil.hasKey(redisKey)) {
- String[] parts = String.valueOf(redisUtil.get(redisKey)).split("-");
+ if (redisTemplate.hasKey(redisKey)) {
+ String[] parts = String.valueOf(redisTemplate.opsForValue().get(redisKey)).split("-");
newCount = Integer.parseInt(parts[0]) + 1;
timestamp = Long.parseLong(parts[1]); // 保持第一次错误时间
}
@@ -166,9 +180,9 @@ public class UserServiceImpl extends ServiceImpl
// 更新Redis,仅首次设置过期时间
String value = newCount + "-" + timestamp;
if (newCount == 1) {
- redisUtil.set(redisKey, value, 5 * 60);
+ redisTemplate.opsForValue().set(redisKey, value, Duration.ofMinutes(5));
} else {
- redisUtil.set(redisKey, value); // 更新次数但保留原TTL
+ redisTemplate.opsForValue().set(redisKey, value); // 更新次数但保留原TTL
}
return Result.fail().message("账号密码不匹配");
@@ -176,13 +190,13 @@ public class UserServiceImpl extends ServiceImpl
// 密码正确,登录成功
// 清除登录错误计数
- if (redisUtil.hasKey(redisKey)) {
- redisUtil.remove(redisKey);
+ if (redisTemplate.hasKey(redisKey)) {
+ redisTemplate.delete(redisKey);
}
user.setAccessCode("");
if (user.getIsUpload() == 1) {
- user.setAvatar(imgUrl + user.getAvatar());
+ user.setAvatar(imgPrefix + user.getAvatar());
}
LoginVo vo = new LoginVo();
vo.setUser(user);
@@ -190,6 +204,7 @@ public class UserServiceImpl extends ServiceImpl
return Result.ok(vo).message("登录成功");
}
+
/**
* 用户信息修改
* 可修改内容:用户名(username)、电话(phone)、邮箱(email)
@@ -415,7 +430,7 @@ public class UserServiceImpl extends ServiceImpl
user.setIsUpload(1);
user.setAvatar(filename);
updateById(user);
- return Result.ok(imgUrl + user.getAvatar()).message("头像上传成功");
+ return Result.ok(imgPrefix + user.getAvatar()).message("头像上传成功");
} catch (MinioRemoveException | ImageValidateException | MinioUploadException e) {
throw e; // 抛出给全局异常处理器
@@ -440,10 +455,32 @@ public class UserServiceImpl extends ServiceImpl
if (Optional.ofNullable(balance).isPresent()) {
user.setBalance(balance.getBalance());
}
- user.setAvatar(imgUrl + user.getAvatar());
+ if(StringUtils.isNotBlank(user.getAvatar())){
+ user.setAvatar(imgPrefix + user.getAvatar());
+ }
user.setAccessCode("");
return Result.ok(user).message("用户信息查询成功");
}
+
+ @Override
+ public Result passwordReset(ResetDTO dto) {
+ User user = getOne(Wrappers.lambdaQuery().eq(User::getUserPhone, dto.getPhone()));
+ if (Optional.ofNullable(user).isEmpty()) {
+ return Result.fail().message("手机号未认证");
+ }
+ if (Optional.ofNullable(user.getEmail()).isEmpty()) {
+ return Result.fail().message("邮箱未认证");
+ }
+ if (!user.getEmail().equals(dto.getEmail())) {
+ return Result.fail().message("手机号信息与邮箱信息不匹配");
+ }
+ try{
+ emailService.sendSimpleMessage(dto.getEmail(), user.getUserPhone() + "用户密码重置", PasswordGenerator.generatePassword());
+ }catch (Exception e){
+ return Result.fail().message("邮件发送失败,请稍后重新尝试");
+ }
+ return Result.ok().message("邮件发送成功");
+ }
}
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/VenueHotServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/VenueHotServiceImpl.java
index 5c9715c..dff8860 100644
--- a/src/main/java/com/example/venue_reservation_service/service/impl/VenueHotServiceImpl.java
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/VenueHotServiceImpl.java
@@ -21,6 +21,7 @@ import com.example.venue_reservation_service.utils.MinioUtil;
import com.example.venue_reservation_service.vo.QueryVO;
import com.example.venue_reservation_service.vo.Result;
import jakarta.annotation.Resource;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -46,12 +47,21 @@ public class VenueHotServiceImpl extends ServiceImpl
@Resource
private PaymentMapper paymentMapper;
+ @Value("${access.url}")
+ private String imgPrefix;
+
@Override
public Result getHots(PageDTO dto) {
Page page = new Page<>(dto.getCurrent(), dto.getSize());
page = page(page, null);
QueryVO vo = new QueryVO<>();
- vo.setList(page.getRecords());
+ List records = page.getRecords();
+ records.forEach(item ->{
+ if(StringUtils.isNotBlank(item.getImgUrl())){
+ item.setImgUrl(imgPrefix + item.getImgUrl());
+ }
+ });
+ vo.setList(records);
vo.setTotal(page.getTotal());
return Result.ok(vo).message("查询成功");
}
@@ -98,9 +108,8 @@ public class VenueHotServiceImpl extends ServiceImpl
String filename = System.currentTimeMillis() + "-" + id + "." +split[split.length - 1] ;
minioUtil.uploadFile(file, filename, file.getContentType());
- String url = minioUtil.getPresignedObjectUrl(filename);
- System.out.println();
- hot.setImgUrl(url);
+
+ hot.setImgUrl(filename);
updateById(hot);
}catch (Exception e){
e.printStackTrace();
diff --git a/src/main/java/com/example/venue_reservation_service/utils/RedisUtil.java b/src/main/java/com/example/venue_reservation_service/utils/RedisUtil.java
index 2cf5bad..34a739a 100644
--- a/src/main/java/com/example/venue_reservation_service/utils/RedisUtil.java
+++ b/src/main/java/com/example/venue_reservation_service/utils/RedisUtil.java
@@ -156,36 +156,6 @@ public class RedisUtil {
return redisTemplate.opsForSet().members(key);
}
- /**
- * 随机获取变量中指定个数的元素
- *
- * @param key 键
- * @param count 值
- * @return
- */
- public void randomMembers(String key, long count) {
- redisTemplate.opsForSet().randomMembers(key, count);
- }
-
- /**
- * 随机获取变量中的元素
- *
- * @param key 键
- * @return
- */
- public Object randomMember(String key) {
- return redisTemplate.opsForSet().randomMember(key);
- }
-
- /**
- * 弹出变量中的元素
- *
- * @param key 键
- * @return
- */
- public Object pop(String key) {
- return redisTemplate.opsForSet().pop("setValue");
- }
/**
* 获取变量中值的长度
diff --git a/src/main/java/com/example/venue_reservation_service/utils/VenueRecommender.java b/src/main/java/com/example/venue_reservation_service/utils/VenueRecommender.java
new file mode 100644
index 0000000..28dd4c1
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/utils/VenueRecommender.java
@@ -0,0 +1,262 @@
+package com.example.venue_reservation_service.utils;
+
+import com.example.venue_reservation_service.vo.excel.ReservationExcel;
+
+import java.util.*;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.stream.Collectors;
+
+public class VenueRecommender {
+
+
+
+ // 用户-物品评分矩阵
+ private Map> userVenueScores;
+ // 物品-物品相似度矩阵
+ private Map> venueSimilarities;
+ // 所有预约记录
+ private List allReservations;
+ // 目标用户ID
+ private int targetUserId;
+
+ public VenueRecommender(List reservations, int targetUserId) {
+ this.allReservations = reservations;
+ this.targetUserId = targetUserId;
+ this.userVenueScores = new HashMap<>();
+ this.venueSimilarities = new HashMap<>();
+ }
+
+ // 构建用户-物品评分矩阵
+ private void buildUserVenueMatrix() {
+ // 按用户分组
+ Map> reservationsByUser = allReservations.stream()
+ .collect(Collectors.groupingBy(ReservationExcel::getUserId));
+
+ for (Map.Entry> entry : reservationsByUser.entrySet()) {
+ int userId = entry.getKey();
+ List userReservations = entry.getValue();
+
+ // 按场地分组,计算用户对每个场地的偏好得分
+ Map venueCounts = userReservations.stream()
+ .collect(Collectors.groupingBy(ReservationExcel::getVenueId, Collectors.counting()));
+
+ // 找到最大计数用于归一化
+ long maxCount = venueCounts.values().stream().max(Long::compare).orElse(1L);
+
+ Map venueScores = new HashMap<>();
+ for (Map.Entry venueEntry : venueCounts.entrySet()) {
+ // 归一化到0-1之间
+ double score = (double) venueEntry.getValue() / maxCount;
+ venueScores.put(venueEntry.getKey(), score);
+ }
+
+ userVenueScores.put(userId, venueScores);
+ }
+ }
+
+ // 计算场地之间的余弦相似度
+ private void calculateVenueSimilarities() {
+ // 获取所有场地ID
+ Set allVenues = allReservations.stream()
+ .map(ReservationExcel::getVenueId)
+ .collect(Collectors.toSet());
+
+ // 为每个场地构建特征向量(基于用户评分)
+ Map> venueUserVectors = new HashMap<>();
+
+ // 初始化
+ for (int venueId : allVenues) {
+ venueUserVectors.put(venueId, new HashMap<>());
+ }
+
+ // 填充向量
+ for (Map.Entry> userEntry : userVenueScores.entrySet()) {
+ int userId = userEntry.getKey();
+ Map venueScores = userEntry.getValue();
+
+ for (Map.Entry venueEntry : venueScores.entrySet()) {
+ int venueId = venueEntry.getKey();
+ double score = venueEntry.getValue();
+ venueUserVectors.get(venueId).put(userId, score);
+ }
+ }
+
+ // 计算场地之间的相似度
+ for (int venue1 : allVenues) {
+ Map vector1 = venueUserVectors.get(venue1);
+ Map similarities = new HashMap<>();
+
+ for (int venue2 : allVenues) {
+ if (venue1 == venue2) {
+ similarities.put(venue2, 1.0); // 自相似度为1
+ continue;
+ }
+
+ Map vector2 = venueUserVectors.get(venue2);
+
+ // 计算余弦相似度
+ double dotProduct = 0.0;
+ double norm1 = 0.0;
+ double norm2 = 0.0;
+
+ // 收集所有共同的用户
+ Set commonUsers = new HashSet<>(vector1.keySet());
+ commonUsers.retainAll(vector2.keySet());
+
+ // 如果没有共同用户,相似度为0
+ if (commonUsers.isEmpty()) {
+ similarities.put(venue2, 0.0);
+ continue;
+ }
+
+ for (int userId : commonUsers) {
+ double score1 = vector1.get(userId);
+ double score2 = vector2.get(userId);
+ dotProduct += score1 * score2;
+ norm1 += score1 * score1;
+ norm2 += score2 * score2;
+ }
+
+ norm1 = Math.sqrt(norm1);
+ norm2 = Math.sqrt(norm2);
+
+ double similarity = (norm1 * norm2 == 0) ? 0 : dotProduct / (norm1 * norm2);
+ similarities.put(venue2, similarity);
+ }
+
+ venueSimilarities.put(venue1, similarities);
+ }
+ }
+
+ // 为目标用户生成推荐
+ public ReservationExcel generateRecommendation() {
+ // 获取目标用户的预约记录
+ List userReservations = allReservations.stream()
+ .filter(r -> r.getUserId() == targetUserId)
+ .collect(Collectors.toList());
+
+ // 如果用户预约记录少于50条,使用热门推荐
+ if (userReservations.size() < 50) {
+ return recommendPopular();
+ }
+
+ // 否则使用基于物品的协同过滤
+ buildUserVenueMatrix();
+ calculateVenueSimilarities();
+
+ // 获取用户预约过的场地
+ Set userVenues = userReservations.stream()
+ .map(ReservationExcel::getVenueId)
+ .collect(Collectors.toSet());
+
+ // 计算用户未预约过的场地的得分
+ Map venueScores = new HashMap<>();
+
+ for (int venueId : venueSimilarities.keySet()) {
+ if (userVenues.contains(venueId)) {
+ continue; // 跳过用户已经预约过的场地
+ }
+
+ double score = 0.0;
+ for (int userVenue : userVenues) {
+ double similarity = venueSimilarities.get(userVenue).get(venueId);
+ double userPreference = userVenueScores.getOrDefault(targetUserId, new HashMap<>())
+ .getOrDefault(userVenue, 0.0);
+ score += similarity * userPreference;
+ }
+
+ venueScores.put(venueId, score);
+ }
+
+ // 如果没有合适的推荐,回退到热门推荐
+ if (venueScores.isEmpty()) {
+ return recommendPopular();
+ }
+
+ // 找到得分最高的场地
+ int recommendedVenueId = Collections.max(venueScores.entrySet(), Map.Entry.comparingByValue()).getKey();
+
+ // 为该场地推荐一个合适的时间段
+ return recommendTimeSlot(recommendedVenueId, userReservations);
+ }
+
+ // 热门推荐(基于所有用户的预约记录)
+ private ReservationExcel recommendPopular() {
+ // 找出最热门的场地
+ Map venuePopularity = allReservations.stream()
+ .collect(Collectors.groupingBy(ReservationExcel::getVenueId, Collectors.counting()));
+
+ int popularVenueId = Collections.max(venuePopularity.entrySet(), Map.Entry.comparingByValue()).getKey();
+
+ // 找出该场地最热门的时间段
+ Map timeSlotPopularity = allReservations.stream()
+ .filter(r -> r.getVenueId() == popularVenueId)
+ .collect(Collectors.groupingBy(ReservationExcel::getTimeSlot, Collectors.counting()));
+
+ String popularTimeSlot = Collections.max(timeSlotPopularity.entrySet(), Map.Entry.comparingByValue()).getKey();
+
+ // 找出该场地最热门的星期几
+ Map dayPopularity = allReservations.stream()
+ .filter(r -> r.getVenueId() == popularVenueId)
+ .collect(Collectors.groupingBy(ReservationExcel::getDayOfWeek, Collectors.counting()));
+
+ String popularDay = Collections.max(dayPopularity.entrySet(), Map.Entry.comparingByValue()).getKey();
+
+ // 创建推荐记录(使用下周的同一天)
+ LocalDate nextWeek = LocalDate.now().plusWeeks(1);
+ LocalDate recommendedDate = nextWeek.with(java.time.DayOfWeek.valueOf(popularDay.toUpperCase()));
+
+ // 设置时间段
+ LocalTime startTime, endTime;
+ if (popularTimeSlot.equals("morning")) {
+ startTime = LocalTime.of(9, 0);
+ endTime = LocalTime.of(11, 0);
+ } else if (popularTimeSlot.equals("afternoon")) {
+ startTime = LocalTime.of(14, 0);
+ endTime = LocalTime.of(16, 0);
+ } else {
+ startTime = LocalTime.of(19, 0);
+ endTime = LocalTime.of(21, 0);
+ }
+
+ return new ReservationExcel(0, targetUserId, popularVenueId, startTime, endTime, recommendedDate);
+ }
+
+ // 为指定场地推荐时间段(基于用户历史偏好)
+ private ReservationExcel recommendTimeSlot(int venueId, List userReservations) {
+ // 分析用户偏好的时间段
+ Map userTimeSlotPref = userReservations.stream()
+ .collect(Collectors.groupingBy(ReservationExcel::getTimeSlot, Collectors.counting()));
+
+ String preferredTimeSlot = Collections.max(userTimeSlotPref.entrySet(), Map.Entry.comparingByValue()).getKey();
+
+ // 分析用户偏好的星期几
+ Map userDayPref = userReservations.stream()
+ .collect(Collectors.groupingBy(ReservationExcel::getDayOfWeek, Collectors.counting()));
+
+ String preferredDay = Collections.max(userDayPref.entrySet(), Map.Entry.comparingByValue()).getKey();
+
+ // 创建推荐记录(使用下周的同一天)
+ LocalDate nextWeek = LocalDate.now().plusWeeks(1);
+ LocalDate recommendedDate = nextWeek.with(java.time.DayOfWeek.valueOf(preferredDay.toUpperCase()));
+
+ // 设置时间段
+ LocalTime startTime, endTime;
+ if (preferredTimeSlot.equals("morning")) {
+ startTime = LocalTime.of(9, 0);
+ endTime = LocalTime.of(11, 0);
+ } else if (preferredTimeSlot.equals("afternoon")) {
+ startTime = LocalTime.of(14, 0);
+ endTime = LocalTime.of(16, 0);
+ } else {
+ startTime = LocalTime.of(19, 0);
+ endTime = LocalTime.of(21, 0);
+ }
+
+ return new ReservationExcel(0, targetUserId, venueId, startTime, endTime, recommendedDate);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/vo/excel/ReservationExcel.java b/src/main/java/com/example/venue_reservation_service/vo/excel/ReservationExcel.java
index e4e89e7..babbf1f 100644
--- a/src/main/java/com/example/venue_reservation_service/vo/excel/ReservationExcel.java
+++ b/src/main/java/com/example/venue_reservation_service/vo/excel/ReservationExcel.java
@@ -11,6 +11,7 @@ import java.time.LocalTime;
import com.example.venue_reservation_service.converter.LocalDateStringConverter;
import com.example.venue_reservation_service.converter.LocalTimeStringConverter;
import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -24,6 +25,7 @@ import org.springframework.format.annotation.DateTimeFormat;
@HeadFontStyle(fontName = "宋体", fontHeightInPoints = 16)
// 内容字体设置成16, 字体默认宋体
@ContentFontStyle(fontName = "宋体", fontHeightInPoints = 16)
+@AllArgsConstructor
public class ReservationExcel implements Serializable {
@ExcelProperty("预约记录编号")
@@ -46,4 +48,19 @@ public class ReservationExcel implements Serializable {
@ExcelProperty(value = "预约日期", converter = LocalDateStringConverter.class)
private LocalDate reservationDate;
+ // 获取时间段标识(上午、下午、晚上)
+ public String getTimeSlot() {
+ if (startTime.isBefore(LocalTime.of(12, 0))) {
+ return "morning";
+ } else if (startTime.isBefore(LocalTime.of(17, 0))) {
+ return "afternoon";
+ } else {
+ return "evening";
+ }
+ }
+
+ // 获取星期几
+ public String getDayOfWeek() {
+ return reservationDate.getDayOfWeek().toString().toLowerCase();
+ }
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 72ab5b6..072baa4 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,7 +1,7 @@
server:
port: 9020
-# servlet:
-# context-path: /
+ servlet:
+ context-path: /api
netty:
port: 9030
@@ -65,10 +65,25 @@ spring:
boot:
admin:
client:
- url: http://119.29.191.232:10020 # 服务端地址
+ url: http://119.29.191.232:10020/monitor # 服务端地址
username: admin # 服务端用户名
password: admin123! # 服务端密码
-
+ mail:
+ host: smtp.qq.com
+ port: 587
+ username: 3158614516@qq.com # 你的QQ邮箱地址
+ password: zjhjoakssikudejc # 不是QQ密码,是SMTP授权码
+ protocol: smtp
+ properties:
+ mail:
+ smtp:
+ auth: true
+ starttls:
+ enable: true
+ required: true
+ connectiontimeout: 5000
+ timeout: 5000
+ writetimeout: 5000
# 暴露完整监控端点
management:
endpoints:
@@ -116,7 +131,7 @@ minio:
secret-key: minioadmin
bucket: venue-sport
access:
- url: http://${localhosturl}:9000/${minio.bucket}/
+ url: https://ruanjiansc.cn/${minio.bucket}/
logging:
level:
diff --git a/src/main/resources/mapper/DeviceMapper.xml b/src/main/resources/mapper/DeviceMapper.xml
new file mode 100644
index 0000000..5e786ef
--- /dev/null
+++ b/src/main/resources/mapper/DeviceMapper.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,timestamp,temperature,
+ humidity,fan_status,warning_status
+
+
diff --git a/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java b/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java
index 1d68150..54074af 100644
--- a/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java
+++ b/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java
@@ -1,8 +1,15 @@
package com.example.venue_reservation_service;
import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.example.venue_reservation_service.domain.Information;
+import com.example.venue_reservation_service.domain.Reservation;
import com.example.venue_reservation_service.entity.PersonInfo;
import com.example.venue_reservation_service.entity.ResponseData;
+import com.example.venue_reservation_service.service.InformationService;
import com.example.venue_reservation_service.service.ReservationService;
+import com.example.venue_reservation_service.service.impl.InformationServiceImpl;
+import com.example.venue_reservation_service.service.impl.ReservationServiceImpl;
import com.example.venue_reservation_service.utils.IdentityUtil;
import com.example.venue_reservation_service.utils.MD5Util;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -13,26 +20,54 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Flux;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
@SpringBootTest
class VenueReservationServiceApplicationTests {
@Resource
- private ReservationService reservationService;
+ private InformationService informationService;
+
@Test
void contextLoads() throws Exception {
+ Map tys = new HashMap<>();
+ tys.put("南区风雨球场", "篮球");
+ tys.put("北区羽毛球场", "羽毛球");
+ tys.put("北区网球场", "北区网球");
- }
+ List names = new ArrayList<>();
+ names.add("1号场");
+ names.add("2号场");
+ names.add("3号场");
+ names.add("4号场");
+ names.add("5号场");
+ names.add("6号场");
+ names.add("7号场");
+ names.add("8号场");
+ names.add("9号场");
+ names.add("10号场");
+ names.add("11号场");
+ names.add("12号场");
+ for (String name : names) {
+ Information information = new Information();
+ information.setLocation("北区网球场");
+ information.setTypeId(1);
+ information.setType("网球");
+ information.setVenueName(name);
+ information.setLocation("北区网球场");
+ informationService.addInformation(information);
+ }
+ }
- private static final ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
- String result = IdentityUtil.getGzzxList("13767489908");
- ResponseData data = objectMapper.readValue(result, ResponseData.class);
- System.out.println(data.getMessage().get(0));
}
// public static void main(String[] args) {