commit 7c16d404cc04e1b3fcc5291b342f717f7050f216 Author: 陈沅 <907037276@qq.com> Date: Tue Jun 20 20:58:07 2023 +0800 天择外汇模拟交易系统后端框架搭建 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..e69de29 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ca5ab4b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..756a523 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ + +# 天择外汇模拟交易系统 + +## foreign_exchange_trading + +**构建**:\ +环境要求:JDK1.8及更高版本、Maven3.6+、postgresql。\ +若项目依赖构建失败,请确认本地环境版本。\ +JDK查看版本方式:win+R进入CMD界面输入java -version即可查看\ +Maven查看版本方式:win+R进入CMD界面输入输入mvn -v即可查看\ +maven构建:进入idea Terminal界面运行命令 mvn clean install.等待项目build完成,注:第一次构建系统可能耗时较长\ +完成以上既可进行开发 + + +**开发**: +命名规范:类名采用大驼峰命名方式,方法名、变量名采用小驼峰方式.二者差异自行访问baidu.com查阅。\ +**注:代码注释需满足javadoc规范\** \ +系统目录及代码归属约束:\ +annotation:系统切面文件。全局日志切面管理及接口权限控制文件.\ +config:整个系统配置文件,如全局token校验,swagger配置,jpa配置及数据库方言配置.\ +controller:接口定义,对外开放的接口文件请放置在该包,注:每个模块有一个对应文件,如对某模块新增接口请 +放置到对应的controller文件内.\ +entity:实体类文件放置地址,与数据库表字段对应,调整数据库字段或新增表请移至改包,注:对应的模块属性请放置于对应的类中.\ +repository:整个系统与数据库交互的文件放置于该包下,如调整数据库语句请移至该包。注:建议复杂查询及多表联查才用JPA原生SQL查询 +简单查询请用Specification条件构造器.用法请参考service包下的子文件\ +service:子模块业务逻辑代码请放置于该包下。\ +util:系统用到的第三方工具文件请放置于该包,如Excel导入导出、几何数据操作,第三方API接入及结果解析、file文件处理,全局异常定义等.\ +**为防止系统结构混乱,开发准备阶段请详细阅读开发规则及java文件归属规则。** + + + +**运行**: +设置ForeignExchangeTradingApplication为项目启动入口。Shift+F10启动. 注:必须完成上述开发节点下的项目构建工作才可启动,否则系统会报错 + +**接口预览及调用**: +默认地址为localhost:8800(端口号可自行更改) + +**测试**: +后续团队将尽快整理和提供系统每一个子模块单元测试. + +**发布**: +CMD进入系统目录,运行 mvn clean package命令,移至target目录复制plantingInspect.jar文件\ +windows发布:cmd界面运行 java -jar plantingInspect.jar.\ +linux发布:请自行查阅baidu.com + + diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..e69de29 diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a3ff6fc --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.7.12</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.sztzjy</groupId> + <artifactId>foreign_exchange_trading</artifactId> + <version>2.0-SNAPSHOT</version> + <name>foreign_exchange_trading</name> + <packaging>jar</packaging> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <java.version>1.8</java.version> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>cn.hutool</groupId> + <artifactId>hutool-all</artifactId> + <version>5.8.10</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-jwt</artifactId> + <version>1.1.1.RELEASE</version> + </dependency> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + <version>9.26</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>druid-spring-boot-starter</artifactId> + <version>1.2.18</version> + </dependency> + + <dependency> + <groupId>com.github.xiaoymin</groupId> + <artifactId>knife4j-spring-boot-starter</artifactId> + <version>3.0.3</version> + </dependency> + <dependency> + <groupId>org.springframework.security.oauth</groupId> + <artifactId>spring-security-oauth2</artifactId> + <version>2.3.8.RELEASE</version> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-spatial</artifactId> + </dependency> + <!-- MySQL连接驱动 --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + <version>8.0.28</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/src/main/java/com/sztzjy/forex/trading_trading/ForeignExchangeTradingApplication.java b/src/main/java/com/sztzjy/forex/trading_trading/ForeignExchangeTradingApplication.java new file mode 100644 index 0000000..729f258 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/ForeignExchangeTradingApplication.java @@ -0,0 +1,13 @@ +package com.sztzjy.forex.trading_trading; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ForeignExchangeTradingApplication { + + public static void main(String[] args) { + SpringApplication.run(ForeignExchangeTradingApplication.class, args); + } + +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/annotation/AnonymousAccess.java b/src/main/java/com/sztzjy/forex/trading_trading/annotation/AnonymousAccess.java new file mode 100644 index 0000000..6247e52 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/annotation/AnonymousAccess.java @@ -0,0 +1,16 @@ +package com.sztzjy.forex.trading_trading.annotation; + +import java.lang.annotation.*; + +/** + * 用于匿名访问配置, 使用时只需在Controller方法上使用此注解即能实现匿名访问 + * + * @author 陈沅 + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { + +} \ No newline at end of file diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/Constant.java b/src/main/java/com/sztzjy/forex/trading_trading/config/Constant.java new file mode 100644 index 0000000..f253b6b --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/Constant.java @@ -0,0 +1,18 @@ +package com.sztzjy.forex.trading_trading.config; + +import org.springframework.stereotype.Component; + +/** + * 常量定义 + */ +@Component +public class Constant { + /** + * 请求头 + */ + public static final String AUTHORIZATION = "Authorization"; + /** + * Basic 请求头 + */ + public static final String BASIC = "Basic"; +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/SpringContextHolder.java b/src/main/java/com/sztzjy/forex/trading_trading/config/SpringContextHolder.java new file mode 100644 index 0000000..b3289fe --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/SpringContextHolder.java @@ -0,0 +1,79 @@ +package com.sztzjy.forex.trading_trading.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@Lazy(false) +public class SpringContextHolder implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("unchecked") + public static <T> T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static <T> T getBean(Class<T> requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + if (applicationContext == null) { + throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" + + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); + } + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + private static void clearHolder() { + log.debug("清除SpringContextHolder中的ApplicationContext:" + + applicationContext); + applicationContext = null; + } + + @Override + public void destroy(){ + SpringContextHolder.clearHolder(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringContextHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext); + } + SpringContextHolder.applicationContext = applicationContext; + } + + /** + * 发布事件 + * + * @param event + */ + public static void publishEvent(ApplicationEvent event) { + if (applicationContext == null) { + return; + } + applicationContext.publishEvent(event); + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/exception/UnAuthorizedException.java b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/UnAuthorizedException.java new file mode 100644 index 0000000..d1479d6 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/UnAuthorizedException.java @@ -0,0 +1,41 @@ +package com.sztzjy.forex.trading_trading.config.exception; + +/** + * 当用户访问受保护资源但不提供任何凭据时将抛出此异常 + * + * @author 陈沅 + * @version 1.0.0 + */ +public class UnAuthorizedException extends RuntimeException { + + /** + * Constructs a new runtime exception with the specified detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public UnAuthorizedException(String message) { + super(message); + } + + /** + * Constructs a new runtime exception with the specified detail message and + * cause. <p>Note that the detail message associated with + * {@code cause} is <i>not</i> automatically incorporated in + * this runtime exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public UnAuthorizedException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/CustomAccessDeniedHandler.java b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..6226f21 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/CustomAccessDeniedHandler.java @@ -0,0 +1,32 @@ +package com.sztzjy.forex.trading_trading.config.exception.handler; + +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * 权限不足异常拦截 + * 当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应 + * + * @author 陈沅 + */ +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + response.setCharacterEncoding("UTF-8"); + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Content-Type", "application/json"); + PrintWriter printWriter = response.getWriter(); + printWriter.write("{\"code\":" + HttpStatus.FORBIDDEN.value() + ", \"msg\": \"权限不足\"}"); + printWriter.flush(); + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/CustomAuthenticationEntryPoint.java b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..22c5c9f --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/CustomAuthenticationEntryPoint.java @@ -0,0 +1,39 @@ +package com.sztzjy.forex.trading_trading.config.exception.handler; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * token异常拦截 + * 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 + * + * @author 陈沅 + */ +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { + // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 + response.setCharacterEncoding("UTF-8"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Content-Type", "application/json"); + Throwable cause = authException.getCause(); + PrintWriter printWriter = response.getWriter(); + if (cause instanceof InvalidTokenException) { + printWriter.write("{\"code\":" + HttpStatus.UNAUTHORIZED.value() + ",\"msg\":\"身份认证失败,无效或过期token\"}"); + } else { + printWriter.write("{\"code\":" + HttpStatus.UNAUTHORIZED.value() + ",\"msg\":\"身份认证失败\"}"); + } + printWriter.flush(); + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..e7799bc --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,79 @@ +package com.sztzjy.forex.trading_trading.config.exception.handler; + +import com.sztzjy.forex.trading_trading.config.exception.UnAuthorizedException; +import com.sztzjy.forex.trading_trading.util.ResultEntity; +import com.sztzjy.forex.trading_trading.util.ThrowableUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResultEntity<String> illegalArgumentException(IllegalArgumentException e) { + log.error(ThrowableUtil.getStackTrace(e)); + return new ResultEntity<>(HttpStatus.BAD_REQUEST, e.getMessage()); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResultEntity<String> accessDeniedException(AccessDeniedException e) { + log.error(ThrowableUtil.getStackTrace(e)); + return new ResultEntity<>(HttpStatus.FORBIDDEN, e.getMessage()); + } + + @ExceptionHandler(UnAuthorizedException.class) + public ResultEntity<String> UnAuthorizedException(UnAuthorizedException e) { + log.error(ThrowableUtil.getStackTrace(e)); + return new ResultEntity<>(HttpStatus.UNAUTHORIZED, e.getMessage()); + } + + /** + * 处理所有接口数据验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResultEntity<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(ThrowableUtil.getStackTrace(e)); + ObjectError objectError = e.getBindingResult().getAllErrors().get(0); + String message = objectError.getDefaultMessage(); + if (objectError instanceof FieldError) { + message = ((FieldError) objectError).getField() + ": " + message; + } + return new ResultEntity<>(HttpStatus.BAD_REQUEST, message); + } + + /** + * 处理所有不可知的异常 + * + * @param e 未知异常 + * @return / + */ + @ExceptionHandler(Throwable.class) + public ResultEntity<String> nullPointerException(Throwable e) { + log.error(ThrowableUtil.getStackTrace(e)); + return new ResultEntity<>(HttpStatus.BAD_REQUEST, e.getMessage()); + } + + /** + * BadCredentialsException + */ + @ExceptionHandler(BadCredentialsException.class) + public ResultEntity<String> badCredentialsException(BadCredentialsException e) { + // 打印堆栈信息 + String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage(); + log.error(message); + return new ResultEntity<>(HttpStatus.BAD_REQUEST, message); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/AuthenticationFilter.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/AuthenticationFilter.java new file mode 100644 index 0000000..25e3773 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/AuthenticationFilter.java @@ -0,0 +1,91 @@ +package com.sztzjy.forex.trading_trading.config.security; + +import cn.hutool.extra.servlet.ServletUtil; +import com.sztzjy.forex.trading_trading.config.Constant; +import com.sztzjy.forex.trading_trading.config.exception.UnAuthorizedException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +/** + * 系统登陆认证拦截 + * + * @author 陈沅 + */ +public class AuthenticationFilter extends OncePerRequestFilter { + private final PathMatcher matcher = new AntPathMatcher(); + private final TokenProvider tokenProvider; + public AuthenticationFilter(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String requestRri = request.getRequestURI(); + if (requestRri.equals("/")) { + response.sendRedirect("/doc.html"); + return; + } + if (!isExclude(request)) { + System.out.println("--------------------------------->>> " + LocalDateTime.now() + " ---ip:" + ServletUtil.getClientIP(request) + " requestUri:" + requestRri); + } + String token = request.getHeader(Constant.AUTHORIZATION); + + if (token == null || token.equals("undefined") || token.equals("") || token.equals("null")) { + filterChain.doFilter(request, response); + return; + } + + if (!StringUtils.startsWithIgnoreCase(token, "Bearer ")) { + throw new UnAuthorizedException("令牌错误: 缺失Bearer.."); + } + JwtUser currentUser = tokenProvider.getClaims(token); + Authentication authentication = new UsernamePasswordAuthenticationToken(currentUser, "****", currentUser.getAuthorities()); + request.getUserPrincipal(); + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } + + + /** + * 排除的请求路径匹配 + */ + private final static List<String> patterns = Arrays.asList( + "/swagger-resources/**", + "/doc.html", + "/webjars/**", + "/v2/**", + "/favicon.ico", + "/login", + "/druid/**" + ); + + /** + * 判断请求路径是否是在排除之外 + * + * @param request / + * @return true-是排除的 + */ + private boolean isExclude(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + for (String pattern : patterns) { + if (matcher.match(pattern, requestURI)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java new file mode 100644 index 0000000..e39b532 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java @@ -0,0 +1,48 @@ +package com.sztzjy.forex.trading_trading.config.security; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * jwt用户对象 + */ +@Getter +@Setter +public class JwtUser implements UserDetails { + private String username; + private String password; + private String name; + private String userId; + private int roleId; + private int schoolId; + private int classId; + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return null; + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/TokenProvider.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/TokenProvider.java new file mode 100644 index 0000000..2538577 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/TokenProvider.java @@ -0,0 +1,20 @@ +package com.sztzjy.forex.trading_trading.config.security; + +import org.springframework.stereotype.Component; + +@Component +public class TokenProvider { + + /** + * 解析jwtToken + * + * @param jwtToken jwtToken + * @return jwt解析对象 + */ + + public JwtUser getClaims(String jwtToken) { + //todo 解析用户token + return null; + } + +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebConfigurerAdapter.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebConfigurerAdapter.java new file mode 100644 index 0000000..fa53b0b --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebConfigurerAdapter.java @@ -0,0 +1,62 @@ +package com.sztzjy.forex.trading_trading.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * 系统配置 + */ +@Configuration +@EnableWebMvc +public class WebConfigurerAdapter implements WebMvcConfigurer { + @Resource + private StringHttpMessageConverter stringHttpMessageConverter; + + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); + config.addAllowedMethod("*"); + config.addAllowedHeader("*"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @Bean + public ReloadableResourceBundleMessageSource messageSource() { + ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource(); + reloadableResourceBundleMessageSource.setBasename("classpath:messages"); + return reloadableResourceBundleMessageSource; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); + } + + @Override + public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { + for (int i = 0; i < converters.size(); i++) { + if (converters.get(i) instanceof StringHttpMessageConverter) { + stringHttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8); + converters.set(i, stringHttpMessageConverter); + } + } + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java new file mode 100644 index 0000000..f3c64ca --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java @@ -0,0 +1,112 @@ +package com.sztzjy.forex.trading_trading.config.security; + +import com.sztzjy.forex.trading_trading.annotation.AnonymousAccess; +import com.sztzjy.forex.trading_trading.config.SpringContextHolder; +import com.sztzjy.forex.trading_trading.config.exception.handler.CustomAccessDeniedHandler; +import com.sztzjy.forex.trading_trading.config.exception.handler.CustomAuthenticationEntryPoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * secret配置 + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Resource + private TokenProvider tokenProvider; + @Resource + private CustomAccessDeniedHandler customAccessDeniedHandler; + @Resource + private CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + + @Override + protected void configure(HttpSecurity http) throws Exception { + RequestMappingHandlerMapping handlerMapping = SpringContextHolder.getBean("requestMappingHandlerMapping"); + Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods(); + Set<String> anonymousUrls = discoveryAnonymousUrls(handlerMethods); + http + .addFilterBefore(new AuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) + // 禁用 CSRF + .csrf().disable() + .exceptionHandling() + .authenticationEntryPoint(customAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + // 防止iframe 造成跨域 + .and().headers().frameOptions().disable() + // 不创建会话 + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + // 所有请求都需要认证 + .authorizeRequests() + .antMatchers("/swagger-resources/**").permitAll() + .antMatchers("/doc.html").permitAll() + .antMatchers("/webjars/**").permitAll() + .antMatchers("/v2/**").permitAll() + .antMatchers("/test/**").permitAll() + .antMatchers(anonymousUrls.toArray(new String[]{})).permitAll() + .anyRequest().authenticated(); + } + + /** + * 认证相关的核心接口 + * + * @return {@link AuthenticationManager} + * @throws Exception / + */ + @Bean + public AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + + + /** + * 根据注解发现需要匿名访问的请求路径 + * + * @param handlerMethodMap ? + * @return 匿名Mapping patterns + */ + private Set<String> discoveryAnonymousUrls(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) { + Set<String> patterns = new HashSet<>(); + for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) { + HandlerMethod handlerMethod = infoEntry.getValue(); + AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class); + if (Objects.nonNull(anonymousAccess)) { + assert infoEntry.getKey().getPatternsCondition() != null; + patterns.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + } + } + return patterns; + } + +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/swagger/Swagger2Config.java b/src/main/java/com/sztzjy/forex/trading_trading/config/swagger/Swagger2Config.java new file mode 100644 index 0000000..b79d7e5 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/swagger/Swagger2Config.java @@ -0,0 +1,104 @@ +package com.sztzjy.forex.trading_trading.config.swagger; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import javax.annotation.Resource; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * swagger 3.0接口文档配置类 + * + * @author 陈沅 + */ +@EnableSwagger2 +@Configuration +@Profile({"dev", "pro", "test"}) +public class Swagger2Config implements ApplicationListener<WebServerInitializedEvent> { + @Resource + private SwaggerProperties swaggerProperties; + + @Bean + public Docket docket() { + return new Docket(DocumentationType.SWAGGER_2) + .enable(swaggerProperties.getEnable()) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + .paths(PathSelectors.any()) +// .globalRequestParameters(requestParameters()) + .build() + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title(swaggerProperties.getTitle()) + .description(swaggerProperties.getDescription()) + .contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactAddress(), swaggerProperties.getContactEmail())) + .version(swaggerProperties.getVersion()) + .build(); + } + + /** + * 设置全局安全策略 + * + * @return / + */ + private List<SecurityScheme> securitySchemes() { + List<SecurityScheme> schemes = new ArrayList<>(); + SecurityScheme scheme = new ApiKey(swaggerProperties.getTokenHeader(), swaggerProperties.getTokenHeader(), In.HEADER.toValue()); + schemes.add(scheme); + return schemes; + } + + /** + * 授权信息全局应用 + */ + private List<SecurityContext> securityContexts() { + SecurityContext securityContext = SecurityContext.builder() + .securityReferences(Collections.singletonList(new SecurityReference(swaggerProperties.getTokenHeader(), new AuthorizationScope[]{new AuthorizationScope("global", "")}))) + .build(); + return Collections.singletonList(securityContext); + + } + + @Override + public void onApplicationEvent(WebServerInitializedEvent webServerInitializedEvent) { + try { + Logger logger = LoggerFactory.getLogger(Swagger2Config.class); + + //获取IP + String hostAddress = Inet4Address.getLocalHost().getHostAddress(); + //获取端口号 + int port = webServerInitializedEvent.getWebServer().getPort(); + //获取应用名 + String applicationName = webServerInitializedEvent.getApplicationContext().getApplicationName(); + logger.info("项目启动成功!接口文档地址: http://" + hostAddress + ":" + webServerInitializedEvent.getWebServer().getPort() + applicationName + "/doc.html#"); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/swagger/SwaggerProperties.java b/src/main/java/com/sztzjy/forex/trading_trading/config/swagger/SwaggerProperties.java new file mode 100644 index 0000000..27d3bcd --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/swagger/SwaggerProperties.java @@ -0,0 +1,115 @@ +package com.sztzjy.forex.trading_trading.config.swagger; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author 陈沅 + */ +@Component +@ConfigurationProperties(prefix = "swagger") +public class SwaggerProperties { + /** + * 是否开启swagger,生产环境一般关闭 + */ + private Boolean enable; + /** + * 授权KEY + */ + private String tokenHeader; + + /** + * 标题 + */ + private String title; + + /** + * 描述 + */ + private String description; + + /** + * 联系人姓名 + */ + private String contactName; + + /** + * 联系人地址 + */ + private String contactAddress; + + /** + * 联系人邮箱 + */ + private String contactEmail; + + /** + * 版本 + */ + private String version; + + public Boolean getEnable() { + return enable; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getContactName() { + return contactName; + } + + public void setContactName(String contactName) { + this.contactName = contactName; + } + + public String getContactAddress() { + return contactAddress; + } + + public void setContactAddress(String contactAddress) { + this.contactAddress = contactAddress; + } + + public String getContactEmail() { + return contactEmail; + } + + public void setContactEmail(String contactEmail) { + this.contactEmail = contactEmail; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getTokenHeader() { + return tokenHeader; + } + + public void setTokenHeader(String tokenHeader) { + this.tokenHeader = tokenHeader; + } +} + diff --git a/src/main/java/com/sztzjy/forex/trading_trading/util/ResultDataEntity.java b/src/main/java/com/sztzjy/forex/trading_trading/util/ResultDataEntity.java new file mode 100644 index 0000000..fc5c05b --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/util/ResultDataEntity.java @@ -0,0 +1,55 @@ +package com.sztzjy.forex.trading_trading.util; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + * 出参对象统一封装 + */ +@Getter +public class ResultDataEntity<T> { + /** + * 状态码 + */ + private final Integer code; + /** + * 异常提示 + */ +// @JsonInclude(JsonInclude.Include.NON_NULL) + private String msg = null; + /** + * 返回体对象 + */ +// @JsonInclude(JsonInclude.Include.NON_NULL) + private T data = null; + + public ResultDataEntity(T data) { + this.code = HttpStatus.OK.value(); + this.data = data; + } + + public ResultDataEntity(HttpStatus status, T data) { + this.code = status.value(); + this.data = data; + } + + public ResultDataEntity(HttpStatus status, String msg, T data) { + this.code = status.value(); + this.msg = msg; + this.data = data; + } + + public ResultDataEntity(String msg) { + this.code = HttpStatus.OK.value(); + this.msg = msg; + } + + public ResultDataEntity(HttpStatus status, String msg) { + this.code = status.value(); + this.msg = msg; + } + + public ResultDataEntity(HttpStatus status) { + this.code = status.value(); + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/util/ResultEntity.java b/src/main/java/com/sztzjy/forex/trading_trading/util/ResultEntity.java new file mode 100644 index 0000000..f89acba --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/util/ResultEntity.java @@ -0,0 +1,38 @@ +package com.sztzjy.forex.trading_trading.util; + +import io.swagger.annotations.ApiModel; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * 对ResponseEntity做了简单包装 + * + */ +@ApiModel("API返回对象") +@Getter +public class ResultEntity<T> extends ResponseEntity<ResultDataEntity<T>> { + public ResultEntity(T data) { + super(new ResultDataEntity<>(HttpStatus.OK, data), HttpStatus.OK); + } + + public ResultEntity(HttpStatus status, T data) { + super(new ResultDataEntity<>(status, data), status); + } + + public ResultEntity(HttpStatus status, String msg, T data) { + super(new ResultDataEntity<>(status, msg, data), status); + } + + public ResultEntity(String msg) { + super(new ResultDataEntity<>(HttpStatus.OK, msg), HttpStatus.OK); + } + + public ResultEntity(HttpStatus status, String msg) { + super(new ResultDataEntity<>(status, msg), status); + } + + public ResultEntity(HttpStatus status) { + super(new ResultDataEntity<>(status), status); + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/util/ThrowableUtil.java b/src/main/java/com/sztzjy/forex/trading_trading/util/ThrowableUtil.java new file mode 100644 index 0000000..0b1e831 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/util/ThrowableUtil.java @@ -0,0 +1,23 @@ +package com.sztzjy.forex.trading_trading.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * 异常工具 + * + * 陈沅 + */ +public class ThrowableUtil { + + /** + * 获取堆栈信息 + */ + public static String getStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + try (PrintWriter pw = new PrintWriter(sw)) { + throwable.printStackTrace(pw); + return sw.toString(); + } + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..3e38a98 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,8 @@ +spring: + datasource: + druid: + db-type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://${DB_HOST:118.31.7.2}:${DB_PORT:3306}}/${DB_NAME:foreign_trading_system}?useSSL=false&serverTimezone=UTC + username: ${DB_USER:root} + password: ${DB_PWD:sztzjy2017} \ No newline at end of file diff --git a/src/main/resources/application-pro.yml b/src/main/resources/application-pro.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..6a4c421 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,82 @@ +server: + port: 8800 + servlet: + context-path: / + +spring: + application: + name: trading_system + profiles: + active: dev + mvc: + pathmatch: + matching-strategy: ant_path_matcher + jackson: + time-zone: GMT+8 + datasource: + druid: + # 初始连接数 + initial-size: 5 + # 最小连接数 + min-idle: 15 + # 最大连接数 + max-active: 500 + # 获取连接超时时间 + max-wait: 5000 + # 连接有效性检测时间 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存的时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存的时间 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 + test-while-idle: true + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: false + # 是否在归还到池中前进行检验 + test-on-return: false + # 检测连接是否有效 + validation-query: select 'X' + # 配置监控统计 + webStatFilter: + enabled: true + stat-view-servlet: + allow: + enabled: true + # 控制台管理用户名和密码 + url-pattern: /druid/* + reset-enable: false + login-username: admin + login-password: 2023inspect + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: false + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + #配置 Jpa + jpa: + hibernate: + ddl-auto: update + open-in-view: true + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect + temp: + use_jdbc_metadata_defaults: false + database: mysql + +swagger: + enable: true + tokenHeader: Authorization + title: 天择外汇模拟交易 • 接口文档 + description: 天择外汇模拟交易WebAPI接口文档 + contactName: 深圳天择教育科技有限公司 + contactAddress: www.sztzjy.com + version: @project.version@ diff --git a/src/test/java/com/sztzjy/foreign_exchange_trading/ForeignExchangeTradingApplicationTests.java b/src/test/java/com/sztzjy/foreign_exchange_trading/ForeignExchangeTradingApplicationTests.java new file mode 100644 index 0000000..b44314f --- /dev/null +++ b/src/test/java/com/sztzjy/foreign_exchange_trading/ForeignExchangeTradingApplicationTests.java @@ -0,0 +1,13 @@ +package com.sztzjy.foreign_exchange_trading; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ForeignExchangeTradingApplicationTests { + + @Test + void contextLoads() { + } + +}