fix(api/users): ページビューのデータの取得範囲が狭すぎる問題を修正 (MisskeyIO#734)
(cherry picked from commit 46e4b8f8e1a7ba77c12360e62623975e3d0f1151)
This commit is contained in:
parent
c7be019fdd
commit
0ed2b06bc4
|
@ -7,6 +7,7 @@ import { Injectable, Inject } from '@nestjs/common';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { AppLockService } from '@/core/AppLockService.js';
|
import { AppLockService } from '@/core/AppLockService.js';
|
||||||
|
import { addTime, dateUTC, subtractTime } from '@/misc/prelude/time.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import Chart from '../core.js';
|
import Chart from '../core.js';
|
||||||
|
@ -54,10 +55,30 @@ export default class PerUserPvChart extends Chart<typeof schema> { // eslint-dis
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getChartUsers(span: 'hour' | 'day', order: 'ASC' | 'DESC', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{
|
public async getUsersRanking(span: 'hour' | 'day', order: 'ASC' | 'DESC', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{ userId: string; count: number; }[]> {
|
||||||
userId: string;
|
const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate();
|
||||||
count: number;
|
const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never;
|
||||||
}[]> {
|
|
||||||
return await this.getChartPv(span, amount, cursor, limit, offset, order);
|
const lt = dateUTC([y, m, d, h, _m, _s, _ms]);
|
||||||
|
|
||||||
|
const gt =
|
||||||
|
span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
|
||||||
|
span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
|
||||||
|
new Error('not happen') as never;
|
||||||
|
|
||||||
|
const repository =
|
||||||
|
span === 'hour' ? this.repositoryForHour :
|
||||||
|
span === 'day' ? this.repositoryForDay :
|
||||||
|
new Error('not happen') as never;
|
||||||
|
|
||||||
|
// ログ取得
|
||||||
|
return await repository.createQueryBuilder()
|
||||||
|
.select('"group" as "userId", sum("___upv_user" + "___upv_visitor") as "count"')
|
||||||
|
.where('date BETWEEN :gt AND :lt', { gt: Chart.dateToTimestamp(gt), lt: Chart.dateToTimestamp(lt) })
|
||||||
|
.groupBy('"userId"')
|
||||||
|
.orderBy('"count"', order)
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.getRawMany<{ userId: string, count: number }>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,10 +150,8 @@ export default abstract class Chart<T extends Schema> {
|
||||||
// ↓にしたいけどfindOneとかで型エラーになる
|
// ↓にしたいけどfindOneとかで型エラーになる
|
||||||
//private repositoryForHour: MiAndOrmRepository<RawRecord<T>>;
|
//private repositoryForHour: MiAndOrmRepository<RawRecord<T>>;
|
||||||
//private repositoryForDay: MiAndOrmRepository<RawRecord<T>>;
|
//private repositoryForDay: MiAndOrmRepository<RawRecord<T>>;
|
||||||
private repositoryForHour: MiAndOrmRepository<{ id: number; group?: string | null; date: number;}>;
|
protected repositoryForHour: MiAndOrmRepository<{ id: number; group?: string | null; date: number; }>;
|
||||||
private repositoryForDay: MiAndOrmRepository<{ id: number; group?: string | null; date: number;}>;
|
protected repositoryForDay: MiAndOrmRepository<{ id: number; group?: string | null; date: number; }>;
|
||||||
private repositoryUserPvForHour: MiAndOrmRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>;
|
|
||||||
private repositoryUserPvForDay: MiAndOrmRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>;
|
|
||||||
/**
|
/**
|
||||||
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
||||||
*/
|
*/
|
||||||
|
@ -189,11 +187,11 @@ export default abstract class Chart<T extends Schema> {
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static dateToTimestamp(x: Date): number {
|
protected static dateToTimestamp(x: Date): number {
|
||||||
return Math.floor(x.getTime() / 1000);
|
return Math.floor(x.getTime() / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static parseDate(date: Date): [number, number, number, number, number, number, number] {
|
protected static parseDate(date: Date): [number, number, number, number, number, number, number] {
|
||||||
const y = date.getUTCFullYear();
|
const y = date.getUTCFullYear();
|
||||||
const m = date.getUTCMonth();
|
const m = date.getUTCMonth();
|
||||||
const d = date.getUTCDate();
|
const d = date.getUTCDate();
|
||||||
|
@ -205,7 +203,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
return [y, m, d, h, _m, _s, _ms];
|
return [y, m, d, h, _m, _s, _ms];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getCurrentDate() {
|
protected static getCurrentDate() {
|
||||||
return Chart.parseDate(new Date());
|
return Chart.parseDate(new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,8 +279,6 @@ export default abstract class Chart<T extends Schema> {
|
||||||
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
||||||
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour).extend(miRepository);
|
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour).extend(miRepository);
|
||||||
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day).extend(miRepository);
|
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day).extend(miRepository);
|
||||||
this.repositoryUserPvForHour = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(hour).extend(miRepository);
|
|
||||||
this.repositoryUserPvForDay = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(day).extend(miRepository);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -732,37 +728,4 @@ export default abstract class Chart<T extends Schema> {
|
||||||
}
|
}
|
||||||
return object as Unflatten<ChartResult<T>>;
|
return object as Unflatten<ChartResult<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async getChartPv(span: 'hour' | 'day', amount: number, cursor: Date | null, limit: number, offset: number, order: 'ASC' | 'DESC'): Promise<
|
|
||||||
{
|
|
||||||
userId: string,
|
|
||||||
count: number,
|
|
||||||
}[]
|
|
||||||
> {
|
|
||||||
const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate();
|
|
||||||
const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never;
|
|
||||||
|
|
||||||
const lt = dateUTC([y, m, d, h, _m, _s, _ms]);
|
|
||||||
|
|
||||||
const gt =
|
|
||||||
span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
|
|
||||||
span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
|
|
||||||
new Error('not happen') as never;
|
|
||||||
|
|
||||||
const repository =
|
|
||||||
span === 'hour' ? this.repositoryUserPvForHour :
|
|
||||||
span === 'day' ? this.repositoryUserPvForDay :
|
|
||||||
new Error('not happen') as never;
|
|
||||||
|
|
||||||
// ログ取得
|
|
||||||
return await repository.createQueryBuilder()
|
|
||||||
.select('"group" as "userId", sum("___upv_user" + "___upv_visitor") as "count"')
|
|
||||||
.where('date BETWEEN :gt AND :lt', { gt: Chart.dateToTimestamp(gt), lt: Chart.dateToTimestamp(lt) })
|
|
||||||
.groupBy('"userId"')
|
|
||||||
.orderBy('"count"', order)
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.getRawMany<{ userId: string, count: number }>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,12 +72,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (ps.hostname) {
|
if (ps.hostname) {
|
||||||
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
|
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
|
||||||
}
|
}
|
||||||
const chartUsers: { userId: string; count: number; }[] = [];
|
|
||||||
|
let pvUsers: { userId: string; count: number; }[] | undefined = undefined;
|
||||||
if (ps.sort?.endsWith('pv')) {
|
if (ps.sort?.endsWith('pv')) {
|
||||||
await this.perUserPvChart.getChartUsers('day', ps.sort === '+pv' ? 'DESC' : 'ASC', 0, null, ps.limit, ps.offset).then(users => {
|
// 直近12時間のPVランキングを取得
|
||||||
chartUsers.push(...users);
|
pvUsers = await this.perUserPvChart.getUsersRanking(
|
||||||
});
|
'hour', ps.sort.startsWith('+') ? 'DESC' : 'ASC',
|
||||||
|
12, null, ps.limit, ps.offset,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ps.sort) {
|
switch (ps.sort) {
|
||||||
case '+follower': query.orderBy('user.followersCount', 'DESC'); break;
|
case '+follower': query.orderBy('user.followersCount', 'DESC'); break;
|
||||||
case '-follower': query.orderBy('user.followersCount', 'ASC'); break;
|
case '-follower': query.orderBy('user.followersCount', 'ASC'); break;
|
||||||
|
@ -85,16 +89,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
case '-createdAt': query.orderBy('user.id', 'ASC'); break;
|
case '-createdAt': query.orderBy('user.id', 'ASC'); break;
|
||||||
case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break;
|
case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break;
|
||||||
case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break;
|
case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break;
|
||||||
case '+pv':
|
case '+pv': query.andWhere('user.id IN (:...userIds)', { userIds: pvUsers?.map(user => user.userId) ?? [] }); break;
|
||||||
if (chartUsers.length > 0) {
|
case '-pv': query.andWhere('user.id IN (:...userIds)', { userIds: pvUsers?.map(user => user.userId) ?? [] }); break;
|
||||||
query.andWhere('user.id IN (:...userIds)', { userIds: chartUsers.map(user => user.userId) });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '-pv':
|
|
||||||
if (chartUsers.length > 0) {
|
|
||||||
query.andWhere('user.id IN (:...userIds)', { userIds: chartUsers.map(user => user.userId) });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: query.orderBy('user.id', 'ASC'); break;
|
default: query.orderBy('user.id', 'ASC'); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,14 +103,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const users = await query.getMany();
|
const users = await query.getMany();
|
||||||
if (ps.sort === '+pv') {
|
if (ps.sort === '+pv') {
|
||||||
users.sort((a, b) => {
|
users.sort((a, b) => {
|
||||||
const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0;
|
const aPv = pvUsers?.find(u => u.userId === a.id)?.count ?? 0;
|
||||||
const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0;
|
const bPv = pvUsers?.find(u => u.userId === b.id)?.count ?? 0;
|
||||||
return bPv - aPv;
|
return bPv - aPv;
|
||||||
});
|
});
|
||||||
} else if (ps.sort === '-pv') {
|
} else if (ps.sort === '-pv') {
|
||||||
users.sort((a, b) => {
|
users.sort((a, b) => {
|
||||||
const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0;
|
const aPv = pvUsers?.find(u => u.userId === a.id)?.count ?? 0;
|
||||||
const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0;
|
const bPv = pvUsers?.find(u => u.userId === b.id)?.count ?? 0;
|
||||||
return aPv - bPv;
|
return aPv - bPv;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue