├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── swordintent
│ │ └── chatgpt
│ │ ├── ChatgptClient.java
│ │ ├── ChatgptClientImpl.java
│ │ ├── client
│ │ ├── DataClient.java
│ │ └── TokenInterceptor.java
│ │ ├── protocol
│ │ ├── ChatGptConfig.java
│ │ ├── ChatRequest.java
│ │ └── ChatResponse.java
│ │ └── utils
│ │ └── JsonUtils.java
└── resources
│ └── server.py
└── test
└── java
└── com
└── swordintent
└── chatgpt
└── AppTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 swordintent
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chatgpt-web-api
2 | **A Java Version ChatGPT SDK**
3 |
4 | integrate with [acheong08/ChatGPT](https://github.com/acheong08/ChatGPT), use official Api of openAI(2023.3.2).
5 |
6 | # update
7 |
8 | `2023.3.2 use official api, model is not free, but you have $18 free quota`
9 |
10 | ```
11 | pip3 install --upgrade revChatGPT
12 | ```
13 |
14 | # How TO
15 |
16 | [中文](https://github.com/swordintent/chatgpt-web-api/wiki/%E7%AE%80%E4%BB%8B)
17 |
18 | ### Start a python server(python >= 3.7)
19 |
20 | you will find it in `src/main/resources/server.py`, and run:
21 |
22 | ```
23 | pip3 install flask flask-restful
24 | pip3 install --upgrade revChatGPT
25 | python3 server.py
26 | ```
27 |
28 | by default, it listen on http://127.0.0.1:5000
29 |
30 |
31 | ### Import maven jar
32 |
33 | https://search.maven.org/artifact/com.swordintent.chatgpt/web-api/
34 |
35 | Maven
36 | ```
37 |
38 | com.swordintent.chatgpt
39 | web-api
40 | 1.0.0
41 |
42 | ```
43 |
44 | Gradle
45 |
46 | ```
47 | implementation 'com.swordintent.chatgpt:web-api:1.0.0'
48 | ```
49 |
50 |
51 | ### Enjoy it in your project
52 |
53 |
54 | 1. first, you can invoke `chatgptClient.init(address, chatGptConfig)` method to init client.
55 |
56 | * you need [create](https://platform.openai.com/) your account firstly.
57 | * modify `password`, the `password` is your openAI's api-keys, you can find [here](https://platform.openai.com/account/api-keys).
58 | * set `address` to http://127.0.0.1:5000 or another.
59 |
60 |
61 | ```
62 | ChatgptClient chatgptClient = ChatgptClientImpl.getInstance();
63 | ChatGptConfig chatGptConfig = ChatGptConfig.builder()
64 | .password("")
65 | .build();
66 | String address = "http://127.0.0.1:5000";
67 | chatgptClient.init(address, chatGptConfig);
68 | ```
69 |
70 | 2. then you can invoke chat `chatgptClient.chat(request)` method to chat.
71 |
72 | * in first round chat, `conversationId` would be null.
73 | when you want reset multiple rounds set them to null too.
74 |
75 |
76 | ```
77 | //first round or reset multiple rounds
78 | ChatRequest request = ChatRequest.builder()
79 | .prompt(content)
80 | .conversationId(null)
81 | .build();
82 |
83 | ChatResponse response = chatgptClient.chat(request);
84 |
85 | ```
86 |
87 |
88 | * if you want to chat multiple rounds. you need get `conversationId` from response and set them to next chat request.
89 |
90 |
91 | ```
92 | //multiple rounds
93 | ChatRequest request = ChatRequest.builder()
94 | .prompt(content)
95 | .conversationId(response.getConversationId())
96 | .build();
97 | ChatResponse response = chatgptClient.chat(request);
98 | ```
99 |
100 |
101 | 3. **Notice**.
102 |
103 | * the `conversationId` now is the full object of python chatbot object, so maybe it was huge.
104 |
105 | * you must set `conversationId` to null in your java program when you restart your python server.
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | com.swordintent.chatgpt
8 | web-api
9 | 1.0.0
10 |
11 | web-api
12 | A Java Version ChatGPT SDK.
13 | https://github.com/swordintent/chatgpt-web-api
14 |
15 |
16 | MIT License
17 | http://www.opensource.org/licenses/mit-license.php
18 | repo
19 |
20 |
21 |
22 |
23 | liuhe36
24 | liuhe36@gmail.com
25 | https://github.com/swordintent
26 | +8
27 |
28 |
29 |
30 | scm:git:git@github.com/swordintent/chatgpt-web-api.git
31 | scm:git:git@github.com/swordintent/chatgpt-web-api.git
32 | git@github.com/swordintent/chatgpt-web-api.git
33 |
34 |
35 |
36 | UTF-8
37 | 1.8
38 | 1.8
39 |
40 |
41 |
42 |
43 | junit
44 | junit
45 | 4.11
46 | test
47 |
48 |
49 | com.google.code.gson
50 | gson
51 | 2.10
52 |
53 |
54 | com.squareup.okhttp3
55 | okhttp
56 | 3.12.2
57 |
58 |
59 | org.projectlombok
60 | lombok
61 | 1.18.24
62 | provided
63 |
64 |
65 |
66 |
67 |
68 |
69 | org.apache.maven.plugins
70 | maven-compiler-plugin
71 | 3.8.1
72 |
73 | 1.8
74 | 1.8
75 |
76 |
77 |
78 |
79 | org.sonatype.plugins
80 | nexus-staging-maven-plugin
81 | 1.6.7
82 | true
83 |
84 |
85 | sonatype_releases
86 | https://s01.oss.sonatype.org/
87 |
88 | true
89 |
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-source-plugin
95 | 2.2.1
96 |
97 |
98 | attach-sources
99 |
100 | jar-no-fork
101 |
102 |
103 |
104 |
105 |
106 | org.apache.maven.plugins
107 | maven-javadoc-plugin
108 | 2.9.1
109 |
110 | true
111 | UTF-8
112 | UTF-8
113 | UTF-8
114 |
115 | -Xdoclint:none
116 |
117 |
118 |
119 | attach-javadocs
120 |
121 | jar
122 |
123 |
124 |
125 |
126 |
127 |
128 | org.apache.maven.plugins
129 | maven-gpg-plugin
130 | 1.5
131 |
132 |
133 |
134 | gpg
135 | verify
136 |
137 | sign
138 |
139 |
140 | swordintent
141 | swordintent
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | sonatype_releases
151 | https://s01.oss.sonatype.org/content/repositories/snapshots
152 |
153 |
154 | sonatype_snapshots
155 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/ChatgptClient.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt;
2 |
3 | import com.swordintent.chatgpt.protocol.ChatGptConfig;
4 | import com.swordintent.chatgpt.protocol.ChatRequest;
5 | import com.swordintent.chatgpt.protocol.ChatResponse;
6 |
7 | public interface ChatgptClient {
8 | /**
9 | * invoke carefully, only when your app start or chat error, you need invoke
10 | */
11 | void init(String agentAddress, ChatGptConfig config) throws Exception;
12 |
13 | ChatResponse chat(ChatRequest request) throws Exception;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/ChatgptClientImpl.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt;
2 |
3 | import com.swordintent.chatgpt.client.DataClient;
4 | import com.swordintent.chatgpt.client.TokenInterceptor;
5 | import com.swordintent.chatgpt.protocol.ChatGptConfig;
6 | import com.swordintent.chatgpt.protocol.ChatRequest;
7 | import com.swordintent.chatgpt.protocol.ChatResponse;
8 |
9 | public class ChatgptClientImpl implements ChatgptClient{
10 |
11 | private static final ChatgptClientImpl instance = new ChatgptClientImpl();
12 |
13 | private ChatgptClientImpl(){
14 |
15 | }
16 |
17 | public static ChatgptClientImpl getInstance(){
18 | return instance;
19 | }
20 |
21 | private static final String LOGIN_URL_PATH = "%s/login";
22 | private static final String CHAT_URL_PATH = "%s/chat";
23 |
24 | private String loginUrlPath;
25 | private String chatUrlPath;
26 |
27 | private DataClient client;
28 |
29 | private TokenInterceptor tokenInterceptor;
30 |
31 | @Override
32 | public void init(String agentAddress, ChatGptConfig config) throws Exception {
33 | initUrls(agentAddress);
34 | initClient(config);
35 | login();
36 | }
37 |
38 | @Override
39 | public ChatResponse chat(ChatRequest request) throws Exception {
40 | return client.getData(request, chatUrlPath, ChatResponse.class);
41 | }
42 |
43 | private void initClient(ChatGptConfig config) {
44 | tokenInterceptor = new TokenInterceptor(config);
45 | client = new DataClient(tokenInterceptor);
46 | client.init();
47 | }
48 |
49 | private void initUrls(String agentAddress) {
50 | loginUrlPath = String.format(LOGIN_URL_PATH, agentAddress);
51 | chatUrlPath = String.format(CHAT_URL_PATH, agentAddress);
52 | }
53 |
54 | private void login() throws Exception {
55 | ChatGptConfig loginConfig = client.getData(this.loginUrlPath, ChatGptConfig.class);
56 | tokenInterceptor.setChatGptConfig(loginConfig);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/client/DataClient.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt.client;
2 |
3 | import com.swordintent.chatgpt.utils.JsonUtils;
4 | import okhttp3.*;
5 |
6 | import javax.net.ssl.*;
7 | import java.io.IOException;
8 | import java.net.InetSocketAddress;
9 | import java.net.Proxy;
10 | import java.security.cert.CertificateException;
11 | import java.security.cert.X509Certificate;
12 | import java.util.concurrent.TimeUnit;
13 |
14 | /**
15 | * @author liuhe
16 | */
17 | public class DataClient {
18 |
19 | private OkHttpClient httpClient;
20 |
21 | private final TokenInterceptor interceptor;
22 |
23 |
24 | public DataClient(TokenInterceptor interceptor) {
25 | this.interceptor = interceptor;
26 | }
27 |
28 | public void init() {
29 | // OkHttpClient.Builder builder = null;
30 | // try {
31 | // builder = getDebugHttpClientBuilder();
32 | // httpClient = builder.build();
33 | // } catch (Exception e) {
34 | // e.printStackTrace();
35 | // }
36 | OkHttpClient.Builder builder = getHttpClientBuilder();
37 | httpClient = builder.build();
38 | }
39 |
40 | private OkHttpClient.Builder getDebugHttpClientBuilder() throws Exception {
41 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888));
42 | final TrustManager[] trustAllCerts = new TrustManager[]{
43 | new X509TrustManager() {
44 | @Override
45 | public void checkClientTrusted(X509Certificate[] chain,
46 | String authType) throws CertificateException {
47 | }
48 |
49 | @Override
50 | public void checkServerTrusted(X509Certificate[] chain,
51 | String authType) throws CertificateException {
52 | }
53 |
54 | @Override
55 | public X509Certificate[] getAcceptedIssuers() {
56 | return new X509Certificate[0];
57 | }
58 | }
59 | };
60 |
61 | // Install the all-trusting trust manager
62 | final SSLContext sslContext = SSLContext.getInstance("SSL");
63 | sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
64 | // Create an ssl socket factory with our all-trusting manager
65 | final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
66 |
67 | return new OkHttpClient.Builder()
68 | .connectTimeout(60, TimeUnit.SECONDS)
69 | .writeTimeout(60, TimeUnit.SECONDS)
70 | .readTimeout(60, TimeUnit.SECONDS)
71 | .proxy(proxy)
72 | .addInterceptor(interceptor)
73 | .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0])
74 | .hostnameVerifier(new HostnameVerifier() {
75 | @Override
76 | public boolean verify(String hostname, SSLSession session) {
77 | return true;
78 | }
79 | });
80 | }
81 |
82 | public R getData(String url, Class responseType) throws Exception {
83 | Request request = new Request.Builder()
84 | .url(url)
85 | .post(RequestBody.create(null, "{}"))
86 | .build();
87 | return makeRequest(request, responseType);
88 | }
89 |
90 | public R getData(T requestData, String url, Class responseType) throws Exception {
91 | String content = JsonUtils.toJson(requestData);
92 | RequestBody body = RequestBody.create(
93 | null, content);
94 | Request request = new Request.Builder()
95 | .url(url)
96 | .post(body)
97 | .build();
98 | return makeRequest(request, responseType);
99 | }
100 |
101 | private R makeRequest(Request request, Class responseType) throws IOException {
102 | Call call = httpClient.newCall(request);
103 | try (Response response = call.execute()) {
104 | ResponseBody body = response.body();
105 | if(body == null){
106 | return null;
107 | }
108 | String string = body.string();
109 | return JsonUtils.fromJson(string, responseType);
110 | }
111 | }
112 |
113 | private OkHttpClient.Builder getHttpClientBuilder() {
114 | return new OkHttpClient.Builder()
115 | .connectTimeout(180, TimeUnit.SECONDS)
116 | .writeTimeout(180, TimeUnit.SECONDS)
117 | .readTimeout(180, TimeUnit.SECONDS)
118 | .addInterceptor(interceptor);
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/client/TokenInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt.client;
2 |
3 | import com.swordintent.chatgpt.protocol.ChatGptConfig;
4 | import com.swordintent.chatgpt.utils.JsonUtils;
5 | import okhttp3.Interceptor;
6 | import okhttp3.Request;
7 | import okhttp3.Response;
8 |
9 | import java.io.IOException;
10 |
11 | /**
12 | * @author liuhe
13 | */
14 | public class TokenInterceptor implements Interceptor {
15 |
16 | private ChatGptConfig chatGptConfig;
17 |
18 | public void setChatGptConfig(ChatGptConfig chatGptConfig) {
19 | this.chatGptConfig = chatGptConfig;
20 | }
21 |
22 | public TokenInterceptor(ChatGptConfig chatGptConfig) {
23 | this.chatGptConfig = chatGptConfig;
24 | }
25 |
26 | @Override
27 | public Response intercept(Chain chain) throws IOException {
28 | Request request = chain.request();
29 | Request newRequest;
30 | newRequest = request.newBuilder()
31 | // .addHeader("accept", "application/json")
32 | .addHeader("chatgpt-config", JsonUtils.toJson(chatGptConfig))
33 | .addHeader("Content-Type", "application/json")
34 | .build();
35 | return chain.proceed(newRequest);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/protocol/ChatGptConfig.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt.protocol;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 | import lombok.Builder;
5 |
6 | @Builder
7 | public class ChatGptConfig {
8 |
9 | private String email;
10 | private String password;
11 | @SerializedName("session_token")
12 | private String sessionToken;
13 | private String proxy;
14 | @SerializedName("Authorization")
15 | private String authorization;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/protocol/ChatRequest.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt.protocol;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 | import lombok.Builder;
5 |
6 | @Builder
7 | public class ChatRequest {
8 |
9 | private String prompt;
10 | @SerializedName("conversation_id")
11 | private String conversationId;
12 | @SerializedName("parent_id")
13 | private String parentId;
14 |
15 | public void setPrompt(String prompt) {
16 | this.prompt = prompt;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/protocol/ChatResponse.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt.protocol;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class ChatResponse {
8 |
9 | private String message;
10 | @SerializedName("conversation_id")
11 | private String conversationId;
12 | @SerializedName("parent_id")
13 | private String parentId;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/swordintent/chatgpt/utils/JsonUtils.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt.utils;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 |
6 | /**
7 | * @author liuhe
8 | */
9 | public class JsonUtils {
10 | private static final Gson prettyGson = new GsonBuilder()
11 | .setPrettyPrinting()
12 | .create();
13 |
14 | private static final Gson gson = new GsonBuilder()
15 | .create();
16 |
17 | public static String toJson(Object obj) {
18 | return gson.toJson(obj);
19 | }
20 |
21 | public static T fromJson(String json, Class t) {
22 | return gson.fromJson(json, t);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/server.py:
--------------------------------------------------------------------------------
1 | import json
2 | import pickle
3 | import base64
4 | import uuid
5 |
6 | from revChatGPT.V3 import Chatbot
7 | from flask import Flask
8 | from flask_restful import reqparse, Api, Resource
9 |
10 | # For the config please go here:
11 | # https://github.com/acheong08/ChatGPT/wiki/Setup
12 |
13 | app = Flask(__name__)
14 | api = Api(app)
15 |
16 | parser = reqparse.RequestParser()
17 | parser.add_argument('prompt')
18 | parser.add_argument('conversation_id')
19 | parser.add_argument('parent_id')
20 | parser.add_argument('chatgpt-config', type=str, location='headers')
21 |
22 |
23 | class Chat(Resource):
24 |
25 | def post(self):
26 | args = parser.parse_args()
27 | print(args)
28 |
29 | prompt = args['prompt']
30 | conversation_id = args['conversation_id']
31 | config = json.loads(args['chatgpt-config'])
32 |
33 | if conversation_id is None:
34 | chatbot = Chatbot(api_key=config['password'])
35 | conversation_id = str(uuid.uuid1())
36 | else:
37 | chatbot = Chatbot(api_key=config['password'])
38 | chatbot.load(conversation_id)
39 | result = chatbot.ask(prompt)
40 | chatbot.save(conversation_id)
41 | print("result", chatbot.conversation)
42 | ret = {'message': result, 'conversation_id': conversation_id}
43 | response = ret
44 | print(response)
45 | return response, 200
46 |
47 |
48 | class Login(Resource):
49 |
50 | def post(self):
51 | args = parser.parse_args()
52 | print(args)
53 | config = json.loads(args['chatgpt-config'])
54 | chatbot = Chatbot(api_key=config['password'])
55 | response = config
56 | print(response)
57 | return response, 200
58 |
59 |
60 | api.add_resource(Chat, '/chat')
61 | api.add_resource(Login, '/login')
62 |
63 | if __name__ == '__main__':
64 | app.run(debug=True)
65 |
--------------------------------------------------------------------------------
/src/test/java/com/swordintent/chatgpt/AppTest.java:
--------------------------------------------------------------------------------
1 | package com.swordintent.chatgpt;
2 |
3 | import com.swordintent.chatgpt.protocol.ChatGptConfig;
4 | import com.swordintent.chatgpt.protocol.ChatResponse;
5 | import org.junit.Test;
6 | import com.swordintent.chatgpt.protocol.ChatRequest;
7 | import com.swordintent.chatgpt.utils.JsonUtils;
8 |
9 | import java.util.Arrays;
10 | import java.util.List;
11 |
12 | /**
13 | * Unit test for simple App.
14 | */
15 | public class AppTest
16 | {
17 | /**
18 | * Rigorous Test :-)
19 | */
20 | @Test
21 | public void test() throws Exception {
22 | ChatGptConfig chatGptConfig = ChatGptConfig.builder()
23 | // .proxy("http://192.168.50.254:9853")
24 | // .email("")
25 | // .password("")
26 | .authorization("")
27 | .build();
28 | ChatgptClientImpl client = ChatgptClientImpl.getInstance();
29 | client.init("http://127.0.0.1:5000", chatGptConfig);
30 | List strings = Arrays.asList("你好", "你能帮我介绍一下中日关系吗?");
31 | int size = strings.size();
32 | int threadNum = 1;
33 | int slice = size / threadNum;
34 |
35 | for(int i = 0; i < threadNum; i ++){
36 | List subList = strings.subList(slice * i, slice * (i + 1));
37 | makeThreadRequest(client, subList);
38 | }
39 | Thread.sleep(60000L);
40 | }
41 |
42 | private void makeThreadRequest(ChatgptClientImpl client, List subList) {
43 | new Thread(() ->{
44 | String conversationId = null;
45 | String parentId = null;
46 | for(String str : subList){
47 | ChatRequest chatRequest = ChatRequest.builder().prompt(str)
48 | .conversationId(conversationId)
49 | .parentId(parentId)
50 | .build();
51 | try {
52 | ChatResponse chat = client.chat(chatRequest);
53 | System.out.println(JsonUtils.toJson(chat));
54 | conversationId = chat.getConversationId();
55 | parentId = chat.getParentId();
56 | } catch (Exception e) {
57 | throw new RuntimeException(e);
58 | }
59 | }
60 | }).start();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------