参考:Dify 社区版手动开启多租户并批量导入用户实战指南_dify 多租户-CSDN博客

我对此做了些改造,dify初次启动后pg数据库的格式并不是原博主说的那样

而是

Dify 的 accounts.id 字段是 UUID 类型,但你的 Excel 中的 WORKCODE 列是字符串,不是合法的 UUID 格式

要确认 Dify 数据库中的 accounts 表是否包含 custom_id 字段,你可以通过以下任一方法操作:

psql -h localhost -p 5432 -U postgres -d dify

查看 accounts 表结构

\d accounts

Table "public.accounts"
       Column       |            Type             | ...
--------------------+-----------------------------+-----
 id                 | uuid                        | ...
 name               | character varying(255)      | ...
 email              | character varying(255)      | ...
 ...(其他字段)...
放弃用 WORKCODE 作为系统 ID,改用 UUID(推荐)

accounts.id 必须是 UUID(由数据库或代码生成)

  • 不再使用 WORKCODE
  • Excel 列最少化
  • 所有用户密码统一设为 CnAI@2025
  • accounts.id 使用 UUID(合法)
  • 保留 nameemail

修改后 Excel 所需列(仅需 2 列):

张三

zhangsan@example.com

李四

lisi@example.com

修改后的代码:

import pandas as pd
import psycopg2
from psycopg2 import sql
import uuid
from datetime import datetime
import secrets
import hashlib
import binascii
import base64
import os
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# --- 配置 ---
DB_CONFIG = {
    'dbname': 'dify',
    'user': 'postgres',
    'password': 'difyai123456',
    'host': 'localhost',
    'port': '5432'
}
XLS_FILE_PATH = r'E:\dify\dify相关\users.xls'
OUTPUT_KEYS_DIR = 'private_keys_to_upload'

# 固定密码
FIXED_PASSWORD = "CnAI@2025"

# --- Dify 密码加密函数 ---
def generate_dify_password_hash(plain_password: str):
    salt_bytes = secrets.token_bytes(16)
    dk_bytes = hashlib.pbkdf2_hmac("sha256", plain_password.encode("utf-8"), salt_bytes, 10000)
    hex_hash = binascii.hexlify(dk_bytes)
    final_hash = base64.b64encode(hex_hash).decode('utf-8')
    final_salt = base64.b64encode(salt_bytes).decode('utf-8')
    return final_hash, final_salt

# --- 密钥对生成 ---
def generate_key_pair():
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    public_pem = private_key.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )
    return public_pem.decode('utf-8'), private_pem

# --- 主函数 ---
def main():
    conn = None
    successful = 0
    failed = 0
    passwords = {}

    os.makedirs(OUTPUT_KEYS_DIR, exist_ok=True)

    try:
        print(f"[*] 读取 Excel: {XLS_FILE_PATH}")
        df = pd.read_excel(XLS_FILE_PATH)
        print(f"[+] 成功读取 {len(df)} 条记录。")

        conn = psycopg2.connect(**DB_CONFIG)
        cur = conn.cursor()
        print("[+] 数据库连接成功!\n--- 开始导入 ---")

        for idx, row in df.iterrows():
            name = str(row['LOGINID']).strip()
            email = str(row['EMAIL']).strip()

            if not name or not email:
                print(f"[!] 跳过第 {idx + 2} 行:LOGINID 或 EMAIL 为空")
                failed += 1
                continue

            # 检查邮箱是否已存在
            cur.execute("SELECT 1 FROM accounts WHERE email = %s;", (email,))
            if cur.fetchone():
                print(f"[!] 邮箱已存在,跳过: {email}")
                failed += 1
                continue

            try:
                # 使用固定密码
                hashed_password, salt = generate_dify_password_hash(FIXED_PASSWORD)
                public_key, private_key = generate_key_pair()
                account_id = str(uuid.uuid4())
                tenant_id = str(uuid.uuid4())
                workspace_name = f"{name}'s Workspace"
                ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

                # 插入 accounts
                cur.execute(
                    sql.SQL("INSERT INTO accounts (id, name, email, password, password_salt, status, created_at, updated_at, timezone) VALUES (%s, %s, %s, %s, %s, 'active', %s, %s, 'Asia/Shanghai')"),
                    (account_id, name, email, hashed_password, salt, ts, ts)
                )

                # 插入 tenants
                cur.execute(
                    sql.SQL("INSERT INTO tenants (id, name, plan, status, created_at, updated_at, encrypt_public_key) VALUES (%s, %s, 'basic', 'normal', %s, %s, %s)"),
                    (tenant_id, workspace_name, ts, ts, public_key)
                )

                # 插入关联
                cur.execute(
                    sql.SQL("INSERT INTO tenant_account_joins (tenant_id, account_id, role, current) VALUES (%s, %s, 'owner', TRUE)"),
                    (tenant_id, account_id)
                )

                # 保存私钥
                key_dir = os.path.join(OUTPUT_KEYS_DIR, tenant_id)
                os.makedirs(key_dir)
                with open(os.path.join(key_dir, 'private.pem'), 'wb') as f:
                    f.write(private_key)

                conn.commit()
                successful += 1
                passwords[email] = FIXED_PASSWORD
                print(f"  -> 成功: {name} ({email})")

            except Exception as e:
                conn.rollback()
                print(f"[!] 导入失败 {name} ({email}): {e}")
                failed += 1

        print("\n--- 完成 ---")

    except Exception as e:
        print(f"\n[!] 致命错误: {e}")
        if conn:
            conn.rollback()
    finally:
        if conn:
            cur.close()
            conn.close()
            print("\n[*] 数据库连接已关闭。")

    # 输出结果
    print(f"\n成功: {successful} | 失败/跳过: {failed}")
    if passwords:
        print("\n============ 用户密码清单 ============")
        print("所有用户密码均为: CnAI@2025")
        print("-" * 50)
        for email in passwords:
            print(email)
        print("-" * 50)

if __name__ == "__main__":
    print("批量导入用户到 Dify(固定密码 CnAI@2025)")
    input("按 Enter 开始...")
    main()

Dify 默认的 PostgreSQL 密码在 .env 文件中,通常是:

POSTGRES_PASSWORD=difyai123456

所以你的 DB_CONFIG 应为:

DB_CONFIG = {

'dbname': 'dify',

'user': 'postgres',

'password': 'difyai123456', # ⚠️ 不是 'postgres'

'host': 'localhost',

'port': '5432'

}

请检查 E:\dify\dify\docker\.env 中的 POSTGRES_PASSWORD 值。

另外有博主提出
创建的空间,访问知识库报错呢Application error: a client-side exception has occurred while loading xx.xx.xx.xx (see the browser console for more information). 创建工作流也报错;Internal Server Error
上述是因为时区为空导致的,你可以登录账号以后,先去设置时区就可以避免这个报错,或者在创建账号的时候,带上时区字段,把字段设置为'Asia/Shanghai'。具体代码路径:
# 1. 插入到 accounts 表
account_sql = sql.SQL("""
INSERT INTO accounts (
id, name, email, password, password_salt, status, created_at, updated_at, timezone
) VALUES (%s, %s, %s, %s, %s, 'active', %s, %s, 'Asia/Shanghai');
""")添加时区字段。
第二个问题内部服务报错是没有设置默认语言,同理可以在数据库建账号的时候修改,或者在数据库里面修改:ALTER TABLE sites ALTER COLUMN default_language DROP NOT NULL;
其实反思回去,我们在社区版注册时,也是需要设置时区和语言的,新建账号这2个字段为空,所以报错了。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