星期五, 四月 17, 2026

194 bitwarden 转 vaultwarden 自建后, creationDate 和 revisionDate 不能写入数据库的 created_at 和 updated_at 问题处理

bitwarden 简称 bw, vaultwarden 简称 vw

思路:  

1, 先将 bw 的数据导出成 bw.json. 

2, 将 bw.json 导入你自建的 vw. 

3,  将你自建的 vw 也导出一个 vw.json

4, 把 vaultwarden 的 docker / 主程序 停止, 启动, 再停止, 以保证 sqlite wal 全部写入数据库 db.sqlite3 文件.  

5, 可以把 db.sqlite3 文件下载到本地, 用 navicat 或者 dbeaver 等工具打开, 这时你看到 vw 的 sqlite 数据中,  created_at updated_at 全部变成了清一色导入的时间, 奇葩行为..

5, 使用 ai 生成代码, 寻找两个 json 中 id 的对应关系, id 是不同的, 如何一一对应呢?
我使用的是 name + username 的组合对应关系. 除此之外, 当没有 username 的时候(银行卡类型), 用 name + notes 来组合, 以防止重复的情况. 

也就是: 

两个不同的数据库 json 导出文件内容相同, id 不同, bw.json 的两个时间 creationDate 和 revisionDate 是对的, vw.json 中的两个时间是错的. 通过 name+username (没有 username 就用 name+notes) 来找到两个 json 的 "id" 对应关系. 

随后根据这个 id 的对应关系, 将 bw.json 中的 creationDate 和 revisionDate 读取出来, 写到数据库对应 id 中的 created_at 和 updated_at 中去. 

6, 我使用 ChatGPT 生成的代码如下: 

<?php

$bwFile = 'bw.json';
$vwFile = 'vw.json';
$dbFile = 'db.sqlite3';

$bwJson = json_decode(file_get_contents($bwFile), true);
$vwJson = json_decode(file_get_contents($vwFile), true);

if (!$bwJson || !$vwJson) {
    die("JSON 解析失败\n");
}

$bwItems = $bwJson['items'] ?? [];
$vwItems = $vwJson['items'] ?? [];

/**
 * key = name + username/notes
 */
function buildKey($item) {
    $name = $item['name'] ?? '';

    $username = $item['login']['username'] ?? null;
    $notes = $item['notes'] ?? '';

    $second = $username;
    if ($second === null || $second === '') {
        $second = $notes;
    }

    return $name . '|' . $second;
}

/**
 * SQLite connect
 */
$pdo = new PDO("sqlite:" . $dbFile);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

/**
 * =======================
 * 1. BW index
 * =======================
 */
$bwMap = [];

foreach ($bwItems as $item) {
    if (!isset($item['name'])) continue;

    $key = buildKey($item);

    $bwMap[$key] = [
        'creationDate' => $item['creationDate'] ?? null,
        'revisionDate' => $item['revisionDate'] ?? null,
    ];
}

/**
 * =======================
 * 2. Update DB
 * =======================
 */
$updateStmt = $pdo->prepare("
    UPDATE ciphers
    SET created_at = :created_at,
        updated_at = :updated_at
    WHERE uuid = :uuid
");

$updated = 0;
$skipped = 0;
$skippedList = [];

foreach ($vwItems as $item) {

    if (!isset($item['name'], $item['id'])) {
        $skipped++;
        $skippedList[] = [
            'reason' => 'missing name or id',
            'item' => $item
        ];
        continue;
    }

    $key = buildKey($item);

    if (!isset($bwMap[$key])) {
        $skipped++;
        $skippedList[] = [
            'reason' => 'bw not found',
            'id' => $item['id'],
            'key' => $key
        ];
        continue;
    }

    $bw = $bwMap[$key];

    if (empty($bw['creationDate']) || empty($bw['revisionDate'])) {
        $skipped++;
        $skippedList[] = [
            'reason' => 'missing bw dates',
            'id' => $item['id'],
            'key' => $key,
            'bw' => $bw
        ];
        continue;
    }

    try {
        $updateStmt->execute([
            ':created_at' => $bw['creationDate'],
            ':updated_at' => $bw['revisionDate'],
            ':uuid' => $item['id'],
        ]);

        $updated++;

    } catch (Exception $e) {
        $skipped++;
        $skippedList[] = [
            'reason' => 'sql error',
            'id' => $item['id'],
            'error' => $e->getMessage()
        ];
    }
}

/**
 * =======================
 * 3. Output
 * =======================
 */
echo "UPDATED: {$updated}\n";
echo "SKIPPED: {$skipped}\n";

echo "\n=== SKIPPED DETAILS ===\n";

if (empty($skippedList)) {
    echo "NONE\n";
} else {
    foreach ($skippedList as $s) {
        echo json_encode($s, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
    }


运行代码之前, 要把 php.ini 中的两个插件启用, 也就是把这两行前面的分号去掉, 然后重启 php: 

extension=pdo_sqlite
extension=sqlite3 

如果你的 extensions 里面甚至都没有这两个 dll, 那么改 php.ini 也没有用了, 推荐使用类似 wamp 的一键端, 它有很多名字: uniform server, unicontroller, miniserver, 都是同一个软件. 内含 apache mysql php, 只有 php 就行. 链接: https://sourceforge.net/projects/miniserver/


最终输出如下: 

UPDATED: 成功的数量 SKIPPED: 1 === SKIPPED DETAILS === { "reason": "bw not found", "id": "这里是uuid序号", "key": "这里是json里的name字段|这里是json里的username字段" }  

没找到就说明 bw 里面没有, 这是你自建了以后新增的记录. 

再去看看数据库里面的 created_at 和 updated_at 已经写好了, 但是格式不对, 简单替换下, sql 执行: 

UPDATE ciphers
SET
  created_at = REPLACE(REPLACE(created_at, 'T', ' '), 'Z', ''),
  updated_at = REPLACE(REPLACE(updated_at, 'T', ' '), 'Z', '');

 

0 条评论: