加入星計劃,您可以享受以下權益:

  • 創(chuàng)作內容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 一、需求場景
    • 二、功能實現(xiàn)
    • 三、功能驗證
    • 四、工程源碼
    • 五、實戰(zhàn)項目
  • 推薦器件
  • 相關推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

看老夫,如何帶你操刀重構ChatGLM-SDK!

01/22 13:40
3395
閱讀需 54 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

作者:小傅哥
博客:https://bugstack.cn

大家好,我是技術UP主小傅哥。

鑒于智譜AI發(fā)布了最新一代 GLM3.0GLM4.0 基座大模型,我又要對自己開發(fā)的這款開源 chatglm-sdk-java 進行改造了!因為需要做新老接口的模型調用中數(shù)據(jù)格式兼容,這將是一場編碼設計復雜場景的對抗挑戰(zhàn)。 請看小傅哥如何操刀改造!

為什么改造SDK會比較復雜?

通常來說,我們開發(fā)好一款SDK后,就會投入到項目中使用,而使用的方式會根據(jù)SDK的出入?yún)藴蔬M行對接。比如一個接口的入?yún)⒃居?個參數(shù),A String、B String 類型,但現(xiàn)在因為有額外的功能添加,從2個參數(shù)調整為3個參數(shù),同時需要對原本的 B 參數(shù) String 類型,擴展為 Object 類型,添加更多的屬性信息。同時出參也有對應響應結構變化。

那么對于這種線上正在使用又需要改造代碼的情況,我們不可能把原有的代碼都鏟了不要,所以需要做一些優(yōu)雅的兼容的開發(fā)處理。讓工程更加好維護、好迭代。

在設計原則和設計模式的錘煉下,寫出高質量的代碼。

那么,接下來小傅哥就帶著大家講講這段關于GLM新增模型后 SDK 的重構操作。

文末有整個 SDK 的源碼,直接免費獲取,拿過去就是嘎嘎學習!

一、需求場景

智譜AI文檔:https://open.bigmodel.cn/overview

本次文檔中新增加了 GLM-3-Turbo、GLM-4、GLM-4v、cogview,這樣四個新模型,與原來的舊版 chatGLM_6b_SSE、chatglm_lite、chatglm_lite_32k、chatglm_std、chatglm_pro,在接口調用上做了不小的修改。因為新版的模型增加了如插件「聯(lián)網(wǎng)、知識庫、函數(shù)庫」、畫圖、圖片識別這樣的能力,所以出入?yún)⒁灿邢鄳淖兓?/p>

1. curl 舊版

curl?-X?POST?
????????-H?"Authorization:?Bearer?BearerTokenUtils.getToken(獲取)"?
????????-H?"Content-Type:?application/json"?
????????-H?"Accept:?text/event-stream"?
????????-d?'{
??????????????"top_p":?0.7,
??????????????"sseFormat":?"data",
??????????????"temperature":?0.9,
??????????????"incremental":?true,
??????????????"request_id":?"xfg-1696992276607",
??????????????"prompt":?[
??????????????????{
??????????????????"role":?"user",
??????????????????"content":?"寫個java冒泡排序"
??????????????????}
??????????????]
????????????}'?
??http://open.bigmodel.cn/api/paas/v3/model-api/chatglm_lite/sse-invoke
    • 注意:舊版的調用方式是把模型放到接口請求中,如;

chatglm_lite

    就是放到請求地址中。

2. curl 3&4

curl?-X?POST?
????????-H?"Authorization:?Bearer?BearerTokenUtils.getToken(獲取)"?
????????-H?"Content-Type:?application/json"?
????????-d?'{
??????????"model":"glm-3-turbo",
??????????"stream":?"true",
??????????"messages":?[
??????????????{
??????????????????"role":?"user",
??????????????????"content":?"寫個java冒泡排序"
??????????????}
??????????]
????????}'?
??https://open.bigmodel.cn/api/paas/v4/chat/completions
    • 注意:新版的模型為入?yún)⒎绞秸{用,接口是統(tǒng)一的接口。此外入?yún)⒏袷降牧魇娇梢酝ㄟ^參數(shù)

"stream": "true"

    控制。

3. curl 4v

