如何构建一个 NodeJS 影院微服务并使用 Docker 部署

news2024/11/26 1:51:45

在这里插入图片描述

文章目录

    • 前言
    • 什么是微服务?
    • 构建电影目录微服务
    • 构建微服务
    • 从 NodeJS 连接到 MongoDB 数据库
    • 总结

前言

如何构建一个 NodeJS 影院微服务并使用 Docker 部署。在这个系列中,将构建一个 NodeJS 微服务,并使用 Docker Swarm 集群进行部署。

以下是将要使用的工具:

  • NodeJS 版本7.2.0
  • MongoDB 3.4.1
  • Docker for Mac 1.12.6

在尝试本指南之前,应该具备:

  • NodeJS 的基本知识
  • Docker 的基本知识(并且已经安装了 Docker)
  • MongoDB 的基本知识(并且数据库服务正在运行)

什么是微服务?

微服务是一个单独的自包含单元,与其他许多单元一起构成一个大型应用程序。通过将应用程序拆分为小单元,每个部分都可以独立部署和扩展,可以由不同的团队和不同的编程语言编写,并且可以单独进行测试。

微服务架构意味着应用程序由许多较小的、独立的应用程序组成,这些应用程序能够在自己的内存空间中运行,并且可以在可能的多个独立计算机上独立扩展。

微服务的好处:

  • 应用程序启动更快,这使得开发人员更具生产力,并加快了部署速度。
  • 每个服务可以独立于其他服务部署 — 更容易频繁部署服务的新版本。
  • 更容易扩展开发,也可能具有性能优势。
  • 消除对技术栈的长期承诺。在开发新服务时,可以选择新的技术栈。
  • 微服务通常更好组织,因为每个微服务有一个非常具体的工作,不涉及其他组件的工作。
  • 解耦的服务也更容易重新组合和重新配置,以服务不同应用程序的目的(例如,同时为 Web 客户端和公共 API 提供服务)。

微服务的缺点:

  • 开发人员必须处理创建分布式系统的额外复杂性。
  • 部署复杂性。在生产环境中,部署和管理许多不同服务类型的系统也会带来操作复杂性。
  • 在构建新的微服务架构时,可能会发现许多交叉关注点,这些交叉关注点在设计时没有预料到。

构建电影目录微服务

假设正在一家电影院的 IT 部门工作,给了我们一个任务,将他们的电影票务和杂货店从单体系统重构为微服务。

因此,在“构建 NodeJS 电影目录微服务”系列中,将仅关注电影目录服务。

在这个架构中,可以看到有 3 种不同的设备使用该微服务,即 POS(销售点)、移动设备/平板电脑和计算机。POS 和移动设备/平板电脑都有自己的应用程序(在 electron 中开发),并直接使用微服务,而计算机则通过 Web 应用程序访问微服务(一些专家也将 Web 应用程序视为微服务)。

构建微服务

现在,来模拟在最喜欢的电影院预订一场电影首映的过程。

首先,想看看电影院目前正在上映哪些电影。以下图表显示了通过 REST 进行的内部通信,通过此 REST 通信,可以使用 API 来获取目前正在上映的电影。

电影服务的 API 将具有以下 RAML 规范:

#%RAML 1.0
title: cinema
version: v1
baseUri: /

types:
  Movie:
    properties:
      id: string
      title: string
      runtime: number
      format: string
      plot: string
      releaseYear: number
      releaseMonth: number
      releaseDay: number
    example:
      id: "123"
      title: "Assasins Creed"
      runtime: 115
      format: "IMAX"
      plot: "Lorem ipsum dolor sit amet"
      releaseYear : 2017
      releaseMonth: 1
      releaseDay: 6

  MoviePremieres:
    type: Movie []


resourceTypes:
  Collection:
    get:
      responses:
        200:
          body:
            application/json:
              type: <<item>>

/movies:
  /premieres:
    type:  { Collection: {item : MoviePremieres } }

  /{id}:
    type:  { Collection: {item : Movie } }

如果不了解 RAML,可以查看这个很好的教程。

API 项目的结构将如下所示:

  • api/ # 我们的API
  • config/ # 应用程序配置
  • mock/ # 不是必需的,仅用于数据示例
  • repository/ # 抽象出数据库
  • server/ # 服务器设置代码
  • package.json # 依赖项
  • index.js # 应用程序的主入口

