鸿蒙开发案例:分贝仪

【1】引言(完整代码在最后面)
分贝仪是一个简单的应用,用于测量周围环境的噪音水平。通过麦克风采集音频数据,计算当前的分贝值,并在界面上实时显示。该应用不仅展示了鸿蒙系统的基础功能,还涉及到了权限管理、音频处理和UI设计等多个方面。
【2】环境准备
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:mate60 pro
语言:ArkTS、ArkUI
权限:ohos.permission.MICROPHONE(麦克风权限)
系统库:
• @kit.AudioKit:用于音频处理的库。
• @kit.AbilityKit:用于权限管理和应用能力的库。
• @kit.BasicServicesKit:提供基本的服务支持,如错误处理等。
【3】功能模块
3.1 权限管理
在使用麦克风之前,需要请求用户的权限。如果用户拒绝,会显示一个对话框引导用户手动开启权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 请求用户权限 requestPermissionsFromUser() { const context = getContext( this ) as common.UIAbilityContext; const atManager = abilityAccessCtrl.createAtManager(); atManager.requestPermissionsFromUser(context, this .requiredPermissions, (err, data) => { const grantStatus: Array<number> = data.authResults; if (grantStatus.toString() == "-1" ) { this .showAlertDialog(); } else if (grantStatus.toString() == "0" ) { this .initialize(); } }); } |
3.2 分贝计算
通过读取麦克风采集的音频数据,计算当前环境的分贝值。计算过程中会对音频样本进行归一化处理,并计算其均方根(RMS)值,最终转换成分贝值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // 分贝计算 calculateDecibel(pcm: ArrayBuffer): number { let sum = 0; const pcmView = new DataView(pcm); const numSamples = pcm.byteLength / 2; for ( let i = 0; i < pcm.byteLength; i += 2) { const sample = pcmView.getInt16(i, true ) / 32767.0; sum += sample * sample; } const meanSquare = sum / numSamples; const rmsAmplitude = Math.sqrt(meanSquare); const referencePressure = 20e-6; const decibels = 20 * Math.log10(rmsAmplitude / referencePressure); if (isNaN(decibels)) { return -100; } const minDb = 20; const maxDb = 100; const mappedValue = ((decibels - minDb) / (maxDb - minDb)) * 100; return Math.max(0, Math.min(100, mappedValue)); } |
3.3 UI设计
界面上包含一个仪表盘显示当前分贝值,以及一段文字描述当前的噪音水平。分贝值被映射到0到100的范围内,以适应仪表盘的显示需求。界面上还有两个按钮,分别用于开始和停止分贝测量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | // 构建UI build() { Column() { Text( "分贝仪" ) .width( '100%' ) .height(44) .backgroundColor( "#fe9900" ) .textAlign(TextAlign.Center) .fontColor(Color.White); Row() { Gauge({ value: this .currentDecibel, min: 1, max: 100 }) { Column() { Text(`${ this .displayedDecibel}分贝`) .fontSize(25) .fontWeight(FontWeight.Medium) .fontColor( "#323232" ) .width( '40%' ) .height( '30%' ) .textAlign(TextAlign.Center) .margin({ top: '22.2%' }) .textOverflow({ overflow: TextOverflow.Ellipsis }) .maxLines(1); Text(`${ this .displayType}`) .fontSize(16) .fontColor( "#848484" ) .fontWeight(FontWeight.Regular) .width( '47.4%' ) .height( '15%' ) .textAlign(TextAlign.Center) .backgroundColor( "#e4e4e4" ) .borderRadius(5); }.width( '100%' ); } .startAngle(225) .endAngle(135) .colors( this .gaugeColors) .height(250) .strokeWidth(18) .description( null ) .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 }) .padding({ top: 30 }); }.width( '100%' ).justifyContent(FlexAlign.Center); Column() { ForEach( this .typeArray, (item: ValueBean, index: number) => { Row() { Text(item.description) .textAlign(TextAlign.Start) .fontColor( "#3d3d3d" ); }.width(250) .padding({ bottom: 10, top: 10 }) .borderWidth({ bottom: 1 }) .borderColor( "#737977" ); }); }.width( '100%' ); Row() { Button( '开始检测' ).clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { if ( this .audioRecorder) { this .startRecording(); } else { this .requestPermissionsFromUser(); } }); Button( '停止检测' ).clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { if ( this .audioRecorder) { this .stopRecording(); } }); }.width( '100%' ) .justifyContent(FlexAlign.SpaceEvenly) .padding({ left: 20, right: 20, top: 40, bottom: 40 }); }.height( '100%' ).width( '100%' ); } |
【4】关键代码解析
4.1 权限检查与请求
在应用启动时,首先检查是否已经获得了麦克风权限。如果没有获得权限,则请求用户授权。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 检查权限 checkPermissions() { const atManager = abilityAccessCtrl.createAtManager(); const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); const tokenId = bundleInfo.appInfo.accessTokenId; const authResults = this .requiredPermissions.map((permission) => atManager.checkAccessTokenSync(tokenId, permission)); return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); } // 请求用户权限 requestPermissionsFromUser() { const context = getContext( this ) as common.UIAbilityContext; const atManager = abilityAccessCtrl.createAtManager(); atManager.requestPermissionsFromUser(context, this .requiredPermissions, (err, data) => { const grantStatus: Array<number> = data.authResults; if (grantStatus.toString() == "-1" ) { this .showAlertDialog(); } else if (grantStatus.toString() == "0" ) { this .initialize(); } }); } |
4.2 音频记录器初始化
在获得权限后,初始化音频记录器,设置采样率、通道数、采样格式等参数,并开始监听音频数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // 初始化音频记录器 initialize() { const streamInfo: audio.AudioStreamInfo = { samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, channels: audio.AudioChannel.CHANNEL_1, sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW }; const recorderInfo: audio.AudioCapturerInfo = { source: audio.SourceType.SOURCE_TYPE_MIC, capturerFlags: 0 }; const recorderOptions: audio.AudioCapturerOptions = { streamInfo: streamInfo, capturerInfo: recorderInfo }; audio.createAudioCapturer(recorderOptions, (err, recorder) => { if (err) { console.error(`创建音频记录器失败, 错误码: ${err.code}, 错误信息: ${err.message}`); return ; } console.info(`${ this .TAG}: 音频记录器创建成功`); this .audioRecorder = recorder; if ( this .audioRecorder !== undefined) { this .audioRecorder.on( 'readData' , (buffer: ArrayBuffer) => { this .currentDecibel = this .calculateDecibel(buffer); this .updateDisplay(); }); } }); } |
4.3 更新显示
每秒钟更新一次显示的分贝值,并根据当前分贝值确定其所属的噪音级别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 更新显示 updateDisplay() { if (Date.now() - this .lastUpdateTimestamp > 1000) { this .lastUpdateTimestamp = Date.now(); this .displayedDecibel = Math.floor( this .currentDecibel); for (const item of this .typeArray) { if ( this .currentDecibel >= item.minDb && this .currentDecibel < item.maxDb) { this .displayType = item.label; break ; } } } } |
【5】完整代码
5.1 配置麦克风权限
路径:src/main/module.json5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "module" : { "requestPermissions" : [ { "name" : "ohos.permission.MICROPHONE" , "reason" : "$string:microphone_reason" , "usedScene" : { "abilities" : [ "EntryAbility" ], "when" : "inuse" } } ], |
5.2 配置权限弹窗时的描述文字
路径:src/main/resources/base/element/string.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | { "string" : [ { "name" : "module_desc" , "value" : "module description" }, { "name" : "EntryAbility_desc" , "value" : "description" }, { "name" : "EntryAbility_label" , "value" : "label" }, { "name" : "microphone_reason" , "value" : "需要麦克风权限说明" } ] } |
5.3 完整代码
路径:src/main/ets/pages/Index.ets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | import { audio } from '@kit.AudioKit' ; // 导入音频相关的库 import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit' ; // 导入权限管理相关的库 import { BusinessError } from '@kit.BasicServicesKit' ; // 导入业务错误处理 // 定义一个类,用于存储分贝范围及其描述 class ValueBean { label: string; // 标签 description: string; // 描述 minDb: number; // 最小分贝值 maxDb: number; // 最大分贝值 colorStart: string; // 起始颜色 colorEnd: string; // 结束颜色 // 构造函数,初始化属性 constructor(label: string, description: string, minDb: number, maxDb: number, colorStart: string, colorEnd: string) { this .label = label; this .description = description; this .minDb = minDb; this .maxDb = maxDb; this .colorStart = colorStart; this .colorEnd = colorEnd; } } // 定义分贝仪组件 @Entry @Component struct DecibelMeter { TAG: string = 'DecibelMeter' ; // 日志标签 audioRecorder: audio.AudioCapturer | undefined = undefined; // 音频记录器 requiredPermissions: Array<Permissions> = [ 'ohos.permission.MICROPHONE' ]; // 需要的权限 @State currentDecibel: number = 0; // 当前分贝值 @State displayedDecibel: number = 0; // 显示的分贝值 lastUpdateTimestamp: number = 0; // 上次更新时间戳 @State displayType: string = '' ; // 当前显示类型 // 定义分贝范围及其描述 typeArray: ValueBean[] = [ new ValueBean( "寂静" , "0~20dB : 寂静,几乎感觉不到" , 0, 20, "#02b003" , "#016502" ), new ValueBean( "安静" , '20~40dB :安静,轻声交谈' , 20, 40, "#7ed709" , "#4f8800" ), new ValueBean( "正常" , '40~60dB :正常,普通室内谈话' , 40, 60, "#ffef01" , "#ad9e04" ), new ValueBean( "吵闹" , '60~80dB :吵闹,大声说话' , 60, 80, "#f88200" , "#965001" ), new ValueBean( "很吵" , '80~100dB: 很吵,可使听力受损' , 80, 100, "#f80000" , "#9d0001" ), ]; gaugeColors: [LinearGradient, number][] = [] // 存储仪表颜色的数组 // 组件即将出现时调用 aboutToAppear(): void { // 初始化仪表颜色 for ( let i = 0; i < this .typeArray.length; i++) { this .gaugeColors.push([ new LinearGradient([{ color: this .typeArray[i].colorStart, offset: 0 }, { color: this .typeArray[i].colorEnd, offset: 1 }]), 1]) } } // 请求用户权限 requestPermissionsFromUser() { const context = getContext( this ) as common.UIAbilityContext; // 获取上下文 const atManager = abilityAccessCtrl.createAtManager(); // 创建权限管理器 // 请求权限 atManager.requestPermissionsFromUser(context, this .requiredPermissions, (err, data) => { const grantStatus: Array<number> = data.authResults; // 获取授权结果 if (grantStatus.toString() == "-1" ) { // 用户拒绝权限 this .showAlertDialog(); // 显示提示对话框 } else if (grantStatus.toString() == "0" ) { // 用户同意权限 this .initialize(); // 初始化音频记录器 } }); } // 显示对话框提示用户开启权限 showAlertDialog() { this .getUIContext().showAlertDialog({ autoCancel: true , // 自动取消 title: '权限申请' , // 对话框标题 message: '如需使用此功能,请前往设置页面开启麦克风权限。' , // 对话框消息 cancel: () => { }, confirm: { defaultFocus: true , // 默认聚焦确认按钮 value: '好的' , // 确认按钮文本 action: () => { this .openPermissionSettingsPage(); // 打开权限设置页面 } }, onWillDismiss: () => { }, alignment: DialogAlignment.Center, // 对话框对齐方式 }); } // 打开权限设置页面 openPermissionSettingsPage() { const context = getContext() as common.UIAbilityContext; // 获取上下文 const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); // 获取包信息 context.startAbility({ bundleName: 'com.huawei.hmos.settings' , // 设置页面的包名 abilityName: 'com.huawei.hmos.settings.MainAbility' , // 设置页面的能力名 uri: 'application_info_entry' , // 打开设置->应用和元服务 parameters: { pushParams: bundleInfo.name // 按照包名打开对应设置页 } }); } // 分贝计算 calculateDecibel(pcm: ArrayBuffer): number { let sum = 0; // 初始化平方和 const pcmView = new DataView(pcm); // 创建数据视图 const numSamples = pcm.byteLength / 2; // 计算样本数量 // 归一化样本值并计算平方和 for ( let i = 0; i < pcm.byteLength; i += 2) { const sample = pcmView.getInt16(i, true ) / 32767.0; // 归一化样本值 sum += sample * sample; // 计算平方和 } // 计算平均平方值 const meanSquare = sum / numSamples; // 计算均方 // 计算RMS(均方根)振幅 const rmsAmplitude = Math.sqrt(meanSquare); // 计算RMS值 // 使用标准参考压力值 const referencePressure = 20e-6; // 20 μPa // 计算分贝值 const decibels = 20 * Math.log10(rmsAmplitude / referencePressure); // 计算分贝 // 处理NaN值 if (isNaN(decibels)) { return -100; // 返回一个极小值表示静音 } // 调整动态范围 const minDb = 20; // 调整最小分贝值 const maxDb = 100; // 调整最大分贝值 // 将分贝值映射到0到100之间的范围 const mappedValue = ((decibels - minDb) / (maxDb - minDb)) * 100; // 映射分贝值 // 确保值在0到100之间 return Math.max(0, Math.min(100, mappedValue)); // 返回映射后的值 } // 初始化音频记录器 initialize() { const streamInfo: audio.AudioStreamInfo = { samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 采样率 channels: audio.AudioChannel.CHANNEL_1, // 单声道 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码类型 }; const recorderInfo: audio.AudioCapturerInfo = { source: audio.SourceType.SOURCE_TYPE_MIC, // 音频源为麦克风 capturerFlags: 0 // 捕获标志 }; const recorderOptions: audio.AudioCapturerOptions = { streamInfo: streamInfo, // 音频流信息 capturerInfo: recorderInfo // 记录器信息 }; // 创建音频记录器 audio.createAudioCapturer(recorderOptions, (err, recorder) => { if (err) { console.error(`创建音频记录器失败, 错误码: ${err.code}, 错误信息: ${err.message}`); // 错误处理 return ; } console.info(`${ this .TAG}: 音频记录器创建成功`); // 成功日志 this .audioRecorder = recorder; // 保存记录器实例 if ( this .audioRecorder !== undefined) { // 监听音频数据 this .audioRecorder.on( 'readData' , (buffer: ArrayBuffer) => { this .currentDecibel = this .calculateDecibel(buffer); // 计算当前分贝值 this .updateDisplay(); // 更新显示 }); } this .startRecording(); // 开始录音 }); } // 开始录音 startRecording() { if ( this .audioRecorder !== undefined) { // 检查音频记录器是否已定义 this .audioRecorder.start((err: BusinessError) => { // 调用开始录音方法 if (err) { console.error( '开始录音失败' ); // 记录错误信息 } else { console.info( '开始录音成功' ); // 记录成功信息 } }); } } // 停止录音 stopRecording() { if ( this .audioRecorder !== undefined) { // 检查音频记录器是否已定义 this .audioRecorder.stop((err: BusinessError) => { // 调用停止录音方法 if (err) { console.error( '停止录音失败' ); // 记录错误信息 } else { console.info( '停止录音成功' ); // 记录成功信息 } }); } } // 更新显示 updateDisplay() { if (Date.now() - this .lastUpdateTimestamp > 1000) { // 每隔1秒更新一次显示 this .lastUpdateTimestamp = Date.now(); // 更新最后更新时间戳 this .displayedDecibel = Math.floor( this .currentDecibel); // 将当前分贝值取整并赋值给显示的分贝值 // 遍历分贝类型数组,确定当前分贝值对应的类型 for (const item of this .typeArray) { if ( this .currentDecibel >= item.minDb && this .currentDecibel < item.maxDb) { // 检查当前分贝值是否在某个范围内 this .displayType = item.label; // 设置当前显示类型 break ; // 找到对应类型后退出循环 } } } } // 检查权限 checkPermissions() { const atManager = abilityAccessCtrl.createAtManager(); // 创建权限管理器 const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); // 获取包信息 const tokenId = bundleInfo.appInfo.accessTokenId; // 获取应用的唯一标识 // 检查每个权限的授权状态 const authResults = this .requiredPermissions.map((permission) => atManager.checkAccessTokenSync(tokenId, permission)); return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); // 返回是否所有权限都被授予 } // 构建UI build() { Column() { Text( "分贝仪" ) // 显示标题 .width( '100%' ) // 设置宽度为100% .height(44) // 设置高度为44 .backgroundColor( "#fe9900" ) // 设置背景颜色 .textAlign(TextAlign.Center) // 设置文本对齐方式 .fontColor(Color.White); // 设置字体颜色 Row() { Gauge({ value: this .currentDecibel, min: 1, max: 100 }) { // 创建仪表,显示当前分贝值 Column() { Text(`${ this .displayedDecibel}分贝`) // 显示当前分贝值 .fontSize(25) // 设置字体大小 .fontWeight(FontWeight.Medium) // 设置字体粗细 .fontColor( "#323232" ) // 设置字体颜色 .width( '40%' ) // 设置宽度为40% .height( '30%' ) // 设置高度为30% .textAlign(TextAlign.Center) // 设置文本对齐方式 .margin({ top: '22.2%' }) // 设置上边距 .textOverflow({ overflow: TextOverflow.Ellipsis }) // 设置文本溢出处理 .maxLines(1); // 设置最大行数为1 Text(`${ this .displayType}`) // 显示当前类型 .fontSize(16) // 设置字体大小 .fontColor( "#848484" ) // 设置字体颜色 .fontWeight(FontWeight.Regular) // 设置字体粗细 .width( '47.4%' ) // 设置宽度为47.4% .height( '15%' ) // 设置高度为15% .textAlign(TextAlign.Center) // 设置文本对齐方式 .backgroundColor( "#e4e4e4" ) // 设置背景颜色 .borderRadius(5); // 设置圆角 }.width( '100%' ); // 设置列宽度为100% } .startAngle(225) // 设置仪表起始角度 .endAngle(135) // 设置仪表结束角度 .colors( this .gaugeColors) // 设置仪表颜色 .height(250) // 设置仪表高度 .strokeWidth(18) // 设置仪表边框宽度 .description( null ) // 设置描述为null .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 }) // 设置阴影效果 .padding({ top: 30 }); // 设置内边距 }.width( '100%' ).justifyContent(FlexAlign.Center); // 设置行宽度为100%并居中对齐 Column() { ForEach( this .typeArray, (item: ValueBean, index: number) => { // 遍历分贝类型数组 Row() { Text(item.description) // 显示每个类型的描述 .textAlign(TextAlign.Start) // 设置文本对齐方式 .fontColor( "#3d3d3d" ); // 设置字体颜色 }.width(250) // 设置行宽度为250 .padding({ bottom: 10, top: 10 }) // 设置上下内边距 .borderWidth({ bottom: 1 }) // 设置下边框宽度 .borderColor( "#737977" ); // 设置下边框颜色 }); }.width( '100%' ); // 设置列宽度为100% Row() { Button( '开始检测' ).clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建开始检测按钮 if ( this .audioRecorder) { // 检查音频记录器是否已定义 this .startRecording(); // 开始录音 } else { this .requestPermissionsFromUser(); // 请求用户权限 } }); Button( '停止检测' ).clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建停止检测按钮 if ( this .audioRecorder) { // 检查音频记录器是否已定义 this .stopRecording(); // 停止录音 } }); }.width( '100%' ) // 设置行宽度为100% .justifyContent(FlexAlign.SpaceEvenly) // 设置内容均匀分布 .padding({ // 设置内边距 left: 20, right: 20, top: 40, bottom: 40 }); }.height( '100%' ).width( '100%' ); // 设置列高度和宽度为100% } // 页面显示时的处理 onPageShow(): void { const hasPermission = this .checkPermissions(); // 检查权限 console.info(`麦克风权限状态: ${hasPermission ? '已开启' : '未开启' }`); // 打印权限状态 if (hasPermission) { // 如果权限已开启 if ( this .audioRecorder) { // 检查音频记录器是否已定义 this .startRecording(); // 开始录音 } else { this .requestPermissionsFromUser(); // 请求用户权限 } } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了