利用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
<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(); }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。