使用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>
	<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

	<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">exclusions</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">exclusion</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-tomcat<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">exclusion</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">exclusions</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-undertow<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

	<span class="hljs-comment">&lt;!--redis支持--&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

	<span class="hljs-comment">&lt;!--mongodb支持--&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-mongodb<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

  • 引入 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> {
<span class="hljs-keyword">public</span> User <span class="hljs-title function_">findByUserName</span><span class="hljs-params">(String userName)</span>;

}

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

Copy
@Component public class AuthService extends BaseService {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-type">UserRepository</span> userRepository;
}

Redis 访问#

使用 StringRedisTemplate 即可直接访问 Redis

Copy
@Component public class BaseService { @Autowired protected MongoTemplate mongoTemplate;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">protected</span> 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 {
<span class="hljs-meta">@RequestMapping</span>(value = {<span class="hljs-string">"/"</span>}, produces = <span class="hljs-string">"application/json;charset=utf-8"</span>, method = {<span class="hljs-type">RequestMethod</span>.<span class="hljs-type">GET</span>, <span class="hljs-type">RequestMethod</span>.<span class="hljs-type">POST</span>})
<span class="hljs-meta">@ResponseBody</span>
public <span class="hljs-type">String</span> main() {
	<span class="hljs-keyword">return</span> <span class="hljs-string">"hello world!"</span>;
}

}

现在启动,应该就能看到 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 {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-type">AuthCheckInterceptor</span> authCheckInterceptor;

<span class="hljs-meta">@Override</span>
public void addInterceptors(<span class="hljs-type">InterceptorRegistry</span> registry) {
	<span class="hljs-keyword">super</span>.addInterceptors(registry);
	<span class="hljs-comment">// 添加拦截器</span>
	registry.addInterceptor(authCheckInterceptor).addPathPatterns(<span class="hljs-string">"/**"</span>);
}

}

自定义认证注解#

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

Copy
/** * 权限检验注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthCheck {
<span class="hljs-comment">/**
 *  角色列表
 * @return
 */</span>
<span class="hljs-selector-tag">String</span><span class="hljs-selector-attr">[]</span> <span class="hljs-selector-tag">roles</span>() <span class="hljs-selector-tag">default</span> {};

}

检验逻辑:

  • 只要接口加上了 AuthCheck 注解,就必须是登陆用户
  • 如果指定了 roles,则除了登录外,用户还应该具备相应的角色。