首先要看的部分是 repository。这是对数据库进行查询的地方。


'use strict'
// factory function, that holds an open connection to the db,
// and exposes some functions for accessing the data.
const repository = (db) => {
  
  // since this is the movies-service, we already know
  // that we are going to query the `movies` collection
  // in all of our functions.
  const collection = db.collection('movies')

  const getMoviePremiers = () => {
    return new Promise((resolve, reject) => {
      const movies = []
      const currentDay = new Date()
      const query = {
        releaseYear: {
          $gt: currentDay.getFullYear() - 1,
          $lte: currentDay.getFullYear()
        },
        releaseMonth: {
          $gte: currentDay.getMonth() + 1,
          $lte: currentDay.getMonth() + 2
        },
        releaseDay: {
          $lte: currentDay.getDate()
        }
      }
      const cursor = collection.find(query)
      const addMovie = (movie) => {
        movies.push(movie)
      }
      const sendMovies = (err) => {
        if (err) {
          reject(new Error('An error occured fetching all movies, err:' + err))
        }
        resolve(movies)
      }
      cursor.forEach(addMovie, sendMovies)
    })
  }

  const getMovieById = (id) => {
    return new Promise((resolve, reject) => {
      const projection = { _id: 0, id: 1, title: 1, format: 1 }
      const sendMovie = (err, movie) => {
        if (err) {
          reject(new Error(`An error occured fetching a movie with id: ${id}, err: ${err}`))
        }
        resolve(movie)
      }
      // fetch a movie by id -- mongodb syntax
      collection.findOne({id: id}, projection, sendMovie)
    })
  }
  
  // this will close the database connection
  const disconnect = () => {
    db.close()
  }

  return Object.create({
    getAllMovies,
    getMoviePremiers,
    getMovieById,
    disconnect
  })
}

const connect = (connection) => {
  return new Promise((resolve, reject) => {
    if (!connection) {
      reject(new Error('connection db not supplied!'))
    }
    resolve(repository(connection))
  })
}
// this only exports a connected repo
module.exports = Object.assign({}, {connect})

可能已经注意到,向 repository 的 connect ( connection ) 方法提供了一个 connection 对象。在这里,使用了 JavaScript 的一个重要特性“闭包”,repository 对象返回了一个闭包,其中的每个函数都可以访问 db 对象和 collection 对象,db 对象保存着数据库连接。在这里,抽象了连接的数据库类型,repository 对象不知道数据库是什么类型的,对于这种情况来说,是一个 MongoDB 连接。甚至不需要知道是单个数据库还是复制集连接。虽然使用了 MongoDB 语法,但可以通过应用 SOLID 原则中的依赖反转原则,将存储库功能抽象得更深,将 MongoDB 语法转移到另一个文件中,并只调用数据库操作的接口(例如,使用 mongoose 模型)。

有一个 repository/repository.spec.js 文件来测试这个模块,稍后在文章中会谈到测试。

接下来要看的是 server.js 文件。

'use strict'
const express = require('express')
const morgan = require('morgan')
const helmet = require('helmet')
const movieAPI = require('../api/movies')

const start = (options) => {
  return new Promise((resolve, reject) => {
    // we need to verify if we have a repository added and a server port
    if (!options.repo) {
      reject(new Error('The server must be started with a connected repository'))
    }
    if (!options.port) {
      reject(new Error('The server must be started with an available port'))
    }
    // let's init a express app, and add some middlewares
    const app = express()
    app.use(morgan('dev'))
    app.use(helmet())
    app.use((err, req, res, next) => {
      reject(new Error('Something went wrong!, err:' + err))
      res.status(500).send('Something went wrong!')
    })
    
    // we add our API's to the express app
    movieAPI(app, options)
    
    // finally we start the server, and return the newly created server 
    const server = app.listen(options.port, () => resolve(server))
  })
}

module.exports = Object.assign({}, {start})

在这里,创建了一个新的 express 应用程序,验证是否提供了 repository 和 server port 对象,然后为 express 应用程序应用一些中间件,例如用于日志记录的 morgan,用于安全性的 helmet,以及一个错误处理函数,最后导出一个 start 函数来启动服务器。

