├── .gitattributes ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── spring │ │ └── demo │ │ └── springbootexample │ │ ├── SpringBootExampleApplication.java │ │ ├── annotation │ │ └── ignoreJWT.java │ │ ├── beans │ │ ├── Product.java │ │ ├── entity │ │ │ └── JWTUser.java │ │ └── msg │ │ │ ├── Request.java │ │ │ └── Response.java │ │ ├── config │ │ ├── Config.java │ │ ├── InterceptorConfiger.java │ │ └── SwaggerConfig.java │ │ ├── constants │ │ ├── ErrCodeEnum.java │ │ ├── JWTConsts.java │ │ └── ReqResConsts.java │ │ ├── controller │ │ ├── DocController.java │ │ ├── JwtTestController.java │ │ ├── TestController.java │ │ └── global │ │ │ ├── GlobalBasicErrorHandler.java │ │ │ └── GlobalExceptionHandler.java │ │ ├── exception │ │ ├── JWTExpiredException.java │ │ └── JWTIllegalException.java │ │ ├── intercepter │ │ └── JWTAuthInterceptor.java │ │ ├── jwt │ │ └── JsonWebTokenUtility.java │ │ ├── result │ │ ├── ERRORDetail.java │ │ ├── Pagination.java │ │ └── WebResult.java │ │ ├── security │ │ └── WebSecurityConfig.java │ │ └── utils │ │ └── JWTUtils.java └── resources │ ├── application-dev.yml │ ├── application-prod.yml │ ├── application-test.yml │ ├── application.yml │ ├── generator │ ├── generator.properties │ └── generatorConfig.xml │ └── logback-spring.xml └── test └── java └── com └── spring └── demo └── springbootexample └── SpringBootExampleApplicationTests.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebman/spring-boot-example/415b7fdff1aea67da8c3033bf5d298a0bf4f3ebb/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 calebman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 本文简介 2 | - 为什么使用SpringBoot 3 | - 搭建怎样一个环境 4 | - 开发环境 5 | - 导入快速启动项目 6 | - 集成前准备 7 | - 集成Mybatis 8 | - 集成Swagger2 9 | - 多环境配置 10 | - 多环境下的日志配置 11 | - jwt配置 12 | - 常用配置 13 | ### 为什么使用SpringBoot 14 | SpringBoot相对于传统的SSM框架的优点是提供了默认的样板化配置,简化了Spring应用的初始搭建过程,如果你不想被众多的xml配置文件困扰,可以考虑使用SpringBoot替代 15 | ### 搭建怎样一个环境 16 | 本文将基于Spring官方提供的快速启动项目模板集成Mybatis、Swagger2框架,并讲解mybatis generator一键生成代码插件、logback、一键生成文档以及多环境的配置方法,最后再介绍一下自定义配置的注解获取、全局异常处理等经常用到的东西。 17 | ### 开发环境 18 | 本人使用IDEA作为开发工具,IDEA下载时默认集成了SpringBoot的快速启动项目可以直接创建,如果使用Eclipse的同学可以考虑安装SpringBoot插件或者直接从[这里](https://start.spring.io/)配置并下载SpringBoot快速启动项目,需要注意的是本次环境搭建选择的是SpringBoot2.0的快速启动框架,SpringBoot2.0要求jdk版本必须要在1.8及以上。 19 | ### 导入快速启动项目 20 | 不管是由IDEA导入还是现实下载模板工程都需要初始化快速启动工程的配置,如果使用IDEA,在新建项目时选择Spring Initializr,主要配置如下图 21 | ![IDEA新建SpringBoot项目-填写项目/包名](http://upload-images.jianshu.io/upload_images/10936059-865b16fcec74d227.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 22 | ![IDEA新建SpringBoot项目-选择依赖包](http://upload-images.jianshu.io/upload_images/10936059-4fc8bad8fe8d75be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 23 | 点击next之后finish之后IDEA显示正在下载模板工程,下载完成后会根据pom.xml下载包依赖,依赖下载完毕后模板项目就算创建成功了,如果是直接从官方网站配置下载快速启动项目可参考下图配置 24 | ![直接下载SpringBoot快速启动项目-项目配置](http://upload-images.jianshu.io/upload_images/10936059-9059e75b2f08fb1a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 25 | 从Search for dependencies 框中输入并选择Web、Mysql、Mybatis加入依赖,点击Generate Project下载快速启动项目,然后在IDE中选择导入Maven项目,项目导入完成后可见其目录结构如下图 26 | ![快速启动项目-项目结构](http://upload-images.jianshu.io/upload_images/10936059-b401a783d6cc8337.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 27 | 需要关注红色方框圈起来的部分,由上往下第一个java类是用来启动项目的入口函数,第二个properties后缀的文件是项目的配置文件,第三个是项目的依赖包以及执行插件的配置 28 | ### 集成前准备 29 | ##### 修改.properties为.yml 30 | yml相对于properties更加精简而且很多官方给出的Demo都是yml的配置形式,在这里我们采用yml的形式代替properties,相对于properties形式主要有以下两点不同 31 | >1. 对于键的描述由原有的 "." 分割变成了树的形状 32 | >2. 对于所有的键的后面一个要跟一个空格,不然启动项目会报配置解析错误 33 | ``` 34 | # properties式语法描述 35 | spring.datasource.name = mysql 36 | spring.datasource.url = jdbc:mysql://localhost:3306/db?characterEncoding=utf-8 37 | spring.datasource.username = root 38 | spring.datasource.password = 123 39 | # yml式语法描述 40 | spring: 41 | datasource: 42 | name: mysql 43 | url: jdbc:mysql://localhost:3306/db?characterEncoding=utf-8 44 | username: root 45 | password: 123 46 | ``` 47 | 48 | ##### 配置所需依赖 49 | 快速启动项目创建成功后我们观察其pom.xml文件中的依赖如下图,包含了我们选择的Web、Mybatis以及Mysql 50 | ``` 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-web 55 | 56 | 57 | 58 | org.mybatis.spring.boot 59 | mybatis-spring-boot-starter 60 | 1.3.1 61 | 62 | 63 | 64 | mysql 65 | mysql-connector-java 66 | runtime 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-test 72 | test 73 | 74 | ``` 75 | 但是我们使用ORM框架一般还会配合数据库连接池以及分页插件来使用,在这里我选择了阿里的druid以及pagehelper这个分页插件,再加上我们还需要整合swagger2文档自动化构建框架,所以增加了以下四个依赖项 76 | ``` 77 | 78 | 79 | com.github.pagehelper 80 | pagehelper-spring-boot-starter 81 | 1.2.3 82 | 83 | 84 | 85 | com.alibaba 86 | druid-spring-boot-starter 87 | 1.1.1 88 | 89 | 90 | 91 | com.alibaba 92 | fastjson 93 | 1.2.31 94 | 95 | 96 | 97 | io.springfox 98 | springfox-swagger2 99 | 2.5.0 100 | 101 | ``` 102 | ### 集成Mybatis 103 | Mybatis的配置主要包括了druid数据库连接池、pagehelper分页插件、mybatis-generator代码逆向生成插件以及mapper、pojo扫描配置 104 | ##### 配置druid数据库连接池 105 | 添加以下配置至application.yml文件中 106 | ``` 107 | spring: 108 | datasource: 109 | # 如果存在多个数据源,监控的时候可以通过名字来区分开来 110 | name: mysql 111 | # 连接数据库的url 112 | url: jdbc:mysql://localhost:3306/db?characterEncoding=utf-8 113 | # 连接数据库的账号 114 | username: root 115 | # 连接数据库的密码 116 | password: 123 117 | # 使用druid数据源 118 | type: com.alibaba.druid.pool.DruidDataSource 119 | # 扩展插件 120 | # 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall 121 | filters: stat 122 | # 最大连接池数量 123 | maxActive: 20 124 | # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 125 | initialSize: 1 126 | # 获取连接时最大等待时间,单位毫秒 127 | maxWait: 60000 128 | # 最小连接池数量 129 | minIdle: 1 130 | timeBetweenEvictionRunsMillis: 60000 131 | # 连接保持空闲而不被驱逐的最长时间 132 | minEvictableIdleTimeMillis: 300000 133 | # 用来检测连接是否有效的sql,要求是一个查询语句 134 | # 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用 135 | validationQuery: select count(1) from 'table' 136 | # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 137 | testWhileIdle: true 138 | # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 139 | testOnBorrow: false 140 | # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 141 | testOnReturn: false 142 | # 是否缓存preparedStatement,即PSCache 143 | poolPreparedStatements: false 144 | # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true 145 | maxOpenPreparedStatements: -1 146 | ``` 147 | ##### 配置pagehelper分页插件 148 | ``` 149 | # pagehelper分页插件 150 | pagehelper: 151 | # 数据库的方言 152 | helperDialect: mysql 153 | # 启用合理化,如果pageNum < 1会查询第一页,如果pageNum > pages会查询最后一页 154 | reasonable: true 155 | ``` 156 | ##### 代码逆向生成插件mybatis-generator的配置及运行 157 | mybatis-generator插件的使用主要分为以下三步 158 | >1. pom.xml中添加mybatis-generator插件 159 | ``` 160 | 161 | 162 | 163 | 164 | org.springframework.boot 165 | spring-boot-maven-plugin 166 | 167 | 168 | 169 | org.mybatis.generator 170 | mybatis-generator-maven-plugin 171 | 1.3.2 172 | 173 | 174 | 175 | ${basedir}/src/main/resources/generator/generatorConfig.xml 176 | 177 | true 178 | true 179 | 180 | 181 | 182 | 183 | ``` 184 | >2.创建逆向代码生成配置文件generatorConfig.xml 185 | 186 | 参照pom.xml插件配置中的扫描位置,在resources目录下创建generator文件夹,在新建的文件夹中创建generatorConfig.xml配置文件,文件的详细配置信息如下 187 | ``` 188 | 189 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | ``` 225 | 为了将generatorConfig.xml配置模板化,在这里将变动性较大的配置项单独提取出来作为一个generatorConfig.xml的配置文件,然后通过properties标签读取此文件的配置,这样做的好处是当需要多处复用此xml时只需要关注少量的配置项。 226 | 在generatorConfig.xml同级创建generator.properties文件,现只需要配置generator.properties文件即可,配置内容如下 227 | ``` 228 | # 请手动配置以下选项 229 | # 数据库驱动:选择你的本地硬盘上面的数据库驱动包 230 | classPathEntry = D:/CJH/maven-repository/mysql/mysql-connector-java/5.1.30/mysql-connector-java-5.1.30.jar 231 | # 数据库名称、用户名、密码 232 | db = db 233 | userId = root 234 | password = 123 235 | # 生成pojo的包名位置 在src/main/java目录下 236 | pojoTargetPackage = com.spring.demo.springbootexample.mybatis.po 237 | # 生成DAO的包名位置 在src/main/java目录下 238 | daoTargetPackage = com.spring.demo.springbootexample.mybatis.mapper 239 | # 生成Mapper的包名位置 位于src/main/resources目录下 240 | mapperTargetPackage = mapper 241 | ``` 242 | >3. 运行mybatis-generator插件生成Dao、Model、Mapping 243 | ``` 244 | # 打开命令行cd到项目pom.xml同级目录运行以下命令 245 | mvn mybatis-generator:generate -e 246 | ``` 247 | ##### mybatis扫描包配置 248 | 至此已经生成了指定数据库对应的实体、映射类,但是还不能直接使用,需要配置mybatis扫描地址后才能正常调用 249 | >1. 在application.yml配置mapper.xml以及pojo的包地址 250 | ``` 251 | mybatis: 252 | # mapper.xml包地址 253 | mapper-locations: classpath:mapper/*.xml 254 | # pojo生成包地址 255 | type-aliases-package: com.spring.demo.springbootexample.mybatis.po 256 | ``` 257 | >2. 在SpringBootExampleApplication.java中开启Mapper扫描注解 258 | ``` 259 | @SpringBootApplication 260 | @MapperScan("com.spring.demo.springbootexample.mybatis.mapper") 261 | public class SpringBootExampleApplication { 262 | 263 | public static void main(String[] args) { 264 | SpringApplication.run(SpringBootExampleApplication.class, args); 265 | } 266 | } 267 | ``` 268 | ##### 测试mapper的有效性 269 | ``` 270 | @Controller 271 | public class TestController { 272 | //替换成自己生成的mapper 273 | @Autowired 274 | UserMapper userMapper; 275 | 276 | @RequestMapping("/test") 277 | @ResponseBody 278 | public Object test(){ 279 | //查询该表的所有数据 280 | return userMapper.selectByExample(null); 281 | } 282 | } 283 | ``` 284 | 启动SpringBootExampleApplication.java的main函数,如果没有在application.yml特意配置server.port那么springboot会采用默认的8080端口运行,运行成功将打印如下日志 285 | ``` 286 | Tomcat started on port(s): 8080 (http) with context path '' 287 | ``` 288 | 在浏览器输入地址如果返回表格的中的所有数据代表mybatis集成成功 289 | ``` 290 | http://localhost:8080/test 291 | ``` 292 | ### 集成Swagger2 293 | Swagger2是一个文档快速构建工具,能够通过注解自动生成一个Restful风格json形式的接口文档,并可以通过如swagger-ui等工具生成html网页形式的接口文档,swagger2的集成比较简单,使用需要稍微熟悉一下,集成、注解与使用分如下四步 294 | >1. 建立SwaggerConfig文件 295 | ``` 296 | @Configuration 297 | public class SwaggerConfig { 298 | // 接口版本号 299 | private final String version = "1.0"; 300 | // 接口大标题 301 | private final String title = "SpringBoot示例工程"; 302 | // 具体的描述 303 | private final String description = "API文档自动生成示例"; 304 | // 服务说明url 305 | private final String termsOfServiceUrl = "http://www.kingeid.com"; 306 | // licence 307 | private final String license = "MIT"; 308 | // licnce url 309 | private final String licenseUrl = "https://mit-license.org/"; 310 | // 接口作者联系方式 311 | private final Contact contact = new Contact("calebman", "https://github.com/calebman", "chenjianhui0428@gmail.com"); 312 | 313 | @Bean 314 | public Docket buildDocket() { 315 | return new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInf()) 316 | .select().build(); 317 | } 318 | 319 | private ApiInfo buildApiInf() { 320 | return new ApiInfoBuilder().title(title).termsOfServiceUrl(termsOfServiceUrl).description(description) 321 | .version(version).license(license).licenseUrl(licenseUrl).contact(contact).build(); 322 | 323 | } 324 | 325 | } 326 | ``` 327 | >2. 在SpringBootExampleApplication.java中启用Swagger2注解 328 | 329 | 在@SpringBootApplication注解下面加上@EnableSwagger2注解 330 | >3. 常用注解示例 331 | ``` 332 | //Contorller中的注解示例 333 | @Controller 334 | @RequestMapping("/v1/product") 335 | // 表示标识这个类是swagger的资源 336 | @Api(value = "DocController", tags = {"restful api示例"}) 337 | public class DocController extends BaseController { 338 | 339 | @RequestMapping(value = "/{id}", method = RequestMethod.PUT) 340 | @ResponseBody 341 | //表示一个http请求的操作 342 | @ApiOperation(value = "修改指定产品", httpMethod = "PUT", produces = "application/json") 343 | //@ApiImplicitParams用于方法,包含多个@ApiImplicitParam表示单独的请求参数 344 | @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "产品ID", required = true, paramType = "path")}) 345 | public WebResult update(@PathVariable("id") Integer id, @ModelAttribute Product product) { 346 | logger.debug("修改指定产品接收产品id与产品信息=>%d,{}", id, product); 347 | if (id == null || "".equals(id)) { 348 | logger.debug("产品id不能为空"); 349 | return WebResult.error(ERRORDetail.RC_0101001); 350 | } 351 | return WebResult.success(); 352 | } 353 | } 354 | //Model中的注解示例 355 | //表示对类进行说明,用于参数用实体类接收 356 | @ApiModel(value = "产品信息") 357 | public class Product { 358 | //表示对model属性的说明或者数据操作更改 359 | @ApiModelProperty(required = true, name = "name", value = "产品名称", dataType = "query") 360 | private String name; 361 | @ApiModelProperty(name = "type", value = "产品类型", dataType = "query") 362 | private String type; 363 | } 364 | ``` 365 | >4. 生成json形式的文档 366 | 367 | 集成成功后启动项目控制台会打印级别为INFO的日志,截取部分如下,表明可通过访问应用的v2/api-docs接口得到文档api的json格式数据,可在浏览器输入指定地址验证集成是否成功 368 | ``` 369 | Mapped "{[/v2/api-docs],methods=[GET],produces=[application/json || application/hal+json]}" 370 | http://localhost:8080/v2/api-docs 371 | ``` 372 | ### 多环境配置 373 | 应用研发过程中多环境是不可避免的,假设我们现在有开发、演示、生产三个不同的环境其配置也不同,如果每次都在打包环节来进行配置难免出错,SpringBoot支持通过命令启动不同的环境,但是配置文件需要满足application-{profile}.properties的格式,profile代表对应环境的标识,加载时可通过不同命令加载不同环境。 374 | ``` 375 | application-dev.properties:开发环境 376 | application-test.properties:演示环境 377 | application-prod.properties:生产环境 378 | # 运行演示环境命令 379 | java -jar spring-boot-example-0.0.1-SNAPSHOT --spring.profiles.active=test 380 | ``` 381 | 基于现在的项目实现多环境我们需要在application.yml同级目录新建application-dev.yml、application-test.yml、application-prod.yml三个不同环境的配置文件,将不变的公有配置如druid的大部分、pagehelper分页插件以及mybatis包扫描配置放置于application.yml中,并在application.yml中配置默认采用开发环境,那么如果不带--spring.profiles.active启动应用就默认为开发环境启动,变动较大的配置如数据库的账号密码分别写入不同环境的配置文件中 382 | ``` 383 | spring: 384 | profiles: 385 | # 默认使用开发环境 386 | active: dev 387 | ``` 388 | 配置到这里我们的项目目录结构如下图所示 389 | ![src/main/java目录结构](http://upload-images.jianshu.io/upload_images/10936059-2acd0bf47c5070c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 390 | ![src/main/resources目录结构](http://upload-images.jianshu.io/upload_images/10936059-bce0976473692a93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 391 | 392 | 至此我们分别完成了Mybatis、Swagger2以及多环境的集成,接下来我们配置多环境下的logger。对于logger我们总是希望在项目研发过程中越多越好,能够给予足够的信息定位bug,项目处于演示或者上线状态时为了不让日志打印影响程序性能我们只需要警告或者错误的日志,并且需要写入文件,那么接下来就基于logback实现多环境下的日志配置 393 | ### 多环境下的日志配置 394 | 创建logback-spring.xml在application.yml的同级目录,springboot推荐使用logback-spring.xml而不是logback.xml文件,logback-spring.xml的配置内容如下所示 395 | ``` 396 | 397 | 398 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | %d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n 432 | 433 | 434 | 435 | 436 | 437 | 442 | ${logdir}/${appname}.log 443 | 444 | 445 | 446 | ${logdir}/${appname}.%d{yyyy-MM-dd}.log 447 | ${maxdays} 448 | ${maxsize} 449 | 450 | 451 | 452 | UTF-8 453 | %d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | ``` 492 | 日志配置中引用了application.yml的配置信息,主要有logdir、appname、basepackage三项,logdir是日志文件的写入地址,可以传入相对路径,appname是应用名称,引入这项是为了通过日志文件名称区分是哪个应该输出的,basepackage是包过滤配置,比如开发环境中需要打印debug级别以上的日志,但是又想使除我写的logger之外的DEBUG不打印,可过滤到本项目的包名才用DEBUG打印,此外包名使用INFO级别打印,在application.yml中新建这三项配置,也可在不同环境配置不同属性 493 | ``` 494 | #应用配置 495 | resources: 496 | # log文件写入地址 497 | logdir: logs/ 498 | # 应用名称 499 | appname: spring-boot-example 500 | # 日志打印的基础扫描包 501 | basepackage: com.spring.demo.springbootexample 502 | ``` 503 | 使用不同环境启动测试logger配置是否生效,在开发环境下将打印DEBUG级别以上的四条logger记录,在演示环境下降打印INFO级别以上的三条记录并写入文件,在生产环境下只打印ERROR级别以上的一条记录并写入文件 504 | ``` 505 | @RequestMapping("/logger") 506 | @ResponseBody 507 | public WebResult logger() { 508 | logger.trace("日志输出 {}", "trace"); 509 | logger.debug("日志输出 {}", "debug"); 510 | logger.info("日志输出 {}", "info"); 511 | logger.warn("日志输出 {}", "warn"); 512 | logger.error("日志输出 {}", "error"); 513 | return "00"; 514 | } 515 | ``` 516 | ### jwt配置 517 | 518 | pom文件添加jwt与spring security依赖 519 | 520 | ``` 521 | 522 | org.springframework.boot 523 | spring-boot-starter-security 524 | 525 | 526 | io.jsonwebtoken 527 | jjwt 528 | 0.7.0 529 | 530 | ``` 531 | 532 | 自定义配置文件中添加jwt超时时间,token生成私钥 533 | 534 | 在/intercepter/JWTAuthInterceptor中定义生成token置于response头的方法以及解析token校验的方法 535 | ``` 536 | 537 | 538 | #jwt配置 539 | jwt: 540 | expiredTime: 600000 541 | key: jwtkey 542 | 543 | ``` 544 | 545 | 546 | 配置某些url不被控制,例如 /login方法,只需要将@ignoreJWT注解用于Controller类或者UrlMapping的方法上即可 547 | JWT校验不通过采取在拦截器中抛出JWTExpiredException、JWTIllegalException来返回响应信息,异常被抛出后的处理在全局异常处理器中,声明捕获以上两个jwt相关异常即可。 548 | ``` 549 | @RestController 550 | @Slf4j 551 | public class TestController { 552 | 553 | @Value("${jwt.key}") 554 | private String JWTKey; 555 | @Value("${jwt.expiredTime}") 556 | private long expiredTime; 557 | 558 | @ignoreJWT 559 | @PostMapping("/login") 560 | public Response JWTLogin(String userName, String password){ 561 | log.info("用户登陆:{}",userName); 562 | Response response = new Response(); 563 | if ("ohaha".equals(userName) && "123456".equals(password)){ 564 | JWTUser user = JWTUser.builder().userName(userName).password(password).build(); 565 | String JWTToken = JWTUtils.createJWT(user, UUID.randomUUID().toString(), user.getUserName(), JWTKey, expiredTime); 566 | response.setData(JWTToken); 567 | }else { 568 | response.fail("登陆失败"); 569 | } 570 | return response; 571 | } 572 | } 573 | ``` 574 | 575 | ### 常用配置 576 | ##### 加载自定义配置 577 | ``` 578 | @Component 579 | @PropertySource(value = {"classpath:application.yml"}, encoding = "utf-8") 580 | public class Config { 581 | 582 | @Value("${resources.midpHost}") 583 | private String midpHost; 584 | 585 | public String getMidpHost() { 586 | return midpHost; 587 | } 588 | } 589 | ``` 590 | ##### 全局异常处理器 591 | ``` 592 | @ControllerAdvice 593 | public class GlobalExceptionHandler { 594 | 595 | Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class); 596 | 597 | @ExceptionHandler(value = Exception.class) 598 | @ResponseBody 599 | public WebResult exceptionHandle(HttpServletRequest req, Exception ex) { 600 | ex.printStackTrace(); 601 | logger.error("未知异常", ex); 602 | return WebResult.error(ERRORDetail.RC_0401001); 603 | } 604 | } 605 | ``` 606 | ### 示例工程开源地址 607 | [github](https://github.com/calebman/spring-boot-example) -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.spring.demo 7 | spring-boot-example 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-example 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-security 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | true 37 | 38 | 39 | 40 | io.jsonwebtoken 41 | jjwt 42 | 0.9.1 43 | 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-web 49 | 50 | 51 | 52 | org.mybatis.spring.boot 53 | mybatis-spring-boot-starter 54 | 1.3.1 55 | 56 | 57 | 58 | mysql 59 | mysql-connector-java 60 | runtime 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-test 66 | test 67 | 68 | 69 | 70 | com.github.pagehelper 71 | pagehelper-spring-boot-starter 72 | 1.2.3 73 | 74 | 75 | 76 | com.alibaba 77 | druid-spring-boot-starter 78 | 1.1.1 79 | 80 | 81 | 82 | com.alibaba 83 | fastjson 84 | 1.2.31 85 | 86 | 87 | 88 | io.springfox 89 | springfox-swagger2 90 | 2.5.0 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-maven-plugin 100 | 101 | 102 | 103 | org.mybatis.generator 104 | mybatis-generator-maven-plugin 105 | 1.3.2 106 | 107 | 108 | 109 | ${basedir}/src/main/resources/generator/generatorConfig.xml 110 | 111 | true 112 | true 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/SpringBootExampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 7 | 8 | @SpringBootApplication 9 | @EnableSwagger2 10 | @MapperScan("com.spring.demo.springbootexample.mybatis.mapper") 11 | public class SpringBootExampleApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SpringBootExampleApplication.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/annotation/ignoreJWT.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 在使用该注解的方法上不进行jwt校验 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(value={ElementType.METHOD, ElementType.TYPE}) 13 | public @interface ignoreJWT { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/beans/Product.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.beans; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | // 6 | @ApiModel(value = "产品信息") 7 | public class Product { 8 | 9 | @ApiModelProperty(required = true, name = "name", value = "产品名称", dataType = "query") 10 | private String name; 11 | @ApiModelProperty(name = "type", value = "产品类型", dataType = "query") 12 | private String type; 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | 22 | public String getType() { 23 | return type; 24 | } 25 | 26 | public void setType(String type) { 27 | this.type = type; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "Product{" + 33 | "name='" + name + '\'' + 34 | ", type='" + type + '\'' + 35 | '}'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/beans/entity/JWTUser.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.beans.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | @Data 8 | @Builder 9 | @ToString 10 | public class JWTUser { 11 | private String userName; 12 | private String password; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/beans/msg/Request.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.beans.msg; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * 通用请求体 9 | * @param 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class Request { 15 | private T data; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/beans/msg/Response.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.beans.msg; 2 | 3 | import com.spring.demo.springbootexample.constants.ReqResConsts; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * 通用响应体 10 | * @param 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class Response { 16 | private T data; 17 | private String code; 18 | private String msg; 19 | 20 | public Response success(String msg){ 21 | this.code = ReqResConsts.SUCCESS_CODE; 22 | this.msg = msg; 23 | return this; 24 | } 25 | public Response success(){ 26 | return success(null); 27 | } 28 | 29 | public Response fail(String msg){ 30 | this.code = ReqResConsts.FAIL_CODE; 31 | this.msg = msg; 32 | return this; 33 | } 34 | public Responsefail(){ 35 | return fail(null); 36 | } 37 | public Response msg(String code, String msg){ 38 | this.code = code; 39 | this.msg = msg; 40 | return this; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.PropertySource; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @PropertySource(value = {"classpath:application.yml"}, encoding = "utf-8") 9 | public class Config { 10 | 11 | @Value("${resources.midpHost}") 12 | private String midpHost; 13 | 14 | @Value("${jwt.SECRET}") 15 | private String secret; 16 | 17 | @Value("${jwt.EXPIRATIONTIME}") 18 | private long expirationtime; 19 | 20 | public String getSecret() { 21 | return secret; 22 | } 23 | 24 | public long getExpirationtime() { 25 | return expirationtime; 26 | } 27 | 28 | public String getMidpHost() { 29 | return midpHost; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/config/InterceptorConfiger.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.config; 2 | 3 | import com.spring.demo.springbootexample.intercepter.JWTAuthInterceptor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.HandlerInterceptor; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | @Configuration 11 | public class InterceptorConfiger implements WebMvcConfigurer { 12 | @Override 13 | public void addInterceptors(InterceptorRegistry registry) { 14 | registry.addInterceptor(getJWTAuthInterceptor()).excludePathPatterns("/error","/user/login"); 15 | } 16 | 17 | @Bean 18 | public HandlerInterceptor getJWTAuthInterceptor(){ 19 | return new JWTAuthInterceptor(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.service.ApiInfo; 7 | import springfox.documentation.service.Contact; 8 | import springfox.documentation.spi.DocumentationType; 9 | import springfox.documentation.spring.web.plugins.Docket; 10 | 11 | @Configuration 12 | public class SwaggerConfig { 13 | // 接口版本号 14 | private final String version = "1.0"; 15 | // 接口大标题 16 | private final String title = "SpringBoot示例工程"; 17 | // 具体的描述 18 | private final String description = "API文档自动生成示例"; 19 | // 服务说明url 20 | private final String termsOfServiceUrl = "http://www.kingeid.com"; 21 | // licence 22 | private final String license = "MIT"; 23 | // licnce url 24 | private final String licenseUrl = "https://mit-license.org/"; 25 | // 接口作者联系方式 26 | private final Contact contact = new Contact("calebman", "https://github.com/calebman", "chenjianhui0428@gmail.com"); 27 | 28 | @Bean 29 | public Docket buildDocket() { 30 | return new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInf()) 31 | .select().build(); 32 | } 33 | 34 | private ApiInfo buildApiInf() { 35 | return new ApiInfoBuilder().title(title).termsOfServiceUrl(termsOfServiceUrl).description(description) 36 | .version(version).license(license).licenseUrl(licenseUrl).contact(contact).build(); 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/constants/ErrCodeEnum.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.constants; 2 | 3 | public enum ErrCodeEnum { 4 | JWT_AUTH_FAIL("J0000001", "JWT校验失败"), 5 | JWT_ILL_ERROR("J0000002", "JWT参数非法"), 6 | SYS_ERROR("S0000001", "系统异常"), 7 | LOGIN_FAIL("U0000001", "用户名或密码不正确"), 8 | NO_USER("U000002", "用户不存在"), 9 | ; 10 | 11 | ErrCodeEnum(String code, String msg){ 12 | this.code = code; 13 | this.msg = msg; 14 | } 15 | 16 | public String code; 17 | public String msg; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/constants/JWTConsts.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.constants; 2 | 3 | public class JWTConsts { 4 | /** 5 | * demo claim key值 6 | */ 7 | public static final String NAME = "user_name"; 8 | 9 | public static final String PASSWORD = "password"; 10 | 11 | public static final String JWT_HEADER_KEY = "JWT-Token"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/constants/ReqResConsts.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.constants; 2 | 3 | public class ReqResConsts { 4 | public static final String SUCCESS_CODE = "000000"; 5 | public static final String SUCCESS_MSG = "成功"; 6 | 7 | public static final String FAIL_CODE = "999999"; 8 | public static final String FAIL_MSG = "失败"; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/controller/DocController.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.controller; 2 | 3 | 4 | import com.spring.demo.springbootexample.beans.Product; 5 | import com.spring.demo.springbootexample.config.Config; 6 | import com.spring.demo.springbootexample.result.ERRORDetail; 7 | import com.spring.demo.springbootexample.result.Pagination; 8 | import com.spring.demo.springbootexample.result.WebResult; 9 | import io.swagger.annotations.Api; 10 | import io.swagger.annotations.ApiImplicitParam; 11 | import io.swagger.annotations.ApiImplicitParams; 12 | import io.swagger.annotations.ApiOperation; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.util.Map; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | @Controller 23 | @RequestMapping("/v1/product") 24 | @Api(value = "DocController", tags = {"restful api示例"}) 25 | public class DocController { 26 | 27 | @Autowired 28 | Config config; 29 | 30 | static Map products = new ConcurrentHashMap(); 31 | 32 | Logger logger = LoggerFactory.getLogger(DocController.class); 33 | 34 | @RequestMapping(value = "", method = RequestMethod.POST) 35 | @ResponseBody 36 | @ApiOperation(value = "添加产品", httpMethod = "POST", produces = "application/json") 37 | public WebResult add(@ModelAttribute Product product) { 38 | logger.debug("添加产品接收信息=>{}", product); 39 | if (product == null) { 40 | logger.debug("产品信息不能为空"); 41 | return WebResult.error(ERRORDetail.RC_0101001); 42 | } 43 | if (product.getName() == null || "".equals(product.getName())) { 44 | logger.debug("产品名称不能为空"); 45 | return WebResult.error(ERRORDetail.RC_0101002); 46 | } 47 | products.put(products.size(), product); 48 | return WebResult.success(); 49 | } 50 | 51 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) 52 | @ResponseBody 53 | @ApiOperation(value = "移除指定产品", httpMethod = "DELETE", produces = "application/json") 54 | @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "产品ID", required = true, paramType = "path")}) 55 | public WebResult delete(@PathVariable("id") Integer id) { 56 | logger.debug("移除指定产品接收产品id=>%d", id); 57 | if (id == null || "".equals(id)) { 58 | logger.debug("产品id不能为空"); 59 | return WebResult.error(ERRORDetail.RC_0101001); 60 | } 61 | products.remove(id); 62 | return WebResult.success(); 63 | } 64 | 65 | @RequestMapping(value = "/{id}", method = RequestMethod.PUT) 66 | @ResponseBody 67 | @ApiOperation(value = "修改指定产品", httpMethod = "PUT", produces = "application/json") 68 | @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "产品ID", required = true, paramType = "path")}) 69 | public WebResult update(@PathVariable("id") Integer id, @ModelAttribute Product product) { 70 | logger.debug("修改指定产品接收产品id与产品信息=>%d,{}", id, product); 71 | if (id == null || "".equals(id)) { 72 | logger.debug("产品id不能为空"); 73 | return WebResult.error(ERRORDetail.RC_0101001); 74 | } 75 | products.put(id, product); 76 | return WebResult.success(); 77 | } 78 | 79 | @RequestMapping(value = "", method = RequestMethod.GET) 80 | @ResponseBody 81 | @ApiOperation(value = "获取以及筛选产品", httpMethod = "GET", produces = "application/json") 82 | @ApiImplicitParams({@ApiImplicitParam(name = "name", value = "根据产品名称筛选", required = false, paramType = "query"), 83 | @ApiImplicitParam(name = "type", value = "根据产品类型筛选", required = false, paramType = "query", dataType = "int")}) 84 | public WebResult searchAll(@ModelAttribute Pagination pagination, String name, String type) { 85 | logger.debug("获取以及筛选产品接收分页、产品名称、产品类型=>{},{},{}", pagination, name, type); 86 | if (!pagination.isRequire()) { 87 | logger.debug("分页参数不能为空"); 88 | return WebResult.error(ERRORDetail.RC_0101003); 89 | } 90 | return WebResult.success(products); 91 | } 92 | 93 | @RequestMapping(value = "/{id}", method = RequestMethod.GET) 94 | @ResponseBody 95 | @ApiOperation(value = "获取指定产品", httpMethod = "GET", produces = "application/json") 96 | @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "产品ID", required = true, paramType = "path")}) 97 | public WebResult search(@PathVariable("id") Integer id) { 98 | logger.debug("获取指定产品接收产品id=>%d", id); 99 | if (id == null || "".equals(id)) { 100 | logger.debug("产品id不能为空"); 101 | return WebResult.error(ERRORDetail.RC_0101001); 102 | } 103 | return WebResult.success(products.get(id)); 104 | } 105 | 106 | @RequestMapping("/logger") 107 | @ResponseBody 108 | public WebResult logger() { 109 | logger.trace("日志输出 {},{}", "trace", config.getMidpHost()); 110 | logger.debug("日志输出 {},{}", "debug", config.getMidpHost()); 111 | logger.info("日志输出 {},{}", "info", config.getMidpHost()); 112 | logger.warn("日志输出 {},{}", "warn", config.getMidpHost()); 113 | logger.error("日志输出 {},{}", "error", config.getMidpHost()); 114 | return WebResult.success(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/controller/JwtTestController.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.controller; 2 | 3 | import com.spring.demo.springbootexample.jwt.JsonWebTokenUtility; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | @Controller 13 | public class JwtTestController { 14 | 15 | @Autowired 16 | private JsonWebTokenUtility jwt; 17 | 18 | @RequestMapping(value = "/testJwt") 19 | @ResponseBody 20 | public String login(HttpServletResponse response){ 21 | jwt.addToken(response,"ohaha~"); 22 | 23 | System.out.println("login controller"); 24 | return "ohaha"; 25 | } 26 | 27 | @RequestMapping(value = "/testJwtdecrypt") 28 | @ResponseBody 29 | public String dec(HttpServletRequest request){ 30 | 31 | jwt.getAuthentication(request); 32 | return "ohaha"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.controller; 2 | 3 | import com.spring.demo.springbootexample.annotation.ignoreJWT; 4 | import com.spring.demo.springbootexample.beans.entity.JWTUser; 5 | import com.spring.demo.springbootexample.beans.msg.Response; 6 | import com.spring.demo.springbootexample.utils.JWTUtils; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.UUID; 13 | 14 | @RestController 15 | @Slf4j 16 | public class TestController { 17 | 18 | @Value("${jwt.key}") 19 | private String JWTKey; 20 | @Value("${jwt.expiredTime}") 21 | private long expiredTime; 22 | 23 | @ignoreJWT 24 | @PostMapping("/login") 25 | public Response JWTLogin(String userName, String password){ 26 | log.info("用户登陆:{}",userName); 27 | Response response = new Response(); 28 | if ("ohaha".equals(userName) && "123456".equals(password)){ 29 | JWTUser user = JWTUser.builder().userName(userName).password(password).build(); 30 | String JWTToken = JWTUtils.createJWT(user, UUID.randomUUID().toString(), user.getUserName(), JWTKey, expiredTime); 31 | response.setData(JWTToken); 32 | }else { 33 | response.fail("登陆失败"); 34 | } 35 | return response; 36 | } 37 | @PostMapping("/test") 38 | public Response test(){ 39 | log.info("test controller"); 40 | Response response = new Response(); 41 | response.success(); 42 | return response; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/controller/global/GlobalBasicErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.controller.global; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.spring.demo.springbootexample.constants.ErrCodeEnum; 5 | import org.springframework.boot.autoconfigure.web.ServerProperties; 6 | import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; 7 | import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.util.Map; 16 | 17 | @RestController 18 | @RequestMapping("${server.error.path:${error.path:/error}}") 19 | public class GlobalBasicErrorHandler extends BasicErrorController { 20 | 21 | public GlobalBasicErrorHandler(ServerProperties serverProperties) { 22 | super(new DefaultErrorAttributes(), serverProperties.getError()); 23 | } 24 | @Override 25 | protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map model) { 26 | return super.resolveErrorView(request,response,status,model); 27 | } 28 | @Override 29 | protected Map getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { 30 | 31 | Map respMap = Maps.newHashMap(); 32 | respMap.put("code",ErrCodeEnum.SYS_ERROR.code); 33 | respMap.put("msg",ErrCodeEnum.SYS_ERROR.msg); 34 | return respMap; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/controller/global/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.controller.global; 2 | 3 | 4 | import com.spring.demo.springbootexample.beans.msg.Response; 5 | import com.spring.demo.springbootexample.constants.ErrCodeEnum; 6 | import com.spring.demo.springbootexample.exception.JWTExpiredException; 7 | import com.spring.demo.springbootexample.exception.JWTIllegalException; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * 全局异常处理 17 | */ 18 | @ControllerAdvice 19 | @ResponseBody 20 | @Slf4j 21 | public class GlobalExceptionHandler { 22 | 23 | @ExceptionHandler(Exception.class) 24 | public Response defaultExceptionHandler(HttpServletResponse response, Exception exception){ 25 | response.setStatus(500); 26 | log.error("exception 异常"); 27 | Response res = new Response(); 28 | return res.msg(ErrCodeEnum.SYS_ERROR.code, ErrCodeEnum.SYS_ERROR.msg); 29 | } 30 | 31 | @ExceptionHandler(JWTIllegalException.class) 32 | public Response JWTIllegalExceptionHandler(HttpServletResponse response, Exception exception){ 33 | response.setStatus(500); 34 | log.error("demo 非法 异常"); 35 | Response res = new Response(); 36 | return res.msg(ErrCodeEnum.JWT_ILL_ERROR.code, ErrCodeEnum.JWT_ILL_ERROR.msg); 37 | } 38 | @ExceptionHandler(JWTExpiredException.class) 39 | public Response JWTExpiredExceptionHandler(HttpServletResponse response, Exception exception){ 40 | response.setStatus(500); 41 | log.error("demo 超时 异常"); 42 | Response res = new Response(); 43 | return res.msg(ErrCodeEnum.JWT_AUTH_FAIL.code, ErrCodeEnum.JWT_AUTH_FAIL.msg); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/exception/JWTExpiredException.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.exception; 2 | 3 | /** 4 | * JWT过期异常 5 | */ 6 | public class JWTExpiredException extends Exception{ 7 | public JWTExpiredException(String msg){ 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/exception/JWTIllegalException.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.exception; 2 | 3 | /** 4 | * JWT非法异常 5 | */ 6 | public class JWTIllegalException extends Exception{ 7 | public JWTIllegalException(String msg){ 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/intercepter/JWTAuthInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.intercepter; 2 | 3 | 4 | import com.spring.demo.springbootexample.annotation.ignoreJWT; 5 | import com.spring.demo.springbootexample.beans.entity.JWTUser; 6 | import com.spring.demo.springbootexample.constants.JWTConsts; 7 | import com.spring.demo.springbootexample.exception.JWTIllegalException; 8 | import com.spring.demo.springbootexample.utils.JWTUtils; 9 | import io.jsonwebtoken.ExpiredJwtException; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.util.StringUtils; 13 | import org.springframework.web.method.HandlerMethod; 14 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | 19 | @Slf4j 20 | public class JWTAuthInterceptor extends HandlerInterceptorAdapter { 21 | @Value("${jwt.key}") 22 | private String JWTKey; 23 | 24 | public JWTAuthInterceptor() { 25 | super(); 26 | } 27 | 28 | @Override 29 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 30 | log.info("开始校验JWT"); 31 | HandlerMethod handlerMethod = (HandlerMethod) handler; 32 | ignoreJWT ignoreAnnotation = handlerMethod.getBeanType().getAnnotation(ignoreJWT.class); 33 | if (ignoreAnnotation == null){ 34 | ignoreAnnotation = handlerMethod.getMethodAnnotation(ignoreJWT.class); 35 | } 36 | if (ignoreAnnotation != null){ 37 | log.info("当前方法不进行jwt校验"); 38 | return super.preHandle(request, response, handler); 39 | } 40 | String jwtToken = request.getHeader(JWTConsts.JWT_HEADER_KEY); 41 | if (!StringUtils.hasText(jwtToken)){ 42 | log.error("no JWT Token"); 43 | throw new JWTIllegalException("no jwt token"); 44 | } 45 | try{ 46 | JWTUser userInfo = JWTUtils.parseJWTInfo(jwtToken, JWTKey); 47 | log.info("用户信息:{}", userInfo); 48 | request.setAttribute("userInfo", userInfo); 49 | }catch (ExpiredJwtException e){ 50 | log.error("JWT Token 失效"); 51 | }catch (Exception e){ 52 | throw new RuntimeException("JWT 异常"); 53 | } 54 | return super.preHandle(request, response, handler); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/jwt/JsonWebTokenUtility.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.jwt; 2 | 3 | 4 | import com.spring.demo.springbootexample.config.Config; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.SignatureAlgorithm; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.util.Date; 15 | 16 | import static java.util.Collections.emptyList; 17 | 18 | @Component 19 | public class JsonWebTokenUtility { 20 | 21 | @Autowired 22 | Config config; 23 | 24 | String HEADER_NAME = "Authorization"; 25 | 26 | public void addToken(HttpServletResponse res, String userId){ 27 | 28 | 29 | String JWT = Jwts.builder() 30 | .setSubject(userId) 31 | .setExpiration(new Date(System.currentTimeMillis() + config.getExpirationtime())) 32 | .signWith(SignatureAlgorithm.HS512, config.getSecret()) 33 | .compact(); 34 | res.addHeader(HEADER_NAME, JWT); 35 | } 36 | 37 | public Authentication getAuthentication(HttpServletRequest request) { 38 | String token = request.getHeader(HEADER_NAME); 39 | if (token != null) { 40 | // parse the token. 41 | String user = Jwts.parser() 42 | .setSigningKey(config.getSecret()) 43 | .parseClaimsJws(token) 44 | .getBody() 45 | .getSubject(); 46 | 47 | return user != null ? 48 | new UsernamePasswordAuthenticationToken(user, null, emptyList()) : 49 | null; 50 | } 51 | return null; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/result/ERRORDetail.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.result; 2 | 3 | 4 | /** 5 | * 平台错误描述定义,可根据索引查询具体错误描述。 6 | */ 7 | public enum ERRORDetail { 8 | 9 | RC_0000000("0000000", "成功"), 10 | 11 | //参数错误【参数为空】 12 | RC_0101001("0101001", "id为空"), 13 | RC_0101002("0101002", "name为空"), 14 | RC_0101003("0101003", "分页参数为空"), 15 | 16 | //参数错误【格式错误】 17 | RC_0102001("0102001", "biz_sequence_id格式错误"), 18 | 19 | //参数错误【参数无效】 20 | RC_0103001("0103001", "id_type无效"), 21 | 22 | //请求错误 23 | RC_0201004("0201004", "version错误"), 24 | RC_0201005("0201005", "请求地址错误"), 25 | 26 | //认证错误【状态错误】 27 | RC_0301001("0301001", "证书已经注销或未注册"), 28 | 29 | //认证错误【验证失败】 30 | RC_0302001("0302001", "eID签名错误"), 31 | 32 | //操作失败 33 | RC_0303001("0303001", "sms用户处理失败"), 34 | RC_0303002("0303002", "用户拒绝处理"), 35 | 36 | //系统错误 37 | RC_0401001("0401001", "服务异常"), 38 | RC_0401002("0401002", "无法返回"), 39 | RC_0401003("0401003", "返回错误"), 40 | RC_0401006("0401006", "应用服务器缓存异常"), 41 | RC_0401007("0401007", "应用服务器超时"), 42 | 43 | RC_6666666("0500000","异常,没有找到合适的结果描述"); 44 | 45 | private String index; 46 | private String meaning; 47 | 48 | ERRORDetail(String index, String meaning) { 49 | this.index = index; 50 | this.setMeaning(meaning); 51 | } 52 | 53 | public String getIndex() { 54 | return this.index; 55 | } 56 | 57 | public void setIndex(String index) { 58 | this.index = index; 59 | } 60 | 61 | public String getMeaning() { 62 | return meaning; 63 | } 64 | 65 | public void setMeaning(String meaning) { 66 | this.meaning = meaning; 67 | } 68 | 69 | public String getResultCode(){ 70 | return this.index.substring(0,2); 71 | } 72 | 73 | public String getDetails(){ 74 | return this.index+"("+this.meaning+")"; 75 | } 76 | 77 | public static ERRORDetail getEnum(String index) { 78 | for (ERRORDetail st : ERRORDetail.values()) { 79 | if (st.index.equals(index)) { 80 | return st; 81 | } 82 | } 83 | return RC_6666666; 84 | } 85 | 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/result/Pagination.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.result; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | 6 | @ApiModel(value = "分页信息") 7 | public class Pagination { 8 | 9 | @ApiModelProperty(required = true, name = "pageCurrent", value = "当前选择页", dataType = "query") 10 | private String pageCurrent; 11 | @ApiModelProperty(required = true, name = "pageSize", value = "每页条数", dataType = "query") 12 | private String pageSize; 13 | 14 | public String getPageCurrent() { 15 | return pageCurrent; 16 | } 17 | 18 | public void setPageCurrent(String pageCurrent) { 19 | this.pageCurrent = pageCurrent; 20 | } 21 | 22 | public String getPageSize() { 23 | return pageSize; 24 | } 25 | 26 | public void setPageSize(String pageSize) { 27 | this.pageSize = pageSize; 28 | } 29 | 30 | public boolean isRequire() { 31 | if (this == null) { 32 | return false; 33 | } 34 | if (this.getPageCurrent() == null || "".equals(this.getPageCurrent())) { 35 | return false; 36 | } 37 | return this.getPageSize() != null && !"".equals(this.getPageSize()); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/result/WebResult.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.result; 2 | 3 | public class WebResult { 4 | 5 | private String errCode; 6 | private String errMsg; 7 | private Object data; 8 | 9 | public String getErrCode() { 10 | return errCode; 11 | } 12 | 13 | public void setErrCode(String errCode) { 14 | this.errCode = errCode; 15 | } 16 | 17 | public String getErrMsg() { 18 | return errMsg; 19 | } 20 | 21 | public void setErrMsg(String errMsg) { 22 | this.errMsg = errMsg; 23 | } 24 | 25 | public Object getData() { 26 | return data; 27 | } 28 | 29 | public void setData(Object data) { 30 | this.data = data; 31 | } 32 | 33 | public static WebResult error(ERRORDetail errorDetail) { 34 | WebResult result = new WebResult(); 35 | result.setErrCode(errorDetail.getResultCode()); 36 | result.setErrMsg(errorDetail.getDetails()); 37 | result.setData(null); 38 | return result; 39 | } 40 | 41 | public static WebResult success() { 42 | return success(null); 43 | } 44 | 45 | public static WebResult success(Object data) { 46 | WebResult result = new WebResult(); 47 | result.setErrCode(ERRORDetail.RC_0000000.getResultCode()); 48 | result.setErrMsg(ERRORDetail.RC_0000000.getDetails()); 49 | result.setData(data); 50 | return result; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/security/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | 10 | @Configuration 11 | @EnableWebSecurity 12 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 13 | 14 | @Override 15 | protected void configure(HttpSecurity http) throws Exception { 16 | http 17 | .authorizeRequests() 18 | .antMatchers("/testJwt","/testJwtdecrypt").permitAll() 19 | .anyRequest().authenticated() 20 | .and() 21 | .formLogin() 22 | .loginPage("/login") 23 | .permitAll() 24 | .and() 25 | .logout() 26 | .permitAll(); 27 | } 28 | 29 | @Autowired 30 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 31 | auth 32 | .inMemoryAuthentication() 33 | .withUser("user").password("password").roles("USER"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/spring/demo/springbootexample/utils/JWTUtils.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample.utils; 2 | 3 | 4 | import com.spring.demo.springbootexample.beans.entity.JWTUser; 5 | import com.spring.demo.springbootexample.constants.JWTConsts; 6 | import io.jsonwebtoken.*; 7 | import org.apache.tomcat.util.codec.binary.Base64; 8 | 9 | import javax.crypto.SecretKey; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import java.util.Date; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class JWTUtils { 16 | 17 | public static String createJWT(JWTUser user, String jwtId, String subject, String secret, long ttlMillis){ 18 | Map claims = new HashMap<>(); 19 | claims.put(JWTConsts.NAME, user.getUserName()); 20 | claims.put(JWTConsts.PASSWORD, user.getPassword()); 21 | return createJWT(claims, jwtId, subject, secret, ttlMillis); 22 | } 23 | 24 | public static String createJWT(Map claims, String jwtId, String subject,String secret, long ttlMillis){ 25 | // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 26 | SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 27 | long nowMillis = System.currentTimeMillis(); 28 | Date now = new Date(nowMillis); 29 | // 生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 30 | SecretKey key = getKey(secret); 31 | // 下面就是在为payload添加各种标准声明和私有声明了 32 | // 这里其实就是new一个JwtBuilder,设置jwt的body 33 | JwtBuilder builder = Jwts.builder() 34 | // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 35 | .setClaims(claims) 36 | // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 37 | .setId(jwtId) 38 | // iat: jwt的签发时间 39 | .setIssuedAt(now) 40 | // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 41 | .setSubject(subject) 42 | // 设置签名使用的签名算法和签名使用的秘钥 43 | .signWith(signatureAlgorithm, key); 44 | if (ttlMillis >= 0) { 45 | long expMillis = nowMillis + ttlMillis; 46 | Date exp = new Date(expMillis); 47 | // 设置过期时间 48 | builder.setExpiration(exp); 49 | } 50 | // 就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt 51 | return builder.compact(); 52 | } 53 | public static Jws parseJWT(String jwt, String secret) throws Exception { 54 | // 签名秘钥,和生成的签名的秘钥一模一样 55 | SecretKey key = getKey(secret); 56 | // 得到DefaultJwtParser 57 | Jws jwsClaims = Jwts.parser() 58 | //设置签名的秘钥 59 | .setSigningKey(key) 60 | //设置需要解析的jwt 61 | .parseClaimsJws(jwt); 62 | return jwsClaims; 63 | } 64 | 65 | public static JWTUser parseJWTInfo(String jwt, String secret) throws Exception { 66 | 67 | Claims claims = parseJWT(jwt,secret).getBody(); 68 | JWTUser jwtUser = JWTUser.builder().userName(claims.get(JWTConsts.NAME,String.class)).build(); 69 | return jwtUser; 70 | } 71 | 72 | public static SecretKey getKey(String secret){ 73 | 74 | byte[] encodedKey = Base64.decodeBase64(secret); 75 | // 根据给定的字节数组使用AES加密算法构造一个密钥,使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。(后面的文章中马上回推出讲解Java加密和解密的一些算法) 76 | return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | datasource: 6 | url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8 7 | username: kingeid 8 | password: 123456 9 | 10 | #自定义配置 11 | resources: 12 | midpHost: 1.1.1.1 13 | 14 | #jwt配置 15 | jwt: 16 | EXPIRATIONTIME: 1000 17 | SECRET: OHAHAHAHA 18 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 18080 3 | 4 | spring: 5 | datasource: 6 | url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8 7 | username: kingeid 8 | password: kingeid 9 | 10 | #自定义配置 11 | resources: 12 | logdir: logs/ 13 | midpHost: 1.1.1.1 14 | 15 | #jwt配置 16 | jwt: 17 | EXPIRATIONTIME: 1000*60*60*24*1 18 | SECRET: OHAHAHAHA 19 | HEADER_NAME: Authorization -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 3 | 4 | spring: 5 | datasource: 6 | url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8 7 | username: kingeid 8 | password: 123456 9 | 10 | #自定义配置 11 | resources: 12 | logdir: logs/ 13 | midpHost: 1.1.1.1 -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | # 如果存在多个数据源,监控的时候可以通过名字来区分开来 4 | name: mysql 5 | # 使用druid数据源 6 | type: com.alibaba.druid.pool.DruidDataSource 7 | # 扩展插件 8 | # 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall 9 | filters: stat 10 | # 最大连接池数量 11 | maxActive: 20 12 | # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 13 | initialSize: 1 14 | # 获取连接时最大等待时间,单位毫秒 15 | maxWait: 60000 16 | # 最小连接池数量 17 | minIdle: 1 18 | timeBetweenEvictionRunsMillis: 60000 19 | # 连接保持空闲而不被驱逐的最长时间 20 | minEvictableIdleTimeMillis: 300000 21 | # 用来检测连接是否有效的sql,要求是一个查询语句 22 | # 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用 23 | validationQuery: select count(1) from 'table' 24 | # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 25 | testWhileIdle: true 26 | # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 27 | testOnBorrow: false 28 | # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 29 | testOnReturn: false 30 | # 是否缓存preparedStatement,即PSCache 31 | poolPreparedStatements: false 32 | # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true 33 | maxOpenPreparedStatements: -1 34 | profiles: 35 | # 默认使用开发环境 36 | active: dev 37 | mybatis: 38 | # mapper.xml包地址 39 | mapper-locations: classpath:mapper/*.xml 40 | # pojo生成包地址 41 | type-aliases-package: com.spring.demo.springbootexample.mybatis.po 42 | 43 | #pagehelper分页插件 44 | pagehelper: 45 | # 数据库的方言 46 | helperDialect: mysql 47 | # 启用合理化,如果pageNum < 1会查询第一页,如果pageNum > pages会查询最后一页 48 | reasonable: true 49 | 50 | resources: 51 | #应用名称 52 | appname: spring-boot-example 53 | #日志打印的基础扫描包 54 | basepackage: com.spring.demo.springbootexample 55 | 56 | 57 | #jwt配置 58 | jwt: 59 | expiredTime: 600000 60 | key: jwtkey -------------------------------------------------------------------------------- /src/main/resources/generator/generator.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebman/spring-boot-example/415b7fdff1aea67da8c3033bf5d298a0bf4f3ebb/src/main/resources/generator/generator.properties -------------------------------------------------------------------------------- /src/main/resources/generator/generatorConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | %d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n 37 | 38 | 39 | 40 | 41 | 42 | 47 | ${logdir}/${appname}.log 48 | 49 | 50 | 51 | ${logdir}/${appname}.%d{yyyy-MM-dd}.log 52 | ${maxdays} 53 | ${maxsize} 54 | 55 | 56 | 57 | UTF-8 58 | %d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/test/java/com/spring/demo/springbootexample/SpringBootExampleApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.spring.demo.springbootexample; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringBootExampleApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------