Teams集成-会议侧边栏应用开发-实时转写
Teams虽然提供了转写的接口,但是不是实时的,即便使用订阅事件也不是实时的,为了达到实时转写的效果,使用recall.ai的转录和assembly_ai的转写实现。
前提:除Teams会议侧边栏应用开发-会议转写-CSDN博客的基本要求外,还需要修改用户的安全设置及设置Teams 工作账号,参考:Setup Guide (recall.ai)
一、服务端需要实现4个服务端点:
1)开始录音(创建机器人)
/** Send's a Recall Bot to start recording the call*/
server.post('/start-recording', async (req, res) => {const meeting_url = req.body.meetingUrl;try {if (!meeting_url) {return res.status(400).json({ error: 'Missing meetingUrl' });}console.log('recall bot start recording', meeting_url);const url = 'https://us-west-2.recall.ai/api/v1/bot/';const options = {method: 'POST',headers: {accept: 'application/json','content-type': 'application/json',Authorization: `Token ${RECALL_API_KEY}`},body: JSON.stringify({bot_name: 'teams bot',real_time_transcription: {destination_url: 'https://shortly-adapted-akita.ngrok-free.app/transcription?secret=' + WEBHOOK_SECRET,partial_results: false},transcription_options: {provider: 'assembly_ai'},meeting_url: meeting_url})};const response = await fetch(url, options);const bot = await response.json();local_botId = bot.idconsole.log('botId:', local_botId);res.send(200, JSON.stringify({botId: local_botId}));} catch (error) {console.error("start-recoding error:", error);}
});
2)停止录音
/*
* Tells the Recall Bot to stop recording the call
*/
server.post('/stop-recording', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}/leave_call`, {method: 'POST',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});console.log('recall bot stopped');res.send(200, {})} catch (error) {console.error("stop-recoding error:", error);}
});
3)轮询机器人状态
/*
* Gets the current state of the Recall Bot
*/
server.get('/recording-state', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}const response = await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}`, {method: 'GET',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});const bot = await response.json();const latestStatus = bot.status_changes.slice(-1)[0].code;console.log('state:', latestStatus);res.send(200, JSON.stringify({state: latestStatus,transcript: db.transcripts[botId] || [],}));} catch (error) {console.error("recoding-state error:", error);}
});
4)接收转写存储在db中(本例使用的是内存)
/** Receives transcription webhooks from the Recall Bot*/
server.post('/transcription', async (req, res) => {try {console.log('transcription webhook received: ', req.body);const { bot_id, transcript } = req.body.data;if (!db.transcripts[bot_id]) {db.transcripts[bot_id] = [];}if (transcript){db.transcripts[bot_id].push(transcript);}res.send(200, JSON.stringify({ success: true }));} catch (error) {console.error("transcription error:", error);}
});
完整的服务端代码:
import restify from "restify";
import send from "send";
import fs from "fs";
import fetch from "node-fetch";
import path from 'path';
import { fileURLToPath } from 'url';
import { storeToken, getToken } from './redisClient.js';
import { WebSocketServer, WebSocket } from 'ws';const __filename = fileURLToPath(import.meta.url);
console.log('__filename: ', __filename);const __dirname = path.dirname(__filename);
console.log('__dirname: ', __dirname);// Create HTTP server.
const server = restify.createServer({key: process.env.SSL_KEY_FILE ? fs.readFileSync(process.env.SSL_KEY_FILE) : undefined,certificate: process.env.SSL_CRT_FILE ? fs.readFileSync(process.env.SSL_CRT_FILE) : undefined,formatters: {"text/html": function (req, res, body) {return body;},},
});server.use(restify.plugins.bodyParser());
server.use(restify.plugins.queryParser());server.get("/static/*",restify.plugins.serveStatic({directory: __dirname,})
);server.listen(process.env.port || process.env.PORT || 3000, function () {console.log(`\n${server.name} listening to ${server.url}`);
});// Adding tabs to our app. This will setup routes to various views
// Setup home page
server.get("/config", (req, res, next) => {send(req, __dirname + "/config/config.html").pipe(res);
});// Setup the static tab
server.get("/meetingTab", (req, res, next) => {send(req, __dirname + "/panel/panel.html").pipe(res);
});//获得用户token
server.get('/auth', (req, res, next) => {res.status(200);res.send(`
<!DOCTYPE html>
<html>
<head><script>// Function to handle the token storageasync function handleToken() {const hash = window.location.hash.substring(1);const hashParams = new URLSearchParams(hash);const access_token = hashParams.get('access_token');console.log('Received hash parameters:', hashParams);if (access_token) {console.log('Access token found:', access_token);localStorage.setItem("access_token", access_token);console.log('Access token stored in localStorage');try {const response = await fetch('https://shortly-adapted-akita.ngrok-free.app/store_user_token', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ "user_token" : access_token })});if (response.ok) {console.log('Token stored successfully');} else {console.error('Failed to store token:', response.statusText);}} catch (error) {console.error('Error storing token:', error);}} else {console.log('No access token found');}window.close();}// Call the function to handle the tokenhandleToken();</script>
</head>
<body></body>
</html>`);next();
});// 存储 user_token
server.post('/store_user_token', async (req, res) => {const user_token = req.body.user_token;if (!user_token) {res.status(400);res.send('user_token are required');}try {// Store user tokenawait storeToken('user_token', user_token);console.log('user_token stored in Redis');} catch (err) {console.error('user_token store Error:', err);}res.status(200); res.send('Token stored successfully');
});// 获取 user_token
server.get('/get_user_token', async (req, res) => {try {// Store user tokenconst user_token = await getToken('user_token');console.log('user_token get in Redis');res.send({"user_token": user_token});} catch (err) {console.error('user_token get Error:', err);}
});//应用token
let app_token = '';
const app_token_refresh_interval = 3000 * 1000; // 3000秒const getAppToken = async () => {try {// 构建请求体const requestBody = new URLSearchParams({"grant_type": "client_credentials","client_id": "Azure注册应用ID","client_secret": "Azure注册应用密钥","scope": "https://graph.microsoft.com/.default",}).toString();// 获取app令牌const tokenUrl = `https://login.microsoftonline.com/864168b4-813c-411a-827a-af408f70c665/oauth2/v2.0/token`;const tokenResponse = await fetch(tokenUrl, {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded',},body: requestBody,});if (!tokenResponse.ok) {const errorData = await tokenResponse.json();throw new Error(errorData.error_description);}const tokenData = await tokenResponse.json();app_token = tokenData.access_token;console.log("app_token received!");} catch (error) {console.error('Error getting app token:', error);}
};// 定期刷新 app_token
setInterval(getAppToken, app_token_refresh_interval);// 确保在服务器启动时获取 app_token
getAppToken();//存储机器人转写信息
const db = {transcripts: {// [bot id]: [transcript]},
};const RECALL_API_KEY = '你的recall.ai的API KEY';
const WEBHOOK_SECRET = '在recall.ai配置webhook端点时的密钥';let local_botId = null;
/** Send's a Recall Bot to start recording the call*/
server.post('/start-recording', async (req, res) => {const meeting_url = req.body.meetingUrl;try {if (!meeting_url) {return res.status(400).json({ error: 'Missing meetingUrl' });}console.log('recall bot start recording', meeting_url);const url = 'https://us-west-2.recall.ai/api/v1/bot/';const options = {method: 'POST',headers: {accept: 'application/json','content-type': 'application/json',Authorization: `Token ${RECALL_API_KEY}`},body: JSON.stringify({bot_name: 'teams bot',real_time_transcription: {destination_url: 'https://shortly-adapted-akita.ngrok-free.app/transcription?secret=' + WEBHOOK_SECRET,partial_results: false},transcription_options: {provider: 'assembly_ai'},meeting_url: meeting_url})};const response = await fetch(url, options);const bot = await response.json();local_botId = bot.idconsole.log('botId:', local_botId);res.send(200, JSON.stringify({botId: local_botId}));} catch (error) {console.error("start-recoding error:", error);}
});/*
* Tells the Recall Bot to stop recording the call
*/
server.post('/stop-recording', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}/leave_call`, {method: 'POST',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});console.log('recall bot stopped');res.send(200, {})} catch (error) {console.error("stop-recoding error:", error);}
});/*
* Gets the current state of the Recall Bot
*/
server.get('/recording-state', async (req, res) => {try {const botId = local_botId;if (!botId) {res.send(400, JSON.stringify({ error: 'Missing botId' }));}const response = await fetch(`https://us-west-2.recall.ai/api/v1/bot/${botId}`, {method: 'GET',headers: {'Content-Type': 'application/json',Accept: 'application/json',Authorization: `Token ${RECALL_API_KEY}`},});const bot = await response.json();const latestStatus = bot.status_changes.slice(-1)[0].code;console.log('state:', latestStatus);res.send(200, JSON.stringify({state: latestStatus,transcript: db.transcripts[botId] || [],}));} catch (error) {console.error("recoding-state error:", error);}
});
/** Receives transcription webhooks from the Recall Bot*/
server.post('/transcription', async (req, res) => {try {console.log('transcription webhook received: ', req.body);const { bot_id, transcript } = req.body.data;if (!db.transcripts[bot_id]) {db.transcripts[bot_id] = [];}if (transcript){db.transcripts[bot_id].push(transcript);}res.send(200, JSON.stringify({ success: true }));} catch (error) {console.error("transcription error:", error);}
});
二、页面需要实现开始录音和停止录音按钮及转写显示。
完整的页面代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Meeting Transcripts</title><script src="https://res.cdn.office.net/teams-js/2.0.0/js/MicrosoftTeams.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script><style>.subtitle {display: flex;align-items: center;margin-bottom: 10px;}.speaker-photo {width: 20px;height: 20px;border-radius: 50%;margin-right: 10px;}button {padding: 5px 10px; /* 调整按钮的 padding 以减小高度 */font-size: 14px; /* 调整按钮的字体大小 */margin-right: 10px;}#transcript {margin-top: 20px;padding: 10px;border: 1px solid #ccc;min-height: 100px;width: 100%;}</style>
</head>
<body><h2>Meeting Transcripts</h2><button id="startRecording">Start Recording</button><button id="stopRecording" disabled>Stop Recording</button><div id="transcripts"></div><script>const clientId = 'Azure注册应用ID';const tenantId = 'Azure注册应用租户ID';const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;const redirectUri = 'https://shortly-adapted-akita.ngrok-free.app/auth'; // 确保与服务器端一致const scope = 'user.read';let user_token = null;let meetingOrganizerUserId = null;let participants = {}; // 用于存储参会者的信息let userPhotoCache = {}; // 用于缓存用户头像let tokenFetched = false; // 标志变量,用于跟踪是否已经获取了 user_tokenlet displayedTranscriptIds = new Set(); // 用于存储已经显示的转录片段的 IDconst getUserInfo = async (userId, accessToken) => {const graphUrl = `https://graph.microsoft.com/v1.0/users/${userId}`;const response = await fetch(graphUrl, {headers: {'Authorization': `Bearer ${accessToken}`}});if (response.status === 401) {// 如果 token 超期,重新触发 initAuthenticationinitAuthentication();return null;}const userInfo = await response.json();return userInfo;};const getUserPhoto = async (userId, accessToken) => {if (userPhotoCache[userId]) {return userPhotoCache[userId];}const graphUrl = `https://graph.microsoft.com/v1.0/users/${userId}/photo/$value`;const response = await fetch(graphUrl, {headers: {'Authorization': `Bearer ${accessToken}`}});if (!response.ok) {const errorData = await response.json();console.error('Error fetching user photo:', errorData);return null;}const photoBlob = await response.blob();const photoUrl = URL.createObjectURL(photoBlob);userPhotoCache[userId] = photoUrl; // 缓存头像 URLreturn photoUrl;};const getMeetingDetails = async (user_token, joinMeetingId) => {const apiUrl = `https://graph.microsoft.com/v1.0/me/onlineMeetings?$filter=joinMeetingIdSettings/joinMeetingId eq '${joinMeetingId}'`;const response = await fetch(apiUrl, {method: 'GET',headers: {'Authorization': `Bearer ${user_token}`,'Content-Type': 'application/json'}});if (!response.ok) {const errorData = await response.json();throw new Error(`getMeetingDetails status: ${response.status}, message: ${errorData.error}`);}const data = await response.json();return data.value[0];};const getTranscriptContent = async (transcripts) => {const subtitles = [];try {transcripts.forEach(transcript => {const startTime = transcript.words[0].start_time;const endTime = transcript.words[transcript.words.length - 1].end_time;const speaker = transcript.speaker;const content = transcript.words.map(word => word.text).join(' ');subtitles.push({ startTime, endTime, speaker, content, id: transcript.original_transcript_id });});return subtitles;} catch (error) {console.error('getTranscriptContent error:', error);return subtitles;}};const displaySubtitle = async (subtitle, transcriptElement, accessToken) => {const subtitleElement = document.createElement('div');subtitleElement.classList.add('subtitle');// 获取说话者的头像const speakerUserId = participants[subtitle.speaker];const speakerPhotoUrl = speakerUserId ? await getUserPhoto(speakerUserId, accessToken) : 'default-avatar.png';// 创建头像元素const speakerPhotoElement = document.createElement('img');speakerPhotoElement.src = speakerPhotoUrl;speakerPhotoElement.alt = subtitle.speaker;speakerPhotoElement.classList.add('speaker-photo');// 创建输出字符串const output = `${subtitle.startTime} - ${subtitle.endTime}\n${subtitle.content}`;subtitleElement.appendChild(speakerPhotoElement);subtitleElement.appendChild(document.createTextNode(output));transcriptElement.appendChild(subtitleElement);};const init = async () => {try {if (!tokenFetched) {const response = await fetch('https://shortly-adapted-akita.ngrok-free.app/get_user_token');const data = await response.json();if (response.ok) {user_token = data.user_token;console.log('user token retrieved:', user_token);tokenFetched = true;} else {console.error('Failed to get token:', response.statusText);return;}}console.log('User Token:', user_token);const joinMeetingId = '45756456529'; // 替换为你要查询的 joinMeetingIdtry {const meetingDetails = await getMeetingDetails(user_token, joinMeetingId);console.log('Meeting Details:', meetingDetails);meetingOrganizerUserId = meetingDetails.participants.organizer.identity.user.id;const meetingId = meetingDetails.id; // 获取会议 IDconsole.log('Organizer User ID:', meetingOrganizerUserId);console.log('Meeting ID:', meetingId);// 获取主持人信息const organizerInfo = await getUserInfo(meetingOrganizerUserId, user_token);const organizerDisplayName = organizerInfo.displayName;participants[organizerDisplayName] = meetingOrganizerUserId;// 获取参会者信息const attendeesPromises = meetingDetails.participants.attendees.map(async attendee => {const userId = attendee.identity.user.id;const userInfo = await getUserInfo(userId, user_token);const displayName = userInfo.displayName;participants[displayName] = userId;});await Promise.all(attendeesPromises);} catch (error) {console.error('Error fetching meeting details:', error);}} catch (error) {console.error('Error getting token:', error);}};const initAuthentication = () => {microsoftTeams.app.initialize();microsoftTeams.authentication.authenticate({url: `${authUrl}?client_id=${clientId}&response_type=token&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}`,width: 600,height: 535,successCallback: async (result) => {console.log('Authentication success:', result);},failureCallback: (error) => {console.error('Authentication failed:', error);}});};// 设置较长的轮询时间来防止 user_token 的超期setInterval(initAuthentication, 3000000); // 每3000秒(50分钟)轮询一次initAuthentication();init();// 录音控制功能const startRecordingButton = document.getElementById('startRecording');const stopRecordingButton = document.getElementById('stopRecording');const transcriptDiv = document.getElementById('transcript');let recordingInterval;// Function to start recordingasync function startRecording() {const meetingUrl = await getMeetingUrl();if (!meetingUrl) return;try {const response = await fetch('/start-recording', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ meetingUrl }),});if (response.ok) {const data = await response.json();console.log('Bot started:', data);startRecordingButton.disabled = true;stopRecordingButton.disabled = false;startPolling();} else {console.error('Failed to start recording:', response.statusText);}} catch (error) {console.error('Error starting recording:', error);}}// Function to stop recordingasync function stopRecording() {try {const response = await fetch('/stop-recording', {method: 'POST',});if (response.ok) {console.log('Bot stopped');startRecordingButton.disabled = false;stopRecordingButton.disabled = true;clearInterval(recordingInterval);} else {console.error('Failed to stop recording:', response.statusText);}} catch (error) {console.error('Error stopping recording:', error);}}// Function to poll the recording stateasync function pollRecordingState() {try {const response = await fetch('/recording-state');if (response.ok) {const data = await response.json();updateUI(data);} else {console.error('Failed to get recording state:', response.statusText);}} catch (error) {console.error('Error polling recording state:', error);}}// Function to update the UI based on the recording statefunction updateUI(data) {const { state, transcript } = data;console.log(state, transcript);// Update the transcript displayconst transcriptsContainer = document.getElementById('transcripts');const transcriptElement = document.createDocumentFragment(); // 使用 DocumentFragment 优化 DOM 操作if (transcript.length > 0) {getTranscriptContent(transcript).then(subtitles => {subtitles.forEach(subtitle => {if (!displayedTranscriptIds.has(subtitle.id)) {displaySubtitle(subtitle, transcriptElement, user_token);displayedTranscriptIds.add(subtitle.id); // 添加到已显示的转录片段 ID 集合中}});}).catch(error => {const errorElement = document.createElement('div');errorElement.innerHTML = `<strong>${error}</strong>`;transcriptElement.appendChild(errorElement);}).finally(() => {transcriptsContainer.appendChild(transcriptElement); // 一次性插入 DOM});}// Update button states based on the recording stateif (state === 'recording') {startRecordingButton.disabled = true;stopRecordingButton.disabled = false;} else if (state === 'stopped') {startRecordingButton.disabled = false;stopRecordingButton.disabled = true;}}// Function to start polling the recording state every 2 secondsfunction startPolling() {recordingInterval = setInterval(pollRecordingState, 2000);}// Event listeners for buttonsstartRecordingButton.addEventListener('click', startRecording);stopRecordingButton.addEventListener('click', stopRecording);// Function to get the meeting URL from the meeting detailsasync function getMeetingUrl() {const joinMeetingId = '45756456529'; // 替换为你要查询的 joinMeetingIdtry {const meetingDetails = await getMeetingDetails(user_token, joinMeetingId);return meetingDetails.joinWebUrl;} catch (error) {console.error('Error fetching meeting URL:', error);return null;}}</script>
</body>
</html>
最终效果:
相关文章:

