Node.js案例 - 记账本

目录

项目效果

项目的搭建

​编辑

响应静态网页

​编辑

​编辑

结合MongoDB数据库

结合API接口

进行会话控制


项目效果

该案例实现账单的添加删除查看,用户的登录注册。功能比较简单,但是案例主要是使用前段时间学习的知识进行实现的,主要包括express服务的搭建以及使用,并结合MongoDB数据库对数据进行存储以及操作,同时编写相应的API接口,最后进行会话控制,确保数据的安全。如果以下的某一些部分感觉不太理解的话可以看我之前对应的文章。案例中使用到的知识点都是前面文章有涉及到的。

项目的搭建

首先我们直接使用express-generator来快速地搭建express应用骨架,输入命令行:express -e 文件名 然后使用npm i来进行项目依赖的下载。完成之后可以在package.json中对运行的命令 "start": "node./bin/www" 修改为 "start": "nodemon ./bin/www",这样后续的内容修改服务器就会自动地运行了。接下来运行:npm start,进行服务的启动。服务默认是监听3000端口,我们输入http://127.0.0.1:3000进行访问。出现以下页面即服务搭建成功。

接下来,我们需要对路由规则进行配置,我们可以app.js文件中进行查看,app.use('/', indexRouter);我们可以找到对应的路由导入var indexRouter = require('./routes/index'); 因此我们在routes文件夹下面的index.js文件进行路由的配置。

//index.js

var express = require('express');
var router = express.Router();

// 记账本列表
router.get('/account', function(req, res, next) {
   res.send('账单列表');
});

// 记账本列表添加
router.get('/account/create', function(req, res, next) {
  res.send('添加记录');
});

module.exports = router;

输入不同的路径得到不同的结构:

响应静态网页

我们事先准备好了两个页面,一个为账单页面,一个为添加页面。我们借助res.rend()可以对ejs中的内容响应给浏览器的功能来进行操作。在views文件夹下面创建两个ejs文件。将账单页面以及添加页面加入。

//list.ejs

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
    <style>
      label {
        font-weight: normal;
      }
      .panel-body .glyphicon-remove{
        display: none;
      }
      .panel-body:hover .glyphicon-remove{
        display: inline-block
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>记账本</h2>
          <hr />
          <div class="accounts">
            <div class="panel panel-danger">
              <div class="panel-heading">2023-04-05</div>
              <div class="panel-body">
                <div class="col-xs-6">抽烟只抽煊赫门,一生只爱一个人</div>
                <div class="col-xs-2 text-center">
                  <span class="label label-warning">支出</span>
                </div>
                <div class="col-xs-2 text-right">25 元</div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            <div class="panel panel-success">
              <div class="panel-heading">2023-04-15</div>
              <div class="panel-body">
                <div class="col-xs-6">3 月份发工资</div>
                <div class="col-xs-2 text-center">
                  <span class="label label-success">收入</span>
                </div>
                <div class="col-xs-2 text-right">4396 元</div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
//create.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>添加记录</title>
    <link
      href="/css/bootstrap.css"
      rel="stylesheet"
    />
    <link href="/css/bootstrap-datepicker.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>添加记录</h2>
          <hr />
          <form method="post" action="/account">
            <div class="form-group">
              <label for="item">事项</label>
              <input
                name="title"
                type="text"
                class="form-control"
                id="item"
              />
            </div>
            <div class="form-group">
              <label for="time">发生时间</label>
              <input
                name="time"
                type="text"
                class="form-control"
                id="time"
              />
            </div>
            <div class="form-group">
              <label for="type">类型</label>
              <select name="type" class="form-control" id="type">
                <option value="-1">支出</option>
                <option value="1">收入</option>
              </select>
            </div>
            <div class="form-group">
              <label for="account">金额</label>
              <input
                name="account"
                type="text"
                class="form-control"
                id="account"
              />
            </div>
            
            <div class="form-group">
              <label for="remarks">备注</label>
              <textarea  name="remarks" class="form-control" id="remarks"></textarea>
            </div>
            <hr>
            <button type="submit" class="btn btn-primary btn-block">添加</button>
          </form>
        </div>
      </div>
    </div>
    <script src="/js/jquery.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/bootstrap-datepicker.min.js"></script>
    <script src="/js/bootstrap-datepicker.zh-CN.min.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>

然后我们修改原本的路由,让其输入/account时为账单页表页面,输入/account/create时为列表添加页面。

// 记账本列表
router.get('/account', function(req, res, next) {
  res.render('list');
});

// 记账本列表添加
router.get('/account/create', function(req, res, next) {
  res.render('create');
});

结合MongoDB数据库

接下来我们结合前几篇文章我们学习到的MongoDB数据库来对数据的添加,删除以及读取等操作。如果不懂这一步操作的小伙伴看完前面发的文章。

我们在我们项目中创建三个文件夹:config,db以及models。config文件用于对配置进行统一的设置,在里面单独地设置域名端口以及数据库名等信息。db文件夹中创建db.js,主要进行导入mongoose,连接mongoose服务,设置成功以及失败的回调等信息。module用于创建文档结构对象。

//config.js
module.exports={
    DBHOST:'127.0.0.1',
    DBPORT:27017,
    DBNAME:'bilibili',
    secret:'atguigu'
}
//db.js
module.exports = function (success, error) {
    //导入配置文件
    const {DBHOST,DBPORT,DBNAME}=require('../config/config');

    //导入mongoose
    const mongoose = require('mongoose');
    //连接mongodb服务
    mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);

    mongoose.connection.once('open', () => {
        success();

    })

    //设置连接错误的回调
    mongoose.connection.on('error', () => {
        error();

    });
    //设置连接关闭的回调
    mongoose.connection.on('close', () => {
        console.log('连接关闭');
    });
}

接着再www文件中导入db.js中的函数,在文件中调用函数,传入两个函数,成功的回调以及失败的回调,成功的回调直接写原本启动http服务的代码,确保数据库连接成功之后再启动http服务。失败的回调我们直接输出失败即可。

//www
const db = require('../db/db');
//连接上数据库再来启动http服务
db(()=>{

//原本文件中的代码

}
},()=>{
  console.log("连接失败")
})

接着想要操作数据库,我们需要先准备好模型文件:

//models/AccountModel.js
const mongoose = require('mongoose');
//创建文档结构对象
let AccountSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    time: Date,
    type: {
        type: Number,
        required: true
    },
    account: {
        type: Number,
        required: true
    },
    remarks: {
        type: String
    }
});
//创建文档模型对象
let AccountModel = mongoose.model('accounts', AccountSchema);
//暴露模型对象
module.exports = AccountModel;

模型文件准备好之后,我们在对路由文件,routes/index.js文件来进行操作,在原本的文件中添加新增数据的操作:

//新增记录
router.post('/account', function(req, res, next) {
  AccountModel.create({
    ...req.body,
    time:moment(req.body.time).toDate()
  }).then(data=>{
    res.render('success',{msg:'添加成功~~',url:'/account'});
  }).catch(err=>{
    res.status(500).send('插入失败~~');
  })
});

这里面使用到了一个moment包,主要是用于对日期进行转换为对象形式方便后续的操作。需要在文件中导入const moment=require('moment');

接着我们添加账单,可以使用数据库可视化工具看到自己添加的数据,我使用的是Navicat。新建连接选择Mongo,连接成功之后就可以看到对应的数据库,以及相应的集合。

以上就是我们所添加的数据,但是我们需要在页表页面上也可以看到对应的数据,我们在路由配置文件中进行读取数据的操作。

// 记账本列表
router.get('/account',function(req, res, next) {
  //获取所有账单信息
  AccountModel.find().sort({time:-1}).exec().then(data=>{
    res.render('list',{accounts:data,moment:moment});

  }).catch(err=>{
    res.status(500).send('读取失败~~')
  })
  
});

接着需要修改list.ejs中的代码,方便对数据进行展示:

