欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

利用Spring Boot如何开发REST服务详解

程序员文章站 2023-12-02 19:49:40
rest服务介绍 restful service是一种架构模式,近几年比较流行了,它的轻量级web服务,发挥http协议的原生的get,put,post,delete...

rest服务介绍

restful service是一种架构模式,近几年比较流行了,它的轻量级web服务,发挥http协议的原生的get,put,post,delete。 rest模式的web服务与复杂的soap和xml-rpc对比来讲明显的更加简洁,越来越多的web服务开始采用rest风格设计和实现。例如,amazon.com提供接近rest风格的web服务进行图书查找;雅虎提供的web服务也是rest风格的。rest 并非始终是正确的选择。 它作为一种设计 web 服务的方法而变得流行,这种方法对专有中间件(例如某个应用程序服务器)的依赖比基于 soap 和 wsdl 的方法更少。 在某种意义上,通过强调uri和http等早期 internet 标准,rest 是对大型应用程序服务器时代之前的 web 方式的回归。

如下图示例:

利用Spring Boot如何开发REST服务详解

使用rest的关键是如何抽象资源,抽象得越精确,对rest的应用就越好。

rest服务关键原则:

       1. 给一切物体一个id

       2.连接物体在一起

       3.使用标准方法

       4.资源多重表述

       5.无状态通信

本文介绍如何基于spring boot搭建一个简易的rest服务框架,以及如何通过自定义注解实现rest服务鉴权

搭建框架

pom.xml

首先,引入相关依赖,数据库使用mongodb,同时使用redis做缓存

注意:这里没有使用tomcat,而是使用undertow

 <dependency>  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter</artifactid>
 </dependency>
 <dependency>  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-test</artifactid>
  <scope>test</scope>
 </dependency>
 <dependency>  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-web</artifactid>
  <exclusions>
  <exclusion>   <groupid>org.springframework.boot</groupid>
   <artifactid>spring-boot-starter-tomcat</artifactid>
  </exclusion>
  </exclusions>
 </dependency>
 <dependency>  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-undertow</artifactid>
 </dependency>
 <!--redis支持-->
 <dependency>  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-data-redis</artifactid>
 </dependency>
 <!--mongodb支持-->
 <dependency>  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-data-mongodb</artifactid>
 </dependency>

引入spring-boot-starter-web支持web服务

引入spring-boot-starter-data-redis 和spring-boot-starter-data-mongodb就可以方便的使用mongodb和redis了

配置文件

profiles功能

为了方便 区分开发环境和线上环境,可以使用profiles功能,在application.properties里增加
spring.profiles.active=dev

然后增加application-dev.properties作为dev配置文件。

mondb配置

配置数据库地址即可

spring.data.mongodb.uri=mongodb://ip:port/database?readpreference=primarypreferred

redis配置

spring.redis.database=0 
# redis服务器地址
spring.redis.host=ip
# redis服务器连接端口
spring.redis.port=6379 
# redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8 
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1 
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8 
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0 
# 连接超时时间(毫秒)
spring.redis.timeout=0 

数据访问

mongdb

mongdb访问很简单,直接定义接口extends mongorepository即可,另外可以支持jpa语法,例如:

@component
public interface userrepository extends mongorepository<user, integer> {
 public user findbyusername(string username);
}

使用时,加上@autowired注解即可。

@component
public class authservice extends baseservice {
 @autowired
 userrepository userrepository;
 }

redis访问

使用stringredistemplate即可直接访问redis

@component
public class baseservice {
 @autowired
 protected mongotemplate mongotemplate;
 @autowired
 protected stringredistemplate stringredistemplate;

 }

储存数据:

.stringredistemplate.opsforvalue().set(token_key, user.getid()+"",token_max_age, timeunit.seconds);

删除数据:

stringredistemplate.delete(getformattoken(accesstoken,platform));

web服务

定义一个controller类,加上restcontroller即可,使用requestmapping用来设置url route

@restcontroller
public class authcontroller extends basecontroller {
 @requestmapping(value = {"/"}, produces = "application/json;charset=utf-8", method = {requestmethod.get, requestmethod.post})
 @responsebody
 public string main() {
 return "hello world!";
 }
}

现在启动,应该就能看到hello world!了

服务鉴权

简易accesstoken机制

提供登录接口,认证成功后,生成一个accesstoken,以后访问接口时,带上accesstoken,服务端通过accesstoken来判断是否是合法用户。

为了方便,可以将accesstoken存入redis,设定有效期。

  string token = encryptionutils.sha256hex(string.format("%s%s", user.getusername(), system.currenttimemillis()));
  string token_key = getformattoken(token, platform);
  this.stringredistemplate.opsforvalue().set(token_key, user.getid()+"",token_max_age, timeunit.seconds);

拦截器身份认证

为了方便做统一的身份认证,可以基于spring的拦截器机制,创建一个拦截器来做统一认证。

public class authcheckinterceptor implements handlerinterceptor {
}

要使拦截器生效,还需要一步,增加配置:

@configuration
public class sessionconfiguration extends webmvcconfigureradapter {
 @autowired
 authcheckinterceptor authcheckinterceptor;
 @override
 public void addinterceptors(interceptorregistry registry) {
  super.addinterceptors(registry);
  // 添加拦截器
  registry.addinterceptor(authcheckinterceptor).addpathpatterns("/**");
 }
}

自定义认证注解

为了精细化权限认证,比如有的接口只能具有特定权限的人才能访问,可以通过自定义注解轻松解决。在自定义的注解里,加上roles即可。

/**
 * 权限检验注解
 */
@target(elementtype.method)
@retention(retentionpolicy.runtime)
@documented
public @interface authcheck {

 /**
  * 角色列表
  * @return
  */
 string[] roles() default {};
}

检验逻辑:

只要接口加上了authcheck注解,就必须是登陆用户

如果指定了roles,则除了登录外,用户还应该具备相应的角色。

 string[] ignoreurls = new string[]{
   "/user/.*",
   "/cat/.*",
   "/app/.*",
   "/error"
 };
 public boolean prehandle(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object handler) throws exception {
  // 0 检验公共参数
  if(!checkparams("platform",httpservletrequest,httpservletresponse)){
   return false;
  }
  // 1、忽略验证的url
  string url = httpservletrequest.getrequesturi().tostring();
  for(string ignoreurl :ignoreurls){
   if(url.matches(ignoreurl)){
    return true;
   }
  }
  // 2、查询验证注解
  handlermethod handlermethod = (handlermethod) handler;
  method method = handlermethod.getmethod();
  // 查询注解
  authcheck authcheck = method.getannotation(authcheck.class);
  if (authcheck == null) {
   // 无注解,不需要
   return true;
  }
  // 3、有注解,先检查accesstoken
  if(!checkparams("accesstoken",httpservletrequest,httpservletresponse)){
   return false;
  }
  // 检验token是否过期
  integer userid = authservice.getuseridfromtoken(httpservletrequest.getparameter("accesstoken"),
    httpservletrequest.getparameter("platform"));
  if(userid==null){
   logger.debug("accesstoken timeout");
   output(responseresult.builder.error("accesstoken已过期").build(),httpservletresponse);
   return false;
  }
  // 4、再检验是否包含必要的角色
  if(authcheck.roles()!=null&&authcheck.roles().length>0){
   user user = authservice.getuser(userid);
   boolean ismatch = false;
   for(string role : authcheck.roles()){
    if(user.getrole().getname().equals(role)){
     ismatch = true;
     break;
    }
   }
   // 角色未匹配,验证失败
   if(!ismatch){
    return false;
   }
  }
  return true;
 }

服务响应结果封装

增加一个builder,方便生成最终结果

public class responseresult {
 public static class builder{
  responseresult responseresult;
  map<string,object> datamap = maps.newhashmap();
  public builder(){
   this.responseresult = new responseresult();
  }
  public builder(string state){
   this.responseresult = new responseresult(state);
  }
  public static builder newbuilder(){
   return new builder();
  }
  public static builder success(){
   return new builder("success");
  }
  public static builder error(string message){
   builder builder = new builder("error");
   builder.responseresult.seterror(message);
   return builder;
  }
  public builder append(string key,object data){
   this.datamap.put(key,data);
   return this;
  }
  /**
   * 设置列表数据
   * @param datas 数据
   * @return
   */
  public builder setlistdata(list<?> datas){
   this.datamap.put("result",datas);
   this.datamap.put("total",datas.size());
   return this;
  }
  public builder setdata(object data){
   this.datamap.clear();
   this.responseresult.setdata(data);
   return this;
  }
  boolean wrapdata = false;
  /**
   * 将数据包裹在data中
   * @param wrapdata
   * @return
   */
  public builder wrap(boolean wrapdata){
   this.wrapdata = wrapdata;
   return this;
  }
  public string build(){
   jsonobject jsonobject = new jsonobject();
   jsonobject.put("state",this.responseresult.getstate());
   if(this.responseresult.getstate().equals("error")){
    jsonobject.put("error",this.responseresult.geterror());
   }
   if(this.responseresult.getdata()!=null){
    jsonobject.put("data", json.tojson(this.responseresult.getdata()));
   }else if(datamap.size()>0){
    if(wrapdata) {
     jsonobject data = new jsonobject();
     datamap.foreach((key, value) -> {
      data.put(key, value);
     });
     jsonobject.put("data", data);
    }else{
     datamap.foreach((key, value) -> {
      jsonobject.put(key, value);
     });
    }
   }
   return jsonobject.tojsonstring();
  }

 }
 private string state;
 private object data;
 private string error;
 public string geterror() {
  return error;
 }
 public void seterror(string error) {
  this.error = error;
 }
 public responseresult(){}

 public responseresult(string rc){
  this.state = rc;
 }
 /**
  * 成功时返回
  * @param rc
  * @param result
  */
 public responseresult(string rc, object result){
  this.state = rc;
  this.data = result;
 }
 public string getstate() {
  return state;
 }
 public void setstate(string state) {
  this.state = state;
 }
 public object getdata() {
  return data;
 }
 public void setdata(object data) {
  this.data = data;
 }
}

调用时可以优雅一点

 @requestmapping(value = {"/user/login","/pc/user/login"}, produces = "application/json;charset=utf-8", method = {requestmethod.get, requestmethod.post})
 @responsebody
 public string login(string username,string password,integer platform) {
  user user = this.authservice.login(username,password);
  if(user!=null){
   // 登陆
   string token = authservice.updatetoken(user,platform);
   return responseresult.builder
      .success()
     .append("accesstoken",token)
     .append("userid",user.getid())
     .build();
  }
  return responseresult.builder.error("用户不存在或密码错误").build();
 } 
 protected string error(string message){
  return responseresult.builder.error(message).build();
 }
 protected string success(){
  return responseresult.builder
    .success()
    .build();
 }
 protected string successdatalist(list<?> data){
  return responseresult.builder
    .success()
    .wrap(true) // data包裹
    .setlistdata(data)
    .build();
 }

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。