├── src ├── main │ ├── resources │ │ ├── application.yml │ │ ├── application-prod.yml │ │ ├── application-dev.yml │ │ └── logback-spring.xml │ └── java │ │ └── com │ │ └── example │ │ ├── service │ │ ├── HallService.java │ │ ├── FileService.java │ │ ├── UserService.java │ │ ├── AuthService.java │ │ ├── VerifyService.java │ │ ├── ChatService.java │ │ ├── RoomService.java │ │ ├── impl │ │ │ ├── FileServiceImpl.java │ │ │ ├── UserServiceImpl.java │ │ │ ├── AuthServiceImpl.java │ │ │ ├── HallServiceImpl.java │ │ │ ├── VerifyServiceImpl.java │ │ │ ├── ChatServiceImpl.java │ │ │ ├── GameServiceImpl.java │ │ │ └── RoomServiceImpl.java │ │ ├── GameService.java │ │ ├── relative │ │ │ └── UserAuthService.java │ │ └── util │ │ │ ├── IpTools.java │ │ │ └── RedisTools.java │ │ ├── anno │ │ ├── MyFilterOrder.java │ │ └── EnableFilterAutoRegister.java │ │ ├── controller │ │ ├── listener │ │ │ ├── event │ │ │ │ ├── AddRoomEvent.java │ │ │ │ └── CreateRoomEvent.java │ │ │ ├── ContextListener.java │ │ │ ├── SessionListener.java │ │ │ └── RoomListener.java │ │ ├── exception │ │ │ ├── ModifyException.java │ │ │ ├── MyFileException.java │ │ │ ├── PasswordWrongException.java │ │ │ ├── NotExistInCookieException.java │ │ │ ├── NotExistInMysqlException.java │ │ │ ├── NotExistInRedisException.java │ │ │ ├── ThreadLocalIsNullException.java │ │ │ ├── NotExistInServletContextException.java │ │ │ └── AlreadyException.java │ │ ├── filter │ │ │ ├── RoomFilter.java │ │ │ ├── ThreadInitFilter.java │ │ │ ├── ExceptionFilter.java │ │ │ ├── GameFilter.java │ │ │ └── OverallExceptionHandler.java │ │ ├── ErrorController.java │ │ ├── HallApiController.java │ │ ├── GamePreController.java │ │ ├── ChatApiController.java │ │ ├── AuthGuestApiController.java │ │ ├── RoomApiController.java │ │ ├── SqlApiController.java │ │ ├── UserApiController.java │ │ ├── AuthApiController.java │ │ └── GameApiController.java │ │ ├── dao │ │ ├── ChatMapper.java │ │ ├── UserMapper.xml │ │ ├── UserMapper.java │ │ └── AuthMapper.java │ │ ├── entity │ │ ├── data │ │ │ ├── UserDetail.java │ │ │ ├── ChessDetail.java │ │ │ ├── UserScoreDetail.java │ │ │ ├── HallInfoDetail.java │ │ │ └── UserInfoDetail.java │ │ ├── repo │ │ │ ├── RestBean.java │ │ │ ├── RestBeanBuilder.java │ │ │ ├── ResultCode.java │ │ │ └── BASE64DecodeMultipartFile.java │ │ └── constant │ │ │ └── ThreadDetails.java │ │ ├── config │ │ ├── RedisMybatisCacheConfiguration.java │ │ ├── ApplicationPro.java │ │ ├── SwaggerConfiguration.java │ │ ├── FilterConfiguration.java │ │ ├── WebFilterFactoryBean.java │ │ ├── RedisMybatisCache.java │ │ ├── RedisTokenRepository.java │ │ ├── WebFilterAutoRegister.java │ │ └── SecurityConfiguration.java │ │ ├── KarGoBangWebServerApplication.java │ │ ├── tool │ │ ├── ResultSetUtil.java │ │ └── PictureTools.java │ │ └── Timer │ │ └── ServletContextTimer.java └── test │ └── java │ └── com │ └── example │ └── KarGoBangWebServerApplicationTests.java ├── Dockerfile ├── Dockerfile.dev ├── .gitignore ├── README.Docker.md ├── compose.prod.yaml ├── .dockerignore ├── .space.kts ├── compose.dev.yaml ├── qodana.yaml ├── README.md └── pom.xml /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: '@environment@' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | WORKDIR /app 3 | COPY pom.xml . 4 | COPY src ./src 5 | RUN apk add maven --no-cache && mvn package -DskipTests 6 | EXPOSE 8080 7 | ENTRYPOINT ["java", "-jar", "target/KarGoBangWebServer-0.0.1-SNAPSHOT.jar"] 8 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | # 使用带有 Maven 的 Java 基础镜像 2 | FROM maven:3.6.3-jdk-8 3 | 4 | # 工作目录设置为 /app 5 | WORKDIR /app 6 | 7 | # 将项目的 pom.xml 和 src 目录复制到容器内 8 | COPY pom.xml . 9 | COPY src ./src 10 | 11 | # 暴露 8080 端口 12 | EXPOSE 8080 13 | 14 | # 运行应用 15 | CMD ["mvn", "spring-boot:run"] 16 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/HallService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.example.entity.data.HallInfoDetail; 4 | 5 | public interface HallService { 6 | 7 | /** 8 | * 返回大厅房间信息 9 | * @return 大厅房间信息 10 | */ 11 | HallInfoDetail[] getHallInfo(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/example/anno/MyFilterOrder.java: -------------------------------------------------------------------------------- 1 | package com.example.anno; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Retention(RetentionPolicy.RUNTIME) 6 | @Target(ElementType.TYPE) 7 | @Documented 8 | public @interface MyFilterOrder { 9 | int value() default 50; 10 | String[] urlPatterns() default {"/*"}; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/listener/event/AddRoomEvent.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.listener.event; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | /** 6 | * 加入房间事件 7 | */ 8 | public class AddRoomEvent extends ApplicationEvent { 9 | 10 | public AddRoomEvent(Object source) { 11 | super(source); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/FileService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import org.springframework.web.multipart.MultipartFile; 4 | 5 | /** 6 | * 处理文件 7 | */ 8 | public interface FileService { 9 | 10 | /** 11 | * 存储头像以Base64形式到数据库 12 | * @param file 头像文件 13 | * @return 存储了几个 14 | */ 15 | int saveIcon(String file); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/listener/event/CreateRoomEvent.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.listener.event; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | /** 6 | * 创建房间事件 7 | */ 8 | public class CreateRoomEvent extends ApplicationEvent { 9 | 10 | public CreateRoomEvent(Object source) { 11 | super(source); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | /** 4 | * 修改用户信息接口 5 | */ 6 | public interface UserService { 7 | 8 | /** 9 | * 修改用户信息 10 | * @param username 新用户名 11 | * @param message 新简介 12 | * @param sex 性别 13 | * @return 是否成功 14 | */ 15 | boolean modifyUserDetails(String username,String message,String sex); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/dao/ChatMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.dao; 2 | 3 | import com.example.config.RedisMybatisCache; 4 | import org.apache.ibatis.annotations.CacheNamespace; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.mybatis.spring.annotation.MapperScan; 7 | 8 | @Mapper 9 | @CacheNamespace(implementation = RedisMybatisCache.class) 10 | public interface ChatMapper { 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/example/anno/EnableFilterAutoRegister.java: -------------------------------------------------------------------------------- 1 | package com.example.anno; 2 | 3 | import com.example.config.WebFilterAutoRegister; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * 自动注册filter 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Import(WebFilterAutoRegister.class) 14 | @Documented 15 | public @interface EnableFilterAutoRegister { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | 4 | import org.springframework.security.core.Authentication; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | public interface AuthService { 9 | 10 | 11 | /** 12 | * 注册用户 13 | * @param username 用户名 14 | * @param password 密码 15 | * @return 是否注册成功 16 | */ 17 | boolean register(String username, String password); 18 | 19 | 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/example/service/VerifyService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | public interface VerifyService{ 6 | 7 | 8 | /** 9 | * 发送邮箱验证码到对应邮箱 10 | * @param mail 邮箱地址 11 | */ 12 | boolean sendVerifyCode(String mail); 13 | 14 | 15 | /** 16 | * 验证邮箱验证码是否正确 17 | * @param mail 邮箱地址 18 | * @param code 验证码 19 | * @return 是否正确 20 | */ 21 | boolean doVerify(String mail,String code) ; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | datasource: 5 | url: jdbc:mysql://localhost:3306/kargobang 6 | driver-class-name: com.mysql.cj.jdbc.Driver 7 | username: mrowen 8 | password: lmq1226lmq 9 | mvc: 10 | pathmatch: 11 | matching-strategy: ant_path_matcher # 修复swagger版本不匹配bug 12 | mail: 13 | host: smtp.qq.com 14 | username: lmq122677@qq.com 15 | password: sobdlxhdpbnlgfdg 16 | constant: 17 | crossOriginValue: https://f01-1309918226.file.myqcloud.com 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/data/UserDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.data; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import lombok.*; 5 | import lombok.experimental.FieldDefaults; 6 | 7 | import java.io.Serializable; 8 | 9 | @ApiModel("用户信息类") 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserDetail implements Serializable { 15 | int id; 16 | String username; 17 | String password; 18 | String role; 19 | UserInfoDetail userInfo; 20 | UserScoreDetail userScore; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/ModifyException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | 4 | /** 5 | * 修改时出现的的异常 6 | */ 7 | public class ModifyException extends RuntimeException{ 8 | 9 | public ModifyException() { 10 | super(""); 11 | } 12 | 13 | public ModifyException(String message) { 14 | super(message); 15 | } 16 | 17 | public ModifyException(String message, Throwable cause) { 18 | super(message,cause); 19 | } 20 | 21 | public ModifyException(Throwable cause) { 22 | super(cause); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/MyFileException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | 4 | /** 5 | * 前后端处理文件的异常 6 | */ 7 | public class MyFileException extends RuntimeException{ 8 | 9 | public MyFileException() { 10 | super(""); 11 | } 12 | 13 | public MyFileException(String message) { 14 | super(message); 15 | } 16 | 17 | public MyFileException(String message, Throwable cause) { 18 | super(message,cause); 19 | } 20 | 21 | public MyFileException(Throwable cause) { 22 | super(cause); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/PasswordWrongException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | /** 4 | * 密码错误时抛出的异常 5 | */ 6 | public class PasswordWrongException extends RuntimeException{ 7 | public PasswordWrongException() { 8 | super(""); 9 | } 10 | 11 | public PasswordWrongException(String message) { 12 | super(message); 13 | } 14 | 15 | public PasswordWrongException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public PasswordWrongException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/NotExistInCookieException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | /** 4 | * 请求头中Cookie不存在该值时抛出的异常 5 | */ 6 | public class NotExistInCookieException extends RuntimeException{ 7 | public NotExistInCookieException() { 8 | super(""); 9 | } 10 | 11 | public NotExistInCookieException(String message) { 12 | super(message); 13 | } 14 | 15 | public NotExistInCookieException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public NotExistInCookieException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/NotExistInMysqlException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | /** 4 | * 数据库中未查到相关信息的异常 5 | */ 6 | public class NotExistInMysqlException extends RuntimeException{ 7 | 8 | public NotExistInMysqlException() { 9 | super(""); 10 | } 11 | 12 | public NotExistInMysqlException(String message) { 13 | super(message); 14 | } 15 | 16 | public NotExistInMysqlException(String message, Throwable cause) { 17 | super(message,cause); 18 | } 19 | 20 | public NotExistInMysqlException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/NotExistInRedisException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | /** 4 | * 数据库中未查到相关信息的异常 5 | */ 6 | public class NotExistInRedisException extends RuntimeException{ 7 | 8 | public NotExistInRedisException() { 9 | super(""); 10 | } 11 | 12 | public NotExistInRedisException(String message) { 13 | super(message); 14 | } 15 | 16 | public NotExistInRedisException(String message,Throwable cause) { 17 | super(message,cause); 18 | } 19 | 20 | public NotExistInRedisException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/data/ChessDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.data; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.experimental.FieldDefaults; 9 | 10 | @ApiModel("棋子移动信息类") 11 | @Data 12 | @AllArgsConstructor 13 | @FieldDefaults(makeFinal = true,level = AccessLevel.PRIVATE) 14 | public class ChessDetail { 15 | 16 | @ApiModelProperty("X坐标") 17 | int x; 18 | @ApiModelProperty("Y坐标") 19 | int y; 20 | @ApiModelProperty("Z坐标") 21 | int z; 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | datasource: 5 | url: jdbc:mysql://localhost:3306/kargobang 6 | driver-class-name: com.mysql.cj.jdbc.Driver 7 | username: mrowen 8 | password: lmq1226lmq 9 | mvc: 10 | pathmatch: 11 | matching-strategy: ant_path_matcher # 修复swagger版本不匹配bug 12 | mail: 13 | host: smtp.qq.com 14 | username: lmq122677@qq.com 15 | password: sobdlxhdpbnlgfdg 16 | #mybatis: 17 | # mapper-locations: classpath:com/example/dao/*.xml 18 | # type-aliases-package: com.example.entity.data 19 | constant: 20 | crossOriginValue: https://f01-1309918226.file.myqcloud.com 21 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/data/UserScoreDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.data; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.FieldDefaults; 9 | 10 | import java.io.Serializable; 11 | 12 | @ApiModel("用户游戏信息类") 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class UserScoreDetail implements Serializable { 17 | String username; 18 | int wins; 19 | int winRate; 20 | int passNumber; 21 | int coins; 22 | int user_id; 23 | int sessions; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/ThreadLocalIsNullException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | /** 4 | * 从ThreadLocal中取出为空时抛出的异常 5 | */ 6 | public class ThreadLocalIsNullException extends RuntimeException{ 7 | 8 | public ThreadLocalIsNullException() { 9 | super(""); 10 | } 11 | 12 | public ThreadLocalIsNullException(String message) { 13 | super(message); 14 | } 15 | 16 | public ThreadLocalIsNullException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public ThreadLocalIsNullException(Throwable cause) { 21 | super(cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/NotExistInServletContextException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | /** 4 | * ServletContext不存在该内容时抛出的异常 5 | */ 6 | public class NotExistInServletContextException extends RuntimeException{ 7 | public NotExistInServletContextException() { 8 | super(""); 9 | } 10 | 11 | public NotExistInServletContextException(String message) { 12 | super(message); 13 | } 14 | 15 | public NotExistInServletContextException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public NotExistInServletContextException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.example.entity.data.UserDetail; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import java.util.List; 7 | 8 | /** 9 | * 发送信息,用户聊天 10 | */ 11 | public interface ChatService { 12 | 13 | 14 | /** 15 | * 用户发送信息 16 | * @param message 信息 17 | */ 18 | void sendMessage(HttpServletRequest request, String message); 19 | 20 | /** 21 | * 获取对方发送的信息 22 | * @return 对方发送的信息 23 | */ 24 | String getMessage(); 25 | 26 | /** 27 | * 获取自己和对方的用户信息 28 | * @return 自己和对方的用户信息 29 | */ 30 | List getMyAndOpponentUserDetailsInGame(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/data/HallInfoDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.data; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.*; 6 | import lombok.experimental.FieldDefaults; 7 | 8 | import java.awt.image.BufferedImage; 9 | 10 | @ApiModel("大厅中的房间信息类") 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @FieldDefaults(level = AccessLevel.PRIVATE) 16 | public class HallInfoDetail { 17 | @ApiModelProperty("房间号") 18 | String roomNumber; 19 | @ApiModelProperty("玩家名称") 20 | String username; 21 | @ApiModelProperty("玩家头像") 22 | String image; 23 | @ApiModelProperty("玩家简介") 24 | String message; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/data/UserInfoDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.data; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.FieldDefaults; 9 | 10 | import java.io.Serializable; 11 | 12 | @ApiModel("用户信息类") 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class UserInfoDetail implements Serializable { 17 | public static final String USER_SEX_BOY = "男"; 18 | public static final String USER_SEX_GIRL = "女"; 19 | 20 | String username; 21 | String message; 22 | String icon; 23 | String sex; 24 | int user_id; 25 | } 26 | -------------------------------------------------------------------------------- /README.Docker.md: -------------------------------------------------------------------------------- 1 | ### Building and running your application 2 | 3 | When you're ready, start your application by running: 4 | `docker compose up --build`. 5 | 6 | ### Deploying your application to the cloud 7 | 8 | First, build your image, e.g.: `docker build -t myapp .`. 9 | If your cloud uses a different CPU architecture than your development 10 | machine (e.g., you are on a Mac M1 and your cloud provider is amd64), 11 | you'll want to build the image for that platform, e.g.: 12 | `docker build --platform=linux/amd64 -t myapp .`. 13 | 14 | Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. 15 | 16 | Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) 17 | docs for more detail on building and pushing. -------------------------------------------------------------------------------- /src/main/java/com/example/config/RedisMybatisCacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.annotation.Resource; 10 | 11 | @Configuration 12 | @Slf4j 13 | public class RedisMybatisCacheConfiguration { 14 | 15 | @Resource 16 | RedisTemplate redisTemplate; 17 | 18 | @PostConstruct 19 | public void init() { 20 | log.info("存储Mybatis缓存的Redis配置加载"); 21 | RedisMybatisCache.setTemplate(redisTemplate); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/exception/AlreadyException.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | import com.example.entity.repo.RestBean; 4 | import com.example.entity.repo.RestBeanBuilder; 5 | import com.example.entity.repo.ResultCode; 6 | 7 | 8 | /** 9 | * 数据库中已经存在该信息的异常 10 | */ 11 | public class AlreadyException extends RuntimeException{ 12 | 13 | public AlreadyException() { 14 | super(""); 15 | } 16 | 17 | public AlreadyException(String message) { 18 | super(message); 19 | } 20 | 21 | public AlreadyException(String message,Throwable cause) { 22 | super(message,cause); 23 | } 24 | 25 | public AlreadyException(Throwable cause) { 26 | super(cause); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /compose.prod.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | app-prod: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - kargobang 11 | environment: 12 | SPRING_PROFILES_ACTIVE: dev 13 | depends_on: 14 | - mysql-prod 15 | - redis-prod 16 | mysql-prod: 17 | image: mysql:latest 18 | environment: 19 | MYSQL_ROOT_PASSWORD: lmq1226lmq # 设置 root 用户的密码 20 | MYSQL_DATABASE: kargobang-prod 21 | # MYSQL_USER 和 MYSQL_PASSWORD 可用于创建一个非 root 用户 22 | MYSQL_USER: mrowen 23 | MYSQL_PASSWORD: lmq1226lmq 24 | ports: 25 | - "3306:3306" 26 | redis-prod: 27 | image: redis:latest 28 | networks: 29 | kargobang: 30 | driver: bridge -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/go/build-context-dockerignore/ 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/.classpath 11 | **/.dockerignore 12 | **/.env 13 | **/.git 14 | **/.gitignore 15 | **/.project 16 | **/.settings 17 | **/.toolstarget 18 | **/.vs 19 | **/.vscode 20 | **/*.*proj.user 21 | **/*.dbmdl 22 | **/*.jfm 23 | **/bin 24 | **/charts 25 | **/docker-compose* 26 | **/compose* 27 | **/Dockerfile* 28 | **/node_modules 29 | **/npm-debug.log 30 | **/obj 31 | **/secrets.dev.yaml 32 | **/values.dev.yaml 33 | LICENSE 34 | README.md 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/repo/RestBean.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.repo; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.AccessLevel; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NonNull; 9 | import lombok.experimental.FieldDefaults; 10 | 11 | @Data 12 | @FieldDefaults(makeFinal = true,level = AccessLevel.PRIVATE) 13 | @Builder(toBuilder = true) 14 | @ApiModel("响应实体封装类") 15 | public class RestBean { 16 | 17 | @ApiModelProperty("是否成功") 18 | @NonNull boolean success; 19 | 20 | @ApiModelProperty("状态码") 21 | @NonNull int code; 22 | 23 | @ApiModelProperty("状态码描述信息") 24 | @NonNull String message; 25 | 26 | @ApiModelProperty("响应实体数据") 27 | T data ; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.space.kts: -------------------------------------------------------------------------------- 1 | job("Build and Deploy") { 2 | container(image = "docker") { 3 | env["DOCKER_USERNAME"] = "{{ project:DOCKER_USERNAME }}" 4 | env["DOCKER_PASSWORD"] = "{{ project:DOCKER_PASSWORD }}" 5 | shellScript { 6 | content = """ 7 | # 构建 Docker 镜像 8 | docker build -t kargobangapp:latest . 9 | 10 | # 推送镜像到 Docker 仓库 11 | echo "${'$'}DOCKER_PASSWORD" | docker login -u "${'$'}DOCKER_USERNAME" --password-stdin 12 | docker tag kargobangapp:latest mrowenovo/kargobang:latest 13 | docker push mrowenovo/kargobang:latest 14 | 15 | # 使用 docker-compose 启动生产环境容器 16 | docker-compose -f compose.prod.yaml up -d 17 | """ 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /compose.dev.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | app-dev: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.dev 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - kargobang 11 | environment: 12 | SPRING_PROFILES_ACTIVE: dev 13 | volumes: 14 | - .:/app 15 | - C:/Application/Programming/Maven/m2:/root/.m2 16 | depends_on: 17 | - mysql-dev 18 | - redis-dev 19 | mysql-dev: 20 | image: mysql:latest 21 | environment: 22 | MYSQL_ROOT_PASSWORD: lmq1226lmq # 设置 root 用户的密码 23 | MYSQL_DATABASE: kargobang-dev 24 | # MYSQL_USER 和 MYSQL_PASSWORD 可用于创建一个非 root 用户 25 | MYSQL_USER: mrowen 26 | MYSQL_PASSWORD: lmq1226lmq 27 | ports: 28 | - "3306:3306" 29 | redis-dev: 30 | image: redis:latest 31 | networks: 32 | kargobang: 33 | driver: bridge -------------------------------------------------------------------------------- /src/main/java/com/example/config/ApplicationPro.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.stereotype.Component; 10 | 11 | @ToString 12 | @Component 13 | @ConfigurationProperties(prefix = "constant") 14 | public class ApplicationPro { 15 | 16 | public static String crossOriginValue = null; 17 | 18 | public void setCrossOriginValue(String crossOriginValue) { 19 | ApplicationPro.crossOriginValue = crossOriginValue; 20 | } 21 | 22 | public String getCrossOriginValue() { 23 | return ApplicationPro.crossOriginValue; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/dao/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | update userdetails 9 | 10 | message=#{message}, 11 | sex=#{sex}, 12 | 13 | where username=#{username} 14 | 15 | 16 | update userscore 17 | 18 | wins=#{wins}, 19 | sessions=#{sessions}, 20 | 21 | where username=#{username} 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/com/example/KarGoBangWebServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.example.anno.EnableFilterAutoRegister; 4 | import com.example.controller.listener.RoomListener; 5 | import org.mybatis.spring.annotation.MapperScan; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.web.servlet.ServletComponentScan; 9 | import org.springframework.context.ConfigurableApplicationContext; 10 | import org.springframework.scheduling.annotation.EnableScheduling; 11 | 12 | @SpringBootApplication 13 | @ServletComponentScan 14 | @EnableFilterAutoRegister 15 | @EnableScheduling 16 | public class KarGoBangWebServerApplication { 17 | public static void main(String[] args) { 18 | ConfigurableApplicationContext context = SpringApplication.run(KarGoBangWebServerApplication.class, args); 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/RoomService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | public interface RoomService { 6 | 7 | /** 8 | * 根据房间号创建房间,不需要加密号 9 | * @param number 房间号 10 | * @return 是否成功 11 | */ 12 | boolean createRoom(HttpServletRequest request,String number); 13 | 14 | /** 15 | * @param number 房间号 16 | * @param password 加密号 17 | * @return 是否成功 18 | */ 19 | boolean createRoomSecret(HttpServletRequest request,String number,String password); 20 | 21 | /** 22 | * 根据房间号创建房间,不需要加密号 23 | * @param number 房间号 24 | * @param password 加密号 25 | * @return 是否成功 26 | */ 27 | boolean addRoom(HttpServletRequest request,String number, String password); 28 | 29 | /** 30 | * 每隔3秒判断对手是否加入房间 31 | * @return 对手是否加入房间 32 | */ 33 | void waitForOpponent(HttpServletRequest request); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/listener/ContextListener.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.listener; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import javax.servlet.ServletContextAttributeEvent; 6 | import javax.servlet.ServletContextAttributeListener; 7 | import javax.servlet.ServletContextEvent; 8 | import javax.servlet.ServletContextListener; 9 | import javax.servlet.annotation.WebListener; 10 | 11 | @Slf4j 12 | @WebListener 13 | public class ContextListener implements ServletContextListener, ServletContextAttributeListener { 14 | 15 | @Override 16 | public void attributeAdded(ServletContextAttributeEvent scae) { 17 | log.info("ServletContext:属性: [{}],Value: [{}] 被注册", scae.getName(), scae.getValue()); 18 | } 19 | 20 | @Override 21 | public void contextInitialized(ServletContextEvent sce) { 22 | log.info("ServletContext被初始化,ServerInfo:[{}]",sce.getServletContext().getServerInfo()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/FileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.controller.exception.MyFileException; 4 | import com.example.dao.UserMapper; 5 | import com.example.entity.constant.ThreadDetails; 6 | import com.example.entity.repo.BASE64DecodeMultipartFile; 7 | import com.example.service.FileService; 8 | import lombok.SneakyThrows; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.multipart.MultipartFile; 11 | import sun.misc.BASE64Encoder; 12 | 13 | import javax.annotation.Resource; 14 | import javax.sql.rowset.serial.SerialBlob; 15 | import java.sql.Blob; 16 | import java.util.UUID; 17 | 18 | @Service 19 | public class FileServiceImpl implements FileService { 20 | 21 | @Resource 22 | UserMapper userMapper; 23 | 24 | @SneakyThrows 25 | @Override 26 | public int saveIcon(String file) { 27 | String username = ThreadDetails.getUsername(); 28 | 29 | return userMapper.modifyIcon(username, file); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------# 2 | # Qodana analysis is configured by qodana.yaml file # 3 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html # 4 | #-------------------------------------------------------------------------------# 5 | version: "1.0" 6 | 7 | #Specify inspection profile for code analysis 8 | profile: 9 | name: qodana.starter 10 | 11 | #Enable inspections 12 | #include: 13 | # - name: 14 | 15 | #Disable inspections 16 | #exclude: 17 | # - name: 18 | # paths: 19 | # - 20 | 21 | projectJDK: 8 #(Applied in CI/CD pipeline) 22 | 23 | #Execute shell command before Qodana execution (Applied in CI/CD pipeline) 24 | #bootstrap: sh ./prepare-qodana.sh 25 | 26 | #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) 27 | #plugins: 28 | # - id: #(plugin id can be found at https://plugins.jetbrains.com) 29 | 30 | #Specify Qodana linter for analysis (Applied in CI/CD pipeline) 31 | linter: jetbrains/qodana-jvm:latest 32 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/repo/RestBeanBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.repo; 2 | 3 | import io.swagger.annotations.ApiModelProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NonNull; 8 | import lombok.experimental.FieldDefaults; 9 | 10 | import java.lang.ref.SoftReference; 11 | 12 | @Data 13 | @Builder(toBuilder = true) 14 | @FieldDefaults(makeFinal = true) 15 | public class RestBeanBuilder { 16 | 17 | public final static boolean DEFAULT = false; 18 | 19 | public final static boolean USER_DEFINED = true; 20 | 21 | @Builder.Default 22 | @NonNull ResultCode code = ResultCode.SUCCESS; 23 | 24 | String message; 25 | 26 | @Builder.Default 27 | boolean messageType = DEFAULT; 28 | 29 | T data ; 30 | 31 | public RestBean ToRestBean() { 32 | SoftReference SoftSuccess =code.getCode() == 200?new SoftReference<>(true):new SoftReference<>(false); 33 | if (messageType) 34 | return (RestBean) RestBean.builder().success(SoftSuccess.get()).code(code.getCode()).message(message).data(data).build(); 35 | return (RestBean) RestBean.builder().success(SoftSuccess.get()).code(code.getCode()).message(code.getMessage()).data(data).build(); 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/example/dao/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.dao; 2 | 3 | import com.example.config.RedisMybatisCache; 4 | import com.example.entity.data.UserDetail; 5 | import com.example.entity.data.UserInfoDetail; 6 | import com.example.entity.data.UserScoreDetail; 7 | import org.apache.ibatis.annotations.*; 8 | 9 | import java.sql.Blob; 10 | import java.util.Optional; 11 | @Mapper 12 | //@CacheNamespace(implementation = RedisMybatisCache.class) 13 | public interface UserMapper { 14 | 15 | @Update("update users set username=#{newUsername} where username=#{username}") 16 | boolean modifyUserDetails(@Param("newUsername") String newUsername, @Param("username") String username); 17 | 18 | boolean modifyUserInfoDetails(@Param("username") String username,@Param("message") String message,@Param("sex") String sex); 19 | 20 | boolean modifyUserScoreDetails(@Param("username") String username,@Param("wins") int wins,@Param("sessions") int sessions); 21 | 22 | @Update("update userscore set passNumber=#{passNumber} where username=#{username}") 23 | boolean modifyPassNumber(@Param("passNumber") int passNumber, @Param("username") String username); 24 | 25 | @Update("update userdetails set icon=#{file} where username=#{username}") 26 | int modifyIcon(@Param("username") String username,@Param("file") String file); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.stereotype.Component; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.service.Contact; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | 13 | @Configuration 14 | public class SwaggerConfiguration { 15 | 16 | private ApiInfo apiInfo() { 17 | return new ApiInfoBuilder().contact(new Contact("MrOwenovo", "https://github.com/MrOwenovo?tab=repositories", "lmq122677@qq.com")) 18 | .title("三维四子棋-后端交互接口") 19 | .version("1.4") 20 | .description("四子棋联机模式的联机接口,以及大厅操作相关接口") 21 | .build(); 22 | } 23 | 24 | @Bean 25 | public Docket docket() { 26 | return new Docket(DocumentationType.OAS_30) 27 | .apiInfo(apiInfo()) 28 | .select() 29 | .apis(RequestHandlerSelectors.basePackage("com.example.controller")) 30 | .build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/GameService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.example.entity.data.ChessDetail; 4 | 5 | public interface GameService { 6 | 7 | int WHITE = 0; 8 | 9 | int BLACK = 1; 10 | 11 | 12 | /** 13 | * @param x x坐标 14 | * @param y y坐标 15 | * @param z z坐标 16 | * @return 是否解析成功 17 | */ 18 | boolean move(int x,int y,int z,int type); 19 | 20 | 21 | /** 22 | * 等待白棋落子 23 | * 24 | * @return 白棋是否落子 25 | */ 26 | ChessDetail waitForWhiteMove(); 27 | 28 | /** 29 | * 等待黑棋落子 30 | * 31 | * @return 黑棋是否落子 32 | */ 33 | ChessDetail waitForBlackMove(); 34 | 35 | 36 | /** 37 | * 等待黑棋对手加入房间 38 | * @return 是否进入房间 39 | */ 40 | boolean whiteWaitForOpponent(); 41 | 42 | /** 43 | * 等待白棋对手加入房间 44 | * @return 是否进入房间 45 | */ 46 | boolean blackWaitForOpponent(); 47 | 48 | /** 49 | * 棋子已经加入房间 50 | * @return 是否加入房间 51 | */ 52 | boolean isIn(int type); 53 | 54 | /** 55 | * 游戏结束后操作数据库 56 | * @param isWin 是否胜利 57 | * @return 操作是否成功 58 | */ 59 | boolean gameResult(boolean isWin); 60 | 61 | /** 62 | * 如果当前熄灯关卡高,修改数据库 63 | * @param number 熄灯关卡数 64 | * @return 操作是否成功 65 | */ 66 | boolean changePassNumber(int number); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.controller.exception.ModifyException; 4 | import com.example.controller.exception.ThreadLocalIsNullException; 5 | import com.example.dao.UserMapper; 6 | import com.example.entity.constant.ThreadDetails; 7 | import com.example.service.UserService; 8 | import lombok.NonNull; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.Objects; 14 | 15 | @Service 16 | public class UserServiceImpl implements UserService { 17 | 18 | @Resource 19 | UserMapper userMapper; 20 | 21 | @Transactional 22 | @Override 23 | public boolean modifyUserDetails(String newUsername, String message, String sex) { 24 | if ("".equals(newUsername)||"".equals(message)||"".equals(sex)) throw new ModifyException("内容不能为空!"); 25 | String name = ThreadDetails.getUsername(); 26 | boolean a = true; 27 | if (message!=null||sex!=null) 28 | a = userMapper.modifyUserInfoDetails(name, message,sex); 29 | if (!Objects.equals(newUsername, name)&&newUsername!=null) { 30 | boolean b = userMapper.modifyUserDetails(newUsername, name); 31 | return a || b; 32 | } 33 | return a; 34 | 35 | 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/relative/UserAuthService.java: -------------------------------------------------------------------------------- 1 | package com.example.service.relative; 2 | 3 | import com.example.dao.AuthMapper; 4 | import com.example.dao.UserMapper; 5 | import com.example.entity.data.UserDetail; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.annotation.Resource; 14 | 15 | @Service 16 | public class UserAuthService implements UserDetailsService { 17 | 18 | @Resource 19 | UserMapper userMapper; 20 | @Resource 21 | AuthMapper authMapper; 22 | 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 26 | UserDetail userDetail = authMapper.findUserByUsername(username).orElseThrow(() -> new UsernameNotFoundException("用户" + username + "不存在!")); 27 | return User 28 | .withUsername(username) 29 | .password(new BCryptPasswordEncoder() 30 | .encode(userDetail.getPassword())) 31 | .roles(userDetail.getRole()) 32 | .build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/AuthServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.example.controller.exception.AlreadyException; 5 | import com.example.dao.AuthMapper; 6 | import com.example.dao.UserMapper; 7 | import com.example.entity.constant.ThreadDetails; 8 | import com.example.entity.data.UserDetail; 9 | import com.example.service.AuthService; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 12 | import org.springframework.stereotype.Service; 13 | 14 | import javax.annotation.Resource; 15 | import javax.servlet.http.Cookie; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.util.HashSet; 18 | import java.util.Optional; 19 | import java.util.Set; 20 | 21 | @Service 22 | public class AuthServiceImpl implements AuthService { 23 | 24 | @Resource 25 | AuthMapper authMapper; 26 | @Resource 27 | UserMapper userMapper; 28 | @Resource 29 | RedisTemplate template; 30 | 31 | 32 | 33 | @Override 34 | public boolean register(String username, String password) { 35 | Optional user = authMapper.findUserByUsername(username); 36 | if (user.isPresent()) throw new AlreadyException("该用户已经被注册"); 37 | return authMapper.registerUser(username, password, "user"); 38 | } 39 | 40 | 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${CONSOLE_LOG_PATTERN} 8 | ${CONSOLE_LOG_CHARSET} 9 | 10 | 11 | 12 | 13 | 14 | ${FILE_LOG_PATTERN} 15 | ${FILE_LOG_CHARSET} 16 | 17 | 18 | log/%d{yyyy-MM-dd}-spring-%i.log 19 | true 20 | 7 21 | 10MB 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/filter/RoomFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.filter; 2 | 3 | import com.example.anno.MyFilterOrder; 4 | import com.example.controller.exception.ThreadLocalIsNullException; 5 | import com.example.entity.constant.ThreadDetails; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.servlet.*; 11 | import javax.servlet.annotation.WebFilter; 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.io.IOException; 14 | 15 | @Slf4j 16 | public class RoomFilter implements Filter { 17 | @Override 18 | public void init(FilterConfig filterConfig) throws ServletException { 19 | log.info("房间线程拦截器启用"); 20 | } 21 | 22 | @Override 23 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 24 | HttpServletRequest request = (HttpServletRequest) servletRequest; 25 | 26 | //把roomNumber从Session中取出 27 | String roomNumber = (String) request.getSession().getAttribute("roomNumber"); 28 | if (roomNumber == null) throw new ThreadLocalIsNullException("Session中没有RoomNumber!"); 29 | 30 | ThreadDetails.roomNumber.set(roomNumber); 31 | 32 | filterChain.doFilter(servletRequest, servletResponse); 33 | } 34 | 35 | @Override 36 | public void destroy() { 37 | Filter.super.destroy(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/example/dao/AuthMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.dao; 2 | 3 | 4 | import com.example.config.RedisMybatisCache; 5 | import com.example.entity.data.UserDetail; 6 | import com.example.entity.data.UserInfoDetail; 7 | import com.example.entity.data.UserScoreDetail; 8 | import org.apache.ibatis.annotations.*; 9 | 10 | import java.util.Optional; 11 | 12 | @Mapper 13 | //@CacheNamespace(implementation = RedisMybatisCache.class) 14 | public interface AuthMapper { 15 | 16 | @Insert("insert into users(username,password,role) values (#{username},#{password},#{role})") 17 | boolean registerUser(@Param("username") String username, @Param("password") String password, @Param("role") String role); 18 | 19 | @Results(id = "user-map" ,value = { 20 | @Result(column = "id",property = "id"), 21 | @Result(column = "username",property = "username"), 22 | @Result(column = "id",property = "userInfo",one = @One(select="findUserDetailsByUserId")), 23 | @Result(column = "id",property = "userScore",one = @One(select="findUserScoreByUserId")) 24 | }) 25 | @Select("select * from users where username=#{username}") 26 | Optional findUserByUsername(@Param("username") String username); 27 | 28 | @Select("select * from userdetails where user_id=#{user_id}") 29 | UserInfoDetail findUserDetailsByUserId(@Param("user_id") int user_id); 30 | 31 | @Select("select * from userscore where user_id=#{user_id}") 32 | UserScoreDetail findUserScoreByUserId(@Param("user_id") int user_id); 33 | 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/constant/ThreadDetails.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.constant; 2 | 3 | import com.example.controller.exception.ThreadLocalIsNullException; 4 | import org.springframework.boot.web.servlet.server.Session; 5 | import org.springframework.security.core.context.SecurityContext; 6 | 7 | import javax.servlet.ServletContext; 8 | import javax.servlet.http.HttpSession; 9 | 10 | /** 11 | * 接收请求后的线程储存的常量 12 | */ 13 | public class ThreadDetails { 14 | 15 | public static ThreadLocal securityContext = new ThreadLocal<>(); 16 | 17 | public static ThreadLocal servletContext = new ThreadLocal<>(); 18 | 19 | public static ThreadLocal roomNumber = new ThreadLocal<>(); 20 | 21 | public static ThreadLocal redisRoomNumber = new ThreadLocal<>(); 22 | 23 | public static ThreadLocal redisUsername = new ThreadLocal<>(); 24 | 25 | public static ThreadLocal redisOpponentUsername = new ThreadLocal<>(); 26 | 27 | public static String getUsername() { 28 | //获取用户 29 | SecurityContext securityContext = ThreadDetails.securityContext.get(); 30 | if (securityContext==null) 31 | throw new ThreadLocalIsNullException("ThreadDetails中没有securityContext!"); 32 | if (securityContext.getAuthentication()==null) 33 | throw new ThreadLocalIsNullException("securityContext中没有登录信息!"); 34 | if (securityContext.getAuthentication().getName()==null) 35 | throw new ThreadLocalIsNullException("securityContext中没有登录信息!"); 36 | return securityContext.getAuthentication().getName(); 37 | } 38 | 39 | 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/util/IpTools.java: -------------------------------------------------------------------------------- 1 | package com.example.service.util; 2 | 3 | import com.example.entity.constant.ThreadDetails; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | /** 9 | * 处理Cookie用户的IP地址的工具类,主要用于处理nginx等反向代理导致的获取不到客户端真实ip 10 | */ 11 | @Slf4j 12 | public class IpTools { 13 | 14 | public static String getIpAddress(HttpServletRequest request) { 15 | String ip = request.getHeader("x-forwarded-for"); 16 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 17 | ip = request.getHeader("Proxy-Client-IP"); 18 | } 19 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 20 | ip = request.getHeader("WL-Proxy-Client-IP"); 21 | } 22 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 23 | ip = request.getHeader("HTTP_CLIENT_IP"); 24 | } 25 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 26 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 27 | } 28 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 29 | ip = request.getRemoteAddr(); 30 | } 31 | if (ThreadDetails.securityContext.get()!=null&&ThreadDetails.securityContext.get().getAuthentication()!=null&&ThreadDetails.securityContext.get().getAuthentication().getName()!=null) 32 | // log.info("用户[{}]的真实ip为[{}]", ThreadDetails.securityContext.get().getAuthentication().getName(), ip); 33 | ip=ip.replace(",","").replace("127.0.0.1","").trim(); 34 | return ip; 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.controller.exception.NotExistInRedisException; 4 | import com.example.controller.exception.ThreadLocalIsNullException; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import springfox.documentation.annotations.ApiIgnore; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | /** 12 | * 重新抛出异常 13 | */ 14 | @ApiIgnore 15 | @RequestMapping("/error") 16 | @RestController 17 | public class ErrorController { 18 | 19 | @ApiIgnore 20 | @RequestMapping("/throwThreadLocalIsNullException") 21 | public void throwThreadLocalIsNullException(HttpServletRequest request) { 22 | throw (ThreadLocalIsNullException)request.getAttribute("filter.error.ThreadLocalIsNullException"); 23 | } 24 | 25 | @ApiIgnore 26 | @RequestMapping("/throwNotExistInRedisException") 27 | public void throwNotExistInRedisException(HttpServletRequest request) { 28 | throw (NotExistInRedisException)request.getAttribute("filter.error.NotExistInRedisException"); 29 | } 30 | 31 | @ApiIgnore 32 | @RequestMapping("/throwNullPointerException") 33 | public void throwNullPointerException(HttpServletRequest request) { 34 | throw (NullPointerException)request.getAttribute("filter.error.NullPointerException"); 35 | } 36 | 37 | 38 | @ApiIgnore 39 | @RequestMapping("/throwException") 40 | public void throwException(HttpServletRequest request) throws Exception { 41 | throw (Exception)request.getAttribute("filter.error.Exception"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/example/tool/ResultSetUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.tool; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.ResultSetMetaData; 5 | import java.sql.SQLException; 6 | 7 | /** 8 | * ResultSet工具类 9 | * @author zql 10 | * @createTime 2020-11-30 22:44:09 11 | * @version 1.1 12 | * @modifyLog 1.1 优化代码 13 | * 14 | */ 15 | public class ResultSetUtil { 16 | 17 | /** 18 | * 屏蔽构造函数,避免被实例化 19 | */ 20 | private ResultSetUtil() {} 21 | 22 | /** 23 | * 将ResultSet结果集转换为Json字符串 24 | * @author zql 25 | * @createTime 2020-11-30 22:44:19 26 | * 27 | * @param rs ResultSet结果集 28 | * @return Json字符串 29 | * @throws SQLException 30 | */ 31 | public static String resultSetToJson(ResultSet rs) throws SQLException { 32 | ResultSetMetaData resultsetmd = rs.getMetaData(); 33 | // 总数 34 | int total = 0; 35 | StringBuffer jsonstr = new StringBuffer(); 36 | jsonstr.append("["); 37 | while (rs.next()) { 38 | total++; 39 | jsonstr.append("{"); 40 | for (int i = 0, r = 1 ,len = resultsetmd.getColumnCount(); i < len; i++, r++) { 41 | jsonstr.append("\"").append(resultsetmd.getColumnName(r)).append("\":\"").append(rs.getString(r)).append("\","); 42 | } 43 | String s = jsonstr.toString(); 44 | s = s.substring(0, s.length() - 1); 45 | jsonstr = new StringBuffer(); 46 | jsonstr.append(s); 47 | jsonstr.append("},"); 48 | } 49 | jsonstr.append("{"); 50 | jsonstr.append("\"total\":\"").append(total).append("\""); 51 | jsonstr.append("}"); 52 | jsonstr.append("]"); 53 | return jsonstr.toString(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/java/com/example/KarGoBangWebServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.example.config.ApplicationPro; 4 | import com.example.controller.exception.NotExistInMysqlException; 5 | import com.example.dao.AuthMapper; 6 | import com.example.dao.UserMapper; 7 | import com.example.entity.data.UserDetail; 8 | import com.example.service.ChatService; 9 | import com.example.service.UserService; 10 | import com.example.service.VerifyService; 11 | import com.example.service.util.RedisTools; 12 | import org.junit.jupiter.api.Test; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | 16 | import javax.annotation.Resource; 17 | 18 | @SpringBootTest 19 | class KarGoBangWebServerApplicationTests { 20 | 21 | 22 | @Resource 23 | VerifyService service; 24 | @Resource 25 | AuthMapper authMapper; 26 | 27 | @Resource 28 | ApplicationPro pro; 29 | @Resource 30 | RedisTemplate template; 31 | @Resource 32 | UserMapper userMapper; 33 | @Resource 34 | RedisTools redisTools; 35 | @Resource 36 | UserService chatService; 37 | 38 | @Test 39 | public void contextLoads() throws Exception { 40 | UserDetail userDetail = authMapper.findUserByUsername("1251031190").orElseThrow(() -> new NotExistInMysqlException("不存在该用户!")); 41 | System.out.println(userDetail.getUserInfo().getMessage()); 42 | chatService.modifyUserDetails(null, "123", null); 43 | UserDetail userDetail2 = authMapper.findUserByUsername("1251031190").orElseThrow(() -> new NotExistInMysqlException("不存在该用户!")); 44 | System.out.println(userDetail2.getUserInfo().getMessage()); 45 | 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/HallServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.controller.exception.NotExistInMysqlException; 4 | import com.example.controller.exception.NotExistInServletContextException; 5 | import com.example.dao.AuthMapper; 6 | import com.example.entity.data.HallInfoDetail; 7 | import com.example.entity.data.UserDetail; 8 | import com.example.service.HallService; 9 | import com.example.service.util.RedisTools; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.context.ServletContextAware; 13 | 14 | import javax.annotation.Resource; 15 | import javax.servlet.ServletContext; 16 | import java.util.HashMap; 17 | import java.util.Set; 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.stream.Collectors; 21 | 22 | @Service 23 | public class HallServiceImpl implements HallService, ServletContextAware { 24 | 25 | private static ServletContext servletContext; 26 | @Resource 27 | RedisTemplate template; 28 | @Resource 29 | RedisTools redisTools; 30 | @Resource 31 | AuthMapper authMapper; 32 | 33 | public HallInfoDetail[] getHallInfo() { 34 | // 从ServletContext中取出所有room 35 | ConcurrentHashMap rooms = 36 | (ConcurrentHashMap) servletContext.getAttribute("rooms"); 37 | 38 | if (rooms == null || rooms.isEmpty()) 39 | throw new NotExistInServletContextException("当前还没有房间!"); 40 | 41 | return rooms.values().toArray(new HallInfoDetail[0]); 42 | } 43 | 44 | 45 | @Override 46 | public void setServletContext(final ServletContext servletContext) { 47 | HallServiceImpl.servletContext = servletContext; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/example/Timer/ServletContextTimer.java: -------------------------------------------------------------------------------- 1 | package com.example.Timer; 2 | 3 | import com.example.entity.data.HallInfoDetail; 4 | import com.example.service.impl.RoomServiceImpl; 5 | import com.example.service.util.RedisTools; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.context.ServletContextAware; 11 | 12 | import javax.annotation.Resource; 13 | import javax.servlet.ServletContext; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | 20 | /** 21 | * 检查ServletContextTimer中的属性是否过期 22 | */ 23 | @Slf4j 24 | @Component 25 | public class ServletContextTimer implements ServletContextAware { 26 | 27 | private static ServletContext servletContext; 28 | @Resource 29 | RedisTemplate redisTemplate; 30 | 31 | @Scheduled(fixedRate = 20000) 32 | public void reFreshRoomNumber() { 33 | ConcurrentHashMap rooms = (ConcurrentHashMap) servletContext.getAttribute("rooms"); 34 | if (rooms != null) { 35 | List toRemove = new ArrayList<>(); 36 | rooms.forEach((roomNumber, room) -> { 37 | Object o = redisTemplate.opsForValue().get(RoomServiceImpl.ROOM_ID_TOKEN_KEY + roomNumber); 38 | if (o == null) { 39 | toRemove.add(roomNumber); 40 | } 41 | }); 42 | toRemove.forEach(rooms::remove); 43 | } 44 | } 45 | 46 | 47 | 48 | @Override 49 | public void setServletContext(ServletContext servletContext) { 50 | ServletContextTimer.servletContext=servletContext; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/HallApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.Timer.ServletContextTimer; 4 | import com.example.entity.data.HallInfoDetail; 5 | import com.example.entity.repo.RestBean; 6 | import com.example.entity.repo.RestBeanBuilder; 7 | import com.example.entity.repo.ResultCode; 8 | import com.example.service.HallService; 9 | import io.swagger.annotations.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.annotation.Resource; 14 | import javax.servlet.http.HttpServletRequest; 15 | 16 | /** 17 | * 大厅信息的相关接口 18 | */ 19 | @Slf4j 20 | @Api(tags = "大厅信息", description = "大厅信息,用户玩家从大厅加入队伍") 21 | @RestController 22 | @RequestMapping("/api/hall") 23 | public class HallApiController { 24 | 25 | 26 | @Resource 27 | ServletContextTimer servletContextTimer; 28 | @Resource 29 | HallService hallService; 30 | 31 | @ApiResponses({ 32 | @ApiResponse(code = 200, message = "创建房间成功"), 33 | @ApiResponse(code = 400, message = "房间已经存在"), 34 | @ApiResponse(code = 400, message = "错误"), 35 | }) 36 | @ApiOperation(value = "初始化大厅", notes = "将所有在房间的房间号等信息返回") 37 | @GetMapping("/getAllRooms") 38 | public RestBean initHall(HttpServletRequest request) { 39 | HallInfoDetail[] hallInfo = hallService.getHallInfo(); 40 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).data(hallInfo).build().ToRestBean(); 41 | 42 | } 43 | 44 | @ApiResponses({ 45 | @ApiResponse(code = 200, message = "创建房间成功"), 46 | @ApiResponse(code = 400, message = "错误"), 47 | }) 48 | @ApiOperation(value = "更新过期的房间号", notes = "点击过期房间后,更新过期的房间号") 49 | @GetMapping("/reFreshHall") 50 | public RestBean reFreshHall(HttpServletRequest request) { 51 | servletContextTimer.reFreshRoomNumber(); 52 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).build().ToRestBean(); 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/FilterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import com.example.controller.filter.ExceptionFilter; 4 | import com.example.controller.filter.GameFilter; 5 | import com.example.controller.filter.RoomFilter; 6 | import com.example.controller.filter.ThreadInitFilter; 7 | import com.example.service.util.RedisTools; 8 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.security.web.session.HttpSessionEventPublisher; 12 | 13 | import javax.annotation.Resource; 14 | import javax.servlet.Filter; 15 | 16 | @Configuration 17 | public class FilterConfiguration { 18 | 19 | @Resource 20 | RedisTools redisTools; 21 | 22 | 23 | @Bean 24 | public FilterRegistrationBean gameFilterRegistration() { 25 | GameFilter.setRedisTools(redisTools); //设置工具类 26 | FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 27 | registrationBean.setFilter(new GameFilter()); 28 | registrationBean.setName("gameFilter"); 29 | registrationBean.addUrlPatterns("/api/game/*", "/api/chat/*"); 30 | //此处尽量小,要比其他Filter靠前 31 | registrationBean.setOrder(20); 32 | return registrationBean; 33 | } 34 | 35 | @Bean 36 | public FilterRegistrationBean roomFilterRegistration() { 37 | FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 38 | registrationBean.setFilter(new RoomFilter()); 39 | registrationBean.setName("roomFilter"); 40 | registrationBean.addUrlPatterns("/api/room/waitForOpponent"); 41 | //此处尽量小,要比其他Filter靠前 42 | registrationBean.setOrder(1); 43 | return registrationBean; 44 | } 45 | 46 | 47 | @Bean 48 | public HttpSessionEventPublisher httpSessionEventPublisher() { 49 | return new HttpSessionEventPublisher(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/filter/ThreadInitFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.filter; 2 | 3 | import com.example.anno.MyFilterOrder; 4 | import com.example.controller.exception.ThreadLocalIsNullException; 5 | import com.example.entity.constant.ThreadDetails; 6 | import com.example.service.impl.RoomServiceImpl; 7 | import com.example.service.util.IpTools; 8 | import com.example.service.util.RedisTools; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.core.annotation.Order; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.security.core.context.SecurityContext; 13 | import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.annotation.Resource; 17 | import javax.servlet.*; 18 | import javax.servlet.annotation.WebFilter; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | 23 | @Slf4j 24 | @MyFilterOrder(value=-50) 25 | public class ThreadInitFilter implements Filter { 26 | 27 | @Override 28 | public void init(FilterConfig filterConfig) throws ServletException { 29 | log.info("线程初始化拦截器启用"); 30 | } 31 | 32 | @Override 33 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 34 | HttpServletRequest request = (HttpServletRequest) servletRequest; 35 | //初始化SecurityContext 36 | SecurityContext securityContext = SecurityContextHolder.getContext(); 37 | ThreadDetails.securityContext.set(securityContext); 38 | 39 | 40 | //初始化ServletContext 41 | ServletContext servletContext = request.getServletContext(); 42 | ThreadDetails.servletContext.set(servletContext); 43 | 44 | filterChain.doFilter(servletRequest,servletResponse); 45 | 46 | } 47 | 48 | @Override 49 | public void destroy() { 50 | ThreadDetails.servletContext.remove(); 51 | ThreadDetails.securityContext.remove(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/filter/ExceptionFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.filter; 2 | 3 | import com.example.anno.MyFilterOrder; 4 | import com.example.controller.exception.NotExistInRedisException; 5 | import com.example.controller.exception.ThreadLocalIsNullException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.servlet.*; 11 | import java.io.IOException; 12 | 13 | 14 | @Slf4j 15 | @MyFilterOrder(-100) 16 | public class ExceptionFilter implements Filter { 17 | 18 | @Override 19 | public void init(FilterConfig filterConfig) throws ServletException { 20 | log.info("异常拦截器启用"); 21 | } 22 | 23 | @Override 24 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 25 | try { 26 | filterChain.doFilter(servletRequest, servletResponse); 27 | } catch (ThreadLocalIsNullException e) { 28 | //异常捕获,发送到error controller 29 | servletRequest.setAttribute("filter.error.ThreadLocalIsNullException", e); 30 | //将异常分发到/error/reThrow控制器 31 | servletRequest.getRequestDispatcher("/error/throwThreadLocalIsNullException").forward(servletRequest, servletResponse); 32 | } catch (NotExistInRedisException e) { 33 | servletRequest.setAttribute("filter.error.NotExistInRedisException", e); 34 | servletRequest.getRequestDispatcher("/error/throwNotExistInRedisException").forward(servletRequest, servletResponse); 35 | }catch (NullPointerException e) { 36 | servletRequest.setAttribute("filter.error.NullPointerException", e); 37 | servletRequest.getRequestDispatcher("/error/throwNullPointerException").forward(servletRequest, servletResponse); 38 | } catch (Exception e) { 39 | servletRequest.setAttribute("filter.error.Exception", e); 40 | servletRequest.getRequestDispatcher("/error/throwException").forward(servletRequest, servletResponse); 41 | 42 | } 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 项目简述 2 | ### 三维四子棋Web服务器: 3 | - springBoot项目,后续转为springCloud项目 4 | - 用于展示三维四子棋的相关网站,如选择AI,本地,联机,熄灯模式 5 | - 将前端页面部署在Nginx反向代理服务器,并转发后端请求 6 | - 实现联机的与后端的对接 7 | - springSecurity登录界面 8 | - 新增游戏中用户信息,发送信息等功能,新增游戏大厅 9 | - 将前端服务器大部分适配手机 10 | - 整合web3d引擎,展示三维四子棋画面,以及加载画面等
11 | 12 | ## 部署条件: 13 | 后端服务器仅提供接口访问,前后端分离
14 | 前端依赖nginx服务器转发请求
15 | [nginx服务器下载地址:https://github.com/MrOwenovo/nginx-KarGoBang](https://github.com/MrOwenovo/nginx-KarGoBang) 16 | 17 | ## 首页地址: 18 | [三维四子棋首页:https://kargobang.top/](https://kargobang.top/) 19 | 20 | 21 | ## 后端接口文档swagger访问地址: 22 | [swagger文档:https://kargobang.top/swagger-ui/](https://kargobang.top/swagger-ui/) 23 | 24 | ## 项目内容: 25 | > ### KarGoBangWebServer内容: 26 | ![1.png](https://s2.loli.net/2022/08/17/r1GHgfbmvZFnKpM.png) 27 | ![2.png](https://s2.loli.net/2022/08/17/EO8tVco2jl1Yzgu.png) 28 |

首页内容

29 | 30 | ![web1.png](https://s2.loli.net/2022/07/01/6Lplj4e1DQzHbUd.png) 31 |

springSecurity登录界面

32 | 33 | ![3.png](https://s2.loli.net/2022/08/17/tn5hapXFLEHWvsm.png) 34 |

维四子棋-AI,本地,熄灯模式选择

35 | 36 | ![wev4.png](https://s2.loli.net/2022/07/01/rN7jUsVJqiX2Fbz.png) 37 |

三维四子棋AI-困难,中等,简单选择

38 | 39 | ![web5.png](https://s2.loli.net/2022/07/01/xs9tub6zK5LFXPV.png) 40 |

自定义联机界面

41 | 42 | ![web7.png](https://s2.loli.net/2022/07/01/C9EMjskc2Iz7YXh.png) 43 |

联机创建房间-加密

44 | 45 | ![web8.png](https://s2.loli.net/2022/07/01/7dyaiexk65EslnP.png) 46 |

联机创建房间成功-等待对方加入

47 | 48 | ![0730fb6fea44c4d71e5a0406a78d77b6.png](https://img.gejiba.com/images/0730fb6fea44c4d71e5a0406a78d77b6.png) 49 |

房间界面游戏大厅

50 | 51 | ![wbe2.png](https://s2.loli.net/2022/07/01/bIhHDMVkUL2Z5Tw.png) 52 |

三维四子棋加载界面

53 | 54 | ![2df0c2696a2dceddde3ab40212ec4871.png](https://img.gejiba.com/images/2df0c2696a2dceddde3ab40212ec4871.png) 55 |

加载界面用户信息以及发送信息

56 | 57 | ![web9.png](https://s2.loli.net/2022/07/01/8oAu592ghDTQMsx.png) 58 |

三维四子棋操作提示

59 | 60 | ![web10.png](https://s2.loli.net/2022/07/01/UcNkqfzlKa5B4JV.png) 61 |

AI游戏过程画面

62 | 63 | 64 |
65 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/VerifyServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.service.VerifyService; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.mail.SimpleMailMessage; 7 | import org.springframework.mail.javamail.JavaMailSender; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.Map; 13 | import java.util.Random; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @Service 17 | public class VerifyServiceImpl implements VerifyService{ 18 | 19 | @Resource 20 | JavaMailSender javaMailSender; 21 | @Resource 22 | RedisTemplate template; 23 | @Value("${spring.mail.username}") 24 | String username; 25 | 26 | private final static String EMAIL_TOKEN_KEY = "verify:code:"; 27 | 28 | 29 | @Override 30 | public boolean sendVerifyCode(String mail) { 31 | try { 32 | SimpleMailMessage message = new SimpleMailMessage(); 33 | message.setSubject("[三维四子棋]您的注册验证码"); 34 | Random random = new Random(); 35 | int code = random.nextInt(899999) + 100000; 36 | template.opsForValue().set(EMAIL_TOKEN_KEY + mail, code); 37 | template.expire(EMAIL_TOKEN_KEY + mail, 3, TimeUnit.MINUTES); 38 | message.setText("您的验证码为: " + code + ",三分钟内有效,请及时完成注册,如果不是本人操作,请忽略。"); 39 | message.setTo(mail); 40 | message.setFrom(username); 41 | javaMailSender.send(message); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | return false; 45 | } 46 | return true; 47 | 48 | } 49 | 50 | @Override 51 | public boolean doVerify(String mail, String code) { 52 | Object redisCode = template.opsForValue().get(EMAIL_TOKEN_KEY + mail); 53 | if (redisCode == null) return false; 54 | if (!code.equals(redisCode.toString())) return false; 55 | template.delete(EMAIL_TOKEN_KEY + mail); 56 | return true; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/listener/SessionListener.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.listener; 2 | 3 | import com.example.entity.constant.ThreadDetails; 4 | import com.example.service.util.IpTools; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.web.context.request.RequestContextHolder; 7 | import org.springframework.web.context.request.ServletRequestAttributes; 8 | 9 | import javax.servlet.ServletContext; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletRequestEvent; 12 | import javax.servlet.ServletRequestListener; 13 | import javax.servlet.annotation.WebListener; 14 | import javax.servlet.http.*; 15 | import java.util.HashMap; 16 | 17 | @Slf4j 18 | @WebListener 19 | public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener { 20 | 21 | @Override 22 | public void sessionCreated(HttpSessionEvent se) { 23 | HttpSession session = se.getSession(); 24 | ServletContext application = session.getServletContext(); 25 | HashMap sessions = (HashMap) application.getAttribute("sessions"); 26 | if (sessions == null) { 27 | sessions = new HashMap<>(); 28 | application.setAttribute("sessions", sessions); 29 | } 30 | 31 | // You need to replace this with the correct way to get the request object if possible 32 | // HttpServletRequest request = ...; 33 | 34 | // Store the IP address in the session 35 | String ipAddress = "IP_ADDRESS"; // Replace with actual IP address from the request 36 | session.setAttribute("ipAddress", ipAddress); 37 | sessions.put(ipAddress, session); 38 | } 39 | 40 | @Override 41 | public void sessionDestroyed(HttpSessionEvent se) { 42 | HttpSession session = se.getSession(); 43 | ServletContext application = session.getServletContext(); 44 | HashMap sessions = (HashMap) application.getAttribute("sessions"); 45 | String ipAddress = (String) session.getAttribute("ipAddress"); 46 | 47 | if (ipAddress != null && sessions != null) { 48 | sessions.remove(ipAddress); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/WebFilterFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import org.springframework.beans.factory.FactoryBean; 4 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 5 | 6 | import javax.servlet.Filter; 7 | import java.util.Arrays; 8 | 9 | public class WebFilterFactoryBean implements FactoryBean { 10 | 11 | private Filter filter; 12 | 13 | private int order; 14 | 15 | private String name; 16 | 17 | private String[] urlPatterns; 18 | 19 | @Override 20 | public FilterRegistrationBean getObject() { 21 | 22 | FilterRegistrationBean registration = new FilterRegistrationBean(); 23 | 24 | registration.setOrder(order); //设置顺序 25 | registration.setFilter(filter); //设置过滤器对象 26 | registration.setName(name); //过滤器名称 27 | registration.setUrlPatterns(Arrays.asList(urlPatterns)); //过滤器名称 28 | registration.addUrlPatterns("/*"); //路径 29 | 30 | return registration; 31 | } 32 | 33 | @Override 34 | public Class getObjectType() { 35 | return FilterRegistrationBean.class; 36 | } 37 | 38 | @Override 39 | public boolean isSingleton() { 40 | return true; 41 | } 42 | 43 | public Filter getFilter() { 44 | return filter; 45 | } 46 | 47 | public WebFilterFactoryBean setFilter(final Filter filter) { 48 | this.filter = filter; 49 | return this; 50 | } 51 | 52 | public int getOrder() { 53 | return order; 54 | } 55 | 56 | public WebFilterFactoryBean setOrder(final int order) { 57 | this.order = order; 58 | return this; 59 | } 60 | 61 | public String getName() { 62 | return name; 63 | } 64 | 65 | public WebFilterFactoryBean setName(final String name) { 66 | this.name = name; 67 | return this; 68 | } 69 | 70 | public String[] getUrlPatterns() { 71 | return urlPatterns; 72 | } 73 | 74 | public WebFilterFactoryBean setUrlPatterns(final String[] urlPatterns) { 75 | this.urlPatterns = urlPatterns; 76 | return this; 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/com/example/entity/repo/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.repo; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import io.swagger.annotations.ApiResponse; 6 | import lombok.*; 7 | import lombok.experimental.FieldDefaults; 8 | 9 | @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) 10 | @AllArgsConstructor 11 | @ToString 12 | @Getter 13 | public enum ResultCode { 14 | 15 | SUCCESS(200, "成功"), 16 | 17 | LOGIN_SUCCESS(200, "登录成功"), 18 | 19 | LOGOUT_SUCCESS(200, "登出成功"), 20 | 21 | VERIFY_SUCCESS(200, "发送验证码成功"), 22 | 23 | REGISTER_SUCCESS(200, "注册成功"), 24 | 25 | CREATE_ROOM_SUCCESS(200, "创建房间成功"), 26 | 27 | OPPONENT_IS_IN(200, "对手已经加入房间!"), 28 | 29 | ADD_ROOM_SUCCESS(200, "加入房间成功!"), 30 | 31 | WHITE_MOVE_SUCCESS(200, "解析白棋行动成功!"), 32 | 33 | BLACK_MOVE_SUCCESS(200, "解析黑棋行动成功!"), 34 | 35 | WAIT_WHITE_MOVE_SUCCESS(200, "白棋已经下子!"), 36 | 37 | WAIT_BLACK_MOVE_SUCCESS(200, "黑棋已经下子!"), 38 | 39 | JSESSIONID_IS_RETURN(200,"已经将jSessionId返回"), 40 | 41 | MODIFY_SUCCESS(200,"修改成功!"), 42 | 43 | SEND_SUCCESS(200,"发送成功!"), 44 | 45 | UPLOAD_SUCCESS(200,"上传成功!"), 46 | 47 | 48 | 49 | 50 | FAILURE(400,"错误"), 51 | 52 | NO_PERMISSION(401,"没有权限"), 53 | 54 | LOGIN_FAILURE(402,"账号或密码错误,请重新输入"), 55 | 56 | LOGOUT_FAILURE(403,"登出错误"), 57 | 58 | VERIFY_FAILURE(404, "发送验证码失败"), 59 | 60 | REGISTER_FAILURE(405, "注册失败"), 61 | 62 | ALREADY(406, "数据库中已经存在该信息"), 63 | 64 | NOT_EXIST(412, "数据库中不存在该信息"), 65 | 66 | VERIFY_WRONG(407, "验证码错误!"), 67 | 68 | OPPONENT_NOT_IN(408, "正在等待...!"), 69 | 70 | ADD_ROOM_FAILURE(409, "加入房间失败"), 71 | 72 | WHITE_MOVE_FAILURE(410, "解析白棋行动失败"), 73 | 74 | BLACK_MOVE_FAILURE(411, "解析黑棋行动失败"), 75 | 76 | THREAD_LOCAL_IS_NULL(412, "从ThreadLocal中取出为空时抛出的异常"), 77 | 78 | PASSWORD_IS_WRONG(413,"密码错误!"), 79 | 80 | NOT_EXIST_IN_COOKIE(414,"在请求头中不存在该Cookie!"), 81 | 82 | MODIFY_FAILURE(415,"修改失败!!"), 83 | 84 | SEND_FAILURE(415,"发送失败!!"), 85 | 86 | UPLOAD_FAILURE(415,"上传失败!!"); 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | int code; 100 | String message; 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/filter/GameFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.filter; 2 | 3 | import com.example.anno.MyFilterOrder; 4 | import com.example.entity.constant.ThreadDetails; 5 | import com.example.service.impl.RoomServiceImpl; 6 | import com.example.service.util.IpTools; 7 | import com.example.service.util.RedisTools; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.core.annotation.Order; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.Resource; 13 | import javax.servlet.*; 14 | import javax.servlet.annotation.WebFilter; 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.io.IOException; 17 | 18 | 19 | 20 | @Slf4j 21 | //@MyFilterOrder(value = 100,urlPatterns ={"/api/game/*", "/api/chat/*"} ) 22 | public class GameFilter implements Filter { 23 | 24 | private static RedisTools redisTools; 25 | 26 | 27 | @Override 28 | public void init(FilterConfig filterConfig) throws ServletException { 29 | log.info("游戏线程拦截器启用"); 30 | } 31 | 32 | @Override 33 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 34 | HttpServletRequest request = (HttpServletRequest) servletRequest; 35 | if (!request.getServletPath().startsWith("/api/game/changePassNumber")) { 36 | 37 | //把roomNumber从redis中取出 38 | String roomNumber = redisTools.getFromRedis(RoomServiceImpl.IP_ROOM_TOKEN_KEY + IpTools.getIpAddress(request), "roomNumber"); 39 | ThreadDetails.redisRoomNumber.set(roomNumber); 40 | 41 | //把自己username从redis中取出 42 | String username = redisTools.getFromRedis(RoomServiceImpl.IP_USERNAME_TOKEN_KEY + IpTools.getIpAddress(request), "username"); 43 | ThreadDetails.redisUsername.set(username); 44 | 45 | //把对方username从redis中取出 46 | String opponentUsername = redisTools.getFromRedis(RoomServiceImpl.ME_OPPONENT_USERNAME_TOKEN_KEY + username, "opponentUsername"); 47 | ThreadDetails.redisOpponentUsername.set(opponentUsername); 48 | 49 | filterChain.doFilter(servletRequest, servletResponse); 50 | 51 | } 52 | } 53 | 54 | @Override 55 | public void destroy() { 56 | Filter.super.destroy(); 57 | } 58 | 59 | public static void setRedisTools(RedisTools redisTools) { 60 | GameFilter.redisTools = redisTools; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/RedisMybatisCache.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import com.example.entity.data.UserDetail; 4 | import lombok.AllArgsConstructor; 5 | import org.apache.ibatis.cache.Cache; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Lazy; 9 | import org.springframework.context.annotation.Scope; 10 | import org.springframework.data.redis.connection.RedisServerCommands; 11 | import org.springframework.data.redis.core.RedisCallback; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.Date; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | /** 23 | * Mybatis 全局二级缓存,token储存在redis中 24 | */ 25 | 26 | public class RedisMybatisCache implements Cache { 27 | 28 | private final String id; 29 | 30 | private static RedisTemplate template; 31 | 32 | private final static String MYBATIS_CACHE_KEY = "mybatis:cache:"; 33 | 34 | //注意构造方法必须带一个String类型的参数接收id 35 | public RedisMybatisCache(String id) { 36 | this.id = id; 37 | } 38 | 39 | //初始化时通过配置类将RedisTemplate给过来 40 | public static void setTemplate(RedisTemplate template) { 41 | RedisMybatisCache.template = template; 42 | } 43 | 44 | 45 | @Override 46 | public String getId() { 47 | return id; 48 | } 49 | 50 | @Override 51 | public void putObject(Object o, Object o1) { 52 | template.opsForValue().set(MYBATIS_CACHE_KEY+o, o1,60, TimeUnit.SECONDS); 53 | } 54 | 55 | @Override 56 | public Object getObject(Object o) { 57 | return template.opsForValue().get(MYBATIS_CACHE_KEY+o); 58 | } 59 | 60 | @Override 61 | public Object removeObject(Object o) { 62 | return template.delete(MYBATIS_CACHE_KEY+o); 63 | } 64 | 65 | @Override 66 | public void clear() { 67 | template.execute((RedisCallback) connection -> { 68 | connection.flushDb(); 69 | return null; 70 | }); 71 | } 72 | 73 | @Override 74 | public int getSize() { 75 | return template.execute(RedisServerCommands::dbSize).intValue(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/example/entity/repo/BASE64DecodeMultipartFile.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.repo; 2 | 3 | import com.sun.mail.iap.ByteArray; 4 | import org.springframework.web.multipart.MultipartFile; 5 | import sun.misc.BASE64Decoder; 6 | 7 | import java.io.*; 8 | 9 | public class BASE64DecodeMultipartFile implements MultipartFile { 10 | 11 | private final byte[] imgContent; 12 | private final String header; 13 | 14 | public BASE64DecodeMultipartFile(byte[] imgContent, String header) { 15 | this.imgContent = imgContent; 16 | this.header = header.split(",")[0]; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1]; 22 | } 23 | 24 | @Override 25 | public String getOriginalFilename() { 26 | return System.currentTimeMillis() + Math.random() * 10000 + "." + header.split("/")[1]; 27 | } 28 | 29 | @Override 30 | public String getContentType() { 31 | return header.split(":")[1]; 32 | } 33 | 34 | @Override 35 | public boolean isEmpty() { 36 | return imgContent == null || imgContent.length == 0; 37 | } 38 | 39 | @Override 40 | public long getSize() { 41 | return imgContent.length; 42 | } 43 | 44 | @Override 45 | public byte[] getBytes() throws IOException { 46 | return imgContent; 47 | } 48 | 49 | @Override 50 | public InputStream getInputStream() throws IOException { 51 | return new ByteArrayInputStream(imgContent); 52 | } 53 | 54 | @Override 55 | public void transferTo(File dest) throws IOException, IllegalStateException { 56 | new FileOutputStream(dest).write(imgContent); 57 | } 58 | 59 | public static MultipartFile base64ToMultipart(String base64) { 60 | try { 61 | String[] baseStrs = base64.split(","); 62 | 63 | BASE64Decoder decoder = new BASE64Decoder(); 64 | byte[] b = new byte[0]; 65 | b = decoder.decodeBuffer(baseStrs[1]); 66 | 67 | for (int i = 0; i < b.length; ++i) { 68 | if (b[i] < 0) { 69 | b[i] += 256; 70 | } 71 | } 72 | return new BASE64DecodeMultipartFile(b, baseStrs[0]); 73 | } catch (IOException e) { 74 | e.printStackTrace(); 75 | return null; 76 | } 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/GamePreController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.controller.exception.NotExistInRedisException; 4 | import com.example.entity.constant.ThreadDetails; 5 | import com.example.entity.repo.RestBean; 6 | import com.example.entity.repo.RestBeanBuilder; 7 | import com.example.entity.repo.ResultCode; 8 | import com.example.service.AuthService; 9 | import com.example.service.impl.RoomServiceImpl; 10 | import com.example.service.util.IpTools; 11 | import io.swagger.annotations.*; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.core.authority.AuthorityUtils; 17 | import org.springframework.security.core.context.SecurityContext; 18 | import org.springframework.security.core.context.SecurityContextHolder; 19 | import org.springframework.security.core.userdetails.User; 20 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 21 | import org.springframework.web.bind.annotation.*; 22 | 23 | import javax.annotation.Resource; 24 | import javax.servlet.ServletContext; 25 | import javax.servlet.http.Cookie; 26 | import javax.servlet.http.HttpServletRequest; 27 | import javax.servlet.http.HttpServletResponse; 28 | import javax.servlet.http.HttpSession; 29 | import java.util.HashMap; 30 | 31 | @Slf4j 32 | @Api(tags = "游戏前的预检和登录", description = "进入游戏房间后后台登录等,后期会换成oauth2登录") 33 | @RestController 34 | @RequestMapping("/pre") 35 | public class GamePreController { 36 | 37 | @Resource 38 | AuthService authService; 39 | @Resource 40 | RedisTemplate template; 41 | 42 | 43 | 44 | @ApiResponses({ 45 | @ApiResponse(code = 200, message = "后台登录成功"), 46 | @ApiResponse(code = 400, message = "后台登录失败") 47 | }) 48 | @ApiOperation(value = "进行后台登录", notes = "从redis中取出账号密码,以usernamePasswordAuthenticationToken形式后台登陆") 49 | @GetMapping("/preLogin") 50 | public RestBean preLogin(HttpServletRequest request,HttpServletResponse response) throws Exception { 51 | //从redis中获取session 52 | Object o = template.opsForValue().get(RoomServiceImpl.IP_SESSION_TOKEN_KEY + IpTools.getIpAddress(request)); 53 | if (o== null) throw new NotExistInRedisException("Redis中不存在该sessionId"); 54 | String sessionId = (String) o; 55 | Cookie cookie = new Cookie("JSESSIONID", sessionId); 56 | response.addCookie(cookie); 57 | return RestBeanBuilder.builder().code(ResultCode.LOGIN_SUCCESS).messageType(RestBeanBuilder.USER_DEFINED).message("后台登陆成功!").build().ToRestBean(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/ChatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.controller.exception.NotExistInMysqlException; 4 | import com.example.dao.AuthMapper; 5 | import com.example.entity.constant.ThreadDetails; 6 | import com.example.entity.data.UserDetail; 7 | import com.example.service.ChatService; 8 | import com.example.service.util.RedisTools; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.stereotype.Service; 11 | 12 | import javax.annotation.Resource; 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | @Service 19 | public class ChatServiceImpl implements ChatService { 20 | 21 | @Resource 22 | AuthMapper authMapper; 23 | @Resource 24 | RedisTools redisTools; 25 | @Resource 26 | RedisTemplate template; 27 | 28 | public final static String SEND_OPPONENT_USERNAME_MESSAGE_TOKEN_KEY = "game:room:user:send:opponent-username-message:"; 29 | 30 | @Override 31 | public void sendMessage(HttpServletRequest request, String message) { 32 | //获取对方username 33 | String opponentUsername = ThreadDetails.redisOpponentUsername.get(); 34 | 35 | //将信息以对方username为key存入redis 36 | redisTools.setToRedis(SEND_OPPONENT_USERNAME_MESSAGE_TOKEN_KEY + opponentUsername, message,15,RedisTools.SECONDS); 37 | } 38 | 39 | @Override 40 | public String getMessage() { 41 | String username = ThreadDetails.redisUsername.get(); 42 | //获取redis中储存的对方的信息 43 | 44 | String message = redisTools.getFromRedis(SEND_OPPONENT_USERNAME_MESSAGE_TOKEN_KEY + username, "对方没有发送信息!"); 45 | template.delete(SEND_OPPONENT_USERNAME_MESSAGE_TOKEN_KEY + username); 46 | return message; 47 | 48 | 49 | } 50 | 51 | @Override 52 | public List getMyAndOpponentUserDetailsInGame() { 53 | List result = new ArrayList(); 54 | String username = ThreadDetails.redisUsername.get(); 55 | String opponentUsername = ThreadDetails.redisOpponentUsername.get(); 56 | //获取自己的用户信息 57 | 58 | CompletableFuture> select = CompletableFuture.supplyAsync(() -> 59 | authMapper.findUserByUsername(username).orElseThrow(() -> new NotExistInMysqlException("数据库中没有该用户")) 60 | ).thenCombine(CompletableFuture.supplyAsync(() -> 61 | authMapper.findUserByUsername(opponentUsername).orElseThrow(() -> new NotExistInMysqlException("数据库中没有该用户")) 62 | ), (me, opponent) -> { 63 | result.add(me); 64 | result.add(opponent); 65 | return result; 66 | }); 67 | 68 | return select.join(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/ChatApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.dao.ChatMapper; 4 | import com.example.entity.data.UserDetail; 5 | import com.example.entity.repo.RestBean; 6 | import com.example.entity.repo.RestBeanBuilder; 7 | import com.example.entity.repo.ResultCode; 8 | import com.example.service.ChatService; 9 | import io.swagger.annotations.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.annotation.Resource; 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.util.List; 16 | 17 | @Slf4j 18 | @Api(tags = "用户聊天", description = "用户互相发送消息或通讯") 19 | @RestController 20 | @RequestMapping("/api/chat") 21 | public class ChatApiController { 22 | 23 | @Resource 24 | ChatService chatService; 25 | 26 | @ApiImplicitParams({ 27 | @ApiImplicitParam(name = "message", paramType = "query", required = true, dataType = "string",dataTypeClass = String.class, example = "123456"), 28 | }) 29 | @ApiResponses({ 30 | @ApiResponse(code = 200, message = "发送成功"), 31 | @ApiResponse(code = 400, message = "发送失败,请查看日志"), 32 | @ApiResponse(code = 401, message = "没有权限") 33 | }) 34 | @ApiOperation(value = "发送消息", notes = "游戏中发送的游戏信息") 35 | @PostMapping("/sendMessage") 36 | public RestBean sendMessage(HttpServletRequest request, @RequestParam(value = "message") String message) { 37 | chatService.sendMessage(request, message); 38 | return RestBeanBuilder.builder().code(ResultCode.SEND_SUCCESS).build().ToRestBean(); 39 | } 40 | 41 | 42 | @ApiResponses({ 43 | @ApiResponse(code = 200, message = "获取成功"), 44 | @ApiResponse(code = 400, message = "获取失败,请查看日志"), 45 | @ApiResponse(code = 401, message = "没有权限") 46 | }) 47 | @ApiOperation(value = "获取对方消息", notes = "获取游戏中对方发送的游戏信息") 48 | @GetMapping("/getMessage") 49 | public RestBean getMessage() { 50 | String message = chatService.getMessage(); 51 | return RestBeanBuilder.builder().code(ResultCode.SEND_SUCCESS).data(message).build().ToRestBean(); 52 | } 53 | 54 | @ApiResponses({ 55 | @ApiResponse(code = 200, message = "获取成功"), 56 | @ApiResponse(code = 400, message = "获取失败,请查看日志"), 57 | @ApiResponse(code = 401, message = "没有权限") 58 | }) 59 | @ApiOperation(value = "获取双方的用户信息", notes = "获取自己和对方的所有数据") 60 | @GetMapping("/getUserDetails") 61 | public RestBean> getMyAndOpponentUserDetailsInGame() { 62 | List myAndOpponentUserDetailsInGame = chatService.getMyAndOpponentUserDetailsInGame(); 63 | return RestBeanBuilder.>builder().code(ResultCode.SEND_SUCCESS).data(myAndOpponentUserDetailsInGame).build().ToRestBean(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/util/RedisTools.java: -------------------------------------------------------------------------------- 1 | package com.example.service.util; 2 | 3 | import com.example.KarGoBangWebServerApplication; 4 | import com.example.controller.exception.NotExistInRedisException; 5 | import com.example.controller.exception.ThreadLocalIsNullException; 6 | import com.example.service.impl.RoomServiceImpl; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @Component 17 | public class RedisTools { 18 | 19 | public final static int MINUTE = 0; 20 | 21 | public final static int SECONDS = 1; 22 | 23 | @Resource 24 | RedisTemplate template; 25 | 26 | public T getFromRedis(String key,String reason) { 27 | Object o1 = template.opsForValue().get(key); 28 | if (o1 == null) throw new ThreadLocalIsNullException(reason); 29 | return (T) o1; 30 | 31 | } 32 | 33 | public Map getHashMapFromRedis(String key,String reason) { 34 | Map entries = template.opsForHash().entries(key); 35 | if (entries.isEmpty()) throw new NotExistInRedisException(reason); 36 | 37 | return entries; 38 | 39 | } 40 | 41 | public void setToRedis(String key,T value,int time,int type) { 42 | template.opsForValue().set(key,value); 43 | if (type ==MINUTE) 44 | template.expire(key, time, TimeUnit.MINUTES); 45 | if (type ==SECONDS) 46 | template.expire(key, time, TimeUnit.SECONDS); 47 | 48 | } 49 | 50 | public void setHashMapToRedis(String key,HashMap value,int time,int type) { 51 | template.opsForHash().putAll(key,value); 52 | if (type ==MINUTE) 53 | template.expire(key, time, TimeUnit.MINUTES); 54 | if (type ==SECONDS) 55 | template.expire(key, time, TimeUnit.SECONDS); 56 | 57 | } 58 | public void setToRedis(String key,T value) { 59 | template.opsForValue().set(key,value); 60 | template.expire(key, 30, TimeUnit.MINUTES); 61 | } 62 | public void setHashMapToRedis(String key,HashMap value) { 63 | template.opsForHash().putAll(key,value); 64 | template.expire(key, 30, TimeUnit.MINUTES); 65 | } 66 | 67 | public Set getKeys(String pattern) { 68 | return template.keys(pattern); 69 | } 70 | 71 | public HashMap getRedisValues() { 72 | //获得所有Key 73 | Set keys = template.keys("*"); 74 | //创建集合 75 | HashMap map = new HashMap<>(); 76 | //循环 77 | for (Object key : keys) { 78 | Object value = template.opsForValue().get(key); 79 | map.put(key, value); 80 | } 81 | //返回集合 82 | return map; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/AuthGuestApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.entity.repo.RestBean; 4 | import com.example.entity.repo.RestBeanBuilder; 5 | import com.example.entity.repo.ResultCode; 6 | import io.swagger.annotations.*; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.authentication.jaas.JaasAuthenticationToken; 9 | import org.springframework.security.core.authority.AuthorityUtils; 10 | import org.springframework.security.core.context.SecurityContext; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.web.bind.annotation.*; 13 | import springfox.documentation.annotations.ApiIgnore; 14 | 15 | @Api(tags = "游客用户验证",description = "以游客身份登录,不支持远程联机") 16 | @RestController 17 | @RequestMapping("/api/auth/guest") 18 | public class AuthGuestApiController extends AuthApiController{ 19 | 20 | @ApiImplicitParams({ 21 | @ApiImplicitParam(name = "username", paramType = "query", dataType = "string",dataTypeClass = String.class,example = "user"), 22 | @ApiImplicitParam(name = "password", paramType = "query", dataType = "string",dataTypeClass = String.class, example = "123456") 23 | }) 24 | @ApiResponses({ 25 | @ApiResponse(code = 200, message = "登录成功"), 26 | }) 27 | @ApiOperation(value = "发送游客登录请求",notes = "以游客身份登录") 28 | @GetMapping("/login") 29 | @Override 30 | public RestBean login(@RequestParam(value = "username",required = false) String username, @RequestParam(value = "password",required = false) String password) { 31 | SecurityContext securityContext = SecurityContextHolder.getContext(); 32 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("guest", null, 33 | AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_guest")); 34 | securityContext.setAuthentication(token); 35 | return RestBeanBuilder.builder().code(ResultCode.LOGIN_SUCCESS).build().ToRestBean(); 36 | } 37 | 38 | @ApiResponses({ 39 | @ApiResponse(code = 200, message = "登录成功"), 40 | @ApiResponse(code = 401, message = "没有权限"), 41 | }) 42 | @ApiOperation(value = "验证游客身份",notes = "如果是游客身份,则拒绝访问远程联机",authorizations = @Authorization("user")) 43 | @GetMapping("/isGuest") 44 | public RestBean isGuest() { 45 | //让security自动判断是否有权限 46 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).build().ToRestBean(); 47 | } 48 | 49 | @ApiIgnore 50 | @Override 51 | public RestBean logout() { 52 | return super.logout(); 53 | } 54 | 55 | @ApiIgnore 56 | @Override 57 | public RestBean getVerify(String email) { 58 | return super.getVerify(email); 59 | } 60 | 61 | @ApiIgnore 62 | @Override 63 | public RestBean register(String username, String password, String email, String code) { 64 | return super.register(username, password, email, code); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/listener/RoomListener.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.listener; 2 | 3 | import com.example.controller.exception.NotExistInMysqlException; 4 | import com.example.controller.listener.event.AddRoomEvent; 5 | import com.example.controller.listener.event.CreateRoomEvent; 6 | import com.example.dao.AuthMapper; 7 | import com.example.entity.data.HallInfoDetail; 8 | import com.example.entity.data.UserDetail; 9 | import com.example.service.impl.RoomServiceImpl; 10 | import com.example.service.util.RedisTools; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.context.event.EventListener; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.context.ServletContextAware; 15 | 16 | import javax.annotation.Resource; 17 | import javax.servlet.ServletContext; 18 | import java.util.HashMap; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | @Slf4j 22 | @Component 23 | public class RoomListener implements ServletContextAware { 24 | 25 | private static ServletContext servletContext; 26 | 27 | @Resource 28 | RedisTools redisTools; 29 | @Resource 30 | AuthMapper authMapper; 31 | 32 | @EventListener 33 | public void createRoomListener(CreateRoomEvent event) { 34 | log.info("{}监听到事件源: {}", RoomListener.class.getName(), event.getSource()); 35 | ConcurrentHashMap rooms = (ConcurrentHashMap) servletContext.getAttribute("rooms"); 36 | if (rooms == null) { 37 | rooms = new ConcurrentHashMap<>(); 38 | servletContext.setAttribute("rooms", rooms); 39 | } 40 | String roomNumber = (String) event.getSource(); 41 | //从redis中获取创建房间的用户 42 | String username = redisTools.getFromRedis(RoomServiceImpl.ROOM_ID_USERNAME_TOKEN_KEY + roomNumber, "Redis中没有roomNumber对应的username"); 43 | UserDetail user = authMapper.findUserByUsername(username).orElseThrow(() -> new NotExistInMysqlException("数据库中没有该用户")); 44 | HallInfoDetail miniRoom = HallInfoDetail.builder().roomNumber(roomNumber).username(username).image(user.getUserInfo().getIcon()).message(user.getUserInfo().getMessage()).build(); 45 | //将用户信息放入servletContext 46 | rooms.put(roomNumber, miniRoom); 47 | log.info("{}处理{}事件成功: {}", RoomListener.class.getName(), event.getSource()); 48 | 49 | } 50 | 51 | @EventListener 52 | public void addRoomListener(AddRoomEvent event) { 53 | log.info("{}监听到事件源: {}", RoomListener.class.getName(), event.getSource()); 54 | ConcurrentHashMap rooms = (ConcurrentHashMap) servletContext.getAttribute("rooms"); 55 | String roomNumber = (String) event.getSource(); 56 | // 将用户信息从 servletContext 删除 57 | if (rooms != null) { 58 | rooms.remove(roomNumber); 59 | } 60 | log.info("{}处理{}事件成功: 删除{}", RoomListener.class.getName(), event.getSource(), roomNumber); 61 | } 62 | 63 | @Override 64 | public void setServletContext(final ServletContext servletContext) { 65 | RoomListener.servletContext = servletContext; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/RedisTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.core.RedisHash; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; 8 | import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; 9 | 10 | import javax.annotation.PostConstruct; 11 | import javax.annotation.Resource; 12 | import java.util.Date; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * springSecurity RememberMe的token 储存在redis 19 | */ 20 | @Slf4j 21 | @Configuration 22 | public class RedisTokenRepository implements PersistentTokenRepository { 23 | 24 | private final static String REMEMBER_ME_KEY = "spring:security:rememberMe:"; 25 | 26 | @Resource 27 | RedisTemplate tokenTemplate; 28 | 29 | @PostConstruct 30 | private void init() { 31 | log.info("存储Spring-RememberMe缓存的Redis配置加载");; 32 | } 33 | 34 | //由于PersistentRememberMeToken没有实现序列化接口,这里用Map存放 35 | private PersistentRememberMeToken getToken(String series) { 36 | Map map = tokenTemplate.opsForHash().entries(series); 37 | if (map.isEmpty()) return null; 38 | Object date = map.get("date"); 39 | if (date==null) return null; 40 | return new PersistentRememberMeToken( 41 | (String) map.get("username"), 42 | (String) map.get("series"), 43 | (String) map.get("tokenValue"), 44 | new Date(Long.parseLong((String) map.get("date"))) 45 | ); 46 | } 47 | 48 | private void setTokenTemplate(PersistentRememberMeToken token) { 49 | Map map = new HashMap<>(); 50 | map.put("username", token.getUsername()); 51 | map.put("series", token.getSeries()); 52 | map.put("tokenValue", token.getTokenValue()); 53 | map.put("date", token.getDate().getTime()); 54 | tokenTemplate.opsForHash().putAll(REMEMBER_ME_KEY + map.get("series"), map); 55 | tokenTemplate.expire(REMEMBER_ME_KEY + map.get("series"), 1, TimeUnit.DAYS); 56 | } 57 | 58 | 59 | @Override 60 | public void createNewToken(PersistentRememberMeToken token) { 61 | tokenTemplate.opsForValue().set(REMEMBER_ME_KEY + "name:" + token.getUsername(), token.getSeries()); 62 | tokenTemplate.expire(REMEMBER_ME_KEY + "name:" + token.getUsername(), 1, TimeUnit.DAYS); 63 | } 64 | 65 | @Override 66 | public void updateToken(String series, String tokenValue, Date lastUsed) { 67 | PersistentRememberMeToken token = getToken(series); 68 | if (token != null) 69 | this.setTokenTemplate(new PersistentRememberMeToken(token.getUsername(), series, tokenValue, lastUsed)); 70 | } 71 | 72 | @Override 73 | public PersistentRememberMeToken getTokenForSeries(String seriesId) { 74 | return getToken(seriesId); 75 | } 76 | 77 | //通过username找series直接删两个 78 | @Override 79 | public void removeUserTokens(String username) { 80 | String series= (String) tokenTemplate.opsForValue().get(REMEMBER_ME_KEY + "name:" + username); 81 | tokenTemplate.delete(REMEMBER_ME_KEY + "name:" + username); 82 | tokenTemplate.delete(REMEMBER_ME_KEY + series); 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/RoomApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.entity.data.UserDetail; 4 | import com.example.entity.repo.RestBean; 5 | import com.example.entity.repo.RestBeanBuilder; 6 | import com.example.entity.repo.ResultCode; 7 | import com.example.service.RoomService; 8 | import com.example.tool.ResultSetUtil; 9 | import io.swagger.annotations.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import javax.annotation.Resource; 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.sql.Connection; 17 | import java.sql.DriverManager; 18 | import java.sql.ResultSet; 19 | import java.sql.Statement; 20 | 21 | @Slf4j 22 | @Api(tags = "房间验证", description = "用于远程联机的房间验证") 23 | @RestController 24 | @RequestMapping("/api/room") 25 | public class RoomApiController { 26 | 27 | @Resource 28 | RoomService roomService; 29 | 30 | 31 | @ApiImplicitParams({ 32 | @ApiImplicitParam(name = "number", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class, example = "user"), 33 | }) 34 | @ApiResponses({ 35 | @ApiResponse(code = 200, message = "创建房间成功"), 36 | @ApiResponse(code = 400, message = "房间已经存在"), 37 | @ApiResponse(code = 400, message = "错误"), 38 | }) 39 | @ApiOperation(value = "发起创建房间请求", notes = "用房间号创建房间,无加密号") 40 | @PostMapping("/createRoom") 41 | public RestBean createRoom(HttpServletRequest request, @RequestParam(value = "number") String number) { 42 | roomService.createRoom(request, number); 43 | return RestBeanBuilder.builder().code(ResultCode.CREATE_ROOM_SUCCESS).build().ToRestBean(); 44 | 45 | } 46 | 47 | @ApiImplicitParams({ 48 | @ApiImplicitParam(name = "number", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class, example = "user"), 49 | @ApiImplicitParam(name = "password", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class, example = "123456") 50 | }) 51 | @ApiResponses({ 52 | @ApiResponse(code = 200, message = "创建房间成功"), 53 | @ApiResponse(code = 400, message = "房间已经存在"), 54 | @ApiResponse(code = 400, message = "错误"), 55 | }) 56 | @ApiOperation(value = "发起创建加密房间请求", notes = "用房间号和加密号创建房间") 57 | @PostMapping("/createRoomSecret") 58 | public RestBean createRoomSecret(HttpServletRequest request, @RequestParam(value = "number") String number, @RequestParam("password") String password) { 59 | roomService.createRoomSecret(request, number, password); 60 | return RestBeanBuilder.builder().code(ResultCode.CREATE_ROOM_SUCCESS).build().ToRestBean(); 61 | } 62 | 63 | @ApiImplicitParams({ 64 | @ApiImplicitParam(name = "number", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class, example = "user"), 65 | @ApiImplicitParam(name = "password", paramType = "query", dataType = "string", dataTypeClass = String.class, example = "123456") 66 | }) 67 | @ApiResponses({ 68 | @ApiResponse(code = 200, message = "加入房间成功"), 69 | @ApiResponse(code = 400, message = "加入房间失败"), 70 | }) 71 | @ApiOperation(value = "发起加入房间请求", notes = "用房间号和加密号加入房间") 72 | @PostMapping("/addRoom") 73 | public RestBean addRoom(HttpServletRequest request, @RequestParam(value = "number") String number, @RequestParam("password") String password) { 74 | return roomService.addRoom(request, number, password) ? 75 | RestBeanBuilder.builder().code(ResultCode.ADD_ROOM_SUCCESS).build().ToRestBean() : 76 | RestBeanBuilder.builder().code(ResultCode.ADD_ROOM_FAILURE).build().ToRestBean(); 77 | } 78 | 79 | 80 | @ApiResponses({ 81 | @ApiResponse(code = 200, message = "对方已经进入房间"), 82 | @ApiResponse(code = 400, message = "正在等待..."), 83 | }) 84 | @ApiOperation(value = "查看对手是否加入房间", notes = "每隔三秒请求一次,判断对手是否进入房间") 85 | @GetMapping("/waitForOpponent") 86 | public RestBean waitForOpponent(HttpServletRequest request) { 87 | roomService.waitForOpponent(request); 88 | return RestBeanBuilder.builder().code(ResultCode.OPPONENT_IS_IN).build().ToRestBean(); 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/SqlApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.entity.repo.RestBean; 4 | import com.example.entity.repo.RestBeanBuilder; 5 | import com.example.entity.repo.ResultCode; 6 | import com.example.tool.ResultSetUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.ibatis.annotations.Select; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.web.bind.annotation.*; 11 | import springfox.documentation.annotations.ApiIgnore; 12 | 13 | import java.sql.Connection; 14 | import java.sql.DriverManager; 15 | import java.sql.ResultSet; 16 | import java.sql.Statement; 17 | 18 | @Slf4j 19 | @ApiIgnore 20 | @RestController 21 | @RequestMapping("/api/dao") 22 | public class SqlApiController { 23 | @Value("${spring.datasource.url}") 24 | String url; 25 | 26 | 27 | /** 28 | * 执行查询 29 | * @param sql sql语句 30 | * @return 后端执行结果 31 | */ 32 | @GetMapping("/executeQuery") 33 | public RestBean executeQuery(@RequestParam(value = "sql") String sql) { 34 | int count = 0; 35 | try ( 36 | Connection connection = DriverManager.getConnection(url); 37 | Statement statement = connection.createStatement(); 38 | ) { 39 | ResultSet rs = statement.executeQuery(sql); 40 | statement.setFetchSize(Integer.MIN_VALUE); 41 | while (rs.next()) { 42 | count++; 43 | } 44 | log.info("executeQuery count:" + count); 45 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).messageType(RestBeanBuilder.USER_DEFINED) 46 | .message("executeQuery count:" + count).data(ResultSetUtil.resultSetToJson(rs)).build().ToRestBean(); 47 | 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | return RestBeanBuilder.builder().code(ResultCode.FAILURE).messageType(RestBeanBuilder.USER_DEFINED) 51 | .message("执行sql语句时出现错误,请联系管理员").build().ToRestBean(); 52 | } 53 | 54 | } 55 | 56 | /** 57 | * 执行更新 58 | * @param sql sql语句 59 | * @return 后端执行结果 60 | */ 61 | @PostMapping("/executeUpdate") 62 | public RestBean executeUpdate(@RequestParam(value = "sql") String sql) { 63 | try ( 64 | Connection connection = DriverManager.getConnection(url); 65 | Statement statement = connection.createStatement(); 66 | ) { 67 | int updateCount = statement.executeUpdate(sql); 68 | log.info("executeUpdate count:" + updateCount); 69 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).messageType(RestBeanBuilder.USER_DEFINED) 70 | .message("executeUpdate:" + updateCount).build().ToRestBean(); 71 | 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | return RestBeanBuilder.builder().code(ResultCode.FAILURE).messageType(RestBeanBuilder.USER_DEFINED) 75 | .message("执行sql语句时出现错误,请联系管理员").build().ToRestBean(); 76 | } 77 | 78 | } 79 | 80 | /** 81 | * 执行全部模糊操作 82 | * @param sql sql语句 83 | * @return 后端执行结果 84 | */ 85 | @RequestMapping("/execute") 86 | public RestBean execute(@RequestParam(value = "sql") String sql) { 87 | int count = 0; 88 | try ( 89 | Connection connection = DriverManager.getConnection(url); 90 | Statement statement = connection.createStatement(); 91 | ) { 92 | ResultSet rs ; 93 | statement.setFetchSize(Integer.MIN_VALUE); 94 | if (statement.execute(sql)) { //执行的是query 95 | rs = statement.getResultSet(); 96 | 97 | while (rs.next()) { 98 | count++; 99 | } 100 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).messageType(RestBeanBuilder.USER_DEFINED) 101 | .message("executeQuery:" + count).data(ResultSetUtil.resultSetToJson(rs)).build().ToRestBean(); 102 | 103 | } else { //执行的是update 104 | int updateCount=statement.getUpdateCount(); 105 | return RestBeanBuilder.builder().code(ResultCode.SUCCESS).messageType(RestBeanBuilder.USER_DEFINED) 106 | .message("executeUpdate:" + updateCount).build().ToRestBean(); 107 | 108 | } 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | return RestBeanBuilder.builder().code(ResultCode.FAILURE).messageType(RestBeanBuilder.USER_DEFINED) 112 | .message("执行sql语句时出现错误,请联系管理员").build().ToRestBean(); 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/example/tool/PictureTools.java: -------------------------------------------------------------------------------- 1 | package com.example.tool; 2 | 3 | import lombok.SneakyThrows; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.logging.Log; 6 | import org.apache.commons.logging.LogFactory; 7 | 8 | import javax.imageio.ImageIO; 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.awt.image.BufferedImage; 12 | import java.io.ByteArrayInputStream; 13 | import java.io.ByteArrayOutputStream; 14 | import java.io.IOException; 15 | 16 | 17 | @Slf4j 18 | public class PictureTools { 19 | 20 | /** 21 | * 将BufferedImage图片转化成字符串储存在数据库中 22 | */ 23 | public static String imageToString(BufferedImage image,String format) { 24 | StringBuilder sb2 = new StringBuilder(); 25 | byte[] img = getBytes(image,format); 26 | 27 | for (byte b : img) { 28 | if (sb2.length() == 0) { 29 | sb2.append(b); 30 | } else { 31 | sb2.append(",").append(b); 32 | } 33 | } 34 | return sb2.toString(); 35 | 36 | } 37 | // 将BufferImage 转换为字节数组 38 | private static byte[] getBytes(BufferedImage image, String format) { 39 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 40 | try { 41 | if (format.equalsIgnoreCase("png")) 42 | ImageIO.write(image, "PNG", baos); 43 | if (format.equalsIgnoreCase("jpg")) 44 | ImageIO.write(image, "JPG", baos); 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | } 48 | return baos.toByteArray(); 49 | } 50 | 51 | 52 | // ------------------ ---------------------------------------------------------------- 53 | 54 | 55 | /** 56 | * 将数据库中的图像字符串解析成BufferedImage 57 | */ 58 | public static BufferedImage stringToImage(String string) { 59 | if (string.contains(",")) { 60 | // 这里没有用自带的那个分割方法,原因是分割速度没有这个快,有人考证在分割字符长度很大的情况下,系统的分割方法容易造成内存溢出。 61 | // 还有subString方法,不知道最新版本的jdk改了源码了么 62 | String[] imagetemp = split(string, ","); 63 | byte[] image = new byte[imagetemp.length]; 64 | for (int i = 0; i < imagetemp.length; i++) { 65 | image[i] = Byte.parseByte(imagetemp[i]); 66 | } 67 | return saveImage(image); 68 | } else { 69 | log.error("不能解析"); 70 | return null; 71 | } 72 | } 73 | 74 | // 将byte[] 转换为BufferedImage 75 | private static BufferedImage readImage(byte[] bytes) throws IOException { 76 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 77 | return ImageIO.read(bais); 78 | } 79 | 80 | // 保存图片 81 | @SneakyThrows 82 | public static BufferedImage saveImage(byte[] imgages) { 83 | BufferedImage bis = readImage(imgages); 84 | return bis; 85 | } 86 | 87 | // 分割字符串 88 | public static String[] split(String s, String token) { 89 | if (s == null) 90 | return null; 91 | if (token == null || s.length() == 0) 92 | return new String[] { s }; 93 | int size = 0; 94 | String[] result = new String[4]; 95 | while (s.length() > 0) { 96 | int index = s.indexOf(token); 97 | String splitOne = s; 98 | if (index > -1) { 99 | splitOne = s.substring(0, index); 100 | s = s.substring(index + token.length()); 101 | } else { 102 | s = ""; 103 | } 104 | if (size >= result.length) { 105 | String[] tmp = new String[result.length * 2]; 106 | System.arraycopy(result, 0, tmp, 0, result.length); 107 | result = tmp; 108 | } 109 | if (splitOne.length() > 0) { 110 | result[size++] = splitOne; 111 | } 112 | } 113 | String[] tmp = result; 114 | result = new String[size]; 115 | System.arraycopy(tmp, 0, result, 0, size); 116 | return result; 117 | } 118 | 119 | 120 | /** 121 | * 将BufferedImage转为image 122 | * @param image image图像 123 | * @return BufferedImage 124 | */ 125 | public static BufferedImage toBufferedImage(Image image) { 126 | if (image instanceof BufferedImage) { 127 | return (BufferedImage)image; 128 | } 129 | image = new ImageIcon(image).getImage(); 130 | BufferedImage bimage = null; 131 | GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 132 | try { 133 | int transparency = Transparency.OPAQUE; 134 | GraphicsDevice gs = ge.getDefaultScreenDevice(); 135 | GraphicsConfiguration gc = gs.getDefaultConfiguration(); 136 | bimage = gc.createCompatibleImage( 137 | image.getWidth(null), image.getHeight(null), transparency); 138 | } catch (HeadlessException e) { 139 | } 140 | 141 | if (bimage == null) { 142 | int type = BufferedImage.TYPE_INT_RGB; 143 | bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type); 144 | } 145 | Graphics g = bimage.createGraphics(); 146 | 147 | g.drawImage(image, 0, 0, null); 148 | g.dispose(); 149 | 150 | return bimage; 151 | } 152 | } -------------------------------------------------------------------------------- /src/main/java/com/example/controller/UserApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.controller.exception.NotExistInMysqlException; 4 | import com.example.dao.AuthMapper; 5 | import com.example.entity.constant.ThreadDetails; 6 | import com.example.entity.data.UserDetail; 7 | import com.example.entity.repo.RestBean; 8 | import com.example.entity.repo.RestBeanBuilder; 9 | import com.example.entity.repo.ResultCode; 10 | import com.example.service.FileService; 11 | import com.example.service.UserService; 12 | import io.swagger.annotations.*; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.web.bind.annotation.*; 15 | import org.springframework.web.multipart.MultipartFile; 16 | import org.springframework.web.multipart.MultipartHttpServletRequest; 17 | import org.springframework.web.util.WebUtils; 18 | 19 | import javax.annotation.Resource; 20 | import javax.servlet.http.HttpServletRequest; 21 | 22 | @Slf4j 23 | @Api(tags = "用户服务", description = "修改用户信息,查询用户信息等") 24 | @RestController 25 | @RequestMapping("/api/user") 26 | public class UserApiController { 27 | 28 | @Resource 29 | AuthMapper authMapper; 30 | @Resource 31 | FileService fileService; 32 | @Resource 33 | UserService userService; 34 | 35 | 36 | @ApiResponses({ 37 | @ApiResponse(code = 200, message = "获取成功"), 38 | @ApiResponse(code = 400, message = "获取失败,请查看日志"), 39 | @ApiResponse(code = 401, message = "没有权限") 40 | }) 41 | @ApiOperation(value = "获取用户详细信息", notes = "获取用户的信息,游戏信息等") 42 | @GetMapping("/getUserDetails") 43 | public RestBean getUserDetails() { 44 | //获取用户 45 | String name = ThreadDetails.getUsername(); 46 | UserDetail userDetail = authMapper.findUserByUsername(name).orElseThrow(() -> new NotExistInMysqlException("不存在该用户!")); 47 | return RestBeanBuilder.builder().code(ResultCode.REGISTER_SUCCESS).data(userDetail).build().ToRestBean(); 48 | } 49 | 50 | 51 | @ApiImplicitParams({ 52 | @ApiImplicitParam(name = "username", paramType = "query", dataType = "string", dataTypeClass = String.class,example = "user"), 53 | @ApiImplicitParam(name = "message", paramType = "query", dataType = "string",dataTypeClass = String.class, example = "123456"), 54 | @ApiImplicitParam(name = "sex", paramType = "query", dataType = "string",dataTypeClass = String.class, example = "男") 55 | }) 56 | @ApiResponses({ 57 | @ApiResponse(code = 200, message = "修改成功"), 58 | @ApiResponse(code = 400, message = "修改失败,请查看日志"), 59 | @ApiResponse(code = 401, message = "没有权限") 60 | }) 61 | @ApiOperation(value = "修改某项用户详细信息", notes = "修改某项用户的用户名、简介等") 62 | @PatchMapping("/modifySomeUserDetails") 63 | public RestBean modifySomeUserDetails(@RequestParam(value = "username",required = false) String username,@RequestParam(value = "message", required = false) String message,@RequestParam(value = "sex",required = false) String sex) { 64 | return userService.modifyUserDetails(username, message,sex)? 65 | RestBeanBuilder.builder().code(ResultCode.MODIFY_SUCCESS).build().ToRestBean(): 66 | RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).build().ToRestBean(); 67 | } 68 | 69 | @ApiImplicitParams({ 70 | @ApiImplicitParam(name = "username", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class,example = "user"), 71 | @ApiImplicitParam(name = "message", paramType = "query", required = true, dataType = "string",dataTypeClass = String.class, example = "123456"), 72 | @ApiImplicitParam(name = "sex", paramType = "query", required = true, dataType = "string",dataTypeClass = String.class, example = "男") 73 | }) 74 | @ApiResponses({ 75 | @ApiResponse(code = 200, message = "修改成功"), 76 | @ApiResponse(code = 400, message = "修改失败,请查看日志"), 77 | @ApiResponse(code = 401, message = "没有权限") 78 | }) 79 | @ApiOperation(value = "修改用户详细信息", notes = "修改用户的用户名、简介等") 80 | @PutMapping("/modifyUserDetails") 81 | public RestBean modifyUserDetails(@RequestParam(value = "username") String username,@RequestParam(value = "message") String message,@RequestParam(value = "sex",required = false) String sex) { 82 | return userService.modifyUserDetails(username, message,sex)? 83 | RestBeanBuilder.builder().code(ResultCode.MODIFY_SUCCESS).build().ToRestBean(): 84 | RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).build().ToRestBean(); 85 | } 86 | 87 | @ApiImplicitParams({ 88 | @ApiImplicitParam(name = "file", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class,example = "file"), 89 | }) 90 | @ApiResponses({ 91 | @ApiResponse(code = 200, message = "上传成功"), 92 | @ApiResponse(code = 400, message = "上传失败,请查看日志"), 93 | @ApiResponse(code = 401, message = "没有权限") 94 | }) 95 | @ApiOperation(value = "上传用户头像", notes = "将用户头像上传到服务器,用blob格式保存") 96 | @PostMapping("/uploadIcon") 97 | public RestBean uploadIcon(HttpServletRequest request, @RequestParam(value = "file") String file) { 98 | fileService.saveIcon(file); 99 | return RestBeanBuilder.builder().code(ResultCode.UPLOAD_SUCCESS).build().ToRestBean(); 100 | } 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/filter/OverallExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.filter; 2 | 3 | import com.example.controller.exception.*; 4 | import com.example.entity.repo.RestBean; 5 | import com.example.entity.repo.RestBeanBuilder; 6 | import com.example.entity.repo.ResultCode; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.catalina.connector.ClientAbortException; 9 | import org.springframework.core.Ordered; 10 | import org.springframework.core.annotation.Order; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.ExceptionHandler; 14 | import org.springframework.web.bind.annotation.RestControllerAdvice; 15 | 16 | import javax.annotation.PostConstruct; 17 | 18 | /** 19 | * 全局异常处理 20 | */ 21 | @Slf4j 22 | @RestControllerAdvice 23 | @Order(Ordered.LOWEST_PRECEDENCE) 24 | public class OverallExceptionHandler { 25 | 26 | @PostConstruct 27 | private void init(){ 28 | log.info("全局异常拦截器启用"); 29 | } 30 | 31 | 32 | @ExceptionHandler(ClientAbortException.class) 33 | public RestBean handleClientAbortException(ClientAbortException e) { 34 | // 这里可以记录日志或者执行其他错误处理逻辑 35 | log.warn("ClientAbortException: ", e); 36 | 37 | // 返回一个自定义的响应,或者您可以根据需要返回任何其他类型的响应 38 | return RestBeanBuilder.builder().code(ResultCode.FAILURE).messageType(RestBeanBuilder.USER_DEFINED).message("连接被客户端中断").build().ToRestBean(); 39 | 40 | } 41 | @ExceptionHandler 42 | public RestBean handleException(Exception e) { 43 | e.printStackTrace(); 44 | return RestBeanBuilder.builder().code(ResultCode.FAILURE).messageType(RestBeanBuilder.USER_DEFINED).message("发生了未知的错误!请联系管理员!").build().ToRestBean(); 45 | } 46 | 47 | @ExceptionHandler(AlreadyException.class) 48 | public RestBean handleAlreadyException(AlreadyException e) { 49 | if ("".equals(e.getMessage())) 50 | return RestBeanBuilder.builder().code(ResultCode.ALREADY).build().ToRestBean(); 51 | return RestBeanBuilder.builder().code(ResultCode.ALREADY).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 52 | 53 | } 54 | 55 | @ExceptionHandler(NotExistInMysqlException.class) 56 | public RestBean handleNotExistInMysqlException(NotExistInMysqlException e) { 57 | if ("".equals(e.getMessage())) 58 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST).build().ToRestBean(); 59 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 60 | 61 | } 62 | 63 | @ExceptionHandler(NotExistInRedisException.class) 64 | public RestBean handleNotExistInRedisException(NotExistInRedisException e) { 65 | if ("".equals(e.getMessage())) 66 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST).build().ToRestBean(); 67 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 68 | 69 | } 70 | @ExceptionHandler(ModifyException.class) 71 | public RestBean handleModifyException(ModifyException e) { 72 | if ("".equals(e.getMessage())) 73 | return RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).build().ToRestBean(); 74 | return RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 75 | 76 | } 77 | 78 | @ExceptionHandler(ThreadLocalIsNullException.class) 79 | public RestBean handleThreadLocalIsNullException(ThreadLocalIsNullException e) { 80 | if ("".equals(e.getMessage())) 81 | return RestBeanBuilder.builder().code(ResultCode.THREAD_LOCAL_IS_NULL).build().ToRestBean(); 82 | return RestBeanBuilder.builder().code(ResultCode.THREAD_LOCAL_IS_NULL).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 83 | 84 | } 85 | 86 | @ExceptionHandler(PasswordWrongException.class) 87 | public RestBean handlePasswordWrongException(PasswordWrongException e) { 88 | if ("".equals(e.getMessage())) 89 | return RestBeanBuilder.builder().code(ResultCode.PASSWORD_IS_WRONG).build().ToRestBean(); 90 | return RestBeanBuilder.builder().code(ResultCode.PASSWORD_IS_WRONG).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 91 | 92 | } 93 | 94 | @ExceptionHandler(NotExistInCookieException.class) 95 | public RestBean handleNotExistInCookieException(NotExistInCookieException e) { 96 | if ("".equals(e.getMessage())) 97 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST_IN_COOKIE).build().ToRestBean(); 98 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST_IN_COOKIE).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 99 | 100 | } 101 | @ExceptionHandler(MyFileException.class) 102 | public RestBean handleNotExistInCookieException(MyFileException e) { 103 | if ("".equals(e.getMessage())) 104 | return RestBeanBuilder.builder().code(ResultCode.UPLOAD_FAILURE).build().ToRestBean(); 105 | return RestBeanBuilder.builder().code(ResultCode.UPLOAD_FAILURE).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 106 | 107 | } 108 | 109 | 110 | @ExceptionHandler(NotExistInServletContextException.class) 111 | public RestBean handleNotExistInServletContextException(NotExistInServletContextException e) { 112 | if ("".equals(e.getMessage())) 113 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST).build().ToRestBean(); 114 | return RestBeanBuilder.builder().code(ResultCode.NOT_EXIST).messageType(RestBeanBuilder.USER_DEFINED).message(e.getMessage()).build().ToRestBean(); 115 | 116 | } 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/WebFilterAutoRegister.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import com.example.anno.MyFilterOrder; 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.ibatis.annotations.Delete; 7 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 8 | import org.springframework.beans.factory.config.BeanDefinition; 9 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 10 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 11 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 12 | import org.springframework.beans.factory.support.BeanNameGenerator; 13 | import org.springframework.context.EnvironmentAware; 14 | import org.springframework.context.ResourceLoaderAware; 15 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 16 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 17 | import org.springframework.context.annotation.ScannedGenericBeanDefinition; 18 | import org.springframework.core.env.Environment; 19 | import org.springframework.core.io.ResourceLoader; 20 | import org.springframework.core.type.AnnotationMetadata; 21 | import org.springframework.core.type.filter.AnnotationTypeFilter; 22 | 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Set; 28 | 29 | /** 30 | * 自动注册 web Filter 31 | */ 32 | @Slf4j 33 | public class WebFilterAutoRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { 34 | 35 | //包名 36 | public static final String DEFAULT_PACKAGE = "com.example.controller.filter"; 37 | 38 | private ResourceLoader resourceLoader; 39 | 40 | private Environment environment; 41 | 42 | @Override 43 | public void setEnvironment(final Environment environment) { 44 | this.environment = environment; 45 | } 46 | 47 | @Override 48 | public void setResourceLoader(ResourceLoader resourceLoader) { 49 | this.resourceLoader = resourceLoader; 50 | } 51 | 52 | 53 | @Override 54 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 55 | //1.初始化类扫描器 56 | ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, this.environment) { 57 | @Override 58 | protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 59 | boolean isCandidate = false; 60 | if (beanDefinition.getMetadata().isIndependent()) { 61 | if (!beanDefinition.getMetadata().isAnnotation()) { 62 | isCandidate = true; 63 | } 64 | } 65 | return isCandidate; 66 | } 67 | }; 68 | 69 | //2.给扫描器加过滤条件,需要在类上有MyFilterOrder注解 70 | AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(MyFilterOrder.class); 71 | scanner.setResourceLoader(this.resourceLoader); 72 | scanner.addIncludeFilter(annotationTypeFilter); 73 | 74 | //3.扫描指定的包,得到一系列bean定义,循环处理 75 | Set candidateBeanDefinitionSet = scanner.findCandidateComponents(DEFAULT_PACKAGE); 76 | for (BeanDefinition beanDefinition : candidateBeanDefinitionSet) { 77 | if (beanDefinition instanceof ScannedGenericBeanDefinition) { 78 | ScannedGenericBeanDefinition annotatedBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition; 79 | AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata(); 80 | 81 | String[] interfaceNames = annotationMetadata.getInterfaceNames(); 82 | 83 | // 带这个注解的类,必须实现 Filter 接口,否则不注册 84 | boolean isImplFilter = Arrays.asList(interfaceNames).contains("javax.servlet.Filter"); 85 | 86 | if (isImplFilter) { 87 | 88 | // 4. 开始注册 89 | registerWebFilter(registry, annotationMetadata); 90 | 91 | log.info("register web filter {} to bean factory", annotationMetadata.getClassName()); 92 | } 93 | } 94 | } 95 | } 96 | 97 | 98 | /** 99 | * 生成一个 FilterRegistrationBean 并注册到 spring 100 | * @param registry 101 | * @param annotationMetadata 102 | */ 103 | @SneakyThrows 104 | private void registerWebFilter(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata) { 105 | 106 | Map attributes = annotationMetadata.getAnnotationAttributes(MyFilterOrder.class.getCanonicalName()); 107 | 108 | Integer order = (Integer) attributes.get("value"); 109 | String[] urlPatterns = (String[]) attributes.get("urlPatterns"); 110 | 111 | String className = annotationMetadata.getClassName(); 112 | try { 113 | // 生成 filter 实例 114 | Object newInstance = Class.forName(className).getConstructor().newInstance(); 115 | 116 | AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(WebFilterFactoryBean.class) 117 | .addPropertyValue("filter", newInstance) 118 | .addPropertyValue("order", order) 119 | .addPropertyValue("name", className) 120 | .addPropertyValue("urlPatterns", urlPatterns) 121 | .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) 122 | .getBeanDefinition(); 123 | 124 | // 注册 125 | registry.registerBeanDefinition(className, beanDefinition); 126 | 127 | log.info("register WebFilter {} success", className); 128 | 129 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { 130 | log.error("error, ", e); 131 | } 132 | 133 | } 134 | 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.example.entity.repo.RestBean; 5 | import com.example.entity.repo.ResultCode; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.core.Ordered; 11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 12 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | import org.springframework.security.core.AuthenticationException; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 17 | import org.springframework.security.web.AuthenticationEntryPoint; 18 | import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; 19 | import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; 20 | import org.springframework.web.cors.CorsConfiguration; 21 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 22 | import org.springframework.web.filter.CorsFilter; 23 | import springfox.documentation.spring.web.json.Json; 24 | 25 | import javax.annotation.Resource; 26 | import javax.servlet.http.HttpServletRequest; 27 | import javax.servlet.http.HttpServletResponse; 28 | import java.io.IOException; 29 | import java.util.Arrays; 30 | import java.util.Collections; 31 | 32 | /** 33 | * springSecurity配置 34 | */ 35 | @Configuration 36 | @Slf4j 37 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 38 | 39 | 40 | @Resource 41 | PersistentTokenRepository tokenRepository; 42 | 43 | @Resource 44 | UserDetailsService userDetailsService; 45 | 46 | @Override 47 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 48 | auth 49 | .userDetailsService(userDetailsService) 50 | .passwordEncoder(new BCryptPasswordEncoder()); 51 | } 52 | 53 | @Override 54 | protected void configure(HttpSecurity http) throws Exception { 55 | http 56 | .authorizeRequests() 57 | .antMatchers("/swagger**/**", "/webjars/**", "/v2/**", "/doc.html", "/pre/**", "/api/game/**", "/api/chat/**", "/api/user/**", "/error/**","/api/hall/**").permitAll() //允许swagger放行 58 | .antMatchers("/api/auth/access-deny", "/api/auth/verify", "/api/auth/register", "/api/auth/logout", "/api/auth/logout-success").permitAll() 59 | .antMatchers("/api/**", "/api/auth/login-success").authenticated() 60 | .antMatchers("/api/auth/guest/isGuest", "/api/room/**").hasRole("user") 61 | .antMatchers("/api/game/**", "/api/chat/**", "/api/user/**","/api/hall/**").permitAll() 62 | .anyRequest().permitAll() 63 | .and() 64 | .formLogin() 65 | .loginPage("/api/auth/access-deny") 66 | .loginProcessingUrl("/api/auth/login") 67 | .successForwardUrl("/api/auth/login-success") 68 | .failureForwardUrl("/api/auth/login-failure") 69 | .and() 70 | .logout() 71 | .logoutUrl("/api/auth/logout") 72 | .logoutSuccessUrl("/api/auth/logout-success") 73 | .and() 74 | .rememberMe() 75 | .tokenValiditySeconds(60 * 60 * 24 * 7) 76 | .useSecureCookie(true) 77 | .alwaysRemember(true) 78 | .tokenRepository(tokenRepository) 79 | .and() 80 | .cors() 81 | .and() 82 | // .exceptionHandling().authenticationEntryPoint(getAuthenticationEntryPoint()) 83 | // .and() 84 | .csrf().disable() 85 | .sessionManagement() 86 | .maximumSessions(1); 87 | } 88 | 89 | /** 90 | * 匿名用户访问无权限资源时的异常 91 | * @return json对象 92 | */ 93 | AuthenticationEntryPoint getAuthenticationEntryPoint() { 94 | return (request, response, authException) -> { 95 | RestBean bean = RestBean.builder().success(false).code(ResultCode.NO_PERMISSION.getCode()).message(ResultCode.NO_PERMISSION.getMessage()).build(); 96 | response.setContentType("test/json;charset=utf-8"); 97 | response.getWriter().write(JSON.toJSONString(bean)); 98 | }; 99 | } 100 | 101 | 102 | private CorsConfiguration buildConfig() { 103 | //创建CorsConfiguration对象后添加配置 104 | CorsConfiguration config = new CorsConfiguration(); 105 | //设置放行哪些原始域,这里设置为所有 106 | config.addAllowedOriginPattern("*"); 107 | // //你可以单独设置放行哪些原始域, 108 | // config.addAllowedOrigin("http://localhost:8668"); 109 | // config.setAllowedOrigins(Collections.singletonList("*")); 110 | //放行哪些原始请求头部信息 111 | // config.addAllowedHeader(Arrays.asList("*")); 112 | config.setAllowedHeaders(Collections.singletonList("*")); 113 | //放行哪些请求方式,*代表所有 114 | config.setAllowedMethods(Arrays.asList("PUT", "POST", "GET", "OPTIONS", "DELETE", "PATCH")); 115 | //是否允许发送Cookie,必须要开启,因为我们的JSESSIONNID需要在Cookie中携带 116 | config.setAllowCredentials(true); 117 | config.setExposedHeaders(Collections.singletonList("*")); 118 | return config; 119 | } 120 | 121 | @Bean 122 | public FilterRegistrationBean configure() { 123 | log.info("CorsFilter跨域过滤器启用"); 124 | // 映射路径 125 | UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); 126 | corsConfigurationSource.registerCorsConfiguration("/**", buildConfig()); 127 | //项目中有多个filter时此处设置改CorsFilter的优先执行顺序 128 | FilterRegistrationBean bean = new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource)); 129 | bean.setOrder(Ordered.HIGHEST_PRECEDENCE); 130 | return bean; 131 | } 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.2 9 | 10 | 11 | com.example 12 | KarGoBangWebServer 13 | 0.0.1-SNAPSHOT 14 | KarGoBangWebServer 15 | KarGoBangWebServer 16 | 17 | 1.8 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-security 33 | 34 | 35 | mysql 36 | mysql-connector-java 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | true 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-data-redis 46 | 47 | 48 | io.springfox 49 | springfox-boot-starter 50 | 3.0.0 51 | 52 | 53 | org.mybatis.spring.boot 54 | mybatis-spring-boot-starter 55 | 2.2.2 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-mail 60 | 61 | 62 | com.alibaba 63 | fastjson 64 | 2.0.10 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-configuration-processor 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-devtools 74 | runtime 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.springframework.cloud 84 | spring-cloud-dependencies 85 | 2021.0.1 86 | pom 87 | import 88 | 89 | 90 | com.alibaba 91 | fastjson 92 | 2.0.10 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-mail 97 | 2.7.2 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-maven-plugin 107 | 108 | 109 | 110 | 111 | src/main/resources 112 | 113 | application*.yml 114 | 115 | 116 | 117 | src/main/resources 118 | true 119 | 120 | application.yml 121 | application-${environment}.yml 122 | 123 | 124 | 125 | src/main/resources 126 | 127 | **/*.properties 128 | **/*.xml 129 | 130 | true 131 | 132 | 133 | src/main/java 134 | 135 | **/*.properties 136 | **/*.xml 137 | 138 | true 139 | 140 | 141 | 142 | 143 | 144 | dev 145 | 146 | true 147 | 148 | 149 | dev 150 | 151 | 152 | 153 | prod 154 | 155 | false 156 | 157 | 158 | prod 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/AuthApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.controller.exception.NotExistInMysqlException; 4 | import com.example.controller.exception.ThreadLocalIsNullException; 5 | import com.example.dao.AuthMapper; 6 | import com.example.entity.constant.ThreadDetails; 7 | import com.example.entity.data.UserDetail; 8 | import com.example.entity.repo.RestBean; 9 | import com.example.entity.repo.RestBeanBuilder; 10 | import com.example.entity.repo.ResultCode; 11 | import com.example.service.AuthService; 12 | import com.example.service.VerifyService; 13 | import com.example.service.util.IpTools; 14 | import io.swagger.annotations.*; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.security.core.Authentication; 18 | import org.springframework.security.core.context.SecurityContext; 19 | import org.springframework.security.core.context.SecurityContextHolder; 20 | import org.springframework.security.core.userdetails.User; 21 | import org.springframework.web.bind.annotation.*; 22 | import springfox.documentation.annotations.ApiIgnore; 23 | 24 | import javax.annotation.Resource; 25 | import javax.servlet.http.HttpServletRequest; 26 | import javax.servlet.http.HttpServletResponse; 27 | import java.text.SimpleDateFormat; 28 | import java.util.Date; 29 | 30 | @Slf4j 31 | @Api(tags = "用户验证", description = "用户登录成功或失败等,含有游客登录") 32 | @RestController 33 | @RequestMapping("/api/auth") 34 | public class AuthApiController { 35 | 36 | @Resource 37 | VerifyService verifyService; 38 | @Resource 39 | AuthService authService; 40 | @Resource 41 | AuthMapper authMapper; 42 | 43 | @ApiIgnore 44 | @PostMapping("/login-success") 45 | public RestBean loginSuccess(HttpServletRequest request, HttpServletResponse response) { 46 | //打印日志 47 | SecurityContext context = SecurityContextHolder.getContext(); 48 | ThreadDetails.securityContext.set(context); 49 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 50 | System.out.println("Ip为["+IpTools.getIpAddress(request)+"],username为["+((User)context.getAuthentication().getPrincipal()).getUsername()+"]的玩家登录:["+format.format(new Date())+"]"); 51 | //将用户的JSESSIONID存入redis 52 | return RestBeanBuilder.builder().code(ResultCode.LOGIN_SUCCESS).build().ToRestBean(); 53 | } 54 | 55 | @ApiIgnore 56 | @PostMapping("/login-failure") 57 | public RestBean loginFailure() { 58 | return RestBeanBuilder.builder().code(ResultCode.LOGIN_FAILURE).build().ToRestBean(); 59 | } 60 | 61 | @ApiIgnore 62 | @GetMapping("/logout-success") 63 | public RestBean logoutSuccess() { 64 | return RestBeanBuilder.builder().code(ResultCode.LOGOUT_SUCCESS).build().ToRestBean(); 65 | 66 | } 67 | 68 | @ApiIgnore 69 | @RequestMapping("/access-deny") 70 | public RestBean accessDeny() { 71 | return RestBeanBuilder.builder().code(ResultCode.NO_PERMISSION).build().ToRestBean(); 72 | 73 | } 74 | 75 | @ApiImplicitParams({ 76 | @ApiImplicitParam(name = "username", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class,example = "user"), 77 | @ApiImplicitParam(name = "password", paramType = "query", required = true, dataType = "string",dataTypeClass = String.class, example = "123456") 78 | }) 79 | @ApiResponses({ 80 | @ApiResponse(code = 200, message = "登录成功"), 81 | @ApiResponse(code = 400, message = "账号或密码错误,请重新输入") 82 | }) 83 | @ApiOperation(value = "发起登录请求", notes = "使用springSecurity自带登录") 84 | @PostMapping("/login") 85 | public RestBean login(@RequestParam(value = "username", required = false) String username, @RequestParam(value = "password", required = false) String password) { 86 | return null; 87 | } 88 | 89 | 90 | @ApiResponses({ 91 | @ApiResponse(code = 200, message = "获取验证码成功"), 92 | }) 93 | @ApiOperation(value = "发起登出请求", notes = "使用springSecurity自带登出") 94 | @PostMapping("/logout") 95 | public RestBean logout() { 96 | return null; 97 | } 98 | 99 | @ApiImplicitParams({ 100 | @ApiImplicitParam(name = "email", paramType = "query", required = true, dataType = "string",dataTypeClass = String.class, example = "user"), 101 | }) 102 | @ApiResponses({ 103 | @ApiResponse(code = 200, message = "请求验证码成功"), 104 | @ApiResponse(code = 400, message = "请求验证码失败,请查看日志") 105 | }) 106 | @ApiOperation(value = "发起验证码请求", notes = "获取验证码,发送到对应邮箱") 107 | @PostMapping("/verify") 108 | public RestBean getVerify(@RequestParam(value = "email") String email) { 109 | return verifyService.sendVerifyCode(email) ? 110 | RestBeanBuilder.builder().code(ResultCode.VERIFY_SUCCESS).build().ToRestBean() : 111 | RestBeanBuilder.builder().code(ResultCode.VERIFY_FAILURE).build().ToRestBean(); 112 | } 113 | 114 | @ApiImplicitParams({ 115 | @ApiImplicitParam(name = "username", paramType = "query", required = true, dataType = "string",dataTypeClass = String.class, example = "user"), 116 | @ApiImplicitParam(name = "password", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class,example = "123456"), 117 | @ApiImplicitParam(name = "email", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class,example = "lmq122677@qq.com"), 118 | @ApiImplicitParam(name = "code", paramType = "query", required = true, dataType = "string", dataTypeClass = String.class,example = "123456"), 119 | }) 120 | @ApiResponses({ 121 | @ApiResponse(code = 200, message = "注册成功"), 122 | @ApiResponse(code = 400, message = "注册失败,请查看日志"), 123 | @ApiResponse(code = 401, message = "该用户已经存在") 124 | }) 125 | @ApiOperation(value = "发起注册请求", notes = "根据用户邮箱和验证码进行注册") 126 | @PostMapping("/register") 127 | public RestBean register(@RequestParam(value = "username") String username, @RequestParam(value = "password") String password, @RequestParam(value = "email") String email, @RequestParam(value = "code") String code) { 128 | //先进行验证码核验 129 | if (!verifyService.doVerify(email, code)) 130 | return RestBeanBuilder.builder().code(ResultCode.VERIFY_WRONG).build().ToRestBean(); 131 | //进行注册判断 132 | return authService.register(username, password) ? 133 | RestBeanBuilder.builder().code(ResultCode.REGISTER_SUCCESS).build().ToRestBean() : 134 | RestBeanBuilder.builder().code(ResultCode.REGISTER_FAILURE).build().ToRestBean(); 135 | } 136 | 137 | } 138 | 139 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/GameServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.controller.exception.NotExistInMysqlException; 4 | import com.example.dao.AuthMapper; 5 | import com.example.dao.UserMapper; 6 | import com.example.entity.constant.ThreadDetails; 7 | import com.example.entity.data.ChessDetail; 8 | import com.example.entity.data.UserDetail; 9 | import com.example.entity.data.UserScoreDetail; 10 | import com.example.service.GameService; 11 | import com.example.service.util.RedisTools; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.concurrent.CompletableFuture; 20 | 21 | @Service 22 | public class GameServiceImpl implements GameService { 23 | 24 | private final static String WHITE_INDEX_TOKEN_KEY = "game:index:white:"; 25 | 26 | private final static String BLACK_INDEX_TOKEN_KEY = "game:index:black:"; 27 | 28 | private final static String WHITE_MOVE_TOKEN_KEY = "game:move:white:"; 29 | 30 | private final static String BLACK_MOVE_TOKEN_KEY = "game:move:black:"; 31 | 32 | private final static String OPPONENT_WHITE_IN_TOKEN_KEY = "game:opponent:white:"; 33 | 34 | private final static String OPPONENT_BLACK_IN_TOKEN_KEY = "game:opponent:black:"; 35 | 36 | 37 | @Resource 38 | RedisTemplate template; 39 | @Resource 40 | UserMapper userMapper; 41 | @Resource 42 | AuthMapper authMapper; 43 | @Resource 44 | RedisTools redisTools; 45 | @Resource 46 | RedisTools redisToolsString; 47 | 48 | 49 | @Transactional 50 | @Override 51 | public boolean move(int x, int y, int z, int type) { 52 | //从redis获取房间号 53 | String roomNumber = ThreadDetails.redisRoomNumber.get(); 54 | Object indexObject = null; 55 | //获得棋子步数 56 | int index = redisTools.getFromRedis((type == WHITE ? WHITE_INDEX_TOKEN_KEY : BLACK_INDEX_TOKEN_KEY) + roomNumber, "redis数据库中没有" + (type == WHITE ? "白" : "黑") + "棋步数数据"); 57 | 58 | //将棋子行动存储到Redis中 59 | HashMap move = new HashMap<>(); 60 | move.put("x", x); 61 | move.put("y", y); 62 | move.put("z", z); 63 | 64 | CompletableFuture set = CompletableFuture.supplyAsync(() -> { 65 | redisTools.setHashMapToRedis((type == WHITE ? WHITE_MOVE_TOKEN_KEY : BLACK_MOVE_TOKEN_KEY) + roomNumber + ":" + index, move); 66 | return null; 67 | }) 68 | .thenCombine(CompletableFuture.supplyAsync(() -> { 69 | redisTools.setToRedis((type == WHITE ? WHITE_INDEX_TOKEN_KEY : BLACK_INDEX_TOKEN_KEY) + roomNumber, index + 1 ); 70 | return null; 71 | }), (a, b) -> null); 72 | //让棋子步数+1 73 | set.join(); 74 | return true; 75 | } 76 | 77 | 78 | @Override 79 | public ChessDetail waitForWhiteMove() { 80 | //获取房间号 81 | String roomNumber = ThreadDetails.redisRoomNumber.get(); 82 | //获取黑棋步数 83 | int blackIndex = redisTools.getFromRedis(BLACK_INDEX_TOKEN_KEY + roomNumber, "redis数据库中没有黑棋步数数据"); 84 | //从redis中查看是否白棋有下子的消息 85 | Map whiteMoveInfo = redisTools.getHashMapFromRedis(WHITE_MOVE_TOKEN_KEY + roomNumber + ":" + blackIndex, "白棋尚未下子"); 86 | //删除旧key 87 | template.delete(WHITE_MOVE_TOKEN_KEY + roomNumber + ":" + blackIndex); 88 | return new ChessDetail((Integer) whiteMoveInfo.get("x"), (Integer) whiteMoveInfo.get("y"), (Integer) whiteMoveInfo.get("z")); 89 | } 90 | 91 | @Override 92 | public ChessDetail waitForBlackMove() { 93 | //获取房间号 94 | String roomNumber = ThreadDetails.redisRoomNumber.get(); 95 | //获取白棋步数 96 | int whiteIndex = redisTools.getFromRedis(WHITE_INDEX_TOKEN_KEY + roomNumber, "redis数据库中没有白棋步数数据"); 97 | //从redis中查看是否黑棋有下子的消息 98 | Map blackMoveInfo = redisTools.getHashMapFromRedis(BLACK_MOVE_TOKEN_KEY + roomNumber + ":" + (whiteIndex - 1), "黑棋尚未下子"); 99 | //删除旧key 100 | template.delete(BLACK_MOVE_TOKEN_KEY + roomNumber + ":" + whiteIndex); 101 | return new ChessDetail((Integer) blackMoveInfo.get("x"), (Integer) blackMoveInfo.get("y"), (Integer) blackMoveInfo.get("z")); 102 | } 103 | 104 | @Override 105 | public boolean whiteWaitForOpponent() { 106 | //获取房间号 107 | String roomNumber = ThreadDetails.redisRoomNumber.get(); 108 | //从redis中查看是否黑棋已经开始准备下子 109 | redisTools.getFromRedis(OPPONENT_BLACK_IN_TOKEN_KEY + roomNumber, "黑棋尚未准备开始游戏"); 110 | //删除旧key 111 | template.delete(OPPONENT_BLACK_IN_TOKEN_KEY + roomNumber); 112 | return true; 113 | } 114 | 115 | @Override 116 | public boolean blackWaitForOpponent() { 117 | //获取房间号 118 | String roomNumber = ThreadDetails.redisRoomNumber.get(); 119 | //从redis中查看是否白棋已经开始准备下子 120 | redisTools.getFromRedis(OPPONENT_WHITE_IN_TOKEN_KEY + roomNumber, "白棋尚未准备开始游戏"); 121 | //删除旧key 122 | template.delete(OPPONENT_WHITE_IN_TOKEN_KEY + roomNumber); 123 | return true; 124 | } 125 | 126 | @Transactional 127 | @Override 128 | public boolean isIn(int type) { 129 | //获取房间号 130 | String roomNumber = ThreadDetails.redisRoomNumber.get(); 131 | CompletableFuture set = CompletableFuture.supplyAsync(() -> { 132 | //将自己已经进入房间的信息存储到Redis中 133 | redisToolsString.setToRedis((type == WHITE ? OPPONENT_WHITE_IN_TOKEN_KEY : OPPONENT_BLACK_IN_TOKEN_KEY) + roomNumber, "true", 10, RedisTools.MINUTE); 134 | return null; 135 | }).thenCombine(CompletableFuture.supplyAsync(() -> { 136 | //初始化棋子步数,防止拿取时空指针异常 137 | redisTools.setToRedis((type == WHITE ? WHITE_INDEX_TOKEN_KEY : BLACK_INDEX_TOKEN_KEY) + roomNumber, 0); 138 | return null; 139 | }), (a, b) -> null); 140 | set.join(); 141 | return true; 142 | } 143 | 144 | @Override 145 | public boolean gameResult(boolean isWin) { 146 | String username = ThreadDetails.redisUsername.get(); 147 | UserDetail userDetail = authMapper.findUserByUsername(username).orElseThrow(() -> new NotExistInMysqlException("不存在该用户!")); 148 | UserScoreDetail userScore = userDetail.getUserScore(); 149 | return isWin ? 150 | userMapper.modifyUserScoreDetails(username, userScore.getWins() + 1, userScore.getSessions() + 1) : 151 | userMapper.modifyUserScoreDetails(username, userScore.getWins(), userScore.getSessions() + 1); 152 | } 153 | 154 | @Override 155 | public boolean changePassNumber(int number) { 156 | String username = ThreadDetails.redisUsername.get(); 157 | UserDetail userDetail = authMapper.findUserByUsername(username).orElseThrow(() -> new NotExistInMysqlException("不存在该用户!")); 158 | UserScoreDetail userScore = userDetail.getUserScore(); 159 | if (userScore.getPassNumber() < number) 160 | return userMapper.modifyPassNumber(number, username); 161 | return true; 162 | } 163 | } 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /src/main/java/com/example/service/impl/RoomServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.controller.exception.AlreadyException; 4 | import com.example.controller.listener.event.AddRoomEvent; 5 | import com.example.controller.listener.event.CreateRoomEvent; 6 | import com.example.entity.constant.ThreadDetails; 7 | import com.example.service.RoomService; 8 | import com.example.service.util.IpTools; 9 | import com.example.service.util.RedisTools; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.context.ApplicationEventPublisher; 14 | import org.springframework.context.ApplicationEventPublisherAware; 15 | import org.springframework.data.redis.core.RedisTemplate; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import javax.annotation.PostConstruct; 20 | import javax.annotation.Resource; 21 | import javax.servlet.http.HttpServletRequest; 22 | import javax.servlet.http.HttpSession; 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.CompletionException; 25 | 26 | @Service 27 | public class RoomServiceImpl implements RoomService , ApplicationEventPublisherAware { 28 | 29 | private static ApplicationEventPublisher applicationEventPublisher; 30 | 31 | public final static String ROOM_ID_TOKEN_KEY = "game:room:id:"; 32 | 33 | public final static String ROOM_ID_USERNAME_TOKEN_KEY = "game:room:id-username:"; 34 | 35 | public final static String IP_USERNAME_TOKEN_KEY = "game:room:user:ip-username:"; 36 | 37 | public final static String IP_OPPONENT_USERNAME_TOKEN_KEY = "game:room:user:ip-opponent-username:"; 38 | 39 | public final static String ME_OPPONENT_USERNAME_TOKEN_KEY = "game:room:user:me-opponent-username:"; 40 | 41 | public final static String IP_ROOM_TOKEN_KEY = "game:room:user:ip-roomNumber:"; 42 | 43 | public final static String IP_SESSION_TOKEN_KEY = "game:room:user:ip-session:"; 44 | 45 | //对方加入房间后,会在redis中存储一个key,用于生产者知晓 46 | private final static String ROOM_ALREADY_IN_TOKEN_KEY = "game:room:success:id:"; 47 | 48 | private final static String NO_PASSWORD_TOKEN_VALUE = "null"; 49 | 50 | 51 | @Resource 52 | RedisTemplate redisTemplate; 53 | @Resource 54 | RedisTools redisTools; 55 | 56 | 57 | 58 | @Transactional 59 | @Override 60 | public boolean createRoom(HttpServletRequest request, String number) { 61 | return createRoomSecret(request, number, null); 62 | } 63 | 64 | @Transactional 65 | @Override 66 | public boolean createRoomSecret(HttpServletRequest request, String number, String password) { 67 | //获取username 68 | String username = ThreadDetails.getUsername(); 69 | //判断redis中是否存有该房间 70 | if (redisTemplate.opsForValue().get(ROOM_ID_TOKEN_KEY + number) != null) 71 | throw new AlreadyException("该房间已经被创建!"); 72 | 73 | CompletableFuture set = CompletableFuture.supplyAsync(() -> { 74 | //以账号为key,密码为value存入redis 75 | if (password == null) { 76 | redisTools.setToRedis(ROOM_ID_TOKEN_KEY + number, NO_PASSWORD_TOKEN_VALUE); 77 | } else { 78 | redisTools.setToRedis(ROOM_ID_TOKEN_KEY + number, password); 79 | } 80 | return null; 81 | }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> { 82 | //以账号为key,username为value存入redis 83 | redisTools.setToRedis(ROOM_ID_USERNAME_TOKEN_KEY + number, username); 84 | return null; 85 | }), (a,b)->{}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> { 86 | //将roomNumber以ip为key存入redis 87 | redisTools.setToRedis(IP_ROOM_TOKEN_KEY + IpTools.getIpAddress(request), number); 88 | return null; 89 | }), (a, b) -> {}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> { 90 | //将username以ip为key存入redis 91 | redisTools.setToRedis(IP_USERNAME_TOKEN_KEY + IpTools.getIpAddress(request), username); 92 | return null; 93 | }), (a, b) -> {}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> { 94 | //获取session 95 | HttpSession session = request.getSession(); 96 | //将roomNumber存入session 97 | session.setAttribute("roomNumber", number); 98 | //将ip与session对应放到redis中 99 | redisTools.setToRedis(IP_SESSION_TOKEN_KEY + IpTools.getIpAddress(request), session.getId()); 100 | return null; 101 | }), (a, b) -> {}).thenApply(a->{ 102 | applicationEventPublisher.publishEvent(new CreateRoomEvent(number)); 103 | return null; 104 | }); 105 | set.join(); 106 | 107 | return true; 108 | } 109 | 110 | 111 | @Transactional 112 | @Override 113 | public boolean addRoom(HttpServletRequest request, String number, String password) { 114 | if (addRoomVerify(number, password)) { //加入房间成功 115 | try { 116 | //获取玩家账号 117 | String username = ThreadDetails.getUsername(); 118 | 119 | CompletableFuture set = CompletableFuture.supplyAsync(() -> { 120 | //将key插入redis 121 | redisTools.setToRedis(ROOM_ALREADY_IN_TOKEN_KEY + number, username, 30, RedisTools.SECONDS); 122 | //删除redis中创建的key 123 | redisTemplate.delete(ROOM_ID_TOKEN_KEY + number); 124 | return null; 125 | }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> { 126 | //将roomNumber以ip为key存入redis 127 | redisTools.setToRedis(IP_ROOM_TOKEN_KEY + IpTools.getIpAddress(request), number); 128 | return null; 129 | }), (a, b) -> { 130 | }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> { 131 | //将username以ip为key存入redis 132 | redisTools.setToRedis(IP_USERNAME_TOKEN_KEY + IpTools.getIpAddress(request), username); 133 | 134 | return null; 135 | }), (a, b) -> { 136 | }).thenCombine(CompletableFuture.supplyAsync(() -> { 137 | //获取对手username 138 | String opponentUsername = redisTools.getFromRedis(ROOM_ID_USERNAME_TOKEN_KEY + number, "对方的username没有读取到!"); 139 | // //删除redis中创建的key 140 | // redisTemplate.delete(ROOM_ID_USERNAME_TOKEN_KEY + number); 141 | return opponentUsername; 142 | }), (a, opponentUsername) -> opponentUsername).thenCompose(opponentUsername -> { 143 | //将对手username以ip为key存入redis 144 | redisTools.setToRedis(IP_OPPONENT_USERNAME_TOKEN_KEY + IpTools.getIpAddress(request), opponentUsername); 145 | redisTools.setToRedis(ME_OPPONENT_USERNAME_TOKEN_KEY + username, opponentUsername); 146 | 147 | return null; 148 | }).thenApply(a -> { 149 | applicationEventPublisher.publishEvent(new AddRoomEvent(number)); 150 | return null; 151 | }); 152 | 153 | //将roomNumber存入session 154 | request.getSession().setAttribute("roomNumber", number); 155 | set.join(); 156 | } catch (CompletionException e) { 157 | //空指针异常 158 | } 159 | 160 | return true; 161 | } return false; 162 | } 163 | 164 | 165 | private boolean addRoomVerify(String number, String password) { 166 | //redis中判断能否加入房间 167 | String value = redisTools.getFromRedis(ROOM_ID_TOKEN_KEY + number, "加入房间失败,不存在该房间!"); 168 | if (value.equals(password) && !password.equals("null")) return true; 169 | return value.equals("null") && ("null".equals(password)||"".equals(password)); 170 | } 171 | 172 | @Override 173 | public void waitForOpponent(HttpServletRequest request) { 174 | //获取username 175 | String username = ThreadDetails.getUsername(); 176 | //从session中获取账号 177 | String roomNumber = ThreadDetails.roomNumber.get(); 178 | //生产者查询redis中是否有消费者放入的key 179 | String opponentUsername = redisTools.getFromRedis(ROOM_ALREADY_IN_TOKEN_KEY + roomNumber, "对方还未加入房间!"); 180 | //将对方username以ip为key存入redis 181 | redisTools.setToRedis(IP_OPPONENT_USERNAME_TOKEN_KEY + IpTools.getIpAddress(request), opponentUsername); 182 | redisTools.setToRedis(ME_OPPONENT_USERNAME_TOKEN_KEY + username, opponentUsername); 183 | 184 | //如果对方已经加入成功,删除key 185 | redisTemplate.delete(ROOM_ALREADY_IN_TOKEN_KEY + roomNumber); 186 | 187 | } 188 | 189 | 190 | @Override 191 | public void setApplicationEventPublisher(final ApplicationEventPublisher applicationEventPublisher) { 192 | RoomServiceImpl.applicationEventPublisher = applicationEventPublisher; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/com/example/controller/GameApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.config.ApplicationPro; 4 | import com.example.entity.data.ChessDetail; 5 | import com.example.entity.repo.RestBean; 6 | import com.example.entity.repo.RestBeanBuilder; 7 | import com.example.entity.repo.ResultCode; 8 | import com.example.service.GameService; 9 | import io.swagger.annotations.*; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.context.properties.ConfigurationProperties; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import javax.annotation.Resource; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.util.Map; 18 | 19 | @Api(tags = "三维四子棋联机交互api",description = "完成三维四子棋的先手,后手,出棋以及获胜") 20 | //@CrossOrigin(value = {"https://f01-1309918226.file.myqcloud.com","http://localhost:8668","https://41271w5c23.zicp.vip"},maxAge = 1800,allowCredentials = "true",allowedHeaders = "*") 21 | @RestController 22 | @RequestMapping("/api/game") 23 | public class GameApiController { 24 | 25 | @Resource 26 | GameService gameService; 27 | 28 | 29 | 30 | @ApiImplicitParams({ 31 | @ApiImplicitParam(name = "X", paramType = "query", required = true, dataType = "int", dataTypeClass = Integer.class,example = "1"), 32 | @ApiImplicitParam(name = "Y", paramType = "query", required = true, dataType = "int",dataTypeClass = Integer.class, example = "1"), 33 | @ApiImplicitParam(name = "z", paramType = "query", required = true, dataType = "int",dataTypeClass = Integer.class, example = "1") 34 | }) 35 | @ApiResponses({ 36 | @ApiResponse(code = 200, message = "解析白棋行动成功"), 37 | @ApiResponse(code = 400,message = "解析白棋行动失败"), 38 | }) 39 | @ApiOperation(value = "白棋移动一次",notes = "白棋子此时移动了一枚棋子,并将坐标信息传入") 40 | @PostMapping("/whiteMove") 41 | public RestBean whiteMove(@RequestBody ChessDetail chessDetail) { 42 | return gameService.move(chessDetail.getX(),chessDetail.getY(),chessDetail.getZ(),GameService.WHITE)? 43 | RestBeanBuilder.builder().code(ResultCode.WHITE_MOVE_SUCCESS).build().ToRestBean(): 44 | RestBeanBuilder.builder().code(ResultCode.WHITE_MOVE_FAILURE).build().ToRestBean(); 45 | } 46 | 47 | 48 | @ApiImplicitParams({ 49 | @ApiImplicitParam(name = "X", paramType = "query", required = true, dataType = "int", dataTypeClass = Integer.class,example = "1"), 50 | @ApiImplicitParam(name = "Y", paramType = "query", required = true, dataType = "int",dataTypeClass = Integer.class, example = "1"), 51 | @ApiImplicitParam(name = "z", paramType = "query", required = true, dataType = "int",dataTypeClass = Integer.class, example = "1") 52 | }) 53 | @ApiResponses({ 54 | @ApiResponse(code = 200, message = "解析黑棋行动成功"), 55 | @ApiResponse(code = 400,message = "解析黑棋行动失败"), 56 | }) 57 | @ApiOperation(value = "黑棋移动一次",notes = "黑棋子此时移动了一枚棋子,并将坐标信息传入") 58 | @PostMapping ("/blackMove") 59 | public RestBean blackMove(@RequestBody ChessDetail chessDetail) { 60 | return gameService.move(chessDetail.getX(),chessDetail.getY(),chessDetail.getZ(),GameService.BLACK)? 61 | RestBeanBuilder.builder().code(ResultCode.BLACK_MOVE_SUCCESS).build().ToRestBean(): 62 | RestBeanBuilder.builder().code(ResultCode.BLACK_MOVE_FAILURE).build().ToRestBean(); 63 | } 64 | 65 | 66 | @ApiResponses({ 67 | @ApiResponse(code = 200, message = "白棋已经下子"), 68 | @ApiResponse(code = 400,message = "白棋尚未下子"), 69 | }) 70 | @ApiOperation(value = "等待白棋下子",notes = "黑棋正在等待白棋下子,每一秒钟核验一次") 71 | @GetMapping("/waitForWhiteMove") 72 | public RestBean waitForWhiteMove() { 73 | ChessDetail chessDetail = gameService.waitForWhiteMove(); 74 | return RestBeanBuilder.builder().code(ResultCode.WAIT_WHITE_MOVE_SUCCESS).data(chessDetail).build().ToRestBean(); 75 | } 76 | 77 | @ApiResponses({ 78 | @ApiResponse(code = 200, message = "黑棋已经下子"), 79 | @ApiResponse(code = 400,message = "黑棋尚未下子"), 80 | }) 81 | @ApiOperation(value = "等待黑棋下子",notes = "白棋正在等待黑棋下子,每一秒钟核验一次") 82 | @GetMapping("/waitForBlackMove") 83 | public RestBean waitForBlackMove() { 84 | ChessDetail chessDetail = gameService.waitForBlackMove(); 85 | return RestBeanBuilder.builder().code(ResultCode.WAIT_BLACK_MOVE_SUCCESS).data(chessDetail).build().ToRestBean(); 86 | } 87 | 88 | 89 | @ApiResponses({ 90 | @ApiResponse(code = 200, message = "对手已经加入对局"), 91 | @ApiResponse(code = 400,message = "对手尚未加入对局"), 92 | }) 93 | @ApiOperation(value = "等待黑棋对手连接",notes = "每隔一秒钟,发送一条验证信息,验证对手是否加入了对局") 94 | @GetMapping ("/whiteWaitForOpponent") 95 | public RestBean whiteWaitForOpponent() { 96 | return gameService.whiteWaitForOpponent()? 97 | RestBeanBuilder.builder().code(ResultCode.OPPONENT_IS_IN).build().ToRestBean(): 98 | RestBeanBuilder.builder().code(ResultCode.OPPONENT_NOT_IN).build().ToRestBean(); 99 | } 100 | 101 | @ApiResponses({ 102 | @ApiResponse(code = 200, message = "对手已经加入对局"), 103 | @ApiResponse(code = 400,message = "对手尚未加入对局"), 104 | }) 105 | @ApiOperation(value = "等待白棋对手连接",notes = "每隔一秒钟,发送一条验证信息,验证对手是否加入了对局") 106 | @GetMapping ("/blackWaitForOpponent") 107 | public RestBean blackWaitForOpponent() { 108 | return gameService.blackWaitForOpponent()? 109 | RestBeanBuilder.builder().code(ResultCode.OPPONENT_IS_IN).build().ToRestBean(): 110 | RestBeanBuilder.builder().code(ResultCode.OPPONENT_NOT_IN).build().ToRestBean(); 111 | } 112 | 113 | @ApiResponses({ 114 | @ApiResponse(code = 200, message = "信息存入redis成功"), 115 | @ApiResponse(code = 400,message = "信息存入redis失败"), 116 | }) 117 | @ApiOperation(value = "白棋已经开始游戏",notes = "将白棋开始游戏的信息存入redis,等待对方接收") 118 | @GetMapping ("/whiteIsIn") 119 | public RestBean whiteIsIn() { 120 | return gameService.isIn(GameService.WHITE)? 121 | RestBeanBuilder.builder().code(ResultCode.SUCCESS).build().ToRestBean(): 122 | RestBeanBuilder.builder().code(ResultCode.FAILURE).build().ToRestBean(); 123 | } 124 | 125 | @ApiResponses({ 126 | @ApiResponse(code = 200, message = "信息存入redis成功"), 127 | @ApiResponse(code = 400,message = "信息存入redis失败"), 128 | }) 129 | @ApiOperation(value = "黑棋已经开始游戏",notes = "将黑棋开始游戏的信息存入redis,等待对方接收") 130 | @GetMapping ("/blackIsIn") 131 | public RestBean blackIsIn() { 132 | return gameService.isIn(GameService.BLACK) ? 133 | RestBeanBuilder.builder().code(ResultCode.SUCCESS).build().ToRestBean() : 134 | RestBeanBuilder.builder().code(ResultCode.FAILURE).build().ToRestBean(); 135 | } 136 | 137 | @PostMapping(value = "/pre") 138 | public void pre(HttpServletResponse response) { //处理预检请求 139 | response.addHeader("Content-Type","text/html; charset=utf-8"); 140 | } 141 | 142 | @ApiResponses({ 143 | @ApiResponse(code = 200, message = "数据修改成功"), 144 | @ApiResponse(code = 400,message = "数据修改失败"), 145 | }) 146 | @ApiOperation(value = "获得胜利",notes = "获得胜利后,修改用户数据库信息") 147 | @GetMapping ("/winGame") 148 | public RestBean winGame() { 149 | return gameService.gameResult(true) ? 150 | RestBeanBuilder.builder().code(ResultCode.MODIFY_SUCCESS).build().ToRestBean() : 151 | RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).build().ToRestBean(); 152 | } 153 | 154 | @ApiResponses({ 155 | @ApiResponse(code = 200, message = "数据修改成功"), 156 | @ApiResponse(code = 400,message = "数据修改失败"), 157 | }) 158 | @ApiOperation(value = "对局失败",notes = "对局失败后,修改用户数据库信息") 159 | @GetMapping ("/failureGame") 160 | public RestBean failureGame() { 161 | return gameService.gameResult(false) ? 162 | RestBeanBuilder.builder().code(ResultCode.MODIFY_SUCCESS).build().ToRestBean() : 163 | RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).build().ToRestBean(); 164 | } 165 | 166 | @ApiImplicitParams({ 167 | @ApiImplicitParam(name = "number", paramType = "query", required = true, dataType = "int", dataTypeClass = Integer.class,example = "1"), 168 | }) 169 | @ApiResponses({ 170 | @ApiResponse(code = 200, message = "数据修改成功"), 171 | @ApiResponse(code = 400,message = "数据修改失败"), 172 | }) 173 | @ApiOperation(value = "修改熄灯",notes = "熄灯每过一关,检查是否修改数据库") 174 | @PatchMapping ("/changePassNumber") 175 | public RestBean changePassNumber(@RequestBody int number) { 176 | return gameService.changePassNumber(number) ? 177 | RestBeanBuilder.builder().code(ResultCode.MODIFY_SUCCESS).build().ToRestBean() : 178 | RestBeanBuilder.builder().code(ResultCode.MODIFY_FAILURE).build().ToRestBean(); 179 | } 180 | 181 | 182 | } 183 | 184 | --------------------------------------------------------------------------------