Vue--》简易资金管理系统后台项目实战(后端)

news2024/11/16 16:44:08

        今天开始使用 node + vue3 + ts搭建一个简易资金管理系统的前后端分离项目,因为前后端分离所以会分两个专栏分别讲解前端与后端的实现,后端项目文章讲解可参考:前端链接,我会在前后端的两类专栏的最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本专栏,学习更多前端node知识,然后开篇先简单介绍一下本项目用到的技术栈都有哪几个方面(阅读本文章能够学习到的技术):

node:基于Chrome V8引擎的开源、跨平台的JavaScript运行环境。

express:基于node.js的Web应用框架,帮助开发者快速构建可靠、高效的Web应用程序。

pm2:针对node应用生产环境进程管理工具,可帮助简化应用程序部署运行和监视等工作。

MongoDB:面向文档的NoSQL数据库系统,它可以灵活地存储非结构化数据。

Apifox:易用界面和功能,帮助开发人员和团队更高效地创建、设计和管理 RESTful API。

jwt:用于定义在网络上传输的信息安全可验证和可信任的方式,用于身份验证和授权机制。

目录

express搭建服务器

连接MongoDB数据库

搭建注册接口

搭建登录接口

获取登录信息

添加和获取信息

编辑和删除信息


express搭建服务器

首先我们需要创建一个文件夹用于存放后端项目,然后将文件夹拖到编辑器vscode当中,接下来需要打开终端执行 npm init 初始化package.json文件,相关步骤如下:

接下来开始装载 express 框架,终端执行如下命令:

npm install express

接下来我们在项目文件夹中创建入口文件 server.js 然后开始使用 express 框架:

const express  = require("express")
const app = express()
// 设置路由
app.get('/', (req,res) =>{
  res.send('hello world')
})
// 设置端口号
const post = process.env.PORT || 8080
app.listen(post, () => {
  console.log(`Server is running on port ${post},url: http://127.0.0.1:${post}`)
})

为了方便代码的执行,这里我们采用 pm2 进程管理工具进行使用,关于pm2的安装和具体的使用教程可以参考我之前的文章:深入理解 PM2:Node.js 应用部署和管理利器 ,为了方便简介,这里我们可以直接在package.json文件中设置一下我们的命令,如 下:

pm2进程管理工具是不会随着你将终端关闭而停止运行的,如果是第一次运行项目的话,可以根据我们上面设置的命令执行:npm run start 即可,如果想关闭项目的话,执行 pm2 stop 加项目名称就行了,如果实时查看日志,执行 pm2 log 即可,具体的实操自己体会,这里不再赘述。

接下来我们打开我们本地的 5000 端口,可以看到我们后端运行的项目了:

连接MongoDB数据库

接下来我们在本地创建一个 MongoDB数据库,用来后面后端编写相应接口所需要存放的数据,如果不了解MongoDB的朋友推荐可以参考一下我之前的文章:MongoDB数据库 ,接下来我们需要开始执行 node 连接MongoDB数据库了。

启动MongoDB服务

点击win键输入cmd,点击以管理员身份运行

执行 net start MongoDB 命令,运行MongoDB数据库服务:

创建MongoDB数据库

使用 Navicat 图形化管理工具,创建数据库。点击新建连接,选择 MongoDB

配置相应参数,连接直接默认即可,一般情况下,我们连接只需要连接主库查数据,所以选择独立的这个连接方式就可以,填写好常规参数可以点击测试连接是否正常,即可连接!因为是个人测试嘛,使用的是本地的localhost:

出现如下界面说明连接成功,我们直接点击确定即可。 

注意:进入到数据库之后,我们删除默认的数据库,重新创建一个名为 node_fund 名称的数据库

连接MongoDB数据库

接下来我们需要借助vscode工具来连接mongodb数据库,首先终端执行如下命令安装相应的包:

npm install mongoose

安装完成之后,我们在server.js中执行如下命令进行验证是否连接数据库成功:

// 引入 express 服务框架
const express  = require("express")
// 引入 mongoose 数据库服务
const mongoose = require("mongoose")
const app = express()
// 连接数据库的 URL
const MongoUrl = 'mongodb://localhost:27017/node_fund'
// 连接数据库
mongoose.connect(MongoUrl).then(() => {
  console.log('连接成功')
}).catch((err) => {
  console.log('连接失败', err)
})
// 设置路由
app.get('/', (req,res) =>{
  res.send('hello world')
})
// 设置端口号
const post = process.env.PORT || 5000
app.listen(post, () => {
  console.log(`Server is running on port ${post},url: http://127.0.0.1:${post}`)
})

当我们执行 pm2 log 查看日志的时候可以看到,我们的终端打印了连接成功的字眼:

当我们更改 MongoUrl 的默认url的时候,终端就会打印出相应的连接失败以及相应报错:

搭建注册接口

接下来我们需要开始搭建真正的接口了,首先我们需要在项目根目录下创建相关文件夹编写接口:

在编写接口之前,我们需要先安装 body-parser ,为了方便地在node中处理 POST 请求的请求体数据,从而更轻松地进行数据处理和响应,终端执行如下命令进行安装:

npm install body-parser

安装完成之后,我们还需要在入口文件 server.js 中使用它来解析请求体:

const express  = require("express")
const bodyParser = require("body-parser")
const app = express()

// 使用 body-parser 中间件
app.use(bodyParser.urlencoded({ extended: false })) // 解析表单数据
app.use(bodyParser.json()) // 解析 JSON 格式的请求体

插件安装完成之后,我们还需要在项目根目录下创建一个models文件,用于存放从数据库获取数据所需要的数据及其相应的类型,这里需要借助 Schema 属性:

const mongoose = require('mongoose')

// 定义模式,用于指定数据的结构和字段。
const Schema = mongoose.Schema
// 使用Schema变量来定义具体的数据模型
const userSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  avatar: {
    type: String,
  },
  identity: {
    type: String,
    required: true
  },
  date: {
    type: Date,
    default: Date.now
  },
})
/** 
 * 创建了一个名为users的MongoDB集合,并使用userSchema指定了集合中文档的结构
 * 将前一步创建的模型赋值给一个变量User,使其成为我们操作users集合的接口。
 */
module.exports = User = mongoose.model('users', userSchema)

定义完相应的注册登录接口所需类型之后,接下来就需要正式在user.js文件中编写相应的注册接口,代码如下:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()

// 引入具体的数据类型
const User = require('../../models/User')

/**
 * 注册接口
 * POST api/users/register 
 */
router.post('/register', (req,res) => {
  // 查询数据库中是否拥有邮箱
  User.findOne({ email: req.body.email }).then((user) => {
    if (user) {
      return res.status(400).json({ email: '邮箱已被注册' })
    } else {
      // 注册新邮箱
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        avatar,
        password: req.body.password,
        identity: req.body.identity
      })
    }
  })
})

module.exports = router

因为密码是至关重要的数据,所以这里我们需要对用户的密码进行一个加密,终端执行如下命令:

npm install bcrypt

下载成功之后,导入 bcrypt 然后对数据进行相应的hash加密,修改后的代码如下:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()
const bcrypt = require("bcrypt")

// 引入具体的数据类型
const User = require('../../models/User')

/**
 * 注册接口
 * POST api/users/register 
 */
router.post('/register', (req,res) => {
  // 查询数据库中是否拥有邮箱
  User.findOne({ email: req.body.email }).then((user) => {
    if (user) {
      return res.status(400).json({ email: '邮箱已被注册' })
    } else {
      // 注册新邮箱
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        avatar,
        password: req.body.password,
        identity: req.body.identity
      })
      // 进行密码加密
      bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(newUser.password, salt, (err, hash) => {
          if (err) throw err
          newUser.password = hash
          newUser.save().then(user => res.json(user)).catch(err => console.log(err))
        })
      })
    }
  })
})

module.exports = router

写完相应代码之后,这里我们还需要将该路由代码在入口文件 server.js 中进行一个引入:

const express  = require("express")
const app = express()

// 引入user.js
const users = require('./routes/api/user')
// 使用routes
app.use('/api/users', users)

接下来我们需要借助接口测试工具 Apifox 进行相应的接口测试,如果不了解 Apifox 的朋友,可以参考我之前的文章:Apifox:详细使用教程,带你轻松拿捏 ,为了便于测试,我们先把 avatar 图像参数先删除,先测试一下其他参数。当我们输入相关路径及其参数点击发送之后:

当我们再次点击发送之后,就会出现当前的邮箱已被注册,符合逻辑规律:

接下来我们需要处理 avatar 头像参数,需要将图片数据保存到数据库中,可以使用 Buffer 对象来处理二进制数据,并将其存储为 Buffer 类型的字段。这里我们需要安装如下的这个插件进行处理

npm install multer

然后代码进行如下方式的修改:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()
const bcrypt = require("bcrypt")
const multer = require('multer');
const jwt = require("jsonwebtoken")
const passport = require("passport")

// 引入具体的数据类型
const User = require('../../models/User')

// 配置 multer
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'public/images') // 设置图片保存的路径
  },
  filename: function (req, file, cb) {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
    cb(null, file.fieldname + '-' + uniqueSuffix + '.' + file.mimetype.split('/')[1]) // 设置图片的文件名
  }
})
const upload = multer({ storage: storage })

/**
 * 注册接口
 * POST api/users/register 
 */
router.post('/register', upload.single('avatar'), (req,res) => {
  // 查询数据库中是否拥有邮箱
  User.findOne({ email: req.body.email }).then((user) => {
    if (user) {
      return res.status(400).json('邮箱已被注册')
    } else {
      const avatarUrl = req.protocol + '://' + req.get('host') + '/images/' + req.file.filename;
      // 注册新邮箱
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        avatar: avatarUrl, // 使用上传的图片的文件名作为 avatar 字段的值
        password: req.body.password,
        identity: req.body.identity
      })
      // 进行密码加密
      bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(newUser.password, salt, (err, hash) => {
          if (err) throw err
          newUser.password = hash
          newUser.save().then(user => res.json(user)).catch(err => console.log(err))
        })
      })
    }
  })
})

接下来进行Apifox接口测试之后,得到的结果如下:

如果想访问图片资源的话,这里我们还需要在入口文件出将静态资源设置为可访问:

app.use(express.static('public'));

当我们打开 Navicat 可视化管理工具之后,找到我们创建的对应数据,打开集合就能看到我们在测试工具Apifox生成的数据,在数据库当中也呈现出来了:

搭建登录接口

和注册接口一样,原理就是我们拿到用户请求过来的email和password之后,进行数据库的一个查询,如果查询当前数据库没有用户传递过来的email数据,就返回用户不存在,否则的话就开始比较密码进行相应的匹配得到对应的结果:

/**
 * 登录接口
 * POST api/users/login
 */
router.post('/login', (req,res) => {
  const email = req.body.email
  const password = req.body.password
  // 查询数据库
  User.findOne({ email }).then(user => {
    if (!user) {
      return res.status(404).json({ email: '用户不存在!' })
    }
    // 密码匹配
    bcrypt.compare(password, user.password).then(isMatch => {
      if (isMatch) {
        res.json({ msg: 'success' })
      } else {
        return res.status(400).json({ password: '密码错误!' })
      }
    })
  })
})

比如说我们拿一下数据库当中真实存在的数据进行测试一下,得到的结果肯定是 success :

如果说我们随便输入密码或者用户名可能不是success了,举个例子改一下密码:

接下来我们需要给登录成功的 success 返回相应的token,终端执行如下命令按照相应包:

npm install jsonwebtoken

在导入 const jwt = require("jsonwebtoken") 之后,在密码匹配的地方执行jwt设置一个标记,过期时间我们设置了一个小时,然后token的话和前面的字符串进行了一个拼接:

在Apifox接口测试工具进行测试得到的相应结果如下:

获取登录信息

登录的接口写完,我们还需要写一个获取登录信息的接口函数,首先判断我们是否登录成功即需要验证一下我们当前登录的token是否存在且正确,所以这里我们需要借助 passport-jwt等工具进行:

npm install passport-jwt passport

接下来我们需要在入口文件 server.js 中初始化passport,然后将逻辑代码单独抽离出来:

// passport初始化
app.use(passport.initialize())
// 引入passport逻辑功能代码
require("./config/passport")(passport)

在抽离出来的config文件夹下的passport文件,这里我们开始书写真正的token校验:

const JwtStrategy = require("passport-jwt").Strategy
ExtractJwt = require("passport-jwt").ExtractJwt
const mongoose = require("mongoose")
const User = mongoose.model("users")

const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken()
opts.secretOrKey = 'secret'

module.exports = passport => {
  passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
    User.findById(jwt_payload.id).then(user => {
      if (user) {
        return done(null, user)
      }
      return done(null, false)
    }).catch((err) => {
      console.log(err)
    })
  }))
}

在user.js路由文件夹下,我们开始书写获取登录信息的接口了,这里我们仍然将图片资源进行应该路径的拼接:

/**
 * 获取登录信息
 * POST api/users/current
 */
router.get('/current', passport.authenticate("jwt", { session: false }), (req,res) => {
  res.json({
    id: req.user.id,
    name: req.user.name,
    email: req.user.email,
    avatar: req.user.avatar,
    identity: req.user.identity
  })
})

通过Apifox得到的结果如下:

添加和获取信息

注册和登录的功能写完之后,接下来需要我们开始编写添加和获取信息的接口,这里我们要在api文件夹下再新建一个 profile 文件,以及在模型models文件夹下新建 Profile文件:

创建的两个文件都要在入口文件 server.js 中进行引入:

// 引入profile.js
const profile = require('./routes/api/profile')
// 使用routes
app.use('/api/profile', profile)

接下来我们先编写数据模型文件的代码,具体代码如下:

const mongoose = require('mongoose')

// 定义模式,用于指定数据的结构和字段。
const Schema = mongoose.Schema
// 使用Schema变量来定义具体的数据模型
const ProfileSchema = new Schema({
  type: {
    type: String,
  },
  describe: {
    type: String,
  },
  income: {
    type: String,
    required: true
  },
  expend: {
    type: String,
    required: true
  },
  cash: {
    type: String,
    required: true
  },
  remark: {
    type: String,
  },
  date: {
    type: Date,
    default: Date.now
  },
})
/** 
 * 创建了一个名为users的MongoDB集合,并使用userSchema指定了集合中文档的结构
 * 将前一步创建的模型赋值给一个变量User,使其成为我们操作users集合的接口。
 */
module.exports = Profile = mongoose.model('profile', ProfileSchema)

根据上面讲解的编写接口的经验,我们很容易就写出添加和获取信息的即可,如下:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()
const passport = require("passport")

// 引入具体的数据类型
const Profile = require('../../models/Profile')

/**
 * 创建信息接口
 * POST api/profiles/add
 */
router.post("/add", (req, res, next) => {
  passport.authenticate("jwt", { session: false }, (err, user, info) => {
    // 判断错误情况
    if (err) return res.status(500).json({ error: "Internal Server Error" });
    if (!user) return res.status(401).json({ error: "Unauthorized" });
    const profileFields = {};
    if (req.body.type) profileFields.type = req.body.type;
    if (req.body.describe) profileFields.describe = req.body.describe;
    if (req.body.income) profileFields.income = req.body.income;
    if (req.body.expend) profileFields.expend = req.body.expend;
    if (req.body.cash) profileFields.cash = req.body.cash;
    if (req.body.remark) profileFields.remark = req.body.remark;
    new Profile(profileFields).save().then((profile) => {
        res.json(profile);
      }).catch(err => {
        res.status(500).json(err);
      })
  })(req, res, next);
})

/**
 * 获取所有信息
 * POST api/profiles
 */
router.get( '/', passport.authenticate('jwt', { session: false }), (req, res) => {
  Profile.find().then((profiles) => {
      if (!profiles || profiles.length === 0) return res.status(404).json({ error: '数据为空!' })
      const profileData = { profiles: profiles }
      res.json(profileData)
    }).catch((err) => res.status(404).json(err));
  }
);
module.exports = router

获取单个信息的接口也很简单,只需要在路径拼接上id,id后面前端传递给我们即可:

/**
 * 获取单个信息
 * POST api/profiles/:id
 */
router.get('/:id', passport.authenticate('jwt', { session: false }), (req, res) => {
  Profile.findOne({ _id: req.params.id }).then((profile) => {
    if (!profile) return res.status(404).json({ error: '数据为空!' });
    res.json(profile);
  }).catch((err) => res.status(404).json(err));
});