<!DOCTYPE html>
<html lang="en">
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <div class="row text-right">
            <div class="col-xs-12" style="padding-top: 20px;">
              <form action="/logout" method="post">
                <button class="btn btn-danger">退出</button>
              </form>
            </div>
          </div>
          <hr>
          <div class="row">
            <h2 class="col-xs-6">记账本</h2>
            <h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2>
          </div>
   
          <hr />
          <div class="accounts">
            <% accounts.forEach(item =>{ %>
            <div class="panel <%= item.type=== -1 ? 'panel-danger':'panel-success' %>">
              <div class="panel-heading"><%= moment(item.time).format('YYYY-MM-DD') %></div>
              <div class="panel-body">
                <div class="col-xs-6"><%= item.title %></div>
                <div class="col-xs-2 text-center">
                  <span class="label <%= item.type=== -1 ? 'label-warning':'label-success' %>"><%= item.type=== -1  ? '支出':'收入' %></span>
                </div>
                <div class="col-xs-2 text-right"><%= item.account %> 元</div>
                <div class="col-xs-2 text-right">
                    <a class="delBtn" href="/account/<%= item._id %>">
                       <span
                        class="glyphicon glyphicon-remove"
                        aria-hidden="true"
                      ></span>
                    </a>
                </div>
              </div>
            </div>
            <% }) %>          
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

接着我们来对数据进行删除操作,我们在list.ejs中对叉号绑定一个事件,当用户确定删除时再进行删除数据,防止数据误删。

 <div class="col-xs-2 text-right">
      <a class="delBtn" href="/account/<%= item._id %>">
        <span  class="glyphicon glyphicon-remove" aria-hidden="true"></span>
      </a>
 </div>

  <script>
    let delBtns=document.querySelectorAll('.delBtn');
    delBtns.forEach(item =>{
      item.addEventListener('click',function(e){
        if(confirm('您确定要删除该文档吗?')){
          return true;
        }else{
          e.preventDefault();
        }
      })
    })
  </script>

并在路由中设置删除的操作:

//删除记录
router.get('/account/:id',(req,res)=>{

  //获取params 的 id参数
  let id=req.params.id;
  //删除
  AccountModel.deleteOne({_id:id}).then(data=>{
    res.render('success',{msg:'删除成功~~',url:'/account'});
  }).catch(err=>{
    res.status(500).send('删除失败~~')
  })

});

结合API接口

当我们需要将项目推广到更多的客户端程序时,而不仅仅是局限在我们的浏览器进行访问时,我们就需要为它添加对应的API接口,同样的前几篇文章也有介绍了API接口的详细内容。如果不是太了解的小伙伴可以回头去看看。

为了更好地区分,我们在routes文件夹下创建一个名为web的文件,将原本的路由配置文件放入其中,再创建一个名为api的文件夹,创建一个名为account.js的文件用于存放API路由的配置。文件路径修改之后需要修改引入该文件的路径。并在app.js中导入并使用:

const accountRouter=require('./routes/api/account');

app.use('/api',accountRouter);

在api的路由文件中实现创建账单接口、删除账单接口、获取单条数据接口以及更新账单接口。对应代码如下:

// /api/account.js
const express = require('express');
const jwt = require('jsonwebtoken');
//导入moment
const moment = require('moment');
const AccountModel = require('../../models/AccountModel');
//导入中间件
let checkTokenMiddleware = require('../../middlewares/checkTokenMiddleware')
const router = express.Router();

// 记账本列表
router.get('/account', checkTokenMiddleware,function (req, res, next) {
    AccountModel.find().sort({ time: -1 }).exec().then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '读取成功',
            //响应数据
            data: data
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1001',
            //响应信息
            msg: '读取失败',
            //响应数据
            data: null
        })
    })

});

// 记账本列表添加
router.get('/account/create',checkTokenMiddleware, function (req, res, next) {
    res.render('create');
});

//新增记录
router.post('/account', checkTokenMiddleware,function (req, res) {

    AccountModel.create({
        ...req.body,
        time: moment(req.body.time).toDate()
    }).then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '创建成功',
            //响应数据
            data: data
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1002',
            //响应信息
            msg: '创建失败',
            //响应数据
            data: null
        })
    })

});

