OpenHarmony应用实现二维码扫码识别
概念介绍
二维码的应用场景非常广泛,在购物应用中,消费者可以直接扫描商品二维码,浏览并购买产品,如图是购物应用的扫描二维码的页面。
本文就以橘子购物示例应用为例,来讲解OpenHarmony应用二维码开发相关的技术点。
我们先看下二维码相关的几个概念。
● 二维码生成
OpenHarmony应用框架提供了QRCode组件,用于显示单个二维码的组件。该组件只能用于显示二维码,无法显示条码与解析码内容。
● 二维码解析
OpenHarmony提供了功能强大的三方库 @ohos/zxing,是一个解析/生成一维码/二维码的库。详细内容可以参考@ohos/zxing。
二维码解析时,通常有两种方式,使用相机拍摄获取图片或打开相册选取图片,然后图片解析合适的图片格式,进行二维码解析。
橘子购物示例应用扫描二维码的示例图:
配置文件
了解了二维码相关的概念后,我们看下橘子购物示例应用的oh-package.json5配置文件。
在橘子购物示例应用中,实现首页二维码扫描的页面的文件位置为:entry/src/main/ets/pages/ScanPage.ets。文件内容如下:
import { QRCodeScanComponent } from "@ohos/scan-component"
@Entry
@Component
struct Scan {
build() {
Column() {
QRCodeScanComponent()
}
}
}
内容非常简单,主要是导入的自定义组件QRCodeScanComponent,这个组件的代码来自:二维码扫描示例应用,后文我们这样分析如何开发这个二维码扫描应用。
从这一行,可以了解到OpenHarmony应用如何引用ohpm本地三方库。
"@ohos/scan-component":"file:../libs/ohos-qr-code-scan-1.0.1.har",
oh-package.json5配置文件片段如下:
{
"license": "ISC",
"devDependencies": {},
"name": "product",
"description": "example description",
"repository": {},
"version": "1.0.0",
"dependencies": {
"@ohos/http": "file:../libs/ohos-http-1.0.0.tgz",
"@ohos/video-component": "file:../libs/ohos-video-component-1.0.5.tgz",
"@ohos/details-page-component": "file:../feature/detailPage",
"@ohos/notification": "file:../libs/ohos-notification-1.0.0.tgz",
"@ohos/scan-component": "file:../libs/ohos-qr-code-scan-1.0.1.har",
"@ohos/updatedialog": "file:../libs/ohos-updatedialog-1.0.0.tgz",
"@ohos/enter-animation": "file:../libs/ohos-enter-animation-1.0.1.tgz",
"@ohos/share-component": "file:../libs/ohos-sharecomponent-1.0.1.tgz",
"@ohos/emitter": "file:../feature/emitter",
"@ohos/navigation-component": "file:../feature/navigationHome"
}
}
开发步骤
我们来看二维码扫描功能是如何开发的。
导入ohpm三方库
在开发前,我们需要导入ohpm组件库:@ohos/zxing。可以使用命令行方式导入ohpm install @ohos/zxing,也可以直接在文件entry\oh-package.json5中配置,如文件片段所示。
可以看出,二维码扫描的核心代码存放在Feature目录,是一个独立的module模块,方便复用:
“@ohos/feature-qr-code-scan”: “file:…/Feature”。
"dependencies": {
"@ohos/feature-qr-code-scan": "file:../Feature",
"@ohos/zxing": "^2.0.0"
}
相机服务
CameraService.ets文件相机服务构造函数中,会创建一个图片接收器。
该图片接收器可以监听’imageArrival’事件,当相机拍照时会触发该事件。在监听事件的回调函数里,实现对拍照的图片进行处理。
CameraService.ets文件相机服务构造函数:
constructor(imgReceiver?: image.ImageReceiver) {
if (imgReceiver === undefined) {
this.imageReceiver = image.createImageReceiver(QRCodeScanConst.IMG_DEFAULT_SIZE.WIDTH,
QRCodeScanConst.IMG_DEFAULT_SIZE.HEIGHT, image.ImageFormat.JPEG, QRCodeScanConst.MAX_IMAGE_CAPACITY)
} else {
this.imageReceiver = image.createImageReceiver(imgReceiver.size.width, imgReceiver.size.height,
imgReceiver.format, imgReceiver.capacity)
}
}
在CameraService.ets文件创建相机函数中,主要包含如下几个步骤:
● 获取支持的相机
根据context获取CameraManager,然后获取支持的相机(摄像头)。如果没有支持的相机,则然后。
如有支持的相机,则默认使用相机列表中的第一个。实际应用中,对于二维码扫描,需要使用后置相机摄像头。
● 获取相机输入输出流
首先,根据指定的相机,创建相机输入流this.cameraInput。
然后,获取相机的cameraOutputCapability参数,接着创建两个输出流:
1、 预览输出流
创建相机预览输出流this.previewOutput,使用的surfaceId来自XComponent组件。预览输出流,对应相机拍照前的图片预览。
2、相片输出流
创建相片输出流this.photoOutput,使用的receivingSurfaceId来自上文创建的图片接收器。相片输出流,用于保存到相片。
● 配置相机会话
配置相机会话,也比较简单,添加输入流和输出流即可,见代码及其注释。
CameraService.ets文件创建相机函数:
/**
* 创建相机
*/
async createCamera(surfaceId: string) {
Logger.info("createCamera start")
// 根据context获取CameraManager
let cameraManager = camera.getCameraManager(AppStorage.Get('context'))
// 获取Camera对象数组
let cameras = cameraManager.getSupportedCameras()
// 没有相机就停止
if (cameras.length === 0) {
Logger.error("createCamera: cameras length is 0.")
return
}
// 拿到相机列表中的第一个默认相机id, 根据id获取相机输入流
this.cameraInput = cameraManager.createCameraInput(cameras[0])
this.cameraInput.open()
// 获取cameraOutputCapability参数
let cameraOutputCapability = cameraManager.getSupportedOutputCapability(cameras[0])
// 获取相机输出流
this.previewOutput = cameraManager.createPreviewOutput(cameraOutputCapability.previewProfiles[0], surfaceId)
// 获取一个可以创建相片输出流的id
let receivingSurfaceId = await this.imageReceiver.getReceivingSurfaceId()
// 创建相片输出流
this.photoOutput = cameraManager.createPhotoOutput(cameraOutputCapability.photoProfiles[0], receivingSurfaceId)
// 获取捕获会话的实例
this.captureSession = cameraManager.createCaptureSession()
// 开始会话配置
this.captureSession.beginConfig()
// 使用相机输入流---添加一个摄像头输入流
this.captureSession.addInput(this.cameraInput)
// 使用相机输出流---添加一个摄像头输出
this.captureSession.addOutput(this.previewOutput)
// 使用相片输出流---添加相机照片的输出
this.captureSession.addOutput(this.photoOutput)
// 结束并提交配置
await this.captureSession.commitConfig()
// 开始捕获会话
await this.captureSession.start()
Logger.info("createCamera end")
}
CameraService.ets文件拍照函数中,指定相片参数设置,然后调用capture()函数完成拍照。
拍照后会触发图片接收器的’imageArrival’事件。拍照函数在使用相机扫描二维码的时候调用。
该图片接收器可以监听’imageArrival’事件,当相机拍照时会触发该事件。在监听事件的回调函数里,实现对拍照的图片进行处理。
CameraService.ets文件拍照函数:
takePicture() {
let photoSetting = {
rotation: camera.ImageRotation.ROTATION_0,
quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM,
mirror: false
}
this.photoOutput.capture(photoSetting)
}
二维码解析实现代码
二维码解析类文件为:QRCodeParser.ets,支持拍照识别二维码,还支持从相册选择二维码图片进行识别。
我们首先看下如何解析从相机获取的二维码图片,对应函数为:parseQRCodeImageFromCamera,该类指定一个时间随机的图片文件名,图片归档格式,然后继续调用函数parseQRCodeImageWithNameFromCamera。
/**
* 解析从相机获取的二维码图片
*
* @param cameraService
* @param canvasContext
*/
parseQRCodeImageFromCamera(cameraService: CameraService,
imageComponentType?: image.ComponentType): void {
Logger.info("parseQRCodeImageFromCamera start")
let fileName = this.getRandomFileName(QRCodeScanConst.IMG_FILE_PREFIX, QRCodeScanConst.IMG_SUFFIX_JPG)
this.parseQRCodeImageWithNameFromCamera(cameraService, fileName, imageComponentType);
Logger.info("parseQRCodeImageFromCamera end")
}
在函数parseQRCodeImageWithNameFromCamera中,注册图片接收器监听’imageArrival’事件,在监听函数里,对二维码图片进行解析识别。
当相机对二维码拍照后,二维码图片会被保存到指定的目录下,返回文件URI。保存图片的函数createPublicDirFileAsset的实现,可以自行查阅源码。
根据返回的图片URI,调用函数parseImageQRCode对二维码进行解析。函数parseImageQRCode后文会介绍。
如果解析失败,弹窗提示解析失败。如果解析成功,会被解析结果保存到AppStorage。
保存到AppStorage的二维码解析结果会被@watch装饰器的变量监视,当监视到有二维码识别结果后,会在界面展示,后文会介绍。
QRCodeParser.ets文件parseQRCodeImageWithNameFromCamera函数代码:
/**
* 解析从相机获取的二维码图片,指定文件名称
*
* @param cameraService
* @param canvasContext
*/
parseQRCodeImageWithNameFromCamera(cameraService: CameraService,
fileDisplayName: string,
imageComponentType?: image.ComponentType): void {
Logger.info("parseQRCodeImageWithNameFromCamera...")
cameraService.imageReceiver.on('imageArrival', async () => {
Logger.info("parseQRCodeImageWithNameFromCamera imageArrival start")
// 从接收器获取下一个图像,并返回结果
let targetImage: image.Image = await cameraService.imageReceiver.readNextImage()
// 默认按JPEG格式处理
let imgComponentType = imageComponentType === undefined ? image.ComponentType.JPEG : imageComponentType
let imageComponent = await targetImage.getComponent(imgComponentType)
// 将image的ArrayBuffer写入指定文件中,返回文件uri
let imageUri = await this.createPublicDirFileAsset(fileDisplayName, mediaLibrary.MediaType.IMAGE,
mediaLibrary.DirectoryType.DIR_IMAGE, imageComponent.byteBuffer);
// 释放已读取的image资源,以便处理下一个资源
await targetImage.release() // 解析二维码
let qrCodeParseRlt = await this.parseImageQRCode(imageUri);
if (!qrCodeParseRlt.isSucess) {
Logger.error("parseQRCodeImageWithNameFromCamera qrCodeParseRlt is null")
prompt.showToast({
message: $r('app.string.qrCodeNotRecognized')
})
return;
}
// 拼接解析结果
AppStorage.SetOrCreate(QRCodeScanConst.QR_CODE_PARSE_RESULT, qrCodeParseRlt.decodeResult);
Logger.info("parseQRCodeImageWithNameFromCamera imageArrival end")
})
}
二维码解析类文件为:QRCodeParser.ets,支持拍照识别二维码,还支持从相册选择二维码图片进行识别。
我们接着,再看下如何解析从相册里挑选的二维码图片。
参数imageSrc为选定图片的URI地址。
getImageSource()代码可以自行查询,实现根据图片URI返回图片的宽、高,以及图片的pixelMap数据。然后,把像素数据写入ArrayBuffer,供zxing二维码识别程序使用。
函数RGBLuminanceSource、BinaryBitmap、BinaryBitmap等都是zxing的类。通过调用MultiFormatReader的decode函数对二维码图像进行解析。
如果解析成功,会返回成功的标记和解析的结果。
如果解析失败,会在catch语句块里进行处理,会返回失败的标记和解析失败的原因。
QRCodeParser.ets文件parseImageQRCode函数代码:
/**
* 解析图片二维码信息
* @param canvasContext
* @param imageSrc
*/
async parseImageQRCode(imageSrc: string): Promise<DecodeResultAttribute> {
Logger.info(`parseImageQRCode start`);
// 获取图片的宽高
let imageSource = await this.getImageSource(imageSrc);
let imageWidth = imageSource.width;
let imageHeight = imageSource.height;
// 获取PixelMap图片数据
let pixMapData = imageSource.pixelMap;
let pixelBytesNumber = pixMapData.getPixelBytesNumber();
let arrayBuffer: ArrayBuffer = new ArrayBuffer(pixelBytesNumber);
// 读取图像像素数据,结果写入ArrayBuffer里
await pixMapData.readPixelsToBuffer(arrayBuffer);
let int32Array = new Int32Array(arrayBuffer);
let luminanceSource = new RGBLuminanceSource(int32Array, imageWidth, imageHeight);
let binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
let mltiFormatReader = new MultiFormatReader();
let hints = new Map();
hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);
mltiFormatReader.setHints(hints);
try {
// 解析二维码
let decodeResult = mltiFormatReader.decode(binaryBitmap);
let decodeText = decodeResult.getText();
Logger.info(`parseImageQRCode end ${decodeText}`);
return { isSucess: true, decodeResult: decodeText };
} catch (err) {
let error = `The error is ${err}`;
Logger.info(`parseImageQRCode end`);
return { isSucess: false, decodeResult: error };
}
}
相机扫描识别二维码
在文件QRCodeScanComponent.ets中实现了二维码扫描自定义组件。我们看下该文件中如何实现相机扫描二维码的。
在二维码扫描组件的aboutToAppear()函数调用的watchCameraPermission()函数,用于使用相机扫描二维码进行识别。
在watchCameraPermission()函数中,使用setInterval函数每100ms判断下是否具有相机权限,当有相机权限的时候,才能使用相机扫描二维码。
当具备相机权限时 ,使用setInterval函数每4000ms轮询判断下是否识别到二维码图片,如果识别到则取消执行轮询。
如果没有识别到二维码,则继续调用函数takePicture()拍照。调用该函数后,会触发图片接收器的监听事件’imageArrival’,对这个事件的监听分析,见上文。
文件QRCodeScanComponent.ets中,相机拍照识别二维码的代码片段:
aboutToAppear() {
// 监听相机权限
this.watchCameraPermission()
// 设置扫描动画
this.setQRCodeScanAnimation()
// 解析二维码图片信息
this.qrCodeParser.parseQRCodeImageFromCamera(this.cameraService);
}
......
// 监听相机权限变化
watchCameraPermission() {
let interval = setInterval(() => {
this.hasCameraPermission = AppStorage.Get(QRCodeScanConst.HAS_CAMERA_PERMISSION)
if (this.hasCameraPermission) {
let qrCodeScanInterval = setInterval(() => {
if (this.qrCodeParseResult.length > 0 || this.isQRCodeScanStopped) {
clearInterval(qrCodeScanInterval)
}
// 拍照
this.cameraService.takePicture()
}, 4000)
clearInterval(interval)
}
}, 100)
}
识别相册二维码图片
在文件QRCodeScanComponent.ets中实现了二维码扫描自定义组件。我们看下该文件中如何识别相册二维码图片。
首先,设置this.isQRCodeScanStopped为true,这个会关闭相机拍照识别二维码。
然后,通过startAbilityForResult启动相册应用,供用户选择二维码图片。
如果选择图片失败,则弹窗报错。
如果选择图片成功,则调用二维码解码函数parseImageQRCode完成对图片二维码的识别。
如果识别二维码成功,则弹窗展示二维码结果。
如果识别识别,则toast展示:未识别到二维码。
文件QRCodeScanComponent.ets中,相册选择二维码图片进行识别代码片段:
Image($r('app.media.scan_photo'))
.width(30)
.height(30)
.id('scanPhoto')
.onClick(async () => {
// 打开相册获取图片
this.isQRCodeScanStopped = true
let context = AppStorage.Get('context') as common.UIAbilityContext
await context.startAbilityForResult({
parameters: { uri: 'singleselect' },
bundleName: 'com.ohos.photos',
abilityName: 'com.ohos.photos.MainAbility',
}).then(data => {
// 获取want数据
let want = data['want'];
if (want) {
// param代表want参数中的paramters
let param = want['parameters'];
if (param) {
// 被选中的图片路径media/image/8
let selectedUri = param['select-item-list'];
setTimeout(async () => {
if (!selectedUri) {
prompt.showToast({
message: $r('app.string.queryImageFailed'),
duration: 1000
})
return;
}
// 获取解析数据
let qrCodeParseRlt = await this.qrCodeParser.parseImageQRCode(selectedUri[0]);
if (qrCodeParseRlt.isSucess) {
prompt.showDialog({
title: $r('app.string.qrcodeResult'),
message: qrCodeParseRlt.decodeResult
})
} else {
prompt.showToast({
message: $r('app.string.qrCodeNotRecognized')
})
}
}, 50)
}
}
})
})
二维码扫描组件界面
在文件QRCodeScanComponent.ets中实现了二维码扫描自定义组件。我们看下二维码扫描组件的页面布局。
整个页面使用Stack进行堆叠布局。
如果有相机权限,会XComponent组件,用于展示相机的预览输出流。XComponent组件的onLoad函数里会创建相机,onDestroy函数里会释放相机。
Image($r('app.media.scan_border'))图片就是二维码扫描框,引导用户把二维码放到框内进行扫描识别。
Divider是个分割线,该分割线使能了动画效果,在识别二维码的过程中,分割线从二维码识别框里从上到下移动。扫描动画实现代码如下:
// 扫描扫描动画
setQRCodeScanAnimation() {
setInterval(() => {
animateTo({
duration: 1000, // 动画时间
tempo: 0.5, // 动画速率
curve: Curve.EaseInOut,
delay: 200, // 动画延迟时间
iterations: -1, // 动画是否重复播放
playMode: PlayMode.Normal,
}, () => {
this.animationOrdinate = 390 // 扫描动画结束Y坐标
})
}, 2000)
}
Text($r('app.string.putTheQRCodeToScan'))引导用户把二维码放到框内进行扫描识别。
Image($r('app.media.scan_back'))返回退出应用。
Image($r('app.media.scan_photo'))从相册里挑选二维码图片进行识别。
build() {
Column() {
Stack() {
if (this.hasCameraPermission) {
XComponent({
id: 'componentId',
type: 'surface',
controller: this.xComponentController
})
.onLoad(() => {
// 适配可能需要获取设备信息
this.xComponentController.setXComponentSurfaceSize({
surfaceWidth: QRCodeScanConst.IMG_DEFAULT_SIZE.WIDTH,
surfaceHeight: QRCodeScanConst.IMG_DEFAULT_SIZE.HEIGHT
})
this.surFaceId = this.xComponentController.getXComponentSurfaceId()
this.cameraService.createCamera(this.surFaceId)
})
.onDestroy(() => {
this.cameraService.releaseCamera()
})
.height('100%')
.width('100%')
}
Column() {
Column() {
Image($r('app.media.scan_border'))
......
Divider()
.strokeWidth(1)
.height(4)
.width('100%')
.color(Color.White)
.width('100%')
.position({ x: 0, y: 0 })
.translate({ x: 0, y: this.animationOrdinate })
}
......
Text($r('app.string.putTheQRCodeToScan'))
......
}
......
Row() {
Image($r('app.media.scan_back'))
......
Row({ space: 16 }) {
Image($r('app.media.scan_photo'))
......
}
运行测试效果
可以下载橘子购物示例应用代码,使用DevEco Studio编译构建,使用Simulator模拟器或者真实设备进行运行体验。可以体验下使用相机对二维码图片进行识别,还可以尝试下识别相册中的二维码图片。
git init
git config core.sparsecheckout true
echo code/Solutions/Shopping/OrangeShopping/ > .git/info/sparse-checkout
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
git pull origin master
注意事项
当前二维码示例应用识别相册的二维码,弹出识别结果后,程序会崩溃,已经提单跟踪。示例程序待改进。
使用相机功能直接拍摄二维码的功能,一直没有成功运行,需要进一步优化。
参考资料
OpenHarmony应用实现二维码扫码识别的更多相关文章
- spring boot高性能实现二维码扫码登录(上)——单服务器版
前言 目前网页的主流登录方式是通过手机扫码二维码登录.我看了网上很多关于扫码登录博客后,发现基本思路大致是:打开网页,生成uuid,然后长连接请求后端并等待登录认证相应结果,而后端每个几百毫秒会循环查 ...
- spring boot高性能实现二维码扫码登录(中)——Redis版
前言 本打算用CountDownLatch来实现,但有个问题我没有考虑,就是当用户APP没有扫二维码的时候,线程会阻塞5分钟,这反而造成性能的下降.好吧,现在回归传统方式:前端ajax每隔1秒或2秒发 ...
- spring boot高性能实现二维码扫码登录(下)——订阅与发布机制版
前言 基于之前两篇(<spring boot高性能实现二维码扫码登录(上)——单服务器版>和<spring boot高性能实现二维码扫码登录(中)——Redis版>)的基础, ...
- iOS Swift WisdomScanKit二维码扫码SDK,自定义全屏拍照SDK,系统相册图片浏览,编辑SDK
iOS Swift WisdomScanKit 是一款强大的集二维码扫码,自定义全屏拍照,系统相册图片编辑多选和系统相册图片浏览功能于一身的 Framework SDK [1]前言: 今天给大家 ...
- Spring Cloud OAuth2(二) 扩展登陆方式:账户密码登陆、 手机验证码登陆、 二维码扫码登陆
概要 基于上文讲解的spring cloud 授权服务的搭建,本文扩展了spring security 的登陆方式,增加手机验证码登陆.二维码登陆. 主要实现方式为使用自定义filter. Authe ...
- java生成二维码扫码网页自动登录功能
找了很多资料,七七八八都试了一遍,最终写出来了这个功能. 菜鸟一枚,此文只为做笔记. 简单的一个生成二维码,通过网页确认登录,实现二维码页面跳转到主页面. 有三个servlet: CodeServle ...
- 利用websocket实现微信二维码码扫码支付
由于业务需要引入微信扫码支付,故利用websocket来实现消息推送技术. 实现大致流程:首先客户端点击微信支付按钮,触发微信支付接口,同时微信支付响应成功参数后,连接websocket客户端,此刻利 ...
- python 全栈开发,Day128(创建二维码,扫码,创建玩具的基本属性)
昨日内容回顾 1.app播放音乐 plus.audio.createPlayer(文件路径/URL) player.play() 播放音乐 player.pause() 暂停播放 player.res ...
- 利用WPF生成Q币充值二维码——扫码登录篇
一.前言 虽然腾讯官方不支持使用二维码充值Q币,但对于喜欢钻研的人来说这不是问题,本文利用WPF技术讲解从扫码登录到生成Q币充值二维码的一整套解决方案. 因为充值Q币需要先用QQ号登录官网.所以我们首 ...
- 二维码扫码登录原理及简单demo
扫码登录原理转载自: https://www.cnblogs.com/liyasong/p/saoma.html 需求介绍 首先,介绍下什么是扫码登录.现在,大部分同学手机上都装有qq和淘宝,天猫等这 ...
随机推荐
- SpringBoot与Thymeleaf入门级操作
使用Thymeleaf 三大理由: 简洁漂亮 容易理解 完美支持HTML5 使用浏览器直接打开页面 不新增标签 只需增强属性 学习目标 快速掌握Thymeleaf的基本使用:五大基础语法,常用内置对象 ...
- jvm调优监控工具jps、jstack、jmap、jhat、jstat使用详解
目录 前言 jps(Java Virtual Machine Process Status Tool) jstack jmap(Memory Map)和jhat(Java Heap Analysis ...
- 【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
问题描述 在Azure Redis的导出页面,突然不能配置Storage Account的SAS作为授权方式. 那么是否可以通过AZ CLI或者是Powershell来实现SAS的配置呢? 问题解答 ...
- 【Azure 应用服务】遇见“无法创建hybrid connection for App Service”的解决办法
Hybrid Connection (混合连接) 在两个联网应用程序之间启用了双向.请求-响应和二进制流通信以及简单的数据报流.通过混合连接,可以实现应用部署在公网环境中,而数据库保存在本地私网环境中 ...
- 【Azure 应用服务】在Azure上部署一套VUE框架的单页面应用,有什么可以参考的文档呢?
问题描述 在Azure上部署一套VUE框架的单页面应用,有什么可以参考的文档呢? 问题回答 Azure官方上并没有VUE框架的实例代码,但是可以参考Node JS项目,来进行设置. 在 Azure 中 ...
- 亲测可用,ChatGPT 对话技巧
"Linux终端" "我希望你充当一个 linux 终端.我会输入命令,你会回复终端应该显示的内容.我希望你只回复一个唯一代码块内的终端输出,没有别的.不要写解释.除 ...
- 代码随想录算法训练营第二十七天| 39. 组合总和 40.组合总和II 131.分割回文串
39. 组合总和 卡哥建议:本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制 题目链接/文章讲解:https://programmercarl.com ...
- [manjaro linux] 安装完成之后的配置工作,以及常用软件的安装
emmm 很久没有更新了,绝对不是丢掉了博客帐号,有时间还是要好好装饰以下博客的... https://zhuanlan.zhihu.com/p/114296129 看到很多过程 sudo pacma ...
- SQLmap 爆破
1.Sqlmap -u "http://114.67.246.176:11055/index.php?" --data="id=1" (这里 --dat ...
- WPF --- 重写圆角DataGrid样式
引言 因要符合UI设计, 需要一个圆角的 DataGrid 样式,其需要一个,所以需要重写DataGrid的样式, 代码 具体样式代码如下: <ResourceDictionary xmlns= ...