INNET CLOUD
type: channel
link: https://innet.cloud/
description: 静待时光清浅，岁月流转，把酒言欢，梦里，青春与你我不似初见。
items: 微信实现免扫码登录
items: DBeaver 23.2.0 分析说明
items: 使用 Docker Api 进行在线操作
items: [javascript] 数字转大写中文
items: [JavaScript]获取汉字首字母
items: [JavaScript]数字1、2、3...转为一、二、三...
items: EduSoho开发指南 - 数据库
items: 给图片添加水印
items: 基于FTP的自动更新程序
items: 自动根据当前Chrom版本更新Chromedriver到最新版本

微信实现免扫码登录
type: item
date: 2025-08-11T09:50:00+08:00
description: #!/usr/bin/env python3 # wechat_multi_gui.py """ 微信多账号免密启动 - Tkinter GUI 版本 支持： - 保存当前账号（同名则覆盖） - 删除账号 """ import os import json import shutil import subprocess from pathlib import Path import tkinter as tk from tkinter import filedialog, messagebox, simpledialog CONFIG_FILE = Path(__file__).with_name("wechat_cli_config.json") def load_config(): if CONFIG_FILE.exists(): try: return json.loads(CONFIG_FILE.read_text(encoding="utf-8")) except Exception: return {} return {} def save_config(cfg: dict): try: CONFIG_FILE.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8") except Exception as e: messagebox.showerror("错误", f"保存配置文件失败: {e}") def ensure_backup_dir(config_path): cfg_parent = Path(config_path).parent backup_dir = cfg_parent / "config_backup" backup_dir.mkdir(parents=True, exist_ok=True) return backup_dir def list_saved_accounts(backup_dir): accounts = [] for p in backup_dir.iterdir(): if p.is_dir() and p.name.isdigit(): display = f"账号 {p.name}" meta = p / "account.meta" if meta.exists(): try: j = json.loads(meta.read_text(encoding="utf-8")) display = j.get("displayName", display) except Exception: pass accounts.append({"id": int(p.name), "folder": p, "displayName": display}) accounts.sort(key=lambda x: x["id"]) return accounts def save_current_account(config_path, backup_dir, display_name=None): # 如果同名则覆盖更新 if display_name: for acc in list_saved_accounts(backup_dir): if acc["displayName"] == display_name: shutil.copy2(config_path, acc["folder"] / "config.data") meta = {"displayName": display_name} (acc["folder"] / "account.meta").write_text(json.dumps(meta, ensure_ascii=False), encoding="utf-8") return acc["id"] # 否则新建 ids = [int(p.name) for p in backup_dir.iterdir() if p.is_dir() and p.name.isdigit()] new_id = max(ids) + 1 if ids else 1 new_dir = backup_dir / str(new_id) new_dir.mkdir(parents=True, exist_ok=True) shutil.copy2(config_path, new_dir / "config.data") meta = {"displayName": display_name or f"账号 {new_id}"} (new_dir / "account.…