通过Apifox测试的结果如下:

编辑和删除信息

编辑信息接口和添加信息接口写法大体一致,如下:

/**
 * 编辑信息接口
 * POST api/profiles/edit
 */
router.post(
  "/edit/:id", 
  passport.authenticate("jwt", { session: false }), 
  (req, res) => {
    const profileFields = {};
    if (req.body.type) profileFields.type = req.body.type;
    if (req.body.describe) profileFields.describe = req.body.describe;
    if (req.body.income) profileFields.income = req.body.income;
    if (req.body.expend) profileFields.expend = req.body.expend;
    if (req.body.cash) profileFields.cash = req.body.cash;
    if (req.body.remark) profileFields.remark = req.body.remark;
    Profile.findOneAndUpdate(
      { _id: req.params.id },
      { $set: profileFields },
      { new: true }
    ).then(profile => res.json(profile))
  }
)

删除信息的接口也很简单:

/**
 * 删除信息
 * POST api/profiles/:id
 */
router.delete(
  '/delete/:id', 
  passport.authenticate('jwt', { session: false }), 
  (req, res) => {
    Profile.findOneAndDelete({ _id: req.params.id }).then(profile => {
      res.json(profile);
    }).catch(err => res.status(404).json('删除失败'));    
});

表示删除成功!

ok!后端的一些基础代码已经写完了,下一篇将借助这些接口开始前端页面的书写了,等前后端项目全部写完,我再将源码开源出来,敬请期待!!!

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

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

相关文章

转行做程序员,多晚都不晚

大家好啊,我是董董灿。 最近有不少小伙伴加我微信咨询一些问题,有同学想了解AI行业的现状,想着转行的,也有在校生想了解毕业后工作方向的,当然也有想学习编程知识的。 诚惶诚恐,没想到之前写的文章&#…

【CHI】CHI协议,transaction事务汇总

前言 CHI协议最难的是什么,就是那一堆各种各样的事务,你不知道什么场景应该使用什么合适的事务,收到X事务又该回复什么事务。相当于CHI给你制定了很多种(尽可能覆盖完全)场景及事务,你需要去了解&#xff0…

英语——分享篇——每日200词——2401-2600

2401——moisture——[mɔɪstʃə(r)]——n.潮气,湿气,水分——moisture——moist潮湿的(熟词)ur你的(编码your)e鹅(编码)——潮湿的地方你的鹅一身潮气——Moisture in the atmosphere condensed into dew during the night.——大气中的水分在夜间凝结…

二阶系统时域响应

二阶系统微分方程 二阶系统传递函数 二阶系统单位阶跃响应 过阻尼系统 临界阻尼系统 欠阻尼系统 无阻尼系统 二阶系统阶跃响应仿真 在Matlab中进行仿真,设置不同阻尼比2、1、0.5和0,可以得到结论: 阻尼比越小,系统响应速度越快&…

YOLOV8改进:RefConv(即插即用!重参数化重聚焦卷积替代常规卷积,无额外推理成本下涨点明显!)

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 3.涨点效果:RefConv,实现有效涨点! 论文地址…

【项目设计】网络对战五子棋(上)

想回家过年… 文章目录 一、项目前置知识1. websocketpp库1.1 http1.0/1.1和websocket协议1.2 websocketpp库接口的前置认识1.3 搭建一个http/websocket服务器 2. jsoncpp库3. mysqlclient库 二、 项目设计1. 项目模块划分2. 实用工具类模块2.1 日志宏封装2.2 mysql_util2.3 j…

蓝桥杯每日一题2023.10.21

后缀表达式 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 30分解法&#xff1a;要求出最大的结果就需要加的数越大&#xff0c;减的数越小&#xff0c;以此为思路简单列举即可 #include<bits/stdc.h> using namespace std; typedef long long ll; const int N 2e5 10…

物联网知识复习

物联网的内涵和体系结构 物联网的基本内涵 物联网的基本内涵在于物联&#xff0c;物物相连或者物和人相连的互联网。 也就是说&#xff0c;它是要由物主动发起的&#xff0c;物物互联的互联网。 它的第一层意思是说物和物相连&#xff1b;第二层意思是说物和人相连。 物联网的…

