微信小程序 - 龙骨图集拆分
微信小程序 - 龙骨图集拆分
- 注意
- 目录结构
- 演示动画
- 废话一下
- 业务逻辑
- 注意点
- 龙骨JSON图集结构
- 源码分享
- dragonbones-split.js
- dragonbones-split.json
- dragonbones-split.wxml
- dragonbones-split.wxss
- imgUtil.js
- 参考资料
注意
只支持了JSON版本
目录结构
演示动画
Spine播放器1.5.0_PC端
Spine播放器1.5.1_移动端
废话一下
这是 SpinePlayer 2D骨骼动画播放器 - 微信小程序版 工具箱中的一个功能。
功能很简单,80%
的代码都在处理交互逻辑,以及移动端PC端兼容方面的问题。
业务逻辑
- 读取JSON文件和PNG图片。
- 解析JSON得到所有小图在图集(PNG图片)中的 x,y 坐标和高宽。
- 将PNG图集绘制到2d画布,然后使用 canvasToTempFilePath 逐个截取区域,保存为小图。
- 最后将所有小图打包为 zip 供用户保存即可。
注意点
- 为了保证截取图片的清晰度,
画布尺寸
需要用图片大小
乘以设备像素比
。 - 图片填充完整,再截图。否则会空白。所以会成两步操作比较稳妥。当然也可以自己控制延时自动调用,一气呵成。
- 因为 2d 画布不便于直接显示,所以使用一个 image 组件来实现预览。
3.1. 方法是将PNG
读取为base64
赋给image
组件<image src="{{textureBase64}}" />
3.2. 读取PNG
为base64
就这句fs.readFileSync(‘临时图片路径’, 'base64')
当然用的时候还要拼接一下头,详情看源码吧。
龙骨JSON图集结构
可以看出结构非常简单,直接读 SubTexture
数组,遍历它进行截取就可以了。
{"imagePath": "body_tex.png","width": 1024,"SubTexture": [{"height": 472,"y": 1,"width": 295,"name": "body/a_arm_L","x": 720}略。。。],"name": "body","height": 1024
}
源码分享
dragonbones-split.js
// packageTools/pages/dragonbones-split/dragonbones-split.js
const fileUtil = require('../../../utils/fileUtil.js');
const imgUtil = require('./imgUtil.js');
const pixelRatio = wx.getSystemInfoSync().pixelRatio; // 设备像素比
let canvas, ctx;
let globalData = getApp().globalData;
let dbsplit = globalData.PATH.dbsplit;
Page({/*** 页面的初始数据*/data: {canvasWidth: 300, // 画布宽canvasHeight: 150, // 画布高texture: {}, // 龙骨图集PNG图片信息 { path, width, height, orientation, type }textureBase64: '', // 龙骨图集PNG图片的 Base64 编码subTextureList:[], // 龙骨图集JSON数据。包含拆分出的小图地址 tempFilePathshareZipFile: { name: '', path: ''}, // 最终生成的ZIPjsonValue: '', // 文本框内容(PC 端用于获取 JSON)// parseInt('0011', 2) === 3status: 0, // 工作状态:0000 初始,0001 有图,0010 有JSON,0100 已拆图,1000 已ZIPisPC: false,},/*** 生命周期函数--监听页面加载*/onLoad(options) {// 获取画布wx.createSelectorQuery().select('#myCanvas') // 在 WXML 中填入的 id.fields({ node: true, size: true }).exec((res) => {canvas = res[0].node; // Canvas 对象ctx = canvas.getContext('2d'); // 渲染上下文});// 创建工作目录fileUtil.mkdir(dbsplit); this.setData({ isPC: globalData.systemInfo.isPC });// 清理场地fileUtil.clearDirSync(globalData.PATH.dbsplit);},/*** 粘贴龙骨图集 JSON 的文本框发生变化*/onChange(event) {try {if(event.detail.value === undefined){return;}let json = imgUtil.parseJSON(event.detail.value);this.data.shareZipFile = { name: `${json.name}.zip`, path: `${dbsplit}/${json.name}.zip`}; // zip路径打包时用this.setData({ jsonValue: event.detail.value,status: 2,subTextureList: json.SubTexture,});} catch (err) {console.log(err);this.setData({ jsonValue: '',status: 1, // this.data.status & 13, // parseInt('1101', 2)subTextureList: [],});wx.showToast({ title: 'JSON格式有误', icon: 'error' })}},/*** 选择龙骨图集PNG、JSON*/async choosePNGJSON(e){console.log('选择龙骨图集PNG、JSON');wx.showLoading({ title: '选择图集' });imgUtil.chooseAtlas({ count: 2}).then(res => {if(res.length != 2){wx.showToast({ title: '文件数量异常!', icon: 'error' });return;}let texture, json;if(res[0].type === 'png'){[texture, json] = res;}else{[json, texture] = res;}wx.showLoading({ title: '解析图集' });this.data.texture = texture; // 更新图集PNG的相关信息。点击预览时会用到它的 paththis.data.shareZipFile = { name: `${json.name}.zip`, path: `${dbsplit}/${json.name}.zip`}; // zip路径打包时用// 图集PNG填充画布this.fillCanvasWithImage(texture).then(()=>{// 填充完成后,在下一个时间片段更新数据this.setData({textureBase64: imgUtil.imageToBase64(texture.path, texture.type), // 更新 image 组件 srcsubTextureList: json.SubTexture, // 更新页面上的 subTexture 列表status: 2, // 已选图,JSON完成解析},()=>{wx.hideLoading();});})}).catch(err => {console.log(err);wx.showToast({ title: '图集选择失败', icon: 'error' });this.setData({textureBase64: '', // 更新 image 组件 srcsubTextureList: [], // 更新页面上的 subTexture 列表status: 0,});}).finally(()=>{wx.hideLoading() })},/*** 选择龙骨图集PNG文件*/async choosePNG(e){console.log('选择龙骨图集PNG文件');let whereFrom = globalData.systemInfo.isPC ? 'media' : 'message';let promises = imgUtil.chooseImg({ count: 1, whereFrom }); // media messageawait promises.then(res => {let texture = res[0];console.log(texture);this.setData({ texture , textureBase64: imgUtil.imageToBase64(texture.path, texture.type),status: 1,// 重选图片后,清空已选的JSONjsonValue: '', // 清除 JSON 数据subTextureList: [] // 清除 解析后的 JSON 数据}, res => {this.fillCanvasWithImage(texture); // 填充画布});}).catch(err => {console.log(err);wx.showToast({ title: '选择图片失败', icon: 'error' });this.setData({ texture: {} , textureBase64: '',status: 0, // this.data.status & 14,});});},/*** 将图片绘制到画布*/fillCanvasWithImage(imageInfo){let { path, width, height, orientation, type } = imageInfo;// 按图片大小更新画布尺寸canvas.width = width * pixelRatio;canvas.height = height * pixelRatio;ctx.scale(pixelRatio, pixelRatio);return new Promise((resolve, reject)=>{// 更新画布 渲染宽高。完成后,绘制图片到画布this.setData({ canvasWidth: width, canvasHeight: height}, res=> {const image = canvas.createImage(); // 创建图片对象image.onload = () => ctx.drawImage(image, 0, 0); // 图片加载完成,在回调中将其绘制到画布image.src = path; // 设置图片 srcresolve();});});},/*** 解析JSON并拆分图集*/async parseJsonAndSplitIMG(e){console.log('解析JSON并拆分图集');if(this.data.status < 1){wx.showToast({ title: '请选择图片', icon: 'error'});return;}if(this.data.status < 2){wx.showToast({ title: '请提供JSON', icon: 'error'});return;}this.splitIMG();},/*** 拆分龙骨图集PNG文件*/async splitIMG(e){console.log('拆分龙骨图集PNG文件');let pArr = this.data.subTextureList.map(subTexture => {return new Promise((resolve, reject)=> {let { x, y, width, height, } = subTexture;wx.canvasToTempFilePath({x, y, width, height, canvas,destWidth: width,destHeight: height,fileType: 'png',success: res => {console.log(res.tempFilePath);subTexture.tempFilePath = res.tempFilePath;resolve(subTexture);},fail: reject});});});Promise.all(pArr).then(async res => {await this.creatZip(res);this.setData({ status: 3, }); // 更新状态,完成拆图}).catch(err => {this.setData({ status: 2, });});},/*** 将拆好的小图打包为 ZIP*/async creatZip(subTextureList){console.log('将拆好的小图打包为 ZIP');try {// 图片列表let fileList = subTextureList.map(subTexture => ({ name: subTexture.name, path: subTexture.tempFilePath}));// 创建压缩包await fileUtil.zip(fileList, this.data.shareZipFile.path, progress => { wx.showLoading({ title: progress.msg, });if(progress.percent == 100){setTimeout(wx.hideLoading, 200);}});// 更新状态console.log(this.data.shareZipFile.path);this.setData({subTextureList});} catch (err) {console.error(err)wx.showToast({ icon: 'error', title: '打包失败' });this.setData({shareZipFile: {}});} finally {wx.hideLoading();}},/*** 将拆分后的文件打包导出*/saveIMG(e){console.log('将拆分后的文件打包导出');console.log(this.data.subTextureList);if(this.data.status < 3){wx.showToast({ title: '尚未拆图', icon: 'error' });return;}// 如果是电脑端,否则是手机端if(globalData.systemInfo.platform == 'windows' || globalData.systemInfo.platform == 'mac'// || globalData.systemInfo.platform == 'devtools'){wx.saveFileToDisk({filePath: this.data.shareZipFile.path,success: console.log,fail: console.error});} else {wx.shareFileMessage({filePath: this.data.shareZipFile.path,fileName: this.data.shareZipFile.name,success: console.log,fail: console.error,complete: console.log});}},async previewTexture(e){if(!!this.data.texture.path == false && globalData.systemInfo.isPC){await this.choosePNG();return;}wx.previewImage({urls: [this.data.texture.path],success: (res) => {},fail: (res) => {},complete: (res) => {},})},previewSubTexture(e){if(this.data.status < 3){wx.showToast({ title: '尚未拆分', icon:"error" });return;}wx.previewImage({urls: this.data.subTextureList.map(obj => obj.tempFilePath),current: e.currentTarget.dataset.texturePath,showmenu: true,success: (res) => {},fail: (res) => {},complete: (res) => {},})}
})
dragonbones-split.json
{"usingComponents": {}
}
dragonbones-split.wxml
<!--packageTools/pages/dragonbones-split/dragonbones-split.wxml-->
<!-- 大家好我是笨笨,笨笨的笨,笨笨的笨,谢谢!https://blog.csdn.net/jx520/ -->
<view class="main-container bg-60" ><view class="top-container poem-container poem-h bg-24" style="min-height: 200rpx;"><view>萍</view><view>无根翡翠顺江涛, 有尾鱼虾逆水潮。</view><view>行宿天涯本无路, 去留飘渺也逍遥。</view></view><view class="top-container scroll-y home-top" disabled><image class="texture chessboard" src="{{textureBase64}}" bind:tap="previewTexture"/><view wx:for="{{subTextureList}}" wx:key="name"bindtap="previewSubTexture" data-texture-name="{{item.name}}" data-texture-path="{{item.tempFilePath}}"class="sub-texture-list {{status < 3 ? 'disabled' : 'splited'}}"><view class="sub-texture-row"> <view>{{item.name}}</view><view>{{item.width}} x {{item.height}}</view></view></view><view class="sub-texture-list" wx:if="{{isPC}}"><textarea class="json-area" auto-height maxlength="-1" placeholder="请在此处粘贴龙骨图集的 JSON 内容" value="{{jsonValue}}"bindblur="onChange" bindlinechange="onChange" bindconfirm="onChange" bindinput="onChange" /></view><canvas id="myCanvas" type="2d" class="canvas2d" style="width: {{canvasWidth}}px; height: {{canvasHeight}}px;" /></view><view class="bottom-container"><!-- 移动端 --><view wx:if="{{isPC == false}}"><view class="but-row"><view class="button button-large" bindtap="choosePNGJSON"><view>选择龙骨PNG、JSON</view></view><view class="button button-large {{status < 2 ? 'disabled' : ''}}" bindtap="parseJsonAndSplitIMG"><view>拆分图集</view></view></view><view class="but-row"><view class="button button-large {{status < 3 ? 'disabled' : ''}}" bindtap="saveIMG"><view>转发到聊天</view></view></view></view><!-- PC端 --><view wx:if="{{isPC}}"><view class="but-row"><view class="button button-large" bindtap="choosePNG"><view wx:if="{{textureBase64 === ''}}">选择图片</view><view wx:if="{{textureBase64 != ''}}">重选图片</view></view><view class="button button-large {{status < 2 ? 'disabled' : ''}}" bindtap="parseJsonAndSplitIMG"><view>拆分图集</view></view></view><view class="but-row"><view class="button button-large {{status < 3 ? 'disabled' : ''}}" bindtap="saveIMG"><view>保存</view></view></view></view><view class="gb-img"></view></view>
</view>
dragonbones-split.wxss
/* packageTools/pages/dragonbones-split/dragonbones-split.wxss */
@import '/common/wxss/player-page-common.wxss';.button{display: flex;flex-direction: column;justify-content: center;align-items: center;margin: 10rpx;padding: 10rpx;color: rgb(236, 236, 236);box-sizing: border-box;
}
.button-large{display: flex;justify-content: center;text-align: center;font-size: large;width: auto;height: 100%;max-height: 50%;box-sizing: border-box;flex-grow: 1;border-radius: 10px;background-color: rgb(102, 102, 102);border-top: 1px solid rgb(112, 112, 112);border-bottom: 2px solid rgb(90, 90, 90);
}.but-row {display: flex;
}
.sub-texture-row {display: flex;align-items: center;padding: 0 20rpx;border-bottom: rgb(102, 102, 102) solid 1px;box-sizing: border-box;
}
.sub-texture-row>view {margin: 4px;
}
.sub-texture-row :nth-child(1) {width: 70%;word-wrap: break-word;border-right: #666 solid 1px;
}
.sub-texture-row :nth-child(2) {margin-left: 5px;width: 30%;height: 100%;
}.texture {padding: 6px;width: 100%;border: rgb(63, 63, 63) solid 2px;box-sizing: border-box;
}
.canvas2d {position: absolute;right: 100vw;
}
.sub-texture-list {display: flex;flex-direction: column;padding: 5rpx 20rpx;
}
.json-area {background-color: #000;border-radius: 10rpx;width: 100%;padding: 20rpx;box-sizing: border-box;font-size: x-small;min-height: 430rpx;
}.splited {color: chartreuse;
}
imgUtil.js
const fs = wx.getFileSystemManager();/*** 选择图集(PNG和JSON一对)* @param {object} options */
function chooseAtlas(_options = {}){const defaultOptions = { count: 2 };let options = { ...defaultOptions, ..._options };// 选择 PNG、JSONlet promise = wx.chooseMessageFile(options).then(res => res.tempFiles) .then(tempFiles => {return tempFiles.map(tempFilePath => {if(tempFilePath.type === 'image'){ // 图片return wx.getImageInfo({ src: tempFilePath.path }).then(res => {let { path, width, height, orientation, type } = res;let imageInfo = { path, width, height, orientation, type };return imageInfo;});}else if(tempFilePath.type === 'file' && tempFilePath.path.toLowerCase().endsWith('.json')){ // JSONreturn parseJSON(fs.readFileSync(tempFilePath.path, 'utf-8'));}else{return null;}}).filter(obj => obj != null);}).catch(err=> {console.log(err);return [];});// 全部完成再返回return promise.then(promiseArr => {return Promise.all(promiseArr).then(res => res);})
}/*** 选择图片* @param {object} options */
function chooseImg(_options = {}){const defaultOptions = {count: 9, mediaType: ['image'],whereFrom: 'message' , // 从何处选取。合法值:message media};let options = { ...defaultOptions, ..._options };let promise;// 根据参数中给的选择方式,调用对应方法。switch (options.whereFrom) {case 'media':promise = wx.chooseMedia(options).then(res => res.tempFiles.map( data => data.tempFilePath) ).catch(err=> {console.log(err);return [];});break;default:promise = wx.chooseMessageFile(options).then(res => res.tempFiles.map( data => data.path) ).catch(err=> {console.log(err);return [];});break;}// 对选择的图片,获取信息。构建好对象返回return promise.then(tempFiles => {return tempFiles.map(tempFilePath => {return wx.getImageInfo({ src: tempFilePath }).then(res => {let { path, width, height, orientation, type } = res;let imageInfo = { path, width, height, orientation, type };return imageInfo;});});}).then(promiseArr => { // 全部完成再返回return Promise.all(promiseArr).then(res => res);});
}/*** 从 tempFilePath 以 base64 格式读取文件内容* @param {*} tempFilePath * @param {*} type 图片类型是提前通过 getImageInfo 获取的*/
function imageToBase64(tempFilePath, type) {let data = fs.readFileSync(tempFilePath, 'base64');return `data:image/${type};base64,${data}`;
}/*** 解析龙骨图集的JSON* @param {string} dbJsonTxt 龙骨图集的JSON*/
function parseJSON(dbJsonTxt){// 解析JSONlet json = JSON.parse(dbJsonTxt);// 从 SubTexture 中取出图片名// 判断是否有重复,如果重复就用完整路径名,否则:就直接用图片名let arr = json.SubTexture.map(st => st.name.substr(st.name.lastIndexOf('/')+1));// { "x": 2, "y": 2, "width": 554, "height": 140, "name": "weapon_hand_r"}arr = json.SubTexture.map(subTexture => {if(arr.length !== new Set(arr).size){subTexture.name = `${subTexture.name.replace(/\//g, '-')}.png`;}else{subTexture.name = `${subTexture.name.substr(subTexture.name.lastIndexOf('/')+1)}.png`;}return subTexture;});console.log(arr);json.SubTexture = arr;json.type = 'json';return json;
}// /**
// * 选择JSON。(PC端的弹窗竟然不支持输入功能。此方法没用上)
// * @param {object} options
// */
// function chooseJSON(_options = {}){
// const defaultOptions = {
// count: 1,
// whereFrom: 'modal' , // 从何处选取。合法值: modal message
// };
// let options = { ...defaultOptions, ..._options };
// let promise;
// // 根据参数中给的选择方式,调用对应方法。
// switch (options.whereFrom) {
// case 'modal':
// promise = wx.showModal({
// title: '龙骨图集JSON',
// placeholderText: '请输入龙骨图集JSON内容',
// confirmText: '解析',
// editable: true,
// }).then(res => {
// if (res.confirm && res.errMsg === "showModal:ok" ) {
// console.log(res.content);
// return res.content;
// } else if (res.cancel) {
// console.log('用户点击取消')
// return Promise.reject();
// }
// });
// break;
// default:
// promise = wx.chooseMessageFile(options)
// .then(res => res.tempFiles.map( data => fs.readFileSync(data.path, 'utf-8')))
// .catch(err=> {
// console.log(err);
// return '';
// });
// break;
// }
// return promise;
// }module.exports = {chooseAtlas,chooseImg,imageToBase64,parseJSON,
}
参考资料
笑虾:微信小程序 - 创建 ZIP 压缩包
笑虾:微信小程序 - 文件工具类 fileUtil.js
相关文章:

微信小程序 - 龙骨图集拆分
微信小程序 - 龙骨图集拆分 注意目录结构演示动画废话一下业务逻辑注意点龙骨JSON图集结构 源码分享dragonbones-split.jsdragonbones-split.jsondragonbones-split.wxmldragonbones-split.wxssimgUtil.js 参考资料 注意 只支持了JSON版本 目录结构 演示动画 Spine播放器1.5.…...
使用React 18和WebSocket构建实时通信功能
1. 引言 WebSocket是一种在Web应用中实现双向通信的协议。它允许服务器主动向客户端推送数据,而不需要客户端发起请求。在现代的实时应用中,WebSocket经常用于实时数据传输、聊天功能、实时通知和多人协作等场景。在本篇博客中,我们将探索如…...

vue3使用vue-router嵌套路由(多级路由)
文章目录 1、Vue3 嵌套路由2、项目结构3、编写相关页面代码3.1、编写route文件下 index.ts文件3.2、main.ts文件代码:3.3、App.vue文件代码:3.4、views文件夹下的Home文件夹下的index.vue文件代码:3.5、views文件夹下的Home文件夹下的Tigerhh…...

openGauss学习笔记-164 openGauss 数据库运维-备份与恢复-导入数据-使用COPY FROM STDIN导入数据-处理错误表
文章目录 openGauss学习笔记-164 openGauss 数据库运维-备份与恢复-导入数据-使用COPY FROM STDIN导入数据-处理错误表164.1 操作场景164.2 查询错误信息164.3 处理数据导入错误 openGauss学习笔记-164 openGauss 数据库运维-备份与恢复-导入数据-使用COPY FROM STDIN导入数据-…...

QT Widget - 随便画个圆
简介 实现在界面中画一个圆, 其实目的是想画一个LED效果的圆。代码 #include <QApplication> #include <QWidget> #include <QPainter> #include <QColor> #include <QPen>class LEDWidget : public QWidget { public:LEDWidget(QWidget *pare…...

js输入框部分内容不可编辑,其余正常输入,el-input和el-select输入框和多个下拉框联动后的内容不可修改
<tr>//格式// required自定义指令<e-td :required"!read" label><span>地区:</span></e-td><td>//v-if"!read && this.data.nationCode 148"显示逻辑<divclass"table-cell-flex"sty…...
分布式文件存储系统minio了解下
什么是minio minio 是一个基于 Apache License v2.0 开源协议的对象存储服务。非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小。 是一种海量、安全、低成本、高可靠的云存储…...

迅为RK3568开发板使用OpenCV处理图像-ROI区域-位置提取ROI
在图像处理过程中,我们可能会对图像的某一个特定区域感兴趣,该区域被称为感兴趣区域(Region of Interest, ROI)。在设定感兴趣区域 ROI 后,就可以对该区域进行整体操作。 位置提取 ROI 本小节代码在配套资料“iTOP-3…...

重新认识Word——尾注
重新认识Word——尾注 参考文献格式文献自动生成器插入尾注将数字带上方括号将参考文献中的标号改为非上标 多处引用一篇文献多篇文献被一处引用插入尾注有横线怎么删除?删除尾注 前面我们学习了如何给图片,公式自动添加编号,今天我们来看看毕…...

所有学前教育专业,一定要刷到这篇啊
我是真的希望所有学前教育的宝子都能刷到这篇啊啊,只要输入需求,几秒它就给你写出来了,而且不满意还可以重新写多,每次都是不一样的内容。重复率真的不高,需求越多,生成的文字内容越精准!&#…...

colmap三维重建核心逻辑梳理
colmap三维重建核心逻辑梳理 1. 算法流程束流2. 初始化3. 重建主流程 1. 算法流程束流 重建核心逻辑见 incremental_mapper.cc 中 IncrementMapperController 中 Reconstruct 初始化变量和对象判断是否有初始重建模型,若有,则获取初始重建模型数量&am…...

查询某个类是在哪个JAR的什么版本开始出现的方法
背景 我们在依赖第三方JAR时,同时也会间接的依赖第三方JAR引用的依赖,而当我们项目中某个依赖的版本与第三方JAR依赖的版本不一致时,可能会导致第三方JAR的在运行时无法找到某些方法或类,从而无法正常使用。 如我正在开发的一个…...

Linux本地搭建StackEdit Markdown编辑器结合内网穿透实现远程访问
文章目录 1. docker部署Stackedit2. 本地访问3. Linux 安装cpolar4. 配置Stackedit公网访问地址5. 公网远程访问Stackedit6. 固定Stackedit公网地址 StackEdit是一个受欢迎的Markdown编辑器,在GitHub上拥有20.7k Star!,它支持将Markdown笔记保…...
k8s中ConfigMap、Secret创建使用演示、配置文件存储介绍
目录 一.ConfigMap(cm) 1.适用场景 2.创建并验证configmap (1)以yaml配置文件创建configmap,验证变化是是否同步 (2)--from-file以目录或文件 3.如何使用configmap (1&#x…...

Linux服务器性能优化小结
文章目录 生产环境监测常见专业名词扫盲服务器平均负载服务器平均负载的定义如何判断平均负载值以及好坏情况如果依据平均负载来判断服务器当前状况系统平均负载和CPU使用率的区别 CPU上下文切换基本概念3种上下文切换进程上下文切换线程上下文切换中断上下文切换 查看上下文切…...

ELF文件结构
ELF文件结构 前文结尾说到编译器编译源代码后生成的文件叫做目标文件,而目标文件经过编译器链接之后得到的就是可执行文件。那么目标文件到底是什么?它和可执行文件又有什么区别?链接到底又做了什么呢?接下来,我们将探…...
【C++】有关string迭代器的几道OJ题详解
目录 一、字符串最后一个单词的长度 题目描述 完整代码 二、验证回文串 题目描述 完整代码 三、反转字符串 题目描述 完整代码 四、反转字符串中的单词 题目描述 完整代码 一、字符串最后一个单词的长度 原题链接 题目描述 计算字符串最后一个单词的长度ÿ…...

谷歌宣布向云计算客户开放 Gemini Pro,开发者可用其构建应用
12 月 14 日消息,美国时间周三,谷歌宣布了一系列升级的人工智能(AI)功能,旨在为其云计算客户提供更好的服务。这家科技巨头正试图赶上竞争对手,比如微软和 OpenAI,它们都在积极利用人工智能的热…...

软件测试用例经典方法 | 单元测试法案例
单元测试又称模块测试,是对软件设计的最小单元的功能、性能、接口和设计约束等的正确性进行检验,检查程序在语法、格式和逻辑上的错误,并验证程序是否符合规范,以发现单元内部可能存在的各种缺陷。 单元测试的对象是软件设计的最…...
Leetcode 2967. Minimum Cost to Make Array Equalindromic
Leetcode 2967. Minimum Cost to Make Array Equalindromic 1. 解题思路2. 代码实现 题目链接:2967. Minimum Cost to Make Array Equalindromic 1. 解题思路 这一题其实我的思路有点笨,多少有点暴力求解的意思。 显然,如果我们给出全部的…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...

群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...