HarmonyOS开发实战( Beta5.0)Native Drawing自绘制能力替代Canvas提升性能

简介

Canvas 画布组件是用来显示自绘内容的组件,它具有保留历史绘制内容、增量绘制的特点。Canvas 有 CanvasRenderingContext2D/OffscreenCanvasRenderingContext2D 和 DrawingRenderingContext 两套API,应用使用两套API绘制的内容都可以在绑定的 Canvas 组件上显示。其中 CanvasRenderingContext2D 按照W3C标准封装了 Native Drawing 接口,可以方便快速复用web应用的绘制逻辑,因此非常适用于web应用和游戏、快速原型设计、数据可视化、在线绘图板、教学工具或创意应用等场景。然而,由于它的性能依赖于浏览器的实现,不如原生API那样接近硬件,因此对于性能要求比较高绘制比较复杂或者硬件依赖性比较强的场景如高性能游戏开发、专业图形处理软件、桌面或移动应用等,使用 Canvas CanvasRenderingContext2D 绘制会存在一定的卡顿、掉帧等性能问题,此时可以直接使用 Native Drawing 接口自绘制替代 Canvas 绘制来提升绘制性能。

方案适用场景特点
适用于Canvas CanvasRenderingContext2D绘制web应用和游戏、快速原型设计、数据可视化、在线绘图板、教学工具或创意应用场景简单、跨平台、快捷灵活、兼容性强、开发维护成本低、性能要求低
适用于Native Drawing绘制高性能游戏开发、专业图形处理软件、桌面或移动应用场景复杂、资源管理精细、硬件依赖强、与平台深度集成、性能要求高

原理机制

由于 Canvas 的 CanvasRenderingContext2D 绘制本质上是对 Native Drawing 接口的封装,相对于直接使用 Native Drawing 接口,使用 Canvas 的 CanvasRenderingContext2D 绘制多了一层接口的调用,并且它依赖于浏览器的具体实现。如果图片绘制比较复杂,执行的绘制指令可能会成倍数的增长,进而绘制性能下降的更加严重,导致卡顿、掉帧等问题。下面我们以实现在背景图上绘制1000个透明空心圆的玻璃效果来对比两者的性能差异。

场景示例

上图是一个绘制1000个透明空心圆与背景图融合的绘制场景,下面我们分别使用 Canvas 的 CanvasRenderingContext2D(反例) 和 Native 侧的 Drawing(正例) 来实现该场景,并分析两者的性能差异。

  • 反例(使用Canvas 的 CanvasRenderingContext2D绘制)

Canvas 的 CanvasRenderingContext2D 绘制使用 globalCompositeOperation 属性来实现各种图层混合模式。此处将该属性值设置为 destination-out 来实现透明空心圆的玻璃融合效果。具体实现步骤如下:

1、使用自定义组件 GlassCoverView 来实现透明圆圈。 在首页点击"Begin Draw"按钮,随机生成1000个0-1的位置列表。

//  /entry/src/main/ets/pages/Index.ets
import GlassCoverView from '../view/GlassCoverView'

@Entry
@Component
struct Index {
  @State pointsToDraw: number[][] = [];

  build() {
    Stack() {
      Image($r('app.media.drawImage'))
        .width('100%')
        .height('100%')
      // 透明圆圈自定义组件,在此组件中绘制1000个透明空心圆
      GlassCoverView({ pointsToDraw: this.pointsToDraw })
        .width('100%')
        .height('100%')
      Button('Begin Draw')
        .width(100)
        .height(40)
        .margin({ bottom: 40 })
        .onClick(() => {
          this.startDraw();
        })
    }.alignContent(Alignment.Bottom)
    .width('100%')
    .height('100%')
  }
  // 随机生成1000个0-1的位置列表
  private startDraw() {
    this.pointsToDraw = [];
    for (let index = 0; index < 1000; index++) {
      this.pointsToDraw.push([Math.random(), Math.random()]);
    }
  }
}

2、GlassCoverView子页面使用@Watch装饰器,监控到母页面位置列表数据pointsToDraw更新后,在屏幕上绘制1000个透明空心圆圈(具体参见 onDraw() 方法)。

