Dell服务器用ssh方式划分磁盘Raid的方法

首先ssh到idrac去,直接会进入racadm的界面 我们以2022年最新的dell机器为例,老的机器显示的东西不一致 ssh 10.18.30.104 Password: racadm>> 注意,shell命令有自动补全功能,按两下tab键会自动补 做raid的全部命令都在storage的子命令下 一、首先拿到raid卡的名称: racadm>>storage get controllers RAID.Slot.1-1 AHCI.Embedded.2-1 AHCI.Embedded.1-1 RAID.Slot.1-1 是Raid卡控制器名称,下面两个是主板内置的,忽略。 二、然后拿到所有物理盘的名称: racadm>>storage get pdisks Disk.Bay.0:Enclosure.Internal.0-1:RAID.Slot.1-1 Disk.Bay.1:Enclosure.Internal.0-1:RAID.Slot.1-1 Disk.Bay.2:Enclosure.Internal.0-1:RAID.Slot.1-1 Disk.Bay.3:Enclosure.Internal.0-1:RAID.Slot.1-1 三、看看有没有已经做好的虚拟磁盘 racadm>>storage get vdisks 显示为空,那我们就可以放心大胆的去做了 四、划分RAID 根据级别不同,精简过的名令如下: racadm storage createvd:<控制器> -rl {r0|r1|r5|r6|r10|r50|r60} -pdkey:<磁盘组,用逗号分割> -rl — Sets the storage level. r0 — storage 0-Striping r1 — storage 1-Mirroring r5 — storage 5-Striping with Parity r6 — storage 6-Striping with Extra Parity r10 — storage 10-Spanned Striping with Mirroring r50 — storage 50-Spanned Striping with Parity r60 — storage 60-Spanned Striping with Extra Parity 给出例子: ...

2022年12月18日 · 1 分钟 · 166 字 · 八戒

Megacli操作磁盘的常用命令

