Tag

DEV

同事误删mysql库补救办法

发现问题

今天下午同事突然跑来找我说不小心把库删了,问我会不会恢复,我从来没有这种删库恢复的经验,自然是要学习一下的,直接开始帮他恢复

查找解决办法

被删库的机器,这儿就叫他243,243的应用和数据库在同一个服务器上

  1. 首先先停止了应用防止继续写入或者丢失服务数据
  2. 检查mysql的binlog是否开启 SHOW VARIABLES LIKE 'log_bin%';,发现是开着的
  3. 检查binlog是否完整 ls /opt/mysql/data/ 发现缺少了几乎一半的binlog,推测是开启了过期清理
  4. 这个时候了解服务器运维的同事提出可以问机房的人恢复硬盘备份,并联系了相关同事
  5. 等待了一段时间,运维同事恢复了前一天的一个备份到一台新机器上,这儿叫他229

解决过程

在等待的时间里,确定了解决方案

  1. 检查两个服务器的binlog id差异
  2. 在243上找到最新的删库的binlog id
  3. 在229上找到最新的binlog id
  4. 在243生成两个binlog id中间差异的sql文件
  5. 将sql文件scp到229服务器上
  6. 229服务器的mysql执行sql补全缺失的数据

其中第二步可以在等待的时候完成

检查binlog的脚本 - 243

mysqlbinlog --no-defaults -v --base64-output=DECODE-ROWS  --stop-position=287225397  binlog.000075 | tail -n 500 > ~/tail.log
# at 287225397
#260227 15:46:29 server id 1  end_log_pos 287225556 CRC32 0x97e6fb47 	Query	thread_id=663510	exec_time=1	error_code=0	Xid = 583720608
use `laboratory`/*!*/;
SET TIMESTAMP=1772178389/*!*/;
SET @@session.pseudo_thread_id=663510/*!*/;
SET @@session.foreign_key_checks=0/*!*/;
DROP TABLE IF EXISTS `assay_report` /* generated by server */

检查binlog的脚本 - 229

mysqlbinlog --no-defaults --database=laboratory ./binlog.000074 > ~/74.sql
# at 1074009846
#260227 10:39:55 server id 1  end_log_pos 1074009890 CRC32 0x5b7eb4ae   Rotate to binlog.000075  pos: 4
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

在243生成差异sql

mysqlbinlog --no-defaults --database=laboratory  --skip-gtids  --start-position=256983036 binlog.000074 --stop-position=287225397 binlog.000075 > all.sql

恢复sql

# 登录sql
mysql -u root -p
# 执行文件 (1.5G的all.sql最终执行了十几分钟,服务器是固态硬盘)
source /home/all.sql

最终效果

在下班前恢复完成数据库并准时下班🎉🎉🎉

ps:本次行动由gemini提供技术支持😂

优化超大单体Spring Boot项目开发环境启动速度

之前维护的一个项目,项目启动需要12分钟,开发环境需要频繁启停,极大影响开发效率

分析

使用IDEA profiler收集启动阶段的数据,并用idea打开火焰图分析

发现启动时间大部分都消耗在注入Autowired资源以及创建Aop切面

同时整理了项目的模块发现开发环境下有一部分是不常用的

优化方案

首先是修改pom使开发环境不加载不常用的模块,启动速度快了60秒左右

然后编写了脚本静态分析源码,收集了所有没有使用的@Autowired和@Resource,并手动处理(项目代码规范不是很好, 不确定是否有特殊的引用, 虽然最后没发现这样的引用),启动速度快了30秒左右

import java.io.File

val dir = File("/path/to/a/project")
val unused = mutableListOf<Pair<String, String>>()
val autowiredPattern = Regex("(private|public|protected)\\s+\\w+\\s+(?<varName>\\w+)\\s*;")

fun collect(file: File) {
    if (file.isDirectory) {
        // 忽略编译产物
        file.name == "target" && file.parentFile.resolve("pom.xml").exists() && return
        file.listFiles()?.forEach { collect(it) }
        return
    }
    if (!file.name.endsWith(".java")) {
        return
    }
    // println("scan ${file.name}")
    // 找到所有autowired
    val autowiredList = mutableListOf<String>()
    var nextIsAutowired = false
    val lines = file.readLines().filter { it.startsWith("//") }
    for (line in lines) {
        if (line.trim().run{ startsWith("@Autowired") || startsWith("@Resource") }) {
            nextIsAutowired = true
        }
        if (nextIsAutowired) {
            val result = autowiredPattern.find(line)
            if (result != null) autowiredList.add(result.groups["varName"]!!.value)
            nextIsAutowired = false
            continue
        }
    }
    if (autowiredList.isEmpty()) return
    // 查询没有使用的autowired
    val filePath = file.absolutePath
        .replace("\\", "/")
        .substringAfter("src/main/java/")
        .replace("/", ".")
        .removeSuffix(".java")
    autowiredList.filter { varName ->
        lines.all { line -> varName !in line || line.trim().matches(autowiredPattern) }
    }.let {
        unused += it.map { v -> filePath to v }
    }
}

collect(dir)
File("unused-autowired-${dir.name}.txt").writeText(unused.joinToString("\n") { (k, v) -> "$k: $v" })

此时启动速度依然很慢,于是使用 lazy-initialization 的方案,环境变量中添加 spring.main.lazy-initialization=true

但是所有bean都懒加载会导致一部分模块出错最终导致启动失败,于是添加filter

@Bean
public LazyInitializationExcludeFilter filter() {
    return (beanName, beanDefinition, beanType) -> {
        String className = beanType.getName();
        return className.startsWith("com.example.module.plugin.important")
                || className.startsWith("com.example.module.common")
                ;
    };
}

最终启动速度从12分钟优化到最快4分钟(不启用可选的模块)

总结

  • 模块拆分解耦是好文明,在这种场景下直接禁用不用的模块也不影响项目启动
  • 在bean初始化里写逻辑是坏文明,遇到一个上游依赖的组件,自己封装了一层XxlJobSpringExecutor,还把class设置成package-private,导致不加载该模块就无法正常初始化xxljob

Arthas

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。
开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了。
如果您正在考虑在代码中添加一些日志以帮助解决问题,您将必须经历以下阶段:测试、预发,然后生产。这种方法效率低下,更糟糕的是,该问题可能无法解决,因为一旦 JVM 重新启动,它可能无法复现,如上文所述。
Arthas 旨在解决这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。

Arthas官方文档

查cpu高占用

  • dashboard 查看占用最高的线程
  • thread -n 3 查看占用高的线程及其堆栈

查内存高占用

  • oom崩溃的情况无法处理,需要-XX:+HeapDumpOnOutOfMemoryError
    • -XX:HeapDumpPath=/tmp/heapdump.hprof 指定dump文件位置
    • 进程还活着才可以分析
  • vmtool --action forceGc 先fullGc一次
  • dashboard 查看Memory
  • jmap -histo:live <pid> | head -n 20 查看是否有大量自定义对象 一般排名靠前的都是java的基本类型
  • heapdump --live /tmp/dump.hprof 导出文件后拖到idea或者mat软件分析
    • 在合并的路径tab中可以按照实例类型和引用关系找到引用最多对象的实例
    • 在最大对象tab中可以看到对象及其引用的对象的占用并层层展开查看引用关系
    • 保留(Retained)是这个对象及其引用的对象层层递归加起来的总内存占用,主要看这个
      • 计算的是对象的独占对象的内存占用,如果一个object还被其他object引用,则不纳入计算
      • 找到保留大小最大的几个对象,通过引用树找到持有这些对象的实例
    • 浅层(Shallow)是这个对象本身占用的内存,一般只有数组的浅层大小会很大

修改方法逻辑

  • jad --source-only com.example.demo.UserController > /tmp/UserController.java 添加 --lineNumber=false 不显示行号 -d dump文件到目录
  • 编辑补全逻辑
  • mc /tmp/UserController.java -d /tmp 编译成class
  • retransform /tmp/com/example/demo/UserController.class 热重载

查慢方法调用

  • trace com.example.demo.OrderController createOrder -n 1 添加监听
    • -n 1 指定触发次数,方法触发指定次数后自动退出,不添加则会连续监听直到ctrl c
    • 若入参或返回值过长可以输出到文件 文件路径不能使用~ 否则会创建名为~的文件
  • 输出包含方法调用栈以及行号的执行耗时
    • 输出的列表和Map不会展开内容,只会显示size

