前言:

本文按照Spring官网构建REST服务的步骤测试,可以得到结论:

到底什么样的风格才是RESTful风格呢?

1,约束请求命令如下:

  • GET,获取资源。例如:/employees表示获取列表资源,/employees/{id}表示获取单个对象资源。
  • POST,新增。例如:/employees,body为json对象,表示新增。
  • PUT,更新。例如:/employees/{id},body为json对象,表示更新。
  • DELETE,删除。例如: /employees/{id},表示更新。

2,约束返回结果:  返回数据为列表,则每个对象资源附加自己的资源链接、列表资源链接以及可操作性链接。

参考链接:

官网demo按照如下步骤介绍如何使用SpringBoot构建REST服务,并强调

  • Pretty URLs like /employees/3 aren’t REST.

  • Merely using GETPOST, etc. aren’t REST.

  • Having all the CRUD operations laid out aren’t REST.

一、普通的http服务

pom.xml文件如下所示:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.htkm.demo</groupId>
  6. <artifactId>demo-restful</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <name>demo-restful</name>
  9. <description>Demo project for Spring Boot</description>
  10. <properties>
  11. <java.version>1.8</java.version>
  12. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  13. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  14. <spring-boot.version>2.3.0.RELEASE</spring-boot.version>
  15. </properties>
  16. <dependencies>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-web</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-hateoas</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-data-jpa</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.projectlombok</groupId>
  31. <artifactId>lombok</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>com.h2database</groupId>
  35. <artifactId>h2</artifactId>
  36. <scope>runtime</scope>
  37. </dependency>
  38. </dependencies>
  39. <dependencyManagement>
  40. <dependencies>
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-dependencies</artifactId>
  44. <version>${spring-boot.version}</version>
  45. <type>pom</type>
  46. <scope>import</scope>
  47. </dependency>
  48. </dependencies>
  49. </dependencyManagement>
  50. <build>
  51. <plugins>
  52. <plugin>
  53. <groupId>org.apache.maven.plugins</groupId>
  54. <artifactId>maven-compiler-plugin</artifactId>
  55. <configuration>
  56. <source>1.8</source>
  57. <target>1.8</target>
  58. <encoding>UTF-8</encoding>
  59. </configuration>
  60. </plugin>
  61. <plugin>
  62. <groupId>org.springframework.boot</groupId>
  63. <artifactId>spring-boot-maven-plugin</artifactId>
  64. </plugin>
  65. </plugins>
  66. </build>
  67. </project>

JPA类型的实体类:

  1. @Data
  2. @Entity
  3. public class Employee {
  4. @Id
  5. @GeneratedValue
  6. private Long id;
  7. private String name;
  8. private String role;
  9. public Employee() {}
  10. public Employee(String name, String role) {
  11. this.name = name;
  12. this.role = role;
  13. }
  14. }

使用H2内存数据库,创建测试数据库:

  1. @Configuration
  2. @Slf4j
  3. public class LoadDatabase {
  4. @Bean
  5. CommandLineRunner initDatabase(EmployeeRepository repository) {
  6. return args -> {
  7. log.info("Preloading " + repository.save(new Employee("Bilbo Baggins", "burglar")));
  8. log.info("Preloading " + repository.save(new Employee("Frodo Baggins", "thief")));
  9. };
  10. }
  11. }

JPA类型的DAO:

  1. public interface EmployeeRepository extends JpaRepository<Employee, Long> {}

EmployeeContrller控制器:

  1. @RestController
  2. public class EmployeeController {
  3. @Autowired
  4. private EmployeeRepository repository;
  5.  
  6. @GetMapping("/employees")
  7. List<Employee> all() {
  8. return repository.findAll();
  9. }
  10. @PostMapping("/employees")
  11. Employee newEmployee(@RequestBody Employee newEmployee) {
  12. return repository.save(newEmployee);
  13. }
  14. @GetMapping("/employees/{id}")
  15. Employee one(@PathVariable Long id) {
  16. return repository.findById(id)
  17. .orElseThrow(() -> new EmployeeNotFoundException(id));
  18. }
  19. @PutMapping("/employees/{id}")
  20. Employee replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
  21. return repository.findById(id)
  22. .map(employee -> {
  23. employee.setName(newEmployee.getName());
  24. employee.setRole(newEmployee.getRole());
  25. return repository.save(employee);
  26. })
  27. .orElseGet(() -> {
  28. newEmployee.setId(id);
  29. return repository.save(newEmployee);
  30. });
  31. }
  32. @DeleteMapping("/employees/{id}")
  33. void deleteEmployee(@PathVariable Long id) {
  34. repository.deleteById(id);
  35. }
  36. }

