Spring Boot RESTful API 开发与测试

Spring Boot RESTful API 开发与测试

Spring Boot RESTful API 开发与测试

在这里插入图片描述
20.1 学习目标与重点提示

学习目标:掌握Spring Boot RESTful API开发与测试的核心概念与使用方法,包括RESTful API的定义与特点、Spring Boot RESTful API的开发、Spring Boot RESTful API的测试、Spring Boot RESTful API的认证与授权、Spring Boot RESTful API的实际应用场景,学会在实际开发中处理RESTful API问题。
重点:RESTful API的定义与特点(资源、表现层、状态转移)Spring Boot RESTful API的开发(@RestController、@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping)Spring Boot RESTful API的测试(单元测试、集成测试、Mock测试)Spring Boot RESTful API的认证与授权(Spring Security、JWT)Spring Boot RESTful API的实际应用场景

20.2 RESTful API概述

RESTful API是Java开发中的主流API设计风格。

20.2.1 RESTful API的定义

定义:RESTful API是一种基于REST架构风格的API设计。
作用

  • 实现Web应用的API设计。
  • 提高开发效率。
  • 提供统一的编程模型。

REST架构风格的特点

  • 资源(Resource):使用URI表示资源。
  • 表现层(Representation):使用HTTP请求方法(GET、POST、PUT、DELETE)表示操作。
  • 状态转移(State Transfer):使用HTTP响应状态码表示操作结果。

✅ 结论:RESTful API是一种基于REST架构风格的API设计,作用是实现Web应用的API设计、提高开发效率、提供统一的编程模型。

20.2.2 RESTful API的常用HTTP方法

定义:RESTful API的常用HTTP方法是指RESTful API使用的HTTP请求方法。
方法

  • GET:获取资源。
  • POST:创建资源。
  • PUT:更新资源。
  • DELETE:删除资源。
  • PATCH:更新部分资源。

常用HTTP响应状态码

  • 200:成功。
  • 201:资源创建成功。
  • 400:请求参数错误。
  • 401:未授权。
  • 403:禁止访问。
  • 404:资源不存在。
  • 500:服务器内部错误。

✅ 结论:RESTful API的常用HTTP方法包括GET、POST、PUT、DELETE、PATCH,常用HTTP响应状态码包括200、201、400、401、403、404、500。

20.3 Spring Boot RESTful API的开发

Spring Boot RESTful API的开发是Java开发中的重要内容。

20.3.1 开发RESTful API的步骤

定义:开发RESTful API的步骤是指使用Spring Boot开发RESTful API的方法。
步骤

  1. 创建Spring Boot项目。
  2. 添加所需的依赖。
  3. 创建实体类。
  4. 创建Repository接口。
  5. 创建Service类。
  6. 创建Controller类。
  7. 测试应用。

示例
pom.xml文件中的依赖:

<dependencies><!-- Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Data JPA依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- H2数据库依赖 --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

实体类:

importjavax.persistence.*;@Entity@Table(name ="product")publicclassProduct{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString productId;privateString productName;privatedouble price;privateint sales;publicProduct(){}publicProduct(String productId,String productName,double price,int sales){this.productId = productId;this.productName = productName;this.price = price;this.sales = sales;}// Getter和Setter方法publicLonggetId(){return id;}publicvoidsetId(Long id){this.id = id;}publicStringgetProductId(){return productId;}publicvoidsetProductId(String productId){this.productId = productId;}publicStringgetProductName(){return productName;}publicvoidsetProductName(String productName){this.productName = productName;}publicdoublegetPrice(){return price;}publicvoidsetPrice(double price){this.price = price;}publicintgetSales(){return sales;}publicvoidsetSales(int sales){this.sales = sales;}@OverridepublicStringtoString(){return"Product{"+"id="+ id +",+ productId +'\''+",+ productName +'\''+", price="+ price +", sales="+ sales +'}';}}

Repository接口:

importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.stereotype.Repository;importjava.util.List;@RepositorypublicinterfaceProductRepositoryextendsJpaRepository<Product,Long>{List<Product>findBySalesGreaterThan(int sales);}

Service类:

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importjava.util.List;@ServicepublicclassProductService{@AutowiredprivateProductRepository productRepository;@TransactionalpublicvoidaddProduct(Product product){ productRepository.save(product);}@TransactionalpublicvoidupdateProduct(Product product){ productRepository.save(product);}@TransactionalpublicvoiddeleteProduct(Long id){ productRepository.deleteById(id);}@Transactional(readOnly =true)publicList<Product>getAllProducts(){return productRepository.findAll();}@Transactional(readOnly =true)publicList<Product>getTopSellingProducts(int topN){List<Product> products = productRepository.findBySalesGreaterThan(0); products.sort((p1, p2)-> p2.getSales()- p1.getSales());if(products.size()> topN){return products.subList(0, topN);}return products;}}

Controller类:

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.*;importjava.util.List;@RestController@RequestMapping("/api/products")publicclassProductController{@AutowiredprivateProductService productService;@GetMapping("/")publicResponseEntity<List<Product>>getAllProducts(){List<Product> products = productService.getAllProducts();returnnewResponseEntity<>(products,HttpStatus.OK);}@PostMapping("/")publicResponseEntity<Void>addProduct(@RequestBodyProduct product){ productService.addProduct(product);returnnewResponseEntity<>(HttpStatus.CREATED);}@PutMapping("/{id}")publicResponseEntity<Void>updateProduct(@PathVariableLong id,@RequestBodyProduct product){ product.setId(id); productService.updateProduct(product);returnnewResponseEntity<>(HttpStatus.OK);}@DeleteMapping("/{id}")publicResponseEntity<Void>deleteProduct(@PathVariableLong id){ productService.deleteProduct(id);returnnewResponseEntity<>(HttpStatus.NO_CONTENT);}@GetMapping("/top-selling")publicResponseEntity<List<Product>>getTopSellingProducts(@RequestParamint topN){List<Product> products = productService.getTopSellingProducts(topN);returnnewResponseEntity<>(products,HttpStatus.OK);}}

测试类:

importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.web.client.TestRestTemplate;importorg.springframework.boot.web.server.LocalServerPort;importjava.util.List;importstaticorg.assertj.core.api.Assertions.assertThat;@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classProductApplicationTests{@LocalServerPortprivateint port;@AutowiredprivateTestRestTemplate restTemplate;@TestvoidcontextLoads(){}@TestvoidtestGetAllProducts(){List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(5);}@TestvoidtestAddProduct(){Product product =newProduct("P006","平板",2000.0,70); restTemplate.postForEntity("http://localhost:"+ port +"/api/products/", product,Void.class);List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(6);}@TestvoidtestUpdateProduct(){Product product =newProduct("P001","手机",1500.0,120); restTemplate.put("http://localhost:"+ port +"/api/products/1", product);List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products.get(0).getPrice()).isEqualTo(1500.0);}@TestvoidtestDeleteProduct(){ restTemplate.delete("http://localhost:"+ port +"/api/products/2");List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(4);}@TestvoidtestGetTopSellingProducts(){List<Product> topSellingProducts = restTemplate.getForObject("http://localhost:"+ port +"/api/products/top-selling?topN=3",List.class);assertThat(topSellingProducts).hasSize(3);assertThat(topSellingProducts.get(0).getProductId()).isEqualTo("P004");assertThat(topSellingProducts.get(1).getProductId()).isEqualTo("P005");assertThat(topSellingProducts.get(2).getProductId()).isEqualTo("P001");}}

✅ 结论:开发RESTful API的步骤包括创建Spring Boot项目、添加所需的依赖、创建实体类、创建Repository接口、创建Service类、创建Controller类、测试应用。

20.4 Spring Boot RESTful API的测试

Spring Boot RESTful API的测试是Java开发中的重要内容。

20.4.1 单元测试

定义:单元测试是指测试单个方法或类的功能。
常用注解

  • @SpringBootTest:标记测试类为Spring Boot测试。
  • @Test:标记方法为测试方法。
  • @Autowired:注入依赖。

示例

importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importjava.util.List;importstaticorg.assertj.core.api.Assertions.assertThat;@SpringBootTestclassProductServiceTests{@AutowiredprivateProductService productService;@TestvoidtestAddProduct(){Product product =newProduct("P006","平板",2000.0,70); productService.addProduct(product);List<Product> products = productService.getAllProducts();assertThat(products).hasSize(6);}@TestvoidtestUpdateProduct(){Product product =newProduct("P001","手机",1500.0,120); product.setId(1L); productService.updateProduct(product);List<Product> products = productService.getAllProducts();assertThat(products.get(0).getPrice()).isEqualTo(1500.0);}@TestvoidtestDeleteProduct(){ productService.deleteProduct(2L);List<Product> products = productService.getAllProducts();assertThat(products).hasSize(4);}@TestvoidtestGetTopSellingProducts(){List<Product> topSellingProducts = productService.getTopSellingProducts(3);assertThat(topSellingProducts).hasSize(3);assertThat(topSellingProducts.get(0).getProductId()).isEqualTo("P004");assertThat(topSellingProducts.get(1).getProductId()).isEqualTo("P005");assertThat(topSellingProducts.get(2).getProductId()).isEqualTo("P001");}}

✅ 结论:单元测试是指测试单个方法或类的功能,常用注解包括@SpringBootTest、@Test、@Autowired。

20.4.2 集成测试