curl?-X?POST?
????????-H?"Authorization:?Bearer?BearerTokenUtils.getToken(獲取)"?
????????-H?"Content-Type:?application/json"?
????????-d?'{
????????????????"messages":?[
????????????????????{
????????????????????????"role":?"user",
????????????????????????"content":?[
??????????????????????????{
????????????????????????????"type":?"text",
????????????????????????????"text":?"這是什么圖片"
??????????????????????????},
??????????????????????????{
????????????????????????????"type":?"image_url",
????????????????????????????"image_url":?{
??????????????????????????????"url"?:?"支持base64和圖片地址;https://bugstack.cn/images/article/project/chatgpt/chatgpt-extra-231011-01.png"
????????????????????????????}
??????????????????????????}
????????????????????????]
????????????????????}
????????????????],
????????????????"model":?"glm-4v",
????????????????"stream":?"true"
????????????}'?
??https://open.bigmodel.cn/api/paas/v4/chat/completions
    注意:多模態(tài)4v模型,content 字符串升級為對象。這部分與 chatgpt 的參數(shù)結構是一致的。所以我們在開發(fā)這部分功能的時候,也需要做兼容處理。因為本身它既可以支持對象也可以支持 conten 為字符串。

4. curl cogview

curl?-X?POST?
????????-H?"Authorization:?Bearer?BearerTokenUtils.getToken(獲取)"?
????????-H?"Content-Type:?application/json"?
????????-d?'{
??????????"model":"cogview-3",
??????????"prompt":"畫一個小狗狗"
????????}'?
??https://open.bigmodel.cn/api/paas/v4/images/generations
    注意:圖片的文生圖是一個新的功能,舊版沒有這個接口。所以開發(fā)的時候按照獨立的接口開發(fā)即可。

綜上,這些接口開發(fā)還需要注意;

    首先,小傅哥根據(jù)官網(wǎng)文檔編寫了對應的 curl 請求格式。方便開發(fā)時可以參考和驗證。之后,curl 舊版是文本類處理,curl 3&4 是新版的文檔處理。兩個可以對比看出,舊版的入?yún)⑹?prompt,新版是 messages另外,本次API文檔新增加了文生圖,和4v(vision)多模態(tài)的圖片識別。

接下來,我們就要來設計怎么在舊版的SDK中兼容這些功能實現(xiàn)。

二、功能實現(xiàn)

1. 調用流程

如圖,是整個本次 SDK 的實現(xiàn)的核心流程,其中執(zhí)行器部分是本次重點開發(fā)的內容。在舊版的 SDK 中是直接從會話請求進入模型的調用,沒有執(zhí)行器的添加。而執(zhí)行器的引入則是為了解耦調用過程,依照于不同的請求模型(chatglm_std、chatglm_pro、GLM_4...),可以調用到不同的執(zhí)行器上去。

2. 執(zhí)行器「解耦」

