物理设备管理及邮件提醒功能集成

master
chenyuepan 2 weeks ago
parent 516a676e66
commit accbc9e869

@ -229,6 +229,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId> <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies> </dependencies>
<!-- 全局 build --> <!-- 全局 build -->
<build> <build>

@ -20,6 +20,7 @@ public class InterceptorConfig implements WebMvcConfigurer {
.addPathPatterns("/school/**") .addPathPatterns("/school/**")
.excludePathPatterns("/user/test") .excludePathPatterns("/user/test")
.excludePathPatterns("/user/admin") .excludePathPatterns("/user/admin")
.excludePathPatterns("/user/reset")
.excludePathPatterns("/reservation/excel") .excludePathPatterns("/reservation/excel")
.excludePathPatterns("/reservation/payment") .excludePathPatterns("/reservation/payment")
.excludePathPatterns("/swagger-ui/index.html") .excludePathPatterns("/swagger-ui/index.html")

@ -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);
// }
}

@ -34,7 +34,7 @@ public class InformationController {
@ApiOperation("获取某个场馆内的所有场地数据") @ApiOperation("获取某个场馆内的所有场地数据")
@GetMapping("/detail") @GetMapping("/detail")
public Result detail(@RequestParam("type_id")Integer typeId){ public Result detail(@RequestParam("type_id") Integer typeId){
return informationService.getDetails(typeId); return informationService.getDetails(typeId);
} }

@ -1,22 +1,15 @@
package com.example.venue_reservation_service.controller; 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.dto.*;
import com.example.venue_reservation_service.mapper.ReservationMapper;
import com.example.venue_reservation_service.service.ReservationService; 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 com.example.venue_reservation_service.vo.Result;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.ibatis.annotations.Param;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.ws.rs.Path;
import java.io.IOException;
import java.util.List;
@RestController @RestController
@RequestMapping("/reservation") @RequestMapping("/reservation")
@Api("预约操作管理") @Api("预约操作管理")
@ -26,6 +19,14 @@ public class ReservationController {
@Resource @Resource
private ReservationService reservationService; private ReservationService reservationService;
@Resource
private ReservationMapper reservationMapper;
@GetMapping("/list")
public Result userList(@RequestParam("userId") Integer userId){
return Result.ok(reservationMapper.selectByUserId(userId)).message("查询成功");
}
@ApiOperation("添加预约信息") @ApiOperation("添加预约信息")
@PostMapping("/create") @PostMapping("/create")
public Result create(@RequestBody RAddDTO dto){ public Result create(@RequestBody RAddDTO dto){
@ -86,4 +87,11 @@ public class ReservationController {
// 调用服务层导出方法 // 调用服务层导出方法
reservationService.exportPayments(response, yearFilter); reservationService.exportPayments(response, yearFilter);
} }
@ApiOperation("智能场地推荐")
@GetMapping("/recommend")
public Result recommend(@RequestParam("userId") Integer userId){
return reservationService.recommend(userId);
}
} }

@ -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.mq.DelayMessageSender;
import com.example.venue_reservation_service.service.UserService; import com.example.venue_reservation_service.service.UserService;
import com.example.venue_reservation_service.vo.Result; 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.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@ -289,4 +290,10 @@ public class UserController {
public Result info(@PathVariable("id") Integer userId) { public Result info(@PathVariable("id") Integer userId) {
return userService.getUserInfo(userId); return userService.getUserInfo(userId);
} }
@ApiOperation("基于qq邮箱重置密码")
@PostMapping("/reset")
public Result reset(@RequestBody ResetDTO dto){
return userService.passwordReset(dto);
}
} }

@ -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;
}

@ -5,7 +5,11 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
/** /**
* *
@ -50,6 +54,13 @@ public class Information implements Serializable {
*/ */
private String qrCode; 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) @TableField(exist = false)
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

@ -0,0 +1,11 @@
package com.example.venue_reservation_service.dto;
import lombok.Data;
@Data
public class ResetDTO {
private String phone;
private String email;
}

@ -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;
}

@ -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_deviceMapper
* @createDate 2025-06-29 14:38:46
* @Entity generator.domain.Device
*/
@Mapper
public interface DeviceMapper extends BaseMapper<Device> {
@Select("SELECT * FROM venue_device ORDER BY timestamp DESC LIMIT #{limit}")
List<Device> selectLatestDevices(@Param("limit") int limit);
@Delete("DELETE FROM venue_device ORDER BY timestamp ASC LIMIT #{count}")
void deleteOldestRecords(@Param("count") int count);
}

