Beego开发博客
一、bee工具的使用
1、安装bee工具
go get -u github.com/beego/bee/v2
//安装完成后直接在终端输入bee获取环境
安装完之后,bee 可执行文件默认存放在 $GOPATH/bin 里面,所以您需要把 $GOPATH/bin 添加到您的环境变量中,才可以进行下一步。
2、new命令
new 命令是新建一个 Web 项目,我们在命令行下执行 bee new <项目名> 就可以创建一个新的项目。但是注意该命令必须在 $GOPATH/src 下执行。最后会在 $GOPATH/src 相应目录下生成如下目录结构的项目:
>bee new Blog
注:生成api项目的时候使用bee api Blog
3、bee run命令
bee run 命令是监控 beego 的项目,通过 fsnotify监控文件系统。但是注意该命令必须在 $GOPATH/src/appname 下执行。
二、下载前端模板
1、使用bootstrap书写;
2、layui下载;
3、使用模板技术对模板进行修改
举例:引入css,将引入的css代码放在link.html中实现代码公用
{{ template "common/link.html" }}
三、定义路由
本博客使用自动路由
web.AutoRouter(&controllers.ObjectController{})
四、导航条的光标移动
1、执行逻辑之Prepare设置
package controllers
import beego "github.com/beego/beego/v2/server/web"
type BaseController struct{
beego.Controller
}
func (c *BaseController) Prepare(){
c.Data["path"]=c.Ctx.Request.RequestURI //获取网页的路径
}
//然后在模板中获取值
{{ .path }}
2、模板自定义函数
beego 支持用户定义模板函数,但是必须在 web.Run() 调用之前
func initTemplate(){
beego.AddFuncMap("equrl",func(x string,y string)bool{
return strings.Compare(x,y)==0 //判断是不是相同
})
}
func main() {
initTemplate() //在Run前面调用模板函数
beego.Run()
}
直接在模板中调用equrl函数
<ul class="layui-nav pull-left">
<li class="layui-nav-item {{if equrl .path `/Auth/Index`}}layui-this{{ end }} "><a href="/Auth/Index">首页</a></li>
<li class="layui-nav-item {{if equrl .path `/Auth/Message`}}layui-this{{ end }} "><a href="/Auth/Message">留言</a></li>
<li class="layui-nav-item {{if equrl .path `/Auth/About`}}layui-this{{ end }} "><a href="/Auth/About">关于</a></li>
</ul>
五、数据库的初始化
package models
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var (
db *gorm.DB
)
func init(){ //初始化数据库连接,创建表结构
//使用dsn连接到数据库,grom自带的数据库池
//账号:密码@连接方式(ip地址:端口号)/数据库?语言方式,时区(未设置时区的话采用8小时制度)
var err error
dsn := "root:root@tcp(127.0.0.1:3306)/testgorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) //使用mysq连接数据库,第二个参数可以增加更多的配置(可有可无)
if err != nil {
fmt.Println(err)
}
db.AutoMigrate(&User{}) //创建User表
//判断如果数据库没有值,则增加管理员
if res:=db.Find(&User{});res.RowsAffected==0&&res.Error==nil{
db.Create(&User{
Name:"admin",
Email:"1102394156@qq.com",
Pwd:"123123",
Head:"/static/images/item.png",
})
}
}
六、Controller错误处理
1、web 框架默认支持 401、403、404、500、503 这几种错误的处理。用户可以自定义相应的错误处理
2、当直接使用Abort出现系统错误界面,使用路由导航后,可以自定义路由错误界面
//router函数
beego.ErrorController(&controllers.ErrorController{}) //定义错误路由
//errorController
package controllers
type ErrorController struct{
BaseController //组合预处理的
}
func (e *ErrorController) Error404(){
e.TplName="404.html"
}
3、自定义接口判断错误
原因:因为可能出现不是go语言自带的error错误类型,比如登录错误,所以需要自定义错误类型
设计思路:一般返回json格式(message与code和系统error),最后判断是不是系统出错来输出
(1)定义错误的接口,通过实现这个接口来返回错误,使其标准化
package syserror
type Error interface{
Code() int
Error() string
Syserr() error
}
func New(msg string,err error)Error{ //获取参数返回错误接口
return SetErr{msg, err}
}
(2)定义错误结构体实现接口,比如505错误,实现接口之后可以调用方法
package syserror
type SetErr struct{
msg string
err error
}
func (this SetErr) Code() int{
return 1000
}
func (this SetErr)Error() string{
if len(this.msg)==0{
return "未知错误"
}
return this.msg
}
func (this SetErr) Syserr() error{
return this.err
}
(3)判断Abort的错误类型,如果是error记录日志,如果是自定义错误,返回json字符串
func (e *ErrorController) Error500(){
e.TplName="500.html"
err,ok:=e.Data["error"].(error) //类型断言,判断是否是系统的error错误,不是系统错误就需要自定义
if !ok{
err=syserror.New("未知错误",nil) //因为接口Error接口实现了接口error的Error方法
}
serr,ok:=err.(syserror.Error) //判断是不是自定义错误,不是就返回系统错误
if !ok{
serr=syserror.New(err.Error(),nil)
}
if serr.Syserr()!=nil{ //返回的系统错误不为空,则直接记录日志
log.Info(serr.Syserr(),serr.Error())
}
if e.IsAjax() { //???
e.jsonErr(serr)
}else{
e.Data["content"]=serr.Error() //返回自定义错误
}
}
func (e *ErrorController) jsonErr(serr syserror.Error){ //传入接口封装
e.Ctx.Output.SetStatus(200) //设置状态码为200
e.Data["json"] = map[string]interface{}{
"code": serr.Code(),
"msg": serr.Error(), //返回自定义错误
}
e.ServeJSON()
}
七、配置session
1、直接定义函数设置session
func initSession(){
beego.BConfig.WebConfig.Session.SessionOn = true //设置是否开启 Session,默认是 false,配置文件对应的参数名:sessionon。
beego.BConfig.WebConfig.Session.SessionName="Blog" //设置 cookies 的名字,Session 默认是保存在用户的浏览器 cookies 里面的,默认名是 beegosessionID,配置文件对应的参数名是:sessionname。
beego.BConfig.WebConfig.Session.SessionProvider="file" //设置 Session 的引擎,默认是 memory,目前支持还有 file、mysql、redis 等,配置文件对应的参数名:sessionprovider。
beego.BConfig.WebConfig.Session.SessionProviderConfig="data/session" //设置对应 file、mysql、redis 引擎的保存路径或者链接地址,默认值是空,配置文件对应的参数:sessionproviderconfig。
}
func main() {
initSession()
initTemplate() //在Run前面调用模板函数
beego.Run()
}
2、配置文件修改
在conf中新建session.conf文件,设置配置文件
SessionOn=true #是否开启Session
SessionName="Blog" #设置Session的名字
SessionProvider="file" #设置储存在文件中
SessionProviderConfig="data/session" #设置储存目录
//设置完成后,在app.conf中调用
include "session.conf" #调用配置文件
八、用户登录功能
1、数据库交互查询用户是否存在
(1) 通过前端获取参数;
//单个参数
GetString(key string) string
GetStrings(key string) []string
GetInt(key string) (int64, error)
GetBool(key string) (bool, error)
GetFloat(key string) (float64, error)
//表单解析到结构体
定义 struct:
type user struct {
Id int `form:"-"`
Name interface{} `form:"username"`
Age int `form:"age"`
Email string
}
表单:
名字:<input name="username" type="text" />
年龄:<input name="age" type="text" />
邮箱:<input name="Email" type="text" />
<input type="submit" value="提交" />
Controller 里解析:
func (this *MainController) Post() {
u := user{}
if err := this.ParseForm(&u); err != nil {
//handle error
}
}
(3) 用户鉴权
//判断用户账号密码不为空
u.JugeNull(user.UserName,"账号不能为空")
u.JugeNull(user.PassWord,"密码不能为空")
//查询数据库中的是否有匹配的值
//首先判断是什么请求,如果是get直接跳转登录页面,post请求在获取值
if u.Ctx.Input.IsPost()
//获取到值,在定义的对象结构中封装函数查询自己
func QueryUser(name string,pwd string)(int64,error,User){
var user User
res:=db.Where("name=? and pwd=?",name,pwd).Take(&User{}).Scan(&user) //查询值并且扫描
return res.RowsAffected,res.Error,user
}
//判断
num,err,val:=models.QueryUser(user.UserName,user.PassWord)
if err!=nil||num==0{
fmt.Println(err)
u.Abort500(errors.New("账号或者密码错误"))
}
_ = u.SetSession(SESSION_USER_KEY, val) //将获得的user保存到session中
u.Redirect(beego.URLFor("AuthController.Index"), http.StatusFound) //重定向到首页
//记得登录成功后设置session
_ = u.SetSession(SESSION_USER_KEY, val) //将获得的user保存到session中
2、用户名称的实现
//beego的预定义Prepare函数中封装session
func (c *BaseController) Prepare(){
c.Data["path"]=c.Ctx.Request.RequestURI //获取网页的路径
u,ok:=c.GetSession(SESSION_USER_KEY).(models.User) //判断获取的session是不是User类型
c.IsLogin=false //设置值
if ok{
c.User=u
c.IsLogin=true
c.Data["User"]=c.User
}
c.Data["IsLogin"]=c.IsLogin
}
//使用模板技术获取值然后渲染页面
<i class="layui-icon layui-icon-username">
<span>{{ if .IsLogin }}{{ .User.Name }}{{ end }}</span>
</i>
3、登录重定向或者Abort报错
4、登出功能
func (c *UserController) LoginOut(){
_ = c.DelSession(SESSION_USER_KEY) //删除session
c.Redirect(beego.URLFor("UserController.Login"),302)
}
九、用户注册功能
(1)表单提交获取;
(2)鉴权;
//注册信息不能为空
//查询判断是否有重复账号
//
十、文章录入功能
1、写入页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>写文章</title>
{{ assets_css "/static/bootstrap-4.5.0/css/bootstrap.min.css" }}
</head>
<body>
<div class="container">
<div class="form-group row offset-4 col-4 mt-4">
<h2>文章发表</h2>
</div>
<div class="form-group row">
<label class="col-form-label col-2">文章标题:</label>
<input class="form-control col-8" name="title" placeholder="请输入文章标题" value="" />
</div>
<div class="form-group row">
<div class="form-group">
<input type="submit" class="btn btn-primary" value="上传文章"/>
</div>
</div>
</div>
</body>
</html>
2、管理员才允许更新文章
//在baseController中定义接口,实现这个接口的方法
type NestPrepare interface {
NestPrepare()
}
//在Prepare里面预定义,判断当前组合结构体是否实现了NestPrepare这个接口,如果实现了,就调用这个方法的函数
if a,ok:=c.AppController.(NestPrepare);ok{
a.NestPrepare()
}
//在更新文章的node路由上设置
type NodeController struct{
BaseController
}
func (c *NodeController) NestPrepare(){
c.MustLogin()
if c.User.Role!=0{
fmt.Println(c.User)
c.Abort500(errors.New("用户权限不足"))
}
}
3、wangEditor3编辑器的使用
//官方网址
https://www.kancloud.cn/wangfupeng/wangeditor3/332599
4、发送json给后台(可以通过表单)
<script language="JavaScript">
layui.use(['form','jquery'], function(){
var form = layui.form,
$=layui.$;
form.render();
//监听提交
form.on('submit(send)', function(rdata){
rdata.field.content=editor.txt.html();
$.post("/node/save",rdata.field,function (data){
if (data.code===0){
layer.msg("保存成功");
if(data.action){
setTimeout(function (){
window.location.href=data.action
},300)
}
}else {
layer.msg("保存失败");
}
},"json").error(function (){
layer.msg("网络异常");
})
return false;
})
})
</script>
5、防止多次点击(uuid的使用)
//"github.com/satori/go.uuid"
func (c *BaseController) UUID()string{
u:=uuid.NewV4() //随机生成uuid
return u.String()
}
6、保存标题和内容(判断文章是否存在?保存:新增)
if c.Ctx.Input.IsPost(){
var node models.Node
key:=c.Ctx.Input.Params() //路由设置的时候可以设置参数,可以获取相关的参数
fmt.Println("获取到的key值为",key["0"])
title:=c.JugeNull("title","标题不能为空")
content:=c.JugeNull("content","文章不能为空")
num,node,err:=models.QueryNodeByKey(key["0"]) //通过key查询是否存在
if err!=nil{
//if err==gorm.ErrRecordNotFound //找不到数据的情况
if num!=0{ //存在文章
node.Title=title
node.Content=content
}else { //没有这篇文章
node=models.Node{
UserId: int(c.User.ID),
Uuid:key["0"],
Title: title,
Content: content,
}
}
}
err=models.SaveNode(node)
if err!=nil{
c.Abort500(err)
}
c.Data["json"]=map[string]interface{}{
"code":0,
"action":"/auth/index",
}
_ = c.ServeJSON()
}
7、goquery进行摘要解析
//"github.com/PuerkitoBio/goquery"
//使用goquery获取摘要
func getSummary(html string)(string,error){
var buf bytes.Buffer
if _,err:=buf.Write([]byte(html));err!=nil{
return "",err
}
doc,err:=goquery. NewDocumentFromReader(&buf)
if err!=nil{
return "",err
}
htmlstr:=doc.Find("p").Text()//获取p标签下的文档
if len(htmlstr)>30{ //将前30个字符当成摘要
htmlstr=htmlstr[:30]
}
return htmlstr,nil
}
8、数据库查询分页功能
//使用数据库的offset查询,渲染首页
if c.Ctx.Input.IsGet(){
limit:=3 //设置每页数量
page,err:=c.GetInt("page")
seaStr:=c.GetString("search")
if err!=nil||page<1{
page=1
}
nodes, err:= models.QueryNodeByPage(seaStr,page, limit)
if err!=nil{
c.Abort500(err)
}
//查询总条数,由此算出一共多少page
num,_:=models.QueryAllCount(seaStr)
allpage:=int(num)/limit
if int(num)%limit!=0{
allpage=allpage+1
}
c.Data["allpage"]=allpage
c.Data["page"]=page
c.Data["nodes"]=nodes //返回前端查询数据
c.TplName="index.html"
}
//数据库查询功能
func QueryNodeByPage(str string,page ,limit int)(nodes []Node,err error){
res:=db.Where("title like ?",fmt.Sprintf("%%%s%%",str)).Offset((page-1)*limit).Limit(limit).Find(&nodes)
return nodes,res.Error
//扫描分页的数据,并且返回错误
}
func QueryAllCount(str string)(int64,error){
var count int64
return count,db.Where("title like ?",fmt.Sprintf("%%%s%%",str)).Model(&Node{}).Count(&count).Error
}
//前端使用模板技术的{{range nodes}}渲染出页面
//使用search可以增加搜索功能
9、展示全文功能
//通过Uuid的唯一性当作key值传递
func (c *NodeController)Details(){
key:=c.GetString("key")
_,node,err:=models.QueryNodeByKey(key)
if err!=nil{
c.Abort500(err)
}
c.Data["node"]=node
c.TplName="details.html"
}
10、文章的修改
通过key值传递,修改
逻辑和保存一样,只需压迫跳转到save方法,进行信息修改就行了
11、文章的删除
需要使用uuid和userid一起判断,只使用uuid可能删除多个文章
func (c *NodeController) Del(){
key:=c.GetString("key") //获取key值
userid:=c.User.ID
err:=models.DeleteNode(key,int(userid))
if err!=nil{
c.Abort500(err)
}
c.Redirect(beego.URLFor("AuthController.Index"),302)
}
func DeleteNode(key string,userid int)error{
res:=db.Where("uuid=? and user_id=?",key,userid).Delete(&Node{}) //根据key值删除
return res.Error
}
12、文章的评论
新建评论结构体保存数据库(包含文章,userid,user,Praise)
type Message struct{
}
13、文章的留言
文章的留言和评论异曲同工,只是判断是否有文章的Uuid,如果有文章的Uuid就是这篇文章的评论,如果没有这篇文章的Uuid,就是文章的留言
func (c *AuthController) Message(){
if c.Ctx.Input.IsGet(){
pageno,err:=c.GetInt("pageno")
if err!=nil||pageno<1{
pageno=1
}
pagesize:=5
num,messages,err:=models.QueryAllMessage(pageno,pagesize)
if err!=nil{
c.Abort500(err)
}
counts,_:=models.QueryAllMessageCount()
allpage:=int(counts)/pagesize
if int(counts)%pagesize!=0{
allpage=allpage+1
}
c.Data["num"]=num
c.Data["allpage"]=allpage
c.Data["page"]=pageno
c.Data["messages"]=messages
c.TplName="message.html"
}
if c.Ctx.Input.IsPost(){
content:=c.GetString("content")
message:=&models.Message{
Content:content,
UserId:int(c.User.ID),
User:c.User,
}
err:=models.SaveMessage(message)
if err!=nil{
c.Abort500(err)
}
c.Data["json"]=map[string]interface{}{
"code":0,
"action":"/Auth/Message",
}
_ = c.ServeJSON()
}
}
程序源码位置
gitee地址: https://gitee.com/casyy/beego-realizes-blog.git
后续会有更新,如果看完对自己有所帮助,请点赞支持