查方法入参返回值

  • watch com.example.demo.UserService login "{params, returnObj}" -n 1 添加监听
    • -n 1 指定触发次数,方法触发指定次数后自动退出,不添加则会连续监听直到ctrl c
    • 添加 -x 2 展开对象, 展开2刚好够展开Map和List
    • watch com.example.demo.UserService login "{ @com.alibaba.fastjson.JSON@toJSONString(params), @com.alibaba.fastjson.JSON@toJSONString(returnObj) }" -n 1 使用OGNL编写表达式可以输出json格式, 此时不需要-x展开
    • watch com.example.demo.UserService login "{ params, returnObj }" "params[0].name == 'test'" -n 1 使用OGNL编写条件表达式,可以在递归调用等地方过滤不想要收集的方法调用 如果在win等无法输入中文的情况下可以用unicode编码 "'\u6d4b\u8bd5\u540d\u5b57' == params[0]"

回退vscode版本 & 多版本共存

最近更新了vscode,有一天需要连接远程服务器的时候发现报错说服务器glibc什么的版本过旧不支持了,但是服务器又不是我的,没法更新系统,所以需要回退旧版本。同时我有一个新版本的vscode,因为claude code插件不支持旧版本vscode,所以需要两个vscode互相隔离,于是就有了这个博客。

下载旧版本vscode

因为我需要多版本共存,所以需要一个免安装的版本,避免他和现有版本产生冲突

首先在faq中找到下载地址的格式,然后填入对应的版本

https://update.code.visualstudio.com/{version}/win32-x64-archive/stable

运行

如果下载之后直接运行,会直接使用当前用户目录下的缓存,比如插件啥的,可能会产生冲突,所以需要指定各种目录,我直接给出启动脚本

@echo off

set VSCODE_DIR=C:\Users\Administrator\Downloads\VSCode-win32-x64-1.96.4
set "VSCODE_EXECUTABLE=%VSCODE_DIR%\Code.exe"
set "USER_DATA_DIRECTORY=%VSCODE_DIR%\user-profile"
set "EXTENSIONS_DIR=%VSCODE_DIR%\user-extensions"

start "VSCode Portable" "%VSCODE_EXECUTABLE%" --user-data-dir "%USER_DATA_DIRECTORY%" --extensions-dir "%EXTENSIONS_DIR%"

用这个脚本启动则vscode会有独立的用户数据目录和插件目录,避免和安装版本的产生冲突

wordpress 非标准端口 https

今天折腾了一天的wordpress,想从http切换到https

首先用acme申请了证书

在wordpress容器到公网之间添加了一个nginx做反代顺便添加https支持

首先遇到的问题是修改站点地址为https后argon的js和css都请求失败,一看发现还都是http

折腾半天找不到配错的地方,最后没写过php也只能硬改代码了,在argon的functions.php里添加了以下代码

function fix_output_urls($buffer) {
    return str_replace('http://e404.top', 'https://e404.top', $buffer);
}

function start_output_buffer() {
    ob_start("fix_output_urls");
}

add_action('wp_loaded', 'start_output_buffer');

然后就返回了正常的https地址

但是打开管理界面的时候一直重定向到自己

又调试半天,找不到问题原因

只能直接改代码看日志debug

半夜把is_ssl函数改了一下发现能跑了

function is_ssl() {
	if ( isset( $_SERVER['HTTPS'] ) ) {
		if ( 'on' === strtolower( $_SERVER['HTTPS'] ) ) {
			return true;
		}

		if ( '1' === (string) $_SERVER['HTTPS'] ) {
			return true;
		}
	} elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' === (string) $_SERVER['SERVER_PORT'] ) ) {
		return true;
	}

	return false;
}

第二天发现是wp-config-docker.php原来是需要替换config.php的

修复误损坏/usr的ubuntu

前情提要

