智能SQL生成:后端技术与LLM的完美结合

文章目录

  • 引言
  • 一、什么是大模型
  • 二、为什么选择LLM
  • 三、开发技术说明
  • 四、系统架构说明
  • 五、编码实战
    • 1. Maven
    • 2. 讯飞大模型配置类
    • 3. LLM相关的封装
    • 4. 编写LLM的service
    • 5. 编写controller
    • 6. 运行测试
  • 六、总结

引言

本篇文章主要是关于实现一个类似Chat2DB的根据自然语言生成SQL的简单Demo,根据此Demo可以入门大模型应用开发,结合大模型开发出属于自己的大模型应用,让自己的应用智能化,可以根据用户不同问题做出不同的回答。

image-20240225175115184

一、什么是大模型

如果各位有关注一些技术文章,难免会注意到这几年有一个词非常火,没错这就是“大模型”!

那大模型是什么?有什么用呢?

“大模型”是可以指任何规模较大、参数众多的机器学习模型,不仅限于自然语言处理(NLP),也可以包括计算机视觉、语音识别等其他领域的模型。大模型的特点是它们通常需要大量的数据来训练,以及相对较大的计算资源。

大模型的用途十分广泛,在很多领域都有不错的应用价值:

  1. 自然语言的生成和理解:大模型可以根据用户的问题生成连贯的文本回答、或是总结某些文章。ChatGPT就是一个不错的例子。
  2. 图像的处理:例如OpenAI的DALL·E模型,它可以根据用户问题,生成新的图像。
  3. 语言的识别和生成:大模型可以将文本信息转化为人类的语言。
  4. 推荐系统:大模型可以根据用户的行为或某些数据,推测用户的行为爱好,实现个性化推荐。

大模型的应用十分广泛,除了上面举的例子外,还有很多例子,这里就不一一举例了。

61b63d3c-3.png

下面我会使用大模型的其中一种**大型语言模型(Large Language Model,简称LLM)**开发出一些简单的应用Demo,读者可以根据这样的思路进一步完善。

在此之前,先来介绍一下LLM是何物。LLM是指专门用来处理和理解自然语言的大型机器学习模型,LLM通常通过在大量文本数据上进行预训练来学习语言的结构和语义,从而能够执行各种语言处理任务,如文本生成、翻译、摘要、问答和情感分析等。


二、为什么选择LLM

LLM大模型是一款专注于理解和生成自然语言的大模型,那我们系统中无论是Redis还是MySQL的数据,都是文本形式的,将这些文本信息的数据交与大模型处理,能够有针对性地获取到我们想要的数据。

其次,LLM的使用成本较低,国内的大模型无论是讯飞星火大模型还是其他互联网厂商自研的大模型都为开发者提供了不少的免费Token,使得学习成本大大降低。

LLM大模型也是我们大部分人群目前接触最多的大模型,学习成本大幅度降低,只需要知道如何和大模型进行聊天即可懂得如何开发,不需要任何额外的学习成本。

img


三、开发技术说明

本文使用的大模型为讯飞星火大模型,但是咱们不限于任何厂商的大模型,有能力的ChatGPT也可以,作者只是觉得讯飞星火大模型送的Token比较多,非常适合初学者。

读者需要自行前往讯飞星火认知大模型-AI大语言模型-星火大模型-科大讯飞 (xfyun.cn)进行领取免费Token,领取教程这里就不多讲了,网上大把教程,不懂的可以下面留言。

其次,该教程后端方面需要懂得使用SpringBoot进行开发,也就是简单的一个接口开发,没有任何的前端。

最后,开发出来的Demo只是一个抛砖引玉的作用,开发过程中不会考虑太多的规范和其他一些限制,只是单纯把一个小功能实现,代码量也不多,一千行不到。

如果这些都准备好了,那么下面开始发车。


四、系统架构说明

本篇文章会带大家结合后端技术与讯飞星火大模型实现实现根据用户的自然语言问题生成SQL的工具。

自然语言生成SQL?是不是很熟悉,没错,这里是参考了阿里开源的Chat2DB数据库管理功能,不过这里是作者对这个功能自主实现的Demo,没有翻阅过Chat2DB源码,故这里的实现可能并不是Chat2DB的底层实现原理

有能力的可以自己去看看源码:Chat2DB: Chat2DB 是一款有开源免费的多数据库客户端工具,支持windows、mac本地安装,也支持服务器端部署,web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力,能够将自然语言转换为SQL,也可以将SQL转换为自然语言,可以给出研发人员SQL的优化建议,极大的提升人员的效率,是AI时代数据库研发人员的利器,未来即使不懂SQL的运营业务也可 (gitee.com)

