This commit is contained in:
2026-08-05 15:56:08 +08:00
parent 01729b1e0b
commit 2d9e2376b6
106 changed files with 10007 additions and 253 deletions
+185
View File
@@ -5,6 +5,7 @@ namespace app\controller\api;
use app\model\AiModel;
use app\model\Conversation as ConversationModel;
use app\model\Department;
use app\model\InvitationCode;
use app\model\MembershipLevel;
use app\model\Message;
use app\model\Role;
@@ -155,6 +156,7 @@ class Admin extends BaseApi
->leftJoin('roles r', 'u.role_id = r.id')
->leftJoin('departments d', 'u.department_id = d.id')
->field('u.id,u.username,u.email,u.nickname,u.role,u.role_id,u.department_id,u.status,u.membership_level_id,u.created_at,u.last_login_at,ml.name as membership_name,r.name as role_name,r.slug as role_slug,d.name as department_name')
->whereRaw('LEFT(u.username, 6) <> ?', ['guest_'])
->order('u.id', 'desc');
AdminScopeService::applyUserScope($query, $this->authUser(), 'u');
@@ -183,6 +185,9 @@ class Admin extends BaseApi
if (strlen($username) < 3 || strlen($username) > 50) {
return $this->error('用户名长度需 3-50 个字符');
}
if (str_starts_with(strtolower($username), 'guest_')) {
return $this->error('guest_ 为系统访客账号保留前缀');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->error('邮箱格式不正确');
}
@@ -249,6 +254,9 @@ class Admin extends BaseApi
if (!$user) {
return $this->error('用户不存在', 404);
}
if (str_starts_with($user->username, 'guest_')) {
return $this->error('访客请在访客管理中操作', 422);
}
$roleSlug = Role::where('id', $user->role_id)->value('slug');
if ($roleSlug === 'super_admin') {
@@ -322,6 +330,9 @@ class Admin extends BaseApi
if (!$user) {
return $this->error('用户不存在', 404);
}
if (str_starts_with($user->username, 'guest_')) {
return $this->error('访客请在访客管理中操作', 422);
}
User::where('id', $targetId)->update($data);
@@ -332,6 +343,77 @@ class Admin extends BaseApi
return $this->success(null, '更新成功');
}
public function guests()
{
$auth = $this->authUser();
AdminScopeService::requireAny($auth, ['menu:guests']);
$page = max(1, (int) $this->request->get('page', 1));
$limit = min(50, max(1, (int) $this->request->get('limit', 20)));
$status = trim((string) $this->request->get('status', ''));
$keyword = trim((string) $this->request->get('keyword', ''));
$query = User::alias('u')
->whereRaw('LEFT(u.username, 6) = ?', ['guest_'])
->field("u.id,u.username,u.nickname,u.status,u.created_at,u.last_login_at,
(SELECT COUNT(*) FROM conversations c WHERE c.user_id = u.id AND c.deleted_at IS NULL) AS conversation_count,
(SELECT COUNT(*) FROM messages msg INNER JOIN conversations c2 ON msg.conversation_id = c2.id WHERE c2.user_id = u.id) AS message_count")
->order('u.last_login_at', 'desc')
->order('u.id', 'desc');
AdminScopeService::applyUserScope($query, $auth, 'u');
if (in_array($status, ['active', 'disabled'], true)) {
$query->where('u.status', $status);
}
if ($keyword !== '') {
$query->where('u.username', 'like', '%' . $keyword . '%');
}
$total = (clone $query)->count();
$list = $query->page($page, $limit)->select();
return $this->success(['list' => $list, 'total' => $total]);
}
public function updateGuestStatus($id)
{
$auth = $this->authUser();
AdminScopeService::requireAny($auth, ['btn:guest:status']);
$guest = User::find((int) $id);
if (!$guest || !str_starts_with($guest->username, 'guest_')) {
return $this->error('访客不存在', 404);
}
if (!AdminScopeService::canViewUser($auth, (int) $guest->id)) {
return $this->error('无权操作该访客', 403);
}
$status = trim((string) $this->request->put('status', ''));
if (!in_array($status, ['active', 'disabled'], true)) {
return $this->error('访客状态无效', 422);
}
$guest->save(['status' => $status]);
return $this->success(null, $status === 'active' ? '访客已启用' : '访客已禁用');
}
public function deleteGuest($id)
{
$auth = $this->authUser();
AdminScopeService::requireAny($auth, ['btn:guest:delete']);
$guest = User::find((int) $id);
if (!$guest || !str_starts_with($guest->username, 'guest_')) {
return $this->error('访客不存在', 404);
}
if (!AdminScopeService::canViewUser($auth, (int) $guest->id)) {
return $this->error('无权操作该访客', 403);
}
User::destroy((int) $guest->id);
return $this->success(null, '访客记录已删除');
}
public function conversations()
{
AdminScopeService::requireAny($this->authUser(), [
@@ -557,6 +639,109 @@ class Admin extends BaseApi
return $this->success($options);
}
public function invitations()
{
AdminScopeService::requireAny($this->authUser(), [
'menu:invitations',
'btn:invitation:create',
'btn:invitation:revoke',
'can_manage_users',
]);
$status = trim((string) $this->request->get('status', ''));
$query = InvitationCode::alias('i')
->leftJoin('departments d', 'i.department_id = d.id')
->leftJoin('users creator', 'i.created_by = creator.id')
->leftJoin('users used', 'i.used_by = used.id')
->field('i.*,d.name as department_name,creator.username as creator_name,used.username as used_by_name')
->order('i.created_at', 'desc')
->order('i.id', 'desc');
if (in_array($status, ['active', 'used', 'revoked'], true)) {
$query->where('i.status', $status);
}
return $this->success($query->limit(500)->select());
}
public function createInvitation()
{
$user = $this->authUser();
AdminScopeService::requireAny($user, [
'btn:invitation:create',
'menu:invitations',
'can_manage_users',
]);
$input = $this->request->post();
$departmentId = $input['department_id'] ?? null;
if ($departmentId !== null && $departmentId !== '') {
$departmentId = (int) $departmentId;
if ($departmentId <= 0 || !Department::find($departmentId)) {
return $this->error('所选部门不存在', 422);
}
} else {
$departmentId = null;
}
$expiresAt = trim((string) ($input['expires_at'] ?? ''));
if ($expiresAt !== '') {
$expiresAt = str_replace('T', ' ', $expiresAt);
$expiresTimestamp = strtotime($expiresAt);
if (!$expiresTimestamp || $expiresTimestamp <= time()) {
return $this->error('过期时间必须晚于当前时间', 422);
}
$expiresAt = date('Y-m-d H:i:s', $expiresTimestamp);
} else {
$expiresAt = null;
}
do {
$code = 'INV-' . strtoupper(bin2hex(random_bytes(5)));
} while (InvitationCode::where('code', $code)->find());
$invitation = InvitationCode::create([
'code' => $code,
'department_id' => $departmentId,
'created_by' => (int) $user['id'],
'status' => 'active',
'expires_at' => $expiresAt,
]);
return $this->success([
'id' => (int) $invitation->id,
'code' => $invitation->code,
'department_id' => $departmentId,
'department_name' => $departmentId ? Department::where('id', $departmentId)->value('name') : null,
'status' => 'active',
'expires_at' => $expiresAt,
'created_at' => $invitation->created_at,
], '邀请码已生成');
}
public function revokeInvitation($id)
{
AdminScopeService::requireAny($this->authUser(), [
'btn:invitation:revoke',
'menu:invitations',
'can_manage_users',
]);
$invitation = InvitationCode::find((int) $id);
if (!$invitation) {
return $this->error('邀请码不存在', 404);
}
if ($invitation->status === 'used') {
return $this->error('已使用的邀请码不能作废', 422);
}
if ($invitation->status === 'revoked') {
return $this->success(null, '邀请码已作废');
}
$invitation->save(['status' => 'revoked']);
return $this->success(null, '邀请码已作废');
}
public function createDepartment()
{
AdminScopeService::requireAny($this->authUser(), ['btn:dept:create', 'can_manage_departments']);