// /entry/src/main/ets/view/GlassCoverView.ets

/**
 * 玻璃蒙层效果
 */

@Preview
@Component
export default struct GlassCoverView {
  /**
   * 位置列表,x、y都在[0,1]之间
   */
  @Prop
  @Watch('onDraw')
  pointsToDraw: number[][] = [];
  private settings = new RenderingContextSettings(true);
  private renderContext = new CanvasRenderingContext2D(this.settings);
  private viewWidth: number = 0;
  private viewHeight: number = 0;

  build() {
    Stack() {
      Canvas(this.renderContext)
        .width('100%')
        .height('100%')
        .onAreaChange((_: Area, newValue: Area) => {
          this.handleAreaChange(newValue)
        })
    }
    .height('100%')
    .width('100%')
  }

  private handleAreaChange(area: Area) {
    this.viewWidth = parseInt(area.width.toString());
    this.viewHeight = parseInt(area.height.toString());
    this.onDraw();
  }

  private onDraw() {
    const canvas = this.renderContext;
    if (canvas === undefined) {
      return;
    }
    hiTraceMeter.startTrace('chisj slow', 1);
    console.info('chisj debug: slow start');
    // 保存绘图上下文
    canvas.save();
    // 删除指定区域内的绘制内容
    canvas.clearRect(0, 0, this.viewWidth, this.viewHeight);
    // 指定绘制的填充色
    canvas.fillStyle = '#77CCCCCC';
    // 填充一个矩形
    canvas.fillRect(0, 0, this.viewWidth, this.viewHeight);
    // 绘制空心圆圈
    canvas.globalCompositeOperation = 'destination-out';
    canvas.fillStyle = '#CCCCCC';
    this.pointsToDraw.forEach((xy: number[]) => {
      this.drawOneCell(canvas, xy[0] * this.viewWidth, xy[1] * this.viewHeight, 5);
    })
    // 对保存的绘图上下文进行恢复
    canvas.restore();
    console.info('chisj debug: slow end');
    hiTraceMeter.finishTrace('chisj slow', 1);
  }

  // 根据指定的位置及宽度绘制圆
  private drawOneCell(canvas: CanvasRenderer, x: number, y: number, width: number) {
    canvas.beginPath();
    // 绘制弧线路径
    canvas.arc(x, y, width, 0, Math.PI * 2);
    canvas.closePath();
    canvas.fill();
  }
}

使用Canvas 的 CanvasRenderingContext2D 绘制trace图

 从图可以看到绘制1000个圆圈耗时34.1毫秒。

  • 正例(使用Native侧Drawing绘制)

Native Drawing 主要使用分层接口 OH_Drawing_CanvasSaveLayer 和融合接口 OH_Drawing_BrushSetBlendMode 来实现多图融合效果。通过在前端创建一个自绘制节点 RenderNode, 并将图形绘制上下文及背景图参数通过 Native 侧暴露的接口传入,由 Native 侧使用相应的 Drawing 接口进行绘制,具体实现步骤如下:

前端实现

1、前端定义一个 RenderNode 自绘制渲染节点,将背景图 this.pMap 和图形绘制上下文 context 传入 Native,调用 Native 侧的 nativeOnDraw 接口进行绘制。

// entry/src/main/ets/pages/Index.ets

enum DrawType { NONE, PATH, TEXT, IMAGE };

class MyRenderNode extends RenderNode {
  private drawType: DrawType = DrawType.NONE;
  private pMap: image.PixelMap | undefined = undefined;
  draw(context: DrawContext) {
    // 调用Native侧的nativeOnDraw接口进行绘制,将背景图 this.pMap 和图形绘制上下文 context 作为参数传入
    testNapi.nativeOnDraw(666, context, vp2px(this.size.width), vp2px(this.size.height), this.drawType, this.pMap);
  }
  // 设置绘制类型
  resetType(type: DrawType) {
    this.drawType = type;
  }
  // 设置背景图
  setPixelMap(p: PixelMap) {
    this.pMap = p;
  }
}

2、新建一个自绘制渲染节点,并定义一个 NodeController,对该节点进行管理。