DBeaver 23.2.0 分析说明
type: item
date: 2023-12-05T12:33:00+08:00
description: DBeaverUE 23.2.0 分析说明通过学习DBeaver Ultimate Edition License验证分析得到此文档，仅供研究学习使用，请勿用于非法用途，请支持正版！一、直接使用分析过的密钥确保是修改过的DBeaverUE压缩包打开后输入如下授权码：[binary] kr49KOrCKzWMjpmA4WWbhiSOcykC4kRXT21w0Q==二、分析过程下载官方DBeaverUE压缩包压缩包：https://download.dbeaver.com/ultimate/23.2.0/dbeaver-ue-23.2.0-win32.win32.x86_64.zip解压到软件需要安装的位置，打开目录：plugins找到这三个文件，后面的数字版本号可能不同：org.jkiss.utils_2.1.196.202309120446.jar org.jkiss.lm_1.0.166.202309120446.jar org.jkiss.dbeaver.core_23.2.0.202309120446.jar同时需要知道com.dbeaver.app.ultimate这个jar包在哪，后续需要修改里面的公钥文件把DBeaverLicenseActiveMain.java文件和上面三个jar文件放进去打开vscode，安装插件”Extension Pack for Java”后左下角有Java项目栏,在”Referenced Libraries”添加jar，把上面的三个jar添加进去打开DBeaverLicenseActiveMain.java文件，点击右上角的运行，终端中会输出无报错的信息，即为环境正常；复制终端的运行命令，在命令后面追加参数 gen-keys，会生成公钥和私钥；创建文件public-key.txt，private-key.txt，dbeaver-ue-public.key，将私钥复制到private-key.txt中，将公钥复制到public-key.txt和dbeaver-ue-public.key中；将public-key.txt和private-key.txt文件放到用户目录下的.jkiss-lm文件夹中，用dbeaver-ue-public.key文件替换com.dbeaver.app.ultimate_22.1.0.202206121739.jar内的keysdbeaver-ue-public.key文件；复制终端的运行命令，在命令后面追加参数 encrypt-license，生成License字符串，此时License为授权码，已经可用；修改配置文件dbeaver.ini，增加lm.debug.mode=true配置，在最后一行添加：-Dlm.debug.mode=true 打开DBeaver，导入License，即可完成分析；三、DBeaverLicenseActiveMain.java 代码import org.jkiss.lm.*; import org.jkiss.utils.Base64; import java.io.*; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Date; public class DBeaverLicenseActiveMain { private static final LMProduct TEST_PRODUCT; static { TEST_PRODUCT = new LMProduct( "dbeaver-ue", "DB", "DBeaver Ultimate", "DBeaver Ultimate Edition", "23.2", LMProductType.DESKTOP, new Date(), new String[0]); } public static void main(String[] args) throws Exception { System.out.println("LM 2.0"); // 生成RAS密钥对 if (args.length > 0 && args[0].equals("gen-keys")) { System.out.println("Test key generation"); generateKeyPair(); // 生成加密License } else if (args.length > 0 && args[0].equals("encrypt-license"))…

使用 Docker Api 进行在线操作
type: item
date: 2022-01-06T09:43:00+08:00
description: 业务场景：后期会做一个类似“实验楼”的在线编程站点，需要用到在线使用Api创建指定镜像的虚拟机。这里先记录一下如何使用Ubuntu 21来进行Docker的安装和api的基础使用。一、Docker网上有很多相关教程文档，这里列举几个常用的1.安装$ sudo apt-get install -y docker.io2.启动$ systemctl start docker3.停止$ systemctl stop docker4.开机自启$ systemctl enable docker5.查看状态$ systemctl status docker6.查看版本$ docker version接下来验证Docker1.输入以下命令，此时不用在乎命令具体含义，后面会解释分析。$ sudo docker run hello-world2.如出现类似信息，表示Docker可正常使用了Hello from Docker!二、Docker操作镜像说明:这里会讲解Docker对镜像的一些简单操作。所谓镜像，可以简单理解为一个软件，在Docker中安装一个软件，Docker即为容器。任何软件都可以进行封装成为Docker中的软件，包括操作系统。1.拉取新的镜像到Docker$ sudo docker pull docker.io/portainer/portainer拉取镜像可以理解为下载软件的行为，将别人封装好的软件安装进自己的容器中，使用如下命令。这里以安装一个docker可视化web面板为例。其中使用pull表示拉取，后面的docker.io/portainer/portainer为镜像名称2.查看Docker中都有哪些镜像$ sudo docker images在未安装镜像之前，使用此命令仅能看到一个名为hello-world的镜像。在上一步使用命令安装了docker.io/portainer/portainer，所以本次的执行结果中会出现名为portainer/portainer的镜像。3.创建Docker中的镜像容器$ sudo docker run -d --name portainerUI -p 192.168.0.182:9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer这里参数可参考菜鸟教程中的说明 Docker run命令 ，意思为：以后台运行的方式创建启动并返回一个唯一ID,这个容器的名称被指定为portainerUI，同时进行端口映射，将宿主机的192.168.0.182:9000端口映射到容器中9000端口中。这里容器端口为固定的，宿主机IP和端口可自行调整。后面-v 进行绑定卷,大概是管理的意思，最后的portainer/portainer表示为启动的容器名称。4.验证启动的容器$ sudo docker ps使用ps命令可查看正在运行的容器仅针对上面的容器，在浏览器中打开地址 http://192.168.0.182:9000 能显示页面，即为成功运行这里提到的是使用命令的方式创建和启动容器，我们后面使用HTTP API的方式进行容器的创建启动5.终止正在运行的容器$ sudo docker stop 68692b82a394其中68692b82a394为使用ps查询到容器对应的id6.启动已经创建的容器$ sudo docker start 68692b82a394这里是启动，使用第3步的方式是创建，这里是启动已经创建的容器。三、Docker开放APIDocker的API需要用到2375端口，当然，这个端口你也可以自定义，此端口默认不开放，需要进行受到开放1.开启API功能，用vim或者vi编辑sudo vim /usr/lib/systemd/system/docker.service2.找到[Service]，在里面添加如下内容ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock3.重启dockersystemctl daemon-reload systemctl restart docker四、使用Docker Api进行容器创建这里开始讲解如何使用Docker的api进行容器的操作，不再使用终端。使用postman或者其他工具,POST方式,发送的数据为JSON格式。其中name=xxxx中的xxx为创建容器的名称，类似于上面的-name参数1.创建容器POST请求地址：http://192.168.0.182:2375/containers/create?name=xxxx请求参数：{ "Hostname…

[javascript] 数字转大写中文
type: item
date: 2021-05-21T16:37:00+08:00
description: function convertCurrency(currencyDigits) { // Constants: var MAXIMUM_NUMBER = 99999999999.99; // Predefine the radix characters and currency symbols for output: var CN_ZERO = "零"; var CN_ONE = "壹"; var CN_TWO = "贰"; var CN_THREE = "叁"; var CN_FOUR = "肆"; var CN_FIVE = "伍"; var CN_SIX = "陆"; var CN_SEVEN = "柒"; var CN_EIGHT = "捌"; var CN_NINE = "玖"; var CN_TEN = "拾"; var CN_HUNDRED = "佰"; var CN_THOUSAND = "仟"; var CN_TEN_THOUSAND = "万"; var CN_HUNDRED_MILLION = "亿"; var CN_SYMBOL = ""; var CN_DOLLAR = "元"; var CN_TEN_CENT = "角"; var CN_CENT = "分"; var CN_INTEGER = "整"; // Variables: var integral; // Represent integral part of digit number. var decimal; // Represent decimal part of digit number. var outputCharacters; // The output result. var parts; var digits, radices, bigRadices, decimals; var zeroCount; var i, p, d; var quotient, modulus; // Validate input string: currencyDigits = currencyDigits.toString(); if (currencyDigits == "") { alert("您的输入为空！"); return ""; } if (currencyDigits.match(/[^,.\d]/) != null) { alert("输入包含非法字符!"); return ""; } if ((currencyDigits).match(/^((\d{1,3}(,\d{3})*(.((\d{3},)*\d{1,3}))?)|(\d+(.\d+)?))$/) == null) { alert("输入有误!"); return ""; } // Normalize the format of input digits: currencyDigits = currencyDigits.replace(/,/g, ""); // Remove comma delimiters. currencyDigits = currencyDigits.replace(/^0+/, ""); // Trim zeros at the beginning. // Assert the number is not greater than the maximum number. if (Number(currencyDigits) > MAXIMUM_NUMBER) { alert("数额太大，转换金额上限小于1千亿!"); return ""; } // Process the coversion from currency digits to characters: // Separate integral and decimal parts before processing coversion: parts = currencyDigits.split("."); if (parts.length > 1) { integral = parts[0]; decimal = parts[1]; // Cut down redundant decimal digits that are after the second. decimal = decimal.substr(0, 2); } else { integral = parts[0]; decimal = ""; } // Prepare the characters corresponding to the digits: digits = new Array(CN_ZERO, CN_…

[JavaScript]获取汉字首字母
type: item
date: 2021-04-29T14:44:00+08:00
description: 代码调用: makePy('张三');

[JavaScript]数字1、2、3...转为一、二、三...
type: item
date: 2021-04-23T16:50:00+08:00
description: 函数// 将1、2、3、4、5、6... 转为 '一','二','三','四','五','六' 等汉字 function numberToChinese(num) { var bit = Array("零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"); //各位数 if (num <= 10) { return bit[num]; } //十位数 if(num < 100){ var array = num.toString().split(""); if(array[0] < 2){ return bit[10]+''+bit[array[1]]; }else{ if(array[1] == 0){ return bit[array[0]]+''+bit[10]; }else{ return bit[array[0]]+''+bit[10]+''+bit[array[1]]; } } } //百位数 if(num < 1000){ var array = num.toString().split(""); if(array[1] == 0 && array[2] == 0){ return bit[array[0]]+"百"; }else if(array[1] == 0 && array[2] != 0){ return bit[array[0]]+"百"+bit[array[1]]+''+bit[array[2]]; }else if(array[1] != 0 && array[2] == 0 ){ return bit[array[0]]+"百"+bit[array[1]]+''+bit[10]; }else{ return bit[array[0]]+"百"+bit[array[1]]+''+bit[10]+''+bit[array[2]]; } } //千位数 if(num < 10000){ var array = num.toString().split(""); if(array[1] == 0 && array[2] == 0 && array[3] == 0){ return bit[array[0]]+"千"; }else if(array[1] == 0 && array[2] != 0 && array[3] != 0){ return bit[array[0]]+"千"+bit[array[1]]+''+bit[array[2]]+''+bit[10]+''+bit[array[3]]; }else if(array[1] == 0 && array[2] == 0 && array[3] != 0){ return bit[array[0]]+"千"+bit[array[1]]+''+bit[array[3]]; }else if(array[1] == 0 && array[2] != 0 && array[3] == 0){ return bit[array[0]]+"千"+bit[array[1]]+''+bit[array[2]]+''+bit[10]; }else if(array[1] != 0 && array[2] == 0 && array[3] == 0){ return bit[array[0]]+"千"+bit[array[1]]+"百"; }else if(array[1] != 0 && array[2] != 0 && array[3] == 0){ return bit[array[0]]+"千"+bit[array[1]]+"百"+bit[array[2]]+''+bit[10]; }else if(array[1] != 0 && array[2] == 0 && array[3] != 0){ return bit[array[0]]+"千"+bit[array[1]]+"百"+bit[array[2]]+''+bit[array[3]]; }else{ return bit[array[0]]+"千"+bit[array[1]]+"百"+bit[array[2]]+''+bit[10]+''+bit[array[3]]; } } return num; } 调用console.log(numberToChinese(3943)) //输出:三千九百四十三

EduSoho开发指南 - 数据库
type: item
date: 2021-04-06T04:31:00+08:00
description: EduSoho开发指南研究背景：该Edusoho基于symfony2.8开发，但根据symfony2.8的文档描述和实际Edusoho项目结构对比，二者有较大的差别，同时Edusoho的开发文档并不全面，有许多坑。通过Symfony提供的文档，无法在Edusoho中通过命令行创建数据表和数据表对应的Dao和Service。我想应该是Edusoho团队已经将其进行了大量的修改，导致原本的Symfony文档无法为Edusoho服务。所以我这里开始对该项目进行数据库新建表实行梳理。在srcBiz目录下，随便找一个文件夹，这里以Activity文件夹为例。在ActivityBizDao下创建XXXActivityDao.php接口文件，这是接口文件，继承基类GeneralDaoInterface；例如TestActivityDao.php<?php # 接口：一种成员属性全部为抽象或常量的特殊抽象类。 namespace Biz\Activity\Dao; #命名空间 use Codeages\Biz\Framework\Dao\GeneralDaoInterface; #基类 interface TestActivityDao extends GeneralDaoInterface { public function getById($id); #自定义一个函数 }在ActivityBizDaoImpl目录下创建XXXActivityDaoImpl.php文件，继承GereralDaoImpl基类，调用上面创建的接口TestActivityDao.php;<?php # 这里的内容参考：https://blog.csdn.net/jyl_sh/article/details/106616322 namespace Biz\Activity\Dao\Impl; # 命名空间 use Biz\Activity\Dao\TestActivityDao; # 刚刚创建的接口文件 use Codeages\Biz\Framework\Dao\GeneralDaoImpl; # 基类 class TestActivityDaoImpl extends GeneralDaoImpl implements TestActivityDao { protected $table = 'test'; # 要操作的表名 public function declares() # 这是必须的函数 { // TODO: Implement declares() method. return array( 'timestamps' => array('created_time', 'update_time'), 'serializes' => array(), 'orderbys' => array(), 'conditions' => array( 'type = :type', ), ); } public function getById($id) # 这是接口中有的函数，必须在这里也调用，并实现增删改查的逻辑 { return $this->get($id); } }完成上面的Dao创建，下面需要创建Service。找到ActivityBizService,创建XXXXActivityService.php接口文件，不用继承任何基类；例如：TestActivityService.php<?php namespace Biz\Activity\Service; interface TestActivityService { public function getActivity($id); public function findActivitiesByIds($ids); public function findActivitiesByMediaIds($mediaIds); public function createActivity($fields); public function updateActivity($id, $fields); public function deleteActivity($id); }找到ActivityBizServiceImpl目录，创建XXXXActivityServiceImpl.php文件，继承基类BaseService，调用刚刚创建的接口文件TestActivityService。其中接口文件中有的函数必须在此文件中创建相同的函数<?php namespace Biz\Activity\Service\Impl; # 命名空间 use Biz\BaseService; # 服务基类 use Biz\Acti…

给图片添加水印
type: item
date: 2020-12-29T10:32:00+08:00
description: 说明:给指定图片添加指定文本的水印from PIL import Image,ImageDraw,ImageFont import base64,time def imgToBase64(image_path,string="发生异常-11"): print("图片地址: "+image_path+",正在添加水印---") try: oriImg = Image.open(image_path) addText(oriImg,image_path,string) except: print(image_path+"图片添加水印失败---") # time.sleep(1) with open(image_path, 'rb') as f: image = f.read() image_base64 = "data:image/png;base64,"+str(base64.b64encode(image), encoding='utf-8') print(image_base64) #图片加水印显示错误信息 def addText(oriImg,path,string): fontpath = "font/kt.ttf" # 64为字体大小 font = ImageFont.truetype(fontpath, 64) draw = ImageDraw.Draw(oriImg) draw.text(xy=(50,50),text=string,fill='red',font=font) # oriImg.show() oriImg.save(path) path = "J:\\Users\\username\\Pictures\\壁纸\\test.png" imgToBase64(path,"HelloWorld")

基于FTP的自动更新程序
type: item
date: 2020-12-29T10:28:00+08:00
description: 说明:基于FTP的程序自动更新，配合bat脚本执行import os,os.path,time,zipfile,socket from ftplib import FTP # 引入ftp模块 #FTP操作 class MyFtp: ftp = FTP() def __init__(self,host,port=21): try: self.ftp.connect(host,port) except: print("FTP服务器连接失败！") def login(self,username,pwd): self.ftp.set_debuglevel(0) # 打开调试级别2，显示详细信息 try: self.ftp.login(username,pwd) except: print("FTP服务器登录失败！") return False print(self.ftp.welcome) data = [] self.ftp.dir(data.append) for line in data: print("-", line) def downloadFile(self,localpath,remotepath,filename): os.chdir(localpath) # 切换工作路径到下载目录 try: self.ftp.cwd(remotepath) # 要登录的ftp目录 except: print("无法打开FTP目录！") return False self.ftp.nlst() # 获取目录下的文件 file_handle = open(filename,"wb").write # 以写模式在本地打开文件 try: self.ftp.retrbinary('RETR %s' % os.path.basename(filename),file_handle,blocksize=1024) # 下载ftp文件 except: print("从FTP下载【"+str(filename)+"】文件失败！") return False # ftp.delete（filename） # 删除ftp服务器上的文件 def close(self): self.ftp.set_debuglevel(0) # 关闭调试 self.ftp.quit() #解压缩操作 class MyZip: def __init__(self): pass # 压缩 def zip_dir(self,dirname,zipfilename): filelist = [] if os.path.isfile(dirname): filelist.append(dirname) else : for root, dirs, files in os.walk(dirname): for name in files: filelist.append(os.path.join(root, name)) zf = zipfile.ZipFile(zipfilename, "w", zipfile.zlib.DEFLATED) for tar in filelist: arcname = tar[len(dirname):] #print arcname zf.write(tar,arcname) zf.close() # 解压 def unzip_file(self,zipfilename, unziptodir): if not os.path.exists(unziptodir): os.mkdir(unziptodir, '0777') zfobj = zipfile.ZipFile(zipfilename) for name in zfobj.namelist(): name = name.replace('\\','/') if name.endswith('/'): try: os.mkdir(os.path.join(unziptodir, name)) except: print("【"+name+"】文件夹已存在，不再创建~") continue # if 'Public.py' in name: # print("【"+name+"】文件夹不允许被替换，跳过~") # continue ext_filename = os.path.join(unziptodir, name) ext_dir= os.path.dirname(ext_filename) if not os.path.exists(ext_dir) : os.mkdir(ext_dir,'0777') print("ext_filename:"+ext_filename) try:…

自动根据当前Chrom版本更新Chromedriver到最新版本
type: item
date: 2020-12-29T10:21:00+08:00
description: 说明:Chrom自动更新后，对应的Chromedriver驱动也要手动下载更新，比较麻烦。这里的程序将根据单曲Chrome的版本将最新的驱动下载到本地。import requests import re import os import zipfile import time dPath = os.getcwd() # 获取chrome版本 def get_chrome_version(): cmd = 'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version' cmdRes = os.popen(cmd).read() #执行命令拿到版本信息 cmdList = cmdRes.split(' ') #根据空格把拿到的信息转为列表 version = cmdList[len(cmdList)-1].replace('\n', '') #只要最后一个数据就是版本号，不要其他的信息 varsionList = version.split('.') #版本号更具.转为列表 varsionList.pop() #删除列表最后一个小版本号 varsionStr = ".".join(varsionList) #组装大版本号 return varsionStr # 已有Chromedriver的版本 def get_fz_chromedriver_version(): cmd = dPath+'\chromedriver\chromedriver.exe -version' cmdRes = os.popen(cmd).read() #执行命令拿到版本信息 cmdList = cmdRes.split(' ') #根据空格把拿到的信息转为列表 return cmdList[1] def get_latest_version(url): '''查询最新的Chromedriver版本''' rep = requests.get(url).text time_list = [] # 用来存放版本时间 time_version_dict = {} # 用来存放版本与时间对应关系 result = re.compile(r'\d.*?/.*?Z').findall(rep) # 匹配文件夹（版本号）和时间 for i in result: time = i[-24:-1] # 提取时间 version = re.compile(r'.*?/').findall(i)[0] # 提取版本号 time_version_dict[time] = version # 构建时间和版本号的对应关系，形成字典 time_list.append(time) # 形成时间列表 latest_version = time_version_dict[max(time_list)][:-1] # 用最大（新）时间去字典中获取最新的版本号 return latest_version def download_driver(download_url): '''下载文件''' file = requests.get(download_url) with open("chromedriver.zip", 'wb') as zip_file: # 保存文件到脚本所在目录 zip_file.write(file.content) print('下载成功') def get_version(): '''查询系统内的Chromedriver版本''' outstd2 = os.popen('chromedriver --version').read() return outstd2.split(' ')[1] def get_path(): '''查询系统内Chromedriver的存放路径''' return dPath+'\chromedriver\\' # outstd1 = os.popen('where chromedriver').read() # return outstd1.strip('chromedriver.exe\n') def unzip_driver(path): '''解压Chromedriver压缩包到指定目录''' f = zipfile.ZipFile("chromedriver.zip",'r') for file in f.namelist(): f.extract(file, path) if __name__ == "__main__": version = get_chrome_version() # 原来是get_version() print('当前…