//删除记录
router.delete('/account/:id', checkTokenMiddleware,(req, res) => {
    //获取params 的 id参数
    let id = req.params.id;
    //删除
    AccountModel.deleteOne({ _id: id }).then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '删除成功',
            //响应数据
            data: {}
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1003',
            //响应信息
            msg: '删除失败',
            //响应数据
            data: null
        })
    })
});
//获取当个账单信息
router.get('/account/:id', checkTokenMiddleware,(req, res) => {
    //获取params 的 id参数
    let id = req.params.id;
    //查询数据库
    AccountModel.findById(id).then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '读取成功',
            //响应数据
            data: data
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1004',
            //响应信息
            msg: '读取失败',
            //响应数据
            data: null
        })
    })
});

//更新单个账单信息
router.patch('/account/:id', checkTokenMiddleware,(req, res) => {
    //获取params 的 id参数
    let id = req.params.id;
    AccountModel.updateOne({ _id: id }, req.body).then(data => {
        //再次查询数据库
        AccountModel.findById(id).then(data => {
            res.json({
                //响应码 
                code: '0000',
                //响应信息
                msg: '更新成功',
                //响应数据
                data: data
            }).catch(err => {
                res.json({
                    //响应码 
                    code: '1004',
                    //响应信息
                    msg: '读取失败',
                    //响应数据
                    data: null
                })
            })
        }).catch(err => {
            res.json({
                //响应码 
                code: '1005',
                //响应信息
                msg: '更新失败',
                //响应数据
                data: null
            })

        })
    });

})
module.exports = router;

那如何对我们写好的接口做测试呢,在前面的文章中,介绍了Apipost软件来测试接口,我们来尝试一下,发一个GET请求来获取表单的信息数据。成功得到对应的数据。

进行会话控制

接下来我们使用我们学过的session以及token来对数据进行保护。具体的知识点可以看我前几篇发的文章。

接下来,我们为项目添加一个注册的页面,在routes中web文件夹下创建一个auth.js文件,用户配置注册以及登录时的session相关信息。我们同样使用模板引擎来响应注册页面。将该创建好的路由文件在app.js中进行导入以及使用。

创建一个reg.ejs文件:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>注册</title>
  <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4">
        <h2>注册</h2>
        <hr />
        <form method="post" action="/reg">
          <div class="form-group">
            <label for="item">用户名</label>
            <input name="username" type="text" class="form-control" id="item" />
          </div>
          <div class="form-group">
            <label for="time">密码</label>
            <input name="password" type="password" class="form-control" id="time" />
          </div>
          <hr>
          <button type="submit" class="btn btn-primary btn-block">注册</button>
        </form>
      </div>
    </div>
  </div>
</body>

</html>

在响应注册页面

//注册
router.get('/reg',(req,res)=>{
    res.render('auth/reg');
});

我们需要先创建用户模型,后续才能对其进行插入数据库以及设置session等操作。

const mongoose = require('mongoose');
//创建文档结构对象
let UserSchema = new mongoose.Schema({
    username:String,
    password:String

});
//创建文档模型对象
let UserModel = mongoose.model('users', UserSchema);
//暴露模型对象
module.exports = UserModel;

将该模型导入到对应的配置文件中,并进行注册等相关操作:

/导入用户模型
const UserModel=require('../../models/UserModel');
const md5=require('md5');

//注册用户
router.post('/reg',(req,res)=>{
    UserModel.create({...req.body,password:md5(req.body.password)}).then(data=>{
        res.render('success',{msg:'注册成功',url:'/login'});

    }).catch(err=>{
        res.status(500).send('注册失败')
    })
});

接下来实现用户登录功能,我们同样创建一个login.ejs文件放置登录页面模板,复制注册页面的代码,将对应的文字以及路径修改一下即可。这部分不进行代码展示。进行登录的相关操作,当用户登录之后,我们需要对他的session进行写入,并返回sessionid。

//登录
router.get('/login',(req,res)=>{
    res.render('auth/login');
});
//登录操作
router.post('/login',(req,res)=>{
    //获取用户名和密码
    let {username,password}=req.body;
    UserModel.findOne({username:username,password:md5(password)}).then(data=>{
        if(!data){
            return res.send('账号或者密码错误~~')
        }
        //写入session
        req.session.username=data.username;
        req.session._id=data._id;
        //登录成功响应
        res.render('success',{msg:'登录成功',url:'/account'});

    }).catch(err=>{
        res.status(500).send('登录失败')
    })
});

这部分需要先安装express-session以及connect-mongo,并在app.js中进行导入,并设置中间件。

//导入 express-session 
const session = require("express-session");
const MongoStore = require('connect-mongo');

//导入配置项
const {DBHOST, DBPORT, DBNAME} = require('./config/config');
//设置 session 的中间件
app.use(session({
  name: 'sid',   //设置cookie的name,默认值是:connect.sid
  secret: 'atguigu', //参与加密的字符串(又称签名)  加盐
  saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
  resave: true,  //是否在每次请求时重新保存session  20 分钟    4:00  4:20
  store: MongoStore.create({
    mongoUrl: `mongodb://${DBHOST}:${DBPORT}/${DBNAME}` //数据库的连接配置
  }),
  cookie: {
    httpOnly: true, // 开启后前端无法通过 JS 操作
    maxAge: 1000 * 60 * 60 * 24 * 7 // 这一条 是控制 sessionID 的过期时间的!!!
  },
}))

当我们登录成功之后我们可以在数据库中看到我们对应的session信息。

写入之后,我们还需要判断用户是否登录,若用户没有进行登录则拒绝访问,跳转到登录页面。我们在web文件夹下的index.js中编写一个中间件。

//检测登录的中间件
const checkLoginMiddleware = (req, res, next) => {
    //判断
    if(!req.session.username){
      return res.redirect('/login');
    }
    next();
  }

并在下面的路由规则中使用它。这里只对查看记账本列表做演示,其他的一致。

// 记账本列表
router.get('/account',checkLoginMiddleware,function(req, res, next) {
  AccountModel.find().sort({time:-1}).exec().then(data=>{
    res.render('list',{accounts:data,moment:moment});

  }).catch(err=>{
    res.status(500).send('读取失败~~')
  })
  
});

接下来继续在auth.js中实现退出登录功能。

//退出登录
router.post('/logout',(req,res)=>{
    //销毁session
    req.session.destroy(()=>{
        res.render('success',{msg:'退出成功',url:'/login'})
    })
})

在退出登录界面,部分进行修改,防止CSRF跨站请求伪造。它会导致用户的session被获取。大部分的CSRF跨站请求伪造都是使用一个天生具有跨域能力的标签。但是它们发送的请求都是get请求,因此我们将原本的退出修改为post请求。可以防止发生。

<div class="col-xs-12" style="padding-top: 20px;">
   <form action="/logout" method="post">
        <button class="btn btn-danger">退出</button>
   </form>
</div>

我们接着在web文件夹下的index.js对首页添加路由规则:

//添加首页路由规则
router.get('/',(req,res)=>{
  //重定向
  res.redirect('/account');
});

在app.js中添加404的响应的模板,在views中创建一个404.ejs文件。

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  res.render('404');
});

以上的操作,我们使用了session对网页端进行了约束,接下来我们使用token来对接口来进行约束。在api文件夹下创建一个auth.js文件对其进行设置。这部分就不再详细介绍了,文件相应的代码如下:

var express = require('express');
var router = express.Router();
//导入jwt
const jwt=require('jsonwebtoken');
//读取配置项
const {secret} =require('../../config/config');
//导入用户模型
const UserModel=require('../../models/UserModel');
const md5=require('md5');


//登录操作
router.post('/login',(req,res)=>{
    //获取用户名和密码
    let {username,password}=req.body;
    UserModel.findOne({username:username,password:md5(password)}).then(data=>{
        if(!data){
            return res.json({
                code:'2002',
                msg:'用户名或者密码错误',
                data:null
            })
        }
        //创建当前用户token
        let token=jwt.sign({
            username:data.username,
            _id:data._id
        },secret,{
            expiresIn:60 * 60 * 24 *7
        });
        //响应token
        res.json({
            code:'0000',
            msg:'登录成功',
            data:token
        })
        

    }).catch(err=>{
        res.json({
            code:'2001',
            msg:'数据库读取失败',
            data:null
        })
    })
});

//退出登录
router.post('/logout',(req,res)=>{
    //销毁session
    req.session.destroy(()=>{
        res.render('success',{msg:'退出成功',url:'/login'})
    })
})


module.exports = router;

中间件文件:

