SPRING 扩展组件

  

1、spring data

 

2、spring security

 

3、自动配置

bean装配、属性注入

 

 

 

配置HTTPS 

自定义keystore - 使用RSA

$ keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA

在配置文件中加载https

#  配置SSL
#  ssl:
#    key-store: classpath:mykeys.jkd
#    key-store-password: lipengfei
#    key-password: lipengfei

 

4、配置(注入)属性

package com.jony.boot5.boottest.util;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

@Data
@Component
@ConfigurationProperties(prefix = "taco.orders")
/**
 * 使用bean 配置为一个配置类
 */

@Validated // 编译验证
public class OrderProps {

    @Max(value = 30)
    @Min(value = 5)
    private int pageSize;
}
taco:
  orders:
    pageSize: 10

配置属性元数据

1)依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

2)文件(idea自动提示可创建)

 

 

 

{
  "properties": [
    {
      "name": "taco.orders.pageSize",
      "type": "java.lang.String",
      "description": "Description for taco.orders.pageSize."
    },
    {
      "name": "taco.orders.rowNum",
      "type": "java.lang.String",
      "description": "Description for taco.orders.rowNum."
    }
  ]
}

 

3)profile (配置模式切换)

spring.profiles.active={profile-name}

创建模式文件-规则 application-{profile-name}.properties 或 application-{profile-name}.yml

在YAML 中可以通过‘---’三横线创建profiles 模式

taco:
  orders:
    rowNum: 20
    pageSize: 20

# yml 支持 使用 application-{profie名}.xml 和在同一个文件中设置数据(使用三个分割线 ------
spring:
  profiles: prod
server:
  port: 8080
taco:
  orders:
    pageSize: 10

在环境变量中设置profiles

linux :export SPRING_PROFILES_ACTIVE=prod,dev

windows:

 

 

 在windows环境下通过jar启动会加载环境变量,在idea开发环境中不生效

4)bean 的profile 创建使用@profile注解

 

 

 

5、rest 服务

通过rest服务实现前后端分离

 

 

 

 

 

 

使用postman测试接口服务

put 与patch

put - 针对全部替换

patch - 针对部分更新

 

 

 hibernate - id-uuid 序列

    @Id
    @GenericGenerator(name = "idGenerator", strategy = "uuid")
    @GeneratedValue(generator = "idGenerator")
    private String id;

 

@PrePersist 、@PreUpdate

hibernate缓存ResultSet

@PrePersist 在数据持久化(create)的情况下调用

@PreUpdate 在数据库中数据发生变化下调用

 

package com.jony.boot5.boottest.controller;

import com.jony.boot5.boottest.entity.Country;
import com.jony.boot5.boottest.repository.CountryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@RestController
//处理rest请求,accept 内容为json格式
@RequestMapping(value = "/rest", produces = {"application/json"})
//允许跨域请求
@CrossOrigin(origins = {"*"})
public class RestfulController {
    @Autowired
    private CountryRepository repository;

    @GetMapping
    public Iterable findAll() {
        return repository.findAll();
    }

    @GetMapping(path = "/{id}")
    public ResponseEntity<Country> findById(@PathVariable("id") String id) {
        Optional<Country> optional = repository.findById(id);
//        返回状态码
        if (optional.isPresent()) {
//            返回ok
            return new ResponseEntity<Country>(optional.get(), HttpStatus.OK);
        }
//返回404
        return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
    }

    /**
     * 处理post请求
     *
     * @return
     */
    @PostMapping(consumes = {"application/json"})
//    consumes 指定用户输入的json数据
    @ResponseStatus(code = HttpStatus.CREATED)
//    返回created 状态码
    public Country postCountry(@RequestBody Country country) {
        Country result = repository.save(country);
        return result;
    }

    @PutMapping(path = "/{id}",consumes = {"application/json"})
    /**
     * 全部更新或插入
     */
    public Country putCountry(@RequestBody Country country,@PathVariable("id") String id){
        country.setId(id);
        return repository.save(country);
    }

    @PatchMapping(path = "/{id}")
    /**
     * 更新
     */
    public Country patchCountry(@PathVariable("id")String id,@RequestBody Country patch){
        Country country = repository.findById(id).get();
        if(country!=null){
//            IF 判断设置
        }
        return repository.save(country);
    }
}

针对restful - csrf 伪造

解决方法:

1、实现RequestMacher 接口

2、每次请求中设置csrf 值

3、使用oauth2 

package com.jony.boot5.boottest.util;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Component
/**
 * 注册bean 重写matcher 组件
 */
@ConfigurationProperties(prefix = "custom.csrf")
public class CsrfRequestMacher implements RequestMatcher {
    private final HashSet<String> allowedMethods = new HashSet<>(
            Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

    private Set<String> excludeUrls;

    public void setExcludeUrls(Set<String> excludeUrls) {
        this.excludeUrls = excludeUrls;
    }

    @Override
    public boolean matches(HttpServletRequest request) {
//        如果是设置在允许范围内的post或put 等请求则直接放过
        for (String exclude:excludeUrls) {
            if(request.getServletPath().startsWith(exclude)){
                return false;
            }
        }
//        if (excludeUrls.contains(request.getServletPath())) {
//            return false;
//        }
        return !this.allowedMethods.contains(request.getMethod());
    }
}

 

6、超媒体链接(数据显示自身信息与链接)

添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

 

