// +---------------------------------------------------------------------- declare(strict_types=1); namespace app\appmall\model; use app\appmall\library\AppmallSchema; use think\facade\Cache; use think\facade\Db; use ywxapp\model\BaseModel; /** * 插件用户评价(评分 + 评论) * 表:appmall_addon_reviews * * 生命周期:用户提交(status=0 待审)→ 后台通过(status=1)/驳回(2)→ 通过后才计入 * addon_list.rating(均分)与 review_count(条数)。同一用户对同一插件只保留一条记录 * (匿名时按 IP 去重),重复提交走「覆盖更新」而不是新增。 * * @mixin \think\Model */ class AddonReview extends BaseModel { /** 状态:待审核 / 已发布 / 已驳回 */ public const STATUS_PENDING = 0; public const STATUS_PUBLISHED = 1; public const STATUS_REJECTED = 2; protected function getOptions(): array { return [ 'strict' => true, 'name' => 'appmall_addon_reviews', 'autoWriteTimestamp' => 'int', 'createTime' => 'create_at', 'updateTime' => 'update_at', ]; } /** * 表结构自愈:缺表建表 + 老库扩展列补齐(事实源 app/appmall/install.sql)。 */ public static function ensureSchema(): void { AppmallSchema::ensureTable('appmall_addon_reviews'); } /** * 模型一被使用(含静态查询)就先兜底建表,任何入口都不会再撞 1146。 */ protected function initialize() { parent::initialize(); self::ensureSchema(); } /** * 汇总某插件的评分到 addon_list(Rating 均分 + 评价条数) * * 只统计已发布(status=1)的评价;没有评价时清零,避免展示残留的旧分数。 * 写完必须 bump 目录版本号:Market::lists() 有 60s 缓存,否则列表页分数不刷新。 * * @param int $aid 插件 ID * @return array ['rating' => float, 'count' => int] */ public static function syncRating(int $aid): array { if ($aid <= 0) { return ['rating' => 0.0, 'count' => 0]; } $stat = Db::name('appmall_addon_reviews') ->where('aid', $aid) ->where('status', self::STATUS_PUBLISHED) ->field('COUNT(*) AS cnt, AVG(rating) AS avg') ->find(); $count = (int) ($stat['cnt'] ?? 0); $avg = $count > 0 ? round((float) ($stat['avg'] ?? 0), 1) : 0.0; try { Db::name('appmall_addon_list')->where('id', $aid)->update([ 'rating' => $avg, 'review_count' => $count, 'update_at' => time(), ]); // 失效列表缓存(审核/驳回后市场列表的评分要立即更新) Cache::set('appmall:catalog:ver', (string) time()); } catch (\Throwable $e) { // 聚合失败不影响主流程:列表页会退化为显示上一次的值 } return ['rating' => $avg, 'count' => $count]; } }