Răsfoiți Sursa

feat(api-v13): issue 对匿名公开(要注入公开的阅读页),不公开的只剩 discussion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
visuddhinanda 18 ore în urmă
părinte
comite
952088cccf

+ 2 - 2
api-v13/app/Http/Controllers/V3/DiscussionBatchCountController.php

@@ -25,8 +25,8 @@ class DiscussionBatchCountController extends Controller
      *
      * 每个对象返回未关闭的评论线程数(total)与其中当前用户发起的数目(mine,未登录为 0),
      * 按请求顺序、一个不少(没有讨论的为 0)。只数评论线程(discussion / qa / help / issue),
-     * 批注与已删除的不算;可见性与列表相同:未登录不数协作类话题,basic 角色在无权管理的
-     * 对象上只数自己发起的协作类话题。最多 500 个对象。
+     * 批注与已删除的不算;可见性与列表相同:未登录不数不公开的话题(discussion),basic 角色在
+     * 无权管理的对象上只数自己发起的不公开话题。最多 500 个对象。
      *
      * 与 v2 的区别:入参是对象 uid 而不是句子坐标;只给计数不给明细;v2 不分类型、不管可见性。
      *

+ 2 - 2
api-v13/app/Http/Controllers/V3/DiscussionController.php

@@ -41,8 +41,8 @@ class DiscussionController extends Controller
      * 列出某个对象上的话题
      *
      * 只列话题(不含回复),删除了但还有回复的话题以墓碑出现(deleted_at 有值、内容为空)。
-     * 未登录看不到协作类话题(discussion / issue);basic 角色在自己无权管理的对象上只看得到
-     * 自己发起的协作类话题。各状态的数量与「能发起哪些类型」见 `GET /v3/discussions/summary`。
+     * 未登录看不到不公开的话题(discussion);basic 角色在自己无权管理的对象上只看得到
+     * 自己发起的不公开话题。各状态的数量与「能发起哪些类型」见 `GET /v3/discussions/summary`。
      * 不传 type 时只列评论线程(discussion / qa / help / issue),批注要显式 type=note / commentary。
      *
      * @unauthenticated

+ 12 - 8
api-v13/app/Services/V3/DiscussionService.php

@@ -48,8 +48,9 @@ use Illuminate\Validation\ValidationException;
  *
  * 可见性沿用 v2 `view=question` 的口径,但这里对列表、单条、回复一视同仁(v2 只在
  * 列表上做了这个判断):
- * - 未登录看不到协作类话题(discussion / issue);
- * - basic 角色在自己无权管理的对象上,只能看到自己发起的协作类话题。
+ * - 未登录看不到不公开的话题(discussion);
+ * - basic 角色在自己无权管理的对象上,只能看到自己发起的不公开话题。
+ * issue 2026-10-21 起公开:它会注入阅读页(高亮 + 点击看内容),阅读页是公开且共享缓存的。
  *
  * TODO: 以下两项需要加列,2026-10-15 用户决定暂缓:
  * - qa 的「已回答」现在用 status=close 表示;社区通行做法是把某条回复标为答案
@@ -59,9 +60,12 @@ use Illuminate\Validation\ValidationException;
  */
 class DiscussionService
 {
-    /** 未登录用户看不到、basic 角色只能看自己的这几类话题 */
+    /** 登录即可发起的话题类型 */
     private const COLLABORATION_TYPES = ['discussion', 'issue'];
 
+    /** 不公开的话题类型:未登录用户看不到,basic 角色在无权管理的对象上只能看自己的 */
+    private const PRIVATE_TYPES = ['discussion'];
+
     /** 批注的锚点字段:定位到句子里的哪一段 */
     private const ANCHOR_FIELDS = ['pos_start', 'pos_end', 'quote_exact', 'quote_prefix', 'quote_suffix'];
 
@@ -169,10 +173,10 @@ class DiscussionService
                 }
             });
         if ($viewerUid === null) {
-            $query->whereNotIn('type', self::COLLABORATION_TYPES);
+            $query->whereNotIn('type', self::PRIVATE_TYPES);
         } elseif ($this->isBasic($viewerUid)) {
             $managed = $this->managedTargets($viewerUid, $idsByType->all());
-            $query->where(fn ($q) => $q->whereNotIn('type', self::COLLABORATION_TYPES)
+            $query->where(fn ($q) => $q->whereNotIn('type', self::PRIVATE_TYPES)
                 ->orWhere('editor_uid', $viewerUid)
                 ->orWhereIn('res_id', $managed));
         }
@@ -580,12 +584,12 @@ class DiscussionService
     private function applyVisibility(Builder $query, ?string $viewerUid, string $targetType, string $targetId): void
     {
         if ($viewerUid === null) {
-            $query->whereNotIn('type', self::COLLABORATION_TYPES);
+            $query->whereNotIn('type', self::PRIVATE_TYPES);
 
             return;
         }
         if ($this->isBasic($viewerUid) && ! $this->canManageTarget($viewerUid, $targetType, $targetId)) {
-            $query->where(fn ($q) => $q->whereNotIn('type', self::COLLABORATION_TYPES)
+            $query->where(fn ($q) => $q->whereNotIn('type', self::PRIVATE_TYPES)
                 ->orWhere('editor_uid', $viewerUid));
         }
     }
@@ -595,7 +599,7 @@ class DiscussionService
      */
     private function assertVisible(Discussion $topic, ?string $viewerUid): void
     {
-        if (! in_array($topic->type, self::COLLABORATION_TYPES, true)) {
+        if (! in_array($topic->type, self::PRIVATE_TYPES, true)) {
             return;
         }
         if ($viewerUid === null) {