mirror of
https://gitee.com/blossom-editor/blossom.git
synced 2025-12-06 08:48:29 +08:00
feat: 自定义临时访问链接的有效时长
This commit is contained in:
parent
b4e001e3e9
commit
90fa7071f3
@ -289,13 +289,19 @@ public class ArticleController {
|
||||
/**
|
||||
* 创建文章的临时访问缓存
|
||||
*
|
||||
* @param id 文章ID
|
||||
* @param id 文章ID
|
||||
* @param duration 临时访问的过期时间
|
||||
* @return 临时访问Key
|
||||
* @since 1.9.0
|
||||
*/
|
||||
@GetMapping("/temp/key")
|
||||
public R<String> createTempVisitKey(@RequestParam("id") Long id) {
|
||||
return R.ok(tempVisitService.create(id, AuthContext.getUserId()));
|
||||
public R<String> createTempVisitKey(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "duration", required = false) Long duration) {
|
||||
if (duration == null) {
|
||||
duration = 3 * 60L;
|
||||
}
|
||||
log.info("创建文章临时访问权限 [{}:{}m]", id, duration);
|
||||
return R.ok(tempVisitService.create(id, AuthContext.getUserId(), duration));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.blossom.backend.server.article.draft;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import com.blossom.common.base.exception.XzException400;
|
||||
import com.blossom.common.base.util.security.SHA256Util;
|
||||
import com.blossom.common.cache.caffeine.DynamicExpiry;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
@ -10,6 +11,7 @@ import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -24,25 +26,31 @@ public class ArticleTempVisitService {
|
||||
|
||||
/**
|
||||
* 存放文章ID的缓存
|
||||
*
|
||||
* @since 1.13.0 支持动态过期时间
|
||||
*/
|
||||
private final Cache<String, TempVisit> tempVisitCache = Caffeine.newBuilder()
|
||||
.expireAfter(new DynamicExpiry())
|
||||
.initialCapacity(500)
|
||||
.expireAfterWrite(3, TimeUnit.HOURS)
|
||||
.removalListener((String key, TempVisit value, RemovalCause cause) ->
|
||||
log.info("临时访问文章 [" + value.getArticleId() + "] 被删除")
|
||||
log.info("remove temp visit articleId [" + Objects.requireNonNull(value).getArticleId() + "]")
|
||||
)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 生成一个缓存 key, key 并非文章的摘要码
|
||||
* 生成一个缓存 key, key 并非文章的摘要码,
|
||||
*
|
||||
* @param articleId 文章ID
|
||||
* @param userId 用户ID
|
||||
* @param duration 过期时间, 单位分钟
|
||||
* @return 缓存 key
|
||||
*/
|
||||
public String create(Long articleId, Long userId) {
|
||||
public String create(Long articleId, Long userId, Long duration) {
|
||||
XzException400.throwBy(ObjUtil.isNull(articleId), "文章ID为必填项");
|
||||
String key = SHA256Util.encode(UUID.randomUUID().toString());
|
||||
tempVisitCache.put(key, new TempVisit(articleId, userId));
|
||||
tempVisitCache.policy().expireVariably().ifPresent(e -> {
|
||||
e.put(key, new TempVisit(articleId, userId), duration, TimeUnit.MINUTES);
|
||||
});
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package com.blossom.common.cache.caffeine;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Expiry;
|
||||
import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* key 使用不同的过期时间
|
||||
*
|
||||
* @since 1.13.0
|
||||
*/
|
||||
public class DynamicExpiry implements Expiry<String, Object> {
|
||||
|
||||
@Override
|
||||
public long expireAfterCreate(@NonNull String key, @NonNull Object value, long currentTime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long expireAfterUpdate(@NonNull String key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
|
||||
return currentDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long expireAfterRead(@NonNull String key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
|
||||
return currentDuration;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.blossom.common.base.caffeine;
|
||||
package com.blossom.common.cache.caffeine;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
@ -13,25 +13,31 @@ import java.util.concurrent.TimeUnit;
|
||||
public class Test {
|
||||
|
||||
private static final Cache<String, String> cache = Caffeine.newBuilder()
|
||||
.expireAfter(new DynamicExpiry())
|
||||
.initialCapacity(100)
|
||||
.expireAfterWrite(10, TimeUnit.SECONDS)
|
||||
.removalListener((String key, String value, RemovalCause cause) -> System.out.println(key + " 被删除"))
|
||||
.removalListener((String key, String value, RemovalCause cause) -> log.info(key + " 被删除"))
|
||||
.build();
|
||||
|
||||
private static final ScheduledExecutorService clearUpScheduled = Executors.newScheduledThreadPool(1);
|
||||
|
||||
public Test() {
|
||||
clearUpScheduled.scheduleWithFixedDelay(this::clear, 5, 1, TimeUnit.SECONDS);
|
||||
clearUpScheduled.scheduleWithFixedDelay(this::clear, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
log.info("尝试过期缓存");
|
||||
log.info("删除");
|
||||
cache.cleanUp();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Test test = new Test();
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
Test t = new Test();
|
||||
cache.policy().expireVariably().ifPresent(e -> {
|
||||
e.put("A1", "1", 7, TimeUnit.SECONDS);
|
||||
});
|
||||
cache.policy().expireVariably().ifPresent(e -> {
|
||||
e.put("A2", "2", 3, TimeUnit.SECONDS);
|
||||
});
|
||||
Thread.sleep(20 * 1000);
|
||||
}
|
||||
|
||||
}
|
||||
@ -120,4 +120,5 @@
|
||||
|
||||
.el-popper.is-small {
|
||||
padding: 0 8px;
|
||||
box-shadow: 1px 1px 3px #f4f4f4;
|
||||
}
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="article-custom-temp-visit-root">
|
||||
<!-- 标题 -->
|
||||
<div class="info-title">
|
||||
<div class="iconbl bl-visit"></div>
|
||||
<div class="article-name">{{ articleInfo.articleName }}</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="duration">有效时长(分钟) <el-input-number v-model="duration" min="1" controls-position="right"></el-input-number></div>
|
||||
<div class="expire">将在 {{ expire }} 后失效。</div>
|
||||
<div class="btns">
|
||||
<el-button size="default" type="primary" @click="create">创建访问链接</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useServerStore } from '@renderer/stores/server'
|
||||
import { articleTempKey, articleTempH } from '@renderer/api/blossom'
|
||||
import { writeText } from '@renderer/assets/utils/electron'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const server = useServerStore()
|
||||
|
||||
const duration = ref(180)
|
||||
const expire = computed(() => {
|
||||
return dayjs().add(duration.value, 'minutes').format('YYYY-MM-DD HH:mm')
|
||||
})
|
||||
|
||||
const articleInfo = ref({
|
||||
articleId: '',
|
||||
articleName: ''
|
||||
})
|
||||
|
||||
const create = () => {
|
||||
articleTempKey({ id: articleInfo.value.articleId, duration: duration.value }).then((resp) => {
|
||||
let url = server.serverUrl + articleTempH + resp.data
|
||||
writeText(url)
|
||||
ElMessage.info({ type: 'info', message: '已复制链接' })
|
||||
emits('created')
|
||||
})
|
||||
}
|
||||
|
||||
const reload = (articleName: string, articleId: string) => {
|
||||
articleInfo.value = {
|
||||
articleId: articleId,
|
||||
articleName: articleName
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ reload })
|
||||
const emits = defineEmits(['created'])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@renderer/assets/styles/bl-dialog-info';
|
||||
|
||||
.article-custom-temp-visit-root {
|
||||
border-radius: 10px;
|
||||
@include box(100%, 100%);
|
||||
|
||||
.article-name {
|
||||
width: 320px;
|
||||
@include font(16px, 500);
|
||||
@include ellipsis();
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
.duration {
|
||||
}
|
||||
|
||||
.expire {
|
||||
@include font(14px, 300);
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--bl-preview-code-bg-color);
|
||||
margin: 13px 0;
|
||||
}
|
||||
|
||||
.btns {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -99,7 +99,6 @@
|
||||
<div v-else class="doc-trees-placeholder">暂无文档,可点击上方 ↑ 添加</div>
|
||||
</div>
|
||||
|
||||
<!-- 右键菜单, 添加到 body 下 -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-show="rMenu.show"
|
||||
@ -111,13 +110,11 @@
|
||||
<div @click="rename"><span class="iconbl bl-pen"></span>重命名</div>
|
||||
<div @click="handleShowDocInfoDialog('upd')"><span class="iconbl bl-a-fileedit-line"></span>编辑详情</div>
|
||||
<div v-if="curDoc.ty === 3" @click="syncDoc()"><span class="iconbl bl-a-cloudrefresh-line"></span>同步文章</div>
|
||||
<!-- <div v-if="curDoc.ty != 3" @click="handleShowDocInfoDialog('add', curDoc.i)"><span class="iconbl bl-a-fileadd-fill"></span>新增子级文档</div> -->
|
||||
<div v-if="curDoc.ty !== 3" @click="addFolder"><span class="iconbl bl-a-fileadd-line"></span>新增文件夹</div>
|
||||
<div v-if="curDoc.ty !== 3" @click="addArticle"><span class="iconbl bl-a-fileadd-fill"></span>新增笔记</div>
|
||||
<div v-if="curDoc.ty === 3" @click="createUrl('link')"><span class="iconbl bl-correlation-line"></span>复制双链引用</div>
|
||||
<div v-if="curDoc.ty !== 3" @click="handleShowArticleImportDialog()"><span class="iconbl bl-file-upload-line"></span>导入文章</div>
|
||||
|
||||
<!-- 更多二级菜单 -->
|
||||
<div @mouseenter="handleHoverRightMenuLevel2($event, 2)" data-bl-prevet="true">
|
||||
<span class="iconbl bl-a-rightsmallline-line"></span>
|
||||
<span class="iconbl bl-apps-line"></span>更多
|
||||
@ -146,7 +143,6 @@
|
||||
<div v-if="curDoc.ty === 3" @click="openArticleWindow"><span class="iconbl bl-a-computerend-line"></span>新窗口查看</div>
|
||||
<div v-if="curDoc.ty === 3" @click="createUrl('tempVisit', true)"><span class="iconbl bl-visit"></span>浏览器临时访问</div>
|
||||
|
||||
<!-- 导出及二级菜单 -->
|
||||
<div v-if="curDoc.ty === 3" @mouseenter="handleHoverRightMenuLevel2($event, 4)" data-bl-prevet="true">
|
||||
<span class="iconbl bl-a-rightsmallline-line"></span>
|
||||
<span class="iconbl bl-file-download-line"></span>导出文章
|
||||
@ -159,10 +155,11 @@
|
||||
</div>
|
||||
<div v-if="curDoc.ty === 3" @mouseenter="handleHoverRightMenuLevel2($event, 2)" data-bl-prevet="true">
|
||||
<span class="iconbl bl-a-rightsmallline-line"></span>
|
||||
<span class="iconbl bl-a-linkspread-line"></span>复制链接
|
||||
<span class="iconbl bl-a-linkspread-line"></span>创建链接
|
||||
<div class="menu-content-level2" :style="rMenuLevel2">
|
||||
<div v-if="curDoc.o === 1" @click="createUrl('copy')"><span class="iconbl bl-planet-line"></span>复制博客链接</div>
|
||||
<div @click="createUrl('tempVisit')"><span class="iconbl bl-visit"></span>复制临时访问链接</div>
|
||||
<div v-if="curDoc.o === 1" @click="createUrl('copy')"><span class="iconbl bl-planet-line"></span>复制博客地址</div>
|
||||
<div @click="createUrl('tempVisit')"><span class="iconbl bl-visit"></span>创建临时访问(3h)</div>
|
||||
<div @click="handleShowACustomTempVisitDialog"><span class="iconbl bl-visit"></span>创建临时访问(自定义)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="curDoc.ty === 3 && curDoc.o === 1" @click="createUrl('open')"><span class="iconbl bl-planet-line"></span>博客中查看</div>
|
||||
@ -224,6 +221,18 @@
|
||||
:close-on-click-modal="true">
|
||||
<ArticleSearch @open-article="openArticle" @create-link="createUrlLink"></ArticleSearch>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 自定义临时访问链接 -->
|
||||
<el-dialog
|
||||
v-model="isShowACustomTempVisitDialog"
|
||||
width="400"
|
||||
style="height: 200px"
|
||||
:align-center="true"
|
||||
:append-to-body="true"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="true">
|
||||
<ArticleCustomTempVisit ref="ArticleCustomTempVisitRef" @created="tempVisitCreated"></ArticleCustomTempVisit>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -232,7 +241,7 @@ import { useServerStore } from '@renderer/stores/server'
|
||||
import { useUserStore } from '@renderer/stores/user'
|
||||
import { useConfigStore } from '@renderer/stores/config'
|
||||
import { ref, provide, onBeforeUnmount, nextTick, computed } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { MenuInstance } from 'element-plus'
|
||||
import { ArrowDownBold, ArrowRightBold } from '@element-plus/icons-vue'
|
||||
import {
|
||||
@ -270,6 +279,7 @@ import ArticleQrCode from './ArticleQrCode.vue'
|
||||
import ArticleInfo from './ArticleInfo.vue'
|
||||
import ArticleImport from './ArticleImport.vue'
|
||||
import ArticleSearch from './ArticleSearch.vue'
|
||||
import ArticleCustomTempVisit from './ArticleCustomTempVisit.vue'
|
||||
|
||||
const server = useServerStore()
|
||||
const user = useUserStore()
|
||||
@ -804,6 +814,22 @@ const openArticle = (article: DocTree) => {
|
||||
})
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region ----------------------------------------< 临时访问时长 >--------------------------------------
|
||||
const isShowACustomTempVisitDialog = ref(false)
|
||||
const ArticleCustomTempVisitRef = ref()
|
||||
const handleShowACustomTempVisitDialog = () => {
|
||||
isShowACustomTempVisitDialog.value = true
|
||||
nextTick(() => {
|
||||
ArticleCustomTempVisitRef.value.reload(curDoc.value.n, curDoc.value.i)
|
||||
})
|
||||
}
|
||||
|
||||
const tempVisitCreated = () => {
|
||||
isShowACustomTempVisitDialog.value = false
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const clickCurDoc = (tree: DocTree) => {
|
||||
emits('clickDoc', tree)
|
||||
}
|
||||
|
||||
@ -29,8 +29,8 @@ window.blconfig = {
|
||||
* 服务器的地址
|
||||
*/
|
||||
DOMAIN: {
|
||||
// 将该值填写为你的后台访问地址, 与 blossom 客户端登录页面填写的地址相同
|
||||
PRD: 'http://localhost:9999',
|
||||
// 将该值填写为你的后台访问地址, 与 blossom 客户端登录 页面填写的地址相同
|
||||
PRD: 'http://192.168.31.6:9999',
|
||||
// 将该值填写你开放为博客的用户ID
|
||||
USER_ID: 1
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user