总结——薄基础_Android开发_简易计算器__非教程

news2024/11/24 7:40:47

关于初学Android开发一段时间的总结,并非教程。主要是为自己防呆,避免以后学习新知识时走一遍老路。再次强调并非教程,如有不妥之处还望见谅

        零基础学习比较庞大的内容是十分吃力不讨好的,建议此前至少学习相关编程知识。此谓“薄基础”是相交而言的,指熟练掌握至少一门编程语言(最好是面向对象),并且开发过桌面应用程序终端应用程序嵌入式程序等,大致了解过打包发布的过程。

        Android开发现在主流推的编程语言是Koltin,IDE是Android Studio(安装过程可能会有一步卡住,gradle下载问题。此时如果你有过一门编程语言的基础,即便是没有任何Java或Koltin基础,它也不会成为障碍的。因为学习编程语言只有第一次打基础时注定会很难很煎熬,要接触变量、函数、类、多态继承、泛型编程等。此后再学习一门新的编程语言就不需要重新再按部就班地从头学习什么变量、函数、循环分支的了,这不仅浪费时间还会耗费学习热情。因为在编程语言这个大框架中,语法基本上大同小异,剩下的基本上是什么新特性,但编程理念却是一脉相承。

        在准备学习Android开发时,由于基础薄弱首推应该是视频教程,相较于书籍文档,虽然知识密度没那么高,但优点是直观、容易上手。比较好一点的视频教程有2022 最新 Android 基础教程_哔哩哔哩

        下面为这个视频教程的P1-P36的个人总结,不包含对知识点的讲解,主要是学习过程

一、基础学习

1,在下版本,有何贵干

         在开始的学习中,会介绍IDE的使用,然后是UI界面的简单设计。从这个教程的名字就可以看出这个版本已经“落后了”,互联网的技术迭代很快,所以要格外注重版本,关注更新了什么。这并不是说教程完全不管用,只不过部分内容需要自行抉择。

        比如学习这个界面显示时,视频教程从最初创建工程就已经不同了,视频里的工程创建有Java这个选项,而2024这个版本就只有Koltin了,索性就从Koltin开始(反正Java、Koltin都不会)。其中关于“Java是Koltin的必由之路”的观点我不是很赞同,掌握一门编程语言后,跨“语种”并非跨天堑。

 

        生成工程后,你会发现初始代码也不同(我是指调用函数的差别而非语言),甚至res目录下都没有layout这个包。此时遇到这种巨大的版本差距时,就应该查询相关信息了

 

         除了直接上网查询外,问AI也不错,因为对话式的问询更容易拓展知识面。从版本差异引发的问询中,可以得出几个关键信息:Compose、XML布局。

        接下来无论是继续问AI还是直接上网查询,都能获得一些教程之外的信息:

——现在谷歌主推Jetpack Compose这种声明式UI

——声明式UI是什么,XML是什么

——Jetpack Compose是什么,有什么优点

——         …… …… ……

 2,初识Jetpack Compose

        了解到这些信息后,无论是否继续学习XML,问题都不大,至少知道“前沿”是什么方向了,而且里面可以对XML进行可视化编程。本着“越新越好”的原则,教程里的UI设计相关部分P17-P27就可以跳过了,直接去学习Jetpack Compose。

        那么怎么学呢?首推应是视频,但在b站上没有找到合适的视频,所幸是谷歌为了大力推广Jetpack Compose,文档教程做的很棒,还是中文 初识 Jetpack Compose | 你好 Compose

         

         初学过程中,免不了要写一些代码,即便此前没有接触过Koltin,但一定的编程基础会让你大致能分辨哪些是变量、函数和类。此时主要以应用出发,需要哪个学哪个,比如需要知道怎么编写函数,那么就查函数,而不是连着协程、反射、解构什么的一块去学。

        而Koltin这块也有详细的中文文档,函数 · Kotlin 官方文档 中文版 (kotlincn.net)。

Tips:

        由于此前学的是C/C++,有过不停地造轮子的经历,初学Koltin时竟有一种解放双手的感觉,比如函数特性多用起来异常顺手、分号可有可无等等,逐渐陷入调包侠的快乐中

3, 页面导航

        经过前面学习,应该可以做一个基础的文本框什么的,而视频教程开头讲了使用XML怎么切换页面。此时由于已经转向Jetpack Compose的怀抱了,那么就先看看Jetpack Compose里有没有关于页面切换的,这里推荐一个插件:TongYi灵码,个体户是免费的。与之相似的好像还有Fittencode什么的

从这里可以获取到一个关键信息——NavHost,更确切的说是NavController这个类

4,Activity

        这一部分是P37节的,但是前面如果好奇心旺盛的话,也许会了解到这。那么通过查阅资料可能会接触到到单Activity模式,而视频中所用的是多Activity,上网查的话会发现后者现在逐渐淡化,版本更迭嘛

二、简易计算器的搭建

1,项目组织

        创建一个项目时,由于此前有过一些单片机小项目的开发经历,所以对于项目组织这一块很重视。此时Android对于我来说还是一个完全陌生的领域,Android项目里的文件组织完全看不懂。那么首要做的自然是了解,并且是带着强烈目的去了解,于是通过一系列对话让通义灵码生成了一个安卓开发常用的树形结构

 

        至于这个结构后续能不能沿用,很难说,因为后续必然会接触到一些新东西,不过船到桥头自然直,且行且看

2,UI设计

        考虑到这只是项目学习的开头,后续可能还会添加,那么就把这次实验(简易计算器)作为一个页面。简陋的UI设计只需理解两点

其一:@Composable注解的函数为可组合函数

其二:熟悉Koltin的传参方式和lambda表达式,以及Jetpack Compose组件的属性调用

        当然,初学在于模仿

 

 

3,运算逻辑

        其实一开始,我是AI式编程,“产品优先”嘛。先由AI给出实现方式,不断提出改进建议,再尝试理解,并以此为基础自行开发完善。

AI体验过程:通义(不是通义灵码)代码常常不能直接用,并且总是喜欢调包,尽管它调的包往往不能找不到。智普清言的话,只要描述的清楚,还是能够给出正确的代码的。不过总得来说,单一AI很难满足所有需求,两个得配合使用

用户: 

        现在按钮一共有数字、操作符+-*/、小数点、=、左右括号、C和CE类型,现在需要你编写函数handleButtonClick,可以传入的参数为label: String, textContent: MutableState<String>,前者为按钮的值,后者为一个组件的文本内容,可通过修改它来显示计算过程和结果(所以函数无需返回值)。
你需要考虑到优先级,并且需要写出完整的算法实现过程。使用Koltin编程,UI界面无需编写

用户: 

        现在发现了以下问题,对刚才代码进行改进:
1,扩展性有待提高,因为后续可能添加其他复杂运算(按钮的值可能是个复杂的字符串,不再是单一字符),而下面代码中tokenize函数里却以一个字符作为判断,这在拓展后可能会出现问题
2,注释太少

AI:

        …… …… …… …… ……

下面为AI生成的简易计算器的代码供参考,涉及到逆波兰表示法

目录结构

app/
├── src/main/java
│   └── com/example/yourapp
│       ├── ui
│       │   ├── theme
│       │   │   └── AppTheme.kt
│       │   ├── screens
│       │   │   ├── CalculatorScreen.kt
│       │   ├── components
│       │   └── navigation
│       │       └── AppNavigator.kt
│       ├── data
│       │   ├── models
│       │   └── repositories
│       ├── domain
│       ├── utils
│       └── viewmodels

│       └── MainActivity.kt
│           ├── CalculatorViewModel.kt
├── src/main/res
│   ├── drawable
│   ├── layout
│   ├── values
│   │   └── colors.xml
│   └── ...
└── build.gradle

代码:

CalculatorScreen.kt

package com.example.test_1.ui.screens

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.test_1.viewmodels.CalculatorViewModel

@Composable
fun CalculatorScreen() {
    val viewModel = CalculatorViewModel()

    val textContent = remember { mutableStateOf("") }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier.padding(16.dp)
        ) {
            // 显示计算结果
            Text(
                text = textContent.value,
                fontSize = 24.sp,
                modifier = Modifier.padding(bottom = 16.dp)
            )

            // 按钮布局
            val buttons = listOf(
                listOf("sqrt", "1/x", "x^2", "X^3"),
                listOf("CE", "(", ")", "C"),
                listOf("7", "8", "9", "/"),
                listOf("4", "5", "6", "*"),
                listOf("1", "2", "3", "-"),
                listOf("0", ".", "=", "+")
            )

            buttons.forEachIndexed { _, row ->
                Row(horizontalArrangement = Arrangement.Center) {
                    row.forEach { buttonLabel ->
                        Button(
                            onClick = {
                                viewModel.handleButtonClick(buttonLabel,textContent)
                            },
                            modifier = Modifier
                                .weight(1f)
                                .height(64.dp)
                                .padding(4.dp)  // 内边距
                        ) {
                            Text(text = buttonLabel)
                        }
                    }
                }
                Spacer(modifier = Modifier.height(8.dp))
            }
        }
    }
}

