黑马头条--day02--2文章详情

一.上传之前的配置

1.上传js和css文件

在minio中创建leadnews桶,

在leadnews下面创建/plugins目录,在该目录下面分别创建js和css目录,

也就是/plugins/css和/plugins/js,向css中上传以下index.css:

html {
    overflow-x: hidden;
}

#app {
    position: relative;
    width: 750px;
    margin: 0 auto;
    color: #333;
    background-color: #f8f8f8;
}

.article {
    padding: 0 40px 120px;
}

.article-title {
    margin-top: 48px;
    font-size: 40px;
    font-weight: bold;
    color: #3A3A3A;
    line-height: 65px;
}

.article-header {
    margin-top: 57px;
}

.article-content {
    margin-top: 39px;
}

.article-avatar {
    width: 70px;
    height: 70px;
}

.article-author {
    font-size: 28px;
    font-weight: 400;
    color: #3A3A3A;
}

.article-publish-time {
    font-size: 24px;
    font-weight: 400;
    color: #B4B4B4;
}

.article-focus {
    width: 170px;
    height: 58px;
    font-size: 28px;
    font-weight: 400;
    color: #FFFFFF;
}

.article-text {
    font-size: 32px;
    font-weight: 400;
    color: #3A3A3A;
    line-height: 56px;
    text-align: justify;
}

.article-action {
    margin-top: 59px;
}

.article-like {
    width: 156px;
    height: 58px;
    font-size: 25px;
    font-weight: 400;
    color: #777777;
}

.article-unlike {
    width: 156px;
    height: 58px;
    margin-left: 42px;
    font-size: 25px;
    font-weight: 400;
    color: #E22829;
}

.article-comment {
    margin-top: 69px;
}

.comment-author {
    font-size: 24px;
    font-weight: 400;
    color: #777777;
    line-height: 49px;
}

.comment-content {
    font-size: 32px;
    font-weight: 400;
    color: #3A3A3A;
    line-height: 49px;
}

.comment-time {
    font-size: 24px;
    font-weight: 400;
    color: #B4B4B4;
    line-height: 49px;
}

.article-comment-reply {
    padding: 40px;
}

.article-bottom-bar, .comment-reply-bottom-bar {
    position: fixed;
    bottom: 0;
    width: 750px;
    height: 99px;
    background: #F4F5F6;
}

.article-bottom-bar .van-field, .comment-reply-bottom-bar .van-field {
    width: 399px;
    height: 64px;
    background: #FFFFFF;
    border: 2px solid #EEEEEE;
    border-radius: 32px;
    font-size: 25px;
    font-weight: 400;
    color: #777777;
}

.article-bottom-bar .van-button, .comment-reply-bottom-bar .van-button {
    background-color: transparent;
    border-color: transparent;
    font-size: 25px;
    font-weight: 400;
    color: #777777;
}

在/plugins/js目录下上传以下index.js文件

// 初始化 Vue 实例
new Vue({
  el: '#app',
  data() {
    return {
      // Minio模板应该写真实接口地址
      baseUrl: 'http://192.168.200.150:51601', //'http://172.16.17.191:5001',
      token: '',
      equipmentId: '',
      articleId: '',
      title: '',
      authorId: 0,
      authorName: '',
      publishTime: '',
      relation: {
        islike: false,
        isunlike: false,
        iscollection: false,
        isfollow: false,
        isforward: false
      },
      followLoading: false,
      likeLoading: false,
      unlikeLoading: false,
      collectionLoading: false,
      // 评论
      comments: [],
      commentsLoading: false,
      commentsFinished: false,
      commentValue: '',
      currentCommentId: '',
      // 评论回复
      commentReplies: [],
      commentRepliesLoading: false,
      commentRepliesFinished: false,
      commentReplyValue: '',
      showPopup: false
    }
  },
  filters: {
    // TODO: js计算时间差
    timestampToDateTime: function (value) {
      if (!value) return ''

      const date = new Date(value)
      const Y = date.getFullYear() + '-'
      const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
      const D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' '
      const h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':'
      const m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':'
      const s = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds())

      return Y + M + D + h + m + s
    }
  },
  created() {
    this.token = this.getQueryVariable('token')
    this.equipmentId = this.getQueryVariable('equipmentId')
    this.articleId = this.getQueryVariable('articleId')
    this.title = this.getQueryVariable('title')
    const authorId = this.getQueryVariable('authorId')
    if (authorId) {
      this.authorId = parseInt(authorId, 10)
    }
    this.authorName = this.getQueryVariable('authorName')
    const publishTime = this.getQueryVariable('publishTime')
    if (publishTime) {
      this.publishTime = parseInt(publishTime, 10)
    }
    this.loadArticleBehavior()
	this.readArticleBehavior()
  },
  
  methods: {
    // 加载文章评论
    async loadArticleComments(index = 1, minDate = 20000000000000) {
      const url = `${this.baseUrl}/comment/api/v1/comment/load`
      const data = { articleId: this.articleId, index: index, minDate: minDate }
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: { code, errorMessage, data: comments } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        if (comments.length) {
          this.comments = this.comments.concat(comments)
        }

        // 加载状态结束
        this.commentsLoading = false;

        // 数据全部加载完成
        if (!comments.length) {
          this.commentsFinished = true
        }
      } catch (err) {
        this.commentsLoading = false
        this.commentsFinished = true
        console.log('err: ' + err)
      }
    },
    // 滚动加载文章评论
    onLoadArticleComments() {
      let index = undefined
      let minDate = undefined
      if (this.comments.length) {
        index = 2
        minDate = this.comments[this.comments.length - 1].createdTime
      }
      this.loadArticleComments(index, minDate)
    },
    // 加载文章行为
    async loadArticleBehavior() {
      const url = `${this.baseUrl}/article/api/v1/article/load_article_behavior/`
      const data = { equipmentId: this.equipmentId, articleId: this.articleId, authorId: this.authorId }
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: { code, errorMessage, data: relation } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        this.relation = relation
      } catch (err) {
        console.log('err: ' + err)
      }
    },
	//阅读文章行为
	async readArticleBehavior(){
		const url = `${this.baseUrl}/behavior/api/v1/read_behavior`
		const data = {equipmentId:this.equipmentId,articleId:this.articleId,count:1,readDuration:0,percentage:0,loadDuration:0}
		const config = {headers:{'token':this.token}}
		
		try{
			const {status,data:{code,errorMessage}} = await axios.post(url,data,config)
			if(status !== 200){
				vant.Toast.fail("当前系统正在维护,请稍后重试")
				return
			}
			if(code !== 0){
				vant.Toast.fail(errorMessage)
				return
			}
		}catch (err){
			console.log('err: '+ err)
		}
	},
    // 关注/取消关注
    async handleClickArticleFollow() {
      const url = `${this.baseUrl}/user/api/v1/user/user_follow/`
      const data = { authorId: this.authorId, operation: this.relation.isfollow ? 1 : 0, articleId: this.articleId }
      const config = { headers: { 'token': this.token } }

      this.followLoading = true
      try {
        const { status, data: { code, errorMessage } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        this.relation.isfollow = !this.relation.isfollow
        vant.Toast.success(this.relation.isfollow ? '成功关注' : '成功取消关注')
      } catch (err) {
        console.log('err: ' + err)
      }
      this.followLoading = false
    },
    // 点赞/取消赞
    async handleClickArticleLike() {
      const url = `${this.baseUrl}/behavior/api/v1/likes_behavior/`
      const data = { equipmentId: this.equipmentId, articleId: this.articleId, type: 0, operation: this.relation.islike ? 1 : 0 }
      const config = { headers: { 'token': this.token } }

      this.likeLoading = true
      try {
        const { status, data: { code, errorMessage } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        this.relation.islike = !this.relation.islike
        vant.Toast.success(this.relation.islike ? '点赞操作成功' : '取消点赞操作成功')
      } catch (err) {
        console.log('err: ' + err)
      }
      this.likeLoading = false
    },
    // 不喜欢/取消不喜欢
    async handleClickArticleUnlike() {
      const url = `${this.baseUrl}/behavior/api/v1/un_likes_behavior/`
      const data = { equipmentId: this.equipmentId, articleId: this.articleId, type: this.relation.isunlike ? 1 : 0 }
      const config = { headers: { 'token': this.token } }

      this.unlikeLoading = true
      try {
        const { status, data: { code, errorMessage } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        this.relation.isunlike = !this.relation.isunlike
        vant.Toast.success(this.relation.isunlike ? '不喜欢操作成功' : '取消不喜欢操作成功')
      } catch (err) {
        console.log('err: ' + err)
      }
      this.unlikeLoading = false
    },
    // 提交评论
    async handleSaveComment() {
      if (!this.commentValue) {
        vant.Toast.fail('评论内容不能为空')
        return
      }
      if (this.commentValue.length > 140) {
        vant.Toast.fail('评论字数不能超过140字')
        return
      }
      const url = `${this.baseUrl}/comment/api/v1/comment/save`
      const data = { articleId: this.articleId, content: this.commentValue }
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: {  code, errorMessage } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        vant.Toast.success('评论成功')
        this.commentValue = ''
		this.comments = []
        this.loadArticleComments()
		this.commentsFinished = false;
      } catch (err) {
        console.log('err: ' + err)
      }
    },
    // 页面滚动到评论区
    handleScrollIntoCommentView() {
      document.getElementById('#comment-view').scrollIntoView({ behavior: 'smooth' })
    },
    // 收藏/取消收藏
    async handleClickArticleCollection() {
      const url = `${this.baseUrl}/article/api/v1/collection_behavior/`
      const data = { equipmentId: this.equipmentId, entryId: this.articleId, publishedTime: this.publishTime, type: 0, operation: this.relation.iscollection ? 1 :0 }
      const config = { headers: { 'token': this.token } }

      this.collectionLoading = true
      try {
        const { status, data: { code, errorMessage } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        this.relation.iscollection = !this.relation.iscollection
        vant.Toast.success(this.relation.iscollection ? '收藏操作成功' : '取消收藏操作成功')
      } catch (err) {
        console.log('err: ' + err)
      }
      this.collectionLoading = false
    },
    // 评论点赞
    async handleClickCommentLike(comment) {
      const commentId = comment.id
      const operation = comment.operation === 0 ? 1 : 0

      const url = `${this.baseUrl}/comment/api/v1/comment/like`
      const data = { commentId: comment.id, operation: operation }
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: { code, errorMessage, data: { likes } } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        const item = this.comments.find((item) => {
          return item.id === commentId
        })
        item.operation = operation
        item.likes = likes
        vant.Toast.success((operation === 0 ? '点赞' : '取消点赞') + '操作成功!')
      } catch (err) {
        console.log('err: ' + err)
      }
    },
    // 弹出评论回复Popup
    showCommentRepliesPopup(commentId) {
      this.showPopup = true;
      this.currentCommentId = commentId
      this.commentReplies = []
      this.commentRepliesFinished = false
    },
    // 加载评论回复
    async loadCommentReplies(minDate = 20000000000000) {
      const url = `${this.baseUrl}/comment/api/v1/comment_repay/load`
      const data = { commentId: this.currentCommentId, 'minDate':  minDate}
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: { code, errorMessage, data: commentReplies } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        if (commentReplies.length) {
          this.commentReplies = this.commentReplies.concat(commentReplies)
        }

        // 加载状态结束
        this.commentRepliesLoading = false;

        // 数据全部加载完成
        if (!commentReplies.length) {
          this.commentRepliesFinished = true
        }
      } catch (err) {
        this.commentRepliesLoading = false
        this.commentRepliesFinished = true
        console.log('err: ' + err)
      }
    },
    // 滚动加载评论回复
    onLoadCommentReplies() {
      let minDate = undefined
      if (this.commentReplies.length) {
        minDate = this.commentReplies[this.commentReplies.length - 1].createdTime
      }
      this.loadCommentReplies(minDate)
    },
    // 提交评论回复
    async handleSaveCommentReply() {
      if (!this.commentReplyValue) {
        vant.Toast.fail('评论内容不能为空')
        return
      }
      if (this.commentReplyValue.length > 140) {
        vant.Toast.fail('评论字数不能超过140字')
        return
      }
      const url = `${this.baseUrl}/comment/api/v1/comment_repay/save`
      const data = { commentId: this.currentCommentId, content: this.commentReplyValue }
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: {  code, errorMessage } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        vant.Toast.success('评论成功')
        this.commentReplyValue = ''
		this.commentReplies = []
		this.comments = []
        // 刷新评论回复列表
        this.loadCommentReplies()
        // 刷新文章评论列表
        this.loadArticleComments()
      } catch (err) {
        console.log('err: ' + err)
      }
    },
    // 评论回复点赞
    async handleClickCommentReplyLike(commentReply) {
      const commentReplyId = commentReply.id
      const operation = commentReply.operation === 0 ? 1 : 0

      const url = `${this.baseUrl}/comment/api/v1/comment_repay/like`
      const data = { commentRepayId: commentReplyId, 'operation': operation }
      const config = { headers: { 'token': this.token } }

      try {
        const { status, data: { code, errorMessage, data: { likes } } } = await axios.post(url, data, config)
        if (status !== 200) {
          vant.Toast.fail('当前系统正在维护,请稍后重试')
          return
        }
        if (code !== 200) {
          vant.Toast.fail(errorMessage)
          return
        }
        const item = this.commentReplies.find((item) => {
          return item.id === commentReplyId
        })
        item.operation = operation
        item.likes = likes
        vant.Toast.success((operation === 0 ? '点赞' : '取消点赞') + '操作成功!')
      } catch (err) {
        console.log('err: ' + err)
      }
    },
    getQueryVariable(aVariable) {
      const query = decodeURI(window.location.search).substring(1)
      const array = query.split('&')
      for (let i = 0; i < array.length; i++) {
        const pair = array[i].split('=')
        if (pair[0] == aVariable) {
          return pair[1]
        }
      }
      return undefined
    },
    // onSelect(option) {
    //   vant.Toast(option.name);
    //   this.showShare = false;
    // }
  }
})

  2.在article微服务模块的resources目录下面创建/templates目录,创建articel.ftl模版文件:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
    <title>黑马头条</title>
    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.12.20/lib/index.css">
    <!-- 页面样式 -->
    <link rel="stylesheet" href="../../../plugins/css/index.css">