//checkTokenMiddleware.js
const jwt=require('jsonwebtoken');
//读取配置项
const {secret} =require('../config/config');
module.exports = (req, res, next) => {
    //获取token
    let token = req.get('token');
    //判断
    if (!token) {
        return res.json({
            code: '2003',
            msg: 'token 缺失',
            data: null
        })
    }
    //校验token
    jwt.verify(token, secret, (err, data) => {
        if (err) {
            return res.json({
                code: '2004',
                msg: '校验失败',
                data: null
            })
        }
         //保存用户的信息
         req.user=data;
        //如果执行成功
        next();

    });

}

通过Apipost来进行校验,当没有携带token时,获取不到数据,当设置请求头token数据时,能够获取到对应的数据。

好啦!本文就到这里了,Node.js系列的文章就告一段落了!如果有不足之处还请见谅~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解原生Spring当中的额外功能开发MethodBeforeAdvice与MethodInterceptor接口!

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

软件测试测试文档的编写和阅读

在软件测试中的流程中&#xff0c;测试文档也是一个重要的流程&#xff0c;所以测试人员也需要学习测试文档的编写和阅读。 一、定义&#xff1a; 测试文档&#xff08;Testing Documentation&#xff09;记录和描述了整个测试流程&#xff0c;它是整个测试活动中非常重要的文…

layui提示框没有渲染bug解决

bug&#xff1a;使用layui时或许是依赖导入又或是ideal和浏览器缓存问题导致前面明明正常的页面显示&#xff0c;后面出现提示框没有css样式&#xff0c;弹出框没有背景css 效果如下 解决后 解决方法 在你的代码中引入layer.js 我这是jsp页面 <script type"text/jav…

idea方法注释模版设置

方法上面的注释模版&#xff1a; Template text: ** Description $desc$ $param$ $return$* Aauthor yimeng* date $DATE$ $TIME$ **/param&#xff1a; groovyScript("def result ;def params \"${_1}\".replaceAll([\\\\[|\\\\]|\\\\s], ).split(,).toLis…

机器学习笔记 - 3D数据的常见表示方式

一、简述 从单一角度而自动合成3D数据是人类视觉和大脑的基本功能,这对计算机视觉算法来说是比较难的。但随着LiDAR、RGB-D 相机(RealSense、Kinect)和3D扫描仪等3D传感器的普及和价格的降低,3D 采集技术的最新进展取得了巨大飞跃。与广泛使用的 2D 数据不同,3D 数据具有丰…

Opencv 极坐标变换

变换后图片 代码 // 以Center为极坐标原点&#xff0c;将RowFrom到RowTo的圆环&#xff0c;仅仅变换该范围内的点&#xff0c;忽略掉其他部分。 #include "polar_transeforme.hpp" #include <string>using namespace cv;void calculate_map(int rouFrom, int …

Matlab 在一个文件中调用另一个文件中的函数

文章目录 Part.I IntroductionPart.II 方法Chap.I A 文件中只有一个函数Chap.II A 文件中有多个函数 Part.I Introduction 本文介绍一下在脚本文件 B 中调用文件 A 中的函数的方法。 Part.II 方法 目的&#xff1a;在文件B.m调用A.m中的函数 默认两个文件在一个文件夹下&…

JSch线上出现com.jcraft.jsch.JSchException: channel is not opened.问题分析

JSch线上出现com.jcraft.jsch.JSchException: channel is not opened.问题分析 文章目录 JSch线上出现com.jcraft.jsch.JSchException: channel is not opened.问题分析1. 背景1.系统使用jsch这个框架做文件发送以及远程命令执行的操作,系统一直运行正常,直到某一个环境发现 2.…

陪诊系统:基于自然语言处理的患者沟通创新

医疗领域的数字化转型正日益引入创新技术&#xff0c;其中基于自然语言处理&#xff08;NLP&#xff09;的陪诊系统成为提升患者沟通的一项关键技术。本文将深入研究这一领域&#xff0c;介绍陪诊系统如何借助NLP实现患者沟通的创新&#xff0c;并提供一个简单的Python代码示例…

Excel导入组件的封装以及使用页面点击弹出该弹框

