基於springboot+redis+bootstrap+mysql開發一套屬於自己的分散式springcloud雲許可權架構(十六)【路由閘道器】
在前面十六章我們完成了註冊中心、鏈路中心、許可權架構生產者、許可權架構消費者的整合開發工作,本章將開始重點講解我們的路由閘道器的實現,由於我們的微服務內部是無許可權的,因此我們的微服務內部是不對外暴露埠的,所有的請求全部通過路由閘道器來進行請求的,因此在本章我們的路由閘道器將實現路由分發以及許可權過濾的功能。
直接在我們的工程中建立路由閘道器的modules如下所示:
接著在我們的api-gateway專案中建立如下的包結構:
接著開啟我們的pom.xml引入以下的MAVEN依賴:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.gateway</groupId> <artifactId>api-gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>api-gateway</name> <description>路由閘道器</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.base</groupId> <artifactId>model</artifactId> <version>[0.0.1-SNAPSHOT,)</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <!-- 引入json的依賴 classifier必須要加這個是json的jdk的依賴--> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> <version>RELEASE</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Edgware.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
接著配置我們的路由閘道器成為鑑權中心的消費者,以及使得我們的路由閘道器成為真正的路由閘道器,我們需要做以下兩個步驟的配置,首先開啟我們的主入口類ApiGatewayApplication.java,增加以下註解:
package com.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy @EnableFeignClients @EnableDiscoveryClient public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
接著在resource底下新建application-prod-5100.properties配置檔案,檔案內容如下:
spring.application.name=api-gateway
server.port=5100
# 鏈路資料收集併發送地址
spring.zipkin.base-url=http://127.0.0.1:9100
# 當前應用收集資訊百分比
spring.sleuth.sampler.percentage=0.1
zuul.routes.v1/rbac.path=/v1/rbac/**
zuul.routes.v1/rbac.serviceId=rbac-consumer
# 實現指定的路由的cookie資訊的傳遞
zuul.routes.v1/rbac.sensitiveHeaders=
# 通過瀏覽器開發工具檢視登入以及登入之後的請求詳情, 可以發現, 引起問題的大致原因是由於SpringSecurity或Shiro在登入完成之後,通過重定向的方式跳轉到登入後的頁
#面,此時登入後的請求結果狀態碼為302, 請求響應頭資訊中的 Location指向了具體的服務例項地址, 而請求頭資訊中的Host也指向 了具體的服務例項 IP地址和埠。 所以, 該
#問題的根本原因在於Spring Cloud Zuul在路由請求時,並沒有將最初的Host資訊設定正確。那麼如何解決 這個問題呢?
#能夠使得閘道器在進行路由轉發前為請求設定Host頭資訊,以標識最初的服務端請求地址。 具體配置方式如下:
zuul.add-host-header=true
# 註冊中心地址
eureka.client.serviceUrl.defaultZone=http://fjhyll: [email protected]:2100/eureka/
zuul.SendErrorFilter.post.disable=true
# 設定通訊的超時時間
ribbon.SocketTimeout=250
# 因此在消費者的重試時間加起來的總和超過的話就直接連線超時
# 設定連線的超時時間
ribbon.ReadTimeout=50000
#斷路器的超時時間,斷路器的超時時間需要大於ribbon的超時時間,不然不會觸發重試。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
# 開啟GZIP的壓縮功能以減少HTTP通訊的消耗。
feign.compression.request.enabled=true;
feign.compression.response.enabled=true;
# 以下的請求的型別且請求資料的大小超過2048的將為會壓縮傳輸。
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
# 該引數用來開啟重試機制,它預設是關閉的。
spring.cloud.loadbalancer.retry.enabled=true
# 請求連線的超時時間。
AUTHENTICATION-SERVICE.ribbon.ConnectTimeout=250
# 請求處理的超時時間,該超時時間的影響層級大於全域性的超時時間,設定了該時間那麼,如果呼叫生產端的時候超過1秒那麼就直接呼叫重試規則,因此若重試次數和切換次數都是為1那麼,響應的時間不超過4秒
AUTHENTICATION-SERVICE.ribbon.ReadTimeout=3000
# 對所有操作請求都進行重試。
AUTHENTICATION-SERVICE.ribbon.OkToRetryOnAllOperations=true
# 以下重試實現響應EUREKA-PRODUCER的最大次數是 :(1 + MaxAutoRetries)* (1 + MaxAutoRetriesNextServer)
# 假設 MaxAutoRetries = 2 ,MaxAutoRetriesNextServer = 4 ,那麼最大的重試次數為15次
# 切換例項的重試次數。
AUTHENTICATION-SERVICE.ribbon.MaxAutoRetriesNextServer=1
# 對當前例項的重試次數。
AUTHENTICATION-SERVICE.ribbon.MaxAutoRetries=1
feign.hystrix.enabled=true
接著引入我們的鑑權中心的生產者服務,我們直接在我們的service包底下建立AuthenticationService.java檔案內容如下:
package com.gateway.service;
import com.base.entity.Identify;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Map;
/*
* 類描述:
* @auther linzf
* @create 2018/1/24 0024
*/
@FeignClient(value="AUTHENTICATION-SERVICE")
public interface AuthenticationService {
/**
* 功能描述:呼叫生產者端的軌跡處理方法
* @param identify
*/
@RequestMapping(value = "/identify" ,method = RequestMethod.POST)
Map<String,Object> identify(@RequestBody Identify identify);
}
接著我們編寫路由閘道器的過濾器,改過濾器主要實現的功能是攔截所有的客戶端請求,並對相應的請求做鑑權處理以後來進行業務的放行,因此我們在filter包底下建立AccessFilter.java檔案,內容如下:
package com.gateway.filter;
import com.base.entity.Identify;
import com.base.util.ip.IPUtil;
import com.gateway.service.AuthenticationService;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/*
* 類描述:
* @auther linzf
* @create 2017/12/22 0022
*/
public class AccessFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
@Autowired
private AuthenticationService authenticationService;
/**
* filterType: 過濾器的型別, 它決定過濾器在請求的哪個生命週期中執行。 這裡定義為pre, 代表會在請求被路由之前執行。
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* filterOrder: 過濾器的執行順序。 當請求在一個階段中存在多個過濾器時, 需要根據該方法返回的值來依次執行。
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* shouldFilter: 判斷該過濾器是否需要被執行。 這裡我們直接返回了true, 因此該過濾器對所有請求都會生效。 實際運用中我們可以利用該函式來指定過濾器的有效範圍。
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
*
* 這裡我們通過ctx.setSendZuulResponse(false)令zuul過濾該請求, 不對其進行路由, 然後通過 ctx.setResponseStatusCode
*(401)設定了其返回的錯誤碼, 當然也可以進 一步優化我們的返回, 比如,通過ctx.se七ResponseBody(body)對返回的body內容進行編輯等。
* @return
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response= ctx.getResponse();
// 設定允許跨域訪問Access-Control-Allow-Origin設定的為當前dinner工程的IP+埠
response.setHeader("Access-Control-Allow-Headers", "Authentication");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,DELETE");
response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
log.info("send {} request to{}", request.getMethod () ,request.getRequestURL().toString()+"--"+ request.getContentType());
Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("access token is empty");
ctx.setSendZuulResponse(false);
// 401錯誤表示需要登陸才可以
ctx.setResponseStatusCode(401);
//為了被error過濾器捕獲
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",new RuntimeException("AccessToken不允許為空!"));
}
Map<String,Object> result = authenticationService.identify(new Identify((String)accessToken, IPUtil.getIpAddress(request)));
log.info("鑑權中心鑑定結果是:", result.get("msg"));
if((boolean)result.get("result")==false){
ctx.setSendZuulResponse(false);
// 401錯誤表示需要登陸才可以
ctx.setResponseStatusCode(401);
//為了被error過濾器捕獲
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",new RuntimeException((String)result.get("msg")));
}
return null;
}
}
在我們的客戶端向我們傳送請求的時候,總會有部分請求在通過路由閘道器呼叫相應的微服務的時候失敗,因此我們需要在路由閘道器做相應的錯誤處理,因此我們在controller、entity、util包底下分別構建了以下的實體類:
entity包底下建立了ErrorException.java內容如下:
package com.gateway.entity;
/*
* 類描述:錯誤資訊實體
* @auther linzf
* @create 2018/1/2 0002
*/
public class ErrorException {
// 報錯的類
private String exceptionClass;
// 錯誤的原因
private String exceptionMessage;
public String getExceptionClass() {
return exceptionClass;
}
public void setExceptionClass(String exceptionClass) {
this.exceptionClass = exceptionClass;
}
public String getExceptionMessage() {
return exceptionMessage;
}
public void setExceptionMessage(String exceptionMessage) {
this.exceptionMessage = exceptionMessage;
}
}
util包底下建立了CombineException.java內容如下:
package com.gateway.util;
import com.gateway.entity.ErrorException;
import net.sf.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/*
* 類描述:封裝返回的錯誤資訊工具類
* @auther linzf
* @create 2018/1/2 0002
*/
public class CombineException {
/**
* 功能描述:獲取錯誤的訊息
* @param throwable
* @return
*/
public static JSONObject getErrorException(Throwable throwable){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
throwable.printStackTrace(new PrintStream(baos));
String exception = baos.toString();
List<ErrorException> exceptionList = recursionException(exception);
JSONObject jobj = new JSONObject();
for(ErrorException errorException:exceptionList){
if(errorException.getExceptionClass().indexOf("com.netflix.client.ClientException")!=-1){
jobj.put("errorCode","500");
jobj.put("info",errorException.getExceptionMessage());
jobj.put("msg","伺服器維護中!");
return jobj;
}else if(errorException.getExceptionClass().indexOf("java.util.concurrent.TimeoutException")!=-1){
jobj.put("errorCode","409");
jobj.put("info",errorException.getExceptionMessage());
jobj.put("msg","伺服器連線超時!");
return jobj;
}
}
if(exceptionList.size()>0){
jobj.put("errorCode","400");
jobj.put("info",exceptionList.get(0).getExceptionMessage());
jobj.put("msg","伺服器響應發生錯誤!");
}
jobj.put("errorCode","400");
jobj.put("msg","伺服器響應發生錯誤!");
return jobj;
}
/**
* 功能描述:遞迴呼叫獲取錯誤資訊的集合
* @param exception
* @return
*/
private static List<ErrorException> recursionException(String exception){
List<ErrorException> exceptionList = new ArrayList<ErrorException>();
int start = exception.indexOf("Caused by:");
if(start!=-1){
int end = exception.substring(start).indexOf("\r\n\t");
String exceptionInfo = exception.substring(start,start+end);
String [] arr = exceptionInfo.split(":");
if(arr!=null&&arr.length>=3){
ErrorException errorException = new ErrorException();
errorException.setExceptionClass(arr[1]);
errorException.setExceptionMessage(arr[2]);
exceptionList.add(errorException);
}else if(arr!=null&&arr.length==2){
ErrorException errorException = new ErrorException();
errorException.setExceptionClass(arr[1]);
errorException.setExceptionMessage("");
exceptionList.add(errorException);
}
if(!exception.substring(start+end).equals("")){
exceptionList.addAll(recursionException(exception.substring(start+end)));
}
}
return exceptionList;
}
/**
* 功能描述:實現獲取錯誤資訊
* @param throwable
*/
public static List<ErrorException> initException(Throwable throwable){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
throwable.printStackTrace(new PrintStream(baos));
String exception = baos.toString();
return recursionException(exception);
}
}
controller包底下建立ErrorHandlerController.java內容如下:
package com.gateway.controller;
import com.gateway.util.CombineException;
import com.netflix.zuul.context.RequestContext;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/*
* 類描述:
* @auther linzf
* @create 2017/12/26 0026
*/
@RestController
public class ErrorHandlerController implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public String error(HttpServletRequest request) {
RequestContext ctx = RequestContext.getCurrentContext();
return CombineException.getErrorException(ctx.getThrowable()).toString();
}
}
最後我們需要在主入口類做出以下的修改,將我們前面編寫的filter過濾器引入:
package com.gateway;
import com.base.util.redis.RedisCache;
import com.gateway.filter.AccessFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableZuulProxy
@EnableFeignClients
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
到此我們就完成了路由閘道器的全部開發工作,我們這時就可以開啟我們的註冊中心、鏈路中心、許可權架構生產者、許可權架構消費者、路由閘道器,以及開啟我們的Advanced REST client工具做如下的測試:
這時候大家會看到我們的返回伺服器給予到我們的返回結果,這是因為我們的路由閘道器在此處開啟了許可權驗證,為了驗證我們的正常服務,我們在此處關閉我們的許可權驗證的環節,修改後的AccessFilter.java程式碼如下:
package com.gateway.filter;
import com.base.entity.Identify;
import com.base.util.ip.IPUtil;
import com.gateway.service.AuthenticationService;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/*
* 類描述:
* @auther linzf
* @create 2017/12/22 0022
*/
public class AccessFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
@Autowired
private AuthenticationService authenticationService;
/**
* filterType: 過濾器的型別, 它決定過濾器在請求的哪個生命週期中執行。 這裡定義為pre, 代表會在請求被路由之前執行。
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* filterOrder: 過濾器的執行順序。 當請求在一個階段中存在多個過濾器時, 需要根據該方法返回的值來依次執行。
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* shouldFilter: 判斷該過濾器是否需要被執行。 這裡我們直接返回了true, 因此該過濾器對所有請求都會生效。 實際運用中我們可以利用該函式來指定過濾器的有效範圍。
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
*
* 這裡我們通過ctx.setSendZuulResponse(false)令zuul過濾該請求, 不對其進行路由, 然後通過 ctx.setResponseStatusCode
*(401)設定了其返回的錯誤碼, 當然也可以進 一步優化我們的返回, 比如,通過ctx.se七ResponseBody(body)對返回的body內容進行編輯等。
* @return
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response= ctx.getResponse();
// 設定允許跨域訪問Access-Control-Allow-Origin設定的為當前dinner工程的IP+埠
response.setHeader("Access-Control-Allow-Headers", "Authentication");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,DELETE");
response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
log.info("send {} request to{}", request.getMethod () ,request.getRequestURL().toString()+"--"+ request.getContentType());
/*
Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("access token is empty");
ctx.setSendZuulResponse(false);
// 401錯誤表示需要登陸才可以
ctx.setResponseStatusCode(401);
//為了被error過濾器捕獲
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",new RuntimeException("AccessToken不允許為空!"));
throw new RuntimeException("AccessToken不允許為空!");
}
Map<String,Object> result = authenticationService.identify(new Identify((String)accessToken, IPUtil.getIpAddress(request)));
log.info("鑑權中心鑑定結果是:", result.get("msg"));
if((boolean)result.get("result")==false){
ctx.setSendZuulResponse(false);
// 401錯誤表示需要登陸才可以
ctx.setResponseStatusCode(401);
//為了被error過濾器捕獲
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",new RuntimeException((String)result.get("msg")));
throw new RuntimeException((String)result.get("msg"));
}
*/
return null;
}
}
接著再做如上的測試結果如下:
到此為止我們已經完成了路由閘道器的整合開發工作,大家以後可以再此基礎上做更大的擴充套件,此處就不再累述了,在下一章我們將講解如何將我們過往的工程改造成我們的spring cloud的微服務。
QQ交流群:578746866
相關推薦
基於springboot+redis+bootstrap+mysql開發一套屬於自己的分散式springcloud雲許可權架構(十六)【路由閘道器】
在前面十六章我們完成了註冊中心、鏈路中心、許可權架構生產者、許可權架構消費者的整合開發工作,本章將開始重點講解我們的路由閘道器的實現,由於我們的微服務內部是無許可權的,因此我們的微服務內部是不對外暴露埠的,所有的請求全部通過路由閘道器來進行請求的,因此在本章我們的
基於springboot+redis+bootstrap+mysql開發一套屬於自己的分散式springcloud雲許可權架構(十五)【許可權架構消費者(完整實現)】
在第十四章我們已經完成了通用業務類的編寫,因此本章我們將講解如何完整的實現我們的許可權架構的消費者的實現,首先開啟我們的rbac-consumer工程,接著開啟我們的主入口檔案RbacConsumerApplication.java加入@EnableDiscove
基於springboot+redis+bootstrap+mysql開發一套屬於自己的分散式springcloud雲許可權架構(十一)【許可權架構生產者(組織架構)】
在第十章我們完成了對使用者管理的整合,本章我們將完成對組織架構管理的整合開發工作,首先開啟我們的rbac-produce工程,接著在com/produce/sys/dao目錄底下建立一個UserDao.java介面內容如下:package com.produce.
基於springboot+redis+bootstrap+mysql開發一套屬於自己的分散式springcloud雲許可權架構(十四)【許可權架構消費者(通用類編寫)】
許可權架構的消費者和許可權架構的生產者一樣可以高度抽象化我們的通用接口出來,因此本章我們將這些消費者介面高度抽象出來,理論上這些高度抽象出來的介面是可以作為一個獨立的module需要的時候使用maven引入,不過此處就不再解耦出來,而是直接寫在我們的許可權架構服
基於springboot+redis+bootstrap+mysql開發一套屬於自己的分散式springcloud雲許可權架構(九)【許可權架構生產者(角色管理)】
在第七章我們完成了對資料字典的整合,本章我們將完成角色管理的整合工作,首先開啟我們的rbac-produce工程,接著在com/produce/sys/dao目錄底下建立一個UserRoleDao.java介面內容如下:package com.produce.sy
Python小白學習之路(十六)—【內置函數一】
tro item 求和 整數 Coding rop 數學運算 memory 保留 將68個內置函數按照其功能分為了10類,分別是: 數學運算(7個) abs() divmod() max() min() pow() round()
Python小白學習之路(十六)—【內建函式一】
將68個內建函式按照其功能分為了10類,分別是: 數學運算(7個) abs() divmod() max() min() pow() round() sum() 型別
【設計和開發一套簡單自己主動化UI框架】
depth 定義 其它 而是 例如 選擇 span debug etl !有興趣的朋友請直接移步Github,本帖子已經不做更新,框架的詳細的實現已經做了優化和代碼整理,本文僅僅介紹了詳細的設計思路! 目標:編寫一個簡單通用UI框架用於管理頁面和完
Java學習總結(十六)——MySQL數據庫(中)分組,嵌套,連接查詢及外鍵與關系表設計
MySQL數據庫 分組查詢 連接查詢 子查詢 外鍵與關系表設計 一.分組查詢 1.語法:group by 分組字段1[,分組字段2,.........] [having 分組後的篩選條件]2.註意:分組字段應該與select後的查詢字段一致,否則查詢結果無意義3.分組查詢經常會與聚合函
一起來學SpringBoot(十六)優雅的整合Shiro
Apache Shiro是一個功能強大且易於使用的Java安全框架,可執行身份驗證,授權,加密和會話管理。藉助Shiro易於理解的API,您可以快速輕鬆地保護任何應用程式 - 從最小的移動應用程式到最大的Web和企業應用程式。網上找到大部分文章都是以前Sprin
(十六)Centos之安裝mysql
sqlyog str conn blog roo 臨時 mysql5.7 測試 mysql- 第一步:獲取mysql YUM源 進入mysql官網獲取RPM包下載地址 https://dev.mysql.com/downloads/repo/yum/ 點擊 下載
企業分布式微服務雲SpringCloud SpringBoot mybatis (十六)Spring Boot中使用LDAP來統一管理用戶信息
數據庫表 repo on() intellij attr ads get 可選 mail LDAP簡介 LDAP(輕量級目錄訪問協議,Lightweight Directory Access Protocol)是實現提供被稱為目錄服務的信息服務。目錄服務是一種特殊的數據庫系
Android項目實戰(十六):QQ空間實現(一)—— 展示說說中的評論內容並有相應點擊事件
con toast short demo append 集合 obj parent 自帶 原文:Android項目實戰(十六):QQ空間實現(一)—— 展示說說中的評論內容並有相應點擊事件大家都玩QQ空間客戶端,對於每一個說說,我們都可以評論,那麽,對於某一條評論:
Java開發筆記(十六)非此即彼的條件分支
目的 占用 true 五行 控制 java highlight 動作 遊戲 前面花了大量篇幅介紹布爾類型及相應的關系運算和邏輯運算,那可不僅僅是為了求真值或假值,更是為了通過布爾值控制流程的走向。在現實生活中,常常需要在岔路口抉擇走去何方,往南還是往北,向東還是向西?在Ja
redis原始碼分析與思考(十六)——集合型別的命令實現(t_set.c)
集合型別是用來儲存多個字串的,與列表型別不一樣,集合中不允許有重複的元素,也不能以索引的方式來通過下標獲取值,集合中的元素還是無序的。在普通的集合上增刪查改外,集合型別還實現了多個集合的取交集、並集、差集,集合的命令如下表所示: 集合命
[搬運工系列]-JMeter(十六)Jmeter之Bean shell使用(二) Jmeter之Bean shell使用(一) Jmeter之Bean shell使用(一)
上一篇Jmeter之Bean shell使用(一)簡單介紹了下Jmeter中的Bean shell,本文是對上文的一個補充,主要總結下常用的幾種場景和方法,相信這些基本可以涵蓋大部分的需求。本節內容如下: 一、操作變數 二、操作屬性 三、自定義函式 四、引用外部java檔案 五、引用外
[搬運工系列]-JMeter(十六)Jmeter之Bean shell使用(一)
一、什麼是Bean Shell BeanShell是一種完全符合Java語法規範的指令碼語言,並且又擁有自己的一些語法和方法; BeanShell是一種鬆散型別的指令碼語言(這點和JS類似); BeanShell是用Java寫成的,一個小型的、免費的、可以下載的、嵌入式的Java原始碼直譯器,
基於spring-cloud-zuul的路由閘道器設定
由於微服務的日益增多,管理也會不方便,所以需要一個可以集中管理所有服務的功能(類似sevelet的filter),可以在此做同一的許可權入口管理 新建一個模組spring-cloud-gateway 增加zuul及相關依賴如下: pom.xml <?xml vers
(十六)mybatis多對一關聯查詢
注:程式碼已託管在GitHub上,地址是:https://github.com/Damaer/Mybatis-Learning ,專案是mybatis-12-many2one,需要自取,需要配置maven環境以及mysql環境(sql語句在resource下的test.sql中),覺得有
使用O2OA二次開發搭建企業辦公平臺(十六)資訊開發篇:資訊欄目和分類的建立及許可權的設定
本部落格為O2OA系列教程、O2OA使用手冊,教程目錄和各章節天梯將在連載完後更新。 使用O2OA二次開發搭建企業辦公平臺(一)平臺部署篇:平臺下載和部署 使用O2OA二次開發搭建企業辦公平臺(二)平臺部署篇:埠衝突和伺服器埠配置 使用O2OA二次開發搭建企業辦公平臺(三)平臺部署篇:使用外部資料庫