 关于HATEOAS 稳定新版本更改部分接口的名称

 

新建资源类

package com.jony.boot5.boottest.entity;

import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.hateoas.RepresentationModel;

import javax.persistence.*;
import java.sql.Timestamp;

//JPA entity
@Data
//@NoArgsConstructor(access=AccessLevel.PRIVATE)
@Entity
//@Table(name = "country")
public class CountryResource extends RepresentationModel {
    private String countryname;

    private String countrycode;

    private byte[] img;

    private Timestamp now;

    @Column(name = "update_date")
    private Timestamp update;

    @PrePersist
    void createDate() {
        now = new Timestamp(System.currentTimeMillis());
    }

    @PreUpdate
    void updateDate(){
        update = new Timestamp(System.currentTimeMillis());
    }
}

 

 

 

 

 

 

 

package com.jony.boot5.boottest.entity.assembler;

import com.jony.boot5.boottest.controller.RestfulController;
import com.jony.boot5.boottest.entity.Country;
import com.jony.boot5.boottest.entity.CountryResource;
import org.springframework.hateoas.server.RepresentationModelAssembler;
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;

/**
 * 资源装配器
 */
public class CountryResourceAssembler extends RepresentationModelAssemblerSupport<Country, CountryResource> {

    /**
     * Creates a new {@link RepresentationModelAssemblerSupport} using the given controller class and resource type.
     *
     * @param controllerClass must not be {@literal null}.
     * @param resourceType    must not be {@literal null}.
     */
    public CountryResourceAssembler(Class<?> controllerClass, Class<CountryResource> resourceType) {
        super(controllerClass, resourceType);
    }

    public CountryResourceAssembler() {
        super(RestfulController.class,CountryResource.class);
    }

    @Override
    protected CountryResource instantiateModel(Country entity) {
        return new CountryResource(entity);
    }

    @Override
    public CountryResource toModel(Country entity) {
        return createModelWithId(entity.getId(),entity);
    }
}
    @GetMapping("/resource")
//    public CollectionModel<EntityModel<Country>> getAllOnLinks() {
    public CollectionModel<CountryResource> getAllOnLinks() {
//        获取资源列表
        List<Country> countries = repository.findAll();
//        包装返回数据
//        CollectionModel<EntityModel<Country>> resources = CollectionModel.wrap(countries);

//        使用装配器为每一条数据添加链接
//        在包装的过程中转换资源
        CollectionModel<CountryResource> resources = new CountryResourceAssembler().toCollectionModel(countries);

//        添加linkes
//        resources.add(
//                WebMvcLinkBuilder.linkTo(RestfulController.class)
//                            .slash("resource")
//                .withRel("resources")
//        );

        resources.add(
                linkTo(methodOn(getClass()).getAllOnLinks())
                        .withRel("resources")
        );
        return resources;
    }

 

配置rest 自动端点

加载依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>

只需实体与repository 接口即可生成对应接口

package com.jony.boot5.boottest.repository;

import com.jony.boot5.boottest.entity.RestData;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.web.bind.annotation.CrossOrigin;

@CrossOrigin(origins="*")
public interface RestDataRepository extends PagingAndSortingRepository<RestData,String> {

}

配置rest api 基础端点

spring:
      data:
        rest:
            base-path:/api

获取可访问的rest资源描述

http://localhost:8080/api
{
  "_links": {
    "restdata": {
      "href": "http://localhost:8080/api/rs{?page,size,sort}",
      "templated": true
    },
    "countries": {
      "href": "http://localhost:8080/api/countries"
    },
    "sysUsers": {
      "href": "http://localhost:8080/api/sysUsers"
    },
    "profile": {
      "href": "http://localhost:8080/api/profile"
    }
  }
}

针对rest 资源设置别名

package com.jony.boot5.boottest.entity;

import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.rest.core.annotation.RestResource;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import java.sql.Timestamp;

@Data
@Entity
@RestResource(path = "rs",rel = "restdata")
public class RestData {

    @Id
    @GenericGenerator(strategy = "uuid",name = "ugen")
    @GeneratedValue(generator = "ugen")
    private String id;
    private String name;
    private Timestamp createDate;

    @PrePersist
    void init(){
        createDate = new Timestamp(System.currentTimeMillis());
    }
}

分页与排序(repository 集成 pagingAndSortingRepository)

URL 查询分页(sort 针对一个属性,页数从0开始)

localhost:8080/api/rs?page=0&size=2&sort=name,desc

 

配置自定义端点(特殊业务逻辑)

package com.jony.boot5.boottest.controller;

import com.jony.boot5.boottest.entity.RestData;
import com.jony.boot5.boottest.repository.RestDataRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.Optional;

@RepositoryRestController
public class RestfulDataController {
    @Autowired
    private RestDataRepository repository;

    @GetMapping(path = "/rs/sign/{id}")
    public ResponseEntity<RestData> takeRestData(@PathVariable("id") String id){
        Optional<RestData> data = repository.findById(id);
        return new ResponseEntity<RestData>(data.get(),HttpStatus.OK);
    }
}

在path中第一个分隔path 必须与api 中已经存在的资源相符(匹配已有的rest服务端点),否则不会成功注册

返回的数据要么使用ResponseEntity 封装,要么加上@RespouseBody注解

 

posted @ 2020-04-07 11:10  李鹏飞ONLINE  阅读(487)  评论(0编辑  收藏  举报