自动生成RESTful API——Spring Data Rest

一、Spring Data Rest

Spring Data REST 是 Spring Data 项目的一部分,它提供了一种快速创建 RESTful Web 服务的方法,可以直接暴露 Spring Data 仓库中的数据。通过 Spring Data REST,开发者可以轻松地将数据持久层的操作(如 CRUD 操作)暴露为 RESTful API,而不需要编写大量的控制器代码。这大大简化了 RESTful API 的开发过程。

1、主要特点

自动暴露 Repository 为 RESTful API:

○ Spring Data REST 可以自动将 Spring Data 仓库中的方法暴露为 RESTful API。例如,如果你有一个 UserRepository,Spring Data REST 会自动为你生成相应的 CRUD 操作的 RESTful 端点。

HATEOAS 支持:

○ HATEOAS(Hypermedia As The Engine Of Application State)是 REST 架构风格的一个重要原则。Spring Data REST 生成的 API 自动包含超媒体链接,帮助客户端发现和导航资源。

自定义控制器:

○ 虽然 Spring Data REST 可以自动暴露仓库方法,但你仍然可以添加自定义控制器来处理特定的业务逻辑。你可以通过 @RepositoryRestResource 注解来自定义仓库的暴露方式。

事件监听:

○ Spring Data REST 提供了事件监听机制,可以在资源创建、更新、删除等操作前后触发自定义逻辑。这可以通过实现 ApplicationListener 接口并监听 BeforeCreateEvent、AfterCreateEvent 等事件来实现。

分页和排序:

○ Spring Data REST 自动支持分页和排序。客户端可以通过 URL 参数(如 ?page=0&size=20&sort=name,asc)来请求分页和排序后的数据。

文档生成:

○ Spring Data REST 可以生成 API 文档,帮助客户端了解可用的端点和操作。你可以通过访问 /api-docs 端点来获取这些文档。

2、CRUD使用示例

以目前数言机器人项目为例,在使用mybatis同时加入Spring Data Rest

示例对应表:msg_robot 机器人表

添加依赖
java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
创建实体

虽然已经有机器人表实体,但作用在mybatis,这里新建实体。后面考虑实体如何合并

java 复制代码
import cn.com.yunke.msgbot.constant.CommonConstant;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;

@Table(name = "msg_robot")
@Entity
@Data
@RequiredArgsConstructor
public class MsgRobotRestPO {
    @GeneratedValue
    @Id
    private Long id;

    /**
     * 租户code
     */

    private String orgCode;

    /**
     * 机器人名称
     */

    private String robotName;

}
创建仓库接口

纯接口不带方法,带方法可以实现自定义查询

java 复制代码
@RepositoryRestResource(collectionResourceRel = "msgRobotRestPoes", path = "msgRobotRestPoes")
public interface MsgRobotRestRepository extends JpaRepository<MsgRobotRestPO, Long> {
 Collection<MsgRobotRestPO> findByRobotName(@Param("robotName") String  robotName);
}

访问 API

启动应用后,你可以通过以下 URL 访问 RESTful API:

● 获取所有机器人:GET /bmsMsgRobotRestPoes

● 分页获取机器人:GET /bmsMsgRobotRestPoes?page=1&size=2&sort=createTime

● 获取单个机器人:GET /bmsMsgRobotRestPoes/{id}

返回体:

java 复制代码
{
    "orgCode": "",
    "robotName": "555",
    "_links": {
        "self": {
            "href": "http://127.0.0.1:9000/bmsMsgRobotRestPoes/1642067545749266432"
        },
        "bmsMsgRobotRestPO": {
            "href": "http://127.0.0.1:9000/bmsMsgRobotRestPoes/1642067545749266432"
        }
    }
}

● 创建机器人:POST /msgRobotRestPoes =>201

● 更新机器人:PATCH /msgRobotRestPoes/{id} =>200

● 删除机器人:DELETE /msgRobotRestPoes/{id} =>204

● 查询机器人:GET /msgRobotRestPoes/search/findByRobotName?robotName=111111

特性分析

1、自动生成的不能直接详细的条件查询

需要在Repository接口上定义方法,上面Repository代码

通过search查询实现,效率比之前快,但没有ZenStack灵活

3、关联关系

Spring Data Rest 会自动检测实体之间的关联广西,并创建关联资源,如:机器人下配置信息

java 复制代码
@Table(name = "msg_robot")
@Entity
@Data
@RequiredArgsConstructor
public class MsgRobotRestPO {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 租户code
     */

    private String orgCode;

    /**
     * 机器人名称
     */

    private String robotName;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "robot_id")
    private List<MsgRobotParamConfigRestPO> paramConfig;
}
java 复制代码
@Table(name = "msg_robot_param_config")
@Entity
@Data
@RequiredArgsConstructor
public class MsgRobotParamConfigRestPO {
    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @Id
    @GeneratedValue
    private Long id;
    /**
     * 对应配置的id
     */
    private String configId;
    /**
     * 对应配置的值
     */
    private String configValue;

    /**
     * 机器人
     */
    @ManyToOne(fetch = FetchType.LAZY)
    private MsgRobotRestPO robot;
}

映射关系查询结果

