久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

微服務(wù)架構(gòu)中整合網(wǎng)關(guān),、權(quán)限服務(wù) | Aoho''s Blog

 airen89 2018-09-18

前言:之前的文章有講過微服務(wù)的權(quán)限系列和網(wǎng)關(guān)實現(xiàn),都是孤立存在,,本文將整合后端服務(wù)與網(wǎng)關(guān),、權(quán)限系統(tǒng)。安全權(quán)限部分的實現(xiàn)還講解了基于前置驗證的方式實現(xiàn),,但是由于與業(yè)務(wù)聯(lián)系比較緊密,,沒有具體的示例。業(yè)務(wù)權(quán)限與業(yè)務(wù)聯(lián)系非常密切,,本次的整合項目將會把這部分的操作權(quán)限校驗實現(xiàn)基于具體的業(yè)務(wù)服務(wù),。

1. 前文回顧與整合設(shè)計

認證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計與實現(xiàn)系列文章中,講解了在微服務(wù)架構(gòu)中Auth系統(tǒng)的授權(quán)認證和鑒權(quán),。在微服務(wù)網(wǎng)關(guān)中,,講解了基于netflix-zuul組件實現(xiàn)的微服務(wù)網(wǎng)關(guān)。下面我們看一下這次整合的架構(gòu)圖,。

ms

微服務(wù)架構(gòu)權(quán)限

整個流程分為兩類:

  • 用戶尚未登錄,??蛻舳耍╳eb和移動端)發(fā)起登錄請求,,網(wǎng)關(guān)對于登錄請求直接轉(zhuǎn)發(fā)到auth服務(wù),,auth服務(wù)對用戶身份信息進行校驗(整合項目省略用戶系統(tǒng),讀者可自行實現(xiàn),,直接硬編碼返回用戶信息),,最終將身份合法的token返回給客戶端。
  • 用戶已登錄,,請求其他服務(wù),。這種情況,客戶端的請求到達網(wǎng)關(guān),,網(wǎng)關(guān)會調(diào)用auth系統(tǒng)進行請求身份合法性的驗證,,驗證不通則直接拒絕,并返回401,;如果通過驗證,,則轉(zhuǎn)發(fā)到具體服務(wù),服務(wù)經(jīng)過過濾器,,根據(jù)請求頭部中的userId,,獲取該user的安全權(quán)限信息。利用切面,,對該接口需要的權(quán)限進行校驗,,通過則proceed,否則返回403,。

第一類其實比較簡單,,在講解認證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計與實現(xiàn)就基本實現(xiàn),現(xiàn)在要做的是與網(wǎng)關(guān)進行結(jié)合,;第二類中,,我們新建了一個后端服務(wù),與網(wǎng)關(guān),、auth系統(tǒng)整合,。

下面對整合項目涉及到的三個服務(wù)分別介紹。網(wǎng)關(guān)和auth服務(wù)的實現(xiàn)已經(jīng)講過,,本文主要講下這兩個服務(wù)進行整合需要的改動,,還有就是對于后端服務(wù)的主要實現(xiàn)進行講解。

2. gateway實現(xiàn)

微服務(wù)網(wǎng)關(guān)已經(jīng)基本介紹完了網(wǎng)關(guān)的實現(xiàn),,包括服務(wù)路由,、幾種過濾方式等。這一節(jié)將重點介紹實際應(yīng)用時的整合,。對于需要修改增強的地方如下:

  • 區(qū)分暴露接口(即對外直接訪問)和需要合法身份登錄之后才能訪問的接口
  • 暴露接口直接放行,,轉(zhuǎn)發(fā)到具體服務(wù),,如登錄、刷新token等
  • 需要合法身份登錄之后才能訪問的接口,,根據(jù)傳入的Access token進行構(gòu)造頭部,,頭部主要包括userId等信息,可根據(jù)自己的實際業(yè)務(wù)在auth服務(wù)中進行設(shè)置,。
  • 最后,,比較重要的一點,引入Spring Security的資源服務(wù)器配置,,對于暴露接口設(shè)置permitAll(),,其余接口進入身份合法性校驗的流程,調(diào)用auth服務(wù),,如果通過則正常繼續(xù)轉(zhuǎn)發(fā),,否則拋出異常,返回401,。

繪制的流程圖如下:

gwflow

網(wǎng)關(guān)路由流程圖

2.1 permitAll實現(xiàn)