@ -15,6 +15,7 @@ import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketSe
import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.stream.ChunkedWriteHandler;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -25,7 +26,7 @@ public class NettyChatServer {
@Value("${server.netty.port}") @Value("${server.netty.port}")
private int port; private int port;
@Autowired @Resource
private ChatServerHandler chatServerHandler; // 确保正确注入 private ChatServerHandler chatServerHandler; // 确保正确注入
private EventLoopGroup bossGroup; private EventLoopGroup bossGroup;

@ -4,6 +4,7 @@ import com.example.venue_reservation_service.entity.ChatMessage;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public interface ChatMessageRepository extends MongoRepository<ChatMessage, String> { public interface ChatMessageRepository extends MongoRepository<ChatMessage, String> {

@ -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_deviceService
* @createDate 2025-06-29 14:38:46
*/
public interface DeviceService extends IService<Device> {
Device generateDeviceData();
List<Device> getLatestDevices();
}

@ -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
);
}

@ -34,4 +34,6 @@ public interface ReservationService extends IService<Reservation> {
Result removeSingle(Integer userId, Integer id); Result removeSingle(Integer userId, Integer id);
void exportPayments(HttpServletResponse response, String yearFilter); void exportPayments(HttpServletResponse response, String yearFilter);
Result recommend(Integer userId);
} }

@ -40,4 +40,6 @@ public interface UserService extends IService<User> {
Result upload(Integer userId, MultipartFile file); Result upload(Integer userId, MultipartFile file);
Result getUserInfo(Integer userId); Result getUserInfo(Integer userId);
Result passwordReset(ResetDTO dto);
} }

@ -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_deviceService
* @createDate 2025-06-29 14:38:46
*/
@Service
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> 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<Device> getLatestDevices() {
return deviceMapper.selectLatestDevices(50);
}
}

@ -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);
}
}
}