CalculatorViewModel.kt

package com.example.test_1.viewmodels

import androidx.compose.runtime.MutableState
import androidx.lifecycle.ViewModel


class CalculatorViewModel : ViewModel() {
    // 处理按钮点击事件
    fun handleButtonClick(label: String, textContent: MutableState<String>) {
        when (label) {
            "C" -> textContent.value = "" // 清空显示
            "CE" -> if (textContent.value.isNotEmpty()) textContent.value =
                textContent.value.dropLast(1) // 删除最后一个字符

            "=" -> {
                try {
                    val result = evaluateExpression(textContent.value)
                    textContent.value = result.toString()
                } catch (e: Exception) {
                    textContent.value = "Error" // 显示错误信息
                }
            }

            // 特殊运算符处理
            "sqrt", "1/x" -> {
                if (textContent.value == "Error") {
                    textContent.value = ""
                }
                textContent.value += " $label(" // 添加运算符和左括号
            }

            else -> {
                if (textContent.value == "Error") {
                    textContent.value = ""
                }
                textContent.value += label // 添加其他字符到显示
            }
        }
    }

    // 计算表达式
    private fun evaluateExpression(expression: String): Double {
        val outputQueue = mutableListOf<String>()
        val operatorStack = mutableListOf<String>()
        val tokens = tokenize(expression)

        for (token in tokens) {
            when {
                token.toDoubleOrNull() != null -> outputQueue.add(token)
                token in listOf("+", "-", "*", "/") -> {
                    while (operatorStack.isNotEmpty() && hasPrecedence(
                            token,
                            operatorStack.last()
                        )
                    ) {
                        outputQueue.add(operatorStack.removeLast())
                    }
                    operatorStack.add(token)
                }

                token == "(" -> operatorStack.add(token)
                token == ")" -> {
                    while (operatorStack.isNotEmpty() && operatorStack.last() != "(") {
                        outputQueue.add(operatorStack.removeLast())
                    }
                    operatorStack.removeLast() // 移除左括号
                }

                // 特殊运算符直接入栈
                token == "sqrt" || token == "1/x" -> operatorStack.add(token)
            }
        }

        while (operatorStack.isNotEmpty()) {
            outputQueue.add(operatorStack.removeLast())
        }

        return evaluateRPN(outputQueue.toList())
    }