定义:集成测试是指测试多个组件之间的交互。
常用注解

  • @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT):标记测试类为Spring Boot集成测试。
  • @LocalServerPort:注入服务器端口。
  • @Autowired:注入依赖。

示例

importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.web.client.TestRestTemplate;importorg.springframework.boot.web.server.LocalServerPort;importjava.util.List;importstaticorg.assertj.core.api.Assertions.assertThat;@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classProductControllerTests{@LocalServerPortprivateint port;@AutowiredprivateTestRestTemplate restTemplate;@TestvoidtestGetAllProducts(){List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(5);}@TestvoidtestAddProduct(){Product product =newProduct("P006","平板",2000.0,70); restTemplate.postForEntity("http://localhost:"+ port +"/api/products/", product,Void.class);List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(6);}@TestvoidtestUpdateProduct(){Product product =newProduct("P001","手机",1500.0,120); restTemplate.put("http://localhost:"+ port +"/api/products/1", product);List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products.get(0).getPrice()).isEqualTo(1500.0);}@TestvoidtestDeleteProduct(){ restTemplate.delete("http://localhost:"+ port +"/api/products/2");List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(4);}@TestvoidtestGetTopSellingProducts(){List<Product> topSellingProducts = restTemplate.getForObject("http://localhost:"+ port +"/api/products/top-selling?topN=3",List.class);assertThat(topSellingProducts).hasSize(3);assertThat(topSellingProducts.get(0).getProductId()).isEqualTo("P004");assertThat(topSellingProducts.get(1).getProductId()).isEqualTo("P005");assertThat(topSellingProducts.get(2).getProductId()).isEqualTo("P001");}}

✅ 结论:集成测试是指测试多个组件之间的交互,常用注解包括@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)、@LocalServerPort、@Autowired。

20.4.3 Mock测试

定义:Mock测试是指模拟对象的行为。
常用注解

  • @WebMvcTest:标记测试类为Spring MVC测试。
  • @MockBean:注入Mock对象。
  • @Autowired:注入依赖。

示例

importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;importorg.springframework.boot.test.mock.mockito.MockBean;importorg.springframework.http.MediaType;importorg.springframework.test.web.servlet.MockMvc;importjava.util.Arrays;importjava.util.List;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.Mockito.*;importstaticorg.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;importstaticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@WebMvcTest(ProductController.class)classProductControllerMockTests{@AutowiredprivateMockMvc mockMvc;@MockBeanprivateProductService productService;@TestvoidtestGetAllProducts()throwsException{List<Product> products =Arrays.asList(newProduct("P001","手机",1000.0,100),newProduct("P002","电脑",5000.0,50),newProduct("P003","电视",3000.0,80),newProduct("P004","手表",500.0,200),newProduct("P005","耳机",300.0,150));when(productService.getAllProducts()).thenReturn(products); mockMvc.perform(get("/api/products/")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$[0].productId").value("P001")).andExpect(jsonPath("$[1].productId").value("P002")).andExpect(jsonPath("$[2].productId").value("P003")).andExpect(jsonPath("$[3].productId").value("P004")).andExpect(jsonPath("$[4].productId").value("P005"));verify(productService,times(1)).getAllProducts();}@TestvoidtestAddProduct()throwsException{Product product =newProduct("P006","平板",2000.0,70);doNothing().when(productService).addProduct(any(Product.class)); mockMvc.perform(post("/api/products/").contentType(MediaType.APPLICATION_JSON).content("{\"productId\":\"P006\",\"productName\":\"平板\",\"price\":2000.0,\"sales\":70}")).andExpect(status().isCreated());verify(productService,times(1)).addProduct(any(Product.class));}@TestvoidtestUpdateProduct()throwsException{Product product =newProduct("P001","手机",1500.0,120);doNothing().when(productService).updateProduct(any(Product.class)); mockMvc.perform(put("/api/products/1").contentType(MediaType.APPLICATION_JSON).content("{\"id\":1,\"productId\":\"P001\",\"productName\":\"手机\",\"price\":1500.0,\"sales\":120}")).andExpect(status().isOk());verify(productService,times(1)).updateProduct(any(Product.class));}@TestvoidtestDeleteProduct()throwsException{doNothing().when(productService).deleteProduct(anyLong()); mockMvc.perform(delete("/api/products/2")).andExpect(status().isNoContent());verify(productService,times(1)).deleteProduct(anyLong());}@TestvoidtestGetTopSellingProducts()throwsException{List<Product> topSellingProducts =Arrays.asList(newProduct("P004","手表",500.0,200),newProduct("P005","耳机",300.0,150),newProduct("P001","手机",1000.0,100));when(productService.getTopSellingProducts(3)).thenReturn(topSellingProducts); mockMvc.perform(get("/api/products/top-selling?topN=3")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$[0].productId").value("P004")).andExpect(jsonPath("$[1].productId").value("P005")).andExpect(jsonPath("$[2].productId").value("P001"));verify(productService,times(1)).getTopSellingProducts(3);}}

✅ 结论:Mock测试是指模拟对象的行为,常用注解包括@WebMvcTest、@MockBean、@Autowired。

20.5 Spring Boot RESTful API的认证与授权

Spring Boot RESTful API的认证与授权是Java开发中的重要内容。

20.5.1 Spring Security

定义:Spring Security是Spring Boot提供的安全框架。
作用

  • 实现用户认证。
  • 实现用户授权。
  • 提供安全的编程模型。

示例
pom.xml文件中的Spring Security依赖:

<dependencies><!-- Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Data JPA依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- H2数据库依赖 --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><!-- Spring Security依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

Spring Security配置类:

importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.crypto.password.NoOpPasswordEncoder;@Configuration@EnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throwsException{ auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("admin").password("admin123").roles("ADMIN").and().withUser("user").password("user123").roles("USER");}@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{ http.authorizeRequests().antMatchers("/api/products/top-selling").hasRole("ADMIN").antMatchers("/api/products/**").hasRole("USER").and().httpBasic();}}

测试类:

importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.web.client.TestRestTemplate;importorg.springframework.boot.web.server.LocalServerPort;importorg.springframework.http.HttpEntity;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpMethod;importorg.springframework.http.ResponseEntity;importjava.util.Base64;importjava.util.List;importstaticorg.assertj.core.api.Assertions.assertThat;@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classProductControllerSecurityTests{@LocalServerPortprivateint port;@AutowiredprivateTestRestTemplate restTemplate;@TestvoidtestGetAllProductsWithoutAuthentication(){ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:"+ port +"/api/products/",List.class);assertThat(response.getStatusCodeValue()).isEqualTo(401);}@TestvoidtestGetAllProductsWithUserAuthentication(){String credentials ="user:user123";String base64Credentials =Base64.getEncoder().encodeToString(credentials.getBytes());HttpHeaders headers =newHttpHeaders(); headers.add("Authorization","Basic "+ base64Credentials);HttpEntity<String> entity =newHttpEntity<>(headers);ResponseEntity<List> response = restTemplate.exchange("http://localhost:"+ port +"/api/products/",HttpMethod.GET, entity,List.class);assertThat(response.getStatusCodeValue()).isEqualTo(200);assertThat(response.getBody()).hasSize(5);}@TestvoidtestGetTopSellingProductsWithUserAuthentication(){String credentials ="user:user123";String base64Credentials =Base64.getEncoder().encodeToString(credentials.getBytes());HttpHeaders headers =newHttpHeaders(); headers.add("Authorization","Basic "+ base64Credentials);HttpEntity<String> entity =newHttpEntity<>(headers);ResponseEntity<List> response = restTemplate.exchange("http://localhost:"+ port +"/api/products/top-selling?topN=3",HttpMethod.GET, entity,List.class);assertThat(response.getStatusCodeValue()).isEqualTo(403);}@TestvoidtestGetTopSellingProductsWithAdminAuthentication(){String credentials ="admin:admin123";String base64Credentials =Base64.getEncoder().encodeToString(credentials.getBytes());HttpHeaders headers =newHttpHeaders(); headers.add("Authorization","Basic "+ base64Credentials);HttpEntity<String> entity =newHttpEntity<>(headers);ResponseEntity<List> response = restTemplate.exchange("http://localhost:"+ port +"/api/products/top-selling?topN=3",HttpMethod.GET, entity,List.class);assertThat(response.getStatusCodeValue()).isEqualTo(200);assertThat(response.getBody()).hasSize(3);}}

✅ 结论:Spring Security是Spring Boot提供的安全框架,作用是实现用户认证、用户授权、提供安全的编程模型。

20.5.2 JWT

定义:JWT是一种基于JSON的开放标准,用于在网络应用之间安全地传输信息。
作用

  • 实现用户认证。
  • 实现用户授权。
  • 提供安全的编程模型。

示例
pom.xml文件中的JWT依赖:

<dependencies><!-- Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Data JPA依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- H2数据库依赖 --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><!-- Spring Security依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- JWT依赖 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

JWT工具类:

importio.jsonwebtoken.Claims;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importjava.util.Date;importjava.util.HashMap;importjava.util.Map;importjava.util.function.Function;@ComponentpublicclassJwtUtil{@Value("${jwt.secret}")privateString secret;@Value("${jwt.expiration}")privateLong expiration;publicStringextractUsername(String token){returnextractClaim(token,Claims::getSubject);}publicDateextractExpiration(String token){returnextractClaim(token,Claims::getExpiration);}public<T>TextractClaim(String token,Function<Claims,T> claimsResolver){finalClaims claims =extractAllClaims(token);return claimsResolver.apply(claims);}privateClaimsextractAllClaims(String token){returnJwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}privateBooleanisTokenExpired(String token){returnextractExpiration(token).before(newDate());}publicStringgenerateToken(String username){Map<String,Object> claims =newHashMap<>();returncreateToken(claims, username);}privateStringcreateToken(Map<String,Object> claims,String subject){returnJwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(newDate(System.currentTimeMillis())).setExpiration(newDate(System.currentTimeMillis()+ expiration *1000)).signWith(SignatureAlgorithm.HS256, secret).compact();}publicBooleanvalidateToken(String token,String username){finalString extractedUsername =extractUsername(token);return(extractedUsername.equals(username)&&!isTokenExpired(token));}}

JWT过滤器:

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.web.authentication.WebAuthenticationDetailsSource;importorg.springframework.stereotype.Component;importorg.springframework.web.filter.OncePerRequestFilter;importjavax.servlet.FilterChain;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;@ComponentpublicclassJwtRequestFilterextendsOncePerRequestFilter{@AutowiredprivateUserDetailsService userDetailsService;@AutowiredprivateJwtUtil jwtUtil;@OverrideprotectedvoiddoFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain)throwsServletException,IOException{finalString authorizationHeader = request.getHeader("Authorization");String username =null;String jwt =null;if(authorizationHeader !=null&& authorizationHeader.startsWith("Bearer ")){ jwt = authorizationHeader.substring(7); username = jwtUtil.extractUsername(jwt);}if(username !=null&&SecurityContextHolder.getContext().getAuthentication()==null){UserDetails userDetails =this.userDetailsService.loadUserByUsername(username);if(jwtUtil.validateToken(jwt, userDetails.getUsername())){UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =newUsernamePasswordAuthenticationToken( userDetails,null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails(newWebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}} chain.doFilter(request, response);}}

Spring Security配置类:

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.config.http.SessionCreationPolicy;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.NoOpPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled =true)publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateUserDetailsService userDetailsService;@AutowiredprivateJwtRequestFilter jwtRequestFilter;@Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throwsException{ auth.userDetailsService(userDetailsService);}@BeanpublicPasswordEncoderpasswordEncoder(){returnNoOpPasswordEncoder.getInstance();}@Bean@OverridepublicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper.authenticationManagerBean();}@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{ http.csrf().disable().authorizeRequests().antMatchers("/authenticate").permitAll().anyRequest().authenticated().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtRequestFilter,UsernamePasswordAuthenticationFilter.class);}}

认证控制器:

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.ResponseEntity;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.authentication.BadCredentialsException;importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.web.bind.annotation.*;@RestControllerpublicclassJwtAuthenticationController{@AutowiredprivateAuthenticationManager authenticationManager;@AutowiredprivateJwtUtil jwtTokenUtil;@AutowiredprivateUserDetailsService userDetailsService;@PostMapping("/authenticate")publicResponseEntity<?>createAuthenticationToken(@RequestBodyJwtRequest authenticationRequest)throwsException{try{ authenticationManager.authenticate(newUsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));}catch(BadCredentialsException e){thrownewException("Incorrect username or password", e);}finalUserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());finalString jwt = jwtTokenUtil.generateToken(userDetails.getUsername());returnResponseEntity.ok(newJwtResponse(jwt));}}