怎么根据用户的自然语言描述生成用户想要的SQL呢?下面我们来分析分析

image-20240225172301437

  1. 首先,用户发送一个包含数据库host、user、password、table、用户问题的请求给后端
  2. 后端根据用户提供的数据库信息,连接数据库查询出该表的DDL
  3. 后端与LLM建立连接,将DDL、用户问题、prompt发送给LLM
  4. LLM根据后端提供的数据以及问题,推测分析并生成SQL,将SQL返回给后端
  5. 后端将SQL返回响应给用户

也就是说,后端需要获取并整理数据,然后与LLM建立连接,将数据发送给LLM,LLM再根据数据做出回答,返回给后端SQL,这样就实现了自然语言生成SQL


五、编码实战

了解了整体架构是如何之后,我们进入了编码实战阶段,编码整体来说比较简单,重要的理解整体的架构


1. Maven

构建SpringBoot项目,引入一些我们需要的Maven依赖

    <dependencies><!-- SpringBoot Web容器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.3</version></dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.12.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--okhttp3--><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.43</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version> <!-- 根据你需要的版本进行调整 --></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

2. 讯飞大模型配置类

为了方便维护,需要将讯飞星火大模型配置成一个类,方便日后维护

并根据接口文档的内容生成鉴权信息

星火认知大模型Web API文档 | 讯飞开放平台文档中心 (xfyun.cn)

