uniapp-原生地图截屏返回base64-进行画板编辑功能

news2025/6/21 0:33:07

一、场景

vue写uniapp打包安卓包,实现原生地图截屏(andirod同事做的)-画板编辑功能

实现效果:

二、逻辑步骤简略

1. 由 原生地图nvue部分,回调返回 地图截屏生成的base64 数据,

2. 通过 uni插件市场 image-tools 插件 base64ToPath方法,将base64数据 转成文件路径

3. 通过 uni -API- uni.createCanvasContext() 、ctx.createPattern() 方法,将 图片数据 创建绘图对象

4. 通过 uni - movable-area+movable-view 控制画布缩放

5. 通过 canvas @touchmove="touchmove"  @touchend="touchend"  @touchstart="touchstart" 等方法实现在画布上绘制画笔

6. 生成图片及清空画布

三、具体实现

1.  由 原生地图nvue部分,回调返回 原生地图截屏生成的base64 数据(andirod同事做的)

2.  image-tools 插件 base64ToPath 

image-tools - DCloud 插件市场

import { pathToBase64, base64ToPath } from '@/js_sdk/mmmm-image-tools/index.js'

3.通过 uni -API- uni.createCanvasContext() 、ctx.createPattern() 方法

uni-app官网 API- createPattern()

initC() {
      const that = this
      // 创建绘图对象
      this.ctx = uni.createCanvasContext('mycanvas', this);
      // 在canvas设置背景 - 入参 仅支持包内路径和临时路径
      const pattern = this.ctx.createPattern(this.imageUrl, 'repeat-x')
      this.ctx.fillStyle = pattern
      this.ctx.setStrokeStyle('red')
      this.ctx.fillRect(0, 0, this.dWidth, this.dHeight)
      this.ctx.draw()
      // 方法二  在画布上插入图片
      // this.img = new Image();
      // this.img.src = this.imageUrl;
      // this.img.onload = () => {
      //   console.log('this.img', that.img.width)
      //   that.ctx.drawImage(that.img, 0, 0, this.dWidth, this.dHeight)
      //   // that.ctx.draw()
      // }
    },

4. 通过 uni - movable-area+movable-view 控制画布缩放

<movable-area :scale-area="true" :style="{'width':windowWidth+'px','height':windowHeight+'px','backgroundColor':'#ddd','overflow':'hidden'}">
      <movable-view 
        direction="all"
        :inertia="false"
        :out-of-bounds="false"
        :scale-min="0.001"
        :scale-max="4"   
        :scale="true"
        :disabled="movableDisabled"
        :scale-value="scaleValue"
        class="pr"
        :style="{'width':widths+'px','height':heights+'px'}"
        @scale="scaleChange">
          <canvas
            id="mycanvas"
            canvas-id="mycanvas"
            :style="{'width':widths+'px','height':heights+'px'}"
            @touchmove="touchmove"
            @touchend="touchend"
            @touchstart="touchstart">
          </canvas>
      </movable-view>
    </movable-area>

5.通过 canvas @touchmove="touchmove"  等方法实现在画布上绘制画笔

touchstart(e) {
      let startX = e.changedTouches[0].x
      let startY = e.changedTouches[0].y
      if (this.scaleValue > 1) {
        startX = e.changedTouches[0].x / this.scaleValue;
        startY = e.changedTouches[0].y / this.scaleValue;
      } else {
        startX = e.changedTouches[0].x * this.scaleValue;
        startY = e.changedTouches[0].y * this.scaleValue;
      }
      console.log('touchstart()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'startX', startX)
      let startPoint = { X: startX, Y: startY };
      this.points.push(startPoint);
      // 每次触摸开始,开启新的路径
      this.ctx.beginPath();
	  },
    touchmove(e) {
      if (this.isEdit) {
        let moveX = e.changedTouches[0].x
        let moveY = e.changedTouches[0].y
        if (this.scaleValue > 1) {
          moveX = e.changedTouches[0].x / this.scaleValue;
          moveY = e.changedTouches[0].y / this.scaleValue;
        } else {
          moveX = e.changedTouches[0].x * this.scaleValue;
          moveY = e.changedTouches[0].y * this.scaleValue;
        }
        console.log('touchmove()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'moveX', moveX)
        let movePoint = { X: moveX, Y: moveY };
        this.points.push(movePoint); // 存点
        let len = this.points.length;
        if (len >= 2) {
          this.draw(); // 绘制路径
        }
      }
	  },
    touchend() {
      this.points = [];
	  },
    draw() {
      let point1 = this.points[0];
      let point2 = this.points[1];
      this.points.shift();
      this.ctx.moveTo(point1.X, point1.Y);
      this.ctx.lineTo(point2.X, point2.Y);
      this.ctx.stroke();
      this.ctx.draw(true);
    },

6.生成图片及清空画布

clear() {
      let that = this;
      this.scaleValue = 1
      this.isEdit = false
      this.movableDisabled = false
      uni.getSystemInfo({
        success: function(res) {
          let canvasw = res.windowWidth;
          let canvash = res.windowHeight;
          that.ctx.clearRect(0, 0, canvasw, canvash);
          const pattern = that.ctx.createPattern(that.imageUrl, 'repeat-x')
          that.ctx.fillStyle = pattern
          that.dWidth = 285
          that.dHeight = 200
          that.ctx.setStrokeStyle('red')
          that.ctx.fillRect(0, 0, that.dWidth, that.dHeight)
          that.ctx.draw()
          // that.ctx.draw(true);
        }
      });
    },
    finish() {
      let that = this;
      uni.canvasToTempFilePath({
          canvasId: 'mycanvas',
          success: function(res) {
            // 这里的res.tempFilePath就是生成的签字图片
            // console.log('tempFilePath', res.tempFilePath);
            that.tempFilePath = res.tempFilePath
            that.$emit('onImgUrl', that.tempFilePath) // 向父级组件传值
          }
      });
    },

utils:

// 是否是 base64数据
export function isBase64Two(str) {
	try {
		return btoa(atob(str)) === str;
	} catch (err) {
		return false;
	}
}
export function isBase64(str) {
	// 正则表达式匹配B4-64编码格式
	const regex = /^[a-zA-Z0-9+\/]+={0,2}$/;
	return regex.test(str);
}
// 校验内容是否包含base64格式的图片
export function isBase64Three(str){
  let imgReg = RegExp(/data:image\/.*;base64,/)
  const res = imgReg.test(str)
  return res
}

四、总结

以下完整代码 DrawingBoard.vue:

<template>
  <view class="canvas-frame">
    <view class="icon-frame">
      <uni-icons 
        :class="{ 'is-edit': isEdit }" 
        type="compose" 
        size="18" class="icon-item mr10" 
        @click="createCanvas">编辑
      </uni-icons>
      <uni-icons
        type="plus"
        size="18" class="icon-item mr10"
        title="放大"
        @click="plusImageScalex">
      </uni-icons>
      <uni-icons
        type="minus"
        size="18" class="icon-item"
        title="缩小"
        @click="minusImageScalex">
      </uni-icons>
    </view>
    <view class="button-frame">
      <button size="mini" class="mr10" @click="clear">清空</button>
      <button size="mini" @click="finish">确定</button>
    </view>
    <!-- style="border: 1rpx solid #ccc;width: 570rpx; height: 400rpx;" -->
    <!-- <canvas
      id="mycanvas"
      canvas-id="mycanvas"
      :style="{'width':widths+'px','height':heights+'px'}"
      @touchmove="touchmove"
      @touchend="touchend"
      @touchstart="touchstart">
    </canvas> -->
    <movable-area :scale-area="true" :style="{'width':windowWidth+'px','height':windowHeight+'px','backgroundColor':'#ddd','overflow':'hidden'}">
      <movable-view 
        direction="all"
        :inertia="false"
        :out-of-bounds="false"
        :scale-min="0.001"
        :scale-max="4"   
        :scale="true"
        :disabled="movableDisabled"
        :scale-value="scaleValue"
        class="pr"
        :style="{'width':widths+'px','height':heights+'px'}"
        @scale="scaleChange">
          <canvas
            id="mycanvas"
            canvas-id="mycanvas"
            :style="{'width':widths+'px','height':heights+'px'}"
            @touchmove="touchmove"
            @touchend="touchend"
            @touchstart="touchstart">
          </canvas>
      </movable-view>
    </movable-area>
  </view>
</template>

<script>
// import { fabric } from 'fabric';
// import { fabric } from '@/utils/fabric.min.js';
// import { Database64ToFile } from '@/utils/index';
import { pathToBase64, base64ToPath } from '@/js_sdk/mmmm-image-tools/index.js'
import { isBase64 } from '@/utils/index.js';
// isBase64 方法判断 原生端返回到的数据格式是否正确
export default {
  props: {
    // 更新 原始地图画布
    mapImageUrl: {
      type: String,
      default: '',
    }
  },
  data() {
    return {
      canvasEle: null,
      isEdit: false,
      imageContainer: null,
      scaleValue: 1,
      ctx: '', // 绘图图像
      points: [], // 路径点集合
      tempFilePath: '', // 签名图片
      imageUrl: require('@/static/res/imgs/all/fushanhou-area.jpg'), // 本地图片画布资源
      img: null,
      dWidth: 285,
      dHeight: 200,
      widths: 285,
      heights: 200,
      windowWidth: 285,
      windowHeight: 200,
      movableDisabled: false,
    };
  },
  mounted() {
    this.initC()
  },
  watch: {
    mapImageUrl(newV, oldV) {
      const that = this
      console.log('watch()-mapImageUrl-newV,监听数据变化-newV', newV? '有值': '无值')
			if (!['',undefined,null].includes(newV)) {
        console.log('watch()-mapImageUrl-isBase64(newV)', isBase64(newV))
        // const base64Image = 'data:image/png;base64,/9j/4AAQSkZJRgA...'; 
        // that.base64ToTempFilePath(newV ,(tempFilePath) => {
        //   console.log('转换成功,临时地址为:', tempFilePath)
        //   that.imageUrl = tempFilePath 
        //   // 会在canvas中调用
        //   that.initC()
        // }, 
        // () =>{
        //   console.log('fail转换失败')
        // });
        const base64 = 'data:image/png;base64,' + newV;
        base64ToPath(base64).then((tempFilePath) => {
          console.log('转换成功,临时地址为:', tempFilePath)
          that.imageUrl = tempFilePath
          that.initC()
        })
			}
		},
  },
  methods: {
    
    initC() {
      const that = this
      // 创建绘图对象
      this.ctx = uni.createCanvasContext('mycanvas', this);
      // 在canvas设置背景 - 入参 仅支持包内路径和临时路径
      const pattern = this.ctx.createPattern(this.imageUrl, 'repeat-x')
      this.ctx.fillStyle = pattern
      this.ctx.setStrokeStyle('red')
      this.ctx.fillRect(0, 0, this.dWidth, this.dHeight)
      this.ctx.draw()
      // 方法二  在画布上插入图片
      // this.img = new Image();
      // this.img.src = this.imageUrl;
      // this.img.onload = () => {
      //   console.log('this.img', that.img.width)
      //   that.ctx.drawImage(that.img, 0, 0, this.dWidth, this.dHeight)
      //   // that.ctx.draw()
      // }
    },
    createCanvas() {
      this.isEdit = !this.isEdit
      if (this.isEdit) {
        this.movableDisabled = true
        // 设置画笔样式
        this.ctx.lineWidth = 2;
        this.ctx.lineCap = 'round';
        this.ctx.lineJoin = 'round';
      } else {
        this.movableDisabled = false
      }
    },
    touchstart(e) {
      let startX = e.changedTouches[0].x
      let startY = e.changedTouches[0].y
      if (this.scaleValue > 1) {
        startX = e.changedTouches[0].x / this.scaleValue;
        startY = e.changedTouches[0].y / this.scaleValue;
      } else {
        startX = e.changedTouches[0].x * this.scaleValue;
        startY = e.changedTouches[0].y * this.scaleValue;
      }
      console.log('touchstart()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'startX', startX)
      let startPoint = { X: startX, Y: startY };
      this.points.push(startPoint);
      // 每次触摸开始,开启新的路径
      this.ctx.beginPath();
	  },
    touchmove(e) {
      if (this.isEdit) {
        let moveX = e.changedTouches[0].x
        let moveY = e.changedTouches[0].y
        if (this.scaleValue > 1) {
          moveX = e.changedTouches[0].x / this.scaleValue;
          moveY = e.changedTouches[0].y / this.scaleValue;
        } else {
          moveX = e.changedTouches[0].x * this.scaleValue;
          moveY = e.changedTouches[0].y * this.scaleValue;
        }
        console.log('touchmove()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'moveX', moveX)
        let movePoint = { X: moveX, Y: moveY };
        this.points.push(movePoint); // 存点
        let len = this.points.length;
        if (len >= 2) {
          this.draw(); // 绘制路径
        }
      }
	  },
    touchend() {
      this.points = [];
	  },
    draw() {
      let point1 = this.points[0];
      let point2 = this.points[1];
      this.points.shift();
      this.ctx.moveTo(point1.X, point1.Y);
      this.ctx.lineTo(point2.X, point2.Y);
      this.ctx.stroke();
      this.ctx.draw(true);
    },
    clear() {
      let that = this;
      this.scaleValue = 1
      this.isEdit = false
      this.movableDisabled = false
      uni.getSystemInfo({
        success: function(res) {
          let canvasw = res.windowWidth;
          let canvash = res.windowHeight;
          that.ctx.clearRect(0, 0, canvasw, canvash);
          const pattern = that.ctx.createPattern(that.imageUrl, 'repeat-x')
          that.ctx.fillStyle = pattern
          that.dWidth = 285
          that.dHeight = 200
          that.ctx.setStrokeStyle('red')
          that.ctx.fillRect(0, 0, that.dWidth, that.dHeight)
          that.ctx.draw()
          // that.ctx.draw(true);
        }
      });
    },
    finish() {
      let that = this;
      uni.canvasToTempFilePath({
          canvasId: 'mycanvas',
          success: function(res) {
            // 这里的res.tempFilePath就是生成的签字图片
            // console.log('tempFilePath', res.tempFilePath);
            that.tempFilePath = res.tempFilePath
            that.$emit('onImgUrl', that.tempFilePath)
          }
      });
    },
    plusImageScalex() {
      const num = this.scaleValue + 0.4
      this.scaleValue = Math.floor(num * 100) / 100;
      // this.setImageScale(this.scaleValue);
    },
    minusImageScalex() {
      const num = this.scaleValue + 0.4
      this.scaleValue = - (Math.floor(num * 100) / 100);
      // this.setImageScale(-this.scaleValue);
    },
    // 设置图片缩放
    setImageScale(scale) {
      const that = this
      console.log('this.ctx.', this.ctx.dWidth, scale)
      // const value = this.imageContainer.scaleX + scale;
      // const zoom = Number(value.toFixed(2));
      // // 设置图片的缩放比例和位置
      // this.imageContainer.set({
      //   scaleX: zoom,
      //   scaleY: zoom,
      // });
      // this.canvasEle.renderAll();
      // that.ctx.fillRect(0, 0, 285, 200)
      // that.ctx.draw()
      const pattern = that.ctx.createPattern(that.imageUrl, 'repeat-x')
      that.ctx.fillStyle = pattern
      const w = that.dWidth * scale 
      const h = that.dHeight * scale
      console.log('this.ctx.',w, h)
      that.ctx.fillRect(0, 0, w, h)
      that.ctx.draw()
    },
    //点击事件 判断缩放比例 
    touchstart(e) {
      let x = e.touches[0].x
      let y = e.touches[0].y
      // this.node.forEach(item => {
      //   if (x > item.x * this.scale && x < (item.x + item.w) * this.scale
      //       && y > item.y * this.scale && y < (item.y + item.h) * this.scale) {
      //       //在范围内,根据标记定义节点类型
      //       // this.lookDetial(item)
      //   }
      // }) 
    },
    //s缩放比例
    scaleChange(e) {
      this.scaleValue = e.detail.scale
    },
    // 将base64图片转换为临时地址
    base64ToTempFilePath(base64Data, success, fail) {
      const fs = uni.getFileSystemManager()
      const fileName = 'temp_image_' + Date.now() + '.png' 
      // 自定义文件名,可根据需要修改
      const USER_DATA_PATH = 'ttfile://user' // uni.env.USER_DATA_PATH
      const filePath = USER_DATA_PATH + '/' + fileName
      const buffer = uni.base64ToArrayBuffer(base64Data)
      fs.writeFile({
        filePath,
        data: buffer,
        encoding: 'binary',
        success() {
          success && success(filePath)
        },
        fail() { fail && fail()}
      });
    },
    // base64转化成本地文件路径
    parseBlob(base64, success) {
      const arr = base64.split(',');
      console.log('parseBlob()-arr:', arr)
      const mime = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]);
      const n = bstr.length;
      const u8arr = new Uint8Array(n);
      for(let i = 0; i < n; i++) {
        u8arr[i] = bstr.charCodeAt(i);
      }
      // const url = URL || webkitURL;
      let a = new Blob([u8arr], {type: mime});
      const file = new File([a], 'test.png', {type: 'image/png'});
      console.log('parseBlob()-file', file);
      success && success(file)
    },
  }
};
</script>

<style lang="scss" scoped>
.pr{
  position: relative;
}
.canvas-frame {
  position: relative;
  width: 570rpx;
  // overflow: hidden;
  .icon-frame {
    position: absolute;
    top: 20rpx;
    right: 40rpx;
    z-index: 2;
  }
  .blockS{
    background: transparent;width: 570rpx; height: 400rpx;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
  }
  .icon-item {
    // font-size: 36rpx;
    // padding: 12rpx;
    // border-radius: 8rpx;
    // margin-right: 16rpx;
    // border: 1rpx solid #ccc;
    // background-color: #fff;

    &:hover {
      // background-color: #f1f1f1;
    }

    &:active {
      opacity: 0.8;
    }
  }
  .is-edit {
    color: #007EF3 !important;
  }
  .button-frame {
    position: absolute;
    bottom: 10rpx;
    right: 40rpx;
    z-index: 2;
  }

  #canvasElement {
    cursor: pointer;
  }
}
</style>

由于hbuildex-真机调试-打印很费劲,需要来回构建打包,从而找问题找了好久,其中因为 原生地图截屏返回的是纯base64的数据,未带 data:image\/.*;base64,然后找了半天的问题,需要一步步的推导和确认有没有错,错在那,花费了很多时间和精力;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go异常处理机制panic和recover

recover 使用panic抛出异常后, 将立即停止当前函数的执行并运行所有被defer的函数&#xff0c;然后将panic抛向上一层&#xff0c;直至程序crash。但是也可以使用被defer的recover函数来捕获异常阻止程序的崩溃&#xff0c;recover只有被defer后才是有意义的。 func main() { p…

如何让ES低成本、高性能?滴滴落地ZSTD压缩算法的实践分享

前文分别介绍了滴滴自研的ES强一致性多活是如何实现的、以及如何提升ES的性能潜力。由于滴滴ES日志场景每天写入量在5PB-10PB量级&#xff0c;写入压力和业务成本压力大&#xff0c;为了提升ES的写入性能&#xff0c;我们让ES支持ZSTD压缩算法&#xff0c;本篇文章详细展开滴滴…

Ceph集群安装部署

Ceph集群安装部署 目录 Ceph集群安装部署 1、环境准备 1.1 环境简介1.2 配置hosts解析(所有节点)1.3 配置时间同步2、安装docker(所有节点)3、配置镜像 3.1 下载ceph镜像(所有节点执行)3.2 搭建制作本地仓库(ceph-01节点执行)3.3 配置私有仓库(所有节点执行)3.4 为 Docker 镜像…

C语言可变数组 嵌套的可变数组,翻过了山跨过了河 又掉进了坑

可变数组 ​专栏内容&#xff1a; postgresql内核源码分析 手写数据库toadb 并发编程 个人主页&#xff1a;我的主页 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 概述 数组中元素是顺序存放&#xff0c;这一特性让我们…

Java获取路径时Class.getResource()和ClassLoader.getResource()区别

Java中取资源时&#xff0c;经常用到Class.getResource()和ClassLoader.getResource()&#xff0c;Class.getResourceAsStream()和ClassLoader().getResourceAsStream()&#xff0c;这里来看看他们在取资源文件时候的路径有什么区别的问题。 环境信息&#xff1a; 系统&#…

css3瀑布流布局遇见截断下一列展示后半截现象

css3 瀑布流布局遇见截断下一列展示后半截现象 注&#xff1a;css3实现瀑布流布局简直不要太香&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 场景-在uniapp项目中 当瀑布流布局column-grap:10px 相邻两列之间的间隙为10px&#xff0c;column-count:2,2列展示…

基于k8s的devOps自动化运维平台架构设计(中英文版本)

▲ 点击上方"DevOps和k8s全栈技术"关注公众号 In the rapidly evolving landscape of software development and IT operations, DevOps has emerged as a transformative approach to bridge the gap between development and operations teams. One of the key ena…

第五期(2022-2023)传统行业云原生技术落地调研报告——央国企篇

随着国务院国资委印发《关于加快推进国有企业数字化转型工作的通知》&#xff0c;开启了国有企业数字化转型的新篇章。大型央、 国企纷纷顺应趋势&#xff0c;加速云化布局&#xff0c;将数字化转型工作定位为“十四五”时期重点任务。同时&#xff0c;越来越多的企业通过云原生…

【Leetcode】155. 最小栈、JZ31 栈的压入、弹出序列

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 155. 最小栈 155. 最小栈 题目描述; 设计一个支持 push &#xff0c;pop &#xff0c;top …

C语言笔记7

#include <stdio.h> int main(void) {int a123;int b052;//十进制42int c0xa2;//十进制162printf("a%d b%o c%x \n",a,b,c);//分别是十进制 八进制 十六进制printf("a%d b%d c%d \n",a,b,c);printf("Hello 凌迟老头\n");return …

uniapp 使用canvas画海报(微信小程序)

效果展示&#xff1a; 项目要求&#xff1a;点击分享绘制海报&#xff0c;并实现分享到好友&#xff0c;朋友圈&#xff0c;并保存 先实现绘制海报 <view class"data_item" v-for"(item,index) in dataList" :key"index"click"goDet…

并发——线程池,Executor 框架

文章目录 1 简介2 Executor 框架结构(主要由三大部分组成)1) 任务(Runnable /Callable)2) 任务的执行(Executor)3) 异步计算的结果(Future) 3 Executor 框架的使用示意图 1 简介 Executor 框架是 Java5 之后引进的&#xff0c;在 Java 5 之后&#xff0c;通过 Executor 来启动…

