>>分享流行的Java框架以及开源软件,对孙卫琴的《精通Spring:Java Web开发技术详解》提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 21442 个阅读者 刷新本主题
 * 贴子主题:  利用Spring Boot如何开发REST服务详解 回复文章 点赞(0)  收藏  
作者:日月光华    发表时间:2020-06-09 00:07:13     消息  查看  搜索  好友  邮件  复制  引用

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

这篇文章主要给大家介绍了关于利用Spring Boot如何开发REST服务的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

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 方式的回归。
如下图示例:
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
使用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;
}
}

[code]调用时可以优雅一点

@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();
}[/code]总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值



程序猿的技术大观园:www.javathinker.net
爪洼道学院高品质Java视频课程推荐:javathinker.ke.qq.com

[这个贴子最后由 admin 在 2020-06-11 09:05:46 重新编辑]
  Java面向对象编程-->Java语言的基本语法和规范
  JavaWeb开发-->开发JavaMail Web应用
  JSP与Hibernate开发-->JPA API的高级用法
  Java网络编程-->对象的序列化与反序列化
  精通Spring-->通过Vuex进行状态管理
  Vue3开发-->绑定CSS样式
  10分钟认识RocketMQ!想进阿里连这个都不会?
  Spring Boot 入门,用 Spring Boot 写第一个 HelloWorld 程序
  NIO的几道常见面试题
  教你手撸一个Spring框架,从Spring.xml解析到注册Bean对象!
  RocketMQ 常用消息类型
  Spring事务的声明和管理
  Spring MVC文件上传与下载
  Spring MVC实现国际化的几种方式
  vue父、子组件相关的传递
  【项目实践】有了SpringBoot还有必要学SSM整合吗 - RudeCrab...
  什么是Redis?Redis的各项功能解决了哪些问题?
  Spring Cloud构建微服务架构的服务注册与发现
  支付结算系统如何应对高并发、热点账户等问题
  3分钟让你明白JSON是什么
  分布式架构知识体系
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。