SpringBoot+Vue打造动漫活动预约系统----后端
目录
Ⅰ、设计数据库表结构
activities表
admins表
bookings表
user表
Ⅱ、创建Spring Bot 项目
Ⅲ、导入maven依赖
Ⅳ、编写项目配置文件
Ⅴ、设计系统结构框架
Ⅵ、设计模型类
1.User
2.Activity
3.Booking
4.Admin
5.Account
6.ActivitySearchBean
7.BookingDTO
Ⅶ、设计业务接口
1.UserService
2.ActivityService
3.BookingService
4.AdminService
5.UploadService
Ⅷ、实现业务接口
1.UserServiceImpl
2.ActivityServiceImpl
3.BookingServiceImpl
4.AdminServiceImpl
5.UploadService
Ⅸ、进行数据访问
1.UserMapper
2.ActivityMapper
3.BookingMapper
4.AdminMapper
5.BookingMapper.xml
Ⅹ、进行报错信息和分页等配置
1.GlobalExceptionHandler
2.BusinessException
3.CommonConfig
Ⅺ、使用工具类设置统一返回值和jwt令牌
1.jsonResult
2.JwtUtils
Ⅻ、完成api接口
1.UserApi
2.ActivityApi
3.BookingApi
4.AdminApi
5.HomeApi
十三、验证前端Jwt
JwtInterceptor
十四、拓展功能
自定义Spring Bot启动横幅
使用工具MySQL,navicat,IDEA,Vue,Spring Bot,myBatis,myBatis-plus,maven,framwork,lombok、redis、jwt、StrongPasswordEncryptor。
Ⅰ、设计数据库表结构
activities表
admins表
bookings表
user表
Ⅱ、创建Spring Bot 项目
Ⅲ、导入maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.4</version><relativePath/></parent><groupId>com.situ</groupId><artifactId>AnimeBooking</artifactId><version>1.0.0</version><name>AnimeBooking</name><description>动漫活动预约管理系统</description><properties><java.version>21</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>3.0.5</version><scope>test</scope></dependency><!--mybatis plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.12</version></dependency><!--mybatis-plus的自动分页插件--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser</artifactId><version>3.5.12</version></dependency><!--自动参数校验--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- <dependency>-->
<!-- <groupId>com.github.pagehelper</groupId>-->
<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>-->
<!-- <version>2.1.1</version>-->
<!-- </dependency>--><!--验证码--><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version></dependency><!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--spring缓存--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!--jasypt加密--><!--加密解密库--><dependency><groupId>org.jasypt</groupId><artifactId>jasypt</artifactId><version>1.9.3</version></dependency><!--JWT令牌--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.5.0</version></dependency><!--json序列化和反序列化--><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.57</version></dependency><!--caffeine缓存--><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency><!--mybatis桥接缓存--><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-caffeine</artifactId><version>1.2.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></path></annotationProcessorPaths></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
-
spring-boot-starter-parent:
这是所有Spring Boot项目的父POM -
spring-boot-starter-web:Web应用核心依赖,包含
Spring MVC、Tomcat(内嵌Web服务器)、Jackson(JSON序列化/反序列化)、验证框架等 -
mybatis-spring-boot-starter:MyBatis框架与Spring Boot的集成启动器。
-
spring-boot-devtools:开发工具,可以用于热启动。
-
mysql-connector-j:MySQL数据库的JDBC驱动,用于连接MySQL数据库。
-
lombok:代码简化工具,
通过注解(如@Data
,@Getter
,@Setter
)使用,让代码变得非常简洁。 -
spring-boot-starter-test
&mybatis-spring-boot-starter-test:测试用的依赖
-
mybatis-plus-spring-boot3-starter:mybatis-plus,用于简化mybatis操作,内嵌多种方法用于进行单表查询
-
mybatis-plus-jsqlparser:mybatis-plus的分页插件,可以在进行数据库查询前自动完成分页属性的查询操作
-
spring-boot-starter-validation:Java Bean验证,使用注解如(
@NotNull
,@Email
,@Size)对模型类接受的参数进行自动校验
-
easy-captcha:生成图形验证码的库,用于登录时验证码的生成使用
-
spring-boot-starter-data-redis:redis缓存的依赖类,使用redis保存数据时要添加此依赖
-
spring-boot-starter-cache:spring缓存的依赖类,使用redis缓存时需要引入此类辅助
-
jasypt:java加密库,用于对账号密码的加密和解密
-
java-jwt:
用于创建和验证JSON Web Tokens (JWT) 的库,在登录成功后服务器生成jwt令牌返回给客户端,客户端后续的请求都携带此令牌来证明自己的身份。 -
fastjson2:用于
进行Java对象与JSON字符串之间的序列化和反序列化。 -
caffeine:咖啡因,类似于redis也是用于进行数据缓存。
-
mybatis-caffeine:
是MyBatis的一个缓存适配器。用于将MyBatis的二级缓存实现转为Caddeine缓存。 -
pagehelper-spring-boot-starter:专门的自动分页插件,会与mybatis-plus的分页插件冲突。
Ⅳ、编写项目配置文件
spring:application:name: AnimeBooking#配置数据源datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/anime?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8username: rootpassword: 123456#配置redisdata:redis:host: localhostport: 6379password: 123456database: 0timeout: 3000ms#spring web 静态资源路径web:resources:static-locations: classpath:/static/ , classpath:/resources/, file:/${upload.location}#配置mybatis-plus
mybatis-plus:configuration:#日志前缀log-prefix: mybatis.#日志实现类log-impl: org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl#开启驼峰式命名map-underscore-to-camel-case: true#开启延迟缓存 可以解决n+1问题lazy-loading-enabled: true#关闭二级缓存cache-enabled: false#模型别名,可以使用简称type-aliases-package: com.situ.animebooking.model#配置mapper文件路径mapper-locations: classpath*:/mapper/**/*.xmlmybatis:type-aliases-package: com.situ.animebooking.modelmapper-locations: classpath*:/mapper/**/*.xmlconfiguration:map-underscore-to-camel-case: true#日志输出
logging:level:mybatis: debugserver:port: 8080#配置文件上传
upload:location: D:/upload/max-size: 20MB#jwt密钥
jwt:secretKey: 3044130958
- spring.application.name:定义了应用的名称。
- server.port:指定列tomcat监听的端口号,客户端将通过这个端口访问你的应用
- spring.datasource:
- dariver-class-name: com.mysql.cj.jdbc.Driver :指定mysql的JDBC驱动类
- url: jdbc:mysql://localhost:3306/{数据库名}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8:数据库链接字符串
- username: 数据库用户名
- password: 数据库密码
- spring.data.redis:
- host: localhost :redis服务器地址
- port: Redis服务器端口
- password: Redis访问密码(需要自己在redis中提前设置)
- database: 使用redis的哪个数据库
- timeout: 连接超时时间
- spring.web.resources.static-locations: 设置springbot可以获取静态资源的地址
- mybatis:
- type-aliases-package: 配置模型类的别名包。在XML中可以直接使用类
- mapper-locations: Mapper XML 文件的位置
- configuration.map-underscore-to-camel-case: true 开启自动驼峰命名映射
- mybatis-plus:
- configuration:
- log-prefis: mybatis日志前缀,方便再日志中筛选
- log-impl: org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl 日志实现类
- configuration.map-underscore-to-camel-case: true 开启自动驼峰命名映射
- lazy-loading-enabled: true 开启延迟加载,关键对象只有在使用时才查询,解决N+1问题
- cache-enabled: false 关闭MyBatis二级缓存,(使用Redis或Caffeine作为更高级的缓存)
- type-aliases-package: 配置模型类的别名包。在XML中可以直接使用类
- mapper-locations: Mapper XML 文件的位置
- configuration:
- logging.level.mybatis: debug 将mybatis的日志级别设置为DEBUG,这会打印出执行的SQL语句和参数,适合开发阶段调试
- upload:
- location: 文件上传后存储再服务器本地的根目录
- jwt:secreKey: 用于生成和解析JWT令牌的密钥
Ⅴ、设计系统结构框架
- AnimeBookingApplication :Spring Boot 应用启动类,是整个程序的入口
- 控制器层:api(controller) :负责接收http请求并返回响应
- 处理前端请求
- 调用服务层完成业务逻辑
- 返回数据
- 服务层:service 业务逻辑接口定义
- impl:接口具体实现
- 数据访问层:dao :包含数据库交互的接口
- mapper(impl):定义数据库操作方法
- 模型层: model: 包含数据模型/实体类
- DTO:数据传输对象
- search:搜索条件相关的模型
- 通用组件:common:存放通用组件
- 配置类:config 包含各种配置
- 拦截器配置
- mybatis-plus自动分页配置
- 统一异常返回配置
- 工具类:util:包含各种工具类
- 字符串处理
- 日企处理
- 返回值处理
- jwt工具
- mapper:mybatis的xml映射文件,包含sql语句
Ⅵ、设计模型类
1.User
package com.situ.animebooking.model;import com.baomidou.mybatisplus.annotation.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.Setter;import java.time.LocalDate;@TableName("users")
@Setter
@Getter
public class User {@TableId(value = "user_id",type = IdType.AUTO)private Integer userId;@NotBlank(message = "用户名不能为空")@TableField(value = "user_name", condition = SqlCondition.LIKE, whereStrategy = FieldStrategy.NOT_EMPTY) //相同可以不用private String userName;@NotBlank(message = "用户性别不能为空")@Pattern(regexp = "^[男|女]$", message = "性别只能是男或女")@TableField(value = "sex", condition = SqlCondition.EQUAL, whereStrategy = FieldStrategy.NOT_EMPTY)private String sex;@NotBlank(message = "联系方式不能为空")@Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式不正确")@TableField(condition = SqlCondition.LIKE, whereStrategy = FieldStrategy.NOT_EMPTY)private String phone;private String email;private LocalDate birthday;
}
2.Activity
package com.situ.animebooking.model;import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;import java.sql.Time;
import java.time.LocalDate;@TableName("activities")
@Getter
@Setter
public class Activity {@TableId(value = "activity_id", type = IdType.AUTO)private Integer activityId;@TableField(condition = SqlCondition.LIKE, whereStrategy = FieldStrategy.NOT_EMPTY)private String title;private String description;private LocalDate eventDate;private Time startTime;private Time endTime;@TableField(condition = SqlCondition.LIKE, whereStrategy = FieldStrategy.NOT_EMPTY)private String location;private Integer maxParticipants;private Integer currentParticipants;// 封面图片private String coverImage;
}
3.Booking
package com.situ.animebooking.model;import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;import java.time.LocalDate;@TableName("bookings")
@Getter
@Setter
public class Booking {@TableId(value = "booking_id", type = IdType.AUTO)// 预约idprivate int bookingId;@TableField(whereStrategy = FieldStrategy.NOT_EMPTY)// 用户idprivate int userId;@TableField(whereStrategy = FieldStrategy.NOT_EMPTY)// 活动idprivate int activityId;// 预约时间private LocalDate bookingTime;private int state;private String description;// 以下为外键,用于关联// 正确做法:使用瞬态注解标记非数据库字段@TableField(exist = false)private User user;@TableField(exist = false)private Activity activity;// 以下为非数据库字段@TableField(exist = false)private String userName;@TableField(exist = false)private String phone;@TableField(exist = false)private String title;
}
4.Admin
package com.situ.animebooking.model;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 lombok.Getter;
import lombok.Setter;// 管理员模型
@Getter
@Setter
@TableName("admins")
public class Admin {@TableId(value = "admin_id", type = IdType.AUTO)private Integer adminId;private String adminName;private String password;@TableField(exist = false)private String checkPassword;
}
5.Account
package com.situ.animebooking.model;import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Getter;
import lombok.Setter;@Getter
@Setter
//登录模型
public class Account {@TableField(whereStrategy = FieldStrategy.NOT_EMPTY)private String adminName;@TableField(whereStrategy = FieldStrategy.NOT_EMPTY)private String password;private String captcha;private String captchaId;}
6.ActivitySearchBean
package com.situ.animebooking.model.search;import com.situ.animebooking.model.Activity;
import lombok.Getter;
import lombok.Setter;import java.time.LocalDate;@Setter
@Getter
public class ActivitySearchBean extends Activity {private LocalDate evenDateFrom;private LocalDate evenDateTo;
}
7.BookingDTO
package com.situ.animebooking.model.DTO;import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;@Getter
@Setter
public class BookingDTO {@NotNull(message = "用户ID列表不能为空")@Size(min = 1, message = "至少需要1个用户ID")private Integer[] userId;@Min(value = 1, message = "活动ID无效")private int activityId;}
Ⅶ、设计业务接口
1.UserService
package com.situ.animebooking.service;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.User;import java.util.List;public interface UserService {//查询所有//条件查询Page<User> findAll(Page<User> page, User user);//根据id删除int deleteById(List<Integer> id);//根据id修改boolean updateById(User user);boolean save(User user);//获取用户总数int getUserCount();
}
2.ActivityService
package com.situ.animebooking.service;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.Activity;
import com.situ.animebooking.model.search.ActivitySearchBean;import java.util.List;public interface ActivityService {Page<Activity> findAll(Page<Activity> page, ActivitySearchBean asb);boolean save(Activity activity);int deleteById(List<Integer> activityId);boolean update(Activity activity);Activity findById(Integer activityId);//获取可预约的活动数int getActivityCount();
}
3.BookingService
package com.situ.animebooking.service;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.Activity;
import com.situ.animebooking.model.Booking;import java.util.List;public interface BookingService {//查询Page<Booking> searchBooking(Page<Booking> page , Booking booking);//添加预约// 添加单个预约//void addBooking(int userId, Activity activity);//取消预约int cancelBooking(List<Integer> bookingId);//删除预约记录int deleteBooking(List<Integer> bookingIds);//修改预约//批量添加预约void addBatchBookings(List<Integer> userIds, int activityId);//获取今日预约人数int getTodayBookingCount();//获取预约总数int getBookingCount();}
4.AdminService
package com.situ.animebooking.service;import com.situ.animebooking.model.Admin;public interface AdminService {Admin findByAdminName(String adminName);boolean register(Admin admin);}
5.UploadService
package com.situ.animebooking.service;import com.situ.animebooking.util.Tuple;
import org.springframework.web.multipart.MultipartFile;public interface UploadService {// 上传图片String uploadImage(MultipartFile file, String type);}
Ⅷ、实现业务接口
1.UserServiceImpl
package com.situ.animebooking.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.dao.UserMapper;
import com.situ.animebooking.model.User;
import com.situ.animebooking.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService {private UserMapper userMapper;@Autowiredpublic void setAnimeBookingMapper(UserMapper userMapper) {this.userMapper = userMapper;}@Overridepublic Page<User> findAll(Page<User> page, User user) {LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(user);return userMapper.selectPage(page, queryWrapper);}@Overridepublic int deleteById(List<Integer> id) {return userMapper.deleteByIds(id);}@Overridepublic boolean updateById(User user) {// 更新时需排除自身boolean exists = userMapper.exists(new QueryWrapper<User>().eq("phone", user.getPhone()).ne("user_id", user.getUserId())); // 排除当前记录if (exists) {throw new RuntimeException("电话号码已存在");}return userMapper.updateById(user) > 0;}@Override@CacheEvict(cacheNames = "Home", allEntries = true)public boolean save(User user) {// 检查电话号码是否已存在boolean exists = userMapper.exists(new QueryWrapper<User>().eq("phone", user.getPhone()));if (exists) {throw new RuntimeException("电话号码已存在");}return userMapper.insert(user) > 0;}@Override@Cacheable(key = "'userCount'", cacheNames = "Home")public int getUserCount() {return Math.toIntExact(userMapper.selectCount(null));}
}
2.ActivityServiceImpl
package com.situ.animebooking.service.impl;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.dao.ActivityMapper;
import com.situ.animebooking.model.Activity;
import com.situ.animebooking.model.search.ActivitySearchBean;
import com.situ.animebooking.service.ActivityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;import java.util.List;/*** 活动服务实现类* 实现ActivityService接口,提供活动相关的业务逻辑处理*/
@Service
public class ActivityServiceImpl implements ActivityService {// 活动数据访问对象,用于与数据库交互private ActivityMapper activityMapper;/*** 使用依赖注入方式设置ActivityMapper** @param activityMapper 活动数据访问对象*/@Autowiredpublic void setActivityMapper(ActivityMapper activityMapper) {this.activityMapper = activityMapper;}/*** 分页查询所有活动信息** @param page 分页对象,包含分页信息* @param asb 活动搜索条件对象* @return 返回分页后的活动列表*/@Overridepublic Page<Activity> findAll(Page<Activity> page, ActivitySearchBean asb) {return activityMapper.findAll(page, asb);}/*** 保存活动信息** @param activity 要保存的活动对象* @return 保存成功返回true,否则返回false*/@CacheEvict(cacheNames = "Home", allEntries = true)@Overridepublic boolean save(Activity activity) {return activityMapper.saveWithCheck(activity);}/*** 根据ID列表删除活动** @param activityId 要删除的活动ID列表* @return 返回删除的记录数*/@Overridepublic int deleteById(List<Integer> activityId) {return activityMapper.deleteByIds(activityId);}/*** 更新活动信息** @param activity 要更新的活动对象* @return 更新成功返回true,否则返回false*/@Overridepublic boolean update(Activity activity) {return activityMapper.saveWithCheck(activity);}@Overridepublic Activity findById(Integer activityId) {return activityMapper.selectById(activityId);}//获取可预约活动数@Cacheable(key = "'activityCount'", cacheNames = "Home")@Overridepublic int getActivityCount() {return activityMapper.getActivityCount();}
}
3.BookingServiceImpl
package com.situ.animebooking.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.dao.ActivityMapper;
import com.situ.animebooking.dao.BookingMapper;
import com.situ.animebooking.dao.UserMapper;
import com.situ.animebooking.model.Activity;
import com.situ.animebooking.model.Booking;
import com.situ.animebooking.model.User;
import com.situ.animebooking.service.BookingService;
import com.situ.animebooking.config.BusinessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;import java.sql.Time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Service
public class BookingServiceImpl implements BookingService {private BookingMapper bookingMapper;private UserMapper userMapper;private ActivityMapper activityMapper;@Autowiredpublic void setActivityMapper(ActivityMapper activityMapper) {this.activityMapper = activityMapper;}@Autowiredpublic void setUserMapper(UserMapper userMapper) {this.userMapper = userMapper;}@Autowiredpublic void setBookingMapper(BookingMapper bookingMapper) {this.bookingMapper = bookingMapper;}// 查询 - 修复实现@Overridepublic Page<Booking> searchBooking(Page<Booking> page, Booking booking) {// 直接调用自定义的 searchBooking 方法return bookingMapper.searchBooking(page, booking);}@Override@Transactionalpublic int cancelBooking(List<Integer> bookingIds) {// 1. 检查传入的预约ID列表是否为空if (bookingIds == null || bookingIds.isEmpty()) {throw new BusinessException("预约ID列表不能为空");}// 2. 批量查询预约记录List<Booking> bookings = bookingMapper.selectByIds(bookingIds);if (bookings == null || bookings.isEmpty()) {throw new BusinessException("未找到相关预约记录");}// 3. 按活动ID分组预约记录Map<Integer, List<Booking>> bookingsByActivity = bookings.stream().collect(Collectors.groupingBy(Booking::getActivityId));// 4. 检查所有活动是否可取消for (Map.Entry<Integer, List<Booking>> entry : bookingsByActivity.entrySet()) {int activityId = entry.getKey();Activity activity = activityMapper.selectById(activityId);if (activity == null) {throw new BusinessException("活动不存在: " + activityId);}if (isActivityStarted(activity)) {throw new BusinessException("活动已开始,无法取消: " + activity.getTitle());}}// 5. 批量删除预约记录int deletedCount = bookingMapper.deleteByIds(bookingIds);if (deletedCount != bookingIds.size()) {throw new BusinessException("部分预约取消失败,请重试");}// 6. 更新相关活动的参与人数for (Integer activityId : bookingsByActivity.keySet()) {bookingMapper.updateBookingCount(activityId);}return deletedCount;}@Override@Transactionalpublic int deleteBooking(List<Integer> bookingIds) {// 2. 批量查询预约记录List<Booking> bookings = bookingMapper.selectByIds(bookingIds);// 3. 按活动ID分组预约记录Map<Integer, List<Booking>> bookingsByActivity = bookings.stream().collect(Collectors.groupingBy(Booking::getActivityId));// 5. 批量删除预约记录int deletedCount = bookingMapper.deleteByIds(bookingIds);// 6. 更新相关活动的参与人数for (Integer activityId : bookingsByActivity.keySet()) {bookingMapper.updateBookingCount(activityId);}return deletedCount;}/*** 批量添加用户预订活动的方法* 该方法使用事务注解确保操作的原子性** @param userIds 用户ID列表,包含所有需要预订活动的用户* @param activityId 活动ID,表示用户要预订的活动*/@CacheEvict(cacheNames = "Home", allEntries = true)@Override//@Transactionalpublic void addBatchBookings(List<Integer> userIds, int activityId) {// 1. 提前获取活动信息(避免在循环中重复查询)Activity activity = activityMapper.selectById(activityId);if (activity == null) {throw new BusinessException("活动不存在");}// 2. 检查活动是否已开始if (isActivityStarted(activity)) {throw new BusinessException("活动已开始/结束,无法预约");}// 3. 检查活动名额是否足够(初步检查)int availableSlots = activity.getMaxParticipants() - activity.getCurrentParticipants();if (availableSlots < userIds.size()) {throw new BusinessException("活动名额不足,剩余名额: " + availableSlots);}// 4. 检查用户是否存在List<User> existingUsers = userMapper.selectByIds(userIds);if (existingUsers.size() < userIds.size()) {List<Integer> existingIds = existingUsers.stream().map(User::getUserId).toList();List<Integer> missingIds = userIds.stream().filter(id -> !existingIds.contains(id)).toList();throw new BusinessException("以下用户不存在: " + missingIds);}// 5. 批量预约(避免循环调用,减少事务嵌套)for (Integer userId : userIds) {// 检查用户是否已预约if (isUserBookedActivity(userId, activityId)) {throw new BusinessException("用户 " + userId + " 已预约该活动");}// 检查时间冲突if (hasBookingConflict(userId, activity)) {throw new BusinessException("用户 " + userId + " 存在时间冲突");}}// 批量添加预约记录for(int userId : userIds){bookingMapper.addBooking(userId, activityId);}//bookingMapper.addBooking(newBookings);// 6. 更新活动人数boolean updated = bookingMapper.updateBookingCount(activityId);if (!updated) {throw new BusinessException("活动人数已满,预约失败");}
}//获取今日用户预约数@Cacheable(key = "'todayCount'", cacheNames = "Home")@Overridepublic int getTodayBookingCount() {return bookingMapper.getTodayBookingCount();}//获取预约总数@Cacheable(key = "'bookingCount'", cacheNames = "Home")@Overridepublic int getBookingCount() {return Math.toIntExact(bookingMapper.selectCount(new QueryWrapper<>()));}// === 辅助验证方法 ===/*** 检查活动是否已开始*/private boolean isActivityStarted(Activity activity) {LocalDateTime now = LocalDateTime.now();return now.isAfter(activity.getEventDate().atTime(activity.getStartTime().toLocalTime()));}/*** 检查用户是否已预约该活动*/private boolean isUserBookedActivity(int userId, int activityId) {// 使用MyBatis-Plus的QueryWrapperQueryWrapper<Booking> query = new QueryWrapper<>();query.eq("user_id", userId).eq("activity_id", activityId);return bookingMapper.selectCount(query) > 0;}/*** 检查时间冲突*/private boolean hasBookingConflict(int userId, Activity newActivity) {// 1. 直接查询活动ID列表,而不是完整Booking对象QueryWrapper<Booking> query = new QueryWrapper<>();query.select("activity_id") // 只查询activity_id.eq("user_id", userId);List<Object> activityIds = bookingMapper.selectObjs(query);if (activityIds.isEmpty()) return false;// 2. 查询这些活动List<Activity> activities = activityMapper.selectByIds(activityIds.stream().map(id -> (Integer) id).collect(Collectors.toList()));// 3. 检查时间冲突for (Activity existingActivity : activities) {if (isTimeConflict(existingActivity, newActivity)) {return true;}}return false;}/*** 检查两个活动时间是否冲突*/private boolean isTimeConflict(Activity a1, Activity a2) {LocalDate date1 = a1.getEventDate();LocalDate date2 = a2.getEventDate();// 不同日期肯定不冲突if (!date1.equals(date2)) return false;// 同一天检查时间段重叠Time start1 = a1.getStartTime();Time end1 = a1.getEndTime();Time start2 = a2.getStartTime();Time end2 = a2.getEndTime();return !(end1.before(start2) || end2.before(start1));}
}
4.AdminServiceImpl
package com.situ.animebooking.service.impl;import com.situ.animebooking.dao.AdminMapper;
import com.situ.animebooking.model.Admin;
import com.situ.animebooking.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class AdminServiceImpl implements AdminService {private AdminMapper adminMapper;@Autowiredpublic void setAdminMapper(AdminMapper adminMapper) {this.adminMapper = adminMapper;}@Overridepublic Admin findByAdminName(String adminName) {return adminMapper.findByAdminName(adminName);}@Overridepublic boolean register(Admin admin) {return adminMapper.insert(admin) > 0;}}
5.UploadService
package com.situ.animebooking.service.impl;import com.situ.animebooking.service.UploadService;
import com.situ.animebooking.util.Tuple;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;@Service
public class UploadServiceImpl implements UploadService {@Value("${upload.location}")private String uploadLocation; //上传文件存放路径@Overridepublic String uploadImage(MultipartFile file, String type) {//1.创建目录File dir = new File(uploadLocation + "/images/" + type);if (!dir.exists()) {boolean b = dir.mkdirs(); //级联创建目录if (!b) {throw new RuntimeException("创建目录失败");}}//2.给上传的文件起别名LocalDateTime now = LocalDateTime.now();String fileName = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));Random random = new Random();int sid = random.nextInt(1000);fileName = fileName + "-" + sid;//3.获取文件后缀名String originalFilename = file.getOriginalFilename();int idx = originalFilename.lastIndexOf(".");String suffix = originalFilename.substring(idx);fileName = fileName + suffix;//完整的文件名String fullName = dir.getAbsolutePath()+"/"+fileName;//4.保存文件File targetFile = new File(fullName);try {file.transferTo(targetFile);} catch (Exception e) {throw new RuntimeException("上传文件失败");}//5.返回访问地址和存储地址//http://localhost:8080/images/xxx/xxx.jpgreturn "/images/" + type + "/" + fileName;}
}
Ⅸ、进行数据访问
1.UserMapper
package com.situ.animebooking.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.situ.animebooking.model.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {}
2.ActivityMapper
package com.situ.animebooking.dao;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.Activity;
import com.situ.animebooking.model.search.ActivitySearchBean;
import org.apache.ibatis.annotations.Mapper;import java.time.LocalDateTime;@Mapper
public interface ActivityMapper extends BaseMapper<Activity> {/*** 分页查询活动列表方法* @param page 分页参数对象* @param asb 活动搜索条件对象* @return 返回分页后的活动列表*/default Page<Activity> findAll(Page<Activity> page, ActivitySearchBean asb){//根据asb自动组装条件LambdaQueryWrapper<Activity> lqw = Wrappers.lambdaQuery(asb);if(asb.getEvenDateFrom()!=null){ //如果开始日期不为空lqw.ge(Activity::getEventDate,asb.getEvenDateFrom()); //添加大于等于开始日期的查询条件}if(asb.getEvenDateTo()!=null){ //如果结束日期不为空lqw.le(Activity::getEventDate,asb.getEvenDateTo()); //添加小于等于结束日期的查询条件}if(asb.getMaxParticipants()!=null){ //如果最大参与人数不为空lqw.ge(Activity::getMaxParticipants,asb.getMaxParticipants()); //添加大于等于最大参与人数的查询条件}return selectPage(page,lqw); //执行分页查询并返回结果}/*** 保存活动并检查冲突* @param activity 活动实体* @return 保存是否成功* @throws RuntimeException 如果存在时间或地点冲突*/default boolean saveWithCheck(Activity activity) {// 1. 检查同一日期同一地点是否已有活动LambdaQueryWrapper<Activity> samePlaceQuery = new LambdaQueryWrapper<>();samePlaceQuery.eq(Activity::getLocation, activity.getLocation()).eq(Activity::getEventDate, activity.getEventDate());Long count = selectCount(samePlaceQuery);if (count > 0) {// 2. 检查时间是否重叠LambdaQueryWrapper<Activity> timeConflictQuery = new LambdaQueryWrapper<>();timeConflictQuery.eq(Activity::getLocation, activity.getLocation()).eq(Activity::getEventDate, activity.getEventDate())// 时间重叠条件:// 新活动开始时间 < 已有活动结束时间 AND// 新活动结束时间 > 已有活动开始时间.apply("(start_time < {0} AND end_time > {1})",activity.getEndTime(),activity.getStartTime());if (activity.getActivityId() != null) {// 如果是更新操作,排除当前活动自身timeConflictQuery.ne(Activity::getActivityId, activity.getActivityId());}Long conflictCount = selectCount(timeConflictQuery);if (conflictCount > 0) {throw new RuntimeException("该地点在指定时间段内已有其他活动,请调整时间或地点");}}// 3. 执行保存操作if (activity.getActivityId() == null) {return insert(activity) > 0;} else {return updateById(activity) > 0;}}//获取可预约活动数default int getActivityCount(){LambdaQueryWrapper<Activity> qw = new LambdaQueryWrapper<>();qw.ge(Activity::getEventDate, LocalDateTime.now());return Math.toIntExact(selectCount(qw));}
}
3.BookingMapper
package com.situ.animebooking.dao;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.Booking;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;@Mapper
public interface BookingMapper extends BaseMapper<Booking>{// 添加预约boolean addBooking(Integer userId, Integer activityId);// 取消预约// 查询预约列表Page<Booking> searchBooking(Page<Booking> page, @Param("condition") Booking booking);// 更新预约人数boolean updateBookingCount(Integer activityId);// 获取今日预约人数default int getTodayBookingCount() {LambdaQueryWrapper<Booking> qw = new LambdaQueryWrapper<>();// 获取今天的日期范围LocalDateTime todayStart = LocalDate.now().atStartOfDay();LocalDateTime todayEnd = LocalDateTime.now().with(LocalTime.MAX);// 使用范围查询代替日期格式化函数(提高性能)qw.between(Booking::getBookingTime, todayStart, todayEnd);return Math.toIntExact(selectCount(qw));}
}
4.AdminMapper
package com.situ.animebooking.dao;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.situ.animebooking.model.Admin;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface AdminMapper extends BaseMapper<Admin> {default Admin findByAdminName(String username){LambdaQueryWrapper<Admin> qw = new LambdaQueryWrapper<>();qw.eq(Admin::getAdminName, username);return selectOne(qw);}
}
5.BookingMapper.xml
<?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.situ.animebooking.dao.BookingMapper"><insert id="addBooking">insert into bookings (user_id, activity_id) values (#{userId}, #{activityId})</insert><resultMap id="bookingDetailMap" type="com.situ.animebooking.model.Booking"><id property="bookingId" column="booking_id" /><result property="bookingTime" column="booking_time" /><result property="state" column="state" /><result property="description" column="u_description" /><!-- 添加这些行 --><result property="userId" column="b_user_id" /><result property="activityId" column="b_activity_id" /><!-- 用户信息 --><association property="user" javaType="com.situ.animebooking.model.User"><id property="userId" column="u_user_id" /><result property="userName" column="user_name" /><result property="sex" column="sex"/><result property="email" column="email" /><result property="phone" column="phone" /><result property="birthday" column="birthday" /></association><!-- 活动信息 --><association property="activity" javaType="com.situ.animebooking.model.Activity"><id property="activityId" column="a_activity_id" /><result property="title" column="title" /><result property="description" column="a_description" /><result property="eventDate" column="event_date" /><result property="startTime" column="start_time" /><result property="endTime" column="end_time" /><result property="location" column="location" /><result property="maxParticipants" column="max_participants" /><result property="currentParticipants" column="current_participants" /></association></resultMap><select id="searchBooking" resultMap="bookingDetailMap">SELECTb.booking_id,b.user_id AS b_user_id, <!-- 添加别名 -->b.activity_id AS b_activity_id, <!-- 添加别名 -->b.booking_time,b.state,b.description AS b_description,u.user_id AS u_user_id, <!-- 添加别名 -->u.user_name,u.sex,u.email,u.phone,u.birthday,a.activity_id AS a_activity_id, <!-- 添加别名 -->a.title,a.description AS a_description,a.event_date,a.start_time,a.end_time,a.location,a.max_participants,a.current_participantsFROM bookings bINNER JOIN users u ON b.user_id = u.user_idINNER JOIN activities a ON b.activity_id = a.activity_id<where><if test="condition.userName != null and condition.userName != ''">AND u.user_name LIKE CONCAT('%', #{condition.userName}, '%')</if><if test="condition.phone != null and condition.phone != ''">AND u.phone = #{condition.phone}</if><if test="condition.title != null and condition.title != ''">AND a.title LIKE CONCAT('%', #{condition.title}, '%')</if><if test="condition.userId != null and condition.userId != ''">AND b.user_id = #{condition.userId}</if></where></select><!--当前预约人数更新--><update id="updateBookingCount">UPDATE activitiesSET current_participants = (SELECT COUNT(*)FROM bookingsWHERE activity_id = #{activityId})WHERE activity_id = #{activityId}AND (SELECT COUNT(*) FROM bookings WHERE activity_id = #{activityId}) <= max_participants</update></mapper>
Ⅹ、进行报错信息和分页等配置
1.GlobalExceptionHandler
package com.situ.animebooking.config;import com.situ.animebooking.util.JsonResult;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;import java.util.stream.Collectors;/*
* 统一处理Spring MVC中的参数校验异常
* */@RestControllerAdvice
public class GlobalExceptionHandler {/*** 当控制器中的方法出现参数校验异常时,即会调用此方法响应值。** @param ex 参数校验异常* @return 响应结果*/@ExceptionHandler(HandlerMethodValidationException.class)public ResponseEntity<JsonResult<?>> handle(HandlerMethodValidationException ex) {String msg = ex.getAllErrors().stream().map(MessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(", "));return ResponseEntity.badRequest().body(JsonResult.fail(msg));}/*** 需要同时监听HandlerMethodValidationException和MethodArgumentNotValidException,二者都可能会出现* 两个是完全不同的异常类型,继承体系结构也不一样,没办法合并为一个。只是恰巧都包含getAllErrors方法而已** @param ex 参数校验异常* @return 响应结果*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<JsonResult<?>> handle(MethodArgumentNotValidException ex) {String msg = ex.getAllErrors().stream().map(MessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(", "));return ResponseEntity.badRequest().body(JsonResult.fail(msg));}@ExceptionHandler(MaxUploadSizeExceededException.class)public ResponseEntity<String> handleMaxSizeException(MaxUploadSizeExceededException e) {return ResponseEntity.badRequest().body("文件大小超过限制");}
}
2.BusinessException
package com.situ.animebooking.config;//业务异常类
public class BusinessException extends RuntimeException {public BusinessException(String message) {super(message);}
}
3.CommonConfig
package com.situ.animebooking.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.situ.animebooking.common.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@EnableCaching //缓存总开关
@Configuration
public class CommonConfig implements WebMvcConfigurer {private JwtInterceptor jwtInterceptor;@Autowiredpublic void setJwtInterceptor(JwtInterceptor jwtInterceptor) {this.jwtInterceptor = jwtInterceptor;}//配置文件,启动mybatis-plus自动分页//mybatis-plus自动分页拦截器@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}//配置文件,启动拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**").excludePathPatterns("/api/*/admin/login/**","/api/*/admin/logout/**","/api/*/admin/captcha/**");}
}
Ⅺ、使用工具类设置统一返回值和jwt令牌
1.jsonResult
package com.situ.animebooking.util;import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;/*
* 工具类
* */@Getter
@Setter //自动生成get和set方法
@AllArgsConstructor //自动生成有参构造
public class JsonResult<T> {//响应错误码private int code;//响应是否成功private boolean success;//响应消息private String message;//响应数据,这里我们不使用Object,而是使用泛型,方便使用private T data;//定义成功响应,单纯通知前端成功public static JsonResult<?> success() {return success(null);}//定义成功响应,通知前端成功,并返回数据// <T>声明泛型 JsonResult<T>获取泛型类型 T data使用泛型public static <T> JsonResult<T> success(T data) {return new JsonResult<>(200, true, "XXXXX操作成功XXXXX", data);}//定义失败响应,通知前端失败,并返回失败码和消息public static JsonResult<?> fail(int code, String message) {return new JsonResult<>(code, false, message, null);}//定义失败响应,通知前端失败,并返回失败消息public static JsonResult<?> fail(String message) {return fail(500, message);}
}
2.JwtUtils
package com.situ.animebooking.util;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Map;/**创建Jwt* 颁发Jwt */
public class JwtUtils {public static String createJwt(String adminId, String adminName, Map<String, Object> payload, //载荷LocalDateTime expireTime, //过期时间String secretKey ) {//密钥JWTCreator.Builder builder = JWT.create(); //构建者模式,通过一步一步指定一个参数,最终组成一个对象String jwt = builder.withPayload(payload)//设置载荷.withExpiresAt(expireTime.toInstant(ZoneOffset.of("+8"))).withIssuer("中享思途Asu").withIssuedAt(LocalDateTime.now().toInstant(ZoneOffset.of("+8"))).withSubject("用户认证").withAudience(adminName).withJWTId(adminId).sign(Algorithm.HMAC256(secretKey));return jwt;}
}
Ⅻ、完成api接口
1.UserApi
package com.situ.animebooking.api;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.User;
import com.situ.animebooking.service.BookingService;
import com.situ.animebooking.service.UserService;
import com.situ.animebooking.util.JsonResult;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
//定义全局地址,设置全局响应格式位json
@RequestMapping(value = "/api/v1/users", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserApi {private UserService userService;private BookingService bookingService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@Autowiredpublic void setBookingService(BookingService bookingService) {this.bookingService = bookingService;}@GetMapping//使用ResponseEntity响应json数据,定义规范工具类,规范返回格式public ResponseEntity<JsonResult<?>> findAll(@RequestParam(defaultValue = "1") Integer pageNo,@RequestParam(defaultValue = "20") Integer pageSize,User user) {//分页对象Page<User> page = new Page<>(pageNo, pageSize);//调用service查询page = this.userService.findAll(page, user);return ResponseEntity.ok(JsonResult.success(page));}//根据id删除用户信息@DeleteMappingpublic ResponseEntity<JsonResult<?>> deleteById(@RequestBody @Validated @NotNull @Size(min = 1) Integer[] ids) {//System.out.println(ids[0]);//在删除用户前先将预约记录删除int result = bookingService.deleteBooking(List.of(ids));//调用service删除int count = this.userService.deleteById(List.of(ids));if(count > 0) {return ResponseEntity.ok(JsonResult.success(count));} else {return ResponseEntity.ok(JsonResult.fail("删除失败"));}}//根据id修改用户信息@PutMappingpublic ResponseEntity<JsonResult<?>> updateById(@RequestBody @Validated User user) {boolean flag = this.userService.updateById(user);if(flag) {return ResponseEntity.ok(JsonResult.success(user));} else {return ResponseEntity.ok(JsonResult.fail("修改失败"));}}//新增用户信息@PostMappingpublic ResponseEntity<JsonResult<?>> save(@RequestBody @Validated User user) {boolean flag = this.userService.save(user);if(flag) {return ResponseEntity.ok(JsonResult.success(user));} elsereturn ResponseEntity.ok(JsonResult.fail("新增失败"));}}
2.ActivityApi
package com.situ.animebooking.api;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.model.Activity;
import com.situ.animebooking.model.search.ActivitySearchBean;
import com.situ.animebooking.service.ActivityService;
import com.situ.animebooking.service.UploadService;
import com.situ.animebooking.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.util.List;/*** 活动API控制器* 提供活动相关的RESTful API接口,包括查询、删除、更新和保存活动等功能*/
@RestController
@RequestMapping(value = "/api/v1/activities", produces = MediaType.APPLICATION_JSON_VALUE)
public class ActivityApi {// 活动服务接口,用于处理业务逻辑private ActivityService activityService;private UploadService uploadService;/*** 使用Spring的依赖注入方式设置ActivityService* @param activityService 活动服务接口实现类*/@Autowiredpublic void setActivityService(ActivityService activityService) {this.activityService = activityService;}@Autowiredpublic void setUploadService(UploadService uploadService) {this.uploadService = uploadService;}/*** 分页查询活动列表* @param pageNo 页码,默认为1* @param pageSize 每页大小,默认为20* @param activity 活动查询条件封装对象* @return 返回分页结果,包含活动列表和分页信息*/@GetMappingpublic ResponseEntity<JsonResult<?>> findAll(@RequestParam(defaultValue = "1") Integer pageNo,@RequestParam(defaultValue = "20") Integer pageSize,ActivitySearchBean activity) {// 创建分页对象Page<Activity> page = new Page<>(pageNo, pageSize);// 调用服务层查询分页数据page = activityService.findAll(page, activity);// 返回查询结果return ResponseEntity.ok(JsonResult.success(page));}@GetMapping("/{activityId}")public ResponseEntity<JsonResult<?>> findById(@PathVariable Integer activityId){return ResponseEntity.ok(JsonResult.success(activityService.findById(activityId)));}/*** 根据ID列表批量删除活动* @param ids 要删除的活动ID列表* @return 返回删除成功的记录数*/@DeleteMappingpublic ResponseEntity<JsonResult<?>> deleteByIds(@RequestParam List<Integer> ids) {// 调用服务层执行删除操作int count = activityService.deleteById(ids);return ResponseEntity.ok(JsonResult.success(count));}/*** 根据ID更新活动信息* @param activity 包含更新信息的活动对象* @return 返回更新是否成功*/@PutMappingpublic ResponseEntity<JsonResult<?>> updateById(@RequestBody Activity activity) {// 调用服务层执行更新操作boolean success = activityService.update(activity);return ResponseEntity.ok(JsonResult.success(success));}/*** 保存新活动* @param activity 要保存的活动对象* @return 返回保存是否成功*/@PostMappingpublic ResponseEntity<JsonResult<?>> save(@RequestBody Activity activity) {// 调用服务层执行保存操作boolean success = activityService.save(activity);return ResponseEntity.ok(JsonResult.success(success));}@PostMapping("/coverImageUpload")public ResponseEntity<JsonResult<?>> coverImageUpload(MultipartFile file){String path = this.uploadService.uploadImage(file, "activity");return ResponseEntity.ok(JsonResult.success(path));}}
3.BookingApi
package com.situ.animebooking.api;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.animebooking.config.BusinessException;
import com.situ.animebooking.model.Booking;
import com.situ.animebooking.model.DTO.BookingDTO;
import com.situ.animebooking.service.BookingService;
import com.situ.animebooking.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping(value = "api/v1/bookings", produces = MediaType.APPLICATION_JSON_VALUE)
public class BookingApi {private BookingService bookingService;@Autowiredpublic void setBookingService(BookingService bookingService) {this.bookingService = bookingService;}//添加预约@PostMapping@Transactionalpublic ResponseEntity<JsonResult<?>> addBooking(@RequestBody @Validated BookingDTO bookingDTO) {try {List<Integer> userIds = List.of(bookingDTO.getUserId());int activityId = bookingDTO.getActivityId();bookingService.addBatchBookings(userIds, activityId);return ResponseEntity.ok(JsonResult.success("预约成功"));}catch (BusinessException e) {// 捕获业务异常并返回错误信息return ResponseEntity.badRequest().body(JsonResult.fail(e.getMessage()));}catch (Exception e) {// 捕获其他异常并记录日志return ResponseEntity.status(500).body(JsonResult.fail("系统错误,请稍后重试"));}}//查询预约信息@GetMappingpublic ResponseEntity<JsonResult<?>> searchBooking(@RequestParam(defaultValue = "1") Integer pageNo,@RequestParam(defaultValue = "20") Integer pageSize,Booking booking) {// 创建 MyBatis-Plus 分页对象Page<Booking> page = new Page<>(pageNo, pageSize);// 执行查询Page<Booking> result = bookingService.searchBooking(page, booking);// 构建响应数据Map<String, Object> data = new HashMap<>();data.put("records", result.getRecords());data.put("total", result.getTotal());data.put("size", result.getSize());data.put("current", result.getCurrent());data.put("pages", result.getPages());//System.out.println(data);return ResponseEntity.ok(JsonResult.success(data));}//取消预约@DeleteMappingpublic ResponseEntity<JsonResult<?>> cancelBooking(@RequestBody List<Integer> ids) {int result = bookingService.cancelBooking(ids);return ResponseEntity.ok(JsonResult.success(result));}//删除记录@DeleteMapping("/delete")public ResponseEntity<JsonResult<?>> deleteBooking(@RequestBody List<Integer> ids) {int result = bookingService.deleteBooking(ids);return ResponseEntity.ok(JsonResult.success(result));}//查询用户预约信息@GetMapping("/user/{userId}")public ResponseEntity<JsonResult<?>> getUserBooking(@PathVariable int userId) {return null;}//查询活动预约信息@GetMapping("/activity/{activityId}")public ResponseEntity<JsonResult<?>> getActivityBooking(@PathVariable int activityId) {return null;}
}
4.AdminApi
package com.situ.animebooking.api;import com.situ.animebooking.model.Account;
import com.situ.animebooking.model.Admin;
import com.situ.animebooking.service.AdminService;
import com.situ.animebooking.util.JsonResult;
import com.situ.animebooking.util.JwtUtils;
import com.wf.captcha.SpecCaptcha;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jasypt.util.password.StrongPasswordEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;@RestController
@RequestMapping(value = "api/v1/admin",produces = MediaType.APPLICATION_JSON_VALUE)
public class AdminApi {private RedisTemplate<Object,Object> redisTemplate;private AdminService adminService;private static final StrongPasswordEncryptor PE = new StrongPasswordEncryptor();@Value("${jwt.secretKey}") //使用Value注解注入配置文件中的值private String jwtSecretKey;@Autowiredpublic void setAdminService(AdminService adminService) {this.adminService = adminService;}@Autowiredpublic void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {this.redisTemplate = redisTemplate;}@GetMapping("/captcha")public void captcha(String id, HttpServletRequest req, HttpServletResponse resp) throws IOException {//生成验证码SpecCaptcha captcha = new SpecCaptcha(140, 40, 4);resp.setContentType("image/gif");resp.setHeader("Pragma", "No-cache");resp.setHeader("Cache-Control", "no-cache");resp.setDateHeader("Expires", 0);//req.getSession().setAttribute("captcha", captcha.text().toLowerCase());redisTemplate.opsForValue().set("captcha-"+id,captcha.text().toLowerCase(), Duration.ofMinutes(1));//System.out.println(captcha.text().toLowerCase());//输出验证码图片captcha.out(resp.getOutputStream());}@PostMapping("/login")public ResponseEntity<JsonResult<?>> login(@RequestBody @Validated Account account){String correct = (String) redisTemplate.opsForValue().get("captcha-"+account.getCaptchaId());if (!account.getCaptcha().equals(correct)){return ResponseEntity.ok(JsonResult.fail("验证码错误"));}Admin admin = adminService.findByAdminName(account.getAdminName());if (admin == null){return ResponseEntity.ok(JsonResult.fail("用户名不存在"));}//校验密码boolean pass = PE.checkPassword(account.getPassword(),admin.getPassword());if(!pass) {return ResponseEntity.ok(JsonResult.fail("密码错误"));}//颁发jwtString jwt = JwtUtils.createJwt(UUID.randomUUID().toString(),admin.getAdminName(),Map.of("adminId",admin.getAdminId(),"adminName",admin.getAdminName()),LocalDateTime.now().plusHours(1), jwtSecretKey);System.out.println(jwt);return ResponseEntity.ok(JsonResult.success(jwt));}// 注册功能public ResponseEntity<JsonResult<?>> register(@RequestBody @Validated Admin admin){return ResponseEntity.ok(JsonResult.success());}}
5.HomeApi
package com.situ.animebooking.api;import com.situ.animebooking.service.ActivityService;
import com.situ.animebooking.service.BookingService;
import com.situ.animebooking.service.UserService;
import com.situ.animebooking.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;@RestController
@RequestMapping("/api/v1/home")
public class HomeApi {private BookingService bookingService;private UserService userService;private ActivityService activityService;@Autowiredpublic void setBookingService(BookingService bookingService) {this.bookingService = bookingService;}@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@Autowiredpublic void setActivityService(ActivityService activityService) {this.activityService = activityService;}@GetMappingpublic ResponseEntity<JsonResult<?>> homeDate(){//获取用户总数int userCount = userService.getUserCount();//获取预约总数int bookingCount = bookingService.getBookingCount();//获取今日预约数int todayBookingCount = bookingService.getTodayBookingCount();//获取可预约活动数int activityCount = activityService.getActivityCount();Map<String, Object> data = Map.of("userCount", userCount, "bookingCount", bookingCount, "todayBookingCount", todayBookingCount, "activityCount", activityCount);return ResponseEntity.ok(JsonResult.success(data));}}
十三、验证前端Jwt
JwtInterceptor
package com.situ.animebooking.common;import com.alibaba.fastjson2.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.situ.animebooking.util.JsonResult;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import java.io.PrintWriter;@Component
public class JwtInterceptor implements HandlerInterceptor {@Value("${jwt.secretKey}")private String jwtSecret;@Overridepublic boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {// 获取请求头中的jwtString jwt = req.getHeader("Authorization");// 验证jwt , 核验器JWTVerifier verifier = JWT.require(Algorithm.HMAC256(jwtSecret)).build();// 获取解密后的jwttry {DecodedJWT dj = verifier.verify(jwt);// 验证通过return true;} catch (JWTVerificationException e) {// 验证失败String msg = "jwt无效或过期";resp.setStatus(HttpStatus.UNAUTHORIZED.value()); //401resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write(JSON.toJSONString(JsonResult.fail(401,msg)));out.flush();return false;}}
}
十四、拓展功能
自定义Spring Bot启动横幅
- 新建banner.txt文件,里面存放展示
-
........................................................................................... .------------------------------.......=++%#+-....@@......@@..-..=@@@+..--..@@@.-----------. .----------------------------...@@#*+*#=---*+##@#..:@-....@@.--..@@@@*.--:.@@@.:----------. .--------------------------..+@+#+--+-:.%+-:=#..-*@%..@@@*+#......@@@@.---.@@@..----------. .------------------------:..@+=-:.+-----::--:-%%:..=@*...@@@#...@@@.@@.-...@@@.:----------. .----------------------...@%-=----------------..+*:.:#@+....+@@@@@.......%@@@..-----------. .--------------------::.@@:.:--==----------=-::-.#+*-::%@.::.....-@@@@@@@.....------------. .--------------------.@.@::=.-*----==------:=--*-:---*+-++.-----..........----------------. .-------------------.=.@-:=--=.----:::------*-::=:-%.::##%..------------------------------. .-------------------.@@=:+-+------=.#.-=-=--:@::*:-.@%---=@-......------------------------. .------------------..@-:-=-=-+==-=-.@.=-----:+:.@.=.--:--..%..#@@.------------------------. .-----------------:.@%.:+=:=.+.--=-.@.-:+:-=-+-.@.+:@+..==%@@@@@*.------------------------. .-----------------.*%=-:=:-+=##=---:-.::#.==:=..+.=.*:@--..%+++@..------------------------. .----------------:.@%-=.=@....++.=..%.%.=.-=.+:@.-..#..%%=@@*+@@.-------------------------. .----------------.@.@=*...@@@+..=-@=@#@-.....-.+.::#%@*.*-..*+@..-------------------------. .----------------.@.-*+@@-*#.@@@.......*@@@@@+.@*%%.@+%@:.-@+@@.--------------------------. .----------------.+@...+-.@@..-@.:----..*-@@@.@..@+.@@@#*+.@%@..--------------------------. .-----------------...-:...@@....:=-----.#:..=...@+.@..:....@+..---------------------------. .------------------------.@%:.--=-----=-::.=....@++..====..@%..---------------------------. .------------------------.@#=.------------...@@%@..%.%=-=..@@-.---------------------------. .------------------------.@%..-..------....@%@-=..+=.@.-=.=@@%.:--------------------------. .-----------------------:.@=..#@........@@@.+@-.:.=:.@.==.%@@*-.--------------------------. .-----------------------.#@=..-@@@%@@@@@#=.#@-.-..@..@.==.-%@%=.--------------------------. .------------......----..@=.-.%#++@#+@-.+==@.:--#@@..@.==.=@%@=..-------------------------. .------------.@@@@.----.@=.-:.@++--=+@..-:@-:-%.@@#..@--=..@%@%%.-------------------------. .------------......---:=@.--.=+=@##%==.+=@...=...@@..@+-=-.+@@@=:.------------------------. .-------:....:--------+-..-..#+@@+*..=.@+..#@@.@+.@.==%--=:-@+-:@.:-----------------------. .------..##:@.------:*----..%-@#...-.@@.##@....@@@@.%.-@:-+.@@#.+%.-----------------------. .---::.-@..@.*-----:*:--..-*=#@.-.-.@-.@@...%-...+@@#..%*.#..@@.-*..----------------------. .---.#.:.=.@.-.---.#.-...@*=@=..*@@::.@...-:=++....@@+-.#.-*..@+-=%.----------------------. .---.@@@.+.@.-#::.+=...@%---.%@%....@.@.----:=#--...@@.+%-.+%..@@.==.:--------------------. .---.%...@+-.-+:.%+...*+.@%@....=..@.@..----::-=#....@#.%@.=+...*@:=#.--------------------. .---:.+:..-=.-+:.@-...@*+-:=@.-#-.@@.@.:=--#-:-...=..@=%.:@.*%...%@:*.:-------------------. .-----.#-.-*+......@@@@@-.+@..=*..@@:%.-=--:#--=@+-#.@.@..-%.=...*.@++.-------------------. .------.%=---.@@..@@@....%@@.@%=...@@%.:-----+::.+#+.#.@@..%=@-.....*%:.------------------. .------:.::.:..@.@..*%..#@@..=%*.*..@@..------=--..@.@==@%..-.#:..@.+#@.:-----------------. .-------.@@..@#-=..:+=@@@*..:.+@*#...@=.----------...@@.@@...@@...@..@==.-----------------. .-------.@=@@..@#...@@#...==-:..-@@-..@.--------+-:...%:-@@:@@@@@+...@:@.:----------------. .-------...@@@@+#:....--..:=--=:...%@@-.---------+.#=.=#.:--@@@#@@@@...*#.----------------. .---------......%*..::.=#+.:----......+.----------=+=.-@%@@.@#@-@%%@@@-...:---------------. .----------.%:-.-@%:..#-.+++:::-@+:..:++:-----===-.==..@+@@.@@.%@@@@@@@@@...:-------------. .----------:----..@@-..-+--=++.---@*-.=+-:---------.=%=+:@@.@@.@#+#@#+%%@@@...------------. .-----------:=---:.=%=-.-==--===.=..#+--+-:---=-----.+.@@@@@.@.@%@##%#*@@%#@@....---------. .-----------:=-=---.@+++:-+====-===#-.+--*-----:.:.......@@@.=@@%*###%@@.@%+%@@....-------. .-----------:+--=--:.+=+#..*+=-=--:-=+::#=.---:=++++*#*..:.@@.@@@%#@@@@@@@@@@+%@@..-------. .------------=------.%@#+*-.++=====::-%##-.....+===+...%%*.@@.@@%@@@=#*....@@@#+@@.-------. .--------------%#=--#*.-@#=-.-##=--=+...*=+#@==.+++=.==....@@@.@@%@@@..-@=...@##@@.-------. .-------------:..*@@.....@%+=..+###-.@#...+%....:..:-===-..+@@.@@%%@@@.....#@%@@@..-------. .----------------...:---..@#*#..:+--...@#@@@@.....---....-@=:.@.@%%%@@....=@@@@@..--------. .------------------------..*-+#%....-@@@@@@@@@.........@@-.@.@@.@@%%@@@@@@@@@@...---------. .------------------------:.@....@@@@@@%##%@@%@@@@@@.%@@@...@@+.#@@%%%%%%@@@@@...----------. .-------------------------.@-@@@@@@###@@@##%#@#%#%@@....--..@@=+@@%%%@@##@@-..-.----------. .-------------------------...@@%##%@@@##@%%%@%@@@@@@@..-===..@+.:@@@@%#@@@.#.-=*.:--------. .---------------------------..@%@@####%%%%%@@=@=-=-:@@...==-.+@=..@@@@@@@..@@-:=*=.:------. .---------------------------:.@@*@#%@@%%%%%@*=+@+##*+@@#..-=..+@@....@@...=.@#+:-+@.:-----. .----------------------------.@@#%@#%%%%%%@@+##*@#####@@@..-=..@@@@..@+@@.-.%.++--*#..----. .----------------------------.@@#%%@#%%%@%@++##########%@@...-..@@@@@+=#@..:-@+--==+*-.:--. .---------------------------:..@@*@@#%%%@@@@+####%######%@@-..:.+@@@:=#+%@#..@.*--+-+++.:-. .---------------------------.-.@@*@%@#%@@#+@#############*@@@....@+-=%+*+%=@::-%=--+=*+%.:. .---------------------------.@..@%@%@#@@%%+*@*#####%######+@@@:..-#++*=#+*=*@.#+-=-=+:+**.. .---------------------------.@.@@@+@%%@#=+##########%#####*@#@@%..%#++%.%+#.##@--+=++#.@*%. .--------------------------..@*+@@%@%@@-@@###%######@######@##@@@..*..=.@**+++@.-+==+#=.%#. .--------------------------.#@#..@+*%@@#@@###%######@#######%##%@@.@@@@..@++-=@--+===+@..@. .-------------------------..@#+.@@=@%@@#@@###%#######%######%###%@@.=*@@.@**-+@=-+==*.@#... .------------------------..@@*..@@+@%@#%%####%#%%%%##%#######%####%@+@@%.=#+-=%::=--*.%@... .-----------------------..@.%..@@@@@%@=+*##%#%#%%#####%######%@###+@@@..-#@##=@=-+#@*-.@.:. .----------------------..@.@=.@@##-@@@=%%#%%#%####%%%#%#######@#*#*@@..:-:-+==.:@-=-+*.@-.. .---------------------..@.@+.@@@%*+#@@=#%%%%###%%%%#%#@########@##*#@@..--:-+*@.==@-+#.@+.. .-------------------...==@@.%@@#@+#+@@+%%%%%##@######%#%######**@#%+@@#..+....+=%:=-*#.@#.. .------------------..#@@%*..@@@+@=#++@=#%%%###@+#%%%###@#######*@%#+@@@.#@%@@-==+-+=#=.#@.. .----------------...@@#++=-@@@##@+#+@@=######+@+#%##############*@#+@@@:-+*@:--===+-%..*@.. .--.....:------...@@#+==+#=.+@@@@+#+@@=%%%##**@=%##%%%#@########*@%+@@@@.+%----+-+==%..#@.. .--.@@@%:----...@@..*++%+=+=..#@@@@@@@*%%%%%##@=######+@########**@*@@@@=#+:==*#-+-+#..@*.. .--.@........=@=..%%*#+=++++=.#-::-..+@@@@@@##@+###%%#+@#+##%##@#@@@@...+-:+-+.+-=-@.:.@+.. .--..@@@@@@@.....@%*=+=+++++:+%++++++-#..=@@@@@@%#%%@@@@@#%@@@@@@@=.+-*@-:+-=@.#+-++.:.@-.. .---...........-@##+=*++++*+-@++++++++*--......@@@@@@@+.:@@@@...-..*##@.-==*#..#--@.:.=@.-. .-----------..*@#*==#++++#%.@#++++++++%+%@=%##=.........-@..:+#@.+@%%=.=--+==..*=#:.-.@+.-. .----------..@@#+==*=++*%+.-@+++++++++@=....:........=@+.......@+@%#--=--%=#..-+**.-.*#.--. .---------..@%#+==#=++#@..+#%+++++++++%.:--:---------....:---:.@#%-:=--=#=+.:.+%%.::.%.:--. .--------..@@@+==+=++#%..#:@#+++++++++@.-------------=-------:=@+.-=--+=-#.:..@+#..-%-:---. .-------:.@@.++==#+*@...@-=@++++++++++#.--------------------..*.----+.==*..:.@=.-*.%::----. .------:.+@..#+=+=*%...#+.##++++++++++#.....::-------........@--==+%.=+#....@#...*#.:---:.. .------.-@@.@*+=#+%...@+=.@*++++++++++=@@@@+.............-@@@@.=*+.##+#...*#=...#=#=....-@. .-----..@@..@=+=*#...+*+--@+++++++++++=*...+@@@@@@@@@@@@@@@@@.-:...%--..%##...=%-..:@%@@+.. .-----.%@%.-#===%=.-.%+..@#++++++++++++#@@@@@@@@@@@@@@@@@%..:....-@*@=%=....%%-::--:.....-. ...........................................................................................
-
修改主启动类
-
package com.situ.animebooking;import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class AnimeBookingApplication {public static void main(String[] args) {//SpringApplication.run(AnimeBookingApplication.class, args);SpringApplication springApplication = newSpringApplication(AnimeBookingApplication.class);springApplication.setBannerMode(Banner.Mode.CONSOLE);springApplication.run(args);}}