Helmet 包含了整整 11 个软件包,它们都用于阻止恶意方破坏或使用应用程序来伤害其用户。

好的,现在既然服务器使用了电影的 API,继续查看 movies.js 文件。

'use strict'
const status = require('http-status')

module.exports = (app, options) => {
  const {repo} = options
  
  // here we get all the movies 
  app.get('/movies', (req, res, next) => {
    repo.getAllMovies().then(movies => {
      res.status(status.OK).json(movies)
    }).catch(next)
  })
  
  // here we retrieve only the premieres
  app.get('/movies/premieres', (req, res, next) => {
    repo.getMoviePremiers().then(movies => {
      res.status(status.OK).json(movies)
    }).catch(next)
  })
  
  // here we get a movie by id
  app.get('/movies/:id', (req, res, next) => {
    repo.getMovieById(req.params.id).then(movie => {
      res.status(status.OK).json(movie)
    }).catch(next)
  })
}

在这里,为API创建了路由,并根据监听的路由调用了 repo 函数。repo 在这里使用了接口技术方法,在这里使用了著名的“为接口编码而不是为实现编码”,因为 express 路由不知道是否有一个数据库对象、数据库查询逻辑等,它只调用处理所有数据库问题的 repo 函数。

所有文件都有与源代码相邻的单元测试,看看 movies.js 的测试是如何进行的。

可以将测试看作是对正在构建的应用程序的安全保障。不仅会在本地机器上运行,还会在 CI 服务上运行,以确保失败的构建不会被推送到生产系统。

为了编写单元测试,必须对所有依赖项进行存根,即为模块提供虚拟依赖项。看看 spec 文件。

/* eslint-env mocha */
const request = require('supertest')
const server = require('../server/server')

describe('Movies API', () => {
  let app = null
  let testMovies = [{
    'id': '3',
    'title': 'xXx: Reactivado',
    'format': 'IMAX',
    'releaseYear': 2017,
    'releaseMonth': 1,
    'releaseDay': 20
  }, {
    'id': '4',
    'title': 'Resident Evil: Capitulo Final',
    'format': 'IMAX',
    'releaseYear': 2017,
    'releaseMonth': 1,
    'releaseDay': 27
  }, {
    'id': '1',
    'title': 'Assasins Creed',
    'format': 'IMAX',
    'releaseYear': 2017,
    'releaseMonth': 1,
    'releaseDay': 6
  }]

  let testRepo = {
    getAllMovies () {
      return Promise.resolve(testMovies)
    },
    getMoviePremiers () {
      return Promise.resolve(testMovies.filter(movie => movie.releaseYear === 2017))
    },
    getMovieById (id) {
      return Promise.resolve(testMovies.find(movie => movie.id === id))
    }
  }

  beforeEach(() => {
    return server.start({
      port: 3000,
      repo: testRepo
    }).then(serv => {
      app = serv
    })
  })

  afterEach(() => {
    app.close()
    app = null
  })

  it('can return all movies', (done) => {
    request(app)
      .get('/movies')
      .expect((res) => {
        res.body.should.containEql({
          'id': '1',
          'title': 'Assasins Creed',
          'format': 'IMAX',
          'releaseYear': 2017,
          'releaseMonth': 1,
          'releaseDay': 6
        })
      })
      .expect(200, done)
  })

  it('can get movie premiers', (done) => {
    request(app)
    .get('/movies/premiers')
    .expect((res) => {
      res.body.should.containEql({
        'id': '1',
        'title': 'Assasins Creed',
        'format': 'IMAX',
        'releaseYear': 2017,
        'releaseMonth': 1,
        'releaseDay': 6
      })
    })
    .expect(200, done)
  })

  it('returns 200 for an known movie', (done) => {
    request(app)
      .get('/movies/1')
      .expect((res) => {
        res.body.should.containEql({
          'id': '1',
          'title': 'Assasins Creed',
          'format': 'IMAX',
          'releaseYear': 2017,
          'releaseMonth': 1,
          'releaseDay': 6
        })
      })
      .expect(200, done)
  })
})
/* eslint-env mocha */
const server = require('./server')

describe('Server', () => {
  it('should require a port to start', () => {
    return server.start({
      repo: {}
    }).should.be.rejectedWith(/port/)
  })

  it('should require a repository to start', () => {
    return server.start({
      port: {}
    }).should.be.rejectedWith(/repository/)
  })
})

