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

网友投稿 369 2023-03-07


利用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 方式的回归。

如下图示例:

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

REST服务关键原则:

1. 给一切物体一个ID

2.连接物体在一起

3.使用标准方法

4.资源多重表述

5.无状态通信

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

搭建框架

pom.xml

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

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

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-tomcat

org.springframework.boot

spring-boot-starter-undertow

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-data-mongodb

引入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 {

public User findByUserName(String userName);

}

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

@Component

public class AuthService extends BaseService {

@Autowired

UserRepository userRepository;

}

Redis访问

使用StringRedisTemplate即可直接访问Redis

@Component

public chttp://lass 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 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;

}

pcsWnXxrublic void setError(String error) {

this.error = error;

}

public ResponseResult(){}csWnXxr

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();

}

总结

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:为什么要上api网关(为什么要ping网关)
下一篇:微信小程序实现action
相关文章

 发表评论

暂时没有评论,来抢沙发吧~