Gradient conjugate priors and multi-layer neural networks

动机 先验参数 m , α , β , v m,\alpha,\beta,v m,α,β,v和随机变量 τ \tau τ KL散度的形式是&#xff1a; Dynamics of m , α , β , v m,\alpha,\beta,v m,α,β,v Dynamics of m , β , v m,\beta,v m,β,v for a fixed α \alpha α 绿色轨迹连接初始点和目标点…

【linux】Linux 查看内存使用情况的几种方法汇总

文章目录 GUI 查看命令获取命令 free命令 vmstat命令 top命令 htop Linux 查看内存使用情况的几种方法包括使用 free 命令、top 命令、htop 命令、vmstat 命令和/proc/meminfo 文件。这些方法可以帮助用户了解系统内存的使用情况&#xff0c;包括总内存、已用内存、空闲内存、缓…

MapperStruct实现类为空

​ 问题描述&#xff1a; MapperStruct生成的实现了为空 按照在MapperStruct官网Installation – MapStruct中的方法配置后&#xff0c;生成的实现了是空的&#xff0c;如下&#xff1a; Overridepublic DeployHistory toEntity(DeployHistoryDto arg0) {if ( arg0 null ) …

经典题型---旋转数组

经典题型—旋转数组 文章目录 经典题型---旋转数组一、题目二、代码实现 一、题目 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步…

EPLAN_010#STEP格式_箱柜模型的定义、拼柜

一、导入 首先创建一个宏项目——在布局空间中导航器新建一个布局空间 菜单栏——布局空间——导入(3D图形&#xff09;——导入下载下来的STEP 如果导入进来的箱柜是这种模样的&#xff0c;表示可以使用。如果左侧只显示一个逻辑组件&#xff0c;则无法使用。&#xff08;如果…

webgl计算包围盒大小

使用three.js&#xff1b; 代码&#xff1b; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>第一个three.js 示例</title><style>body {margin: 0;overflow: hidden;}</style><…

canvas绘制动态视频并且在视频上加上自定义logo

实现的效果&#xff1a;可以在画布上播放动态视频&#xff0c;并且加上自定义的图片logo放在视频的右下角 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthd…

Linux---(三)基本指令大全

前提引入&#xff1a;历史上先出现的键盘还是鼠标&#xff1f; 答案&#xff1a;键盘 ✨所以刚开始的时候绝对没有图形化界面&#xff0c;因此操作系统刚开始兴起的时候绝对没有图形化界面&#xff0c;因为当时没有鼠标。 ✨因为没有图形化界面&#xff0c;只有键盘&#xff0c…

【912.排序数组】

目录 一、题目描述二、算法原理2.1快速排序2.2归并排序 三、代码实现3.1快排代码实现3.2归并代码实现 一、题目描述 二、算法原理 2.1快速排序 2.2归并排序 三、代码实现 3.1快排代码实现 class Solution { public:int getRandom(int left,int right,vector<int>&…

CoDeSys系列-2、CoDeSys安装及Windows下创建项目测试

CoDeSys系列-2、CoDeSys安装及Windows下创建项目测试 文章目录 CoDeSys系列-2、CoDeSys安装及Windows下创建项目测试一、前言二、下载及安装三、Windows下软PLC项目创建及运行测试1、创建HMI工程1.1、新建标准工程&#xff1a;1.2、添加可视化对象&#xff1a;1.3、拖动添加拨码…

31二叉树-递归遍历二叉树

目录 LeetCode之路——145. 二叉树的后序遍历 分析 LeetCode之路——94. 二叉树的中序遍历 分析 LeetCode之路——145. 二叉树的后序遍历 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出…

【Java 进阶篇】深入理解 Bootstrap 导航条与分页条

Bootstrap 是一个强大的前端框架&#xff0c;为网页和应用程序开发提供了丰富的组件和工具。其中&#xff0c;导航条和分页条是两个常用的组件&#xff0c;用于创建网站的导航和分页功能。本篇博客将深入探讨 Bootstrap 导航条和分页条的使用&#xff0c;适用于那些希望提升网页…