在安装telegraf的时候需要复制文件到/etc /usr /var 等目录下,移动文件的时候错误使用了mv usr/* /usr/*的指令,导致系统损坏,缺少bash无法ssh远程连接

尝试远程修复

ssh连不上的情况下发现跑在docker中的容器居然还在正常运行,于是尝试通过portainer新建容器挂在根目录修复,发现portainer会执行宿主机上的可执行文件调用docker,失败

物理机处理

在无法远程修复的情况下只能直接操作物理机修复/备份数据重装。此时物理机/usr目录损坏已经无法重启正常进入系统,启动时显示run-init: can't execute '/sbin/init': No such file or directory,猜测是因为该文件是从/usr/sbin下软链接过来的

使用安装盘进入系统

我的安装盘是rufus创建的,使用的是之前安装系统时的ubuntu 24.04.1 server

首先进入启动盘选择界面,根据教程需要try ubuntu without install然后进入Live 环境,但是server的启动盘中并不包含这个选项,所以直接进入install界面,然后ctrl + alt + f2进入安装器的 BusyBox / Shell

挂载磁盘

使用lsblk指令列出可挂载的磁盘,我的机器中有一块固态和一块机械,固态的主要数据在 /dev/nvme0n1p2,机械的数据在/dev/sdb1,同时我还插入了移动硬盘以便在系统无法修复的情况下直接备份数据,移动硬盘在 /dev/sdc

首先挂载系统盘检查损坏情况mkdir /nvme && mount /dev/nvme0n1p2 /nvme,然后ls /nvme/usr检查损坏情况

非常神奇的,/nvme/usr/bin/nvme/usr/lib/nvme/usr/src没有被删除,于是我直接从安装器的系统中复制了其余的includelib64libexeclocalsbinshare目录到/nvme/usr下,并重启系统,此时正常进入系统

检查系统损坏情况

虽然正常进入了系统,但是并不是所有问题都解决了,首先检查了一些系统服务是否正常运行

dpkg -V | grep -v '^??'列出所有有缺失的包,apt reinstall重新安装确保丢失文件被重新安装

最后安装mariadb的时候忘了指定版本导致启动失败,回忆起以前安装的时候找的对标mysql8的版本,于是卸载自动安装的最新版本换成了mariadb-server=1:10.11.13-0ubuntu0.24.04.1,重启服务后正常运行

检查docker的时候发现docker compose指令不见了,于是重新安装apt install docker-compose-plugin

一天后发现博客无法访问,博客是部署在docker中的wordpress(也就是你现在看到的博客),显示Error establishing a database connection,检查后发现mariadb的默认绑定地址是127.0.0.1,所以无法从容器中访问

后记

至此,系统已修复完成,所有服务都可以正常运行

docker 部署 nexus

docker 安装 nexus

#!/bin/bash

docker run -d --user root -p 8081:8081 -v /opt/nexus-data:/nexus-data --name nexus3 sonatype/nexus3:latest

数据目录映射到主机的 /opt/nexus-data,容器的/nexus-data

配置签名

本来不知道有这步的,但是偶然点进了system status界面,看到一个红叉和Nexus was not configured with an encryption key and is using the Default key.

签名配置是一个json文件,默认没有生成(起码我没找到),格式如下

{
  "active": "key-id",
  "keys": [
    {
      "id": "key-id",
      "key": "base64 key"
    }
  ]
}

这个key可以通过 openssl rand -base64 16 生成

有两种方式指定配置文件路径,一种是使用环境变量,另一种是nexus配置文件(/opt/nexus-data/etc/nexus.properties),我使用的是nexus配置文件,在配置文件添加一行

nexus.secrets.file=/nexus-data/secrets.json

关闭central

因为我的nexus搭建在家里云,本身上行带宽就不高,不希望跑太多流量

取消勾选 repository > centralonline

安全

禁用admin用户

因为admin用户名是默认的管理员账号,所以禁用之,然后创建子管理用户,给管理角色,并保存

创建ci用户并分配权限

准备后续使用ci推送构件到nexus,所以专门分配一个ci用户

给了如下权限

  • nx-repository-view-maven2-maven-central-add
  • nx-repository-view-maven2-maven-central-browse
  • nx-repository-view-maven2-maven-central-edit
  • nx-repository-view-maven2-maven-central-read
  • nx-repository-view-maven2-maven-public-add
  • nx-repository-view-maven2-maven-public-browse
  • nx-repository-view-maven2-maven-public-edit
  • nx-repository-view-maven2-maven-public-read
  • nx-repository-view-maven2-maven-releases-add
  • nx-repository-view-maven2-maven-releases-browse
  • nx-repository-view-maven2-maven-releases-edit
  • nx-repository-view-maven2-maven-releases-read
  • nx-repository-view-maven2-maven-snapshots-add
  • nx-repository-view-maven2-maven-snapshots-browse
  • nx-repository-view-maven2-maven-snapshots-edit
  • nx-repository-view-maven2-maven-snapshots-read

尝试推送

招了一个gradle项目,配置publish

publishing {
    repositories {
        maven {
            name = "snapshot"
            url = uri("http://localhost:8081/repository/maven-snapshots/")
            isAllowInsecureProtocol = true
            credentials {
                username = "homo"
                password = "114514"
            }
        }
    }
}

注意此处推送的是snapshot,需要在项目version后添加后缀-SNAPSHOT,否则推送响应400

总结

还没有使用所以没有总结(咕咕咕

kotlin script

kotlin是我最喜欢用的语言,语法简洁功能丰富,但是项目管理略嫌麻烦,那有什么办法可以跳过麻烦的项目管理,同时又直接使用jvm庞大生态的依赖呢

答案就是 kotlin script

使用

首先需要一个最新的idea,旧版本的idea对这类新特性的支持并不太好

kotlin脚本有好几种,临时文件里创建的scratch,gradle项目管理的build.gradle.kts,以及本文的主题 main.kts

用idea在任意位置创建一个 test.main.kts ,随便写一段代码

println("hello kotlin script")

虽然文件行号上没有任何标记,但是我们可以用 ctrl + shilt + f10 运行该文件

当然也可以直接新建一个运行项

引入依赖

引入依赖的方式如下 @file:DependsOn("com.google.code.gson:gson:2.11.0")

也可以用 @file:Repository("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") 指定maven仓库

修改代码如下,然后点一下代码编辑器右上角的加载脚本依赖项按钮

@file:DependsOn("com.google.code.gson:gson:2.11.0")

import com.google.gson.JsonElement
import com.google.gson.JsonParser

val json: JsonElement = JsonParser.parseString("""{ "name": "kotlin script", "age": 1 }""")

println(json)

此时就已经可以使用gson的类了

当然在没有科学上网的情况下有时候是无法正常下载依赖的,对于这一点我修改了maven镜像源,以便在不开tun的情况下正常下载依赖

序列化

虽然我一直都很喜欢kotlinx.serialization的序列化,但是该序列化依赖gradle插件,我还没有研究明白怎么在 kts 脚本里使用这种插件

所以为了在脚本里使用序列化,我会选择gson作为序列化实现

提供服务

我有一些简单的http服务需要实现,这个时候我会选择ktor,他和kotlin相性极佳(毕竟都是一家的项目)

@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")

@file:DependsOn("io.ktor:ktor-server-content-negotiation:2.3.13")
@file:DependsOn("io.ktor:ktor-server-compression-jvm:2.3.13")
@file:DependsOn("io.ktor:ktor-serialization-kotlinx-json:2.3.13")
@file:DependsOn("io.ktor:ktor-server-core-jvm:2.3.13")
@file:DependsOn("io.ktor:ktor-server-netty-jvm:2.3.13")
@file:DependsOn("io.ktor:ktor-server-call-logging-jvm:2.3.13")

@file:DependsOn("com.google.code.gson:gson:2.11.0")

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.compression.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.coroutines.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import kotlin.system.exitProcess

val logger: Logger = LoggerFactory.getLogger("App")
val scope = CoroutineScope(Dispatchers.IO) + CoroutineExceptionHandler { coroutineContext, throwable ->
    logger.warn("", throwable)
}
val gson = Gson()
val json = """
    |{
    |  "kotlin": 1,
    |  "java": 2
    |}
""".trimMargin()

// 信号处理
run {
    Runtime.getRuntime().addShutdownHook(Thread {
        logger.info("exit...")
        scope.cancel()
        server.stop(0, 0)
    })
}

fun Route.configureRouting() {
    get("/{id}/status") {
        val id = call.parameters["id"]!!
        val status = gson.fromJson<Map<String, Int>>(json, object : TypeToken<Map<String, Int>>() {}.type)[id]
        call.respondText(status.toString())
    }
}

// http服务
val server = embeddedServer(Netty, 5753) {
    install(CallLogging) {
        level = Level.INFO
    }
    install(Compression) {
        gzip {
            priority = 1.0
            matchContentType(ContentType.Text.Any)
        }
        deflate {
            priority = 10.0
            minimumSize(1024)
        }
    }
    routing {
        configureRouting()
    }
}
server.start(true)
exitProcess(0)

部署

在idea里面我们已经可以运行main.kts脚本了,但是如果要部署,那怎么办呢

这儿需要到 kotlinrelease 下载 kotlin-compiler

#!/bin/bash

kotlinc/bin/kotlin -Dfile.encoding=utf8 your_file.main.kts

然而添加jvm参数的部分在win上用不了,虽然我提了issue但是并没有解决

优势

很多时候我会写很多测试代码,调研某个库的使用方法和功能实现,但是这些代码如果集中在一个项目,会让项目变得很大,加载很慢,如果分开会不方便统一管理

这个时候使用kotlin script就可以很方便的管理

小问题

当我有如下代码

@file:DependsOn("com.google.code.gson:gson:2.11.0")

import com.google.gson.JsonElement
import com.google.gson.JsonParser

val json: JsonElement = JsonParser.parseString("""{ "name": "kotlin script", "age": 1 }""")

object MyService {
    fun someFunction(): String {
        return json.toString()
    }
}

println(MyService.someFunction())

那么运行之后会出现

虽然这是合理的是可能的,但是这是合理的是不太可能的

另外idea在重载kts脚本依赖的时候会阻塞ui线程导致idea无响应

再另外,对于低版本的idea使用kts脚本引用外部依赖,会在打开反编译代码的时候放一个索引按钮,但是点了压根没用(新版本修了

总结

对于一些简单项目,使用 kotlin script非常爽,不管是修改还是部署,都很方便

总之好用爱用多用😋

kotlin notebook 使用体验

最近遇到一些数据分析的需求,正好之前看到又新又好的[kotlin notebook](https://book.kotlincn.net/text/d-notebook-get-started.html),就拿来玩了一下

前置条件

首先按照教程,需要安装插件,此处由于公司电脑的idea版本较旧(不是不想更,是更新了jrebel启动慢一倍),所以一开始新建了文件也没有代码高亮,最终装个两个版本的idea,旧版本idea专门跑jrebel项目

然后按照教程需要一个项目,此处测试了项目类型,intellij和gradle都是可以的

使用

在项目文件夹内任意位置新建一个kotlin notebook文件,即可使用

引入依赖

// 使用最新版本的依赖
%useLatestDescriptors

// 引入 Kotlin DataFrame 依赖
%use dataframe

// 引入 Gson 依赖 (引入方式和 main.kts 一样)
@file:DependsOn("com.google.code.gson:gson:2.11.0")

高亮

在运行notebook中的代码之后,关闭项目并重新打开,会发现代码高亮没了,此时需要重新完整运行notebook的代码,才能重置高亮(另外代码块不支持折叠很坏

小问题

可以在块1定义class块2引用,但是直接在块2引用块1定义的变量,代码高亮会出现错误

总结

虽然体验上任有改进空间,但是实际使用是没有大问题的,好用爱用多用😋

安装docker

2025-04-01ubuntu-24.04.1 上安装 docker

参考自 https://www.sysgeek.cn/install-docker-ubuntu/

安装前置

sudo apt update
sudo apt install apt-transport-https curl

添加gpg密钥

这一步遇到问题,可能失败,失败后会在 /etc/apt/keyrings/docker.gpg 写一个空文件,需要删除后重试

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

添加仓库

虽然格式和apt自带的不一致,但是能用

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

更新软件包列表

sudo apt update

安装

这一步下载很慢,选择给apt设置代理(http_proxy环境变量居然无效) 创建文件 /etc/apt/apt.conf.d/proxy.conf 并写入

Acquire::http::Proxy "http://192.168.1.1:7890/";
Acquire::https::Proxy "http://192.168.1.1:7890/";
# 安装
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

检查docker状态

sudo systemctl is-active docker

hello world

下不动哈哈哈
https://cloud.tencent.com/developer/article/2485043 找到了可用的两个镜像源

编辑文件 /etc/docker/daemon.json

{
  "registry-mirrors": [
    "https://docker.xuanyuan.me",
    "https://docker.1ms.run"
  ]
}

配置完成后

sudo systemctl daemon-reload
sudo systemctl restart docker

github student developer pack

申请时间 2024.03.26

准备github账号

  1. 注册并登录github
  2. 右上角头像 -> 设置
  3. 左边选择 billing and plans 下的 payment infomation
  4. 填写账单地址,主要名字必须是真名,和学籍信息一致的名字,firstname长度不够要求可以在后面加空格

绑定edu邮箱

  1. 链接:https://github.com/settings/emails
  2. 绑定邮箱并通过验证,这儿我绑定的是edu.cn的邮箱

开始申请

  1. 链接:https://education.github.com/discount_requests/application
  2. 在这之前需要确定你的定位在学校,手机可以使用fake location(要root),pc可以打开开发者工具,点右上角三个点 -> 更多工具 -> 传感器,在打开的界面中位置选择其他,然后输入经纬度
  3. 修改好定位后刷新网页,并确保接下来的步骤中不要使用梯子
  4. 在下面的邮箱列表中选择 edu.cn 邮箱
  5. 输入学校全名,此处会有补全,但是速度可能较慢,最终提交时应该是全英文的名字
  6. 点击下方的 continue(ps:此处我一开始用手机还是pc都点continue无效,最后用的f12修改传感器解决)
  7. 第二个界面要求拍照上传凭证,pc无法完成,但是申请的进度是可以保留的,因此我们用手机打开申请链接,可以继续完成第二步;手机上传照片,网上很多推荐用学籍报告之类的,实际上手写也是可以的,github应该是ocr识别的;此处我上传先是submit怎么点都不成功,回到原界面,最后用开发者工具打开手机chrome的调试才发现是payload too large了(点名批评gh开发者),最终使用前置摄像头拍了一张很糊的照片(因为很糊,所以上面只有关键信息,没有把二维码和标题之类的拍进去)
  8. 提交

tmux

在后台启动tmux tmux new -d -s name

给tmux输入发送指令 tmux send-keys -t name:0 "echo hello" C-m

unidbg自行支持其他版本的协议

两个so文件

首先从自带的文件中可以看出,需要获取两个so文件

这两个so文件只要下载对应版本的qqapk,使用解压软件打开就可以获得

config.json中的协议信息

这儿使用jadx来反编译qqapk

通过自带的版本信息可以知道,qua是V1_AND_SQ开头的,我们在jadx中搜索

可以看到,前缀匹配上了,但是后缀不一样,所以这儿我们只复制版本信息以及code(版本号后面的四位数字应该就是需要的code)

dtconfig

一样在jadx中搜索dtconfig,可以看到搜出来很多选项,但是我们一个一个看过去

可以看到,在第5个搜索出来的文件里面,看到了一串硬编码的二进制数据

有些常量可以ctrl+左键跳转,将反编译的内容复制出来然后替换掉常量并修改格式,这样就获取到了dtconfig

至此所有需要的内容就都从apk中获取到了

Windows部署MongoDB

下载

MongoDB 6 的zip中只包含mongod即服务端,需要额外下载mongosh作为客户端

mongo下载:https://www.mongodb.com/try/download/community

mongosh下载:https://www.mongodb.com/try/download/shell

两者的下载都需要选择平台和架构

mongod安装服务

下载的MongoDB解压到目标位置,然后在其中新建一个文件mongod.yml(名字和后缀名不重要,随便起)

这儿的两个path,都换成自己的,目录需要自己手动创建,比如我这儿就是在MongoDB文件夹下新建的data文件夹和logs文件夹

systemLog:
    destination: file
    path: D:\App\MongoDB\logs\mongod.log
storage:
    dbPath: D:\App\MongoDB\data

在命令行中执行以下指令注册服务(这儿的serviceName是不可缺少的)

# 安装mongo服务
mongod --config "D:\App\MongoDB\mongo.yml" --install --serviceName "MongoDB"
# 启动mongo服务
net start MongoDB

此时mongodb的服务就已经安装并启动了,可以用mongosh连接了(添加path不再赘述)

创建用户

// 参考 https://www.mongodb.com/docs/manual/tutorial/create-users/#create-additional-users-for-your-deployment
use test
db.createUser(
  {
    user: "用户名",
    // passwordPrompt()会要求在控制台中输入密码, 避免密码出现在日志中, 也可以直接使用密码
    pwd:  passwordPrompt(),
    // 权限参考 https://www.mongodb.com/docs/manual/reference/built-in-roles/#database-user-roles
    roles: [ { role: "readWrite", db: "test" },
             { role: "read", db: "reporting" } ]
  }
)

mongosh登录

mongosh "mongodb://admin:123456@localhost:27017/test?authSource=admin"

构建KonaJDK

最近准备参加OpenSourceTalent,题目是KonaJDK相关的,首先要构建KonaJDK,这儿我踩了很多坑

尝试在windows构建

  • 我一开始尝试使用mingw构建,但是configure中要求的make版本不一致
    configure: Found GNU make version GNU Make 4.2.1 at /f/App/mingw64/bin/make, but it is not for msys (it says: Built for x86_64-w64-mingw32). Ignoring. configure: error: Cannot find GNU make 3.81 or newer! Please put it in the path, or add e.g. MAKE=/opt/gmake3.81/make as argument to configure.

  • 然后我尝试使用msys2构建,make版本没有问题,但是configure死活不认我装的vs2022,使用vs2013也一样构建失败(后来在ci里看到它使用的是vs2017)
    configure: Could not succesfully extract the envionment variables needed for the VS setup.
    configure: Try setting --with-tools-dir to the VC/bin directory within the VS installation
    configure: or run "bash.exe -l" from a VS command prompt and then run configure from there.
    configure: error: Cannot continue

  • 宣告放弃,下各种版本的vs弄得我头都大了

尝试在Ubuntu构建

  • 首先我电脑里有一个之前用过的Ubuntu22.04,我先尝试在上面构建,但是编译的时候会出现各种语法错误导致无法完成编译

  • 然后我去翻Kona的ci,看到了他的构建脚本,使用的是Ubuntu20.04
  • 最终我下了一个新的Ubuntu20.04镜像,按照ci的构建脚本构建成功了(但是还是要自己apt装一些依赖,这ci依赖不全怎么能构建出来的?

Ubuntu上的构建过程

# ci里装的依赖
apt-get install openjdk-8-jdk gcc-9 g++-9 libxrandr-dev libxtst-dev libcups2-dev libasound2-dev
# 自己安装时缺少的依赖
apt-get install libfontconfig1-dev libfreetype6-dev

bash configure \
--with-conf-name=linux-x64 \
--with-target-bits=64 \
--enable-debug \
--with-build-number=b00 \
--with-boot-jdk=/usr/lib/jvm/java-8-openjdk-amd64 \
--with-zlib=bundled \
--with-freetype-lib=/usr/lib/x86_64-linux-gnu \
--with-freetype-include=/usr/include/freetype2

make images

总结:构建文档没一个能信的,只有ci不会骗人

准备等空了再挑战一下在win构建KonaJDK,成功了再来更新

log4j+slf4j Ignoring binding 的解决方案

SLF4J: Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier.
SLF4J: Ignoring binding found at [jar:file:/F:/AppData/Gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.20.0/7ab4f082fd162f60afcaf2b8744a3d959feab3e8/log4j-slf4j-impl-2.20.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See https://www.slf4j.org/codes.html#ignoredBindings for an explanation.

引入依赖org.apache.logging.log4j:log4j-slf4j-impl:$_version_时替换为org.apache.logging.log4j:log4j-slf4j2-impl:$_version_

参考官方文档

使用powershell批量将xlsx转csv

最近帮朋友写自动转xlsx到csv的脚本,尝试了aspose-cells和poi,前者闭源还混淆,后者速度慢很多,后来发现powershell脚本可以直接调用excel的api,最后就选择了powershell脚本

注意事项

  1. 此脚本需要电脑上安装Excel
  2. 此脚本需要电脑上安装高版本的powershell

使用方式

  1. 复制脚本到文件并修改文件后缀名到.ps1
  2. 在脚本所在的文件夹中新建文件夹并重命名为input,把需要转换的文件放进去
  3. 右键脚本,选择使用powershell7运行 或者 右键 -> 属性 修改打开方式为pwsh(在安装高版本powershell的位置找),然后直接运行

脚本内容

chcp 65001 > nul
Echo "将需要转换的文件放在input文件夹中, 输出的文件放在output文件夹中"
if (!(Test-Path input)) { Mkdir input > $null }
if (!(Test-Path output)) { Mkdir output > $null }
$WorkingDir = (Get-Location).Path
$Start = Get-Date -UFormat %s
$Files = Get-ChildItem -Path "input" -Recurse | Where-Object { $_.Name -match ".*\.xls[xm]?" }
$Files | ForEach-Object -Parallel {
    $Start = Get-Date -UFormat %s
    $Excel = New-Object -ComObject Excel.Application
    $WorkingDir = $using:WorkingDir
    $InFileName = $_.name
    $OutFileName = "$InFileName.csv"
    Echo "处理中... $InFileName -> $OutFileName"
    $OutFilePath = "$WorkingDir\output\$OutFileName"
    if (Test-Path $OutFilePath) { Remove-Item -Path $OutFilePath }

    $Excel.Workbooks.Open("$WorkingDir\input\$InFileName").Worksheets.Item(1).SaveAs("$OutFilePath", 62)

    $Time = (Get-Date -UFormat %s) - $Start
    Echo "处理完成 $OutFilePath 耗时${Time}秒"
    $Excel.Quit()
}
Get-Process -name Excel | Where-Object { $_.mainWindowTitle -eq "" } | ForEach-Object { Stop-Process $_.Id }
$Time = (Get-Date -UFormat %s) - $Start
$Count = $Files.Count
Echo "全部文件处理完成, 共${Count}个文件, 耗时${Time}秒"
Timeout -t 3

脚本会从input文件夹中寻找后缀为.xls.xlsx.xlsm的文件,将他的第一张表转换成csv格式并保存到output文件夹

SaveAs的第二个参数可以自行修改,参考

ps powershell还挺好用,比batch强多了

以及一个合并csv的脚本(合并表头)

chcp 65001 > nul
Write-Output "将合并output文件夹中的csv文件, 输出到output.csv"
if (!(Test-Path input)) { Mkdir input > $null }
$WorkingDir = (Get-Location).Path
$Start = Get-Date -UFormat %s
$Files = Get-ChildItem -Path "output" -Recurse | Where-Object { $_.Name.EndsWith(".csv") }
if ($Files.Count -le 1) {
    Timeout -t 3
    exit
}
# 忽略的行数, 不包括表头
$Ignore = 0
$Flag = $true
$Out = [System.IO.File]::OpenWrite("$WorkingDir\output.csv")
$Writer = New-Object System.IO.StreamWriter($Out, [System.Text.UTF8Encoding]::new($true))
$Files | ForEach-Object {
    $Stream = [System.IO.File]::OpenRead("$WorkingDir\output\$($_.name)")
    $Reader = New-Object System.IO.StreamReader($Stream)
    # 忽略前n行
    if ($Ignore -ne 0) {
        foreach ($__ in 1..$Ignore) { $Reader.ReadLine() > $null }
    }
    $Head = $Reader.ReadLine()
    # 表头行
    if ($Flag) {
        $Flag = $false
        $Writer.WriteLine($Head)
    }

    while ($Line = $Reader.ReadLine()) {
        $Writer.WriteLine($Line)
    }

    $Reader.Close()
    $Stream.Close()
}
Write-Output "全部文件合并完成, 共$($Files.Count)个文件, 耗时$((Get-Date -UFormat %s) - $Start)秒"
Timeout -t 3

win环境下配置gpg密钥

本文中使用的密钥为示例密钥,并无实际作用

安装git

https://git-scm.com/downloads

打开git bash

在桌面上右键打开菜单,点击

生成gpg密钥

参考文档 https://docs.github.com/zh/authentication/managing-commit-signature-verification/generating-a-new-gpg-key

输入指令 gpg --full-generate-key

$ gpg --full-generate-key # 输入生成gpg密钥的指令
gpg (GnuPG) 2.2.29-unknown; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want: # 选择你想要的密钥类型,默认即可
   (1) RSA and RSA (default) # 默认值
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
  (14) Existing key from card
Your selection? # 回车,使用默认值(rsa)
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096 # 此处我手动输入4096,因为git文档中注明 `密钥必须至少是 4096 位`
Please specify how long the key should be valid. # 选择有效期
         0 = key does not expire # 默认值,永久生效
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) # 回车,使用默认值
Key does not expire at all # 提示密钥不会过期
Is this correct? (y/N) y # 输入y并回车

GnuPG needs to construct a user ID to identify your key. # 需要输入个人信息

Real name: qwert # 输入名字,此处输入的是qwert
Email address: [email protected] # 输入邮箱,此处输入的是[email protected]
Comment: test # 输入注释,此处输入的是test
You selected this USER-ID:
    "qwert (test) <[email protected]>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? # 输入o则确认信息无误,生成密钥;输入q则取消生成并退出

输入o并回车后会跳出弹窗让你输入密码并确认(过短会提示,可以强制使用)

查看已有密钥 gpg --list-secret-keys --keyid-format=long

$ gpg --list-secret-keys --keyid-format=long
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
/c/Users/Administrator/.gnupg/pubring.kbx
-----------------------------------------
sec   rsa4096/25F10366757A2676 2023-02-13 [SC]
      112C798ADD267E3918A0663825F10366757A2676
uid                 [ultimate] qwert (test) <[email protected]>
ssb   rsa4096/9DE000ED74E49E6B 2023-02-13 [E]

导出公钥

在git bash中输入 gpg --armor --export 25F10366757A2676

注意,此处最后的数组是你生成的密钥对应的id,也就是上述步骤中sec开头的行,rsa4096/之后的那一串字符,此处是 25F10366757A2676

导出得到的公钥格式应该是如下格式

$ gpg --armor --export 25F10366757A2676
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
=i2+O
-----END PGP PUBLIC KEY BLOCK-----

将公钥导入github

github设置 https://github.com/settings/keys

添加新gpg密钥 https://github.com/settings/gpg/new

文档参考 https://docs.github.com/zh/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account

打开添加新gpg密钥界面并填入title和key

title可以写一个名字,用于区分不同的公钥

key写上面步骤中导出的公钥

按照上述生成的内容填充后应该是如下格式

点击下方的Add GPG key将此gpg密钥添加到git中

设置git在提交中启用gpg签名

开启commit使用gpg签名 git config --global commit.gpgsign true

将 Git 配置为默认对所有提交进行签名 git config --global user.signingkey 25F10366757A2676

设置之后idea若不开启使用gpg签名则会提交失败

设置idea在提交时使用gpg签名

在idea设置中找到版本控制 > git,点击配置GPG密钥,勾选使用GPG密钥签署提交并在下方选择框中选择使用的密钥

之后提交推送时会弹窗提示输入密码

删除gpg密钥

删除密钥有三个选项:--delete-keys--delete-secret-keys--delete-secret-and-public-key
--delete-keys表示从公钥钥匙圈上删除密钥,也就是一同删除公钥和对应的私钥。在分批模式(batch mode)下,密钥必须使用指纹表示,或者使用--yes选项。
--delete-secret-keys表示从私钥钥匙圈上删除密钥。
--delete-secret-and-public-key--delete-keys一样,但是如果私钥存在的话,会先移除私钥。在分批模式下,密钥必须使用指纹表示。

此处在git bash中输入 gpg --delete-secret-and-public-key 25F10366757A2676

注意,此处最后的数组是你生成的密钥对应的id,也就是上述步骤中sec开头的行,rsa4096/之后的那一串字符,此处是 25F10366757A2676

$ gpg --delete-secret-and-public-key 25F10366757A2676
gpg (GnuPG) 2.2.29-unknown; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


sec  rsa4096/25F10366757A2676 2023-02-13 qwert (test) <[email protected]>

Delete this key from the keyring? (y/N) y # 输入y并回车
This is a secret key! - really delete? (y/N) y # 输入y并回车

pub  rsa4096/25F10366757A2676 2023-02-13 qwert (test) <[email protected]>

Delete this key from the keyring? (y/N) y # 输入y并回车
# 弹窗验证

gradle导入authlib依赖

repositories {
    maven("https://libraries.minecraft.net")
}

dependencies {
    implementation("com.mojang:authlib:3.11.49")
}

Ubuntu安装Mysql5.7笔记

下载

https://downloads.mysql.com/archives/community/

解压

tar -xf mysql-5.7.39-linux-glibc2.12-x86_64.tar.gz

创建linux用户和组

groupadd mysql && useradd -r -g mysql mysql

设置

注意,以下示例步骤中安装mysql的位置为/usr/local/mysql,mysql数据文件夹为/usr/local/mysql/data

vim /etc/my.cnf

参考:http://c.biancheng.net/view/7618.html

这儿的不要乱写,容易导致各种奇奇怪怪的问题

[mysqld]
port=3306
user=mysql
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
socket=/tmp/mysql.sock
max_connections=200
symbolic-links=0
character-set-server=utf8
default-storage-engine=INNODB
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

[mysql]
default-character-set=utf8

[mysqld_safe]
log-error=/usr/local/mysql/error.log
pid-file=/usr/local/mysql/mysqld.pid

文档:https://www.mysqlzh.com/doc/13/78.html

# 创建数据文件夹, 日志文件夹, 设置所有者
cd /usr/local/mysql && mkdir logs && touch error.log && chown mysql:mysql -R *

初始化

bin/mysqld --defaults-file=/etc/my.cnf --basedir=/usr/local/mysql/ --datadir=/usr/local/mysql/data/ --user=mysql --initialize

解决./mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory

sudo apt install -y libaio-dev numactl

复制 mysql 到系统服务

cp support-files/mysql.server /etc/init.d/mysql

解决Unit mysql.service does not exist, proceeding anyway.

修改vim /etc/init.d/mysql

# 若/etc/my.cnf中指定了mysql目录,则此处也需要指定
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data

启动mysql

systemctl enable mysql

或者

service mysql start

进入mysql命令行

mysql -u root -p
# 在接下来的一行中输入密码

解决./mysql: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory

apt-get install -y libncurses*

修改密码

-- 更新密码
alter user 'user'@'host' identified by 'password';
-- 刷新权限
flush privileges;

常用操作

以下内容参考于 https://blog.csdn.net/qpzkobe/article/details/102833458

# 创建用户 username替换为用户名, host替换为地址, password替换为密码
# 示例 CREATE USER 'mc'@'localhost' IDENTIFIED BY '123456';
CREATE USER 'username'@'host' IDENTIFIED BY 'password';
# 删除用户 username替换为用户名, host替换为地址
# 示例 DROP USER 'mc'@'localhost';
DROP USER 'username'@'host';
# 授权 privileges替换为权限, databasename替换为库名, tablename替换为表名, username替换为用户名, host替换为地址
# 示例 GRANT all ON playerpoints.* TO 'mc'@'localhost';
GRANT privileges ON databasename.tablename TO 'username'@'host';
# 撤销授权 privileges替换为权限, databasename替换为库名, tablename替换为表名, username替换为用户名, host替换为地址
# 示例 REVOKE all ON playerpoints.* FROM 'mc'@'localhost';
REVOKE privilege ON databasename.tablename FROM 'username'@'host';

# 刷新权限
flush privileges;

screen简单教程

Linux screen命令用于多重视窗管理程序。

screen为多重视窗管理程序。此处所谓的视窗,是指一个全屏幕的文字模式画面。通常只有在使用telnet登入主机或是使用老式的终端机时,才有可能用到screen程序。

https://www.runoob.com/linux/linux-comm-screen.html

下文中使用name作为默认的screen名字示例,实际使用中需要替换成你创建的screen的名字

简单的入门使用

创建并进入

screen -S name # 创建一个名字为name的screen

挂起当前screen(还可以进入)

在screen中按下ctrl + a,然后按d(注意如果开启中文输入法可能会失效)

查看存在的screen

screen -ls # 查看存在的screen

进入挂起的screen

screen -x name # 进入名为name的screen(允许多个终端同时进入)

若打开了多个同名的screen, 则需要先使用 screen -ls 列出并找到要打开的screen对应的id, 然后使用 screen -x id 进入对应的screen

screen -x 12345 # 进入id为12345的screen

退出当前screen(无法再进入)

exit # 若当前处于screen则退出screen, 否则会退出当前bash

特殊用法

后台创建

screen -dmS name # 创建一个名为name的screen但是不进入

外部执行指令

screen -x -S name -p 0 -X stuff "ls\n" # 外部调用名为name的screen输入ls并回车(\n代表回车)

此处的 -p 0 意为若存在多个screen则选择第0个(从0开始),若你没有同名的screen则可以去掉

外部关闭screen

screen -S name -X quit # 从外部关闭名为name的screen

滚动

先按 ctrl + a , 然后按esc

gradle打包依赖进jar

tasks.create<Jar>("fatJar") {
    archiveBaseName.set(project.name + "-all")
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    manifest {
        attributes["Main-Class"] = "top.e404.bot.loader.Main"
    }
    val exclude = listOf(
        "LICENSE.txt",
        "META-INF/MANIFSET.MF",
        "META-INF/maven/**",
        "META-INF/*.RSA",
        "META-INF/*.SF"
    )
    val dependencies = configurations.runtimeClasspath.get().map(::zipTree)
    from(dependencies).exclude {
        it.path in exclude
    }
    with(tasks.jar.get())
}

或者使用 shadow 插件

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
  kotlin("jvm") version "1.7.21"
  id("com.github.johnrengelman.shadow") version "7.1.2"
}

group = "top.e404"
version = "1.0.0"

repositories {}

dependencies {}

tasks.shadowJar {
  archiveFileName.set("${project.name}-${project.version}-all.jar")
}

记录一次MySQL遇到的问题

昨天自己的家里云突然死机,重启之后MySQL启动不了

经过半天的百度,无论怎么设置权限,结果都是Permission denied

最终重装mysql解决

首先备份mysql文件(包括数据文件夹),之后删除原有mysql用户及存放mysql的mysql用户家目录

重新创建mysql用户,下载并初始化mysql

初始化完成后停止mysql服务,将新的mysql的数据文件夹重命名,然后将之前备份的数据文件夹复制过来,复制完成后重新启动mysql服务

完成

Mirai设置设备信息

本教程对应的是V2的设备信息,若你在 device.json 中看到 "deviceInfoVersion": 2 ,那么你用的就是V2的设备信息

下面是一份默认的"随机"生成的设备信息,可以很明显的看出,这是一个野生的机器人

{
  "deviceInfoVersion": 2,
  "data": {
    "display": "MIRAI.524848.001",
    "product": "mirai",
    "device": "mirai",
    "board": "mirai",
    "brand": "mamoe",
    "model": "mirai",
    "bootloader": "unknown",
    "fingerprint": "mamoe/mirai/mirai:10/MIRAI.200122.001/7868854:user/release-keys",
    "bootId": "16FD34B2-AD5D-469D-8535-C6C6859DB3DB",
    "procVersion": "Linux version 3.0.31-7nHaGuio ([email protected])",
    "baseBand": "",
    "version": {
      "incremental": "5891938",
      "release": "10",
      "codename": "REL"
    },
    "simInfo": "T-Mobile",
    "osType": "android",
    "macAddress": "02:00:00:00:00:00",
    "wifiBSSID": "02:00:00:00:00:00",
    "wifiSSID": "<unknown ssid>",
    "imsiMd5": "947794dd7c61f984994e89de00b2ba75",
    "imei": "950450581801049",
    "apn": "wifi"
  }
}

如果不知道对应的参数表示什么,编也编不出来。所以我直接不编,拿个旧手机用旧手机的设备信息

设置adb环境

首先需要adb,下载地址:https://dl.google.com/android/repository/platform-tools-latest-windows.zip

下载之后解压,完成之后应该是如图的样式

然后打开cmd

  1. 在adb的文件夹中(这个文件夹中必须包含adb.exe)按住shift然后右键空白处(不要点文件)
  2. 点击 在此处打开 powershell 窗口
  3. 在窗口中输入cmd

连接设备(手机平板均可)

这里我用旧平板做演示

启用开发者选项和USB调试

连接并开启USB调试

使用adb获取设备信息

此时我们已经成功将设备连接至pc

下面的表格是每个信息的获取指令,使用方式如图

因为设备制造厂商不同,以下内容可能会有不同的,可以使用 adb shell getprop 获取所有属性,然后自行寻找对应的属性

信息

对应指令

display

adb shell getprop ro.build.id

product

adb shell getprop ro.build.product

device

adb shell getprop ro.vendor.product.device

board

adb shell getprop ro.product.board

brand

adb shell getprop ro.product.brand

model

adb shell getprop ro.product.model

fingerprint

adb shell getprop ro.vendor.build.fingerprint

procVersion

adb shell getprop cat /proc/version

除了表格中的内容,还有一些内容可以自己编的

macAddress:按照原有个格式,将里面的数字和字母替换(可用字符 0123456789abcdef)

wifiBSSID:同上,乱写就可以

wifiSSID:写WIFI名字,也可以乱写一个

到此设备信息就编好了

win10部署MySql5.7并修改密码

下载

https://downloads.mysql.com/archives/community/

下载完成后解压到自己想安装到的文件夹

这里我使用的是 D:\app\MySql

配置

在此文件夹下新建 my.ini 文件

这里贴出我的配置文件

[mysqld]
port=3306
basedir=D:/app/MySql
datadir=D:/appData/MySql
max_connections=200
character-set-server=utf8
default-storage-engine=INNODB
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
[mysql]
default-character-set=utf8

详细解释见中文文档 https://www.mysqlzh.com/doc/13/78.html

设置Path

  1. 此电脑右键 属性
  2. 点击 高级系统设置
  3. 点击 环境变量
  4. 在系统变量中找到Path
  5. 点击编辑
  6. 点击右侧的新建
  7. 输入 D:\app\MySql\bin (这里写自己的安装位置\bin)
  8. 一路确定退出

初始化

  1. win + R 打开运行
  2. 在运行中输入 cmd 并回车
  3. 在cmd中进入mysql文件夹
# 进入mysql文件夹的bin目录
d:
cd app\MySql\bin

# 安装
mysqld install

# 初始化数据文件夹
mysqld --initialize --console
# 执行完成后会给出初始密码(看起来像乱码的那串字符串), 记下来, 后面登录需要用

# 启动
net start mysql

进入MySql

在cmd中输入 mysql -u root -p

根据提示输入密码(上面初始化的时候给你的密码)

输入密码 输入密码 登录完成 登录完成

修改密码

-- 选择数据库
use mysql;
-- 更新密码
update user set authentication_string=password('新密码') where user='root' and Host='localhost';
-- 刷新权限
flush privileges;

use mysql; 失败报错则使用

-- 修改密码
alter user user() identified by '密码';
-- 退出重进
exit

Maven设置阿里云镜像源

win: C:\Users\用户名\.m2

linux: /home/用户名/.m2

正常情况下在此文件夹下会有

  1. repository:存放下载的依赖
  2. settings.xml:maven的配置

此处的settings.xml就是需要设置的文件,若没有可自行新建

下面贴出我的设置

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                          https://maven.apache.org/xsd/settings-1.0.0.xsd">
  	<!-- 设置储存库位置 -->
    <localRepository>D:/.m2/repository</localRepository>
    <!-- 设置下载源 -->
  	<mirrors>
      	<!-- 阿里云 -->
		<mirror>  
			<id>nexus-aliyun</id>  
			<mirrorOf>central</mirrorOf>    
			<name>Nexus aliyun</name>  
			<url>http://maven.aliyun.com/nexus/content/groups/public</url>  
		</mirror>
		<!-- 默认仓库 -->
		<mirror>
			<id>repo1</id>
			<mirrorOf>central</mirrorOf>
			<name>Human Readable Name for this Mirror.</name>
			<url>http://repo1.maven.org/maven2/</url>
		</mirror>
		<mirror>
			<id>repo2</id>
			<mirrorOf>central</mirrorOf>
			<name>Human Readable Name for this Mirror.</name>
			<url>http://repo2.maven.org/maven2/</url>
		</mirror>
	</mirrors>
</settings>

Linux环境变量设置完没有改变

我自己的服务器用的是Centos7, 最近设置环境变量的时候发现设置完source之后PATH居然没有变化

# 修改环境变量(我是写在文件最前面的)
vim /etc/profile

刷新环境变量

source /etc/profile

然后在profile里面设置完环境变量的地方echo了一下, 发现输出的PATH是正确的

经过排查之后发现

/etc/profile.d 里面有一个 mysql.sh

PATH=/usr/local/src/openJDK/bin:/usr/local/src/node/bin:/home/mysql/mysql/bin:/usr/local/src/ffmpeg:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/home/mysql/mysql/bin

怪不得PATH总是改完没反应

这里将它重命名为 mysql.sh.bak , 再source刷新之后, PATH就正确了

Linux设置ssh并通过ssh密钥登陆

进入 .ssh

# 进入 .ssh 文件夹
cd ~/.ssh

处理旧密钥

# 处理旧的密钥文件
mv id_rsa id_rsa.bak
mv id_rsa.pub id_rsa.pub.bak

创建密钥

# 输入后按3次回车
ssh-keygen -t rsa

公钥写入到文件

# 将公钥写入authorized_keys
cat id_rsa.pub >> authorized_keys

问题

若出现问题无法登陆,则检查日志 /var/log/secure

写入公钥后无法登陆

Authentication refused: bad ownership or modes for file /root/.ssh/authorized_keys

原因:

sshd 为了安全,对属主的目录和文件权限有所要求。如果权限不对,则 ssh 的免密码登陆不生效

用户目录权限为 755 或者 700,就是不能是 77x

.ssh 目录权限一般为 755 或者 700

rsa_id.pub 及 authorized_keys 权限一般为 644

rsa_id 权限必须为 600

# 设置权限(权限错误会无法登陆)
cd ~/.ssh
chmod 644 authorized_keys id_rsa.pub && chmod 600 id_rsa && chmod 700 .

Idea安装&白嫖JetBrain软件方法

这里以IDEA为例,JetBrain的软件都可以使用此方法白嫖

下载

官网下载

https://www.jetbrains.com/idea/download/#section=windows

安装

其实不需要改什么设置,这里我只改了安装路径

很多新手在意的,但其实这里什么都不点也没事 很多新手在意的,但其实这里什么都不点也没事

激活

获取激活工具

首先访问**https://jetbra.in/s**获取软件

打开后从所有可用页面中选择任意一个能够访问的, 打开后是如下界面

点击jetbra.zip下载软件

下载并解压到新目录(目录路径中不要有中文字符)

安装

点击脚本安装(安装需要一段时间)

添加javaagent

安装完成后打开idea安装目录(默认在C:\Program Files\JetBrains\IntelliJ IDEA 202x.x.1\bin)

选中文件idea64.exe.vmoptions右键编辑, 在最后一行添加如下内容

此处我将jetbra直接解压在c盘, 所以路径是C:/jetbra/ja-netfilter.jar. 若你安装在D盘的App目录下, 则需要使用D:/App/jetbra/ja-netfilter.jar

添加完成后打开之前的网站, 选择你要激活的软件(比如idea / pycharm), 点击下面的按钮复制激活码(若点击后没有复制,请在之前的步骤中选择别的镜像网站)

将激活码复制到软件中点击激活

最后

享受专业版的IDEA

此方法适用于JetBrain家的所有软件, 截至2023.3.24最后一次更新, 此激活工具都是可以正常使用的

补充

若激活一直失败可以参考https://www.cnblogs.com/airlongdianblog/p/18418285

UTC时间转北京时间

fun main() {
    val formatPattern = "yyyy-MM-dd HH:mm:ss"
    val utc = "2019-07-10T16:00:00.000Z"
    val time = parse(utc, formatPattern)
    println(time)
}

fun parse(utc: String, formatPattern: String): String {
    val zdt = ZonedDateTime.parse(utc)
    val ldt = zdt.toLocalDateTime()
    val formatter = DateTimeFormatter.ofPattern(formatPattern)
    return formatter.format(ldt.plusHours(8))
}

参考 https://www.jianshu.com/p/62fa934ad2bc

IDEA中点击提交并推送时没有反应的解决办法

最近使用IDEA时遇到了点击提交或者提交并推送的按钮时没有反应的问题

这里点开左侧的提交

不勾选分析代码

设置完了之后就不会再点击提交无响应了

Gradle设置阿里云镜像源

Gradle设置

首先找到Gradle的用户目录,默认情况下在 C:\Users\你的用户名\.gradle

如果设置了 GRADLE_HOME 环境变量的话就是你设置的目录

找到家目录中的 init.gradle 文件,如果没有就新建一个

在其中添加如下代码

buildscript {
    repositories {
        maven {
            url 'https://maven.aliyun.com/repository/public'
        }
        maven {
            url 'https://maven.aliyun.com/repositories/jcenter'
        }
    }
}

allprojects {
    repositories {
        maven {
            url 'https://maven.aliyun.com/repository/public'
        }
        maven {
            url 'https://maven.aliyun.com/repositories/jcenter'
        }
    }
}

仓库名

简介

实际地址

使用地址

jcenter

JFrog公司提供的仓库

http://jcenter.bintray.com

https://maven.aliyun.com/repository/jcenter
https://maven.aliyun.com/nexus/content/repositories/jcenter

mavenLocal

本台电脑上的仓库

{USER_HOME}/.m2/repository

C:/Users/{USER_NAME}/.m2/repository (Windows)
/home/{USER_NAME}/.m2/repository (Linux)

mavenCentral

Sonatype公司提供的中央库

http://central.maven.org/maven2

https://maven.aliyun.com/repository/central
https://maven.aliyun.com/nexus/content/repositories/central

google

Google公司提供的仓库

https://maven.google.com

https://maven.aliyun.com/repository/google
https://maven.aliyun.com/nexus/content/repositories/google
https://dl.google.com/dl/android/maven2

jitpack

JitPack提供的仓库

https://jitpack.io

https://jitpack.io

public

jcenter和mavenCentral的聚合仓库

https://maven.aliyun.com/repository/public
https://maven.aliyun.com/nexus/content/groups/public

gradle-plugin

Gradle插件仓库

https://plugins.gradle.org/m2

https://maven.aliyun.com/repository/gradle-plugin
https://maven.aliyun.com/nexus/content/repositories/gradle-plugin

Ubuntu20.04安装&配置下载源/中文

下载ISO & 安装

官网下载,这里选LTS(长期支持版)

这里我用的vmware虚拟机安装的ubuntu,如果要在实体机上安装ubuntu的话,可以使用ubuntu官网推荐的rufus

安装过程比较长并且没有什么需要操作的地方,跳过

配置下载源

安装好系统后,默认是英文,系统默认没有附带中文的语言文件,要更换语言就得下载。

下载之前首先要设置下载源,国内不配置下载源下载会非常慢(如果有弹窗让你更新的话先放着,等配置好下载源之后再更新)

关闭并且重载

等到进度条走完就完成了换源(如果有报错跳过即可)

更换系统语言

安装完成后

设置格式

设置完成后重启就是中文得了

不要忘了设置输入法 不要忘了设置输入法

其他设置

标准文件夹改名

将语言设置为中文后系统会询问是否改名标准文件夹,这里我选择不改

原因是之前用centos7(gnome桌面),中文输入法有时会失灵,无法输入中文,没办法进入中文名字的文件夹

自动息屏

时区

SU & ROOT

ubuntu默认不允许直接使用root账户登陆,需要使用时请使用 su 指令切换到 root 账户,使用完毕之后用 exit 指令退出

第一次使用 su 切换toot账户之前需要先为root账户设置密码

输入 sudo passwd 为root账户设置密码,输入指令后需要输入3此密码,第一次是当前账户的密码,后两次是设置root账户的密码,输入后不显示是正常情况

代理

wget的时候是不走系统代理的,解决办法

export https_proxy=192.168.48.1:7890 export http_proxy=192.168.48.1:7890 export ftp_proxy=192.168.48.1:7890

这里的 192.168.48.1:7890 是我本机的代理,使用时修改成你的代理地址

远程连接/SSH

Ubuntu默认带了openssh-client,但是没有openssh-server

直接 apt install -y openssh-server 会提示

这里我们直接卸载已有的openssh-clileht

apt remove -y openssh-client

然后再安装openssh-server

apt install -y openssh-server

ps: 这里我使用的root账户安装,若是普通账户请在指令前添加 sudo

sudo apt remove -y openssh-client

sudo apt install -y openssh-server

使用zxing生成&识别二维码

GitHub

zxing/zxing

引入依赖

Gradle

implementation 'com.google.zxing:core:3.4.1'

Maven

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.1</version>
</dependency>

示例代码

import com.google.zxing.*
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import java.awt.geom.AffineTransform
import java.awt.image.BufferedImage


object QrUtil {
    private val mfw = MultiFormatWriter()
    private val encodeHints = object : HashMap<EncodeHintType, Any>() {
        init {
            put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H)
            put(EncodeHintType.CHARACTER_SET, Charsets.UTF_8)
            put(EncodeHintType.MARGIN, 1)
        }
    }
    
    private val decodeHints = object : HashMap<DecodeHintType, Any>() {
        init {
            put(DecodeHintType.CHARACTER_SET, Charsets.UTF_8)
            //优化精度
            put(DecodeHintType.TRY_HARDER, true)
            //复杂模式,开启PURE_BARCODE模式
            put(DecodeHintType.PURE_BARCODE, true)
        }
    }

    /**
     * 生成二维码
     *
     * 若宽/高小于生成的二维码的最小宽度则会忽略此宽/高度, 否则会生成此宽/高度的二维码图片
     *
     * @param content 正文
     * @param width 目标图片宽度
     * @param height 目标图片高度
     * @param color1 二维码颜色
     * @param color2 二维码背景
     * @return 二维码图片
     */
    @Throws(WriterException::class)
    fun getQrImage(
        content: String,
        width: Int,
        height: Int,
        color1: Int,
        color2: Int,
    ): BufferedImage {
        val bitMatrix = mfw.encode(content, BarcodeFormat.QR_CODE, width, height, encodeHints)
        val w = bitMatrix.width
        val h = bitMatrix.height
        val image = BufferedImage(w, h, 1)
        for (x in 0 until w) for (y in 0 until h) image.setRGB(x, y, if (bitMatrix[x, y]) color1 else color2)
        return image
    }

    /**
     * 识别二维码
     *
     * @param image 要识别的图片
     * @return 识别的结果
     */
    @Throws(NotFoundException::class)
    fun decode(image: BufferedImage): String {
        val source = BufferedImageLuminanceSource(image)
        val bitmap = BinaryBitmap(HybridBinarizer(source))
        return MultiFormatReader().decode(bitmap, decodeHints).text
    }

    class BufferedImageLuminanceSource(
        image: BufferedImage,
        left: Int = 0,
        top: Int = 0,
        width: Int = image.width,
        height: Int = image.height,
    ) :
        LuminanceSource(width, height) {
        private val image: BufferedImage
        private val left: Int
        private val top: Int
        override fun getRow(y: Int, rowBytes: ByteArray): ByteArray {
            var row = rowBytes
            require(!(y < 0 || y >= height)) { "Requested row is outside the image: $y" }
            if (row.size < width) row = ByteArray(width)
            image.raster.getDataElements(left, top + y, width, 1, row)
            return row
        }

        override fun getMatrix(): ByteArray {
            val width = width
            val height = height
            val area = width * height
            val matrix = ByteArray(area)
            image.raster.getDataElements(left, top, width, height, matrix)
            return matrix
        }

        override fun isCropSupported() = true
        override fun crop(left: Int, top: Int, width: Int, height: Int) =
            BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height)

        override fun isRotateSupported() = true

        override fun rotateCounterClockwise(): LuminanceSource {
            val sourceWidth = image.width
            val sourceHeight = image.height
            val transform = AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth.toDouble())
            val rotatedImage = BufferedImage(sourceHeight, sourceWidth, BufferedImage.TYPE_BYTE_GRAY)
            val g = rotatedImage.createGraphics()
            g.drawImage(image, transform, null)
            g.dispose()
            val width = width
            return BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width), height, width)
        }

        init {
            val sourceWidth = image.width
            val sourceHeight = image.height
            require(!(left + width > sourceWidth || top + height > sourceHeight)) { "Crop rectangle does not fit within image data." }
            for (y in top until top + height) {
                for (x in left until left + width) {
                    if (image.getRGB(x, y) and -0x1000000 == 0) {
                        image.setRGB(x, y, -0x1) // = white
                    }
                }
            }
            this.image = BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_BYTE_GRAY)
            this.image.graphics.drawImage(image, 0, 0, null)
            this.left = left
            this.top = top
        }
    }
}

Gradle高版本编译时Lombok不起作用

今天使用lombok时出现问题,maven项目能正常使用,拿到gradle里面就出问题,在网上找了一圈,最终解决

解决办法:引入lombok时需要用如下格式

dependencies {
    annotationProcessor 'org.projectlombok:lombok:1.18.20'
    compileOnly 'org.projectlombok:lombok:1.18.20'
}

错误的格式(少了 annotationProcessor 'org.projectlombok:lombok:1.18.20'

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.20'
}

其他没有生效的方法

打开此选项