这Megacli是操作磁盘Raid的常用软件,日常用的大多是dell家的机器,都是这软件,记录一下。 #!/bin/sh #/opt/MegaRAID/MegaCli/MegaCli64 -PDList -a0 /opt/MegaRAID/MegaCli/MegaCli64 -PDList -a0 | grep -A1 "Enclosure Device" # clear foreign disk #/opt/MegaRAID/MegaCli/MegaCli64 -cfgforeign -scan -a0 #/opt/MegaRAID/MegaCli/MegaCli64 -cfgforeign -clear -a0 # clear Firmware state #/opt/MegaRAID/MegaCli/MegaCli64 -PDList -a0 |grep 'Firmware state' #/opt/MegaRAID/MegaCli/MegaCli64 -PDMakeGood -Physdrv "[32:3]" -a0 # clear Firmware JBOD mode, first turn into unconfig mode, then MakeGood #/opt/MegaRAID/MegaCli/MegaCli64 -PDList -a0 |grep 'Firmware state' #/opt/MegaRAID/MegaCli/MegaCli64 -PDMakeGood -Physdrv [32:3] -force -a0 #/opt/MegaRAID/MegaCli/MegaCli64 AdpGetProp EnableJBOD -aALL #/opt/MegaRAID/MegaCli/MegaCli64 -AdpSetProp -EnableJBOD -1 -a0 #/opt/MegaRAID/MegaCli/MegaCli64 -PDMakeJBOD -PhysDrv[252:0] -a0 # create Raid0 #/opt/MegaRAID/MegaCli/MegaCli64 -CfgLdAdd -r0[252:3] -a0 # Get old Cache, ex: Virtual Drive(Target ID 02) #/opt/MegaRAID/MegaCli/MegaCli64 -GetPreservedCacheList -a0 # Clear cache, ex: Virtual Drive(Target ID 02) #/opt/MegaRAID/MegaCli/MegaCli64 -DiscardPreservedCache -L2 -a0 # create Raid1 #/opt/MegaRAID/MegaCli/MegaCli64 -CfgLdAdd -r1[252:4,252:5] -a0 # create Raid5 #/opt/MegaRAID/MegaCli/MegaCli64 -CfgLdAdd -r5[252:2,252:3,252:4,252:5] -a0 # create Raid10 #/opt/MegaRAID/MegaCli/MegaCli64 -CfgSpanAdd -r10 -Array0[32:4,32:5] -Array1[32:6,32:7] -a0 #delete raid #/opt/MegaRAID/MegaCli/MegaCli64 -CfgLdDel -L0 -a0 #Virtual Drive: 0 (Target Id: 0) # check Raid #/opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aALL #RAID 1 #RAID Level : Primary-1, Secondary-0, RAID Level Qualifier-0 #RAID 5 #RAID Level : Primary-5, Secondary-0, RAID Level Qualifier-3 # check Raid disks #/opt/MegaRAID/MegaCli/MegaCli64 -LdPdInfo -aAll | egrep "^Adapter|^Number of Virtual|^Virtual Drive:|^Name|^Enclosure Device ID:|^Slot Number:" # GPT part #parted -s /dev/sde mklabel gpt mkpart primary 0% 100% # check disk rebuild progress #/opt/MegaRAID/MegaCli/MegaCli64 -PDRbld -ShowProg -physdrv[32:1] -aALL #Rebuild Progress on Device at Enclosure 32, Slot 1 Completed 7% in 3 Minutes. # Force rebuild #/opt/MegaRAID/MegaCli/MegaCli64 -PDRbld -Start -physdrv[32:1] -a0 # OR #/opt/MegaRAID/MegaCli/MegaCli64 -pdlocate -start -physdrv[32:1] -a0 #Start rebuild, first clean the foreign configuration and then make the device hot spare (only if the above command failed) #/opt/MegaRAID/MegaCli/MegaCli64 -CfgForeign -Clear -aALL #set global hostspare #/opt/MegaRAID/MegaCli/MegaCli64 -PDHSP -Set -PhysDrv [32:1] -a0 #If you need to unset/remove a global hotspare: #/opt/MegaRAID/MegaCli/MegaCli64 -PDHSP -Rmv -PhysDrv [32:1] -aN #downgrade raid6 --> raid5 ,and more space now can be used # -L0 virtual disk 0 # /opt/MegaRAID/MegaCli/MegaCli64 -LDRecon -Start -r5 -L0 -a0 # echo 1 > /sys/block/sda/device/rescan #Add disk to a raid5 #/opt/MegaRAID/MegaCli/MegaCli64 -LDRecon -Start -r5 -Add -PhysDrv[32:3] -L0 -a #Configure WriteThrough or WriteBack #/opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -WT -Immediate -Lall -aAll #/opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -WB -Immediate -Lall -aAll #/opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL |grep "Inquiry Data:" #Inquiry Data: SEAGATE ST600MP0005 VS08S7M04B0C #Inquiry Data: SEAGATE ST600MP0005 VS08S7M04AFD #Inquiry Data: SEAGATE ST600MP0005 VS08S7M04JAY #Inquiry Data: SEAGATE ST600MP0005 VS08S7M04AAG #/opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL | grep "Drive Temperature" #Drive Temperature :36C (96.80 F) #Drive Temperature :35C (95.00 F)

2022年12月17日 · 2 分钟 · 384 字 · 八戒

Oracle使用rman定时清除7天前的日志

这一篇正规的DBA看到肯定会呲之以鼻,但对于没有用过oracle的系统运维来说,就是必须知道的事情了。 公司的 Oracle 实例有两台数据库,定时用rman备份,但是没有自动清理机制 ,过一阵子磁盘就会超过 80% 告警,需要手动清理,烦不胜烦。 凡是要手动做三次的事情必须自动化处理,启荣大师如是说,照办就是。 cat del_log.sh #!/bin/bash source /home/oracle/.bash_profile rman target / << EOF delete noprompt archivelog until time 'sysdate-7'; exit; EOF 很简单吧。上面引用的 .bash_profile 内容如下: # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH=$PATH:$HOME/.local/bin:$HOME/bin export PATH export ORACLE_BASE=/data/u01/app/oracle export ORACLE_HOME=/data/u01/app/oracle/product/18.3.0/dbhome_1 export ORACLE_SID=oradb export PATH=/usr/sbin:$PATH export PATH=$ORACLE_HOME/bin:$PATH:/usr/local/bin export LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib:$ORACLE_HOME/rdbms/lib export CLASSPATH=$ORACLE_HOME/JRE:$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib export NLS_DATE_FORMAT='yyyy/mm/dd hh24:mi:ss' export EDITOR=vi umask 022 这个 del_log.sh 的脚本放进 oracle 用户的 crontab 里执行就好 ...