可以看到,为 movies API 存根了依赖项,并验证了需要提供一个 server port 和一个 repository 对象。

继续看一下如何创建传递给 repository 模块的 db 连接对象,现在定义说每个微服务都必须有自己的数据库,但是对于示例,将使用一个 MongoDB 复制集服务器,但每个微服务都有自己的数据库。

从 NodeJS 连接到 MongoDB 数据库

以下是需要从 NodeJS 连接到 MongoDB 数据库的配置。

const MongoClient = require('mongodb')

// here we create the url connection string that the driver needs
const getMongoURL = (options) => {
  const url = options.servers
    .reduce((prev, cur) => prev + `${cur.ip}:${cur.port},`, 'mongodb://')

  return `${url.substr(0, url.length - 1)}/${options.db}`
}

// mongoDB function to connect, open and authenticate
const connect = (options, mediator) => {
  mediator.once('boot.ready', () => {
    MongoClient.connect( getMongoURL(options), {
        db: options.dbParameters(),
        server: options.serverParameters(),
        replset: options.replsetParameters(options.repl)
      }, (err, db) => {
        if (err) {
          mediator.emit('db.error', err)
        }

        db.admin().authenticate(options.user, options.pass, (err, result) => {
          if (err) {
            mediator.emit('db.error', err)
          }
          mediator.emit('db.ready', db)
        })
      })
  })
}

module.exports = Object.assign({}, {connect})

这里可能有更好的方法,但基本上可以这样创建与 MongoDB 的复制集连接。

传递了一个 options 对象,其中包含 Mongo 连接所需的所有参数,并且传递了一个事件 — 中介者对象,当通过认证过程时,它将发出 db 对象。

注意 在这里,使用了一个事件发射器对象,因为使用 promise 的方法在某种程度上并没有在通过认证后返回 db 对象,顺序变得空闲。所以这可能是一个很好的挑战,看看发生了什么,并尝试使用 promise 的方法。

现在,既然正在传递一个 options 对象来进行参数设置,让我们看看这是从哪里来的,因此要查看的下一个文件是 config.js。

// simple configuration file

// database parameters
const dbSettings = {
  db: process.env.DB || 'movies',
  user: process.env.DB_USER || 'cristian',
  pass: process.env.DB_PASS || 'cristianPassword2017',
  repl: process.env.DB_REPLS || 'rs1',
  servers: (process.env.DB_SERVERS) ? process.env.DB_SERVERS.split(' ') : [
    '192.168.99.100:27017',
    '192.168.99.101:27017',
    '192.168.99.102:27017'
  ],
  dbParameters: () => ({
    w: 'majority',
    wtimeout: 10000,
    j: true,
    readPreference: 'ReadPreference.SECONDARY_PREFERRED',
    native_parser: false
  }),
  serverParameters: () => ({
    autoReconnect: true,
    poolSize: 10,
    socketoptions: {
      keepAlive: 300,
      connectTimeoutMS: 30000,
      socketTimeoutMS: 30000
    }
  }),
  replsetParameters: (replset = 'rs1') => ({
    replicaSet: replset,
    ha: true,
    haInterval: 10000,
    poolSize: 10,
    socketoptions: {
      keepAlive: 300,
      connectTimeoutMS: 30000,
      socketTimeoutMS: 30000
    }
  })
}

// server parameters
const serverSettings = {
  port: process.env.PORT || 3000
}

module.exports = Object.assign({}, { dbSettings, serverSettings })

这是配置文件,大部分配置代码都是硬编码的,但正如看到的,一些属性使用环境变量作为选项。环境变量被视为最佳实践,因为这可以隐藏数据库凭据、服务器参数等。

最后,对于构建电影服务 API 的最后一步是使用 index.js 将所有内容组合在一起。

'use strict'
// we load all the depencies we need
const {EventEmitter} = require('events')
const server = require('./server/server')
const repository = require('./repository/repository')
const config = require('./config/')
const mediator = new EventEmitter()

// verbose logging when we are starting the server
console.log('--- Movies Service ---')
console.log('Connecting to movies repository...')