</head>

<body>
<div id="app">
    <div class="article">
        <van-row>
            <van-col span="24" class="article-title" v-html="title"></van-col>
        </van-row>

        <van-row type="flex" align="center" class="article-header">
            <van-col span="3">
                <van-image round class="article-avatar" src="https://p3.pstatp.com/thumb/1480/7186611868"></van-image>
            </van-col>
            <van-col span="16">
                <div v-html="authorName"></div>
                <div>{{ publishTime | timestampToDateTime }}</div>
            </van-col>
            <van-col span="5">
                <van-button round :icon="relation.isfollow ? '' : 'plus'" type="info" class="article-focus"
                            :text="relation.isfollow ? '取消关注' : '关注'" :loading="followLoading" @click="handleClickArticleFollow">
                </van-button>
            </van-col>
        </van-row>

        <van-row class="article-content">
            <#if content??>
                <#list content as item>
                    <#if item.type='text'>
                        <van-col span="24" class="article-text">${item.value}</van-col>
                    <#else>
                        <van-col span="24" class="article-image">
                            <van-image width="100%" src="${item.value}"></van-image>
                        </van-col>
                    </#if>
                </#list>
            </#if>
        </van-row>

        <van-row type="flex" justify="center" class="article-action">
            <van-col>
                <van-button round :icon="relation.islike ? 'good-job' : 'good-job-o'" class="article-like"
                            :loading="likeLoading" :text="relation.islike ? '取消赞' : '点赞'" @click="handleClickArticleLike"></van-button>
                <van-button round :icon="relation.isunlike ? 'delete' : 'delete-o'" class="article-unlike"
                            :loading="unlikeLoading" @click="handleClickArticleUnlike">不喜欢</van-button>
            </van-col>
        </van-row>

        <!-- 文章评论列表 -->
        <van-list v-model="commentsLoading" :finished="commentsFinished" finished-text="没有更多了"
                  @load="onLoadArticleComments">
            <van-row id="#comment-view" type="flex" class="article-comment" v-for="(item, index) in comments" :key="index">
                <van-col span="3">
                    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
                </van-col>
                <van-col span="21">
                    <van-row type="flex" align="center" justify="space-between">
                        <van-col class="comment-author" v-html="item.authorName"></van-col>
                        <van-col>
                            <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
                                        @click="handleClickCommentLike(item)">{{ item.likes || '' }}
                            </van-button>
                        </van-col>
                    </van-row>

                    <van-row>
                        <van-col class="comment-content" v-html="item.content"></van-col>
                    </van-row>
                    <van-row type="flex" align="center">
                        <van-col span="10" class="comment-time">
                            {{ item.createdTime | timestampToDateTime }}
                        </van-col>
                        <van-col span="3">
                            <van-button round size="normal" v-html="item.reply" @click="showCommentRepliesPopup(item.id)">回复 {{
                                item.reply || '' }}
                            </van-button>
                        </van-col>
                    </van-row>
                </van-col>
            </van-row>
        </van-list>
    </div>
    <!-- 文章底部栏 -->
    <van-row type="flex" justify="space-around" align="center" class="article-bottom-bar">
        <van-col span="13">
            <van-field v-model="commentValue" placeholder="写评论">
                <template #button>
                    <van-button icon="back-top" @click="handleSaveComment"></van-button>
                </template>
            </van-field>
        </van-col>
        <van-col span="3">
            <van-button icon="comment-o" @click="handleScrollIntoCommentView"></van-button>
        </van-col>
        <van-col span="3">
            <van-button :icon="relation.iscollection ? 'star' : 'star-o'" :loading="collectionLoading"
                        @click="handleClickArticleCollection"></van-button>
        </van-col>
        <van-col span="3">
            <van-button icon="share-o"></van-button>
        </van-col>
    </van-row>

    <!-- 评论Popup 弹出层 -->
    <van-popup v-model="showPopup" closeable position="bottom"
               :style="{ width: '750px', height: '60%', left: '50%', 'margin-left': '-375px' }">
        <!-- 评论回复列表 -->
        <van-list v-model="commentRepliesLoading" :finished="commentRepliesFinished" finished-text="没有更多了"
                  @load="onLoadCommentReplies">
            <van-row id="#comment-reply-view" type="flex" class="article-comment-reply"
                     v-for="(item, index) in commentReplies" :key="index">
                <van-col span="3">
                    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
                </van-col>
                <van-col span="21">
                    <van-row type="flex" align="center" justify="space-between">
                        <van-col class="comment-author" v-html="item.authorName"></van-col>
                        <van-col>
                            <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
                                        @click="handleClickCommentReplyLike(item)">{{ item.likes || '' }}
                            </van-button>
                        </van-col>
                    </van-row>

                    <van-row>
                        <van-col class="comment-content" v-html="item.content"></van-col>
                    </van-row>
                    <van-row type="flex" align="center">
                        <!-- TODO: js计算时间差 -->
                        <van-col span="10" class="comment-time">
                            {{ item.createdTime | timestampToDateTime }}
                        </van-col>
                    </van-row>
                </van-col>
            </van-row>
        </van-list>
        <!-- 评论回复底部栏 -->
        <van-row type="flex" justify="space-around" align="center" class="comment-reply-bottom-bar">
            <van-col span="13">
                <van-field v-model="commentReplyValue" placeholder="写评论">
                    <template #button>
                        <van-button icon="back-top" @click="handleSaveCommentReply"></van-button>
                    </template>
                </van-field>
            </van-col>
            <van-col span="3">
                <van-button icon="comment-o"></van-button>
            </van-col>
            <van-col span="3">
                <van-button icon="star-o"></van-button>
            </van-col>
            <van-col span="3">
                <van-button icon="share-o"></van-button>
            </van-col>
        </van-row>
    </van-popup>
