用户管理增加用户名搜索

This commit is contained in:
mtvpls
2026-05-15 12:06:34 +08:00
parent ae3a054e56
commit 09fc63c116
6 changed files with 268 additions and 80 deletions
+45 -17
View File
@@ -1306,7 +1306,8 @@ export class D1Storage implements IStorage {
async getUserListV2(
offset = 0,
limit = 20,
ownerUsername?: string
ownerUsername?: string,
search?: string
): Promise<{
users: Array<{
username: string;
@@ -1320,10 +1321,16 @@ export class D1Storage implements IStorage {
total: number;
}> {
try {
const trimmedSearch = search?.trim() || '';
const searchPattern = `%${trimmedSearch}%`;
// 获取总数
const countResult = await this.db
.prepare('SELECT COUNT(*) as total FROM users')
.first();
const countQuery = trimmedSearch
? this.db
.prepare('SELECT COUNT(*) as total FROM users WHERE username LIKE ?')
.bind(searchPattern)
: this.db.prepare('SELECT COUNT(*) as total FROM users');
const countResult = await countQuery.first();
let total = (countResult?.total as number) || 0;
// 检查站长是否在数据库中
@@ -1333,8 +1340,11 @@ export class D1Storage implements IStorage {
ownerInfo = await this.getUserInfoV2(ownerUsername);
ownerInDatabase = !!ownerInfo && ownerInfo.created_at !== 0;
// 如果站长不在数据库中,总数+1
if (!ownerInDatabase) {
// 如果站长不在数据库中且匹配搜索条件,总数+1
if (
!ownerInDatabase &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
total += 1;
}
}
@@ -1343,7 +1353,11 @@ export class D1Storage implements IStorage {
let actualOffset = offset;
let actualLimit = limit;
if (ownerUsername && !ownerInDatabase) {
if (
ownerUsername &&
!ownerInDatabase &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
if (offset === 0) {
// 第一页:只获取 limit-1 个用户,为站长留出位置
actualLimit = limit - 1;
@@ -1354,20 +1368,34 @@ export class D1Storage implements IStorage {
}
// 获取用户列表(按创建时间降序)
const result = await this.db
.prepare(`
SELECT username, role, banned, tags, oidc_sub, enabled_apis, created_at
FROM users
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`)
.bind(actualLimit, actualOffset)
.all();
const listQuery = trimmedSearch
? this.db
.prepare(`
SELECT username, role, banned, tags, oidc_sub, enabled_apis, created_at
FROM users
WHERE username LIKE ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`)
.bind(searchPattern, actualLimit, actualOffset)
: this.db
.prepare(`
SELECT username, role, banned, tags, oidc_sub, enabled_apis, created_at
FROM users
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`)
.bind(actualLimit, actualOffset);
const result = await listQuery.all();
const users = [];
// 如果有站长且在第一页,确保站长始终在第一位
if (ownerUsername && offset === 0) {
if (
ownerUsername &&
offset === 0 &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
users.push({
username: ownerUsername,
role: 'owner' as const,
+3 -2
View File
@@ -555,7 +555,8 @@ export class DbManager {
async getUserListV2(
offset = 0,
limit = 20,
ownerUsername?: string
ownerUsername?: string,
search?: string
): Promise<{
users: Array<{
username: string;
@@ -569,7 +570,7 @@ export class DbManager {
total: number;
}> {
if (typeof (this.storage as any).getUserListV2 === 'function') {
return (this.storage as any).getUserListV2(offset, limit, ownerUsername);
return (this.storage as any).getUserListV2(offset, limit, ownerUsername, search);
}
return { users: [], total: 0 };
}
+45 -17
View File
@@ -563,7 +563,8 @@ export class PostgresStorage implements IStorage {
async getUserListV2(
offset = 0,
limit = 20,
ownerUsername?: string
ownerUsername?: string,
search?: string
): Promise<{
users: Array<{
username: string;
@@ -577,10 +578,16 @@ export class PostgresStorage implements IStorage {
total: number;
}> {
try {
const trimmedSearch = search?.trim() || '';
const searchPattern = `%${trimmedSearch}%`;
// 获取总数
const countResult = await this.db
.prepare('SELECT COUNT(*) as total FROM users')
.first();
const countQuery = trimmedSearch
? this.db
.prepare('SELECT COUNT(*) as total FROM users WHERE username LIKE $1')
.bind(searchPattern)
: this.db.prepare('SELECT COUNT(*) as total FROM users');
const countResult = await countQuery.first();
let total = (countResult?.total as number) || 0;
// 检查站长是否在数据库中
@@ -590,8 +597,11 @@ export class PostgresStorage implements IStorage {
ownerInfo = await this.getUserInfoV2(ownerUsername);
ownerInDatabase = !!ownerInfo && ownerInfo.created_at !== 0;
// 如果站长不在数据库中,总数+1
if (!ownerInDatabase) {
// 如果站长不在数据库中且匹配搜索条件,总数+1
if (
!ownerInDatabase &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
total += 1;
}
}
@@ -600,7 +610,11 @@ export class PostgresStorage implements IStorage {
let actualOffset = offset;
let actualLimit = limit;
if (ownerUsername && !ownerInDatabase) {
if (
ownerUsername &&
!ownerInDatabase &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
if (offset === 0) {
// 第一页:只获取 limit-1 个用户,为站长留出位置
actualLimit = limit - 1;
@@ -611,20 +625,34 @@ export class PostgresStorage implements IStorage {
}
// 获取用户列表(按创建时间降序)
const result = await this.db
.prepare(`
SELECT username, role, banned, tags, oidc_sub, enabled_apis, created_at
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`)
.bind(actualLimit, actualOffset)
.all();
const listQuery = trimmedSearch
? this.db
.prepare(`
SELECT username, role, banned, tags, oidc_sub, enabled_apis, created_at
FROM users
WHERE username LIKE $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`)
.bind(searchPattern, actualLimit, actualOffset)
: this.db
.prepare(`
SELECT username, role, banned, tags, oidc_sub, enabled_apis, created_at
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`)
.bind(actualLimit, actualOffset);
const result = await listQuery.all();
const users = [];
// 如果有站长且在第一页,确保站长始终在第一位
if (ownerUsername && offset === 0) {
if (
ownerUsername &&
offset === 0 &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
users.push({
username: ownerUsername,
role: 'owner' as const,
+41 -7
View File
@@ -1347,7 +1347,8 @@ export abstract class BaseRedisStorage implements IStorage {
async getUserListV2(
offset = 0,
limit = 20,
ownerUsername?: string
ownerUsername?: string,
search?: string
): Promise<{
users: Array<{
username: string;
@@ -1360,6 +1361,8 @@ export abstract class BaseRedisStorage implements IStorage {
}>;
total: number;
}> {
const trimmedSearch = search?.trim() || '';
// 获取总数
let total = await this.withRetry(() => this.adapter.zCard(this.userListKey()));
@@ -1386,8 +1389,11 @@ export abstract class BaseRedisStorage implements IStorage {
ownerExistenceCache.set(ownerUsername, ownerInDatabase);
}
// 如果站长不在数据库中,总数+1(无论在哪一页都要加)
if (!ownerInDatabase) {
// 如果站长不在数据库中且匹配搜索条件,总数+1(无论在哪一页都要加)
if (
!ownerInDatabase &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
total += 1;
}
}
@@ -1396,7 +1402,11 @@ export abstract class BaseRedisStorage implements IStorage {
let actualOffset = offset;
let actualLimit = limit;
if (ownerUsername && !ownerInDatabase) {
if (
ownerUsername &&
!ownerInDatabase &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
if (offset === 0) {
// 第一页:只获取 limit-1 个用户,为站长留出位置
actualLimit = limit - 1;
@@ -1407,14 +1417,38 @@ export abstract class BaseRedisStorage implements IStorage {
}
// 获取用户列表(按注册时间升序)
const usernames = await this.withRetry(() =>
this.adapter.zRange(this.userListKey(), actualOffset, actualOffset + actualLimit - 1)
let usernames = await this.withRetry(() =>
trimmedSearch
? this.adapter.zRange(this.userListKey(), 0, -1)
: this.adapter.zRange(
this.userListKey(),
actualOffset,
actualOffset + actualLimit - 1
)
);
if (trimmedSearch) {
usernames = usernames.filter((username) =>
ensureString(username).includes(trimmedSearch)
);
total = usernames.length;
if (
ownerUsername &&
!ownerInDatabase &&
ownerUsername.includes(trimmedSearch)
) {
total += 1;
}
usernames = usernames.slice(actualOffset, actualOffset + actualLimit);
}
const users = [];
// 如果有站长且在第一页,确保站长始终在第一位
if (ownerUsername && offset === 0) {
if (
ownerUsername &&
offset === 0 &&
(!trimmedSearch || ownerUsername.includes(trimmedSearch))
) {
// 即使站长不在数据库中,也要添加站长(站长使用环境变量认证)
users.push({
username: ownerUsername,