對外暴露的接口可以直接訪問,,這可以依賴配置文件,而配置文件又可以通過配置中心進行動態(tài)更新,,所以不用擔心有hard-code的問題,。
在配置文件中定義需要permitall的路徑。

1
2
3
4
5
6
auth:
permitall:
-
pattern: /login/**
-
pattern: /web/public/**

服務(wù)啟動時,,讀入相應(yīng)的Configuration,,下面的配置屬性讀取以auth開頭的配置。

1
2
3
4
5
@Bean
@ConfigurationProperties(prefix = "auth")
public PermitAllUrlProperties getPermitAllUrlProperties() {
return new PermitAllUrlProperties();
}

當然還需要有PermitAllUrlProperties對應(yīng)的實體類,,比較簡單,,不列出來了。

2.2 加強頭部

Filter過濾器,,它是Servlet技術(shù)中最實用的技術(shù),,Web開發(fā)人員通過Filter技術(shù),對web服務(wù)器管理的所有web資源進行攔截,。這邊使用Filter進行頭部增強,,解析請求中的token,構(gòu)造統(tǒng)一的頭部信息,,到了具體服務(wù),,可以利用頭部中的userId進行操作權(quán)限獲取與判斷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class HeaderEnhanceFilter implements Filter {
//...
@Autowired
private PermitAllUrlProperties permitAllUrlProperties;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
//主要的過濾方法
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String authorization = ((HttpServletRequest) servletRequest).getHeader("Authorization");
String requestURI = ((HttpServletRequest) servletRequest).getRequestURI();
// test if request url is permit all , then remove authorization from header
LOGGER.info(String.format("Enhance request URI : %s.", requestURI));
//將isPermitAllUrl的請求進行傳遞
if(isPermitAllUrl(requestURI) && isNotOAuthEndpoint(requestURI)) {
//移除頭部,,但不包括登錄端點的頭部
HttpServletRequest resetRequest = removeValueFromRequestHeader((HttpServletRequest) servletRequest);
filterChain.doFilter(resetRequest, servletResponse);
return;
}
//判斷是不是符合規(guī)范的頭部
if (StringUtils.isNotEmpty(authorization)) {
if (isJwtBearerToken(authorization)) {
try {
authorization = StringUtils.substringBetween(authorization, ".");
String decoded = new String(Base64.decodeBase64(authorization));
Map properties = new ObjectMapper().readValue(decoded, Map.class);
//解析authorization中的token,,構(gòu)造USER_ID_IN_HEADER
String userId = (String) properties.get(SecurityConstants.USER_ID_IN_HEADER);
RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, userId);
} catch (Exception e) {
LOGGER.error("Failed to customize header for the request", e);
}
}
} else {
//為了適配,設(shè)置匿名頭部
RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, ANONYMOUS_USER_ID);
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
//...
}

上面代碼列出了頭部增強的基本處理流程,,將isPermitAllUrl的請求進行直接傳遞,,否則判斷是不是符合規(guī)范的頭部,,然后解析authorization中的token,構(gòu)造USER_ID_IN_HEADER,。最后為了適配,,設(shè)置匿名頭部。
需要注意的是,,HeaderEnhanceFilter也要進行注冊,。Spring 提供了FilterRegistrationBean類,此類提供setOrder方法,,可以為filter設(shè)置排序值,讓spring在注冊web filter之前排序后再依次注冊,。

2.3 資源服務(wù)器配置

利用資源服務(wù)器的配置,,控制哪些是暴露端點不需要進行身份合法性的校驗,直接路由轉(zhuǎn)發(fā),,哪些是需要進行身份loadAuthentication,,調(diào)用auth服務(wù)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//...
//配置permitAll的請求pattern,,依賴于permitAllUrlProperties對象
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests()
.antMatchers(permitAllUrlProperties.getPermitallPatterns()).permitAll()
.anyRequest().authenticated();
}
//通過自定義的CustomRemoteTokenServices,,植入身份合法性的相關(guān)驗證
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
CustomRemoteTokenServices resourceServerTokenServices = new CustomRemoteTokenServices();
//...
resources.tokenServices(resourceServerTokenServices);
}
}

資源服務(wù)器的配置大家看了筆者之前的文章應(yīng)該很熟悉,此處不過多重復(fù)講了,。關(guān)于ResourceServerSecurityConfigurer配置類,,之前的安全系列文章已經(jīng)講過,ResourceServerTokenServices接口,,當時我們也用到了,,只不過用的是默認的DefaultTokenServices。這邊通過自定義的CustomRemoteTokenServices,,植入身份合法性的相關(guān)驗證,。

當然這個配置還要引入Spring Cloud Security oauth2的相應(yīng)依賴。

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

2.4 自定義RemoteTokenServices實現(xiàn)

ResourceServerTokenServices接口其中的一個實現(xiàn)是RemoteTokenServices,。

Queries the /check_token endpoint to obtain the contents of an access token.
If the endpoint returns a 400 response, this indicates that the token is invalid.

RemoteTokenServices主要是查詢auth服務(wù)的/check_token端點以獲取一個token的校驗結(jié)果,。如果有錯誤,則說明token是不合法的,。筆者這邊的的CustomRemoteTokenServices實現(xiàn)就是沿用該思路,。需要注意的是,筆者的項目基于Spring cloud,,auth服務(wù)是多實例的,,所以這邊使用了Netflix Ribbon獲取auth服務(wù)進行負載均衡。Spring Cloud Security添加如下默認配置,,對應(yīng)auth服務(wù)中的相應(yīng)端點,。

1
2
3
4
5
6
7
8
9
security:
oauth2:
client:
accessTokenUri: /oauth/token
clientId: gateway
clientSecret: gateway
resource:
userInfoUri: /user
token-info-uri: /oauth/check_token

至于具體的CustomRemoteTokenServices實現(xiàn),,可以參考上面講的思路以及RemoteTokenServices,很簡單,,此處略去,。

至此,網(wǎng)關(guān)服務(wù)的增強完成,,下面看一下我們對auth服務(wù)和后端backend服務(wù)的實現(xiàn),。
強調(diào)一下,為什么頭部傳遞的userId等信息需要在網(wǎng)關(guān)構(gòu)造,?讀者可以自己思考一下,,結(jié)合安全等方面,??筆者暫時不給出答案,。

3. auth整合

auth服務(wù)的整合修改,,其實沒那么多,之前對于user,、role以及permission之間的定義和關(guān)系沒有給出實現(xiàn),,這部分的sql語句已經(jīng)在auth.sql中。所以為了能給出一個完整的實例,,筆者把這部分實現(xiàn)給補充了,,主要就是user-role,role,、role-permission的相應(yīng)接口定義與實現(xiàn),,實現(xiàn)增刪改查。

讀者要是想?yún)⒖颊享椖窟M行實際應(yīng)用,,這部分完全可以根據(jù)自己的業(yè)務(wù)進行增強,,包括token的創(chuàng)建,其自定義的信息還可以在網(wǎng)關(guān)中進行統(tǒng)一處理,,構(gòu)造好之后傳遞給后端服務(wù),。

這邊的接口只是列出了需要的幾個,其他接口沒寫(因為懶,。,。)

這兩個接口也是給backend項目用來獲取相應(yīng)的userId權(quán)限。

1
2
3
4
5
6
7
8
9
//根據(jù)userId獲取用戶對應(yīng)的權(quán)限
@RequestMapping(method = RequestMethod.GET, value = "/api/userPermissions?userId={userId}",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<Permission> getUserPermissions(@RequestParam("userId") String userId);
//根據(jù)userId獲取用戶對應(yīng)的accessLevel(好像暫時沒用到,。,。)
@RequestMapping(method = RequestMethod.GET, value = "/api/userAccesses?userId={userId}",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<UserAccess> getUserAccessList(@RequestParam("userId") String userId);

好了,這邊的實現(xiàn)已經(jīng)講完了,,具體見項目中的實現(xiàn),。

4. backend項目實現(xiàn)

本節(jié)是進行實現(xiàn)一個backend的實例,后端項目主要實現(xiàn)哪些功能呢?我們考慮一下,,之前網(wǎng)關(guān)服務(wù)和auth服務(wù)所做的準備:

  • 網(wǎng)關(guān)構(gòu)造的頭部userId(可能還有其他信息,,這邊只是示例),可以在backend獲得
  • 轉(zhuǎn)發(fā)到backend服務(wù)的請求,,都是經(jīng)過身份合法性校驗,,或者是直接對外暴露的接口
  • auth服務(wù),提供根據(jù)userId進行獲取相應(yīng)的權(quán)限的接口

根據(jù)這些,,筆者繪制了一個backend的通用流程圖:

bf

backend流程圖

上面的流程圖其實已經(jīng)非常清晰了,,首先經(jīng)過filter過濾器,填充SecurityContextHolder的上下文,。其次,,通過切面來實現(xiàn)注解,是否需要進入切面表達式處理,。不需要的話,,直接執(zhí)行接口內(nèi)的方法;否則解析注解中需要的權(quán)限,,判斷是否有權(quán)限執(zhí)行,有的話繼續(xù)執(zhí)行,,否則返回403 forbidden,。

4.1 filter過濾器

Filter過濾器,和上面網(wǎng)關(guān)使用一樣,,攔截客戶的HttpServletRequest,。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class AuthorizationFilter implements Filter {
@Autowired
private FeignAuthClient feignAuthClient;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("過濾器正在執(zhí)行...");
// pass the request along the filter chain
String userId = ((HttpServletRequest) servletRequest).getHeader(SecurityConstants.USER_ID_IN_HEADER);
if (StringUtils.isNotEmpty(userId)) {
UserContext userContext = new UserContext(UUID.fromString(userId));
userContext.setAccessType(AccessType.ACCESS_TYPE_NORMAL);
List<Permission> permissionList = feignAuthClient.getUserPermissions(userId);
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
for (Permission permission : permissionList) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority();
authority.setAuthority(permission.getPermission());
authorityList.add(authority);
}
CustomAuthentication userAuth = new CustomAuthentication();
userAuth.setAuthorities(authorityList);
userContext.setAuthorities(authorityList);
userContext.setAuthentication(userAuth);
SecurityContextHolder.setContext(userContext);
}
filterChain.doFilter(servletRequest, servletResponse);
}
//...
}

上述代碼主要實現(xiàn)了,根據(jù)請求頭中的userId,,利用feign client獲取auth服務(wù)中的該user所具有的權(quán)限集合,。之后構(gòu)造了一個UserContext,UserContext是自定義的,,實現(xiàn)了Spring Security的UserDetails, SecurityContext接口,。

4.2 通過切面來實現(xiàn)@PreAuth注解

基于Spring的項目,使用Spring的AOP切面實現(xiàn)注解是比較方便的一件事,,這邊我們使用了自定義的注解@PreAuth

1
2
3
4
5
6
7
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
String value();
}

Target用于描述注解的使用范圍,,超出范圍時編譯失敗,可以用在方法或者類上面。在運行時生效,。不了解注解相關(guān)知識的,,可以自行Google。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Component
@Aspect
public class AuthAspect {
@Pointcut("@annotation(com.blueskykong.auth.demo.annotation.PreAuth)")
private void cut() {
}
/**
* 定制一個環(huán)繞通知,,當想獲得注解里面的屬性,,可以直接注入該注解
*
* @param joinPoint
* @param preAuth
*/
@Around("cut()&&@annotation(preAuth)")
public Object record(ProceedingJoinPoint joinPoint, PreAuth preAuth) throws Throwable {
//取出注解中的表達式
String value = preAuth.value();
//Spring EL 對value進行解析
SecurityExpressionOperations operations = new CustomerSecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication());
StandardEvaluationContext operationContext = new StandardEvaluationContext(operations);
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(value);
//獲取表達式判斷的結(jié)果
boolean result = expression.getValue(operationContext, boolean.class);
if (result) {
//繼續(xù)執(zhí)行接口內(nèi)的方法
return joinPoint.proceed();
}
return "Forbidden";
}
}

