文章订阅加密,本地文件上传访问

master
haitao 1 year ago
parent f15a625569
commit 51ef94166c

@ -54,6 +54,12 @@
- 优化:文件上传模块改造,每次上传之前获取上传密钥,每个密钥只能上传一个文件
- 优化个别Bug修复
### 2023年10月1日更新
- 新增:文章加密
- 新增:文章订阅
- 新增:文件上传模块改造,支持多平台(目前对接本地)
- 优化:友人帐及其他模块样式调整
### 首页
![首页](首页.jpg)
@ -110,6 +116,8 @@ npm run build
一定要`Star`
注意:[poetize.cn](https://poetize.cn)可能会下线,也可能不会,看缘分吧。
## 欢迎进群一定要Star
1. 交流(摸鱼)
2. 安装部署:互相帮助,争取每个人都零基础拥有自己的个人网站

@ -5,7 +5,7 @@
<link rel="icon" href="./poetize.jpg" sizes="16x16">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Sara</title>
<title>Poetize</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script>

@ -53,6 +53,10 @@
<el-input maxlength="30" v-model="article.password"></el-input>
</el-form-item>
<el-form-item v-if="article.viewStatus === false" label="密码提示" prop="tips">
<el-input maxlength="60" v-model="article.tips"></el-input>
</el-form-item>
<el-form-item label="封面" prop="articleCover">
<div style="display: flex">
<el-input v-model="article.articleCover"></el-input>
@ -112,6 +116,7 @@
recommendStatus: false,
viewStatus: true,
password: "",
tips: "",
articleCover: "",
sortId: null,
labelId: null
@ -176,11 +181,39 @@
suffix = file.name.substring(file.name.lastIndexOf('.'));
}
let key = "articlePicture" + "/" + this.$store.state.currentAdmin.username.replace(/[^a-zA-Z]/g, '') + this.$store.state.currentAdmin.id + new Date().getTime() + Math.floor(Math.random() * 1000) + suffix;
let storeType = localStorage.getItem("defaultStoreType");
let fd = new FormData();
fd.append("file", file);
fd.append("key", key);
fd.append("relativePath", key);
fd.append("type", "articlePicture");
fd.append("storeType", storeType);
this.$http.get(this.$constant.baseURL + "/qiniu/getUpToken", {key: key}, true)
if (storeType === "local") {
this.saveLocal(pos, fd);
} else if (storeType === "qiniu") {
this.saveQiniu(pos, fd);
}
},
saveLocal(pos, fd) {
this.$http.upload(this.$constant.baseURL + "/resource/upload", fd, true)
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
let url = res.data;
this.$refs.md.$img2Url(pos, url);
}
})
.catch((error) => {
this.$message({
message: error.message,
type: "error"
});
});
},
saveQiniu(pos, fd) {
this.$http.get(this.$constant.baseURL + "/qiniu/getUpToken", {key: fd.get("key")}, true)
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
fd.append("token", res.data);
@ -189,7 +222,8 @@
.then((res) => {
if (!this.$common.isEmpty(res.key)) {
let url = this.$constant.qiniuDownload + res.key;
this.$common.saveResource(this, "articlePicture", url, file.size, file.type, true);
let file = fd.get("file");
this.$common.saveResource(this, "articlePicture", url, file.size, file.type, "qiniu", true);
this.$refs.md.$img2Url(pos, url);
}
})

@ -58,6 +58,7 @@
</template>
</el-table-column>
<el-table-column prop="mimeType" label="类型" align="center"></el-table-column>
<el-table-column prop="storeType" label="存储平台" align="center"></el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center"></el-table-column>
<el-table-column label="操作" width="180" align="center">
<template slot-scope="scope">
@ -85,7 +86,19 @@
destroy-on-close
center>
<div>
<div style="display: flex;margin-bottom: 10px">
<div style="line-height: 40px">存储平台</div>
<el-select v-model="storeType" placeholder="存储平台" style="width: 120px">
<el-option
v-for="(item, i) in storeTypes"
:key="i"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<uploadPicture :isAdmin="true" :prefix="pagination.resourceType" @addPicture="addPicture" :maxSize="5"
:storeType="storeType"
:maxNumber="10"></uploadPicture>
</div>
</el-dialog>
@ -109,7 +122,12 @@
resourceType: ""
},
resources: [],
resourceDialog: false
resourceDialog: false,
storeTypes: [
{label: "服务器", value: "local"},
{label: "七牛云", value: "qiniu"}
],
storeType: localStorage.getItem("defaultStoreType")
}
},