java 复制代码
{
    "orgCode": "222",
    "robotName": "555",
    "paramConfig": [],
    "_links": {
        "self": {
            "href": "http://127.0.0.1:9000/msgRobotRestPoes/1762047555096432644"
        },
        "bmsMsgRobotRestPO": {
            "href": "http://127.0.0.1:9000/msgRobotRestPoes/1762047555096432644"
        }
    }
}

4、事件

通过监听事件,我们可以在资源的创建、更新、删除等操作时执行自定义逻辑

java 复制代码
@Component
@RepositoryEventHandler(BmsMsgRobot.class)
public class BmsMsgRobotEventHandler {
    @HandleBeforeCreate
    public void handleBeforeCreate(BmsMsgRobot robot) {
        System.out.println("handleBeforeCreate robot");
    }
}

5、数据权限

1、Spring Data Rest 数据权限,可以通过集成Spring Security实现对资源的安全控制。配置安全规则,限制对部分资源的访问权限。

a. 添加依赖
java 复制代码
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
b.配置 Spring Security

创建一个配置类来配置 Spring Security,并实现自定义的认证和授权机制。

自定义认证过滤器

创建一个自定义的认证过滤器,用于调用外部接口验证用户的权限。

java 复制代码
package cn.com.yunke.msgbot.rest.filter;
import cn.com.yunke.msgbot.exception.BotServiceException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public CustomAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String sessionId = request.getParameter("sessionId");
        if (sessionId == null || sessionId.isEmpty()) {
            throw new BotServiceException("Session ID is required");
        }

        // 调用外部接口验证 session ID
        boolean isValid = callExternalApiToValidateSession(sessionId);
        if (!isValid) {
            throw new BotServiceException("Invalid session ID");
        }

        // 创建并返回 Authentication 对象
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sessionId, "", Collections.emptyList());
        return getAuthenticationManager().authenticate(authRequest);
    }

    private boolean callExternalApiToValidateSession(String sessionId) {
        // 调用外部接口验证 session ID
        // 返回 true 或 false
        // 示例代码:
        // 假设外部接口返回一个 JSON 对象,包含 isValid 字段
        // 使用 HttpClient 或其他 HTTP 客户端库调用外部接口
       return true;

    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request, response);
    }
}
自定义认证提供者

创建一个自定义的认证提供者,用于处理认证逻辑。

java 复制代码
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;

    public CustomAuthenticationProvider(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String sessionId = (String) authentication.getPrincipal();

        UserDetails userDetails = userDetailsService.loadUserByUsername(sessionId);
        if (userDetails == null) {
            throw new UsernameNotFoundException("User not found");
        }

        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
自定义用户详细信息服务

创建一个自定义的用户详细信息服务,用于从外部接口获取用户详细信息。

java 复制代码
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String sessionId) throws UsernameNotFoundException {
        // 调用外部接口获取用户详细信息
        List<GrantedAuthority> authorities = callExternalApiToGetUserAuthorities(sessionId);
        if (authorities == null || authorities.isEmpty()) {
            throw new UsernameNotFoundException("User not found");
        }

        return new User(sessionId, "", authorities);
    }

    private List<GrantedAuthority> callExternalApiToGetUserAuthorities(String sessionId) {
        // 调用外部接口获取用户权限
        // 返回权限列表
        // 示例代码:
        // 假设外部接口返回一个 JSON 对象,包含 authorities 字段
       List<GrantedAuthority> authorities = new ArrayList<>();

       authorities.add(new SimpleGrantedAuthority("ROLE_admin"));
       return authorities;
        //return Collections.emptyList();
    }
}
配置 Spring Security

创建一个配置类来配置 Spring Security,并注册自定义的认证过滤器和认证提供者。

java 复制代码
import cn.com.yunke.msgbot.rest.filter.CustomAuthenticationFilter;
import cn.com.yunke.msgbot.rest.provider.CustomAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter("/", authenticationManager());
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/api-docs", "/h2-console/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .httpBasic()
                .and()
                .csrf().disable()
                .headers().frameOptions().disable(); // For H2 console
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService));
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
使用 @PreAuthorize 注解

在服务或控制器中使用 @PreAuthorize 注解来控制实体或者方法级别的访问。

java 复制代码
@PreAuthorize("hasRole('ROLE_admin')")
@RepositoryRestResource(collectionResourceRel = "bmsMsgRobotRestPoes", path = "bmsMsgRobotRestPoes")
public interface BmsMsgRobotRestRepository extends JpaRepository<BmsMsgRobotRestPO, Long> {

 @PreAuthorize("hasRole('ROLE_member')")
 Collection<BmsMsgRobotRestPO> findByRobotName(@Param("robotName") String  robotName);

 //Page<BmsMsgRobotRestPO> findAll(@NotNull Pageable pageable);
}

注意:1、引入Spring Security需要考虑与之前全局拦截器兼容,不要冲突,考虑之前需要不拦截的接口等。

2、已有的全局拦截器,控制数据权限,权限体系与已有接口权限体系一致

6、其他功能

如更改路径、更改自动生产API名称、字段是否显示等更多内容参考官方文档

Spring Data REST官网

相关推荐
先睡2 小时前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
Piper蛋窝5 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
Bug退退退1236 小时前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq
六毛的毛7 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack8 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669138 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong8 小时前
curl案例讲解
后端
一只叫煤球的猫9 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学10 小时前
身弱武修法:玄之又玄,奇妙之门
后端
轻语呢喃11 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端