因為Aspect作用在bean上,所以先用Component把這個類添加到容器中。@Pointcut定義要攔截的注解,。@Around定制一個環(huán)繞通知,,當想獲得注解里面的屬性,可以直接注入該注解,。切面表達式內(nèi)主要實現(xiàn)了,,利用Spring EL對value進行解析,將SecurityContextHolder.getContext()轉(zhuǎn)換成標準的操作上下文,,然后解析注解中的表達式,,最后獲取對表達式判斷的結(jié)果。

1
2
3
4
5
6
public class CustomerSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomerSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
}

CustomerSecurityExpressionRoot繼承的是抽象類SecurityExpressionRoot,,而我們用到的實際表達式是定義在SecurityExpressionOperations接口,,SecurityExpressionRoot又實現(xiàn)了SecurityExpressionOperations接口。不過這里面的具體判斷實現(xiàn),,Spring Security 調(diào)用的也是Spring EL,。

4.3 controller接口

下面我們看看最終接口是怎么用上面實現(xiàn)的注解。

1
2
3
4
5
@RequestMapping(value = "/test", method = RequestMethod.GET)
@PreAuth("hasAuthority('CREATE_COMPANY')") // 還可以定義很多表達式,,如hasRole('Admin')
public String test() {
return "ok";
}

