基于Spring的RESTful API开发:让Web应用更加开放和灵活

命运对每个人都是一样的,不一样的是各自的努力和付出不同,付出的越多,努力的越多,得到的回报也越多,在你累的时候请看一下身边比你成功却还比你更努力的人,这样,你就会更有动力。

导读:本篇文章讲解 基于Spring的RESTful API开发:让Web应用更加开放和灵活,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、RESTful API

1. API是什么

API全称Application Programming Interface,即应用程序接口。在计算机编程中,API是一些预先定义的函数,目的是提供应用程序之间的通信接口以使系统与系统、应用程序与应用程序之间互相通讯。

2. REST是什么

REST(Representational State Transfer)即表现层状态转移,是一种轻量级的基于HTTP协议的传输方式,常用于Web服务设计。

REST风格反对采用将所有操作都转化为CURD(Create, Update, Read, Delete)操作,而提倡面向资源的设计,资源即可以是实体 数据 文件等等。

3. RESTful API是什么

RESTful API是一种建立在HTTP协议之上的Web API。它遵循REST设计规范使用HTTP请求通过网络进行通信,实现对资源的增、删、改、查等操作。RESTful API与传统的Web API相比更具有灵活性、可扩展性与可维护性,已经成为现代Web服务的首选技术之一。

二、spring框架简介

1. Spring框架基本概念

Spring框架是一套开源的企业级应用程序开发框架,通过IoC(Inversion of Control)和AOP(Aspect Oriented Programming)技术,帮助开发者实现松耦合的、模块化的可重用代码。

2. Spring框架的优点

  • 通过IoC机制可以实现代码之间的松耦合,降低了程序之间的耦合度;
  • AOP机制可以实现统一处理日志、安全、事务等系统级别的功能,提高了系统的可维护性、可扩展性;
  • Spring框架的模块化设计,使得开发者可以使用需要的模块而不需要引入所有模块,提高了系统的可拓展性。

3. Spring框架的模块

Spring框架分为20多个模块包括:

  • 核心容器(Core Container):IoC和DI功能的支持
  • Spring AOP和Instrumenation
  • 数据访问和集成(Data Access/Integration):支持JDBC、ORM、事务管理和NoSQLe等技术
  • 消息(Message):支持基于java的消息服务
  • 测试(Test):提供了Mock模块用来进行自动化测试
  • Web: 包括Web MVC、Web sockets、Web 测试等模块

其中Spring MVC是Spring框架中的Web框架通过MVC(Model-View-Controller)模式,帮助开发者实现Web应用程序的设计、开发和部署。

三、SpringMVC框架

1. SpringMVC框架简介

SpringMVC是Spring框架中的一个模块是一个基于MVC模式的Web框架,通过它可以方便地构建Web应用程序。

2. SpringMVC框架的基本流程

SpringMVC框架基本流程如下:

/* DispatcherServlet是前端控制器
 * 处理所有的请求和响应
 */
public class DispatcherServlet {

  // 1. 客户端发送请求到前端控制器DispatcherServlet
  public void service(HttpServletRequest request, HttpServletResponse response) {

    // 2. DispatcherServlet收到请求后调用HandlerMapping解析请求对应的Handler
    Handler handler = HandlerMapping.getHandler(request);

    // 3. 解析器返回一个Handler,即处理器,处理器会由DispatcherServlet去处理
    ModelAndView mv = handler.handle(request, response);

    // 5. DispatcherServlet接收到ModelAndView后,将ViewResolver渲染数据,以产生数据
    View view = ViewResolver.resolve(mv.getViewName());
    view.render(mv.getModel(), request, response);
  }
}

/* HandlerMapping的作用是:根据URL映射到对应的Handler处理器
 * @RequestMapping注解用于指定URL
 */
public class HandlerMapping {

  // HandlerMapping映射表
  private static Map<String, Handler> mapping = new HashMap<>();