自定义异常:

  1. public class EmployeeNotFoundException extends RuntimeException {
  2. public EmployeeNotFoundException(Long id) {
  3. super("Could not find employee " + id);
  4. }
  5. }

增加@ControllerAdvice注解,实现异常处理器:

  1. @ControllerAdvice
  2. public class EmployeeNotFoundAdvice {
  3. @ResponseBody
  4. @ExceptionHandler(EmployeeNotFoundException.class)
  5. @ResponseStatus(HttpStatus.NOT_FOUND)
  6. String employeeNotFoundHandler(EmployeeNotFoundException ex) {
  7. return ex.getMessage();
  8. }
  9. }

使用postman或者curl工具测试执行:

1,GET http://localhost:8080/employees

  1. [
  2. {
  3. "id": 1,
  4. "name": "Bilbo Baggins",
  5. "role": "burglar"
  6. },
  7. {
  8. "id": 2,
  9. "name": "Frodo Baggins",
  10. "role": "thief"
  11. }
  12. ]

2,GET http://localhost:8080/employees/1

  1. {
  2. "id": 1,
  3. "name": "Bilbo Baggins",
  4. "role": "burglar"
  5. }

3,DELETE http://localhost:8080/employees/1 资源被删除

二、restful的http服务

pom.xml增加hateoas依赖 :

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-hateoas</artifactId>
  4. </dependency>

控制器更改,在原来的返回json基础之上附加操作链接,红色加粗部分可以重构简化,在下一章节。

  1. @GetMapping("/employees")
  2. CollectionModel<EntityModel<Employee>> all() {
  3. List<EntityModel<Employee>> employees = repository.findAll().stream()
  4. .map(employee -> EntityModel.of(employee,
  5. linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(), // 附加自身链接
  6. linkTo(methodOn(EmployeeController.class).all()).withRel("employees"))) // 附加all操作链接
  7. .collect(Collectors.toList());
  8. return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel()); // 附加自身链接
  9. }
  10. @GetMapping("/employees/{id}")
  11. EntityModel<Employee> one(@PathVariable Long id) {
  12. Employee employee = repository.findById(id) //
  13. .orElseThrow(() -> new EmployeeNotFoundException(id));
  14.  
  15. return EntityModel.of(employee, linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel(), // 附加自身链接
  16. linkTo(methodOn(EmployeeController.class).all()).withRel("employees")); // 附加all操作链接
  17. }

Postman测试执行

1,GET http://localhost:8080/employees,可以看到附加链接

  1. {
  2. "_embedded": {
  3. "employeeList": [
  4. {
  5. "id": 1,
  6. "name": "Bilbo Baggins",
  7. "role": "burglar",
  8. "_links": {
  9. "self": {
  10. "href": "http://localhost:8080/employees/1"
  11. },
  12. "employees": {
  13. "href": "http://localhost:8080/employees"
  14. }
  15. }
  16. },
  17. {
  18. "id": 2,
  19. "name": "Frodo Baggins",
  20. "role": "thief",
  21. "_links": {
  22. "self": {
  23. "href": "http://localhost:8080/employees/2"
  24. },
  25. "employees": {
  26. "href": "http://localhost:8080/employees"
  27. }
  28. }
  29. }
  30. ]
  31. },
  32. "_links": {
  33. "self": {
  34. "href": "http://localhost:8080/employees"
  35. }
  36. }
  37. }

2,GET http://localhost:8080/employees/1

  1. {
  2. "id": 1,
  3. "name": "Bilbo Baggins",
  4. "role": "burglar",
  5. "_links": {
  6. "self": {
  7. "href": "http://localhost:8080/employees/1"
  8. },
  9. "employees": {
  10. "href": "http://localhost:8080/employees"
  11. }
  12. }
  13. }

三、扩展的restful服务

扩展实体,将name拆分为fristname和lastname,同时通过增加虚拟的get/set保留name属性:

  1. @Data
  2. @Entity
  3. public class Employee {
  4. @Id
  5. @GeneratedValue
  6. private Long id;
  7. private String firstName;
  8. private String lastName;
  9. private String role;
  10. public Employee() {}
  11. public Employee(String firstName, String lastName, String role) {
  12. this.firstName = firstName;
  13. this.lastName = lastName;
  14. this.role = role;
  15. }
  16. public String getName() {
  17. return this.firstName + " " + this.lastName;
  18. }
  19. public void setName(String name) {
  20. String[] parts = name.split(" ");
  21. this.firstName = parts[0];
  22. this.lastName = parts[1];
  23. }
  24. }