    // 将表达式转换为令牌列表
    private fun tokenize(expression: String): List<String> {
        val tokens = mutableListOf<String>()
        val buffer = StringBuilder()

        // 使用正则表达式来分割字符串,支持更复杂的运算符
        val tokenPattern = Regex("[+\\-*/()\\s]|sqrt|1/x|\\d+(\\.\\d+)?")
        tokenPattern.findAll(expression).forEach { matchResult ->
            val token = matchResult.value
            when {
                token.toDoubleOrNull() != null || token == "." -> buffer.append(token)
                token.matches(Regex("[+\\-*/()\\s]")) -> {
                    if (buffer.isNotEmpty()) {
                        tokens.add(buffer.toString())
                        buffer.clear()
                    }
                    tokens.add(token)
                }

                else -> tokens.add(token) // 添加特殊运算符
            }
        }

        if (buffer.isNotEmpty()) {
            tokens.add(buffer.toString())
        }

        return tokens
    }

    // 判断操作符优先级
    private fun hasPrecedence(op1: String, op2: String): Boolean {
        return (op2 == "*" || op2 == "/") && (op1 == "+" || op1 == "-")
    }

    // 计算逆波兰表达式
    private fun evaluateRPN(tokens: List<String>): Double {
        val stack = mutableListOf<Double>()

        tokens.forEach { token ->
            when {
                token.toDoubleOrNull() != null -> stack.add(token.toDouble())
                token in listOf("+", "-", "*", "/") -> {
                    if (stack.size < 2) throw IllegalArgumentException("Insufficient values in the expression.")
                    val b = stack.removeLast()
                    val a = stack.removeLast()
                    stack.add(
                        when (token) {
                            "+" -> a + b
                            "-" -> a - b
                            "*" -> a * b
                            "/" -> {
                                if (b == 0.0) throw ArithmeticException("Cannot divide by zero.")
                                a / b
                            }

                            else -> throw IllegalArgumentException("Unknown operator.")
                        }
                    )
                }

                token == "sqrt" -> {
                    if (stack.isEmpty()) throw IllegalArgumentException("Insufficient values in the expression.")
                    val a = stack.removeLast()
                    if (a < 0) throw ArithmeticException("Cannot take the square root of a negative number.")
                    stack.add(kotlin.math.sqrt(a))
                }

                token == "1/x" -> {
                    if (stack.isEmpty()) throw IllegalArgumentException("Insufficient values in the expression.")
                    val a = stack.removeLast()
                    if (a == 0.0) throw ArithmeticException("Cannot take the reciprocal of zero.")
                    stack.add(1 / a)
                }

                else -> throw IllegalArgumentException("Unknown token: $token")
            }
        }

        if (stack.size != 1) throw IllegalArgumentException("The user input has too many values.")
        return stack.last()
    }
}


