使用SpringBoot开发REST服务
本文介绍如何基于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();
}
作者:Jadepeng
出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
使用SpringBoot开发REST服务的更多相关文章
- 基于SpringBoot开发一个Restful服务,实现增删改查功能
前言 在去年的时候,在各种渠道中略微的了解了SpringBoot,在开发web项目的时候是如何的方便.快捷.但是当时并没有认真的去学习下,毕竟感觉自己在Struts和SpringMVC都用得不太熟练. ...
- SpringBoot开发案例之多任务并行+线程池处理
前言 前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑.当然了,优化是无止境的,前人栽树后人乘凉.作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序 ...
- SpringBoot开发案例从0到1构建分布式秒杀系统
前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场 ...
- SpringBoot系列八:SpringBoot整合消息服务(SpringBoot 整合 ActiveMQ、SpringBoot 整合 RabbitMQ、SpringBoot 整合 Kafka)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合消息服务 2.具体内容 对于异步消息组件在实际的应用之中会有两类: · JMS:代表作就是 ...
- SpringBoot系列四:SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清 ...
- SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)
1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清楚的认识到以下的问题,在实际的项目开发之中,尤其是 Java 的 MVC ...
- 我的spring-boot开发环境
我的spring-boot开发环境,目的方便我快速搭建开发环境,同时可以最佳实践.使用spring-boot 2.1.x. 代码地址:GitHub my-springboot-examples 目的是 ...
- 项目二:企业级java电商网站开发(服务端)
声明:项目源于网络,支持正版教程,学习使用,仅记录在此 项目介绍 企业级java电商网站开发(服务端),模块划分:用户管理,商品管理,商品品类管理,订单管理,订单详情管理,购物车管理,收货地址管理,支 ...
- SpringBoot开发mockserver及生成swagger接口文档
通过springboot开发mock server,包含get及post接口,用于练习接口自动化及jmeter很方便 当然,也为后面jenkins持续集成做基础(开发push代码后 → jenkin ...
随机推荐
- 关于 Swift 4 中内存安全访问
前言 本文主要翻译今年 The Swift Programming Language (Swift 4) 中新出的章节 -<Memory Safety>.在 Swift 4 中,内存安全访 ...
- 微信小程序之给项目设置id后提示不在合法域名列别中
hotapp 有免费的https proxy ,可以免费代理请求任何http或者https服务,只要设置好合法域名为https://wxapi.hotapp.cn, 就可以请求网址如请求小程序联盟的例 ...
- Ionic3 组件懒加载
使用懒加载能够减少程序启动时间,减少打包后的体积,而且可以很方便的使用路由的功能. 使用懒加载: 右侧红色区域可以省略掉(引用.声明也删掉) 若使用ionic命令新建page,则无需进行下面的操作,否 ...
- 哈尔滨理工大学第六届程序设计团队 E-Mod
/* 成功水过,哈哈哈,可能数据水吧 */ #include <stdio.h> #include <algorithm> #include <string.h> ...
- 关于php加密库加密数据上传数据库或解密出错的问题
php加密拓展库随着php版本的更新,函数的使用方法有所改变,所以加密模式推荐使用ecb,其中加密算法19种,加密模式8种,通过这种方式加密后的数据上传数据库后提取出来进行解密会发现结果是乱码,我认为 ...
- VMware驱动程序"vmci.sys"的版本不正确 怎么解决
解决办法: 1.创建好虚拟机之后,别打开电源,然后到建好的虚拟机文件夹里: 2.找到后缀vmx的文件,记事本打开: 3.找到vmci0.present='TRUE',把true改为false: 4.保 ...
- 如何实现border-width:0.5px;
工作中遇到了一个产品需求,要求把列表分割线改成0.5px,直接写成border:0.5px solid #cccccc;是不符合规范的写法,会存在Android和IOS手机上的兼容问题,故,我们可以利 ...
- MacOS 下安装mysqlclient 的问题及解决办法
[操作环境] 操作系统:MacOS X 10.13.1 mysql运行环境:Docker Docker版本:17.09-ce 在开发Django时,刚开始使用的sqlite进行开发,想部署到生产环境需 ...
- 重构手法之Split Temporary Variable(分解临时变量)
返回总目录 本小节目录 Split Temporary Variable(分解临时变量) Remove Assignments to Parameters(移除对参数的赋值) 6.6Split Tem ...
- 使用 Rust 构建分布式 Key-Value Store
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 引子 构建一个分布式 Key-Value Store 并不是一件容易的事情,我们需要考虑很多的问题,首先就是我们的系统到底需要提供什么样的功能 ...