用户管理
本文介绍 CarefreeCMS 的用户管理功能和最佳实践。
用户体系
用户类型
CarefreeCMS 支持多种用户类型:
- 管理员:后台管理权限
- 编辑:内容创作和编辑
- 审核员:内容审核
- 普通用户:前台注册用户
用户字段
// 用户表结构
[
'id' => '用户ID',
'username' => '用户名',
'email' => '邮箱',
'mobile' => '手机号',
'password' => '密码(加密)',
'nickname' => '昵称',
'avatar' => '头像',
'role_id' => '角色ID',
'status' => '状态(normal/disabled)',
'last_login_time' => '最后登录时间',
'last_login_ip' => '最后登录IP',
'create_time' => '创建时间',
'update_time' => '更新时间',
]
用户注册
前台注册
注册页面
<form action="/api/auth/register" method="POST">
<input type="text" name="username" required>
<input type="email" name="email" required>
<input type="password" name="password" required>
<input type="password" name="password_confirm" required>
<button type="submit">注册</button>
</form>
注册逻辑
class AuthController
{
public function register()
{
$data = request()->post();
// 验证
$validate = new UserValidate();
if (!$validate->scene('register')->check($data)) {
return json(['code' => 400, 'msg' => $validate->getError()]);
}
// 检查用户名是否存在
if (User::where('username', $data['username'])->find()) {
return json(['code' => 400, 'msg' => '用户名已存在']);
}
// 创建用户
$user = User::create([
'username' => $data['username'],
'email' => $data['email'],
'password' => password_hash($data['password'], PASSWORD_BCRYPT),
'role_id' => 3, // 普通用户
'status' => 'normal',
]);
// 发送欢迎邮件
Queue::push('app\job\SendEmail', [
'email' => $user->email,
'subject' => '欢迎注册',
'content' => '...',
]);
return json(['code' => 200, 'msg' => '注册成功']);
}
}
邮箱验证
发送验证邮件
public function sendVerifyEmail($userId)
{
$user = User::find($userId);
// 生成验证码
$code = md5($user->id . $user->email . time());
// 保存验证码
Cache::set("verify_email:$userId", $code, 3600);
// 发送邮件
$link = url('auth/verify', ['id' => $userId, 'code' => $code]);
Queue::push('app\job\SendEmail', [
'email' => $user->email,
'subject' => '验证邮箱',
'content' => "请点击链接验证邮箱:$link",
]);
}
public function verifyEmail()
{
$userId = request()->param('id');
$code = request()->param('code');
// 验证码校验
$savedCode = Cache::get("verify_email:$userId");
if ($code !== $savedCode) {
return '验证码无效';
}
// 更新用户状态
User::where('id', $userId)->update(['email_verified' => 1]);
Cache::delete("verify_email:$userId");
return '邮箱验证成功';
}
手机验证
public function sendSmsCode()
{
$mobile = request()->post('mobile');
// 生成验证码
$code = rand(100000, 999999);
// 保存验证码
Cache::set("sms_code:$mobile", $code, 300);
// 发送短信(接入短信服务商)
$sms = new SmsService();
$sms->send($mobile, "您的验证码是:$code");
return json(['code' => 200, 'msg' => '验证码已发送']);
}
public function registerWithMobile()
{
$mobile = request()->post('mobile');
$code = request()->post('code');
// 验证码校验
$savedCode = Cache::get("sms_code:$mobile");
if ($code != $savedCode) {
return json(['code' => 400, 'msg' => '验证码错误']);
}
// 创建用户
$user = User::create([
'mobile' => $mobile,
'username' => $mobile,
'mobile_verified' => 1,
]);
Cache::delete("sms_code:$mobile");
return json(['code' => 200, 'msg' => '注册成功']);
}
用户登录
账号密码登录
public function login()
{
$username = request()->post('username');
$password = request()->post('password');
// 查找用户
$user = User::where('username', $username)
->whereOr('email', $username)
->find();
if (!$user) {
return json(['code' => 400, 'msg' => '用户不存在']);
}
// 验证密码
if (!password_verify($password, $user->password)) {
return json(['code' => 400, 'msg' => '密码错误']);
}
// 检查状态
if ($user->status !== 'normal') {
return json(['code' => 400, 'msg' => '账号已被禁用']);
}
// 更新登录信息
$user->last_login_time = time();
$user->last_login_ip = request()->ip();
$user->save();
// 生成 Token
$token = $this->generateToken($user);
return json([
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user' => $user->hidden(['password']),
],
]);
}
第三方登录
微信登录
public function wechatLogin()
{
$code = request()->param('code');
// 获取 access_token
$response = $this->getWechatAccessToken($code);
$openid = $response['openid'];
// 查找或创建用户
$user = User::where('wechat_openid', $openid)->find();
if (!$user) {
// 获取用户信息
$userInfo = $this->getWechatUserInfo($response['access_token'], $openid);
// 创建用户
$user = User::create([
'wechat_openid' => $openid,
'nickname' => $userInfo['nickname'],
'avatar' => $userInfo['headimgurl'],
'username' => 'wx_' . $openid,
]);
}
// 生成 Token
$token = $this->generateToken($user);
return json([
'code' => 200,
'data' => ['token' => $token],
]);
}
GitHub 登录
public function githubLogin()
{
$code = request()->param('code');
// OAuth 授权
$token = $this->getGithubAccessToken($code);
$userInfo = $this->getGithubUserInfo($token);
// 查找或创建用户
$user = User::where('github_id', $userInfo['id'])->find();
if (!$user) {
$user = User::create([
'github_id' => $userInfo['id'],
'username' => $userInfo['login'],
'nickname' => $userInfo['name'],
'avatar' => $userInfo['avatar_url'],
'email' => $userInfo['email'],
]);
}
$token = $this->generateToken($user);
return json(['code' => 200, 'data' => ['token' => $token]]);
}
记住登录
public function login()
{
// ... 登录逻辑
$remember = request()->post('remember', false);
if ($remember) {
// 生成长期 token
$refreshToken = $this->generateRefreshToken($user);
// 保存到数据库
UserToken::create([
'user_id' => $user->id,
'token' => $refreshToken,
'expire_time' => time() + 30 * 86400, // 30天
]);
return json([
'code' => 200,
'data' => [
'token' => $token,
'refresh_token' => $refreshToken,
],
]);
}
return json(['code' => 200, 'data' => ['token' => $token]]);
}
用户信息管理
查看个人信息
public function profile()
{
$user = request()->user;
return json([
'code' => 200,
'data' => $user->hidden(['password']),
]);
}
修改个人信息
public function updateProfile()
{
$user = request()->user;
$data = request()->post();
// 允许修改的字段
$allowedFields = ['nickname', 'avatar', 'email', 'mobile'];
foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$user->$field = $data[$field];
}
}
$user->save();
return json(['code' => 200, 'msg' => '修改成功']);
}
修改密码
public function changePassword()
{
$user = request()->user;
$oldPassword = request()->post('old_password');
$newPassword = request()->post('new_password');
// 验证旧密码
if (!password_verify($oldPassword, $user->password)) {
return json(['code' => 400, 'msg' => '原密码错误']);
}
// 更新密码
$user->password = password_hash($newPassword, PASSWORD_BCRYPT);
$user->save();
// 清除所有 token
UserToken::where('user_id', $user->id)->delete();
return json(['code' => 200, 'msg' => '密码修改成功,请重新登录']);
}
找回密码
public function forgotPassword()
{
$email = request()->post('email');
$user = User::where('email', $email)->find();
if (!$user) {
return json(['code' => 400, 'msg' => '邮箱不存在']);
}
// 生成重置链接
$token = md5($user->id . $email . time());
Cache::set("reset_password:$token", $user->id, 3600);
// 发送邮件
$link = url('auth/reset-password', ['token' => $token]);
Queue::push('app\job\SendEmail', [
'email' => $email,
'subject' => '重置密码',
'content' => "请点击链接重置密码:$link",
]);
return json(['code' => 200, 'msg' => '重置链接已发送']);
}
public function resetPassword()
{
$token = request()->param('token');
$newPassword = request()->post('password');
$userId = Cache::get("reset_password:$token");
if (!$userId) {
return json(['code' => 400, 'msg' => '链接已失效']);
}
// 更新密码
User::where('id', $userId)->update([
'password' => password_hash($newPassword, PASSWORD_BCRYPT),
]);
Cache::delete("reset_password:$token");
return json(['code' => 200, 'msg' => '密码重置成功']);
}
后台管理
用户列表
public function index()
{
$keyword = request()->param('keyword', '');
$status = request()->param('status', '');
$roleId = request()->param('role_id', '');
$query = User::with('role');
if ($keyword) {
$query->where('username|email|mobile', 'like', "%$keyword%");
}
if ($status) {
$query->where('status', $status);
}
if ($roleId) {
$query->where('role_id', $roleId);
}
$users = $query->order('id', 'desc')->paginate(20);
return view('user/index', ['users' => $users]);
}
添加用户
public function add()
{
if (request()->isPost()) {
$data = request()->post();
$user = User::create([
'username' => $data['username'],
'email' => $data['email'],
'password' => password_hash($data['password'], PASSWORD_BCRYPT),
'role_id' => $data['role_id'],
'status' => $data['status'],
]);
return json(['code' => 200, 'msg' => '添加成功']);
}
$roles = Role::select();
return view('user/add', ['roles' => $roles]);
}
编辑用户
public function edit($id)
{
$user = User::find($id);
if (request()->isPost()) {
$data = request()->post();
$user->username = $data['username'];
$user->email = $data['email'];
$user->role_id = $data['role_id'];
$user->status = $data['status'];
// 如果修改密码
if (!empty($data['password'])) {
$user->password = password_hash($data['password'], PASSWORD_BCRYPT);
}
$user->save();
return json(['code' => 200, 'msg' => '修改成功']);
}
$roles = Role::select();
return view('user/edit', ['user' => $user, 'roles' => $roles]);
}
禁用/启用用户
public function toggleStatus($id)
{
$user = User::find($id);
$user->status = $user->status === 'normal' ? 'disabled' : 'normal';
$user->save();
// 如果禁用,清除 token
if ($user->status === 'disabled') {
UserToken::where('user_id', $id)->delete();
}
return json(['code' => 200, 'msg' => '操作成功']);
}
删除用户
public function delete($id)
{
// 不能删除自己
if ($id == request()->user->id) {
return json(['code' => 400, 'msg' => '不能删除当前用户']);
}
$user = User::find($id);
// 删除关联数据
UserToken::where('user_id', $id)->delete();
Article::where('user_id', $id)->delete();
$user->delete();
return json(['code' => 200, 'msg' => '删除成功']);
}
用户统计
统计数据
public function statistics()
{
$stats = [
'total' => User::count(),
'today' => User::whereTime('create_time', 'today')->count(),
'week' => User::whereTime('create_time', 'week')->count(),
'month' => User::whereTime('create_time', 'month')->count(),
'active' => User::where('status', 'normal')->count(),
'disabled' => User::where('status', 'disabled')->count(),
];
return json(['code' => 200, 'data' => $stats]);
}
增长趋势
public function trend()
{
$days = 30;
$data = [];
for ($i = $days - 1; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
$count = User::whereTime('create_time', $date)->count();
$data[] = [
'date' => $date,
'count' => $count,
];
}
return json(['code' => 200, 'data' => $data]);
}
最佳实践
密码安全
// 强密码策略
public function validatePassword($password)
{
$config = config('app.password');
if (strlen($password) < $config['min_length']) {
throw new Exception('密码长度不足');
}
if ($config['require_uppercase'] && !preg_match('/[A-Z]/', $password)) {
throw new Exception('密码必须包含大写字母');
}
if ($config['require_lowercase'] && !preg_match('/[a-z]/', $password)) {
throw new Exception('密码必须包含小写字母');
}
if ($config['require_numbers'] && !preg_match('/[0-9]/', $password)) {
throw new Exception('密码必须包含数字');
}
if ($config['require_special_chars'] && !preg_match('/[^A-Za-z0-9]/', $password)) {
throw new Exception('密码必须包含特殊字符');
}
}
防止暴力破解
public function login()
{
$username = request()->post('username');
$ip = request()->ip();
// 检查登录次数
$key = "login_attempt:$ip";
$attempts = Cache::get($key, 0);
if ($attempts >= 5) {
return json(['code' => 400, 'msg' => '登录失败次数过多,请30分钟后再试']);
}
// ... 登录逻辑
// 登录失败,记录次数
Cache::set($key, $attempts + 1, 1800);
}
会话管理
// 单设备登录
public function singleLogin($user)
{
// 删除旧 token
UserToken::where('user_id', $user->id)->delete();
// 生成新 token
return $this->generateToken($user);
}
// 多设备登录
public function multipleLogin($user)
{
// 限制最多5个设备
$tokenCount = UserToken::where('user_id', $user->id)->count();
if ($tokenCount >= 5) {
// 删除最旧的 token
UserToken::where('user_id', $user->id)
->order('create_time', 'asc')
->limit(1)
->delete();
}
return $this->generateToken($user);
}