  // 初始化映射表
  static {
    mapping.put("/list", new ListHandler());
    mapping.put("/detail", new DetailHandler());
  }

  // 根据URL获取Handler处理器
  public static Handler getHandler(HttpServletRequest request) {
    String url = request.getRequestURI();
    return mapping.get(url);
  }
}

/* Handler是控制器,也称为处理器
 * 用于处理特定类型的请求,返回一个ModelAndView对象
 */
public interface Handler {

  // 控制器处理客户端请求
  ModelAndView handle(HttpServletRequest request, HttpServletResponse response);
}

/* ModelAndView包含Model数据和View的名称
 * Model用于设置控制器返回的数据
 */
public class ModelAndView {

  // Model数据
  private Map<String, Object> model = new HashMap<>();

  // View的名称
  private String viewName;

  // 获取Model
  public Map<String, Object> getModel() {
    return model;
  }

  // 设置Model
  public void setModel(Map<String, Object> model) {
    this.model = model;
  }

  // 获取View的名称
  public String getViewName() {
    return viewName;
  }

  // 设置View的名称
  public void setViewName(String viewName) {
    this.viewName = viewName;
  }
}

/* ViewResolver是视图解析器
 * 用于根据View的名称解析出对应的View对象
 */
public class ViewResolver {

  // 根据View的名称获取对应的View对象
  public static View resolve(String viewName) {
    if ("jsp".equals(viewName)) {
      return new JspView();
    } else if ("json".equals(viewName)) {
      return new JsonView();
    } else if ("xml".equals(viewName)) {
      return new XmlView();
    } else {
      throw new RuntimeException("Unknown view name: " + viewName);
    }
  }
}

/* View是视图
 * 渲染Model数据以产生结果
 */
public interface View {

  // 渲染Model数据
  void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response);
}

/* JspView渲染Model数据并产生Result
 * 下面只是示例代码
 */
public class JspView implements View {

  // 渲染Model数据
  public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
    String viewName = model.getViewName();
    // 根据View的名称,使用RequestDispatcher.forward()将数据转发到对应的JSP页面
    request.getRequestDispatcher("/" + viewName + ".jsp").forward(request, response);
  }
}

/* JsonView用于将Model数据转换成JSON格式
 * 下面只是示例代码
 */
public class JsonView implements View {

  // 渲染Model数据
  public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
    // 将Model数据序列化成JSON格式
    String json = JSON.toJSONString(model);
    response.setContentType("application/json");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write(json);
  }
}

/* XmlView用于将Model数据转换成XML格式
 * 下面只是示例代码
 */
public class XmlView implements View {

  // 渲染Model数据
  public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
    // 将Model数据序列化成XML格式
    String xml = XMLSerializer.serialize(model);
    response.setContentType("text/xml");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write(xml);
  }
}

3. SpringMVC框架的核心组件

SpringMVC框架的核心组件包括:

  • DispatcherServlet: 前端控制器用于处理所有的请求和响应
  • HandlerMapping: 请求到Handler的映射器用于确定请求对应的处理器
  • HandlerAdapter: 适配器将不同类型的请求转换为Handler类可以处理的形式
  • Handler: 控制器也称为处理器用于处理特定类型的请求,返回ModelAndView
  • ViewResolver: 视图解析器用于解析请求对应的View
  • View: 视图,渲染数据以产生结果

四、RESTful API的设计

1. RESTful API的设计原则

RESTful API的设计应遵循以下原则:

  1. 采用基于资源的URL结构
  2. 将HTTP动词(GET、POST、PUT、DELETE等)映射到资源操作上
  3. 返回标准的HTTP状态码,如200、404、500等
  4. 使用标准格式(如JSON、XML)传输数据
  5. 不使用会话即无状态(Stateless)设计

2. RESTful API的命名方式