@ -99,7 +99,7 @@
<div style="margin-bottom: 5px">标题</div>
<el-input maxlength="60" v-model="resourcePath.title"></el-input>
<div style="margin-top: 10px;margin-bottom: 5px">分类</div>
<el-input :disabled="!['lovePhoto', 'funny', 'favorites'].includes(resourcePath.type)"
<el-input :disabled="!['friendUrl', 'lovePhoto', 'funny', 'favorites'].includes(resourcePath.type)"
maxlength="30" v-model="resourcePath.classify"></el-input>
<div style="margin-top: 10px;margin-bottom: 5px">简介</div>
<el-input :disabled="!['friendUrl', 'favorites'].includes(resourcePath.type)"

@ -1,4 +1,5 @@
<template>
<div>
<div v-if="!$common.isEmpty(article)">
<!-- 封面 -->
<div class="article-head my-animation-slide-top">
@ -145,9 +146,9 @@
版权声明转载请注明文章出处
</div>
</blockquote>
<!-- 点赞 -->
<div class="myCenter" id="article-like">
<i class="el-icon-thumb article-like-icon" :class="{'article-like': article.likeCount}"></i>
<!-- 订阅 -->
<div class="myCenter" id="article-like" @click="subscribeLabel()">
<i class="el-icon-thumb article-like-icon" :class="{'article-like': subscribe}"></i>
</div>
<!-- 评论 -->
@ -185,6 +186,32 @@
</div>
</el-dialog>
</div>
<!-- 微信 -->
<el-dialog title="密码"
:modal="false"
:visible.sync="showPasswordDialog"
width="25%"
:append-to-body="true"
destroy-on-close
center>
<div>
<div>
<div class="password-content">{{tips}}</div>
</div>
<div style="margin: 20px auto">
<el-input maxlength="30" v-model="password"></el-input>
</div>
<div style="display: flex;justify-content: center">
<proButton :info="'提交'"
@click.native="submitPassword()"
:before="$constant.before_color_2"
:after="$constant.after_color_2">
</proButton>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
@ -192,6 +219,7 @@
const comment = () => import( "./comment/comment");
const process = () => import( "./common/process");
const commentBox = () => import( "./comment/commentBox");
const proButton = () => import( "./common/proButton");
import MarkdownIt from 'markdown-it';
export default {
@ -199,26 +227,91 @@
myFooter,
comment,
commentBox,
proButton,
process
},
data() {
return {
id: this.$route.query.id,
subscribe: false,
article: {},
articleContentHtml: "",
treeHoleList: [],
weiYanDialogVisible: false,
newsTime: ""
newsTime: "",
showPasswordDialog: false,
password: "",
tips: ""
};
},
created() {
this.getArticle();
if (!this.$common.isEmpty(this.id)) {
this.getArticle(localStorage.getItem("article_password_" + this.id));
if ("0" !== localStorage.getItem("showSubscribe")) {
this.$notify({
title: '文章订阅',
type: 'success',
message: '点击文章下方小手 - 订阅/取消订阅专栏(标签)',
duration: 0,
onClose: () => localStorage.setItem("showSubscribe", "0")
});
}
}
},
mounted() {
// window.addEventListener("scroll", this.onScrollPage);
},
methods: {
subscribeLabel() {
if (this.$common.isEmpty(this.$store.state.currentUser)) {
this.$message({
message: "请先登录!",
type: "error"
});
return;
}
this.$confirm('确认' + (this.subscribe ? '取消订阅' : '订阅') + '专栏【' + this.article.label.labelName + '】?' + (this.subscribe ? "" : "订阅专栏后,该专栏发布新文章将通过邮件通知订阅用户。"), this.subscribe ? "取消订阅" : "文章订阅", {
confirmButtonText: '确定',
cancelButtonText: '取消',
center: true
}).then(() => {
this.$http.get(this.$constant.baseURL + "/user/subscribe", {
labelId: this.article.labelId,
flag: !this.subscribe
})
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
this.$store.commit("loadCurrentUser", res.data);
}
this.subscribe = !this.subscribe;
})
.catch((error) => {
this.$message({
message: error.message,
type: "error"
});
});
}).catch(() => {
this.$message({
type: 'success',
message: '已取消!'
});
});
},
submitPassword() {
if (this.$common.isEmpty(this.password)) {
this.$message({
message: "请先输入密码!",
type: "error"
});
return;
}
this.getArticle(this.password);
},
deleteTreeHole(id) {
if (this.$common.isEmpty(this.$store.state.currentUser)) {
this.$message({
@ -334,8 +427,8 @@
let headings = $(".entry-content").find("h1, h2, h3, h4, h5, h6");
headings.attr('id', (i, id) => id || 'toc-' + i);
},
getArticle() {
this.$http.get(this.$constant.baseURL + "/article/getArticleById", {id: this.id, flag: true})
getArticle(password) {
this.$http.get(this.$constant.baseURL + "/article/getArticleById", {id: this.id, password: password})
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
this.article = res.data;
@ -348,13 +441,31 @@
// todo toc
// this.getTocbot();
});
if (!this.$common.isEmpty(password)) {
localStorage.setItem("article_password_" + this.id, password);
}
this.showPasswordDialog = false;
if (!this.$common.isEmpty(this.$store.state.currentUser) && !this.$common.isEmpty(this.$store.state.currentUser.subscribe)) {
this.subscribe = JSON.parse(this.$store.state.currentUser.subscribe).includes(this.article.labelId);
}
}
})
.catch((error) => {
if ("密码错误" === error.message.substr(0, 4)) {
if (!this.$common.isEmpty(password)) {
this.$message({
message: "密码错误,请重新输入!",
type: "error"
});
}
this.tips = error.message.substr(4);
this.showPasswordDialog = true;
} else {
this.$message({
message: error.message,
type: "error"
});
}
});
},
highlight() {
@ -583,6 +694,12 @@
border-bottom: unset;
}
.password-content {
font-size: 13px;
color: var(--maxGreyFont);
line-height: 1.5;
}
@media screen and (max-width: 700px) {
.article-info-container {
left: 20px;

@ -266,11 +266,41 @@
}
let obj = new Blob([u8arr], {type: mine});
let key = "graffiti" + "/" + this.$store.state.currentUser.username.replace(/[^a-zA-Z]/g, '') + this.$store.state.currentUser.id + new Date().getTime() + Math.floor(Math.random() * 1000) + ".png";
let storeType = localStorage.getItem("defaultStoreType");
let fd = new FormData();
fd.append("file", obj);
fd.append("key", key);
fd.append("relativePath", key);
fd.append("type", "graffiti");
fd.append("storeType", storeType);
this.$http.get(this.$constant.baseURL + "/qiniu/getUpToken", {key: key})
if (storeType === "local") {
this.saveLocal(fd);
} else if (storeType === "qiniu") {
this.saveQiniu(fd);
}
},
saveLocal(fd) {
this.$http.upload(this.$constant.baseURL + "/resource/upload", fd)
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
this.clearContext();
let url = res.data;
let img = "<你画我猜," + url + ">";
this.$emit("addGraffitiComment", img);
}
})
.catch((error) => {
this.$message({
message: error.message,
type: "error"
});
});
},
saveQiniu(fd) {
this.$http.get(this.$constant.baseURL + "/qiniu/getUpToken", {key: fd.get("key")})
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
fd.append("token", res.data);
@ -280,7 +310,8 @@
if (!this.$common.isEmpty(res.key)) {
this.clearContext();
let url = this.$constant.qiniuDownload + res.key;
this.$common.saveResource(this, "graffiti", url, obj.size, obj.type);
let file = fd.get("file");
this.$common.saveResource(this, "graffiti", url, file.size, file.type, "qiniu");
let img = "<你画我猜," + url + ">";
this.$emit("addGraffitiComment", img);
}

@ -65,6 +65,10 @@
type: String,
default: "picture"
},
storeType: {
type: String,
default: localStorage.getItem("defaultStoreType")
},
accept: {
type: String,
default: "image/*"
@ -107,6 +111,16 @@
let key = this.prefix + "/" + (!this.$common.isEmpty(this.$store.state.currentUser.username) ? (this.$store.state.currentUser.username.replace(/[^a-zA-Z]/g, '') + this.$store.state.currentUser.id) : (this.$store.state.currentAdmin.username.replace(/[^a-zA-Z]/g, '') + this.$store.state.currentAdmin.id)) + new Date().getTime() + Math.floor(Math.random() * 1000) + suffix;
if (this.storeType === "local") {
let fd = new FormData();
fd.append("file", options.file);
fd.append("key", key);
fd.append("relativePath", key);
fd.append("type", this.prefix);
fd.append("storeType", this.storeType);
return this.$http.upload(this.$constant.baseURL + "/resource/upload", fd, this.isAdmin);
} else if (this.storeType === "qiniu") {
const xhr = new XMLHttpRequest();
xhr.open('get', this.$constant.baseURL + "/qiniu/getUpToken?key=" + key, false);
if (this.isAdmin) {
@ -132,12 +146,18 @@
} catch (e) {
return Promise.reject(e.message);
}
}
},
//
handleSuccess(response, file, fileList) {
let url = this.$constant.qiniuDownload + response.key;
this.$common.saveResource(this, this.prefix, url, file.size, file.raw.type, this.isAdmin);
let url;
if (this.storeType === "local") {
url = response.data;
} else if (this.storeType === "qiniu") {
url = this.$constant.qiniuDownload + response.key;
this.$common.saveResource(this, this.prefix, url, file.size, file.raw.type, "qiniu", this.isAdmin);
}
this.$emit("addPicture", url);
},
handleError(err, file, fileList) {

@ -67,10 +67,34 @@
<img class="after-img" :src="$constant.friendLetterBottom" style="width: 100%"/>
</div>
<div style="font-size: 20px;font-weight: bold;margin-top: 40px">🌸本站信息</div>
<div>
<blockquote>
<div>网站名称: $$$$POETIZE</div>
<div>网址: $$$$https://poetize.cn</div>
<div>头像: $$$$https://s1.ax1x.com/2022/11/10/z9E7X4.jpg</div>
<div>描述: $$$$这是一个 Vue2 Vue3 SpringBoot 结合的产物</div>
<div>网站封面: $$$$https://s1.ax1x.com/2022/11/10/z9VlHs.png</div>
</blockquote>
</div>
<div style="font-size: 20px;font-weight: bold">🌸申请方式</div>
<div>
<blockquote>
<div>点击上方信封</div>
<div>不会添加带有广告营销和没有实质性内容的友链🚫🚫🚫</div>
<div>申请之前请将本网站添加为您的友链哦🎟🎟🎟</div>
</blockquote>
</div>
<hr>
<h2>🥇友情链接</h2>
<card :resourcePathList="friendList" @clickResourcePath="clickFriend"></card>
<h2 style="margin-top: 60px">青出于蓝</h2>
<card :resourcePathList="friendList['♥️青出于蓝']" @clickResourcePath="clickFriend"></card>
<hr>
<h2 style="margin-top: 60px">🥇友情链接</h2>
<card :resourcePathList="friendList['🥇友情链接']" @clickResourcePath="clickFriend"></card>
</div>
</div>
@ -93,13 +117,7 @@
data() {
return {
pagination: {
current: 1,
size: 9999,
desc: false,
resourceType: "friendUrl"
},
friendList: [],
friendList: {},
friend: {
title: "",
introduction: "",
@ -189,10 +207,10 @@
window.open(path);
},
getFriends() {
this.$http.post(this.$constant.baseURL + "/webInfo/listResourcePath", this.pagination)
this.$http.get(this.$constant.baseURL + "/webInfo/listFriend")
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
this.friendList = res.data.records;
this.friendList = res.data;
}
})
.catch((error) => {
@ -342,6 +360,16 @@
padding: 20px 0;
}
blockquote {
line-height: 2;
border-left: 0.2rem solid #ed6ea0;
padding: 10px 1rem;
background-color: #ffe6fa;
border-radius: 4px;
margin: 20px auto;
color: var(--maxGreyFont);
}
@media screen and (max-width: 700px) {
.form-wrap {
width: auto;

@ -371,7 +371,7 @@
});
} else {
let userToken = this.$common.encrypt(localStorage.getItem("userToken"));
window.open(this.$constant.imBaseURL + "?userToken=" + userToken);
window.open(this.$constant.imBaseURL + "?userToken=" + userToken + "&defaultStoreType=" + localStorage.getItem("defaultStoreType"));
}
},
logout() {
@ -393,6 +393,7 @@
.then((res) => {
if (!this.$common.isEmpty(res.data)) {
this.$store.commit("loadWebInfo", res.data);
localStorage.setItem("defaultStoreType", res.data.defaultStoreType);
}
})
.catch((error) => {

@ -24,7 +24,7 @@
</div>
<!-- 搜索 -->
<div style="padding: 15px;border-radius: 10px;margin-top: 40px;animation: hideToShow 1s ease-in-out"
<div style="padding: 15px;border-radius: 10px;margin-top: 30px;animation: hideToShow 1s ease-in-out"
class="shadow-box background-opacity wow">
<div style="color: var(--lightGreen);font-size: 20px;font-weight: bold;margin-bottom: 10px">
搜索
@ -48,7 +48,7 @@
<!-- 推荐文章 -->
<div v-if="!$common.isEmpty(recommendArticles)"
style="padding: 25px;border-radius: 10px;margin-top: 40px;animation: hideToShow 1s ease-in-out"
style="padding: 25px;border-radius: 10px;margin-top: 30px;animation: hideToShow 1s ease-in-out"
class="shadow-box background-opacity wow">
<div class="card-content2-title">
<i class="el-icon-reading card-content2-icon"></i>
@ -77,39 +77,13 @@
</div>
</div>
<!-- 赞赏 -->
<div class="shadow-box-mini background-opacity wow admire-box"
v-if="!$common.isEmpty(admires)">
<div style="font-weight: bold;margin-bottom: 20px">🧨赞赏名单</div>
<div>
<vue-seamless-scroll :data="admires" style="height: 200px;overflow: hidden">
<div v-for="(item, i) in admires"
style="display: flex;justify-content: space-between"
:key="i">
<div style="display: flex">
<el-avatar style="margin-bottom: 10px" :size="36" :src="item.avatar"></el-avatar>
<div style="margin-left: 10px;height: 36px;line-height: 36px;overflow: hidden;max-width: 80px">
{{ item.username }}
</div>
</div>
<div style="height: 36px;line-height: 36px">
{{ item.admire }}
</div>
</div>
</vue-seamless-scroll>
</div>
<div class="admire-btn" @click="showAdmire()">
赞赏
</div>
</div>
<!-- 速览 -->
<div v-for="(sort, index) in sortInfo"
@click="selectSort(sort)"
:key="index"
:style="{background: $constant.sortColor[index % $constant.sortColor.length]}"
class="shadow-box-mini background-opacity wow"
style="position: relative;padding: 20px 25px 40px;border-radius: 10px;animation: hideToShow 1s ease-in-out;margin-top: 40px;cursor: pointer;color: var(--white)">
style="position: relative;padding: 10px 25px 15px;border-radius: 10px;animation: hideToShow 1s ease-in-out;margin-top: 30px;cursor: pointer;color: var(--white)">
<div>速览</div>
<div class="sort-name">
{{sort.sortName}}
@ -121,7 +95,7 @@
<!-- 分类 -->
<div class="shadow-box background-opacity wow"
style="margin-top: 40px;padding: 25px 25px 5px;border-radius: 10px;animation: hideToShow 1s ease-in-out">
style="margin-top: 30px;padding: 25px 25px 5px;border-radius: 10px;animation: hideToShow 1s ease-in-out">
<div class="card-content2-title">
<i class="el-icon-folder-opened card-content2-icon"></i>
<span>分类</span>
@ -136,6 +110,32 @@
</div>
</div>
<!-- 赞赏 -->
<div class="shadow-box-mini background-opacity wow admire-box"
v-if="!$common.isEmpty(admires)">
<div style="font-weight: bold;margin-bottom: 20px">🧨赞赏名单</div>
<div>
<vue-seamless-scroll :data="admires" style="height: 200px;overflow: hidden">
<div v-for="(item, i) in admires"
style="display: flex;justify-content: space-between"
:key="i">
<div style="display: flex">
<el-avatar style="margin-bottom: 10px" :size="36" :src="item.avatar"></el-avatar>
<div style="margin-left: 10px;height: 36px;line-height: 36px;overflow: hidden;max-width: 80px">
{{ item.username }}
</div>
</div>
<div style="height: 36px;line-height: 36px">
{{ item.admire }}
</div>
</div>
</vue-seamless-scroll>
</div>
<div class="admire-btn" @click="showAdmire()">
赞赏
</div>
</div>
<!-- 微信 -->
<el-dialog title="赞赏"
:visible.sync="showAdmireDialog"
@ -380,14 +380,14 @@
.sort-name {
font-weight: bold;
font-size: 25px;
margin-top: 30px;
margin-top: 15px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.sort-name:after {
top: 98px;
top: 74px;
width: 22px;
left: 26px;
height: 2px;
@ -402,7 +402,7 @@
padding: 25px;
border-radius: 10px;
animation: hideToShow 1s ease-in-out;
margin-top: 40px;
margin-top: 30px;
}
.admire-btn {

@ -140,12 +140,13 @@ export default {
/**
* 保存资源
*/
saveResource(that, type, path, size, mimeType, isAdmin = false) {
saveResource(that, type, path, size, mimeType, storeType, isAdmin = false) {
let resource = {
type: type,
path: path,
size: size,
mimeType: mimeType
mimeType: mimeType,
storeType: storeType
};
that.$http.post(that.$constant.baseURL + "/resource/saveResource", resource, isAdmin)

@ -90,11 +90,13 @@ export default {
let config;
if (isAdmin) {
config = {
headers: {"Authorization": localStorage.getItem("adminToken"), "Content-Type": "multipart/form-data"}
headers: {"Authorization": localStorage.getItem("adminToken"), "Content-Type": "multipart/form-data"},
timeout: 60000
};
} else {
config = {
headers: {"Authorization": localStorage.getItem("userToken"), "Content-Type": "multipart/form-data"}
headers: {"Authorization": localStorage.getItem("userToken"), "Content-Type": "multipart/form-data"},
timeout: 60000
};
}
return new Promise((resolve, reject) => {

Loading…
Cancel
Save