🚀 添加文章的加密访问

master
linhaojun 3 years ago
parent 0113c32a87
commit 448005287c

@ -57,4 +57,8 @@ public class RedisPrefixConst {
*/
public static final String LOGIN_USER = "login_user";
/**
* 访
*/
public static final String USER_ARTICLE_ACCESS = "article_access";
}

@ -57,6 +57,13 @@ public class ArticleController {
return Result.ok(articleService.getArticleById(articleId));
}
@ApiOperation("校验文章访问密码")
@PostMapping("/articles/access")
public Result<?> accessArticle(@Valid @RequestBody ArticlePasswordVO articlePasswordVO) {
articleService.accessArticle(articlePasswordVO);
return Result.ok();
}
@ApiOperation("根据标签id获取文章")
@GetMapping("/articles/tagId")
public Result<PageResult<ArticleCardDTO>> listArticlesByTagId(@RequestParam Integer tagId) {
@ -139,5 +146,5 @@ public class ArticleController {
public Result<List<ArticleSearchDTO>> listArticlesBySearch(ConditionVO condition) {
return Result.ok(articleService.listArticlesBySearch(condition));
}
}

@ -24,6 +24,7 @@ public class ArticleCardDTO {
private UserInfo author;
private String categoryName;
private List<Tag> tags;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

@ -30,6 +30,7 @@ public class Article {
private Integer isDelete;
private Integer status;
private Integer type;
private String password;
private String originalUrl;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@ -43,13 +43,13 @@ public enum StatusCodeEnum {
*/
USERNAME_NOT_EXIST(52002, "用户名不存在"),
/**
* qq
*
*/
QQ_LOGIN_ERROR(53001, "qq登录错误"),
ARTICLE_ACCESS_FAIL(52003, "文章密码认证未通过"),
/**
*
* qq
*/
WEIBO_LOGIN_ERROR(53002, "微博登录错误");
QQ_LOGIN_ERROR(53001, "qq登录错误");
/**
*

@ -19,6 +19,8 @@ public interface ArticleService extends IService<Article> {
ArticleDTO getArticleById(Integer articleId);
void accessArticle(ArticlePasswordVO articlePasswordVO);
PageResult<ArticleCardDTO> listArticlesByTagId(Integer tagId);
PageResult<ArchiveDTO> listArchives();

@ -8,6 +8,7 @@ import com.aurora.entity.Category;
import com.aurora.entity.Tag;
import com.aurora.enums.FileExtEnum;
import com.aurora.enums.FilePathEnum;
import com.aurora.enums.StatusCodeEnum;
import com.aurora.exception.BizException;
import com.aurora.mapper.ArticleMapper;
import com.aurora.mapper.ArticleTagMapper;
@ -42,6 +43,7 @@ import java.util.stream.Collectors;
import static com.aurora.constant.MQPrefixConst.SUBSCRIBE_EXCHANGE;
import static com.aurora.constant.RedisPrefixConst.*;
import static com.aurora.enums.ArticleStatusEnum.*;
import static com.aurora.enums.StatusCodeEnum.ARTICLE_ACCESS_FAIL;
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
@ -115,6 +117,21 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
@SneakyThrows
@Override
public ArticleDTO getArticleById(Integer articleId) {
Article articleForCheck = articleMapper.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articleId));
if (Objects.isNull(articleForCheck)) {
return null;
}
if (articleForCheck.getStatus().equals(2)) {
Boolean isAccess;
try {
isAccess = redisService.sIsMember(USER_ARTICLE_ACCESS + ":" + UserUtils.getUserDetailsDTO().getId(), articleId);
} catch (Exception exception) {
throw new BizException(ARTICLE_ACCESS_FAIL);
}
if (isAccess.equals(false)) {
throw new BizException(ARTICLE_ACCESS_FAIL);
}
}
updateArticleViewsCount(articleId);
CompletableFuture<ArticleDTO> asyncArticle = CompletableFuture.supplyAsync(() -> articleMapper.getArticleById(articleId));
CompletableFuture<ArticleCardDTO> asyncPreArticle = CompletableFuture.supplyAsync(() -> {
@ -144,6 +161,19 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
return article;
}
@Override
public void accessArticle(ArticlePasswordVO articlePasswordVO) {
Article article = articleMapper.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articlePasswordVO.getArticleId()));
if (Objects.isNull(article)) {
throw new BizException("文章不存在");
}
if (article.getPassword().equals(articlePasswordVO.getArticlePassword())) {
redisService.sAdd(USER_ARTICLE_ACCESS + ":" + UserUtils.getUserDetailsDTO().getId(), articlePasswordVO.getArticleId());
} else {
throw new BizException("密码错误");
}
}
@SneakyThrows
@Override
public PageResult<ArticleCardDTO> listArticlesByTagId(Integer tagId) {

@ -58,6 +58,7 @@ public class TokenServiceImpl implements TokenService {
redisService.hSet(LOGIN_USER, userId, userDetailsDTO, expireTime);
}
@Override
public void renewToken(UserDetailsDTO userDetailsDTO) {
LocalDateTime expireTime = userDetailsDTO.getExpireTime();
LocalDateTime currentTime = LocalDateTime.now();

@ -0,0 +1,16 @@
package com.aurora.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArticlePasswordVO {
private Integer articleId;
private String articlePassword;
}

@ -8,6 +8,7 @@
<result property="articleContent" column="article_content"/>
<result property="isTop" column="is_top"/>
<result property="isFeatured" column="is_featured"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="categoryName" column="category_name"/>
@ -65,6 +66,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -91,7 +93,7 @@
LEFT JOIN t_category c ON a.category_id = c.id
LEFT JOIN t_user_info u ON a.user_id = u.id
where a.is_delete = 0
and a.status = 1
and a.status in (1, 2)
order by is_top desc, is_featured desc
</select>
<select id="listArticles" resultMap="ArticleCardDTOResultMap">
@ -101,6 +103,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -122,7 +125,7 @@
update_time
FROM t_article
where is_delete = 0
and status = 1
and status in (1, 2)
order by id desc
LIMIT #{current} , #{size}) a
LEFT JOIN t_article_tag at
@ -138,6 +141,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -160,7 +164,7 @@
FROM t_article
WHERE category_id = #{categoryId}
and is_delete = 0
and status = 1
and status in (1, 2)
order by id desc
LIMIT #{current} , #{size}) a
LEFT JOIN t_article_tag at
@ -202,7 +206,7 @@
FROM t_article
WHERE id = #{articleId}
and is_delete = 0
and status = 1) a
and status in (1, 2)) a
LEFT JOIN t_article_tag at
ON a.id = at.article_id
LEFT JOIN t_tag t ON t.id = at.tag_id
@ -216,6 +220,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -238,7 +243,7 @@
FROM t_article
WHERE id &lt; #{articleId}
and is_delete = 0
and status = 1
and status in (1, 2)
order by id desc
limit 0, 1) a
LEFT JOIN t_article_tag at
@ -254,6 +259,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -276,7 +282,7 @@
FROM t_article
WHERE id &gt; #{articleId}
and is_delete = 0
and status = 1
and status in (1, 2)
order by id
limit 0,1) a
LEFT JOIN t_article_tag at
@ -292,6 +298,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -313,7 +320,7 @@
update_time
FROM t_article
WHERE is_delete = 0
and status = 1
and status in (1, 2)
order by id
limit 0,1) a
LEFT JOIN t_article_tag at
@ -329,6 +336,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -350,7 +358,7 @@
update_time
FROM t_article
WHERE is_delete = 0
and status = 1
and status in (1, 2)
order by id desc
limit 0,1) a
LEFT JOIN t_article_tag at
@ -367,6 +375,7 @@
SUBSTR(article_content, 1, 500) AS article_content,
is_top,
is_featured,
status,
a.create_time AS create_time,
a.update_time AS update_time,
u.nickname AS author_nickname,
@ -380,7 +389,9 @@
LEFT JOIN t_tag t ON t.id = at.tag_id
LEFT JOIN t_category c ON a.category_id = c.id
LEFT JOIN t_user_info u ON a.user_id = u.id
WHERE at.tag_id = #{tagId} and a.is_delete = 0 and status = 1
WHERE at.tag_id = #{tagId}
and a.is_delete = 0
and status in (1, 2)
LIMIT #{current} , #{size}
</select>
<select id="listArchives" resultType="com.aurora.dto.ArticleCardDTO">

@ -134,5 +134,8 @@ export default {
},
updatePassword: (params: any) => {
return axios.put('/api/users/password', params)
},
accessArticle:(params:any)=>{
return axios.post('/api/articles/access',params)
}
}

@ -40,9 +40,9 @@
</ul>
</span>
<router-link v-if="article.articleTitle" :to="'/articles/' + article.id">
<h1 data-dia="article-link">{{ article.articleTitle }}</h1>
</router-link>
<h1 class="article-title" v-if="article.articleTitle" @click="toArticle" data-dia="article-link">
<a>{{ article.articleTitle }}</a>
</h1>
<ob-skeleton v-else tag="h1" height="3rem" />
<p v-if="article.articleContent">{{ article.articleContent }}</p>
@ -81,15 +81,21 @@
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, getCurrentInstance } from 'vue'
import { useAppStore } from '@/stores/app'
import { computed, defineComponent, toRefs } from 'vue'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import emitter from '@/utils/mitt'
export default defineComponent({
name: 'ArticleCard',
props: ['data'],
setup(props) {
const proxy: any = getCurrentInstance()?.appContext.config.globalProperties
const appStore = useAppStore()
const userStore = useUserStore()
const router = useRouter()
const { t } = useI18n()
const handleAuthorClick = (link: string) => {
@ -97,12 +103,35 @@ export default defineComponent({
window.open(link)
}
const toArticle = () => {
let isAccess = false
userStore.accessArticles.forEach((item: any) => {
if (item == props.data.id) {
isAccess = true
}
})
if (props.data.status === 2 && isAccess == false) {
if (userStore.userInfo === '') {
proxy.$notify({
title: 'Warning',
message: '该文章受密码保护,请登录后访问',
type: 'warning'
})
} else {
emitter.emit('changeArticlePasswordDialogVisible', props.data.id)
}
} else {
router.push({ path: '/articles/' + props.data.id })
}
}
return {
gradientBackground: computed(() => {
return { background: appStore.themeConfig.header_gradient_css }
}),
article: toRefs(props).data,
handleAuthorClick,
toArticle,
t
}
}
@ -110,9 +139,7 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.feature-sign {
width: calc(100% - 0.5rem);
height: calc(100% - 0.5rem);
margin: 0.25rem;
.article-title:hover {
cursor: default;
}
</style>
</style>

@ -100,6 +100,16 @@
<span class="text" @click="returnLoginDialog"></span>
</el-form>
</el-dialog>
<el-dialog v-model="articlePasswordDialogVisible" width="30%" :fullscreen="isMobile">
<el-form>
<el-form-item model="userInfo" class="mt-5">
<el-input v-model="articlePassword" placeholder="文章受密码保护,请输入密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="accessArticle" size="large" class="mx-auto mt-3">校验密码</el-button>
</el-form-item>
</el-form>
</el-dialog>
<teleport to="body">
<SearchModel />
</teleport>
@ -111,13 +121,14 @@ import { Dropdown, DropdownMenu, DropdownItem } from '@/components/Dropdown'
import { useAppStore } from '@/stores/app'
import { useCommonStore } from '@/stores/common'
import { useUserStore } from '@/stores/user'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import ThemeToggle from '@/components/ToggleSwitch/ThemeToggle.vue'
import api from '@/api/api'
import SearchModel from '@/components/SearchModel.vue'
import { useSearchStore } from '@/stores/search'
import config from '@/config/config'
import { useI18n } from 'vue-i18n'
import emitter from '@/utils/mitt'
export default defineComponent({
name: 'Controls',
@ -136,6 +147,7 @@ export default defineComponent({
const userStore = useUserStore()
const searchStore = useSearchStore()
const route = useRoute()
const router = useRouter()
const loginInfo = reactive({
username: '' as any,
password: '' as any,
@ -144,7 +156,10 @@ export default defineComponent({
const reactiveDate = reactive({
loginDialogVisible: false,
registerDialogVisible: false,
forgetPasswordDialogVisible: false
forgetPasswordDialogVisible: false,
articlePasswordDialogVisible: false,
articlePassword: '',
articleId: ''
})
const handleClick = (name: string): void => {
@ -191,9 +206,22 @@ export default defineComponent({
const logout = () => {
api.logout().then(({ data }) => {
userStore.userInfo = ''
userStore.token = ''
sessionStorage.removeItem('token')
if (data.flag) {
userStore.userInfo = ''
userStore.token = ''
sessionStorage.removeItem('token')
proxy.$notify({
title: 'Success',
message: '登出成功',
type: 'success'
})
} else {
proxy.$notify({
title: 'Error',
message: data.message,
type: 'error'
})
}
})
}
@ -301,6 +329,40 @@ export default defineComponent({
})
}
emitter.on('changeArticlePasswordDialogVisible', (articleId: any) => {
reactiveDate.articlePasswordDialogVisible = true
reactiveDate.articleId = articleId
})
const accessArticle = () => {
if (reactiveDate.articlePassword.trim().length == 0) {
proxy.$notify({
title: 'Warning',
message: '密码不能为空',
type: 'warning'
})
return
}
api
.accessArticle({
articleId: reactiveDate.articleId,
articlePassword: reactiveDate.articlePassword
})
.then(({ data }) => {
if (data.flag) {
reactiveDate.articlePasswordDialogVisible = false
userStore.accessArticles.push(reactiveDate.articleId)
router.push({ path: '/articles/' + reactiveDate.articleId })
} else {
proxy.$notify({
title: 'Error',
message: data.message,
type: 'error'
})
}
})
}
return {
handleOpenModel,
loginInfo,
@ -319,6 +381,7 @@ export default defineComponent({
register,
updatePassword,
openForgetPasswordDialog,
accessArticle,
multiLanguage: computed(() => {
let websiteConfig: any = appStore.websiteConfig
return websiteConfig.multiLanguage
@ -425,4 +488,4 @@ export default defineComponent({
.avatar-img:hover {
transform: rotate(360deg);
}
</style>
</style>

@ -2,7 +2,7 @@
<div class="flex flex-row items-center hover:opacity-50 mr-2 mb-2 cursor-pointer transition-all">
<router-link
class="bg-ob-deep-900 text-center px-3 py-1 rounded-tl-md rounded-bl-md text-sm"
:to="{path:'/article-list/'+id,query:{tagName:name}}"
:to="{ path: '/article-list/' + id, query: { tagName: name } }"
:style="stylingTag()">
<em class="opacity-50">#</em>
{{ name }}
@ -69,4 +69,4 @@ export default defineComponent({
return { stylingTag }
}
})
</script>
</script>

@ -6,7 +6,8 @@ export const useUserStore = defineStore('userStore', {
currentUrl: '' as any,
userVisible: false,
userInfo: '' as any,
token: '' as any
token: '' as any,
accessArticles: [] as any
}
},
actions: {},

@ -143,7 +143,18 @@
<script lang="ts">
import { Sidebar, Profile, Navigator } from '@/components/Sidebar'
import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, reactive, ref, toRefs, provide } from 'vue'
import {
computed,
defineComponent,
nextTick,
onBeforeUnmount,
onMounted,
reactive,
ref,
toRefs,
provide,
getCurrentInstance
} from 'vue'
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Comment } from '@/components/Comment'
@ -165,6 +176,7 @@ export default defineComponent({
name: 'Article',
components: { Sidebar, Comment, SubTitle, ArticleCard, Profile, Sticky, Navigator },
setup() {
const proxy: any = getCurrentInstance()?.appContext.config.globalProperties
const metaStore = useMetaStore()
const commonStore = useCommonStore()
const commentStore = useCommentStore()
@ -233,6 +245,15 @@ export default defineComponent({
const fetchArticle = async (id: any) => {
api.getArticeById(id).then(({ data }) => {
if (data.code === 52003) {
proxy.$notify({
title: 'Error',
message: '文章密码认证未通过',
type: 'error'
})
router.push({ path: '/出错啦' })
return
}
if (data.data === null) {
router.push({ path: '/出错啦' })
return
@ -471,4 +492,4 @@ export default defineComponent({
.my-gap {
gap: 1rem;
}
</style>
</style>
Loading…
Cancel
Save