RESTful API的命名方式遵循以下规则:

  1. 资源采用复数名词形式
  2. URL不含动词,假设资源为(animals),则可以使用/animals路径表示所有动物资源(类似于/cars路径下的所有汽车资源)
  3. 在URL中使用多级分隔符”/”,如/animals/1表示编号为1的动物资源
  4. 在使用HTTP动词时,应该将GET视为查询指令,POST、PUT和DELETE视为增、改和删操作。用选择性的URL路径模式(restful path patterns)实现更具体的细化操作,如/animals/dogs用于查询所有狗的资源

3. RESTful API的URL规范

RESTful API的URL设计需要遵循以下规范:

  1. 使用名词复数而非单数(如使用/animals而非/animal)
  2. 使用连字符(-)而不是下划线(_)来分割单词
  3. URL路径必须全部小写
  4. 需要区分大小写的资源操作,应该使用在路径中表示操作的动词,如/animals/DELETE用于删除指定动物资源

五、springboot框架

SpringBoot是一个基于Spring框架的开源框架,是简化了Spring应用程序开发的框架。SpringBoot可以让您更快地、更容易地创建Spring应用程序。

1. SpringBoot基本概念

SpringBoot是一个基于Spring框架用于快速创建Spring应用程序的框架。它强调了约定优于配置,大量减少了开发者的配置工作,可以让开发者更容易地创建和部署Spring应用程序。

2. SpringBoot配置方式

SpringBoot提供了多种配置方式包括:

  1. 属性配置文件(application.properties或application.yml):可以在应用程序启动时通过读取属性配置文件来设置应用程序的配置参数。
  2. Java Config:通过Java Config配置类来配置应用程序的参数。
  3. 命令行参数:可以在启动应用程序的时候通过传递参数设置应用程序的配置参数。

3. springboot的优点

SpringBoot的优点包括:

  • 简化了Spring应用程序的开发和部署过程。SpringBoot提供了内嵌的Web服务器,可以简化Web应用程序的部署过程。
  • 约定优于配置。SpringBoot提供了很多默认配置,可以简化应用程序配置的工作。
  • 避免了繁琐的配置文件。SpringBoot的配置方式可以使应用程序的配置变得更加简单易懂。
  • 自动配置。SpringBoot提供了自动配置的机制,可以自动配置应用程序的各种组件。

六、使用Spring框架开发RESTful API

1. 创建Spring Boot项目

在Eclipse中创建Spring Boot项目可以使用Spring Tool Suite插件,也可以使用Spring Boot CLI命令行工具创建。

# Spring Boot CLI创建项目的命令行方式
$ spring init --dependencies=web myproject

2. 添加SpringBoot相关依赖

在项目的pom.xml文件中添加Spring Boot相关依赖,如下所示:

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

3. 创建RESTful API

创建RESTful API时需要遵循以下原则:

  1. 使用HTTP动词(GET、POST、PUT、DELETE)表示资源的操作类型。
  2. 使用资源的URL表示资源的访问地址,如/orders。
  3. 返回HTTP状态码表示资源的状态信息,同时提供具体的错误信息。
  4. 使用JSON格式表示资源的数据信息。

示例代码:

@RestController // 声明Controller类是一个RESTful API的控制器
@RequestMapping("/orders") // 指定API的访问路径
public class OrderController {

