Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/zh/modules/user.md
T
2026-10-08 17:50:29 +08:00

8.4 KiB
Raw Blame History

6.1 用户管理

:::tip 概述 用户管理是系统核心模块,UserService 继承 BaseTenantService 实现租户隔离,支持 CRUD、重置密码、Excel 导入导出,并通过钩子完成编码生成、密码加密、角色关联等业务。 :::

模块特点

特性 说明
租户隔离 继承 BaseTenantService,自动按 tenant_id 过滤与填充
数据权限 dataScopeDeptField='dept_id',支持按部门过滤
编码生成 beforeAdd 自动生成用户编码(租户编码-6位序号,如 T0001-000020)
唯一校验 username 全局唯一;mobile 按租户分组唯一(['mobile','tenant_id'])
枚举映射 gender→gender 字典,status→user_status 字典
密码加密 bcrypt 双重加盐,新增/重置时自动加密
角色关联 通过 UserRole 中间表关联,支持多角色(roles 数组)
导入导出 Excel 分批导出 + 表头映射导入(字典/部门/城市反查)

文件清单

文件 说明
app/Http/Controllers/Api/UserController.php 控制器(9 个接口)
app/Services/UserService.php 业务服务(含 import/export/resetPassword + 6 个钩子)
app/Models/User.php 模型
app/Models/UserRole.php 用户角色关联模型
app/Http/Requests/UserRequest.php 表单请求验证

API 接口

方法 路径 权限码 说明
GET /api/user/page sys:user:page 分页列表
GET /api/user/detail/{id} sys:user:detail 详情
POST /api/user/add sys:user:add 新增
PUT /api/user/update sys:user:update 修改
PUT /api/user/resetPwd sys:user:resetPwd 重置密码
DELETE /api/user/delete/{id} sys:user:delete 删除
DELETE /api/user/batchDelete sys:user:batchDelete 批量删除
POST /api/user/import sys:user:import 导入
GET /api/user/export sys:user:export 导出

Service 属性配置

// app/Services/UserService.php(来自项目实际代码)

class UserService extends BaseTenantService
{
    // HasDataScope(数据权限 + 租户隔离)由 BaseTenantService 继承提供
    // HasFileProcessing:avatar 头像字段的保存前迁移与读取后补域名
    use HasCrudOperations, HasFileProcessing, HasQueryScopes, HasSerializeMaps, HasUniqueCheck;

    protected string $modelClass = User::class;

    // 单文件字段:保存时迁移临时文件,读取时补全域名
    protected array $fileFields = ['avatar'];

    // 文件保存子目录
    protected string $fileSaveDir = 'user';

    // LIKE 模糊匹配字段
    protected array $pageLikeFields = ['username', 'realname', 'mobile'];

    // 精确匹配字段
    protected array $pageEqFields = ['status', 'dept_id', 'gender', 'tenant_id'];

    // 默认排序:先按租户分组,再按 id 倒序
    protected array $pageOrderBy = [
        ['field' => 'tenant_id', 'type' => 'asc'],
        ['field' => 'id', 'type' => 'desc'],
    ];

    // 唯一性校验:username 全局唯一;mobile 在同一租户内唯一
    protected array $uniqueFields = ['username', ['mobile', 'tenant_id']];

    // 字典翻译
    protected array $serializeMaps = [
        'gender' => 'gender',
        'status' => 'user_status',
    ];

    // 数据权限:按部门过滤
    protected string $dataScopeDeptField = 'dept_id';
}

特殊逻辑

重置密码

// app/Http/Controllers/Api/UserController.php(来自项目实际代码)

public function resetPwd(): JsonResponse
{
    $data = $this->getJsonBody();
    $id = $data['userId'] ?? 0;
    if (empty($id)) {
        return $this->fail('缺少id参数');
    }

    try {
        $this->userService->resetPassword($id);

        return $this->success(null, '密码重置成功');
    } catch (BizException $e) {
        return $this->fail($e->getMessage(), $e->getCode());
    } catch (\Exception $e) {
        logger()->error('操作失败', ['error' => $e->getMessage()]);

        return $this->fail('操作失败');
    }
}

请求参数:

参数 类型 必填 说明
userId int 是 用户ID

:::tip 默认密码与全端下线 重置密码接口无需传递密码参数,系统自动使用默认密码(系统参数 DEFAULT_PASSWORD,默认 123456)加密写入。UserService::resetPassword() 成功后会自动调用 JwtService::revokeAllTokens($id) 吊销该用户全部令牌(见 5.6 密码加密服务)。 :::

新增用户(beforeAdd 钩子)

// app/Services/UserService.php(行为说明)

protected function beforeAdd(array $data): array
{
    // 1. 自动生成用户编码:租户编码 + 租户内序号(如 T0001-000020)
    //    序号 = 当前租户下未删除用户数 + 1
    //    平台用户(tenantId=0)自动代入默认租户生成编码

    // 2. 处理城市字段:city 数组拆为 province_code / city_code /
    //    district_code / street_code 四个编码 + city_info 名称拼接

    // 3. 密码加密:PasswordService::encrypt()(未传密码时用默认密码)
    //    → $data['password'] = 哈希; $data['salt'] = 外部盐

    return $data;
}

:::tip 编码规则

  • 用户编码格式:租户编码-6位序号,如 T0001-000020
  • 序号 = 当前租户下未删除用户数 + 1
  • 编码由系统自动生成,前端不传,修改时不可变更 :::

角色关联(afterAdd / afterUpdate 钩子)

// afterAdd:新增后保存用户角色关联
protected function afterAdd(int $id, array $data): void
{
    $roleIds = $data['roles'] ?? [];
    foreach ($roleIds as $roleId) {
        UserRole::create(['user_id' => $id, 'role_id' => $roleId]);
    }
}

// afterUpdate:先删后插实现全量覆盖

:::info 多角色支持 新增/修改时前端传 roles: [1, 2, 3] ID 数组,afterAdd 遍历写入 UserRole 中间表,afterUpdate 先删除旧关联再重建;详情接口返回 ID 数组供表单回显,分页列表返回 [{id, name}] 对象数组,表格可直接取 name 展示。 :::

详情补全(afterDetail 钩子)

// 详情查询后处理(引用传递)
protected function afterDetail(int $id, array &$data): void
{
    // 补充角色 ID 数组
    $data['roles'] = UserRole::where('user_id', $id)->pluck('role_id');

    // 补充关联名称(部门/职级/岗位显示名)
    $data['deptName'] = ...;
    $data['levelName'] = ...;
    $data['positionName'] = ...;

    // 组装城市编码数组(供前端级联回显)
    $data['city'] = [
        $data['province_code'] ?? '', $data['city_code'] ?? '',
        $data['district_code'] ?? '', $data['street_code'] ?? '',
    ];

    // 移除敏感字段:password / salt 绝不返回前端
    unset($data['password'], $data['salt']);
}

:::warning 敏感字段 password 与 salt 在详情/列表输出时必须剔除——这也是日志安全(操作日志 param 排除 password/token)之外的第二道防线。 :::

列表补全(afterPageList 钩子)

分页列表批量补充角色名、部门/职级/岗位名称与字典翻译(避免 N+1,见 3.5 Service 层开发)。

导出(export)

导出为分批导出(ExcelService),每批 500 条,避免内存溢出。

导出列 字段来源
用户编码 code(系统自动生成)
姓名 realname
性别 genderText(字典自动转换)
手机号 / 邮箱 mobile / email
部门 / 职级 / 岗位 deptName / levelName / positionName
城市 cityInfo(四个城市编码拼接为名称)
地址 address
用户名 username
状态 statusText(字典自动转换)
创建时间 create_time

导入(import)

导入时需处理以下反向映射:

导入列 处理逻辑
性别/状态 非数字时 DictCacheService::getValue 反查
部门/职级/岗位 名称通过名称映射反查 ID
城市 cityInfo 通过城市工具拆为 4 个编码
用户编码 忽略(由 beforeAdd 自动生成)

单条失败不中断整体,失败明细(行号 + 原因)汇总返回。

:::warning 导入不处理 code 导入时即使 Excel 中有「用户编码」列也会被忽略,编码由 beforeAdd 自动生成。 :::