使用SpringBoot开发REST服务

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

搭建框架

pom.xml#

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

Copy
注意,这里没有使用tomcat,而是使用undertow
Copy
<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配置#

配置数据库地址即可

Copy
spring.data.mongodb.uri=mongodb://ip:port/database?readPreference=primaryPreferred

redis配置#

Copy
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语法,例如:

Copy
@Component public interface UserRepository extends MongoRepository<User, Integer> { public User findByUserName(String userName); }

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

Copy
@Component public class AuthService extends BaseService { @Autowired UserRepository userRepository; }

Redis访问#

使用StringRedisTemplate即可直接访问Redis

Copy
@Component public class BaseService { @Autowired protected MongoTemplate mongoTemplate; @Autowired protected StringRedisTemplate stringRedisTemplate; }

储存数据:

Copy
.stringRedisTemplate.opsForValue().set(token_key, user.getId()+"",token_max_age, TimeUnit.SECONDS);

删除数据:

Copy
stringRedisTemplate.delete(getFormatToken(accessToken,platform));

Web服务#

定义一个Controller类,加上RestController即可,使用RequestMapping用来设置url route

Copy
@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,设定有效期。

Copy
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的拦截器机制,创建一个拦截器来做统一认证。

Copy
public class AuthCheckInterceptor implements HandlerInterceptor { }

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

Copy
@Configuration public class SessionConfiguration extends WebMvcConfigurerAdapter { @Autowired AuthCheckInterceptor authCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { super.addInterceptors(registry); // 添加拦截器 registry.addInterceptor(authCheckInterceptor).addPathPatterns("/**"); } }

自定义认证注解#

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

Copy
/** * 权限检验注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthCheck { /** * 角色列表 * @return */ String[] roles() default {}; }

检验逻辑:

  • 只要接口加上了AuthCheck注解,就必须是登陆用户
  • 如果指定了roles,则除了登录外,用户还应该具备相应的角色。
Copy
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,方便生成最终结果

Copy
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; } }

调用时可以优雅一点

Copy
@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(); }

作者:Jadepeng
出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

关注作者

欢迎关注作者微信公众号, 一起交流软件开发:欢迎关注作者微信公众号

posted @   JadePeng  阅读(8921)  评论(2编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示
CONTENTS