refactor(member, auth): streamline product info retrieval and enhance dashboard statistics
- Updated `getProductInfo` method in `Member` model to use a unified model for fetching product data. - Refactored `logLogin` method to utilize `MemberLoginLog` for logging login attempts. - Introduced `getDashboardStats` method in `AuthService` to gather user-specific statistics, including remaining quotas and counts of authorized accounts, published tasks, and works. - Added new database configuration for Douyin business statistics.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 发布任务模型(对应 dy_video_cron 表)
|
||||
*
|
||||
* 首页“发布任务”统计与 acgpmw 一致,只排除 status=3 的记录。
|
||||
*/
|
||||
class DyVideoCron extends Model
|
||||
{
|
||||
protected $connection = 'dbmember';
|
||||
|
||||
protected $name = 'dy_video_cron';
|
||||
|
||||
protected $pk = 'id';
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 统计当前用户的有效发布任务数量。
|
||||
*
|
||||
* @param int $userid 用户ID
|
||||
* @return int
|
||||
*/
|
||||
public static function countActiveByUserId(int $userid): int
|
||||
{
|
||||
return (int) self::where('userid', $userid)
|
||||
->where('status', '<>', 3)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 授权账号模型(对应 dy_video_user 表)
|
||||
*
|
||||
* 首页“授权账号”卡片按 acgpmw 首页逻辑统计未禁用账号数。
|
||||
*/
|
||||
class DyVideoUser extends Model
|
||||
{
|
||||
protected $connection = 'dbmember';
|
||||
|
||||
protected $name = 'dy_video_user';
|
||||
|
||||
protected $pk = 'id';
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 统计当前用户可用的授权账号数量。
|
||||
*
|
||||
* @param int $userid 用户ID
|
||||
* @return int
|
||||
*/
|
||||
public static function countActiveByUserId(int $userid): int
|
||||
{
|
||||
return (int) self::where('userid', $userid)
|
||||
->where('disabled', 0)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 抖音发布日志模型(对应分表 dys_video_log_{userid % 1000})
|
||||
*
|
||||
* 该表在 douying 库中按用户取模分表,首页“发布作品”统计需要按原系统规则动态定位表名。
|
||||
*/
|
||||
class DysVideoLog extends Model
|
||||
{
|
||||
protected $connection = 'dbdouying';
|
||||
|
||||
// 仅作为默认占位,真正查询时会按用户ID动态切换分表名。
|
||||
protected $name = 'dys_video_log_0';
|
||||
|
||||
protected $pk = 'id';
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 统计当前用户已发布作品数量。
|
||||
*
|
||||
* 此处按 acgpmw 首页逻辑对齐,仅统计 status<=1 的发布记录。
|
||||
*
|
||||
* @param int $userid 用户ID
|
||||
* @return int
|
||||
*/
|
||||
public static function countPublishedByUserId(int $userid): int
|
||||
{
|
||||
$tableName = self::resolveTableName($userid);
|
||||
|
||||
return (int) (new self())
|
||||
->db()
|
||||
->name($tableName)
|
||||
->where('userid', $userid)
|
||||
->where('status', '<=', 1)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID计算分表名。
|
||||
*
|
||||
* @param int $userid 用户ID
|
||||
* @return string
|
||||
*/
|
||||
private static function resolveTableName(int $userid): string
|
||||
{
|
||||
return sprintf('dys_video_log_%d', $userid % 1000);
|
||||
}
|
||||
}
|
||||
@@ -95,13 +95,10 @@ class Member extends Model
|
||||
*/
|
||||
public function getProductInfo(): ?array
|
||||
{
|
||||
// 从主库获取套餐信息
|
||||
$product = \think\facade\Db::connect('dbbiz')
|
||||
->name('product_list')
|
||||
->where('v_type', $this->v_type)
|
||||
->find();
|
||||
// 套餐配置改由模型统一封装,避免业务层散落裸表查询。
|
||||
$product = ProductList::findByVType((int) $this->v_type);
|
||||
|
||||
return $product;
|
||||
return $product ? $product->toArray() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,9 +123,7 @@ class Member extends Model
|
||||
public function logLogin(bool $success, string $loginType = 'password'): void
|
||||
{
|
||||
try {
|
||||
\think\facade\Db::connect('dbmember')
|
||||
->name('member_login_log')
|
||||
->insert([
|
||||
MemberLoginLog::recordLogin([
|
||||
'userid' => $this->userid,
|
||||
'ip' => request()->ip(),
|
||||
'time' => time(),
|
||||
@@ -138,7 +133,7 @@ class Member extends Model
|
||||
'adminid' => 0,
|
||||
'v_type' => $this->v_type ?? 0,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// 日志记录失败不影响登录
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 会员登录日志模型(对应 member_login_log 表)
|
||||
*
|
||||
* 这里封装登录日志写入,避免业务层直接拼接表名。
|
||||
*/
|
||||
class MemberLoginLog extends Model
|
||||
{
|
||||
protected $connection = 'dbmember';
|
||||
|
||||
protected $name = 'member_login_log';
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 记录一次登录结果。
|
||||
*
|
||||
* @param array $payload 登录日志字段
|
||||
* @return void
|
||||
*/
|
||||
public static function recordLogin(array $payload): void
|
||||
{
|
||||
$model = new self();
|
||||
$model->save($payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 套餐模型(对应业务库 product_list 表)
|
||||
*
|
||||
* 该模型用于读取会员套餐配置,供登录态与首页工作台展示使用。
|
||||
*/
|
||||
class ProductList extends Model
|
||||
{
|
||||
// 套餐配置存放在业务库。
|
||||
protected $connection = 'dbbiz';
|
||||
|
||||
// 对齐原系统 product_list 表。
|
||||
protected $name = 'product_list';
|
||||
|
||||
// 当前项目仅做读取,不依赖自动时间戳。
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 按套餐类型获取套餐信息。
|
||||
*
|
||||
* @param int $vType 套餐类型
|
||||
* @return self|null
|
||||
*/
|
||||
public static function findByVType(int $vType): ?self
|
||||
{
|
||||
return self::where('v_type', $vType)->find();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
namespace app\api\service;
|
||||
|
||||
use app\api\common\Jwt;
|
||||
use app\api\model\DyVideoCron;
|
||||
use app\api\model\DyVideoUser;
|
||||
use app\api\model\DysVideoLog;
|
||||
use app\api\model\Member;
|
||||
|
||||
/**
|
||||
@@ -155,6 +158,7 @@ class AuthService
|
||||
|
||||
// 获取套餐信息
|
||||
$productInfo = $member->getProductInfo();
|
||||
$dashboardStats = $this->getDashboardStats($member, $productInfo);
|
||||
|
||||
return [
|
||||
'userid' => $member->userid,
|
||||
@@ -163,14 +167,66 @@ class AuthService
|
||||
'endtime' => $member->endtime,
|
||||
'formtypeid' => $member->formtypeid,
|
||||
'disabled' => $member->disabled,
|
||||
'video_num' => $member->video_num,
|
||||
'sp_num' => $member->sp_num,
|
||||
'product' => $productInfo ? [
|
||||
'v_type' => $productInfo['v_type'] ?? null,
|
||||
'nameu' => $productInfo['nameu'] ?? '',
|
||||
'video_num' => $productInfo['video_num'] ?? 0,
|
||||
'account_num' => $productInfo['account_num'] ?? 0,
|
||||
] : null,
|
||||
'stats' => $dashboardStats,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页统计数据。
|
||||
*
|
||||
* 此处按 acgpmw `dyai/controller/index.php::right()` 的首页语义对齐:
|
||||
* - 账户剩余条数:优先展示用户余额 `member.video_num`
|
||||
* - 授权账号:统计 `dy_video_user`
|
||||
* - 发布任务:统计 `dy_video_cron`
|
||||
* - 发布作品:统计抖音日志分表 `dys_video_log_{userid % 1000}`
|
||||
*
|
||||
* 查询失败时返回 null,避免前端把异常误展示为 0。
|
||||
*
|
||||
* @param Member $member 当前登录用户
|
||||
* @param array|null $productInfo 当前套餐信息
|
||||
* @return array
|
||||
*/
|
||||
private function getDashboardStats(Member $member, ?array $productInfo = null): array
|
||||
{
|
||||
$isUnlimitedQuota = $productInfo && (int) ($productInfo['video_num'] ?? 0) < 0;
|
||||
|
||||
$stats = [
|
||||
'remaining_quota' => $isUnlimitedQuota ? null : (int) ($member->video_num ?? 0),
|
||||
'remaining_quota_unlimited' => $isUnlimitedQuota,
|
||||
'published_works_count' => null,
|
||||
'authorized_account_count' => null,
|
||||
'published_task_count' => null,
|
||||
];
|
||||
|
||||
try {
|
||||
$stats['authorized_account_count'] = DyVideoUser::countActiveByUserId((int) $member->userid);
|
||||
} catch (\Throwable $exception) {
|
||||
// 统计接口需要尽量稳健,单项查询失败时不阻断登录信息返回。
|
||||
}
|
||||
|
||||
try {
|
||||
$stats['published_task_count'] = DyVideoCron::countActiveByUserId((int) $member->userid);
|
||||
} catch (\Throwable $exception) {
|
||||
// 这里与 acgpmw 一样只排除 status=3 的任务,其余状态均计入首页统计。
|
||||
}
|
||||
|
||||
try {
|
||||
$stats['published_works_count'] = DysVideoLog::countPublishedByUserId((int) $member->userid);
|
||||
} catch (\Throwable $exception) {
|
||||
// 发布作品日志使用分表存储,查询失败时前端按“待接入”兜底展示。
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param int $userid
|
||||
|
||||
@@ -62,6 +62,27 @@ return [
|
||||
'fields_cache' => false,
|
||||
],
|
||||
|
||||
// 抖音业务数据库(用于首页发布作品统计分表)
|
||||
'dbdouying' => [
|
||||
'type' => 'mysql',
|
||||
'hostname' => env('DB_DOUYING_HOSTNAME', 'rm-m5e6936bb24oj5272co.mysql.rds.aliyuncs.com'),
|
||||
'database' => env('DB_DOUYING_DATABASE', 'douying'),
|
||||
'username' => env('DB_DOUYING_USERNAME', 'contenttpl'),
|
||||
'password' => env('DB_DOUYING_PASSWORD', 'QXYxgg123!@#q'),
|
||||
'hostport' => env('DB_DOUYING_HOSTPORT', '3306'),
|
||||
'params' => [],
|
||||
'charset' => env('DB_DOUYING_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'deploy' => 0,
|
||||
'rw_separate' => false,
|
||||
'master_num' => 1,
|
||||
'slave_no' => '',
|
||||
'fields_strict' => false,
|
||||
'break_reconnect' => false,
|
||||
'trigger_sql' => env('APP_DEBUG', true),
|
||||
'fields_cache' => false,
|
||||
],
|
||||
|
||||
// 后台管理数据库
|
||||
'dbxgg' => [
|
||||
'type' => 'mysql',
|
||||
|
||||
Reference in New Issue
Block a user