halo网站利用api批量导入用户

🌾

📦 最终完整脚本(已调试通过)

将以下代码保存为 ImpHalo.py,并按后续步骤配置后运行。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests
import csv
import os
import sys
import time

# ==================== 配置区 ====================
HALO_URL = "你的网站域名"          # 你的 Halo 站点地址 (结尾无 /)
API_TOKEN = "pat_xxxxxxxxxxxxxxxxxxxx"        # 个人令牌
DEFAULT_EMAIL_DOMAIN = "default.com"          # 邮箱为空时自动补全的域名
# ================================================

API_URL = f"{HALO_URL}/apis/api.console.halo.run/v1alpha1/users"
HEADERS = {
    "Authorization": f"Bearer {API_TOKEN}",
    "Content-Type": "application/json"
}

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CSV_PATH = os.path.join(SCRIPT_DIR, "users.csv")


def load_users():
    if not os.path.exists(CSV_PATH):
        print(f"❌ 找不到文件: {CSV_PATH}")
        sys.exit(1)

    users = []
    with open(CSV_PATH, "r", encoding="utf-8-sig", errors="ignore") as f:
        reader = csv.DictReader(f)

        # 检查必填列头
        if "username" not in reader.fieldnames or "password" not in reader.fieldnames:
            print("❌ CSV 必须包含 'username' 和 'password' 列")
            sys.exit(1)

        for idx, row in enumerate(reader, start=2):
            username = row.get("username", "").strip()
            if not username:
                print(f"⚠️  第 {idx} 行 username 为空,已跳过")
                continue

            password = row.get("password", "").strip()
            if not password:
                print(f"⚠️  第 {idx} 行 username={username} password 为空,已跳过")
                continue

            email = row.get("email", "").strip()
            if not email:
                email = f"{username}@{DEFAULT_EMAIL_DOMAIN}"
                print(f"ℹ️  {username} 邮箱为空,已自动生成: {email}")

            display_name = row.get("displayName", "").strip()
            bio = row.get("description", "").strip()      # CSV 列名保持 description,内部映射为 bio
            role_raw = row.get("role", "").strip()
            roles = [role_raw] if role_raw else []

            user = {
                "name": username,           # API 要求显式 name 字段
                "username": username,
                "displayName": display_name,
                "email": email,
                "password": password,
                "bio": bio,                 # Halo 用户简介字段为 bio
                "roles": roles
            }
            users.append(user)

    print(f"📄 已处理 {len(users)} 条有效用户数据\n")
    return users


def create_user(user):
    """发送创建请求,返回 (是否成功, 错误信息)"""
    try:
        resp = requests.post(API_URL, json=user, headers=HEADERS, timeout=30)

        # 200 和 201 都视为成功
        if resp.status_code in (200, 201):
            return True, ""
        # 409 表示冲突(用户已存在)
        elif resp.status_code == 409:
            return True, "already exists"
        else:
            error_msg = resp.text.strip() or f"状态码 {resp.status_code}"
            try:
                err = resp.json()
                if "message" in err:
                    error_msg = err["message"]
                elif "detail" in err:
                    error_msg = err["detail"]
            except:
                pass
            return False, error_msg
    except requests.exceptions.RequestException as e:
        return False, str(e)


def main():
    print(f"🚀 开始导入用户到 {HALO_URL} ...\n")
    users = load_users()
    if not users:
        sys.exit(0)

    success = 0
    exists = 0
    fail = 0
    total = len(users)

    for i, user in enumerate(users, start=1):
        username = user["username"]
        print(f"[{i}/{total}] 正在导入: {username} ... ", end="")
        ok, error = create_user(user)

        if ok and error == "already exists":
            print("ℹ️ 已存在,跳过")
            exists += 1
        elif ok:
            print("✅ 成功")
            success += 1
        else:
            print(f"❌ 失败 | 原因: {error[:80]}")
            fail += 1

        time.sleep(0.3)  # 避免请求过快,可自行调整

    print("\n" + "=" * 50)
    print(f"🎉 导入完成!新增成功: {success} 人,已存在: {exists} 人,失败: {fail} 人")


if __name__ == "__main__":
    main()

📋 标准操作流程

1. 准备用户数据(CSV 文件)

创建一个 users.csv 文件,必须使用 UTF-8 编码(用 VS Code 或记事本另存为 UTF-8)。
列头严格如下:

username,displayName,email,password,description,role

示例数据:

210102071009,刘辉,stu@ycit.cn,Abc12345,期中成绩:24,
220102021022,李四,lisi@example.com,Pass5678,备注信息,role-template-viewer

字段说明

  • username(必填) – 登录用户名/学号
  • displayName(可选) – 显示名称(建议填写真实姓名,否则后台显示 username)
  • email(可选,留空则自动生成 username@default.com
  • password(必填) – 初始密码
  • description(可选) – 个人简介(对应 Halo 中的 bio 字段)
  • role(可选) – 角色名,如 super-adminrole-template-editor 等,留空则不分配角色

⚠️ 编码务必为 UTF-8,否则中文姓名会乱码。

2. 获取 Halo 个人令牌

  1. 登录 Halo 后台。
  2. 点击右上角头像 → 个人中心个人令牌
  3. 点击 创建令牌,填写名称,权限选择 全部,生成后复制 Token 值(格式为 pat_...)。

3. 配置脚本

用文本编辑器打开 ImpHalo.py,修改顶部两行:

HALO_URL = "https://你的域名"
API_TOKEN = "pat_你的令牌"

4. 运行脚本

在脚本所在目录打开终端,执行:

pip install requests   # 若未安装
python3 ImpHalo.py

脚本会逐行显示导入结果:

  • ✅ 成功 – 新增用户
  • ℹ️ 已存在 – 用户名已存在,自动跳过
  • ❌ 失败 – 显示具体错误原因

5. 验证结果

登录 Halo 后台 → 用户管理,检查:

  • 用户列表是否显示正常的中文姓名(而非学号或乱码)
  • 点击用户查看详情,个人简介是否正确填入

🧩 关键问题与解决方案回顾

错误现象原因解决方法
404 No static resourceAPI 分组错误,使用了公开 API改为控制台 API:/apis/api.console.halo.run/v1alpha1/users
Name is required请求体缺少 name 字段添加 "name": username
角色导入失败字段应为 roles 数组,不是 role 字符串改为 "roles": [角色名] 或空数组
失败但用户已创建状态码为 200 被误判;重复导入导致 JSON 解析失败同时接受 200/201 为成功,单独处理 409
姓名显示为学号或乱码CSV 非 UTF-8 编码;displayName 为空保存为 UTF-8 编码,确保 displayName 列有汉字
个人简介为空Halo 字段名为 bio 不是 description请求体中改用 "bio": 内容

📌 重要注意事项

  • 密码强度:Halo 默认要求密码至少 6 位,不能为纯数字等弱密码。
  • 邮箱唯一性:多个用户使用同一邮箱可能导致后续操作异常,建议使用唯一邮箱。
  • 角色名称:必须与后台已有的角色名完全一致,否则创建失败。
  • 请求频率:脚本已加入 0.3 秒延时,可根据服务器情况调整。

按照上述流程,即可一次性完成 Halo 用户的批量导入,且所有字段正常显示。如有特殊需求(如批量更新、使用 Excel 自动填充等),可在本流程基础上扩展。


python代码解决macos打开word乱码问题 2026-05-21

评论区