// log unhandled execpetions
process.on('uncaughtException', (err) => {
  console.error('Unhandled Exception', err)
})
process.on('uncaughtRejection', (err, promise) => {
  console.error('Unhandled Rejection', err)
})

// event listener when the repository has been connected
mediator.on('db.ready', (db) => {
  let rep
  repository.connect(db)
    .then(repo => {
      console.log('Repository Connected. Starting Server')
      rep = repo
      return server.start({
        port: config.serverSettings.port,
        repo
      })
    })
    .then(app => {
      console.log(`Server started succesfully, running on port: ${config.serverSettings.port}.`)
      app.on('close', () => {
        rep.disconnect()
      })
    })
})
mediator.on('db.error', (err) => {
  console.error(err)
})

// we load the connection to the repository
config.db.connect(config.dbSettings, mediator)
// init the repository connection, and the event listener will handle the rest
mediator.emit('boot.ready')

在这里,组合了所有的电影 API 服务,添加了一些错误处理,然后加载配置、启动存储库,并最后启动服务器。

因此,到目前为止,已经完成了与 API 开发相关的所有内容。

下面是项目中需要用到的初始化以及运行命令:

  • npm install # 设置Node依赖项
  • npm test # 使用mocha进行单元测试
  • npm start # 启动服务
  • npm run node-debug # 以调试模式运行服务器
  • npm run chrome-debug # 使用chrome调试Node
  • npm run lint # 使用standard进行代码lint

最后,第一个微服务已经在本地运行,并通过执行 npm start 命令启动。

现在是时候将其放入 Docker 容器中。

首先,需要使用“使用 Docker 部署 MongoDB 复制集”的文章中的 Docker 环境,如果没有,则需要进行一些额外的修改步骤,以便为微服务设置数据库,以下是一些命令,进行测试电影服务。

首先创建 Dockerfile,将 NodeJS 微服务制作成 Docker 容器。

# Node v7作为基本映像以支持ES6
FROM node:7.2.0
# 为新容器创建一个新用户,并避免root用户
RUN useradd --user-group --create-home --shell /bin/false nupp && \
    apt-get clean
ENV HOME=/home/nupp
COPY package.json npm-shrinkwrap.json $HOME/app/
COPY src/ $HOME/app/src
RUN chown -R nupp:nupp $HOME/* /usr/local/
WORKDIR $HOME/app
RUN npm cache clean && \
    npm install --silent --progress=false --production
RUN chown -R nupp:nupp $HOME/*
USER nupp
EXPOSE 3000
CMD ["npm", "start"]

使用 NodeJS 镜像作为 Docker 镜像的基础,然后为镜像创建一个用户以避免非 root 用户,接下来,将 src 复制到镜像中,然后安装依赖项,暴露一个端口号,并最后实例化电影服务。

接下来,需要构建 Docker 镜像,使用以下命令:

$ docker build -t movies-service .

首先看一下构建命令。

  • docker build 告诉引擎要创建一个新的镜像。
  • -t movies-service 用标记 movies-service 标记此镜像。从现在开始,可以根据标记引用此镜像。
  • .:使用当前目录来查找 Dockerfile

经过一些控制台输出后,新镜像中就有了 NodeJS 应用程序,所以现在需要做的就是使用以下命令运行镜像:

$ docker run --name movie-service -p 3000:3000 -e DB_SERVERS="192.168.99.100:27017 192.168.99.101:27017 192.168.99.100:27017" -d movies-service

在上面的命令中,传递了一个环境变量,它是一个服务器数组,需要连接到 MongoDB 复制集的服务器,这只是为了说明,有更好的方法可以做到这一点,比如读取一个环境变量文件。

现在,容器已经运行起来了,获取 docker-machine IP地址,以获取微服务的 IP 地址,现在准备对微服务进行一次集成测试,另一个测试选项可以是JMeter,它是一个很好的工具,可以模拟HTTP请求。

这是集成测试,将检查一个 API 调用。

/* eslint-env mocha */
const supertest = require('supertest')

describe('movies-service', () => {

  const api = supertest('http://192.168.99.100:3000')

  it('returns a 200 for a collection of movies', (done) => {

    api.get('/movies/premiers')
      .expect(200, done)
  })
})

总结