Teams集成-会议侧边栏应用开发-实时转写
Teams虽然提供了转写的接口,但是不是实时的,即便使用订阅事件也不是实时的,为了达到实时转写的效果,使用recall.ai的转录和assembly_ai的转写实现。 前提:除Teams会议侧边栏应用开发-会议转写-CSDN博客的基本要求外&a…...

归并排序,外排序,计数排序(非比较排序)
归并排序:(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序…...

使用离火插件yoloV8数据标注,模型训练
1. 启动 2.相关配置 2.1 data.yaml path: D:/yolo-tool/yaunshen-yolov8/YOLOv8ys/YOLOv8-CUDA10.2/1/datasets/ceshi001 train: images val: images names: [蔡徐坤,篮球] 2.2 cfg.yaml # Ultralytics YOLOv8, GPL-3.0 license # Default training settings and hyp…...

JavaScript 学习
一、输出 为方便调试可以输出内容,但是用户是看不到的。要在开发者模式中看。 console . log ( "Hello" ); 二、外部文件引用 可以直接在html中写JS <head> <meta charset"utf-8"> <script> console.log("he…...
【算法】分治:归并之 912.排序数组(medium)
系列专栏 双指针 模拟算法 分治思想 目录 1、题目链接 2、题目介绍 3、解法 解决方案选择 解题步骤 4、代码 1、题目链接 912. 排序数组 - 力扣(LeetCode) 2、题目介绍 给你一个整数数组 nums,请你将该数组升序排列。 你必须在 …...

Cocos 3.8.3 实现外描边效果(逃课玩法)
本来想着用Cocos 的Shader Graph照搬Unity的思路来加外描边,发现不行,然后我就想弄两个物体不就行了吗,一个是放大的版本,再放大的版本上加一个材质,这个材质面剔除选择前面的面剔除就行了,果不其然还真行。…...

著名建筑物检测与识别系统源码分享
著名建筑物检测与识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comp…...
使用php生成图片
可以用这方法生成图片 水印 字体可以在资源绑定下载,如果字体路径不对,则不会输出文字图片 public function generateImage($text,$id) { header("Cache-Control: no-cache, must-revalidate"); header("Expires: Mon, 26 Jul 1997 05:0…...

C++ 数据类型分类
在C中,数据类型可以大致分为内置类型(Built-in Types)、标准库类型(Standard Library Types)和自定义类型(User-Defined Types)三大类。 内置类型(Built-in Types) 内置…...
java安装更新jdk11后设置环境JAVA_HOME
背景,已经安装成功,但是环境还是java1.8 java -version openjdk version "11.0.23" 2024-04-16 LTS OpenJDK Runtime Environment (Red_Hat-11.0.23.0.9-2.el7_9) (build 11.0.23+9-LTS) OpenJDK 64-Bit Server VM (Red_Hat-11.0.23.0.9-2.el7_9) (build 11.0.…...

Java.动态代理
1.创建一个接口 package Mydynamicproxy1;public interface Star {public abstract String sing(String str);public abstract void dance(String str); }2.创建一个BigStar类,要实现Star这个接口 package Mydynamicproxy1;public class BigStar implements Star{…...
SpringBoot自定义异常
前言 在前后端开发中,后端接口返回的数据都是JSON格式的,但是后端可能会出现一些可以未知从异常,在后端抛出这些异常的时候,也需要返回相同格式的JSON数据,这时候就需要我们设置全局异常处理器。在后端开发中…...

华为源NAT技术与目的NAT技术
1)源NAT对报文源地址进行转换,分为NAT NO-PAT,NAPT,EASY-IP,三元组NAT; (1)NAT NO-PAT原理: no-port address translation:非端口地址转换:只转换地址,不转换端口&…...
人工智能与机器学习原理精解【25】
文章目录 正则化概述一、正则化的种类二、正则化的定义三、正则化的计算四、正则化的性质五、正则化的例子 公式与计算一、正则化的种类Dropout正则化一、基本思想二、实现方法三、作用机制四、使用注意事项五、总结Dropout正则化的例子和公式。一、Dropout正则化的例子二、Dro…...

一篇文章讲清楚synchronized关键字的作用及原理
概述 在应用Sychronized关键字时需要把握如下注意点: 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待; 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修…...

深度学习模型之BERT的24个小模型源码与预训练紧凑模型的重要性
原始信息 论文: Well-Read Students Learn Better: On the Importance of Pre-training Compact Models作者:Iulia Turc, Ming-Wei Chang, Kenton Lee, Kristina Toutanova地址:arxiv.org/pdf/1908.08…中文:阅读良好的学生学得更…...

【HarmonyOS】深入理解@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
【HarmonyOS】深入理解Observed装饰器和ObjectLink装饰器:嵌套类对象属性变化 前言 之前就Observed和ObjectLink写过一篇讲解博客【HarmonyOS】 多层嵌套对象通过ObjectLink和Observed实现渲染更新处理! 其中就Observe监听类的使用,Object…...

Java笔试面试题AI答之设计模式(1)
文章目录 1. 简述什么是设计模式 ?2. 叙述常见Java设计模式分类 ?3. Java 设计模式的六大原则 ?4. 简述对 MVC 的理解, MVC 有什么优缺点?MVC 的三个核心部分:MVC 的优点:MVC 的缺点:…...
java调用opencv部署到centos7
1、官网下载opencv https://opencv.org/releases/ 2、下载opencv并解压 unzip opencv-3.4.7.zip cd opencv-3.4.7 mkdir build cd build/ 3、安装cmake yum remove cmake -y ; yum install -y gcc gcc-c make automake openssl openssl-devel wget https://cmake.org/files/…...
【python qdrant 向量数据库 完整示例代码】
测试一下python版本的dqrant向量数据库的效果,完整代码如下: 安装库 !pip install qdrant-client>1.1.1 !pip install -U sentence-transformers导入 from qdrant_client import models, QdrantClient from sentence_transformers import SentenceT…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...

解析“道作为序位生成器”的核心原理
解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制,重点解析"道作为序位生成器"的核心原理与实现框架: 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...