// entry/src/main/ets/pages/Index.ets

// 创建一个 MyRenderNode 对象
const newNode = new MyRenderNode();
// 定义 newNode 的大小和位置
newNode.frame = {
  x: 0,
  y: 0,
  width: 980,
  height: 1280
};

class MyNodeController extends NodeController {
  private rootNode: FrameNode | null = null;

  makeNode(uiContext: UIContext): FrameNode | null {
    this.rootNode = new FrameNode(uiContext);
    if (this.rootNode === null) {
      return null;
    }
    const renderNode = this.rootNode.getRenderNode();
    if (renderNode !== null) {
      renderNode.appendChild(newNode);
    }
    return this.rootNode;
  }
}

3、在页面中将自绘制节点挂载到 NodeContainer 上。

// entry/src/main/ets/pages/Index.ets

@Entry
@Component
struct Index {
  private myNodeController: MyNodeController = new MyNodeController();

  aboutToAppear(): void {
    const context: Context = getContext(this);
    const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
    resourceMgr.getRawFileContent('drawImage.png').then((fileData: Uint8Array) => {
      console.info('success in getRawFileContent');
      const buffer = fileData.buffer.slice(0);
      const imageSource: image.ImageSource = image.createImageSource(buffer);
      imageSource.createPixelMap().then((pMap: image.PixelMap) => {
        // 自绘制渲染节点背景图
        newNode.setPixelMap(pMap);

      }).catch((err: BusinessError) => {
        console.error('fail to create PixelMap');
      }).catch((err: BusinessError) => {
        console.error('fail to getRawFileContent');
      })
    })
  }

  build() {
    Column() {
      Row() {
        // 将自绘制渲染节点挂载到 NodeContainer
        NodeContainer(this.myNodeController)
          .height('100%')
      }
      .width('100%')
      .height('80%')

      Row() {
        Button('Draw Image')
          .margin({ bottom: 50, right: 12 })
          .onClick(() => {
            newNode.resetType(DrawType.IMAGE);
            newNode.invalidate();
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .shadow(ShadowStyle.OUTER_DEFAULT_SM)
      .alignItems(VerticalAlign.Bottom)
      .layoutWeight(1)
    }
  }
}
Native侧实现

1、Native 侧暴露绘制接口 nativeOnDraw 供前端调用,该接口绑定 Native侧的 OnDraw 函数,ArkTs传入的参数在该函数中处理。

// entry/src/main/cpp/native_bridge.cpp

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"nativeOnDraw", nullptr, OnDraw, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

2、在 OnDraw 函数中接收前端传入的参数,此处主要是图形绘制上下文及背景图参数。

// entry/src/main/cpp/native_bridge.cpp

static napi_value OnDraw(napi_env env, napi_callback_info info) {
    size_t argc = 6;
    napi_value args[6] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    int32_t id;
    napi_get_value_int32(env, args[0], &id);
    
    // 图形绘制上下文参数
    void *temp = nullptr;
    napi_unwrap(env, args[1], &temp);
    OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas *>(temp);

    int32_t width;
    napi_get_value_int32(env, args[2], &width);

    int32_t height;
    napi_get_value_int32(env, args[3], &height);
    
    DRAWING_LOGI("OnDraw, width:%{public}d, helght:%{public}d", width, height);
    int32_t drawOption;
    napi_get_value_int32(env, args[4], &drawOption);
    // 背景图参数
    NativePixelMap *nativePixelMap = OH_PixelMap_InitNativePixelMap(env, args[5]);
    if (drawOption == IMAGE) {
        // 调用绘制图形融合接口进行绘制
        NativeOnDrawPixelMap(canvas, nativePixelMap, width, height);
    }
    return nullptr;
}

3、在 NativeOnDrawPixelMap 函数中实现透明圆圈绘制(主要使用分层接口 OH_Drawing_CanvasSaveLayer 和融合接口 OH_Drawing_BrushSetBlendMode 来实现多图融合效果)。

// entry/src/main/cpp/native_bridge.cpp

enum DrawType { NONE, PATH, TEXT, IMAGE };
#define DRAW_MAX_NUM 1000   // 最大绘制圆圈数量

// 随机生成坐标位置
static int RangedRand(int range_min, int range_max) {
    int r = ((double)rand() / RAND_MAX) * (range_max - range_min) + range_min;
    return r;
}

static void NativeOnDrawPixelMap(OH_Drawing_Canvas *canvas, NativePixelMap *nativeMap) {
    // 画背景图
    OH_Drawing_CanvasSave(canvas);
    OH_Drawing_PixelMap *pixelMap = OH_Drawing_PixelMapGetFromNativePixelMap(nativeMap);
    // 创建采样选项对象
    OH_Drawing_SamplingOptions *sampling = OH_Drawing_SamplingOptionsCreate(FILTER_MODE_NEAREST, MIPMAP_MODE_NONE);
    // 获取背景图原图区域
    OH_Drawing_Rect *src = OH_Drawing_RectCreate(0, 0, 360, 693);
    // 创建渲染区域
    OH_Drawing_Rect *dst = OH_Drawing_RectCreate(0, 0, 1300, 2500);
    // 创建画刷
    OH_Drawing_Brush *brush = OH_Drawing_BrushCreate();
    OH_Drawing_CanvasAttachBrush(canvas, brush);
    // 将背景图渲染到画布指定区域
    OH_Drawing_CanvasDrawPixelMapRect(canvas, pixelMap, src, dst, sampling);
    OH_Drawing_CanvasDetachBrush(canvas);
    
    // 调用分层接口
    OH_Drawing_CanvasSaveLayer(canvas, dst, brush);
    
    // 画蒙层
    OH_Drawing_Rect *rect2 = OH_Drawing_RectCreate(0, 0, 1300, 2500);
    OH_Drawing_Brush *brush2 = OH_Drawing_BrushCreate();
    // 设置画刷颜色
    OH_Drawing_BrushSetColor(brush2, OH_Drawing_ColorSetArgb(0x77, 0xCC, 0xCC, 0xCC));
    OH_Drawing_CanvasAttachBrush(canvas, brush2);
    OH_Drawing_CanvasDrawRect(canvas, rect2);
    OH_Drawing_CanvasDetachBrush(canvas);

    OH_Drawing_Point *pointArray[DRAW_MAX_NUM];
    int x = 0;
    int y = 0;
    for (int i = 0; i < DRAW_MAX_NUM; i++) {
        // 生成随机坐标
        x = RangedRand(0, 1300);
        y = RangedRand(0, 2500);
        pointArray[i] = OH_Drawing_PointCreate(x, y);
    }

    OH_Drawing_Point *point = OH_Drawing_PointCreate(800, 1750);
    OH_Drawing_Brush *brush3 = OH_Drawing_BrushCreate();
    // 设置圆圈的画刷和混合模式
    OH_Drawing_BrushSetBlendMode(brush3, BLEND_MODE_DST_OUT);
    OH_Drawing_CanvasAttachBrush(canvas, brush3);
    // 画圈
    for (int i = 0; i < DRAW_MAX_NUM; i++) {
        OH_Drawing_CanvasDrawCircle(canvas, pointArray[i], 15);
    }

    // 销毁对象
    OH_Drawing_CanvasDetachBrush(canvas);
    OH_Drawing_RectDestroy(rect2);
    OH_Drawing_BrushDestroy(brush2);
    OH_Drawing_BrushDestroy(brush3);
    OH_Drawing_PointDestroy(point);
    OH_Drawing_BrushDestroy(brush);
    OH_Drawing_CanvasRestore(canvas);
    OH_Drawing_SamplingOptionsDestroy(sampling);
    OH_Drawing_RectDestroy(src);
    OH_Drawing_RectDestroy(dst);
}

使用Native侧Drawing绘制trace图

 从图可以看到绘制1000个圆圈耗时1.2毫秒,相较于 Canvas 的 CanvasRenderingContext2D 绘制有较大的性能提升。

效果对比

方案圆圈数量耗时
Canvas 的 CanvasRenderingContext2D画透明圈(反例)100034.1毫秒
Native Drawing画透明圈(正例)10001.2毫秒

通过上述对比可以发现,在实现较大数量透明空心圆绘制时,相比于Canvas 的 CanvasRenderingContext2D,使用 Native Drawing 绘制可以得到明显的性能提升。以上只是实现透明空心圆,针对实心圆及其他场景(如 globalCompositeOperation 属性的其他值),由于实现机制的不同,绘制的指令数量也存在差异,从而性能数据会存在一些差异。实际应用中,我们可以根据自己的需要等实际情况,在对性能要求不高的情况下采用 Canvas 的 CanvasRenderingContext2D 绘制,如果对性能要求比较高或者比较依赖于硬件,建议使用 Native Drawing 进行绘制。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~

GitCode - 全球开发者的开源社区,开源代码托管平台


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【WPS Excel】复制表格时,提示“图片太大,超过部份将被截去“ 问题

WPS表格 2019版本 升级到 WPS最新版 WPS-支持多人在线协作编辑Word、Excel和PPT文档_WPS官方网站 使用最新版就能够解决这个问题&#xff0c;如果仍旧无法解决可以勾选如下配置 重启Excel解决。 请勾选&#xff1a;文件 - 选项 - 编辑 - 不提示且不压缩文件中的图像

html 页面引入 vue 组件之 http-vue-loader.js

一、http-vue-loader.js http-vue-loader.js 是一个 Vue 单文件组件加载器&#xff0c;可以让我们在传统的 HTML 页面中使用 Vue 单文件组件&#xff0c;而不必依赖 Node.js 等其他构建工具。它内置了 Vue.js 和样式加载器&#xff0c;并能自动解析 Vue 单文件组件中的所有内容…

电脑知识:如何恢复 Word、媒体和存档文件?

如果您是 Word 用户&#xff0c;那么您一定对无法打开 Word 文档的问题很熟悉。当文档包含大量关键信息时&#xff0c;情况会变得更加复杂。如果您遇到这种情况&#xff0c;那么您将如何处理&#xff1f; 我们再怎么强调在外部存储位置&#xff08;如外部硬盘、网络位置&#…

jenkins 工具使用

使用方式 替代手动&#xff0c;自动化拉取、集成、构建、测试&#xff1b;是CI/CD持续集成、持续部署主流开发模式中重要的环节&#xff1b;必须组件 jenkins-gitlab&#xff0c;代码公共仓库服务器&#xff08;至少6G内存&#xff09;&#xff1b;jenkins-server&#xff0c;…

flutter Image

Flutter中&#xff0c;Image是一个用于显示图片的控件&#xff0c;可以显示网络图片、本地图片以及Asset中的图片。Image控件支持多种常见的图片格式&#xff0c;例如PNG、JPEG、GIF等。 const Image({super.key,required this.image,this.frameBuilder,this.loadingBuilder,th…

社区电商系统源码之卷轴模式:商业模式分析

随着互联网技术的发展&#xff0c;电商平台的竞争日益激烈&#xff0c;如何留住用户并提升用户粘性成为了各大电商平台关注的重点。卷轴模式作为一种新兴的用户参与和激励机制&#xff0c;在社区电商系统中得到了广泛的应用。本文将从技术角度探讨卷轴模式在社区电商系统中的实…

数据分析:R语言计算XGBoost线性回归模型的SHAP值

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍SHAP用途计算方法:应用加载R包导入数据数据预处理函数模型介绍 SHAP(SHapley Additive exPlanations)值是一种解释机器学习模型预测的方法。它基于博弈论中的Shapley值概念,…

第10讲 后端2

主要目标&#xff1a;理解滑动窗口法、位姿图优化、带IMU紧耦合的优化、掌握g2o位姿图。 第9讲介绍了以为BA为主的图优化。BA能精确优化每个相机位姿与特征点位置。不过在更大的场景中&#xff0c;大量特征点的存在会严重降低计算效率&#xff0c;导致计算量越来越大&#xff0…

3.8 串操作指令

&#x1f393; 微机原理考点专栏&#xff08;通篇免费&#xff09; 欢迎来到我的微机原理专栏&#xff01;我将帮助你在最短时间内掌握微机原理的核心内容&#xff0c;为你的考研或期末考试保驾护航。 为什么选择我的视频&#xff1f; 全程考点讲解&#xff1a;每一节视频都…

2024 高教社杯 数学建模国赛 (C题)深度剖析|农作物的种植策略|数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; CS团队倾注了大量时间和心血&#xff0c;深入挖掘解…

ChauffeurNet:通过模仿最佳驾驶和合成最坏情况进行学习驾驶

ChauffeurNet: Learning to Drive by Imitating the Best and Synthesizing the Worst ChauffeurNet&#xff1a;通过模仿最佳驾驶和合成最坏情况进行学习驾驶 https://arxiv.org/abs/1812.03079 Abstract Our goal is to train a policy for autonomous driving via imit…

4000字三合一!Stata、SPSS、MATLAB实现多元线性回归详解!

参加数学建模的小伙伴要注意了&#xff1a;多元线性回归几乎是所有分析方式里面最核心、最常用、最全面的模型&#xff0c;博主本科大致参加了10次数模&#xff0c;还有一次正大杯市场调研赛&#xff0c;其中获得拿得出手的奖有9次&#xff0c;有5次都用到了多元线性回归——至…

分享7款实现社会实践报告AI生成论文网站

在当今社会&#xff0c;AI技术的快速发展极大地改变了我们的生活方式和工作方式。特别是在学术研究和写作领域&#xff0c;AI工具的应用已经变得越来越普遍。本文将详细介绍7款实现社会实践报告AI生成的论文网站&#xff0c;并重点推荐千笔-AIPassPaper。 1. 千笔-AIPassPaper…

Flink 1.14.*中flatMap,filter等基本转换函数源码

这里以flatMap&#xff0c;filter为例&#xff0c;介绍Flink如果要实现这些基本转换需要实现哪些接口&#xff0c;Flink运行时调用这些实现类的入口&#xff0c;这些基本转换函数之间的类关系 一、创建基本转换函数需要实现类继承AbstractRichFunction并实现特性接口1、RichFla…

批量为某跟空间下的pod添加env(例如标签)

1、修改kube-apiserver配置文件&#xff0c;添加PodNodeSelector参数&#xff1a; –enable-admission-pluginsPodNodeSelector # 多个参数&#xff0c;则用逗号隔开systemctl daemon-reload systemctl restart kube-apiserver 2、修改指定命名空间内容&#xff1a; kubec…

SprinBoot+Vue校园活动报名微信小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平…

C# NX二次开发-获取体全部面

使用 UF_MODL_ask_body_faces 或获取一个体的全部面&#xff1a; 代码&#xff1a; theUf.Modl.AskBodyFaces(body.Tag, out var face_list);face_list.Foreach(x > x.NxListing()); 免责声明&#xff1a; 只用于参考&#xff0c;如果有什么问题不要找我呀。

Web开发的艺术:C#开发中实现代码简洁性与规范性的终极指南

一、变量的要求 变量名 1.简短有意义: 合适: student_count&#xff0c;student_ids&#xff0c;student_enable_list, water_price 不合适: numberOfItemsInTheCart, totalPriceOfTheOrderInTheShoppingCart,temp, data,list 2.变量名之间不要太像: 合适: totalAmount, disc…

NetSuite AI 图生代码

去年的ChatGPT热潮期间&#xff0c;我们写过一篇文章说GTP辅助编程的事。 NetSuite GPT的辅助编程实践_如何打开netsuite: html script notes的视图-CSDN博客文章浏览阅读2.2k次&#xff0c;点赞4次&#xff0c;收藏3次。作为GPT综合症的一种表现&#xff0c;我们今朝来探究下…

Kettle 锁表原因及解决办法【源码级分析】

文章目录 背景源码分析锁表场景1:资源库锁表锁表场景2:写日志锁表在哪里配置的kettle_log_table?官方解释自增 SQL 获取 BatchI 原理解决自增 SQL 获取 BatchID背景 Kettle 7.1.0 经常出现锁表的情况,体现为在数据库里有一条锁表 SQL,然后整个 Kettle 都无法运行。😂�…