Java實(shí)現(xiàn)年獸大作戰(zhàn)游戲詳解
前言
春節(jié)要到了,看慣了前端各種小游戲,確實(shí)做得很好,很精致。但是我也要為后端程序員稍微做一點(diǎn)貢獻(xiàn),做一款java版本的【年獸大作戰(zhàn)】。
這個(gè)游戲加上編寫(xiě)文章,上班摸魚(yú)時(shí)間加上回家的空閑時(shí)間,大概花了三天多。
java寫(xiě)這玩應(yīng)真的很痛苦,各種狀態(tài)位,各種圖片和邏輯判斷,腦袋都快炸了。而且肯定沒(méi)有前端的精致,效果一般,偶爾會(huì)有卡頓,各位就圖一樂(lè),隨便捧捧場(chǎng)啊。過(guò)程大于結(jié)果。
一、玩法介紹
進(jìn)入初始界面,會(huì)看到一只大年獸位于正中間,然后是一直小老虎,也就是我們的玩家,點(diǎn)擊【空格】即可開(kāi)始游戲:

敲擊空格,將進(jìn)入游戲。從上至下分別是:
- 年獸的血量【NIAN'S HP】
- 移動(dòng)的年獸
- 最下方的小老虎【玩家】

玩家通過(guò)【←】【→】鍵移動(dòng)小老虎方向,使用【S】鍵發(fā)射炮彈:

當(dāng)擊中年獸后,會(huì)有煙花出現(xiàn)在背景:

每擊中年獸三次,年獸會(huì)扔下炸彈:

如果玩家被擊中,則直接【game over】 ,通過(guò)【空格】鍵重新開(kāi)始:

當(dāng)每擊中年獸10次,其血量就會(huì)減少一個(gè),年獸會(huì)隨機(jī)扔下不同種類的爆竹,當(dāng)前是11種,玩家可以移動(dòng)方向鍵獲?。?/p>

當(dāng)玩家成功接到炮彈后,再次擊中年獸,會(huì)更換背景煙花的種類。原本我想把子彈也換了,后來(lái)是實(shí)在整不動(dòng)了。我玩了半天,想截個(gè)圖,半天沒(méi)成功,給自己心態(tài)玩崩了,就是下面的煙火:

當(dāng)把年獸擊敗后,會(huì)出現(xiàn)新年快樂(lè)的字樣:

上述就是全部玩法了,其實(shí)可以有更多擴(kuò)展的,java寫(xiě)這東西實(shí)在寫(xiě)的太痛苦了。
二、代碼介紹
效果不太好,但是學(xué)學(xué)代碼實(shí)現(xiàn)總是好的吧,下面我簡(jiǎn)單說(shuō)說(shuō)怎么實(shí)現(xiàn)的。
2.1 程序入口【Frame】
使用Frame作為界面的基礎(chǔ)和入口,可以設(shè)置大小,標(biāo)題,展示位置等等,最主要的再次基礎(chǔ)上添加一個(gè)面板,是我們游戲的實(shí)現(xiàn):
public static void main(String[] args) {
//1.創(chuàng)建窗口對(duì)象
Frame frame = new Frame("年獸大作戰(zhàn)");
// 設(shè)置窗體大小為900x800
frame.setSize(900, 800);
// 設(shè)置窗體為居中格式
frame.setLocationRelativeTo(null);
// 設(shè)置窗體不可改變
frame.setResizable(false);
// 在窗體中添加一個(gè)面板
frame.add(new GamePanel());
// 設(shè)置窗體可見(jiàn)
frame.setVisible(true);
// 窗口點(diǎn)擊關(guān)閉
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent arg0) {
System.exit(0);
}
});
}2.2 構(gòu)造器【GamePanel】
第一步,定義一個(gè)空參構(gòu)造,需要添加焦點(diǎn)事件,和鍵盤(pán)事件監(jiān)聽(tīng),定時(shí)器啟動(dòng)頁(yè)面刷新,后面后有定時(shí)器的創(chuàng)建:
public GamePanel() {
// 獲取焦點(diǎn)事件
this.setFocusable(true);
// 添加鍵盤(pán)監(jiān)聽(tīng)事件
this.addKeyListener(this);
// 啟動(dòng)定時(shí)器
timer.start();
}2.3 游戲邏輯實(shí)現(xiàn)【GamePanel】
在啟動(dòng)類當(dāng)中,我們?cè)贔rame當(dāng)中添加了一個(gè)GamePanel,作用是后面游戲的所有內(nèi)容展現(xiàn)都在其中,包括頁(yè)面,游戲邏輯等。
代碼較為復(fù)雜,我只說(shuō)關(guān)鍵點(diǎn),全部代碼在全篇開(kāi)頭的gitee鏈接,感興趣自己獲取。
class GamePanel extends JPanel implements KeyListener, ActionListener
如上所示,GamePannel繼承了JPanel,同時(shí)實(shí)現(xiàn)了KeyListener和ActionListener。
- JPanel
這是jdk提供的,使用java進(jìn)行繪圖的基礎(chǔ)容器。面板不會(huì)向除其自身背景以外的任何內(nèi)容添加顏色。但是,您可以輕松地為它們添加邊框,并以其他方式自定義它們的繪畫(huà)。
- KeyListener
這個(gè)接口是用來(lái)監(jiān)聽(tīng)鍵盤(pán)事件的接口,提供一下幾個(gè)方法:
public interface KeyListener extends EventListener {
/**
* 當(dāng)按鍵被鍵入時(shí)調(diào)用
*/
public void keyTyped(KeyEvent e);
/**
* 當(dāng)按鍵下壓時(shí)調(diào)用
*/
public void keyPressed(KeyEvent e);
/**
* 當(dāng)按鍵釋放時(shí)調(diào)用
public void keyReleased(KeyEvent e);
}本文中我使用了keyPressed和keyReleased。
keyPressed主要用來(lái)完成鍵盤(pán)操作的移動(dòng),和射擊功能。每當(dāng)我們有按鍵操作,都會(huì)被它監(jiān)聽(tīng)到,產(chǎn)生相應(yīng)的事件。
此處有坑: 如果將按鍵一個(gè)一個(gè)的在此處判斷,比如 if(左鍵) else if(設(shè)計(jì)) 這樣,那么當(dāng)你同時(shí)按下這兩個(gè)按鍵,將會(huì)導(dǎo)致它們都失效。
解決辦法如下:
定義一個(gè)全局Set,用來(lái)存放每次按鍵的事件。
static Set<Integer> keys = new HashSet<>();
當(dāng)按鍵下壓時(shí)添加:
/**
* description: 鍵盤(pán)按下未釋放
*
* @param e
* @return: void
* @author: weirx
* @time: 2022/1/10 14:02
*/
@SneakyThrows
@Override
public void keyPressed(KeyEvent e) {
// 添加按鈕下壓事件到set
InitProcessor.keys.add(e.getKeyCode());
// 遍歷執(zhí)行按鈕事件
multiKeys();
}在執(zhí)行一個(gè)遍歷方法,不斷地去執(zhí)行業(yè)務(wù)邏輯判斷:
public void multiKeys() {
for (Integer key : InitProcessor.keys) {
int keyCode = key;
//空格鍵
if (keyCode == KeyEvent.VK_SPACE) {
}
// 方向左鍵
else if (keyCode == KeyEvent.VK_LEFT) {
}
// 射擊
else if (keyCode == KeyEvent.VK_S) {
}
}
}然后在我們釋放按鍵的時(shí)候,使用如下的方式將這個(gè)set的key釋放掉:
/**
* description: 釋放按鍵
* @param e
* @return: void
* @author: weirx
* @time: 2022/1/11 15:39
*/
@Override
public void keyReleased(KeyEvent e) {
//按鈕釋放,則將該事件移除
InitProcessor.keys.remove(e.getKeyCode());
}動(dòng)作監(jiān)聽(tīng)器【ActionListener】
這個(gè)是整個(gè)畫(huà)面能夠動(dòng)態(tài)呈現(xiàn)的引擎,我們使用定時(shí)器的方式,每到定時(shí)時(shí)間則會(huì)監(jiān)聽(tīng)到動(dòng)作事件,進(jìn)行數(shù)據(jù)邏輯判斷。
其接口如下:
public interface ActionListener extends EventListener {
/**
* 當(dāng)事件發(fā)生時(shí)
*/
public void actionPerformed(ActionEvent e);
}定時(shí)器定義:
/** * 定時(shí)器 */ private Timer timer = new Timer(15, this);
接口actionPerformed部分代碼展示:
/**
* description: 定時(shí)器回調(diào)位置
* @param e
* @return: void
* @author: weirx
* @time: 2022/1/11 15:38
*/
@Override
public void actionPerformed(ActionEvent e) {
// 當(dāng)前年獸向右移動(dòng)的情況
if (InitProcessor.LEFT.equals(InitProcessor.moveDirection)) {
// 被擊中,換方向
if (InitProcessor.hit) {
InitProcessor.moveDirection = InitProcessor.RIGHT;
}
// 判斷移動(dòng)到邊界
if (InitProcessor.nian_x > 30) {
InitProcessor.nian_x -= InitProcessor.moveSpeed * 2;
} else {
InitProcessor.moveDirection = InitProcessor.RIGHT;
InitProcessor.nian_x += InitProcessor.moveSpeed * 2;
}
} else {
// 被擊中,換方向
if (InitProcessor.hit) {
InitProcessor.moveDirection = InitProcessor.LEFT;
}
// 當(dāng)前年獸向左移動(dòng)的情況
// 判斷移動(dòng)到邊界
if (InitProcessor.nian_x < 640) {
InitProcessor.nian_x += InitProcessor.moveSpeed * 2;
} else {
InitProcessor.moveDirection = InitProcessor.LEFT;
InitProcessor.nian_x -= InitProcessor.moveSpeed * 2;
}
}
//設(shè)置煙火的展示時(shí)間,定時(shí)器刷新50次,不準(zhǔn)確,但是至少能明顯感受到煙花存在
if (InitProcessor.hitShow == 50) {
InitProcessor.hit = false;
InitProcessor.hitShow = 0;
}
// 自增展示次數(shù)
InitProcessor.hitShow++;
// 刷新頁(yè)面
repaint();
timer.start();//啟動(dòng)計(jì)時(shí)器
}到以上為止,按鍵事件,和定時(shí)器事件都完成了,可以說(shuō)全部的邏輯判斷都在上面去實(shí)現(xiàn)。下面我們關(guān)注在圖像是如何出現(xiàn)的。
圖像展示基礎(chǔ)【JComponent】
前面我們似乎沒(méi)有看到這個(gè)組件的身影,那么它是在哪里呢?看下面的類圖:

如上所示,GamePanel繼承JPanel,而JPanel又繼承了JComponent。JComponent有一個(gè)方法我們需要重寫(xiě),這也就是我們實(shí)現(xiàn)圖像展示的方法,其提供了繪制UI的能力,我們重寫(xiě)即可,部分代碼如下:
/**
* description: 畫(huà)頁(yè)面
*
* @param g
* @return: void
* @author: weirx
* @time: 2022/1/10 13:40
*/
@Override
protected void paintComponent(Graphics g) {
// 清屏效果
super.paintComponent(g);
// 游戲未開(kāi)始
if (!InitProcessor.isStared) {
background.paintIcon(this, g,0,0);
InitProcessor.nian.paintIcon(this, g, 250, 130);
InitProcessor.tiger.paintIcon(this, g, 220, 470);
// 繪制首頁(yè)
// 設(shè)置游戲文字
g.setColor(Color.ORANGE);
g.setFont(new Font("幼圓", Font.BOLD, 50));
g.drawString("年獸大作戰(zhàn)", 325, 550);
// 設(shè)置開(kāi)始提示
g.setColor(Color.GREEN);
g.setFont(new Font("幼圓", Font.BOLD, 30));
g.drawString("按【空格】鍵開(kāi)始游戲", 300, 620);
g.drawString("按【←】【→】鍵移動(dòng)", 300, 660);
g.drawString("按【S】鍵發(fā)射炮彈", 300, 700);
} else if (isGameOver) {
//輸出gameover
InitProcessor.gameOver.paintIcon(this, g, 10, 10);
// 設(shè)置開(kāi)始提示
g.setColor(Color.GREEN);
g.setFont(new Font("幼圓", Font.BOLD, 20));
g.drawString("按【空格】再次開(kāi)始游戲", 340, 600);
}
}關(guān)鍵點(diǎn)是使用Graphics繪制文字,背景,顏色等等內(nèi)容。
圖片需要使用ImageIcon類來(lái)進(jìn)行繪畫(huà),我將ImageIcon初始化部分封裝了,所以上面沒(méi)顯示,常規(guī)使用如下:
ImageIcon nian = new ImageIcon(PATH_PREFIX + "nian.png"); nian.paintIcon(this, g, 250, 130);
2.4 游戲的血液【InitProcessor】
為什么這么說(shuō)是血液呢?因?yàn)檫@個(gè)類是我自己實(shí)現(xiàn)的一個(gè)初始化類,其中的內(nèi)容是串聯(lián)整個(gè)游戲的關(guān)鍵點(diǎn),像身體的血液一樣。
通過(guò)寫(xiě)這個(gè)游戲,我發(fā)現(xiàn)最關(guān)鍵的點(diǎn)在于【狀態(tài)】,可以說(shuō)全部的頁(yè)面動(dòng)畫(huà)展示都在于一個(gè)狀態(tài),無(wú)論是子彈的運(yùn)動(dòng),年獸的運(yùn)動(dòng),包括禮花圖片的切換,以及各種圖片的坐標(biāo)等等。
所以我專門(mén)抽象了這個(gè)類,用于各種狀態(tài)的初始化,部分代碼如下:
/**
* @description: 初始化處理器
* @author:weirx
* @date:2022/1/11 10:15
* @version:3.0
*/
public class InitProcessor {
/**
* 游戲是否開(kāi)始,默認(rèn)是false
*/
public static Boolean isStared = false;
/**
* 游戲是否暫停,默認(rèn)是false
*/
public static Boolean isStopped = false;
/**
* 禮花橫坐標(biāo)
*/
public static int youWillBeKill_x = 0;
/**
* 禮花縱坐標(biāo)
*/
public static int youWillBeKill_y = nian_y + 200;
/**
* 展示炮彈
*/
public static Boolean showYouWillBeKill = false;
public static Boolean isGameOver = false;
/**
* 圖片路徑
*/
public final static String PATH_PREFIX = "src/main/java/com/wjbgn/nianfight/pic/";
public static ImageIcon nian = new ImageIcon(PATH_PREFIX + "nian.png");
public static ImageIcon tiger = new ImageIcon(PATH_PREFIX + "tiger\tiger2.png");
public static ImageIcon heart = new ImageIcon(PATH_PREFIX + "blood\heart.png");
/**
* 禮花容器
*/
public static List<FireworksDO> fireworksDOS = initFireworks();
/**
* 花容器
*/
public static List<FlowersDO> flowersDOS = initFlowers();
/**
* 初始化爆竹
*/
private static List<FlowersDO> initFlowers() {
List<FlowersDO> list = new ArrayList<>();
list.add(new FlowersDO(1, PATH_PREFIX + "flowers\flower1.png"));
list.add(new FlowersDO(2, PATH_PREFIX + "fireworks\flower2.png"));
list.add(new FlowersDO(3, PATH_PREFIX + "fireworks\flower3.png"));
list.add(new FlowersDO(4, PATH_PREFIX + "fireworks\flower4.png"));
list.add(new FlowersDO(5, PATH_PREFIX + "fireworks\flower5.png"));
list.add(new FlowersDO(6, PATH_PREFIX + "fireworks\flower6.png"));
list.add(new FlowersDO(7, PATH_PREFIX + "fireworks\flower7.png"));
list.add(new FlowersDO(8, PATH_PREFIX + "fireworks\flower8.png"));
list.add(new FlowersDO(9, PATH_PREFIX + "fireworks\flower9.png"));
list.add(new FlowersDO(10, PATH_PREFIX + "fireworks\flower10.png"));
list.add(new FlowersDO(11, PATH_PREFIX + "fireworks\flower11.png"));
return list;
}
/**
* description: 初始化禮花種類
*
* @return: void
* @author: weirx
* @time: 2022/1/11 10:58
*/
public static List<FireworksDO> initFireworks() {
List<FireworksDO> list = new ArrayList<>();
list.add(new FireworksDO(1, PATH_PREFIX + "fireworks\fireworks1.png"));
list.add(new FireworksDO(2, PATH_PREFIX + "fireworks\fireworks2.png"));
list.add(new FireworksDO(3, PATH_PREFIX + "fireworks\fireworks3.png"));
list.add(new FireworksDO(4, PATH_PREFIX + "fireworks\fireworks4.png"));
list.add(new FireworksDO(5, PATH_PREFIX + "fireworks\fireworks5.png"));
list.add(new FireworksDO(6, PATH_PREFIX + "fireworks\fireworks6.png"));
list.add(new FireworksDO(7, PATH_PREFIX + "fireworks\fireworks7.png"));
list.add(new FireworksDO(8, PATH_PREFIX + "fireworks\fireworks8.png"));
list.add(new FireworksDO(9, PATH_PREFIX + "fireworks\fireworks9.png"));
list.add(new FireworksDO(10, PATH_PREFIX + "fireworks\fireworks10.png"));
list.add(new FireworksDO(11, PATH_PREFIX + "fireworks\fireworks11.png"));
return list;
}
/**
* description: 初始化方法,用于重新開(kāi)始游戲
*
* @return: void
* @author: weirx
* @time: 2022/1/11 10:39
*/
public static void init() {
isStared = false;
isStopped = false;
attack = false;
nian_x = 325;
nian_y = 50;
tiger_x = 325;
tiger_y = 660;
bullet_x = tiger_x + 20;
bullet_y = tiger_y - 20;
moveSpeed = 1;
moveDirection = LEFT;
hit = false;
hitCount = 0;
hitShow = 0;
nianBlood = 10;
success = false;
keys = new HashSet<>();
fireworks_x = 0;
fireworks_y = nian_y + 200;
showFireworks = false;
currentFireworks = null;
takeFireworks = false;
currentFlowers = null;
youWillBeKill = 0;
youWillBeKill_x = 0;
youWillBeKill_y = nian_y + 200;
showYouWillBeKill = false;
isGameOver = false;
}
}2.5 實(shí)體類【FireworksDO】【FlowersDO】
這是兩個(gè)實(shí)體類,分別定義的爆竹和禮花樣式,用于初始化,代碼如下:
import javax.swing.*;
import static com.wjbgn.nianfight.nianshou.InitProcessor.PATH_PREFIX;
/**
* @description: 花容器
* @author:weirx
* @date:2022/1/11 11:05
* @version:3.0
*/
public class FlowersDO {
private Integer id;
private String path;
private ImageIcon flower;
public ImageIcon getFlower() {
return flower;
}
public void setFlower(ImageIcon flower) {
this.flower = flower;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public FlowersDO(Integer id, String path) {
this.id = id;
this.path = path;
this.flower = new ImageIcon(PATH_PREFIX + "flowers\flower" + id + ".png");
}
}/**
* @description: 禮花實(shí)體類
* @author:weirx
* @date:2022/1/11 11:01
* @version:3.0
*/
public class FireworksDO {
/**
* id
*/
private Integer id;
/**
* 圖片路徑
*/
private String path;
public ImageIcon getFirework() {
return firework;
}
public void setFirework(ImageIcon firework) {
this.firework = firework;
}
private ImageIcon firework;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public FireworksDO(Integer id, String path) {
this.id = id;
this.path = path;
this.firework = new ImageIcon(PATH_PREFIX + "fireworks\fireworks" + id + ".png");
}
}2.6 圖片素材
游戲中使用了大量的圖片素材,我在網(wǎng)上找了兩個(gè)網(wǎng)站不錯(cuò),一個(gè)是png圖片網(wǎng)站,是免費(fèi)的,還有一個(gè)是免費(fèi)切圖的,挺好用,都分享給大家
免費(fèi)png圖片網(wǎng)址(英文):www.cleanpng.com/

- 免費(fèi)切圖網(wǎng)址(簡(jiǎn)單版直接微信關(guān)注):www.uupoop.com/

我使用的素材都在項(xiàng)目的pic目錄下:

三、總結(jié)
寫(xiě)java好幾年了,其實(shí)從沒(méi)有使用 javax.swing 和 java.awt 包下面的內(nèi)容開(kāi)發(fā)過(guò)代碼,對(duì)于現(xiàn)在用戶體驗(yàn)為前提的大環(huán)境下,綜合編碼體驗(yàn),和游戲運(yùn)行體驗(yàn)來(lái)看,確實(shí)是不太友好,不太符合環(huán)境背景。但是也是一次不錯(cuò)的學(xué)習(xí)過(guò)程。
問(wèn)題總結(jié)
目前整個(gè)游戲還是存在一些bug的,后面有時(shí)間再翻出來(lái)調(diào)試吧,此處先記錄一下:
- 子彈有時(shí)并不是從老虎正前方射出的,與實(shí)際的坐標(biāo)存在偏差:此問(wèn)題出現(xiàn)的原因我推測(cè)來(lái)自線程間共享變量的同步問(wèn)題。子彈的初始橫坐標(biāo)取決于小老虎當(dāng)前所在的橫坐標(biāo),這個(gè)坐標(biāo)同步?jīng)]做好。
- 關(guān)于按鍵切換、同時(shí)兩個(gè)按鍵等情況造成卡頓的問(wèn)題:前面解決兩個(gè)按鍵同時(shí)按下的方案可能不是最優(yōu)解,后面還需要優(yōu)化。
- 怪獸扔炸彈、爆竹隨著年獸移動(dòng)存在偏移:炸彈和爆竹的初始橫坐標(biāo),是年獸當(dāng)前的橫坐標(biāo),需要一個(gè)變量記錄當(dāng)前年獸的位置,作為炸彈和爆竹的初始橫坐標(biāo)。
關(guān)于游戲就說(shuō)這么多了,感興趣的去文章開(kāi)篇的gitee下載源碼就行了。
以上就是Java實(shí)現(xiàn)年獸大作戰(zhàn)游戲詳解的詳細(xì)內(nèi)容,更多關(guān)于Java年獸大作戰(zhàn)游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring cloud-給Eureka Server加上安全的用戶認(rèn)證詳解
這篇文章主要介紹了spring cloud-給Eureka Server加上安全的用戶認(rèn)證詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Java三目運(yùn)算符的實(shí)戰(zhàn)案例
三目運(yùn)算符在java中運(yùn)用可以說(shuō)非常的廣泛,下面這篇文章主要給大家介紹了關(guān)于Java三目運(yùn)算符的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
mybatis-plus批處理IService的實(shí)現(xiàn)示例
這篇文章主要介紹了mybatis-plus批處理IService的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
詳解Java中的線程讓步y(tǒng)ield()與線程休眠sleep()方法
Java中的線程讓步會(huì)讓線程讓出優(yōu)先級(jí),而休眠則會(huì)讓線程進(jìn)入阻塞狀態(tài)等待被喚醒,這里我們對(duì)比線程等待的wait()方法,來(lái)詳解Java中的線程讓步y(tǒng)ield()與線程休眠sleep()方法2016-07-07
Java實(shí)戰(zhàn)之在線寄查快遞系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java制作一個(gè)在線寄查快遞系統(tǒng),文中采用的技術(shù)有java、SpringBoot、FreeMarker、Mysql,需要的可以參考一下2022-02-02
Spring?Boot集成JavaMailSender發(fā)送郵件功能的實(shí)現(xiàn)
spring提供了發(fā)送郵件的接口JavaMailSender,通過(guò)JavaMailSender可以實(shí)現(xiàn)后端發(fā)送郵件,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot集成JavaMailSender發(fā)送郵件功能的相關(guān)資料,需要的朋友可以參考下2022-05-05