AppNavigator.kt

package com.example.test_1.ui.navigation

import android.util.Log
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.test_1.ui.screens.CalculatorScreen


@Composable
fun AppNavigator() {
    // 创建一个导航控制器实例,用于在不同的页面之间进行切换
    val navController = rememberNavController()

    // 设置导航主机,管理页面的跳转逻辑
    NavHost(navController = navController, startDestination = "calculator_screen") {
        // 定义导航到第一页的路由
        composable("calculator_screen") {CalculatorScreen() }
    }

    /**
     * Log为日志打印,一共有5个级别,从低到高依次为:Verbose、Debug、Info、Warn、Error
     * 即冗余信息,调试信息,信息,警告,错误
     * */
    // Log.d("导航", "已完成导航初始化")
}

 MainActivity.kt

package com.example.test_1

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.test_1.ui.navigation.AppNavigator
import com.example.test_1.ui.theme.Test_1Theme


open class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Test_1Theme {
                AppNavigator()
            }
        }
    }
}



/**
 * 预览函数
 * ① 也是一个函数,只不过在函数名前加@Preview
 * ② 函数不能含参
 */
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    AppNavigator()
}




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

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

相关文章

从0到1教你搭建Android自动化Python+appium环境

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、需要软件 1. JDK:JAVA安装后配置JDK环境 2. SDK:SDK下载后配置adb环境 3. Python:pyhton语言 4. Pycharm:python脚本编译工具 5. Appium-python-clie…

CCF推荐B类会议和期刊总结:(计算机网络领域)