JwtRequest类:

publicclassJwtRequest{privateString username;privateString password;publicJwtRequest(){}publicJwtRequest(String username,String password){this.username = username;this.password = password;}// Getter和Setter方法publicStringgetUsername(){return username;}publicvoidsetUsername(String username){this.username = username;}publicStringgetPassword(){return password;}publicvoidsetPassword(String password){this.password = password;}}

JwtResponse类:

publicclassJwtResponse{privatefinalString jwt;publicJwtResponse(String jwt){this.jwt = jwt;}publicStringgetJwt(){return jwt;}}

用户服务类:

importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.User;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importjava.util.ArrayList;importjava.util.List;@ServicepublicclassMyUserDetailsServiceimplementsUserDetailsService{@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{if("user".equals(username)){List<GrantedAuthority> authorities =newArrayList<>(); authorities.add(newSimpleGrantedAuthority("ROLE_USER"));returnnewUser("user","user123", authorities);}elseif("admin".equals(username)){List<GrantedAuthority> authorities =newArrayList<>(); authorities.add(newSimpleGrantedAuthority("ROLE_ADMIN"));returnnewUser("admin","admin123", authorities);}else{thrownewUsernameNotFoundException("User not found with username: "+ username);}}}

应用配置文件(application.properties):

# 服务器端口 server.port=8080 # 数据库连接信息 spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password # JPA配置 spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true # H2数据库控制台 spring.h2.console.enabled=true spring.h2.console.path=/h2-console # JWT配置 jwt.secret=mysecret jwt.expiration=3600 

测试类:

importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.web.client.TestRestTemplate;importorg.springframework.boot.web.server.LocalServerPort;importorg.springframework.http.HttpEntity;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpMethod;importorg.springframework.http.ResponseEntity;importjava.util.Map;importstaticorg.assertj.core.api.Assertions.assertThat;@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classJwtAuthenticationControllerTests{@LocalServerPortprivateint port;@AutowiredprivateTestRestTemplate restTemplate;@TestvoidtestAuthenticateUser(){JwtRequest request =newJwtRequest("user","user123");ResponseEntity<JwtResponse> response = restTemplate.postForEntity("http://localhost:"+ port +"/authenticate", request,JwtResponse.class);assertThat(response.getStatusCodeValue()).isEqualTo(200);assertThat(response.getBody().getJwt()).isNotNull();}@TestvoidtestAuthenticateAdmin(){JwtRequest request =newJwtRequest("admin","admin123");ResponseEntity<JwtResponse> response = restTemplate.postForEntity("http://localhost:"+ port +"/authenticate", request,JwtResponse.class);assertThat(response.getStatusCodeValue()).isEqualTo(200);assertThat(response.getBody().getJwt()).isNotNull();}@TestvoidtestAuthenticateInvalidUser(){JwtRequest request =newJwtRequest("invalid","invalid123");ResponseEntity<JwtResponse> response = restTemplate.postForEntity("http://localhost:"+ port +"/authenticate", request,JwtResponse.class);assertThat(response.getStatusCodeValue()).isEqualTo(401);}@TestvoidtestGetAllProductsWithUserJwt(){JwtRequest request =newJwtRequest("user","user123");ResponseEntity<JwtResponse> authResponse = restTemplate.postForEntity("http://localhost:"+ port +"/authenticate", request,JwtResponse.class);String token = authResponse.getBody().getJwt();HttpHeaders headers =newHttpHeaders(); headers.add("Authorization","Bearer "+ token);HttpEntity<String> entity =newHttpEntity<>(headers);ResponseEntity<Map> response = restTemplate.exchange("http://localhost:"+ port +"/api/products/",HttpMethod.GET, entity,Map.class);assertThat(response.getStatusCodeValue()).isEqualTo(200);}@TestvoidtestGetTopSellingProductsWithAdminJwt(){JwtRequest request =newJwtRequest("admin","admin123");ResponseEntity<JwtResponse> authResponse = restTemplate.postForEntity("http://localhost:"+ port +"/authenticate", request,JwtResponse.class);String token = authResponse.getBody().getJwt();HttpHeaders headers =newHttpHeaders(); headers.add("Authorization","Bearer "+ token);HttpEntity<String> entity =newHttpEntity<>(headers);ResponseEntity<Map> response = restTemplate.exchange("http://localhost:"+ port +"/api/products/top-selling?topN=3",HttpMethod.GET, entity,Map.class);assertThat(response.getStatusCodeValue()).isEqualTo(200);}}

✅ 结论:JWT是一种基于JSON的开放标准,作用是实现用户认证、用户授权、提供安全的编程模型。

20.6 Spring Boot RESTful API的实际应用场景

在实际开发中,Spring Boot RESTful API的应用场景非常广泛,如:

  • 实现商品的展示与购买。
  • 实现订单的管理。
  • 实现用户的管理。
  • 实现博客的发布与管理。

示例

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.stereotype.Repository;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importorg.springframework.web.bind.annotation.*;importjavax.persistence.*;importjava.util.List;// 产品类@Entity@Table(name ="product")publicclassProduct{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString productId;privateString productName;privatedouble price;privateint sales;publicProduct(){}publicProduct(String productId,String productName,double price,int sales){this.productId = productId;this.productName = productName;this.price = price;this.sales = sales;}// Getter和Setter方法publicLonggetId(){return id;}publicvoidsetId(Long id){this.id = id;}publicStringgetProductId(){return productId;}publicvoidsetProductId(String productId){this.productId = productId;}publicStringgetProductName(){return productName;}publicvoidsetProductName(String productName){this.productName = productName;}publicdoublegetPrice(){return price;}publicvoidsetPrice(double price){this.price = price;}publicintgetSales(){return sales;}publicvoidsetSales(int sales){this.sales = sales;}@OverridepublicStringtoString(){return"Product{"+"id="+ id +",+ productId +'\''+",+ productName +'\''+", price="+ price +", sales="+ sales +'}';}}// 产品Repository@RepositorypublicinterfaceProductRepositoryextendsJpaRepository<Product,Long>{List<Product>findBySalesGreaterThan(int sales);}// 产品Service@ServicepublicclassProductService{@AutowiredprivateProductRepository productRepository;@TransactionalpublicvoidaddProduct(Product product){ productRepository.save(product);}@TransactionalpublicvoidupdateProduct(Product product){ productRepository.save(product);}@TransactionalpublicvoiddeleteProduct(Long id){ productRepository.deleteById(id);}@Transactional(readOnly =true)publicList<Product>getAllProducts(){return productRepository.findAll();}@Transactional(readOnly =true)publicList<Product>getTopSellingProducts(int topN){List<Product> products = productRepository.findBySalesGreaterThan(0); products.sort((p1, p2)-> p2.getSales()- p1.getSales());if(products.size()> topN){return products.subList(0, topN);}return products;}}// 产品控制器@RestController@RequestMapping("/api/products")publicclassProductController{@AutowiredprivateProductService productService;@GetMapping("/")publicList<Product>getAllProducts(){return productService.getAllProducts();}@PostMapping("/")publicvoidaddProduct(@RequestBodyProduct product){ productService.addProduct(product);}@PutMapping("/{id}")publicvoidupdateProduct(@PathVariableLong id,@RequestBodyProduct product){ product.setId(id); productService.updateProduct(product);}@DeleteMapping("/{id}")publicvoiddeleteProduct(@PathVariableLong id){ productService.deleteProduct(id);}@GetMapping("/top-selling")publicList<Product>getTopSellingProducts(@RequestParamint topN){return productService.getTopSellingProducts(topN);}}// 应用启动类@SpringBootApplicationpublicclassProductApplication{publicstaticvoidmain(String[] args){SpringApplication.run(ProductApplication.class, args);}@AutowiredprivateProductService productService;publicvoidrun(String... args){// 初始化数据 productService.addProduct(newProduct("P001","手机",1000.0,100)); productService.addProduct(newProduct("P002","电脑",5000.0,50)); productService.addProduct(newProduct("P003","电视",3000.0,80)); productService.addProduct(newProduct("P004","手表",500.0,200)); productService.addProduct(newProduct("P005","耳机",300.0,150));}}// 测试类@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classProductApplicationTests{@LocalServerPortprivateint port;@AutowiredprivateTestRestTemplate restTemplate;@TestvoidcontextLoads(){}@TestvoidtestGetAllProducts(){List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(5);}@TestvoidtestAddProduct(){Product product =newProduct("P006","平板",2000.0,70); restTemplate.postForEntity("http://localhost:"+ port +"/api/products/", product,Void.class);List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(6);}@TestvoidtestUpdateProduct(){Product product =newProduct("P001","手机",1500.0,120); restTemplate.put("http://localhost:"+ port +"/api/products/1", product);List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products.get(0).getPrice()).isEqualTo(1500.0);}@TestvoidtestDeleteProduct(){ restTemplate.delete("http://localhost:"+ port +"/api/products/2");List<Product> products = restTemplate.getForObject("http://localhost:"+ port +"/api/products/",List.class);assertThat(products).hasSize(4);}@TestvoidtestGetTopSellingProducts(){List<Product> topSellingProducts = restTemplate.getForObject("http://localhost:"+ port +"/api/products/top-selling?topN=3",List.class);assertThat(topSellingProducts).hasSize(3);assertThat(topSellingProducts.get(0).getProductId()).isEqualTo("P004");assertThat(topSellingProducts.get(1).getProductId()).isEqualTo("P005");assertThat(topSellingProducts.get(2).getProductId()).isEqualTo("P001");}}

输出结果

  • 访问http://localhost:8080/api/products/:返回产品列表。
  • 访问http://localhost:8080/api/products/top-selling?topN=3:返回销量TOP3的产品列表。

✅ 结论:在实际开发中,Spring Boot RESTful API的应用场景非常广泛,需要根据实际问题选择合适的RESTful API设计。

总结

本章我们学习了Spring Boot RESTful API开发与测试,包括RESTful API的定义与特点、Spring Boot RESTful API的开发、Spring Boot RESTful API的测试、Spring Boot RESTful API的认证与授权、Spring Boot RESTful API的实际应用场景,学会了在实际开发中处理RESTful API问题。其中,RESTful API的定义与特点、Spring Boot RESTful API的开发、Spring Boot RESTful API的测试、Spring Boot RESTful API的认证与授权、Spring Boot RESTful API的实际应用场景是本章的重点内容。从下一章开始,我们将学习Spring Boot的其他组件、微服务等内容。

Read more

260223-Gartner Hype Cycle 2026 AI 报告调研与解读

260223-Gartner Hype Cycle 2026 AI 报告调研与解读

2026年人工智能技术成熟度曲线战略报告与深度总结 在2026年的全球科技版图中,人工智能(AI)正经历着从“技术狂热”向“运营务实”的根本性转变。根据行业领先的分析机构研究,2026年被界定为人工智能进入“幻灭期”(Trough of Disillusionment)的关键节点1。这一阶段并非预示着技术的失败,而是一次必要的情绪修正和市场洗礼。企业不再盲目追求宏大的“登月计划”,转而将注意力集中在可预测的投资回报率(ROI)、系统架构的稳健性以及跨职能的运营集成上1。与此同时,2026年也标志着人工智能从单一的模型应用演变为复杂的智能系统协同,通过代理式AI(Agentic AI)、多代理系统(Multiagent Systems)以及主权AI(Sovereign AI)等核心趋势,重新定义了全球商业竞争的规则3。 第一部分:2026年全球AI经济格局与支出分析 2026年全球人工智能支出预计将达到2.52万亿美元,较2025年增长44%1。这一巨大的财务投入反映了企业对AI基础设施建设的持续承诺。尽管市场处于幻灭期,但这种增长主要由技术供应商构建AI基础架构的战略驱动,基础

By Ne0inhk
Flutter 组件 genkit 的适配 鸿蒙Harmony 实战 - 驾驭大模型开发套件、实现鸿蒙端 AI 智能流式响应与提示词工程自动化方案

Flutter 组件 genkit 的适配 鸿蒙Harmony 实战 - 驾驭大模型开发套件、实现鸿蒙端 AI 智能流式响应与提示词工程自动化方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 genkit 的适配 鸿蒙Harmony 实战 - 驾驭大模型开发套件、实现鸿蒙端 AI 智能流式响应与提示词工程自动化方案 前言 在鸿蒙(OpenHarmony)生态向智能化、全场景自动化的演进过程中,“生成式 AI(Generative AI)”不再仅仅是一个噱头,而是重塑应用交互逻辑的核心底座。面对日益复杂的 LLM(大语言模型)调用链路、层出不穷的提示词(Prompt)版本管理以及对实时流式响应(Streaming)的严苛要求。如果仅仅依靠原始的 HTTP POST 请求。那么不仅会导致开发效率极低。更难以应对 AI 业务中常见的“幻觉审计”与“多模型动态切换”等高阶挑战方案。 我们需要一种“开发者友好、

By Ne0inhk
人工智能:自然语言处理与计算机视觉的融合应用

人工智能:自然语言处理与计算机视觉的融合应用

人工智能:自然语言处理与计算机视觉的融合应用 学习目标 💡 理解自然语言处理(NLP)与计算机视觉(CV)融合的基本概念和重要性 💡 掌握NLP与CV融合的主要方法和技术 💡 学会使用前沿模型(如CLIP、ALIGN、ViLT)进行多模态融合 💡 理解融合应用的场景(如图像字幕生成、视觉问答、多模态检索) 💡 通过实战项目,开发一个图像字幕生成应用 重点内容 * NLP与CV融合的基本概念 * 主要融合方法和技术 * 前沿融合模型(CLIP、ALIGN、ViLT) * 融合应用场景(图像字幕生成、视觉问答、多模态检索) * 实战项目:图像字幕生成应用开发 一、NLP与CV融合的基本概念 1.1 多模态学习的重要性 多模态学习(Multimodal Learning)是指处理和理解来自多个模态(如文本、图像、音频)的数据的过程。NLP与CV的融合是多模态学习的一个重要分支,它结合了文本理解和图像分析的能力,使计算机能够更全面地理解和解释现实世界的信息。 1.

By Ne0inhk
【AI 学习】解锁Claude Skills:开启AI应用新维度

【AI 学习】解锁Claude Skills:开启AI应用新维度

一、Claude Skills 是什么? 1.1 官方定义剖析 Claude Skills 是 Anthropic 公司为其人工智能模型 Claude 打造的一项创新性的功能扩展机制。从 Anthropic 的官方阐述来看,它本质上是一种标准化的、可复用的模块化系统,旨在赋予 Claude 执行特定领域复杂任务的能力 。通过 Claude Skills,用户能够让 Claude 迅速化身为专业领域的 “专家”,完成从常规的文本处理到复杂的业务流程自动化等多样化任务。 举例来说,在文档处理领域,以往使用普通的 AI 模型处理合同文档时,可能需要多次详细地输入指令,要求其提取关键条款、检查格式规范等,且每次处理都需重复这些指令,而借助 Claude Skills,用户只需创建一个专门用于合同处理的 Skill,将合同处理的流程、关键信息提取规则等内容封装其中,后续再处理合同时,Claude 就能自动调用该 Skill,

By Ne0inhk