一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring Cloud gateway 網關如何攔截Post請求日志

Spring Cloud gateway 網關如何攔截Post請求日志

2021-10-13 14:53wsdl-king Java教程

這篇文章主要介紹了Spring Cloud gateway 網關如何攔截Post請求日志的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

gateway版本是 2.0.1

1.pom結構

(部分內部項目依賴已經隱藏)

?
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
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--監控相關-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- redis -->
<!--<dependency>-->
    <!--<groupId>org.springframework.boot</groupId>-->
    <!--<artifactId>spring-boot-starter-data-redis</artifactId>-->
<!--</dependency>-->
<!-- test-scope -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.11</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.11</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>
<!--第三方的jdbctemplatetool-->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>jdbctemplatetool</artifactId>
    <version>1.0.4-RELEASE</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- alibaba start -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>

2.表結構

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `zc_log_notes` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '日志信息記錄表主鍵id',
  `notes` varchar(255) DEFAULT NULL COMMENT '操作記錄信息',
  `amenu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '一級菜單',
  `bmenu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '二級菜單',
  `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '操作人ip地址,先用varchar存',
  `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '請求值',
  `response` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '返回值',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作時間',
  `create_user` int(11) DEFAULT NULL COMMENT '操作人id',
  `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '響應時間',
  `status` int(1) NOT NULL DEFAULT '1' COMMENT '響應結果1成功0失敗',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='日志信息記錄表';

3.實體結構

?
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@Table(catalog = "zhiche", name = "zc_log_notes")
public class LogNotes {
    /**
     * 日志信息記錄表主鍵id
     */
    private Integer id;
    /**
     * 操作記錄信息
     */
    private String notes;
    /**
     * 一級菜單
     */
    private String amenu;
    /**
     * 二級菜單
     */
    private String bmenu;
    /**
     * 操作人ip地址,先用varchar存
     */
    private String ip;
    /**
     * 請求參數記錄
     */
    private String params;
    /**
     * 返回結果記錄
     */
    private String response;
    /**
     * 操作時間
     */
    private Date createTime;
    /**
     * 操作人id
     */
    private Integer createUser;
    /**
     * 響應時間
     */
    private Date endTime;
    /**
     * 響應結果1成功0失敗
     */
    private Integer status;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getNotes() {
        return notes;
    }
    public void setNotes(String notes) {
        this.notes = notes;
    }
    public String getAmenu() {
        return amenu;
    }
    public void setAmenu(String amenu) {
        this.amenu = amenu;
    }
    public String getBmenu() {
        return bmenu;
    }
    public void setBmenu(String bmenu) {
        this.bmenu = bmenu;
    }
    public String getIp() {
        return ip;
    }
    public void setIp(String ip) {
        this.ip = ip;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
    public Integer getCreateUser() {
        return createUser;
    }
    public void setCreateUser(Integer createUser) {
        this.createUser = createUser;
    }
    public Date getEndTime() {
        return endTime;
    }
    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
    public String getParams() {
        return params;
    }
    public void setParams(String params) {
        this.params = params;
    }
    public String getResponse() {
        return response;
    }
    public void setResponse(String response) {
            this.response = response;
    }
    public void setAppendResponse(String response){
        if (StringUtils.isNoneBlank(this.response)) {
            this.response = this.response + response;
        } else {
            this.response = response;
        }
    }
}

4.dao層和Service層省略..

5.filter代碼

1. RequestRecorderGlobalFilter 實現了GlobalFilter和Order

?
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package com.zc.gateway.filter;
import com.zc.entity.LogNotes;
import com.zc.gateway.service.FilterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
 * @author qiwenshuai
 * @note 目前只記錄了request方式為POST請求的方式
 * @since 19-5-16 17:29 by jdk 1.8
 */
@Component
public class RequestRecorderGlobalFilter implements GlobalFilter, Ordered {
    @Autowired
    FilterService filterService;
    private Logger logger = LoggerFactory.getLogger(RequestRecorderGlobalFilter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest originalRequest = exchange.getRequest();
        URI originalRequestUrl = originalRequest.getURI();
        //只記錄http的請求
        String scheme = originalRequestUrl.getScheme();
        if ((!"http".equals(scheme) && !"https".equals(scheme))) {
            return chain.filter(exchange);
        }
        //這是我要打印的log-StringBuilder
        StringBuilder logbuilder = new StringBuilder();
        //我自己的log實體
        LogNotes logNotes = new LogNotes();
        // 返回解碼
        RecorderServerHttpResponseDecorator response = new RecorderServerHttpResponseDecorator(exchange.getResponse(), logNotes, filterService);
        //請求解碼
        RecorderServerHttpRequestDecorator recorderServerHttpRequestDecorator = new RecorderServerHttpRequestDecorator(exchange.getRequest());
        //增加過濾攔截吧
        ServerWebExchange ex = exchange.mutate()
                .request(recorderServerHttpRequestDecorator)
                .response(response)
                .build();
        //  觀察者模式 打印一下請求log
        // 這里可以在 配置文件中我進行配置
//        if (logger.isDebugEnabled()) {
        response.beforeCommit(() -> Mono.defer(() -> printLog(logbuilder, response)));
//        }
        return recorderOriginalRequest(logbuilder, ex, logNotes)
                .then(chain.filter(ex))
                .then();
    }
    private Mono<Void> recorderOriginalRequest(StringBuilder logBuffer, ServerWebExchange exchange, LogNotes logNotes) {
        logBuffer.append(System.currentTimeMillis())
                .append("------------");
        ServerHttpRequest request = exchange.getRequest();
        Mono<Void> result = recorderRequest(request, logBuffer.append("\n原始請求:\n"), logNotes);
        try {
            filterService.addLog(logNotes);
        } catch (Exception e) {
            logger.error("保存請求參數出現錯誤, e->{}", e.getMessage());
        }
        return result;
    }
    /**
     * 記錄原始請求邏輯
     */
    private Mono<Void> recorderRequest(ServerHttpRequest request, StringBuilder logBuffer, LogNotes logNotes) {
        URI uri = request.getURI();
        HttpMethod method = request.getMethod();
        HttpHeaders headers = request.getHeaders();
        logNotes.setIp(headers.getHost().getHostString());
        logNotes.setAmenu("一級菜單");
        logNotes.setBmenu("二級菜單");
        logNotes.setNotes("操作記錄");
        logBuffer
                .append(method.toString()).append(' ')
                .append(uri.toString()).append('\n');
        logBuffer.append("------------請求頭------------\n");
        headers.forEach((name, values) -> {
            values.forEach(value -> {
                logBuffer.append(name).append(":").append(value).append('\n');
            });
        });
        Charset bodyCharset = null;
        if (hasBody(method)) {
            long length = headers.getContentLength();
            if (length <= 0) {
                logBuffer.append("------------無body------------\n");
            } else {
                logBuffer.append("------------body 長度:").append(length).append(" contentType:");
                MediaType contentType = headers.getContentType();
                if (contentType == null) {
                    logBuffer.append("null,不記錄body------------\n");
                } else if (!shouldRecordBody(contentType)) {
                    logBuffer.append(contentType.toString()).append(",不記錄body------------\n");
                } else {
                    bodyCharset = getMediaTypeCharset(contentType);
                    logBuffer.append(contentType.toString()).append("------------\n");
                }
            }
        }
        if (bodyCharset != null) {
            return doRecordReqBody(logBuffer, request.getBody(), bodyCharset, logNotes)
                    .then(Mono.defer(() -> {
                        logBuffer.append("\n------------ end ------------\n\n");
                        return Mono.empty();
                    }));
        } else {
            logBuffer.append("------------ end ------------\n\n");
            return Mono.empty();
        }
    }
    //日志輸出返回值
    private Mono<Void> printLog(StringBuilder logBuilder, ServerHttpResponse response) {
        HttpStatus statusCode = response.getStatusCode();
        assert statusCode != null;
        logBuilder.append("響應:").append(statusCode.value()).append(" ").append(statusCode.getReasonPhrase()).append('\n');
        HttpHeaders headers = response.getHeaders();
        logBuilder.append("------------響應頭------------\n");
        headers.forEach((name, values) -> {
            values.forEach(value -> {
                logBuilder.append(name).append(":").append(value).append('\n');
            });
        });
        logBuilder.append("\n------------ end at ")
                .append(System.currentTimeMillis())
                .append("------------\n\n");
        logger.info(logBuilder.toString());
        return Mono.empty();
    }
    //
    @Override
    public int getOrder() {
        //在GatewayFilter之前執行
        return -1;
    }
    private boolean hasBody(HttpMethod method) {
        //只記錄這3種謂詞的body
//        if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.PATCH)
        return true;
//        return false;
    }
    //記錄簡單的常見的文本類型的request的body和response的body
    private boolean shouldRecordBody(MediaType contentType) {
        String type = contentType.getType();
        String subType = contentType.getSubtype();
        if ("application".equals(type)) {
            return "json".equals(subType) || "x-www-form-urlencoded".equals(subType) || "xml".equals(subType) || "atom+xml".equals(subType) || "rss+xml".equals(subType);
        } else if ("text".equals(type)) {
            return true;
        }
        //暫時不記錄form
        return false;
    }
    // 獲取請求的參數
    private Mono<Void> doRecordReqBody(StringBuilder logBuffer, Flux<DataBuffer> body, Charset charset, LogNotes logNotes) {
        return DataBufferUtils.join(body).doOnNext(buffer -> {
            CharBuffer charBuffer = charset.decode(buffer.asByteBuffer());
            //記錄我實體的請求體
            logNotes.setParams(charBuffer.toString());
            logBuffer.append(charBuffer.toString());
            DataBufferUtils.release(buffer);
        }).then();
    }
    private Charset getMediaTypeCharset(@Nullable MediaType mediaType) {
        if (mediaType != null && mediaType.getCharset() != null) {
            return mediaType.getCharset();
        } else {
            return StandardCharsets.UTF_8;
        }
    }
}

2.RecorderServerHttpRequestDecorator 繼承了ServerHttpRequestDecorator

?
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
package com.zc.gateway.filter;
import com.zc.entity.LogNotes;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.LinkedList;
import java.util.List;
/**
 * @author qiwenshuai
 * @note
 * @since 19-5-16 17:30 by jdk 1.8
 */
// request
public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator {
    private final List<DataBuffer> dataBuffers = new LinkedList<>();
    private boolean bufferCached = false;
    private Mono<Void> progress = null;
    public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) {
        super(delegate);
    }
//重寫request請求體
    @Override
    public Flux<DataBuffer> getBody() {
        synchronized (dataBuffers) {
            if (bufferCached)
                return copy();
            if (progress == null) {
                progress = cache();
            }
            return progress.thenMany(Flux.defer(this::copy));
        }
    }
    private Flux<DataBuffer> copy() {
        return Flux.fromIterable(dataBuffers)
                .map(buf -> buf.factory().wrap(buf.asByteBuffer()));
    }
    private Mono<Void> cache() {
        return super.getBody()
                .map(dataBuffers::add)
                .then(Mono.defer(()-> {
                    bufferCached = true;
                    progress = null;
                    return Mono.empty();
                }));
    }
}

3.RecorderServerHttpResponseDecorator 繼承了 ServerHttpResponseDecorator

?
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
59
60
61
62
63
64
65
66
67
68
package com.zc.gateway.filter;
import com.zc.entity.LogNotes;
import com.zc.gateway.service.FilterService;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
/**
 * @author qiwenshuai
 * @note
 * @since 19-5-16 17:32 by jdk 1.8
 */
public class RecorderServerHttpResponseDecorator extends ServerHttpResponseDecorator {
    private Logger logger = LoggerFactory.getLogger(RecorderServerHttpResponseDecorator.class);
    private LogNotes logNotes;
    private FilterService filterService;
    RecorderServerHttpResponseDecorator(ServerHttpResponse delegate, LogNotes logNotes, FilterService filterService) {
        super(delegate);
        this.logNotes = logNotes;
        this.filterService = filterService;
    }
    /**
     * 基于netty,我這里需要顯示的釋放一次dataBuffer,但是slice出來的byte是不需要釋放的,
     * 與下層共享一個字符串緩沖池,gateway過濾器使用的是nettyWrite類,會發生response數據多次才能返回完全。
     * 在 ServerHttpResponseDecorator 之后會釋放掉另外一個refCount.
     */
    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        DataBufferFactory bufferFactory = this.bufferFactory();
        if (body instanceof Flux) {
            Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
            Publisher<? extends DataBuffer> re = fluxBody.map(dataBuffer -> {
                // probably should reuse buffers
                byte[] content = new byte[dataBuffer.readableByteCount()];
                // 數據讀入數組
                dataBuffer.read(content);
                // 釋放掉內存
                DataBufferUtils.release(dataBuffer);
                // 記錄返回值
                String s = new String(content, Charset.forName("UTF-8"));
                logNotes.setAppendResponse(s);
                try {
                    filterService.updateLog(logNotes);
                } catch (Exception e) {
                    logger.error("Response值修改日志記錄出現錯誤->{}", e);
                }
                byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
                return bufferFactory.wrap(uppedContent);
            });
            return super.writeWith(re);
        }
        return super.writeWith(body);
    }
    @Override
    public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return writeWith(Flux.from(body).flatMapSequential(p -> p));
    }
}

注意:

網關過濾返回值 底層用到了Netty服務,在response返回的時候,有時候會寫的數據是不全的,于是我在實體類中新增了一個setAppendResponse方法進行拼接, 再者,gateway的過濾器是鏈式結構,需要定義order排序為最先(-1),然后和預置的gateway過濾器做一個combine.

代碼中用到的 dataBuffer 結構,底層其實也是類似netty的byteBuffer,用到了字節數組池,同時也用到了 引用計數器 (refInt).

為了讓jvm在gc的時候垃圾得到回收,避免內存泄露,我們需要在轉換字節使用的地方,顯示的釋放一次

?
1
DataBufferUtils.release(dataBuffer);

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/gpdsjqws/article/details/90437732

延伸 · 閱讀

精彩推薦
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7472021-02-04
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
主站蜘蛛池模板: 国产图片综合区 | 日韩在线中文字幕 | 5g影院天天| 国产日韩精品欧美一区 | 亚洲四虎在线 | 黄在线观看www免费看 | 手机国产乱子伦精品视频 | 女人肮脏的交易中文字幕未删减版 | 武侠古典久久亚洲精品 | 精品在线免费观看 | 91久久国产成人免费观看资源 | 秋霞午夜伦午夜高清福利片 | 四虎永久在线精品国产馆v视影院 | 国产精品高清一区二区三区 | 国产成人精品免费2021 | 日韩在线观看网址 | 色戒完整版 | 国产在视频 | 日韩欧美精品一区二区 | 国产成年人网站 | 亚洲四虎 | 荡女人人爱全文免费阅读 | 纲手被强喷水羞羞漫画 | 激情乱文| 亚洲 欧美 国产 在线观看 | 动漫精品午夜在线播放 | 国自产精品手机在线视频 | 本土自拍| pregnant欧美孕交xxx | 国产精品欧美韩国日本久久 | 乌克兰17一18处交 | 日本videossexx日本人 | a级毛片毛片免费很很综合 a级黄色视屏 | 边吃胸边膜下刺激免费男对女 | 无限资源在线观看完整版免费下载 | 久草热8精品视频在线观看 久草草在线视视频 | 美女被的在线网站91 | 欧美又大又粗又长又硬 | 精品国产福利片在线观看 | 国产精品二区高清在线 | 亚洲琪琪 |