CCF推荐B类会议和期刊总结&#xff08;计算机网络领域&#xff09; 在计算机网络领域&#xff0c;中国计算机学会&#xff08;CCF&#xff09;推荐的B类会议和期刊代表了该领域的较高水平。以下是对所有B类会议和期刊的总结&#xff0c;包括全称、出版社、dblp文献网址以及所属…

QML与widget

杂谈 QML-各类建材&#xff08;水泥、沙石、钢筋等各种材料&#xff09;&#xff0c;可以做出各种炫酷建筑wiget-板房&#xff0c;可靠、功能性强。 QML&#xff08;Qt Meta Language or Qt Modeling Language&#xff09;&#xff1a; QML 是一种基于 JavaScript 的声明式语…

‌汽车一键式启动系统‌包含哪些功能

‌汽车一键式启动系统‌是一种智能化的汽车启动系统&#xff0c;它通过一个按钮来启动和熄灭发动机&#xff0c;取代了传统的钥匙启动方式。这个系统不仅简化了启动和熄火的步骤&#xff0c;还提供了多种智能化的功能&#xff0c;如自动开锁、自动关锁、自动关窗、自动防盗等。…

HarmonyOS开发之(下拉刷新,上拉加载)控件pulltorefresh组件的使用

效果图&#xff1a; 一&#xff1a;下载安装&#xff08;地址&#xff1a;OpenHarmony-SIG/PullToRefresh&#xff09; ohpm install ohos/pulltorefresh 二&#xff1a;使用lazyForEarch的数据作为数据源 export class BasicDataSource implements IDataSource{private l…

一种简易CAN数据分析器的实现(一)【工程创建+CAN波特率计算工具】

程序实现详见《一种简易CAN数据分析器的实现&#xff08;二&#xff09;【程序实现】》 微信公众号关注&#xff1a;掌芯元器&#xff0c;免费为大家提供嵌入式相关的技术咨询&#xff01;&#xff01;&#xff01; 目录 一、功能需求及实现 1、功能设计及实现 2、软硬件需求…

视频怎么转换成mp3格式?分享5种便捷的转换方法

在日常生活中&#xff0c;我们经常会遇到需要将视频文件中的音频提取出来&#xff0c;转换成MP3格式的情况&#xff0c;以便在手机、MP3播放器或其他设备上播放。今天&#xff0c;我将为大家介绍5种视频转MP3的方法&#xff0c;非常简单便捷&#xff0c;一起来学习下吧。 方法一…

oelove奥壹婚恋征婚相亲交友系统v10.0原生小程序源码(无编译加密)上架经验让你少走弯路

首先大家要明白什么是原生小程序&#xff01;以微信小程序为例&#xff0c;原生小程序就是指在微信指定的平台“开发者工具”按照平台规则开发出来的小程序才是原生小程序&#xff0c;像uniapp它就不属于原生小程序是第三方&#xff0c;原生小程序的好处那一万字也说不完&#…

JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级

这里写目录标题 JavaScript高级第03天1.函数的定义和调用1.1函数的定义方式1.2函数的调用 2.this2.1函数内部的this指向2.2改变函数内部 this 指向2.2.1 call方法2.2.2 apply方法2.2.3 bind方法2.2.4 call、apply、bind三者的异同 3.严格模式3.1什么是严格模式3.2开启严格模式3…

21. Revit API: 几何对象(二)- Curve

上篇讲了Revit几何对象的类层次结构&#xff0c;讲了几何元素和几何实例&#xff0c;简单提到了Solid。 这一篇呢&#xff0c;就从构成Solid的边讲起。 一、Edge&#xff08;边&#xff09; Edge在Revit中就是用来表示边的&#xff0c;并且在各种表示几何结构的类中串门。 前…

使用 Elementary 实现开源数据可观测性 — 从零到精通(第一部分)

欢迎来到雲闪世界。我希望在我还是初学者时能有一份循序渐进的实践指南 数据可观测性及其重要性经常被讨论和撰写为现代数据和分析工程的一个重要方面。市场上有许多工具&#xff0c;具有各种功能和价格。在这篇由两部分组成的文章中&#xff0c;我们将重点介绍 Elementary 的…