vue+springboot基于web的火车高铁铁路订票管理系统

铁路订票管理系统按照权限的类型进行划分&#xff0c;分为用户和管理员两个模块。管理员模块主要针对整个系统的管理进行设计&#xff0c;提高了管理的效率和标准。主要功能包括个人中心、用户管理、火车类型管理、火车信息管理、车票预订管理、车票退票管理、系统管理等&#…

解决遥感技术在生态、能源、大气等领域的碳排放监测及模拟问题

以全球变暖为主要特征的气候变化已成为全球性环境问题&#xff0c;对全球可持续发展带来严峻挑战。2015年多国在《巴黎协定》上明确提出缔约方应尽快实现碳达峰和碳中和目标。2019年第49届 IPCC全会明确增加了基于卫星遥感的排放清单校验方法。随着碳中和目标以及全球碳盘点的现…

单源最短路

无负环 Dijkstra 迪杰斯特拉算法 采用的贪心的策略 每次遍历到始点距离最近且未访问过的顶点的邻接节点&#xff0c;直到扩展到终点为止 Dijkstra求最短路 I 给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;所有边权均为正值。 请你求出 1 …

微服务 云原生:基于 Gogs + Drone 实现 CI/CD 自动化

一般构建部署 以一个简单的前后端项目来说&#xff0c;分别编写前后端的 Dockerfile 文件并构建镜像&#xff0c;然后编写 docker-compose.yml 构建部署&#xff0c;启动运行。每次代码变更后都需重新手动打包、构建、推送。 一个简单的例子&#xff1a; 前端&#xff1a; 项…

解读HTML-入门第一文

HTML详细解读 概念解读基本结构常用标签标题标签&#xff08;h1~h6&#xff09;段落标签&#xff08;p&#xff09;链接标签&#xff08;a&#xff09;图像标签&#xff08;img&#xff09;列表标签&#xff08;ul、ol、li&#xff09;表格标签&#xff08;table、tr、td&#…

轻量级锁实现1——结构体解析、初始化

瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;14 文档用途 从底层理解轻量级锁的实现&#xff0c;从保护共享内存的角度理解轻量级锁的使用场景&#xff0c;包括上锁、等待、释放&#xff0c;理…

android 如何分析应用的内存(十六)——使用AS查看Android堆

android 如何分析应用的内存&#xff08;十六&#xff09;——使用AS查看Android堆 在前面&#xff0c;先介绍了如何使用jdb和VS code查看应用栈相关内容。 本文将介绍&#xff0c;如何查看堆中的内容。大概有&#xff1a; 堆中的对象&#xff0c;有哪些堆中的对象&#xff0…

“Can‘t open perl script configure : No such file or directory”的解决办法

编译OpenSSL的时候执行到 perl configure 时提示找不到configure&#xff0c; 然后在网上搜了搜&#xff0c;大家给的解决办法一般都是说设置环境变量或者指定configure路径再执行&#xff1b;我试了都不行&#xff0c; 最后我把perl卸了重装就正常了&#xff1b; 然后我换了…