vue+java实现语音转文字思路

思路:

前端录音生成wav文件后端去解析

技术:

后端:
Vosk是一个离线开源语音识别工具。它可以识别16种语言,包括中文。
API接口,让您可以只用几行代码,即可迅速免费调用、体验功能。
目前支持 WAV声音文件格式,支持中英文等18种语言。
前端:
js-audio-recorder 录音组件

资料:

下载vosk语言模型:
在这里插入图片描述

springboot整合vosk实现简单的语音识别功能
javaswing窗体

问题:

就是录音组件会要求后端使用https协议,生产环境必须将后端http转https,测试环境中有以下两种方法第一种录音只能在(http://localhost:项目端口)中使用,第二种使用谷歌配置网站具有使用录音权限

后端:

在这里插入图片描述

依赖:

  <!-- 语音识别 --><!-- 获取音频信息 --><dependency><groupId>org</groupId><artifactId>jaudiotagger</artifactId><version>2.0.3</version></dependency><dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId><version>5.13.0</version></dependency><dependency><groupId>com.alphacephei</groupId><artifactId>vosk</artifactId><version>0.3.45</version></dependency><!-- JAVE2(Java音频视频编码器)库是ffmpeg项目上的Java包装器。 --><dependency><groupId>ws.schild</groupId><artifactId>jave-core</artifactId><version>3.1.1</version></dependency><!-- 在windows上开发 开发机可实现压缩效果 window64位 --><dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-win32</artifactId><version>3.1.1</version></dependency><dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-win64</artifactId><version>3.1.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency>

代码(我会按代码的调用顺序展示)

package com.rouyi.yuyin.model;public class VoskResult {private String text;public String getText() {return text;}public void setText(String text) {this.text = text;}}

vosk模型加载(将你从官网下的语言模型放到你的项目中并解压,修改下面的modelStr的值)
在这里插入图片描述

package com.rouyi.yuyin.model;import org.vosk.LibVosk;
import org.vosk.LogLevel;
import org.vosk.Model;import java.io.IOException;/*** vosk模型加载* @author zhou*/
public class VoskModel {/*** 3. 使用 volatile 保证线程安全* 禁止指令重排* 保证可见性* 不保证原子性*/private static volatile VoskModel instance;private Model voskModel;public Model getVoskModel() {return voskModel;}/*** 1.私有构造函数*/private VoskModel() {System.out.println("SingleLazyPattern实例化了");//String modelStr = "D:\\work\\project\\fjdci-vosk\\src\\main\\resources\\vosk-model-small-cn-0.22";String modelStr = "H:\\afterProject\\qiyedianzixuke\\RuoYi-Cloud\\ruoyi-modules\\yuyinshibie\\src\\main\\resources\\lib\\vosk-model-cn-0.22";try {voskModel = new Model(modelStr);LibVosk.setLogLevel(LogLevel.INFO);} catch (IOException e) {e.printStackTrace();}}/*** 2.通过静态方法获取一个唯一实例* DCL 双重检查锁定 (Double-CheckedLocking)* 在多线程情况下保持⾼性能*/public static VoskModel getInstance() {if (instance == null) {synchronized (VoskModel.class) {if (instance == null) {// 1. 分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间instance = new VoskModel();}}}return instance;}/*** 多线程测试加载* @param args*/public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(() -> {VoskModel.getInstance();}).start();}}}
package com.rouyi.yuyin.model;import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.InputFormatException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;import java.io.File;public class Jave2Util {/*** @param src      来源文件路径* @param target   目标文件路径* @param offset   设置起始偏移量(秒)* @param duration 设置切片的音频长度(秒)* @throws EncoderException*/public static void cut(String src, String target, Float offset, Float duration) throws EncoderException {File targetFile = new File(target);if (targetFile.exists()) {targetFile.delete();}File srcFile = new File(src);MultimediaObject srcMultiObj = new MultimediaObject(srcFile);MultimediaInfo srcMediaInfo = srcMultiObj.getInfo();Encoder encoder = new Encoder();EncodingAttributes encodingAttributes = new EncodingAttributes();//设置起始偏移量(秒)encodingAttributes.setOffset(offset);//设置切片的音频长度(秒)encodingAttributes.setDuration(duration);// 输入格式encodingAttributes.setInputFormat("wav");//设置音频属性AudioAttributes audio = new AudioAttributes();audio.setBitRate(srcMediaInfo.getAudio().getBitRate());//audio.setSamplingRate(srcMediaInfo.getAudio().getSamplingRate());// 转换为16KHZ 满足vosk识别的标准audio.setSamplingRate(16000);audio.setChannels(srcMediaInfo.getAudio().getChannels());//如果截取的时候,希望同步调整编码,可以设置不同的编码
//        audio.setCodec("pcm_u8");//audio.setCodec(srcMediaInfo.getAudio().getDecoder().split(" ")[0]);encodingAttributes.setAudioAttributes(audio);//写文件encoder.encode(srcMultiObj, new File(target), encodingAttributes);}/*** 转化音频格式** @param oldFormatPath : 原音乐路径* @param newFormatPath : 目标音乐路径* @return*/public static boolean transforMusicFormat(String oldFormatPath, String newFormatPath) {File source = new File(oldFormatPath);File target = new File(newFormatPath);// 音频转换格式类Encoder encoder = new Encoder();// 设置音频属性AudioAttributes audio = new AudioAttributes();audio.setCodec(null);// 设置转码属性EncodingAttributes attrs = new EncodingAttributes();attrs.setInputFormat("wav");attrs.setAudioAttributes(audio);try {encoder.encode(new MultimediaObject(source), target, attrs);System.out.println("传唤已完成...");return true;} catch (IllegalArgumentException e) {e.printStackTrace();} catch (InputFormatException e) {e.printStackTrace();} catch (EncoderException e) {e.printStackTrace();}return false;}public static void main(String[] args) throws EncoderException {String src = "D:\\fjFile\\annex\\xwbl\\ly8603f22f24e0409fa9747d50a78ff7e5.wav";String target = "D:\\fjFile\\annex\\xwbl\\tem_2.wav";Jave2Util.cut(src, target, 0.0F, 60.0F);String inputFormatPath = "D:\\fjFile\\annex\\xwbl\\ly8603f22f24e0409fa9747d50a78ff7e5.m4a";String outputFormatPath = "D:\\fjFile\\annex\\xwbl\\ly8603f22f24e0409fa9747d50a78ff7e5.wav";info(inputFormatPath);// audioEncode(inputFormatPath, outputFormatPath);}/*** 获取音频文件的编码信息** @param filePath* @throws EncoderException*/private static void info(String filePath) throws EncoderException {File file = new File(filePath);MultimediaObject multimediaObject = new MultimediaObject(file);MultimediaInfo info = multimediaObject.getInfo();// 时长long duration = info.getDuration();String format = info.getFormat();// format:movSystem.out.println("format:" + format);AudioInfo audio = info.getAudio();// 它设置将在重新编码的音频流中使用的音频通道数(1 =单声道,2 =立体声)。如果未设置任何通道值,则编码器将选择默认值。int channels = audio.getChannels();// 它为新的重新编码的音频流设置比特率值。如果未设置比特率值,则编码器将选择默认值。// 该值应以每秒位数表示。例如,如果您想要128 kb / s的比特率,则应调用setBitRate(new Integer(128000))。int bitRate = audio.getBitRate();// 它为新的重新编码的音频流设置采样率。如果未设置采样率值,则编码器将选择默认值。该值应以赫兹表示。例如,如果您想要类似CD// 采样率、音频采样级别 16000 = 16KHzint samplingRate = audio.getSamplingRate();// 设置音频音量// 可以调用此方法来更改音频流的音量。值为256表示音量不变。因此,小于256的值表示音量减小,而大于256的值将增大音频流的音量。// setVolume(Integer volume)String decoder = audio.getDecoder();System.out.println("声音时长:毫秒" + duration);System.out.println("声道:" + channels);System.out.println("bitRate:" + bitRate);System.out.println("samplingRate 采样率、音频采样级别 16000 = 16KHz:" + samplingRate);// aac (LC) (mp4a / 0x6134706D)System.out.println("decoder:" + decoder);}/*** 音频格式转换* @param inputFormatPath* @param outputFormatPath* @return*/public static boolean audioEncode(String inputFormatPath, String outputFormatPath) {String outputFormat = getSuffix(outputFormatPath);String inputFormat = getSuffix(inputFormatPath);File source = new File(inputFormatPath);File target = new File(outputFormatPath);try {MultimediaObject multimediaObject = new MultimediaObject(source);// 获取音频文件的编码信息MultimediaInfo info = multimediaObject.getInfo();AudioInfo audioInfo = info.getAudio();//设置音频属性AudioAttributes audio = new AudioAttributes();audio.setBitRate(audioInfo.getBitRate());audio.setSamplingRate(audioInfo.getSamplingRate());audio.setChannels(audioInfo.getChannels());// 设置转码属性EncodingAttributes attrs = new EncodingAttributes();attrs.setInputFormat(inputFormat);attrs.setOutputFormat(outputFormat);attrs.setAudioAttributes(audio);// 音频转换格式类Encoder encoder = new Encoder();// 进行转换encoder.encode(new MultimediaObject(source), target, attrs);return true;} catch (IllegalArgumentException | EncoderException e) {e.printStackTrace();}return false;}/*** 获取文件路径的.后缀* @param outputFormatPath* @return*/private static String getSuffix(String outputFormatPath) {return outputFormatPath.substring(outputFormatPath.lastIndexOf(".") + 1);}}

修改wavFilePath的值为你的wav格式的文件所在路径,wav文件可以自行使用手机自带的录音功能去生成最后点击main方法就可以测试了,如果需要与前台对接请自行修改为接口

package com.rouyi.yuyin.model;import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.vosk.Model;
import org.vosk.Recognizer;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;@Slf4j
@Component
public class VoiceUtil {public static void main(String[] args) throws EncoderException {String wavFilePath = "H:\\afterProject\\qiyedianzixuke\\RuoYi-Cloud\\ruoyi-modules\\yuyinshibie\\src\\main\\resources\\audio\\11月7日 下午4点10分.wav";// 秒long cutDuration = 20;String waveForm = acceptWaveForm( wavFilePath, cutDuration);System.out.println( waveForm );}/*** 对Wav格式音频文件进行语音识别翻译** @param wavFilePath* @param cutDuration* @return* @throws EncoderException*/private static String acceptWaveForm(String wavFilePath, long cutDuration) throws EncoderException {// 判断视频的长度long startTime = System.currentTimeMillis();MultimediaObject multimediaObject = new MultimediaObject(new File(wavFilePath));MultimediaInfo info = multimediaObject.getInfo();// 时长/毫秒long duration = info.getDuration();AudioInfo audio = info.getAudio();// 通道数int channels = audio.getChannels();// 秒long offset = 0;long forNum = (duration / 1000) / cutDuration;if (duration % (cutDuration * 1000) > 0) {forNum = forNum + 1;}// 进行切块处理List<String> strings = cutWavFile(wavFilePath, cutDuration, offset, forNum);// 循环进行翻译StringBuilder result = new StringBuilder();for (String string : strings) {File f = new File(string);result.append(VoiceUtil.getRecognizerResult(f, channels));}long endTime = System.currentTimeMillis();String msg = "耗时:" + (endTime - startTime) + "ms";System.out.println(msg);return result.toString();}/*** 对wav进行切块处理** @param wavFilePath 处理的wav文件路径* @param cutDuration 切割的固定长度/秒* @param offset      设置起始偏移量(秒)* @param forNum      切块的次数* @return* @throws EncoderException*/private static List<String> cutWavFile(String wavFilePath, long cutDuration, long offset, long forNum) throws EncoderException {UUID uuid = UUID.randomUUID();// 大文件切割为固定时长的小文件List<String> strings = new ArrayList<>();for (int i = 0; i < forNum; i++) {String target = "D:\\fjFile\\annex\\xwbl\\" + uuid + "\\" + i + ".wav";Float offsetF = Float.valueOf(String.valueOf(offset));Float cutDurationF = Float.valueOf(String.valueOf(cutDuration));Jave2Util.cut(wavFilePath, target, offsetF, cutDurationF);offset = offset + cutDuration;strings.add(target);}return strings;}/*** 进行翻译** @param f* @param channels*/public static String getRecognizerResult(File f, int channels) {StringBuilder result = new StringBuilder();Model voskModel = VoskModel.getInstance().getVoskModel();// 采样率为音频采样率的声道倍数log.info("====加载完成,开始分析====");try (Recognizer recognizer = new Recognizer(voskModel, 16000 * channels);InputStream ais = new FileInputStream(f)) {int nbytes;byte[] b = new byte[4096];while ((nbytes = ais.read(b)) >= 0) {if (recognizer.acceptWaveForm(b, nbytes)) {// 返回语音识别结果result.append(getResult(recognizer.getResult()));}}// 返回语音识别结果。和结果一样,但不要等待沉默。你通常在流的最后调用它来获得音频的最后部分。它刷新功能管道,以便处理所有剩余的音频块。result.append(getResult(recognizer.getFinalResult()));log.info("识别结果:{}", result.toString());} catch (Exception e) {e.printStackTrace();}return result.toString();}/*** 获取返回结果** @param result* @return*/private static String getResult(String result) {VoskResult vr=JSON.parseObject(result,VoskResult.class);return  Optional.ofNullable(vr).map(VoskResult::getText).orElse("");}}

在这里插入图片描述

vue:

这里呢我前端也没整完前端这里生成录音后传给后台就可以了,后台用上面的Java代码一解析,别说我懒,做东西还不做完美,想啥呢搬砖很累的哈哈!!!

<template><div style="padding: 20px;"><h3>录音上传</h3><div style="font-size:14px"><h3>录音时长:{{ recorder && recorder.duration.toFixed(4) }}</h3><br /><el-button type="primary" @click="handleStart">开始录音</el-button><el-button type="info" @click="handlePause">暂停录音</el-button><el-button type="success" @click="handleResume">继续录音</el-button><el-button type="warning" @click="handleStop">停止录音</el-button><el-button type="error" @click="handleDestroy">销毁录音</el-button><el-button type="primary" @click="uploadRecord">上传</el-button><!-- <br /><br /><h3>播放时长:{{recorder &&(playTime > recorder.duration? recorder.duration.toFixed(4): playTime.toFixed(4))}}</h3><br /><el-button type="primary" @click="handlePlay">播放录音</el-button><el-button type="info" @click="handlePausePlay">暂停播放</el-button><el-button type="success" @click="handleResumePlay">继续播放</el-button><el-button type="warning" @click="handleStopPlay">停止播放</el-button><el-button type="error" @click="handleDestroy">销毁录音</el-button><el-button type="primary" @click="uploadRecord">上传</el-button> --></div></div>
</template><script>import Recorder from 'js-audio-recorder'export default {data() {return {recorder: null,playTime: 0,timer: null,src: null}},created() {this.recorder = new Recorder()},methods: {// 开始录音handleStart() {this.recorder = new Recorder()Recorder.getPermission().then(() => {console.log('开始录音')this.recorder.start() // 开始录音}, (error) => {this.$message({message: '请先允许该网页使用麦克风',type: 'info'})console.log(`${error.name} : ${error.message}`)})},handlePause() {console.log('暂停录音')this.recorder.pause() // 暂停录音},handleResume() {console.log('恢复录音')this.recorder.resume() // 恢复录音},handleStop() {console.log('停止录音')this.recorder.stop() // 停止录音},handlePlay() {console.log('播放录音')console.log(this.recorder)this.recorder.play() // 播放录音// 播放时长this.timer = setInterval(() => {try {this.playTime = this.recorder.getPlayTime()} catch (error) {this.timer = null}}, 100)},handlePausePlay() {console.log('暂停播放')this.recorder.pausePlay() // 暂停播放// 播放时长this.playTime = this.recorder.getPlayTime()this.time = null},handleResumePlay() {console.log('恢复播放')this.recorder.resumePlay() // 恢复播放// 播放时长this.timer = setInterval(() => {try {this.playTime = this.recorder.getPlayTime()} catch (error) {this.timer = null}}, 100)},handleStopPlay() {console.log('停止播放')this.recorder.stopPlay() // 停止播放// 播放时长this.playTime = this.recorder.getPlayTime()this.timer = null},handleDestroy() {console.log('销毁实例')this.recorder.destroy() // 毁实例this.timer = null},uploadRecord() {if (this.recorder == null || this.recorder.duration === 0) {this.$message({message: '请先录音',type: 'error'})return false}this.recorder.pause() // 暂停录音this.timer = nullconsole.log('上传录音')// 上传录音const formData = new FormData()const blob = this.recorder.getWAVBlob()// 获取wav格式音频数据// 此处获取到blob对象后需要设置fileName满足当前项目上传需求,其它项目可直接传把blob作为file塞入formDataconst newbolb = new Blob([blob], { type: 'audio/wav' })const fileOfBlob = new File([newbolb], new Date().getTime() + '.wav')formData.append('file', fileOfBlob)const url = window.URL.createObjectURL(fileOfBlob)this.src = url// const axios = require('axios')// axios.post(url, formData).then(res => {//console.log(res.data.data[0].url)// })}}}</script>

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于8086家具门安全控制系统设计

**单片机设计介绍&#xff0c;基于8086家具门安全控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 # 8086家具门安全控制系统设计介绍 8086家具门安全控制系统是一种用于保护家具和保证室内安全的系统。该系统基于808…

小程序游戏对接广告收益微信小游戏抖音游戏软件

小程序游戏对接广告是一种常见的游戏开发模式&#xff0c;开发者可以通过在游戏中嵌入广告来获取收益。以下是一些与小程序游戏对接广告收益相关的关键信息&#xff1a; 小程序游戏广告平台选择&#xff1a; 选择适合你的小程序游戏的广告平台非常重要。不同的平台提供不同类型…

ubuntu18-recvfrom接收不到广播报文异常分析

目录 前言 一、UDP广播接收程序 二、异常原因分析 总结 前言 在ubuntu18.04系统中&#xff0c;编写udp接收程序发现接收不到广播报文&#xff0c;使用抓包工具tcpdump可以抓取到广播报文&#xff0c;在此对该现象分析解析如下文所示。 一、UDP广播接收程序 UDP广播接收程序如…

【解决方案】vue 项目 npm run dev 时报错:‘cross-env‘ 不是内部或外部命令,也不是可运行的程序

报错 cross-env 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! estate1.0.0 dev: cross-env webpack-dev-server --inline --progress --config build/webpack.dev.conf.js npm ERR! Exit status 1 np…

Go语言用Colly库编写的图像爬虫程序

下面是一个使用Colly库编写的Go语言图像爬虫程序&#xff0c;该程序会爬取news.qq上的图片&#xff0c;并使用proxy_host:duoip和proxy_port:8000的爬虫IP服务器进行抓取。 package mainimport ("fmt""net/http""github.com/crawlab-collective/go-co…

21 移动网络的前世今生

1、移动网络的发展历程 发展过程就是&#xff1a;2G,3G,4G,5G的过程&#xff0c;用2G看txt&#xff0c;用3G看jpg&#xff0c;用4G看avi。 2、2G网络 手机本来是用来打电话的&#xff0c;不是用来上网的&#xff0c;所以原来在2G时代&#xff0c;上网使用的不是IP网络&#…

论文阅读——What Can Human Sketches Do for Object Detection?(cvpr2023)

论文&#xff1a;https://openaccess.thecvf.com/content/CVPR2023/papers/Chowdhury_What_Can_Human_Sketches_Do_for_Object_Detection_CVPR_2023_paper.pdf 代码&#xff1a;What Can Human Sketches Do for Object Detection? (pinakinathc.me) 一、 Baseline SBIR Fram…

【Qt之QAssociativeIterable】使用

介绍 QAssociativeIterable类是QVariant中一个关联式容器的可迭代接口。这个类允许多种访问在QVariant中保存的关联式容器元素的方法。如果一个QVariant可以转换为QVariantHash或QVariantMap&#xff0c;那么QAssociativeIterable的实例可以从中提取出来。 QHash<int, QSt…

UPLOAD-LABS1

less1 (js验证) 我们上传PHP的发现不可以&#xff0c;只能是jpg&#xff0c;png&#xff0c;gif&#xff08;白名单限制了&#xff09; 我们可以直接去修改限制 在查看器中看到使用了onsubmit这个函数&#xff0c;触发了鼠标的单击事件&#xff0c;在表单提交后马上调用了re…

css排版—— 一篇优雅的文章(中英文) vs 聊天框的特别排版

文章 <div class"contentBox"><p>这是一篇范文——仅供测试使用</p><p>With the coming of national day, I have a one week holiday. I reallyexpect to it, because it want to have a short trip during these days. Iwill travel to Ji…

osgEarth之添加shp

目录 效果 代码 代码分析 加载模式 效果 代码 #include "stdafx.h" #include <osg/Notify> #include <osgGA/StateSetManipulator> #include <osgViewer/Viewer> #include <osgViewer/ViewerEventHandlers>#include <osgEarth/MapNo…

蓝桥杯练习

即约分数 题目 思路 遍历所有的x&#xff0c;y&#xff0c;判断x/y是不是即越约分数。 代码 #include <iostream> using namespace std; int gcd(int x,int y) {int r;while(y!0){rx%y;xy;yr;}return x; } int main() {// 请在此输入您的代码int sum4039;//1/y和x/1都…

火爆全网!用 Pyecharts 就能做出来“迁徙图“和“轮播图“

1.pyecharts知识点回顾 1&#xff09;知识回顾 前面我们已经讲述了&#xff0c;如何使用pyecharts进行图形的绘制&#xff0c;一共涉及到如下四步。我们今天就是按照下面这几步来进行迁徙图和轮播图的绘制。 ① 选择图表类型&#xff1b; ② 声明图形类并添加数据&#xff1…

学术论文的实证数据来源

一、引言 在当今的学术研究中&#xff0c;数据是至关重要的。无论是自然科学、社会科学还是人文科学&#xff0c;都需要借助数据来支撑和证明其研究假设和理论。然而&#xff0c;数据的来源却是多种多样的&#xff0c;而且不同的学科领域也有其特定的数据来源。本文旨在探讨论文…

开放智慧,助力学习——电大搜题,打开学无止境的新篇章

随着信息技术的迅猛发展&#xff0c;学习已经不再受时间和空间的限制。电大搜题微信公众号为广播电视大学和河南开放大学的学子们带来了便利和智慧&#xff0c;让学习变得更加高效和愉快。 电大搜题微信公众号作为一款专为电大学生而设计的学习助手&#xff0c;是学习中不可或…

【2】Spring Boot 3 项目搭建

目录 【2】Spring Boot 3 初始项目搭建项目生成1. 使用IDEA商业版创建2. 使用官方start脚手架创建 配置与启动Git版本控制 个人主页: 【⭐️个人主页】 需要您的【&#x1f496; 点赞关注】支持 &#x1f4af; 【2】Spring Boot 3 初始项目搭建 项目生成 1. 使用IDEA商业版创…

ppt聚光灯效果

1.放入三张图片内容或其他 2.全选复制成图片 3.设置黑色矩形&#xff0c;透明度30% 4.粘贴复制后的图片&#xff0c;制定图层 5.插入椭圆&#xff0c;先选中矩形&#xff0c;再选中椭圆&#xff0c;点击绘图工具&#xff0c;选择相交即可&#xff08;关键&#xff09;

全景房屋装修vr可视化编辑软件功能及特点

VR样板间、VR景观、VR商业街&#xff0c;全方位展示建筑内外空间使用及功能表現&#xff0c;让目标客戶能够身临其境体验項目的每处细节。 同时支持微信传播&#xff0c;线上看房&#xff0c;手机端VR沉浸式体验 3D互动售楼系统 3D互动售楼系统&#xff0c;集项目展示、智能选房…

设计模式是测试模式咩?

设计模式和测试模式概述 软件的生命周期为什么要进行测试&#xff08;测试的目的&#xff09;&#xff1f;软件的设计模式1. **瀑布模型**3. 增量和迭代模型4. 敏捷模型5. 喷泉模型 测试模型V模型W模型 一个应用程序从出生到“死亡”会经过非常漫长的流程…… 软件的生命周期 …

学习笔记4——JVM运行时数据区梳理

学习笔记系列开头惯例发布一些寻亲消息 链接&#xff1a;https://baobeihuijia.com/bbhj/contents/3/192489.html 类装载器classLoader&#xff1a; 将本地的字节码文件.class 加载到内存方法区中成为元数据模板&#xff08;两个class对象是否为同一个类要求&#xff1a;完整…