@PreAuth中,,可以定義的表達式很多,可以看SecurityExpressionOperations接口中的方法,。目前筆者只是實現(xiàn)了hasAuthority()表達式,,如果你想支持其他所有表達式,只需要構(gòu)造相應(yīng)的SecurityContextHolder即可,。

4.4 為什么這樣設(shè)計,?

有些讀者看了上面的設(shè)計,既然好多用到了Spring Security的工具類,,肯定會問,,為什么要引入這么復(fù)雜的工具類?

其實很簡單,,首先因為SecurityExpressionOperations接口中定義的表達式足夠多,,且較為合理,能夠覆蓋我們在平時用到的大部分場景,;其次,,筆者之前的設(shè)計是直接在注解中指定所需權(quán)限,沒有擴展性,,且可讀性差,;最后,Spring Security 4 確實引入了@PreAuthorize,@PostAuthorize等注解,,本來想用來著,,自己嘗試了一下,發(fā)現(xiàn)對于微服務(wù)架構(gòu)這樣的接口級別的操作權(quán)限校驗不是很適合,十多個過濾器太過復(fù)雜,,而且還涉及到的Principal,、Credentials等信息,這些已經(jīng)在auth系統(tǒng)實現(xiàn)了身份合法性校驗,。筆者認為這邊的功能實現(xiàn)并不是很復(fù)雜,,需要很輕量的實現(xiàn),讀者有興趣可以試著這部分的實現(xiàn)封裝成jar包或者Spring Boot的starter,。

4.5 后期優(yōu)化

優(yōu)化的地方主要有兩點:

  • 現(xiàn)在的設(shè)計是,,每次請求過來都會去調(diào)用auth服務(wù)獲取該user相應(yīng)的權(quán)限信息。而后端微服務(wù)數(shù)量有很多,,沒必要每個服務(wù),,或者說一個服務(wù)的多個服務(wù)實例,每次都去調(diào)用auth服務(wù),,筆者認為完全可以引入redis集群的緩存機制,,在請求到達一個服務(wù)的某個實例時,首先去查詢對應(yīng)的user的緩存中的權(quán)限,,如果沒有再調(diào)用auth服務(wù),,最后寫入redis緩存。當然,,如果權(quán)限更新了,,在auth服務(wù)肯定要delete相應(yīng)的user權(quán)限緩存。
  • 關(guān)于被拒絕的請求,,在切面表達式中,直接返回了對象,,筆者認為可以和response status 403進行綁定,,定制返回對象的內(nèi)容,返回的response更加友好,。

5. 總結(jié)

如上,,首先講了整合的設(shè)計思路,主要包含三個服務(wù):gateway,、auth和backend demo,。整合的項目,總體比較復(fù)雜,,其中g(shù)ateway服務(wù)擴充了好多內(nèi)容,,對于暴露的接口進行路由轉(zhuǎn)發(fā),這邊引入了Spring Security 的starter,,配置資源服務(wù)器對暴露的路徑進行放行,;對于其他接口需要調(diào)用auth服務(wù)進行身份合法性校驗,保證到達backend的請求都是合法的或者公開的接口;auth服務(wù)在之前的基礎(chǔ)上,,補充了role,、permission、user相應(yīng)的接口,,供外部調(diào)用,;backend demo是新起的服務(wù),實現(xiàn)了接口級別的操作權(quán)限的校驗,,主要用到了自定義注解和Spring AOP切面,。

由于實現(xiàn)的細節(jié)實在有點多,本文限于篇幅,,只對部分重要的實現(xiàn)進行列出與講解,。如果讀者有興趣實際的應(yīng)用,可以根據(jù)實際的業(yè)務(wù)進行擴增一些信息,,如auth授權(quán)的token,、網(wǎng)關(guān)攔截請求構(gòu)造的頭部信息、注解支持的表達式等等,。

可以優(yōu)化的地方當然還有很多,,整合項目中設(shè)計不合理的地方,各位同學(xué)可以多多提意見,。

推薦閱讀

系列文章:認證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計與實現(xiàn)

源碼

網(wǎng)關(guān),、auth權(quán)限服務(wù)和backend服務(wù)的整合項目地址為:
GitHub:https://github.com/keets2012/microservice-integration
或者 碼云:https:///keets/microservice-integration

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多