</div>

<!-- 引入 Vue 和 Vant 的 JS 文件 -->
<script src=" https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js">
</script>
<script src="https://cdn.jsdelivr.net/npm/vant@2.12.20/lib/vant.min.js"></script>
<!-- 引入 Axios 的 JS 文件 -->
<#--<script src="https://unpkg.com/axios/dist/axios.min.js"></script>-->
<script src="../../../plugins/js/axios.min.js"></script>
<!-- 页面逻辑 -->
<script src="../../../plugins/js/index.js"></script>
</body>

</html>

 3.测试文章内容生成html文件上传到minio

3.1.查看文章内容格式

我们把数据库中的文章内容格式化一下: 

[
  {
    "type": "text",
    "value": "杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸"
  },
  {
    "type": "image",
    "value": "http://192.168.200.130/group1/M00/00/00/wKjIgl892wKAZLhtAASZUi49De0836.jpg"
  },
  {
    "type": "text",
    "value": "杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸杨澜回应一秒变脸"
  },
  {
    "type": "text",
    "value": "请在这里输入正文"
  }
]

我们发现当type为text时,是文本内容,当type是image时,是图片,所以可以通过判断来组建一个html文章网页

 3.2测试文章生成上传Minio

@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {

    @Autowired
    private Configuration configuration;

    @Autowired
    private MinIoTemplate minIoTemplate;


    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Test
    public void createStaticUrlTest() throws Exception {
        //1.获取文章内容
        ApArticleContent apArticleContent =
                apArticleContentMapper.selectOne(
                        Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1302977558807060482L)
                   );

        //TODO
        System.out.println(apArticleContent);

        if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
            //2.文章内容通过freemarker生成html文件
            StringWriter out = new StringWriter();
            Template template = configuration.getTemplate("article.ftl");

            Map<String, Object> params = new HashMap<>();
            params.put("content", JSONArray.parseArray(apArticleContent.getContent()));

            template.process(params, out);
            InputStream is = new ByteArrayInputStream(out.toString().getBytes());

            //3.把html文件上传到minio中
            String path = minIoTemplate.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", is);
            //TODO
            System.out.println("上传成功:" + path);

            //4.修改ap_article表,保存static_url字段
            ApArticle article = new ApArticle();
            article.setId(apArticleContent.getArticleId());
            article.setStaticUrl(path);
            apArticleMapper.updateById(article);
            System.out.println(path);

        }
    }
}

流程:

1.先从数据库中根据文章id从文章内容表中将文章内容查询出来返回封装为文章对象

2.通过freemarker将文章对象中的值和模版对应,生成一个html静态页面

3.然后将该页面上传到minio中,返回上传成功后访问的url路径

4.在把文章信息表中的url路径修改为这个路径

5.之后前端在访问文章详情的时候,就可以通过该路径url去minio中获取html页面了

 因为有些文件它在线访问很慢可能被拒绝访问,所以我们把模版文件中需要引入的文件都上传到minio里面:

还有一个vant的css文件

 

 在加上之前的css文件,一共是6个文件,后面我会把这六个文件上传到资源里面,按照目录结构排版好,以及article.ftl模版文件

等我们都配置好之后,我们再去访问文章详情:

可以看到已经成功访问到文章详情了,以及里面包含的图片, 

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值