(k8s)kubernetes 挂载 minio csi 的方式

一、安装Minio&#xff08;Minio分布式集群搭建部署_minio集群最少几台-CSDN博客&#xff09; 生成accessKeyID和secretAccessKey&#xff1a; 二、安装csi-s3插件(在k8s集群上) 首先我们把插件的yaml文件都下载下来&#xff0c;为了保证版本测试的一致性&#xff0c;我们下载…

论文阅读:RGBD GS-ICP SLAM

目录 概要 Motivation 整体框架流程 技术细节 小结 论文地址&#xff1a;[2403.12550] RGBD GS-ICP SLAM (arxiv.org) 代码地址&#xff1a;https://github.com/Lab-of-AI-and-Robotics/GS-ICP-SLAM 概要 RGBD GS-ICP SLAM 是一种结合通用迭代最近点算法&#xff08;Ge…

【基础算法总结】前缀和

目录 一&#xff0c;前缀和算法介绍二&#xff0c;算法原理和代码实现【模板】前缀和【模板】二维前缀和724.寻找数组的中心下标238.除自身以外数组的乘积560.和为k的子数组974.和可被k整除的子数组525.连续数组1314.矩阵区域和 三&#xff0c;算法总结 一&#xff0c;前缀和算…

可能一拆为二,英特尔为何走到今天这一步?

【科技明说 &#xff5c; 科技热点关注】 近来看到外媒消息说&#xff0c;英特尔迫于经营压力&#xff0c;也不得不铤而走险&#xff0c;欲将英特尔一分为二&#xff0c;即芯片制造与芯片设计分离开&#xff0c;互相剥离&#xff0c;独立发展。 于是乎&#xff0c;英特尔将分拆…

图卷积神经网络GNN(一)

图卷积神经网络GNN 研究学习的背景 对于图神经网络&#xff08;GNN&#xff09;来输入的数据是图。&#xff08;解决输入数据不规则情况&#xff09;输入的格式不是固定的 研究涵盖&#xff1a;节点分类&#xff08;nodeclassification&#xff09;、边预测&#xff08;link…

​ArcGIS Pro和ArcGIS的10大区别

本文来源&#xff1a;水经注GIS公众号 如果你经常使用ArcGIS 进行制图和分析&#xff0c;那么你一定听说过ArcGIS Pro&#xff0c;这款软件是Esri未来主打的一款桌面GIS软件&#xff0c;那么这款软件和ArcGIS相比有什么不同呢&#xff0c;这里为你列举了两款软件的10大区别&am…

海康威视相机在QTcreate上的环境配置教程(qt+opencv+海康SDK)

环境配置教程 前言&#xff1a;环境配置&#xff1a;1.海康SDK2.opencv 参考导入文件 前言&#xff1a; 配置环境是编程的第一步&#xff0c;所以写这篇文章来指导环境的配置。如果已经配置好了&#xff0c;想在qt上使用海康的摄像头&#xff0c;可以参考这篇文章&#xff1a;…

骨传导耳机哪个品牌好用?良心测评推荐5大高分骨传导耳机!

在快节奏、数字化的生活时代&#xff0c;耳机成为连接外界与个人世界的桥梁&#xff0c;尤其在户外运动和健身场景中更是不可或缺。传统入耳式耳机虽然携带方便、音质优秀&#xff0c;但长时间佩戴会对耳道和鼓膜造成压力&#xff0c;甚至引发耳部不适。为解决这一痛点&#xf…

windows下自启springboot项目(jar+nginx)

1、将springboot项目打包为jar 2、新建文本文档 test.txt&#xff0c;并输入 java -jar D:\test\test.jar&#xff08;修改为自己的jar包位置&#xff09; 保存 然后修将后缀名改为 .bat 3、在同一目录再新建 文本文档test.txt&#xff0c;输入以下内容&#xff0c;&…