2022年12月08日 · 1 分钟 · 90 字 · 八戒

CoreDNS替代dnsmasq

CoreDNS是k8s的御用DNS解析软件 支持很多插件,各种DoH、DoT、gRPC ,各种特性,安装配置也十分简单 替代dnsmasq后,基本dnsmasq的功能都支持,修改劫持域名后也不用重启,会自动刷文件更新 下面说一下装法: 安装: wget https://github.com/coredns/coredns/releases/download/v1.7.0/coredns_1.7.0_linux_amd64.tgz 解压后就一个文件,直接放到/usr/local/bin 做个systemd启动文件 cat /etc/systemd/system/coredns.service [Unit] Description=CoreDNS DNS server After=network.target [Service] PermissionsStartOnly=true LimitNOFILE=1048576 LimitNPROC=512 CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE NoNewPrivileges=true ExecStart=/usr/local/bin/coredns -conf=/usr/local/bin/coredns.conf ExecReload=/bin/kill -SIGUSR1 $MAINPID Restart=on-failure [Install] WantedBy=multi-user.target 东西全都在/usr/local/bin/coredns.conf配置文件里: .:5353 https://.:443 { any log debug bind 172.18.30.1 tls /usr/local/bin/full.pem /usr/local/bin/key.pem hosts /etc/hosts { ttl 60 reload 1m no_reverse fallthrough } cache 120 reload 6s loadbalance forward . 114.114.114.114:53 223.5.5.5:53 223.6.6.6:53 { policy sequential } prometheus 172.18.30.1:9253 } 解释一下: ...

2022年12月07日 · 1 分钟 · 156 字 · 八戒

sed中的正则表达式用法

之前说过sed的高阶用法,其实普通情况下,正则是最常用的,下面就来说一下,用不到的就暂时不说 ^ # 匹配行开始,如:/^sed/匹配所有以sed开头的行。 $ # 匹配行结束,如:/sed$/匹配所有以sed结尾的行。 . # 匹配一个非换行符的任意字符,如:/s.d/匹配s后接一个任意字符,最后是d。 * # 匹配0个或多次字符,如:/a*sed/匹配所有模板是0个或多个a后紧跟sed的行。后紧跟sed的行。 \? # 匹配0次或1次他前面的字符 \+ # 匹配1次或多次他前面的字符,如:空格\+ 或 “\+“匹配至少一个或多个空格 | #管道符号用来匹配两边任意一个子表达式,如:'/101\|102/p' 匹配包含101或者102的行打印 [] # 匹配一个指定范围内的字符,如/[sS]ed/匹配sed和Sed。 [^] # 匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行。 \(..\) # 匹配子串,保存匹配的字符,如s/\(love\)able/\1rs,loveable被替换成lovers。 & # 保存搜索字符用来替换其他字符,如s/love/ **&** /,love这成 **love** 。 \< # 匹配单词的开始,如:/\<love/匹配包含以love开头的单词的行。 \> # 匹配单词的结束,如/love\>/匹配包含以love结尾的单词的行。 \{m\} # 重复字符x,m次,如:/0\{5\}/匹配包含5个0的行。 \{m,\} # 重复字符x,至少m次,如:/0\{5,\}/匹配至少有5个0的行。 \{m,n\} # 重复字符x,至少m次,不多于n次,如:/0\{5,10\}/匹配5~10个0的行。 例子很多: 用户名,后面跟1个或者多个空格,再跟密码;更换成用户名 密码 MAC地址 sed -i "s/${username} \+${password}$/${username} ${password} ${IV_HWADDR}/" ${PASSFILE} 跟 shell 脚本的参数一起记比较好: $0 脚本本身的名字 $1 传递给该shell脚本的第1个参数 $2 传递给该shell脚本的第2个参数 $@ 传给脚本的所有参数的列表 $# 传给脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数,与位置变量不同,>参数可超过9个 $$ 脚本运行的当前进程ID号 $? 命令执行结果反馈,0表示执行成功,其余数字表示执行不成功。 还有一个重要的地方,sed 使用 -i 对文件进行修改时,执行者需要有对文件目录的写权限,因为sed实际是产生了一个临时文件,然后再挪回去得!!!! ...