2.1 接口
public?interface?Executor?{

????/**
?????*?問答模式,流式反饋
?????*
?????*?@param?chatCompletionRequest?請求信息
?????*?@param?eventSourceListener???實現(xiàn)監(jiān)聽;通過監(jiān)聽的?onEvent?方法接收數(shù)據(jù)
?????*?@return?應答結果
?????*?@throws?Exception?異常
?????*/
????EventSource?completions(ChatCompletionRequest?chatCompletionRequest,?EventSourceListener?eventSourceListener)?throws?Exception;
????
????//?...?省略部分接口
}????
2.2 舊版
public?class?GLMOldExecutor?implements?Executor?{

????/**
?????*?OpenAi?接口
?????*/
????private?final?Configuration?configuration;
????/**
?????*?工廠事件
?????*/
????private?final?EventSource.Factory?factory;

????public?GLMOldExecutor(Configuration?configuration)?{
????????this.configuration?=?configuration;
????????this.factory?=?configuration.createRequestFactory();
????}

????@Override
????public?EventSource?completions(ChatCompletionRequest?chatCompletionRequest,?EventSourceListener?eventSourceListener)?throws?Exception?{
????????//?構建請求信息
????????Request?request?=?new?Request.Builder()
????????????????.url(configuration.getApiHost().concat(IOpenAiApi.v3_completions).replace("{model}",?chatCompletionRequest.getModel().getCode()))
????????????????.post(RequestBody.create(MediaType.parse("application/json"),?chatCompletionRequest.toString()))
????????????????.build();

????????//?返回事件結果
????????return?factory.newEventSource(request,?eventSourceListener);
????}
????
????//?...?省略部分接口
????
}????
2.3 新版
public?class?GLMExecutor?implements?Executor,?ResultHandler?{

????/**
?????*?OpenAi?接口
?????*/
????private?final?Configuration?configuration;
????/**
?????*?工廠事件
?????*/
????private?final?EventSource.Factory?factory;
????/**
?????*?統(tǒng)一接口
?????*/
????private?IOpenAiApi?openAiApi;

????private?OkHttpClient?okHttpClient;

????public?GLMExecutor(Configuration?configuration)?{
????????this.configuration?=?configuration;
????????this.factory?=?configuration.createRequestFactory();
????????this.openAiApi?=?configuration.getOpenAiApi();
????????this.okHttpClient?=?configuration.getOkHttpClient();
????}

????@Override
????public?EventSource?completions(ChatCompletionRequest?chatCompletionRequest,?EventSourceListener?eventSourceListener)?throws?Exception?{
????????//?構建請求信息
????????Request?request?=?new?Request.Builder()
????????????????.url(configuration.getApiHost().concat(IOpenAiApi.v4))
????????????????.post(RequestBody.create(MediaType.parse(Configuration.JSON_CONTENT_TYPE),?chatCompletionRequest.toString()))
????????????????.build();

????????//?返回事件結果
????????return?factory.newEventSource(request,?chatCompletionRequest.getIsCompatible()???eventSourceListener(eventSourceListener)?:?eventSourceListener);
????}

????
????//?...?省略部分接口
????
}????

在新版的執(zhí)行實現(xiàn)中,除了 IOpenAiApi.v4 接口的變動,還有一塊是對外結果的封裝處理。這是因為在舊版的接口對接中使用的是 EventType「add、finish、error、interrupted」枚舉來判斷。但在新版中則只是判斷 stop 標記符。所以為了讓之前的SDK使用用戶更少的改動代碼,這里做了結果的封裝。

3. 注入配置

源碼:cn.bugstack.chatglm.session.Configuration

public?HashMap<Model,?Executor>?newExecutorGroup()?{
????this.executorGroup?=?new?HashMap<>();
????//?舊版模型,兼容
????Executor?glmOldExecutor?=?new?GLMOldExecutor(this);
????this.executorGroup.put(Model.CHATGLM_6B_SSE,?glmOldExecutor);
????this.executorGroup.put(Model.CHATGLM_LITE,?glmOldExecutor);
????this.executorGroup.put(Model.CHATGLM_LITE_32K,?glmOldExecutor);
????this.executorGroup.put(Model.CHATGLM_STD,?glmOldExecutor);
????this.executorGroup.put(Model.CHATGLM_PRO,?glmOldExecutor);
????this.executorGroup.put(Model.CHATGLM_TURBO,?glmOldExecutor);
????//?新版模型,配置
????Executor?glmExecutor?=?new?GLMExecutor(this);
????this.executorGroup.put(Model.GLM_3_5_TURBO,?glmExecutor);
????this.executorGroup.put(Model.GLM_4,?glmExecutor);
????this.executorGroup.put(Model.GLM_4V,?glmExecutor);
????this.executorGroup.put(Model.COGVIEW_3,?glmExecutor);
????return?this.executorGroup;
}
    對于不同的模型,走哪個執(zhí)行器,在 Configuration 配置文件中寫了這樣的配置信息。這樣當你調用 CHATGLM_TURBO 就會走到 glmOldExecutor 模型,調用 GLM_4V 就會走到 glmExecutor 模型。

4. 參數(shù)兼容

ChatCompletionRequest 作為一個重要的應答參數(shù)對象,在本次的接口變化中也是調整了不少字段。但好在小傅哥之前就提供了一個 toString 對象的方法。在這里我們可以做不同類型參數(shù)的處理。

