当前位置: 首页 > news >正文

用 vue3 + phaser 实现经典小游戏:飞机大战

cb8a11e8bd3bbef243d0797ea0bbb72d.jpeg

a658413058f122948eb0676cdd217980.gif

本文字数:7539

预计阅读时间:30分钟

01

前言

说起小游戏,最经典的莫过于飞机大战了,相信很多同学都玩过。今天我们也来试试开发个有趣的小游戏吧!我们将从零开始,看看怎样一步步实现一个H5版的飞机大战!

首先我们定好目标,要做一个怎样的飞机大战,以及去哪整游戏素材?

刚好微信小程序官方提供了一个飞机大战小游戏的模板,打开【微信开发者工具】,选择【新建项目】-【小游戏】,选择飞机大战的模板,创建后就是一个小程序版飞机大战。

1704ad87855556aa4882da88d03193a5.png

运行小程序之后可以看到下面的效果:

115a965de65c1fe3611d37ef8d57dca3.gif

从运行效果上看,这个飞机大战已经比较完整,包含了以下内容:

1.地图滚动,播放背景音效;

2.玩家控制飞机移动;

3.飞机持续发射子弹,播放发射音效;

4.随机出现向下移动的敌军;

5.子弹碰撞敌军时,播放爆炸动画和爆炸音效,同时子弹和敌军都销毁,并增加1个得分;

6.飞机碰撞敌军时,游戏结束,弹出结束面板。

接下来我们以这个效果为参考,并拷贝这个项目中的图片和音效素材,从头做一个H5版飞机大战吧!

02

选择游戏框架

你可能会好奇,既然微信小程序官方已经生成好了完整代码,直接参考那套代码不就好吗?

这里就涉及到游戏框架的问题,小程序那套代码是没有使用游戏框架的,所以很多基础的地方都需要自己实现,比如说子弹移动,子弹与敌军碰撞检测等。

我们以碰撞为例,在小程序项目中是这样实现的:

1.先定义好碰撞检测的方法isCollideWith(),通过两个物体的坐标和宽高进行碰撞检测计算:

isCollideWith(sp) {let spX = sp.x + sp.width / 2;let spY = sp.y + sp.height / 2;if (!this.visible || !sp.visible) return false;return !!(spX >= this.x && spX <= this.x + this.width && spY >= this.y && spY <= this.y + this.height);
},

2.然后在每一帧的回调中,遍历所有子弹和所有敌军,依次调用isCollideWith()进行碰撞检测:

update() {bullets.forEach((bullet) => {for (let i = 0, il = enemys.length; i < il; i++) {if (enemys[i].isCollideWith(bullet)) {// Do Something}}});
}

3.而通过游戏框架,可能只需要一行代码。我们以Phaser为例:

this.physics.add.overlap(bullets, enemys, () => { // Do Something
}, null, this);

上面代码的含义是:bullets(子弹组)和enemys(敌军组)发生overlap(重叠)则触发回调。

从上面的例子可以看出,选择一个游戏框架来开发游戏,可以大大降低开发难度,减少代码量。

当开发一个专业的游戏时,我们一般会选择专门的游戏引擎,比如Cocos,Egret,LayaBox,Unity等。但是如果只是做一个简单的H5小游戏,嵌入我们的前端项目中,使用Phaser就可以了。

引用Phaser官网上的介绍:

【Phaser是一个快速、免费且有趣的开源HTML5游戏框架,可在桌面和移动Web浏览器上提供WebGL和Canvas渲染。可以使用第三方工具将游戏编译为iOS、Android和本机应用程序。您可以使用JavaScript或TypeScript进行开发。】

同时Phaser在社区也非常受欢迎,Github上收获35.5k的Star,Npm上最近一周下载量19k。

因此我们采用Phaser作为游戏框架。接下来,开始正式我们的飞机大战之旅啦!

03

准备工作

3.1 创建项目

项目采用的技术栈是:Phaser + Vue3 + TypeScript + Vite。

当然对于这个游戏来说,核心的框架是Phaser,其他都是可选的。只使用Phaser + Html也是可以开发的,只是我们希望采用目前更主流的开发方式。

进行工作目录,直接使用vue手脚架创建名为plane-war的项目。

npm create vue

项目创建完成,安装依赖,检查是否运行正常。

cd plane-war
npm install
npm run dev

接下来再安装phaser。

npm install phaser

3.2 整理素材

接下来我们重新整理下项目,清除不需要的文件,并把游戏素材拷贝到assets目录,最终目录结构如下:

plane-war
├── src
│   ├── assets
│   │   ├── audio
│   │   │   ├── bgm.mp3
│   │   │   ├── boom.mp3
│   │   │   └── bullet.mp3
│   │   ├── images
│   │   │   ├── background.jpg
│   │   │   ├── boom.png
│   │   │   ├── bullet.png
│   │   │   ├── enemy.png
│   │   │   ├── player.png
│   │   │   └── sprites.png
│   │   └── json
│   │       └── sprites.json
│   ├── App.vue
│   └── main.ts

素材处理1:

原本游戏素材中,爆炸动画是由19张独立图片组成,在Phaser中需要合成一张雪碧图,可以通过雪碧图合成工具合成,命名为boom.png,效果如下:

196d9eb820160c3513808605a3afae52.png

素材处理2:

原本游戏素材中,结束面板的图片来源一张叫Common.png的雪碧图,我们重命名为sprites.png。并且我们还需要为这个雪碧图制作一份说明,起名为sprites.json。通过它来指定我们需要用到目标图片及其在雪碧图中的位置。

这里我们指定2个目标图片,result是结束面板,button是按钮。

{"textures": [{"image": "sprites.png","size": {"w": 512,"h": 512},"frames": [{"filename": "result","frame": { "x": 0, "y": 0, "w": 119, "h": 108 }},{"filename": "button","frame": { "x": 120, "y": 6, "w": 39, "h": 24 }}]}]
}

3.3 初步运行

我们重构App.vue,创建了一个游戏对象game,指定父容器为#container,创建成功后则会在父容器中生成一个canvas 元素,游戏的所有内容都通过这个canvas进行呈现和交互。

<template><div id="container"></div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
import { Game, AUTO, Scale } from "phaser";let game: Game;
onMounted(() => {game = new Game({parent: "container",type: AUTO,width: 375,// 高度依据屏幕宽高比计算height: (window.innerHeight / window.innerWidth) * 375,scale: {// 自动缩放至宽或高与父容器一致,类似css中的contain// 由于宽高比与屏幕宽高比一致,最终就是刚好全屏效果mode: Scale.FIT,},physics: {default: "arcade",arcade: {debug: false,},},});
});onUnmounted(() => {game.destroy(true);
});
</script>
<style>
body {margin: 0;
}
#app {height: 100%;
}
</style>

通过npm run dev再次运行项目,我们把浏览器展示区切换:为移动设备展示,此时可以看到canvas,并且其宽高应该正好全屏。

cb48c9979495fbddb6af06ad3224bcff.png

3.4 场景设计

可以看到现在画布还是全黑的,这是因为创建game对象时还没有接入任何场景。在Phaser中,一个游戏可以包含多个场景,而具体的游戏画面和交互都是在各个场景中实现的。

接下来我们设计3个场景:

  • 预载场景 :加载整个游戏资源,创建动画,展示等待开始画面。

  • 主场景:游戏的主要画面和交互。

  • 结束场景:展示游戏结束画面。

2ac74f5fad2664e4c2f773412f80d92d.jpeg

在项目中我们新增3个自定义场景类:

plane-war
├── src
│   ├── game
│   │   ├── Preloader.ts
│   │   ├── Main.ts
│   │   └── End.ts

自定义场景类继承Scene类,包含了以下基本结构:

import { Scene } from "phaser";export class Preloader extends Scene {constructor() {// 场景命名,这个命名在后面场景切换使用super("Preloader");}// 加载游戏资源preload() {}// preload中的资源全部加载完成后执行create() {}// 每一帧的回调update() {}
}

按上面的基本结构分别实现好3个场景类,并导入到game对象的创建中:

import { onMounted, onUnmounted } from "vue";
import { Game, AUTO, Scale } from "phaser";
import { Preloader } from "./game/Preloader";
import { Main } from "./game/Main";
import { End } from "./game/End";let game: Game;
onMounted(() => {game = new Game({// 其他参数省略...// 定义场景,默认初始化数组中首个场景,即 Preloaderscene: [Preloader, Main, End],});
});

04

预载场景

准备工作完成后,接下来我们开始真正开发第一个游戏场景:预载场景,对应Preloader.ts文件。

4.1 加载游戏资源

preload方法中加载整个游戏所需的资源。

import { Scene } from "phaser";
import backgroundImg from "../assets/images/background.jpg";
import enemyImg from "../assets/images/enemy.png";
import playerImg from "../assets/images/player.png";
import bulletImg from "../assets/images/bullet.png";
import boomImg from "../assets/images/boom.png";
import bgmAudio from "../assets/audio/bgm.mp3";
import boomAudio from "../assets/audio/boom.mp3";
import bulletAudio from "../assets/audio/bullet.mp3";export class Preloader extends Scene {constructor() {super("Preloader");}preload() {// 加载图片this.load.image("background", backgroundImg);this.load.image("enemy", enemyImg);this.load.image("player", playerImg);this.load.image("bullet", bulletImg);this.load.spritesheet("boom", boomImg, {frameWidth: 64,frameHeight: 48,});// 加载音频this.load.audio("bgm", bgmAudio);this.load.audio("boom", boomAudio);this.load.audio("bullet", bulletAudio);}create() {}
}

4.2 添加元素

接下来我们在create()方法中去添加背景,背景音乐,标题,开始按钮,后续使用的动画,并且为开始按钮绑定了点击事件。

const { width, height } = this.cameras.main;
// 背景
this.add.tileSprite(0, 0, width, height, "background").setOrigin(0, 0);
// 背景音乐
this.sound.play("bgm");// 标题
this.add.text(width / 2, height / 4, "飞机大战", {fontFamily: "Arial",fontSize: 60,color: "#e3f2ed",stroke: "#203c5b",strokeThickness: 6,}).setOrigin(0.5);// 开始按钮
let button = this.add.image(width / 2, (height / 4) * 3, "sprites", "button").setScale(3, 2).setInteractive().on("pointerdown", () => {// 点击事件:关闭当前场景,打开Main场景this.scene.start("Main");});// 按钮文案
this.add.text(button.x, button.y, "开始游戏", {fontFamily: "Arial",fontSize: 20,color: "#e3f2ed",}).setOrigin(0.5);// 创建动画,命名为 boom,后面使用
this.anims.create({key: "boom",frames: this.anims.generateFrameNumbers("boom", { start: 0, end: 18 }),repeat: 0,
});

运行效果如下:

733389cc10aae37b13d15e0ad5663449.png

有个细节可以留意下,就是这个背景是怎样铺满整个屏幕的?

上面的代码是this.add.tileSprite()创建了一个瓦片精灵,素材中的背景图就像一个一个瓦片一样铺满屏幕,所以就要求素材中的背景图是一张首尾能无缝相连的图片,这样就能无限平铺。主场景中的背景移动也是基于此。

05

主场景

5.1 梳理场景元素

在预载场景中点击“开始游戏”按钮,可以看到画面又变成黑色,此时预载场景被关闭,游戏打开主场景。

在主场景中,涉及到的场景元素一共有:背景、玩家、子弹、敌军、爆炸,我们可以先尝试把它们都渲染出来,并加一些简单的动作,比如移动背景,子弹和敌军添加垂直方向速度,播放爆炸动画等。

import { Scene, GameObjects, type Types } from "phaser";// 场景元素
let background: GameObjects.TileSprite;
let enemy: Types.Physics.Arcade.SpriteWithDynamicBody;
let player: Types.Physics.Arcade.SpriteWithDynamicBody;
let bullet: Types.Physics.Arcade.SpriteWithDynamicBody;
let boom: GameObjects.Sprite;export class Main extends Scene {constructor() {super("Main");}create() {const { width, height } = this.cameras.main;// 背景background = this.add.tileSprite(0, 0, width, height, "background").setOrigin(0, 0);// 玩家this.physics.add.sprite(100, 600, "player").setScale(0.5);// 子弹this.physics.add.sprite(100, 500, "bullet").setScale(0.25).setVelocityY(-100);// 敌军this.physics.add.sprite(100, 100, "enemy").setScale(0.5).setVelocityY(100);// 爆炸this.add.sprite(200, 100, "boom").play("boom");}update() {// 设置背景瓦片不断移动background.tilePositionY -= 1;}
}

效果如下:

20fa8241e27a5e4d2c86c6581761d12d.gif

看起来似乎已经有了雏形,但是这里还需要优化一下代码设计。我们不希望场景中的所有元素创建,交互都糅合Main.ts这个文件中,这样就显得有点臃肿,不好维护。

我们再设计出:玩家类、子弹类、敌军类、炸弹类,让每个元素它们自身的事件和行为都各自去实现,而主场景只负责创建它们,并且处理它们之间的交互事件,不需要去关心它们内部的实现。

虽然这个游戏的整体代码也不多,但是通过这个设计思想,可以让我们的代码设计更加合理,当以后开发其他更复杂的小游戏时也可以套用这种模式。

6aff7204438698398b77449617cd0c71.jpeg

5.2 玩家类

回顾上面的创建玩家的代码:

this.physics.add.sprite(100, 600, "player").setScale(0.5);

原本的代码是直接创建了一个“物理精灵对象“,我们现在改成新建一个Player类,这个类继承Physics.Arcade.Sprite,然后在主场景中通过new Player()也同样生成"物理精灵对象"。相当于Player类拓展了原本Physics.Arcade.Sprite,增加了对自身的一些事件处理和行为封装。后续的子弹类,敌军类等也是同样的方式。

Player类主要拓展了"长按移动事件",具体实现如下:

import { Physics, Scene } from "phaser";export class Player extends Physics.Arcade.Sprite {isDown: boolean = false;downX: number;downY: number;constructor(scene: Scene) {// 创建对象let { width, height } = scene.cameras.main;super(scene, width / 2, height - 80, "player");scene.add.existing(this);scene.physics.add.existing(this);// 设置属性this.setInteractive();this.setScale(0.5);this.setCollideWorldBounds(true);// 注册事件this.addEvent();}addEvent() {// 手指按下我方飞机this.on("pointerdown", () => {this.isDown = true;// 记录按下时的飞机坐标this.downX = this.x;this.downY = this.y;});// 手指抬起this.scene.input.on("pointerup", () => {this.isDown = false;});// 手指移动this.scene.input.on("pointermove", (pointer) => {if (this.isDown) {this.x = this.downX + pointer.x - pointer.downX;this.y = this.downY + pointer.y - pointer.downY;}});}
}

5.3 子弹类

Bullet类主要拓展了"发射子弹"和"子弹出界事件",具体实现如下:

import { Physics, Scene } from "phaser";export class Bullet extends Physics.Arcade.Sprite {constructor(scene: Scene, x: number, y: number, texture: string) {// 创建对象super(scene, x, y, texture);scene.add.existing(this);scene.physics.add.existing(this);// 设置属性this.setScale(0.25);}// 发射子弹fire(x: number, y: number) {this.enableBody(true, x, y, true, true);this.setVelocityY(-300);this.scene.sound.play("bullet");}// 每一帧更新回调preUpdate(time: number, delta: number) {super.preUpdate(time, delta);// 子弹出界事件(子弹走到顶部超出屏幕)if (this.y <= -14) {this.disableBody(true, true);}}
}

5.4 敌军类

Enemy类主要拓展了"生成敌军"和"敌军出界事件",具体实现如下:

import { Physics, Math, Scene } from "phaser";export class Enemy extends Physics.Arcade.Sprite {constructor(scene: Scene, x: number, y: number, texture: string) {// 创建对象super(scene, x, y, texture);scene.add.existing(this);scene.physics.add.existing(this);// 设置属性this.setScale(0.5);}// 生成敌军born() {let x = Math.Between(30, 345);let y = Math.Between(-20, -40);this.enableBody(true, x, y, true, true);this.setVelocityY(Math.Between(150, 300));}// 每一帧更新回调preUpdate(time: number, delta: number) {super.preUpdate(time, delta);let { height } = this.scene.cameras.main;// 敌军出界事件(敌军走到底部超出屏幕)if (this.y >= height + 20) {this.disableBody(true, true)}}
}

5.5 爆炸类

Boom 类主要拓展了"显示爆炸"和“隐藏爆炸”,具体实现如下:

import { GameObjects, Scene } from "phaser";export class Boom extends GameObjects.Sprite {constructor(scene: Scene, x: number, y: number, texture: string) {super(scene, x, y, texture);// 爆炸动画播放结束事件this.on("animationcomplete-boom", this.hide, this);}// 显示爆炸show(x: number, y: number) {this.x = x;this.y = y;this.setActive(true);this.setVisible(true);this.play("boom");this.scene.sound.play("boom");}// 隐藏爆炸hide() {this.setActive(false);this.setVisible(false);}
}

5.6 重构主场景

上面我们实现了玩家类,子弹类,敌军类,爆炸类,接下来我们在主场景中重新创建这些元素,并加入分数文本元素。

import { Scene, Physics, GameObjects } from "phaser";
import { Player } from "./Player";
import { Bullet } from "./Bullet";
import { Enemy } from "./Enemy";
import { Boom } from "./Boom";// 场景元素
let background: GameObjects.TileSprite;
let player: Player;
let enemys: Physics.Arcade.Group;
let bullets: Physics.Arcade.Group;
let booms: GameObjects.Group;
let scoreText: GameObjects.Text;// 场景数据
let score: number;export class Main extends Scene {constructor() {super("Main");}create() {let { width, height } = this.cameras.main;// 创建背景background = this.add.tileSprite(0, 0, width, height, "background").setOrigin(0, 0);// 创建玩家player = new Player(this);// 创建敌军enemys = this.physics.add.group({frameQuantity: 30,key: "enemy",enable: false,active: false,visible: false,classType: Enemy,});// 创建子弹bullets = this.physics.add.group({frameQuantity: 15,key: "bullet",enable: false,active: false,visible: false,classType: Bullet,});// 创建爆炸booms = this.add.group({frameQuantity: 30,key: "boom",active: false,visible: false,classType: Boom,});// 分数score = 0;scoreText = this.add.text(10, 10, "0", {fontFamily: "Arial",fontSize: 20,});// 注册事件this.addEvent();},update() {// 背景移动background.tilePositionY -= 1;}
}

需要注意的是,这里的子弹,敌军,爆炸都是按组创建的,这样我们可以直接监听子弹组和敌军组的碰撞,而不需要监听每一个子弹和每一个敌军的碰撞。另一方面,创建组时已经把组内的元素全部创建好了,比如创建敌军时指定frameQuantity: 30,表示直接创建30个敌军元素,后续敌军不断出现和销毁其实就是这30个元素在循环使用而已,而并非源源不断地创建新元素,以此减少性能损耗。

最后再把注册事件实现,主场景就全部完成了。

// 注册事件
addEvent() {// 定时器this.time.addEvent({delay: 400,callback: () => {// 生成2个敌军for (let i = 0; i < 2; i++) {enemys.getFirstDead()?.born();}// 发射1颗子弹bullets.getFirstDead()?.fire(player.x, player.y - 32);},callbackScope: this,repeat: -1,});// 子弹和敌军碰撞this.physics.add.overlap(bullets, enemys, this.hit, null, this);// 玩家和敌军碰撞this.physics.add.overlap(player, enemys, this.gameOver, null, this);
}
// 子弹击中敌军
hit(bullet, enemy) {// 子弹和敌军隐藏enemy.disableBody(true, true);bullet.disableBody(true, true);// 显示爆炸booms.getFirstDead()?.show(enemy.x, enemy.y);// 分数增加scoreText.text = String(++score);
}
// 游戏结束
gameOver() {// 暂停当前场景,并没有销毁this.sys.pause();// 保存分数this.registry.set("score", score);// 打开结束场景this.game.scene.start("End");
}

06

结束场景

最后再实现一下结束场景,很简单,主要包含结束面板,得分,重新开始按钮。

import { Scene } from "phaser";export class End extends Scene {constructor() {super("End");}create() {let { width, height } = this.cameras.main;// 结束面板this.add.image(width / 2, height / 2, "sprites", "result").setScale(2.5);// 标题this.add.text(width / 2, height / 2 - 85, "游戏结束", {fontFamily: "Arial",fontSize: 24,}).setOrigin(0.5);// 当前得分let score = this.registry.get("score");this.add.text(width / 2, height / 2 - 10, `当前得分:${score}`, {fontFamily: "Arial",fontSize: 20,}).setOrigin(0.5);// 重新开始按钮let button = this.add.image(width / 2, height / 2 + 50, "sprites", "button").setScale(3, 2).setInteractive().on("pointerdown", () => {// 点击事件:关闭当前场景,打开Main场景this.scene.start("Main");});// 按钮文案this.add.text(button.x, button.y, "重新开始", {fontFamily: "Arial",fontSize: 20,}).setOrigin(0.5);}
}

07

优化

经过上面的代码,整个游戏已经基本完成。不过在测试的时候,感觉玩家和敌军还存在一定距离就触发了碰撞事件。在创建game时,我们可以打开debug模式,这样就可以看到Phaser为我们提供的一些调试信息。

game = new Game({physics: {default: "arcade",arcade: {debug: true,},},// ...
});

测试一下碰撞:

659a49a34156e429b8f85b929e561111.png

可以看到两个元素的边框确实发生碰撞了,但是这并不符合我们的要求,我们希望两个飞机看起来是真的挨到一起才触发碰撞事件。所以我们可以再优化一下,飞机本身不变,但是边框缩小。

Player.ts的构造函数中追加如下:

export class Player extends Physics.Arcade.Sprite {constructor() {// ...// 追加下面一行this.body.setSize(120, 120);}
}

Enemy.ts的构造函数中追加如下:

export class Enemy extends Physics.Arcade.Sprite {constructor() {// ...// 追加下面一行this.body.setSize(100, 60);}
}

最终可以看到边框已经被缩小,效果如下:

bd0449c9774f541645e1c55f0881fae9.png

08

结语

至此,飞机大战全部开发完成。

回顾一下开发过程,我们先搭建项目,创建游戏对象,接下来又设计了:预载场景、主场景、结束场景,并且为了减少主场景的复杂度,我们以场景元素的维度,将涉及到的场景元素进行封装,形成:玩家类、子弹类、敌军类、爆炸类,让这些场景元素各自实现自身的事件和行为。

在Phaser中的场景元素又可以分为普通元素和物理元素,物理元素是来自Physics,其中玩家类,子弹类,敌军类都是物理元素,物理元素具有物理属性,比如重力,速度,加速度,弹性,碰撞等。

在本文代码中涉及到了很多Phaser的API,介于篇幅没有一一解释,但是很多通过字面意思也可以理解,比如说disableBody表示禁用元素,setVelocityY表示设置Y 轴方向速度。并且我们也可以通过编译器的代码提示功能去了解这些方法的说明和参数含义:

66091e0ad6ef2fe1bd9084fe31ea805f.png

最后,本文的所有代码都已上传gitee,有兴趣的同学可以拉取代码看下。

演示效果:https://yuhuo.online/plane-war/(点击"阅读原文"访问链接)

源码地址:https://gitee.com/yuhuo520/plane-war

2e26c0512a6576830f535367bf07f682.jpeg

相关文章:

用 vue3 + phaser 实现经典小游戏:飞机大战

本文字数&#xff1a;7539字 预计阅读时间&#xff1a;30分钟 01 前言 说起小游戏&#xff0c;最经典的莫过于飞机大战了&#xff0c;相信很多同学都玩过。今天我们也来试试开发个有趣的小游戏吧&#xff01;我们将从零开始&#xff0c;看看怎样一步步实现一个H5版的飞机大战&a…...

【Linux|数据恢复】extundelete和ext4magic数据恢复工具使用

环境&#xff1a;Centos7.6_x86 一、extundelete工具 1、extundelete介绍 Extundelete 是一个数据恢复工具&#xff0c;用于从 ext3 或 ext4 分区中恢复删除文件。根据官网0.2.4版本介绍是支持ext4&#xff0c;但实际上使用发现ext4格式不行&#xff0c;会报以下错误&#xf…...

用户接入和认证技术

一、用户接入和认证配置 称为网络接入控制&#xff0c;通过对接入网络的客NAC (Network Admission Control)户端和用户的认证保证网络的安全&#xff0c;是一种“端到端”的安全技术。包括802.1x认证、MAC认证与Portal认证。 二、三种认证方式简介 1、Portal认证 Portal认证通…...

【面试】Java虚拟机的生命周期

目录 1. 说明2. 启动&#xff08;Initialization&#xff09;3. 运行&#xff08;Running&#xff09;4. 服务&#xff08;Servicing&#xff09;5. 终止&#xff08;Termination&#xff09; 1. 说明 1.Java虚拟机&#xff08;JVM&#xff09;的生命周期通常指的是JVM实例从启…...

Nginx高可用性架构:实现负载均衡与故障转移的探索

随着网络应用的不断发展和用户访问量的增长&#xff0c;如何确保系统的高可用性、实现负载均衡以及快速响应故障转移成为了每个运维和开发团队必须面对的挑战。Nginx作为一款高性能的HTTP和反向代理服务器&#xff0c;凭借其强大的功能和灵活的配置&#xff0c;成为了实现这些目…...

计算机网络-运输层

运输层 网络层在两个端系统之间的交付服务拓展到运行在两个不同端系统上的应用层进程之间的交付服务。 概述和运输层服务 运输层协议为运行在不同主机上的引用进程之间提供了逻辑通信功能。通过逻辑通信&#xff0c;运行在不同进程之间的主机好像直接连接一样。 运输层协议…...

网络通信(一)

网络编程 1.网络编程概念及相关名词 &#xff1a; 网络编程是计算机科学中一个重要的领域&#xff0c;它涉及到在不同计算机之间通过计算机网络进行通信和数据交换的程序设计。网络编程的核心是实现网络通信协议&#xff0c;这些协议定义了数据如何在网络上发送、接收和解释。…...

Linux环境中部署docker私有仓库Registry与远程访问详细流程

目录 前言 1. 部署Docker Registry 2. 本地测试推送镜像 3. Linux 安装cpolar 4. 配置Docker Registry公网访问地址 5. 公网远程推送Docker Registry 6. 固定Docker Registry公网地址 前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊…...

springboot项目使用validated参数校验框架

目录 前言 一、validated是什么&#xff1f; 二、使用步骤 1.引入maven依赖 2.使用实现 总结 前言 当谈到Spring的参数校验功能时&#xff0c;Validated注解无疑是一个重要的利器。它为我们提供了一种简单而又强大的方式来验证请求参数的合法性&#xff0c;保证了系统的稳…...

Azure Chatgpt demo部署——本地CentOS Docker

参见上一篇 http://t.csdnimg.cn/JcyfM 由于本地部署环境&#xff0c;与之前系统、网络、配置等环境不同&#xff0c;可能会遇见一些新的问题。 取2023年8月27日代码 git checkout -b a02796b063381c10ca9ca8189590b289a4d09129 由于本地情况的网络等环境不太一样&#xff0c…...

MybatisPlus中自定义sql

背景 在开发过程中&#xff0c;可能会出现除了where条件&#xff0c;其它sql比较复杂&#xff0c;这时候就需要用到自定义sql了。 问题 如&#xff1a;用户状态为正常的数据年龄加一&#xff08;所有用户年龄加一&#xff09; 数据库中sql&#xff1a; UPDATE USER SET…...

HCIA--DHCP: 动态主机配置协议 (复习)

DHCP: 动态主机配置协议 -- 同一分发管理ip地址 基于UDP 67/68端口工作 网络中存在DHCP的服务器为需要自动生成ip地址的设备分配ip地址&#xff1b;--C/S模型 成为DHCP服务器的条件&#xff1a; 该设备存在接口或网卡连接到所要分发ip地址的广播域内该接口或网卡必须已经配置…...

MySQL select for update 加锁

背景 当多人操作同一个客户下账号的时候&#xff0c;希望顺序执行&#xff0c;某个时刻只有一个人在操作&#xff1b;当然可以通过引入redis这种中间件实现&#xff0c;但考虑到并发不会很多&#xff0c;所以不想再引入别的中间件。 表结构 create table jiankunking_accoun…...

MongoDB CRUD操作:投影Project详解

MongoDB CRUD操作&#xff1a;投影Project详解 文章目录 MongoDB CRUD操作&#xff1a;投影Project详解返回文档的全部字段返回指定的字段和_id字段不输出_id字段指定排除的字段返回内嵌文档中的指定字段禁止内嵌文档中的特定字段数组中内嵌文档的投影聚合表达式的投影字段 默认…...

redis 集群 底层原理以及实操

前言 上篇我们讲解了哨兵集群是怎么回事 也说了对应的leader选举raft算法 也说了对应的slave节点是怎么被leader提拔的 主要是比较优先级 比较同步偏移量 比较runid等等 今天我们再说说,其实哨兵也有很多缺点 虽然在master挂了之后能很快帮我们选举出新的master 但是对于单个ma…...

MVC架构中的servlet层重定向404小坑

servlet层中的UserLoginServlet.java package com.mhys.servlet; /*** ClassName: ${NAME}* Description:** Author 数开_11* Create 2024-05-29 20:32* Version 1.0*/import com.mhys.pojo.User; import com.mhys.service.UserService; import com.mhys.service.impl.UserSer…...

Java-RabbitMQ

RabbitMQ使用场景 1、跨系统异步通信 2、多应用之间解耦 3、应用内流程同步变异步 4、整体架构即采用消息驱动 5、应用内部解耦 RabbitMQ内部角色 角色简介生产者消息创建者消费者消息接收者代理RabbitMQ本身&#xff0c;用于存储转发消息&#xff0c;快递功能 RabbitMQ有哪…...

ABAP 在增强中COMMIT

前言 呃&#xff0c;又是很磨人的需求&#xff0c;正常情况下是不允许在增强中COMMIT的&#xff0c;会影响源程序本身的逻辑&#xff0c;但是这个需求就得这么干… 就是在交货单增强里面要再调用一次交货单BAPI&#xff0c;通过SO的交货单自动创建STO的交货单&#xff0c;如果…...

【UML用户指南】-02-UML的14种图

1、结构图 1、类图&#xff08;class diagram&#xff09; 展现了一组类、接口、协作和它们之间的关系。 在面向对象系统的建模中所建立的最常见的图就是类图。类图给出系统的静态设计视图。 包含主动类的类图给出系统的静态进程视图。构件图是类图的变体。 2、对象图&a…...

Linux驱动开发笔记(二) 基于字符设备驱动的I/O操作

文章目录 前言一、设备驱动的作用与本质1. 驱动的作用2. 有无操作系统的区别 二、内存管理单元MMU三、相关函数1. ioremap( )2. iounmap( )3. class_create( )4. class_destroy( ) 四、GPIO的基本知识1. GPIO的寄存器进行读写操作流程2. 引脚复用2. 定义GPIO寄存器物理地址 五、…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...