重构简化代码,增加对象包装处理类 :

  1. @Component
  2. class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> {
  3.  
  4. @Override
  5. public EntityModel<Employee> toModel(Employee employee) {
  6. return EntityModel.of(employee,
  7. linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(),
  8. linkTo(methodOn(EmployeeController.class).all()).withRel("employees"));
  9. }
  10. }

控制器更改:

  1. @RestController
  2. public class EmployeeController {
  3.  
  4. @Autowired
  5. private EmployeeRepository repository;
  6.  
  7. @Autowired
  8. private EmployeeModelAssembler assembler;
  9.  
  10. @GetMapping("/employees")
  11. CollectionModel<EntityModel<Employee>> all() {
  12. List<EntityModel<Employee>> employees = repository.findAll().stream()
  13. .map(assembler::toModel) // 包装对象
  14. .collect(Collectors.toList());
  15. return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel()); // 附加自身链接
  16. }
  17.  
  18. @PostMapping("/employees")
  19. ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
  20. EntityModel<Employee> entityModel = assembler.toModel(repository.save(newEmployee));
  21. return ResponseEntity
  22. .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri())
  23. .body(entityModel); // 返回状态码201,增加Location头 http://localhost:8080/employees/3
  24.  
  25. }
  26.  
  27. @GetMapping("/employees/{id}")
  28. EntityModel<Employee> one(@PathVariable Long id) {
  29. Employee employee = repository.findById(id) //
  30. .orElseThrow(() -> new EmployeeNotFoundException(id));
  31.  
  32. return assembler.toModel(employee); // 包装对象
  33. }
  34.  
  35. @PutMapping("/employees/{id}")
  36. ResponseEntity<?> replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
  37. Employee updatedEmployee = repository.findById(id) //
  38. .map(employee -> {
  39. employee.setName(newEmployee.getName());
  40. employee.setRole(newEmployee.getRole());
  41. return repository.save(employee);
  42. }) //
  43. .orElseGet(() -> {
  44. newEmployee.setId(id);
  45. return repository.save(newEmployee);
  46. });
  47. EntityModel<Employee> entityModel = assembler.toModel(updatedEmployee);
  48. return ResponseEntity
  49. .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri())
  50. .body(entityModel); // 返回状态码201,增加Location头 http://localhost:8080/employees/3
  51.  
  52. }
  53.  
  54. @DeleteMapping("/employees/{id}")
  55. ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
  56. repository.deleteById(id);
  57. return ResponseEntity.noContent().build();// 返回状态码204
  58.  
  59. }
  60. }

curl测试执行

$ curl -v -X POST localhost:8080/employees -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}'

请求相应状态为201,包含Location 响应头

  1. > POST /employees HTTP/1.1
  2. > Host: localhost:8080
  3. > User-Agent: curl/7.54.0
  4. > Accept: */*
  5. > Content-Type:application/json
  6. > Content-Length: 46
  7. >
  8. < Location: http://localhost:8080/employees/3
  9. < Content-Type: application/hal+json;charset=UTF-8
  10. < Transfer-Encoding: chunked
  11. < Date: Fri, 10 Aug 2018 19:44:43 GMT
  12. <
  13. {
  14. "id": 3,
  15. "firstName": "Samwise",
  16. "lastName": "Gamgee",
  17. "role": "gardener",
  18. "name": "Samwise Gamgee",
  19. "_links": {
  20. "self": {
  21. "href": "http://localhost:8080/employees/3"
  22. },
  23. "employees": {
  24. "href": "http://localhost:8080/employees"
  25. }
  26. }
  27. }

$ curl -v -X PUT localhost:8080/employees/3 -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}'

  1. > PUT /employees/3 HTTP/1.1
  2. > Host: localhost:8080
  3. > User-Agent: curl/7.54.0
  4. > Accept: */*
  5. > Content-Type:application/json
  6. > Content-Length: 49
  7. >
  8. < HTTP/1.1 201
  9. < Location: http://localhost:8080/employees/3
  10. < Content-Type: application/hal+json;charset=UTF-8
  11. < Transfer-Encoding: chunked
  12. < Date: Fri, 10 Aug 2018 19:52:56 GMT
  13. {
  14. "id": 3,
  15. "firstName": "Samwise",
  16. "lastName": "Gamgee",
  17. "role": "ring bearer",
  18. "name": "Samwise Gamgee",
  19. "_links": {
  20. "self": {
  21. "href": "http://localhost:8080/employees/3"
  22. },
  23. "employees": {
  24. "href": "http://localhost:8080/employees"
  25. }
  26. }
  27. }