创建了用于查询电影院正在上映的电影的 movies 服务,使用 RAML 规范设计了 API,然后开始构建 API,并进行了相应的单元测试,最后,组合了所有内容,使微服务完整,并能够启动 movies 服务服务器。

然后,将微服务放入 Docker 容器中,以进行一些集成测试。

微服务架构可以为大型应用程序带来许多好处,但也需要小心管理和设计,以处理分布式系统的复杂性和其他挑战。使用 Docker 容器可以简化微服务的部署和管理,使其更加灵活和可扩展。

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

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

相关文章

华为开源自研AI框架昇思MindSpore应用案例:基于MindSpore框架的UNet-2D案例实现

目录 一、环境准备1.进入ModelArts官网2.使用CodeLab体验Notebook实例 二、环境准备与数据读取三、模型解析Transformer基本原理Attention模块 Transformer EncoderViT模型的输入整体构建ViT 四、模型训练与推理模型训练模型验证模型推理 近些年&#xff0c;随着基于自注意&…

【CTF-MISC】1和0的故事(二维码定位点补全)

题目链接&#xff1a;https://ctf.bugku.com/challenges/detail/id/216.html 文件中得到一个01方阵&#xff0c;可以在010 Editor中高亮设置将1涂为黑色、0涂为白色&#xff0c;如下图所示。 截图以后调整大小再加入三个定位点即可得到二维码。 扫描即可得到答案。 要注意的是…

基于TICK的DevOps监控实战(Ubuntu20.04系统,Telegraf+InfluDB+Chronograf+Kapacitor)

1、TICK简介 TICK是InfluxData开发的开源高性能时序中台&#xff0c;集成了采集、存储、分析、可视化等能力&#xff0c;由Telegraf, InfluDB, Chronograf, Kapacitor等4个组件以一种灵活松散、但又紧密配合&#xff0c;互为补充的方式构成。TICK专注于DevOps监控、IoT监控、实…

vscode的配置和使用

1.侧边栏调整大小 放大&#xff1a;View -> Appearance -> Zoom in&#xff08;快捷键Ctrl &#xff09; 缩小&#xff1a;View -> Appearance -> Zoom out&#xff08;快捷键Ctrl -&#xff09; 侧边栏字体调整到合适大小后&#xff0c;可以按下一步调整代码区…

【软件测试】我的2023面试经验谈

最近行业里有个苦涩的笑话&#xff1a;公司扛过了之前的三年&#xff0c;没扛过摘下最近的一年&#xff0c;真是让人想笑又笑不出来。年前听说政策的变化&#xff0c;大家都满怀希望觉得年后行情一片大好&#xff0c;工作岗位激增&#xff0c;至少能有更多的机会拥抱未来。然而…

网页显示摄像头数据的方法---基于web video server

1. 背景&#xff1a; 在ros系统中有发布摄像头的相关驱动rgb数据&#xff0c;需求端需要将rgb数据可以直接在网页上去显示。 问题解决&#xff1a; web_video_server功能包&#xff0c;相关链接&#xff1a; web_video_server - ROS Wiki 2. 下载&#xff0c;安装和编译&a…

scope穿透(二)

上篇文章已经讲了,如何穿透样式,今天我们进入element-ui官网进行大规模的穿透处理。 1.输入框 <template><div class""><el-input v-model"input" placeholder"请输入内容"></el-input></div> </template>…

shiro框架基本概念介绍

目录 什么是Shiro: Shiro的核心功能包括&#xff1a; Shiro主要组件及相互作用&#xff1a; Shiro 认证过程&#xff1a; Shiro 授权过程&#xff1a; 资料获取方法 什么是Shiro: Shiro 是一个强大灵活的开源安全框架&#xff0c;可以完全处理身份验证、授权、加密和会话…

亚马逊悄悄创建广告产品团队,并将自己定位为广告领域的有力竞争者。

据外媒报道&#xff0c;为了加速广告收入的增长&#xff0c;亚马逊正在采取战略举措&#xff0c;建立一个专门面向发布商的广告产品团队。这家零售巨头正在为其新成立的“PubTech”团队&#xff08;亚马逊广告的一个部门&#xff09;招募人才。目前&#xff0c;亚马逊“PubTech…

对于d3dcompiler_47.dll丢失问题,几种详细解决方法

d3dcompiler_47.dll是Direct3D编译器的动态链接库文件&#xff0c;它是DirectX的一部分。DirectX是由微软开发的一组应用程序接口&#xff08;API&#xff09;&#xff0c;用于在Windows操作系统上实现多媒体和游戏的高性能图形和声音效果。d3dcompiler_47.dll的作用是编译Dire…

虹科展会 | 自动驾驶展品:上海汽车测试展精彩回顾

2023年8月9日-8月11日&#xff0c;上海国际汽车测试及质量监控博览会在上海圆满落幕。本次展会提供了一个了解最新汽车测试及质量监控技术、产品和趋势的机会&#xff0c;同时也是汽车测试及质量监控领域的专业人士和业内人士的重要交流平台。 雅名特是虹科旗下子公司&#xff…

服务器数据恢复-RAID5多块磁盘离线导致崩溃的数据恢复案例

服务器数据恢复环境&#xff1a; DELL POWEREDGE某型号服务器中有一组由6块SCSI硬盘组建的RAID5阵列&#xff0c;LINUX REDHAT操作系统&#xff0c;EXT3文件系统&#xff0c;存放图片文件。 服务器故障&分析&#xff1a; 服务器raid5阵列中有一块硬盘离线&#xff0c;管理员…

MySQL中按月统计并逐月累加统计值的几种写法

有时候&#xff0c;我们可能有这样的场景&#xff0c;需要将销量按月统计&#xff0c;并且按月逐月累加。写惯了GROUP BY,按月统计倒是小case,但是逐月累加实现起来&#xff0c;要稍微麻烦一点。下面就整理几种写法&#xff0c;以备不时之需。 本月第一天 -- 本月第一天 SELE…

改造旧项目-长安分局人事费用管理系统

一、系统环境搭建 1、搭建前台环境 vue3vite构建项目复制“银税系统”页面结构&#xff0c;包括&#xff1a;路由、vuex存储、菜单、登录&#xff08;复制一个干净的空架子&#xff09; 2、搭建后台环境 新三大框架 SSMP聚合工程&#xff1a;common、admin&#xff0c;新的…

jmeter提取token方式以及设置成全局变量(跨线程组传token值)方式

前言 今天Darren洋教大家如何使用jmeter中的插件来进行token值的提取与调用&#xff0c;今天Darren洋介绍两种jmeter提取token值的方式&#xff0c;一种是在当前线程组中直接提取token值&#xff0c;一种是跨线程组的方式进行token值的提取并调用给不同线程组里的HTTP接口使用。…

用户端Web自动化测试-L1

目录&#xff1a; Web自动化测试价值与体系环境安装与使用自动化用例录制自动化测试用例结构分析web浏览器控制常见控件定位方法强制等待与隐式等待常见控件交互方法自动化测试定位策略搜索功能自动化测试用户端Web自动化测试 1.Web自动化测试价值与体系 功能测试场景: UI 自…

三个月从零入门深度学习,保姆级学习路线图!

小伙伴们大家好&#xff0c;这里是长沙图灵教育&#xff0c;我们从2001年开始进入教育行业&#xff0c;立足泛IT类职业教育&#xff0c;以打造新兴高新技术人才为宗旨&#xff0c;致力于成为优质的职业教育内容提供商;于2017年正式成立图灵&#xff0c; 在线教育有限公司。 到…

IDEA创建项目常见问题

1.IDEA修改maven路径无效 创建spring项目&#xff0c;Maven导入报错&#xff0c;无法正常导入jar报&#xff0c;发现setting中设置的maven路径不是自己下载的路径&#xff0c;修改后无效。运行之后maven路径又恢复为其默认的路径 解决方案&#xff1a; 删除.mvn文件&#xff0…

2023年测试岗分析,功能/自动化测试/测试开发,你会选哪个?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 功能测试关注点比…

iTOP-3568开发板使用OpenCV处理图像-颜色转换

本小节代码在配套资料“iTOP-3568 开发板\03_【iTOP-RK3568 开发板】指南教程 \04_OpenCV 开发配套资料\05”目录下&#xff0c;如下图所示&#xff1a; cv2.cvtColor()函数功能&#xff1a; 将一幅图像从一个色彩空间转换到另一个色彩空间。 函数原型&#xff1a; cv2.cvt…