├── .gitattributes
├── .gitignore
├── Dockerfile
├── README.md
├── api
└── v1
│ ├── article.go
│ ├── category.go
│ ├── login.go
│ ├── profile.go
│ ├── upload.go
│ └── user.go
├── blog.sql
├── config
└── config.ini
├── go.mod
├── go.sum
├── main.go
├── middleware
├── cors.go
├── jwt.go
└── logger.go
├── model
├── Article.go
├── Category.go
├── Comment.go
├── Email.go
├── Profile.go
├── Update.go
├── User.go
├── UserArticle.go
├── db.go
└── session.go
├── proto
├── req.go
└── rsp.go
├── routes
└── router.go
├── serve.sh
├── static
├── admin
│ ├── favicon.ico
│ ├── index.html
│ └── static
│ │ ├── css
│ │ ├── app.1c53c242.css
│ │ └── chunk-vendors.34cdb930.css
│ │ └── js
│ │ ├── app.46f36fce.js
│ │ ├── app.46f36fce.js.map
│ │ ├── chunk-vendors.1ec65ce1.js
│ │ └── chunk-vendors.1ec65ce1.js.map
└── front
│ ├── favicon.ico
│ ├── index.html
│ └── static
│ ├── css
│ ├── app.e55e68b9.css
│ └── chunk-vendors.65985091.css
│ └── js
│ ├── app.7b3b961d.js
│ ├── app.7b3b961d.js.map
│ ├── chunk-vendors.dfe34836.js
│ └── chunk-vendors.dfe34836.js.map
├── utils
├── errmsg
│ └── errmsg.go
├── setting.go
├── time.go
├── uuid.go
└── validator
│ └── validator.go
└── web
├── admin
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── css
│ │ │ └── style.css
│ │ └── logo.png
│ ├── components
│ │ ├── admin
│ │ │ ├── Footer.vue
│ │ │ ├── Header.vue
│ │ │ ├── Index.vue
│ │ │ └── Nav.vue
│ │ ├── article
│ │ │ ├── AddArt.vue
│ │ │ └── ArtList.vue
│ │ ├── category
│ │ │ └── CateList.vue
│ │ ├── editor
│ │ │ ├── icons
│ │ │ │ └── default
│ │ │ │ │ └── icons.min.js
│ │ │ ├── index.vue
│ │ │ ├── langs
│ │ │ │ ├── README.md
│ │ │ │ └── zh_CN.js
│ │ │ ├── plugins
│ │ │ │ ├── code
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ └── plugin.min.js
│ │ │ │ ├── codesample
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ └── plugin.min.js
│ │ │ │ ├── image
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ └── plugin.min.js
│ │ │ │ ├── imagetools
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ └── plugin.min.js
│ │ │ │ ├── paste
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ └── plugin.min.js
│ │ │ │ ├── preview
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ ├── plugin.min.js
│ │ │ │ │ └── save
│ │ │ │ │ │ ├── plugin.js
│ │ │ │ │ │ └── plugin.min.js
│ │ │ │ └── wordcount
│ │ │ │ │ ├── plugin.js
│ │ │ │ │ └── plugin.min.js
│ │ │ ├── skins
│ │ │ │ ├── content
│ │ │ │ │ ├── dark
│ │ │ │ │ │ └── content.min.css
│ │ │ │ │ ├── default
│ │ │ │ │ │ └── content.min.css
│ │ │ │ │ ├── document
│ │ │ │ │ │ └── content.min.css
│ │ │ │ │ └── writer
│ │ │ │ │ │ └── content.min.css
│ │ │ │ └── ui
│ │ │ │ │ ├── oxide-dark
│ │ │ │ │ ├── content.inline.min.css
│ │ │ │ │ ├── content.min.css
│ │ │ │ │ ├── content.mobile.min.css
│ │ │ │ │ ├── fonts
│ │ │ │ │ │ └── tinymce-mobile.woff
│ │ │ │ │ ├── skin.min.css
│ │ │ │ │ └── skin.mobile.min.css
│ │ │ │ │ └── oxide
│ │ │ │ │ ├── content.inline.min.css
│ │ │ │ │ ├── content.min.css
│ │ │ │ │ ├── content.mobile.min.css
│ │ │ │ │ ├── fonts
│ │ │ │ │ └── tinymce-mobile.woff
│ │ │ │ │ ├── skin.min.css
│ │ │ │ │ └── skin.mobile.min.css
│ │ │ ├── themes
│ │ │ │ ├── mobile
│ │ │ │ │ └── theme.min.js
│ │ │ │ └── silver
│ │ │ │ │ └── theme.min.js
│ │ │ └── tinymce.min.js
│ │ └── user
│ │ │ ├── Profile.vue
│ │ │ └── UserList.vue
│ ├── config
│ │ └── index.js
│ ├── main.js
│ ├── plugin
│ │ ├── antui.js
│ │ └── http.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── index.js
│ └── views
│ │ ├── Admin.vue
│ │ └── Login.vue
├── vue.config.js
└── yarn.lock
└── front
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ ├── bg1.png
│ ├── bg5.webp.png
│ ├── logo.png
│ ├── logo.svg
│ ├── my4.jpg
│ └── 一色3.jpg
├── components
│ ├── ArticleList.vue
│ ├── Detail.vue
│ ├── Footer.vue
│ ├── Nav.vue
│ └── TopBar.vue
├── main.js
├── plugins
│ ├── http.js
│ └── vuetify.js
├── router
│ └── index.js
├── store
│ └── index.js
└── views
│ ├── Home.vue
│ └── Login.vue
├── vue.config.js
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | # THIS IS ONLY FOR THE gitattributes REPOSITORY.
2 | # Handle line endings automatically for files detected as text
3 | # and leave all files detected as binary untouched.
4 | * text=auto
5 |
6 | #
7 | # The above will handle all files NOT found below
8 | #
9 | # These files are text and should be normalized (Convert crlf => lf)
10 | *.gitattributes text
11 | .gitignore text
12 | *.md text diff=markdown
13 |
14 |
15 |
16 | #
17 | # Enable syntax highlighting for files with `.gitattributes` extensions.
18 | #
19 | *.gitattributes linguist-language=gitattributes
20 | *.js linguist-language=golang
21 | *.css linguist-language=golang
22 | *.html linguist-language=golang
23 | *.vue linguist-language=golang
24 |
25 | *.sh linguist-language=shell
26 | *.go -text diff=golang
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | log/*
2 | *.log
3 | .idea
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.16 as builder
2 |
3 | WORKDIR /build
4 | COPY . /build
5 |
6 | ENV GOPROXY=https://goproxy.cn,direct
7 |
8 | RUN go build -a -o basic
9 |
10 | FROM centos:latest
11 |
12 | WORKDIR /data
13 |
14 | COPY --from=builder /build/basic .
15 |
16 | VOLUME ["/data/log"]
17 |
18 | EXPOSE 80
19 | ENTRYPOINT ["./basic"]
20 |
21 | #FROM golang:latest
22 | #
23 | #MAINTAINER tmnhs
24 | #
25 | #RUN go env -w GO111MODULE=on
26 | #RUN go env -w GOPROXY=https://goproxy.cn,direct
27 | #
28 | #WORKDIR $GOPATH/src/myblog
29 | #COPY . $GOPATH/src/myblog
30 | #
31 | #RUN go build -o myblog .
32 | #
33 | #EXPOSE 80
34 | #
35 | #CMD ["./myblog"]
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 这是一个用gin框架和vue框架搭建一个简易博客前台展示和后台管理系统
2 |
3 | ### 博客前台展示页面
4 |
5 |
6 |

7 |
8 |
9 | ### 博客后台管理页面
10 |
11 |

12 |
13 |
14 | ### 一.技术选型
15 | -前端:用基于vue的``ant design vue``构建后台管理页面和基于vue的``vuetify``构建前台展示页面。
16 |
17 | -后端:用``Gin``快速搭建基础restful风格API,Gin是一个go语言编写的Web框架。
18 |
19 | -数据库:采用``MySql``,使用gorm实现对数据库的基本操作。
20 |
21 | -缓存:使用``Redis``实现记录缓存。
22 |
23 | -日志:使用``logrus``实现日志记录。
24 |
25 | ### 二.项目结构
26 |
27 | ```shell
28 | ├── api
29 | │ └── v1
30 | ├── config
31 | ├── middleware
32 | ├── model
33 | ├── proto
34 | ├── routes
35 | ├── static
36 | │ └── admin
37 | │ └── front
38 | └── utils
39 | | └── errmsg
40 | | └── validator
41 | ├── web
42 | │ └── admin
43 | │ └── front
44 | ```
45 | | 文件夹 | 说明 | 描述 |
46 | | ------------- | --------- | ---------------- |
47 | | `api` | api层 | api层 |
48 | | `--v1` | v1版本接口 | v1版本接口 |
49 | | `config` | 配置包 | 配置文件 |
50 | | `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |
51 | | `model` | 模型层 | 模型对应数据表和数据库查询 |
52 | | `routes` | 路由层 | 路由层 |
53 | | `resource` | 静态资源文件夹 | 负责存放静态文件 |
54 | | `--admin` | admin | 后台管理dist文件打包 |
55 | | `--front` | front | 前台展示dist文件打包 |
56 | | `utils` | 工具包 | 工具函数封装 |
57 | | `--errmsg` | errmsg | 错误信息的封装 |
58 | | `--validator` | validator | 后端数据校验 |
59 | | `web` | web | 前端代码 |
60 | | `--admin` | admin | 后台管理的前端代码 |
61 | | `--front` | front | 前台展示的前端代码 |
62 |
63 | ### 三.使用说明
64 |
65 | 1-需要把config/config.ini里的文件配置(特别是数据库mysql的配置)修改成自己需要的配置,本项目使用七牛云对象存储上传的文件,你可以自己在七牛云注册一个账号,可以免费获赠10G的存储空间
66 |
67 | 2-本项目可以直接在windows上运行,建议使用goland,可以将blog.sql导入数据库中,初始登录用户为admin,密码为123456
68 |
69 | 3-[前台展示](http://localhost:8080/front)和 [后台管理] (http://localhost:8080/admin) 的切换需要修改routes/router.go,打开相关注释即可
70 |
71 | 4-如果需要修改前端部分,可以修改web下的文件,修改完后运行`npm run build`,把dist文件覆盖掉static里的文件
72 |
73 | 5-本项目是前后端分离项目,可以注释掉routes/router.go中加载静态资源的代码,把后端代码运行起来,然后在web/admin(或者web/front)目录下,运行`npm run serve`即可
74 |
75 | ### 四.部署项目(Linux系统下)
76 |
77 | 方式一:使用dockerfile部署(确保服务器上有下载docker)
78 |
79 | ```shell
80 | #克隆项目
81 | git clone git@github.com:tmnhs/gin-blog.git
82 | #在gin-blog目录下
83 | cd gin-blog
84 | #docker编译镜像
85 | docker build -t myblog .
86 | #运行docker
87 | docker run -p 8080:8080 -t --name myblog myblog
88 | #注意此时的数据库配置应该改为服务器上的配置
89 | ```
90 |
91 | 方式二:使用脚本部署
92 |
93 | ```shell
94 | #克隆项目
95 | git clone git@github.com:tmnhs/gin-blog.git
96 | #在gin-blog目录下
97 | cd gin-blog
98 | #必须在linux系统下
99 | #启动项目
100 | ./serve.sh start
101 | #暂停项目
102 | ./serve.sh stop
103 | #重启服务
104 | ./serve.sh restart
105 | #注意,脚本运行的端口号必须大于1024,不然可能会没有权限
106 | ```
107 |
108 |
--------------------------------------------------------------------------------
/api/v1/article.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "demo1/MyBlog/model"
5 | "demo1/MyBlog/proto"
6 | "demo1/MyBlog/utils/errmsg"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | "strconv"
10 | )
11 |
12 | /**
13 | 文章处理模块的接口
14 | */
15 |
16 | //todo 添加文章
17 | func AddArticle(c *gin.Context) {
18 | var data model.Article
19 | var userArticle model.UserArticle
20 | //绑定
21 | _ = c.ShouldBind(&data)
22 | code := model.CreateArticle(&data)
23 |
24 | userArticle.ArticleId = data.ID
25 |
26 | userName, _ := c.Get("username")
27 | userArticle.UserId = model.FindUserIdByName(userName)
28 | _ = model.CreateUserArticle(&userArticle)
29 |
30 | c.JSON(http.StatusOK, gin.H{
31 | "status": code,
32 | "data": data,
33 | "message": errmsg.GetErrMsg(code),
34 | })
35 | }
36 |
37 | // todo 添加评论
38 | func AddComment(c *gin.Context) {
39 | var data model.Comment
40 | _ = c.ShouldBindJSON(&data)
41 | id := data.ArticleID
42 |
43 | code := model.CreateComment(id, data)
44 |
45 | c.JSON(http.StatusOK, gin.H{
46 | "status": code,
47 | "data": data,
48 | "message": errmsg.GetErrMsg(code),
49 | })
50 | }
51 |
52 | //todo 删除评论
53 | func DeleteComment(c *gin.Context) {
54 | id, _ := strconv.Atoi(c.Param("id"))
55 |
56 | code := model.DeleteComment(id)
57 |
58 | c.JSON(http.StatusOK, gin.H{
59 | "status": code,
60 | "message": errmsg.GetErrMsg(code),
61 | })
62 | }
63 |
64 | //todo 查询单个文章
65 | func GetArticleInfo(c *gin.Context) {
66 | id, _ := strconv.Atoi(c.Param("id"))
67 |
68 | data, code := model.GetArticleInfo(id)
69 |
70 | c.JSON(http.StatusOK, gin.H{
71 | "status": code,
72 | "data": data,
73 | "message": errmsg.GetErrMsg(code),
74 | })
75 | }
76 |
77 | //todo 查询文章下的所有评论
78 | func GetComments(c *gin.Context) {
79 | id, _ := strconv.Atoi(c.Param("id"))
80 |
81 | data, code := model.GetCommentsByArticleId(id)
82 |
83 | c.JSON(http.StatusOK, gin.H{
84 | "status": code,
85 | "data": data,
86 | "message": errmsg.GetErrMsg(code),
87 | })
88 | }
89 |
90 | //todo 查询某分类下的所有文章
91 | func GetCateArticle(c *gin.Context) {
92 | //获取参数
93 | //var req proto.ReqCommon
94 | id, _ := strconv.Atoi(c.Param("id"))
95 | pageNum, _ := strconv.Atoi(c.Query("pagenum"))
96 | pageSize, _ := strconv.Atoi(c.Query("pagesize"))
97 |
98 | //_=c.ShouldBindJSON(&req)
99 | if pageNum == 0 {
100 | pageNum = -1
101 | }
102 | if pageSize == 0 {
103 | pageSize = -1
104 | }
105 | //fmt.Println(req)
106 | data, code, total := model.GetCateArticle(id, pageSize, pageNum)
107 | articles := make([]*proto.RspFindArticle, 0)
108 | for i := 0; i < len(data); i++ {
109 | article := &proto.RspFindArticle{
110 | ID: data[i].ID,
111 | Title: data[i].Title,
112 | CreatedAt: data[i].CreatedAt,
113 | Desc: data[i].Desc,
114 | Content: data[i].Content,
115 | Img: data[i].Img,
116 | }
117 | Cate, err := model.FindCategoryById(data[i].Cid)
118 | if err != nil {
119 | article.Name = ""
120 | } else {
121 | article.Name = Cate.Name
122 | }
123 | articles = append(articles, article)
124 | }
125 | c.JSON(http.StatusOK, gin.H{
126 | "status": code,
127 | "data": articles,
128 | "total": total,
129 | "message": errmsg.GetErrMsg(code),
130 | })
131 | }
132 |
133 | //todo 查询文章列表
134 | func GetArticle(c *gin.Context) {
135 | //获取参数
136 | var req proto.ReqFindArticle
137 | _ = c.ShouldBindJSON(&req)
138 | //如果pageSize等于0,则取消分页
139 | if req.PageSize == 0 {
140 | req.PageSize = -1
141 | }
142 | if req.PageNum == 0 {
143 | req.PageNum = -1
144 | }
145 | //调用函数查看所有用户
146 | data, code, total := model.GetArticles(req.Title, req.PageSize, req.PageNum)
147 |
148 | articles := make([]*proto.RspFindArticle, 0)
149 | for i := 0; i < len(data); i++ {
150 | article := &proto.RspFindArticle{
151 | ID: data[i].ID,
152 | Title: data[i].Title,
153 | CreatedAt: data[i].CreatedAt,
154 | Desc: data[i].Desc,
155 | Content: data[i].Content,
156 | Img: data[i].Img,
157 | }
158 | Cate, err := model.FindCategoryById(data[i].Cid)
159 | if err != nil {
160 | article.Name = ""
161 | } else {
162 | article.Name = Cate.Name
163 | }
164 | articles = append(articles, article)
165 | }
166 | c.JSON(http.StatusOK, gin.H{
167 | "status": code,
168 | "data": articles,
169 | "total": total,
170 | "message": errmsg.GetErrMsg(code),
171 | })
172 | }
173 |
174 | //todo 编辑文章
175 | func EditArticle(c *gin.Context) {
176 | var data model.Article
177 | //获取参数
178 | id, _ := strconv.Atoi(c.Param("id"))
179 | _ = c.ShouldBindJSON(&data)
180 |
181 | code := model.EditArticle(id, &data)
182 |
183 | c.JSON(http.StatusOK, gin.H{
184 | "status": code,
185 | "message": errmsg.GetErrMsg(code),
186 | })
187 | }
188 |
189 | //todo 删除文章
190 | func DeleteArticle(c *gin.Context) {
191 | //获取参数
192 | id, _ := strconv.Atoi(c.Param("id"))
193 |
194 | //调用删除的函数
195 | code := model.DeleteArticle(id)
196 |
197 | c.JSON(http.StatusOK, gin.H{
198 | "status": code,
199 | "message": errmsg.GetErrMsg(code),
200 | })
201 | }
202 |
--------------------------------------------------------------------------------
/api/v1/category.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | /**
4 | 分类处理模块的接口
5 | */
6 | import (
7 | "demo1/MyBlog/model"
8 | "demo1/MyBlog/proto"
9 | "demo1/MyBlog/utils/errmsg"
10 | "github.com/gin-gonic/gin"
11 | "net/http"
12 | "strconv"
13 | )
14 |
15 | //添加分类
16 | func AddCategory(c *gin.Context) {
17 | var data model.Category
18 | //绑定
19 | _ = c.ShouldBind(&data)
20 | //判断分类是否存在
21 | code := model.CheckCategory(data.Name)
22 | if code == errmsg.SUCCESS {
23 | code = model.CreateCategory(&data)
24 | }
25 | c.JSON(http.StatusOK, gin.H{
26 | "status": code,
27 | "data": data,
28 | "message": errmsg.GetErrMsg(code),
29 | })
30 | }
31 |
32 | //查询单个分类
33 |
34 | //查询分类列表
35 | func GetCategory(c *gin.Context) {
36 | //获取参数
37 | var req proto.ReqCommon
38 | _ = c.ShouldBind(&req)
39 | //如果pageSize等于0,则取消分页
40 | if req.PageSize == 0 {
41 | req.PageSize = -1
42 | }
43 | if req.PageNum == 0 {
44 | req.PageNum = -1
45 | }
46 | //调用函数查看所有用户
47 | data, total, err := model.GetCategory(req.PageSize, req.PageNum)
48 | var code int
49 | if err != nil {
50 | code = errmsg.ERROR
51 | } else {
52 | code = errmsg.SUCCESS
53 | }
54 | c.JSON(http.StatusOK, gin.H{
55 | "status": code,
56 | "data": data,
57 | "total": total,
58 | "message": errmsg.GetErrMsg(code),
59 | })
60 | }
61 |
62 | //todo 编辑分类名
63 | func EditCategory(c *gin.Context) {
64 | var data model.Category
65 | //获取参数
66 | id, _ := strconv.Atoi(c.Param("id"))
67 | _ = c.ShouldBindJSON(&data)
68 | code := model.CheckCategory(data.Name)
69 | if code == errmsg.SUCCESS {
70 | code = model.EditCategory(id, &data)
71 | }
72 | c.JSON(http.StatusOK, gin.H{
73 | "status": code,
74 | "message": errmsg.GetErrMsg(code),
75 | })
76 | }
77 |
78 | //todo 删除分类
79 | func DeleteCategory(c *gin.Context) {
80 | //获取参数
81 | id, _ := strconv.Atoi(c.Param("id"))
82 | //调用删除的函数
83 | code := model.DeleteCategory(id)
84 |
85 | c.JSON(http.StatusOK, gin.H{
86 | "status": code,
87 | "message": errmsg.GetErrMsg(code),
88 | })
89 | }
90 |
91 | //通过id查找分类
92 | func FindCategoryById(c *gin.Context) {
93 | var code int
94 | //获取参数
95 | id, _ := strconv.Atoi(c.Param("id"))
96 | //调用删除的函数
97 | cate, err := model.FindCategoryById(id)
98 | if err != nil {
99 | code = errmsg.ERROR
100 | } else {
101 | code = errmsg.SUCCESS
102 | }
103 | c.JSON(http.StatusOK, gin.H{
104 | "data": cate,
105 | "status": code,
106 | "message": errmsg.GetErrMsg(code),
107 | })
108 | }
109 |
--------------------------------------------------------------------------------
/api/v1/login.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "demo1/MyBlog/middleware"
5 | "demo1/MyBlog/model"
6 | "demo1/MyBlog/utils"
7 | "demo1/MyBlog/utils/errmsg"
8 | "demo1/MyBlog/utils/validator"
9 | "encoding/json"
10 | "fmt"
11 | "github.com/gin-gonic/gin"
12 | "net/http"
13 | )
14 |
15 | //todo 注册
16 | func Register(c *gin.Context) {
17 | var user model.User
18 | _ = c.ShouldBindJSON(&user)
19 | //对传过来的数据进行校验
20 | msg, code := validator.Validate(&user)
21 | //校验失败,返回错误信息(注册失败)
22 | if code != errmsg.SUCCESS {
23 | c.JSON(http.StatusOK, gin.H{
24 | "code": code,
25 | "message": msg,
26 | })
27 | return
28 | }
29 | //判断用户名是否存在
30 | code = model.CheckUser(user.Username)
31 | if code == errmsg.SUCCESS {
32 | //用户名不存在,可以注册
33 | //设置激活码,唯一字符串
34 | user.Code = utils.CreateUUID()
35 | //设置激活状态
36 | user.Status = "N"
37 | //激活邮件发送,邮件正文
38 |
39 | content := "点我激活"
40 |
41 | model.SendEmail(user.Email, "激活邮件", content)
42 |
43 | //将注册的用户保存到数据库
44 | _, code = model.CreateUser(&user)
45 | }
46 | c.JSON(http.StatusOK, gin.H{
47 | "code": code,
48 | "message": errmsg.GetErrMsg(code),
49 | })
50 |
51 | }
52 |
53 | //todo 邮件激活
54 | func ActiveEmail(c *gin.Context) {
55 | var code int
56 | //获取参数(激活码)
57 | vCode := c.Query("code")
58 | if len(vCode) == 0 {
59 | code = errmsg.ERROR_EMAIL_CODE_NOT_EXIST
60 | c.JSON(http.StatusOK, gin.H{
61 | "code": code,
62 | "message": errmsg.GetErrMsg(code),
63 | })
64 | return
65 | }
66 | code = model.UpdateUserStatus(vCode)
67 |
68 | c.JSON(http.StatusOK, gin.H{
69 | "code": code,
70 | "message": errmsg.GetErrMsg(code),
71 | })
72 | }
73 |
74 | //todo 登录
75 | func Login(c *gin.Context) {
76 | var data model.User
77 | _ = c.ShouldBindJSON(&data)
78 | //fmt.Println(data)
79 | var token string
80 | var code int
81 | user, code := model.CheckLogin(data.Username, data.Password)
82 |
83 | if code == errmsg.SUCCESS {
84 | token, code = middleware.SetToken(data.Username)
85 | model.ClearSession(c)
86 | model.SetSession(c, token, int(utils.Expiration))
87 | profile, codeGetProfile := model.GetProfileById(int(user.ID))
88 | profileJson, _ := json.Marshal(profile)
89 | if codeGetProfile == errmsg.SUCCESS {
90 | _, err := model.Redis.Set(token, string(profileJson), utils.Expiration).Result()
91 | //result, err:= model.Redis.Do("SET",token, profileJson).Result()
92 | if err != nil {
93 | fmt.Println("redis set fail", err)
94 | }
95 | //fmt.Println("set Profile :",result)
96 | }
97 | }
98 |
99 | c.JSON(http.StatusOK, gin.H{
100 | "code": code,
101 | "message": errmsg.GetErrMsg(code),
102 | "token": token,
103 | })
104 | }
105 |
106 | //todo 发送邮件
107 | func SendEmailForCode(c *gin.Context) {
108 | to := c.Query("email")
109 | code := utils.CreateVcode()
110 | content := "欢迎使用博客,您的验证码是
" + code + "
"
111 | //将验证码存储到redis中
112 | model.Redis.Do("set", to, code)
113 | status := model.SendEmail(to, "验证码", content)
114 | c.JSON(http.StatusOK, gin.H{
115 | "status": status,
116 | "message": errmsg.GetErrMsg(status),
117 | })
118 | }
119 |
120 | //todo 使用邮件登录
121 | func LoginByEmail(c *gin.Context) {
122 | var token string
123 | var code int
124 | var user model.User
125 | to := c.Query("email")
126 | userCode := c.Query("code")
127 | //从redis中拿到正确的验证码做比较
128 | serverCode := model.Redis.Get(to).Val()
129 |
130 | //比较验证码
131 | if userCode != serverCode {
132 | code = errmsg.ERROR_CODE_WRONG
133 | c.JSON(http.StatusOK, gin.H{
134 | "code": code,
135 | "message": errmsg.GetErrMsg(code),
136 | })
137 | return
138 | }
139 | //通过email查找用户
140 | user, code = model.GetUserByEmail(to)
141 | if code == errmsg.SUCCESS {
142 | //jwt验证
143 | token, code = middleware.SetToken(user.Username)
144 | }
145 | //登录成功删除redis中的email,防止重复使用
146 | model.Redis.Del(to)
147 | c.JSON(http.StatusOK, gin.H{
148 | "code": code,
149 | "message": errmsg.GetErrMsg(code),
150 | "token": token,
151 | })
152 | }
153 |
--------------------------------------------------------------------------------
/api/v1/profile.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "demo1/MyBlog/model"
5 | "demo1/MyBlog/utils/errmsg"
6 | "github.com/gin-gonic/gin"
7 | "net/http"
8 | )
9 |
10 | func GetProfile(c *gin.Context) {
11 | profile, code := model.GetProfile(c)
12 | //fmt.Println("profile",profile)
13 | c.JSON(http.StatusOK, gin.H{
14 | "status": code,
15 | "data": profile,
16 | "message": errmsg.GetErrMsg(code),
17 | })
18 | }
19 |
20 | func UpdateProfile(c *gin.Context) {
21 | var req model.Profile
22 | _ = c.ShouldBindJSON(&req)
23 |
24 | profile, code := model.GetProfile(c)
25 | req.ID = profile.ID
26 | //fmt.Println("update profile",req)
27 | code = model.UpdateProfile(c, req.ID, &req)
28 | c.JSON(http.StatusOK, gin.H{
29 | "status": code,
30 | "message": errmsg.GetErrMsg(code),
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/api/v1/upload.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "demo1/MyBlog/model"
5 | "demo1/MyBlog/utils/errmsg"
6 | "github.com/gin-gonic/gin"
7 | "net/http"
8 | )
9 |
10 | //todo 上传文件
11 | func Upload(c *gin.Context) {
12 | file ,fileHeadr,_:=c.Request.FormFile("file")
13 |
14 | fileSize:=fileHeadr.Size
15 |
16 | url,code:= model.UploadFile(file,fileSize)
17 | c.JSON(http.StatusOK,gin.H{
18 | "status":code,
19 | "message":errmsg.GetErrMsg(code),
20 | "url":url,
21 | })
22 | }
--------------------------------------------------------------------------------
/api/v1/user.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | /**
4 | 用户处理模块的接口
5 | */
6 |
7 | import (
8 | "demo1/MyBlog/model"
9 | "demo1/MyBlog/proto"
10 | "demo1/MyBlog/utils/errmsg"
11 | "github.com/gin-gonic/gin"
12 | "net/http"
13 | "strconv"
14 | )
15 |
16 | //todo 查询用户详细信息(包括文章)
17 | func GetUserInfo(c *gin.Context) {
18 |
19 | id, _ := strconv.Atoi(c.Param("id"))
20 |
21 | data, code := model.GetUserInfo(id)
22 |
23 | c.JSON(http.StatusOK, gin.H{
24 | "status": code,
25 | "data": data,
26 | "message": errmsg.GetErrMsg(code),
27 | })
28 | }
29 |
30 | //todo 添加用户
31 | func AddUser(c *gin.Context) {
32 | var data proto.ReqAddUser
33 | var code int
34 | //绑定
35 | _ = c.ShouldBindJSON(&data)
36 | //msg,code= validator.Validate(&data)
37 | //if code!=errmsg.SUCCESS{
38 | // c.JSON(http.StatusOK,gin.H{
39 | // "status":code,
40 | // "message":msg,
41 | // })
42 | // return
43 | //}
44 | //判断用户名是否存在
45 | code = model.CheckUser(data.UserName)
46 | if code == errmsg.SUCCESS {
47 | user := &model.User{
48 | Username: data.UserName,
49 | Password: data.Password,
50 | Email: data.Email,
51 | Role: data.Role,
52 | Status: "Y",
53 | }
54 | result, _ := model.CreateUser(user)
55 | profile := &model.Profile{
56 | ID: int(result.ID),
57 | Name: result.Username,
58 | Email: result.Email,
59 | }
60 | //fmt.Println("create user:",result)
61 | code = model.CreateProfile(profile)
62 | }
63 | c.JSON(http.StatusOK, gin.H{
64 | "status": code,
65 | "message": errmsg.GetErrMsg(code),
66 | })
67 | }
68 |
69 | //查询单个用户
70 |
71 | // todo 查询用户列表
72 | func GetUsers(c *gin.Context) {
73 | //获取参数
74 | var req proto.ReqFindUser
75 | _ = c.ShouldBindJSON(&req)
76 | //fmt.Println(req)
77 | //如果pageSize等于0,则取消分页
78 | if req.PageSize == 0 {
79 | req.PageSize = -1
80 | }
81 | if req.PageNum == 0 {
82 | req.PageNum = -1
83 | }
84 | //调用函数查看所有用户
85 | data, total, err1 := model.GetUsers(req.IdOrName, req.PageSize, req.PageNum)
86 | code := errmsg.SUCCESS
87 | if err1 != nil {
88 | code = errmsg.ERROR
89 | }
90 | c.JSON(http.StatusOK, gin.H{
91 | "status": code,
92 | "data": data,
93 | "total": total,
94 | "message": errmsg.GetErrMsg(code),
95 | })
96 | }
97 |
98 | //todo 编辑用户
99 | func EditUser(c *gin.Context) {
100 | var req proto.ReqEditUser
101 | var code int
102 | //获取参数
103 | _ = c.ShouldBindJSON(&req)
104 | user, _ := model.GetUserInfo(req.Id)
105 | if user.Username != req.UserName {
106 | code = model.CheckUser(req.UserName)
107 | if code != errmsg.SUCCESS {
108 | c.JSON(code, gin.H{
109 | "status": code,
110 | "message": errmsg.GetErrMsg(code),
111 | })
112 | return
113 | }
114 | }
115 | code = model.EditUser(req.Id, &req)
116 |
117 | profileOld, _ := model.GetProfileById(req.Id)
118 | profile := &model.Profile{
119 | ID: req.Id,
120 | Name: req.UserName,
121 | Email: req.Email,
122 | Desc: profileOld.Desc,
123 | QqChat: profileOld.QqChat,
124 | WeChat: profileOld.WeChat,
125 | Weibo: profileOld.Weibo,
126 | Img: profileOld.Img,
127 | Avatar: profileOld.Avatar,
128 | }
129 | code = model.UpdateProfile(c, profile.ID, profile)
130 | c.JSON(http.StatusOK, gin.H{
131 | "status": code,
132 | "message": errmsg.GetErrMsg(code),
133 | })
134 | }
135 |
136 | //todo 删除用户
137 | func DeleteUser(c *gin.Context) {
138 | //获取参数
139 | id, _ := strconv.Atoi(c.Param("id"))
140 | //调用删除的函数
141 | code := model.DeleteUser(id)
142 |
143 | c.JSON(http.StatusOK, gin.H{
144 | "status": code,
145 | "message": errmsg.GetErrMsg(code),
146 | })
147 | }
148 |
--------------------------------------------------------------------------------
/config/config.ini:
--------------------------------------------------------------------------------
1 | [server]
2 | ;debug 开发模式 release 生产模式
3 | AppMode = debug
4 | ;服务器端口
5 | HttpPort = :8080
6 |
7 | JwtKey=45df45rds4
8 |
9 |
10 | ;数据库相关信息
11 | [database]
12 | ;数据库名称
13 | Db = mysql
14 | ;数据库地址
15 | DbHost = 114.55.178.217
16 | ;数据库端口号
17 | DbPort = :3307
18 | ;数据库用户
19 | DbUser = root
20 | ;数据库密码
21 | DbPassWord = root
22 | ;使用的数据库名称
23 | DbName = blog
24 |
25 | ;使用七牛对象存储
26 | [qiniuyun]
27 | AccessKey = 3v1jdAbn2NJ6tOS36ZKvYLDdobjFQjkecYkdKNBr
28 | SecretKey = ZjM5P7RFoLFO6-A-Hf0F6GYlmw-MORAEljeNPw70
29 | Bucket = tmnhginblog
30 | QiniuServer = http://tmnhs.top/
31 |
32 | ; redis地址
33 | [redis]
34 | ;redis地址及端口号
35 | RedisAddr = 127.0.0.1:6379
36 | RedisPassword =
37 | RedisDB = 0
38 |
39 | ;邮箱信息
40 | [email]
41 | ;邮箱服务地址
42 | ServerHost=smtp.qq.com
43 | ;邮箱服务端口
44 | ServerPort=465
45 | ;发件人邮箱地址
46 | FromEmail=yourEmail
47 | ;发件人邮箱密码(秘钥)
48 | FromPassword=yourPassword
49 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module demo1/MyBlog
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
7 | github.com/garyburd/redigo v1.6.2
8 | github.com/gin-contrib/cors v1.3.1
9 | github.com/gin-gonic/gin v1.7.4
10 | github.com/go-playground/locales v0.13.0
11 | github.com/go-playground/universal-translator v0.17.0
12 | github.com/go-playground/validator/v10 v10.4.1
13 | github.com/go-redis/redis v6.15.9+incompatible
14 | github.com/go-sql-driver/mysql v1.5.0
15 | github.com/jinzhu/gorm v1.9.16
16 | github.com/jonboulle/clockwork v0.2.2 // indirect
17 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
18 | github.com/lestrrat-go/strftime v1.0.5 // indirect
19 | github.com/onsi/ginkgo v1.16.5 // indirect
20 | github.com/onsi/gomega v1.16.0 // indirect
21 | github.com/pkg/errors v0.9.1 // indirect
22 | github.com/qiniu/api.v7/v7 v7.4.0
23 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
24 | github.com/satori/go.uuid v1.2.0
25 | github.com/sirupsen/logrus v1.8.1
26 | github.com/stretchr/testify v1.7.0 // indirect
27 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
28 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
29 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
30 | gopkg.in/ini.v1 v1.63.2
31 | )
32 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "demo1/MyBlog/model"
5 | "demo1/MyBlog/routes"
6 | )
7 |
8 | func main() {
9 | //连接数据库
10 | model.InitDb()
11 | model.InitRedis()
12 | routes.InitRouter()
13 | //model.SendEmail()
14 | //fe202a84-64f6-4908-bf7f-cb06e056ac8a
15 | //CreateUUID()
16 | //utils.CreateVcode()
17 | }
18 |
--------------------------------------------------------------------------------
/middleware/cors.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | //func Cors() gin.HandlerFunc {
9 | // //return cors.New(
10 | // // cors.Config{
11 | // // AllowAllOrigins: true,
12 | // // AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONs"},
13 | // // AllowHeaders: []string{"*"},
14 | // // ExposeHeaders: []string{"Content-Length", "Authorization", "Content-Type"},
15 | // // AllowCredentials: true,
16 | // // MaxAge: 12 * time.Hour,
17 | // // })
18 | // return cors.Default()
19 | //
20 | //}
21 | //func Cors() gin.HandlerFunc {
22 | // return func(c *gin.Context) {
23 | // c.Header("Access-Control-Allow-Origin", "true")
24 | // c.Header("Access-Control-Allow-Headers", "*")
25 | // c.Header("Access-Control-Allow-Methods", "POST, GET, PUT, PATCH, OPTIONS")
26 | // c.Header("Access-Control-Allow-Credentials", "true")
27 | // c.Header("Access-Control-Expose-Headers", "*")
28 | // if c.Request.Method == "OPTIONS" {
29 | // c.JSON(http.StatusOK, "")
30 | // c.Abort()
31 | // return
32 | // }
33 | // c.Next()
34 | // }
35 | //
36 | //}
37 |
38 | // Cors 直接放行所有跨域请求并放行所有 OPTIONS 方法
39 | func Cors() gin.HandlerFunc {
40 | return func(c *gin.Context) {
41 | method := c.Request.Method
42 | origin := c.Request.Header.Get("Origin")
43 | c.Header("Access-Control-Allow-Origin", origin)
44 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token,Content-Length, Authorization, Token,X-Token,X-User-Id,x-requested-with")
45 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
46 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
47 | c.Header("Access-Control-Allow-Credentials", "true")
48 | //Access to XMLHttpRequest at 'http://localhost/api/v1/upload' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field x-requested-with is not allowed by Access-Control-Allow-Headers in preflight response.
49 | //AddArt.vue?e24c:105
50 | // 放行所有OPTIONS方法
51 | if method == "OPTIONS" {
52 | c.AbortWithStatus(http.StatusNoContent)
53 | }
54 |
55 | // 处理请求
56 | c.Next()
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/middleware/jwt.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "demo1/MyBlog/utils"
5 | "demo1/MyBlog/utils/errmsg"
6 | "github.com/dgrijalva/jwt-go"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | "strings"
10 | "time"
11 | )
12 |
13 | var JwyKey = []byte(utils.JwtKey)
14 |
15 | type MyClaims struct {
16 | Username string `json:"username"`
17 | jwt.StandardClaims
18 | }
19 |
20 | //生成token
21 | func SetToken(username string) (string, int) {
22 | //设置到期时间
23 | expireTime := time.Now().Add(24 * time.Hour) //24小时
24 | //创建Jwt声明
25 | SetClaims := MyClaims{
26 | username,
27 | jwt.StandardClaims{
28 | ExpiresAt: expireTime.Unix(),
29 | Issuer: "ginblog",
30 | },
31 | }
32 | //使用用于签名的算法和令牌
33 | reqClain := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims)
34 | //创建jwt字符串
35 | token, err := reqClain.SignedString(JwyKey)
36 | //如果出错,则返回服务器内部错误
37 | if err != nil {
38 | return "", errmsg.ERROR
39 | }
40 | return token, errmsg.SUCCESS
41 | }
42 |
43 | //验证token
44 | func CheckToken(token string) (*MyClaims, int) {
45 |
46 | //解析jwt字符串并将结果存储
47 | setToken, _ := jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
48 | return JwyKey, nil
49 | })
50 | if key, _ := setToken.Claims.(*MyClaims); setToken.Valid {
51 | return key, errmsg.SUCCESS
52 | }
53 | return nil, errmsg.ERROR
54 | }
55 |
56 | //jwt中间件
57 | var code int
58 |
59 | func JwtToken() gin.HandlerFunc {
60 | return func(c *gin.Context) {
61 |
62 | tokenHeader := c.Request.Header.Get("Authorization")
63 | //如果token为空,返回错误
64 | if tokenHeader == "" {
65 | code = errmsg.ERROR_TOKEN_NOT_EXIST //1004
66 | c.JSON(http.StatusOK, gin.H{
67 | "status": code,
68 | "message": errmsg.GetErrMsg(code),
69 | })
70 | c.Abort()
71 | return
72 | }
73 | checkToken := strings.SplitN(tokenHeader, " ", 2)
74 | //token类型有错误
75 | if len(checkToken) != 2 && checkToken[0] != "Bearer" {
76 | code = errmsg.ERROR_TOKEN_TYPE_WRONG
77 | c.JSON(http.StatusOK, gin.H{
78 | "status": code,
79 | "message": errmsg.GetErrMsg(code),
80 | })
81 | c.Abort()
82 | return
83 | }
84 | //验证token
85 | key, tCode := CheckToken(checkToken[1])
86 | if tCode == errmsg.ERROR {
87 | code = errmsg.ERROR_TOKEN_WRONG
88 | c.JSON(http.StatusOK, gin.H{
89 | "status": code,
90 | "message": errmsg.GetErrMsg(code),
91 | })
92 | c.Abort()
93 | return
94 | }
95 | //判断时间是否过期
96 | if time.Now().Unix() > key.ExpiresAt {
97 | code = errmsg.ERROR_TOKEN_RUNTIEM
98 | c.JSON(http.StatusOK, gin.H{
99 | "status": code,
100 | "message": errmsg.GetErrMsg(code),
101 | })
102 | c.Abort()
103 | return
104 | }
105 | //成功情况下
106 | c.Set("username", key.Username)
107 | //c.Set("userid",key.Id)
108 | c.Next()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/middleware/logger.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | retalog "github.com/lestrrat-go/file-rotatelogs"
7 | "github.com/rifflock/lfshook"
8 | "github.com/sirupsen/logrus"
9 | "math"
10 | "os"
11 | "time"
12 | )
13 | //日志,此操作可以复用
14 | func Logger() gin.HandlerFunc {
15 | //设置日志文件的路径
16 | filePath:="log/log"
17 | //建立软连接,需要管理员权限
18 | linkName:="latest_log.log"
19 | src,err:=os.OpenFile(filePath,os.O_RDWR|os.O_CREATE,0755)
20 | if err != nil {
21 | fmt.Println("err: ",err)
22 | }
23 | //创建日志
24 | logger := logrus.New()
25 | //输出
26 | logger.Out=src
27 | //设置日志级别
28 | logger.SetLevel(logrus.DebugLevel)
29 | //添加时间分割
30 | logWriter,_:=retalog.New(
31 | filePath+"%Y%m%d.log",
32 | retalog.WithMaxAge(7*24*time.Hour), //日志保留时间:一周
33 | retalog.WithRotationTime(24*time.Hour), //24小时分割一次
34 | retalog.WithLinkName(linkName), //建立软连接
35 | )
36 | writeMap:=lfshook.WriterMap{
37 | logrus.InfoLevel: logWriter,
38 | logrus.FatalLevel: logWriter,
39 | logrus.DebugLevel: logWriter,
40 | logrus.WarnLevel: logWriter,
41 | logrus.ErrorLevel: logWriter,
42 | logrus.PanicLevel: logWriter,
43 | }
44 | //实例化
45 | Hook:=lfshook.NewHook(writeMap,&logrus.TextFormatter{
46 | TimestampFormat: "2006-01-02 15:04:05",
47 | })
48 | logger.AddHook(Hook)
49 | return func(c *gin.Context) {
50 | startTime := time.Now()
51 | c.Next()
52 | stopTime := time.Since(startTime)
53 | spendTime := fmt.Sprintf("%d ms", int(math.Ceil(float64(stopTime.Nanoseconds()/1000000.0))))
54 | hostName, err := os.Hostname()
55 | if err != nil {
56 | hostName = "unkown"
57 | }
58 | statusCode := c.Writer.Status()
59 | clientIp := c.ClientIP()
60 | userAgent := c.Request.UserAgent()
61 | dataSize := c.Writer.Size()
62 | if dataSize < 0 {
63 | dataSize = 0
64 | }
65 | method := c.Request.Method
66 | path := c.Request.RequestURI
67 |
68 | entry := logger.WithFields(logrus.Fields{
69 | "HostName": hostName,
70 | "status": statusCode,
71 | "SpendTime": spendTime,
72 | "Ip": clientIp,
73 | "Method": method,
74 | "Path": path,
75 | "DataSize": dataSize,
76 | "Agent": userAgent,
77 | })
78 | if len(c.Errors) > 0 {
79 | entry.Error(c.Errors.ByType(gin.ErrorTypePrivate).String())
80 | }
81 | if statusCode>=500{
82 | entry.Error()
83 | }else if statusCode>=400{
84 | entry.Warn()
85 | }else{
86 | entry.Info()
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/model/Article.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | //文章模型
4 |
5 | import (
6 | "demo1/MyBlog/utils/errmsg"
7 | "github.com/jinzhu/gorm"
8 | )
9 |
10 | type Article struct {
11 | gorm.Model
12 | Title string `gorm:"type:varchar(100);not null" json:"title"`
13 | Cid int `gorm:"type:int;not null" json:"cid"`
14 | Desc string `gorm:"type:varchar(200)" json:"desc"`
15 | Content string `gorm:"type:longtext;not null" json:"content"`
16 | Img string `gorm:"type:varchar(200)" json:"img"`
17 | Category Category `gorm:"foreignkey:Cid"`
18 | Comments []Comment
19 | }
20 |
21 | //todo 添加文章,此时需要建立中间表
22 | func CreateArticle(data *Article) int {
23 |
24 | //密码加密
25 | //data.Password=ScryptPw(data.Password)
26 | //添加中间表
27 |
28 | err = db.Create(&data).Error
29 | //_=db.Create(&userArticle).Error
30 | if err != nil {
31 | return errmsg.ERROR //500
32 | }
33 | return errmsg.SUCCESS
34 | }
35 |
36 | //todo 添加评论
37 | func CreateComment(id int, data Comment) int {
38 | var article Article
39 | err = db.Model(&Article{}).Where("id = ?", id).First(&article).Error
40 | if err != nil {
41 | return errmsg.ERROR_ARTICLE_NOT_EXIST
42 | }
43 | article.Comments = append(article.Comments, data)
44 | err = db.Save(&article).Error
45 | if err != nil {
46 | return errmsg.ERROR
47 | }
48 | return errmsg.SUCCESS
49 | }
50 |
51 | //todo 查询文章下的所有评论
52 | func GetCommentsByArticleId(id int) ([]Comment, int) {
53 | var comments []Comment
54 | var article Article
55 | var _ = db.Model(&Article{}).Where("id = ?", id).First(&article).Error
56 | err = db.Where("article_id = ?", id).Find(&comments).Error
57 | if article.ID == 0 {
58 | return nil, errmsg.ERROR_ARTICLE_NOT_EXIST
59 | }
60 | if len(comments) == 0 {
61 | return nil, errmsg.ERROR_ARTICLE_NO_COMMENTS
62 | }
63 | return comments, errmsg.SUCCESS
64 | }
65 |
66 | //todo 查询文章列表,带分页效果
67 | func GetArticles(title string, pageSize int, pageNum int) ([]Article, int, int) {
68 | var articles []Article
69 | var total int
70 | DB := db.Model(&Article{})
71 | if title != "" {
72 | DB = DB.Where("title like ?", "%"+title+"%")
73 | }
74 | err = DB.Limit(pageSize).Offset((pageNum - 1) * pageSize).Find(&articles).Error
75 | if err != nil && err != gorm.ErrRecordNotFound {
76 | return nil, errmsg.ERROR, 0
77 | }
78 | //查找总数
79 | err = DB.Count(&total).Error
80 | if err != nil {
81 | return nil, errmsg.ERROR, 0
82 | }
83 | return articles, errmsg.SUCCESS, total
84 | }
85 |
86 | // todo 查询分类下的所有文章
87 | func GetCateArticle(id int, pageSize int, pageNum int) ([]Article, int, int) {
88 | var cateArticleList []Article
89 | var total int
90 | DB := db.Model(&Article{})
91 | if id > 0 {
92 | DB = DB.Where("cid = ?", id)
93 | }
94 | err := DB.Limit(pageSize).Offset((pageNum - 1) * pageSize).Find(&cateArticleList).Error
95 | //查找总数
96 | _ = DB.Count(&total).Error
97 | if err != nil {
98 | return cateArticleList, errmsg.ERROR_CATE_NOT_EXIST, 0
99 | }
100 | return cateArticleList, errmsg.SUCCESS, total
101 | }
102 |
103 | //todo 查询单个文章详细信息
104 | func GetArticleInfo(id int) (Article, int) {
105 | var article Article
106 | //根据文章id查找出其评论
107 | comments, _ := GetCommentsByArticleId(id)
108 |
109 | err := db.Where("id = ?", id).First(&article).Error
110 | for _, comment := range comments {
111 | article.Comments = append(article.Comments, comment)
112 | }
113 | if err != nil {
114 | return article, errmsg.ERROR_ARTICLE_NOT_EXIST
115 | }
116 | return article, errmsg.SUCCESS
117 | }
118 |
119 | //todo 编辑文章信息
120 | func EditArticle(id int, data *Article) int {
121 | //利用map集合更新
122 | var maps = make(map[string]interface{})
123 | maps["title"] = data.Title
124 | maps["cid"] = data.Cid
125 | maps["desc"] = data.Desc
126 | maps["content"] = data.Content
127 | maps["img"] = data.Img
128 | err = db.Model(&Article{}).Where("id = ?", id).Update(maps).Error
129 | if err != nil {
130 | return errmsg.ERROR
131 | }
132 | return errmsg.SUCCESS
133 | }
134 |
135 | //todo 删除文章
136 | func DeleteArticle(id int) int {
137 | //删除文章时,需要把与之相关联的中间表也删除
138 | DeleteMidByArticleId(id)
139 | err = db.Where("id = ?", id).Delete(&Article{}).Error
140 | if err != nil {
141 | return errmsg.ERROR
142 | }
143 | return errmsg.SUCCESS
144 | }
145 |
--------------------------------------------------------------------------------
/model/Category.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | //分类模型
4 |
5 | import (
6 | "demo1/MyBlog/utils/errmsg"
7 | "github.com/jinzhu/gorm"
8 | )
9 |
10 | type Category struct {
11 | ID uint `gorm:"primary_key;auto_increment" json:"id"`
12 | Name string `gorm:"type:varchar(20);not null" json:"name"`
13 | }
14 |
15 | //todo 查询分类是否存在
16 | func CheckCategory(name string) (code int) {
17 | var cate Category
18 | db.Model(&Category{}).Select("id").Where("name = ?", name).First(&cate)
19 | if cate.ID > 0 {
20 | return errmsg.ERROR_CATENAME_USED //1001
21 | }
22 | return errmsg.SUCCESS
23 | }
24 |
25 | //todo 添加分类
26 | func CreateCategory(data *Category) int {
27 | //密码加密
28 | //data.Password=ScryptPw(data.Password)
29 | err = db.Model(&Category{}).Create(&data).Error
30 | if err != nil {
31 | return errmsg.ERROR //500
32 | }
33 | return errmsg.SUCCESS
34 | }
35 |
36 | //todo 查询分类列表,带分页效果
37 | func GetCategory(pageSize int,pageNum int )([]Category,int,error) {
38 | var cates []Category
39 | var total int
40 | err=db.Model(&Category{}).Count(&total).Error
41 | if err != nil {
42 | return nil,0,err
43 | }
44 | err=db.Model(&Category{}).Limit(pageSize).Offset((pageNum-1)*pageSize).Find(&cates).Error
45 | if err != nil &&err!=gorm.ErrRecordNotFound{
46 | return nil,0,err
47 | }
48 | return cates,total,nil
49 | }
50 |
51 | //todo 编辑分类信息
52 | func EditCategory(id int,data *Category) int {
53 | //利用map集合更新
54 | var maps=make(map[string]interface{})
55 | maps["name"]=data.Name
56 | err = db.Model(&Category{}).Where("id = ?", id).Update(maps).Error
57 | if err != nil {
58 | return errmsg.ERROR
59 | }
60 | return errmsg.SUCCESS
61 | }
62 | //todo 删除分类
63 | func DeleteCategory( id int) int{
64 | err= db.Where("id = ?",id).Delete(&Category{}).Error
65 | if err != nil {
66 | return errmsg.ERROR
67 | }
68 | return errmsg.SUCCESS
69 | }
70 | //通过id查找分类
71 | func FindCategoryById(id int)(*Category,error) {
72 | var cate Category
73 | err=db.Model(&Category{}).Where("id = ? ",id).Find(&cate).Error
74 | if err != nil {
75 | return nil,err
76 | }
77 | return &cate,nil
78 | }
--------------------------------------------------------------------------------
/model/Comment.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | //评论模型
4 |
5 | import (
6 | "demo1/MyBlog/utils/errmsg"
7 | "github.com/jinzhu/gorm"
8 | )
9 |
10 | type Comment struct {
11 | gorm.Model
12 | Commentator string `gorm:"type:varchar(20);not null" json:"commentator"`
13 | Content string `gorm:"type:longtext;not null" json:"content"`
14 | ArticleID int `gorm:"type:int;not null" json:"article_id"`
15 | ParentID int `gorm:"type:int " json:"parent_id"`
16 | }
17 |
18 | //todo 根据id删除评论
19 | func DeleteComment(id int) int {
20 | err=db.Where("id = ?",id).Delete(&Comment{}).Error
21 | if err != nil {
22 | return errmsg.ERROR
23 | }
24 | return errmsg.SUCCESS
25 | }
--------------------------------------------------------------------------------
/model/Email.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "demo1/MyBlog/utils"
5 | "demo1/MyBlog/utils/errmsg"
6 | "fmt"
7 | "gopkg.in/gomail.v2"
8 | "strconv"
9 | )
10 |
11 | func SendEmail(to string,title string,text string) int{
12 | m := gomail.NewMessage()
13 |
14 | m.SetHeader("From", utils.FromEmail)
15 | m.SetHeader("To", to)
16 | //m.SetAddressHeader("Cc", "dan@example.com", "Dan")
17 | m.SetHeader("Subject", title)
18 | m.SetBody("text/html", text)
19 | //m.Attach("/home/Alex/lolcat.jpg")
20 | serverPort,_:= strconv.Atoi(utils.ServerPort)
21 | d := gomail.NewDialer(utils.ServerHost, serverPort , "1685290935@qq.com", utils.FromPassword)
22 |
23 | if err := d.DialAndSend(m); err != nil {
24 | return errmsg.ERROR_EMAIL_SEND
25 | }
26 | fmt.Println("发送结束")
27 | return errmsg.SUCCESS
28 | }
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/model/Profile.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "demo1/MyBlog/utils"
5 | "demo1/MyBlog/utils/errmsg"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | type Profile struct {
12 | ID int `gorm:"primaryKey" json:"id"`
13 | Name string `gorm:"type:varchar(20)" json:"name"`
14 | Desc string `gorm:"type:varchar(200)" json:"desc"`
15 | QqChat string `gorm:"type:varchar(32)" json:"qq_chat"`
16 | WeChat string `gorm:"ype:varchar(32)" json:"wechat"`
17 | Weibo string `gorm:"type:varchar(32)" json:"weibo"`
18 | Email string `gorm:"type:varchar(32)" json:"email"`
19 | Img string `gorm:"type:varchar(80)" json:"img"`
20 | Avatar string `gorm:"type:varchar(80)" json:"avatar"`
21 | }
22 |
23 | //获取个人信息
24 | func GetProfileById(id int) (*Profile, int) {
25 | var profile Profile
26 | err = db.Model(&profile).Where("ID = ?", id).First(&profile).Error
27 | //profile:=Redis.Get("profile")
28 | if err != nil {
29 | return nil, errmsg.ERROR
30 | }
31 | return &profile, errmsg.SUCCESS
32 | }
33 |
34 | //获取个人信息
35 | func GetProfile(c *gin.Context) (*Profile, int) {
36 | var profile *Profile
37 | sessionId, err := c.Cookie(SessionName)
38 | //fmt.Println("seesionId:",sessionId)
39 | if err != nil {
40 | return nil, errmsg.ERROR
41 | }
42 | result, err := Redis.Get(sessionId).Result()
43 | if err != nil {
44 | return nil, errmsg.ERROR
45 | }
46 | err = json.Unmarshal([]byte(result), &profile)
47 | if err != nil {
48 | fmt.Println("unmarshall json false")
49 | }
50 | return profile, errmsg.SUCCESS
51 | }
52 |
53 | //更新个人信息
54 | func UpdateProfile(c *gin.Context, id int, profile *Profile) int {
55 | var _profile *Profile
56 | err = db.Model(&Profile{}).Where("ID = ?", id).Update(profile).Error
57 | if err != nil {
58 | return errmsg.ERROR
59 | }
60 | sessionId, err := c.Cookie(SessionName)
61 | if err != nil {
62 | return errmsg.ERROR
63 | }
64 | result, err := Redis.Get(sessionId).Result()
65 | if err != nil {
66 | return errmsg.ERROR
67 | }
68 | err = json.Unmarshal([]byte(result), &_profile)
69 | if err != nil {
70 | fmt.Println("unmarshall json false")
71 | }
72 | if _profile.ID == profile.ID {
73 | //需要更新redis
74 | profileJson, _ := json.Marshal(profile)
75 | _, err = Redis.Set(sessionId, string(profileJson), utils.Expiration).Result()
76 | if err != nil {
77 | return errmsg.ERROR
78 | }
79 | }
80 | return errmsg.SUCCESS
81 | }
82 |
83 | func CreateProfile(profile *Profile) int {
84 | err = db.Model(&Profile{}).Create(&profile).Error
85 | if err != nil {
86 | return errmsg.ERROR
87 | }
88 | return errmsg.SUCCESS
89 | }
90 |
--------------------------------------------------------------------------------
/model/Update.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "context"
5 | "demo1/MyBlog/utils"
6 | "demo1/MyBlog/utils/errmsg"
7 | "github.com/qiniu/api.v7/v7/auth/qbox"
8 | "github.com/qiniu/api.v7/v7/storage"
9 | "mime/multipart"
10 | )
11 | //使用七牛对象存储
12 | var AccessKey = utils.AccessKey
13 | var SecretKey =utils.SecretKey
14 | var Bucket =utils.Bucket
15 | var ImgUrl = utils.QiniuServer
16 |
17 | //todo 上传文件
18 | func UploadFile(file multipart.File,fileSize int64)(string,int) {
19 | putPolicy:= storage.PutPolicy{
20 | Scope: Bucket,
21 | }
22 | mac:=qbox.NewMac(AccessKey,SecretKey)
23 | upToken:=putPolicy.UploadToken(mac)
24 |
25 | cfg:=storage.Config{
26 | Zone: &storage.ZoneHuadong,
27 | UseCdnDomains: false,
28 | UseHTTPS: false,
29 | }
30 |
31 | putExtra:=storage.PutExtra{}
32 |
33 | formUploader:=storage.NewFormUploader(&cfg)
34 | ret:=storage.PutRet{}
35 |
36 | err:= formUploader.PutWithoutKey(context.Background(),&ret,upToken,file,fileSize,&putExtra)
37 | if err != nil {
38 | return "",errmsg.ERROR
39 | }
40 | url:=ImgUrl+ret.Key
41 | return url,errmsg.SUCCESS
42 | }
43 |
--------------------------------------------------------------------------------
/model/User.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "demo1/MyBlog/proto"
5 | "demo1/MyBlog/utils/errmsg"
6 | "encoding/base64"
7 | "fmt"
8 | "github.com/jinzhu/gorm"
9 | "golang.org/x/crypto/scrypt"
10 | "log"
11 | "strconv"
12 | )
13 |
14 | type User struct {
15 | gorm.Model
16 | Username string `gorm:"type:varchar(20);not null" json:"username" validate:"required,min=4,max=12" label:"用户名"`
17 | Password string `gorm:"type:varchar(20);not null" json:"password" validate:"required,min=6,max=20" label:"密码"`
18 | Email string `gorm:"type:varchar(32);not null" json:"email" validate:"required,email" label:"邮箱"`
19 | Role int `gorm:"type:int ;DEFAULT:2" json:"role" validate:"required,gte=2" label:"角色"`
20 | Article []Article `gorm:"many2many:user_article"`
21 | Status string `gorm:"type:varchar(12)" default:"N"` //激活状态
22 | Code string `gorm:"type:varchar(80)"` //激活码
23 | }
24 |
25 | //todo 查询用户是否存在
26 | func CheckUser(name string) (code int) {
27 | var user User
28 | db.Model(&user).Select("id").Where("username = ?", name).First(&user)
29 | if user.ID > 0 {
30 | return errmsg.ERROR_USERNAME_USERD //1001
31 | }
32 | return errmsg.SUCCESS
33 | }
34 |
35 | //todo 添加用户
36 | func CreateUser(data *User) (*User, int) {
37 | //密码加密
38 | //data.Password=ScryptPw(data.Password)
39 | err = db.Model(&User{}).Create(&data).Error
40 | if err != nil {
41 | return nil, errmsg.ERROR //500
42 | }
43 | return data, errmsg.SUCCESS
44 | }
45 |
46 | //todo 查询用户详细信息,包括文章
47 | func GetUserInfo(id int) (User, int) {
48 | var user User
49 |
50 | err = db.Model(&user).Preload("Article").Where("id = ?", id).First(&user).Error
51 | if err != nil {
52 | return user, errmsg.ERROR
53 | }
54 | return user, errmsg.SUCCESS
55 | }
56 |
57 | //todo 查询用户列表,带分页效果
58 | func GetUsers(IdOrName string, pageSize int, pageNum int) ([]User, int, error) {
59 | var users []User
60 | var total int
61 | DB := db.Model(&users)
62 | Id, err := strconv.Atoi(IdOrName)
63 | if err != nil {
64 | if IdOrName != "" {
65 | DB = DB.Where("username like ?", "%"+IdOrName+"%")
66 | }
67 | } else {
68 | if Id > 0 {
69 | DB = DB.Where("id = ?", Id)
70 | }
71 | }
72 | err = DB.Count(&total).Error
73 | if err != nil {
74 | return nil, 0, err
75 | }
76 | err = DB.Limit(pageSize).Offset((pageNum - 1) * pageSize).Find(&users).Error
77 | if err != nil {
78 | return nil, 0, err
79 | }
80 |
81 | return users, total, nil
82 | }
83 |
84 | //todo 编辑用户
85 | func EditUser(id int, data *proto.ReqEditUser) int {
86 | var maps = make(map[string]interface{})
87 |
88 | maps["username"] = data.UserName
89 | maps["role"] = data.Role
90 | maps["email"] = data.Email
91 | err = db.Model(&User{}).Where("id = ?", id).Update(maps).Error
92 | if err != nil {
93 | return errmsg.ERROR
94 | }
95 | return errmsg.SUCCESS
96 | }
97 |
98 | //todo 删除用户
99 | func DeleteUser(id int) int {
100 | //删除与该用户相关联的中间表
101 | DeleteMidByUserId(id)
102 | fmt.Println(id)
103 | err = db.Model(&User{}).Where("id = ?", id).Delete(&User{}).Error
104 |
105 | if err != nil {
106 | return errmsg.ERROR
107 | }
108 | return errmsg.SUCCESS
109 | }
110 |
111 | //在调用函数之前执行
112 | func (u *User) BeforeSave() {
113 | u.Password = ScryptPw(u.Password)
114 | }
115 |
116 | // todo 使用scrypt密码加密
117 | func ScryptPw(password string) string {
118 | const KeyLen = 10
119 | salt := make([]byte, 8)
120 | salt = []byte{12, 32, 14, 6, 66, 22, 43, 11}
121 | //加密
122 | HashPw, err := scrypt.Key([]byte(password), salt, 16384, 8, 1, KeyLen)
123 | if err != nil {
124 | log.Fatal(err)
125 | }
126 | //将加密后的密码转化为字符串
127 | fpw := base64.StdEncoding.EncodeToString(HashPw)
128 | return fpw
129 | }
130 |
131 | //todo 登录验证
132 | func CheckLogin(username string, password string) (*User, int) {
133 | var user User
134 | db.Where("username = ?", username).First(&user)
135 | //fmt.Println(user)
136 | if user.Status == "N" {
137 | return nil, errmsg.ERROR_EMAIL_HAVE_NOT_ACTIVE
138 | }
139 | //判断该用户是否存在
140 | if user.ID == 0 {
141 | return nil, errmsg.ERROR_USER_NOT_EXIST
142 | }
143 | //判断用户密码是否正确
144 | if ScryptPw(password) != user.Password {
145 | return nil, errmsg.ERROR_PASSWORD_WRONG
146 | }
147 | //用户无权限
148 | //if user.Role!=2{
149 | // return errmsg.ERROR_USER_NO_RIGHT
150 | //}
151 |
152 | return &user, errmsg.SUCCESS
153 | }
154 |
155 | //todo 通过用户名查找用户id
156 | func FindUserIdByName(username interface{}) uint {
157 |
158 | var user User
159 |
160 | db.Model(&user).Where("username = ?", username).First(&user)
161 |
162 | return user.ID
163 |
164 | }
165 |
166 | //todo 通过激活码查找用户并且激活
167 | func UpdateUserStatus(code string) int {
168 |
169 | err = db.Model(&User{}).Where("code = ?", code).UpdateColumn(map[string]interface{}{
170 | "status": "Y",
171 | }).Error
172 | if err != nil {
173 | return errmsg.ERROR_EMAIL_ACTIVE
174 | }
175 | //激活
176 | return errmsg.SUCCESS
177 | }
178 |
179 | //todo 通过邮箱查找用户
180 | func GetUserByEmail(email string) (User, int) {
181 | var user User
182 |
183 | err := db.Where("email = ?", email).First(&user).Error
184 | if err != nil {
185 | return user, errmsg.ERROR_USER_NOT_EXIST
186 | }
187 | return user, errmsg.SUCCESS
188 | }
189 |
--------------------------------------------------------------------------------
/model/UserArticle.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "demo1/MyBlog/utils/errmsg"
5 | )
6 |
7 | type UserArticle struct {
8 | ArticleId uint `gorm:"type:not null" json:"article_id"`
9 | UserId uint `gorm:"type:not null" json:"user_id"`
10 | }
11 |
12 | //todo 创建中间表
13 | func CreateUserArticle(userArticle *UserArticle) int {
14 | err=db.Model(&UserArticle{}).Create(&userArticle).Error
15 | if err != nil {
16 | return errmsg.ERROR //500
17 | }
18 | return errmsg.SUCCESS
19 | }
20 | //todo 根据用户id删除中间表
21 | func DeleteMidByUserId(id int) int {
22 | err=db.Model(&UserArticle{}).Where("user_id = ?",id).Delete(&UserArticle{}).Error
23 | if err != nil {
24 | return errmsg.ERROR //500
25 | }
26 | return errmsg.SUCCESS
27 | }
28 | //todo 根据文章id删除中间表
29 | func DeleteMidByArticleId(id int) int {
30 | err=db.Model(&UserArticle{}).Where("article_id = ?",id).Delete(&UserArticle{}).Error
31 | if err != nil {
32 | return errmsg.ERROR //500
33 | }
34 | return errmsg.SUCCESS
35 | }
36 |
--------------------------------------------------------------------------------
/model/db.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | //数据库入口
4 |
5 | import (
6 | "demo1/MyBlog/utils"
7 | "fmt"
8 | _ "github.com/garyburd/redigo/redis"
9 | "github.com/go-redis/redis"
10 | _ "github.com/go-sql-driver/mysql"
11 | "github.com/jinzhu/gorm"
12 | "time"
13 | )
14 |
15 | //定义全局变量
16 | var db *gorm.DB
17 | var err error
18 | var Redis *redis.Client
19 |
20 | //初始化数据库
21 | func InitDb() {
22 | //连接数据库
23 | db, err = gorm.Open(utils.Db, fmt.Sprintf("%s:%s@(%s%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
24 | utils.DbUser,
25 | utils.DbPassWord,
26 | utils.DbHost,
27 | utils.DbPort,
28 | utils.DbName,
29 | ))
30 | if err != nil {
31 | fmt.Println("连接数据库失败,请检查参数: ", err)
32 | return
33 | }
34 | //defer db.Close()
35 |
36 | //禁用默认表的复数形式
37 | db.SingularTable(true)
38 | //迁移
39 | db.AutoMigrate(&User{}, &Article{}, &Category{}, &Comment{}, &UserArticle{}, &Profile{})
40 |
41 | //设置连接池的最大闲置连接数
42 | db.DB().SetMaxIdleConns(10)
43 | //设置连接池中的最大连接数量
44 | db.DB().SetMaxOpenConns(100)
45 | //设置连接的最大复用时间
46 | db.DB().SetConnMaxLifetime(10 * time.Second)
47 | }
48 |
49 | func InitRedis() {
50 | Redis = redis.NewClient(&redis.Options{
51 | Addr: utils.RedisAddr,
52 | Password: utils.RedisPassword, // no password set
53 | DB: utils.RedisDB, // use default DB
54 | })
55 | _, err = Redis.Ping().Result()
56 | if err != nil {
57 | fmt.Println("redis连接失败")
58 | return
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/model/session.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | const SessionName = "session"
8 |
9 | //设置客户端session_id
10 | func SetSession(c *gin.Context, sessionID string, expireTime int) {
11 | c.SetCookie(SessionName, sessionID, expireTime, "/", "localhost", false, true)
12 | //http.SetCookie(c.Writer, &http.Cookie{
13 | // Name: SessionName,
14 | // Value: sessionID,
15 | // MaxAge: expireTime,
16 | // Path: "/",
17 | //})
18 | }
19 |
20 | func ClearSession(c *gin.Context) {
21 | c.SetCookie(SessionName, "", -1, "/", "localhost", false, true)
22 | }
23 |
--------------------------------------------------------------------------------
/proto/req.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | type (
4 | ReqCommon struct {
5 | PageNum int `json:"pagenum"`
6 | PageSize int `json:"pagesize"`
7 | }
8 | ReqFindUser struct {
9 | PageNum int `json:"pagenum"`
10 | PageSize int `json:"pagesize"`
11 | IdOrName string `json:"idorname"`
12 | }
13 | ReqAddUser struct {
14 | UserName string `json:"username"`
15 | Password string `json:"password"`
16 | Email string `json:"email"`
17 | Role int `json:"role"`
18 | }
19 | ReqEditUser struct {
20 | Id int `json:"id"`
21 | UserName string `json:"username"`
22 | Email string `json:"email"`
23 | Role int `json:"role"`
24 | }
25 | ReqFindArticle struct {
26 | PageNum int `json:"pagenum"`
27 | PageSize int `json:"pagesize"`
28 | Title string `json:"title"`
29 | }
30 | ReqCateArticle struct {
31 | PageNum int `json:"pagenum"`
32 | PageSize int `json:"pagesize"`
33 | Id int `json:"id"`
34 | }
35 | )
36 |
--------------------------------------------------------------------------------
/proto/rsp.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import "time"
4 |
5 | type RspFindArticle struct {
6 | ID uint `json:"id"`
7 | Title string `json:"title"`
8 | Name string `json:"name"`
9 | CreatedAt time.Time `json:"CreatedAt"`
10 | Desc string ` json:"desc"`
11 | Content string `json:"content"`
12 | Img string ` json:"img"`
13 | }
--------------------------------------------------------------------------------
/routes/router.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | v1 "demo1/MyBlog/api/v1"
5 | "demo1/MyBlog/middleware"
6 | "demo1/MyBlog/utils"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | )
10 |
11 | func InitRouter() {
12 |
13 | gin.SetMode(utils.AppMode)
14 | r := gin.New()
15 | r.Use(gin.Recovery())
16 | r.Use(middleware.Logger())
17 | r.Use(middleware.Cors())
18 | //这里是后台管理的页面加载
19 | r.LoadHTMLGlob("static/admin/index.html")
20 | r.Static("admin/static", "static/admin/static")
21 | r.StaticFile("admin/favicon.ico", "static/admin/favicon.ico")
22 | r.GET("admin", func(c *gin.Context) {
23 | c.HTML(http.StatusOK, "index.html", nil)
24 | })
25 | //这里是博客前台展示的页面加载,如需要可以打开,切记不可同时打开
26 | //r.LoadHTMLGlob("static/front/index.html")
27 | //r.Static("front/static", "static/front/static")
28 | //r.StaticFile("front/favicon.ico", "static/front/favicon.ico")
29 | //r.GET("front", func(c *gin.Context) {
30 | // c.HTML(http.StatusOK, "index.html", nil)
31 | //})
32 | //设置中间件,以下操作需要用户权限(凭证)
33 | auth := r.Group("api/v1")
34 | auth.Use(middleware.JwtToken())
35 | {
36 |
37 | // 用户模块的路由接口
38 |
39 | //编辑用户信息
40 | auth.POST("user/update", v1.EditUser)
41 | //删除用户
42 | auth.DELETE("user/:id", v1.DeleteUser)
43 |
44 | // 分类模块的路由接口
45 | //添加分类
46 | auth.POST("category/add", v1.AddCategory)
47 |
48 | //编辑分类信息
49 | auth.PUT("category/:id", v1.EditCategory)
50 | //删除分类
51 | auth.DELETE("category/:id", v1.DeleteCategory)
52 |
53 | // 文章模块的路由接口
54 | //添加文章
55 | auth.POST("article/add", v1.AddArticle)
56 | //添加评论
57 | auth.POST("comment", v1.AddComment)
58 | //编辑文章
59 | auth.PUT("article/:id", v1.EditArticle)
60 | //删除文章
61 | auth.DELETE("article/:id", v1.DeleteArticle)
62 | //删除评论
63 | auth.DELETE("comment/:id", v1.DeleteComment)
64 | //上传文件
65 | auth.POST("upload", v1.Upload)
66 |
67 | //更新个人设置
68 | auth.PUT("profile", v1.UpdateProfile)
69 | }
70 |
71 | routeV1 := r.Group("api/v1")
72 | {
73 | // 添加用户
74 | routeV1.POST("user/add", v1.AddUser)
75 | //查询所有用户
76 | routeV1.POST("users", v1.GetUsers)
77 | //查询用户详细信息,包括文章
78 | routeV1.GET("user/:id", v1.GetUserInfo)
79 | //通过id查询分类信息
80 | routeV1.GET("category/:id", v1.FindCategoryById)
81 | //查询所有分类
82 | routeV1.GET("category", v1.GetCategory)
83 | //查询所有文章信息
84 | routeV1.POST("articles", v1.GetArticle)
85 | //查询某分类下的所有文章
86 | routeV1.GET("article/cate/:id", v1.GetCateArticle)
87 | //查询谋篇文章的基本信息
88 | routeV1.GET("article/info/:id", v1.GetArticleInfo)
89 | //查询某文章下的所有评论
90 | routeV1.GET("comment/:id", v1.GetComments)
91 | //登录
92 | routeV1.POST("login", v1.Login)
93 | //注册用户
94 | routeV1.POST("/register", v1.Register)
95 | //邮件激活
96 | routeV1.GET("/active", v1.ActiveEmail)
97 | //登录发送邮件,需要参数email
98 | routeV1.GET("sendemail", v1.SendEmailForCode)
99 | //使用邮箱登录,需要参数email和验证码
100 | routeV1.GET("loginbyemail", v1.LoginByEmail)
101 |
102 | //获取个人信息
103 | routeV1.GET("profile", v1.GetProfile)
104 | }
105 |
106 | r.Run(utils.HttpPort)
107 | }
108 |
--------------------------------------------------------------------------------
/serve.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
#变量
PROJECTNAME="myblog"
PROJECTBASE="."
PROJECTBIN="$PROJECTBASE/bin"
PROJECTLOGS="$PROJECTBASE/log"
prog=$PROJECTNAME
#获取当前目录
CURDIR=$(dirname $0)
cd $CURDIR
#运行服务
start() {
echo -e "Begin to compile the project ---$PROJECTNAME..."
#编译go项目
go build -o $PROJECTNAME main.go
#赋予权限
chmod 777 "$CURDIR/$PROJECTNAME"
echo "Compilation completed"
echo "starting $PROJECTNAME,please waiting..."
#后台运行项目
nohup ./$PROJECTNAME > $PROJECTLOGS/run.log 2>&1 &
echo -e "ok"
}
#暂停服务
stop(){
echo -e $"Stopping the project ---$prog: "
#获取进程
pid=$(ps -ef | grep $prog | grep -v grep | awk '{print $2}')
if [ "$pid" ]; then
echo -n $"kill process pid: $pid "
#杀掉进程
kill -9 $pid
ret=0
#多次循环杀掉进程
for ((i=1;i<=15;i++)); do
sleep 1
pid=$(ps -ef | grep $prog | grep -v grep | awk '{print $2}')
if [ "$pid" ]; then
kill -9 $pid
ret=0
else
ret=1
break
fi
done
if [ "$ret" ]; then
echo -e $"ok"
else
echo -e $"no"
fi
else
echo -e $"no program process to stop"
fi
}
#重启服务
restart(){
stop
sleep 2
start
}
#判断第一个参数
case "$1" in
start)
$1
;;
stop)
$1
;;
restart)
$1
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 2
;;
esac
--------------------------------------------------------------------------------
/static/admin/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/static/admin/favicon.ico
--------------------------------------------------------------------------------
/static/admin/index.html:
--------------------------------------------------------------------------------
1 | admin
--------------------------------------------------------------------------------
/static/front/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/static/front/favicon.ico
--------------------------------------------------------------------------------
/static/front/index.html:
--------------------------------------------------------------------------------
1 | front
--------------------------------------------------------------------------------
/static/front/static/css/app.e55e68b9.css:
--------------------------------------------------------------------------------
1 | .container[data-v-dec4de66]{height:100%;background-color:#282c34;width:100%}.loginBox[data-v-dec4de66]{width:400px;height:300px;background-color:#fff;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border:1px solid #000;border-radius:9px}.loginForm[data-v-dec4de66]{width:100%;position:absolute;bottom:10%;padding:0 20px;box-sizing:border-box}.loginBtn[data-v-dec4de66]{display:flex;justify-content:flex-end}
--------------------------------------------------------------------------------
/static/front/static/js/app.7b3b961d.js:
--------------------------------------------------------------------------------
1 | (function(t){function e(e){for(var r,o,i=e[0],c=e[1],l=e[2],d=0,f=[];d=4&&t.length<=12||"用户名长度必须在4-12之间"}],passwordRules:[function(t){return!!t||"密码不能为空"},function(t){return t&&t.length>=6&&t.length<=20||"密码长度必须在6-20之间"}]}},methods:{resetForm:function(){this.$refs.loginFormRef.reset()},login:function(){var t=this;return Object(b["a"])(regeneratorRuntime.mark((function e(){var a,r,n;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(a=t.$refs.loginFormRef.validate(),a){e.next=5;break}return e.abrupt("return");case 5:return e.next=7,t.$http.post("login",t.formdata);case 7:if(r=e.sent,n=r.data,200==n.code){e.next=11;break}return e.abrupt("return");case 11:window.sessionStorage.setItem("token",n.token),t.$router.push("/home");case 13:case"end":return e.stop()}}),e)})))()}}},_t=wt,kt=(a("a2c7"),a("4bd4")),xt=a("8654"),yt=Object(c["a"])(_t,ht,gt,!1,null,"dec4de66",null),Ct=yt.exports;w()(yt,{VApp:ot["a"],VBtn:x["a"],VForm:kt["a"],VTextField:xt["a"]}),r["a"].use(d["a"]);var Vt=[{path:"/login",name:"login",component:Ct,meta:{title:"请先登录"}},{path:"/home",name:"home",component:ut,children:[{path:"",component:rt,meta:{title:"欢迎来到GinBlog"}},{path:"detail/:id",component:jt,props:!0,meta:{title:"文章详情"}},{path:":id",component:rt,meta:{title:"欢迎来到GinBlog"},props:!0}]},{path:"*",redirect:"login"}],It=new d["a"]({mode:"history",base:"/front/",routes:Vt});It.beforeEach((function(t,e,a){t.meta.title&&(document.title=t.meta.title);var r=window.sessionStorage.getItem("token");if(console.log("token",r),"/"==t.path&&a("/login"),"/login"===t.path)return a();r||"/home"!=t.path?a():a("/login")}));var zt=It,Ot=a("f309");r["a"].use(Ot["a"]);var $t=new Ot["a"]({}),Rt=a("2f62");r["a"].use(Rt["a"]);var At=new Rt["a"].Store({state:{cateId:-1},getters:{},mutations:{saveCateId:function(t,e){t.cateId=e}},actions:{}}),qt=a("c1df"),Mt=a.n(qt),Pt=a("bc3a"),Ft=a.n(Pt);Ft.a.defaults.baseURL="http://localhost:8080/api/v1",Ft.a.defaults.withCredentials=!0,Ft.a.interceptors.request.use((function(t){return t.headers.Authorization="Bearer ".concat(window.sessionStorage.getItem("token")),t})),r["a"].prototype.$http=Ft.a,r["a"].config.productionTip=!1,r["a"].filter("dateformat",(function(t,e){return Mt()(t).format(e)})),new r["a"]({store:At,router:zt,vuetify:$t,render:function(t){return t(u)}}).$mount("#app")},"80e2":function(t,e,a){},a2c7:function(t,e,a){"use strict";a("80e2")}});
2 | //# sourceMappingURL=app.7b3b961d.js.map
--------------------------------------------------------------------------------
/utils/errmsg/errmsg.go:
--------------------------------------------------------------------------------
1 | package errmsg
2 |
3 | //错误处理常量及信息
4 |
5 | const (
6 | SUCCESS = 200
7 | ERROR = 500
8 | ERROR_Params = 5001
9 | //code=1000...用户模块的错误
10 | //用户名已被使用
11 | ERROR_USERNAME_USERD = 1001
12 | //用户名密码错误
13 | ERROR_PASSWORD_WRONG = 1002
14 | //用户不存在
15 | ERROR_USER_NOT_EXIST = 1003
16 | //TOKEN不存在
17 | ERROR_TOKEN_NOT_EXIST = 1004
18 | //TOKEN过期了
19 | ERROR_TOKEN_RUNTIEM = 1005
20 | //TOKEN错误
21 | ERROR_TOKEN_WRONG = 1006
22 | //TOKEN格式错误
23 | ERROR_TOKEN_TYPE_WRONG=1007
24 | //用户无权限
25 | ERROR_USER_NO_RIGHT=1008
26 |
27 | //code=2000...文章模块的错误
28 | ERROR_ARTICLE_NOT_EXIST=2001
29 | ERROR_ARTICLE_NO_COMMENTS=2002
30 |
31 | //code =3000...分类模块的错误
32 | ERROR_CATENAME_USED=3001
33 | ERROR_CATE_NOT_EXIST=3002
34 |
35 | //邮件激活码不存在
36 | ERROR_EMAIL_CODE_NOT_EXIST=4001
37 |
38 | ERROR_EMAIL_ACTIVE=4002
39 |
40 | ERROR_EMAIL_HAVE_NOT_ACTIVE=4003
41 | ERROR_EMAIL_SEND=4004
42 | ERROR_CODE_WRONG=4005
43 | )
44 |
45 | var codeMsg = map[int]string{
46 | SUCCESS :"OK",
47 | ERROR :"Fail",
48 | ERROR_Params:"参数有问题",
49 | ERROR_USERNAME_USERD :"用户名已存在!",
50 | ERROR_PASSWORD_WRONG :"密码错误!",
51 | ERROR_USER_NOT_EXIST :"用户名不存在!",
52 | ERROR_TOKEN_NOT_EXIST :"TOKEN不存在",
53 | ERROR_TOKEN_RUNTIEM :"TOKEN已过期",
54 | ERROR_TOKEN_WRONG :"TOKEN不正确",
55 | ERROR_TOKEN_TYPE_WRONG:"TOKEN格式错误",
56 | ERROR_USER_NO_RIGHT:"该用户无权限",
57 |
58 | ERROR_CATENAME_USED:"该分类已存在!",
59 | ERROR_CATE_NOT_EXIST:"给分类不存在!",
60 |
61 | ERROR_ARTICLE_NOT_EXIST:"文章不存在!",
62 | ERROR_ARTICLE_NO_COMMENTS:"该文章没有评论",
63 |
64 | ERROR_EMAIL_CODE_NOT_EXIST:"邮件激活码不存在",
65 | ERROR_EMAIL_ACTIVE:"邮件激活失败",
66 | ERROR_EMAIL_HAVE_NOT_ACTIVE:"邮箱尚未激活",
67 | ERROR_EMAIL_SEND:"邮件发送失败",
68 | ERROR_CODE_WRONG:"验证码错误",
69 | }
70 |
71 | func GetErrMsg(code int) string {
72 | return codeMsg[code]
73 | }
74 |
--------------------------------------------------------------------------------
/utils/setting.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | //解析配置文件并设置参数
4 |
5 | import (
6 | "fmt"
7 | "gopkg.in/ini.v1"
8 | )
9 |
10 | var (
11 | AppMode string
12 | HttpPort string
13 | JwtKey string
14 |
15 | Db string
16 | DbHost string
17 | DbPort string
18 | DbUser string
19 | DbPassWord string
20 | DbName string
21 |
22 | AccessKey string
23 | SecretKey string
24 | Bucket string
25 | QiniuServer string
26 |
27 | RedisAddr string
28 | RedisPassword string
29 | RedisDB int
30 |
31 | ServerHost string
32 | ServerPort string
33 | FromEmail string
34 | FromPassword string
35 | )
36 |
37 | func init() {
38 | //解析配置文件config.ini
39 | file, err := ini.Load("config/config.ini")
40 | if err != nil {
41 | fmt.Println("配置文件读取错误,请检查文件路径:", err)
42 | }
43 |
44 | //读取服务端相关配置
45 | LoadServer(file)
46 | //读取数据库相关配置
47 | LoadDate(file)
48 | //读取七牛云相关配置
49 | LoadQiniu(file)
50 | //获取redis相关配置
51 | LoadRedis(file)
52 | //获取email相关配置
53 | LoadEmailServer(file)
54 | }
55 |
56 | func LoadServer(file *ini.File) {
57 | AppMode = file.Section("server").Key("AppMode").MustString("debug")
58 | HttpPort = file.Section("server").Key("HttpPort").MustString(":80")
59 | JwtKey = file.Section("server").Key("JwtKey").MustString("45df45rds4")
60 | }
61 |
62 | func LoadDate(file *ini.File) {
63 | Db = file.Section("database").Key("Db").MustString("mysql")
64 | DbHost = file.Section("database").Key("DbHost").MustString("localhost")
65 | DbPort = file.Section("database").Key("DbPort").MustString(":3306")
66 | DbUser = file.Section("database").Key("DbUser").MustString("root")
67 | DbPassWord = file.Section("database").Key("DbPassWord").MustString("020821mnh")
68 | DbName = file.Section("database").Key("DbName").MustString("ginblog")
69 | }
70 |
71 | func LoadQiniu(file *ini.File) {
72 | AccessKey = file.Section("qiniuyun").Key("AccessKey").String()
73 | SecretKey = file.Section("qiniuyun").Key("SecretKey").String()
74 | Bucket = file.Section("qiniuyun").Key("Bucket").String()
75 | QiniuServer = file.Section("qiniuyun").Key("QiniuServer").String()
76 | }
77 | func LoadRedis(file *ini.File) {
78 | var redisSection = file.Section("redis")
79 | RedisAddr = redisSection.Key("RedisAddr").String()
80 | RedisPassword = redisSection.Key("RedisPassword").String()
81 | RedisDB = redisSection.Key("RedisDB").MustInt(0)
82 | }
83 | func LoadEmailServer(file *ini.File) {
84 | var emailSection=file.Section("email")
85 | ServerHost =emailSection.Key("ServerHost").String()
86 | ServerPort =emailSection.Key("ServerPort").String()
87 | FromEmail =emailSection.Key("FromEmail").String()
88 | FromPassword =emailSection.Key("FromPassword").String()
89 | }
90 |
--------------------------------------------------------------------------------
/utils/time.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "time"
4 |
5 | const (
6 | TimeFormatSecond = "2006-01-02 15:04:05"
7 | TimeFormatMinute = "2006-01-02 15:04"
8 | TimeFormatDateV1 = "2006-01-02"
9 | TimeFormatDateV2 = "2006_01_02"
10 | Expiration time.Duration = 24 * 60 * 60 * 1000 * 1000 * 1000 //过期时间
11 | )
12 |
--------------------------------------------------------------------------------
/utils/uuid.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | uuid "github.com/satori/go.uuid"
6 | "math/rand"
7 | "strings"
8 | "time"
9 | )
10 |
11 | //工具,生成唯一字符串(激活码)
12 | func CreateUUID() string {
13 | id := uuid.NewV4()
14 | return id.String()
15 | }
16 |
17 | //生成四位数验证码
18 | func CreateVcode() string {
19 | codeHub := "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
20 | rand.Seed(time.Now().Unix())
21 | var code strings.Builder
22 | for i := 0; i < 4; i++ {
23 | index := rand.Intn(len(codeHub))
24 | fmt.Fprintf(&code, "%c", codeHub[index])
25 | }
26 | //fmt.Println(code.String())
27 | return code.String()
28 | }
29 |
--------------------------------------------------------------------------------
/utils/validator/validator.go:
--------------------------------------------------------------------------------
1 | package validator
2 |
3 | import (
4 | "demo1/MyBlog/utils/errmsg"
5 | "fmt"
6 | "github.com/go-playground/locales/zh_Hans_CN"
7 | unTrans "github.com/go-playground/universal-translator"
8 | "github.com/go-playground/validator/v10"
9 | zhTrans "github.com/go-playground/validator/v10/translations/zh"
10 | "reflect"
11 | )
12 |
13 | //后端数据验证
14 | func Validate(data interface{}) (string, int) {
15 | validate:=validator.New()
16 | uni:=unTrans.New(zh_Hans_CN.New())
17 | trans,_:=uni.GetTranslator("zh_Hans_CN")
18 |
19 | err:= zhTrans.RegisterDefaultTranslations(validate,trans)
20 | if err != nil {
21 | fmt.Println("err: ",err)
22 | }
23 |
24 | validate.RegisterTagNameFunc(func(field reflect.StructField) string {
25 | label:=field.Tag.Get("label")
26 | return label
27 | })
28 |
29 | err = validate.Struct(data)
30 | if err != nil {
31 | for _,v:=range err.(validator.ValidationErrors){
32 | return v.Translate(trans),errmsg.ERROR
33 | }
34 | }
35 | return "",errmsg.SUCCESS
36 | }
--------------------------------------------------------------------------------
/web/admin/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/web/admin/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/web/admin/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | // 'plugin:vue/essential',
8 | '@vue/standard'
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint'
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/web/admin/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/web/admin/README.md:
--------------------------------------------------------------------------------
1 | # admin
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/web/admin/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ],
5 | plugins: [
6 | [
7 | "import",
8 | { libraryName: "ant-design-vue", libraryDirectory: "es", style: 'css' }
9 | ]
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/web/admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@tinymce/tinymce-vue": "^3.2.2",
12 | "ant-design-vue": "^1.7.8",
13 | "axios": "^0.24.0",
14 | "core-js": "^3.6.5",
15 | "tinymce": "^5.4.1",
16 | "vue": "^2.6.11",
17 | "vue-router": "^3.2.0",
18 | "vuex": "^3.6.2"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "~4.5.0",
22 | "@vue/cli-plugin-eslint": "~4.5.0",
23 | "@vue/cli-plugin-router": "~4.5.0",
24 | "@vue/cli-service": "~4.5.0",
25 | "@vue/eslint-config-standard": "^5.1.2",
26 | "babel-eslint": "^10.1.0",
27 | "babel-plugin-import": "^1.13.3",
28 | "eslint": "^6.7.2",
29 | "eslint-plugin-import": "^2.20.2",
30 | "eslint-plugin-node": "^11.1.0",
31 | "eslint-plugin-promise": "^4.2.1",
32 | "eslint-plugin-standard": "^4.0.0",
33 | "eslint-plugin-vue": "^6.2.2",
34 | "vue-template-compiler": "^2.6.11"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/web/admin/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/admin/public/favicon.ico
--------------------------------------------------------------------------------
/web/admin/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/web/admin/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
--------------------------------------------------------------------------------
/web/admin/src/assets/css/style.css:
--------------------------------------------------------------------------------
1 | html,body,#app{
2 | height: 100%;
3 | margin: 0;
4 | padding: 0;
5 | }
6 | .ant-card {
7 | margin:10px;
8 | background-color: #fff;
9 | }
10 |
11 | .ant-table{
12 | margin-top: 15px;
13 | }
--------------------------------------------------------------------------------
/web/admin/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/admin/src/assets/logo.png
--------------------------------------------------------------------------------
/web/admin/src/components/admin/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/web/admin/src/components/admin/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
23 |
24 |
--------------------------------------------------------------------------------
/web/admin/src/components/admin/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
欢迎来到GINBLOG后台管理页面
4 |
5 |
--------------------------------------------------------------------------------
/web/admin/src/components/admin/Nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 | 仪表盘
12 |
13 |
14 | 文章管理
15 | 写文章
16 | 文章列表
17 |
18 |
19 |
20 | 分类列表
21 |
22 |
23 |
24 | 用户列表
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
41 |
42 |
--------------------------------------------------------------------------------
/web/admin/src/components/article/AddArt.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ id ? '编辑文章':'新增文章' }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{item.name}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
28 | 点击上传
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{articleInfo.id?'更新':'提交'}}
41 |
42 | 取消
43 |
44 |
45 |
46 |
47 |
48 |
49 |
142 |
--------------------------------------------------------------------------------
/web/admin/src/components/article/ArtList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
文章列表
4 |
5 |
6 |
7 |
8 |
9 |
10 | 新增
11 |
12 |
13 |
14 |
15 | {{item.name}}
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
195 |
196 |
202 |
--------------------------------------------------------------------------------
/web/admin/src/components/category/CateList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
分类列表
4 |
5 |
6 |
7 | 新增
8 |
9 |
10 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
34 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
54 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
213 |
214 |
--------------------------------------------------------------------------------
/web/admin/src/components/editor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
63 |
64 |
67 |
--------------------------------------------------------------------------------
/web/admin/src/components/editor/langs/README.md:
--------------------------------------------------------------------------------
1 | This is where language files should be placed.
2 |
3 | Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/
4 |
--------------------------------------------------------------------------------
/web/admin/src/components/editor/plugins/code/plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved.
3 | * Licensed under the LGPL or a commercial license.
4 | * For LGPL see License.txt in the project root for license information.
5 | * For commercial licenses see https://www.tiny.cloud/
6 | *
7 | * Version: 5.4.2 (2020-08-17)
8 | */
9 | (function () {
10 | 'use strict';
11 |
12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
13 |
14 | var setContent = function (editor, html) {
15 | editor.focus();
16 | editor.undoManager.transact(function () {
17 | editor.setContent(html);
18 | });
19 | editor.selection.setCursorLocation();
20 | editor.nodeChanged();
21 | };
22 | var getContent = function (editor) {
23 | return editor.getContent({ source_view: true });
24 | };
25 |
26 | var open = function (editor) {
27 | var editorContent = getContent(editor);
28 | editor.windowManager.open({
29 | title: 'Source Code',
30 | size: 'large',
31 | body: {
32 | type: 'panel',
33 | items: [{
34 | type: 'textarea',
35 | name: 'code'
36 | }]
37 | },
38 | buttons: [
39 | {
40 | type: 'cancel',
41 | name: 'cancel',
42 | text: 'Cancel'
43 | },
44 | {
45 | type: 'submit',
46 | name: 'save',
47 | text: 'Save',
48 | primary: true
49 | }
50 | ],
51 | initialData: { code: editorContent },
52 | onSubmit: function (api) {
53 | setContent(editor, api.getData().code);
54 | api.close();
55 | }
56 | });
57 | };
58 |
59 | var register = function (editor) {
60 | editor.addCommand('mceCodeEditor', function () {
61 | open(editor);
62 | });
63 | };
64 |
65 | var register$1 = function (editor) {
66 | editor.ui.registry.addButton('code', {
67 | icon: 'sourcecode',
68 | tooltip: 'Source code',
69 | onAction: function () {
70 | return open(editor);
71 | }
72 | });
73 | editor.ui.registry.addMenuItem('code', {
74 | icon: 'sourcecode',
75 | text: 'Source code',
76 | onAction: function () {
77 | return open(editor);
78 | }
79 | });
80 | };
81 |
82 | function Plugin () {
83 | global.add('code', function (editor) {
84 | register(editor);
85 | register$1(editor);
86 | return {};
87 | });
88 | }
89 |
90 | Plugin();
91 |
92 | }());
93 |
--------------------------------------------------------------------------------
/web/admin/src/components/editor/plugins/code/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved.
3 | * Licensed under the LGPL or a commercial license.
4 | * For LGPL see License.txt in the project root for license information.
5 | * For commercial licenses see https://www.tiny.cloud/
6 | *
7 | * Version: 5.4.2 (2020-08-17)
8 | */
9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),o=function(o){var e=o.getContent({source_view:!0});o.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:e},onSubmit:function(e){var t,n;t=o,n=e.getData().code,t.focus(),t.undoManager.transact(function(){t.setContent(n)}),t.selection.setCursorLocation(),t.nodeChanged(),e.close()}})};!function t(){e.add("code",function(e){var t,n;return(t=e).addCommand("mceCodeEditor",function(){o(t)}),(n=e).ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:function(){return o(n)}}),n.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:function(){return o(n)}}),{}})}()}();
--------------------------------------------------------------------------------
/web/admin/src/components/editor/plugins/imagetools/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved.
3 | * Licensed under the LGPL or a commercial license.
4 | * For LGPL see License.txt in the project root for license information.
5 | * For commercial licenses see https://www.tiny.cloud/
6 | *
7 | * Version: 5.4.2 (2020-08-17)
8 | */
9 | !function(p){"use strict";var t,e,n,l=function(t){var e=t;return{get:function(){return e},set:function(t){e=t}}},r=tinymce.util.Tools.resolve("tinymce.PluginManager"),d=tinymce.util.Tools.resolve("tinymce.util.Tools"),o=function(){},f=function(t){return function(){return t}},i=f(!1),u=f(!0),a=function(){return c},c=(t=function(t){return t.isNone()},{fold:function(t,e){return t()},is:i,isSome:i,isNone:u,getOr:n=function(t){return t},getOrThunk:e=function(t){return t()},getOrDie:function(t){throw new Error(t||"error: getOrDie called on none.")},getOrNull:f(null),getOrUndefined:f(undefined),or:n,orThunk:e,map:a,each:o,bind:a,exists:i,forall:u,filter:a,equals:t,equals_:t,toArray:function(){return[]},toString:f("none()")}),s=function(n){var t=f(n),e=function(){return o},r=function(t){return t(n)},o={fold:function(t,e){return e(n)},is:function(t){return n===t},isSome:u,isNone:i,getOr:t,getOrThunk:t,getOrDie:t,getOrNull:t,getOrUndefined:t,or:e,orThunk:e,map:function(t){return s(t(n))},each:function(t){t(n)},bind:r,exists:r,forall:r,filter:function(t){return t(n)?o:c},toArray:function(){return[n]},toString:function(){return"some("+n+")"},equals:function(t){return t.is(n)},equals_:function(t,e){return t.fold(i,function(t){return e(n,t)})}};return o},v={some:s,none:a,from:function(t){return null===t||t===undefined?c:s(t)}};function m(t,e){return y(p.document.createElement("canvas"),t,e)}function h(t){var e=m(t.width,t.height);return g(e).drawImage(t,0,0),e}function g(t){return t.getContext("2d")}function y(t,e,n){return t.width=e,t.height=n,t}var w,b,I,T=window.Promise?window.Promise:(b=(w=function(t){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof t)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],L(t,_(U,this),_(A,this))}).immediateFn||"function"==typeof window.setImmediate&&window.setImmediate||function(t){p.setTimeout(t,1)},I=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},w.prototype["catch"]=function(t){return this.then(null,t)},w.prototype.then=function(n,r){var o=this;return new w(function(t,e){R.call(o,new x(n,r,t,e))})},w.all=function(){for(var t=[],e=0;e';
54 | if (contentStyle) {
55 | headHtml += '';
56 | }
57 | var cors = shouldUseContentCssCors(editor) ? ' crossorigin="anonymous"' : '';
58 | global$2.each(editor.contentCSS, function (url) {
59 | headHtml += '';
60 | });
61 | var bodyId = getBodyId(editor);
62 | var bodyClass = getBodyClass(editor);
63 | var isMetaKeyPressed = global$1.mac ? 'e.metaKey' : 'e.ctrlKey && !e.altKey';
64 | var preventClicksOnLinksScript = ' ';
65 | var directionality = editor.getBody().dir;
66 | var dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : '';
67 | var previewHtml = '' + '' + '' + headHtml + '' + '' + editor.getContent() + preventClicksOnLinksScript + '' + '';
68 | return previewHtml;
69 | };
70 |
71 | var open = function (editor) {
72 | var content = getPreviewHtml(editor);
73 | var dataApi = editor.windowManager.open({
74 | title: 'Preview',
75 | size: 'large',
76 | body: {
77 | type: 'panel',
78 | items: [{
79 | name: 'preview',
80 | type: 'iframe',
81 | sandboxed: true
82 | }]
83 | },
84 | buttons: [{
85 | type: 'cancel',
86 | name: 'close',
87 | text: 'Close',
88 | primary: true
89 | }],
90 | initialData: { preview: content }
91 | });
92 | dataApi.focus('close');
93 | };
94 |
95 | var register = function (editor) {
96 | editor.addCommand('mcePreview', function () {
97 | open(editor);
98 | });
99 | };
100 |
101 | var register$1 = function (editor) {
102 | editor.ui.registry.addButton('preview', {
103 | icon: 'preview',
104 | tooltip: 'Preview',
105 | onAction: function () {
106 | return editor.execCommand('mcePreview');
107 | }
108 | });
109 | editor.ui.registry.addMenuItem('preview', {
110 | icon: 'preview',
111 | text: 'Preview',
112 | onAction: function () {
113 | return editor.execCommand('mcePreview');
114 | }
115 | });
116 | };
117 |
118 | function Plugin () {
119 | global.add('preview', function (editor) {
120 | register(editor);
121 | register$1(editor);
122 | });
123 | }
124 |
125 | Plugin();
126 |
127 | }());
128 |
--------------------------------------------------------------------------------
/web/admin/src/components/editor/plugins/preview/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved.
3 | * Licensed under the LGPL or a commercial license.
4 | * For LGPL see License.txt in the project root for license information.
5 | * For commercial licenses see https://www.tiny.cloud/
6 | *
7 | * Version: 5.4.2 (2020-08-17)
8 | */
9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),g=tinymce.util.Tools.resolve("tinymce.Env"),w=tinymce.util.Tools.resolve("tinymce.util.Tools"),i=function(e){var t=function(t){var n="",i=t.dom.encode,e=t.getParam("content_style","");n+='',e&&(n+='");var o=t.getParam("content_css_cors",!1,"boolean")?' crossorigin="anonymous"':"";w.each(t.contentCSS,function(e){n+='"});var r,a,c,s,d,m,l,u=-1===(s=(r=t).getParam("body_id","tinymce","string")).indexOf("=")?s:(c=(a=r).getParam("body_id","","hash"))[a.id]||c,y=-1===(l=(d=t).getParam("body_class","","string")).indexOf("=")?l:(m=d).getParam("body_class","","hash")[m.id]||"",v='
130 |
131 |
--------------------------------------------------------------------------------
/web/admin/src/components/user/UserList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
用户列表
4 |
5 |
6 |
7 |
8 |
9 |
10 | 新增
11 |
12 |
13 |
20 | {{role==1?'管理员':'订阅者'}}
21 |
22 |
26 |
27 |
28 |
29 |
30 |
38 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 是
57 | 否
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
73 |
80 |
81 |
82 |
83 |
84 |
85 | 是
86 | 否
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
310 |
311 |
--------------------------------------------------------------------------------
/web/admin/src/config/index.js:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/web/admin/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import './plugin/antui'
5 | import './assets/css/style.css'
6 | import store from './store' //引入vuex
7 | Vue.prototype.$store = store //引入vuex
8 |
9 |
10 |
11 | Vue.config.productionTip = false
12 |
13 | new Vue({
14 | // store,
15 | router,
16 | render: h => h(App)
17 | }).$mount('#app')
18 |
--------------------------------------------------------------------------------
/web/admin/src/plugin/antui.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import {
3 | Button,
4 | FormModel ,
5 | Input,
6 | Icon,
7 | message,
8 | Layout,
9 | Menu,
10 | Card,
11 | Table,
12 | Row,
13 | Col,
14 | ConfigProvider,
15 | Modal,
16 | Select,
17 | Upload
18 | } from 'ant-design-vue'
19 |
20 | message.config({
21 | top: `100px`,
22 | duration: 2,
23 | maxCount: 3,
24 | });
25 |
26 | Vue.prototype.$message=message
27 | Vue.prototype.$confirm=Modal.confirm
28 |
29 | Vue.use(Button)
30 | Vue.use(FormModel)
31 | Vue.use(Input)
32 | Vue.use(Icon)
33 | Vue.use(Layout)
34 | Vue.use(Menu)
35 | Vue.use(Card)
36 | Vue.use(Table)
37 | Vue.use(Row)
38 | Vue.use(Col)
39 | Vue.use(ConfigProvider)
40 | Vue.use(Modal)
41 | Vue.use(Select)
42 | Vue.use(Upload)
43 |
--------------------------------------------------------------------------------
/web/admin/src/plugin/http.js:
--------------------------------------------------------------------------------
1 |
2 | import Vue from 'vue'
3 | import axios from 'axios'
4 |
5 | let Url='http://localhost:8080/api/v1'
6 |
7 | axios.defaults.baseURL=Url
8 | axios.defaults.withCredentials=true
9 | axios.interceptors.request.use(config=>{
10 | config.headers.Authorization=`Bearer ${window.sessionStorage.getItem("token")}`
11 | return config
12 | })
13 | Vue.prototype.$http=axios
14 |
15 | export {Url}
16 |
--------------------------------------------------------------------------------
/web/admin/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Login from '../views/Login.vue'
4 | import Admin from '../views/Admin.vue'
5 | Vue.use(VueRouter)
6 | //页面路由组建
7 | import Index from '../components/admin/Index.vue'
8 | import AddArt from '../components/article/AddArt.vue'
9 | import ArtList from '../components/article/ArtList.vue'
10 | import CateList from '../components/category/CateList.vue'
11 | import UserList from '../components/user/UserList.vue'
12 | import Profile from '../components/user/Profile.vue'
13 |
14 |
15 |
16 | const routes = [
17 | {
18 | path: '/login',
19 | name: 'login',
20 | component: Login
21 | },
22 | {
23 | path: '/',
24 | name: 'admin ',
25 | component: Admin,
26 | children:[
27 | {path:'index',component:Index},
28 | {path:'addart',component:AddArt},
29 | { path:'addart/:id',component:AddArt, props:true},
30 | {path:'artlist',component:ArtList},
31 | {path:'catelist',component:CateList},
32 | {path:'userlist',component:UserList},
33 | {path:'profile',component:Profile,props:true},
34 | ]
35 | }
36 | ]
37 |
38 | const router = new VueRouter({
39 | mode: 'history',
40 | base: process.env.BASE_URL,
41 | routes
42 | })
43 |
44 | router.beforeEach((to,from,next)=>{
45 | const token = window.sessionStorage.getItem('token')
46 | console.log("token",token)
47 | if(to.path==='/login')return next()
48 | if(!token &&to.path==='/'){
49 | Vue.prototype.$message.error("请先登录")
50 | next('/login')
51 | }else{
52 | next()
53 | }
54 | })
55 |
56 | export default router
57 |
--------------------------------------------------------------------------------
/web/admin/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 | import Vuex from "vuex"
3 | Vue.use(Vuex)
4 | export default new Vuex.Store({
5 | //当做data
6 | state:{
7 | // collapsed:false
8 | },
9 | //相当于计算属性
10 | getters:{
11 | },
12 | //同步一些方法
13 | mutations:{
14 | },
15 | //存放异步的方法
16 | actions:{
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/web/admin/src/views/Admin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{collapsed ? 'Blog' : 'My Blog'}}
6 |
7 |
8 |
9 |
10 |
11 | 仪表盘
12 |
13 |
14 |
15 | 文章管理
16 | 写文章
17 | 文章列表
18 |
19 |
20 |
21 | 分类列表
22 |
23 |
24 |
25 | 用户列表
26 |
27 |
28 |
29 | 个人设置
30 |
31 |
32 |
33 |
34 |
35 | (collapsed = !collapsed)"
39 | />
40 | 退出
41 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
78 |
79 |
--------------------------------------------------------------------------------
/web/admin/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 | 登录
32 | 取消
33 |
34 |
35 |
36 |
37 |
38 |
39 |
99 |
100 |
128 |
--------------------------------------------------------------------------------
/web/admin/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports={
2 | lintOnSave:false,
3 | publicPath:'/admin',
4 | outputDir:'dist',
5 | assetsDir:'static',
6 | }
--------------------------------------------------------------------------------
/web/front/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/web/front/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/web/front/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/essential',
8 | 'eslint:recommended'
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint'
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/web/front/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/web/front/README.md:
--------------------------------------------------------------------------------
1 | # front
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/web/front/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/web/front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "ant-design-vue": "^1.7.8",
12 | "axios": "^0.25.0",
13 | "core-js": "^3.6.5",
14 | "moment": "^2.29.1",
15 | "vue": "^2.6.11",
16 | "vue-bus": "^1.2.1",
17 | "vue-router": "^3.2.0",
18 | "vuetify": "^2.4.0",
19 | "vuex": "^3.6.2"
20 | },
21 | "devDependencies": {
22 | "@mdi/font": "^6.5.95",
23 | "@vue/cli-plugin-babel": "~4.5.0",
24 | "@vue/cli-plugin-eslint": "~4.5.0",
25 | "@vue/cli-plugin-router": "~4.5.0",
26 | "@vue/cli-service": "~4.5.0",
27 | "babel-eslint": "^10.1.0",
28 | "eslint": "^6.7.2",
29 | "eslint-plugin-vue": "^6.2.2",
30 | "sass": "~1.32.0",
31 | "sass-loader": "^10.0.0",
32 | "vue-cli-plugin-vuetify": "~2.4.5",
33 | "vue-template-compiler": "^2.6.11",
34 | "vuetify-loader": "^1.7.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/web/front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/front/public/favicon.ico
--------------------------------------------------------------------------------
/web/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/web/front/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
--------------------------------------------------------------------------------
/web/front/src/assets/bg1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/front/src/assets/bg1.png
--------------------------------------------------------------------------------
/web/front/src/assets/bg5.webp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/front/src/assets/bg5.webp.png
--------------------------------------------------------------------------------
/web/front/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/front/src/assets/logo.png
--------------------------------------------------------------------------------
/web/front/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/web/front/src/assets/my4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/front/src/assets/my4.jpg
--------------------------------------------------------------------------------
/web/front/src/assets/一色3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmnhs/gin-blog/dc3fb392c932c9b6a7621a67000d2f93fe8fb230/web/front/src/assets/一色3.jpg
--------------------------------------------------------------------------------
/web/front/src/components/ArticleList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{item.name}}
10 | {{item.title}}
11 |
12 |
13 |
14 |
15 |
16 | {{'mdi-calendar-month'}}
17 |
18 | {{item.CreatedAt | dateformat('YYYY-MM-DD HH::mm')}}
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
70 |
71 |
--------------------------------------------------------------------------------
/web/front/src/components/Detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{articleInfo.title}}
4 |
5 |
6 | {{'mdi-calendar-month'}}
7 |
8 | {{articleInfo.CreatedAt | dateformat('YYYY-MM-DD HH::mm')}}
9 |
10 |
11 |
12 |
{{articleInfo.desc}}
13 |
14 |
15 |
16 |
17 |
38 |
39 |
--------------------------------------------------------------------------------
/web/front/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{new Date().getFullYear()}}-MyBlog
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/web/front/src/components/Nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{profileInfo.name}}
10 |
11 |
12 |
13 |
14 |
15 | About Me :
16 | {{profileInfo.desc}}
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{`mdi-qqchat`}}
24 |
25 | {{profileInfo.qq_chat}}
26 |
27 |
28 |
29 | {{`mdi-wechat`}}
30 |
31 | {{profileInfo.wechat}}
32 |
33 |
34 |
35 | {{`mdi-sina-weibo`}}
36 |
37 | {{profileInfo.weibo}}
38 |
39 |
40 |
41 | {{`mdi-email`}}
42 |
43 | {{profileInfo.email}}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
80 |
81 |
--------------------------------------------------------------------------------
/web/front/src/components/TopBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 首页
9 | {{item.name}}
10 |
11 | 退出
12 |
17 |
18 |
19 |
20 |
21 |
54 |
55 |
--------------------------------------------------------------------------------
/web/front/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import vuetify from './plugins/vuetify'
5 | import store from './store'
6 | import moment from 'moment'
7 | import './plugins/http'
8 | Vue.config.productionTip = false
9 | Vue.filter('dateformat',function(indate,outdate){
10 | return moment(indate).format(outdate)
11 | })
12 | new Vue({
13 | store,
14 | router,
15 | vuetify,
16 | render: h => h(App)
17 | }).$mount('#app')
18 |
--------------------------------------------------------------------------------
/web/front/src/plugins/http.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import axios from 'axios'
3 |
4 | axios.defaults.baseURL = 'http://localhost:8080/api/v1'
5 |
6 | axios.defaults.withCredentials=true
7 | axios.interceptors.request.use(config=>{
8 | config.headers.Authorization=`Bearer ${window.sessionStorage.getItem("token")}`
9 | return config
10 | })
11 | Vue.prototype.$http=axios
12 |
13 |
14 |
--------------------------------------------------------------------------------
/web/front/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuetify from 'vuetify/lib/framework';
3 |
4 | Vue.use(Vuetify);
5 |
6 | export default new Vuetify({
7 | });
8 |
--------------------------------------------------------------------------------
/web/front/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Home from '../views/Home.vue'
4 | import ArticleList from '../components/ArticleList.vue'
5 | import Detail from '../components/Detail.vue'
6 | import Login from '../views/Login.vue'
7 |
8 | Vue.use(VueRouter)
9 |
10 | const routes = [
11 | {
12 | path: '/login',
13 | name: 'login',
14 | component: Login,
15 | meta:{title:"请先登录",},
16 |
17 | },
18 | {
19 | path: '/home',
20 | name: 'home',
21 | component: Home,
22 | children:[
23 | {
24 | path:'',
25 | component:ArticleList,
26 | meta:{title:"欢迎来到GinBlog",},
27 | },
28 |
29 | {
30 | path:`detail/:id`,
31 | component:Detail,
32 | props:true,
33 | meta:{title:"文章详情",},
34 | },
35 | {
36 | path:`:id`,
37 | component:ArticleList,
38 | meta:{title:"欢迎来到GinBlog",},
39 | props:true
40 | },
41 | ]
42 | },
43 | {
44 | path: "*",
45 | redirect: "login"
46 | }
47 |
48 | ]
49 |
50 | const router = new VueRouter({
51 | mode: 'history',
52 | base: process.env.BASE_URL,
53 | routes
54 | })
55 | router.beforeEach((to,from,next)=>{
56 | if (to.meta.title){
57 | document.title=to.meta.title
58 | }
59 | const token = window.sessionStorage.getItem('token')
60 | console.log("token",token)
61 | if (to.path=="/")next("/login")
62 | if(to.path==='/login')return next()
63 | if(!token &&to.path=='/home'){
64 | // Vue.prototype.$message.error("请先登录")
65 | next("/login")
66 | }else{
67 | next()
68 | }
69 | })
70 |
71 | export default router
72 |
--------------------------------------------------------------------------------
/web/front/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 | import Vuex from "vuex"
3 | Vue.use(Vuex)
4 | export default new Vuex.Store({
5 | //当做data
6 | state:{
7 | // collapsed:false
8 | cateId:-1
9 | },
10 | //相当于计算属性
11 | getters:{
12 | },
13 | //同步一些方法
14 | mutations:{
15 | saveCateId(state, cateId) {
16 | state.cateId= cateId;
17 | },
18 | },
19 | //存放异步的方法
20 | actions:{
21 |
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/web/front/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
42 |
--------------------------------------------------------------------------------
/web/front/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
17 |
18 |
24 |
25 |
26 |
27 |
33 | 登录
34 |
35 |
36 |
41 | 取消
42 |
43 |
44 |
45 |
46 |
47 |
48 |
86 |
87 |
116 |
--------------------------------------------------------------------------------
/web/front/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transpileDependencies: [
3 | 'vuetify'
4 | ],
5 | lintOnSave:false,
6 | publicPath:'/front',
7 | outputDir:'dist',
8 | assetsDir:'static',
9 | }
10 |
--------------------------------------------------------------------------------