【Go + Nuxt.js 搭建一个 BBS 系统】9. 搭建帖子模块
点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。
- 购买地址:https://www.shiyanlou.com/courses/1436
- 购买九折优惠邀请码:
ZHwfIjb1
作者简介
大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货:
实验介绍
实验内容
相信通过上一个实验已经能够独立的完成一个完整的模块开发了,那么本章节的帖子模块开发对你来也许是一个体力活。接下来,我们继续基于上一个实验的源码来完成帖子模块。
知识点
- 帖子模块服务端接口开发
- 帖子模块Nuxt.js页面开发
服务端功能开发
同样的Go语言的服务端帖子模块相关代码我们分为以下三部分:
- 结构体:topic_model
- 业务服务:topic_service
- 控制器:topic_controller
结构体(topic_model)
新建文件server/topic_model.go
,在该文件中定义帖子结构体,完整代码如下:
package main
import "time"
type Topic struct {
Id int64 `gorm:"PRIMARY_KEY;AUTO_INCREMENT" json:"id"` // 编号
UserId int64 `gorm:"not null" json:"userId"` // 作者编号
Title string `gorm:"size:128;not null" json:"title"` // 标题
Content string `gorm:"type:longtext;not null" json:"content"` // 内容
CreateTime time.Time `gorm:"not null" json:"createTime"` // 创建时间
}
帖子结构体定义完成之后不要忘记将Topic
结构体放入gorm
的AutoMigrate
中,这样启动服务的时候gorm
会自动创建topic表,打开server/main.go
,修改gorm
配置如下:
db.AutoMigrate(&User{}, &UserToken{}, &Topic{})
业务服务(topic_service)
新建文件server/topic_service.go
,在该文件中我们去实现帖子相关的业务逻辑,并进行数据库的读写操作,完整代码如下:
package main
import (
"time"
)
var TopicService = &topicService{}
type topicService struct {
}
// 创建帖子
func (topicService) Create(userId int64, title, content string) (*Topic, error) {
topic := &Topic{
UserId: userId,
Title: title,
Content: content,
CreateTime: time.Now(),
}
err := db.Create(topic).Error
if err != nil {
return nil, err
}
return topic, nil
}
// 根据编号获取帖子
func (topicService) Get(id int64) *Topic {
ret := &Topic{}
if err := db.First(ret, "id = ?", id).Error; err != nil {
return nil
}
return ret
}
// 查询帖子列表
func (topicService) List(page int) (topics []Topic, totalCount int) {
if page <= 0 {
page = 1
}
offset := 20 * (page - 1)
db.Order("id desc").Offset(offset).Limit(20).Find(&topics) // 查列表
db.Model(&Topic{}).Count(&totalCount) // 查计数
return
}
控制器(topic_controller)
新建文件server/topic_controller.go
,该文件中定义帖子相关接口,完整代码如下:
package main
import (
"github.com/kataras/iris/context"
"github.com/mlogclub/simple"
)
type TopicController struct {
Ctx context.Context
}
// 创建帖子
func (this *TopicController) PostAdd() *simple.JsonResult {
// 获取当前登录用户,发帖必须要求用户已经登录了
user := UserService.GetCurrent(this.Ctx)
if user == nil {
return simple.JsonErrorMsg("请先登录")
}
var (
title = this.Ctx.FormValue("title")
content = this.Ctx.FormValue("content")
)
topic, err := TopicService.Create(user.Id, title, content)
if err != nil {
return simple.JsonErrorMsg(err.Error())
}
return simple.JsonData(topic)
}
// 根据id获取
func (this *TopicController) GetBy(id int64) *simple.JsonResult {
topic := TopicService.Get(id)
if topic == nil {
return simple.JsonErrorMsg("帖子不存在")
}
user := UserService.Get(topic.UserId)
return simple.NewRspBuilder(topic).Put("user", user).JsonResult()
}
// 帖子列表
func (this *TopicController) GetListBy(page int) *simple.JsonResult {
topics, totalCount := TopicService.List(page)
return simple.NewEmptyRspBuilder().Put("topics", topics).Put("totalCount", totalCount).JsonResult()
}
完成topic_controller
功能开发之后,需要将topic_controller
添加到iris
路由中,打开server/main.go
文件,修改代码如下:
mvc.Configure(app.Party("/api/topic"), func(mvcApp *mvc.Application) {
mvcApp.Handle(new(TopicController))
})
启动接口服务
综上我们已经完成帖子模块服务端相关接口开发,下面我们来启动服务,命令如下:
➜ server git:(master) ✗ go run *.go
Now listening on: http://localhost:8081
Application started. Press CMD+C to shut down.
Nuxt.js页面功能开发
上面我们完成了帖子模块服务端接口开发,并且成功启动了接口服务,接下来我们使用Nuxt.js
来完成页面开发。
发表帖子页面
新建文件site/pages/topic/create.vue
,完整代码如下:
<template>
<section>
<my-nav/>
<section class="section">
<div class="container">
<div class="field">
<label class="label">标题</label>
<div class="control">
<input v-model="form.title" class="input" type="text" placeholder="请输入帖子标题">
</div>
</div>
<div class="field">
<label class="label">内容</label>
<div class="control">
<textarea v-model="form.content" class="textarea has-fixed-size" rows="10"
placeholder="请输入帖子内容"></textarea>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link" @click="createTopic">发表</button>
</div>
</div>
</div>
</section>
<my-footer/>
</section>
</template>
<script>
import MyNav from '~/components/MyNav'
import MyFooter from '~/components/MyFooter'
export default {
components: {
MyNav, MyFooter
},
data () {
return {
form: {
title: '',
content: ''
}
}
},
methods: {
async createTopic () {
try {
const resp = await this.$axios.post('/api/topic/add', this.form)
console.log('发表成功', resp)
this.$router.push('/topic/' + resp.id) // 发表成功后跳转到帖子详情页
} catch (err) {
alert(err.message || err)
}
}
}
}
</script>
启动前端页面服务之后,访问路径/topic/create
就可以看到页面效果,如下图:
接下来我们就可以发表一篇帖子啦,当然发表之前你需要先登录。
帖子详情页面
新建文件site/pages/topic/_id.vue
,完整代码如下:
<template>
<section>
<my-nav/>
<section class="section">
<div class="container">
<article>
<div class="title">{{ topic.title }} <span class="meta">By {{topic.user.nickname}} @ {{topic.createTime}}</span></div>
<pre class="content">{{ topic.content }}</pre>
</article>
</div>
</section>
<my-footer/>
</section>
</template>
<script>
import MyNav from '~/components/MyNav'
import MyFooter from '~/components/MyFooter'
export default {
components: {
MyNav, MyFooter
},
async asyncData ({params, $axios}) {
try {
const topicId = params.id // 从动态路由参数中获取帖子id
const topic = await $axios.get('/api/topic/' + topicId)
return {
topic: topic
}
} catch (err) {
console.log(err)
}
}
}
</script>
<style scoped>
article .title {
font-weight: bold;
font-size: 15px;
border-bottom: 2px #f7f8fb dashed;
padding-bottom: 10px;
}
article .title .meta {
font-weight: normal;
font-size: 12px;
color: #3b8070;
}
article .content {
margin-top: 10px;
}
</style>
启动前端页面服务之后,访问路径/topic/1
就可以看到页面效果(路径中的1
是你刚刚发表的帖子编号,请按实际情况填写),页面效果如下图:
帖子列表页面
新建文件site/pages/topics/_page.vue
,完整代码如下:
<template>
<section>
<my-nav/>
<section class="section">
<div class="container">
<ul class="topics">
<li class="topic" v-for="topic in topics" :key="topic.id">
<a :href="'/topic/' + topic.id">{{topic.title}}<span class="meta">@{{topic.createTime}}</span></a>
</li>
</ul>
<nav class="pagination" role="navigation" aria-label="pagination">
<a class="pagination-previous" :href="page > 1 ? '/topics/' + (page - 1) : 'javascript:void(0)'">上一页</a>
<a class="pagination-next" :href="page < maxPage ? '/topics/' + (page + 1) : 'javascript:void(0)'">下一页</a>
</nav>
</div>
</section>
<my-footer/>
</section>
</template>
<script>
import MyNav from '~/components/MyNav'
import MyFooter from '~/components/MyFooter'
export default {
components: {
MyNav, MyFooter
},
async asyncData ({params, $axios}) {
try {
const page = params.page || 1 // 从动态路由参数中获取页码,如果没获取到默认认为是第一页
const resp = await $axios.get('/api/topic/list/' + page)
// 计算帖子的最大页码
const maxPage = resp.totalCount % 20 > 0 ? parseInt(resp.totalCount / 20) + 1 : resp.totalCount / 20
return {
topics: resp.topics,
page: parseInt(page), // 当前页码
maxPage: maxPage // 最大页码
}
} catch (err) {
console.log(err)
}
}
}
</script>
<style scoped>
.topics .topic {
padding: 10px 0px;
border-bottom: 1px solid #f7f8fb;
}
.topics .topic .meta {
font-weight: normal;
font-size: 12px;
color: #3b8070;
float: right;
}
.pagination {
margin-top: 20px;
}
</style>
启动前端页面服务之后,访问路径/topics/1
就可以看到页面效果,如下图:
列表页是有分页功能的哦,每页20条数据,多添加点数据之后可以看到分页效果。
总结
本实例中我们完成了整个帖子模块的功能。下面我们看下本实例完整源码的目录结构:
.
├── server
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── topic_controller.go
│ ├── topic_model.go
│ ├── topic_service.go
│ ├── user_controller.go
│ ├── user_model.go
│ └── user_service.go
└── site
├── README.md
├── assets
│ └── README.md
├── components
│ ├── Logo.vue
│ ├── MyFooter.vue
│ ├── MyNav.vue
│ └── README.md
├── jsconfig.json
├── layouts
│ ├── README.md
│ └── default.vue
├── middleware
│ └── README.md
├── nuxt.config.js
├── package-lock.json
├── package.json
├── pages
│ ├── README.md
│ ├── index.vue
│ ├── topic
│ │ ├── _id.vue
│ │ └── create.vue
│ ├── topics
│ │ └── _page.vue
│ └── user
│ ├── login.vue
│ └── reg.vue
├── plugins
│ ├── README.md
│ └── axios.js
├── static
│ ├── README.md
│ └── favicon.ico
└── store
└── README.md
文章转载请注明出处,原文链接:https://mlog.club/topic/651