@ConfigurationProperties("xun-fei.xing-huo")
@Component
@Data
public class XFLlmConfig {private String appId;private String domain;private String wsUrl;private String role;private String host;private String path;private String apiSecret;private String apiKey;private Long maxResponseTime;private String prompt;public String getWsUrl() {try {String httpUrl = wsUrl.replaceAll("wss", "https");Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);sdf.setTimeZone(TimeZone.getTimeZone("GMT"));String formatData = sdf.format(date);String tmp = "host: " + host + "\n";tmp += "date: " + formatData + "\n";tmp += "GET " + path + " HTTP/1.1";Mac mac = Mac.getInstance("hmacsha256");SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");mac.init(spec);byte[] hexDigits = mac.doFinal(tmp.getBytes(StandardCharsets.UTF_8));// Base64加密String sha = Base64.getEncoder().encodeToString(hexDigits);String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);// 拼接地址HttpUrl url = Objects.requireNonNull(HttpUrl.parse(httpUrl)).newBuilder().addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).addQueryParameter("date", formatData).addQueryParameter("host", host).build();return url.toString().replace("http://", "ws://").replace("https://", "wss://");} catch (Exception e) {throw new RuntimeException("getWsUrl 发生异常");}}}

application.yml

xun-fei:xing-huo:appId: 你的appIddomain: xxxxwsUrl: wss://${xun-fei.xing-huo.host}${xun-fei.xing-huo.path}role: userapiSecret: xxxxapiKey: xxxxhost: spark-api.xf-yun.compath: /v3.5/chat#  30smaxResponseTime: 30000prompt: '下面是一些表的DDL语句, 请严格根据这些DDL语句结合用户的问题为用户生成所需的SQL: \n%s \n用户问题是: %s\n注意只需要回复SQL即可!'

3. LLM相关的封装

下面的request以及response都是参考的接口文档封装的,需要自己看看文档。

请求LLM的Request封装

@Data
public class XFLlmRequest {@JsonProperty("header")private Header header;@JsonProperty("parameter")private Parameter parameter;@JsonProperty("payload")private Payload payload;@Data@Builderpublic static class Header {@JsonProperty("app_id")private String appId;@JsonProperty("uid")private String uid;}@Data@Builderpublic static class Parameter {@JsonProperty("chat")private Chat chat;}@Data@Builderpublic static class Chat {@JsonProperty("domain")private String domain;@JsonProperty("temperature")private Double temperature;@JsonProperty("max_tokens")private Long maxTokens;}@Data@Builderpublic static class Payload {@JsonProperty("message")private Message message;}@Data@Builderpublic static class Message {@JsonProperty("text")private List<Text> text;}@Data@Builderpublic static class Text {@JsonProperty("role")private String role;@JsonProperty("content")private String content;}
}

响应体封装

@Data
public class XFLlmResponse {@JsonProperty("header")private Header header;@JsonProperty("payload")private Payload payload;@Datapublic static class Header {@JsonProperty("code")private Long code;@JsonProperty("message")private String message;@JsonProperty("sid")private String sid;@JsonProperty("status")private Long status;}@Datapublic static class Payload {@JsonProperty("choices")private Choices choices;}@Datapublic static class Choices {@JsonProperty("status")private Long status;@JsonProperty("seq")private Long seq;@JsonProperty("text")private List<Text> text;}@Datapublic class Text {@JsonProperty("content")private String content;@JsonProperty("role")private String role;@JsonProperty("index")private Long index;}
}

WebSocketListener的具体实现,用于收集llm返回的结果

@Slf4j
public class XFWebSocketListener extends WebSocketListener {/*** 断开websocket标志位*/@Getterprivate volatile boolean wsCloseFlag = false;/*** 锁*/@Getterprivate final Object lock = new Object();/*** 语句组装buffer,将大模型返回结果全部接收,在组装成一句话返回*/private StringBuilder answer = new StringBuilder();public String getAnswer() {return answer.toString();}@Overridepublic void onOpen(WebSocket webSocket, Response response) {super.onOpen(webSocket, response);log.info("讯飞星火大模型连接成功!");}@Overridepublic void onMessage(WebSocket webSocket, String text) {synchronized (lock) {super.onMessage(webSocket, text);XFLlmResponse response = JSON.parseObject(text, XFLlmResponse.class);log.info("response:{}", JSON.toJSONString(response));if (response.getHeader().getCode() != 0) {log.error("发生错误,错误信息为:{}", JSON.toJSONString(response.getHeader()));answer.append("大模型响应异常,请联系管理员");// 关闭连接标识wsCloseFlag = true;lock.notify();return;}List<XFLlmResponse.Text> textList = response.getPayload().getChoices().getText();for (XFLlmResponse.Text temp : textList) {log.info("讯飞大模型返回结果信息为:{}", JSON.toJSONString(temp));answer.append(temp.getContent());}log.info("result:{}", answer.toString());if (response.getHeader().getStatus() == 2) {wsCloseFlag = true;log.info("wsCloseFlag为:{}", wsCloseFlag + " result: " + answer);webSocket.close(1000, "Closing WebSocket connection");lock.notify();}}}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, Response response) {super.onFailure(webSocket, t, response);try {if (null != response) {int code = response.code();assert response.body() != null;log.error("onFailure body:{}", response.body().string());if (101 != code) {log.error("讯飞星火大模型连接异常");}}} catch (IOException e) {log.error("IO异常:" + e);}}@Overridepublic void onClosing(WebSocket webSocket, int code, String reason) {super.onClosing(webSocket, code, reason);wsCloseFlag = false;answer = new StringBuilder();}
}

4. 编写LLM的service

LLMService提供两个方法,一个是sendMsg用于给LLM发送数据,getResult则是获取LLM的响应结果

@Service
@Slf4j
public class LlmServiceImpl implements LlmService {@Resourceprivate XFLlmConfig xfLlmConfig;@Overridepublic WebSocket sendMsg(String text, WebSocketListener webSocketListener) {String wsUrl = xfLlmConfig.getWsUrl();Request request = new Request.Builder().url(wsUrl).build();OkHttpClient client = new OkHttpClient.Builder().build();String body = buildBody(text);log.info("llm request body: {}", body);WebSocket webSocket = client.newWebSocket(request, webSocketListener);webSocket.send(body);return webSocket;}@SneakyThrows@Overridepublic String getResult(XFWebSocketListener webSocketListener) {synchronized (webSocketListener.getLock()) {Long maxResponseTime = xfLlmConfig.getMaxResponseTime();long start = System.currentTimeMillis();if (!webSocketListener.isWsCloseFlag()) {log.info("Thread ID: {}, wsCloseFlag:{}, 线程等待", Thread.currentThread().getId(), webSocketListener.isWsCloseFlag());webSocketListener.getLock().wait(maxResponseTime);}long end = System.currentTimeMillis();log.info("Thread ID: {}, wsCloseFlag:{}, 等待时长:{} 线程被唤醒", Thread.currentThread().getId(), webSocketListener.isWsCloseFlag(), (end - start));if ((end - start) > maxResponseTime) {throw new RuntimeException("响应超时,请联系相关人员");}return webSocketListener.getAnswer();}}private String buildBody(String content) {XFLlmRequest request = new XFLlmRequest();XFLlmRequest.Header header = XFLlmRequest.Header.builder().appId(xfLlmConfig.getAppId()).build();request.setHeader(header);XFLlmRequest.Chat chat = XFLlmRequest.Chat.builder().domain(xfLlmConfig.getDomain()).build();XFLlmRequest.Parameter parameter = XFLlmRequest.Parameter.builder().chat(chat).build();request.setParameter(parameter);XFLlmRequest.Text text = XFLlmRequest.Text.builder().content(content).role(xfLlmConfig.getRole()).build();XFLlmRequest.Message message = XFLlmRequest.Message.builder().text(Collections.singletonList(text)).build();XFLlmRequest.Payload payload = XFLlmRequest.Payload.builder().message(message).build();request.setPayload(payload);return JSON.toJSONString(request);}
}

5. 编写controller

用户发起请求的request封装

@Data
public class GenerateSqlRequest {/*** 端口号*/private String port;/*** 主机*/private String host;/*** 密码*/private String password;/*** 用户名*/private String userName;/*** 数据库名字*/private String databaseName;/*** 表的名字*/private String tableName;/*** 用户的问题*/private String problem;
}
@RequestMapping("generate-sql")
@RestController
@RequiredArgsConstructor
public class GenerateSqlController {private final static String URL = "jdbc:mysql://%s:%s/%s";private final XFLlmConfig xfLlmConfig;private final LlmService llmService;@SneakyThrows@PostMappingpublic R generateSql(@RequestBody GenerateSqlRequest request) {String url = getUrl(request);String userName = request.getUserName();String password = request.getPassword();Class.forName("com.mysql.cj.jdbc.Driver");try (Connection conn = DriverManager.getConnection(url, userName, password)) {String tableName = request.getTableName();String query = "SHOW CREATE TABLE " + tableName;try (Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(query)) {if (rs.next()) {String ddl = rs.getString(2);String prompt = getPrompt(ddl, request);XFWebSocketListener webSocketListener = new XFWebSocketListener();llmService.sendMsg(prompt, webSocketListener);String result = llmService.getResult(webSocketListener);return R.success(result);}}} catch (SQLException e) {e.printStackTrace();}return R.fail();}public String getUrl(GenerateSqlRequest request) {return String.format(URL, request.getHost(), request.getPort(), request.getDatabaseName());}public String getPrompt(String ddl, GenerateSqlRequest request) {return String.format(xfLlmConfig.getPrompt(), ddl, request.getProblem());}

6. 运行测试

image-20240225174110107


六、总结

按照这样的一套流程,我们就把LLM接入到系统中,将我们的一个系统实现了智能化,让我们的系统有了自主思考的能力,这是一个不错的Demo。

对此,大家可以根据这种思想,将自己的项目改造,不再是简单的CRUD功能,这也是不错的亮点。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2808331.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

新改进!基于改进粒子群算法的微网/综合能源系统多目标优化调度程序代码!

适用平台&#xff1a;MatlabYalmipCplex 程序提出了一种综合考虑微电网系统运行成本和环境保护成本的并网模式下微电网多目标优化调度模型&#xff0c;采用改进的粒子群算法对优化模型进行求解。程序算例丰富、注释清晰、干货满满&#xff0c;可扩展性和创新性很高&#xff01…

readproc.h

Ubuntu22.04系统中 编译自己写的程序的时候&#xff0c;报错&#xff0c;显示找不到readproc.h文件&#xff0c;通过安装libprocps-dev解决 sudo apt install libprocps-dev

c#高级-正则表达式

正则表达式是由普通字符和元字符&#xff08;特殊符号&#xff09;组成的文字形式 应用场景 1.用于验证输入的邮箱是否合法。 2.用于验证输入的电话号码是否合法。 3.用于验证输入的身份证号码是否合法。等等 正则表达式常用的限定符总结&#xff1a; 几种常用的正则简写表达式…

女生常用的社交app软件有哪些?分享女生用的最多的社交软件

随着科技的迅猛发展&#xff0c;社交软件也日益多样化。除了常见的社交平台&#xff0c;一些全新的社交软件如雨后春笋般涌现&#xff0c;为用户带来了更多元、更有趣的社交体验。这里为大家介绍 5 款女生用的最多的社交软件&#xff0c;它们分别是丛丛、青藤之恋、meetu、小奢…

【Node.js】自动生成 API 文档

目录 1、直接使用swagger-ui-express 2、配合swagger-jsdoc 如何在Node.js项目中使用 Swagger 来自动生成 API接口文档&#xff0c;使用生成方式有很多种。本文基于swagger-jsdocswagger-ui-express快速实现 1、直接使用swagger-ui-express // 方便来浏览和测试api npm i sw…

分布式知识整理

分布式锁 以商场系统超卖现象举例 超卖现象一 现象&#xff1a; 商品卖出数量超出了库存数量。 产生原因&#xff1a; 扣减库存的动作在程序中进行&#xff0c;在程序中计算剩余库存&#xff0c;在并发场景下&#xff0c;导致库存计算错误。 代码复现 es.shutdown(); cycl…

二分算法(c++版)

二分的本质是什么&#xff1f; 很多人会认为单调性是二分的本质&#xff0c;但其实其本质并非单调性&#xff0c;只是说&#xff0c;有单调性的可以进行二分&#xff0c;但是有些题目没有单调性我们也可以进行二分。其本质其实是一个边界问题&#xff0c;给定一个条件&#xf…

2.WEB渗透测试-前置基础知识-web基础知识和操作系统

web基础知识 1.http协议 超文本传输协议是互联网上应用最广泛的一种网络协议。所有www文件都必须遵守的一个标准&#xff0c;是以 ASCII 码传输&#xff0c;建立在 TCP/IP 协议之上的应用层规范&#xff0c;通俗点说就是一种固定的通讯规则。 2、网络的三种架构及特点 网络应…

【C++STL】迭代器分类 失效问题

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

堆排序、快速排序和归并排序

堆排序、快速排序和归并排序是所有排序中最重要的三个排序&#xff0c;也是难度最大的三个排序&#xff1b;所以本文单独拿这三个排序来讲解 目录 一、堆排序 1.建堆 2.堆排序 二、快速排序 1.思想解析 2.Hoare版找基准 3.挖坑法找基准 4.快速排序的优化 5.快速排序非…

自定义神经网络二之模型训练推理

文章目录 前言模型概念模型是什么&#xff1f;模型参数有哪些神经网络参数案例 为什么要生成模型模型的大小什么是大模型 模型的训练和推理模型训练训练概念训练过程训练过程中的一些概念 模型推理推理概念推理过程 总结 前言 自定义神经网络一之Tensor和神经网络 通过上一篇…

IOBR2 更新(学习自备)

IOBR查看其收录的相关基因集(自备)_肿瘤 tme特征 iobr-CSDN博客 IOBR2&#xff1a;多维度解析肿瘤微环境 - 知乎 (zhihu.com) 学习手册&#xff1a;https://iobr.github.io/book/ &#xff08;里面有详细教程&#xff09; 系统综合的分析工具&#xff08;Immuno-Oncology Bi…

学习python的第7天,她不再开放她的听歌榜单

我下午登录上小号&#xff0c;打开聊天消息看到了她的回复&#xff0c;我很开心兴奋&#xff0c;可是她不再开放她的听歌榜单了&#xff0c;我感觉得到&#xff0c;我要失恋了。 “因为当年电视上看没有王菲版本的” “行”。 “那你以后还会开放听歌榜单吗&#xff1f;”我…

opencv基础 python与c++

question: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple matplotlib Opencv 一、读取图片 (1).imshow Mat imread(const string& filename, intflags1 );flags: enum { /* 8bit, color or not */CV_LOAD_IMAGE_UNCHANGED -1, /* 8bit, gray */CV_LOAD_I…

基于springboot+vue的大型商场应急预案管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

应用回归分析:非参数回归

非参数回归是一种统计方法&#xff0c;它在建模和分析数据时不假设固定的模型形式。与传统的参数回归模型不同&#xff0c;如线性回归和多项式回归&#xff0c;非参数回归不需要预先定义模型的结构&#xff08;例如&#xff0c;模型是否为线性或多项式&#xff09;。这使得非参…

小米标准模组+MCU 快速上手开发(一)——之固件下载

小米标准模组+MCU 开发笔记之固件下载 背景技术名词简介● 小米IoT开发者平台● 小米IoT 模组● ESP系列简介问题描述 + 解决方式问题1:固件下载是否有示例,如何下载到硬件板卡中?问题2:固件下载的官方程序是什么?在哪里?该如何使用?问题3:固件下载时,Flash和Ram 有什…

VCRUNTIME140_1.dll丢失是怎么回事,如何解决

当计算机系统中找不到vcruntime140_1.dll文件时&#xff0c;运行依赖于该文件的软件通常会显示错误消息&#xff0c;这类错误消息可能会包含以下几种形式&#xff1a; 明确提示缺失文件&#xff1a;错误信息可能直接指出“无法找到vcruntime140_1.dll”或“vcruntime140_1.dll…

怎么自学python,大概要多久?python多久上手?

无限时长~~~~技术不断在更新&#xff0c;你的自学不也需要一直进行吗&#xff1f; 但如果是问&#xff1a;自学多长时间可以入门&#xff1f;或者可以找到工作&#xff1f;那我可以告诉你答案。 从零基础开始自学Python&#xff0c;依照每个人理解能力的不同&#xff0c;大致…

no main manifest attribute, in app.jar

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…