public?String?toString()?{
????try?{
????????//?24年1月發(fā)布新模型后調整
????????if?(Model.GLM_3_5_TURBO.equals(this.model)?||?Model.GLM_4.equals(this.model)?||?Model.GLM_4V.equals(this.model))?{
????????????Map<String,?Object>?paramsMap?=?new?HashMap<>();
????????????paramsMap.put("model",?this.model.getCode());
????????????if?(null?==?this.messages?&&?null?==?this.prompt)?{
????????????????throw?new?RuntimeException("One?of?messages?or?prompt?must?not?be?empty!");
????????????}
????????????paramsMap.put("messages",?this.messages?!=?null???this.messages?:?this.prompt);
????????????if?(null?!=?this.requestId)?{
????????????????paramsMap.put("request_id",?this.requestId);
????????????}
????????????if?(null?!=?this.doSample)?{
????????????????paramsMap.put("do_sample",?this.doSample);
????????????}
????????????paramsMap.put("stream",?this.stream);
????????????paramsMap.put("temperature",?this.temperature);
????????????paramsMap.put("top_p",?this.topP);
????????????paramsMap.put("max_tokens",?this.maxTokens);
????????????if?(null?!=?this.stop?&&?this.stop.size()?>?0)?{
????????????????paramsMap.put("stop",?this.stop);
????????????}
????????????if?(null?!=?this.tools?&&?this.tools.size()?>?0)?{
????????????????paramsMap.put("tools",?this.tools);
????????????????paramsMap.put("tool_choice",?this.toolChoice);
????????????}
????????????return?new?ObjectMapper().writeValueAsString(paramsMap);
????????}
????????
????????//?默認
????????Map<String,?Object>?paramsMap?=?new?HashMap<>();
????????paramsMap.put("request_id",?requestId);
????????paramsMap.put("prompt",?prompt);
????????paramsMap.put("incremental",?incremental);
????????paramsMap.put("temperature",?temperature);
????????paramsMap.put("top_p",?topP);
????????paramsMap.put("sseFormat",?sseFormat);
????????return?new?ObjectMapper().writeValueAsString(paramsMap);
????}?catch?(JsonProcessingException?e)?{
????????throw?new?RuntimeException(e);
????}
}
    • 如果為本次調整的新增模型,則走新的方式裝配參數(shù)信息。通過這樣的方式可以很輕松的把以前叫做 prompt 的字段調整為 messages 名稱。類似的操作可以看具體的代碼。

關于字段的出入?yún)⑻幚?,但比較同類,就不一一列舉了

三、功能驗證

注意:測試前需要申請ApiKey https://open.bigmodel.cn/overview 有非常多的免費額度。

@Before
public?void?test_OpenAiSessionFactory()?{
????//?1.?配置文件
????Configuration?configuration?=?new?Configuration();
????configuration.setApiHost("https://open.bigmodel.cn/");
????configuration.setApiSecretKey("62ddec38b1d0b9a7b0fddaf271e6ed90.HpD0SUBUlvqd05ey");
????configuration.setLevel(HttpLoggingInterceptor.Level.BODY);
????//?2.?會話工廠
????OpenAiSessionFactory?factory?=?new?DefaultOpenAiSessionFactory(configuration);
????//?3.?開啟會話
????this.openAiSession?=?factory.openSession();
}
    申請后把你的 ApiKey 替換 setApiSecretKey 就可以使用了。

1. 文生文「支持聯(lián)網(wǎng)」

@Test
public?void?test_completions()?throws?Exception?{
????CountDownLatch?countDownLatch?=?new?CountDownLatch(1);
????//?入?yún)ⅲ荒P?、請求信?
????ChatCompletionRequest?request?=?new?ChatCompletionRequest();
????request.setModel(Model.GLM_3_5_TURBO);?//?chatGLM_6b_SSE、chatglm_lite、chatglm_lite_32k、chatglm_std、chatglm_pro
????request.setIncremental(false);
????request.setIsCompatible(true);?//?是否對返回結果數(shù)據(jù)做兼容,24年1月發(fā)布的?GLM_3_5_TURBO、GLM_4?模型,與之前的模型在返回結果上有差異。開啟?true?可以做兼容。
????//?24年1月發(fā)布的?glm-3-turbo、glm-4?支持函數(shù)、知識庫、聯(lián)網(wǎng)功能
????request.setTools(new?ArrayList<ChatCompletionRequest.Tool>()?{
????????private?static?final?long?serialVersionUID?=?-7988151926241837899L;
????????{
????????????add(ChatCompletionRequest.Tool.builder()
????????????????????.type(ChatCompletionRequest.Tool.Type.web_search)
????????????????????.webSearch(ChatCompletionRequest.Tool.WebSearch.builder().enable(true).searchQuery("小傅哥").build())
????????????????????.build());
????????}
????});
????request.setPrompt(new?ArrayList<ChatCompletionRequest.Prompt>()?{
????????private?static?final?long?serialVersionUID?=?-7988151926241837899L;
????????{
????????????add(ChatCompletionRequest.Prompt.builder()
????????????????????.role(Role.user.getCode())
????????????????????.content("小傅哥的是誰")
????????????????????.build());
????????}
????});
????//?請求
????openAiSession.completions(request,?new?EventSourceListener()?{
????????@Override
????????public?void?onEvent(EventSource?eventSource,?@Nullable?String?id,?@Nullable?String?type,?String?data)?{
????????????ChatCompletionResponse?response?=?JSON.parseObject(data,?ChatCompletionResponse.class);
????????????log.info("測試結果?onEvent:{}",?response.getData());
????????????//?type?消息類型,add?增量,finish?結束,error?錯誤,interrupted?中斷
????????????if?(EventType.finish.getCode().equals(type))?{
????????????????ChatCompletionResponse.Meta?meta?=?JSON.parseObject(response.getMeta(),?ChatCompletionResponse.Meta.class);
????????????????log.info("[輸出結束]?Tokens?{}",?JSON.toJSONString(meta));
????????????}
????????}
????????@Override
????????public?void?onClosed(EventSource?eventSource)?{
????????????log.info("對話完成");
????????????countDownLatch.countDown();
????????}
????????@Override
????????public?void?onFailure(EventSource?eventSource,?@Nullable?Throwable?t,?@Nullable?Response?response)?{
????????????log.info("對話異常");
????????????countDownLatch.countDown();
????????}
????});
????//?等待
????countDownLatch.await();
}

2. 文生圖

@Test
public?void?test_genImages()?throws?Exception?{
????ImageCompletionRequest?request?=?new?ImageCompletionRequest();
????request.setModel(Model.COGVIEW_3);
????request.setPrompt("畫個小狗");
????ImageCompletionResponse?response?=?openAiSession.genImages(request);
????log.info("測試結果:{}",?JSON.toJSONString(response));
}

3. 多模態(tài)

public?void?test_completions_v4()?throws?Exception?{
????CountDownLatch?countDownLatch?=?new?CountDownLatch(1);
????//?入?yún)ⅲ荒P?、請求信?
????ChatCompletionRequest?request?=?new?ChatCompletionRequest();
????request.setModel(Model.GLM_4V);?//?GLM_3_5_TURBO、GLM_4
????request.setStream(true);
????request.setMessages(new?ArrayList<ChatCompletionRequest.Prompt>()?{
????????private?static?final?long?serialVersionUID?=?-7988151926241837899L;
????????{
????????????//?content?字符串格式
????????????add(ChatCompletionRequest.Prompt.builder()
????????????????????.role(Role.user.getCode())
????????????????????.content("這個圖片寫了什么")
????????????????????.build());
????????????//?content?對象格式
????????????add(ChatCompletionRequest.Prompt.builder()
????????????????????.role(Role.user.getCode())
????????????????????.content(ChatCompletionRequest.Prompt.Content.builder()
????????????????????????????.type(ChatCompletionRequest.Prompt.Content.Type.text.getCode())
????????????????????????????.text("這是什么圖片")
????????????????????????????.build())
????????????????????.build());
????????????//?content?對象格式,上傳圖片;圖片支持url、basde64
????????????add(ChatCompletionRequest.Prompt.builder()
????????????????????.role(Role.user.getCode())
????????????????????.content(ChatCompletionRequest.Prompt.Content.builder()
????????????????????????????.type(ChatCompletionRequest.Prompt.Content.Type.image_url.getCode())
????????????????????????????.imageUrl(ChatCompletionRequest.Prompt.Content.ImageUrl.builder().url("https://bugstack.cn/images/article/project/chatgpt/chatgpt-extra-231011-01.png").buil
????????????????????????????.build())
????????????????????.build());
????????}
????});
????openAiSession.completions(request,?new?EventSourceListener()?{
????????@Override
????????public?void?onEvent(EventSource?eventSource,?@Nullable?String?id,?@Nullable?String?type,?String?data)?{
????????????if?("[DONE]".equals(data))?{
????????????????log.info("[輸出結束]?Tokens?{}",?JSON.toJSONString(data));
????????????????return;
????????????}
????????????ChatCompletionResponse?response?=?JSON.parseObject(data,?ChatCompletionResponse.class);
????????????log.info("測試結果:{}",?JSON.toJSONString(response));
????????}
????????@Override
????????public?void?onClosed(EventSource?eventSource)?{
????????????log.info("對話完成");
????????????countDownLatch.countDown();
????????}
????????@Override
????????public?void?onFailure(EventSource?eventSource,?@Nullable?Throwable?t,?@Nullable?Response?response)?{
????????????log.error("對話失敗",?t);
????????????countDownLatch.countDown();
????????}
????});
????//?等待
????countDownLatch.await();
}

四、工程源碼

    • 申請ApiKey:https://open.bigmodel.cn/usercenter/apikeys - 注冊申請開通,即可獲得 ApiKey運行環(huán)境:JDK 1.8+支持模型:chatGLM_6b_SSE、chatglm_lite、chatglm_lite_32k、chatglm_std、chatglm_pro、chatglm_turbo、glm-3-turbo、glm-4、glm-4v、cogview-3maven pom -

已發(fā)布到Maven倉庫

<dependency>
    <groupId>cn.bugstack</groupId>
    <artifactId>chatglm-sdk-java</artifactId>
    <version>2.0</version>
</dependency>
    源碼(Github):https://github.com/fuzhengwei/chatglm-sdk-java源碼(Gitee):https://gitee.com/fustack/chatglm-sdk-java源碼(Gitcode):https://gitcode.net/KnowledgePlanet/road-map/chatglm-sdk-java

五、實戰(zhàn)項目

小傅哥耗時了4個多月前后端 + Dev-Ops,全棧式編程。手把手&漸進式,逐個章節(jié)錄制視頻 + 編寫小冊開發(fā)的 —— OpenAI 項目完結了。使用了 ChatGPT、ChatGLM 模型的對接使用。

這個項目運用了全新的 DDD 領域驅動設計、使用設計模式處理多模型、交易、登錄鑒權等各個場景,完整對接支付「解耦、補償」、可上線部署和配置監(jiān)控。體驗地址:https://gaga.plus

注意,本項目也只是【星球:碼農會鎖】眾多項目中的1個,其他的項目還包括:大營銷平臺、API網(wǎng)關、Lottery抽獎、IM通信、SpringBoot Starter 組件開發(fā)、IDEA Plugin 插件開發(fā)等,并還有開源項目學習。

這樣整套項目,放在一些平臺售賣,至少都是上千元。但小傅哥的星球,只需要100多,就可以獲得全部的學習項目!

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風險等級 參考價格 更多信息
FT232RQ-TRAY 1 FTDI Chip USB Bus Controller, CMOS, 5 X 5 MM, GREEN, QFN-32

ECAD模型

下載ECAD模型
$4.5 查看
STM32H743XIH6TR 1 STMicroelectronics High-performance and DSP with DP-FPU, Arm Cortex-M7 MCU with 2MBytes of Flash memory, 1MB RAM, 480 MHz CPU, Art Accelerator, L1 cache, external memory interface, large set of peripherals

ECAD模型

下載ECAD模型
暫無數(shù)據(jù) 查看
MCF52259CAG80 1 Freescale Semiconductor 32-BIT, FLASH, 80MHz, RISC MICROCONTROLLER, PQFP144, 20 X 20 MM, ROHS COMPLIANT, LQFP-144

ECAD模型

下載ECAD模型
$17.28 查看

相關推薦

電子產(chǎn)業(yè)圖譜

作者小傅哥多年從事一線互聯(lián)網(wǎng)Java開發(fā),從19年開始編寫工作和學習歷程的技術匯總,旨在為大家提供一個較清晰詳細的核心技能學習文檔。如果本文能為您提供幫助,請給予支持(關注、點贊、分享)!