使用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
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM