Compare commits

...

6 Commits

Author SHA1 Message Date
Tim
680e44e743 feat: 给出仅重启后台的vibe coding方式 2026-02-04 20:23:19 +08:00
Tim
657b76bb1e feat: 评论分页 2026-02-04 20:14:46 +08:00
Tim
f695db62c6 Clear filters on logo click 2026-02-04 19:03:58 +08:00
Tim
e5b386cdc2 Merge pull request #1136 from nagisa77/bugfix/1053
fix: 性能优化,首页下拉更新,实测6秒左右,稍慢 #1053
2026-01-16 16:47:46 +08:00
Tim
179699dd66 Merge pull request #1135 from nagisa77/bugfix/1130
fix: 1. 修复dev_local_backend报错问题 2.由于内存占用过高,直接去除opensearch
2026-01-16 16:45:35 +08:00
Tim
e13ee1ca46 fix: 1. 修复dev_local_backend报错问题 2.由于内存占用过高,直接去除opensearch 2026-01-16 15:22:59 +08:00
5 changed files with 186 additions and 79 deletions

View File

@@ -57,6 +57,14 @@ cd OpenIsle
--profile dev up -d --force-recreate
```
仅重启后端容器(不重建镜像、不影响前端):
```shell
docker compose \
-f docker/docker-compose.yaml \
--env-file .env \
--profile dev restart springboot websocket-service
```
数据初始化sql会创建几个帐户供大家测试使用
> username:admin/user1/user2 password:123456

View File

@@ -112,7 +112,9 @@ public class CommentController {
)
public List<TimelineItemDto<?>> listComments(
@PathVariable Long postId,
@RequestParam(value = "sort", required = false, defaultValue = "OLDEST") CommentSort sort
@RequestParam(value = "sort", required = false, defaultValue = "OLDEST") CommentSort sort,
@RequestParam(value = "page", required = false, defaultValue = "0") int page,
@RequestParam(value = "pageSize", required = false, defaultValue = "20") int pageSize
) {
log.debug("listComments called for post {} with sort {}", postId, sort);
List<CommentDto> commentDtoList = commentService
@@ -183,8 +185,23 @@ public class CommentController {
createdAtComparator = createdAtComparator.reversed();
}
itemDtoList.sort(comparator.thenComparing(createdAtComparator));
log.debug("listComments returning {} comments", itemDtoList.size());
return itemDtoList;
int safePage = Math.max(0, page);
int safePageSize = Math.max(1, pageSize);
int fromIndex = safePage * safePageSize;
int toIndex = Math.min(fromIndex + safePageSize, itemDtoList.size());
List<TimelineItemDto<?>> pagedItems =
fromIndex >= itemDtoList.size() ? List.of() : itemDtoList.subList(fromIndex, toIndex);
log.debug(
"listComments returning {} items for post {} page {} size {} (total {})",
pagedItems.size(),
postId,
safePage,
safePageSize,
itemDtoList.size()
);
return pagedItems;
}
@GetMapping("/comments/{commentId}/context")

View File

@@ -30,62 +30,62 @@ services:
- dev_local_backend
- prod
# OpenSearch Service
opensearch:
user: "1000:1000"
build:
context: .
dockerfile: opensearch.Dockerfile
container_name: ${COMPOSE_PROJECT_NAME}-opensearch
environment:
- cluster.name=os-single
- node.name=os-node-1
- discovery.type=single-node
- bootstrap.memory_lock=true
- OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
- DISABLE_SECURITY_PLUGIN=true
- cluster.blocks.create_index=false
ulimits:
memlock: { soft: -1, hard: -1 }
nofile: { soft: 65536, hard: 65536 }
volumes:
- opensearch-data:/usr/share/opensearch/data
- opensearch-snapshots:/snapshots
ports:
- "${OPENSEARCH_PORT:-9200}:9200"
- "${OPENSEARCH_METRICS_PORT:-9600}:9600"
restart: unless-stopped
healthcheck:
test:
- CMD-SHELL
- curl -fsS http://127.0.0.1:9200/_cluster/health >/dev/null
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
networks:
- openisle-network
profiles:
- dev
- dev_local_backend
# # OpenSearch Service
# opensearch:
# user: "1000:1000"
# build:
# context: .
# dockerfile: opensearch.Dockerfile
# container_name: ${COMPOSE_PROJECT_NAME}-opensearch
# environment:
# - cluster.name=os-single
# - node.name=os-node-1
# - discovery.type=single-node
# - bootstrap.memory_lock=true
# - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
# - DISABLE_SECURITY_PLUGIN=true
# - cluster.blocks.create_index=false
# ulimits:
# memlock: { soft: -1, hard: -1 }
# nofile: { soft: 65536, hard: 65536 }
# volumes:
# - opensearch-data:/usr/share/opensearch/data
# - opensearch-snapshots:/snapshots
# ports:
# - "${OPENSEARCH_PORT:-9200}:9200"
# - "${OPENSEARCH_METRICS_PORT:-9600}:9600"
# restart: unless-stopped
# healthcheck:
# test:
# - CMD-SHELL
# - curl -fsS http://127.0.0.1:9200/_cluster/health >/dev/null
# interval: 10s
# timeout: 5s
# retries: 30
# start_period: 60s
# networks:
# - openisle-network
# profiles:
# - dev
# - dev_local_backend
dashboards:
image: opensearchproject/opensearch-dashboards:3.0.0
container_name: ${COMPOSE_PROJECT_NAME}-os-dashboards
environment:
OPENSEARCH_HOSTS: '["http://opensearch:9200"]'
DISABLE_SECURITY_DASHBOARDS_PLUGIN: "true"
ports:
- "${OPENSEARCH_DASHBOARDS_PORT:-5601}:5601"
depends_on:
- opensearch
restart: unless-stopped
networks:
- openisle-network
profiles:
- dev
- dev_local_backend
- prod
# dashboards:
# image: opensearchproject/opensearch-dashboards:3.0.0
# container_name: ${COMPOSE_PROJECT_NAME}-os-dashboards
# environment:
# OPENSEARCH_HOSTS: '["http://opensearch:9200"]'
# DISABLE_SECURITY_DASHBOARDS_PLUGIN: "true"
# ports:
# - "${OPENSEARCH_DASHBOARDS_PORT:-5601}:5601"
# depends_on:
# - opensearch
# restart: unless-stopped
# networks:
# - openisle-network
# profiles:
# - dev
# - dev_local_backend
# - prod
rabbitmq:
image: rabbitmq:3.13-management
@@ -200,7 +200,6 @@ services:
- openisle-network
profiles:
- dev
- dev_local_backend
- prod

View File

@@ -159,6 +159,12 @@ const selectedTags = ref([])
const route = useRoute()
const tagOptions = ref([])
const categoryOptions = ref([])
const clearFilters = () => {
selectedCategory.value = ''
selectedTags.value = []
selectedCategoryGlobal.value = ''
selectedTagsGlobal.value = []
}
const topics = ref(['最新回复', '最新', '精选', '排行榜' /*, '热门', '类别'*/])
const selectedTopicCookie = useCookie('homeTab')
@@ -218,8 +224,18 @@ watch(
(query) => {
const category = query.category
const tags = query.tags
category && selectedCategorySet(category)
tags && selectedTagsSet(tags)
if (category) {
selectedCategorySet(category)
} else {
selectedCategory.value = ''
selectedCategoryGlobal.value = ''
}
if (tags) {
selectedTagsSet(tags)
} else {
selectedTags.value = []
selectedTagsGlobal.value = []
}
},
)
@@ -367,12 +383,18 @@ watch(selectedTopic, (val) => {
if (import.meta.server) {
await loadOptions()
}
const handleRefreshHome = () => {
clearFilters()
refreshFirst()
}
onMounted(() => {
if (categoryOptions.value.length === 0 && tagOptions.value.length === 0) loadOptions()
window.addEventListener('refresh-home', refreshFirst)
window.addEventListener('refresh-home', handleRefreshHome)
})
onBeforeUnmount(() => {
window.removeEventListener('refresh-home', refreshFirst)
window.removeEventListener('refresh-home', handleRefreshHome)
})
/** 供 InfiniteLoadMore 重建用的 key筛选/Tab 改变即重建内部状态 */

View File

@@ -181,6 +181,12 @@
<PostChangeLogItem v-else :log="item" :title="title" />
</template>
</BaseTimeline>
<InfiniteLoadMore
v-if="timelineItems.length > 0"
:key="commentSort"
:on-load="loadMoreTimeline"
:pause="isLoadingMoreComments || isFetchingComments"
/>
</div>
</div>
@@ -211,6 +217,7 @@ import CommentEditor from '~/components/CommentEditor.vue'
import BaseTimeline from '~/components/BaseTimeline.vue'
import BasePlaceholder from '~/components/BasePlaceholder.vue'
import PostChangeLogItem from '~/components/PostChangeLogItem.vue'
import InfiniteLoadMore from '~/components/InfiniteLoadMore.vue'
import ArticleTags from '~/components/ArticleTags.vue'
import ArticleCategory from '~/components/ArticleCategory.vue'
import ReactionsGroup from '~/components/ReactionsGroup.vue'
@@ -276,6 +283,10 @@ const currentIndex = ref(1)
const subscribed = ref(false)
const commentSort = ref('NEWEST')
const isFetchingComments = ref(false)
const commentPage = ref(0)
const commentPageSize = 10
const hasMoreComments = ref(true)
const isLoadingMoreComments = ref(false)
const isMobile = useIsMobile()
const timelineItems = ref([])
@@ -869,17 +880,33 @@ const fetchCommentSorts = () => {
])
}
const fetchCommentsAndChangeLog = async () => {
isFetchingComments.value = true
console.info('Fetching comments and chang log', { postId, sort: commentSort.value })
const fetchCommentsAndChangeLog = async ({ pageNo = 0, append = false } = {}) => {
if (isLoadingMoreComments.value) return false
if (!append) {
hasMoreComments.value = true
commentPage.value = 0
}
if (pageNo === 0) {
isFetchingComments.value = true
} else {
isLoadingMoreComments.value = true
}
console.info('Fetching comments and chang log', {
postId,
sort: commentSort.value,
page: pageNo,
pageSize: commentPageSize,
})
let done = false
try {
const token = getToken()
const res = await fetch(
`${API_BASE_URL}/api/posts/${postId}/comments?sort=${commentSort.value}`,
{
headers: { Authorization: token ? `Bearer ${token}` : '' },
},
)
const url = new URL(`${API_BASE_URL}/api/posts/${postId}/comments`)
url.searchParams.set('sort', commentSort.value)
url.searchParams.set('page', String(pageNo))
url.searchParams.set('pageSize', String(commentPageSize))
const res = await fetch(url.toString(), {
headers: { Authorization: token ? `Bearer ${token}` : '' },
})
console.info('Fetch comments response status', res.status)
if (res.ok) {
const data = await res.json()
@@ -902,23 +929,48 @@ const fetchCommentsAndChangeLog = async () => {
}
}
comments.value = commentList
changeLogs.value = changeLogList
timelineItems.value = newTimelineItemList
if (append) {
comments.value.push(...commentList)
changeLogs.value.push(...changeLogList)
timelineItems.value.push(...newTimelineItemList)
commentPage.value = pageNo
} else {
comments.value = commentList
changeLogs.value = changeLogList
timelineItems.value = newTimelineItemList
commentPage.value = 0
}
isFetchingComments.value = false
done = data.length < commentPageSize
hasMoreComments.value = !done
await nextTick()
gatherPostItems()
return done
}
} catch (e) {
console.debug('Fetch comments error', e)
hasMoreComments.value = false
return true
} finally {
isFetchingComments.value = false
isLoadingMoreComments.value = false
}
}
const fetchTimeline = async () => {
await fetchCommentsAndChangeLog()
hasMoreComments.value = true
commentPage.value = 0
comments.value = []
changeLogs.value = []
timelineItems.value = []
await fetchCommentsAndChangeLog({ pageNo: 0, append: false })
}
const loadMoreTimeline = async () => {
if (!hasMoreComments.value || isLoadingMoreComments.value) return true
const nextPage = commentPage.value + 1
const done = await fetchCommentsAndChangeLog({ pageNo: nextPage, append: true })
return done || !hasMoreComments.value
}
watch(commentSort, async () => {
@@ -929,8 +981,17 @@ const jumpToHashComment = async () => {
const hash = location.hash
if (hash.startsWith('#comment-')) {
const id = hash.substring('#comment-'.length)
await new Promise((resolve) => setTimeout(resolve, 500))
const el = document.getElementById('comment-' + id)
await new Promise((resolve) => setTimeout(resolve, 300))
let el = document.getElementById('comment-' + id)
// 若未加载到目标评论,尝试继续分页加载直到找到或无更多
while (!el && hasMoreComments.value) {
const done = await loadMoreTimeline()
await nextTick()
el = document.getElementById('comment-' + id)
if (done) break
}
if (el) {
const top = el.getBoundingClientRect().top + window.scrollY - headerHeight - 20 // 20 for beauty
window.scrollTo({ top, behavior: 'smooth' })