@ -18,6 +18,7 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -29,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream; import java.io.InputStream;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
@ -78,6 +80,9 @@ public class InformationServiceImpl extends ServiceImpl<InformationMapper, Infor
@Resource @Resource
private AdminMapper adminMapper; private AdminMapper adminMapper;
@Value("${access.url}")
private String imgPrefix;
@Override @Override
public Result getTypes(PageDTO dto) { public Result getTypes(PageDTO dto) {
Page<Type> page = new Page<>(dto.getCurrent(), dto.getSize()); Page<Type> page = new Page<>(dto.getCurrent(), dto.getSize());
@ -106,6 +111,11 @@ public class InformationServiceImpl extends ServiceImpl<InformationMapper, Infor
.select(Price::getPrice) .select(Price::getPrice)
.leftJoin(Price.class, Price::getVenueId, Information::getId); .leftJoin(Price.class, Price::getVenueId, Information::getId);
List<Information> informationList = list(wrapper); List<Information> informationList = list(wrapper);
informationList.forEach(item -> {
if(StringUtils.isNotBlank(item.getImgUrl())){
item.setImgUrl(imgPrefix + item.getImgUrl());
}
});
return Result.ok(informationList).message("场地信息获取成功"); return Result.ok(informationList).message("场地信息获取成功");
} }
@ -131,7 +141,14 @@ public class InformationServiceImpl extends ServiceImpl<InformationMapper, Infor
page = page(page, wrapper); page = page(page, wrapper);
QueryVO<Information> vo = new QueryVO(); QueryVO<Information> vo = new QueryVO();
vo.setTotal(page.getTotal()); vo.setTotal(page.getTotal());
vo.setList(page.getRecords()); List<Information> 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("场地信息查询成功"); return Result.ok(vo).message("场地信息查询成功");
} }
@ -144,16 +161,17 @@ public class InformationServiceImpl extends ServiceImpl<InformationMapper, Infor
if (Optional.ofNullable(one).isPresent()) { if (Optional.ofNullable(one).isPresent()) {
return Result.fail().message("当前已经存在该名称的场地"); return Result.fail().message("当前已经存在该名称的场地");
} }
information.setImgUrl(""); information.setImgUrl("");
information.setUpdateTime(LocalDateTime.now());
save(information); save(information);
// 生成场地二维码图片 // 生成场地二维码图片
MultipartFile file = QRCodeGenerator.generateQRCode(information.getId() + ""); MultipartFile file = QRCodeGenerator.generateQRCode(information.getId() + "");
// 保存二维码图片 // 保存二维码图片
try { try {
minioUtil.uploadFile(file,file.getOriginalFilename() ,file.getContentType()); minioUtil.uploadFile(file ,file.getOriginalFilename() ,file.getContentType());
String url = minioUtil.getPresignedObjectUrl(file.getOriginalFilename());
information.setQrCode(url); information.setQrCode(file.getOriginalFilename());
updateById(information); updateById(information);
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();
@ -557,16 +575,8 @@ public class InformationServiceImpl extends ServiceImpl<InformationMapper, Infor
if (Optional.ofNullable(information).isEmpty()) { if (Optional.ofNullable(information).isEmpty()) {
return Result.fail().message("场地信息不存在"); return Result.fail().message("场地信息不存在");
} }
// 对照片重新命名
// 获取文件后缀名
String extension = FilenameUtils.getExtension( file.getOriginalFilename());
try { try {
// 重新生成文件名称
String filename = UUID.randomUUID() + "-" + id + "." + extension;
// 调用minio接口上传照片
minioUtil.uploadFile(file, filename, file.getContentType());
String url = minioUtil.getPresignedObjectUrl(filename);
// 删除原来的照片 // 删除原来的照片
String imgUrl = information.getImgUrl(); String imgUrl = information.getImgUrl();
if (imgUrl!=null && !"".equals(imgUrl)){ if (imgUrl!=null && !"".equals(imgUrl)){
@ -575,7 +585,15 @@ public class InformationServiceImpl extends ServiceImpl<InformationMapper, Infor
String img = split[split.length - 1]; String img = split[split.length - 1];
minioUtil.removeFile(img); minioUtil.removeFile(img);
} }
information.setImgUrl(url);
// 获取文件后缀名
String extension = FilenameUtils.getExtension( file.getOriginalFilename());
// 重新生成文件名称
String filename = UUID.randomUUID() + "-" + id + "." + extension;
// 调用minio接口上传照片
minioUtil.uploadFile(file, filename, file.getContentType());
information.setImgUrl(filename);
updateById(information); updateById(information);
} catch (Exception e) { } catch (Exception e) {

@ -12,12 +12,14 @@ import com.example.venue_reservation_service.dto.DelDTO;
import com.example.venue_reservation_service.dto.RAddDTO; import com.example.venue_reservation_service.dto.RAddDTO;
import com.example.venue_reservation_service.dto.ReservationDTO; import com.example.venue_reservation_service.dto.ReservationDTO;
import com.example.venue_reservation_service.dto.SignInDTO; import com.example.venue_reservation_service.dto.SignInDTO;
import com.example.venue_reservation_service.entity.RecommendVO;
import com.example.venue_reservation_service.mapper.*; import com.example.venue_reservation_service.mapper.*;
import com.example.venue_reservation_service.mq.DelayMessageSender; import com.example.venue_reservation_service.mq.DelayMessageSender;
import com.example.venue_reservation_service.service.*; import com.example.venue_reservation_service.service.*;
import com.example.venue_reservation_service.utils.DateUtil; import com.example.venue_reservation_service.utils.DateUtil;
import com.example.venue_reservation_service.utils.DownExcel; import com.example.venue_reservation_service.utils.DownExcel;
import com.example.venue_reservation_service.utils.ScheduleUtil; import com.example.venue_reservation_service.utils.ScheduleUtil;
import com.example.venue_reservation_service.utils.VenueRecommender;
import com.example.venue_reservation_service.vo.*; import com.example.venue_reservation_service.vo.*;
import com.example.venue_reservation_service.vo.excel.PaymentExportDTO; import com.example.venue_reservation_service.vo.excel.PaymentExportDTO;
import com.example.venue_reservation_service.vo.excel.ReservationExcel; import com.example.venue_reservation_service.vo.excel.ReservationExcel;
@ -68,9 +70,6 @@ public class ReservationServiceImpl extends ServiceImpl<ReservationMapper, Reser
@Resource @Resource
private UserService userService; private UserService userService;
@Resource
private SchoolMapper schoolMapper;
@Resource @Resource
private ScheduleUtil scheduleUtil ; private ScheduleUtil scheduleUtil ;
@ -83,6 +82,11 @@ public class ReservationServiceImpl extends ServiceImpl<ReservationMapper, Reser
@Resource @Resource
private DelayMessageSender delayMessageSender; private DelayMessageSender delayMessageSender;
@Resource
private ReservationMapper reservationMapper;
@Resource
private PriceService priceService;
@Transactional @Transactional
@Override @Override
@ -203,10 +207,10 @@ public class ReservationServiceImpl extends ServiceImpl<ReservationMapper, Reser
// 设置定时任务(即到预约结束时间进行检查签到情况) // 设置定时任务(即到预约结束时间进行检查签到情况)
// 任务 // 任务
Runnable task = () -> { // Runnable task = () -> {
// 处理当前预约 // // 处理当前预约
dealReservation(reservation.getId()); // dealReservation(reservation.getId());
}; // };
// scheduleUtil.scheduleOneTimeTask(task, reservationDate, end); // scheduleUtil.scheduleOneTimeTask(task, reservationDate, end);
// // 定时任务解耦设计 // // 定时任务解耦设计
@ -563,6 +567,21 @@ public class ReservationServiceImpl extends ServiceImpl<ReservationMapper, Reser
.in(Payment::getSituation, "预约花费", "账户余额充值") .in(Payment::getSituation, "预约花费", "账户余额充值")
.between(Payment::getTime, startOfYear, endOfYear)); .between(Payment::getTime, startOfYear, endOfYear));
} }
@Override
public Result recommend(Integer userId) {
long count = count(Wrappers.<Reservation>lambdaQuery().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("推荐结果");
}
} }

@ -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.Information;
import com.example.venue_reservation_service.domain.InformationTime; import com.example.venue_reservation_service.domain.InformationTime;
import com.example.venue_reservation_service.dto.PageDTO; 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.InformationMapper;
import com.example.venue_reservation_service.mapper.TypeMapper; import com.example.venue_reservation_service.mapper.TypeMapper;
import com.example.venue_reservation_service.domain.Type; import com.example.venue_reservation_service.domain.Type;
@ -41,11 +42,23 @@ public class TypeServiceImpl extends ServiceImpl<TypeMapper, Type>
@Resource @Resource
private InformationMapper informationMapper; private InformationMapper informationMapper;
@Value("${access.url}")
private String imgPrefix;
@Override @Override
public Result queryTypes(PageDTO dto) { public Result queryTypes(PageDTO dto) {
Page<Type> page = new Page<>(dto.getCurrent(), dto.getSize()); Page<Type> page = new Page<>(dto.getCurrent(), dto.getSize());
page = page(page, null); page = page(page, null);
return Result.ok(new QueryVO<Type>(page.getRecords(), page.getTotal())).message("场馆信息获取成功"); List<Type> records = page.getRecords();
records.forEach(item -> {
if(StringUtils.isNotBlank(item.getImgUrl())){
item.setImgUrl(imgPrefix + item.getImgUrl());
}
});
QueryVO<Type> vo = new QueryVO<>();
vo.setList(records);
vo.setTotal(page.getTotal());
return Result.ok(vo).message("场馆信息获取成功");
} }
private boolean isSame(Type type){ private boolean isSame(Type type){
@ -103,35 +116,28 @@ public class TypeServiceImpl extends ServiceImpl<TypeMapper, Type>
if (Optional.ofNullable(type).isEmpty()) { if (Optional.ofNullable(type).isEmpty()) {
return Result.fail().message("场馆信息不存在"); 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()); String extension = FilenameUtils.getExtension( file.getOriginalFilename());
try { try {
// 重新生成文件名称 // 重新生成文件名称
String filename = UUID.randomUUID() + "-" + id + "." + extension; String filename = UUID.randomUUID() + "-" + id + "." + extension;
// 调用minio接口上传照片 // 调用minio接口上传照片
minioUtil.uploadFile(file, filename, file.getContentType()); minioUtil.uploadFile(file, filename, file.getContentType());
String url = minioUtil.getPresignedObjectUrl(filename); type.setImgUrl(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);
updateById(type); updateById(type);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return Result.fail().message("图片上传失败"); return Result.fail().message("图片上传失败");
} }
return Result.ok().message("图片上传成功"); return Result.ok().message("图片上传成功");
} }

@ -21,10 +21,14 @@ import com.example.venue_reservation_service.vo.Result;
import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@ -65,12 +69,21 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
@Value("${spring.application.name}") @Value("${spring.application.name}")
private String name; private String name;
@Value("${access.url}")
private String imgUrl;
@Resource @Resource
private MinioUtil minioUtil; private MinioUtil minioUtil;
@Resource
private EmailService emailService;
@Autowired
private RedisTemplate<String, String> 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 @Override
public Result getOpenId(String code) { public Result getOpenId(String code) {
Map<String, String> vo = restTemplate.getForObject("https://api.weixin.qq.com/sns/jscode2session?"+ Map<String, String> vo = restTemplate.getForObject("https://api.weixin.qq.com/sns/jscode2session?"+
@ -102,6 +115,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
user.setAccessCode(PasswordGenerator.generatePassword()); user.setAccessCode(PasswordGenerator.generatePassword());
user.setRegisterTime(LocalDateTime.now()); user.setRegisterTime(LocalDateTime.now());
user.setIsUpload(0); user.setIsUpload(0);
user.setAvatar("");
save(user); save(user);
// 添加新的账户余额信息 // 添加新的账户余额信息
Balance balance = new Balance(0.0, user.getId()); Balance balance = new Balance(0.0, user.getId());
@ -113,7 +127,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
Double money = balanceService.getOne(Wrappers.<Balance>lambdaQuery().eq(Balance::getUserId, user.getId())) Double money = balanceService.getOne(Wrappers.<Balance>lambdaQuery().eq(Balance::getUserId, user.getId()))
.getBalance(); .getBalance();
if(user.getIsUpload() == 1){ if(user.getIsUpload() == 1){
user.setAvatar(imgUrl + user.getAvatar()); user.setAvatar(imgPrefix + user.getAvatar());
} }
user.setBalance(money); user.setBalance(money);
loginVo.setUser(user); loginVo.setUser(user);
@ -132,24 +146,24 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
} }
String password = dto.getPasswd(); String password = dto.getPasswd();
String redisKey = "login_lock:" + account; // 使用固定前缀 String redisKey = LOGIN_LOCK_PREFIX + account; // 使用固定前缀
if (!user.getAccessCode().equals(password)) { if (!user.getAccessCode().equals(password)) {
// 处理密码错误的情况 // 处理密码错误的情况
if (redisUtil.hasKey(redisKey)) { if (redisTemplate.hasKey(redisKey)) {
String[] parts = String.valueOf(redisUtil.get(redisKey)).split("-"); String[] parts = String.valueOf(redisTemplate.opsForValue().get(redisKey)).split("-");
int failCount = Integer.parseInt(parts[0]); int failCount = Integer.parseInt(parts[0]);
long firstFailTime = Long.parseLong(parts[1]); long firstFailTime = Long.parseLong(parts[1]);
// 错误次数已达上限 // 错误次数已达上限
if (failCount >= 3) { if (failCount >= MAX_LOGIN_ATTEMPTS) {
long lockTime = 5 * 60 * 1000; // 5分钟毫秒数 long lockTime = LOCK_DURATION_MS; // 5分钟毫秒数
long remainMillis = lockTime - (System.currentTimeMillis() - firstFailTime); long remainMillis = lockTime - (System.currentTimeMillis() - firstFailTime);
if (remainMillis > 0) { if (remainMillis > 0) {
return Result.fail().message("该账号已被锁定,请在 " + (remainMillis / 1000) + " 秒后重新登录"); return Result.fail().message("该账号已被锁定,请在 " + (remainMillis / 1000) + " 秒后重新登录");
} else { } else {
// 锁定已过期,重置计数器 // 锁定已过期,重置计数器
redisUtil.remove(redisKey); redisTemplate.delete(redisKey);
} }
} }
} }
@ -157,8 +171,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
// 更新错误计数器(包含首次错误情况) // 更新错误计数器(包含首次错误情况)
int newCount = 1; int newCount = 1;
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
if (redisUtil.hasKey(redisKey)) { if (redisTemplate.hasKey(redisKey)) {
String[] parts = String.valueOf(redisUtil.get(redisKey)).split("-"); String[] parts = String.valueOf(redisTemplate.opsForValue().get(redisKey)).split("-");
newCount = Integer.parseInt(parts[0]) + 1; newCount = Integer.parseInt(parts[0]) + 1;
timestamp = Long.parseLong(parts[1]); // 保持第一次错误时间 timestamp = Long.parseLong(parts[1]); // 保持第一次错误时间
} }
@ -166,9 +180,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
// 更新Redis仅首次设置过期时间 // 更新Redis仅首次设置过期时间
String value = newCount + "-" + timestamp; String value = newCount + "-" + timestamp;
if (newCount == 1) { if (newCount == 1) {
redisUtil.set(redisKey, value, 5 * 60); redisTemplate.opsForValue().set(redisKey, value, Duration.ofMinutes(5));
} else { } else {
redisUtil.set(redisKey, value); // 更新次数但保留原TTL redisTemplate.opsForValue().set(redisKey, value); // 更新次数但保留原TTL
} }
return Result.fail().message("账号密码不匹配"); return Result.fail().message("账号密码不匹配");
@ -176,13 +190,13 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
// 密码正确,登录成功 // 密码正确,登录成功
// 清除登录错误计数 // 清除登录错误计数
if (redisUtil.hasKey(redisKey)) { if (redisTemplate.hasKey(redisKey)) {
redisUtil.remove(redisKey); redisTemplate.delete(redisKey);
} }
user.setAccessCode(""); user.setAccessCode("");
if (user.getIsUpload() == 1) { if (user.getIsUpload() == 1) {
user.setAvatar(imgUrl + user.getAvatar()); user.setAvatar(imgPrefix + user.getAvatar());
} }
LoginVo vo = new LoginVo(); LoginVo vo = new LoginVo();
vo.setUser(user); vo.setUser(user);
@ -190,6 +204,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
return Result.ok(vo).message("登录成功"); return Result.ok(vo).message("登录成功");
} }
/** /**
* *
* usernamephoneemail * usernamephoneemail
@ -415,7 +430,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
user.setIsUpload(1); user.setIsUpload(1);
user.setAvatar(filename); user.setAvatar(filename);
updateById(user); updateById(user);
return Result.ok(imgUrl + user.getAvatar()).message("头像上传成功"); return Result.ok(imgPrefix + user.getAvatar()).message("头像上传成功");
} catch (MinioRemoveException | ImageValidateException | MinioUploadException e) { } catch (MinioRemoveException | ImageValidateException | MinioUploadException e) {
throw e; // 抛出给全局异常处理器 throw e; // 抛出给全局异常处理器
@ -440,10 +455,32 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
if (Optional.ofNullable(balance).isPresent()) { if (Optional.ofNullable(balance).isPresent()) {
user.setBalance(balance.getBalance()); user.setBalance(balance.getBalance());
} }
user.setAvatar(imgUrl + user.getAvatar()); if(StringUtils.isNotBlank(user.getAvatar())){
user.setAvatar(imgPrefix + user.getAvatar());
}
user.setAccessCode(""); user.setAccessCode("");
return Result.ok(user).message("用户信息查询成功"); return Result.ok(user).message("用户信息查询成功");
} }
@Override
public Result passwordReset(ResetDTO dto) {
User user = getOne(Wrappers.<User>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("邮件发送成功");
}
} }

@ -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.QueryVO;
import com.example.venue_reservation_service.vo.Result; import com.example.venue_reservation_service.vo.Result;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -46,12 +47,21 @@ public class VenueHotServiceImpl extends ServiceImpl<VenueHotMapper, VenueHot>
@Resource @Resource
private PaymentMapper paymentMapper; private PaymentMapper paymentMapper;
@Value("${access.url}")
private String imgPrefix;
@Override @Override
public Result getHots(PageDTO dto) { public Result getHots(PageDTO dto) {
Page<VenueHot> page = new Page<>(dto.getCurrent(), dto.getSize()); Page<VenueHot> page = new Page<>(dto.getCurrent(), dto.getSize());
page = page(page, null); page = page(page, null);
QueryVO<VenueHot> vo = new QueryVO<>(); QueryVO<VenueHot> vo = new QueryVO<>();
vo.setList(page.getRecords()); List<VenueHot> records = page.getRecords();
records.forEach(item ->{
if(StringUtils.isNotBlank(item.getImgUrl())){
item.setImgUrl(imgPrefix + item.getImgUrl());
}
});
vo.setList(records);
vo.setTotal(page.getTotal()); vo.setTotal(page.getTotal());
return Result.ok(vo).message("查询成功"); return Result.ok(vo).message("查询成功");
} }
@ -98,9 +108,8 @@ public class VenueHotServiceImpl extends ServiceImpl<VenueHotMapper, VenueHot>
String filename = System.currentTimeMillis() + "-" + id + "." +split[split.length - 1] ; String filename = System.currentTimeMillis() + "-" + id + "." +split[split.length - 1] ;
minioUtil.uploadFile(file, filename, file.getContentType()); minioUtil.uploadFile(file, filename, file.getContentType());
String url = minioUtil.getPresignedObjectUrl(filename);
System.out.println(); hot.setImgUrl(filename);
hot.setImgUrl(url);
updateById(hot); updateById(hot);
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();

@ -156,36 +156,6 @@ public class RedisUtil {
return redisTemplate.opsForSet().members(key); 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");
}
/** /**
* *

@ -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<Integer, Map<Integer, Double>> userVenueScores;
// 物品-物品相似度矩阵
private Map<Integer, Map<Integer, Double>> venueSimilarities;
// 所有预约记录
private List<ReservationExcel> allReservations;
// 目标用户ID
private int targetUserId;
public VenueRecommender(List<ReservationExcel> reservations, int targetUserId) {
this.allReservations = reservations;
this.targetUserId = targetUserId;
this.userVenueScores = new HashMap<>();
this.venueSimilarities = new HashMap<>();
}
// 构建用户-物品评分矩阵
private void buildUserVenueMatrix() {
// 按用户分组
Map<Integer, List<ReservationExcel>> reservationsByUser = allReservations.stream()
.collect(Collectors.groupingBy(ReservationExcel::getUserId));
for (Map.Entry<Integer, List<ReservationExcel>> entry : reservationsByUser.entrySet()) {
int userId = entry.getKey();
List<ReservationExcel> userReservations = entry.getValue();
// 按场地分组,计算用户对每个场地的偏好得分
Map<Integer, Long> venueCounts = userReservations.stream()
.collect(Collectors.groupingBy(ReservationExcel::getVenueId, Collectors.counting()));
// 找到最大计数用于归一化
long maxCount = venueCounts.values().stream().max(Long::compare).orElse(1L);
Map<Integer, Double> venueScores = new HashMap<>();
for (Map.Entry<Integer, Long> 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<Integer> allVenues = allReservations.stream()
.map(ReservationExcel::getVenueId)
.collect(Collectors.toSet());
// 为每个场地构建特征向量(基于用户评分)
Map<Integer, Map<Integer, Double>> venueUserVectors = new HashMap<>();
// 初始化
for (int venueId : allVenues) {
venueUserVectors.put(venueId, new HashMap<>());
}
// 填充向量
for (Map.Entry<Integer, Map<Integer, Double>> userEntry : userVenueScores.entrySet()) {
int userId = userEntry.getKey();
Map<Integer, Double> venueScores = userEntry.getValue();
for (Map.Entry<Integer, Double> venueEntry : venueScores.entrySet()) {
int venueId = venueEntry.getKey();
double score = venueEntry.getValue();
venueUserVectors.get(venueId).put(userId, score);
}
}
// 计算场地之间的相似度
for (int venue1 : allVenues) {
Map<Integer, Double> vector1 = venueUserVectors.get(venue1);
Map<Integer, Double> similarities = new HashMap<>();
for (int venue2 : allVenues) {
if (venue1 == venue2) {
similarities.put(venue2, 1.0); // 自相似度为1
continue;
}
Map<Integer, Double> vector2 = venueUserVectors.get(venue2);
// 计算余弦相似度
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 收集所有共同的用户
Set<Integer> 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<ReservationExcel> userReservations = allReservations.stream()
.filter(r -> r.getUserId() == targetUserId)
.collect(Collectors.toList());
// 如果用户预约记录少于50条使用热门推荐
if (userReservations.size() < 50) {
return recommendPopular();
}
// 否则使用基于物品的协同过滤
buildUserVenueMatrix();
calculateVenueSimilarities();
// 获取用户预约过的场地
Set<Integer> userVenues = userReservations.stream()
.map(ReservationExcel::getVenueId)
.collect(Collectors.toSet());
// 计算用户未预约过的场地的得分
Map<Integer, Double> 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<Integer, Long> venuePopularity = allReservations.stream()
.collect(Collectors.groupingBy(ReservationExcel::getVenueId, Collectors.counting()));
int popularVenueId = Collections.max(venuePopularity.entrySet(), Map.Entry.comparingByValue()).getKey();
// 找出该场地最热门的时间段
Map<String, Long> 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<String, Long> 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<ReservationExcel> userReservations) {
// 分析用户偏好的时间段
Map<String, Long> userTimeSlotPref = userReservations.stream()
.collect(Collectors.groupingBy(ReservationExcel::getTimeSlot, Collectors.counting()));
String preferredTimeSlot = Collections.max(userTimeSlotPref.entrySet(), Map.Entry.comparingByValue()).getKey();
// 分析用户偏好的星期几
Map<String, Long> 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);
}
}

@ -11,6 +11,7 @@ import java.time.LocalTime;
import com.example.venue_reservation_service.converter.LocalDateStringConverter; import com.example.venue_reservation_service.converter.LocalDateStringConverter;
import com.example.venue_reservation_service.converter.LocalTimeStringConverter; import com.example.venue_reservation_service.converter.LocalTimeStringConverter;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
@ -24,6 +25,7 @@ import org.springframework.format.annotation.DateTimeFormat;
@HeadFontStyle(fontName = "宋体", fontHeightInPoints = 16) @HeadFontStyle(fontName = "宋体", fontHeightInPoints = 16)
// 内容字体设置成16, 字体默认宋体 // 内容字体设置成16, 字体默认宋体
@ContentFontStyle(fontName = "宋体", fontHeightInPoints = 16) @ContentFontStyle(fontName = "宋体", fontHeightInPoints = 16)
@AllArgsConstructor
public class ReservationExcel implements Serializable { public class ReservationExcel implements Serializable {
@ExcelProperty("预约记录编号") @ExcelProperty("预约记录编号")
@ -46,4 +48,19 @@ public class ReservationExcel implements Serializable {
@ExcelProperty(value = "预约日期", converter = LocalDateStringConverter.class) @ExcelProperty(value = "预约日期", converter = LocalDateStringConverter.class)
private LocalDate reservationDate; 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();
}
} }

@ -1,7 +1,7 @@
server: server:
port: 9020 port: 9020
# servlet: servlet:
# context-path: / context-path: /api
netty: netty:
port: 9030 port: 9030
@ -65,10 +65,25 @@ spring:
boot: boot:
admin: admin:
client: client:
url: http://119.29.191.232:10020 # 服务端地址 url: http://119.29.191.232:10020/monitor # 服务端地址
username: admin # 服务端用户名 username: admin # 服务端用户名
password: admin123! # 服务端密码 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: management:
endpoints: endpoints:
@ -116,7 +131,7 @@ minio:
secret-key: minioadmin secret-key: minioadmin
bucket: venue-sport bucket: venue-sport
access: access:
url: http://${localhosturl}:9000/${minio.bucket}/ url: https://ruanjiansc.cn/${minio.bucket}/
logging: logging:
level: level:

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.venue_reservation_service.mapper.DeviceMapper">
<resultMap id="BaseResultMap" type="com.example.venue_reservation_service.domain.Device">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="timestamp" column="timestamp" jdbcType="TIMESTAMP"/>
<result property="temperature" column="temperature" jdbcType="FLOAT"/>
<result property="humidity" column="humidity" jdbcType="FLOAT"/>
<result property="fanStatus" column="fan_status" jdbcType="TINYINT"/>
<result property="warningStatus" column="warning_status" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,timestamp,temperature,
humidity,fan_status,warning_status
</sql>
</mapper>

@ -1,8 +1,15 @@
package com.example.venue_reservation_service; package com.example.venue_reservation_service;
import cn.hutool.json.JSONUtil; 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.PersonInfo;
import com.example.venue_reservation_service.entity.ResponseData; 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.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.IdentityUtil;
import com.example.venue_reservation_service.utils.MD5Util; import com.example.venue_reservation_service.utils.MD5Util;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -13,26 +20,54 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest @SpringBootTest
class VenueReservationServiceApplicationTests { class VenueReservationServiceApplicationTests {
@Resource @Resource
private ReservationService reservationService; private InformationService informationService;
@Test @Test
void contextLoads() throws Exception { void contextLoads() throws Exception {
Map<String, String> tys = new HashMap<>();
tys.put("南区风雨球场", "篮球");
tys.put("北区羽毛球场", "羽毛球");
tys.put("北区网球场", "北区网球");
} List<String> 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 { 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) { // public static void main(String[] args) {

Loading…
Cancel
Save