2022年12月07日 · 1 分钟 · 89 字 · 八戒

Zabbix下发往钉钉告警

zabbix 是很熟悉的东西,但是实际上博主已经跳过了这个东西,直接蹦到 Prometheus 去了 但是,存在即合理,当下公司用的是这个,那么用就用吧,zabbix发到钉钉告警。 那么我们也研究一下如何发到钉钉告警,而且好看一些 原理: 原理就是用 post 向钉钉机器人的 webhook 地址提交 Markdown 的 json 信息 首先我们要建立个钉钉群,然后在群中添加一个群机器人,这里就会有两个选择,一个是这个机器人只接受特定的词语,二是向机器人发送消息的机器的ip是固定的。 阿里云建议的是关键词:云监控、云服务、监控、Monitor、ECS、报警 当然,这里更加建议IP,IP是死的,报警里带关键词意味着发送内容被部分固定了。 如上,我们会得到一个钉钉机器人的Webhook地址: https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxx 然后我们要准备一个发送的脚本,python很合适 #!/usr/bin/env python #coding:utf-8 #zabbix钉钉报警 import requests,json,sys,os,datetime #说明:这里改为自己创建的机器人的webhook webhook="https://oapi.dingtalk.com/robot/send?access_token=xxxxxx" def log(info): if os.path.isfile("/tmp/dingding.log") == False: f = open(log_file, 'a+') else: f = open(log_file,'w+') f.write(info) f.close() def msg(text,user): json_text= { "msgtype": "markdown", "markdown": { "title": "zabbix monitor", "text": text }, "at": { "atMobiles": [ user ], "isAtAll": False } } headers = {'Content-Type': 'application/json'} r=requests.post(url=webhook,data=json.dumps(json_text),headers=headers).json() code = r["errcode"] time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) if code == 0: log(time + ":消息发送成功 返回码:" + str(code) + "\n") else: log(time + ":消息发送失败 返回码:" + str(code) + "\n") exit(3) if __name__ == '__main__': user = sys.argv[1] #用户 subject = sys.argv[2] #标题 text = sys.argv[3] #消息 msg(text,user) 我们把上面的文件内容放到 /usr/lib/zabbix/alertscripts 目录下,名字就叫做 dingding.py ,只能是这个目录,因为这是 zabbix 缺省外挂脚本文件的目录。 ...

2022年11月30日 · 2 分钟 · 234 字 · 八戒

kubernetes使用nfs做持久化卷存储

用NFS来做kubernetes的持久化卷虽然不是最佳实践,但存在即合理。在现在的公司就是这么用的,记录一下,可用但不是最佳。 一、搭建NFS服务端和所有工作节点安装客户端 #NFS服务端 (centos7) yum install nfs-utils rpcbind -y mkdir -p /data/nfs/{download,bakup,www} echo "/data/nfs 10.10.0.0/16(rw,all_squash,insecure,sync)" >> /etc/exports exportfs -r systemctl enable rpcbind nfs-server systemctl restart rpcbind nfs-server showmount -e localhost #客户端根本不装也可以,客户端不需要启动任何nfs服务 #只是为了使用showmount工具来验证 #NFS客户端(CentOS,所有WorkNode节点都需要执行) yum install -y nfs-utils #NFS客户端(Ubuntu,所有WorkNode节点都需要执行) apt install -y nfs-common NFS 服务端的参数如下: rw 能读能写 insecure NFS通过1024以上的端口发送 root_squash root会被变成noboby,其他人不变 no_root_squash root身份保持不变,其他人不变。(不能用这个,容易被黑) all_squash 不论登入NFS的使用者身份为何,他的身份都会被压缩成为匿名使用者,通常也就是nobody sync 数据直接落盘,性能略损。 async 数据先落内存,然后落盘,性能略升。 综上,保险的参数就是 /data/nfs 10.10.0.0/16(rw,insecure,all_squash,sync) 注意:修改了/etc/exports后,并不需要重启nfs服务,只要用exportfs重新扫描一次/etc/exports,并且重新加载即可 exports -rv 二、POD内直接使用 由于nfs是内核自带的东西,所以最简单的使用方法就是直接在pod内使用 apiVersion: v1 kind: Pod metadata: name: test labels: app.kubernetes.io/name: alpine spec: containers: - name: alpine image: alpine:latest command: - touch - /data/test volumeMounts: - name: nfs-volume mountPath: /data volumes: - name: nfs-volume nfs: server: 10.10.247.38 path: /data readOnly: no 这个pod是一次性运行的,运行完后就可以看到nfs中多了个test文件 ...