Copy
String[] ignoreUrls = new String[]{ "/user/.*", "/cat/.*", "/app/.*", "/error" }; public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
    <span class="hljs-comment">// 0 检验公共参数</span>
    <span class="hljs-keyword">if</span>(!checkParams(<span class="hljs-string">"platform"</span>,httpServletRequest,httpServletResponse)){
        <span class="hljs-keyword">return</span>  <span class="hljs-literal">false</span>;
    }

    <span class="hljs-comment">// 1、忽略验证的URL</span>
    <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> httpServletRequest.getRequestURI().toString();
    <span class="hljs-keyword">for</span>(String ignoreUrl :ignoreUrls){
        <span class="hljs-keyword">if</span>(url.matches(ignoreUrl)){
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    }

    <span class="hljs-comment">// 2、查询验证注解</span>
    <span class="hljs-type">HandlerMethod</span> <span class="hljs-variable">handlerMethod</span> <span class="hljs-operator">=</span> (HandlerMethod) handler;
    <span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> handlerMethod.getMethod();
    <span class="hljs-comment">// 查询注解</span>
    <span class="hljs-type">AuthCheck</span> <span class="hljs-variable">authCheck</span> <span class="hljs-operator">=</span> method.getAnnotation(AuthCheck.class);
    <span class="hljs-keyword">if</span> (authCheck == <span class="hljs-literal">null</span>) {
        <span class="hljs-comment">// 无注解,不需要</span>
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-comment">// 3、有注解,先检查accessToken</span>
    <span class="hljs-keyword">if</span>(!checkParams(<span class="hljs-string">"accessToken"</span>,httpServletRequest,httpServletResponse)){
        <span class="hljs-keyword">return</span>  <span class="hljs-literal">false</span>;
    }
    <span class="hljs-comment">// 检验token是否过期</span>
    <span class="hljs-type">Integer</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> authService.getUserIdFromToken(httpServletRequest.getParameter(<span class="hljs-string">"accessToken"</span>),
            httpServletRequest.getParameter(<span class="hljs-string">"platform"</span>));
    <span class="hljs-keyword">if</span>(userId==<span class="hljs-literal">null</span>){
        logger.debug(<span class="hljs-string">"accessToken timeout"</span>);
        output(ResponseResult.Builder.error(<span class="hljs-string">"accessToken已过期"</span>).build(),httpServletResponse);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }

    <span class="hljs-comment">// 4、再检验是否包含必要的角色</span>
    <span class="hljs-keyword">if</span>(authCheck.roles()!=<span class="hljs-literal">null</span>&amp;&amp;authCheck.roles().length&gt;<span class="hljs-number">0</span>){
        <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> authService.getUser(userId);
        <span class="hljs-type">boolean</span> <span class="hljs-variable">isMatch</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
        <span class="hljs-keyword">for</span>(String role : authCheck.roles()){
            <span class="hljs-keyword">if</span>(user.getRole().getName().equals(role)){
                isMatch =  <span class="hljs-literal">true</span>;
                <span class="hljs-keyword">break</span>;
            }
        }
        <span class="hljs-comment">// 角色未匹配,验证失败</span>
        <span class="hljs-keyword">if</span>(!isMatch){
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

服务响应结果封装

增加一个 Builder,方便生成最终结果

Copy
public class ResponseResult {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Builder</span>{
    ResponseResult responseResult;

    Map&lt;String,Object&gt; dataMap = Maps.newHashMap();

    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Builder</span><span class="hljs-params">()</span>{
        <span class="hljs-built_in">this</span>.responseResult = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ResponseResult</span>();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Builder</span><span class="hljs-params">(String state)</span>{
        <span class="hljs-built_in">this</span>.responseResult = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ResponseResult</span>(state);
    }


    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Builder <span class="hljs-title function_">newBuilder</span><span class="hljs-params">()</span>{
       <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Builder</span>();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Builder <span class="hljs-title function_">success</span><span class="hljs-params">()</span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Builder</span>(<span class="hljs-string">"success"</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Builder <span class="hljs-title function_">error</span><span class="hljs-params">(String message)</span>{
        <span class="hljs-type">Builder</span> <span class="hljs-variable">builder</span> <span class="hljs-operator">=</span>  <span class="hljs-keyword">new</span> <span class="hljs-title class_">Builder</span>(<span class="hljs-string">"error"</span>);
        builder.responseResult.setError(message);
        <span class="hljs-keyword">return</span> builder;
    }

    <span class="hljs-keyword">public</span>  Builder <span class="hljs-title function_">append</span><span class="hljs-params">(String key,Object data)</span>{
        <span class="hljs-built_in">this</span>.dataMap.put(key,data);
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
    }

    <span class="hljs-comment">/**
     *  设置列表数据
     * <span class="hljs-doctag">@param</span> datas 数据
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-keyword">public</span>  Builder <span class="hljs-title function_">setListData</span><span class="hljs-params">(List&lt;?&gt; datas)</span>{
        <span class="hljs-built_in">this</span>.dataMap.put(<span class="hljs-string">"result"</span>,datas);
        <span class="hljs-built_in">this</span>.dataMap.put(<span class="hljs-string">"total"</span>,datas.size());
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
    }

    <span class="hljs-keyword">public</span>  Builder <span class="hljs-title function_">setData</span><span class="hljs-params">(Object data)</span>{
        <span class="hljs-built_in">this</span>.dataMap.clear();
        <span class="hljs-built_in">this</span>.responseResult.setData(data);
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
    }

    <span class="hljs-type">boolean</span> <span class="hljs-variable">wrapData</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;

    <span class="hljs-comment">/**
     * 将数据包裹在data中
     * <span class="hljs-doctag">@param</span> wrapData
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-keyword">public</span>  Builder <span class="hljs-title function_">wrap</span><span class="hljs-params">(<span class="hljs-type">boolean</span> wrapData)</span>{
        <span class="hljs-built_in">this</span>.wrapData = wrapData;
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
    }

    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">build</span><span class="hljs-params">()</span>{

        <span class="hljs-type">JSONObject</span> <span class="hljs-variable">jsonObject</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JSONObject</span>();
        jsonObject.put(<span class="hljs-string">"state"</span>,<span class="hljs-built_in">this</span>.responseResult.getState());
        <span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.responseResult.getState().equals(<span class="hljs-string">"error"</span>)){
            jsonObject.put(<span class="hljs-string">"error"</span>,<span class="hljs-built_in">this</span>.responseResult.getError());
        }
        <span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.responseResult.getData()!=<span class="hljs-literal">null</span>){
            jsonObject.put(<span class="hljs-string">"data"</span>, JSON.toJSON(<span class="hljs-built_in">this</span>.responseResult.getData()));
        }<span class="hljs-keyword">else</span>  <span class="hljs-keyword">if</span>(dataMap.size()&gt;<span class="hljs-number">0</span>){
            <span class="hljs-keyword">if</span>(wrapData) {
                <span class="hljs-type">JSONObject</span> <span class="hljs-variable">data</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JSONObject</span>();
                dataMap.forEach((key, value) -&gt; {
                    data.put(key, value);
                });
                jsonObject.put(<span class="hljs-string">"data"</span>, data);
            }<span class="hljs-keyword">else</span>{
                dataMap.forEach((key, value) -&gt; {
                    jsonObject.put(key, value);
                });
            }
        }
        <span class="hljs-keyword">return</span> jsonObject.toJSONString();
    }

}

<span class="hljs-keyword">private</span> String state;
<span class="hljs-keyword">private</span> Object data;
<span class="hljs-keyword">private</span> String error;


<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getError</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> error;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setError</span><span class="hljs-params">(String error)</span> {
    <span class="hljs-built_in">this</span>.error = error;
}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">ResponseResult</span><span class="hljs-params">()</span>{}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">ResponseResult</span><span class="hljs-params">(String rc)</span>{
    <span class="hljs-built_in">this</span>.state = rc;
}

<span class="hljs-comment">/**
 * 成功时返回
 * <span class="hljs-doctag">@param</span> rc
 * <span class="hljs-doctag">@param</span> result
 */</span>
<span class="hljs-keyword">public</span> <span class="hljs-title function_">ResponseResult</span><span class="hljs-params">(String rc, Object result)</span>{
    <span class="hljs-built_in">this</span>.state = rc;
    <span class="hljs-built_in">this</span>.data = result;
}

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getState</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> state;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setState</span><span class="hljs-params">(String state)</span> {
    <span class="hljs-built_in">this</span>.state = state;
}

<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">getData</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> data;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setData</span><span class="hljs-params">(Object data)</span> {
    <span class="hljs-built_in">this</span>.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(); }
<span class="hljs-keyword">protected</span> String <span class="hljs-title function_">error</span><span class="hljs-params">(String message)</span>{
    <span class="hljs-keyword">return</span>  ResponseResult.Builder.error(message).build();
}

<span class="hljs-keyword">protected</span> String <span class="hljs-title function_">success</span><span class="hljs-params">()</span>{
    <span class="hljs-keyword">return</span>  ResponseResult.Builder
            .success()
            .build();
}

<span class="hljs-keyword">protected</span> String <span class="hljs-title function_">successDataList</span><span class="hljs-params">(List&lt;?&gt; data)</span>{
    <span class="hljs-keyword">return</span> ResponseResult.Builder
            .success()
            .wrap(<span class="hljs-literal">true</span>) <span class="hljs-comment">// data包裹</span>
            .setListData(data)
            .build();
}


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

关注作者

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