    // 获取订单信息的API,使用HTTP GET方法
    @GetMapping("/{id}")
    public ResponseEntity<Order> findById(@PathVariable Long id) {
        // 从数据库中读取订单信息
        Order order = orderRepository.findOne(id);
        // 如果订单不存在,则返回404状态码
        if (order == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        // 返回订单信息和200状态码
        return new ResponseEntity<>(order, HttpStatus.OK);
    }

    // 创建新订单的API,使用HTTP POST方法
    @PostMapping
    public ResponseEntity<Void> create(@RequestBody Order order, UriComponentsBuilder ucBuilder) {
        // 保存订单到数据库
        orderRepository.save(order);
        // 创建订单的URL地址
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(ucBuilder.path("/orders/{id}").buildAndExpand(order.getId()).toUri());
        // 返回状态码201,表示订单创建成功
        return new ResponseEntity<>(headers, HttpStatus.CREATED);
    }

    // 修改订单信息的API,使用HTTP PUT方法
    @PutMapping("/{id}")
    public ResponseEntity<Order> update(@PathVariable Long id, @RequestBody Order order) {
        // 从数据库中读取订单信息
        Order currentOrder = orderRepository.findOne(id);
        // 如果订单不存在,则返回404状态码
        if (currentOrder == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        // 更新订单信息
        currentOrder.setName(order.getName());
        currentOrder.setPrice(order.getPrice());
        orderRepository.save(currentOrder);
        // 返回订单信息和200状态码
        return new ResponseEntity<>(currentOrder, HttpStatus.OK);
    }

    // 删除订单信息的API,使用HTTP DELETE方法
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        // 从数据库中读取订单信息
        Order order = orderRepository.findOne(id);
        // 如果订单不存在,则返回404状态码
        if (order == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        // 删除订单信息
        orderRepository.delete(order);
        // 返回204状态码,表示删除成功
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

4. 配置RESTful API

在应用程序启动时可以通过编写配置文件的方式来配置RESTful API的相关参数,如下所示:

# 配置端口号,设置为8080
server.port=8080
# 配置Context Path,表示Web应用程序的名称为/myapp
server.servlet.contextPath=/myapp
# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 配置JPA相关参数
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

七、使用Spring框架实现RESTful API的安全性

在使用RESTful API时为了保证数据安全性和用户权限的控制,需要实现RESTful API的安全性。本文将介绍基于HTTP协议、OAuth2和JWT等认证方式,并提供代码示例和中文注释。

1. 基于HTTP协议的认证方式

HTTP协议提供了两个基本的认证方式:基本认证和摘要认证

基本认证

基本认证是一种基于用户名和密码进行验证的认证方式。客户端向服务端发送请求时,在头部信息中携带Authorization字段,格式为”Authorization: Basic username:password”,username和password是以base64编码形式进行传输的。

Spring Security提供了多种基本认证方式的实现,需要在pom.xml配置文件中加入以下依赖:

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

然后在SecurityConfig文件中配置Spring Security:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password("{noop}password").roles("USER");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .httpBasic();
    }
}

摘要认证

摘要认证是一种在HTTP请求和响应过程中对消息进行摘要计算的认证方式。客户端向服务端发送请求时,在头部信息中携带Authentication字段,格式为”Authentication: Digest {digest}”,其中digest是通过对消息体进行摘要计算得到的数据。

Spring Security也提供了基本认证方式的实现。需要在SecurityConfig文件中进行配置:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .headers().addHeaderWriter(
                    new DigestAuthHeaderWriter())
                .and()
            .httpDigest();
    }
}

2. 基于OAuth2的认证方式

OAuth2是一种基于HTTP协议的认证方式,常用于资源的授权和访问管理。OAuth2可以实现授权码、隐式授权、密码授权和客户端凭证等四种授权方式。

在使用Spring Security实现OAuth2认证时,需要在pom.xml配置文件中加入以下依赖:

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>

然后在SecurityConfig文件中进行配置:

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource)
            .withClient("client").secret("{noop}secret")
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("read", "write")
            .accessTokenValiditySeconds(3600)
            .refreshTokenValiditySeconds(7200);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService)
            .tokenStore(tokenStore());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.passwordEncoder(NoOpPasswordEncoder.getInstance())
            .allowFormAuthenticationForClients();
    }
}

3. 基于JWT的认证方式

JWT(Json Web Token)是一种开放标准(RFC 7519),用于在网路上传输声明的信息。JWT通常由三部分组成:头部信息、有效载荷和签名。头部信息和有效载荷都是JSON格式的数据,可以包含多种信息。

在使用Spring Security实现JWT认证时需要在pom.xml配置文件中加入以下依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

然后编写JWT工具类用于生成和验证JWT:

@Component
public class JwtUtils {
    private static final String SECRET_KEY = "mysecretkey";

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
            .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
            .compact();
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    private boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Date getExpirationDateFromToken(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration();
    }
}

在SecurityConfig文件中进行配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtUtils jwtUtils;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(jwtUtils), UsernamePasswordAuthenticationFilter.class);
            .csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password("{noop}password").roles("USER")
            .and()
            .withUser("admin").password("{noop}password").roles("ADMIN");
    }
}

八、常见问题及解决方法

1. 如何处理HTTP请求和响应

在处理HTTP请求和响应时可以使用Spring MVC的注解进行标注。示例代码:

@RestController
@RequestMapping("/api")
public class ApiController {
    @GetMapping("/books")
    public List<Book> getBooks() {
        List<Book> books = bookDao.findAll();
        return books;
    }

    @PostMapping("/books")
    public ResponseEntity<Void> addBook(@RequestBody Book book) {
        bookDao.save(book);
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(uriComponentsBuilder.path("/api/books/{id}").buildAndExpand(book.getId()).toUri());
        // 返回状态码201,表示创建成功
        return new ResponseEntity<>(headers, HttpStatus.CREATED);
    }
}

2. 如何处理异常情况

在RESTful API中在发生异常时应该返回相应的错误信息。可以使用Spring MVC的注解进行处理。示例代码:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        ErrorResponse errorResponse = new ErrorResponse();
        errorResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        errorResponse.setMessage(ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

3. 如何排查RESTful API性能问题

在RESTful API的开发过程中性能问题是非常重要的一环。可以使用各种工具进行性能测试,如JMeter、Gatling等。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/144124.html

(0)

相关推荐

  • Java中泛型概述和基本使用

    导读:本篇文章讲解 Java中泛型概述和基本使用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    Java 2022年5月24日
    00
  • Python桌面开发实战之员工信息管理系统

    前言 学无止境,无止境学。大家好,我是张大鹏,之前在抖音有5万多粉丝,不过现在不拍视频,专心写公众号了。笔者目前是高级Python工程师,之前是全栈工程师,主要擅长Golang和P…

    2022年11月2日
    00
  • 根据不同时间统计不同类型的数据(存储过程)

    生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

    导读:本篇文章讲解 根据不同时间统计不同类型的数据(存储过程),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    后端开发 2023年4月23日
    00
  • 如何在Spring环境中优雅地使用策略模式

        本文主要介绍策略模式的一般实现以及在spring环境下如何优雅地使用策略模式。 基本定义     首先介绍一下策略模式基本…

    2022年9月25日
    00
  • Django框架学习笔记(二)URL路由

    前言 大家好,我是小雨。 这一节我们继续介绍Django的知识,我们知道 URL是web访问的第一步,当用户对服务器有一个http请求的时候,Django开始响应URL路由,校验这…

    2022年10月1日
    00
  • mysql 慢sql自动化优化系统

    导读:本篇文章讲解 mysql 慢sql自动化优化系统,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    后端开发 2022年5月29日
    00
  • MIT 线性代数导论 第八讲:Ax=b可解性以及解的结构

    导读:本篇文章讲解 MIT 线性代数导论 第八讲:Ax=b可解性以及解的结构,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    Python 2023年2月21日
    00
  • java后台开发调试

    导读:本篇文章讲解 java后台开发调试,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    后端开发 2022年5月31日
    00
  • ES倒排索引与分词详解

    导读:本篇文章讲解 ES倒排索引与分词详解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    Java 2022年5月24日
    00
  • 设计模式-工厂方法-创建型模式

    导读:本篇文章讲解 设计模式-工厂方法-创建型模式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    后端开发 2022年5月18日
    00

发表回复

登录后才能评论