2022年11月24日 · 1 分钟 · 209 字 · 八戒

钉钉告警发送

#!/usr/bin/env python #coding:utf-8 #zabbix钉钉报警 import requests import json import sys import os import datetime #这里是自己创建的机器人的webhook webhook="https://oapi.dingtalk.com/robot/send?access_token=xxxxxx" user=sys.argv[1] text=sys.argv[3] data={ "msgtype": "text", "text": { "content": text }, "at": { "atMobiles": [ user ], "isAtAll": False } } headers = {'Content-Type': 'application/json'} x=requests.post(url=webhook,data=json.dumps(data),headers=headers) 下面放shell脚本的 #!/bin/bash function ddmsg() { Token="xxxxxxxxxxxx" Weburl="https://oapi.dingtalk.com/robot/send?access_token=" curl -ks -m 2 "${Weburl}${Token}" \ -H 'Content-Type: application/json;charset=utf-8' \ -d "{'msgtype': 'text', 'text': { 'content': '$*'} }" &>/tmp/ddmsg.log if [ `grep "errmsg.*ok" /tmp/ddmsg.log |wc -l` -ne 1 ]; then echo 'send error !';cat /tmp/ddmsg.log;exit 1; fi } #测试内容 echo "@警报 主机:$(hostname) 信息:Node test 时间:$(date +"%F %T") ">.msg cat .msg ddmsg "`cat .msg`"

2022年11月23日 · 1 分钟 · 101 字 · 八戒

Freelancer之aws

客户发了一个需求如下: 用shell 或者python3 控制aws去获取新IP、绑定IP到实例 这个就非常简单了,因为本身aws就提供python的工具,一切都可以api化的 做法如下: #安装aws的工具 cd /tmp curl -kL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install aws --version #配置aws key aws configure #查看配置 aws configure list ---------------------------- #申请一个新IP并且打tag aws ec2 allocate-address --region $Region |tee /tmp/eip.log eip_id=$(jq -r ".AllocationId" /tmp/eip.log) aws ec2 create-tags --resources ${eip_id} --tags Key=Name,Value=eip-01 eip=$(jq -r ".PublicIp" /tmp/eip.log) #给ec2赋ip,前提是知道ec2_id aws ec2 associate-address --instance-id ${ec2_id} --allocation-id ${eip_id} ---------------------------- #启动新ec2的方法: #启动ec2 aws ec2 run-instances \ --region $Region --count 1 \ --instance-type t2.micro \ --subnet-id ${lan_a_public_id} \ --security-group-ids $gid \ --key-name MySshKey \ --image-id ami-04ff9e9b51c1f62ca \ |tee /tmp/ec2.log ec2_id=$(jq -r ".Instances[0].InstanceId" /tmp/ec2.log) 参考: https://www.cnblogs.com/elvi/p/16542406.html

2022年11月23日 · 1 分钟 · 95 字 · 八戒

Freelancer之QRCode

客户发了一个需求如下: TODO: 在sortFiles中,获取最新的域名,生成二维码,覆盖QRCodePath图片 TODO:将QRCodePath图片上传到阿里云0S根目录,重复则覆盖 又具体询问了一下细节,是一个 csharp 的程序 var sortFiles = Directory.GetFiles(zipPath, "*.zip").Select(fn => new FileInfo(fn)).OrderBy(f => f.LastAccessTime); // TODO: 在sortFiles中,获取最新的域名(文件名去掉.zip就是域名),结合logoUrl生成二维码,覆盖QRCodePath图片 // TODO: 将QRCodePath图片上传到阿里云OOS根目录,重复则覆盖, 看了一下,觉得思路如下,这是个管道流,无论徒手写代码生成QRCODE(还有个logo图需要放到二维码中央),还是集成OSS的SDK都麻烦 最快速方法是直接调用外部成程序做这两步,第一步用Go写个程序生成二维码,第二步用阿里的ossutil64工具上传 一、Go程序Gen出QRCode go 有很多库,我们直接拿来用好了,但是基本上都是老代码,所以需要下载 go1.13.15 来用 # 下载go 1.13.15,解压到E:\go,并建立一个E:\go\go-work目录,下载的模块会放到这里 https://dl.google.com/go/go1.13.15.windows-amd64.zip # 设置三个变量 GOROOT=E:\go GOPATH=E:\go\go-work PATH中增加好E:\go\bin的目录 # 设置一下代理,有时候模块拉不下来 go env -w GOPROXY=https://goproxy.cn,direct 然后就准备gen qrcode的程序,main.go package main import ( "flag" "image" "image/draw" "image/jpeg" _ "image/png" "os" "path/filepath" "github.com/LyricTian/logger" "github.com/nfnt/resize" "github.com/skip2/go-qrcode" ) var ( text string logo string percent int size int out string ) func init() { flag.StringVar(&text, "t", "", "二维码内容") flag.StringVar(&logo, "l", "", "二维码Logo(png)") flag.IntVar(&percent, "p", 15, "二维码Logo的显示比例(默认15%)") flag.IntVar(&size, "s", 256, "二维码的大小(默认256)") flag.StringVar(&out, "o", "", "输出文件") } func main() { flag.Parse() if text == "" { logger.Fatalf("请指定二维码的生成内容") } if out == "" { logger.Fatalf("请指定输出文件") } if exists, err := checkFile(out); err != nil { logger.Fatalf("检查输出文件发生错误:%s", err.Error()) } else if exists { logger.Fatalf("输出文件已经存在,请重新指定") } code, err := qrcode.New(text, qrcode.Highest) if err != nil { logger.Fatalf("创建二维码发生错误:%s", err.Error()) } srcImage := code.Image(size) if logo != "" { logoSize := float64(size) * float64(percent) / 100 srcImage, err = addLogo(srcImage, logo, int(logoSize)) if err != nil { logger.Fatalf("增加Logo发生错误:%s", err.Error()) } } outAbs, err := filepath.Abs(out) if err != nil { logger.Fatalf("获取输出文件绝对路径发生错误:%s", err.Error()) } os.MkdirAll(filepath.Dir(outAbs), 0777) outFile, err := os.Create(outAbs) if err != nil { logger.Fatalf("创建输出文件发生错误:%s", err.Error()) } defer outFile.Close() jpeg.Encode(outFile, srcImage, &jpeg.Options{Quality: 100}) logger.Infof("二维码生成成功,文件路径:%s", outAbs) } func checkFile(name string) (bool, error) { _, err := os.Stat(name) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } return true, nil } func resizeLogo(logo string, size uint) (image.Image, error) { file, err := os.Open(logo) if err != nil { return nil, err } defer file.Close() img, _, err := image.Decode(file) if err != nil { return nil, err } img = resize.Resize(size, size, img, resize.Lanczos3) return img, nil } func addLogo(srcImage image.Image, logo string, size int) (image.Image, error) { logoImage, err := resizeLogo(logo, uint(size)) if err != nil { return nil, err } offset := image.Pt((srcImage.Bounds().Dx()-logoImage.Bounds().Dx())/2, (srcImage.Bounds().Dy()-logoImage.Bounds().Dy())/2) b := srcImage.Bounds() m := image.NewNRGBA(b) draw.Draw(m, b, srcImage, image.ZP, draw.Src) draw.Draw(m, logoImage.Bounds().Add(offset), logoImage, image.ZP, draw.Over) return m, nil } 上面很简单,用到了别人的三个模块,需要安装一下 ...

2022年11月23日 · 3 分钟 · 567 字 · 八戒