• java c++通用DES/AES对称加密算法(包含源代码)

    java c++通用DES加密算法(包含源代码),本来觉得DES、AES这种流行加密算法,使用起来应该很简单。但研究后发现有两个变数:1分块的方式。加密是逐块进行的。2.padding的方式。当数据的位数不及块的大小时,需要填充。<h2>一说明</h2> 本来觉得DES、AES这种流行加密算法,使用起来应该很简单。但研究后发现有两个变数: <ul> <li>分块的方式。加密是逐块进行的。分块方法有:CBC、ECB、CFB……</li> <li>padding的方式。当数据的位数不及块的大小时,需要填充。填充方式有:NoPadding、PKCS5Padding……</li> </ul> 如果加解密端采用不同的分块方式或padding方式,即使都是采用DES/AES算法,同样无法解密成功。上次需要C端和Java端进行密文传输,就跪在这一点上(那时候没时间研究)。<br /> 参考文章:<a href="http://my.oschina.net/u/267094/blog/174035" rel="external nofollow" target="_blank">Java AES算法和openssl配对</a> ,主要通过其了解openssl里比较高级的EVP系列API(其默认padding和java一样都是PKCS5Padding),节约了搜索时间。<br /> 贴代码了,以下代码测试通过了。Java和C++均可以正确解密对方的密文。<br /> 约定:分块和padding采用Java默认的 ECB + PKCS5Padding。 <h2>二 DES加解密</h2> <h3>Java端DES加解密</h3> <pre> <code class="language-java">import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class DESUtil { /** 默认算法格式 **/ static String default_transformation = "DES/ECB/PKCS5Padding"; /** * 根据key获取加密器 * * @param keyData * key 8byte * @return 加密器 * @throws Exception * 异常 */ private static Cipher getEncryptCipher(byte[] keyData) throws Exception { if (keyData.length != 8) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "DES"); // 指定分块ECB模式,填充PKCS5Padding模式 Cipher encryptCipher = Cipher.getInstance(default_transformation); // 初始化加密的容器 encryptCipher.init(Cipher.ENCRYPT_MODE, key); return encryptCipher; } /** * 根据key获取解码器 * * @return 解码器 * @throws Exception * 异常 */ private static Cipher getDecryptCipher(byte[] keyData) throws Exception { if (keyData.length != 8) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "DES"); Cipher decryptCipher = Cipher.getInstance(default_transformation); // 初始化解密的容器 decryptCipher.init(Cipher.DECRYPT_MODE, key); return decryptCipher; } /** * DES加密 * * @param data * 待加密数据 * @param keyData * key值 * @return 加密后的数据 * @throws Exception * 异常 */ public static byte[] encrypt(byte[] data, byte[] keyData) throws Exception { return getEncryptCipher(keyData).doFinal(data); } /** * DES解密 * * @param data * 加密后的数据 * * @param keyData * key值 * @return 解密数据 * @throws Exception * 异常 */ public static byte[] decrypt(byte[] data, byte[] keyData) throws Exception { return getDecryptCipher(keyData).doFinal(data); } /** * 测试 * * @param args */ public static void main(String[] args) { try { byte[] data = "测试123456".getBytes(); byte[] keyData = "12345678".getBytes(); System.out.println("原文:" + new String(data)); byte[] enData = encrypt(data, keyData); System.out.println("加密后:" + new String(enData)); byte[] deData = decrypt(enData, keyData); System.out.println("解密后:" + new String(deData)); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> <strong><span style="color:#27ae60">说明:Java必须指定填充模式和padding模式。DES/ECB/PKCS5Padding,好与C++同步</span></strong><br /> 测试执行结果: <pre> <code class="language-html">原文:测试123456 加密后:�a���O���?I�]�Y 解密后:测试123456</code></pre> <h3>C++端DES加解密</h3> c++DES加密代码 <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string desEncrypt(const std::string& source, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_EncryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[source.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("hellodes", 8);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("src.txt", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string source(buf, bytes); // 二进制数据     // 加密     std::string enc = desEncrypt(source, key);     std::cout << "desEncrypt:" << source.length() << "->" << enc.length() << std::endl;     // 输出到文件     fp =  fopen("enc.bin", "wb");     fwrite(enc.data(), 1, enc.length(), fp);     fclose(fp); }</code></pre> c++DES解密 <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string desDecrypt(const std::string& ciphertext, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_DecryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[ciphertext.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("hellodes", 8);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("enc.bin", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string data(buf, bytes); // 二进制数据     // 加密     std::string dec = desDecrypt(data, key);     std::cout << "desDecrypt:" << data.length() << "->" << dec.length() << std::endl;     // 输出到文件     fp =  fopen("dec.txt", "wb");     fwrite(dec.data(), 1, dec.length(), fp);     fclose(fp); }</code></pre> <br /> 说明:DES、AES加密算法都是针对数据块,Java加解密函数参数使用byte数组。C++用std::string,那是因为这是C++中使用byte数组的最简单方式(std::string可以存储二进制数据,很多人没想到吧),缺点是拷贝内存的次数可能会略多些。如果想要优化拷贝效率,可以使用自己封装的Buffer类来代替std::string。 <h2>三 AES加解密</h2> <h3>Java端AES加解密</h3> <pre> <code class="language-java">import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; /** * DES加解密算法工具类 * * @author xqlee * */ public class AESUtil { /** 默认算法格式 **/ static String default_transformation = "AES/ECB/PKCS5Padding"; /** * 根据key获取加密器 * * @param keyData * key 8byte * @return 加密器 * @throws Exception * 异常 */ private static Cipher getEncryptCipher(byte[] keyData) throws Exception { if (keyData.length != 16) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "AES"); // 指定分块ECB模式,填充PKCS5Padding模式 Cipher encryptCipher = Cipher.getInstance(default_transformation); // 初始化加密的容器 encryptCipher.init(Cipher.ENCRYPT_MODE, key); return encryptCipher; } /** * 根据key获取解码器 * * @return 解码器 * @throws Exception * 异常 */ private static Cipher getDecryptCipher(byte[] keyData) throws Exception { if (keyData.length != 16) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "AES"); Cipher decryptCipher = Cipher.getInstance(default_transformation); // 初始化解密的容器 decryptCipher.init(Cipher.DECRYPT_MODE, key); return decryptCipher; } /** * AES加密 * * @param data * 待加密数据 * @param keyData * key值 * @return 加密后的数据 * @throws Exception * 异常 */ public static byte[] encrypt(byte[] data, byte[] keyData) throws Exception { return getEncryptCipher(keyData).doFinal(data); } /** * AES解密 * * @param data * 加密后的数据 * * @param keyData * key值 * @return 解密数据 * @throws Exception * 异常 */ public static byte[] decrypt(byte[] data, byte[] keyData) throws Exception { return getDecryptCipher(keyData).doFinal(data); } /** * 测试 * * @param args */ public static void main(String[] args) { try { byte[] data = "测试123456".getBytes(); byte[] keyData = "1234567887654321".getBytes(); System.out.println("原文:" + new String(data)); byte[] enData = encrypt(data, keyData); System.out.println("加密后:" + new String(enData)); byte[] deData = decrypt(enData, keyData); System.out.println("解密后:" + new String(deData)); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> <h3>C++端AES加解密</h3> 加密: <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string aesEncrypt(const std::string& source, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[source.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("helloaeshelloaes", 16);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("src.txt", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string source(buf, bytes); // 二进制数据     // 加密     std::string enc = aesEncrypt(source, key);     std::cout << "aesEncrypt:" << source.length() << "->" << enc.length() << std::endl;     // 输出到文件     fp =  fopen("enc.bin", "wb");     fwrite(enc.data(), 1, enc.length(), fp);     fclose(fp); }</code></pre> 解密: <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string aesDecrypt(const std::string& ciphertext, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[ciphertext.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("helloaeshelloaes", 16);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("enc.bin", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string data(buf, bytes); // 二进制数据     // 加密     std::string dec = aesDecrypt(data, key);     std::cout << "aesDecrypt:" << data.length() << "->" << dec.length() << std::endl;     // 输出到文件     fp =  fopen("dec.txt", "wb");     fwrite(dec.data(), 1, dec.length(), fp);     fclose(fp); }</code></pre>
  • Postman 环境变量使用

    Postman 环境变量使用Postman环境变量配置添加一个dev的环境和添加一些dev的环境变量Postman环境变量使用 环境选择变量使用使用双大括号包裹变量名称postman环境变量查看postman支持添加多组环境变量方便切换各个Postman 环境变量使用Postman环境变量配置添加一个dev的环境和添加一些dev的环境变量Postman环境变量使用 环境选择变量使用使用双大括号包裹变量名称postman环境变量查看postman支持添加多组环境变量方便切换各个环境使用。​​​​​​​还有很多地方可以使用到环境变量后续持续讲解
  • Postman MD5加密赋值_postman请求前参数预处理值

    Postman 请求前修改密码为MD5值Postman请求参数设置 选择Pre-request Script栏目 获取明文密码并md5加密后设置到变量中 脚本://获取明文密码 let pwd=pm.request.body.formdatPostman 请求前修改密码为MD5值Postman请求参数设置 选择Pre-request Script栏目 获取明文密码并md5加密后设置到变量中 脚本://获取明文密码 let pwd=pm.request.body.formdata.get("pwd"); console.log('pwd:'+pwd); //md5加密 let md5Pwd=CryptoJS.MD5(pwd).toString(); console.log('md5Pwd:'+md5Pwd); //赋值到pwdMd5环境变量 pm.environment.set("pwdMd5",md5Pwd);点击请求并查看console结果console在这里点击出来参数设置 图像 小部件
  • Postman获取返回值数据并保存到环境变量中

    Postman获取返回值数据并保存到环境变量中首先是普通请求一个接口返回数据示例接下来我们将学校名称也就是字段xxmc 赋值到postman变量xxmc中并打印出来首先编写请求完毕后的脚本接下来执行访问接口,并查看console输出结果最后Postman获取返回值数据并保存到环境变量中首先是普通请求一个接口返回数据示例接下来我们将学校名称也就是字段xxmc 赋值到postman变量xxmc中并打印出来首先编写请求完毕后的脚本接下来执行访问接口,并查看console输出结果最后我们查看环境变量中的值可以看到环境变量当前值已经是刚才赋值的内容了。到这里postman工具获取返回值并赋值到环境变量操作已完成。
  • Spring boot 参数分组校验

    Spring boot 参数分组校验项目源码下载:demo-boot-group-validation.zip​​​​​​​ 访问密码:9987分组校验演示项目结构演示项目创建maven主要依赖: <dependency>l; Spring boot 参数分组校验项目源码下载:demo-boot-group-validation.zip​​​​​​​ 访问密码:9987分组校验演示项目结构演示项目创建maven主要依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>提示:低版本的spring boot 无需单独引入spring-boot-starter-validation演示数据模型:@Data public class UserInfo { @NotNull(groups = {Update.class},message = "请填写id信息") private Long id; @NotEmpty(groups = {Add.class,Update.class},message = "请填写用户名称") private String name; }controller编写: @PostMapping("/test/add") public Object add(@RequestBody @Validated(value = {Add.class})UserInfo userInfo){ return userInfo; } @PostMapping("/test/update") public Object update(@RequestBody @Validated(value = {Update.class})UserInfo userInfo){ return userInfo; }  Postman 数据测试分组校验 运行项目,然后通过postman提交数据测试 可以看到用户名称校验的错误信息,因为新增我们只校验了Add.class分组,所以当前的结果是正常的。 调用更新接口,校验了更新组的字段,可以看到 id和name字段都参与了校验,验证了分组校验的成功。当填入正确数据,返回的结果是正常的,没有走校验错误返回
  • 必应(Bing)主动推送URL

    这里主要讲解如何通过调用api自动推送URL到bing站长平台,让Bing蜘蛛及时抓取你发布的内容。随着Windows 10 Windows11 全面推广后,自带的edge浏览器也越来越流行。默认情况Windows 11的全局搜索都是用的必这里主要讲解如何通过调用api自动推送URL到bing站长平台,让Bing蜘蛛及时抓取你发布的内容。随着Windows 10 Windows11 全面推广后,自带的edge浏览器也越来越流行。默认情况Windows 11的全局搜索都是用的必应,所以站长也有必要重视下Bing来的浏览了。首先通过工具Postman推送使用 我们可以看看API请求结构。bing站长主动推送API接口调用首先是apiKey,apiKey是bing站长平台登陆后申请的一串字符密钥接口请求类型:POST接口主要参数:siteUrl urlListsiteUrl 站长后台认证通过的网站地址urlList 需要推送的认证网站的新建/更新内容url地址 成功返回:固定 "d":null下面是Java推送URL到站长平台代码 public String bingPush(String blogUrl) throws IOException { JSONObject params=new JSONObject(); params.put("siteUrl","https://www.leftso.com"); params.put("urlList",Arrays.asList(blogUrl)); String apiUrl="https://ssl.bing.com/webmaster/api.svc/json/SubmitUrlbatch?apikey="+appProperties.getBingCode(); HttpResponse httpResponse = HttpSimpleClient.doPost(apiUrl, params.toJSONString(), null); log.info("Bing Push:"+httpResponse.getResult()); return httpResponse.getResult(); }​​​​​​​
  • java编程中通过easypoi导入excel文件并验证导入数据

    引言    现如今越来越多的web网站或者内部管理web系统都有自己的数据分析中心引言    现如今越来越多的web网站或者内部管理web系统都有自己的数据分析中心。其数据中心的数据有些来源于人工单独操作,某些来自人工搜集大量的信息后通过excel文件批量导入进系统。本博客将讲解在java编程中,通过excel文件导入数据的方法,其实现是通过国人基于Apache晦涩难懂的POI编写的easypoi框架。一.准备jdk 1.8+(由于本博客使用的spring boot框架所以jdk版本偏高,普通项目1.6即可);eclipse(由于创建的spring boot项目,eclipse中已经安装spring的插件STS)或者你自己喜欢的java IDE工具maven 3+二.创建java web项目(spring boot框架的web项目) 本人偏好使用spring boot来创建web项目。下面通过eclipse的STS创建一个spring boot项目并选择了web模块。项目结构图三.编码3.1添加easypoi的依赖 创建好一个基于maven的项目以后都会有个pom.xml的配置文件.打开该配置文件找到dependencies节点.添加以下easypoi的依赖配置<!-- https://mvnrepository.com/artifact/cn.afterturn/easypoi-web --> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-web</artifactId> <version>3.0.3</version> </dependency> 我的spring boot添加好依赖以后完整的pom.xml文件如下:<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project.demo</groupId> <artifactId>demo-springboot-easypoi-excel</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-easypoi-excel</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <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-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/cn.afterturn/easypoi-web --> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-web</artifactId> <version>3.0.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 3.2编写一个简单的POJO对象 编写一个劲简单的POJO对象用来做导入测试package net.xqlee.project.demo.pojo; import java.util.Date; import javax.validation.constraints.Max; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.NotBlank; import cn.afterturn.easypoi.excel.annotation.Excel; public class User { @Excel(name = "id") @NotBlank(message = "该字段不能为空") private String id; @Excel(name = "姓名") @Pattern(regexp = "[\\u4E00-\\u9FA5]{2,5}", message = "姓名中文2-5位") private String name; @Max(value=20) @Excel(name = "年龄") private Integer age; @Excel(name = "生日", importFormat = "yyyy-MM-dd") private Date birthday; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } } 或许你已经发现,在创建的pojo的属性上我添加了一些注解。其中excel的注解是用于与excel文件做映射使用的。另外的一部分是验证使用。通过上面的依赖导入我们可以看项目的maven实际导入的包中含有hibernate的验证框架。对easypoi的验证机制就是引入了hibernate的验证框架。所以使用也是相同的。3.3编写一个handler     easypoi 的handler是用来处理一些特殊事情的。比如导入的数据某些信息不能与现有数据库中的数据冲突,那么必然会查询数据库。这样的处理是上面hibernate验证框架也无法轻易实现的。所以他这里设计了一个handler。下面是一个简单的hander,只做了打印出导入数据的信息:package net.xqlee.project.demo.handler; import cn.afterturn.easypoi.excel.entity.result.ExcelVerifyHanlderResult; import cn.afterturn.easypoi.handler.inter.IExcelDataHandler; import cn.afterturn.easypoi.handler.inter.IExcelVerifyHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.afterturn.easypoi.handler.impl.ExcelDataHandlerDefaultImpl; import net.xqlee.project.demo.pojo.User; /*** * 自定义验证 */ public class UserExcelHandler implements IExcelVerifyHandler<User> { private static final Logger log = LoggerFactory.getLogger(UserExcelHandler.class); /*** * 自定义验证实现 * @param user * @return */ @Override public ExcelVerifyHanlderResult verifyHandler(User user) { ExcelVerifyHanlderResult result=new ExcelVerifyHanlderResult(true);//默认设置验证通过为true //TODO================可以根据对象字段进行各种验证================= //例如:(注意正则验证在字段注解无效,也可以通过该方式来验证) if (user.getAge()<0){ result.setSuccess(false);//错误标记,该数据会放在导入的返回集合 getFailList()中 result.setMsg("年纪错误"); return result; } return result; } } 3.4编写controllerpackage net.xqlee.project.demo.controller; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult; import cn.afterturn.easypoi.handler.inter.IExcelDataHandler; import net.xqlee.project.demo.handler.UserExcelHandler; import net.xqlee.project.demo.pojo.User; @RestController public class ExcelImportController { private static final Logger log = LoggerFactory.getLogger(ExcelImportController.class); @PostMapping("excelImport.do") public void excelImport(@RequestParam("file") MultipartFile file) { ImportParams importParams = new ImportParams(); // 数据处理 UserExcelHandler handler = new UserExcelHandler(); importParams.setVerifyHanlder(handler); // 需要验证 importParams.setNeedVerfiy(true); try { ExcelImportResult<User> result = ExcelImportUtil.importExcelMore(file.getInputStream(), User.class, importParams); List<User> successList = result.getList(); List<User> failList = result.getFailList(); log.info("是否存在验证未通过的数据:" + result.isVerfiyFail()); log.info("验证通过的数量:" + successList.size()); log.info("验证未通过的数量:" + failList.size()); for (User user : successList) { log.info("成功列表信息:ID=" + user.getId() + user.getName() + "-" + new SimpleDateFormat("yyyy-MM-dd").format(user.getBirthday())); } for (User user : failList) { log.info("失败列表信息:" + user.getName()); } } catch (IOException e) { log.error(e.getMessage(), e); } catch (Exception e) { log.error(e.getMessage(), e); } } } 这里就详细的使用了easypoi导入excel的代码;主要的是首先创建一个导入的参数,参数中可以设置handler还有是否验证等。然后通过easypoi的一个静态工具类调用导入。四.演示4.1创建一个用于测试的excel文件 我这创建的excel文件内容如下:4.2启动项目并测试 首先启动项目,我这里是spring boot项目直接运行主类就启动了:由于我在demo中并没有编写页面只是写了一个接口,所以这里测试用了工具postman提示:工具下载,访问密码 9987Postman官方版.exe  在postmain中输入接口的地址并选择提交方式。将上传的文件以form-data的方式设置,具体如下图:重点的已经在图中圈出来了。点击postman的SEND按钮发起请求。观察eclipse控制台的日志输出:可以看到,导入时候handler已经执行。(需注意的是handler的字段名是excel的代码中已经注明)验证结果和我们预想的一样。因年龄大于20,中文名各自失败一个。成功一条。好啦到这里通spring boot项目通过easypoi导入excel文件数据的例子就讲解完毕了。如果有疑问欢迎留言讨论提示:项目源码下载首选下载:demo-springboot-easypoi-excel.zip 访问密码9987
  • Spring boot JPA MySQL整合实现CRUD REST接口(基础篇)

    Spring boot JPA MySQL整合实现CRUD REST接口,在这篇文章中,我们将为简单的笔记应用程序构建一个Restful CRUD API。注释可以有标题和一些内容。我们将首先构建apis来创建,检索,更新和删除一个Note,然后使用POSTMAN测试它们。<h2 style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">1.前言</span></span></span></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot将Spring框架提升到了一个新的水平。它极大地缩短了Spring项目所需的配置和设置时间。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以设置几乎为零配置的项目,并开始构建对您的应用程序而言非常重要的事物。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果你是Spring的新手,想快速入门,那么这篇博文就是为你准备的。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在这篇文章中,我们将为简单的笔记应用程序构建一个Restful CRUD API。注释可以有标题和一些内容。我们将首先构建apis来创建,检索,更新和删除一个Note,然后使用POSTMAN测试它们。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">2.创建项目</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot提供了一个名为<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">Spring Initializer</a>的Web工具来快速引导应用程序。只需访问<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">http://start.spring.io</a>并按照以下步骤生成一个新项目。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>第1步</strong>:在<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">http://start.spring.io</a>页面上单击<strong>切换到完整版本</strong>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>第2步</strong>:输入详细信息如下 -</span></span></span></p> <ul> <li>Group : com.example</li> <li>Artifact : easy-notes</li> <li>Name : easy-notes</li> <li>Description : Rest API for a Simple Note Taking Application</li> <li>Package Name : com.example.easynotes</li> <li>Packaging : jar (This is the default value)</li> <li>Java Version : 1.8 (Default)</li> <li>Dependencies : Web, JPA, MySQL, DevTools</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><img alt="图1" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/d608951119e348f9b793ff4381442142.png" /><br /> <span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">输入完所有细节后,单击<strong>生成项目</strong>以生成并下载项目。Spring初始化程序将生成具有您输入的详细信息的项目并下载包含所有项目文件夹的zip文件。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">接下来,解压下载的zip文件并将其导入到您最喜欢的IDE中。</span></span></span><br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">3.查看目录结构</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">以下是我们的笔记记录应用程序的目录结构 -</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><img alt="查看目录结构" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/cc41763f46504dea9b627c77cad4a9db.png" /><br /> 让我们了解一些重要文件和目录的细节 -</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>1. EasyNotesApplication</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">这是我们的Spring Boot应用程序的主要入口点。</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class EasyNotesApplication { public static void main(String[] args) { SpringApplication.run(EasyNotesApplication.class, args); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它包含一个简单的注释<code>@SpringBootApplication</code>,它是以下更具体的Spring注释的组合 -</span></span></span></p> <ul> <li><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">@Configuration</a>:用<code>@Configuration</code>Annotation注解的任何类由Spring引导,并且也被认为是其他bean定义的来源。</li> <li><a href="https://docs.spring.io/spring-boot/docs/1.2.1.RELEASE/api/org/springframework/boot/autoconfigure/EnableAutoConfiguration.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">@EnableAutoConfiguration</a>:这个注解告诉Spring根据你添加到<code>pom.xml</code>文件中的依赖来自动配置你的应用程序。</li> <li>例如,如果<code>spring-data-jpa</code>在类路径中,则它会自动尝试<code>DataSource</code>通过从<code>application.properties</code>文件中读取数据库属性来配置一个。</li> <li><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">@ComponentScan</a>:它告诉Spring扫描并引导当前包(com.example.easynotes)和所有子包中定义的其他组件。</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>main()</code>方法调用Spring Boot的<code>SpringApplication.run()</code>方法来启动应用程序。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>2.资源/</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">顾名思义,该目录专用于所有静态资源,模板和属性文件。</span></span></span></p> <ul style="margin-left:30px; margin-right:0px"> <li> <p style="margin-left:0px; margin-right:0px"><strong>资源/静态</strong> - 包含静态资源,如css,js和图像。</p> </li> <li> <p style="margin-left:0px; margin-right:0px"><strong>资源/模板</strong> - 包含由Spring呈现的服务器端模板。</p> </li> <li> <p style="margin-left:0px; margin-right:0px"><strong>resources / application.properties</strong> - 这个文件非常重要。它包含应用程序范围内的属性。Spring读取这个文件中定义的属性来配置你的应用程序。您可以在此文件中定义服务器的默认端口,服务器的上下文路径,数据库URL等。</p> <p style="margin-left:0px; margin-right:0px">您可以参考<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">此页面</a>来了解Spring Boot中使用的常见应用程序属性。</p> </li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>3. EasyNotesApplicationTests</strong> - 在这里定义单元和集成测试。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>4. pom.xml</strong> - 包含所有的项目依赖关系</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">4.配置MySQL数据库</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">正如我前面指出的那样,Spring Boot 通过从文件中读取数据库配置,尝试在类路径中自动配置<code>DataSource</code>if 。<code>spring-data-jpa</code><code>application.properties</code></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">所以,我们只需要添加配置,而Spring Boot将负责其余部分。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">打开<code>application.properties</code>文件并向其中添加以下属性。</span></span></span></p> <pre> <code class="language-html">## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) spring.datasource.url = jdbc:mysql://localhost:3306/notes_app?useSSL=false spring.datasource.username = root spring.datasource.password = root ## Hibernate Properties # The SQL dialect makes Hibernate generate better SQL for the chosen database spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto = update</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您需要在MySQL中创建一个名为<strong>notes_app</strong>的数据库,并根据您的MySQL安装更改<code>spring.datasource.username</code>&<code>spring.datasource.password</code>属性。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在上面的属性文件中,最后两个属性用于休眠。Spring Boot使用Hibernate作为默认的JPA实现。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该属性<code>spring.jpa.hibernate.ddl-auto</code>用于数据库初始化。我已经使用这个属性的值<strong>“update”</strong>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它做了两件事 -</span></span></span></p> <ul> <li>定义域模型时,将自动在数据库中创建一个表,并将域模型的字段映射到表中的相应列。</li> <li>对域模型的任何更改也会触发表的更新。例如,如果您更改字段的名称或类型,或将其他字段添加到模型中,则所有这些更改也会反映在映射表中。</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">使用<em>更新</em>的<code>spring.jpa.hibernate.ddl-auto</code>财产是好的发展。但是,对于生产,您应该保留此属性的值以<strong>“验证”</strong>,并使用像<a href="https://www.callicoder.com/spring-boot-flyway-database-migration-example/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">Flyway</a>这样的数据库迁移工具来管理数据库模式中的更改。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">6.创建Note模型</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">好吧!现在让我们来创建<code>Note</code>模型。我们的<code>Note</code>模型有以下领域 -</span></span></span></p> <ul> <li><strong><code>id</code></strong>:带自动递增的主键。</li> <li><strong><code>title</code></strong>:备注的标题。(NOT NULL字段)</li> <li><strong><code>content</code></strong>:笔记的内容。(NOT NULL字段)</li> <li><strong><code>createdAt</code></strong>:<code>Note</code>创建时间。</li> <li><strong><code>updatedAt</code></strong>:<code>Note</code>更新时间。</li> </ul> <br /> 现在,让我们看看我们如何在Spring中对它进行建模。创建一个名为<code>model</code>inside 的新包,<code>com.example.easynotes</code>并添加一个名为<code>Note.java</code>以下内容的类- <pre> <code class="language-java">package com.example.easynotes.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; import javax.validation.constraints.NotBlank; import java.util.Date; @Entity @Table(name = "notes") @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties(value = {"createdAt", "updatedAt"}, allowGetters = true) public class Note implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank private String title; @NotBlank private String content; @Column(nullable = false, updatable = false) @Temporal(TemporalType.TIMESTAMP) @CreatedDate private Date createdAt; @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP) @LastModifiedDate private Date updatedAt; // Getters and Setters ... (Omitted for brevity) }</code></pre> <ul> <li>您的所有域模型必须使用注释进行<strong><code>@Entity</code></strong>注释。它用于将该类标记为持久的Java类。</li> <li><strong><code>@Table</code></strong> 注释用于提供此实体将映射到的表的详细信息。</li> <li><strong><code>@Id</code></strong> 注释用于定义主键。</li> <li><strong><code>@GeneratedValue</code></strong>注释用于定义主键生成策略。在上述情况下,我们宣布主键是一个<code>Auto Increment</code>字段。</li> <li><strong><code>@NotBlank</code></strong>注释用于验证注释字段是否为<code>not null</code>空。</li> <li><strong><code>@Column</code></strong>注释用于定义将映射到注释字段的列的属性。您可以定义多个属性,如名称,长度,可为空,可更新等。</li> </ul> <p style="margin-left:0px; margin-right:0px">默认情况下,名为的字段将<code>createdAt</code>映射到<code>created_at</code>数据库表中指定的列。即所有的骆驼案件都被下划线取代。</p> <p style="margin-left:0px; margin-right:0px">如果您想将字段映射到不同的列,可以使用以下命令指定它 -</p> <pre> <code class="language-java">@Column(name = "created_on") private String createdAt;</code></pre> <ul> <li><strong><code>@Temporal</code></strong>注释<code>java.util.Date</code>和<code>java.util.Calendar</code>类一起使用。它将Java Object中的日期和时间值转换为兼容的数据库类型,反之亦然。</li> <li><strong><code>@JsonIgnoreProperties</code></strong>注释是杰克逊的注释。Spring Boot使用Jackson将JSON序列化和反序列化Java对象。</li> <li>使用这个注解是因为我们不希望剩下的api的客户端提供<code>createdAt</code>和<code>updatedAt</code>值。如果他们提供这些值,那么我们会简单地忽略它们。但是,我们将在JSON响应中包含这些值。</li> </ul> <h2 style="margin-left:0px; margin-right:0px; text-align:start">7.启用JPA审核</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在我们的<code>Note</code>模型中,我们已经注解<code>createdAt</code>,并<code>updatedAt</code>与领域<code>@CreatedDate</code>,并<code>@LastModifiedDate</code>分别标注。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在,我们想要的是,只要我们创建或更新实体,这些字段就会自动填充。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">要做到这一点,我们需要做两件事 -</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>1.将Spring Data JPA添加<code>AuditingEntityListener</code>到域模型中。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们已经在我们的<code>Note</code>模型中使用了注释<code>@EntityListeners(AuditingEntityListener.class)</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>2.在主应用程序中启用JPA审核。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">打开<code>EasyNotesApplication.java</code>并添加<code>@EnableJpaAuditing</code>注释。</span></span></span></p> <pre> <code class="language-java">@SpringBootApplication @EnableJpaAuditing public class EasyNotesApplication { public static void main(String[] args) { SpringApplication.run(EasyNotesApplication.class, args); } }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">8.创建NoteRepository以访问数据库中的数据</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">接下来我们要做的是创建一个存储库来访问数据库中的Note数据。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">那么,Spring Data JPA已经在这里介绍了我们。它带有一个<a href="https://docs.spring.io/autorepo/docs/spring-data-jpa/current/api/org/springframework/data/jpa/repository/JpaRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank"><code>JpaRepository</code></a>接口,该接口定义实体上所有CRUD操作的方法,以及一个默认的<code>JpaRepository</code>调用实现<a href="http://docs.spring.io/autorepo/docs/spring-data-jpa/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank"><code>SimpleJpaRepository</code></a>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在创建存储库。首先,创建一个<code>repository</code>在基础包内调用的新包<code>com.example.easynotes</code>。然后,创建一个接口<code>NoteRepository</code>并将其从<code>JpaRepository</code>-</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes.repository; import com.example.easynotes.model.Note; import org.springframework.data.jpa.repository.JpaRepository; @Repository public interface NoteRepository extends JpaRepository<Note, Long> { }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">请注意,我们使用注释标注了界面<code>@Repository</code>。这告诉Spring在组件扫描期间引导存储库。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">大!这就是您在存储库层中必须做的所有事情。现在,您将能够使用JpaRepository的方法,如<code>save()</code>,<code>findOne()</code>,<code>findAll()</code>,<code>count()</code>,<code>delete()</code>等。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">你不需要实现这些方法。Spring Data JPA已经实现了它们<code>SimpleJpaRepository</code>。这个实现在运行时被Spring自动插入。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">检查<a href="http://docs.spring.io/autorepo/docs/spring-data-jpa/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">SimpleJpaRepository文档中</a>提供的所有方法。</span></span></span><br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">9.创建自定义业务例外</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们将<code>Note</code>在下一节中定义用于创建,检索,更新和删除a的Rest API 。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><code>ResourceNotFoundException</code>只要在数据库中找不到<code>Note</code>给定的API,API就会抛出<code>id</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">以下是定义<code>ResourceNotFoundException</code>。(我创建了一个名为<code>exception</code>inside 的包<code>com.example.easynotes</code>来存储这个异常类) -</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { private String resourceName; private String fieldName; private Object fieldValue; public ResourceNotFoundException( String resourceName, String fieldName, Object fieldValue) { super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); this.resourceName = resourceName; this.fieldName = fieldName; this.fieldValue = fieldValue; } public String getResourceName() { return resourceName; } public String getFieldName() { return fieldName; } public Object getFieldValue() { return fieldValue; } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">注意<code>@ResponseStatus</code>在上述异常类中使用注释。这将导致Spring引导以指定的HTTP状态码进行响应,无论何时从您的控制器抛出此异常。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">10.创建NoteController</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>最后一步</strong> - 我们现在将创建REST API来创建,检索,更新和删除注释。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">首先,<code>controller</code>在里面创建一个新的包<code>com.example.easynotes</code>。然后,创建一个<code>NoteController.java</code>包含以下内容的新课程-</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes.controller; import com.example.easynotes.exception.ResourceNotFoundException; import com.example.easynotes.model.Note; import com.example.easynotes.repository.NoteRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/api") public class NoteController { @Autowired NoteRepository noteRepository; // Get All Notes // Create a new Note // Get a Single Note // Update a Note // Delete a Note }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong><code>@RestController</code></strong>注释是Spring <code>@Controller</code>和<code>@ResponseBody</code>注释的组合。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>@Controller</code>注释被用来定义一个控制器和所述<code>@ResponseBody</code>注释被用于指示一个方法的返回值应作为所述请求的响应主体。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong><code>@RequestMapping("/api")</code></strong>声明此控制器中所有apis的url将以<code>/api</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在我们来看看所有apis的执行情况。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">1.获取所有Notes(GET / api / notes)</h4> <pre> <code class="language-java">// Get All Notes @GetMapping("/notes") public List<Note> getAllNotes() { return noteRepository.findAll(); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">上述方法非常简单。它调用JpaRepository的<code>findAll()</code>方法从数据库中检索所有注释并返回整个列表。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">此外,<code>@GetMapping("/notes")</code>注释是一种简短形式<code>@RequestMapping(value="/notes", method=RequestMethod.GET)</code>。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">2.创建一个新的Note(POST / api / notes)</h4> <pre> <code class="language-java">// Create a new Note @PostMapping("/notes") public Note createNote(@Valid @RequestBody Note note) { return noteRepository.save(note); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>@RequestBody</code>注释用于将请求主体与方法参数绑定。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>@Valid</code>注释可以确保请求主体是有效的。请记住,我们在模型中标注了注释的标题和<code>@NotBlank</code>注释内容<code>Note</code>?</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果请求主体没有标题或内容,那么spring会<code>400 BadRequest</code>向客户端返回错误。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">3.获取单个Note(Get / api / notes / {noteId})</h4> <pre> <code class="language-java">// Get a Single Note @GetMapping("/notes/{id}") public Note getNoteById(@PathVariable(value = "id") Long noteId) { return noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">的<code>@PathVariable</code>注释,顾名思义,是用来与方法参数路径变量绑定。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在上面的方法中,我们抛出一个没有找到给定id的<code>ResourceNotFoundException</code>a <code>Note</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">这将导致Spring Boot向客户端返回一个404 Not Found错误(请记住,我们已经<code>@ResponseStatus(value = HttpStatus.NOT_FOUND)</code>为<code>ResourceNotFoundException</code>该类添加了注释)。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">4.更新Note(PUT / api / notes / {noteId})</h4> <pre> <code class="language-java">// Update a Note @PutMapping("/notes/{id}") public Note updateNote(@PathVariable(value = "id") Long noteId, @Valid @RequestBody Note noteDetails) { Note note = noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); note.setTitle(noteDetails.getTitle()); note.setContent(noteDetails.getContent()); Note updatedNote = noteRepository.save(note); return updatedNote; }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start">5.删除一个Note(DELETE / api / notes / {noteId})</h4> <pre> <code class="language-java">// Delete a Note @DeleteMapping("/notes/{id}") public ResponseEntity<?> deleteNote(@PathVariable(value = "id") Long noteId) { Note note = noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); noteRepository.delete(note); return ResponseEntity.ok().build(); }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">运行应用程序</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们已经成功为我们的应用程序构建了所有apis。现在运行该应用并测试apis。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">只需转到应用程序的根目录并输入以下命令即可运行它 -</span></span></span></p> <pre style="margin-left:0px; margin-right:0px; text-align:left"> <span style="background-color:#f6f8fa"><span style="font-family:monospace,monospace"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">$ mvn spring-boot:run </code></span></span></span></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该应用程序将从Spring Boot的默认tomcat端口8080开始。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">大!现在,是时候用邮差测试我们的apis了。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">测试API</h2> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>POST /api/notes</code>API 创建新的note</strong></h4> <img alt="使用POST /api/notesAPI 创建新的note" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/cb5cfce6fa5a4e48a68fbef46dccf83b.png" /> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>GET /api/notes</code>API 检索所有Notes</strong></h4> <img alt="使用GET /api/notesAPI 检索所有Notes" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/f04e78160b664266b9ae53ac746f0b16.png" /> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>GET /api/notes/{noteId}</code>API 检索单个Note</strong></h4> <img alt="使用GET /api/notes/{noteId}API 检索单个Note" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/aad1aebfe57b49429a81b7f00bae2e8d.png" /> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>PUT /api/notes/{noteId}</code>API 更新note</strong></h4> <img alt="使用PUT /api/notes/{noteId}API 更新note" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/b9926529dfaf4b6694043cc11afb2363.png" /><br />   <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>DELETE /api/notes/{noteId}</code>API 删除note</strong></h4> <img alt="使用DELETE /api/notes/{noteId}API 删除note" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-04/1e7f7a50e90b4889aa499b48ffbc3754.png" /><br />  
  • Java 11 新特性和增强功能说明

    Java 11(2018 年 9 月发布)包含许多重要且有用的更新Java 11(2018 年 9 月发布)包含许多重要且有用的更新。让我们看看它为开发人员和架构师带来的新功能和改进。1. HTTP 客户端 API Java 有HttpURLConnection很长一段时间的 HTTP 通信类。但随着时间的推移,应用程序的需求变得复杂和更加苛刻。在 Java 11 之前,开发人员不得不求助于功能丰富的库,如Apache HttpComponents或OkHttp等。我们看到Java 9版本包含一个HttpClient实现作为实验性功能。它随着时间的推移不断发展,现在是 Java 11 的最终特性。现在 Java 应用程序可以进行 HTTP 通信,而无需任何外部依赖。1.1. 如何使用HttpClient 与java.net.http模块的典型 HTTP 交互看起来像-创建HttpClient的实例并根据需要对其进行配置。创建HttpRequest的实例并填充信息。将请求传递给客户端,执行请求,并检索HttpResponse的实例。处理包含在HttpResponse. HTTP API 可以处理同步和异步通信。让我们看一个简单的例子。1.2. 同步请求示例 请注意 http 客户端 API 如何使用构建器模式来创建复杂对象。import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); try { String urlEndpoint = "https://postman-echo.com/get"; URI uri = URI.create(urlEndpoint + "?foo1=bar1&foo2=bar2"); HttpRequest request = HttpRequest.newBuilder() .uri(uri) .build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } System.out.println("Status code: " + response.statusCode()); System.out.println("Headers: " + response.headers().allValues("content-type")); System.out.println("Body: " + response.body()); 1.2. 异步请求示例 如果我们不想等待响应,异步通信很有用。我们提供回调处理程序,它在响应可用时执行。注意使用sendAsync()方法发送异步请求。import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; final List<URI> uris = Stream.of( "https://www.google.com/", "https://www.github.com/", "https://www.yahoo.com/" ).map(URI::create).collect(toList()); HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .followRedirects(HttpClient.Redirect.ALWAYS) .build(); CompletableFuture[] futures = uris.stream() .map(uri -> verifyUri(httpClient, uri)) .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures).join(); private CompletableFuture<Void> verifyUri(HttpClient httpClient, URI uri) { HttpRequest request = HttpRequest.newBuilder() .timeout(Duration.ofSeconds(5)) .uri(uri) .build(); return httpClient.sendAsync(request,HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::statusCode) .thenApply(statusCode -> statusCode == 200) .exceptionally(ex -> false) .thenAccept(valid -> { if (valid) { System.out.println("[SUCCESS] Verified " + uri); } else { System.out.println("[FAILURE] Could not " + "verify " + uri); } }); }2. 启动单文件程序无需编译 传统上,对于我们想要执行的每个程序,我们需要先编译它。对于用于测试目的的小程序来说,这似乎是不必要的冗长过程。Java 11 改变了它,现在我们可以执行包含在单个文件中的 Java 源代码,而无需先编译它。 public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } } 要执行上面的类,直接用java命令运行它。$ java HelloWorld.java Hello World!请注意,该程序不能使用除java.base module. 而 program 只能是单文件 program。3. 字符串 API 变化3.1. String.repeat(整数) 这个方法只是简单地重复一个字符串的 n次数。它返回一个字符串,其值是给定字符串重复 N 次的串联。如果此字符串为空或计数为零,则返回空字符串。public class HelloWorld { public static void main(String[] args) { String str = "1".repeat(5); System.out.println(str); //11111 } }3.2. 字符串.isBlank() 此方法指示字符串是否为空或仅包含空格。以前,我们一直在使用 Apache 的StringUtils.java.public class HelloWorld { public static void main(String[] args) { "1".isBlank(); //false "".isBlank(); //true " ".isBlank(); //true } }3.3. String.strip() 此方法负责删除前导和尾随空格。我们可以通过使用去除刚刚主人公更加具体String.stripLeading()或只是尾部字符使用String.stripTrailing() 。public class HelloWorld { public static void main(String[] args) { " hi ".strip(); //"hi" " hi ".stripLeading(); //"hi " " hi ".stripTrailing(); //" hi" } }3.4. String.lines() 此方法有助于将多行文本作为Stream 处理。public class HelloWorld { public static void main(String[] args) { String testString = "hello\nworld\nis\nexecuted"; List<String> lines = new ArrayList<>(); testString.lines().forEach(line -> lines.add(line)); assertEquals(List.of("hello", "world", "is", "executed"), lines); } }4. Collection.toArray(IntFunction)在 Java 11 之前,将集合转换为数组并不简单。Java 11 使转换更加方便。public class HelloWorld { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("alex"); names.add("brian"); names.add("charles"); String[] namesArr1 = names.toArray(new String[names.size()]); //Before Java 11 String[] namesArr2 = names.toArray(String[]::new); //Since Java 11 } }5. Files.readString() 和 Files.writeString() 使用这些重载方法,Java 11 旨在减少大量样板代码,从而更容易读写文件。public class HelloWorld { public static void main(String[] args) { //Read file as string URI txtFileUri = getClass().getClassLoader().getResource("helloworld.txt").toURI(); String content = Files.readString(Path.of(txtFileUri),Charset.defaultCharset()); //Write string to file Path tmpFilePath = Path.of(File.createTempFile("tempFile", ".tmp").toURI()); Path returnedFilePath = Files.writeString(tmpFilePath,"Hello World!", Charset.defaultCharset(), StandardOpenOption.WRITE); } }6. Optional.isEmpty() Optional是一个容器对象,它可能包含也可能不包含非空值。如果不存在任何值,则该对象被视为空。如果存在值,则先前存在的方法isPresent()返回true,否则返回false。有时,它迫使我们编写不可读的否定条件。isEmpty()方法与isPresent()方法相反,false如果存在值则返回,否则返回true。所以我们无论如何都不要写否定条件。适当时使用这两种方法中的任何一种。public class HelloWorld { public static void main(String[] args) { String currentTime = null; assertTrue(!Optional.ofNullable(currentTime).isPresent()); //It's negative condition assertTrue(Optional.ofNullable(currentTime).isEmpty()); //Write it like this currentTime = "12:00 PM"; assertFalse(!Optional.ofNullable(currentTime).isPresent()); //It's negative condition assertFalse(Optional.ofNullable(currentTime).isEmpty()); //Write it like this } }
  • 使用OAuth2安全的Spring security REST API(含demo代码下载)

    使用OAuth2安全的Spring REST API,Secure Spring REST API using OAuth2(含demo代码下载)<p>    让我们<code>Spring REST API</code>使用<code>OAuth2</code>这个时间来保护我们,这是一个简单的指南,显示使用REST API来保护REST API所需的内容<code>Spring OAuth2</code>。我们的用例符合<code>Resource-owner Password Grant</code>OAUth2规范的流程。我们将使用两个不同的客户端(Postman和<code>Spring RestTemplate</code>基于Java 的应用程序)访问我们的OAuth2受保护的REST资源。</p> <p>    如果您已经熟悉OAuth2概念,您可能希望跳过该理论,并直接跳转到代码中。一如以往,在本文末尾附件中可以找到完整的代码。让我们开始吧。<br />  </p> <h3>    什么是OAuth2</h3> <p>    OAuth2是一个标准化的授权协议/框架。根据官方OAuth2规范:</p> <p>    OAuth 2.0授权框架使得第三方应用程序可以通过协调资源所有者和HTTP服务之间的批准交互来代替资源所有者来<u>获取</u>对HTTP服务的<u>有限访问权限</u>,也可以允许第三方应用程序以自己的身份获得访问权限。</p> <p>    Google,Facebook等大型玩家已经在使用自己的OAuth2实现了很长一段时间。企业也正在朝OAuth2采纳方向发展。</p> <p>我发现OAuth2规范比较简单。然而,如果你想开始更快,可以在这里找到一篇关于OAuth2基础知识的优秀文章,从而深入了解OAUth2理论概念。</p> <p>Spring Security OAuth项目提供了使用Spring开发符合OAuth2标准的实现所需的所有API。官方的Spring安全oauth项目提供了一个实施OAuth2的综合示例。这个帖子的代码示例灵感来自这个例子本身。这篇文章的目的是为了保护我们的REST API,只需使用所需的最低限度的功能。</p> <p>至少你应该知道OAuth2中的四个关键概念:<br />  </p> <h3>OAuth2角色</h3> <p>OAuth2定义了四个角色:</p> <ul> <li><strong><code>resource owner</code>:</strong><br /> 可能是你 能够授予访问受保护资源的实体。当资源所有者是个人时,它被称为最终用户。</li> <li><strong><code>resource server</code>:</strong><br /> 托管受保护资源的服务器,能够使用访问令牌接受和响应受保护的资源请求。</li> <li><strong><code>client</code>:</strong><br /> 代表资源所有者及其授权的应用程序生成受保护的资源请求。它可能是一个移动应用程序,要求您访问您的Facebook订阅源,REST客户端尝试访问REST API,一个网站[Stackoverflow例如]使用Facebook帐户提供备用登录选项。</li> <li><strong><code>authorization server</code>:</strong><br /> 服务器在成功验证资源所有者并获得授权后,向客户端发出访问令牌。</li> </ul> <p>在我们的示例中,我们的REST API只能通过资源服务器进行访问,这将需要存在请求的访问令牌<br />  </p> <h3>2. OAuth2授权授权类型</h3> <p>授权授权是表示资源所有者授权(访问其受保护的资源)的凭据,由客户端使用以获取访问令牌。规范定义了四种授权类型:</p> <ul> <li><code>authorization code</code></li> <li><code>implicit</code></li> <li><code>resource owner password credentials</code></li> <li><code>client credentials</code></li> </ul> <p>我们将使用<code>resource owner password credentials</code>授权类型。原因很简单,我们没有实现将我们重定向到登录页面的视图。仅客户端[Postman或RestTemplate为基础的Java客户端例如]拥有资源所有者的凭据,并且他们将这些凭证以及客户机凭证提供给授权服务器,以便最终接收访问令牌[和可选刷新令牌],然后使用该令牌实际访问资源。</p> <p>一个常见的例子是<code>GMail app</code>您的智能手机上的[客户端],您需要您的凭据并使用它们进行连接<code>GMail servers</code>。它还显示“密码凭证授予”最适合当客户端和服务器与信任在同一个公司时,您不想向第三方提供凭据。</p> <h3>OAuth2令牌</h3> <p>令牌是实现特定的随机字符串,由授权服务器生成,并在客户端请求时发出。</p> <ul> <li><code>Access Token</code> :发送每个请求,通常有效期很短的一段时间[一小时例如]</li> <li><code>Refresh Token</code> :主要用于获取新的访问令牌,不发送每个请求,通常比访问令牌寿命更长。</li> </ul> <strong>HTTPS上的一个词</strong>:对于任何类型的安全实现,从基本身份验证到完整的OAuth2实现<strong><code>HTTPS</code></strong>都是必须的。没有HTTPS,无论您的实现是什么,安全性都将受到威胁。 <h3>OAuth2访问令牌范围</h3> <p>客户端可以使用范围[想要访问此用户的Facebook帐户的Feed和照片]来查询具有特定访问权限的资源,授权服务器又返回显示实际授予客户端访问权限的范围[资源所有者只允许Feed访问,没有照片例如]。</p> <hr />我们进入代码 <p>我们来实现使用Spring Security实现OAuth的必要构建块,以便访问我们的REST资源。</p> <h3>资源服务器</h3> <p>资源服务器托管客户端感兴趣的资源[我们的REST API]。资源位于<code>/user/</code>。<code>@EnableResourceServer</code>注释,应用于OAuth2资源服务器,启用使用传入OAuth2令牌对请求进行身份验证的Spring Security过滤器。类<code>ResourceServerConfigurerAdapter</code>实现<code>ResourceServerConfigurer</code>提供了调整由OAuth2安全保护的访问规则和路径的方法。</p> <pre> <code class="language-java">package com.websystique.springmvc.security;   import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;   @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {       private static final String RESOURCE_ID = "my_rest_api";           @Override     public void configure(ResourceServerSecurityConfigurer resources) {         resources.resourceId(RESOURCE_ID).stateless(false);     }       @Override     public void configure(HttpSecurity http) throws Exception {         http.         anonymous().disable()         .requestMatchers().antMatchers("/user/**")         .and().authorizeRequests()         .antMatchers("/user/**").access("hasRole('ADMIN')")         .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());     }   }</code></pre> <h3><br /> 授权服务器是一个负责验证凭据的人员,如果凭据确定,提供令牌[刷新令牌以及访问令牌]。它还包含有关注册的客户端和可能的访问范围和授权类型的信息。令牌存储用于存储令牌。我们将使用内存中的令牌存储。<code>@EnableAuthorizationServer</code>在当前应用程序上下文中启用授权服务器(即AuthorizationEndpoint和TokenEndpoint)。类<code>AuthorizationServerConfigurerAdapter</code>实现<code>AuthorizationServerConfigurer</code>,它提供了配置授权服务器的所有必要方法。2.授权服务器</h3> <pre> <code class="language-java"> package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.token.TokenStore;   @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {       private static String REALM="MY_OAUTH_REALM";           @Autowired     private TokenStore tokenStore;       @Autowired     private UserApprovalHandler userApprovalHandler;       @Autowired     @Qualifier("authenticationManagerBean")     private AuthenticationManager authenticationManager;       @Override     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {           clients.inMemory()             .withClient("my-trusted-client")             .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")             .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")             .scopes("read", "write", "trust")             .secret("secret")             .accessTokenValiditySeconds(120).//Access token is only valid for 2 minutes.             refreshTokenValiditySeconds(600);//Refresh token is only valid for 10 minutes.     }       @Override     public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {         endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)                 .authenticationManager(authenticationManager);     }       @Override     public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {         oauthServer.realm(REALM+"/client");     }   }</code></pre> <p><br /> <br /> 向客户端注册客户端“我的信任客户端”和密码“秘密”以及允许的角色和范围。以上配置</p> <ul> <li>指定任何生成的访问令牌只有120秒有效</li> <li>指定任何生成的刷新令牌只有600秒有效</li> </ul> <h3>3.安全配置</h3> <p>粘在一起 端点<code>/oauth/token</code>用于请求令牌[访问或刷新]。资源所有者[bill,bob]在这里配置。</p> <pre> <code class="language-java">package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.approval.ApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;   @Configuration @EnableWebSecurity public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {       @Autowired     private ClientDetailsService clientDetailsService;           @Autowired     public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("bill").password("abc123").roles("ADMIN").and()         .withUser("bob").password("abc123").roles("USER");     }       @Override     protected void configure(HttpSecurity http) throws Exception {         http         .csrf().disable()         .anonymous().disable()         .authorizeRequests()         .antMatchers("/oauth/token").permitAll();     }       @Override     @Bean     public AuthenticationManager authenticationManagerBean() throws Exception {         return super.authenticationManagerBean();     }         @Bean     public TokenStore tokenStore() {         return new InMemoryTokenStore();     }       @Bean     @Autowired     public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){         TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();         handler.setTokenStore(tokenStore);         handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));         handler.setClientDetailsService(clientDetailsService);         return handler;     }           @Bean     @Autowired     public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {         TokenApprovalStore store = new TokenApprovalStore();         store.setTokenStore(tokenStore);         return store;     }       }</code></pre> <p>另外,启用全局方法安全性,如果我们想要使用它,它将激活@PreFilter,@PostFilter,@PreAuthorize @PostAuthorize注释。</p> <pre> <code class="language-java"> package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;   @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {     @Autowired     private OAuth2SecurityConfiguration securityConfig;       @Override     protected MethodSecurityExpressionHandler createExpressionHandler() {         return new OAuth2MethodSecurityExpressionHandler();     } }</code></pre> <h3>4.终点及其目的</h3> <ul> <li>尝试访问资源[REST API],无需任何授权[将当然失败]。<br /> <code>GET http://localhost:8080/SpringSecurityOAuth2Example/user/</code></li> <li>问令牌[接入+刷新]使用<strong>HTTP POST</strong>上<code>/oauth/token</code>,与grant_type =密码和资源所有者凭证REQ-PARAMS。另外,在授权头中发送客户端凭据。 <p> </p> <p><code>POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=password&username=bill&password=abc123</code></p> </li> <li>要求通过有效的刷新令牌新的访问令牌,使用<strong>HTTP POST</strong>上<code>/oauth/token</code>,与grant_type = refresh_token,并发送实际的刷新令牌。另外,在授权头中发送客户端凭据。 <p> </p> <p><code>POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=refresh_token&refresh_token=094b7d23-973f-4cc1-83ad-8ffd43de1845</code></p> </li> <li>通过使用<code>access_token</code>具有请求的查询参数提供访问令牌来访问资源。<br /> <code>GET http://localhost:8080/SpringSecurityOAuth2Example/user/?access_token=3525d0e4-d881-49e7-9f91-bcfd18259109</code></li> </ul> <h3>5.休息API</h3> <p>我在大部分帖子中使用的简单的Spring REST API。</p> <pre> <code class="language-java"> package com.websystique.springmvc.controller;    import java.util.List;    import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder;    import com.websystique.springmvc.model.User; import com.websystique.springmvc.service.UserService;    @RestController public class HelloWorldRestController {        @Autowired     UserService userService;  //Service which will do all data retrieval/manipulation work               //-------------------Retrieve All Users--------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.GET)     public ResponseEntity<List<User>> listAllUsers() {         List<User> users = userService.findAllUsers();         if(users.isEmpty()){             return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND         }         return new ResponseEntity<List<User>>(users, HttpStatus.OK);     }           //-------------------Retrieve Single User--------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE})     public ResponseEntity<User> getUser(@PathVariable("id") long id) {         System.out.println("Fetching User with id " + id);         User user = userService.findById(id);         if (user == null) {             System.out.println("User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }         return new ResponseEntity<User>(user, HttpStatus.OK);     }                      //-------------------Create a User--------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.POST)     public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {         System.out.println("Creating User " + user.getName());            if (userService.isUserExist(user)) {             System.out.println("A User with name " + user.getName() + " already exist");             return new ResponseEntity<Void>(HttpStatus.CONFLICT);         }            userService.saveUser(user);            HttpHeaders headers = new HttpHeaders();         headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());         return new ResponseEntity<Void>(headers, HttpStatus.CREATED);     }               //------------------- Update a User --------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)     public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {         System.out.println("Updating User " + id);                    User currentUser = userService.findById(id);                    if (currentUser==null) {             System.out.println("User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }            currentUser.setName(user.getName());         currentUser.setAge(user.getAge());         currentUser.setSalary(user.getSalary());                    userService.updateUser(currentUser);         return new ResponseEntity<User>(currentUser, HttpStatus.OK);     }        //------------------- Delete a User --------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)     public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {         System.out.println("Fetching & Deleting User with id " + id);            User user = userService.findById(id);         if (user == null) {             System.out.println("Unable to delete. User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }            userService.deleteUserById(id);         return new ResponseEntity<User>(HttpStatus.NO_CONTENT);     }               //------------------- Delete All Users --------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.DELETE)     public ResponseEntity<User> deleteAllUsers() {         System.out.println("Deleting All Users");            userService.deleteAllUsers();         return new ResponseEntity<User>(HttpStatus.NO_CONTENT);     }    }</code></pre> <h3><br /> 运行应用程序</h3> <p>运行它并使用两个不同的客户端进行测试。<br /> <br />  </p> <h4>客户端1:Postman</h4> <p>尝试访问一个没有任何信息的资源,将获得一个401。</p> <p><img alt="SpringOAuth2_img1" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/bbd70c98-9e02-46e9-b4b7-c4bdfadb1d93.png" /></p> <p>让我们得到令牌。首先添加一个<strong>客户端凭证</strong> [my-trusted-client / secret] 的授权头文件。</p> <p><img alt="SpringOAuth2_img2" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/9c19299c-5b53-4dfb-97f7-30b81963ac1f.png" /></p> <p>单击更新请求,验证标题标签中的标题。</p> <p><img alt="SpringOAuth2_img3" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/e404605e-f273-476d-a582-0147146e96d9.png" /></p> <p>发送POST请求时,您将收到包含响应<code>access-token</code>以及<code>refresh-token</code>。</p> <p><img alt="SpringOAuth2_img4" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/a0e2e85e-0a1b-40f8-86e4-3c9f1c062276.png" /></p> <p>将这些令牌保存在某个地方,您将需要它们。现在,您可以使用此访问令牌(有效期为2分钟)来访问资源。</p> <p><img alt="SpringOAuth2_img5" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/77e1a43f-c866-4b33-b1f0-f1c7d5232f89.png" /></p> <p>2分钟后,access-token将过期,您的进一步资源请求将失败。</p> <p><img alt="SpringOAuth2_img6" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/dc75aaec-9ce3-4fe0-8bb7-c98e3e8c6054.png" /></p> <p>我们需要一个新的访问令牌。通过刷新令牌来触发一个帖子,以获得全新的访问令牌。</p> <p><img alt="SpringOAuth2_img7" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/34a5980f-0d49-416a-adf4-fe4182056648.png" /></p> <p>使用这个新的访问令牌访问资源。</p> <p><img alt="SpringOAuth2_img8" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/4e0b3771-92ed-438a-bc0f-15de7bc6fcf1.png" /></p> <p>刷新令牌也过期[10分钟]。之后,您将看到刷新请求失败。</p> <p><img alt="SpringOAuth2_img9" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/c8760e84-cdfa-4760-ad3e-0ff07768c117.png" /></p> <p>这意味着您需要请求新的刷新+访问令牌,如步骤2中所示。</p> <h4>客户端2:基于RestTemplate的java应用程序</h4> <p>方法<strong>sendTokenRequest</strong>用于实际获取令牌。然后,我们收到的访问令牌将被用于每个请求。如果需要,您可以在下面的示例中轻松实现刷新令牌流。</p> <pre> <code class="language-java">package com.websystique.springmvc; import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; import com.websystique.springmvc.model.AuthTokenInfo; import com.websystique.springmvc.model.User; public class SpringRestClient { public static final String REST_SERVICE_URI = "http://localhost:8080/SpringSecurityOAuth2Example"; public static final String AUTH_SERVER_URI = "http://localhost:8080/SpringSecurityOAuth2Example/oauth/token"; public static final String QPM_PASSWORD_GRANT = "?grant_type=password&username=bill&password=abc123"; public static final String QPM_ACCESS_TOKEN = "?access_token="; /* * Prepare HTTP Headers. */ private static HttpHeaders getHeaders(){ HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); return headers; } /* * Add HTTP Authorization header, using Basic-Authentication to send client-credentials. */ private static HttpHeaders getHeadersWithClientCredentials(){ String plainClientCredentials="my-trusted-client:secret"; String base64ClientCredentials = new String(Base64.encodeBase64(plainClientCredentials.getBytes())); HttpHeaders headers = getHeaders(); headers.add("Authorization", "Basic " + base64ClientCredentials); return headers; } /* * Send a POST request [on /oauth/token] to get an access-token, which will then be send with each request. */ @SuppressWarnings({ "unchecked"}) private static AuthTokenInfo sendTokenRequest(){ RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeadersWithClientCredentials()); ResponseEntity<Object> response = restTemplate.exchange(AUTH_SERVER_URI+QPM_PASSWORD_GRANT, HttpMethod.POST, request, Object.class); LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>)response.getBody(); AuthTokenInfo tokenInfo = null; if(map!=null){ tokenInfo = new AuthTokenInfo(); tokenInfo.setAccess_token((String)map.get("access_token")); tokenInfo.setToken_type((String)map.get("token_type")); tokenInfo.setRefresh_token((String)map.get("refresh_token")); tokenInfo.setExpires_in((int)map.get("expires_in")); tokenInfo.setScope((String)map.get("scope")); System.out.println(tokenInfo); //System.out.println("access_token ="+map.get("access_token")+", token_type="+map.get("token_type")+", refresh_token="+map.get("refresh_token") //+", expires_in="+map.get("expires_in")+", scope="+map.get("scope"));; }else{ System.out.println("No user exist----------"); } return tokenInfo; } /* * Send a GET request to get list of all users. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static void listAllUsers(AuthTokenInfo tokenInfo){ Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting listAllUsers API-----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); ResponseEntity<List> response = restTemplate.exchange(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.GET, request, List.class); List<LinkedHashMap<String, Object>> usersMap = (List<LinkedHashMap<String, Object>>)response.getBody(); if(usersMap!=null){ for(LinkedHashMap<String, Object> map : usersMap){ System.out.println("User : id="+map.get("id")+", Name="+map.get("name")+", Age="+map.get("age")+", Salary="+map.get("salary"));; } }else{ System.out.println("No user exist----------"); } } /* * Send a GET request to get a specific user. */ private static void getUser(AuthTokenInfo tokenInfo){ Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting getUser API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.GET, request, User.class); User user = response.getBody(); System.out.println(user); } /* * Send a POST request to create a new user. */ private static void createUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting create User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(0,"Sarah",51,134); HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders()); URI uri = restTemplate.postForLocation(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), request, User.class); System.out.println("Location : "+uri.toASCIIString()); } /* * Send a PUT request to update an existing user. */ private static void updateUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting update User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(1,"Tomy",33, 70000); HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders()); ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.PUT, request, User.class); System.out.println(response.getBody()); } /* * Send a DELETE request to delete a specific user. */ private static void deleteUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting delete User API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); restTemplate.exchange(REST_SERVICE_URI+"/user/3"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.DELETE, request, User.class); } /* * Send a DELETE request to delete all users. */ private static void deleteAllUsers(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting all delete Users API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); restTemplate.exchange(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.DELETE, request, User.class); } public static void main(String args[]){ AuthTokenInfo tokenInfo = sendTokenRequest(); listAllUsers(tokenInfo); getUser(tokenInfo); createUser(tokenInfo); listAllUsers(tokenInfo); updateUser(tokenInfo); listAllUsers(tokenInfo); deleteUser(tokenInfo); listAllUsers(tokenInfo); deleteAllUsers(tokenInfo); listAllUsers(tokenInfo); } }</code></pre> <br /> 以上代码将产生以下输出: <pre> <code>AuthTokenInfo [access_token=fceed386-5923-4bf8-b193-1d76f95da4c4, token_type=bearer, refresh_token=29d28ee2-9d09-483f-a2d6-7f93e7a31667, expires_in=71, scope=read write trust] Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 Testing getUser API---------- User [id=1, name=Sam, age=30, salary=70000.0] Testing create User API---------- Location : http://localhost:8080/SpringSecurityOAuth2Example/user/5 Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing update User API---------- User [id=1, name=Tomy, age=33, salary=70000.0] Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing delete User API---------- Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing all delete Users API---------- Testing listAllUsers API----------- No user exist----------</code></pre>   <h3>项目结构</h3> <img alt="10" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-03/aab904e5-276f-4fac-8bd4-3762adb1a28c.png" /> <h3>pom.xml</h3> <pre> <code class="language-xml"><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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.springmvc</groupId> <artifactId>SpringSecurityOAuth2Example</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>SpringSecurityOAuth2Example</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <springframework.version>4.3.1.RELEASE</springframework.version> <springsecurity.version>4.1.1.RELEASE</springsecurity.version> <springsecurityoauth2.version>2.0.10.RELEASE</springsecurityoauth2.version> <jackson.library>2.7.5</jackson.library> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${springsecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${springsecurity.version}</version> </dependency> <!-- Spring Security OAuth2--> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>${springsecurityoauth2.version}</version> </dependency> <!-- Jackson libraries --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.library}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>${jackson.library}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringSecurityOAuth2Example</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> <finalName>SpringSecurityOAuth2Example</finalName> </build> </project></code></pre> <h4><strong><a href="http://websystique.com/?smd_process_download=1&download_id=2926" rel="external nofollow" target="_blank"><em><u>下载源代码</u></em></a></strong></h4> <br />