$ curl -v -X DELETE localhost:8080/employees/1

  1. > DELETE /employees/1 HTTP/1.1
  2. > Host: localhost:8080
  3. > User-Agent: curl/7.54.0
  4. > Accept: */*
  5. >
  6. < HTTP/1.1 204
  7. < Date: Fri, 10 Aug 2018 21:30:26 GMT

四、附加可操作链接的restful服务

订单实体转换器,如果订单状态为可执行的订单则附加取消和完成链接 :

  1. @Component
  2. public class OrderModelAssembler implements RepresentationModelAssembler<Order, EntityModel<Order>> {
  3. @Override
  4. public EntityModel<Order> toModel(Order order) {
  5. // Unconditional links to single-item resource and aggregate root
  6. EntityModel<Order> orderModel = EntityModel.of(order,
  7. linkTo(methodOn(OrderController.class).one(order.getId())).withSelfRel(),
  8. linkTo(methodOn(OrderController.class).all()).withRel("orders"));
  9. // Conditional links based on state of the order
  10. if (order.getStatus() == Status.IN_PROGRESS) {
  11. orderModel.add(linkTo(methodOn(OrderController.class).cancel(order.getId())).withRel("cancel")); // 附加cancel链接
  12. orderModel.add(linkTo(methodOn(OrderController.class).complete(order.getId())).withRel("complete")); // 附加complete链接
  13. }
  14. return orderModel;
  15. }
  16. }

控制器中取消和完成操作

  1. @DeleteMapping("/orders/{id}/cancel")
  2. ResponseEntity<?> cancel(@PathVariable Long id) {
  3.  
  4. Order order = orderRepository.findById(id)
  5. .orElseThrow(() -> new OrderNotFoundException(id));
  6.  
  7. if (order.getStatus() == Status.IN_PROGRESS) {
  8. order.setStatus(Status.CANCELLED);
  9. return ResponseEntity.ok(assembler.toModel(orderRepository.save(order)));
  10. }
  11. return ResponseEntity
  12. .status(HttpStatus.METHOD_NOT_ALLOWED)
  13. .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE)
  14. .body(Problem.create()
  15. .withTitle("Method not allowed")
  16. .withDetail("You can't cancel an order that is in the " + order.getStatus() + " status"));
  17. }
  18. @PutMapping("/orders/{id}/complete")
  19. ResponseEntity<?> complete(@PathVariable Long id) {
  20.  
  21. Order order = orderRepository.findById(id)
  22. .orElseThrow(() -> new OrderNotFoundException(id));
  23.  
  24. if (order.getStatus() == Status.IN_PROGRESS) {
  25. order.setStatus(Status.COMPLETED);
  26. return ResponseEntity.ok(assembler.toModel(orderRepository.save(order)));
  27. }
  28. return ResponseEntity
  29. .status(HttpStatus.METHOD_NOT_ALLOWED)
  30. .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE)
  31. .body(Problem.create()
  32. .withTitle("Method not allowed")
  33. .withDetail("You can't complete an order that is in the " + order.getStatus() + " status"));
  34. }

PostMan测试执行:

1,GET http://localhost:8080

  1. {
  2. "_links": {
  3. "employees": {
  4. "href": "http://localhost:8080/employees"
  5. },
  6. "orders": {
  7. "href": "http://localhost:8080/orders"
  8. }
  9. }
  10. }

2,GET http://localhost:8080/orders

  1. {
  2. "_embedded": {
  3. "orderList": [
  4. {
  5. "id": 3,
  6. "description": "MacBook Pro",
  7. "status": "COMPLETED",
  8. "_links": {
  9. "self": {
  10. "href": "http://localhost:8080/orders/3"
  11. },
  12. "orders": {
  13. "href": "http://localhost:8080/orders"
  14. }
  15. }
  16. },
  17. {
  18. "id": 4,
  19. "description": "iPhone",
  20. "status": "IN_PROGRESS",
  21. "_links": {
  22. "self": {
  23. "href": "http://localhost:8080/orders/4"
  24. },
  25. "orders": {
  26. "href": "http://localhost:8080/orders"
  27. },
  28. "cancel": {
  29. "href": "http://localhost:8080/orders/4/cancel"
  30. },
  31. "complete": {
  32. "href": "http://localhost:8080/orders/4/complete"
  33. }
  34. }
  35. }
  36. ]
  37. },
  38. "_links": {
  39. "self": {
  40. "href": "http://localhost:8080/orders"
  41. }
  42. }
  43. }

3,DELETE http://localhost:8080/orders/3/cancel

  1. {
  2. "title": "Method not allowed",
  3. "detail": "You can't cancel an order that is in the COMPLETED status"
  4. }

4,DELETE http://localhost:8080/orders/4/cancel

  1. {
  2. "id": 4,
  3. "description": "iPhone",
  4. "status": "CANCELLED",
  5. "_links": {
  6. "self": {
  7. "href": "http://localhost:8080/orders/4"
  8. },
  9. "orders": {
  10. "href": "http://localhost:8080/orders"
  11. }
  12. }
  13. }

5,POST localhost:8080/orders

设置header为Content-Type:application/json

body为{"name": "Samwise Gamgee", "role": "gardener"}'

  1. {
  2. "id": 5,
  3. "description": "新的订单",
  4. "status": "IN_PROGRESS",
  5. "_links": {
  6. "self": {
  7. "href": "http://localhost:8080/orders/5"
  8. },
  9. "orders": {
  10. "href": "http://localhost:8080/orders"
  11. },
  12. "cancel": {
  13. "href": "http://localhost:8080/orders/5/cancel"
  14. },
  15. "complete": {
  16. "href": "http://localhost:8080/orders/5/complete"
  17. }
  18. }
  19. }

五、总结

RESTful风格的http服务,包含以下特征(百度百科摘录):

  • 1、每一个URI代表1种资源;
  • 2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
  • 3、通过操作资源的表现形式来操作资源;
  • 4、资源的表现形式是XML或HTML;
  • 5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。

官网demo描述:

What’s important to realize is that REST, however ubiquitous, is not a standard, per se, but an approach, a style, a set of constraints on your architecture that can help you build web-scale systems.

到底什么样的风格才是为RESTful风格呢?

首先约束请求命令如下:

  • GET,获取资源。例如:/employees表示获取列表资源,/employees/{id}表示获取单个对象资源。
  • POST,新增。例如:/employees,body为json对象,表示新增。
  • PUT,更新。例如:/employees/{id},body为json对象,表示更新。
  • DELETE,删除。例如: /employees/{id},表示更新。

其次约束返回结果:  返回数据为列表,则每个对象资源附加自己的资源链接、列表资源链接以及可操作性链接。

以下例子是可能的返回结果:

  1. {
  2. "_embedded": {
  3. "orderList": [
  4. {
  5. "id": 3,
  6. "description": "MacBook Pro",
  7. "status": "COMPLETED",
  8. "_links": {
  9. "self": {
  10. "href": "http://localhost:8080/orders/3"
  11. },
  12. "orders": {
  13. "href": "http://localhost:8080/orders"
  14. }
  15. }
  16. },
  17. {
  18. "id": 4,
  19. "description": "iPhone",
  20. "status": "IN_PROGRESS",
  21. "_links": {
  22. "self": {
  23. "href": "http://localhost:8080/orders/4"
  24. },
  25. "orders": {
  26. "href": "http://localhost:8080/orders"
  27. },
  28. "cancel": {
  29. "href": "http://localhost:8080/orders/4/cancel"
  30. },
  31. "complete": {
  32. "href": "http://localhost:8080/orders/4/complete"
  33. }
  34. }
  35. }
  36. ]
  37. },
  38. "_links": {
  39. "self": {
  40. "href": "http://localhost:8080/orders"
  41. }
  42. }
  43. }

使用SpringBoot构建REST服务-什么是REST服务的更多相关文章

  1. Springboot 构建http服务,返回的http行是'HTTP/1.1 200' 无状态码描述 客户端解析错误

    ————————————————————————————————————————— *** 响应的数据格式  HTTP/1.1 200 OK  Server: Apache-Coyote/1.1  A ...

  2. SpringBoot 搭建基于 MinIO 的高性能存储服务

    1.什么是MinIO MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储.它与Amazon S3云存储服务兼容.使用MinIO构建用于机器学习,分析和应用程序数据工作负载的 ...

  3. Spring-Boot构建多模块项目

    Spring-Boot构建多模块项目 功能模块单独项目开发,可以将一个庞大的项目分解成多个小项目,便于细分开发 Maven多模块项目不能独立存在,必须有一个介质来包含. 1.创建一个Maven 项目, ...

  4. java springboot activemq 邮件短信微服务,解决国际化服务的国内外兼容性问题,含各服务商调研情况

    java springboot activemq 邮件短信微服务,解决国际化服务的国内外兼容性问题,含各服务商调研情况 邮件短信微服务 spring boot 微服务 接收json格式参数 验证参数合 ...

  5. SpringBoot 构建RestFul API 含单元测试

    相关博文: 从消费者角度评估RestFul的意义 SpringBoot 构建RestFul API 含单元测试 首先,回顾并详细说明一下在快速入门中使用的  @Controller .  @RestC ...

  6. 基于SpringBoot构建分模块项目

    前言 步骤过于详细,多图慎入!!! 假设一个场景,要开发一个4s店维修部的办公系统,其功能有:前台接待,维修抢单,财务结算,库存管理.于是我们创建一个项目balabalabala写完交工. 一段时间后 ...

  7. Azure 项目构建 – 构建直播教学系统之媒体服务篇

    本课程主要介绍如何在 Azure 平台上快速构建和部署基于 Azure 媒体服务的点播和直播教学系统, 实践讲解如何使用 Azure 门户创建媒体服务, 配置视频流进行传输,连接 CDN 加速等. 具 ...

  8. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  9. lucene构建restful风格的简单搜索引擎服务

    来自于本人博客: lucene构建restful风格的简单搜索引擎服务 本人的博客如今也要改成使用lucene进行全文检索的功能,因此在这里把代码贴出来与大家分享 一,文件夹结构: 二,配置文件: 总 ...

随机推荐

  1. Java实现 LeetCode 60 第k个排列

    60. 第k个排列 给出集合 [1,2,3,-,n],其所有元素共有 n! 种排列. 按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下: "123" &q ...

  2. Java实现字符串的旋转

    1 问题描述 给定一个字符串,要求将字符串前面的若干个字符移到字符串的尾部.例如,将字符串"abcdef"的前3个字符'a'.'b'和'c'移到字符串的尾部,那么原字符串将变成&q ...

  3. Java实现第九届蓝桥杯猴子分香蕉

    猴子分香蕉 题目描述 5只猴子是好朋友,在海边的椰子树上睡着了.这期间,有商船把一大堆香蕉忘记在沙滩上离去. 第1只猴子醒来,把香蕉均分成5堆,还剩下1个,就吃掉并把自己的一份藏起来继续睡觉. 第2只 ...

  4. Linux 权限管理-ACL权限

    ACL权限是为了在现有的所有者.所属组.其他人不够使用的情况下使用的,使用它必须保证文件所在的分区支持ACL df -h:查看系统所有分区信息 dumpe2fs -h /dev/vda1,可以查看分区 ...

  5. (二)SQL注入常用的内置函数整理(以MySql为例)

    [1]@@datadir 函数作用:返回数据库的存储目录构造SQL语句 select @@datadir;   [2]@@version_compile_os 函数作用:查看服务器的操作系统SQL语句 ...

  6. CUDA优化

    cuda程序优化 一:程序优化概述 1:精度 在关键步骤使用双精度,其他步骤使用单精度,以获得指令吞吐量和精度的平衡. 2:延迟 先缓冲一定量数据,在交给GPU计算.可以获得较高的数据吞吐量. 3:计 ...

  7. iOS-PCH File的快速导入方法和使用

    PCH的文件的用途:      在实际的项目开发中,如果很多地方都在使用某个类的头文件,很多地方都在使用同一个”宏”的时候:很多地方用到了NSLog()函数, 在app发布的时候,想清除掉时,此时就需 ...

  8. Istio的运维-诊断工具(istio 系列五)

    Istio的运维-诊断工具 在参考官方文档的时候发现环境偶尔会出现问题,因此插入一章与调试有关的内容,便于简单问题的定位.涵盖官方文档的诊断工具章节 目录 Istio的运维-诊断工具 使用istioc ...

  9. redis的5种数据结构和基本操作

    1.字符串(string) 1.1设置值 set key value [ex seconds] [px milliseconds] [nx|xx] 例如: 127.0.0.1:6379> set ...

  10. Codeforces Round #561 (Div. 2) A Tale of Two Lands 【二分】

    A Tale of Two Lands 题目链接(点击) The legend of the foundation of Vectorland talks of two integers xx and ...