封装的组件 <template><el-dialogwidth"500px"title"员工导入":visible"showExcelDialog"close"$emit(update:showExcelDialog, false)"><el-row type"flex" justify"center"><div class&q…

如何跑通跨窗口渲染:multipleWindow3dScene

New 这是一个跨窗口渲染的示例&#xff0c;用 Three.js 和 localStorage 在同一源&#xff08;同产品窗口&#xff09;上跨窗口设置 3D 场景。而这也是本周推特和前端圈的一个热点&#xff0c;有不少人在争相模仿它的实现&#xff0c;如果你对跨窗口的渲染有兴趣&#xff0c;可…

Rust之构建命令行程序(一):接受命令行参数

开发环境 Windows 10Rust 1.73.0 VS Code 1.84.2 项目工程 这次创建了新的工程minigrep. IO工程&#xff1a;构建命令行程序 这一章回顾了到目前为止你所学的许多技能&#xff0c;并探索了一些更标准的库特性。我们将构建一个与文件和命令行输入/输出交互的命令行工具&#…

微信小程序——给按钮添加点击音效

今天来讲解一下如何给微信小程序的按钮添加点击音效 注意&#xff1a;这里的按钮不一定只是 <button>&#xff0c;也可以是一张图片&#xff0c;其实只是添加一个监听点击事件的函数而已 首先来看下按钮的定义 <button bind:tap"onInput" >点我有音效&…

PlantUML语法(全)及使用教程-用例图

目录 1. 用例图1.1、什么是用例图1.2、用例图的构成1.3、参与者1.4、用例1.4.1、用例基本概念1.4.2、用例的识别1.4.3、用例的要点1.4.3、用例的命名1.4.4、用例的粒度 1.5、应用示例1.5.1、用例1.5.2、角色1.5.3、改变角色的样式1.5.4、用例描述1.5.5、改变箭头方向1.5.6、使用…

市场调研:智能音响市场分析与前景预测

信息技术的不断发展下我国智能音响产品逐渐出现在大众眼前&#xff0c;并且市场普及率也在不断提高。智能化成为各行业发展的新趋势&#xff0c;受到疫情的影响目前智能音响市场增速有所放缓。 智能音响又称为智能音箱是音箱升级的产物&#xff0c;是家庭消费者用语音进行上网的…

npm上传发布自定义组件超详细流程

前言 vue3&#xff0c;vite&#xff0c;基于element Plus 的el-table二次封装表格并且上传到npm上&#xff0c;让别人可以通过npm安装你的插件。 一、创建一个新的vue 项目 npm create vuelatest 自己取一个名字&#xff0c;然后一直回车 完成以后进入项目npm i,有用到eleme…

Android 10.0 Launcher3定制之首页时钟小部件字体大小的修改

1.前言 在10.0的产品开发中,在一些Launcher3的定制化开发中,在对于一些小屏幕的产品开发中,在首页添加时钟小部件会显得字体有点小, 所以为了整体布局美观就需要改动小部件的布局日期字体的大小来实现整体的布局美观效果,接下来来具体实现相关的功能 具体效果图: 2.Lau…

html-video:计算视频是否完整播放 / 计算视频完播率

一、video 播放视频 <video width"100%"id"myVideo"object-fit"fill":autoplay"true":loop"false":enable-auto-rotation"true":enable-play-gesture"true":src"videoInfo.videoUrl":pos…

Ubuntu 22.04 LTS 上 安装 Redis

Ubuntu 22.04 LTS 上的Redis安装指南 Redis是一种开源的内存数据存储&#xff0c;可以用作数据库、缓存和消息代理等。本文将会介绍两种不同的安装方式&#xff0c;包括从源代码编译安装以及通过apt包管理器安装。 一、从源代码编译安装Redis 首先&#xff0c;我们需要下载最…

MacOS 安装 Android Studio 通过 WIFI 无线真机调试

环境&#xff1a;Apple M1 MacOS Sonoma 14.1.1 软件&#xff1a;Android Studio Giraffe | 2022.3.1 Patch 3 设备&#xff1a;小米10 Android 13 一、创建测试项目 安卓 HelloWorld 项目: 安卓 HelloWorld 项目 二、Android Studio 与手机配对 1. 手机开启开发者模式 参考…