利用SpringBoot如何开发REST服务详解

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

利⽤SpringBoot如何开发REST服务详解
REST服务介绍
RESTful service是⼀种架构模式,近⼏年⽐较流⾏了,它的轻量级web服务,发挥HTTP协议的原⽣的
GET,PUT,POST,DELETE。

REST模式的Web服务与复杂的SOAP和XML-RPC对⽐来讲明显的更加简洁,越来越多的web服务开始采⽤REST风格设计和实现。

例如,提供接近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了
配置⽂件
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 {
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)){
// 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 数据
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) {
}
调⽤时可以优雅⼀点
@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();
}
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,如果有疑问⼤家可以留⾔交
流,谢谢⼤家对的⽀持。

相关文档
最新文档